Decompiled source of ShipConfig v1.4.2

plugins\ShipConfig.dll

Decompiled 2 days ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using Charter;
using ConfigReload;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using PatchGuard;
using SyncedConfig;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using YamlConfig;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Core.Tokens;
using YamlDotNet.Helpers;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.BufferedDeserialization;
using YamlDotNet.Serialization.BufferedDeserialization.TypeDiscriminators;
using YamlDotNet.Serialization.Callbacks;
using YamlDotNet.Serialization.Converters;
using YamlDotNet.Serialization.EventEmitters;
using YamlDotNet.Serialization.NamingConventions;
using YamlDotNet.Serialization.NodeDeserializers;
using YamlDotNet.Serialization.NodeTypeResolvers;
using YamlDotNet.Serialization.ObjectFactories;
using YamlDotNet.Serialization.ObjectGraphTraversalStrategies;
using YamlDotNet.Serialization.ObjectGraphVisitors;
using YamlDotNet.Serialization.Schemas;
using YamlDotNet.Serialization.TypeInspectors;
using YamlDotNet.Serialization.TypeResolvers;
using YamlDotNet.Serialization.Utilities;
using YamlDotNet.Serialization.ValueDeserializers;

[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("ShipConfig")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("ShipConfig: configure every ship in Valheim")]
[assembly: AssemblyFileVersion("1.4.2.0")]
[assembly: AssemblyInformationalVersion("1.4.2+e50f37f63d8e3c570a7a95a035e18c20dc2ae350")]
[assembly: AssemblyProduct("ShipConfig")]
[assembly: AssemblyTitle("ShipConfig")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.4.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 ShipConfig
{
	public static class GrindstoneLink
	{
		private const string Guid = "com.GrindstoneSkills";

		private const string TypeName = "GrindstoneSkills.Api.SailingApi";

		private const int Required = 1;

		private static bool bound;

		private static bool ready;

		private static Func<Ship, float> speedFactor;

		private static Func<float> exploreFactor;

		private static Func<string[]> abilities;

		private static Func<string, string> abilityName;

		private static Func<string, string> abilityKey;

		private static Func<string, string> abilityDescription;

		private static Func<string, float> abilityCooldown;

		private static Func<string, float> abilityCooldownLength;

		public static bool Present => Bind();

		public static float SpeedFactor(Ship ship)
		{
			if (!Bind())
			{
				return 1f;
			}
			return Ask(speedFactor, ship, 1f);
		}

		public static float ExploreFactor()
		{
			if (!Bind())
			{
				return 1f;
			}
			return Ask(exploreFactor, 1f);
		}

		public static string[] Abilities()
		{
			if (!Bind())
			{
				return Array.Empty<string>();
			}
			return Ask<string[]>(abilities, Array.Empty<string>());
		}

		public static string AbilityName(string id)
		{
			if (!Bind())
			{
				return id;
			}
			return Ask(abilityName, id, id);
		}

		public static string AbilityKey(string id)
		{
			if (!Bind())
			{
				return "";
			}
			return Ask(abilityKey, id, "");
		}

		public static string AbilityDescription(string id)
		{
			if (!Bind())
			{
				return "";
			}
			return Ask(abilityDescription, id, "");
		}

		public static float AbilityCooldown(string id)
		{
			if (!Bind())
			{
				return 0f;
			}
			return Ask(abilityCooldown, id, 0f);
		}

		public static float AbilityCooldownLength(string id)
		{
			if (!Bind())
			{
				return 0f;
			}
			return Ask(abilityCooldownLength, id, 0f);
		}

		private static T Ask<T>(Func<T> call, T fallback)
		{
			try
			{
				T val = call();
				return (val != null) ? val : fallback;
			}
			catch (Exception e)
			{
				return Failed(e, fallback);
			}
		}

		private static T Ask<TArg, T>(Func<TArg, T> call, TArg arg, T fallback)
		{
			try
			{
				T val = call(arg);
				return (val != null) ? val : fallback;
			}
			catch (Exception e)
			{
				return Failed(e, fallback);
			}
		}

		private static T Failed<T>(Exception e, T fallback)
		{
			ready = false;
			ShipConfig.Log.LogWarning((object)$"GrindstoneSkills' Sailing API failed; the ship panel stops asking it: {e}");
			return fallback;
		}

		private static bool Bind()
		{
			if (bound)
			{
				return ready;
			}
			bound = true;
			Type type = FindApi();
			if (type != null)
			{
				BindAll(type);
			}
			return ready;
		}

		private static void BindAll(Type api)
		{
			speedFactor = Create<Func<Ship, float>>(api, "GetShipSpeedFactor");
			exploreFactor = Create<Func<float>>(api, "GetExploreRadiusFactor");
			abilities = Create<Func<string[]>>(api, "GetAbilities");
			abilityName = Create<Func<string, string>>(api, "GetAbilityName");
			abilityKey = Create<Func<string, string>>(api, "GetAbilityKey");
			abilityDescription = Create<Func<string, string>>(api, "GetAbilityDescription");
			abilityCooldown = Create<Func<string, float>>(api, "GetAbilityCooldown");
			abilityCooldownLength = Create<Func<string, float>>(api, "GetAbilityCooldownLength");
			ready = speedFactor != null && exploreFactor != null && abilities != null && abilityName != null && abilityKey != null && abilityDescription != null && abilityCooldown != null && abilityCooldownLength != null;
		}

		private static Type FindApi()
		{
			if (!Chainloader.PluginInfos.TryGetValue("com.GrindstoneSkills", out var value) || (Object)(object)value.Instance == (Object)null)
			{
				return null;
			}
			Type type = ((object)value.Instance).GetType().Assembly.GetType("GrindstoneSkills.Api.SailingApi");
			Func<int> func = ((type != null) ? Create<Func<int>>(type, "GetApiVersion") : null);
			if (func == null || func() < 1)
			{
				return null;
			}
			return type;
		}

		private static T Create<T>(Type api, string name) where T : Delegate
		{
			MethodInfo method = typeof(T).GetMethod("Invoke");
			Type[] types = Array.ConvertAll(method.GetParameters(), (ParameterInfo p) => p.ParameterType);
			MethodInfo method2 = api.GetMethod(name, BindingFlags.Static | BindingFlags.Public, null, types, null);
			if (!(method2 != null) || !(method2.ReturnType == method.ReturnType))
			{
				return null;
			}
			return (T)Delegate.CreateDelegate(typeof(T), method2);
		}
	}
	public sealed class PanelHover
	{
		private const int AboveWindows = 650;

		private static readonly List<RaycastResult> hits = new List<RaycastResult>();

		private readonly RectTransform root;

		private PointerEventData pointer;

		private GameObject hovered;

		public PanelHover(RectTransform root)
		{
			this.root = root;
		}

		public void Update(bool cursorFree)
		{
			GameObject val = (cursorFree ? LineUnderPointer() : null);
			if ((Object)(object)val != (Object)(object)hovered)
			{
				Enter(val);
			}
			Lift();
		}

		public void Reset()
		{
			hovered = null;
		}

		private void Enter(GameObject line)
		{
			hovered = line;
			UITooltip val = (((Object)(object)line != (Object)null) ? line.GetComponent<UITooltip>() : null);
			if ((Object)(object)val != (Object)null)
			{
				val.OnHoverStart(line);
			}
			else if (Ours())
			{
				UITooltip.HideTooltip();
			}
		}

		private GameObject LineUnderPointer()
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: 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_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: 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)
			EventSystem current = EventSystem.current;
			Vector2 val = Vector2.op_Implicit(ZInput.pointerPosition);
			if ((Object)(object)current == (Object)null || !RectTransformUtility.RectangleContainsScreenPoint(root, val))
			{
				return null;
			}
			if (pointer == null)
			{
				pointer = new PointerEventData(current);
			}
			pointer.position = val;
			hits.Clear();
			current.RaycastAll(pointer, hits);
			foreach (RaycastResult hit in hits)
			{
				RaycastResult current2 = hit;
				if (!Backdrop(((RaycastResult)(ref current2)).gameObject))
				{
					return Line(((RaycastResult)(ref current2)).gameObject);
				}
			}
			return null;
		}

		private GameObject Line(GameObject hit)
		{
			Transform val = hit.transform;
			while ((Object)(object)val != (Object)null && (Object)(object)val.parent != (Object)(object)root)
			{
				val = val.parent;
			}
			if (!((Object)(object)val != (Object)null))
			{
				return null;
			}
			return ((Component)val).gameObject;
		}

		private static bool Backdrop(GameObject hit)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: 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)
			Transform transform = hit.transform;
			RectTransform val = (RectTransform)(object)((transform is RectTransform) ? transform : null);
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			Vector3 lossyScale = ((Transform)val).lossyScale;
			Rect rect = val.rect;
			if (((Rect)(ref rect)).width * lossyScale.x >= (float)Screen.width - 1f)
			{
				rect = val.rect;
				return ((Rect)(ref rect)).height * lossyScale.y >= (float)Screen.height - 1f;
			}
			return false;
		}

		private void Lift()
		{
			GameObject tooltip = UITooltip.m_tooltip;
			if (!((Object)(object)tooltip == (Object)null) && Ours() && !((Object)(object)tooltip.GetComponent<Canvas>() != (Object)null))
			{
				Canvas obj = tooltip.AddComponent<Canvas>();
				obj.overrideSorting = true;
				obj.sortingLayerID = ((Component)root).GetComponent<Canvas>().sortingLayerID;
				obj.sortingOrder = 650;
			}
		}

		private bool Ours()
		{
			if ((Object)(object)UITooltip.m_current != (Object)null)
			{
				return ((Component)UITooltip.m_current).transform.IsChildOf((Transform)(object)root);
			}
			return false;
		}
	}
	public static class PanelLook
	{
		public const float FontSize = 12f;

		public static readonly Color Label = new Color(0.72f, 0.72f, 0.72f, 1f);

		public static readonly Color Line = new Color(1f, 1f, 1f, 0.14f);

		public static readonly Color Bar = new Color(1f, 0.713f, 0.361f, 0.8f);

		public const string Orange = "#FFB65C";

		public const string Red = "#FF8A80";

		public static RectTransform Node(string name, Transform parent)
		{
			//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)
			//IL_002a: 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_0042: Expected O, but got Unknown
			GameObject val = new GameObject(name, new Type[1] { typeof(RectTransform) })
			{
				layer = ((Component)parent).gameObject.layer
			};
			val.transform.SetParent(parent, false);
			return (RectTransform)val.transform;
		}

		public static Image Block(string name, Transform parent, Color colour)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			Image obj = ((Component)Node(name, parent)).gameObject.AddComponent<Image>();
			((Graphic)obj).color = colour;
			((Graphic)obj).raycastTarget = false;
			return obj;
		}

		public static TMP_Text Text(RectTransform parent, string name, TMP_FontAsset font, TextAlignmentOptions align, Color colour)
		{
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			RectTransform obj = Node(name, (Transform)(object)parent);
			((Component)obj).gameObject.SetActive(false);
			Stretch(obj);
			TextMeshProUGUI val = ((Component)obj).gameObject.AddComponent<TextMeshProUGUI>();
			((TMP_Text)val).font = font;
			((Component)obj).gameObject.SetActive(true);
			((TMP_Text)val).fontSize = 12f;
			((TMP_Text)val).alignment = align;
			((TMP_Text)val).textWrappingMode = (TextWrappingModes)0;
			((TMP_Text)val).overflowMode = (TextOverflowModes)0;
			((Graphic)val).raycastTarget = false;
			((Graphic)val).color = colour;
			return (TMP_Text)(object)val;
		}

		public static void Stretch(RectTransform rect)
		{
			//IL_0001: 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_0017: 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)
			rect.anchorMin = Vector2.zero;
			rect.anchorMax = Vector2.one;
			rect.offsetMin = Vector2.zero;
			rect.offsetMax = Vector2.zero;
		}

		public static void Frame(Image image)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: 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)
			((Graphic)image).raycastTarget = false;
			((Graphic)image).color = new Color(0f, 0f, 0f, 0.4f);
			Image val = (((Object)(object)Minimap.instance != (Object)null && (Object)(object)Minimap.instance.m_smallRoot != (Object)null) ? Minimap.instance.m_smallRoot.GetComponent<Image>() : null);
			if (!((Object)(object)val == (Object)null))
			{
				image.sprite = val.sprite;
				image.type = val.type;
				((Graphic)image).color = ((Graphic)val).color;
			}
		}

		public static TMP_FontAsset Font()
		{
			TMP_Text val = (((Object)(object)Minimap.instance != (Object)null) ? Minimap.instance.m_biomeNameSmall : null);
			if (!((Object)(object)val != (Object)null) || !((Object)(object)val.font != (Object)null))
			{
				return TMP_Settings.defaultFontAsset;
			}
			return val.font;
		}

		public static GameObject TipPrefab()
		{
			InventoryGui instance = InventoryGui.instance;
			GameObject val = (((Object)(object)instance != (Object)null && (Object)(object)instance.m_playerGrid != (Object)null) ? instance.m_playerGrid.m_elementPrefab : null);
			UITooltip val2 = (((Object)(object)val != (Object)null) ? val.GetComponent<UITooltip>() : null);
			if (!((Object)(object)val2 != (Object)null))
			{
				return null;
			}
			return val2.m_tooltipPrefab;
		}
	}
	public sealed class PanelRow
	{
		private const float BarHeight = 2f;

		private readonly RectTransform rect;

		private readonly TMP_Text label;

		private readonly TMP_Text value;

		private RectTransform track;

		private RectTransform fill;

		private int shownA = int.MinValue;

		private int shownB = int.MinValue;

		private string shownName;

		private string shownKey;

		public float Height { get; }

		public UITooltip Tip { get; }

		public PanelRow(RectTransform parent, string name, float height, TMP_FontAsset font, GameObject tipPrefab)
		{
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: 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_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			Height = height;
			rect = PanelLook.Node(name, (Transform)(object)parent);
			rect.anchorMin = new Vector2(0f, 1f);
			rect.anchorMax = new Vector2(1f, 1f);
			rect.pivot = new Vector2(0.5f, 1f);
			((Graphic)((Component)rect).gameObject.AddComponent<Image>()).color = Color.clear;
			label = PanelLook.Text(rect, "label", font, (TextAlignmentOptions)4097, PanelLook.Label);
			value = PanelLook.Text(rect, "value", font, (TextAlignmentOptions)4100, Color.white);
			if (!((Object)(object)tipPrefab == (Object)null))
			{
				Tip = ((Component)rect).gameObject.AddComponent<UITooltip>();
				Tip.m_tooltipPrefab = tipPrefab;
			}
		}

		public void AddBar()
		{
			//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_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			track = ((Graphic)PanelLook.Block("bar", (Transform)(object)rect, PanelLook.Line)).rectTransform;
			track.anchorMin = Vector2.zero;
			track.anchorMax = new Vector2(1f, 0f);
			track.pivot = new Vector2(0.5f, 0f);
			track.sizeDelta = new Vector2(0f, 2f);
			fill = ((Graphic)PanelLook.Block("fill", (Transform)(object)track, PanelLook.Bar)).rectTransform;
			PanelLook.Stretch(fill);
			((Component)track).gameObject.SetActive(false);
		}

		public bool Changed(int a, int b, string name = null, string key = null)
		{
			if (a == shownA && b == shownB && shownName == name && shownKey == key)
			{
				return false;
			}
			shownA = a;
			shownB = b;
			shownName = name;
			shownKey = key;
			return true;
		}

		public void Set(string labelText, string valueText, string topic, string text)
		{
			label.text = labelText;
			value.text = valueText;
			if (!((Object)(object)Tip == (Object)null))
			{
				Tip.m_topic = topic;
				Tip.m_text = text;
			}
		}

		public void SetBar(float progress)
		{
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)track == (Object)null))
			{
				bool flag = progress > 0f && progress < 1f;
				if (((Component)track).gameObject.activeSelf != flag)
				{
					((Component)track).gameObject.SetActive(flag);
				}
				if (flag)
				{
					fill.anchorMax = new Vector2(progress, 1f);
				}
			}
		}

		public void Place(float top, float side)
		{
			//IL_0030: 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)
			if (!((Component)rect).gameObject.activeSelf)
			{
				((Component)rect).gameObject.SetActive(true);
			}
			rect.anchoredPosition = new Vector2(0f, 0f - top);
			rect.sizeDelta = new Vector2(-2f * side, Height);
		}

		public void Hide()
		{
			if (((Component)rect).gameObject.activeSelf)
			{
				((Component)rect).gameObject.SetActive(false);
			}
		}
	}
	public static class PanelSettings
	{
		public const string Section = "Display";

		public static ConfigEntry<bool> ShipPanel { get; private set; }

		public static void Initialize(SyncedConfiguration config)
		{
			ShipPanel = config.Bind("Display", "Ship Panel", true, "Each player's own. A small panel under the minimap while you are aboard a ship: its speed, its speed multiplier against vanilla, your map exploration radius and, with GrindstoneSkills, your Sailing abilities with their cooldowns (hover a line to read more). Windows such as the inventory draw over it.", false, null);
		}
	}
	public sealed class ShipPanel
	{
		private const float StatHeight = 14f;

		private const float AbilityHeight = 15f;

		private const float Padding = 4f;

		private const float Side = 7f;

		private const float DividerSpace = 3f;

		private const float Gap = 6f;

		private const float VanillaExploreRadius = 100f;

		private static readonly Vector3[] corners = (Vector3[])(object)new Vector3[4];

		private readonly RectTransform root;

		private readonly CanvasGroup group;

		private readonly TMP_FontAsset font;

		private readonly GameObject tipPrefab;

		private readonly PanelRow speed;

		private readonly PanelRow multiplier;

		private readonly PanelRow explore;

		private readonly RectTransform divider;

		private readonly List<PanelRow> abilities = new List<PanelRow>();

		private int laidOut = -1;

		private bool interactive = true;

		public PanelHover Hover { get; }

		private ShipPanel(RectTransform root)
		{
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			this.root = root;
			font = PanelLook.Font();
			tipPrefab = PanelLook.TipPrefab();
			group = ((Component)root).gameObject.AddComponent<CanvasGroup>();
			PanelLook.Frame(((Component)root).gameObject.AddComponent<Image>());
			speed = new PanelRow(root, "ShipConfig_speed", 14f, font, tipPrefab);
			multiplier = new PanelRow(root, "ShipConfig_multiplier", 14f, font, tipPrefab);
			explore = new PanelRow(root, "ShipConfig_explore", 14f, font, tipPrefab);
			divider = ((Graphic)PanelLook.Block("ShipConfig_divider", (Transform)(object)root, PanelLook.Line)).rectTransform;
			Hover = new PanelHover(root);
		}

		public static ShipPanel Create(Transform hudRoot)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			RectTransform obj = PanelLook.Node("ShipConfig_ShipPanel", hudRoot);
			obj.anchorMin = Vector2.one;
			obj.anchorMax = Vector2.one;
			obj.pivot = new Vector2(0.5f, 1f);
			((Component)obj).gameObject.AddComponent<Canvas>();
			((Component)obj).gameObject.AddComponent<GraphicRaycaster>();
			return new ShipPanel(obj);
		}

		public void SetVisible(bool visible)
		{
			if (((Component)root).gameObject.activeSelf != visible)
			{
				((Component)root).gameObject.SetActive(visible);
			}
		}

		public void Interactive(bool free)
		{
			if (interactive != free)
			{
				interactive = free;
				group.blocksRaycasts = free;
			}
		}

		public void Refresh(Ship ship, float metresPerSecond)
		{
			int num = Mathf.RoundToInt(metresPerSecond * 36f);
			if (speed.Changed(num, 0))
			{
				speed.Set("Speed", $"{(float)num / 10f:0.0} km/h", "Speed", "How fast the ship moves over the water, in kilometres per hour.");
			}
			RefreshMultiplier(ship);
			RefreshExplore();
			string[] array = GrindstoneLink.Abilities();
			for (int i = 0; i < array.Length; i++)
			{
				RefreshAbility(Ability(i), array[i]);
			}
			for (int j = array.Length; j < abilities.Count; j++)
			{
				abilities[j].Hide();
			}
			Layout(array.Length);
		}

		private void RefreshMultiplier(Ship ship)
		{
			SpeedBonus.Parts(ship, out var rowing, out var settings, out var skill);
			int a = Mathf.RoundToInt(settings * 100f);
			int num = Mathf.RoundToInt(skill * 100f);
			if (multiplier.Changed(a, num * 2 + (rowing ? 1 : 0)))
			{
				multiplier.Set("Speed multiplier", Multiplier(settings * skill), "Speed multiplier", SpeedBonus.Account(rowing, settings, skill));
			}
		}

		private static string Multiplier(float factor)
		{
			string text = $"x{factor:0.00}";
			if (factor > 1.005f)
			{
				return "<color=#FFB65C>" + text + "</color>";
			}
			if (!(factor < 0.995f))
			{
				return text;
			}
			return "<color=#FF8A80>" + text + "</color>";
		}

		private void RefreshExplore()
		{
			float num = GrindstoneLink.ExploreFactor();
			float num2 = (((Object)(object)Minimap.instance != (Object)null) ? Minimap.instance.m_exploreRadius : 100f);
			if (explore.Changed(Mathf.RoundToInt(num2 * num), Mathf.RoundToInt(num * 100f) * 2 + (GrindstoneLink.Present ? 1 : 0)))
			{
				string text = $"How far around you the map uncovers as you sail. Vanilla {100f:0} m.";
				if (GrindstoneLink.Present)
				{
					text += $"\nYour Sailing skill: x{num:0.00}";
				}
				explore.Set("Explore radius", $"{num2 * num:0} m", "Explore radius", text);
			}
		}

		private static void RefreshAbility(PanelRow row, string id)
		{
			string text = GrindstoneLink.AbilityName(id);
			string text2 = GrindstoneLink.AbilityKey(id);
			float num = GrindstoneLink.AbilityCooldown(id);
			float num2 = GrindstoneLink.AbilityCooldownLength(id);
			row.SetBar((num > 0f && num2 > 0f) ? (1f - num / num2) : 1f);
			if (row.Changed(Mathf.Max(0, Mathf.CeilToInt(num)), 0, text, text2))
			{
				string labelText = ((text2.Length > 0) ? ("[<color=yellow>" + text2 + "</color>] <color=#FFFFFF>" + text + "</color>") : text);
				row.Set(labelText, Cooldown(num), text, GrindstoneLink.AbilityDescription(id));
			}
		}

		private static string Cooldown(float seconds)
		{
			int num = Mathf.CeilToInt(seconds);
			if (num <= 0)
			{
				return "<color=#FFB65C>Ready</color>";
			}
			string text = ((num < 60) ? $"{num} s" : $"{num / 60}:{num % 60:00}");
			return "<color=#B8B8B8>" + text + "</color>";
		}

		private PanelRow Ability(int index)
		{
			while (abilities.Count <= index)
			{
				PanelRow panelRow = new PanelRow(root, "ShipConfig_ability" + abilities.Count, 15f, font, tipPrefab);
				panelRow.AddBar();
				abilities.Add(panelRow);
			}
			return abilities[index];
		}

		private void Layout(int abilityCount)
		{
			//IL_0089: 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)
			if (abilityCount != laidOut)
			{
				laidOut = abilityCount;
				float y = 4f;
				y = Stack(speed, y);
				y = Stack(multiplier, y);
				y = Stack(explore, y);
				((Component)divider).gameObject.SetActive(abilityCount > 0);
				if (abilityCount > 0)
				{
					y = PlaceDivider(y);
				}
				for (int i = 0; i < abilityCount; i++)
				{
					y = Stack(abilities[i], y);
				}
				root.sizeDelta = new Vector2(root.sizeDelta.x, y + 4f);
			}
		}

		private static float Stack(PanelRow row, float y)
		{
			row.Place(y, 7f);
			return y + row.Height;
		}

		private float PlaceDivider(float y)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: 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_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			divider.anchorMin = new Vector2(0f, 1f);
			divider.anchorMax = new Vector2(1f, 1f);
			divider.pivot = new Vector2(0.5f, 1f);
			divider.anchoredPosition = new Vector2(0f, 0f - (y + 3f));
			divider.sizeDelta = new Vector2(-14f, 1f);
			return y + 6f + 1f;
		}

		public void Place()
		{
			//IL_006b: 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_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			Minimap instance = Minimap.instance;
			RectTransform val = (RectTransform)(((Object)(object)instance != (Object)null && (Object)(object)instance.m_smallRoot != (Object)null) ? /*isinst with value type is only supported in some contexts*/: null);
			if (!((Object)(object)val == (Object)null))
			{
				val.GetWorldCorners(corners);
				float num = Mathf.Min(corners[0].y, WindIndicatorBottom()) - 6f * ((Transform)root).lossyScale.y;
				((Transform)root).position = new Vector3((corners[0].x + corners[3].x) * 0.5f, num, ((Transform)root).position.z);
				root.sizeDelta = new Vector2((corners[3].x - corners[0].x) / ((Transform)root).lossyScale.x, root.sizeDelta.y);
			}
		}

		private static float WindIndicatorBottom()
		{
			//IL_0044: 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)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			Hud instance = Hud.instance;
			RectTransform val = (((Object)(object)instance != (Object)null && (Object)(object)instance.m_shipHudRoot != (Object)null && instance.m_shipHudRoot.activeInHierarchy) ? instance.m_shipWindIndicatorRoot : null);
			if (!((Object)(object)val != (Object)null))
			{
				return float.MaxValue;
			}
			float y = ((Transform)val).position.y;
			Rect rect = val.rect;
			return y - ((Rect)(ref rect)).height * 0.5f * ((Transform)val).lossyScale.y;
		}
	}
	public class ShipPanelHud : MonoBehaviour
	{
		private const float RefreshSeconds = 0.25f;

		private readonly Speedometer speedometer = new Speedometer();

		private ShipPanel panel;

		private float nextRefresh;

		private void Update()
		{
			try
			{
				Tick();
			}
			catch (Exception exception)
			{
				Guard.Report(exception, "ship panel");
				((Behaviour)this).enabled = false;
				panel?.SetVisible(visible: false);
			}
		}

		private void Tick()
		{
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Invalid comparison between Unknown and I4
			Ship val = ShownFor();
			if ((Object)(object)val != (Object)null && panel == null && (Object)(object)Hud.instance != (Object)null && (Object)(object)Hud.instance.m_rootObject != (Object)null)
			{
				panel = ShipPanel.Create(Hud.instance.m_rootObject.transform);
			}
			if (panel == null)
			{
				return;
			}
			panel.SetVisible((Object)(object)val != (Object)null);
			if ((Object)(object)val == (Object)null)
			{
				Hidden();
				return;
			}
			bool flag = (int)ZCursor.LockState != 1 && ZCursor.IsVisible;
			panel.Interactive(flag);
			panel.Hover.Update(flag);
			if (!(Time.unscaledTime < nextRefresh))
			{
				nextRefresh = Time.unscaledTime + 0.25f;
				speedometer.Sample(val);
				panel.Refresh(val, speedometer.Speed);
				panel.Place();
			}
		}

		private void Hidden()
		{
			speedometer.Reset();
			panel.Hover.Reset();
			nextRefresh = 0f;
		}

		private static Ship ShownFor()
		{
			Player localPlayer = Player.m_localPlayer;
			if (!PanelSettings.ShipPanel.Value || (Object)(object)localPlayer == (Object)null || ((Character)localPlayer).IsDead() || Minimap.IsOpen())
			{
				return null;
			}
			return Ship.GetLocalShip();
		}
	}
	[HarmonyPatch(typeof(Hud), "Awake")]
	public static class HudAwakePatch
	{
		[HarmonyPostfix]
		public static void Postfix(Hud __instance)
		{
			Guard.Run("ship panel setup", () => ((Component)__instance).gameObject.AddComponent<ShipPanelHud>());
		}
	}
	public static class SpeedBonus
	{
		private const float LeastDrag = 0.0001f;

		private static Ship namedShip;

		private static string shipName;

		public static bool Rowing(Ship ship)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Invalid comparison between Unknown and I4
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Invalid comparison between Unknown and I4
			Speed speedSetting = ship.GetSpeedSetting();
			if ((int)speedSetting != 2)
			{
				return (int)speedSetting == 1;
			}
			return true;
		}

		public static float FromSettings(Ship ship, bool rowing)
		{
			if (!ShipConfiguration.TryGet(PrefabName(ship), out var entries))
			{
				return 1f;
			}
			float num = (float)(rowing ? ((ConfigEntryBase)entries.PaddleForce).DefaultValue : ((ConfigEntryBase)entries.SailForce).DefaultValue);
			float num2 = (float)((ConfigEntryBase)entries.ForwardDrag).DefaultValue;
			if (num <= 0f || num2 <= 0f)
			{
				return 1f;
			}
			float num3 = Mathf.Max(0f, rowing ? ship.m_backwardForce : ship.m_sailForceFactor);
			float num4 = Mathf.Max(0.0001f, ship.m_dampingForward);
			return Mathf.Sqrt(num3 / num * (num2 / num4));
		}

		public static void Parts(Ship ship, out bool rowing, out float settings, out float skill)
		{
			rowing = Rowing(ship);
			settings = FromSettings(ship, rowing);
			skill = (GrindstoneLink.Present ? GrindstoneLink.SpeedFactor(ship) : 1f);
		}

		public static string Account(bool rowing, float settings, float skill)
		{
			string text = "Top speed against a vanilla ship of this kind, " + (rowing ? "rowing" : "under sail") + ".\n" + $"Ship settings: x{settings:0.00}";
			if (!GrindstoneLink.Present)
			{
				return text;
			}
			return text + $"\nHelmsman's Sailing skill: x{skill:0.00}";
		}

		private static string PrefabName(Ship ship)
		{
			if ((Object)(object)ship != (Object)(object)namedShip || shipName == null)
			{
				string prefabName = Utils.GetPrefabName(((Component)ship).gameObject);
				namedShip = ship;
				shipName = prefabName;
			}
			return shipName;
		}
	}
	public sealed class Speedometer
	{
		private const float MostMetresPerSecond = 60f;

		private const float Ease = 0.5f;

		private Ship ship;

		private Vector3 lastPosition;

		private float lastTime;

		public float Speed { get; private set; }

		public void Sample(Ship current)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//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_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: 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)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			Vector3 position = ((Component)current).transform.position;
			float num = Time.time - lastTime;
			if (!((Object)(object)current == (Object)(object)ship) || !(num <= 0f))
			{
				bool flag = (Object)(object)current == (Object)(object)ship;
				Vector3 val = position - lastPosition;
				ship = current;
				lastPosition = position;
				lastTime = Time.time;
				float num2;
				if (!flag)
				{
					num2 = 0f;
				}
				else
				{
					Vector2 val2 = new Vector2(val.x, val.z);
					num2 = ((Vector2)(ref val2)).magnitude / num;
				}
				float num3 = num2;
				Speed = ((num3 > 60f) ? 0f : Mathf.Lerp(Speed, num3, flag ? 0.5f : 1f));
			}
		}

		public void Reset()
		{
			ship = null;
			Speed = 0f;
		}
	}
	public static class ShipBuildCost
	{
		public static int[] ReadOriginals(Piece piece)
		{
			if ((Object)(object)piece == (Object)null || piece.m_resources == null || piece.m_resources.Length == 0)
			{
				return null;
			}
			int[] array = new int[piece.m_resources.Length];
			for (int i = 0; i < array.Length; i++)
			{
				array[i] = ((piece.m_resources[i] != null) ? piece.m_resources[i].m_amount : 0);
			}
			return array;
		}

		public static void Apply(Piece piece, ShipEntries entries)
		{
			int[] originalBuildCosts = entries.OriginalBuildCosts;
			if ((Object)(object)piece == (Object)null || originalBuildCosts == null || piece.m_resources == null)
			{
				return;
			}
			float effectiveBuildCost = entries.EffectiveBuildCost;
			int num = Mathf.Min(originalBuildCosts.Length, piece.m_resources.Length);
			for (int i = 0; i < num; i++)
			{
				Requirement val = piece.m_resources[i];
				if (val != null)
				{
					val.m_amount = Mathf.Max(1, Mathf.RoundToInt((float)originalBuildCosts[i] * effectiveBuildCost));
				}
			}
		}
	}
	[BepInPlugin("com.ShipConfig", "ShipConfig", "1.4.2")]
	public class ShipConfig : BaseUnityPlugin
	{
		public const string PluginGuid = "com.ShipConfig";

		public const string PluginName = "ShipConfig";

		public const string PluginVersion = "1.4.2";

		public static ManualLogSource Log { get; private set; }

		public static SyncedConfiguration Synced { get; private set; }

		private void Awake()
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Synced = new SyncedConfiguration((BaseUnityPlugin)(object)this, ((BaseUnityPlugin)this).Logger, "ShipConfig", "1.4.2");
			ShipConfiguration.Initialize(Synced);
			ShipValueQueue.Initialize((MonoBehaviour)(object)this);
			Harmony val = new Harmony("com.ShipConfig");
			val.PatchAll(Assembly.GetExecutingAssembly());
			Synced.Finish(val);
			Guard.Install(val, ((BaseUnityPlugin)this).Logger, Assembly.GetExecutingAssembly());
		}
	}
	public static class ShipConfiguration
	{
		private static SyncedConfiguration synced;

		public static ConfigEntry<bool> LockConfiguration { get; private set; }

		public static Dictionary<string, ShipEntries> Ships { get; } = new Dictionary<string, ShipEntries>();

		public static void Initialize(SyncedConfiguration config)
		{
			synced = config;
			LockConfiguration = config.BindLocking("General", "Lock Configuration", defaultValue: true, "Server only. When on, every player uses the server's values for this file and cannot override them locally.");
			ShipMultipliers.Initialize(config);
			PanelSettings.Initialize(config);
		}

		public static bool TryGet(string name, out ShipEntries entries)
		{
			return Ships.TryGetValue(name, out entries);
		}

		public static bool Register(string name, ShipDefaults defaults)
		{
			if (defaults == null || Ships.ContainsKey(name))
			{
				return false;
			}
			Ships[name] = ShipEntries.Bind(synced, name, defaults);
			return true;
		}

		public static void RegisterBatch(Action register)
		{
			ConfigFile config = synced.Config;
			bool saveOnConfigSet = config.SaveOnConfigSet;
			int count = Ships.Count;
			config.SaveOnConfigSet = false;
			try
			{
				register();
			}
			finally
			{
				config.SaveOnConfigSet = saveOnConfigSet;
			}
			if (saveOnConfigSet && Ships.Count != count)
			{
				config.Save();
			}
		}
	}
	public static class ShipDamage
	{
		private static readonly Dictionary<int, ShipEntries> byPrefab = new Dictionary<int, ShipEntries>();

		public static ShipEntries ForShip(WearNTear wearNTear)
		{
			ZDO val = (((Object)(object)wearNTear != (Object)null && (Object)(object)wearNTear.m_nview != (Object)null) ? wearNTear.m_nview.GetZDO() : null);
			if (val == null)
			{
				return null;
			}
			if (byPrefab.Count != ShipConfiguration.Ships.Count)
			{
				Index();
			}
			if (!byPrefab.TryGetValue(val.GetPrefab(), out var value))
			{
				return null;
			}
			return value;
		}

		private static void Index()
		{
			byPrefab.Clear();
			foreach (KeyValuePair<string, ShipEntries> ship in ShipConfiguration.Ships)
			{
				byPrefab[StringExtensionMethods.GetStableHashCode(ship.Key)] = ship.Value;
			}
		}
	}
	[HarmonyPatch(typeof(WearNTear), "RPC_Damage")]
	public static class WearNTearRpcDamagePatch
	{
		[HarmonyPrefix]
		public static bool Prefix(WearNTear __instance, HitData hit)
		{
			ShipEntries shipEntries = ShipDamage.ForShip(__instance);
			if (shipEntries == null)
			{
				return true;
			}
			if (shipEntries.TakesNoDamage)
			{
				return false;
			}
			((DamageTypes)(ref hit.m_damage)).Modify(shipEntries.EffectiveDamageTaken);
			return true;
		}
	}
	[HarmonyPatch(typeof(WearNTear), "ApplyDamage")]
	public static class WearNTearApplyDamagePatch
	{
		[HarmonyPrefix]
		public static bool Prefix(WearNTear __instance, ref bool __result)
		{
			ShipEntries shipEntries = ShipDamage.ForShip(__instance);
			if (shipEntries == null || !shipEntries.TakesNoDamage)
			{
				return true;
			}
			__result = false;
			return false;
		}
	}
	public sealed class ShipDefaults
	{
		public float Health;

		public float SailForce;

		public float SailForceOffset;

		public float PaddleForce;

		public float RudderSpeed;

		public float TurnForceSailing;

		public float TurnForcePaddling;

		public float ForwardDrag;

		public float SidewaysDrag;

		public float AngularDamping;

		public float WaterImpactDamage;

		public float UpsideDownDamage;

		public bool WeatherWear;

		public bool AshlandsOceanDamage;

		public int[] BuildCosts;

		public static ShipDefaults From(GameObject source, bool isInstance)
		{
			Ship val = (((Object)(object)source != (Object)null) ? source.GetComponent<Ship>() : null);
			WearNTear val2 = (((Object)(object)source != (Object)null) ? source.GetComponent<WearNTear>() : null);
			if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null)
			{
				return null;
			}
			ShipDefaults shipDefaults = new ShipDefaults();
			shipDefaults.Health = (isInstance ? (val2.m_health / ShipHealthUpdater.WorldLevelFactor()) : val2.m_health);
			shipDefaults.WeatherWear = val2.m_noRoofWear;
			shipDefaults.AshlandsOceanDamage = !val.m_ashlandsReady;
			shipDefaults.BuildCosts = ShipBuildCost.ReadOriginals(source.GetComponent<Piece>());
			shipDefaults.ReadSailing(val);
			return shipDefaults;
		}

		private void ReadSailing(Ship ship)
		{
			SailForce = ship.m_sailForceFactor;
			SailForceOffset = ship.m_sailForceOffset;
			PaddleForce = ship.m_backwardForce;
			RudderSpeed = ship.m_rudderSpeed;
			TurnForceSailing = ship.m_stearVelForceFactor;
			TurnForcePaddling = ship.m_stearForce;
			ForwardDrag = ship.m_dampingForward;
			SidewaysDrag = ship.m_dampingSideway;
			AngularDamping = ship.m_angularDamping;
			WaterImpactDamage = ship.m_waterImpactDamage;
			UpsideDownDamage = ship.m_upsideDownDmg;
		}
	}
	public static class ShipDiscovery
	{
		public static void LoadShips()
		{
			ZNetScene scene = ZNetScene.instance;
			if ((Object)(object)scene == (Object)null)
			{
				return;
			}
			int count = ShipConfiguration.Ships.Count;
			ShipConfiguration.RegisterBatch(delegate
			{
				foreach (GameObject prefab in scene.m_prefabs)
				{
					RegisterPrefab(prefab);
				}
			});
			LogCount(count);
			ShipValues.ApplyAll();
		}

		private static void RegisterPrefab(GameObject prefab)
		{
			if (!((Object)(object)prefab == (Object)null) && !((Object)(object)prefab.GetComponent<Ship>() == (Object)null) && !((Object)(object)prefab.GetComponent<WearNTear>() == (Object)null))
			{
				ShipConfiguration.Register(((Object)prefab).name, ShipDefaults.From(prefab, isInstance: false));
			}
		}

		public static void OnShipAwake(WearNTear wearNTear, Ship ship)
		{
			string prefabName = Utils.GetPrefabName(((Component)wearNTear).gameObject);
			if (!ShipConfiguration.TryGet(prefabName, out var entries))
			{
				entries = RegisterLate(prefabName, ((Component)wearNTear).gameObject);
				if (entries == null)
				{
					return;
				}
				ShipValues.Apply(prefabName);
			}
			ShipValues.ApplyToInstance(ship, wearNTear, entries);
		}

		private static ShipEntries RegisterLate(string name, GameObject instance)
		{
			GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab(name) : null);
			ShipDefaults defaults = (((Object)(object)val != (Object)null) ? ShipDefaults.From(val, isInstance: false) : ShipDefaults.From(instance, isInstance: true));
			int count = ShipConfiguration.Ships.Count;
			ShipConfiguration.RegisterBatch(delegate
			{
				ShipConfiguration.Register(name, defaults);
			});
			LogCount(count);
			if (!ShipConfiguration.TryGet(name, out var entries))
			{
				return null;
			}
			return entries;
		}

		private static void LogCount(int before)
		{
			if (ShipConfiguration.Ships.Count != before)
			{
				ShipConfig.Log.LogInfo((object)$"Loaded settings for {ShipConfiguration.Ships.Count} ships.");
			}
		}
	}
	[HarmonyPatch(typeof(ZNetScene), "Awake")]
	public static class ZNetSceneAwakePatch
	{
		[HarmonyPostfix]
		[HarmonyPriority(0)]
		public static void Postfix()
		{
			Guard.Run("ship discovery", ShipDiscovery.LoadShips);
		}
	}
	[HarmonyPatch(typeof(WearNTear), "Awake")]
	public static class WearNTearAwakePatch
	{
		[HarmonyPostfix]
		public static void Postfix(WearNTear __instance)
		{
			Ship second = default(Ship);
			if (((Component)__instance).TryGetComponent<Ship>(ref second))
			{
				Guard.Run("ship awake", delegate(WearNTear w, Ship s)
				{
					ShipDiscovery.OnShipAwake(w, s);
				}, __instance, second);
			}
		}
	}
	public sealed class ShipEntries
	{
		public const string Section = "Ship";

		public string Name { get; }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

		public int[] OriginalBuildCosts { get; }

		public float EffectiveHealth => Positive(Health.Value) * ShipMultipliers.Health;

		public float EffectiveSailForce => Positive(SailForce.Value) * ShipMultipliers.SailForce;

		public float EffectiveSailForceOffset => SailForceOffset.Value;

		public float EffectivePaddleForce => Positive(PaddleForce.Value) * ShipMultipliers.PaddleForce;

		public float EffectiveRudderSpeed => Positive(RudderSpeed.Value);

		public float EffectiveTurnForceSailing => Positive(TurnForceSailing.Value) * ShipMultipliers.Turning;

		public float EffectiveTurnForcePaddling => Positive(TurnForcePaddling.Value) * ShipMultipliers.Turning;

		public float EffectiveForwardDrag => Positive(ForwardDrag.Value);

		public float EffectiveSidewaysDrag => Positive(SidewaysDrag.Value);

		public float EffectiveAngularDamping => Positive(AngularDamping.Value);

		public float EffectiveWaterImpactDamage => Positive(WaterImpactDamage.Value);

		public float EffectiveUpsideDownDamage => Positive(UpsideDownDamage.Value);

		public float EffectiveDamageTaken => Positive(DamageTaken.Value) * ShipMultipliers.DamageTaken;

		public float EffectiveBuildCost
		{
			get
			{
				if (BuildCost != null)
				{
					return Positive(BuildCost.Value) * ShipMultipliers.BuildCost;
				}
				return 1f;
			}
		}

		public bool TakesNoDamage
		{
			get
			{
				if (!Invulnerable.Value)
				{
					return EffectiveDamageTaken <= 0f;
				}
				return true;
			}
		}

		private ShipEntries(string name, int[] originalBuildCosts)
		{
			Name = name;
			OriginalBuildCosts = originalBuildCosts;
		}

		public static ShipEntries Bind(SyncedConfiguration synced, string name, ShipDefaults defaults)
		{
			ShipEntries shipEntries = new ShipEntries(name, defaults.BuildCosts);
			shipEntries.Health = shipEntries.BindEntry(synced, "Health", defaults.Health, "Health of " + name + ".");
			shipEntries.BindSailing(synced, defaults);
			shipEntries.BindWater(synced, defaults);
			shipEntries.BindDamage(synced, defaults);
			return shipEntries;
		}

		private void BindSailing(SyncedConfiguration synced, ShipDefaults defaults)
		{
			SailForce = BindEntry(synced, "SailForce", defaults.SailForce, "Thrust from the sail. Top speed under sail, scaled by wind and sail size.");
			SailForceOffset = BindEntry(synced, "SailForceOffset", defaults.SailForceOffset, "Height above the ship's center of mass, in meters, where the sail's force pushes. Lower makes the ship heel less and capsize less easily at high sail force; negative pushes below the center of mass. 0 removes the heeling from the sail entirely.");
			PaddleForce = BindEntry(synced, "PaddleForce", defaults.PaddleForce, "Force when paddling forward (slow) and backward.");
			RudderSpeed = BindEntry(synced, "RudderSpeed", defaults.RudderSpeed, "How fast the rudder swings to full lock while steering.");
			TurnForceSailing = BindEntry(synced, "TurnForceSailing", defaults.TurnForceSailing, "Turning force while under sail, grows with forward speed.");
			TurnForcePaddling = BindEntry(synced, "TurnForcePaddling", defaults.TurnForcePaddling, "Turning force while paddling.");
		}

		private void BindWater(SyncedConfiguration synced, ShipDefaults defaults)
		{
			ForwardDrag = BindEntry(synced, "ForwardDrag", defaults.ForwardDrag, "Forward water drag. Lower is faster and coasts longer; this caps the top speed.");
			SidewaysDrag = BindEntry(synced, "SidewaysDrag", defaults.SidewaysDrag, "Sideways water drag. Lower lets the ship slide in a crosswind. Tune with care.");
			AngularDamping = BindEntry(synced, "AngularDamping", defaults.AngularDamping, "Resistance to rolling and pitching. Higher is a steadier deck; too high and the ship stops turning. Tune with care.");
			WaterImpactDamage = BindEntry(synced, "WaterImpactDamage", defaults.WaterImpactDamage, "Damage the ship takes from slamming into rough seas with players aboard. 0 turns it off.");
			UpsideDownDamage = BindEntry(synced, "UpsideDownDamage", defaults.UpsideDownDamage, "Damage per second while the ship is capsized. 0 turns it off.");
		}

		private void BindDamage(SyncedConfiguration synced, ShipDefaults defaults)
		{
			WeatherWear = BindEntry(synced, "WeatherWear", defaults.WeatherWear, "true: the ship loses health in rain and when its hull is under water, down to half health, like vanilla. false: no weather wear.");
			AshlandsOceanDamage = BindEntry(synced, "AshlandsOceanDamage", defaults.AshlandsOceanDamage, "true: the burning Ashlands ocean damages this ship, like vanilla for every ship except the Drakkar. false: this ship sails the Ashlands ocean unharmed. WARNING: turning this off removes the progression gate that makes the Drakkar necessary, and the Ashlands ocean effects and world key it triggers.");
			DamageTaken = BindEntry(synced, "DamageTaken", 1f, "Multiplier on every hit the ship takes: attacks, collisions, rough seas, capsizing, Ashlands ocean. 1 is vanilla, 0.5 halves it, 0 is the same as Invulnerable.");
			Invulnerable = BindEntry(synced, "Invulnerable", defaultValue: false, "true: the ship takes no damage at all, not from hits, weather or the Ashlands ocean. Ships cannot be dismantled, so an invulnerable ship can only be removed by turning this off.");
			if (defaults.BuildCosts != null)
			{
				BuildCost = BindEntry(synced, "BuildCost", 1f, "Multiplier on the materials needed to build this ship. 1 is vanilla. Each material is rounded to the nearest whole number and never drops below 1.");
			}
		}

		private ConfigEntry<T> BindEntry<T>(SyncedConfiguration synced, string key, T defaultValue, string description)
		{
			ConfigEntry<T> obj = synced.Bind("Ship", Name + "." + key, defaultValue, description, true, null);
			obj.SettingChanged += Guard.Wrap("apply " + Name + "." + key, (EventHandler)delegate
			{
				ShipValueQueue.Request(Name);
			});
			return obj;
		}

		private static float Positive(float value)
		{
			if (!(value < 0f))
			{
				return value;
			}
			return 0f;
		}
	}
	public static class ShipHealthUpdater
	{
		public static float WorldLevelFactor()
		{
			if (Game.m_worldLevel <= 0 || (Object)(object)Game.instance == (Object)null)
			{
				return 1f;
			}
			return 1f + (float)Game.m_worldLevel * Game.instance.m_worldLevelPieceHPMultiplier;
		}

		public static void ApplyToPrefab(WearNTear wearNTear, float health)
		{
			wearNTear.m_health = health;
		}

		public static void ApplyToInstance(WearNTear wearNTear, float health)
		{
			wearNTear.m_health = health * WorldLevelFactor();
			ZNetView nview = wearNTear.m_nview;
			if (!((Object)(object)nview == (Object)null) && nview.IsValid())
			{
				float num = nview.GetZDO().GetFloat(ZDOVars.s_health, wearNTear.m_health);
				wearNTear.m_healthPercentage = ((wearNTear.m_health > 0f) ? Mathf.Clamp01(num / wearNTear.m_health) : 0f);
				wearNTear.UpdateVisual(false);
			}
		}
	}
	public static class ShipMultipliers
	{
		public const string Section = "Multipliers";

		public static ConfigEntry<float> HealthMultiplier { get; private set; }

		public static ConfigEntry<float> SailForceMultiplier { get; private set; }

		public static ConfigEntry<float> PaddleForceMultiplier { get; private set; }

		public static ConfigEntry<float> TurningMultiplier { get; private set; }

		public static ConfigEntry<float> DamageTakenMultiplier { get; private set; }

		public static ConfigEntry<float> BuildCostMultiplier { get; private set; }

		public static float Health => Positive(HealthMultiplier);

		public static float SailForce => Positive(SailForceMultiplier);

		public static float PaddleForce => Positive(PaddleForceMultiplier);

		public static float Turning => Positive(TurningMultiplier);

		public static float DamageTaken => Positive(DamageTakenMultiplier);

		public static float BuildCost => Positive(BuildCostMultiplier);

		public static void Initialize(SyncedConfiguration config)
		{
			HealthMultiplier = Bind(config, "Health Multiplier", "health");
			SailForceMultiplier = Bind(config, "Sail Force Multiplier", "sail force");
			PaddleForceMultiplier = Bind(config, "Paddle Force Multiplier", "paddle force");
			TurningMultiplier = Bind(config, "Turning Multiplier", "turning force");
			DamageTakenMultiplier = Bind(config, "Damage Taken Multiplier", "damage taken");
			BuildCostMultiplier = Bind(config, "Build Cost Multiplier", "build cost");
		}

		private static ConfigEntry<float> Bind(SyncedConfiguration config, string key, string subject)
		{
			ConfigEntry<float> obj = config.Bind("Multipliers", key, 1f, "Multiplier on the " + subject + " of every ship, on top of the per-ship value. 1 is vanilla.", true, null);
			obj.SettingChanged += Guard.Wrap("apply " + key, (EventHandler)delegate
			{
				ShipValueQueue.RequestAll();
			});
			return obj;
		}

		private static float Positive(ConfigEntry<float> entry)
		{
			if (entry == null)
			{
				return 1f;
			}
			if (!(entry.Value < 0f))
			{
				return entry.Value;
			}
			return 0f;
		}
	}
	public static class ShipValueQueue
	{
		private static readonly HashSet<string> pending = new HashSet<string>();

		private static bool pendingAll;

		private static MonoBehaviour host;

		private static int waitingSince = -1;

		public static void Initialize(MonoBehaviour plugin)
		{
			host = plugin;
		}

		public static void Request(string name)
		{
			pending.Add(name);
			Schedule();
		}

		public static void RequestAll()
		{
			pendingAll = true;
			Schedule();
		}

		private static void Schedule()
		{
			if (waitingSince < 0 || Time.frameCount > waitingSince + 1)
			{
				if ((Object)(object)host == (Object)null || !((Behaviour)host).isActiveAndEnabled)
				{
					Flush();
					return;
				}
				waitingSince = Time.frameCount;
				host.StartCoroutine(NextFrame());
			}
		}

		private static IEnumerator NextFrame()
		{
			yield return null;
			Guard.Run("apply ship values", Flush);
		}

		private static void Flush()
		{
			waitingSince = -1;
			if (pendingAll)
			{
				ShipValues.ApplyAll();
			}
			else
			{
				foreach (string item in pending)
				{
					ShipValues.Apply(item);
				}
			}
			pendingAll = false;
			pending.Clear();
		}
	}
	public static class ShipValues
	{
		public static void ApplyAll()
		{
			foreach (ShipEntries value in ShipConfiguration.Ships.Values)
			{
				Apply(value);
			}
		}

		public static void Apply(string name)
		{
			if (ShipConfiguration.TryGet(name, out var entries))
			{
				Apply(entries);
			}
		}

		private static void Apply(ShipEntries entries)
		{
			ApplyToPrefab(entries);
			foreach (IMonoUpdater instance in Ship.Instances)
			{
				Ship val = (Ship)(object)((instance is Ship) ? instance : null);
				if ((Object)(object)val != (Object)null && Utils.GetPrefabName(((Component)val).gameObject) == entries.Name)
				{
					ApplyToInstance(val, ((Component)val).GetComponent<WearNTear>(), entries);
				}
			}
		}

		public static void ApplyToPrefab(ShipEntries entries)
		{
			GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab(entries.Name) : null);
			Ship val2 = (((Object)(object)val != (Object)null) ? val.GetComponent<Ship>() : null);
			WearNTear val3 = (((Object)(object)val != (Object)null) ? val.GetComponent<WearNTear>() : null);
			if (!((Object)(object)val2 == (Object)null) && !((Object)(object)val3 == (Object)null))
			{
				WriteFields(val2, val3, entries);
				ShipHealthUpdater.ApplyToPrefab(val3, entries.EffectiveHealth);
				ShipBuildCost.Apply(val.GetComponent<Piece>(), entries);
			}
		}

		public static void ApplyToInstance(Ship ship, WearNTear wearNTear, ShipEntries entries)
		{
			if (!((Object)(object)ship == (Object)null) && !((Object)(object)wearNTear == (Object)null))
			{
				WriteFields(ship, wearNTear, entries);
				ShipHealthUpdater.ApplyToInstance(wearNTear, entries.EffectiveHealth);
			}
		}

		private static void WriteFields(Ship ship, WearNTear wearNTear, ShipEntries entries)
		{
			ship.m_sailForceFactor = entries.EffectiveSailForce;
			ship.m_sailForceOffset = entries.EffectiveSailForceOffset;
			ship.m_backwardForce = entries.EffectivePaddleForce;
			ship.m_rudderSpeed = entries.EffectiveRudderSpeed;
			ship.m_stearVelForceFactor = entries.EffectiveTurnForceSailing;
			ship.m_stearForce = entries.EffectiveTurnForcePaddling;
			ship.m_dampingForward = entries.EffectiveForwardDrag;
			ship.m_dampingSideway = entries.EffectiveSidewaysDrag;
			ship.m_angularDamping = entries.EffectiveAngularDamping;
			ship.m_waterImpactDamage = entries.EffectiveWaterImpactDamage;
			ship.m_upsideDownDmg = entries.EffectiveUpsideDownDamage;
			ship.m_ashlandsReady = !entries.AshlandsOceanDamage.Value;
			wearNTear.m_noRoofWear = entries.WeatherWear.Value;
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

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

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

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
}
namespace SyncedConfig
{
	public sealed class SyncedConfiguration
	{
		public global::Charter.Charter Sync { get; }

		public ConfigFile Config { get; }

		public ManualLogSource Log { get; }

		public YamlFileHub Yaml { get; }

		public YamlEditorWindow YamlEditor { get; }

		public IReadOnlyList<string> SearchPaths { get; }

		public IDisposable? ConfigWatcher { get; private set; }

		public bool IsLocked
		{
			get
			{
				if (Sync.IsBound)
				{
					return !Sync.MayAmend;
				}
				return false;
			}
		}

		public bool IsAdmin => Sync.IsSteward;

		public bool IsAuthor => Sync.IsAuthor;

		public SyncedConfiguration(BaseUnityPlugin plugin, ManualLogSource log, string title, string version, string? oldestAccepted = null, bool mandatory = true)
		{
			Config = plugin.Config;
			ConfigReloader.HoldSaves(Config);
			Log = log;
			string gUID = plugin.Info.Metadata.GUID;
			Sync = new global::Charter.Charter(gUID, title, version, oldestAccepted, mandatory);
			SearchPaths = new string[2]
			{
				Path.GetDirectoryName(Config.ConfigFilePath) ?? Paths.ConfigPath,
				Path.GetDirectoryName(((object)plugin).GetType().Assembly.Location) ?? Paths.PluginPath
			};
			Yaml = new YamlFileHub(title, Log, SearchPaths, Sync);
			YamlEditor = new YamlEditorWindow(Yaml, title + " YAML Editor");
			YamlEditorHost.Add(((Component)plugin).gameObject, YamlEditor);
		}

		public ConfigEntry<T> Bind<T>(string section, string key, T defaultValue, string description, bool synced = true, AcceptableValueBase? acceptableValues = null, params object[] tags)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Expected O, but got Unknown
			return Bind(section, key, defaultValue, new ConfigDescription(description, acceptableValues, tags), synced);
		}

		public ConfigEntry<T> Bind<T>(string section, string key, T defaultValue, ConfigDescription description, bool synced = true)
		{
			ConfigEntry<T> val = Config.Bind<T>(section, key, defaultValue, description);
			Sync.Clause<T>(val, !synced);
			return val;
		}

		public Clause<T> Add<T>(ConfigEntry<T> entry, bool synced = true)
		{
			return Sync.Clause<T>(entry, !synced);
		}

		public ConfigEntry<bool> BindLocking(string section, string key, bool defaultValue, string description)
		{
			ConfigEntry<bool> val = Config.Bind<bool>(section, key, defaultValue, description);
			Sync.Binding(val);
			return val;
		}

		public YamlFileSet AddYaml(YamlFileSet set)
		{
			return Yaml.Register(set);
		}

		public void Finish(Harmony harmony, int yamlApplyPriority = 400)
		{
			global::Charter.Charter.Install(harmony);
			ConfigWatcher = ConfigReloader.Setup(Config, Log);
			if (Yaml.Sets.Count > 0)
			{
				Yaml.HookGame(harmony, yamlApplyPriority);
			}
		}

		public static Func<byte[]?> EmbeddedResource(Assembly assembly, string resourceName)
		{
			return delegate
			{
				using Stream stream = assembly.GetManifestResourceStream(resourceName);
				if (stream != null)
				{
					using MemoryStream memoryStream = new MemoryStream();
					stream.CopyTo(memoryStream);
					return memoryStream.ToArray();
				}
				return (byte[]?)null;
			};
		}
	}
}
namespace YamlConfig
{
	internal sealed class YamlEditorHost : MonoBehaviour
	{
		private YamlEditorWindow? window;

		private bool laidOut;

		public static YamlEditorHost Add(GameObject on, YamlEditorWindow window)
		{
			YamlEditorHost yamlEditorHost = on.AddComponent<YamlEditorHost>();
			yamlEditorHost.window = window;
			window.Host = yamlEditorHost;
			((Behaviour)yamlEditorHost).enabled = window.IsOpen;
			return yamlEditorHost;
		}

		private void Update()
		{
			window?.Update();
		}

		private void LateUpdate()
		{
			window?.Update();
		}

		private void OnDisable()
		{
			laidOut = false;
		}

		private void OnGUI()
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Invalid comparison between Unknown and I4
			if (laidOut || (int)Event.current.type == 8)
			{
				laidOut = true;
				window?.Draw();
			}
		}
	}
	public sealed class YamlEditorWindow
	{
		private const float ValidationHeight = 100f;

		private readonly YamlFileHub hub;

		private readonly string windowTitle;

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

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

		private readonly List<bool> expanded = new List<bool>();

		private readonly List<Vector2> scrolls = new List<Vector2>();

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

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

		private Vector2 validationScroll;

		private YamlFileSet? set;

		private Texture2D? background;

		private GUIStyle? errorStyle;

		public Func<string, string> Translate { get; set; } = (string caption) => caption;

		public bool IsOpen => set != null;

		internal YamlEditorHost? Host { get; set; }

		private Texture2D Background
		{
			get
			{
				//IL_0011: Unknown result type (might be due to invalid IL or missing references)
				//IL_001b: Expected O, but got Unknown
				//IL_0037: Unknown result type (might be due to invalid IL or missing references)
				if ((Object)(object)background == (Object)null)
				{
					background = new Texture2D(1, 1);
					background.SetPixel(0, 0, new Color(0f, 0f, 0f, 0.9f));
					background.Apply();
				}
				return background;
			}
		}

		private GUIStyle ErrorStyle
		{
			get
			{
				//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_0020: Unknown result type (might be due to invalid IL or missing references)
				//IL_002a: Unknown result type (might be due to invalid IL or missing references)
				//IL_002c: Expected O, but got Unknown
				//IL_0031: Expected O, but got Unknown
				GUIStyle obj = errorStyle;
				if (obj == null)
				{
					GUIStyle val = new GUIStyle(GUI.skin.label);
					val.normal.textColor = Color.red;
					GUIStyle val2 = val;
					errorStyle = val;
					obj = val2;
				}
				return obj;
			}
		}

		public YamlEditorWindow(YamlFileHub hub, string windowTitle)
		{
			this.hub = hub;
			this.windowTitle = windowTitle;
		}

		public void Open(YamlFileSet set)
		{
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			this.set = set;
			paths.Clear();
			texts.Clear();
			expanded.Clear();
			scrolls.Clear();
			foreach (KeyValuePair<string, string> file in set.Files)
			{
				paths.Add(file.Key);
				texts.Add(file.Value);
				expanded.Add(item: true);
				scrolls.Add(Vector2.zero);
			}
			Validate();
			FollowHost();
		}

		public void Close()
		{
			set = null;
			FollowHost();
		}

		private void FollowHost()
		{
			if ((Object)(object)Host != (Object)null)
			{
				((Behaviour)Host).enabled = IsOpen;
			}
		}

		public void DrawButtons()
		{
			foreach (YamlFileSet item in hub.Sets.Where((YamlFileSet s) => s.Files.Count > 0))
			{
				if (GUILayout.Button(Translate(item.EditorLabel()), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }))
				{
					Open(item);
				}
			}
		}

		public void Update()
		{
			if (IsOpen)
			{
				Cursor.lockState = (CursorLockMode)0;
				Cursor.visible = true;
			}
		}

		public void OnGUI()
		{
			if ((Object)(object)Host == (Object)null)
			{
				Draw();
			}
		}

		internal void Draw()
		{
			//IL_0025: 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)
			if (set != null)
			{
				HandleEditingKeys();
				GUI.DrawTexture(new Rect(0f, 0f, (float)Screen.width, (float)Screen.height), (Texture)(object)Background);
				GUILayout.BeginArea(new Rect(20f, 20f, (float)(Screen.width - 40), (float)(Screen.height - 40)));
				GUILayout.Label(Translate(windowTitle), Array.Empty<GUILayoutOption>());
				DrawTopRow();
				for (int i = 0; i < texts.Count; i++)
				{
					DrawFile(i);
				}
				DrawValidation();
				GUILayout.EndArea();
			}
		}

		private void DrawTopRow()
		{
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUI.enabled = hub.IsAuthor && errors.Count == 0;
			if (GUILayout.Button(Translate("Save and apply"), Array.Empty<GUILayoutOption>()))
			{
				Submit(saveToDisk: true);
			}
			if (GUILayout.Button(Translate("Apply without saving"), Array.Empty<GUILayoutOption>()))
			{
				Submit(saveToDisk: false);
			}
			GUI.enabled = true;
			if (GUILayout.Button(Translate("Discard"), Array.Empty<GUILayoutOption>()))
			{
				Close();
			}
			GUILayout.EndHorizontal();
		}

		private void Submit(bool saveToDisk)
		{
			if (set != null)
			{
				Dictionary<string, string> dictionary = new Dictionary<string, string>();
				for (int i = 0; i < paths.Count; i++)
				{
					dictionary[paths[i]] = texts[i];
				}
				hub.Replace(set, dictionary, saveToDisk);
				Close();
			}
		}

		private void DrawFile(int index)
		{
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			if (texts.Count > 1)
			{
				expanded[index] = GUILayout.Toggle(expanded[index], Path.GetFileName(paths[index]), Array.Empty<GUILayoutOption>());
				if (!expanded[index])
				{
					return;
				}
			}
			scrolls[index] = GUILayout.BeginScrollView(scrolls[index], (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandHeight(true) });
			GUI.SetNextControlName(ControlName(index));
			string text = GUILayout.TextArea(texts[index], (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandHeight(true) });
			GUILayout.EndScrollView();
			if (text != texts[index])
			{
				texts[index] = text;
				Validate();
			}
		}

		private void DrawValidation()
		{
			//IL_0002: 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_001f: Unknown result type (might be due to invalid IL or missing references)
			validationScroll = GUILayout.BeginScrollView(validationScroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(100f) });
			if (errors.Count == 0 && warnings.Count == 0)
			{
				GUILayout.Label(Translate("Configuration is valid."), Array.Empty<GUILayoutOption>());
			}
			foreach (string error in errors)
			{
				GUILayout.Label(error, ErrorStyle, Array.Empty<GUILayoutOption>());
			}
			foreach (string warning in warnings)
			{
				GUILayout.Label(warning, Array.Empty<GUILayoutOption>());
			}
			GUILayout.EndScrollView();
		}

		private void Validate()
		{
			errors.Clear();
			warnings.Clear();
			if (set != null)
			{
				YamlModel yamlModel = set.CreateModel();
				yamlModel.LoadAll(paths.Select((string path, int i) => new KeyValuePair<string, string>(path, texts[i])));
				errors.AddRange(yamlModel.Errors);
				warnings.AddRange(yamlModel.Warnings);
			}
		}

		private void HandleEditingKeys()
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Invalid comparison between Unknown and I4
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Invalid comparison between Unknown and I4
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Invalid comparison between Unknown and I4
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Invalid comparison between Unknown and I4
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Expected O, but got Unknown
			Event current = Event.current;
			if ((int)current.type == 4)
			{
				bool flag = (int)current.keyCode == 9 || current.character == '\t';
				bool flag2 = (int)current.keyCode == 13 || (int)current.keyCode == 271 || current.character == '\n';
				int num = FocusedFile();
				if (num >= 0 && (flag || flag2))
				{
					TextEditor val = (TextEditor)GUIUtility.GetStateObject(typeof(TextEditor), GUIUtility.keyboardControl);
					val.ReplaceSelection(flag ? "  " : ("\n" + IndentationOfLineAt(val.text, val.cursorIndex)));
					texts[num] = val.text;
					current.Use();
					Validate();
				}
			}
		}

		private int FocusedFile()
		{
			string nameOfFocusedControl = GUI.GetNameOfFocusedControl();
			for (int i = 0; i < texts.Count; i++)
			{
				if (nameOfFocusedControl == ControlName(i))
				{
					return i;
				}
			}
			return -1;
		}

		private static string IndentationOfLineAt(string text, int position)
		{
			int num = Math.Max(0, Math.Min(position, text.Length));
			while (num > 0 && text[num - 1] != '\n')
			{
				num--;
			}
			int i;
			for (i = num; i < text.Length && text[i] == ' '; i++)
			{
			}
			return text.Substring(num, i - num);
		}

		private static string ControlName(int index)
		{
			return "YamlEditorWindow.File" + index;
		}
	}
	public sealed class YamlFileHub
	{
		private readonly string modName;

		private readonly ManualLogSource log;

		private readonly global::Charter.Charter? charter;

		private readonly List<YamlFileSet> sets = new List<YamlFileSet>();

		private readonly YamlFileStore store;

		private readonly YamlFileWatcher watcher;

		private bool loaded;

		private (YamlFileSet Set, YamlModel Model)? built;

		public IReadOnlyList<YamlFileSet> Sets => sets;

		public bool IsAuthor => charter?.IsAuthor ?? true;

		public bool IsLocked
		{
			get
			{
				if (charter != null && charter.IsBound)
				{
					return !charter.MayAmend;
				}
				return false;
			}
		}

		public bool SuppressWriteBack { get; set; }

		public Func<bool> CanApply { get; set; } = () => true;

		public event Action<YamlFileSet>? Applied;

		public YamlFileHub(string modName, ManualLogSource log, IEnumerable<string> searchFolders, global::Charter.Charter? charter)
		{
			this.modName = modName;
			this.log = log;
			this.charter = charter;
			store = new YamlFileStore(modName, log, searchFolders);
			watcher = new YamlFileWatcher(store, sets, ReloadFromDisk, log, modName);
		}

		public YamlFileSet Register(YamlFileSet set)
		{
			if (sets.Any((YamlFileSet s) => string.Equals(s.SyncKey, set.SyncKey, StringComparison.OrdinalIgnoreCase)))
			{
				throw new ArgumentException(modName + ": a YAML file set with sync key '" + set.SyncKey + "' is already registered");
			}
			sets.Add(set);
			if (charter != null)
			{
				Article<List<string>> channel = new Article<List<string>>(charter, set.SyncKey, new List<string>());
				set.Channel = channel;
				channel.Changed += delegate
				{
					Receive(set, YamlFileList.Unflatten(channel.Value));
				};
			}
			return set;
		}

		public void LoadAll()
		{
			loaded = true;
			foreach (YamlFileSet set in sets)
			{
				LoadSet(set);
			}
			watcher.Start();
		}

		public void ApplyAll()
		{
			if (!CanApply())
			{
				log.LogDebug((object)(modName + ": YAML apply skipped, the game is not ready"));
				return;
			}
			foreach (YamlFileSet set in sets)
			{
				Apply(set);
			}
		}

		public bool TryBuild(YamlFileSet set, IReadOnlyDictionary<string, string> files, out YamlModel model, string context)
		{
			model = set.CreateModel();
			model.LoadAll(files);
			foreach (string warning in model.Warnings)
			{
				log.LogWarning((object)(modName + ": " + warning));
			}
			if (model.Errors.Count == 0)
			{
				return true;
			}
			log.LogError((object)$"{modName}: {set.MainFileName} ({context}) has {model.Errors.Count} error(s), the previous configuration stays:");
			foreach (string error in model.Errors)
			{
				log.LogError((object)(modName + ": " + error));
			}
			return false;
		}

		public void Replace(YamlFileSet set, IReadOnlyDictionary<string, string> files, bool saveToDisk)
		{
			if (TryBuild(set, files, out YamlModel model, "editor"))
			{
				Publish(set, files, model, saveToDisk);
			}
		}

		public void HookGame(Harmony harmony, int applyPriority)
		{
			YamlGameHooks.Install(this, harmony, applyPriority);
		}

		internal void LoadFromHook()
		{
			if (loaded)
			{
				return;
			}
			try
			{
				LoadAll();
			}
			catch (Exception arg)
			{
				log.LogError((object)$"{modName}: loading YAML files failed: {arg}");
			}
		}

		internal void ApplyFromHook()
		{
			try
			{
				ApplyAll();
			}
			catch (Exception arg)
			{
				log.LogError((object)$"{modName}: applying YAML files failed: {arg}");
			}
		}

		private void LoadSet(YamlFileSet set)
		{
			Dictionary<string, string> dictionary = watcher.ReadOrCreate(set);
			YamlModel model;
			if (dictionary.Count == 0)
			{
				if (set.Enabled())
				{
					log.LogWarning((object)(modName + ": no " + set.FilePattern + " found in " + string.Join(" or ", store.SearchFolders)));
				}
			}
			else if (TryBuild(set, dictionary, out model, "startup"))
			{
				Publish(set, dictionary, model, writeBack: false);
			}
		}

		private void ReloadFromDisk(YamlFileSet set)
		{
			Dictionary<string, string> dictionary = watcher.Read(set);
			YamlModel model;
			if (dictionary.Count == 0)
			{
				log.LogWarning((object)(modName + ": every " + set.FilePattern + " file is gone, keeping the loaded configuration"));
			}
			else if (TryBuild(set, dictionary, out model, "reload"))
			{
				Publish(set, dictionary, model, writeBack: false);
				log.LogInfo((object)(modName + ": " + set.MainFileName + " reloaded" + (IsAuthor ? "" : ", not applied, remote configuration active")));
			}
		}

		private void Publish(YamlFileSet set, IReadOnlyDictionary<string, string> files, YamlModel model, bool writeBack)
		{
			bool suppressWriteBack = SuppressWriteBack;
			(YamlFileSet, YamlModel)? tuple = built;
			SuppressWriteBack = suppressWriteBack || !writeBack;
			built = (set, model);
			try
			{
				if (set.Channel == null)
				{
					Receive(set, files);
				}
				else
				{
					set.Channel.Assign(YamlFileList.Flatten(files));
				}
			}
			finally
			{
				SuppressWriteBack = suppressWriteBack;
				built = tuple;
			}
		}

		private void Receive(YamlFileSet set, IReadOnlyDictionary<string, string> files)
		{
			(YamlFileSet, YamlModel)? tuple = built;
			YamlModel? yamlModel;
			if (tuple.HasValue)
			{
				(YamlFileSet, YamlModel) valueOrDefault = tuple.GetValueOrDefault();
				if (valueOrDefault.Item1 == set)
				{
					yamlModel = valueOrDefault.Item2;
					goto IL_0031;
				}
			}
			yamlModel = BuildReceived(set, files);
			goto IL_0031;
			IL_0031:
			YamlModel yamlModel2 = yamlModel;
			if (yamlModel2 != null)
			{
				set.Files = files;
				set.Current = yamlModel2;
				if (CanApply())
				{
					Apply(set);
				}
				if (IsAuthor && !SuppressWriteBack)
				{
					store.WriteAll(files);
					watcher.TakeSnapshot(set);
				}
			}
		}

		private YamlModel? BuildReceived(YamlFileSet set, IReadOnlyDictionary<string, string> files)
		{
			if (TryBuild(set, files, out YamlModel model, "received"))
			{
				return model;
			}
			foreach (KeyValuePair<string, string> file in files)
			{
				log.LogWarning((object)(modName + ": content of " + file.Key + ":\n" + file.Value));
			}
			return null;
		}

		private void Apply(YamlFileSet set)
		{
			if (set.Current == null || !set.Enabled())
			{
				return;
			}
			try
			{
				set.Apply(set.Current);
				this.Applied?.Invoke(set);
			}
			catch (Exception arg)
			{
				log.LogError((object)$"{modName}: applying {set.MainFileName} failed: {arg}");
			}
		}
	}
	internal static class YamlFileList
	{
		public static List<string> Flatten(IReadOnlyDictionary<string, string> files)
		{
			List<string> list = new List<string>(files.Count * 2);
			foreach (KeyValuePair<string, string> file in files)
			{
				list.Add(file.Key);
				list.Add(file.Value);
			}
			return list;
		}

		public static Dictionary<string, string> Unflatten(List<string>? list)
		{
			Dictionary<string, string> dictionary = new Dictionary<string, string>();
			int num = 0;
			while (list != null && num + 1 < list.Count)
			{
				dictionary[list[num]] = list[num + 1];
				num += 2;
			}
			return dictionary;
		}
	}
	public sealed class YamlFileSet
	{
		internal int ReloadPending;

		private Regex? nameFilter;

		public string FilePattern { get; }

		public string MainFileName { get; }

		public string SyncKey { get; }

		public Func<YamlModel> CreateModel { get; }

		public Action<YamlModel> Apply { get; }

		public Func<bool> Enabled { get; set; } = () => true;

		public Func<byte[]?>? DefaultContent { get; set; }

		public Func<string> EditorLabel { get; set; }

		public IReadOnlyDictionary<string, string> Files { get; internal set; } = new Dictionary<string, string>();

		public YamlModel? Current { get; internal set; }

		internal Article<List<string>>? Channel { get; set; }

		internal Dictionary<string, DateTime> Snapshot { get; } = new Dictionary<string, DateTime>(StringComparer.OrdinalIgnoreCase);

		internal Regex NameFilter => nameFilter ?? (nameFilter = new Regex("^" + Regex.Escape(FilePattern).Replace("\\*", ".*") + "$", RegexOptions.IgnoreCase));

		public YamlFileSet(string filePattern, string syncKey, Func<YamlModel> createModel, Action<YamlModel> apply)
		{
			FilePattern = filePattern;
			MainFileName = filePattern.Replace("*", "");
			SyncKey = syncKey;
			CreateModel = createModel;
			Apply = apply;
			EditorLabel = () => "Edit " + MainFileName;
		}
	}
	internal sealed class YamlFileStore
	{
		private readonly string modName;

		private readonly ManualLogSource log;

		private readonly List<string> searchFolders;

		public IReadOnlyList<string> SearchFolders => searchFolders;

		public YamlFileStore(string modName, ManualLogSource log, IEnumerable<string> searchFolders)
		{
			this.modName = modName;
			this.log = log;
			this.searchFolders = searchFolders.ToList();
		}

		public List<string> Discover(YamlFileSet set)
		{
			Regex nameFilter = set.NameFilter;
			List<string> list = new List<string>();
			foreach (string item in searchFolders.Where(Directory.Exists))
			{
				list.AddRange(from p in Directory.GetFiles(item, set.FilePattern)
					where nameFilter.IsMatch(Path.GetFileName(p))
					select p);
			}
			return list.OrderBy((string p) => (!string.Equals(Path.GetFileName(p), set.MainFileName, StringComparison.OrdinalIgnoreCase)) ? 1 : 0).ThenBy<string, string>(Path.GetFileName, StringComparer.OrdinalIgnoreCase).ToList();
		}

		public Dictionary<string, string> Read(IEnumerable<string> paths)
		{
			Dictionary<string, string> dictionary = new Dictionary<string, string>();
			foreach (string path in paths)
			{
				try
				{
					dictionary[path] = File.ReadAllText(path);
				}
				catch (Exception ex)
				{
					log.LogError((object)(modName + ": reading " + path + " failed: " + ex.Message));
				}
			}
			return dictionary;
		}

		public Dictionary<string, string> ReadOrCreate(YamlFileSet set)
		{
			Dictionary<string, string> dictionary = Read(Discover(set));
			if (dictionary.Count <= 0)
			{
				return WriteDefault(set);
			}
			return dictionary;
		}

		public Dictionary<string, DateTime> WriteTimes(YamlFileSet set)
		{
			Dictionary<string, DateTime> dictionary = new Dictionary<string, DateTime>(StringComparer.OrdinalIgnoreCase);
			foreach (string item in Discover(set))
			{
				dictionary[item] = File.GetLastWriteTimeUtc(item);
			}
			return dictionary;
		}

		public void WriteAll(IReadOnlyDictionary<string, string> files)
		{
			foreach (KeyValuePair<string, string> file in files)
			{
				WriteAtomic(file.Key, file.Value);
			}
		}

		private Dictionary<string, string> WriteDefault(YamlFileSet set)
		{
			byte[] array = set.DefaultContent?.Invoke();
			if (array == null || searchFolders.Count == 0)
			{
				return new Dictionary<string, string>();
			}
			string text = Path.Combine(searchFolders[0], set.MainFileName);
			try
			{
				Directory.CreateDirectory(searchFolders[0]);
				File.WriteAllBytes(text, array);
				log.LogInfo((object)(modName + ": wrote default " + text));
			}
			catch (Exception ex)
			{
				log.LogError((object)(modName + ": writing default " + text + " failed: " + ex.Message));
				return new Dictionary<string, string>();
			}
			return Read(new string[1] { text });
		}

		private void WriteAtomic(string path, string content)
		{
			try
			{
				if (!File.Exists(path) || !(File.ReadAllText(path) == content))
				{
					ReplaceThroughTempFile(path, content);
					log.LogInfo((object)(modName + ": wrote " + path));
				}
			}
			catch (Exception ex)
			{
				log.LogError((object)(modName + ": writing " + path + " failed: " + ex.Message));
			}
		}

		private static void ReplaceThroughTempFile(string path, string content)
		{
			string text = path + ".tmp";
			File.WriteAllText(text, content, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
			if (File.Exists(path))
			{
				File.Replace(text, path, null);
			}
			else
			{
				File.Move(text, path);
			}
		}
	}
	internal sealed class YamlFileWatcher
	{
		private const int PollMilliseconds = 5000;

		private readonly YamlFileStore store;

		private readonly IReadOnlyList<YamlFileSet> sets;

		private readonly Action<YamlFileSet> reload;

		private readonly ManualLogSource log;

		private readonly string modName;

		private Timer? timer;

		public YamlFileWatcher(YamlFileStore store, IReadOnlyList<YamlFileSet> sets, Action<YamlFileSet> reload, ManualLogSource log, string modName)
		{
			this.store = store;
			this.sets = sets;
			this.reload = reload;
			this.log = log;
			this.modName = modName;
		}

		public void Start()
		{
			if (timer == null)
			{
				timer = new Timer(delegate
				{
					Poll();
				}, null, 5000, 5000);
			}
		}

		public Dictionary<string, string> ReadOrCreate(YamlFileSet set)
		{
			TakeSnapshot(set);
			bool flag;
			lock (set.Snapshot)
			{
				flag = set.Snapshot.Count > 0;
			}
			Dictionary<string, string> dictionary = store.ReadOrCreate(set);
			if (!flag && dictionary.Count > 0)
			{
				TakeSnapshot(set);
			}
			return dictionary;
		}

		public Dictionary<string, string> Read(YamlFileSet set)
		{
			TakeSnapshot(set);
			return store.Read(store.Discover(set));
		}

		public void TakeSnapshot(YamlFileSet set)
		{
			Dictionary<string, DateTime> dictionary = store.WriteTimes(set);
			lock (set.Snapshot)
			{
				set.Snapshot.Clear();
				foreach (KeyValuePair<string, DateTime> item in dictionary)
				{
					set.Snapshot[item.Key] = item.Value;
				}
			}
		}

		private void Poll()
		{
			try
			{
				foreach (YamlFileSet set in sets.Where(HasChanged))
				{
					if (Interlocked.CompareExchange(ref set.ReloadPending, 1, 0) == 0)
					{
						ThreadingHelper.SynchronizingObject.BeginInvoke((Action)delegate
						{
							Reload(set);
						}, null);
					}
				}
			}
			catch (Exception ex)
			{
				log.LogError((object)(modName + ": watching YAML files failed: " + ex.Message));
			}
		}

		private bool HasChanged(YamlFileSet set)
		{
			Dictionary<string, DateTime> dictionary = store.WriteTimes(set);
			lock (set.Snapshot)
			{
				DateTime value;
				return dictionary.Count != set.Snapshot.Count || dictionary.Any((KeyValuePair<string, DateTime> pair) => !set.Snapshot.TryGetValue(pair.Key, out value) || value != pair.Value);
			}
		}

		private void Reload(YamlFileSet set)
		{
			try
			{
				reload(set);
			}
			catch (Exception arg)
			{
				log.LogError((object)$"{modName}: reloading {set.MainFileName} failed: {arg}");
			}
			finally
			{
				Interlocked.Exchange(ref set.ReloadPending, 0);
			}
		}
	}
	internal static class YamlGameHooks
	{
		private static class Patches
		{
			public static void AfterMenuStart()
			{
				LoadHooked();
			}

			public static void AfterNetworkStart(ZNet __instance)
			{
				if (__instance.IsServer() && __instance.IsDedicated())
				{
					LoadHooked();
				}
			}

			public static void AfterRespawnRequest(Game __instance)
			{
				object obj = firstSpawnField?.GetValue(__instance);
				if (obj is bool && (bool)obj)
				{
					ApplyHooked();
				}
			}
		}

		private static readonly List<YamlFileHub> hooked = new List<YamlFileHub>();

		private static readonly FieldInfo? firstSpawnField = AccessTools.Field(typeof(Game), "m_firstSpawn");

		private static bool patched;

		public static void Install(YamlFileHub hub, Harmony harmony, int applyPriority)
		{
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Expected O, but got Unknown
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Expected O, but got Unknown
			//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_00af: Expected O, but got Unknown
			if (!hooked.Contains(hub))
			{
				hooked.Add(hub);
			}
			if (!patched)
			{
				patched = true;
				harmony.Patch((MethodBase)AccessTools.Method(typeof(FejdStartup), "Start", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(Patches), "AfterMenuStart", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)AccessTools.Method(typeof(ZNet), "Awake", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(Patches), "AfterNetworkStart", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				HarmonyMethod val = new HarmonyMethod(typeof(Patches), "AfterRespawnRequest", (Type[])null)
				{
					priority = applyPriority
				};
				harmony.Patch((MethodBase)AccessTools.Method(typeof(Game), "RequestRespawn", (Type[])null, (Type[])null), (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
		}

		private static void LoadHooked()
		{
			foreach (YamlFileHub item in hooked)
			{
				item.LoadFromHook();
			}
		}

		private static void ApplyHooked()
		{
			foreach (YamlFileHub item in hooked)
			{
				item.ApplyFromHook();
			}
		}
	}
	internal sealed class YamlGroupTable
	{
		private readonly Dictionary<string, List<string>> members = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);

		private readonly Dictionary<string, List<string>> parents = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);

		public IReadOnlyCollection<string> Names => members.Keys;

		public bool IsGroup(string name)
		{
			return members.ContainsKey(name);
		}

		public void Add(string group, IEnumerable<string> names)
		{
			if (!members.TryGetValue(group, out List<string> value))
			{
				value = new List<string>();
				members.Add(group, value);
			}
			foreach (string name in names)
			{
				value.Add(name);
				if (!parents.TryGetValue(name, out List<string> value2))
				{
					value2 = new List<string>();
					parents.Add(name, value2);
				}
				value2.Add(group);
			}
		}

		public IReadOnlyList<string> Resolve(string name)
		{
			List<string> list = new List<string>();
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			Queue<string> queue = new Queue<string>();
			queue.Enqueue(name);
			while (queue.Count > 0)
			{
				if (!parents.TryGetValue(queue.Dequeue(), out List<string> value))
				{
					continue;
				}
				foreach (string item in value)
				{
					if (hashSet.Add(item))
					{
						list.Add(item);
						queue.Enqueue(item);
					}
				}
			}
			return list;
		}

		public IReadOnlyList<string> Expand(string group)
		{
			List<string> result = new List<string>();
			HashSet<string> seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			Expand(group, seen, result);
			return result;
		}

		private void Expand(string name, HashSet<string> seen, List<string> result)
		{
			if (!seen.Add(name))
			{
				return;
			}
			if (!members.TryGetValue(name, out List<string> value))
			{
				result.Add(name);
				return;
			}
			foreach (string item in value)
			{
				Expand(item, seen, result);
			}
		}
	}
	public abstract class YamlModel
	{
		private static readonly IDeserializer Parser = new DeserializerBuilder().Build();

		private string? currentFile;

		public List<string> Errors { get; } = new List<string>();

		public List<string> Warnings { get; } = new List<string>();

		protected YamlGroupTable Groups { get; } = new YamlGroupTable();

		public void Load(string yamlText)
		{
			ReadText(yamlText);
			Verify();
		}

		public void LoadAll(IEnumerable<KeyValuePair<string, string>> files)
		{
			foreach (KeyValuePair<string, string> file in files)
			{
				currentFile = Path.GetFileName(file.Key);
				ReadText(file.Value);
			}
			currentFile = null;
			Verify();
		}

		public IReadOnlyList<string> ResolveGroups(string name)
		{
			return Groups.Resolve(name);
		}

		protected abstract void Read(YamlNode root);

		protected virtual void Verify()
		{
		}

		protected void ReadGroups(YamlNode root, string key = "groups")
		{
			foreach (KeyValuePair<string, YamlNode> entry in root.Get(key).Entries)
			{
				if (entry.Value.TryStringList(out List<string> items))
				{
					Groups.Add(entry.Key, items);
				}
			}
		}

		internal void AddError(string message)
		{
			Errors.Add(WithFile(message));
		}

		internal void AddWarning(string message)
		{
			Warnings.Add(WithFile(message));
		}

		private string WithFile(string message)
		{
			if (currentFile != null)
			{
				return currentFile + ": " + message;
			}
			return message;
		}

		private void ReadText(string yamlText)
		{
			if (TryParse(yamlText, out object graph) && graph != null)
			{
				if (!(graph is IDictionary<object, object>))
				{
					AddError("the file must be a map of keys at the top level, not " + ((graph is IList<object>) ? "a list" : "a single value"));
					return;
				}
				YamlNode yamlNode = new YamlNode(this, graph, "");
				Read(yamlNode);
				yamlNode.WarnUnknownKeys();
			}
		}

		private bool TryParse(string yamlText, out object? graph)
		{
			try
			{
				graph = Parser.Deserialize<object>(yamlText.TrimStart(new char[1] { '\ufeff' }));
				return true;
			}
			catch (Exception ex)
			{
				AddError("invalid YAML: " + ex.Message + ((ex.InnerException == null) ? "" : (" (" + ex.InnerException.Message + ")")));
				graph = null;
				return false;
			}
		}
	}
	internal enum YamlNodeKind
	{
		Scalar,
		List,
		Map,
		Null,
		Missing
	}
	internal sealed class YamlNode
	{
		private readonly YamlModel model;

		private readonly object? value;

		private List<KeyValuePair<string, YamlNode>>? mapEntries;

		private Dictionary<string, YamlNode>? mapLookup;

		private List<YamlNode>? listItems;

		private HashSet<string>? askedKeys;

		private bool visited;

		private bool shapeReported;

		public string Path { get; }

		public YamlNodeKind Kind { get; }

		public string? Text
		{
			get
			{
				if (Kind != YamlNodeKind.Scalar)
				{
					return null;
				}
				return Convert.ToString(value, CultureInfo.InvariantCulture);
			}
		}

		public int Count => Kind switch
		{
			YamlNodeKind.Map => MapEntries.Count, 
			YamlNodeKind.List => ListItems.Count, 
			_ => 0, 
		};

		public IReadOnlyList<KeyValuePair<string, YamlNode>> Entries
		{
			get
			{
				if (!ExpectShape(YamlNodeKind.Map, "a map"))
				{
					return Array.Empty<KeyValuePair<string, YamlNode>>();
				}
				visited = true;
				AskedKeys.UnionWith(MapEntries.Select<KeyValuePair<string, YamlNode>, string>((KeyValuePair<string, YamlNode> e) => e.Key));
				return MapEntries;
			}
		}

		public IReadOnlyList<YamlNode> Items
		{
			get
			{
				if (!ExpectShape(YamlNodeKind.List, "a list"))
				{
					return Array.Empty<YamlNode>();
				}
				return ListItems;
			}
		}

		private HashSet<string> AskedKeys => askedKeys ?? (askedKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase));

		private List<KeyValuePair<string, YamlNode>> MapEntries
		{
			get
			{
				if (mapEntries == null)
				{
					BuildMap();
				}
				return mapEntries;
			}
		}

		private Dictionary<string, YamlNode> MapLookup
		{
			get
			{
				if (mapLookup == null)
				{
					BuildMap();
				}
				return mapLookup;
			}
		}

		private List<YamlNode> ListItems
		{
			get
			{
				if (listItems == null)
				{
					IList<object> source = (IList<object>)value;
					listItems = source.Select((object item, int i) => new YamlNode(model, item, $"{Path}[{i}]")).ToList();
				}
				return listItems;
			}
		}

		internal YamlNode(YamlModel model, object? value, string path, YamlNodeKind kind)
		{
			this.model = model;
			this.value = value;
			Path = path;
			Kind = kind;
		}

		internal YamlNode(YamlModel model, object? value, string path)
			: this(model, value, path, KindOf(value))
		{
		}

		public YamlNode Get(string key)
		{
			string path = (string.IsNullOrEmpty(Path) ? key : (Path + "." + key));
			if (!ExpectShape(YamlNodeKind.Map, "a map"))
			{
				return new YamlNode(model, null, path, YamlNodeKind.Missing);
			}
			visited = true;
			AskedKeys.Add(key);
			if (!MapLookup.TryGetValue(key, out YamlNode result))
			{
				return new YamlNode(model, null, path, YamlNodeKind.Missing);
			}
			return result;
		}

		public bool Is(string keyword)
		{
			if (Kind == YamlNodeKind.Scalar)
			{
				return string.Equals(Text.Trim(), keyword, StringComparison.OrdinalIgnoreCase);
			}
			return false;
		}

		public void Error(string message)
		{
			model.AddError(Prefixed(message));
		}

		public void Warn(string message)
		{
			model.AddWarning(Prefixed(message));
		}

		public void WarnUnknownKeys()
		{
			if (Kind == YamlNodeKind.Map && visited)
			{
				List<string> list = (from e in MapEntries
					select e.Key into k
					where !AskedKeys.Contains(k)
					select k).ToList();
				if (list.Count > 0)
				{
					Warn("unknown keys " + string.Join(", ", list));
				}
				{
					foreach (KeyValuePair<string, YamlNode> mapEntry in MapEntries)
					{
						mapEntry.Value.WarnUnknownKeys();
					}
					return;
				}
			}
			if (Kind == YamlNodeKind.List && listItems != null)
			{
				listItems.ForEach(delegate(YamlNode item)
				{
					item.WarnUnknownKeys();
				});
			}
		}

		private string Prefixed(string message)
		{
			if (!string.IsNullOrEmpty(Path))
			{
				return Path + ": " + message;
			}
			return message;
		}

		private void BuildMap()
		{
			mapEntries = new List<KeyValuePair<string, YamlNode>>();
			mapLookup = new Dictionary<string, YamlNode>(StringComparer.OrdinalIgnoreCase);
			foreach (KeyValuePair<object, object> item in (IDictionary<object, object>)value)
			{
				string text = Convert.ToString(item.Key, CultureInfo.InvariantCulture) ?? "";
				string path = (string.IsNullOrEmpty(Path) ? text : (Path + "." + text));
				YamlNode yamlNode = new YamlNode(model, item.Value, path);
				if (mapLookup.ContainsKey(text))
				{
					yamlNode.Warn("duplicate key (differs only in case), ignored");
					continue;
				}
				mapLookup.Add(text, yamlNode);
				mapEntries.Add(new KeyValuePair<string, YamlNode>(text, yamlNode));
			}
		}

		private bool ExpectShape(YamlNodeKind wanted, string description)
		{
			if (Kind == wanted)
			{
				return true;
			}
			if (Kind != YamlNodeKind.Missing && !shapeReported)
			{
				shapeReported = true;
				Error((Kind == YamlNodeKind.Null) ? ("expected " + description + " but the key has no value") : ("expected " + description + ", found " + Describe(Kind)));
			}
			return false;
		}

		private static string Describe(YamlNodeKind kind)
		{
			return kind switch
			{
				YamlNodeKind.Scalar => "a single value", 
				YamlNodeKind.List => "a list", 
				YamlNodeKind.Map => "a map", 
				_ => "nothing", 
			};
		}

		private static YamlNodeKind KindOf(object? value)
		{
			if (value != null)
			{
				if (!(value is IDictionary<object, object>))
				{
					if (value is IList<object>)
					{
						return YamlNodeKind.List;
					}
					return YamlNodeKind.Scalar;
				}
				return YamlNodeKind.Map;
			}
			return YamlNodeKind.Null;
		}

		public bool TryInt(out int result)
		{
			return TryScalar<int>(out result);
		}

		public bool TryFloat(out float result)
		{
			return TryScalar<float>(out result);
		}

		public bool TryBool(out bool result)
		{
			return TryScalar<bool>(out result);
		}

		public bool TryString(out string result)
		{
			return TryScalar<string>(out result);
		}

		public bool TryEnum<T>(out T result) where T : struct, Enum
		{
			return TryScalar<T>(out result);
		}

		public bool TryList<T>(out List<T> items)
		{
			item