Decompiled source of Disaster Control v0.9.1

BepInEx/plugins/DisasterControl/DiceKingdoms.DisasterControl.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Text;
using System.Text.Json;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Core.Logging.Interpolation;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using DiceKingdoms.Game;
using DiceKingdoms.Interface;
using DiceKingdoms.Meta;
using HarmonyLib;
using Il2CppInterop.Runtime.Attributes;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSystem;
using Il2CppSystem.Collections;
using Il2CppSystem.Collections.Generic;
using Il2CppSystem.IO;
using Il2CppSystem.Reflection;
using MLAPI;
using MLAPI.Connection;
using MLAPI.Messaging;
using MLAPI.Security;
using MLAPI.Transports.UNET;
using Microsoft.CodeAnalysis;
using TMPro;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")]
[assembly: AssemblyCompany("DiceKingdoms.DisasterControl")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.9.1.0")]
[assembly: AssemblyInformationalVersion("0.9.1")]
[assembly: AssemblyProduct("DiceKingdoms.DisasterControl")]
[assembly: AssemblyTitle("DiceKingdoms.DisasterControl")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.9.1.0")]
[module: UnverifiableCode]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

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

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

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
}
namespace DiceKingdoms.DisasterControl
{
	public sealed class ControlPanel : MonoBehaviour
	{
		private GameObject? panel;

		private SettingSetsInterface? window;

		private TabArea? tabs;

		private int tabIndex;

		private BaseButton? buttonTemplate;

		private RectTransform? content;

		private TextMeshProUGUI? status;

		private TMP_FontAsset? font;

		private readonly List<Button> editors = new List<Button>();

		private float next;

		private string rendered = "";

		private bool Ru => Plugin.Settings.Language.Value.Equals("ru", StringComparison.OrdinalIgnoreCase);

		public int RuleRows { get; private set; }

		public bool Opened
		{
			get
			{
				if ((Object)(object)panel != (Object)null)
				{
					return panel.activeInHierarchy;
				}
				return false;
			}
		}

		public ControlPanel(IntPtr p)
			: base(p)
		{
		}

		private string L(string en, string ru)
		{
			if (!Ru)
			{
				return en;
			}
			return ru;
		}

		public void Update()
		{
			Plugin.Session.Tick(Time.realtimeSinceStartup);
			if (Time.realtimeSinceStartup < next)
			{
				return;
			}
			next = Time.realtimeSinceStartup + 0.3f;
			try
			{
				SettingSetsInterface[] source = ((IEnumerable<SettingSetsInterface>)Resources.FindObjectsOfTypeAll<SettingSetsInterface>()).Where(delegate(SettingSetsInterface s)
				{
					//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)
					Scene scene = ((Component)s).gameObject.scene;
					return ((Scene)(ref scene)).IsValid();
				}).ToArray();
				SettingSetsInterface val = ((IEnumerable<SettingSetsInterface>)source).FirstOrDefault((Func<SettingSetsInterface, bool>)((SettingSetsInterface s) => ((Component)s).gameObject.activeInHierarchy)) ?? source.LastOrDefault();
				if ((Object)(object)val != (Object)null && ((Object)(object)window == (Object)null || ((Il2CppObjectBase)val).Pointer != ((Il2CppObjectBase)window).Pointer))
				{
					Build(val);
				}
				if ((Object)(object)panel == (Object)null || !Opened)
				{
					return;
				}
				if (Settings.Serialize(Plugin.Session.Draft) + Ru != rendered)
				{
					Render();
				}
				foreach (Button editor in editors)
				{
					((Selectable)editor).interactable = Plugin.Session.CanEdit;
				}
				((TMP_Text)status).text = L(Plugin.Session.Status, TranslateStatus()) + (Plugin.Session.CanEdit ? "" : L("  • Read only", "  • Только просмотр"));
			}
			catch (Exception e)
			{
				Plugin.Error(e);
			}
		}

		private string TranslateStatus()
		{
			if (Plugin.Healthy)
			{
				if (!Plugin.Session.CanEdit || !Plugin.Session.Frozen)
				{
					if (!Plugin.Session.Frozen)
					{
						if (Plugin.Session.InScope)
						{
							if (!Plugin.Session.IsHost)
							{
								return "Правила хоста; только просмотр.";
							}
							return "Для старта все участники должны подтвердить правила и версию мода.";
						}
						return "Открой одиночное или пользовательское лобби.";
					}
					return "Сетевая игра: правила заблокированы до конца матча.";
				}
				if (!Plugin.Session.Pending)
				{
					return "Одиночная игра: изменения применятся со следующего сбора урожая.";
				}
				return "Изменения ожидают начала следующего сбора урожая.";
			}
			return "Ошибка мода: перезапусти игру. Изменённые правила заблокированы.";
		}

		public void SetOpen(bool value)
		{
			if (!((Object)(object)panel == (Object)null) && !((Object)(object)window == (Object)null) && !((Object)(object)tabs == (Object)null))
			{
				if (value)
				{
					((ToggleableWindow)window).Open();
					tabs.SelectTab(tabIndex);
					Render();
				}
				else
				{
					((ToggleableWindow)window).Close();
				}
			}
		}

		private void Build(SettingSetsInterface settings)
		{
			//IL_01fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0206: Unknown result type (might be due to invalid IL or missing references)
			//IL_0212: Unknown result type (might be due to invalid IL or missing references)
			//IL_0217: Unknown result type (might be due to invalid IL or missing references)
			//IL_0218: Unknown result type (might be due to invalid IL or missing references)
			//IL_021f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0297: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_02cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_0312: Unknown result type (might be due to invalid IL or missing references)
			//IL_0339: Unknown result type (might be due to invalid IL or missing references)
			//IL_0340: Unknown result type (might be due to invalid IL or missing references)
			//IL_034d: Unknown result type (might be due to invalid IL or missing references)
			//IL_03b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_03f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_03fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0404: Unknown result type (might be due to invalid IL or missing references)
			//IL_0410: Expected O, but got Unknown
			window = settings;
			Label? obj = ((IEnumerable<Label>)Resources.FindObjectsOfTypeAll<Label>()).FirstOrDefault((Func<Label, bool>)((Label l) => (Object)(object)l.visual != (Object)null));
			font = ((obj != null) ? ((TMP_Text)obj.visual).font : null);
			buttonTemplate = settings.closeButton;
			tabs = ((Component)settings).GetComponentInChildren<TabArea>(true);
			int num = Array.FindIndex(((IEnumerable<TabDefinition>)tabs.tabs).ToArray(), (TabDefinition t) => ((Object)t.content).name == "DisasterControl.Panel");
			if (num >= 0)
			{
				tabIndex = num;
				panel = ((Component)((Il2CppArrayBase<TabDefinition>)(object)tabs.tabs)[num].content).gameObject;
				content = ((Il2CppObjectBase)panel.transform.Find("Viewport/Rules")).Cast<RectTransform>();
				status = ((Component)panel.transform.Find("Text")).GetComponent<TextMeshProUGUI>();
				rendered = "";
				return;
			}
			TabDefinition val = ((Il2CppArrayBase<TabDefinition>)(object)tabs.tabs)[0];
			TabAreaTabTitle val2 = Object.Instantiate<TabAreaTabTitle>(val.title, ((Component)val.title).transform.parent);
			((Object)val2).name = "DisasterControl.Title";
			foreach (Behaviour componentsInChild in ((Component)val2).GetComponentsInChildren<Behaviour>(true))
			{
				if ((Object)(object)((Il2CppObjectBase)componentsInChild).TryCast<Label>() != (Object)null || ((MemberInfo)((Object)componentsInChild).GetIl2CppType()).Name.Contains("Localizer"))
				{
					componentsInChild.enabled = false;
				}
			}
			TextMeshProUGUI componentInChildren = ((Component)val2).GetComponentInChildren<TextMeshProUGUI>(true);
			((TMP_Text)componentInChildren).text = "Disaster Control";
			((TMP_Text)componentInChildren).fontSize = 21f;
			((TMP_Text)componentInChildren).enableAutoSizing = true;
			((TMP_Text)componentInChildren).fontSizeMin = 15f;
			val2.Selected = false;
			RectTransform val3 = Rect("DisasterControl.Panel", ((Transform)val.content).parent, 0f, 0f);
			val3.anchorMin = Vector2.zero;
			val3.anchorMax = Vector2.one;
			Vector2 val4 = (val3.offsetMin = (val3.offsetMax = Vector2.zero));
			panel = ((Component)val3).gameObject;
			status = Text(panel.transform, "", 18, 700f, 78f);
			Position(((TMP_Text)status).rectTransform, 25f, -8f);
			RectTransform val6 = Rect("Viewport", panel.transform, 700f, 590f);
			val6.anchorMin = Vector2.zero;
			val6.anchorMax = Vector2.one;
			val6.offsetMin = new Vector2(25f, 15f);
			val6.offsetMax = new Vector2(-25f, -90f);
			((Component)val6).gameObject.AddComponent<RectMask2D>();
			content = Rect("Rules", (Transform)(object)val6, 700f, 2000f);
			content.pivot = new Vector2(0f, 1f);
			RectTransform? obj2 = content;
			RectTransform? obj3 = content;
			((Vector2)(ref val4))..ctor(0f, 1f);
			obj3.anchorMax = val4;
			obj2.anchorMin = val4;
			content.anchoredPosition = Vector2.zero;
			ScrollRect obj4 = ((Component)val6).gameObject.AddComponent<ScrollRect>();
			obj4.viewport = val6;
			obj4.content = content;
			obj4.horizontal = false;
			obj4.vertical = true;
			obj4.movementType = (MovementType)2;
			obj4.scrollSensitivity = 35f;
			((Graphic)((Component)val6).gameObject.AddComponent<Image>()).color = new Color(0f, 0f, 0f, 0.01f);
			panel.SetActive(false);
			tabIndex = ((Il2CppArrayBase<TabDefinition>)(object)tabs.tabs).Length;
			tabs.tabs = new Il2CppReferenceArray<TabDefinition>(((IEnumerable<TabDefinition>)tabs.tabs).ToArray().Append(new TabDefinition
			{
				title = val2,
				content = val3
			}).ToArray());
			tabs.titleIndex.Add(((Component)val2).gameObject, tabIndex);
			rendered = "";
		}

		[HideFromIl2Cpp]
		private void Edit(Func<Rules, Rules> edit)
		{
			if (Plugin.Session.Set(edit(Plugin.Session.Draft)with
			{
				Preset = Preset.Custom
			}))
			{
				rendered = "";
			}
		}

		private void Render()
		{
			//IL_04dc: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)content == (Object)null)
			{
				return;
			}
			foreach (Button editor in editors)
			{
				if ((Object)(object)editor != (Object)null)
				{
					((Selectable)editor).interactable = false;
				}
			}
			editors.Clear();
			for (int num = ((Transform)content).childCount - 1; num >= 0; num--)
			{
				Transform child = ((Transform)content).GetChild(num);
				((Component)child).gameObject.SetActive(false);
				Object.Destroy((Object)(object)((Component)child).gameObject);
			}
			float y = 8f;
			RuleRows = 0;
			Rules draft = Plugin.Session.Draft;
			string hint = ((draft.Preset == Preset.Chaos) ? L("Chaos forces native Multi-Meteor on; 32 meteors maximum per island; Disease/Revolt 200%.", "Chaos включает Multi-Meteor: до 32 ударов на остров; Disease/Revolt 200%.") : L("Vanilla is the original game. Manual changes select Custom.", "Vanilla — оригинальная игра. Ручные изменения включают Custom."));
			Row(ref y, L("Preset", "Пресет"), draft.Preset.ToString(), hint, delegate
			{
				Preset preset = (Preset)((int)(Plugin.Session.Draft.Preset + 1) % Enum.GetValues<Preset>().Length);
				Plugin.Session.Set((preset == Preset.Custom) ? Plugin.Session.Draft with
				{
					Preset = Preset.Custom
				} : Rules.For(preset, Catalog.Ids));
				rendered = "";
			});
			Row(ref y, L("Meteor targeting", "Попадания метеоритов"), TargetName(draft.Targeting), L("Protect Castle: exclude the full castle footprint plus impact radius. Safer: prefer 2 extra tiles of clearance. No safe land: skip this meteor.", "Защита учитывает всю площадь замка и радиус удара. Safer добавляет отступ 2 клетки. Нет безопасной земли — удар пропускается."), delegate
			{
				Edit((Rules v) => v with
				{
					Targeting = (MeteorTargeting)((int)(v.Targeting + 1) % 3)
				});
			});
			Row(ref y, L("Excess skulls", "Избыток черепов"), ExcessName(draft.Excess), L($"Cycle uses native thresholds {Catalog.First}–{Catalog.Last}. Single / NoExcess keep one threshold meteor; extra meteors are removed.", $"Цикл использует реальные пороги {Catalog.First}–{Catalog.Last}. Single / NoExcess сохраняют первый метеорит и убирают дополнительные."), delegate
			{
				Edit((Rules v) => v with
				{
					Excess = (ExcessSkullMode)((int)(v.Excess + 1) % 4)
				});
			});
			Row(ref y, L("Skull reroll", "Переброс черепов"), On(draft.AllowSkullReroll), L("Select skull dice normally. Each reroll spends a native attempt; research and face probabilities are unchanged.", "Выбирайте кубики с черепами. Переброс расходует штатную попытку с учётом исследований."), delegate
			{
				Edit((Rules v) => v with
				{
					AllowSkullReroll = !v.AllowSkullReroll
				});
			});
			Row(ref y, L("Force Multi-Meteor", "Несколько метеоритов"), On(draft.ForceMultiMeteor), L("Forces the lobby option with Vanilla excess skulls. Other excess modes allow at most one meteor.", "Включает Multi-Meteor при оригинальном режиме избытка. В остальных режимах остаётся не больше одного удара."), delegate
			{
				Edit((Rules v) => v with
				{
					ForceMultiMeteor = !v.ForceMultiMeteor
				});
			});
			Row(ref y, L("Disease intensity", "Сила болезни"), draft.DiseasePercent + "%", L("Multiplier of the original Disease percentage. Separate Plague has its own toggle.", "Множитель штатной силы Disease. У Plague отдельный переключатель."), delegate
			{
				Edit((Rules v) => v with
				{
					DiseasePercent = ((v.DiseasePercent < 200) ? Math.Min(200, v.DiseasePercent + 25) : 0)
				});
			});
			Row(ref y, L("Revolt intensity", "Сила восстания"), draft.RevoltPercent + "%", L("Multiplier of original military point loss; actual loss is capped at 100%.", "Множитель потери военной силы (мечи). Итоговая потеря не больше 100%."), delegate
			{
				Edit((Rules v) => v with
				{
					RevoltPercent = ((v.RevoltPercent < 200) ? Math.Min(200, v.RevoltPercent + 25) : 0)
				});
			});
			Row(ref y, L("Meteor safety limit", "Лимит метеоритов"), draft.MeteorLimit.ToString(), L("Changing this value enables a bounded custom rule set. Balanced uses a cycle with at most one meteor per island.", "Изменение лимита включает свои правила. В Balanced цикл даёт не больше одного метеорита на остров."), delegate
			{
				Edit((Rules v) => v with
				{
					MeteorLimit = ((v.MeteorLimit >= 32) ? 1 : Math.Min(32, v.MeteorLimit * 2))
				});
			});
			string[] ids = Catalog.Ids;
			foreach (string text in ids)
			{
				string captured = text;
				Row(ref y, DisasterName(text), On(draft.Enabled(text)), L("Disable the real event and its consequences for players and AI. Independent starvation stays original.", "Отключает событие и последствия для игроков и ИИ. Независимый голод сохраняется."), delegate
				{
					Edit((Rules v) => v with
					{
						Disabled = (v.Enabled(captured) ? v.Disabled.Append(captured).OrderBy<string, string>((string x) => x, StringComparer.Ordinal).ToArray() : v.Disabled.Where((string x) => x != captured).ToArray())
					});
				});
			}
			Row(ref y, L("Restore original rules", "Вернуть оригинальные правила"), "Vanilla", L("Reset every gameplay option to the unmodified game.", "Сброс всех параметров к оригинальной игре."), delegate
			{
				Plugin.Session.Set(new Rules());
				rendered = "";
			});
			Position(((TMP_Text)Text((Transform)(object)content, L("Custom matches require the same mod on every player. Joining/reconnecting after a custom match starts is currently blocked; create a new lobby. Scroll to see every disaster.", "Для своих правил мод нужен всем игрокам. Вход и переподключение после старта такого матча пока закрыты; создайте новое лобби. Прокрутите список всех катастроф."), 17, 680f, 70f)).rectTransform, 15f, 0f - y);
			y += 80f;
			RectTransform val = Rect("Language", (Transform)(object)content, 680f, 45f);
			Position(val, 15f, 0f - y);
			ButtonOn(((Component)val).gameObject, Ru ? "English" : "Русский", delegate
			{
				Plugin.Settings.Language.Value = (Ru ? "en" : "ru");
				rendered = "";
			});
			content.sizeDelta = new Vector2(700f, y + 65f);
			rendered = Settings.Serialize(draft) + Ru;
		}

		private string On(bool value)
		{
			if (!value)
			{
				return L("Disabled", "Отключено");
			}
			return L("Enabled", "Включено");
		}

		private string TargetName(MeteorTargeting value)
		{
			if (!Ru)
			{
				return value.ToString();
			}
			return value switch
			{
				MeteorTargeting.ProtectCastle => "Защита замка", 
				MeteorTargeting.SaferTargeting => "Усиленный отступ", 
				_ => "Оригинальные", 
			};
		}

		private string ExcessName(ExcessSkullMode value)
		{
			if (!Ru)
			{
				return value.ToString();
			}
			return value switch
			{
				ExcessSkullMode.SingleMeteor => "Один метеорит", 
				ExcessSkullMode.CyclicDisasters => "По кругу", 
				ExcessSkullMode.NoExcessMeteors => "Без лишних ударов", 
				_ => "Оригинальные", 
			};
		}

		private string DisasterName(string id)
		{
			string text = id.Split('.').Last();
			if (!Ru)
			{
				return text;
			}
			return text switch
			{
				"Robbers" => "Грабители", 
				"Disease" => "Болезнь", 
				"Lightning" => "Молния", 
				"Fire" => "Пожар", 
				"Thunderstorm" => "Гроза", 
				"Revolt" => "Восстание", 
				"Tornado" => "Торнадо", 
				"Plague" => "Чума", 
				"Satanism" => "Сатанизм", 
				"Pirates" => "Пираты", 
				"Tsunami" => "Цунами", 
				"Meteorite" => "Метеорит", 
				_ => text, 
			};
		}

		[HideFromIl2Cpp]
		private void Row(ref float y, string label, string value, string hint, Action click)
		{
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			RectTransform val = Rect(label, (Transform)(object)content, 700f, 128f);
			Position(val, 15f, 0f - y);
			Position(((TMP_Text)Text((Transform)(object)val, label, 23, 380f, 34f)).rectTransform, 0f, 0f);
			RectTransform val2 = Rect("Value", (Transform)(object)val, 285f, 40f);
			Position(val2, 395f, 0f);
			editors.Add(ButtonOn(((Component)val2).gameObject, value, click));
			TextMeshProUGUI obj = Text((Transform)(object)val, hint, 17, 680f, 62f);
			((Graphic)obj).color = new Color(0.7f, 0.75f, 0.8f);
			Position(((TMP_Text)obj).rectTransform, 0f, -43f);
			y += 136f;
			RuleRows++;
		}

		private static RectTransform Rect(string name, Transform parent, float width, float height)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			RectTransform obj = new GameObject(name).AddComponent<RectTransform>();
			((Transform)obj).SetParent(parent, false);
			obj.sizeDelta = new Vector2(width, height);
			return obj;
		}

		private static void Position(RectTransform r, float x, float y)
		{
			//IL_0013: 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_0037: Unknown result type (might be due to invalid IL or missing references)
			Vector2 val = default(Vector2);
			((Vector2)(ref val))..ctor(0f, 1f);
			r.anchorMax = val;
			r.anchorMin = val;
			r.pivot = new Vector2(0f, 1f);
			r.anchoredPosition = new Vector2(x, y);
		}

		private TextMeshProUGUI Text(Transform parent, string text, int size, float width, float height)
		{
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			TextMeshProUGUI val = ((Component)Rect("Text", parent, width, height)).gameObject.AddComponent<TextMeshProUGUI>();
			if ((Object)(object)font != (Object)null)
			{
				((TMP_Text)val).font = font;
			}
			((TMP_Text)val).text = text;
			((TMP_Text)val).fontSize = size;
			((Graphic)val).color = new Color(0.95f, 0.9f, 0.77f);
			((TMP_Text)val).enableWordWrapping = true;
			((Graphic)val).raycastTarget = false;
			return val;
		}

		[HideFromIl2Cpp]
		private Button ButtonOn(GameObject go, string caption, Action action)
		{
			//IL_0032: 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_0048: 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)
			//IL_004e: 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_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Expected O, but got Unknown
			BaseButton val = Object.Instantiate<BaseButton>(buttonTemplate, go.transform);
			val.onClick = null;
			RectTransform obj = ((Il2CppObjectBase)((Component)val).transform).Cast<RectTransform>();
			obj.anchorMin = Vector2.zero;
			obj.anchorMax = Vector2.one;
			Vector2 offsetMin = (obj.offsetMax = Vector2.zero);
			obj.offsetMin = offsetMin;
			LayoutElement component = ((Component)val).GetComponent<LayoutElement>();
			if ((Object)(object)component != (Object)null)
			{
				component.ignoreLayout = true;
			}
			foreach (Behaviour componentsInChild in ((Component)val).GetComponentsInChildren<Behaviour>(true))
			{
				if ((Object)(object)((Il2CppObjectBase)componentsInChild).TryCast<Label>() != (Object)null || ((MemberInfo)((Object)componentsInChild).GetIl2CppType()).Name.Contains("Localizer"))
				{
					componentsInChild.enabled = false;
				}
			}
			TextMeshProUGUI componentInChildren = ((Component)val).GetComponentInChildren<TextMeshProUGUI>(true);
			((TMP_Text)componentInChildren).text = caption;
			((TMP_Text)componentInChildren).fontSize = 21f;
			((TMP_Text)componentInChildren).enableAutoSizing = true;
			((TMP_Text)componentInChildren).fontSizeMin = 14f;
			Button unityButton = val.unityButton;
			unityButton.onClick = new ButtonClickedEvent();
			((UnityEvent)unityButton.onClick).AddListener(UnityAction.op_Implicit((Action)delegate
			{
				try
				{
					action();
				}
				catch (Exception e)
				{
					Plugin.Error(e);
				}
			}));
			return unityButton;
		}
	}
	[BepInPlugin("io.github.mikhasan.dicekingdoms.disastercontrol", "Disaster Control", "0.9.1")]
	[BepInProcess("Dice-Kingdoms.exe")]
	public sealed class Plugin : BasePlugin
	{
		public const string Id = "io.github.mikhasan.dicekingdoms.disastercontrol";

		public const string Version = "0.9.1";

		internal static Plugin Instance = null;

		internal static Settings Settings = null;

		internal static bool Healthy;

		internal static string GameFingerprint = "";

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

		public static RuleSession Session { get; private set; } = null;

		public override void Load()
		{
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Expected O, but got Unknown
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			Instance = this;
			Settings = new Settings(((BasePlugin)this).Config);
			Session = new RuleSession();
			try
			{
				using FileStream inputStream = File.OpenRead(Path.Combine(Paths.GameRootPath, "GameAssembly.dll"));
				using SHA256 sHA = SHA256.Create();
				GameFingerprint = Application.version + "/" + Application.unityVersion + "/" + Convert.ToHexString(sHA.ComputeHash(inputStream));
				new Harmony("io.github.mikhasan.dicekingdoms.disastercontrol").PatchAll(typeof(Plugin).Assembly);
				Healthy = true;
			}
			catch (Exception e)
			{
				Error(e);
				((BasePlugin)this).Log.LogError((object)"Required hooks unavailable. Modified rules are blocked.");
			}
			((BasePlugin)this).AddComponent<ControlPanel>();
			ManualLogSource log = ((BasePlugin)this).Log;
			bool flag = default(bool);
			BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(78, 1, ref flag);
			if (flag)
			{
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Disaster Control ");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>("0.9.1");
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; default Vanilla; native seeded disaster execution retained.");
			}
			log.LogInfo(val);
		}

		internal static void Error(Exception e)
		{
			if (Instance.errors.Add(e.GetType().Name + e.Message))
			{
				((BasePlugin)Instance).Log.LogError((object)e);
			}
		}
	}
	public static class Catalog
	{
		internal static Disasters? Data;

		public static string[] Ids { get; private set; } = Array.Empty<string>();

		public static int First { get; private set; }

		public static int Last { get; private set; }

		public static string Signature { get; private set; } = "";

		public static string Id(Disaster disaster)
		{
			return ((Object)disaster).GetIl2CppType().FullName;
		}

		public static bool Starvation(Disaster d)
		{
			return ((Il2CppObjectBase)d).TryCast<Starvation>() != null;
		}

		internal static void Discover(Disasters data)
		{
			if ((Object)(object)Data != (Object)null && ((Il2CppObjectBase)Data).Pointer == ((Il2CppObjectBase)data).Pointer && Ids.Length != 0)
			{
				return;
			}
			Disaster[] array = (from d in ((IEnumerable<Disaster>)data.Values)?.ToArray()
				where d != null
				select d).ToArray();
			if (array != null && array.Length != 0)
			{
				Data = data;
				Disaster[] source = array.Where((Disaster d) => !Starvation(d)).ToArray();
				Ids = source.Select(Id).Distinct().OrderBy<string, string>((string x) => x, StringComparer.Ordinal)
					.ToArray();
				First = source.Where((Disaster d) => d.threshold >= 0).Min((Disaster d) => d.threshold);
				Last = source.Max((Disaster d) => d.threshold);
				Signature = Plugin.GameFingerprint + "|" + string.Join(";", array.Select((Disaster d) => $"{Id(d)}:{d.threshold}:{d.affectedOthers}")) + $"|radius={data.meteorite.radius}|castle={data.meteorite.minimumCastleTiles},{data.meteorite.meteoriteTilesToFullyAllowHitCastle}|disease={data.disease.percentageAffected}|revolt={data.revolt.percentageLost}|plague={data.plague.percentageAffected}|pirates={data.pirates.percentageLost}:{string.Join(",", (IEnumerable<Resource>)data.pirates.losableResources)}|robbers={string.Join(",", (IEnumerable<Resource>)data.robbers.losableResources)}|lightning={data.lightning.nbTile}|thunderstorm={data.thunderstorm.nbTile}|tsunami={data.tsunami.radius},{data.tsunami.startDistance},{data.tsunami.startToImpactDurationMilliseconds}";
				Plugin.Settings.Discover(Ids);
				((BasePlugin)Plugin.Instance).Log.LogInfo((object)("Native disaster scale: " + Signature + $"; inclusive period={Last - First + 1}"));
			}
		}
	}
	[HarmonyPatch(typeof(Disasters), "Init")]
	internal static class CatalogPatch
	{
		private static void Postfix(Disasters __instance)
		{
			try
			{
				Catalog.Discover(__instance);
			}
			catch (Exception e)
			{
				Plugin.Healthy = false;
				Plugin.Error(e);
			}
		}
	}
	[HarmonyPatch(typeof(DisastersPhase), "RandomOccurrences")]
	internal static class OccurrencesPatch
	{
		[ThreadStatic]
		internal static bool Selecting;

		private static void Prefix(DisastersPhase __instance, out bool __state)
		{
			Catalog.Discover(__instance.disasters);
			__state = Selecting;
			Selecting = Plugin.Session.Active.Modified;
		}

		private static void Postfix(ref Il2CppReferenceArray<DisasterOccurrence> __result)
		{
			if (Plugin.Session.Active.Modified && __result != null)
			{
				__result = new Il2CppReferenceArray<DisasterOccurrence>(((IEnumerable<DisasterOccurrence>)__result).Where((DisasterOccurrence o) => o == null || Catalog.Starvation(o.Disaster) || Plugin.Session.Active.Enabled(Catalog.Id(o.Disaster))).ToArray());
			}
		}

		private static Exception? Finalizer(Exception? __exception, bool __state)
		{
			Selecting = __state;
			return __exception;
		}
	}
	[HarmonyPatch(typeof(Statistics), "get_Storage")]
	internal static class SelectionStoragePatch
	{
		private static void Postfix(ref ResourceStorage __result)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: 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_0041: Unknown result type (might be due to invalid IL or missing references)
			if (OccurrencesPatch.Selecting)
			{
				ResourceAmount stored = __result.stored;
				stored.disaster = SkullRules.Map(stored.disaster, Catalog.First, Catalog.Last, Plugin.Session.Active);
				__result = new ResourceStorage(stored, __result.capacity);
			}
		}
	}
	[HarmonyPatch(typeof(LobbySettings), "get_MultiMeteor")]
	internal static class MultiMeteorPatch
	{
		private static void Postfix(ref bool __result)
		{
			Rules active = Plugin.Session.Active;
			if (active.Modified)
			{
				if (active.Excess != ExcessSkullMode.Vanilla)
				{
					__result = false;
				}
				else if (active.ForceMultiMeteor)
				{
					__result = true;
				}
			}
		}
	}
	[HarmonyPatch(typeof(DisastersPhase), "Disaster")]
	internal static class SelectionPatch
	{
		private static void Prefix(ref int __0)
		{
			if (Plugin.Session.Active.Modified)
			{
				__0 = SkullRules.Map(__0, Catalog.First, Catalog.Last, Plugin.Session.Active);
			}
		}

		private static void Postfix(ref Disaster __result, ref int __1)
		{
			if (__result != null && !Catalog.Starvation(__result) && !Plugin.Session.Active.Enabled(Catalog.Id(__result)))
			{
				__result = null;
				__1 = 0;
			}
		}
	}
	[HarmonyPatch(typeof(DisasterGauge), "set_Value")]
	internal static class GaugePatch
	{
		private sealed record Tint(DisasterGauge Gauge, List<(Image Image, Color Color)> Colors);

		private static readonly Dictionary<IntPtr, Tint> tints = new Dictionary<IntPtr, Tint>();

		private static void Prefix(DisasterGauge __instance, ref int __0)
		{
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			IntPtr[] array = (from p in tints
				where (Object)(object)p.Value.Gauge == (Object)null
				select p.Key).ToArray();
			foreach (IntPtr key in array)
			{
				tints.Remove(key);
			}
			if (tints.Remove(((Il2CppObjectBase)__instance).Pointer, out Tint value))
			{
				foreach (var color in value.Colors)
				{
					if ((Object)(object)color.Image != (Object)null)
					{
						((Graphic)color.Image).color = color.Color;
					}
				}
			}
			if (Plugin.Session.Active.Modified)
			{
				__0 = SkullRules.Map(__0, Catalog.First, Catalog.Last, Plugin.Session.Active);
			}
		}

		private static void Postfix(DisasterGauge __instance)
		{
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00eb: 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_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			//IL_0125: Unknown result type (might be due to invalid IL or missing references)
			//IL_0132: Unknown result type (might be due to invalid IL or missing references)
			if (Plugin.Session.Active.Disabled.Length == 0 || __instance.icons == null || __instance.thresholds == null || (Object)(object)__instance.disasters == (Object)null)
			{
				return;
			}
			List<(Image, Color)> list = new List<(Image, Color)>();
			Disaster[] source = ((IEnumerable<Disaster>)__instance.disasters.Values).ToArray();
			int i;
			for (i = 0; i < Math.Min(__instance.icons.Count, __instance.thresholds.Count); i++)
			{
				Disaster val = ((IEnumerable<Disaster>)source).FirstOrDefault((Func<Disaster, bool>)((Disaster d) => !Catalog.Starvation(d) && d.threshold == __instance.thresholds[i]));
				if (val != null && !Plugin.Session.Active.Enabled(Catalog.Id(val)))
				{
					DisasterGaugeDisasterIcon val2 = __instance.icons[i];
					Color color = ((Graphic)val2.disasterImage).color;
					list.Add((val2.disasterImage, color));
					((Graphic)val2.disasterImage).color = new Color(color.r * 0.35f, color.g * 0.35f, color.b * 0.35f, color.a * 0.6f);
					val2.Selected = false;
					val2.Multiplier = 0;
				}
			}
			if (list.Count > 0)
			{
				tints[((Il2CppObjectBase)__instance).Pointer] = new Tint(__instance, list);
			}
		}
	}
	[HarmonyPatch(typeof(HarvestInterface), "UpdateResources")]
	internal static class SkullAccountingPatch
	{
		private static void Postfix(HarvestInterface __instance)
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_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)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			if (Plugin.Session.Active.AllowSkullReroll && !((Object)(object)((PhaseInterface<HarvestData, HarvestIslandState>)(object)__instance).Data == (Object)null) && !((Object)(object)__instance.disasterGauge == (Object)null))
			{
				HarvestData data = ((PhaseInterface<HarvestData, HarvestIslandState>)(object)__instance).Data;
				long value = (long)data.TotalYields.disaster + (long)((PhaseData<HarvestData, HarvestIslandState>)(object)data).Island.statistics.Storage.stored.disaster;
				__instance.disasterGauge.Value = (int)Math.Clamp(value, 0L, 2147483647L);
				ResourceAmount alreadyCollected = __instance.alreadyCollected;
				alreadyCollected.disaster = data.TotalYields.disaster;
				__instance.alreadyCollected = alreadyCollected;
			}
		}
	}
	[HarmonyPatch(typeof(HarvestInterface), "OnRollResults")]
	internal static class SkullCachePatch
	{
		private static void Postfix(HarvestInterface __instance, Il2CppReferenceArray<ValueTuple<Id, ResourceAmount>> __0)
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			if (!Plugin.Session.Active.AllowSkullReroll || __instance.harvestedSkulls == null || __instance.dices == null)
			{
				return;
			}
			BuildingDice val = default(BuildingDice);
			foreach (ValueTuple<Id, ResourceAmount> item in (Il2CppArrayBase<ValueTuple<Id, ResourceAmount>>)(object)__0)
			{
				if (item.Item2.disaster == 0 && __instance.dices.TryGetValue(item.Item1, ref val))
				{
					__instance.harvestedSkulls.Remove(val);
				}
			}
		}
	}
	[HarmonyPatch]
	internal static class MeteorPatch
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.Method(typeof(Meteorite).GetNestedTypes().Single((Type t) => t.Name == "_Execute_d__4"), "MoveNext", (Type[])null, (Type[])null);
		}

		private static void Postfix(_Execute_d__4 __instance, ref bool __result)
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0117: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_014c: Unknown result type (might be due to invalid IL or missing references)
			//IL_014e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0154: Invalid comparison between Unknown and I4
			//IL_0201: Unknown result type (might be due to invalid IL or missing references)
			//IL_0156: Unknown result type (might be due to invalid IL or missing references)
			//IL_0165: Unknown result type (might be due to invalid IL or missing references)
			//IL_017e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0190: Unknown result type (might be due to invalid IL or missing references)
			if (!__result || __instance.__1__state != 1 || Plugin.Session.Active.Targeting == MeteorTargeting.VanillaTargeting)
			{
				return;
			}
			try
			{
				IslandGrid grid = __instance._island_5__2.grid;
				int radius = __instance.__4__this.radius;
				int2 impact_5__ = __instance._impact_5__3;
				List<int2> castleCells = new List<int2>();
				Enumerator<Id, Building> enumerator = grid.Buildings.GetEnumerator();
				while (enumerator.MoveNext())
				{
					Building current = enumerator.Current;
					if (((Il2CppObjectBase)current.type).TryCast<BuildingTypeCastle>() == null)
					{
						continue;
					}
					IEnumerator<int2> enumerator2 = current.Positions().GetEnumerator();
					IEnumerator val = ((Il2CppObjectBase)enumerator2).Cast<IEnumerator>();
					try
					{
						while (val.MoveNext())
						{
							castleCells.Add(enumerator2.Current);
						}
					}
					finally
					{
						((Il2CppObjectBase)enumerator2).Cast<IDisposable>().Dispose();
					}
				}
				if (Plugin.Session.Active.Targeting == MeteorTargeting.ProtectCastle && Safe(impact_5__, radius))
				{
					return;
				}
				List<int2> list = new List<int2>();
				List<int2> list2 = new List<int2>();
				if (grid.size.x <= 0 || grid.size.y <= 0 || (long)grid.size.x * (long)grid.size.y > 1048576)
				{
					throw new InvalidOperationException("Invalid meteor grid size");
				}
				int2 val2 = default(int2);
				for (int i = 0; i < grid.size.y; i++)
				{
					for (int j = 0; j < grid.size.x; j++)
					{
						((int2)(ref val2))..ctor(j, i);
						if ((int)grid.Ground(val2) == 2 && Safe(val2, radius))
						{
							list.Add(val2);
							if (Plugin.Session.Active.Targeting == MeteorTargeting.SaferTargeting && Safe(val2, (long)radius + 2L))
							{
								list2.Add(val2);
							}
						}
					}
				}
				if (list2.Count > 0)
				{
					list = list2;
				}
				if (list.Count == 0)
				{
					__instance.__1__state = -1;
					__result = false;
				}
				else
				{
					uint seed = __instance.occurrence.Seed;
					__instance._impact_5__3 = list[(int)(seed % (uint)list.Count)];
				}
				bool Safe(int2 p, long r)
				{
					//IL_0010: Unknown result type (might be due to invalid IL or missing references)
					//IL_0015: Unknown result type (might be due to invalid IL or missing references)
					//IL_0016: 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_0025: 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)
					foreach (int2 item in castleCells)
					{
						long num = (long)p.x - (long)item.x;
						long num2 = (long)p.y - (long)item.y;
						if (num * num + num2 * num2 <= r * r)
						{
							return false;
						}
					}
					return true;
				}
			}
			catch (Exception error)
			{
				__instance.__1__state = -1;
				__result = false;
				Plugin.Session.Fail(error);
			}
		}
	}
	[HarmonyPatch(typeof(DisasterOccurrence), "Execute")]
	internal static class IntensityPatch
	{
		private static bool Prefix(DisasterOccurrence __instance, ref IEnumerable<int> __result)
		{
			Plugin.Session.ApplyIntensity();
			if (!Catalog.Starvation(__instance.Disaster) && !Plugin.Session.Active.Enabled(Catalog.Id(__instance.Disaster)))
			{
				__result = ((Il2CppObjectBase)new Il2CppStructArray<int>(0L)).Cast<IEnumerable<int>>();
				return false;
			}
			return true;
		}
	}
	[HarmonyPatch(typeof(HarvestInterface), "InputSelect")]
	internal static class SkullSelectPatch
	{
		[ThreadStatic]
		internal static bool Selecting;

		private static void Prefix(out bool __state)
		{
			__state = Selecting;
			Selecting = Plugin.Session.Active.AllowSkullReroll;
		}

		private static Exception? Finalizer(Exception? __exception, bool __state)
		{
			Selecting = __state;
			return __exception;
		}
	}
	[HarmonyPatch(typeof(HarvestData), "Yield")]
	internal static class SkullYieldPatch
	{
		private static void Postfix(ref ResourceAmount __result)
		{
			if (SkullSelectPatch.Selecting)
			{
				__result.disaster = 0;
			}
		}
	}
	[HarmonyPatch(typeof(HarvestInterface), "UpdateDices")]
	internal static class SkullHoverPatch
	{
		private static void Postfix(HarvestInterface __instance)
		{
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Invalid comparison between Unknown and I4
			if (!Plugin.Session.Active.AllowSkullReroll || __instance.sortedDices == null)
			{
				return;
			}
			Enumerator<BuildingDice> enumerator = __instance.sortedDices.GetEnumerator();
			while (enumerator.MoveNext())
			{
				BuildingDice current = enumerator.Current;
				if ((Object)(object)current.dice != (Object)null && (Object)(object)current.dice.hooverCursor != (Object)null)
				{
					((Behaviour)current.dice.hooverCursor).enabled = (int)current.state != 2;
				}
			}
		}
	}
	[HarmonyPatch(typeof(GameState), "UpdateInit")]
	internal static class StartGuardPatch
	{
		private static bool Prefix(GameState __instance)
		{
			Plugin.Session.Attach(__instance);
			if (((NetworkedBehaviour)__instance).IsServer && !__instance.GameStarted && (__instance.startGame || __instance.islandsSpawned))
			{
				if (!Plugin.Session.CanStart)
				{
					__instance.startGame = false;
					return false;
				}
				Plugin.Session.Freeze();
			}
			return true;
		}
	}
	[HarmonyPatch(typeof(GameState), "StartGame")]
	internal static class FreezePatch
	{
		private static bool Prefix(GameState __instance)
		{
			Plugin.Session.Attach(__instance);
			if (((NetworkedBehaviour)__instance).IsServer && !Plugin.Session.CanStart)
			{
				return false;
			}
			return Plugin.Session.Freeze();
		}
	}
	public enum Preset
	{
		Vanilla,
		Balanced,
		Peaceful,
		Chaos,
		Custom
	}
	public enum MeteorTargeting
	{
		VanillaTargeting,
		ProtectCastle,
		SaferTargeting
	}
	public enum ExcessSkullMode
	{
		Vanilla,
		SingleMeteor,
		CyclicDisasters,
		NoExcessMeteors
	}
	public sealed record Rules
	{
		public Preset Preset { get; init; }

		public MeteorTargeting Targeting { get; init; }

		public ExcessSkullMode Excess { get; init; }

		public bool AllowSkullReroll { get; init; }

		public bool ForceMultiMeteor { get; init; }

		public int DiseasePercent { get; init; } = 100;

		public int RevoltPercent { get; init; } = 100;

		public int MeteorLimit { get; init; } = 16;

		public string[] Disabled { get; init; } = Array.Empty<string>();

		public bool Modified
		{
			get
			{
				if (Targeting == MeteorTargeting.VanillaTargeting && Excess == ExcessSkullMode.Vanilla && !AllowSkullReroll && !ForceMultiMeteor && DiseasePercent == 100 && RevoltPercent == 100 && MeteorLimit == 16)
				{
					return Disabled.Length != 0;
				}
				return true;
			}
		}

		public bool Valid
		{
			get
			{
				if (Enum.IsDefined(Preset) && Enum.IsDefined(Targeting) && Enum.IsDefined(Excess))
				{
					int diseasePercent = DiseasePercent;
					if (diseasePercent >= 0 && diseasePercent <= 200)
					{
						diseasePercent = RevoltPercent;
						if (diseasePercent >= 0 && diseasePercent <= 200)
						{
							diseasePercent = MeteorLimit;
							if (diseasePercent >= 1 && diseasePercent <= 32 && Disabled != null && Disabled.Length <= 64 && Disabled.All((string x) => !string.IsNullOrWhiteSpace(x) && x.Length <= 100))
							{
								return Disabled.Distinct<string>(StringComparer.Ordinal).Count() == Disabled.Length;
							}
						}
					}
				}
				return false;
			}
		}

		public bool Enabled(string id)
		{
			return !Disabled.Contains<string>(id, StringComparer.Ordinal);
		}

		public static Rules For(Preset preset, IEnumerable<string> disasters)
		{
			return preset switch
			{
				Preset.Balanced => new Rules
				{
					Preset = preset,
					Targeting = MeteorTargeting.ProtectCastle,
					Excess = ExcessSkullMode.CyclicDisasters,
					MeteorLimit = 1
				}, 
				Preset.Peaceful => new Rules
				{
					Preset = preset,
					Targeting = MeteorTargeting.ProtectCastle,
					Excess = ExcessSkullMode.SingleMeteor,
					AllowSkullReroll = true,
					Disabled = disasters.OrderBy<string, string>((string x) => x, StringComparer.Ordinal).ToArray()
				}, 
				Preset.Chaos => new Rules
				{
					Preset = preset,
					ForceMultiMeteor = true,
					MeteorLimit = 32,
					DiseasePercent = 200,
					RevoltPercent = 200
				}, 
				_ => new Rules
				{
					Preset = preset
				}, 
			};
		}
	}
	public static class SkullRules
	{
		public static int Map(int skulls, int firstThreshold, int lastThreshold, Rules rules)
		{
			if (skulls <= 0 || lastThreshold < firstThreshold || lastThreshold <= 0)
			{
				return Math.Max(0, skulls);
			}
			switch (rules.Excess)
			{
			case ExcessSkullMode.CyclicDisasters:
				if (skulls > lastThreshold)
				{
					return (int)(((long)skulls - (long)firstThreshold) % ((long)lastThreshold - (long)firstThreshold + 1) + firstThreshold);
				}
				break;
			case ExcessSkullMode.SingleMeteor:
			case ExcessSkullMode.NoExcessMeteors:
				return Math.Min(skulls, lastThreshold);
			}
			if (rules.Modified)
			{
				return (int)Math.Min(skulls, (long)lastThreshold + (long)rules.MeteorLimit - 1);
			}
			return skulls;
		}

		public static int Scale(int original, int percent)
		{
			return (int)Math.Clamp(((long)original * (long)percent + 50) / 100, 0L, 100L);
		}
	}
	public sealed class RuleSession
	{
		public sealed class Packet
		{
			public string Kind { get; set; } = "";

			public string Version { get; set; } = "";

			public string Session { get; set; } = "";

			public long Revision { get; set; }

			public string Digest { get; set; } = "";

			public string Catalog { get; set; } = "";

			public string Rules { get; set; } = "";
		}

		internal const string Channel = "Mikhasan.DisasterControl.v1";

		private const int MaxPayload = 16000;

		private GameState? game;

		private Rules active = new Rules();

		private string session = "";

		private string digest = "";

		private string sent = "";

		private string clientError = "";

		private readonly Dictionary<ulong, string> acknowledgements = new Dictionary<ulong, string>();

		private readonly Dictionary<ulong, string> failures = new Dictionary<ulong, string>();

		private readonly HashSet<ulong> exposedPeers = new HashSet<ulong>();

		private float next;

		private bool received;

		private bool frozen;

		private bool networkEver;

		private bool soloMatch;

		private Rules? pending;

		private long revision;

		private float requestedHarvestEnter;

		private IntPtr requestedHarvest;

		private bool loggedIncoming;

		private int originalDisease = -1;

		private int originalRevolt = -1;

		private Disasters? intensityData;

		public Rules Active => active;

		public Rules Draft => pending ?? active;

		public bool Pending => pending != null;

		internal bool RejectLateJoin
		{
			get
			{
				if (frozen)
				{
					if (!active.Modified && !soloMatch)
					{
						return pending != null;
					}
					return true;
				}
				return false;
			}
		}

		public bool Frozen => frozen;

		public bool IsHost
		{
			get
			{
				if ((Object)(object)game != (Object)null)
				{
					return ((NetworkedBehaviour)game).IsServer;
				}
				return false;
			}
		}

		public bool InScope
		{
			get
			{
				if ((Object)(object)game != (Object)null)
				{
					Interface obj = game.@interface;
					if ((Object)(object)((obj != null) ? obj.startGame : null) != (Object)null && !game.@interface.startGame.isMatchmaking)
					{
						return !game.IsTraining;
					}
				}
				return false;
			}
		}

		public bool CanEdit
		{
			get
			{
				if ((Object)(object)game != (Object)null && IsHost && InScope && Plugin.Healthy)
				{
					if (game.GameStarted || game.startGame || frozen)
					{
						if (game.GameStarted && soloMatch && !networkEver)
						{
							return !HasRemotePeers();
						}
						return false;
					}
					return true;
				}
				return false;
			}
		}

		public string Status { get; private set; } = "Open a solo or custom lobby.";

		public string Digest => digest;

		public int AcknowledgedPeers => acknowledgements.Count<KeyValuePair<ulong, string>>((KeyValuePair<ulong, string> p) => p.Value == digest);

		public bool CanStart
		{
			get
			{
				RefreshLocal();
				if (!active.Modified && exposedPeers.Count == 0)
				{
					return true;
				}
				if (!Plugin.Healthy || !InScope)
				{
					Status = "Modified rules unavailable in this session.";
					return false;
				}
				NetworkingManager singleton = NetworkingManager.Singleton;
				if ((Object)(object)singleton == (Object)null || !singleton.IsServer)
				{
					return false;
				}
				if (singleton.PendingClients.Count > 0)
				{
					Status = "Waiting for pending connections.";
					return false;
				}
				Enumerator<NetworkedClient> enumerator = singleton.ConnectedClientsList.GetEnumerator();
				while (enumerator.MoveNext())
				{
					NetworkedClient current = enumerator.Current;
					if (current.ClientId != singleton.ServerClientId && current.ClientId != singleton.LocalClientId && (active.Modified || exposedPeers.Contains(current.ClientId)) && (!acknowledgements.TryGetValue(current.ClientId, out string value) || value != digest))
					{
						Status = (failures.TryGetValue(current.ClientId, out string value2) ? ("Cannot start: " + value2) : "Waiting for Disaster Control 0.9.1 on every player.");
						return false;
					}
				}
				Status = "All connected players confirmed these rules.";
				return true;
			}
		}

		internal void Fail(Exception error)
		{
			Plugin.Healthy = false;
			pending = null;
			Plugin.Error(error);
			Status = "Disaster Control stopped this match after an execution error. Restart the game.";
			if ((Object)(object)game != (Object)null)
			{
				game.Exit(Status);
			}
		}

		internal void Attach(GameState value)
		{
			if (!((Object)(object)game != (Object)null) || !(((Il2CppObjectBase)game).Pointer == ((Il2CppObjectBase)value).Pointer))
			{
				Reset();
				game = value;
				RefreshLocal();
			}
		}

		internal void Reset()
		{
			RestoreIntensity();
			game = null;
			active = new Rules();
			pending = null;
			frozen = (received = (soloMatch = (networkEver = false)));
			revision = 0L;
			session = Guid.NewGuid().ToString("N");
			digest = "";
			sent = "";
			clientError = "";
			acknowledgements.Clear();
			failures.Clear();
			exposedPeers.Clear();
			next = 0f;
		}

		internal void Detach(GameState value)
		{
			if ((Object)(object)game != (Object)null && ((Il2CppObjectBase)game).Pointer == ((Il2CppObjectBase)value).Pointer)
			{
				Reset();
			}
		}

		private void RefreshLocal()
		{
			if (frozen || (Object)(object)game == (Object)null)
			{
				return;
			}
			if (!InScope)
			{
				active = new Rules();
				return;
			}
			if (IsHost)
			{
				active = Plugin.Settings.Local;
			}
			else if (!received)
			{
				active = new Rules();
			}
			string json = Settings.Serialize(active);
			string text = Hash(json);
			if (text != digest)
			{
				digest = text;
				revision++;
				acknowledgements.Clear();
				failures.Clear();
				sent = "";
			}
		}

		private string Hash(string json)
		{
			return Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(Catalog.Signature + "\n" + json)));
		}

		public bool Set(Rules rules)
		{
			if (!CanEdit || !rules.Valid)
			{
				return false;
			}
			Plugin.Settings.Set(rules);
			if (frozen)
			{
				pending = rules;
				ITurnPhase turnPhase = game.TurnPhase;
				HarvestPhase val = ((turnPhase != null) ? ((Il2CppObjectBase)turnPhase).TryCast<HarvestPhase>() : null);
				requestedHarvest = ((val != null) ? ((Il2CppObjectBase)val).Pointer : IntPtr.Zero);
				requestedHarvestEnter = ((TurnPhase<HarvestData, HarvestIslandState>)(object)val)?.enterTime ?? 0f;
				Status = "Changes apply at the next harvest phase.";
			}
			else
			{
				RefreshLocal();
			}
			sent = "";
			next = 0f;
			return true;
		}

		internal void PhaseBoundary()
		{
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)game == (Object)null) && !(pending == null) && soloMatch && !networkEver && !HasRemotePeers())
			{
				ITurnPhase turnPhase = game.TurnPhase;
				HarvestPhase val = ((turnPhase != null) ? ((Il2CppObjectBase)turnPhase).TryCast<HarvestPhase>() : null);
				if (!((Object)(object)val == (Object)null) && (int)((TurnPhase<HarvestData, HarvestIslandState>)(object)val).State == 0 && (!(((Il2CppObjectBase)val).Pointer == requestedHarvest) || ((TurnPhase<HarvestData, HarvestIslandState>)(object)val).enterTime != requestedHarvestEnter))
				{
					RestoreIntensity();
					active = pending;
					pending = null;
					digest = Hash(Settings.Serialize(active));
					revision++;
					ApplyIntensity();
					Status = "Solo rules applied at the harvest boundary.";
				}
			}
		}

		public bool Freeze()
		{
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Expected O, but got Unknown
			if (frozen)
			{
				return true;
			}
			RefreshLocal();
			if (IsHost && !CanStart)
			{
				return false;
			}
			frozen = true;
			soloMatch = IsHost && InScope && game.@interface.startGame.isSolo && !networkEver && !HasRemotePeers();
			ApplyIntensity();
			ManualLogSource log = ((BasePlugin)Plugin.Instance).Log;
			bool flag = default(bool);
			BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(37, 3, ref flag);
			if (flag)
			{
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Match rules frozen: ");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Preset>(active.Preset);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; ");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(digest);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; acknowledged=");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(AcknowledgedPeers);
			}
			log.LogInfo(val);
			return true;
		}

		internal void ApplyIntensity()
		{
			if ((Object)(object)Catalog.Data == (Object)null)
			{
				return;
			}
			if (active.DiseasePercent == 100 && active.RevoltPercent == 100)
			{
				RestoreIntensity();
				return;
			}
			if ((Object)(object)intensityData != (Object)null && ((Il2CppObjectBase)intensityData).Pointer != ((Il2CppObjectBase)Catalog.Data).Pointer)
			{
				RestoreIntensity();
			}
			intensityData = Catalog.Data;
			if (originalDisease < 0)
			{
				originalDisease = Catalog.Data.disease.percentageAffected;
			}
			if (originalRevolt < 0)
			{
				originalRevolt = Catalog.Data.revolt.percentageLost;
			}
			Catalog.Data.disease.percentageAffected = ((active.DiseasePercent == 100) ? originalDisease : SkullRules.Scale(originalDisease, active.DiseasePercent));
			Catalog.Data.revolt.percentageLost = ((active.RevoltPercent == 100) ? originalRevolt : SkullRules.Scale(originalRevolt, active.RevoltPercent));
		}

		private void RestoreIntensity()
		{
			if ((Object)(object)intensityData != (Object)null)
			{
				if (originalDisease >= 0)
				{
					intensityData.disease.percentageAffected = originalDisease;
				}
				if (originalRevolt >= 0)
				{
					intensityData.revolt.percentageLost = originalRevolt;
				}
			}
			intensityData = null;
			originalDisease = (originalRevolt = -1);
		}

		public void Tick(float now)
		{
			try
			{
				Player localPlayer = Player.LocalPlayer;
				GameState val = ((localPlayer != null) ? localPlayer.GameState : null);
				if ((Object)(object)val == (Object)null)
				{
					if ((Object)(object)game != (Object)null)
					{
						Reset();
					}
					Status = "Open a solo or custom lobby.";
					return;
				}
				Attach(val);
				if (HasRemotePeers())
				{
					networkEver = true;
				}
				PhaseBoundary();
				if (now < next)
				{
					return;
				}
				next = now + 1f;
				RefreshLocal();
				if (!InScope)
				{
					Status = "Original rules: matchmaking / training.";
				}
				else if (IsHost)
				{
					if (!frozen)
					{
						_ = CanStart;
						if (!active.Modified)
						{
							Status = "Vanilla — original game rules.";
						}
					}
					else
					{
						Status = ((!CanEdit) ? "Network rules are locked for this match." : (Pending ? "Changes apply at the next harvest phase." : "Solo: changes apply at the next harvest phase."));
					}
					NetworkingManager singleton = NetworkingManager.Singleton;
					if ((Object)(object)singleton == (Object)null)
					{
						return;
					}
					HashSet<ulong> peers = ((IEnumerable<NetworkedClient>)singleton.ConnectedClientsList.ToArray()).Select((NetworkedClient p) => p.ClientId).ToHashSet();
					ulong[] array = acknowledgements.Keys.Where((ulong id) => !peers.Contains(id)).ToArray();
					foreach (ulong key in array)
					{
						acknowledgements.Remove(key);
					}
					array = failures.Keys.Where((ulong id) => !peers.Contains(id)).ToArray();
					foreach (ulong key2 in array)
					{
						failures.Remove(key2);
					}
					exposedPeers.RemoveWhere((ulong id) => !peers.Contains(id));
					Packet packet = new Packet
					{
						Kind = "rules",
						Version = "0.9.1",
						Session = session,
						Revision = revision,
						Digest = digest,
						Catalog = Catalog.Signature,
						Rules = Settings.Serialize(active)
					};
					foreach (ulong item in peers)
					{
						if (item != singleton.ServerClientId && item != singleton.LocalClientId && (sent != digest || !acknowledgements.TryGetValue(item, out string value) || value != digest))
						{
							if (active.Modified)
							{
								exposedPeers.Add(item);
							}
							Send(item, packet);
						}
					}
					sent = digest;
				}
				else if (!received)
				{
					Status = ((clientError.Length > 0) ? clientError : "Vanilla until host rules are received.");
				}
				else
				{
					Status = (frozen ? "Host rules — locked for this match." : "Host rules — read only; confirmed.");
				}
			}
			catch (Exception e)
			{
				Plugin.Error(e);
				Status = "Rules synchronization error; modified start is blocked.";
				acknowledgements.Clear();
			}
		}

		internal void Receive(ulong sender, Stream stream)
		{
			try
			{
				if (!loggedIncoming)
				{
					((BasePlugin)Plugin.Instance).Log.LogInfo((object)"Received first disaster rules protocol message.");
					loggedIncoming = true;
				}
				List<byte> list = new List<byte>();
				int num;
				while (list.Count <= 16000 && (num = stream.ReadByte()) >= 0)
				{
					list.Add((byte)num);
				}
				if (list.Count == 0 || list.Count > 16000)
				{
					return;
				}
				Packet packet = JsonSerializer.Deserialize<Packet>(Encoding.UTF8.GetString(list.ToArray()));
				if (packet == null || (Object)(object)game == (Object)null || !InScope)
				{
					return;
				}
				NetworkingManager singleton = NetworkingManager.Singleton;
				if ((Object)(object)singleton == (Object)null)
				{
					return;
				}
				if (IsHost)
				{
					if (!singleton.ConnectedClients.ContainsKey(sender))
					{
						return;
					}
					if (packet.Kind == "ack" && packet.Version == "0.9.1" && packet.Session == session && packet.Revision == revision && packet.Digest == digest)
					{
						acknowledgements[sender] = digest;
						failures.Remove(sender);
						if (!active.Modified)
						{
							exposedPeers.Remove(sender);
						}
					}
					else if (packet.Kind == "error" || (packet.Kind == "ack" && packet.Version != "0.9.1"))
					{
						failures[sender] = "incompatible version or game data on player " + sender;
						acknowledgements.Remove(sender);
					}
					return;
				}
				ulong serverClientId = singleton.NetworkConfig.NetworkTransport.ServerClientId;
				bool flag = sender == singleton.ServerClientId || sender == serverClientId;
				UnetTransport val = ((Il2CppObjectBase)singleton.NetworkConfig.NetworkTransport).TryCast<UnetTransport>();
				if (!flag && (Object)(object)val != (Object)null)
				{
					flag = sender == val.GetMLAPIClientId((byte)val.serverHostId, (ushort)val.serverConnectionId, false);
				}
				if (!flag || packet.Kind != "rules" || frozen || (received && (packet.Session != session || packet.Revision < revision)))
				{
					return;
				}
				if (!Plugin.Healthy || packet.Version != "0.9.1" || packet.Catalog != Catalog.Signature)
				{
					active = new Rules();
					received = false;
					clientError = "Incompatible Disaster Control version, game binary or disaster data. Modified start is blocked.";
					Send(singleton.ServerClientId, new Packet
					{
						Kind = "error",
						Version = "0.9.1"
					});
					return;
				}
				Rules rules = Settings.Parse(packet.Rules);
				if (rules.Disabled.Any((string id) => !Catalog.Ids.Contains<string>(id, StringComparer.Ordinal)))
				{
					throw new ArgumentException("Unknown disaster");
				}
				string text = Hash(Settings.Serialize(rules));
				if (!(text != packet.Digest) && packet.Session.Length == 32 && packet.Revision > 0)
				{
					active = rules;
					digest = text;
					session = packet.Session;
					revision = packet.Revision;
					received = true;
					clientError = "";
					Send(singleton.ServerClientId, new Packet
					{
						Kind = "ack",
						Version = "0.9.1",
						Session = session,
						Revision = revision,
						Digest = text
					});
				}
			}
			catch (Exception e)
			{
				Plugin.Error(e);
			}
		}

		private bool HasRemotePeers()
		{
			NetworkingManager net = NetworkingManager.Singleton;
			if ((Object)(object)net != (Object)null)
			{
				return ((IEnumerable<NetworkedClient>)net.ConnectedClientsList.ToArray()).Any((NetworkedClient p) => p.ClientId != net.ServerClientId && p.ClientId != net.LocalClientId);
			}
			return false;
		}

		private static void Send(ulong peer, Packet packet)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Expected O, but got Unknown
			byte[] bytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(packet));
			MemoryStream val = new MemoryStream();
			try
			{
				((Stream)val).Write(new Il2CppStructArray<byte>(bytes), 0, bytes.Length);
				((Stream)val).Position = 0L;
				CustomMessagingManager.SendNamedMessage("Mikhasan.DisasterControl.v1", peer, (Stream)(object)val, "MLAPI_INTERNAL", (SecuritySendFlags)0);
			}
			finally
			{
				((Stream)val).Dispose();
			}
		}
	}
	[HarmonyPatch(typeof(GameState), "FullReset")]
	internal static class ResetRulesPatch
	{
		private static void Prefix(GameState __instance)
		{
			Plugin.Session.Detach(__instance);
		}
	}
	[HarmonyPatch(typeof(GameState), "Exit")]
	internal static class ExitRulesPatch
	{
		private static void Postfix(GameState __instance)
		{
			Plugin.Session.Detach(__instance);
		}
	}
	[HarmonyPatch(typeof(GameState), "ChangeTurnPhase")]
	internal static class SoloBoundaryPatch
	{
		private static void Postfix()
		{
			Plugin.Session.PhaseBoundary();
		}
	}
	[HarmonyPatch(typeof(CustomMessagingManager), "InvokeNamedMessage")]
	internal static class RuleMessagePatch
	{
		private static bool Prefix(ulong __0, ulong __1, Stream __2)
		{
			if (__0 != NetworkedBehaviour.HashMethodName("Mikhasan.DisasterControl.v1"))
			{
				return true;
			}
			Plugin.Session.Receive(__1, __2);
			return false;
		}
	}
	[HarmonyPatch(typeof(NetworkingManager), "InvokeConnectionApproval")]
	internal static class LateJoinGuard
	{
		private static bool Prefix(ConnectionApprovedDelegate __2)
		{
			if (!Plugin.Session.IsHost || !Plugin.Session.RejectLateJoin)
			{
				return true;
			}
			__2.Invoke(false, new Nullable<ulong>(), false, new Nullable<Vector3>(), new Nullable<Quaternion>());
			return false;
		}
	}
	internal sealed class Settings
	{
		private readonly ConfigFile file;

		private readonly ConfigEntry<Preset> preset;

		private readonly ConfigEntry<MeteorTargeting> targeting;

		private readonly ConfigEntry<ExcessSkullMode> excess;

		private readonly ConfigEntry<bool> reroll;

		private readonly ConfigEntry<bool> multi;

		private readonly ConfigEntry<int> disease;

		private readonly ConfigEntry<int> revolt;

		private readonly ConfigEntry<int> limit;

		private readonly Dictionary<string, ConfigEntry<bool>> events = new Dictionary<string, ConfigEntry<bool>>(StringComparer.Ordinal);

		private bool writing;

		public Rules Local { get; private set; } = new Rules();

		public ConfigEntry<string> Language { get; }

		public event Action? Changed;

		public Settings(ConfigFile file)
		{
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Expected O, but got Unknown
			//IL_0109: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Expected O, but got Unknown
			//IL_0138: Unknown result type (might be due to invalid IL or missing references)
			//IL_0142: Expected O, but got Unknown
			this.file = file;
			preset = file.Bind<Preset>("Rules", "Preset", Preset.Vanilla, "Vanilla on first installation. Manual edits select Custom. Peaceful disables all skull disasters; starvation stays original.");
			targeting = file.Bind<MeteorTargeting>("Rules", "MeteorTargeting", MeteorTargeting.VanillaTargeting, "Choose original targeting, full castle footprint protection, or protection with a wider margin.");
			excess = file.Bind<ExcessSkullMode>("Rules", "ExcessSkullMode", ExcessSkullMode.Vanilla, "SingleMeteor and NoExcessMeteors retain the first meteor; CyclicDisasters repeats the real threshold range.");
			reroll = file.Bind<bool>("Rules", "AllowSkullReroll", false, "Unlock skull dice selection. The native reroll action, cost and face probabilities are preserved.");
			multi = file.Bind<bool>("Rules", "ForceMultiMeteor", false, "Force native Multi-Meteor when excess mode is Vanilla. Retained when Chaos becomes Custom.");
			disease = file.Bind<int>("Rules", "DiseaseIntensityPercent", 100, new ConfigDescription("Scale the original Disease percentage. Applies to Disease, not the separate Plague event.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 200), Array.Empty<object>()));
			revolt = file.Bind<int>("Rules", "RevoltIntensityPercent", 100, new ConfigDescription("Scale the original Revolt loss, capped at 100% actual loss.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 200), Array.Empty<object>()));
			limit = file.Bind<int>("Rules", "MeteorLimit", 16, new ConfigDescription("Safety limit for modified Vanilla/Chaos rules; ignored by completely Vanilla rules.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 32), Array.Empty<object>()));
			Language = file.Bind<string>("Interface", "Language", "en", "Panel language: en or ru. Does not alter game translations.");
			file.SettingChanged += delegate(object? _, SettingChangedEventArgs e)
			{
				if (!writing && (object)e.ChangedSetting != Language)
				{
					Read((object)e.ChangedSetting == preset);
				}
			};
		}

		public void Discover(IEnumerable<string> ids)
		{
			writing = true;
			try
			{
				foreach (string id in ids)
				{
					if (!events.ContainsKey(id))
					{
						events.Add(id, file.Bind<bool>("Disasters", id, true, "Enable " + id + ". Starvation is independent and is never disabled."));
					}
				}
			}
			finally
			{
				writing = false;
			}
			Read(usePreset: true);
		}

		private void Read(bool usePreset)
		{
			if (!Enum.IsDefined(preset.Value))
			{
				Set(new Rules());
				return;
			}
			if (usePreset && preset.Value != Preset.Custom)
			{
				Set(Rules.For(preset.Value, events.Keys));
				return;
			}
			Set(new Rules
			{
				Preset = Preset.Custom,
				Targeting = (Enum.IsDefined(targeting.Value) ? targeting.Value : MeteorTargeting.VanillaTargeting),
				Excess = (Enum.IsDefined(excess.Value) ? excess.Value : ExcessSkullMode.Vanilla),
				AllowSkullReroll = reroll.Value,
				ForceMultiMeteor = multi.Value,
				DiseasePercent = Math.Clamp(disease.Value, 0, 200),
				RevoltPercent = Math.Clamp(revolt.Value, 0, 200),
				MeteorLimit = Math.Clamp(limit.Value, 1, 32),
				Disabled = (from e in events
					where !e.Value.Value
					select e.Key).OrderBy<string, string>((string x) => x, StringComparer.Ordinal).ToArray()
			});
		}

		public void Set(Rules rules)
		{
			if (!rules.Valid)
			{
				throw new ArgumentException("Invalid disaster rules");
			}
			writing = true;
			bool saveOnConfigSet = file.SaveOnConfigSet;
			file.SaveOnConfigSet = false;
			try
			{
				Local = rules;
				preset.Value = rules.Preset;
				targeting.Value = rules.Targeting;
				excess.Value = rules.Excess;
				reroll.Value = rules.AllowSkullReroll;
				multi.Value = rules.ForceMultiMeteor;
				disease.Value = rules.DiseasePercent;
				revolt.Value = rules.RevoltPercent;
				limit.Value = rules.MeteorLimit;
				foreach (KeyValuePair<string, ConfigEntry<bool>> @event in events)
				{
					@event.Value.Value = rules.Enabled(@event.Key);
				}
				file.Save();
			}
			finally
			{
				file.SaveOnConfigSet = saveOnConfigSet;
				writing = false;
			}
			this.Changed?.Invoke();
		}

		public static string Serialize(Rules r)
		{
			return JsonSerializer.Serialize(r);
		}

		public static Rules Parse(string json)
		{
			if (json.Length > 12000)
			{
				throw new ArgumentException("Rules message too large");
			}
			Rules? obj = JsonSerializer.Deserialize<Rules>(json) ?? throw new ArgumentException("Missing rules");
			if (!obj.Valid)
			{
				throw new ArgumentException("Invalid rules");
			}
			return obj;
		}
	}
}