Decompiled source of Void Tunnel Decay v0.9.2

VoidTunnelDecay.dll

Decompiled a month ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using CG.Client.UserData;
using CG.Rendering;
using CG.Ship.Object;
using CG.Space;
using Client.Player.Interactions;
using Gameplay.Mutators;
using Gameplay.Power;
using Gameplay.Quests;
using Gameplay.Ship.VoidJump;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Photon.Pun;
using Photon.Realtime;
using ResourceAssets;
using UI.MainHUD;
using UnityEngine;
using VFX;
using VFX.Lights;
using VoidManager;
using VoidManager.CustomGUI;
using VoidManager.MPModChecks;
using VoidManager.ModMessages;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("Void Tunnel Decay")]
[assembly: AssemblyDescription("Mutator: void-tunnel stability decays on a clock, shorter for high-power ships and risky jumps. The tunnel flickers as the odds climb. Stay long enough and interdiction becomes certain.")]
[assembly: AssemblyProduct("VoidTunnelDecay")]
[assembly: AssemblyCompany("Jack")]
[assembly: AssemblyFileVersion("0.9.2.0")]
[assembly: ComVisible(false)]
[assembly: AssemblyMetadata("ModCategories", "Mods, Disables Progression")]
[assembly: AssemblyMetadata("MPType", "Client")]
[assembly: AssemblyMetadata("ProgressionFlag", "enabled")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("0.9.2.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
internal static class MenuKit
{
	private static readonly Dictionary<string, bool> foldouts = new Dictionary<string, bool>();

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

	public static void Header(string text)
	{
		GUILayout.Space(10f);
		GUILayout.Label("<b>" + text + "</b>", Array.Empty<GUILayoutOption>());
	}

	public static void Rule()
	{
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		//IL_0028: Unknown result type (might be due to invalid IL or missing references)
		//IL_002d: 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_0056: Unknown result type (might be due to invalid IL or missing references)
		GUILayout.Space(6f);
		Rect rect = GUILayoutUtility.GetRect(1f, 1f, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) });
		Color color = GUI.color;
		GUI.color = new Color(1f, 1f, 1f, 0.25f);
		GUI.DrawTexture(rect, (Texture)(object)Texture2D.whiteTexture);
		GUI.color = color;
		GUILayout.Space(6f);
	}

	public static bool Foldout(string id, string label)
	{
		foldouts.TryGetValue(id, out var value);
		if (GUILayout.Button((value ? "[-] " : "[+] ") + label, Array.Empty<GUILayoutOption>()))
		{
			value = !value;
			foldouts[id] = value;
		}
		return value;
	}

	public static void DevZone(string id, Action body)
	{
		if (!Configs.IsDebugMode)
		{
			return;
		}
		GUILayout.Space(12f);
		Rule();
		if (!Foldout(id + ".dev", "Developer tools"))
		{
			return;
		}
		GUILayout.BeginVertical(GUI.skin.box, Array.Empty<GUILayoutOption>());
		try
		{
			body();
		}
		catch (Exception ex)
		{
			GUILayout.Label("Dev section failed: " + ex.Message + " (see log)", Array.Empty<GUILayoutOption>());
			if (loggedFailures.Add(id))
			{
				Debug.LogError((object)("[MenuKit] dev zone '" + id + "' threw: " + ex));
			}
		}
		finally
		{
			GUILayout.EndVertical();
		}
	}
}
internal static class ModFlagPolicy
{
	internal const bool FlagsSession = true;

	internal const MultiplayerType MPType = (MultiplayerType)20;

	internal const SessionModificationEffect NetworkSpawnedEffect = (SessionModificationEffect)2;

	internal const SessionModificationEffect RequiredEffect = (SessionModificationEffect)94;

	internal const string BuildTag = "enabled";
}
internal static class MutatorSlots
{
	private const string TargetType = "Gameplay.Hub.HubMutatorManager";

	private const string TargetMethod = "GetMaxMutatorsCount";

	internal static ConfigEntry<bool> ExtraSlot;

	internal static bool Installed;

	internal static string Status = "not installed";

	private static ManualLogSource _log;

	private static string _tag;

	internal static int Bonus
	{
		get
		{
			try
			{
				return (ExtraSlot != null && ExtraSlot.Value) ? 1 : 0;
			}
			catch
			{
				return 0;
			}
		}
	}

	internal static void Install(ConfigFile config, Harmony harmony, ManualLogSource log, string tag)
	{
		//IL_0096: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a3: Expected O, but got Unknown
		_log = log;
		_tag = tag;
		try
		{
			ExtraSlot = config.Bind<bool>("Mutators", "ExtraMutatorSlot", true, "Raise the Mutators menu's selection limit by one, so this mod's mutator does not cost one of the vanilla slots. Every installed mod with this setting on adds its own +1. The limit is checked by the host, so the host's setting is the one that counts in a multiplayer room. Takes effect immediately.");
			if (!Installed)
			{
				Type type = AccessTools.TypeByName("Gameplay.Hub.HubMutatorManager");
				MethodInfo methodInfo = ((type == null) ? null : AccessTools.Method(type, "GetMaxMutatorsCount", (Type[])null, (Type[])null));
				if (methodInfo == null)
				{
					Status = "FAILED - Gameplay.Hub.HubMutatorManager.GetMaxMutatorsCount not found (game update?)";
					log.LogError((object)(tag + ": extra mutator slot not installed: " + Status));
				}
				else
				{
					harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(MutatorSlots), "Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					Installed = true;
					Status = "installed";
				}
			}
		}
		catch (Exception ex)
		{
			Status = "FAILED - " + ex.GetType().Name;
			log.LogError((object)(tag + ": extra mutator slot not installed, vanilla limit stays: " + ex));
		}
	}

	private static void Postfix(ref int __result)
	{
		try
		{
			__result += Bonus;
		}
		catch (Exception ex)
		{
			if (_log != null)
			{
				_log.LogWarning((object)(_tag + ": extra mutator slot postfix failed: " + ex));
			}
		}
	}

	internal static void Draw()
	{
		if (ExtraSlot == null)
		{
			GUILayout.Label("Extra slot   : " + Status, Array.Empty<GUILayoutOption>());
			return;
		}
		bool flag = GUILayout.Toggle(ExtraSlot.Value, " Extra mutator slot (+1 to the selection limit; host's setting counts)", Array.Empty<GUILayoutOption>());
		if (flag != ExtraSlot.Value)
		{
			ExtraSlot.Value = flag;
		}
		if (!Installed)
		{
			GUILayout.Label("Extra slot   : " + Status, Array.Empty<GUILayoutOption>());
		}
	}
}
internal static class StatContrib
{
	internal enum Kind
	{
		Additive,
		Multiplicative,
		Override
	}

	private const string SlotKey = "jack.voidcrew.statcontrib.v1";

	internal static ManualLogSource Log;

	private static object[] Root()
	{
		object[] array = AppDomain.CurrentDomain.GetData("jack.voidcrew.statcontrib.v1") as object[];
		if (array == null)
		{
			array = new object[2]
			{
				new object(),
				new Dictionary<object, object>()
			};
			AppDomain.CurrentDomain.SetData("jack.voidcrew.statcontrib.v1", array);
		}
		return array;
	}

	internal static bool Set(object target, string field, string ownerId, Func<float> getter, Action<float> setter, Kind kind, float value, Func<bool> alive = null)
	{
		if (target == null || string.IsNullOrEmpty(field) || string.IsNullOrEmpty(ownerId) || getter == null || setter == null)
		{
			Error("StatContrib.Set called with a null argument (field '" + field + "', owner '" + ownerId + "') - ignored.");
			return false;
		}
		if (float.IsNaN(value) || float.IsInfinity(value))
		{
			Error("StatContrib.Set: owner '" + ownerId + "' offered a non-finite value for '" + field + "' - ignored.");
			return false;
		}
		try
		{
			object[] array = Root();
			lock (array[0])
			{
				Dictionary<object, object> dictionary = (Dictionary<object, object>)array[1];
				object value2;
				Dictionary<string, object> dictionary2 = (Dictionary<string, object>)(dictionary.TryGetValue(target, out value2) ? ((Dictionary<string, object>)value2) : (dictionary[target] = new Dictionary<string, object>()));
				object[] array2;
				if (dictionary2.TryGetValue(field, out value2))
				{
					array2 = (object[])value2;
				}
				else
				{
					array2 = (object[])(dictionary2[field] = new object[4]
					{
						getter(),
						getter,
						setter,
						new List<object[]>()
					});
					Info("captured base " + ((float)array2[0]).ToString("0.####") + " for " + Describe(target, field));
				}
				List<object[]> list = (List<object[]>)array2[3];
				object[] array3 = null;
				for (int i = 0; i < list.Count; i++)
				{
					if ((string)list[i][0] == ownerId)
					{
						array3 = list[i];
						break;
					}
				}
				if (array3 == null)
				{
					array3 = new object[4] { ownerId, null, null, null };
					list.Add(array3);
				}
				array3[1] = alive;
				array3[2] = (int)kind;
				array3[3] = value;
				Recompute(dictionary2, field, array2);
				SweepDeadOwners(dictionary, target, field);
			}
			return true;
		}
		catch (Exception ex)
		{
			Error("StatContrib.Set('" + field + "', '" + ownerId + "') threw: " + ex);
			return false;
		}
	}

	internal static void Remove(object target, string field, string ownerId)
	{
		if (target == null || string.IsNullOrEmpty(field) || string.IsNullOrEmpty(ownerId))
		{
			return;
		}
		try
		{
			object[] array = Root();
			lock (array[0])
			{
				Dictionary<object, object> dictionary = (Dictionary<object, object>)array[1];
				if (!dictionary.TryGetValue(target, out var value))
				{
					return;
				}
				Dictionary<string, object> dictionary2 = (Dictionary<string, object>)value;
				if (!dictionary2.TryGetValue(field, out value))
				{
					return;
				}
				object[] array2 = (object[])value;
				List<object[]> list = (List<object[]>)array2[3];
				bool flag = false;
				for (int num = list.Count - 1; num >= 0; num--)
				{
					if ((string)list[num][0] == ownerId)
					{
						list.RemoveAt(num);
						flag = true;
					}
				}
				if (flag)
				{
					Recompute(dictionary2, field, array2);
					if (!dictionary2.ContainsKey(field) && dictionary2.Count == 0)
					{
						dictionary.Remove(target);
					}
					SweepDeadOwners(dictionary, target, field);
				}
			}
		}
		catch (Exception ex)
		{
			Error("StatContrib.Remove('" + field + "', '" + ownerId + "') threw: " + ex);
		}
	}

	internal static string DescribeField(object target, string field)
	{
		try
		{
			object[] array = Root();
			lock (array[0])
			{
				Dictionary<object, object> dictionary = (Dictionary<object, object>)array[1];
				if (target == null || !dictionary.TryGetValue(target, out var value))
				{
					return null;
				}
				if (!((Dictionary<string, object>)value).TryGetValue(field, out value))
				{
					return null;
				}
				object[] array2 = (object[])value;
				List<object[]> list = (List<object[]>)array2[3];
				string text = field + ": base " + ((float)array2[0]).ToString("0.####");
				for (int i = 0; i < list.Count; i++)
				{
					text = text + ", " + (string)list[i][0] + " " + ((Kind)(int)list[i][2]/*cast due to .constrained prefix*/).ToString() + " " + ((float)list[i][3]).ToString("0.####");
				}
				return text;
			}
		}
		catch
		{
			return null;
		}
	}

	private static void Recompute(Dictionary<string, object> byField, string field, object[] entry)
	{
		List<object[]> list = (List<object[]>)entry[3];
		for (int num = list.Count - 1; num >= 0; num--)
		{
			Func<bool> func = list[num][1] as Func<bool>;
			bool flag = true;
			if (func != null)
			{
				try
				{
					flag = func();
				}
				catch
				{
					flag = false;
				}
			}
			if (!flag)
			{
				Info("dropping contribution '" + (string)list[num][0] + "' to '" + field + "' - its owner is gone.");
				list.RemoveAt(num);
			}
		}
		float num2 = (float)entry[0];
		Action<float> setter = (Action<float>)entry[2];
		if (list.Count == 0)
		{
			byField.Remove(field);
			TryWrite(setter, num2, field, "base restore");
			return;
		}
		float num3 = num2;
		float num4 = 1f;
		object[] array = null;
		for (int i = 0; i < list.Count; i++)
		{
			float num5 = (float)list[i][3];
			switch ((Kind)(int)list[i][2])
			{
			case Kind.Additive:
				num3 += num5;
				break;
			case Kind.Multiplicative:
				num4 *= num5;
				break;
			case Kind.Override:
				array = list[i];
				break;
			}
		}
		float num6;
		if (array != null)
		{
			num6 = (float)array[3];
			if (list.Count > 1)
			{
				Info("override '" + (string)array[0] + "' wins '" + field + "' over " + (list.Count - 1) + " other contribution(s).");
			}
		}
		else
		{
			num6 = num3 * num4;
		}
		if (float.IsNaN(num6) || float.IsInfinity(num6))
		{
			Error("composed value for '" + field + "' is non-finite; writing the base back instead. " + DescribeEntry(field, entry));
			num6 = num2;
		}
		TryWrite(setter, num6, field, "recompute");
	}

	private static void SweepDeadOwners(Dictionary<object, object> entries, object exceptTarget, string exceptField)
	{
		List<object> list = null;
		foreach (KeyValuePair<object, object> entry in entries)
		{
			Dictionary<string, object> dictionary = (Dictionary<string, object>)entry.Value;
			List<string> list2 = null;
			foreach (KeyValuePair<string, object> item in dictionary)
			{
				if (entry.Key == exceptTarget && item.Key == exceptField)
				{
					continue;
				}
				List<object[]> list3 = (List<object[]>)((object[])item.Value)[3];
				for (int i = 0; i < list3.Count; i++)
				{
					Func<bool> func = list3[i][1] as Func<bool>;
					bool flag = true;
					if (func != null)
					{
						try
						{
							flag = func();
						}
						catch
						{
							flag = false;
						}
					}
					if (!flag)
					{
						List<string> obj2 = list2 ?? new List<string>();
						list2 = obj2;
						obj2.Add(item.Key);
						break;
					}
				}
			}
			if (list2 != null)
			{
				for (int j = 0; j < list2.Count; j++)
				{
					Recompute(dictionary, list2[j], (object[])dictionary[list2[j]]);
				}
			}
			if (dictionary.Count == 0)
			{
				List<object> obj3 = list ?? new List<object>();
				list = obj3;
				obj3.Add(entry.Key);
			}
		}
		if (list != null)
		{
			for (int k = 0; k < list.Count; k++)
			{
				entries.Remove(list[k]);
			}
		}
	}

	private static void TryWrite(Action<float> setter, float value, string field, string why)
	{
		try
		{
			setter(value);
		}
		catch (Exception ex)
		{
			Info("write to '" + field + "' (" + why + ") failed: " + ex.Message);
		}
	}

	private static string Describe(object target, string field)
	{
		string text;
		try
		{
			text = target.GetType().Name;
		}
		catch
		{
			text = "<target>";
		}
		return text + "." + field;
	}

	private static string DescribeEntry(string field, object[] entry)
	{
		List<object[]> list = (List<object[]>)entry[3];
		string text = "base " + ((float)entry[0]).ToString("0.####") + ";";
		for (int i = 0; i < list.Count; i++)
		{
			text = text + " " + (string)list[i][0] + "=" + ((Kind)(int)list[i][2]/*cast due to .constrained prefix*/).ToString() + ":" + ((float)list[i][3]).ToString("0.####");
		}
		return text;
	}

	private static void Info(string m)
	{
		if (Log != null)
		{
			Log.LogInfo((object)("StatContrib: " + m));
		}
	}

	private static void Error(string m)
	{
		if (Log != null)
		{
			Log.LogError((object)("StatContrib: " + m));
		}
	}
}
internal interface ITuningKnob
{
	string Name { get; }

	string Help { get; }

	bool IsInert { get; }

	void WriteJson(JObject o);

	void ReadJson(JObject o);

	string Describe();

	void DrawMenuRow();

	string BakeSnippet();
}
internal sealed class TuningFile
{
	private readonly string _guid;

	private readonly string _tag;

	private readonly string _readme;

	private readonly ManualLogSource _log;

	private readonly List<ITuningKnob> _order = new List<ITuningKnob>();

	private readonly Dictionary<string, ITuningKnob> _byName = new Dictionary<string, ITuningKnob>(StringComparer.OrdinalIgnoreCase);

	private readonly List<Action> _reloadHandlers = new List<Action>();

	private DateTime _lastWrite = DateTime.MinValue;

	private JObject _baseline;

	private JObject _previousBaseline;

	internal string LastBakePath = "<bake not run>";

	private string _menuSignature;

	internal string Status { get; private set; } = "not loaded";

	internal string Path { get; private set; }

	internal string BaselinePath { get; private set; }

	internal IList<ITuningKnob> Knobs => _order;

	internal bool AllInert
	{
		get
		{
			for (int i = 0; i < _order.Count; i++)
			{
				if (!_order[i].IsInert)
				{
					return false;
				}
			}
			return true;
		}
	}

	internal TuningFile(string pluginGuid, string logTag, string readme, ManualLogSource log)
	{
		_guid = pluginGuid;
		_tag = logTag;
		_readme = readme;
		_log = log;
	}

	internal T Add<T>(T knob) where T : ITuningKnob
	{
		if (knob == null)
		{
			return default(T);
		}
		if (_byName.ContainsKey(knob.Name))
		{
			Warn("duplicate tuning knob '" + knob.Name + "' ignored. Two declarations of one name means one of them is silently dead.");
			return knob;
		}
		_byName[knob.Name] = knob;
		_order.Add(knob);
		return knob;
	}

	internal ITuningKnob Find(string name)
	{
		if (!_byName.TryGetValue(name, out var value))
		{
			return null;
		}
		return value;
	}

	internal void OnReload(Action handler)
	{
		if (handler != null && !_reloadHandlers.Contains(handler))
		{
			_reloadHandlers.Add(handler);
		}
	}

	internal void Initialise(string configDir)
	{
		try
		{
			Path = System.IO.Path.Combine(configDir, _guid + ".tuning.json");
			BaselinePath = System.IO.Path.Combine(configDir, _guid + ".tuning.baseline.json");
			_baseline = BuildKnobObject();
			_previousBaseline = ReadPreviousBaseline();
			WriteBaseline();
			if (!File.Exists(Path))
			{
				Save("created at the declared defaults");
			}
			else
			{
				Load();
			}
		}
		catch (Exception e)
		{
			Status = "FAILED to initialise: " + Brief(e);
			Error(Status);
		}
	}

	internal bool PollForChanges()
	{
		if (string.IsNullOrEmpty(Path))
		{
			return false;
		}
		try
		{
			if (!File.Exists(Path))
			{
				return false;
			}
			if (File.GetLastWriteTimeUtc(Path) <= _lastWrite)
			{
				return false;
			}
			Load();
			Info("tuning reloaded - " + Status);
			FireReloadHandlers();
			return true;
		}
		catch (Exception e)
		{
			Warn("tuning poll threw: " + Brief(e));
			return false;
		}
	}

	private void FireReloadHandlers()
	{
		for (int i = 0; i < _reloadHandlers.Count; i++)
		{
			try
			{
				_reloadHandlers[i]();
			}
			catch (Exception ex)
			{
				Error("a tuning re-apply handler threw: " + ex);
			}
		}
	}

	private void Load()
	{
		string text;
		try
		{
			text = File.ReadAllText(Path);
		}
		catch (Exception e)
		{
			Status = "could not read the file (" + Brief(e) + "); previous values stand";
			Error(Status);
			return;
		}
		_lastWrite = File.GetLastWriteTimeUtc(Path);
		JObject val;
		try
		{
			val = JObject.Parse(text);
		}
		catch (Exception e2)
		{
			Status = "MALFORMED tuning file (" + Brief(e2) + "); previous values stand";
			Error(Status + ". Fix " + Path + " - nothing was applied from it.");
			return;
		}
		JToken obj = val["knobs"];
		JObject val2 = (JObject)(object)((obj is JObject) ? obj : null);
		if (val2 == null)
		{
			Status = "tuning file has no 'knobs' object; every knob left at its declared default";
			Warn(Status);
			return;
		}
		int num = 0;
		int num2 = 0;
		int num3 = 0;
		int num4 = 0;
		foreach (KeyValuePair<string, JToken> item in val2)
		{
			if (!_byName.TryGetValue(item.Key, out var value))
			{
				num2++;
				Warn("tuning file names unknown knob '" + item.Key + "' - ignored. This is a typo or a rename, not a tuning value.");
				continue;
			}
			JToken value2 = item.Value;
			JObject val3 = (JObject)(object)((value2 is JObject) ? value2 : null);
			if (val3 == null)
			{
				continue;
			}
			if (FollowsNewDeclaration(item.Key, val3))
			{
				num4++;
				num++;
				continue;
			}
			try
			{
				value.ReadJson(val3);
			}
			catch (Exception e3)
			{
				Warn("knob '" + item.Key + "' could not be read: " + Brief(e3));
			}
			num++;
			if (!value.IsInert)
			{
				num3++;
			}
		}
		Status = num + " knob(s) read, " + num3 + " active" + ((num2 > 0) ? (", " + num2 + " unknown name(s) ignored") : "") + ((num4 > 0) ? (", " + num4 + " untouched knob(s) moved to this build's new declared value") : "");
		if (num3 == 0)
		{
			Status += " - every knob inert";
		}
		int count = DriftReport().Count;
		if (count > 0)
		{
			Status = Status + "; " + count + " differ(s) from source";
		}
		if (num2 > 0 || num < _order.Count || num4 > 0)
		{
			string status = Status;
			Save("rewritten to match this version's knobs (" + (_order.Count - num) + " added, " + num2 + " stale name(s) dropped)");
			Status = status + "; file rewritten to match this version's knobs";
		}
	}

	private JObject ReadPreviousBaseline()
	{
		try
		{
			if (!File.Exists(BaselinePath))
			{
				return null;
			}
			JToken obj = JObject.Parse(File.ReadAllText(BaselinePath))["knobs"];
			return (JObject)(object)((obj is JObject) ? obj : null);
		}
		catch
		{
			return null;
		}
	}

	private bool FollowsNewDeclaration(string name, JObject fromFile)
	{
		if (_previousBaseline == null || _baseline == null)
		{
			return false;
		}
		JToken obj = _previousBaseline[name];
		JObject val = (JObject)(object)((obj is JObject) ? obj : null);
		JToken obj2 = _baseline[name];
		JObject val2 = (JObject)(object)((obj2 is JObject) ? obj2 : null);
		if (val == null || val2 == null)
		{
			return false;
		}
		if (JToken.DeepEquals((JToken)(object)val, (JToken)(object)val2))
		{
			return false;
		}
		return JToken.DeepEquals((JToken)(object)val, (JToken)(object)fromFile);
	}

	internal void Save(string why)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Expected O, but got Unknown
		try
		{
			JObject val = new JObject();
			val["_readme"] = JToken.op_Implicit(_readme + "  The '_' field in each knob is a comment and is ignored. Saved changes are picked up within a second - no restart. This file is NOT shipped: values dialled in here reach nobody else until they are written back into the mod's source declarations.");
			val["knobs"] = (JToken)(object)BuildKnobObject();
			EnsureDir(Path);
			File.WriteAllText(Path, ((JToken)val).ToString((Formatting)1, Array.Empty<JsonConverter>()));
			_lastWrite = File.GetLastWriteTimeUtc(Path);
			Status = why;
			Info("tuning file " + why + " -> " + Path);
		}
		catch (Exception e)
		{
			Status = "could not write the tuning file: " + Brief(e);
			Error(Status);
		}
	}

	private JObject BuildKnobObject()
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Expected O, but got Unknown
		//IL_0017: Unknown result type (might be due to invalid IL or missing references)
		//IL_001d: Expected O, but got Unknown
		JObject val = new JObject();
		for (int i = 0; i < _order.Count; i++)
		{
			ITuningKnob tuningKnob = _order[i];
			JObject val2 = new JObject();
			val2["_"] = JToken.op_Implicit(tuningKnob.Help);
			tuningKnob.WriteJson(val2);
			val[tuningKnob.Name] = (JToken)(object)val2;
		}
		return val;
	}

	private void WriteBaseline()
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Expected O, but got Unknown
		try
		{
			JObject val = new JObject();
			val["_readme"] = JToken.op_Implicit("Generated every launch. These are the knob values as DECLARED IN SOURCE, which is what a build handed to anyone else runs at. Diff the sibling .tuning.json against this to see which dialled-in numbers would be lost by shipping. Do not edit; edit the source declarations and relaunch.");
			val["knobs"] = (JToken)(object)_baseline;
			EnsureDir(BaselinePath);
			File.WriteAllText(BaselinePath, ((JToken)val).ToString((Formatting)1, Array.Empty<JsonConverter>()));
		}
		catch (Exception e)
		{
			Warn("could not write the tuning baseline (" + Brief(e) + "). The release drift check has nothing to compare against for this mod.");
		}
	}

	internal List<string> DriftReport()
	{
		//IL_0024: Unknown result type (might be due to invalid IL or missing references)
		//IL_002a: Expected O, but got Unknown
		List<string> list = new List<string>();
		if (_baseline == null)
		{
			return list;
		}
		for (int i = 0; i < _order.Count; i++)
		{
			ITuningKnob tuningKnob = _order[i];
			JObject val = new JObject();
			tuningKnob.WriteJson(val);
			JToken obj = _baseline[tuningKnob.Name];
			JObject val2 = (JObject)(object)((obj is JObject) ? obj : null);
			if (val2 == null)
			{
				continue;
			}
			foreach (KeyValuePair<string, JToken> item in val)
			{
				JToken val3 = val2[item.Key];
				if (val3 != null && !JToken.DeepEquals(val3, item.Value))
				{
					list.Add(tuningKnob.Name + "." + item.Key + ": source " + ((object)val3)?.ToString() + " -> live " + (object)item.Value);
				}
			}
		}
		return list;
	}

	internal string WriteBakeSnippet(string outputDir)
	{
		try
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine("// " + _tag + " - tuning bake, taken from the live tuning file.");
			stringBuilder.AppendLine("//");
			stringBuilder.AppendLine("// Paste over the knob declarations. After this the declared defaults");
			stringBuilder.AppendLine("// and the tuning file agree, the drift check is clean, and a build");
			stringBuilder.AppendLine("// handed to anyone else plays at the numbers you dialled in.");
			stringBuilder.AppendLine("//");
			stringBuilder.AppendLine("// Source : " + (Path ?? "<none>"));
			stringBuilder.AppendLine("// State  : " + Status);
			stringBuilder.AppendLine();
			for (int i = 0; i < _order.Count; i++)
			{
				stringBuilder.AppendLine(_order[i].BakeSnippet());
			}
			List<string> list = DriftReport();
			stringBuilder.AppendLine();
			if (list.Count == 0)
			{
				stringBuilder.AppendLine("// Nothing has drifted - source already matches the tuning file.");
			}
			else
			{
				stringBuilder.AppendLine("// Differs from source (" + list.Count + "):");
				for (int j = 0; j < list.Count; j++)
				{
					stringBuilder.AppendLine("//   " + list[j]);
				}
			}
			string text = System.IO.Path.Combine(outputDir, _guid + ".tuning.bake.cs.txt");
			EnsureDir(text);
			File.WriteAllText(text, stringBuilder.ToString());
			Info("tuning bake snippet -> " + text);
			return text;
		}
		catch (Exception ex)
		{
			Error("tuning bake snippet threw: " + ex);
			return "<failed: " + Brief(ex) + ">";
		}
	}

	internal void DrawMenu()
	{
		try
		{
			GUILayout.Label("Tuning : " + Status, Array.Empty<GUILayoutOption>());
			GUILayout.Label("  " + (Path ?? "<not initialised>"), Array.Empty<GUILayoutOption>());
			for (int i = 0; i < _order.Count; i++)
			{
				_order[i].DrawMenuRow();
			}
			string text = MenuSignature();
			if (_menuSignature == null)
			{
				_menuSignature = text;
			}
			else if (text != _menuSignature)
			{
				_menuSignature = text;
				FireReloadHandlers();
			}
			List<string> list = DriftReport();
			GUILayout.Space(6f);
			if (list.Count == 0)
			{
				GUILayout.Label("Source and tuning file agree - safe to package.", Array.Empty<GUILayoutOption>());
			}
			else
			{
				GUILayout.Label("NOT IN SOURCE (" + list.Count + ") - a build sent to anyone else", Array.Empty<GUILayoutOption>());
				GUILayout.Label("would run at the source values, not these:", Array.Empty<GUILayoutOption>());
				for (int j = 0; j < list.Count; j++)
				{
					GUILayout.Label("    " + list[j], Array.Empty<GUILayoutOption>());
				}
			}
			GUILayout.Space(4f);
			if (GUILayout.Button("Save these values to the tuning file", Array.Empty<GUILayoutOption>()))
			{
				Save("saved from the F5 menu");
			}
			if (GUILayout.Button("Reload the tuning file now", Array.Empty<GUILayoutOption>()))
			{
				_lastWrite = DateTime.MinValue;
				PollForChanges();
			}
			if (GUILayout.Button("Write the bake snippet (the release step)", Array.Empty<GUILayoutOption>()))
			{
				LastBakePath = WriteBakeSnippet(Paths.ConfigPath);
			}
			GUILayout.Label("  " + LastBakePath, Array.Empty<GUILayoutOption>());
		}
		catch (Exception e)
		{
			GUILayout.Label("Tuning menu failed to draw: " + Brief(e), Array.Empty<GUILayoutOption>());
		}
	}

	private string MenuSignature()
	{
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0010: Expected O, but got Unknown
		StringBuilder stringBuilder = new StringBuilder();
		for (int i = 0; i < _order.Count; i++)
		{
			JObject val = new JObject();
			_order[i].WriteJson(val);
			stringBuilder.Append(((JToken)val).ToString((Formatting)0, Array.Empty<JsonConverter>()));
		}
		return stringBuilder.ToString();
	}

	private static void EnsureDir(string file)
	{
		string directoryName = System.IO.Path.GetDirectoryName(file);
		if (!string.IsNullOrEmpty(directoryName) && !Directory.Exists(directoryName))
		{
			Directory.CreateDirectory(directoryName);
		}
	}

	private static string Brief(Exception e)
	{
		if (e != null)
		{
			return e.GetType().Name + ": " + e.Message;
		}
		return "<null>";
	}

	private void Info(string m)
	{
		if (_log != null)
		{
			_log.LogInfo((object)(_tag + ": " + m));
		}
	}

	private void Warn(string m)
	{
		if (_log != null)
		{
			_log.LogWarning((object)(_tag + ": " + m));
		}
	}

	private void Error(string m)
	{
		if (_log != null)
		{
			_log.LogError((object)(_tag + ": " + m));
		}
	}

	internal static string F(float v)
	{
		return v.ToString("0.####", CultureInfo.InvariantCulture);
	}
}
internal sealed class TuningScalar : ITuningKnob
{
	public float Value;

	public readonly float Declared;

	public readonly float Min;

	public readonly float Max;

	public readonly float Sentinel;

	private readonly NumericField _field = new NumericField();

	public string Name { get; private set; }

	public string Help { get; private set; }

	public bool IsInert
	{
		get
		{
			if (!float.IsNaN(Sentinel) && Nearly(Value, Sentinel))
			{
				return true;
			}
			return Nearly(Value, Declared);
		}
	}

	public bool IsSentinel
	{
		get
		{
			if (!float.IsNaN(Sentinel))
			{
				return Nearly(Value, Sentinel);
			}
			return false;
		}
	}

	internal TuningScalar(string name, string help, float declared, float min, float max, float sentinel = float.NaN)
	{
		Name = name;
		Help = help;
		Value = declared;
		Declared = declared;
		Min = min;
		Max = max;
		Sentinel = sentinel;
	}

	public void WriteJson(JObject o)
	{
		o["value"] = JToken.op_Implicit(Value);
	}

	public void ReadJson(JObject o)
	{
		JToken val = o["value"];
		if (val == null)
		{
			return;
		}
		try
		{
			float num = Extensions.Value<float>((IEnumerable<JToken>)val);
			if (!float.IsNaN(num) && !float.IsInfinity(num))
			{
				Value = num;
			}
		}
		catch
		{
		}
	}

	public string Describe()
	{
		if (!IsSentinel)
		{
			return TuningFile.F(Value);
		}
		return "keep the game's own value";
	}

	public void DrawMenuRow()
	{
		GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
		GUILayout.Label(Name + "  " + Describe(), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(280f) });
		Value = _field.Draw(Value);
		Value = GUILayout.HorizontalSlider(Value, Min, Max, Array.Empty<GUILayoutOption>());
		GUILayout.EndHorizontal();
	}

	public string BakeSnippet()
	{
		string text = (float.IsNaN(Sentinel) ? "" : (", " + TuningFile.F(Sentinel) + "f"));
		return "Scalar(\"" + Name + "\", \"" + Help.Replace("\"", "\\\"") + "\", " + TuningFile.F(Value) + "f, " + TuningFile.F(Min) + "f, " + TuningFile.F(Max) + "f" + text + ");";
	}

	private static bool Nearly(float a, float b)
	{
		return Math.Abs(a - b) < 0.0001f;
	}
}
internal sealed class TuningString : ITuningKnob
{
	public string Value;

	public readonly string Declared;

	public string Name { get; private set; }

	public string Help { get; private set; }

	public bool HasValue => !string.IsNullOrEmpty(Value);

	public bool IsInert => string.Equals(Value, Declared, StringComparison.Ordinal);

	internal TuningString(string name, string help, string declared)
	{
		Name = name;
		Help = help;
		Value = declared ?? "";
		Declared = declared ?? "";
	}

	public void WriteJson(JObject o)
	{
		o["text"] = JToken.op_Implicit(Value ?? "");
	}

	public void ReadJson(JObject o)
	{
		JToken val = o["text"];
		if (val == null)
		{
			return;
		}
		try
		{
			string text = Extensions.Value<string>((IEnumerable<JToken>)val);
			Value = (text ?? "").Trim();
		}
		catch
		{
		}
	}

	public string Describe()
	{
		if (!HasValue)
		{
			return "the game's own choice";
		}
		return Value;
	}

	public void DrawMenuRow()
	{
		GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
		GUILayout.Label(Name, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(280f) });
		Value = GUILayout.TextField(Value ?? "", Array.Empty<GUILayoutOption>()) ?? "";
		GUILayout.EndHorizontal();
	}

	public string BakeSnippet()
	{
		return "Text(\"" + Name + "\", \"" + Help.Replace("\"", "\\\"") + "\", \"" + (Value ?? "").Replace("\\", "\\\\").Replace("\"", "\\\"") + "\");";
	}
}
internal sealed class NumericField
{
	private string _text;

	private float _synced = float.NaN;

	internal float Draw(float value, float width = 64f, bool integer = false)
	{
		if (_text == null || value != _synced)
		{
			_text = (integer ? ((int)Math.Round(value)).ToString(CultureInfo.InvariantCulture) : TuningFile.F(value));
			_synced = value;
		}
		string text = GUILayout.TextField(_text, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(width) });
		if (text == _text)
		{
			return value;
		}
		_text = text;
		if (!float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
		{
			return value;
		}
		if (float.IsNaN(result) || float.IsInfinity(result))
		{
			return value;
		}
		if (integer)
		{
			result = (float)Math.Round(result);
		}
		_synced = result;
		return result;
	}
}
internal sealed class TuningToggle : ITuningKnob
{
	public bool Value;

	public readonly bool Declared;

	public string Name { get; private set; }

	public string Help { get; private set; }

	public bool IsInert => Value == Declared;

	internal TuningToggle(string name, string help, bool declared)
	{
		Name = name;
		Help = help;
		Value = declared;
		Declared = declared;
	}

	public void WriteJson(JObject o)
	{
		o["on"] = JToken.op_Implicit(Value);
	}

	public void ReadJson(JObject o)
	{
		JToken val = o["on"];
		if (val == null)
		{
			return;
		}
		try
		{
			Value = Extensions.Value<bool>((IEnumerable<JToken>)val);
		}
		catch
		{
		}
	}

	public string Describe()
	{
		if (!Value)
		{
			return "off";
		}
		return "on";
	}

	public void DrawMenuRow()
	{
		Value = GUILayout.Toggle(Value, "  " + Name + " - " + Help, Array.Empty<GUILayoutOption>());
	}

	public string BakeSnippet()
	{
		return "Toggle(\"" + Name + "\", \"" + Help.Replace("\"", "\\\"") + "\", " + (Value ? "true" : "false") + ");";
	}
}
namespace VoidCrewVoidTunnelDecay
{
	[BepInPlugin("jack.voidcrew.voidtunneldecay", "Void Tunnel Decay", "0.9.2")]
	[BepInProcess("Void Crew.exe")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		internal static ManualLogSource Log;

		internal static bool PatchesApplied;

		private void Awake()
		{
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Expected O, but got Unknown
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			Log = ((BaseUnityPlugin)this).Logger;
			VtdConfig.Bind(((BaseUnityPlugin)this).Config);
			VtdTuning.Initialise(Paths.ConfigPath);
			if (!VtdConfig.Enabled.Value)
			{
				Log.LogWarning((object)"VoidTunnelDecay: disabled via config ([General] Enabled = false). No patches applied, no mutator registered - vanilla tunnel-stability behaviour only.");
				return;
			}
			Harmony harmony;
			try
			{
				harmony = Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), "jack.voidcrew.voidtunneldecay");
				PatchesApplied = true;
			}
			catch (Exception ex)
			{
				PatchesApplied = false;
				Log.LogError((object)("VoidTunnelDecay: patching failed, going inert - vanilla tunnel-stability behaviour only: " + ex));
				return;
			}
			MutatorSlots.Install(((BaseUnityPlugin)this).Config, harmony, Log, "VoidTunnelDecay");
			GameObject val = new GameObject("VoidTunnelDecay.Driver");
			Object.DontDestroyOnLoad((Object)val);
			((Object)val).hideFlags = (HideFlags)61;
			val.AddComponent<VtdDriver>();
			Log.LogInfo((object)("Void Tunnel Decay v0.9.2 loaded. Void-tunnel clock: " + VtdTuning.BaseClockSeconds.Value + "s at " + VtdTuning.BasePower.Value + " generated power, scaled by the ship's power and the jump's vanilla interdiction chance, rolled every " + VtdConfig.TickIntervalSeconds.Value + "s, once selected in the Mutators menu."));
		}
	}
	internal class VtdDriver : MonoBehaviour
	{
		private float _nextRegister;

		private float _nextTuningPoll;

		private void Update()
		{
			try
			{
				float unscaledTime = Time.unscaledTime;
				if (!VtdRegistration.Registered && unscaledTime >= _nextRegister)
				{
					_nextRegister = unscaledTime + 2f;
					VtdRegistration.EnsureRegistered();
				}
				VtdFlicker.Update();
				if (!(unscaledTime < _nextTuningPoll))
				{
					_nextTuningPoll = unscaledTime + 1f;
					VtdTuning.PollForChanges();
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("VoidTunnelDecay: driver poll failed: " + ex));
			}
		}
	}
	public class VtdVoidPlugin : VoidPlugin
	{
		public override MultiplayerType MPType => (MultiplayerType)8;

		public override string Author => "Jack";

		public override string Description => "Mutator: void-tunnel stability decays on a clock, shorter for high-power ships and risky jumps. The tunnel flickers as the odds climb. Stay long enough and interdiction becomes certain.";

		public override string ThunderstoreID => "Jack_Modding/Void_Tunnel_Decay";

		public override SessionChangedReturn OnSessionChange(SessionChangedInput input)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			return new SessionChangedReturn
			{
				SetMod_Session = VtdMutator.Active
			};
		}
	}
	public class VtdSettingsMenu : ModSettingsMenu
	{
		public override string Name()
		{
			return "Void Tunnel Decay";
		}

		public override void Draw()
		{
			GUILayout.Label("Mutator      : " + VtdRegistration.Status, Array.Empty<GUILayoutOption>());
			GUILayout.Label("Active now   : " + (VtdMutator.Active ? "yes" : "no") + (VtdMutator.ForcedOn ? " (testing override forced on)" : ""), Array.Empty<GUILayoutOption>());
			GUILayout.Label("Enforcing    : " + (VtdMutator.Enforcing ? "yes" : (VtdMutator.Active ? "no - quest disallows interdiction, immune" : "no")), Array.Empty<GUILayoutOption>());
			GUILayout.Label("Base clock   : " + VtdTuning.BaseClockSeconds.Value + "s at " + VtdTuning.BasePower.Value + " generated power, rolled every " + VtdConfig.TickIntervalSeconds.Value + "s", Array.Empty<GUILayoutOption>());
			GUILayout.Label("Last jump    : P=" + VtdMutator.LastPowerVariable.ToString("0.#") + " c=" + VtdMutator.LastVanillaChance.ToString("0.###") + " => clock " + VtdMutator.LastClockLengthSeconds.ToString("0.#") + "s, " + VtdMutator.LastPercentRemaining.ToString("0") + "% remaining", Array.Empty<GUILayoutOption>());
			GUILayout.Space(8f);
			MutatorSlots.Draw();
			GUILayout.Space(8f);
			GUILayout.Label("Config: BepInEx/config/jack.voidcrew.voidtunneldecay.cfg", Array.Empty<GUILayoutOption>());
			MenuKit.DevZone("void-tunnel-decay", DrawDevTools);
		}

		private static void DrawDevTools()
		{
			GUILayout.Label("Patches      : " + (Plugin.PatchesApplied ? "applied" : "NOT APPLIED - inert"), Array.Empty<GUILayoutOption>());
			GUILayout.Label("Flicker      : " + VtdFlicker.Status, Array.Empty<GUILayoutOption>());
			GUILayout.Label("Last roll    : gate=" + VtdMutator.LastGateValue.ToString("0.###") + " roll=" + VtdMutator.LastRollValue.ToString("0.###") + " (also LogDebug'd every tick as 'VoidTunnelDecay: tick' - enable Debug in BepInEx.cfg's LogLevels to capture it in LogOutput.log)", Array.Empty<GUILayoutOption>());
			GUILayout.Space(10f);
			GUILayout.Label("TESTING ONLY - never shipped. Bake into source before packaging.", Array.Empty<GUILayoutOption>());
			VtdTuning.File?.DrawMenu();
		}
	}
	internal static class VtdPluginInfo
	{
		public const string PLUGIN_GUID = "jack.voidcrew.voidtunneldecay";

		public const string PLUGIN_NAME = "VoidTunnelDecay";

		public const string USERS_PLUGIN_NAME = "Void Tunnel Decay";

		public const string PLUGIN_VERSION = "0.9.2";

		public const string PLUGIN_DESCRIPTION = "Mutator: void-tunnel stability decays on a clock, shorter for high-power ships and risky jumps. The tunnel flickers as the odds climb. Stay long enough and interdiction becomes certain.";

		public const string PLUGIN_AUTHORS = "Jack";

		public const string PLUGIN_THUNDERSTORE_ID = "Jack_Modding/Void_Tunnel_Decay";

		public const float RUN_XP_BONUS = 0.25f;

		public const string MUTATOR_GUID = "7cb9e33adfb6443888d7fe719c27e65d";

		public const string MUTATOR_PATH = "Mutators/Mutator_InterdictionDecay";

		public const string MUTATOR_NAME = "Void Tunnel Decay";

		public const string MUTATOR_DESC = "Void-tunnel stability decays on a clock while travelling, independent of the normal chance of interdiction. Bigger reactors and overloaded grids draw the void's attention sooner; repairs made in the void buy time. Left alone long enough, interdiction stops being a matter of luck.";

		public const string MUTATOR_ICON_SPRITE_NAME = "icon_voidsanction";
	}
	internal static class Refl
	{
		private const BindingFlags ALL = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

		public static FieldInfo Field(Type t, string name)
		{
			while (t != null)
			{
				FieldInfo field = t.GetField(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				if (field != null)
				{
					return field;
				}
				t = t.BaseType;
			}
			return null;
		}

		public static object GetField(object target, string name)
		{
			if (target == null)
			{
				return null;
			}
			FieldInfo fieldInfo = Field(target.GetType(), name);
			if (!(fieldInfo == null))
			{
				return fieldInfo.GetValue(target);
			}
			return null;
		}

		public static bool SetField(object target, string name, object value)
		{
			if (target == null)
			{
				return false;
			}
			FieldInfo fieldInfo = Field(target.GetType(), name);
			if (fieldInfo == null)
			{
				return false;
			}
			fieldInfo.SetValue(target, value);
			return true;
		}

		public static string Brief(Exception e)
		{
			if (e != null)
			{
				return e.GetType().Name + ": " + e.Message;
			}
			return "<null>";
		}
	}
	internal static class VtdConfig
	{
		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<float> TickIntervalSeconds;

		internal static void Bind(ConfigFile config)
		{
			Enabled = config.Bind<bool>("General", "Enabled", true, "Master switch. Off means this mod applies no patches and registers no assets - the void tunnel behaves exactly like vanilla, decay clock included.");
			TickIntervalSeconds = config.Bind<float>("Gameplay", "TickIntervalSeconds", 1f, "Seconds between interdiction rolls. The clock's length and the odds curve's shape are set elsewhere (the ship's power and the jump's vanilla interdiction chance scale the clock; the curve is fixed), so this only sets how finely that curve is sampled - a coarser tick changes nothing about when interdiction is likely, only how many distinct moments it can land on.");
		}
	}
	internal static class VtdFlicker
	{
		private struct Stroke
		{
			public float StartAt;

			public float Hold;

			public float Peak;
		}

		private sealed class Burst
		{
			public readonly List<Stroke> Strokes = new List<Stroke>();

			public bool Lights;

			public bool Color;

			public bool Shake;

			public int CurrentStroke = -1;

			public bool LightsHeld;

			public bool ColorHeld;
		}

		private const string SHAKE_ID = "VoidTunnelDecay.flicker";

		private const int MAX_STROKES = 4;

		private const float STALE_SECONDS = 5f;

		private static bool _running;

		private static float _x;

		private static float _gate;

		private static float _lastUpdateTime;

		private static VoidJumpTunnelTweener _tweener;

		private static VoidJumpPostEffects _post;

		private static float _nextFindTime;

		private static Burst _burst;

		private static EnvironmentFlickeringLightSource _envelopeSource;

		private static float[] _originalIntensities;

		private static Color _originalOnColor;

		private static bool _envelopeCaptured;

		internal static int BurstCount;

		internal static string Status
		{
			get
			{
				if (_running)
				{
					return "running x=" + _x.ToString("0.###") + " gate=" + _gate.ToString("0.###") + " rate=" + (VtdTuning.FlickerRate.Value * _gate).ToString("0.##") + "/s bursts=" + BurstCount + ((_burst != null) ? (" (in burst, " + _burst.Strokes.Count + " strokes)") : "") + (((Object)(object)_tweener == (Object)null) ? " (no tunnel tweener found)" : "") + (((Object)(object)_post == (Object)null) ? " (no post effects found)" : "");
				}
				return "idle";
			}
		}

		internal static void HostBegin()
		{
			Apply(running: true, 0f, 0f);
			BurstCount = 0;
			Broadcast(running: true, 0f, 0f, reliable: true);
		}

		internal static void HostTick(float x, float gate)
		{
			Apply(running: true, x, gate);
			Broadcast(running: true, x, gate, reliable: false);
		}

		internal static void HostStop()
		{
			if (_running)
			{
				Apply(running: false, _x, 0f);
				Broadcast(running: false, _x, 0f, reliable: true);
			}
		}

		private static void Broadcast(bool running, float x, float gate, bool reliable)
		{
			try
			{
				if (PhotonNetwork.InRoom)
				{
					ModMessage.Send("jack.voidcrew.voidtunneldecay", ModMessage.GetIdentifier(typeof(VtdFlickerMessage)), (ReceiverGroup)0, new object[3] { running, x, gate }, reliable);
				}
			}
			catch (Exception e)
			{
				Plugin.Log.LogWarning((object)("VoidTunnelDecay: flicker broadcast failed: " + Refl.Brief(e)));
			}
		}

		internal static void Apply(bool running, float x, float gate)
		{
			bool running2 = _running;
			_running = running;
			_x = Mathf.Clamp01(x);
			_gate = Mathf.Clamp01(gate);
			_lastUpdateTime = Time.unscaledTime;
			if (running2 && !running)
			{
				EndBurst();
			}
		}

		internal static void Update()
		{
			float unscaledTime = Time.unscaledTime;
			if (_running && unscaledTime - _lastUpdateTime > 5f)
			{
				Plugin.Log.LogInfo((object)("VoidTunnelDecay: no clock update for " + 5f + "s, flicker stopping"));
				Apply(running: false, _x, 0f);
			}
			if (_burst != null)
			{
				StepBurst(unscaledTime);
			}
			if (!_running)
			{
				return;
			}
			if ((Object)(object)_tweener == (Object)null || (Object)(object)_post == (Object)null)
			{
				FindTargets(unscaledTime);
			}
			if (_burst != null)
			{
				return;
			}
			float num = VtdTuning.FlickerRate.Value * _gate;
			if (!(num <= 0f))
			{
				float num2 = 1f - Mathf.Exp((0f - num) * Time.deltaTime);
				if (Random.value < num2)
				{
					StartBurst(unscaledTime);
				}
			}
		}

		private static void FindTargets(float now)
		{
			if (now < _nextFindTime)
			{
				return;
			}
			_nextFindTime = now + 1f;
			try
			{
				if ((Object)(object)_tweener == (Object)null)
				{
					_tweener = Object.FindObjectOfType<VoidJumpTunnelTweener>();
				}
				if ((Object)(object)_post == (Object)null)
				{
					_post = Object.FindObjectOfType<VoidJumpPostEffects>();
				}
			}
			catch (Exception e)
			{
				Plugin.Log.LogWarning((object)("VoidTunnelDecay: tunnel effect lookup failed: " + Refl.Brief(e)));
			}
		}

		private static float Fuzz(float amount)
		{
			return 1f + Random.Range(0f - amount, amount);
		}

		private static void StartBurst(float now)
		{
			BurstCount++;
			float x = _x;
			float value = VtdTuning.FlickerDurationFuzz.Value;
			float num = Mathf.Lerp(VtdTuning.FlickerMinSeconds.Value, VtdTuning.FlickerMaxSeconds.Value, x);
			float num2 = Mathf.Max(1f, VtdTuning.FlickerPeak.Value);
			float num3 = Mathf.Clamp01(VtdTuning.FlickerRestrikeDecay.Value);
			Burst burst = new Burst
			{
				Lights = Roll(VtdTuning.FlickerLightsChance.Value * 1f),
				Color = Roll(VtdTuning.FlickerColorChance.Value * Mathf.Sqrt(x)),
				Shake = Roll(VtdTuning.FlickerShakeChance.Value * x * x)
			};
			float num4 = now;
			float num5 = 1f;
			for (int i = 0; i < 4; i++)
			{
				if (i > 0)
				{
					if (!Roll(VtdTuning.FlickerRestrikeChance.Value))
					{
						break;
					}
					num4 += Random.Range(VtdTuning.FlickerGapMinSeconds.Value, VtdTuning.FlickerGapMaxSeconds.Value) * Fuzz(value);
				}
				num5 *= ((i > 0) ? Mathf.Clamp01(num3 * Fuzz(value)) : 1f);
				float num6 = Mathf.Max(0.02f, num * num5 * Fuzz(value));
				burst.Strokes.Add(new Stroke
				{
					StartAt = num4,
					Hold = num6,
					Peak = 1f + (num2 - 1f) * num5
				});
				num4 += num6;
			}
			_burst = burst;
			StepBurst(now);
		}

		private static bool Roll(float chance)
		{
			return Random.value < Mathf.Clamp01(chance);
		}

		private static void StepBurst(float now)
		{
			Burst burst = _burst;
			int num = -1;
			for (int i = 0; i < burst.Strokes.Count; i++)
			{
				Stroke stroke = burst.Strokes[i];
				if (now >= stroke.StartAt && now < stroke.StartAt + stroke.Hold)
				{
					num = i;
					break;
				}
			}
			try
			{
				if (num != burst.CurrentStroke)
				{
					if (burst.CurrentStroke >= 0)
					{
						LeaveStroke(burst);
					}
					burst.CurrentStroke = num;
					if (num >= 0)
					{
						EnterStroke(burst, burst.Strokes[num]);
					}
				}
				if (num >= 0)
				{
					Stroke stroke2 = burst.Strokes[num];
					float num2 = Mathf.Clamp01((now - stroke2.StartAt) / stroke2.Hold);
					float num3 = 1f - num2;
					ApplyEnvelope(1f + (stroke2.Peak - 1f) * num3 * num3 * num3);
				}
			}
			catch (Exception e)
			{
				Plugin.Log.LogWarning((object)("VoidTunnelDecay: flicker burst step failed: " + Refl.Brief(e)));
			}
			Stroke stroke3 = burst.Strokes[burst.Strokes.Count - 1];
			if (now >= stroke3.StartAt + stroke3.Hold)
			{
				EndBurst();
			}
		}

		private static void EnterStroke(Burst b, Stroke s)
		{
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0114: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: Expected O, but got Unknown
			if (!StillStable())
			{
				return;
			}
			if (b.Lights)
			{
				GameObject val = LightObject("lightSource_Stable");
				GameObject val2 = LightObject("lightSource_Unstable");
				if ((Object)(object)val != (Object)null && (Object)(object)val2 != (Object)null)
				{
					val.SetActive(false);
					val2.SetActive(true);
					b.LightsHeld = true;
				}
			}
			if (b.Color && (Object)(object)_post != (Object)null && (Object)(object)_post.UnstableDirectionalColor != (Object)null)
			{
				DirectionalColorGradingPP.SetDirectionalColorGradingValues(_post.UnstableDirectionalColor, VtdTuning.FlickerColorAttackSeconds.Value * Fuzz(VtdTuning.FlickerDurationFuzz.Value));
				b.ColorHeld = true;
			}
			if (b.Shake)
			{
				CameraShakeEffectController instance = CameraShakeEffectController.Instance;
				if ((Object)(object)instance != (Object)null)
				{
					float num = (s.Peak - 1f) / Mathf.Max(0.001f, VtdTuning.FlickerPeak.Value - 1f);
					instance.Shake(new CameraShakeDetail("VoidTunnelDecay.flicker")
					{
						Duration = s.Hold,
						Intensity = VtdTuning.FlickerShakeIntensity.Value * Mathf.Clamp01(num),
						Frequency = VtdTuning.FlickerShakeFrequency.Value,
						Fade = true
					});
				}
			}
		}

		private static void LeaveStroke(Burst b)
		{
			RestoreEnvelope();
			if (!StillStable())
			{
				b.LightsHeld = false;
				b.ColorHeld = false;
				return;
			}
			if (b.LightsHeld)
			{
				GameObject val = LightObject("lightSource_Stable");
				GameObject val2 = LightObject("lightSource_Unstable");
				if ((Object)(object)val2 != (Object)null)
				{
					val2.SetActive(false);
				}
				if ((Object)(object)val != (Object)null)
				{
					val.SetActive(true);
				}
				b.LightsHeld = false;
			}
			if (b.ColorHeld)
			{
				if ((Object)(object)_post != (Object)null && (Object)(object)_post.StableDirectionalColor != (Object)null)
				{
					DirectionalColorGradingPP.SetDirectionalColorGradingValues(_post.StableDirectionalColor, VtdTuning.FlickerColorReleaseSeconds.Value * Fuzz(VtdTuning.FlickerDurationFuzz.Value));
				}
				b.ColorHeld = false;
			}
		}

		private static void EndBurst()
		{
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Expected O, but got Unknown
			Burst burst = _burst;
			if (burst == null)
			{
				return;
			}
			_burst = null;
			try
			{
				if (burst.CurrentStroke >= 0)
				{
					LeaveStroke(burst);
				}
				else
				{
					RestoreEnvelope();
				}
				if (burst.Shake && (Object)(object)CameraShakeEffectController.Instance != (Object)null)
				{
					CameraShakeEffectController.Instance.Shake(new CameraShakeDetail("VoidTunnelDecay.flicker", true));
				}
			}
			catch (Exception e)
			{
				Plugin.Log.LogWarning((object)("VoidTunnelDecay: flicker burst end failed: " + Refl.Brief(e)));
			}
		}

		private static void ApplyEnvelope(float envelope)
		{
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0141: Unknown result type (might be due to invalid IL or missing references)
			//IL_0146: Unknown result type (might be due to invalid IL or missing references)
			//IL_015e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			object field = Refl.GetField(_tweener, "lightSource_Unstable");
			EnvironmentFlickeringLightSource val = (EnvironmentFlickeringLightSource)((field is EnvironmentFlickeringLightSource) ? field : null);
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			if (!_envelopeCaptured || (Object)(object)_envelopeSource != (Object)(object)val)
			{
				if (!(Refl.GetField(val, "_lightData") is Array array))
				{
					return;
				}
				_originalIntensities = new float[array.Length];
				for (int i = 0; i < array.Length; i++)
				{
					object value = array.GetValue(i);
					_originalIntensities[i] = ((LightSourceData)(((value is LightSourceData) ? value : null)?)).OriginalLightIntensity ?? 1f;
				}
				_originalOnColor = ((Refl.GetField(val, "onColor") is Color val2) ? val2 : Color.white);
				_envelopeSource = val;
				_envelopeCaptured = true;
			}
			if (Refl.GetField(val, "_lightData") is Array array2)
			{
				for (int j = 0; j < array2.Length && j < _originalIntensities.Length; j++)
				{
					object? value2 = array2.GetValue(j);
					LightSourceData val3 = (LightSourceData)((value2 is LightSourceData) ? value2 : null);
					if (val3 != null)
					{
						Refl.SetField(val3, "OriginalLightIntensity", _originalIntensities[j] * envelope);
					}
				}
			}
			Color val4 = _originalOnColor * envelope;
			val4.a = _originalOnColor.a;
			Refl.SetField(val, "onColor", val4);
		}

		private static void RestoreEnvelope()
		{
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			if (!_envelopeCaptured || (Object)(object)_envelopeSource == (Object)null)
			{
				return;
			}
			try
			{
				if (Refl.GetField(_envelopeSource, "_lightData") is Array array)
				{
					for (int i = 0; i < array.Length && i < _originalIntensities.Length; i++)
					{
						object? value = array.GetValue(i);
						LightSourceData val = (LightSourceData)((value is LightSourceData) ? value : null);
						if (val != null)
						{
							Refl.SetField(val, "OriginalLightIntensity", _originalIntensities[i]);
						}
					}
				}
				Refl.SetField(_envelopeSource, "onColor", _originalOnColor);
			}
			catch (Exception e)
			{
				Plugin.Log.LogWarning((object)("VoidTunnelDecay: envelope restore failed: " + Refl.Brief(e)));
			}
		}

		private static GameObject LightObject(string field)
		{
			if ((Object)(object)_tweener == (Object)null)
			{
				return null;
			}
			object field2 = Refl.GetField(_tweener, field);
			Component val = (Component)((field2 is Component) ? field2 : null);
			if (!((Object)(object)val != (Object)null))
			{
				return null;
			}
			return val.gameObject;
		}

		private static bool StillStable()
		{
			if ((Object)(object)_tweener == (Object)null)
			{
				return true;
			}
			object field = Refl.GetField(_tweener, "_voidJumpSystem");
			VoidJumpSystem val = (VoidJumpSystem)((field is VoidJumpSystem) ? field : null);
			if ((Object)(object)val == (Object)null)
			{
				return true;
			}
			return val.ActiveState is VoidJumpTravellingStable;
		}
	}
	public sealed class VtdFlickerMessage : ModMessage
	{
		public override void Handle(object[] args, Player sender)
		{
			try
			{
				if (Plugin.PatchesApplied && sender != null && sender.IsMasterClient && !PhotonNetwork.IsMasterClient && args != null && args.Length >= 3)
				{
					VtdFlicker.Apply((bool)args[0], (float)args[1], (float)args[2]);
				}
			}
			catch (Exception e)
			{
				Plugin.Log.LogWarning((object)("VoidTunnelDecay: flicker message failed: " + Refl.Brief(e)));
			}
		}
	}
	internal static class VtdMutator
	{
		private class StabilityClock
		{
			public int StartTimestamp;

			public int LastTickProcessed;

			public float LengthSeconds;

			public float X;

			public float LastHullFraction;

			public float PowerVariable;

			public float VanillaChance;
		}

		internal static bool ForcedOn;

		internal static float ForcedBaseClockSeconds;

		internal static float ForcedTickIntervalSeconds;

		private static readonly ConditionalWeakTable<VoidJumpSystem, object> _tunnelDecayArm = new ConditionalWeakTable<VoidJumpSystem, object>();

		private static readonly object ArmSentinel = new object();

		private static readonly ConditionalWeakTable<VoidJumpSystem, StabilityClock> _clocks = new ConditionalWeakTable<VoidJumpSystem, StabilityClock>();

		internal static float LastGateValue;

		internal static float LastRollValue;

		internal static float LastPowerVariable;

		internal static float LastVanillaChance;

		internal static float LastClockLengthSeconds;

		internal static float LastPercentRemaining = 100f;

		internal static bool Active
		{
			get
			{
				//IL_0033: Unknown result type (might be due to invalid IL or missing references)
				//IL_0038: Unknown result type (might be due to invalid IL or missing references)
				if (ForcedOn)
				{
					return true;
				}
				GameSession activeSession = GameSessionManager.ActiveSession;
				if (activeSession == null)
				{
					return false;
				}
				foreach (Mutator activeMutator in activeSession.ActiveMutators)
				{
					if ((Object)(object)activeMutator != (Object)null && ((SelfReferencingScriptableObject)activeMutator).ContainerGuid == VtdRegistration.OurGuid)
					{
						return true;
					}
				}
				return false;
			}
		}

		internal static bool Enforcing
		{
			get
			{
				if (!Active || !PhotonNetwork.IsMasterClient)
				{
					return false;
				}
				GameSession activeSession = GameSessionManager.ActiveSession;
				if (activeSession != null && activeSession.ActiveQuest != null)
				{
					return activeSession.ActiveQuest.AllowsInterdiction;
				}
				return false;
			}
		}

		private static float BaseClockSeconds
		{
			get
			{
				if (!ForcedOn)
				{
					return VtdTuning.BaseClockSeconds.Value;
				}
				return ForcedBaseClockSeconds;
			}
		}

		private static float TickIntervalSeconds
		{
			get
			{
				if (!ForcedOn)
				{
					return VtdConfig.TickIntervalSeconds.Value;
				}
				return ForcedTickIntervalSeconds;
			}
		}

		private static float ReferencePower => 3f * VtdTuning.BasePower.Value;

		internal static void SubscribeForceOn(float baseClockSeconds, float tickIntervalSeconds)
		{
			ForcedOn = true;
			ForcedBaseClockSeconds = baseClockSeconds;
			ForcedTickIntervalSeconds = tickIntervalSeconds;
		}

		internal static void ArmTunnelDecay(VoidJumpSystem system)
		{
			if (!((Object)(object)system == (Object)null))
			{
				_tunnelDecayArm.Remove(system);
				_tunnelDecayArm.Add(system, ArmSentinel);
			}
		}

		internal static void DisarmTunnelDecay(VoidJumpSystem system)
		{
			if ((Object)(object)system != (Object)null)
			{
				_tunnelDecayArm.Remove(system);
			}
		}

		internal static bool TryConsumeTunnelDecayArm(VoidJumpSystem system)
		{
			if ((Object)(object)system == (Object)null || !_tunnelDecayArm.TryGetValue(system, out var _))
			{
				return false;
			}
			_tunnelDecayArm.Remove(system);
			return true;
		}

		internal static void SkipClock(VoidJumpSystem system)
		{
			if ((Object)(object)system != (Object)null)
			{
				_clocks.Remove(system);
			}
			LastPercentRemaining = 100f;
		}

		private static float HullFraction(VoidJumpSystem system)
		{
			PlayerControlledShip val = (((Object)(object)system != (Object)null) ? system.PlayerShip : null);
			if ((Object)(object)val == (Object)null)
			{
				return 1f;
			}
			float maxHitPointsValue = ((OrbitObject)val).MaxHitPointsValue;
			if (!(maxHitPointsValue > 0f))
			{
				return 1f;
			}
			return Mathf.Clamp01(((OrbitObject)val).HitPoints / maxHitPointsValue);
		}

		private static float PowerVariable(VoidJumpSystem system)
		{
			PlayerControlledShip val = (((Object)(object)system != (Object)null) ? system.PlayerShip : null);
			PowerSystem val2 = (((Object)(object)val != (Object)null) ? val.ShipsPowerSystem : null);
			if ((Object)(object)val2 == (Object)null)
			{
				return ReferencePower;
			}
			float num = ((PowerDistributor)val2).PowerGenerated();
			float num2 = ((PowerDistributor)val2).PowerUsed();
			if (num <= 0f)
			{
				return ReferencePower;
			}
			return 3f * num + 2f * Mathf.Max(0f, num2 - num);
		}

		private static float VanillaChance()
		{
			Quest val = GameSessionManager.ActiveSession?.ActiveQuest;
			if (val == null)
			{
				return 0f;
			}
			return Mathf.Clamp01(val.CurrentInterdictionChance);
		}

		private static float ClockLengthSeconds(float powerVariable, float vanillaChance)
		{
			float num = Mathf.Pow(ReferencePower / Mathf.Max(1f, powerVariable), VtdTuning.PowerExponent.Value);
			float num2 = Mathf.Pow(Mathf.Max(0f, 1f - vanillaChance), VtdTuning.ChanceExponent.Value);
			return Mathf.Max(TickIntervalSeconds, BaseClockSeconds * num * num2);
		}

		internal static void ResetClock(VoidJumpSystem system)
		{
			float num = PowerVariable(system);
			float num2 = VanillaChance();
			float num3 = ClockLengthSeconds(num, num2);
			_clocks.Remove(system);
			_clocks.Add(system, new StabilityClock
			{
				StartTimestamp = PhotonNetwork.ServerTimestamp,
				LastTickProcessed = -1,
				LengthSeconds = num3,
				X = 0f,
				LastHullFraction = HullFraction(system),
				PowerVariable = num,
				VanillaChance = num2
			});
			LastPowerVariable = num;
			LastVanillaChance = num2;
			LastClockLengthSeconds = num3;
			LastPercentRemaining = 100f;
			VtdFlicker.HostBegin();
			Plugin.Log.LogInfo((object)("VoidTunnelDecay: clock started - P=" + num.ToString("0.#") + " (ref " + ReferencePower.ToString("0.#") + ") c=" + num2.ToString("0.###") + " => T_max=" + num3.ToString("0.#") + "s"));
		}

		internal static bool RollForInterdiction(VoidJumpSystem system)
		{
			if (!_clocks.TryGetValue(system, out var value))
			{
				return false;
			}
			int num = Mathf.Max(1, Mathf.RoundToInt(TickIntervalSeconds * 1000f));
			int num2 = (PhotonNetwork.ServerTimestamp - value.StartTimestamp) / num;
			if (num2 <= value.LastTickProcessed)
			{
				return false;
			}
			float num3 = HullFraction(system);
			float num4 = Mathf.Max(0f, num3 - value.LastHullFraction);
			float num5 = num4 * VtdTuning.RepairCredit.Value;
			value.LastHullFraction = num3;
			float num6 = TickIntervalSeconds / value.LengthSeconds;
			for (int i = value.LastTickProcessed + 1; i <= num2; i++)
			{
				float x = value.X;
				float num7 = Mathf.Clamp01(x - num5 + num6);
				num5 = 0f;
				value.X = num7;
				float num8 = InterdictionGate(x, num7);
				float value2 = Random.value;
				bool flag = value2 < num8;
				Plugin.Log.LogDebug((object)("VoidTunnelDecay: tick " + i + " hull=" + num3.ToString("0.##") + ((num4 > 0f) ? (" (+" + num4.ToString("0.###") + " repaired)") : "") + " x=" + num7.ToString("0.###") + " gate=" + num8.ToString("0.###") + " roll=" + value2.ToString("0.###") + (flag ? " => INTERDICT" : " => survives")));
				num4 = 0f;
				LastGateValue = num8;
				LastRollValue = value2;
				if (flag)
				{
					value.LastTickProcessed = i;
					LastPercentRemaining = (1f - num7) * 100f;
					VtdFlicker.HostStop();
					return true;
				}
				VtdFlicker.HostTick(num7, num8);
			}
			value.LastTickProcessed = num2;
			LastPercentRemaining = PercentRemaining(system);
			return false;
		}

		internal static float TargetCdf(float x)
		{
			float num = (float)Math.PI * Mathf.Clamp01(x) / 2f;
			float num2 = Mathf.Pow(Mathf.Sin(num), VtdTuning.ShapeA.Value);
			return 1f - Mathf.Cos(num) * Mathf.Pow(Mathf.Max(0f, 1f - num2), VtdTuning.ShapeB.Value);
		}

		internal static float InterdictionGate(float xPrev, float x)
		{
			if (x >= 1f)
			{
				return 1f;
			}
			if (x <= xPrev)
			{
				return 0f;
			}
			float num = 1f - TargetCdf(xPrev);
			if (num <= 0f)
			{
				return 1f;
			}
			return Mathf.Clamp01(1f - (1f - TargetCdf(x)) / num);
		}

		internal static float PercentRemaining(VoidJumpSystem system)
		{
			if (!_clocks.TryGetValue(system, out var value))
			{
				return 100f;
			}
			float num = Mathf.Clamp01((float)(PhotonNetwork.ServerTimestamp - value.StartTimestamp) / (TickIntervalSeconds * 1000f) - (float)(value.LastTickProcessed + 1));
			float num2 = TickIntervalSeconds / value.LengthSeconds;
			return Mathf.Clamp((1f - value.X - num * num2) * 100f, 0f, 100f);
		}
	}
	internal static class VtdReflection
	{
		internal static readonly FieldInfo VoidJumpSystemField = AccessTools.Field(typeof(VoidJumpState), "voidJumpSystem");
	}
	[HarmonyPatch(typeof(VoidJumpTravellingStable), "OnEnter")]
	internal static class Patch_Stable_OnEnter
	{
		private static void Postfix(VoidJumpTravellingStable __instance)
		{
			try
			{
				object? value = VtdReflection.VoidJumpSystemField.GetValue(__instance);
				VoidJumpSystem val = (VoidJumpSystem)((value is VoidJumpSystem) ? value : null);
				if (val == null)
				{
					return;
				}
				bool flag = VtdMutator.TryConsumeTunnelDecayArm(val);
				if (VtdMutator.Enforcing)
				{
					if (!flag)
					{
						VtdMutator.SkipClock(val);
						Plugin.Log.LogInfo((object)"VoidTunnelDecay: entered the tunnel without spinning up (restored save, or a forced transition) - decay clock not started for this jump.");
					}
					else
					{
						VtdMutator.ResetClock(val);
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("VoidTunnelDecay: OnEnter hook failed, clock not started for this jump: " + ex));
			}
		}
	}
	[HarmonyPatch(typeof(VoidJumpTravellingStable), "OnExit")]
	internal static class Patch_Stable_OnExit
	{
		private static void Postfix()
		{
			if (!VtdMutator.Enforcing)
			{
				return;
			}
			try
			{
				VtdFlicker.HostStop();
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("VoidTunnelDecay: OnExit hook failed: " + ex));
			}
		}
	}
	[HarmonyPatch(typeof(VoidJumpSpinningUp), "OnEnter")]
	internal static class Patch_SpinningUp_OnEnter
	{
		private static void Postfix(VoidJumpSpinningUp __instance)
		{
			try
			{
				object? value = VtdReflection.VoidJumpSystemField.GetValue(__instance);
				VoidJumpSystem val = (VoidJumpSystem)((value is VoidJumpSystem) ? value : null);
				if (val != null)
				{
					VtdMutator.ArmTunnelDecay(val);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("VoidTunnelDecay: spin-up arm hook failed - decay clock will not start for this jump: " + ex));
			}
		}
	}
	[HarmonyPatch(typeof(VoidJumpTravellingStable), "UpdateState")]
	internal static class Patch_Stable_UpdateState
	{
		private static void Postfix(VoidJumpTravellingStable __instance)
		{
			if (!VtdMutator.Enforcing)
			{
				return;
			}
			try
			{
				object? value = VtdReflection.VoidJumpSystemField.GetValue(__instance);
				VoidJumpSystem val = (VoidJumpSystem)((value is VoidJumpSystem) ? value : null);
				if (val != null && (object)val.ActiveState == __instance && VtdMutator.RollForInterdiction(val))
				{
					val.ChangeActiveState<VoidJumpTravellingUnstable>();
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("VoidTunnelDecay: UpdateState hook failed for this tick: " + ex));
			}
		}
	}
	[HarmonyPatch(typeof(TunnelStabilityTextController), "UpdateVoidTunnelStability", new Type[] { typeof(int) })]
	internal static class Patch_HudStability
	{
		private static void Prefix(ref int stabilityValue)
		{
			if (!VtdMutator.Enforcing)
			{
				return;
			}
			try
			{
				stabilityValue = Mathf.RoundToInt((float)stabilityValue * (VtdMutator.LastPercentRemaining / 100f));
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("VoidTunnelDecay: HUD stability scale failed, showing vanilla's raw number: " + ex));
			}
		}
	}
	[HarmonyPatch(typeof(UnlockContainer), "IsUnlocked")]
	internal static class Patch_IsUnlocked
	{
		private static bool Prefix(GUIDUnion guid, ref bool __result)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (!VtdRegistration.Registered || !VtdRegistration.IsOurs(guid))
				{
					return true;
				}
				__result = true;
				return false;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("VoidTunnelDecay: IsUnlocked override failed, falling back to vanilla check: " + ex));
				return true;
			}
		}
	}
	internal static class VtdRegistration
	{
		public static string Status = "not registered yet";

		public static string LastError = "";

		private static string _stage = "<none>";

		private static GUIDUnion _ourGuid;

		private static bool _guidParsed;

		private static Mutator _mutator;

		private static MutatorDef _def;

		private static ContextInfo _ctx;

		public static bool Registered { get; private set; }

		public static GUIDUnion OurGuid
		{
			get
			{
				//IL_001c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0012: Unknown result type (might be due to invalid IL or missing references)
				//IL_0017: Unknown result type (might be due to invalid IL or missing references)
				if (!_guidParsed)
				{
					_guidParsed = true;
					_ourGuid = new GUIDUnion("7cb9e33adfb6443888d7fe719c27e65d");
				}
				return _ourGuid;
			}
		}

		public unsafe static bool IsOurs(GUIDUnion guid)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			return ((object)(*(GUIDUnion*)(&guid))/*cast due to .constrained prefix*/).Equals((object?)OurGuid);
		}

		public static bool ContainersReady()
		{
			try
			{
				return (Object)(object)ResourceAssetContainerRegister.Instance != (Object)null && RuntimeAssetsRegister.Instance != null && (Object)(object)ResourceAssetContainer<MutatorContainer, Mutator, MutatorDef>.Instance != (Object)null && (Object)(object)ResourceAssetContainer<UnlockContainer, Object, UnlockItemDef>.Instance != (Object)null;
			}
			catch
			{
				return false;
			}
		}

		public static bool EnsureRegistered()
		{
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_017f: Unknown result type (might be due to invalid IL or missing references)
			//IL_012c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0136: Expected O, but got Unknown
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0145: Unknown result type (might be due to invalid IL or missing references)
			//IL_014f: Expected O, but got Unknown
			//IL_01d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_018b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0192: Expected O, but got Unknown
			//IL_01b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e0: Unknown result type (might be due to invalid IL or missing references)
			if (Registered)
			{
				return true;
			}
			if (!ContainersReady())
			{
				Status = "waiting for asset containers";
				return false;
			}
			try
			{
				_stage = "find icon sprite";
				Sprite val = FindLoadedSprite("icon_voidsanction");
				if ((Object)(object)val == (Object)null)
				{
					Plugin.Log.LogWarning((object)"VoidTunnelDecay: no loaded sprite named 'icon_voidsanction' yet - the mutator will register with a blank icon until something else loads it this session.");
				}
				_stage = "create Mutator";
				if ((Object)(object)_mutator == (Object)null)
				{
					_mutator = ScriptableObject.CreateInstance<Mutator>();
					((Object)_mutator).name = "Mutator_InterdictionDecay";
					Keep((Object)(object)_mutator);
				}
				((SelfReferencingScriptableObject)_mutator).ContainerGuid = OurGuid;
				if (_mutator.Effects == null)
				{
					_mutator.Effects = new List<MutatorEffect>();
				}
				_mutator.RunXPBonus = 0.25f;
				_stage = "create ContextInfo";
				if ((Object)(object)_ctx == (Object)null)
				{
					_ctx = ContextInfo.Create(val, "Void Tunnel Decay", "Void-tunnel stability decays on a clock while travelling, independent of the normal chance of interdiction. Bigger reactors and overloaded grids draw the void's attention sooner; repairs made in the void buy time. Left alone long enough, interdiction stops being a matter of luck.");
					Keep((Object)(object)_ctx);
				}
				else if ((Object)(object)val != (Object)null && (Object)(object)_ctx.Icon == (Object)null)
				{
					_ctx.Icon = val;
				}
				_stage = "create def";
				if (_def == null)
				{
					_def = new MutatorDef();
					((ResourceAssetDef<Mutator>)(object)_def).Ref = new ResourceAssetRef(OurGuid, "Mutators/Mutator_InterdictionDecay");
					((ResourceAssetDef<Mutator>)(object)_def).Ref.IsRuntime = true;
				}
				_def.ContextInfo = (IResourceAssetContextInfo)(object)_ctx;
				_stage = "RuntimeAssetsRegister";
				RuntimeAssetsRegister instance = RuntimeAssetsRegister.Instance;
				if (!instance.HasAsset(OurGuid))
				{
					RuntimeAssetInfo val2 = new RuntimeAssetInfo();
					val2.Name = ((Object)_mutator).name;
					val2.DisplayName = "Void Tunnel Decay";
					instance.RegisterAsset(OurGuid, (Object)(object)_mutator, (SessionModificationEffect)0, val2);
				}
				_stage = "MutatorContainer";
				MutatorContainer instance2 = ResourceAssetContainer<MutatorContainer, Mutator, MutatorDef>.Instance;
				if (!((ResourceAssetContainer<MutatorContainer, Mutator, MutatorDef>)(object)instance2).HasItem(OurGuid))
				{
					((ResourceAssetContainer<MutatorContainer, Mutator, MutatorDef>)(object)instance2).RegisterRuntimeAsset(OurGuid, _def);
				}
				_stage = "AssetDescriptions";
				AppendToAssetDescriptions(instance2, _def);
				_stage = "UnlockContainer";
				RegisterUnlock();
				_stage = "verify";
				string text = Verify();
				if (text != null)
				{
					Fail("verification failed: " + text);
					return false;
				}
				Registered = true;
				LastError = "";
				Status = "registered as Void Tunnel Decay (7cb9e33adfb6443888d7fe719c27e65d)";
				Plugin.Log.LogInfo((object)("VoidTunnelDecay: " + Status));
				return true;
			}
			catch (Exception ex)
			{
				Fail("threw at stage '" + _stage + "': " + ex);
				return false;
			}
		}

		private static void Fail(string msg)
		{
			LastError = msg;
			Status = "FAILED - " + msg;
			Plugin.Log.LogError((object)("VoidTunnelDecay: " + msg));
		}

		private static Sprite FindLoadedSprite(string name)
		{
			if (string.IsNullOrEmpty(name))
			{
				return null;
			}
			Sprite[] array = Resources.FindObjectsOfTypeAll<Sprite>();
			foreach (Sprite val in array)
			{
				if ((Object)(object)val != (Object)null && ((Object)val).name == name)
				{
					return val;
				}
			}
			return null;
		}

		private static void RegisterUnlock()
		{
			//IL_0010: 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_0023: Expected O, but got Unknown
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Expected O, but got Unknown
			//IL_0046: 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_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			UnlockContainer instance = ResourceAssetContainer<UnlockContainer, Object, UnlockItemDef>.Instance;
			if (!((Object)(object)instance == (Object)null) && !((ResourceAssetContainer<UnlockContainer, Object, UnlockItemDef>)(object)instance).HasItem(OurGuid))
			{
				UnlockItemDef val = new UnlockItemDef();
				((ResourceAssetDef<Object>)(object)val).Ref = new ResourceAssetRef(OurGuid, "Mutators/Mutator_InterdictionDecay");
				((ResourceAssetDef<Object>)(object)val).Ref.IsRuntime = true;
				val.rarity = (RarityType)3;
				object obj = (object)default(UnlockOptions);
				Refl.SetField(obj, "UnlockCriteria", (object)(UnlockCriteriaType)8);
				Refl.SetField(obj, "RankRequirement", 0);
				if (!Refl.SetField(val, "unlockOptions", obj))
				{
					Plugin.Log.LogWarning((object)"VoidTunnelDecay: could not set UnlockItemDef.unlockOptions");
				}
				((ResourceAssetContainer<UnlockContainer, Object, UnlockItemDef>)(object)instance).RegisterRuntimeAsset(OurGuid, val);
			}
		}

		private static void AppendToAssetDescriptions(MutatorContainer container, MutatorDef def)
		{
			try
			{
				if (Refl.GetField(container, "AssetDescriptions") is List<MutatorDef> list && !list.Contains(def))
				{
					list.Add(def);
				}
			}
			catch (Exception e)
			{
				Plugin.Log.LogWarning((object)("VoidTunnelDecay: appending to AssetDescriptions threw: " + Refl.Brief(e)));
			}
		}

		private static string Verify()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			MutatorDef val = default(MutatorDef);
			if (!((ResourceAssetContainer<MutatorContainer, Mutator, MutatorDef>)(object)ResourceAssetContainer<MutatorContainer, Mutator, MutatorDef>.Instance).TryGetByGuid(OurGuid, ref val) || val == null)
			{
				return "container does not resolve our GUID";
			}
			Mutator asset = ((ResourceAssetDef<Mutator>)(object)val).Asset;
			if ((Object)(object)asset == (Object)null)
			{
				return "def.Asset is null (RuntimeAssetsRegister lookup or IsRuntime flag)";
			}
			if (asset.Effects == null)
			{
				return "Mutator.Effects is null - Mutator.Activate iterates it unguarded";
			}
			if (val.ContextInfo == null)
			{
				return "ContextInfo is null - the selection menu tooltip dereferences it unguarded";
			}
			if (string.IsNullOrEmpty(val.ContextInfo.HeaderText))
			{
				return "ContextInfo.HeaderText is empty";
			}
			if (!((ResourceAssetContainer<UnlockContainer, Object, UnlockItemDef>)(object)ResourceAssetContainer<UnlockContainer, Object, UnlockItemDef>.Instance).HasItem(OurGuid))
			{
				return "no UnlockContainer entry - UpdateMutatorStates dereferences it unguarded";
			}
			return null;
		}

		private static void Keep(Object o)
		{
			if (!(o == (Object)null))
			{
				o.hideFlags = (HideFlags)61;
				Object.DontDestroyOnLoad(o);
			}
		}
	}
	internal static class VtdTuning
	{
		internal static TuningFile File;

		internal static TuningScalar BaseClockSeconds;

		internal static TuningScalar BasePower;

		internal static TuningScalar PowerExponent;

		internal static TuningScalar ChanceExponent;

		internal static TuningScalar ShapeA;

		internal static TuningScalar ShapeB;

		internal static TuningScalar RepairCredit;

		internal static TuningScalar FlickerRate;

		internal static TuningScalar FlickerMinSeconds;

		internal static TuningScalar FlickerMaxSeconds;

		internal static TuningScalar FlickerDurationFuzz;

		internal static TuningScalar FlickerLightsChance;

		internal static TuningScalar FlickerColorChance;

		internal static TuningScalar FlickerShakeChance;

		internal static TuningScalar FlickerPeak;

		internal static TuningScalar FlickerRestrikeChance;

		internal static TuningScalar FlickerRestrikeDecay;

		internal static TuningScalar FlickerGapMinSeconds;

		internal static TuningScalar FlickerGapMaxSeconds;

		internal static TuningScalar FlickerColorAttackSeconds;

		internal static TuningScalar FlickerColorReleaseSeconds;

		internal static TuningScalar FlickerShakeIntensity;

		internal static TuningScalar FlickerShakeFrequency;

		internal static void Initialise(string configDir)
		{
			File = new TuningFile("jack.voidcrew.voidtunneldecay", "VoidTunnelDecay", "Testing-only knobs for the interdiction clock. T_max = BaseClockSeconds * (3*BasePower / P)^PowerExponent * (1 - c)^ChanceExponent, where P = 3G + 2*max(0, U - G) from the ship's generated/used power and c is the vanilla per-jump interdiction chance. The clock x runs 0 -> 1 over T_max; a hull repair of r moves it back by r*RepairCredit. Interdiction time has cumulative distribution F(x) = 1 - cos(pi*x/2)*(1 - sin(pi*x/2)^ShapeA)^ShapeB. Never shipped - bake into source before packaging a build for anyone else.", Plugin.Log);
			BaseClockSeconds = File.Add(new TuningScalar("BaseClockSeconds", "Clock length for a ship at BasePower with no overload on a jump whose vanilla interdiction chance is 0. Every other ship's clock is this scaled by power and chance.", 240f, 30f, 1200f));
			BasePower = File.Add(new TuningScalar("BasePower", "Generated power of the reference ship. A ship generating this much, with no overload, runs the full BaseClockSeconds.", 6f, 1f, 60f));
			PowerExponent = File.Add(new TuningScalar("PowerExponent", "How hard power shortens the clock: T_max scales by (P_ref / P)^PowerExponent. 1 is inverse-proportional; 0 ignores power.", 0.75f, 0f, 3f));
			ChanceExponent = File.Add(new TuningScalar("ChanceExponent", "How hard the vanilla per-jump interdiction chance shortens the clock: T_max scales by (1 - c)^ChanceExponent. 0 ignores it.", 1.1f, 0f, 3f));
			ShapeA = File.Add(new TuningScalar("ShapeA", "Exponent on sin in the target distribution. Raising it pushes interdictions later in the clock and flattens the early stretch.", 9f, 0.25f, 32f));
			ShapeB = File.Add(new TuningScalar("ShapeB", "Outer exponent in the target distribution. Raising it pulls interdictions earlier and tightens their spread.", 3f, 0.1f, 16f));
			RepairCredit = File.Add(new TuningScalar("RepairCredit", "Clock fraction bought back per unit of hull fraction repaired during the jump. At 2, repairing 10% of the hull rewinds the clock by 20% of T_max. The clock never rewinds below its start; damage taken in the void does nothing.", 2f, 0f, 10f));
			FlickerRate = File.Add(new TuningScalar("FlickerRate", "Tunnel flickers per second when the per-tick gate is 1; the live rate is this times the gate, so it is near zero early in the clock. 0 disables flickers.", 50f, 0f, 200f));
			FlickerMinSeconds = File.Add(new TuningScalar("FlickerMinSeconds", "Flicker hold time at the start of the clock, before fuzz.", 0.08f, 0.02f, 2f));
			FlickerMaxSeconds = File.Add(new TuningScalar("FlickerMaxSeconds", "Flicker hold time at the end of the clock, before fuzz. Hold time lerps from min to max with clock position.", 0.21f, 0.02f, 3f));
			FlickerDurationFuzz = File.Add(new TuningScalar("FlickerDurationFuzz", "Every timed quantity in a burst - stroke hold, dark gap, restrike decay, colour attack and release - is multiplied by its own random factor in [1 - fuzz, 1 + fuzz], so no two bursts have the same shape.", 0.5f, 0f, 0.95f));
			FlickerLightsChance = File.Add(new TuningScalar("FlickerLightsChance", "Multiplier on the chance a flicker swaps the stable light source for the unstable (red, self-flickering) one. Base chance 1 at every clock position.", 1f, 0f, 4f));
			FlickerColorChance = File.Add(new TuningScalar("FlickerColorChance", "Multiplier on the chance a flicker swaps the directional colour grade to the unstable one. Base chance sqrt(x).", 1f, 0f, 4f));
			FlickerShakeChance = File.Add(new TuningScalar("FlickerShakeChance", "Multiplier on the chance a flicker jolts the camera. Base chance x^2, so it is rare until the clock is nearly spent.", 1f, 0f, 4f));
			FlickerPeak = File.Add(new TuningScalar("FlickerPeak", "Brightness multiplier on the unstable light and tunnel emission at the instant a stroke lands, falling off cubically to 1 by the end of its hold. 1 is a plain switch with no overshoot.", 2.2f, 1f, 6f));
			FlickerRestrikeChance = File.Add(new TuningScalar("FlickerRestrikeChance", "After each stroke, the chance of another after a short gap (up to 4 strokes per burst). Each restrike is dimmer and shorter by FlickerRestrikeDecay.", 0.55f, 0f, 1f));
			FlickerRestrikeDecay = File.Add(new TuningScalar("FlickerRestrikeDecay", "Per-restrike multiplier on hold time and on the overshoot above 1, fuzzed per restrike.", 0.65f, 0.1f, 1f));
			FlickerGapMinSeconds = File.Add(new TuningScalar("FlickerGapMinSeconds", "Shortest dark gap between strokes of one burst, before fuzz.", 0.03f, 0f, 1f));
			FlickerGapMaxSeconds = File.Add(new TuningScalar("FlickerGapMaxSeconds", "Longest dark gap between strokes of one burst, before fuzz.", 0.14f, 0f, 2f));
			FlickerColorAttackSeconds = File.Add(new TuningScalar("FlickerColorAttackSeconds", "Blend time into the unstable colour grade when a stroke lands. Near 0 is a snap.", 0.01f, 0f, 2f));
			FlickerColorReleaseSeconds = File.Add(new TuningScalar("FlickerColorReleaseSeconds", "Blend time back to the stable colour grade when a stroke ends - longer than the attack so the grade lingers like an afterimage. Vanilla's own transition uses 2.", 0.15f, 0f, 2f));
			FlickerShakeIntensity = File.Add(new TuningScalar("FlickerShakeIntensity", "CameraShakeDetail.Intensity of a shake flicker.", 3f, 0f, 10f));
			FlickerShakeFrequency = File.Add(new TuningScalar("FlickerShakeFrequency", "CameraShakeDetail.Frequency of a shake flicker.", 20f, 0f, 100f));
			File.Initialise(configDir);
		}

		internal static bool PollForChanges()
		{
			if (File != null)
			{
				return File.PollForChanges();
			}
			return false;
		}
	}
}