Decompiled source of WispReach v1.1.0

BepInEx/plugins/WispReach/WispReach.dll

Decompiled 4 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")]
[assembly: AssemblyCompany("WispReach")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: AssemblyInformationalVersion("1.1.0")]
[assembly: AssemblyProduct("WispReach")]
[assembly: AssemblyTitle("WispReach")]
[assembly: AssemblyVersion("1.1.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace WispReach
{
	internal sealed class FaultRecovery
	{
		public const int MaximumRetries = 3;

		public const float RetryDelay = 5f;

		private int failures;

		private int context;

		private float retryAt;

		public bool Faulted { get; private set; }

		public bool Exhausted
		{
			get
			{
				if (Faulted)
				{
					return failures > 3;
				}
				return false;
			}
		}

		public void Fail(float now)
		{
			failures++;
			Faulted = true;
			retryAt = now + 5f;
		}

		public bool TryResume(float now)
		{
			if (!Faulted || Exhausted || now < retryAt)
			{
				return false;
			}
			Faulted = false;
			return true;
		}

		public bool ObserveContext(int instanceId)
		{
			if (instanceId == 0 || instanceId == context)
			{
				return false;
			}
			bool faulted = Faulted;
			context = instanceId;
			Reset();
			return faulted;
		}

		public void Reset()
		{
			failures = 0;
			Faulted = false;
			retryAt = 0f;
		}
	}
	internal sealed class MistTransition
	{
		private struct State
		{
			public float Density;

			public float Local;

			public float Hidden;

			public RadiusPair Radii;
		}

		public const float Duration = 0.5f;

		private State current = Native();

		private State start = Native();

		private State target = Native();

		private float elapsed = 0.5f;

		public bool LocalActive
		{
			get
			{
				if (!(current.Local > 0f))
				{
					return target.Local > 0f;
				}
				return true;
			}
		}

		public bool IsNative
		{
			get
			{
				if (current.Density == 1f && current.Local == 0f && current.Hidden == 0f)
				{
					return elapsed >= 0.5f;
				}
				return false;
			}
		}

		public bool FullyHidden
		{
			get
			{
				if (current.Hidden != 1f)
				{
					return current.Density == 0f;
				}
				return true;
			}
		}

		public float LocalStrength => current.Local;

		public RadiusPair Radii => current.Radii;

		public float GlobalFactor => current.Density * (1f - current.Hidden);

		private static State Native()
		{
			return new State
			{
				Density = 1f,
				Radii = new RadiusPair(15f, 15f)
			};
		}

		private static float Lerp(float a, float b, float t)
		{
			return a + (b - a) * t;
		}

		public void Advance(RadiusPair radii, float density, bool local, bool hidden, float deltaTime)
		{
			density = (float.IsNaN(density) ? 1f : Math.Max(0f, Math.Min(1f, density)));
			State state = new State
			{
				Density = density,
				Local = (local ? 1f : 0f),
				Hidden = (hidden ? 1f : 0f),
				Radii = radii
			};
			if (state.Density != target.Density || state.Local != target.Local || state.Hidden != target.Hidden || state.Radii.Inner != target.Radii.Inner || state.Radii.Outer != target.Radii.Outer)
			{
				start = current;
				target = state;
				elapsed = 0f;
			}
			if (float.IsNaN(deltaTime) || deltaTime < 0f)
			{
				deltaTime = 0f;
			}
			elapsed = Math.Min(0.5f, elapsed + deltaTime);
			if (elapsed >= 0.5f)
			{
				current = target;
				return;
			}
			float num = elapsed / 0.5f;
			num = num * num * (3f - 2f * num);
			current = new State
			{
				Density = Lerp(start.Density, target.Density, num),
				Local = Lerp(start.Local, target.Local, num),
				Hidden = Lerp(start.Hidden, target.Hidden, num),
				Radii = new RadiusPair(Lerp(start.Radii.Inner, target.Radii.Inner, num), Lerp(start.Radii.Outer, target.Radii.Outer, num))
			};
		}

		public float Factor(float distance)
		{
			float num = RadiusRules.DensityFactor(distance, current.Radii.Inner, current.Radii.Outer);
			return current.Density * (1f - current.Local * (1f - num)) * (1f - current.Hidden);
		}

		public void Reset()
		{
			current = (start = (target = Native()));
			elapsed = 0.5f;
		}
	}
	internal sealed class NativeMistFade : IDisposable
	{
		private ParticleSystem particles;

		private ParticleSystemRenderer renderer;

		private bool rendererOverridden;

		private bool originalRendererEnabled;

		private Particle[] buffer = (Particle[])(object)new Particle[0];

		private readonly ParticleAlphaCache alpha = new ParticleAlphaCache();

		private readonly HashSet<uint> seenSeeds = new HashSet<uint>();

		private readonly HashSet<uint> duplicateSeeds = new HashSet<uint>();

		private readonly MistTransition transition = new MistTransition();

		private string lastFailure;

		public bool Active => Object.op_Implicit((Object)(object)particles);

		public bool LocalActive => transition.LocalActive;

		public bool IsNative => transition.IsNative;

		public bool Ensure(ParticleMist mist, ManualLogSource log)
		{
			if (!Object.op_Implicit((Object)(object)mist))
			{
				Dispose();
				return false;
			}
			ParticleSystem component = ((Component)mist).GetComponent<ParticleSystem>();
			if ((Object)(object)particles == (Object)(object)component && Active)
			{
				return true;
			}
			Dispose();
			if (!Object.op_Implicit((Object)(object)component))
			{
				if (lastFailure != "ParticleMist has no ParticleSystem; native mist is preserved.")
				{
					log.LogWarning((object)"ParticleMist has no ParticleSystem; native mist is preserved.");
				}
				lastFailure = "ParticleMist has no ParticleSystem; native mist is preserved.";
				return false;
			}
			particles = component;
			renderer = ((Component)component).GetComponent<ParticleSystemRenderer>();
			string text = ((Object.op_Implicit((Object)(object)renderer) && Object.op_Implicit((Object)(object)((Renderer)renderer).sharedMaterial) && Object.op_Implicit((Object)(object)((Renderer)renderer).sharedMaterial.shader)) ? ((Object)((Renderer)renderer).sharedMaterial.shader).name : "<missing>");
			log.LogInfo((object)("WispReach attached to native ParticleMist particles; shader retained: " + text));
			lastFailure = null;
			return true;
		}

		private void HideRenderer()
		{
			if (Object.op_Implicit((Object)(object)particles) && Object.op_Implicit((Object)(object)renderer))
			{
				if (!rendererOverridden)
				{
					originalRendererEnabled = ((Renderer)renderer).enabled;
					rendererOverridden = true;
				}
				((Renderer)renderer).enabled = false;
			}
		}

		public void Update(Vector3 center, RadiusPair radii, float density, bool local, bool hidden, float deltaTime, IList<MistZone> zones = null)
		{
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: 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)
			//IL_00a2: Invalid comparison between Unknown and I4
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Unknown result type (might be due to invalid IL or missing references)
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0192: Unknown result type (might be due to invalid IL or missing references)
			//IL_0197: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0170: Unknown result type (might be due to invalid IL or missing references)
			if (!Object.op_Implicit((Object)(object)particles))
			{
				return;
			}
			transition.Advance(radii, density, local, hidden, deltaTime);
			int num = Read();
			seenSeeds.Clear();
			duplicateSeeds.Clear();
			for (int i = 0; i < num; i++)
			{
				if (!seenSeeds.Add(((Particle)(ref buffer[i])).randomSeed))
				{
					duplicateSeeds.Add(((Particle)(ref buffer[i])).randomSeed);
				}
			}
			MainModule main = particles.main;
			Transform val = (((int)((MainModule)(ref main)).simulationSpace == 0) ? ((Component)particles).transform : (((int)((MainModule)(ref main)).simulationSpace == 2) ? ((MainModule)(ref main)).customSimulationSpace : null));
			alpha.BeginFrame();
			for (int j = 0; j < num; j++)
			{
				Particle val2 = buffer[j];
				Vector3 val3 = (Object.op_Implicit((Object)(object)val) ? val.TransformPoint(((Particle)(ref val2)).position) : ((Particle)(ref val2)).position);
				bool flag = !duplicateSeeds.Contains(((Particle)(ref val2)).randomSeed);
				float num2 = (flag ? transition.Factor(transition.LocalActive ? Vector3.Distance(center, val3) : 0f) : 1f);
				if (flag && zones != null)
				{
					for (int k = 0; k < zones.Count; k++)
					{
						num2 = Math.Min(num2, transition.GlobalFactor * zones[k].Factor(val3));
					}
				}
				Color32 startColor = ((Particle)(ref val2)).startColor;
				startColor.a = ((!flag) ? alpha.Restore(((Particle)(ref val2)).randomSeed, ((Particle)(ref val2)).remainingLifetime, startColor.a) : alpha.Apply(((Particle)(ref val2)).randomSeed, ((Particle)(ref val2)).remainingLifetime, startColor.a, num2));
				((Particle)(ref val2)).startColor = startColor;
				buffer[j] = val2;
			}
			particles.SetParticles(buffer, num);
			alpha.EndFrame();
			if (transition.FullyHidden)
			{
				HideRenderer();
			}
			else
			{
				RestoreRenderer();
			}
		}

		private int Read()
		{
			//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)
			MainModule main = particles.main;
			int num = Math.Max(((MainModule)(ref main)).maxParticles, particles.particleCount);
			if (buffer.Length < num)
			{
				buffer = (Particle[])(object)new Particle[num];
			}
			return particles.GetParticles(buffer);
		}

		private void RestoreParticleAlpha()
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			if (Object.op_Implicit((Object)(object)particles))
			{
				int num = Read();
				for (int i = 0; i < num; i++)
				{
					Particle val = buffer[i];
					Color32 startColor = ((Particle)(ref val)).startColor;
					startColor.a = alpha.Restore(((Particle)(ref val)).randomSeed, ((Particle)(ref val)).remainingLifetime, startColor.a);
					((Particle)(ref val)).startColor = startColor;
					buffer[i] = val;
				}
				particles.SetParticles(buffer, num);
			}
			alpha.Clear();
			seenSeeds.Clear();
			duplicateSeeds.Clear();
		}

		private void RestoreRenderer()
		{
			if (rendererOverridden && Object.op_Implicit((Object)(object)renderer))
			{
				((Renderer)renderer).enabled = originalRendererEnabled;
			}
			rendererOverridden = false;
		}

		public void Dispose()
		{
			try
			{
				RestoreParticleAlpha();
			}
			finally
			{
				RestoreRenderer();
				renderer = null;
				particles = null;
				alpha.Clear();
				transition.Reset();
			}
		}
	}
	internal sealed class ParticleAlphaCache
	{
		private struct State
		{
			public byte Native;

			public byte Written;

			public float Remaining;

			public int Seen;
		}

		private readonly Dictionary<uint, State> states = new Dictionary<uint, State>();

		private readonly List<uint> expired = new List<uint>();

		private int frame;

		public void BeginFrame()
		{
			frame++;
		}

		public byte Apply(uint seed, float remaining, byte incoming, float factor)
		{
			if (!states.TryGetValue(seed, out var value) || remaining > value.Remaining + 0.001f || incoming != value.Written)
			{
				value.Native = incoming;
			}
			if (float.IsNaN(factor))
			{
				factor = 1f;
			}
			factor = Math.Max(0f, Math.Min(1f, factor));
			value.Written = (byte)Math.Round((float)(int)value.Native * factor);
			value.Remaining = remaining;
			value.Seen = frame;
			states[seed] = value;
			return value.Written;
		}

		public byte Restore(uint seed, float remaining, byte incoming)
		{
			if (states.TryGetValue(seed, out var value) && remaining <= value.Remaining + 0.001f && incoming == value.Written)
			{
				return value.Native;
			}
			return incoming;
		}

		public void EndFrame()
		{
			expired.Clear();
			foreach (KeyValuePair<uint, State> state in states)
			{
				if (state.Value.Seen != frame)
				{
					expired.Add(state.Key);
				}
			}
			foreach (uint item in expired)
			{
				states.Remove(item);
			}
		}

		public void Clear()
		{
			states.Clear();
			expired.Clear();
		}
	}
	internal enum RadiusChange
	{
		Load,
		Inner,
		Outer
	}
	internal struct RadiusPair
	{
		public float Inner;

		public float Outer;

		public RadiusPair(float inner, float outer)
		{
			Inner = inner;
			Outer = outer;
		}
	}
	internal static class RadiusRules
	{
		public const float DefaultInner = 15f;

		public const float DefaultOuter = 15f;

		public const float Minimum = 0f;

		public const float Maximum = 100f;

		public static bool ShouldApply(bool enabled, bool removeAllMist, RadiusPair radii, int densityPercent = 100, bool torches = false)
		{
			if (enabled)
			{
				return removeAllMist || densityPercent != 100 || CustomRadii(radii) || torches;
			}
			return false;
		}

		public static bool CustomRadii(RadiusPair radii)
		{
			if (radii.Inner == 15f)
			{
				return radii.Outer != 15f;
			}
			return true;
		}

		public static RadiusPair Normalize(float inner, float outer, RadiusChange change)
		{
			inner = Sanitize(inner, 15f);
			outer = Sanitize(outer, 15f);
			if (outer < inner)
			{
				outer = inner;
			}
			return new RadiusPair(inner, outer);
		}

		public static RadiusPair ShiftInner(float inner, RadiusPair previous)
		{
			inner = Sanitize(inner, 15f);
			previous = Normalize(previous.Inner, previous.Outer, RadiusChange.Load);
			return Normalize(inner, inner + previous.Outer - previous.Inner, RadiusChange.Inner);
		}

		private static float Sanitize(float value, float fallback)
		{
			if (float.IsNaN(value) || float.IsInfinity(value))
			{
				value = fallback;
			}
			return Math.Max(0f, Math.Min(100f, value));
		}

		public static int WholeMeters(float value, float fallback)
		{
			return (int)Math.Round(Sanitize(value, fallback), MidpointRounding.AwayFromZero);
		}

		public static float DensityFactor(float distance, float inner, float outer)
		{
			if (outer <= inner)
			{
				if (!(distance < inner))
				{
					return 1f;
				}
				return 0f;
			}
			float num = Math.Max(0f, Math.Min(1f, (distance - inner) / (outer - inner)));
			return num * num * (3f - 2f * num);
		}
	}
	internal sealed class ConfigurationManagerAttributes
	{
		public bool? ShowRangeAsPercent = false;

		public bool? IsAdvanced = false;

		public int? Order;
	}
	internal sealed class ReachSettings
	{
		private readonly ConfigFile file;

		private bool normalizing;

		public readonly ConfigEntry<bool> Enabled;

		public readonly ConfigEntry<bool> RemoveAllMist;

		public readonly ConfigEntry<int> Inner;

		public readonly ConfigEntry<int> Outer;

		public readonly ConfigEntry<int> Density;

		public readonly ConfigEntry<bool> LinkRadii;

		public readonly ConfigEntry<bool> TorchEnabled;

		public readonly ConfigEntry<int> TorchInner;

		public readonly ConfigEntry<int> TorchOuter;

		public RadiusPair Radii { get; private set; }

		public RadiusPair TorchRadii { get; private set; }

		public bool ShouldApply => RadiusRules.ShouldApply(Enabled.Value, RemoveAllMist.Value, Radii, Density.Value, TorchEnabled.Value);

		public event Action Changed;

		public ReachSettings(ConfigFile file)
		{
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Expected O, but got Unknown
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Expected O, but got Unknown
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Expected O, but got Unknown
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fe: 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
			//IL_0174: Unknown result type (might be due to invalid IL or missing references)
			//IL_017e: Expected O, but got Unknown
			//IL_01b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ba: Expected O, but got Unknown
			//IL_01f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fe: Expected O, but got Unknown
			//IL_0238: Unknown result type (might be due to invalid IL or missing references)
			//IL_0242: Expected O, but got Unknown
			this.file = file;
			Enabled = file.Bind<bool>("General", "Enabled", true, new ConfigDescription("Enable WispReach. Switching modes fades over half a second. Disabling restores native mist and wisplight behavior while retaining your settings.", (AcceptableValueBase)null, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 5
				}
			}));
			RemoveAllMist = file.Bind<bool>("General", "Remove all Mistlands mist", false, new ConfigDescription("Hide all native Mistlands particle mist, even without an equipped wisplight. Overrides radius, density and torch settings while on. Ordinary weather fog in other biomes is unchanged. Turning this off smoothly restores your configured visibility. General / Enabled must be on.", (AcceptableValueBase)null, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 4
				}
			}));
			Inner = BindWholeRadius("R1 - Clear radius (m)", 15, new ConfigDescription("Fully clear Mistlands mist within this whole-meter radius of your equipped wisplight. Increasing R1 also increases R2 when necessary. The maximum is a finite 100 m radius; use the separate Remove all Mistlands mist checkbox for complete hiding.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 3
				}
			}));
			Outer = BindWholeRadius("R2 - Full mist radius (m)", 15, new ConfigDescription("Mist smoothly returns between R1 and this whole-meter radius. Outside R2 its opacity follows R3. R2 cannot be lower than R1. R1=15, R2=15 and R3=100 retain native wisplight behavior.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 2
				}
			}));
			Density = file.Bind<int>("Mist clearance", "R3 - Mist density (%)", 100, new ConfigDescription("Mist opacity in whole percentages: 100 is native mist, 0 is invisible. Applies outside R2 and throughout Mistlands without a wisplight. With a custom wisplight radius, R1 stays clear and opacity rises to this value at R2. Ordinary weather fog is unchanged.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 1
				}
			}));
			LinkRadii = file.Bind<bool>("Mist clearance", "Link R1 and R2", false, new ConfigDescription("When changing R1, move R2 by the same amount to preserve the transition width, up to the 100 m limit. You can still change R2 independently to choose a new width. Enabling this leaves saved radii unchanged.", (AcceptableValueBase)null, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 0
				}
			}));
			TorchEnabled = file.Bind<bool>("Wisp torches", "Enabled", false, new ConfigDescription("Use separate clear and transition radii for loaded stationary wisp torches. Disabling smoothly returns them to their original behavior. Does not change other players' wisplights or torch illumination. R3 sets the shared mist opacity.", (AcceptableValueBase)null, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 3
				}
			}));
			TorchInner = file.Bind<int>("Wisp torches", "T1 - Clear radius (m)", 24, new ConfigDescription("Clear radius around each loaded wisp torch in whole meters. Applies only when Wisp torches / Enabled is on. Increasing T1 raises T2 when needed.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 2
				}
			}));
			TorchOuter = file.Bind<int>("Wisp torches", "T2 - Full mist radius (m)", 24, new ConfigDescription("Mist returns gradually between T1 and T2, reaching R3 opacity outside T2. Cannot be lower than T1. Overlapping clear zones combine without increasing mist opacity.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Order = 1
				}
			}));
			Normalize(RadiusChange.Load);
			NormalizeTorches();
			Inner.SettingChanged += InnerChanged;
			Outer.SettingChanged += OuterChanged;
			Enabled.SettingChanged += EnabledChanged;
			RemoveAllMist.SettingChanged += EnabledChanged;
			Density.SettingChanged += EnabledChanged;
			LinkRadii.SettingChanged += EnabledChanged;
			TorchEnabled.SettingChanged += EnabledChanged;
			TorchInner.SettingChanged += TorchChanged;
			TorchOuter.SettingChanged += TorchChanged;
		}

		private ConfigEntry<int> BindWholeRadius(string key, int defaultValue, ConfigDescription description)
		{
			bool saveOnConfigSet = file.SaveOnConfigSet;
			file.SaveOnConfigSet = false;
			ConfigEntry<int> val2;
			try
			{
				ConfigEntry<float> val = file.Bind<float>("Mist clearance", key, (float)defaultValue, (ConfigDescription)null);
				int value = RadiusRules.WholeMeters(val.Value, defaultValue);
				file.Remove(((ConfigEntryBase)val).Definition);
				val2 = file.Bind<int>("Mist clearance", key, defaultValue, description);
				val2.Value = value;
			}
			finally
			{
				file.SaveOnConfigSet = saveOnConfigSet;
			}
			if (saveOnConfigSet)
			{
				file.Save();
			}
			return val2;
		}

		private void EnabledChanged(object sender, EventArgs args)
		{
			NotifyChanged();
		}

		private void InnerChanged(object sender, EventArgs args)
		{
			Normalize(RadiusChange.Inner);
		}

		private void OuterChanged(object sender, EventArgs args)
		{
			Normalize(RadiusChange.Outer);
		}

		private void TorchChanged(object sender, EventArgs args)
		{
			NormalizeTorches();
		}

		private void Normalize(RadiusChange change)
		{
			if (normalizing)
			{
				return;
			}
			normalizing = true;
			try
			{
				RadiusPair radii = ((LinkRadii.Value && change == RadiusChange.Inner) ? RadiusRules.ShiftInner(Inner.Value, Radii) : RadiusRules.Normalize(Inner.Value, Outer.Value, change));
				bool num = (float)Inner.Value != radii.Inner || (float)Outer.Value != radii.Outer;
				if ((float)Inner.Value != radii.Inner)
				{
					Inner.Value = (int)radii.Inner;
				}
				if ((float)Outer.Value != radii.Outer)
				{
					Outer.Value = (int)radii.Outer;
				}
				Radii = radii;
				if (num)
				{
					file.Save();
				}
			}
			finally
			{
				normalizing = false;
			}
			NotifyChanged();
		}

		private void NormalizeTorches()
		{
			if (normalizing)
			{
				return;
			}
			normalizing = true;
			try
			{
				RadiusPair torchRadii = RadiusRules.Normalize(TorchInner.Value, TorchOuter.Value, RadiusChange.Load);
				if ((float)TorchOuter.Value != torchRadii.Outer)
				{
					TorchOuter.Value = (int)torchRadii.Outer;
				}
				TorchRadii = torchRadii;
			}
			finally
			{
				normalizing = false;
			}
			NotifyChanged();
		}

		private void NotifyChanged()
		{
			if (this.Changed != null)
			{
				this.Changed();
			}
		}

		public void Dispose()
		{
			Inner.SettingChanged -= InnerChanged;
			Outer.SettingChanged -= OuterChanged;
			Enabled.SettingChanged -= EnabledChanged;
			RemoveAllMist.SettingChanged -= EnabledChanged;
			Density.SettingChanged -= EnabledChanged;
			LinkRadii.SettingChanged -= EnabledChanged;
			TorchEnabled.SettingChanged -= EnabledChanged;
			TorchInner.SettingChanged -= TorchChanged;
			TorchOuter.SettingChanged -= TorchChanged;
			this.Changed = null;
		}
	}
	internal static class TorchRules
	{
		public static bool IsWispTorchName(string name)
		{
			if (name == null)
			{
				return false;
			}
			if (!(name == "piece_groundtorch_mist"))
			{
				return name == "piece_groundtorch_mist(Clone)";
			}
			return true;
		}
	}
	[BepInPlugin("Slikfoul.WispReach", "WispReach", "1.1.0")]
	public sealed class WispReachPlugin : BaseUnityPlugin
	{
		[HarmonyPatch(typeof(SE_Demister), "UpdateStatusEffect")]
		private static class WispUpdatePatch
		{
			private static void Postfix(SE_Demister __instance, GameObject ___m_ballInstance)
			{
				if (Object.op_Implicit((Object)(object)Instance))
				{
					Instance.Observe(__instance, ___m_ballInstance);
				}
			}
		}

		[HarmonyPatch(typeof(SE_Demister), "Stop")]
		private static class WispStopPatch
		{
			private static void Prefix(SE_Demister __instance)
			{
				if (Object.op_Implicit((Object)(object)Instance))
				{
					Instance.Remove(__instance);
				}
			}
		}

		[HarmonyPatch(typeof(SE_Demister), "OnDestroy")]
		private static class WispDestroyPatch
		{
			private static void Prefix(SE_Demister __instance)
			{
				if (Object.op_Implicit((Object)(object)Instance))
				{
					Instance.Remove(__instance);
				}
			}
		}

		[HarmonyPatch(typeof(ParticleMist), "GetAllForcefields")]
		private static class MistEmissionPatch
		{
			private static void Postfix(List<Demister> fields)
			{
				if (!Object.op_Implicit((Object)(object)Instance))
				{
					return;
				}
				for (int num = fields.Count - 1; num >= 0; num--)
				{
					if (Instance.IsMasked(fields[num]))
					{
						fields.RemoveAt(num);
					}
				}
			}
		}

		public const string PluginGuid = "Slikfoul.WispReach";

		public const string PluginName = "WispReach";

		public const string PluginVersion = "1.1.0";

		private static readonly FieldInfo CharacterField = AccessTools.Field(typeof(StatusEffect), "m_character");

		internal static WispReachPlugin Instance;

		private ReachSettings settings;

		private Harmony harmony;

		private NativeMistFade overlay;

		private readonly WispTorchController torches = new WispTorchController();

		private SE_Demister effect;

		private GameObject ball;

		private Demister field;

		private ParticleSystemForceField force;

		private bool fieldOverridden;

		private bool originalForceEnabled;

		private float originalEndRange;

		private bool ready;

		private readonly FaultRecovery recovery = new FaultRecovery();

		private Vector3 lastCenter;

		private bool lastEnabled;

		internal Demister MaskedDemister
		{
			get
			{
				if (!ready || recovery.Faulted || !fieldOverridden || !overlay.Active)
				{
					return null;
				}
				return field;
			}
		}

		internal bool IsMasked(Demister candidate)
		{
			if (ready && !recovery.Faulted && overlay.Active)
			{
				if (!((Object)(object)candidate == (Object)(object)MaskedDemister))
				{
					return torches.IsMasked(candidate);
				}
				return true;
			}
			return false;
		}

		private void Awake()
		{
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Invalid comparison between Unknown and I4
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Expected O, but got Unknown
			Instance = this;
			settings = new ReachSettings(((BaseUnityPlugin)this).Config);
			lastEnabled = settings.Enabled.Value;
			settings.Changed += SettingsChanged;
			if ((int)SystemInfo.graphicsDeviceType == 4)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"WispReach is a client visual mod; no rendering patches on this headless host.");
				return;
			}
			if (CharacterField == null)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)"StatusEffect.m_character is missing; vanilla mist is preserved.");
				return;
			}
			overlay = new NativeMistFade();
			harmony = new Harmony("Slikfoul.WispReach");
			try
			{
				harmony.PatchAll(typeof(WispReachPlugin).Assembly);
				ready = true;
				((BaseUnityPlugin)this).Logger.LogInfo((object)("WispReach 1.1.0 native particle fade ready. R1=" + settings.Radii.Inner + " m, R2=" + settings.Radii.Outer + " m, R3=" + settings.Density.Value + "%."));
			}
			catch (Exception ex)
			{
				harmony.UnpatchSelf();
				((BaseUnityPlugin)this).Logger.LogError((object)("WispReach patches failed; vanilla mist is preserved. " + ex));
			}
		}

		internal void Observe(SE_Demister current, GameObject instance)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Expected O, but got Unknown
			if (!ready || recovery.Faulted || !Object.op_Implicit((Object)(object)current) || !Object.op_Implicit((Object)(object)instance) || !Object.op_Implicit((Object)(object)Player.m_localPlayer))
			{
				return;
			}
			try
			{
				if (!((Object)(Character)CharacterField.GetValue(current) != (Object)(object)Player.m_localPlayer) && (!((Object)(object)ball == (Object)(object)instance) || !((Object)(object)effect == (Object)(object)current)))
				{
					DetachWisp();
					effect = current;
					ball = instance;
					field = ball.GetComponentInChildren<Demister>(true);
				}
			}
			catch (Exception error)
			{
				HandleFault(error);
			}
		}

		internal void Remove(SE_Demister current)
		{
			try
			{
				if ((Object)(object)effect == (Object)(object)current)
				{
					DetachWisp();
				}
			}
			catch (Exception error)
			{
				HandleFault(error);
			}
		}

		private void LateUpdate()
		{
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Expected O, but got Unknown
			//IL_0224: Unknown result type (might be due to invalid IL or missing references)
			//IL_0229: 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)
			if (!ready)
			{
				return;
			}
			try
			{
				bool num = recovery.ObserveContext(Object.op_Implicit((Object)(object)ParticleMist.instance) ? ((Object)ParticleMist.instance).GetInstanceID() : 0);
				bool flag = recovery.TryResume(Time.unscaledTime);
				if (num || flag)
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)"WispReach is retrying mist rendering after restoring native state.");
				}
				if (recovery.Faulted)
				{
					return;
				}
				bool flag2 = Object.op_Implicit((Object)(object)effect) && Object.op_Implicit((Object)(object)ball) && Object.op_Implicit((Object)(object)field) && Object.op_Implicit((Object)(object)Player.m_localPlayer) && (Object)(Character)CharacterField.GetValue(effect) == (Object)(object)Player.m_localPlayer && Object.op_Implicit((Object)(object)field.m_forceField);
				bool flag3 = settings.Enabled.Value && !settings.RemoveAllMist.Value && flag2 && (RadiusRules.CustomRadii(settings.Radii) || settings.Density.Value != 100);
				torches.Update(settings.Enabled.Value && !settings.RemoveAllMist.Value && settings.TorchEnabled.Value, settings.TorchRadii, Time.unscaledDeltaTime);
				bool flag4 = (settings.Enabled.Value && (settings.RemoveAllMist.Value || settings.Density.Value != 100 || flag3)) || torches.Active;
				if (!flag4 && !overlay.Active)
				{
					RestoreField();
					return;
				}
				if (!overlay.Ensure(ParticleMist.instance, ((BaseUnityPlugin)this).Logger))
				{
					RestoreField();
					torches.Dispose();
					return;
				}
				if (flag2)
				{
					lastCenter = ((Component)field).transform.position;
				}
				if (flag2 && (flag3 || overlay.LocalActive))
				{
					if (!fieldOverridden)
					{
						force = field.m_forceField;
						originalEndRange = force.endRange;
						originalForceEnabled = ((Behaviour)force).enabled;
						fieldOverridden = true;
					}
					force.endRange = settings.Radii.Inner;
					((Behaviour)force).enabled = false;
				}
				else
				{
					RestoreField();
				}
				overlay.Update(lastCenter, settings.Radii, settings.Enabled.Value ? ((float)settings.Density.Value / 100f) : 1f, flag3, settings.Enabled.Value && settings.RemoveAllMist.Value, Time.unscaledDeltaTime, torches.Zones);
				if (!overlay.LocalActive)
				{
					RestoreField();
				}
				if (!flag4 && overlay.IsNative)
				{
					overlay.Dispose();
				}
			}
			catch (Exception error)
			{
				HandleFault(error);
			}
		}

		private void SettingsChanged()
		{
			if (settings.Enabled.Value && !lastEnabled)
			{
				recovery.Reset();
			}
			lastEnabled = settings.Enabled.Value;
		}

		private void HandleFault(Exception error)
		{
			recovery.Fail(Time.unscaledTime);
			try
			{
				Release();
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("WispReach native-state restoration failed: " + ex));
			}
			((BaseUnityPlugin)this).Logger.LogError((object)("WispReach rendering failed; native-state restoration attempted. " + (recovery.Exhausted ? "Automatic retries exhausted; re-enable the mod or load a new scene to retry. " : "Retrying in five seconds. ") + error));
		}

		private void RestoreField()
		{
			try
			{
				if (fieldOverridden && Object.op_Implicit((Object)(object)force))
				{
					force.endRange = originalEndRange;
					((Behaviour)force).enabled = originalForceEnabled;
				}
			}
			finally
			{
				force = null;
				fieldOverridden = false;
			}
		}

		private void DetachWisp()
		{
			try
			{
				RestoreField();
			}
			finally
			{
				effect = null;
				ball = null;
				field = null;
			}
		}

		private void Release()
		{
			try
			{
				if (overlay != null)
				{
					overlay.Dispose();
				}
			}
			finally
			{
				try
				{
					torches.Dispose();
				}
				finally
				{
					DetachWisp();
				}
			}
		}

		private void OnDestroy()
		{
			ready = false;
			try
			{
				if (harmony != null)
				{
					harmony.UnpatchSelf();
				}
			}
			finally
			{
				try
				{
					Release();
				}
				finally
				{
					if (settings != null)
					{
						settings.Changed -= SettingsChanged;
						settings.Dispose();
					}
					if ((Object)(object)Instance == (Object)(object)this)
					{
						Instance = null;
					}
				}
			}
		}
	}
	internal struct MistZone
	{
		public Vector3 Center;

		public RadiusPair Radii;

		public float Strength;

		public float Factor(Vector3 position)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = position - Center;
			float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude;
			if (sqrMagnitude >= Radii.Outer * Radii.Outer)
			{
				return 1f;
			}
			return 1f - Strength * (1f - RadiusRules.DensityFactor(Mathf.Sqrt(sqrMagnitude), Radii.Inner, Radii.Outer));
		}
	}
	internal sealed class WispTorchController : IDisposable
	{
		private sealed class TorchState
		{
			public ParticleSystemForceField Force;

			public float OriginalRange;

			public bool OriginalEnabled;

			public readonly MistTransition Fade = new MistTransition();

			public void Restore()
			{
				if (Object.op_Implicit((Object)(object)Force))
				{
					try
					{
						Force.endRange = OriginalRange;
					}
					finally
					{
						((Behaviour)Force).enabled = OriginalEnabled;
					}
				}
			}
		}

		private readonly Dictionary<Demister, TorchState> states = new Dictionary<Demister, TorchState>();

		private readonly HashSet<Demister> seen = new HashSet<Demister>();

		private readonly List<Demister> expired = new List<Demister>();

		public readonly List<MistZone> Zones = new List<MistZone>();

		public bool Active => states.Count > 0;

		public bool IsMasked(Demister field)
		{
			if (Object.op_Implicit((Object)(object)field))
			{
				return states.ContainsKey(field);
			}
			return false;
		}

		public static bool IsWispTorch(Demister field)
		{
			if (!Object.op_Implicit((Object)(object)field))
			{
				return false;
			}
			Transform val = ((Component)field).transform;
			while (Object.op_Implicit((Object)(object)val))
			{
				if (TorchRules.IsWispTorchName(((Object)val).name))
				{
					return true;
				}
				val = val.parent;
			}
			return false;
		}

		public void Update(bool enabled, RadiusPair radii, float deltaTime)
		{
			//IL_012b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			seen.Clear();
			Zones.Clear();
			expired.Clear();
			foreach (Demister demister in Demister.GetDemisters())
			{
				if (!Object.op_Implicit((Object)(object)demister) || !Object.op_Implicit((Object)(object)demister.m_forceField))
				{
					continue;
				}
				if (!states.TryGetValue(demister, out var value))
				{
					if (!enabled || !IsWispTorch(demister))
					{
						continue;
					}
					value = new TorchState
					{
						Force = demister.m_forceField,
						OriginalRange = demister.m_forceField.endRange,
						OriginalEnabled = ((Behaviour)demister.m_forceField).enabled
					};
					states.Add(demister, value);
				}
				seen.Add(demister);
				value.Fade.Advance(radii, 1f, enabled, hidden: false, deltaTime);
				if (!value.Fade.LocalActive)
				{
					value.Restore();
					expired.Add(demister);
					continue;
				}
				value.Force.endRange = radii.Inner;
				((Behaviour)value.Force).enabled = false;
				Zones.Add(new MistZone
				{
					Center = ((Component)demister).transform.position,
					Radii = value.Fade.Radii,
					Strength = value.Fade.LocalStrength
				});
			}
			foreach (KeyValuePair<Demister, TorchState> state in states)
			{
				if (!seen.Contains(state.Key))
				{
					state.Value.Restore();
					expired.Add(state.Key);
				}
			}
			foreach (Demister item in expired)
			{
				states.Remove(item);
			}
		}

		public void Dispose()
		{
			Exception ex = null;
			try
			{
				foreach (TorchState value in states.Values)
				{
					try
					{
						value.Restore();
					}
					catch (Exception ex2)
					{
						if (ex == null)
						{
							ex = ex2;
						}
					}
				}
			}
			finally
			{
				states.Clear();
				seen.Clear();
				expired.Clear();
				Zones.Clear();
			}
			if (ex != null)
			{
				throw ex;
			}
		}
	}
}