Decompiled source of Undertow v1.0.4

plugins/Undertow.dll

Decompiled 17 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using RavenIron.Undertow.Bridge;
using RavenIron.Undertow.Config;
using RavenIron.Undertow.Core;
using RavenIron.Undertow.Net;
using RavenIron.Undertow.Patches;
using RavenIron.Undertow.Systems;
using RavenIron.Undertow.Visuals;
using UnityEngine;
using UnityEngine.Rendering;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("Raven Iron")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.4.0")]
[assembly: AssemblyInformationalVersion("1.0.4+16ee4aea5b6120dc5eb2bfcb7157abcc3eab10be")]
[assembly: AssemblyProduct("Undertow")]
[assembly: AssemblyTitle("Undertow")]
[assembly: AssemblyVersion("1.0.4.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 RavenIron.Undertow
{
	[BepInPlugin("com.raveniron.undertow", "Undertow", "1.0.4")]
	[BepInProcess("valheim.exe")]
	[BepInProcess("valheim_server.exe")]
	public class Undertow : BaseUnityPlugin
	{
		public const string PluginId = "com.raveniron.undertow";

		public const string PluginName = "Undertow";

		public const string PluginVersion = "1.0.4";

		private Harmony _harmony;

		public static Undertow Instance { get; private set; }

		public static ManualLogSource Log { get; private set; }

		public static bool IsSimulationAuthority()
		{
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return false;
			}
			return instance.IsServer();
		}

		public static bool IsDedicated()
		{
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance != (Object)null)
			{
				return instance.IsDedicated();
			}
			return false;
		}

		private void Awake()
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Expected O, but got Unknown
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Invalid comparison between Unknown and I4
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			ModConfig.Bind(((BaseUnityPlugin)this).Config);
			ConfigSync.Bind();
			_harmony = new Harmony("com.raveniron.undertow");
			_harmony.PatchAll();
			ReportPatches();
			((Component)this).gameObject.AddComponent<SeaTick>();
			RegisterSystems();
			if ((int)SystemInfo.graphicsDeviceType != 4)
			{
				((Component)this).gameObject.AddComponent<DriftLines>();
			}
			Log.LogInfo((object)"Undertow v1.0.4 loaded.");
		}

		private static void RegisterSystems()
		{
			SeaTick.Register(new FlotsamSystem());
		}

		private void ReportPatches()
		{
			try
			{
				List<string> list = new List<string>();
				foreach (MethodBase patchedMethod in _harmony.GetPatchedMethods())
				{
					list.Add(patchedMethod.DeclaringType?.Name + "." + patchedMethod.Name);
				}
				if (list.Count == 0)
				{
					Log.LogWarning((object)"Harmony attached NO patches — the console and the drift will both be absent.");
				}
				else
				{
					Log.LogInfo((object)string.Format("Harmony patched {0}: {1}", list.Count, string.Join(", ", list.ToArray())));
				}
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("could not enumerate patches: " + ex.Message));
			}
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			_harmony = null;
			Instance = null;
		}
	}
}
namespace RavenIron.Undertow.Visuals
{
	public class DriftLines : MonoBehaviour
	{
		private enum Slot : byte
		{
			Dormant,
			Active
		}

		private struct Memo
		{
			public long Key;

			public float Expires;

			public FieldSample Sample;

			public bool Ok;
		}

		private const bool FlipRotation = false;

		public const int MaxPool = 512;

		private const float FieldCellMetres = 16f;

		private const float FieldMemoSeconds = 3f;

		private const int MaxAttemptsPerFrame = 4;

		private const float MaxSpawnDebt = 8f;

		private const float MeanLifeSeconds = 10f;

		private const float BaseAlpha = 0.26f;

		private const float MaxAlpha = 0.6f;

		private const float DefaultLiftMetres = 0.02f;

		private const float ClusterJitterMetres = 2f;

		private const float BearingJitterDegrees = 16f;

		private const float SeaStateTau = 2f;

		private const float IdleProbeSeconds = 1f;

		private const float UnderwaterMargin = 0.2f;

		private const int OverBudgetFrames = 120;

		private const int WarmupFrames = 60;

		private const float VerboseSeconds = 10f;

		private const float WindowSeconds = 10f;

		private ParticleSystem _system;

		private ParticleSystemRenderer _renderer;

		private Particle[] _buf;

		private Material _streakMaterial;

		private bool _built;

		private int _builtAtFrame = -1;

		private string _shaderName = "-";

		private bool _shaderHasFog;

		private bool _latched;

		private string _lastError = "none";

		private int _configuredPool;

		private int _effectivePool;

		private Slot[] _state;

		private float[] _x;

		private float[] _z;

		private float[] _y;

		private float[] _yVel;

		private float[] _vx;

		private float[] _vz;

		private float[] _age;

		private float[] _life;

		private float[] _length;

		private float[] _width;

		private float[] _rotation;

		private float[] _alphaMul;

		private float[] _retry;

		private float[] _lastRide;

		private float[] _speed;

		private WaterVolume[] _vol;

		private Bounds[] _volBounds;

		private readonly WaterSurfaceCache _cache = new WaterSurfaceCache();

		private WaterVolume _camVol;

		private Bounds _camBounds;

		private uint _rng;

		private float _spawnDebt;

		private float _seaState;

		private int _drawn;

		private bool _idle;

		private string _idleWord = "IDLE: no water nearby";

		private float _nextIdleProbe;

		private string _stateWord = "not started";

		private bool _firstAfloatLogged;

		private Vector3 _centre;

		private float _camAbove = float.NaN;

		private float _ambientLum;

		private float _fogLum;

		private float _day;

		private float _tintR;

		private float _tintG;

		private float _tintB;

		private float _alphaScale;

		private Color _fogColor;

		private int _fogMode;

		private float _fogDensity;

		private float _fogStart;

		private float _fogEnd;

		private readonly Memo[] _memo = new Memo[256];

		private readonly Stopwatch _sw = new Stopwatch();

		private float _costEma;

		private float _lastCost;

		private int _overBudget;

		private int _surfaceReads;

		private int _fieldEvals;

		private float _nextVerbose;

		private float _windowStart;

		private int _wTried;

		private int _wSpawned;

		private int _wRejSlack;

		private int _wRejShallow;

		private int _wRejWeak;

		private int _wRejNoVolume;

		private int _lTried;

		private int _lSpawned;

		private int _lRejSlack;

		private int _lRejShallow;

		private int _lRejWeak;

		private int _lRejNoVolume;

		private long _retiredLife;

		private long _retiredReflect;

		private long _retiredNoVolume;

		private long _totalSpawned;

		public static DriftLines Instance { get; private set; }

		private void Awake()
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			Instance = this;
			_rng = (uint)(Environment.TickCount | 1);
			Undertow.Log.LogInfo((object)$"DriftLines: armed on this client (graphics device {SystemInfo.graphicsDeviceType}).");
		}

		private void OnDestroy()
		{
			if ((Object)(object)Instance == (Object)(object)this)
			{
				Instance = null;
			}
			DestroyEmitter();
		}

		private void Update()
		{
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: 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_00ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_0139: Unknown result type (might be due to invalid IL or missing references)
			//IL_06ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_06e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_06ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_070f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0747: Unknown result type (might be due to invalid IL or missing references)
			//IL_0782: Unknown result type (might be due to invalid IL or missing references)
			//IL_0784: Unknown result type (might be due to invalid IL or missing references)
			if (_latched)
			{
				return;
			}
			if (Undertow.IsDedicated())
			{
				_stateWord = "DORMANT: dedicated";
				return;
			}
			if (!ModConfig.EnableDriftLines.Value)
			{
				Sleep("OFF in config");
				return;
			}
			int num = Mathf.Clamp(ModConfig.DriftLineCount.Value, 0, 512);
			if (num == 0)
			{
				Sleep("OFF: count 0");
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				Sleep("IDLE: no local player");
				return;
			}
			try
			{
				_sw.Restart();
				_surfaceReads = 0;
				_fieldEvals = 0;
				EnsurePool(num);
				if (!_built)
				{
					Build();
				}
				if (_latched)
				{
					return;
				}
				float time = Time.time;
				float num2 = DriftLineMath.ClampDt(Time.deltaTime);
				Camera main = Camera.main;
				_centre = (((Object)(object)main != (Object)null) ? ((Component)main).transform.position : (((Component)localPlayer).transform.position + Vector3.up * 2f));
				float radius = Mathf.Clamp(ModConfig.DriftLineRadius.Value, 20f, 120f);
				if (time >= _nextIdleProbe)
				{
					_nextIdleProbe = time + 1f;
					_idle = NoWaterNear(_centre, radius, time);
					if (_idle)
					{
						_idleWord = "IDLE: no water within " + radius.ToString("0", CultureInfo.InvariantCulture) + "m";
					}
				}
				if (_idle)
				{
					Sleep(_idleWord);
					return;
				}
				_cache.Refresh(time, force: false);
				float waterLevel = SeaContext.WaterLevel;
				if (_cache.TryGetSurface(_centre.x, _centre.z, waterLevel, ref _camVol, ref _camBounds, out var surface))
				{
					_surfaceReads++;
					_camAbove = _centre.y - surface;
				}
				else
				{
					_camAbove = float.NaN;
				}
				bool flag = !float.IsNaN(_camAbove) && _camAbove < -0.2f;
				((Renderer)_renderer).enabled = !flag;
				_stateWord = (flag ? "IDLE: camera under water" : "LIVE");
				ReadScene();
				float num3 = DriftLineMath.Chop(_seaState);
				float num4 = DriftLineMath.ChopFade(_seaState);
				float num5 = Mathf.Clamp(ModConfig.DriftLineLiftMetres.Value, 0f, 0.25f);
				float num6 = Mathf.Min(1.25f, 1f + 0.25f * num3);
				float num7 = Mathf.Clamp(ModConfig.DriftLineOpacity.Value, 0f, 2f);
				float maxSpeed = ModConfig.MaxCurrentSpeed.Live();
				float minDepth = Mathf.Clamp(ModConfig.DriftLineMinDepth.Value, 2f, 30f);
				float slackFloor = Mathf.Clamp01(ModConfig.DriftLineSlackFloor.Value);
				_spawnDebt = Mathf.Min(8f, _spawnDebt + num2 * (float)_effectivePool / 10f);
				int num8 = Mathf.Min(4, (int)_spawnDebt);
				float num9 = -1f;
				int frameCount = Time.frameCount;
				int num10 = 0;
				for (int i = 0; i < _effectivePool; i++)
				{
					if (_state[i] == Slot.Dormant)
					{
						_retry[i] -= num2;
						if (!(_retry[i] > 0f) && num8 > 0)
						{
							num8--;
							_spawnDebt -= 1f;
							TrySpawnCluster(i, radius, waterLevel, time, num3, maxSpeed, minDepth, slackFloor);
						}
						continue;
					}
					_age[i] += num2;
					if (_age[i] >= _life[i])
					{
						_retiredLife++;
						Retire(i, 0f);
						continue;
					}
					_x[i] += _vx[i] * num2;
					_z[i] += _vz[i] * num2;
					float dx = _x[i] - _centre.x;
					float dz = _z[i] - _centre.z;
					bool flag2 = false;
					if (DriftLineMath.Reflect(ref dx, ref dz, radius))
					{
						_retiredReflect++;
						_x[i] = _centre.x + dx;
						_z[i] = _centre.z + dz;
						if (!Reroll(i, time, num3, maxSpeed, minDepth, slackFloor))
						{
							continue;
						}
						flag2 = true;
					}
					if (flag2 || ((i + frameCount) & 1) == 0)
					{
						if (!_cache.TryGetSurface(_x[i], _z[i], waterLevel, ref _vol[i], ref _volBounds[i], out var surface2))
						{
							_retiredNoVolume++;
							Retire(i, 1f);
							continue;
						}
						_surfaceReads++;
						_yVel[i] = DriftLineMath.VerticalVelocity(_y[i], surface2, time - _lastRide[i]);
						_y[i] = surface2;
						_lastRide[i] = time;
						float num11 = Mathf.Abs(surface2 - waterLevel);
						if (num11 > num9)
						{
							num9 = num11;
						}
						if (!_firstAfloatLogged)
						{
							LogFirstAfloat(i, surface2, waterLevel);
						}
					}
					else
					{
						_y[i] += _yVel[i] * num2;
					}
					float distance = Mathf.Sqrt(dx * dx + dz * dz);
					float num12 = 0.26f * num7 * _alphaMul[i] * DriftLineMath.LifeEnvelope(_age[i] / _life[i]) * DriftLineMath.EdgeFade(distance, radius) * _alphaScale * num6 * num4;
					if (num12 > 0.6f)
					{
						num12 = 0.6f;
					}
					if (!(num12 <= 0f))
					{
						float num13 = _tintR;
						float num14 = _tintG;
						float num15 = _tintB;
						if (!_shaderHasFog)
						{
							float num16 = DriftLineMath.FogVisibility(distance, _fogDensity, _fogMode, _fogStart, _fogEnd);
							num13 = Mathf.Lerp(_fogColor.r, num13, num16);
							num14 = Mathf.Lerp(_fogColor.g, num14, num16);
							num15 = Mathf.Lerp(_fogColor.b, num15, num16);
						}
						Particle val = default(Particle);
						((Particle)(ref val)).position = new Vector3(_x[i], _y[i] + num5, _z[i]);
						((Particle)(ref val)).velocity = Vector3.zero;
						((Particle)(ref val)).startSize3D = new Vector3(_length[i], _width[i], 1f);
						((Particle)(ref val)).rotation = _rotation[i];
						((Particle)(ref val)).startColor = new Color32(ToByte(num13), ToByte(num14), ToByte(num15), ToByte(num12));
						((Particle)(ref val)).remainingLifetime = 1000000f;
						((Particle)(ref val)).startLifetime = 1000000f;
						((Particle)(ref val)).angularVelocity = 0f;
						_buf[num10++] = val;
					}
				}
				_system.SetParticles(_buf, num10);
				_drawn = num10;
				if (num9 >= 0f)
				{
					_seaState = DriftLineMath.SeaState(_seaState, num9, num2, 2f);
				}
				RollWindow(time);
				MeasureCost(time);
			}
			catch (Exception ex)
			{
				Latch("disabled after error", ex);
			}
		}

		private void TrySpawnCluster(int slot, float radius, float level, float now, float chop, float maxSpeed, float minDepth, float slackFloor)
		{
			//IL_00eb: 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_01ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e3: Unknown result type (might be due to invalid IL or missing references)
			_wTried++;
			DriftLineMath.DiscPoint(Roll(), Roll(), radius, out var dx, out var dz);
			float num = _centre.x + dx;
			float num2 = _centre.z + dz;
			if (!FieldAt(num, num2, now, out var sample))
			{
				_retry[slot] = 1f;
				return;
			}
			float num3 = DriftLineMath.SpawnWeight(sample.Speed, sample.Depth, maxSpeed, minDepth, slackFloor);
			if (num3 <= 0f || Roll() >= num3)
			{
				if (sample.Speed <= 0.144f)
				{
					_wRejSlack++;
				}
				else if (sample.Depth <= minDepth)
				{
					_wRejShallow++;
				}
				else
				{
					_wRejWeak++;
				}
				_retry[slot] = 0.5f + Roll();
				return;
			}
			WaterVolume volume = null;
			Bounds bounds = default(Bounds);
			if (!_cache.TryGetSurface(num, num2, level, ref volume, ref bounds, out var surface))
			{
				_wRejNoVolume++;
				_retry[slot] = 1f;
				return;
			}
			_surfaceReads++;
			int num4 = DriftLineMath.ClusterSize(Roll());
			Fill(slot, num, num2, sample, volume, bounds, surface, now, chop, maxSpeed);
			int num5 = slot;
			for (int i = 1; i < num4; i++)
			{
				num5 = NextDormant(num5);
				if (num5 >= 0)
				{
					float num6 = num + (Roll() - 0.5f) * 2f * 2f;
					float num7 = num2 + (Roll() - 0.5f) * 2f * 2f;
					WaterVolume volume2 = volume;
					Bounds bounds2 = bounds;
					if (_cache.TryGetSurface(num6, num7, level, ref volume2, ref bounds2, out var surface2))
					{
						_surfaceReads++;
						Fill(num5, num6, num7, sample, volume2, bounds2, surface2, now, chop, maxSpeed);
					}
					continue;
				}
				break;
			}
		}

		private void Fill(int i, float px, float pz, FieldSample sample, WaterVolume vol, Bounds bounds, float surface, float now, float chop, float maxSpeed)
		{
			//IL_011e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0120: Unknown result type (might be due to invalid IL or missing references)
			_state[i] = Slot.Active;
			_x[i] = px;
			_z[i] = pz;
			_y[i] = surface;
			_yVel[i] = 0f;
			_lastRide[i] = now;
			_vx[i] = sample.X;
			_vz[i] = sample.Z;
			_speed[i] = sample.Speed;
			_life[i] = DriftLineMath.LifetimeSeconds(Roll());
			_age[i] = 0f;
			DriftLineMath.StreakSize(sample.Speed, maxSpeed, chop, Roll(), Roll(), out _length[i], out _width[i]);
			float bearingDegrees = DriftLineMath.BearingDegrees(sample.X, sample.Z) + (Roll() - 0.5f) * 16f;
			_rotation[i] = DriftLineMath.QuadRotationDegrees(bearingDegrees, flip: false);
			_alphaMul[i] = 0.7f + 0.3f * Roll();
			_vol[i] = vol;
			_volBounds[i] = bounds;
			_wSpawned++;
			_totalSpawned++;
		}

		private bool Reroll(int i, float now, float chop, float maxSpeed, float minDepth, float slackFloor)
		{
			if (!FieldAt(_x[i], _z[i], now, out var sample))
			{
				Retire(i, 1f);
				return false;
			}
			float num = DriftLineMath.SpawnWeight(sample.Speed, sample.Depth, maxSpeed, minDepth, slackFloor);
			if (num <= 0f || Roll() >= num)
			{
				Retire(i, 0.5f + Roll());
				return false;
			}
			_vx[i] = sample.X;
			_vz[i] = sample.Z;
			_speed[i] = sample.Speed;
			_life[i] = DriftLineMath.LifetimeSeconds(Roll());
			_age[i] = 0f;
			DriftLineMath.StreakSize(sample.Speed, maxSpeed, chop, Roll(), Roll(), out _length[i], out _width[i]);
			float bearingDegrees = DriftLineMath.BearingDegrees(sample.X, sample.Z) + (Roll() - 0.5f) * 16f;
			_rotation[i] = DriftLineMath.QuadRotationDegrees(bearingDegrees, flip: false);
			_vol[i] = null;
			return true;
		}

		private void Retire(int i, float retry)
		{
			_state[i] = Slot.Dormant;
			_retry[i] = retry;
			_vol[i] = null;
		}

		private int NextDormant(int after)
		{
			for (int i = 1; i < _effectivePool; i++)
			{
				int num = (after + i) % _effectivePool;
				if (_state[num] == Slot.Dormant)
				{
					return num;
				}
			}
			return -1;
		}

		private float Roll()
		{
			return DriftLineMath.NextRoll(ref _rng);
		}

		private bool FieldAt(float x, float z, float now, out FieldSample sample)
		{
			long num = DriftLineMath.CellKey(x, z, 16f);
			int num2 = ((int)num ^ (int)(num >> 32)) * -1640531535 >>> 24;
			int num3 = -1;
			for (int i = 0; i < 4; i++)
			{
				int num4 = (num2 + i) & 0xFF;
				ref Memo reference = ref _memo[num4];
				if (reference.Expires > now && reference.Key == num)
				{
					sample = reference.Sample;
					return reference.Ok;
				}
				if (num3 < 0 && reference.Expires <= now)
				{
					num3 = num4;
				}
			}
			bool flag = SeaContext.TryEvaluate(x, z, out sample);
			_fieldEvals++;
			int num5 = ((num3 >= 0) ? num3 : num2);
			_memo[num5].Key = num;
			_memo[num5].Expires = now + 3f;
			_memo[num5].Sample = sample;
			_memo[num5].Ok = flag;
			return flag;
		}

		private int MemoLiveCells(float now)
		{
			int num = 0;
			for (int i = 0; i < _memo.Length; i++)
			{
				if (_memo[i].Expires > now)
				{
					num++;
				}
			}
			return num;
		}

		private bool NoWaterNear(Vector3 c, float radius, float now)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: 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)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: 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_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: 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)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			float num = 0.7f * radius;
			float num2 = 0.495f * radius;
			if (IsLand(c.x, c.z, now) && IsLand(c.x + num, c.z, now) && IsLand(c.x - num, c.z, now) && IsLand(c.x, c.z + num, now) && IsLand(c.x, c.z - num, now) && IsLand(c.x + num2, c.z + num2, now) && IsLand(c.x - num2, c.z + num2, now) && IsLand(c.x + num2, c.z - num2, now))
			{
				return IsLand(c.x - num2, c.z - num2, now);
			}
			return false;
		}

		private bool IsLand(float x, float z, float now)
		{
			if (FieldAt(x, z, now, out var sample))
			{
				return sample.Depth <= 0f;
			}
			return true;
		}

		private void ReadScene()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: 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_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			Color ambientLight = RenderSettings.ambientLight;
			_ambientLum = DriftLineMath.Luminance(ambientLight.r, ambientLight.g, ambientLight.b);
			_fogColor = RenderSettings.fogColor;
			_fogLum = DriftLineMath.Luminance(_fogColor.r, _fogColor.g, _fogColor.b);
			_day = DriftLineMath.DayFactor(_fogLum);
			DriftLineMath.Tint(_fogColor.r, _fogColor.g, _fogColor.b, _day, Mathf.Clamp01(ModConfig.DriftLineNightFloor.Value), out _tintR, out _tintG, out _tintB, out _alphaScale);
			_fogMode = (RenderSettings.fog ? ((int)RenderSettings.fogMode) : 0);
			_fogDensity = RenderSettings.fogDensity;
			_fogStart = RenderSettings.fogStartDistance;
			_fogEnd = RenderSettings.fogEndDistance;
		}

		private void EnsurePool(int pool)
		{
			//IL_012a: Unknown result type (might be due to invalid IL or missing references)
			//IL_012f: Unknown result type (might be due to invalid IL or missing references)
			if (pool != _configuredPool || _state == null)
			{
				_configuredPool = pool;
				_effectivePool = pool;
				_state = new Slot[pool];
				_x = new float[pool];
				_z = new float[pool];
				_y = new float[pool];
				_yVel = new float[pool];
				_vx = new float[pool];
				_vz = new float[pool];
				_age = new float[pool];
				_life = new float[pool];
				_length = new float[pool];
				_width = new float[pool];
				_rotation = new float[pool];
				_alphaMul = new float[pool];
				_retry = new float[pool];
				_lastRide = new float[pool];
				_speed = new float[pool];
				_vol = (WaterVolume[])(object)new WaterVolume[pool];
				_volBounds = (Bounds[])(object)new Bounds[pool];
				_buf = (Particle[])(object)new Particle[pool];
				_spawnDebt = 0f;
				_overBudget = 0;
				if ((Object)(object)_system != (Object)null)
				{
					MainModule main = _system.main;
					((MainModule)(ref main)).maxParticles = pool;
				}
			}
		}

		private void Build()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Expected O, but got Unknown
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: 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_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				GameObject val = new GameObject("undertow_drift_lines");
				val.transform.SetParent(((Component)this).transform, false);
				val.transform.position = Vector3.zero;
				_system = val.AddComponent<ParticleSystem>();
				MainModule main = _system.main;
				((MainModule)(ref main)).simulationSpace = (ParticleSystemSimulationSpace)1;
				((MainModule)(ref main)).scalingMode = (ParticleSystemScalingMode)1;
				((MainModule)(ref main)).maxParticles = _configuredPool;
				((MainModule)(ref main)).startSize3D = true;
				((MainModule)(ref main)).startSpeed = MinMaxCurve.op_Implicit(0f);
				((MainModule)(ref main)).gravityModifier = MinMaxCurve.op_Implicit(0f);
				((MainModule)(ref main)).playOnAwake = false;
				((MainModule)(ref main)).loop = true;
				((MainModule)(ref main)).cullingMode = (ParticleSystemCullingMode)3;
				EmissionModule emission = _system.emission;
				((EmissionModule)(ref emission)).enabled = false;
				ShapeModule shape = _system.shape;
				((ShapeModule)(ref shape)).enabled = false;
				_renderer = val.GetComponent<ParticleSystemRenderer>();
				_renderer.renderMode = (ParticleSystemRenderMode)2;
				_renderer.sortMode = (ParticleSystemSortMode)0;
				_renderer.sortingFudge = 0f;
				_renderer.maxParticleSize = 1f;
				_renderer.pivot = Vector3.zero;
				((Renderer)_renderer).shadowCastingMode = (ShadowCastingMode)0;
				((Renderer)_renderer).receiveShadows = false;
				_streakMaterial = ParticleKit.BuildStreakMaterial("DriftLines", out _shaderName, out _shaderHasFog);
				((Renderer)_renderer).material = _streakMaterial;
				_system.Play();
				_built = true;
				_builtAtFrame = Time.frameCount;
				_windowStart = Time.time;
				_nextVerbose = Time.time + 10f;
				Undertow.Log.LogInfo((object)("DriftLines: emitter built — shader '" + _shaderName + "' (" + (_shaderHasFog ? "shader fog" : "manual fog") + "), " + string.Format("pool {0}, radius {1} m, ", _configuredPool, ModConfig.DriftLineRadius.Value.ToString("0", CultureInfo.InvariantCulture)) + $"texture {128}x{32}, queue {3100}."));
			}
			catch (Exception ex)
			{
				Latch("build failed, streaks disabled", ex);
			}
		}

		private void Sleep(string word)
		{
			_stateWord = word;
			if ((Object)(object)_system != (Object)null && _drawn > 0)
			{
				try
				{
					_system.SetParticles(_buf, 0);
				}
				catch
				{
				}
				_drawn = 0;
			}
			if ((Object)(object)_renderer != (Object)null)
			{
				((Renderer)_renderer).enabled = false;
			}
		}

		private void Latch(string what, Exception ex)
		{
			_latched = true;
			string text = ((ex.StackTrace != null) ? FirstFrame(ex.StackTrace) : "");
			_lastError = ex.GetType().Name + ": " + ex.Message + text;
			_stateWord = "LATCHED: " + _lastError;
			Undertow.Log.LogWarning((object)("DriftLines: " + what + ": " + _lastError));
			DestroyEmitter();
		}

		private static string FirstFrame(string stack)
		{
			int num = stack.IndexOf('\n');
			string text = ((num > 0) ? stack.Substring(0, num) : stack);
			return " @ " + text.Trim();
		}

		private void DestroyEmitter()
		{
			try
			{
				if ((Object)(object)_system != (Object)null)
				{
					_system.SetParticles(_buf, 0);
					Object.Destroy((Object)(object)((Component)_system).gameObject);
				}
				if ((Object)(object)_streakMaterial != (Object)null)
				{
					Texture mainTexture = _streakMaterial.mainTexture;
					Object.Destroy((Object)(object)_streakMaterial);
					if ((Object)(object)mainTexture != (Object)null)
					{
						Object.Destroy((Object)(object)mainTexture);
					}
				}
			}
			catch
			{
			}
			_streakMaterial = null;
			_system = null;
			_renderer = null;
			_built = false;
			_drawn = 0;
		}

		public string Reset()
		{
			DestroyEmitter();
			_latched = false;
			_lastError = "none";
			_state = null;
			_configuredPool = 0;
			_effectivePool = 0;
			_seaState = 0f;
			_costEma = 0f;
			_overBudget = 0;
			_firstAfloatLogged = false;
			_cache.Clear();
			_camVol = null;
			Array.Clear(_memo, 0, _memo.Length);
			_idle = false;
			_nextIdleProbe = 0f;
			_wTried = (_wSpawned = (_wRejSlack = (_wRejShallow = (_wRejWeak = (_wRejNoVolume = 0)))));
			_lTried = (_lSpawned = (_lRejSlack = (_lRejShallow = (_lRejWeak = (_lRejNoVolume = 0)))));
			_retiredLife = (_retiredReflect = (_retiredNoVolume = (_totalSpawned = 0L)));
			_stateWord = "reset — rebuilding on the next frame";
			return "drift lines reset: latch cleared, emitter destroyed, rebuilding on the next frame.";
		}

		private void LogFirstAfloat(int i, float surface, float level)
		{
			_firstAfloatLogged = true;
			CultureInfo invariantCulture = CultureInfo.InvariantCulture;
			string text = (((Object)(object)_vol[i] != (Object)null) ? ((Object)_vol[i]).name : "?");
			Undertow.Log.LogInfo((object)("DriftLines: first streak afloat at (" + _x[i].ToString("0", invariantCulture) + ", " + _z[i].ToString("0", invariantCulture) + ") — " + _speed[i].ToString("0.###", invariantCulture) + " m/s bearing " + DriftLineMath.BearingDegrees(_vx[i], _vz[i]).ToString("0", invariantCulture) + "°, surface " + surface.ToString("0.00", invariantCulture) + " (flat " + level.ToString("0.00", invariantCulture) + ") in volume '" + text + "'."));
		}

		private void RollWindow(float now)
		{
			if (!(now - _windowStart < 10f))
			{
				_lTried = _wTried;
				_lSpawned = _wSpawned;
				_lRejSlack = _wRejSlack;
				_lRejShallow = _wRejShallow;
				_lRejWeak = _wRejWeak;
				_lRejNoVolume = _wRejNoVolume;
				_wTried = (_wSpawned = (_wRejSlack = (_wRejShallow = (_wRejWeak = (_wRejNoVolume = 0)))));
				_windowStart = now;
			}
		}

		private void MeasureCost(float now)
		{
			_sw.Stop();
			if (Time.frameCount == _builtAtFrame)
			{
				return;
			}
			_lastCost = (float)_sw.Elapsed.TotalMilliseconds;
			_costEma += (_lastCost - _costEma) / 60f;
			if (Time.frameCount - _builtAtFrame < 60)
			{
				return;
			}
			float num = Mathf.Clamp(ModConfig.DriftLineBudgetMs.Value, 0.1f, 4f);
			if (_costEma > num)
			{
				_overBudget++;
			}
			else
			{
				_overBudget = 0;
			}
			if (_overBudget >= 120)
			{
				_overBudget = 0;
				int num2 = DriftLineMath.DegradedCap(_effectivePool, _costEma, num);
				if (num2 < _effectivePool)
				{
					CultureInfo invariantCulture = CultureInfo.InvariantCulture;
					Undertow.Log.LogWarning((object)(string.Format("DriftLines: cost averaged {0} ms over {1} frames ", _costEma.ToString("0.00", invariantCulture), 120) + string.Format("against a {0} budget — pool {1} → {2}.", num.ToString("0.00", invariantCulture), _effectivePool, num2)));
					for (int i = num2; i < _effectivePool; i++)
					{
						Retire(i, 0f);
					}
					_effectivePool = num2;
				}
			}
			if (ModConfig.VerboseLogging.Value && now >= _nextVerbose)
			{
				_nextVerbose = now + 10f;
				Undertow.Log.LogInfo((object)Summary(now));
			}
		}

		private string Summary(float now)
		{
			CultureInfo invariantCulture = CultureInfo.InvariantCulture;
			Means(out var active, out var meanBearing, out var meanSpeed, out var meanLength);
			float num = 0f;
			float num2 = 0f;
			if (FieldAt(_centre.x, _centre.z, now, out var sample))
			{
				num = DriftLineMath.BearingDegrees(sample.X, sample.Z);
				num2 = sample.Speed;
			}
			return string.Format("DriftLines: active {0}/{1} bearing {2}° vs {3}° ", active, _effectivePool, meanBearing.ToString("0", invariantCulture), num.ToString("0", invariantCulture)) + "speed " + meanSpeed.ToString("0.00", invariantCulture) + " vs " + num2.ToString("0.00", invariantCulture) + " len " + meanLength.ToString("0.0", invariantCulture) + "m cost " + _costEma.ToString("0.00", invariantCulture) + "ms chop " + DriftLineMath.Chop(_seaState).ToString("0.00", invariantCulture) + " day " + _day.ToString("0.00", invariantCulture);
		}

		private void Means(out int active, out float meanBearing, out float meanSpeed, out float meanLength)
		{
			active = 0;
			float num = 0f;
			float num2 = 0f;
			float num3 = 0f;
			float num4 = 0f;
			if (_state != null)
			{
				for (int i = 0; i < _effectivePool; i++)
				{
					if (_state[i] == Slot.Active)
					{
						active++;
						num += _vx[i];
						num2 += _vz[i];
						num3 += _speed[i];
						num4 += _length[i];
					}
				}
			}
			meanBearing = ((active > 0) ? DriftLineMath.BearingDegrees(num, num2) : 0f);
			meanSpeed = ((active > 0) ? (num3 / (float)active) : 0f);
			meanLength = ((active > 0) ? (num4 / (float)active) : 0f);
		}

		public string Describe()
		{
			//IL_05e1: Unknown result type (might be due to invalid IL or missing references)
			CultureInfo invariantCulture = CultureInfo.InvariantCulture;
			StringBuilder stringBuilder = new StringBuilder(1024);
			float time = Time.time;
			bool value = ModConfig.EnableDriftLines.Value;
			stringBuilder.Append("drift lines " + (value ? "ON" : "OFF") + " — " + _stateWord);
			if (_built)
			{
				stringBuilder.Append(" (shader '" + _shaderName + "', " + (_shaderHasFog ? "shader fog" : "manual fog") + ")");
			}
			stringBuilder.Append('\n');
			Means(out var active, out var meanBearing, out var meanSpeed, out var meanLength);
			if (_state != null)
			{
				_ = _state.Length;
			}
			stringBuilder.Append($"pool {_configuredPool}");
			if (_effectivePool < _configuredPool)
			{
				stringBuilder.Append($" (auto-degraded: {_configuredPool} → {_effectivePool})");
			}
			stringBuilder.Append($", active {active}, dormant {Math.Max(0, _effectivePool - active)}, drawn {_drawn} | ");
			stringBuilder.Append("radius " + ModConfig.DriftLineRadius.Value.ToString("0", invariantCulture) + "m, opacity " + ModConfig.DriftLineOpacity.Value.ToString("0.00", invariantCulture) + ", ");
			stringBuilder.Append("min depth " + ModConfig.DriftLineMinDepth.Value.ToString("0.#", invariantCulture) + "m, ");
			stringBuilder.Append("lift " + (ModConfig.DriftLineLiftMetres.Value * 100f).ToString("0.#", invariantCulture) + "cm, ");
			stringBuilder.Append("slack floor " + ModConfig.DriftLineSlackFloor.Value.ToString("0.##", invariantCulture) + ", ");
			stringBuilder.Append("night floor " + ModConfig.DriftLineNightFloor.Value.ToString("0.##", invariantCulture) + "\n");
			float waterLevel = SeaContext.WaterLevel;
			stringBuilder.Append("centre (" + _centre.x.ToString("0", invariantCulture) + ", " + _centre.z.ToString("0", invariantCulture) + ")");
			stringBuilder.Append(float.IsNaN(_camAbove) ? " camera: no water volume" : (" camera " + _camAbove.ToString("0.0", invariantCulture) + "m above water"));
			if (SeaContext.TryEvaluate(_centre.x, _centre.z, out var sample))
			{
				stringBuilder.Append(" | field here " + sample.Speed.ToString("0.###", invariantCulture) + " m/s bearing " + DriftLineMath.BearingDegrees(sample.X, sample.Z).ToString("0", invariantCulture) + "° " + string.Format("({0}, {1}) {2}, depth {3}m, ", sample.X.ToString("0.###", invariantCulture), sample.Z.ToString("0.###", invariantCulture), sample.Dominant, sample.Depth.ToString("0.#", invariantCulture)) + "surge x" + sample.StormSurge.ToString("0.00", invariantCulture));
			}
			stringBuilder.Append('\n');
			stringBuilder.Append("streaks: mean bearing " + meanBearing.ToString("0", invariantCulture) + "°, mean speed " + meanSpeed.ToString("0.00", invariantCulture) + ", mean length " + meanLength.ToString("0.0", invariantCulture) + "m | ");
			stringBuilder.Append($"last {10f:0}s window: {_lTried} tried, {_lSpawned} spawned; rejected slack {_lRejSlack}, shallow {_lRejShallow}, weak {_lRejWeak}, no-volume {_lRejNoVolume} | ");
			stringBuilder.Append($"retired: life {_retiredLife}, reflected {_retiredReflect}, no-volume {_retiredNoVolume} | spawned {_totalSpawned} since build\n");
			int num = -1;
			float num2 = float.MaxValue;
			if (_state != null)
			{
				for (int i = 0; i < _effectivePool; i++)
				{
					if (_state[i] == Slot.Active)
					{
						float num3 = _x[i] - _centre.x;
						float num4 = _z[i] - _centre.z;
						float num5 = num3 * num3 + num4 * num4;
						if (num5 < num2)
						{
							num2 = num5;
							num = i;
						}
					}
				}
			}
			if (num >= 0)
			{
				int num6 = num;
				WaterVolume val = null;
				float waterLevel2 = Floating.GetWaterLevel(new Vector3(_x[num6], waterLevel, _z[num6]), ref val);
				stringBuilder.Append("nearest streak " + Mathf.Sqrt(num2).ToString("0.0", invariantCulture) + "m: surface " + _y[num6].ToString("0.00", invariantCulture) + " (flat " + waterLevel.ToString("0.00", invariantCulture) + ", wave " + (_y[num6] - waterLevel).ToString("+0.00;-0.00", invariantCulture) + ") | ");
				stringBuilder.Append((waterLevel2 <= -9999f) ? "vanilla Floating.GetWaterLevel: no water volume | " : ("vanilla Floating.GetWaterLevel " + waterLevel2.ToString("0.00", invariantCulture) + " (delta " + (_y[num6] - waterLevel2).ToString("0.000", invariantCulture) + ") | "));
				stringBuilder.Append("rot " + _rotation[num6].ToString("0", invariantCulture) + "° for bearing " + DriftLineMath.BearingDegrees(_vx[num6], _vz[num6]).ToString("0", invariantCulture) + "° | ");
				stringBuilder.Append("len " + _length[num6].ToString("0.0", invariantCulture) + "m wid " + _width[num6].ToString("0.00", invariantCulture) + "m | age " + _age[num6].ToString("0.0", invariantCulture) + "/" + _life[num6].ToString("0.0", invariantCulture) + "s\n");
			}
			else
			{
				stringBuilder.Append("nearest streak: none active\n");
			}
			stringBuilder.Append("sea state " + _seaState.ToString("0.00", invariantCulture) + "m (chop " + DriftLineMath.Chop(_seaState).ToString("0.00", invariantCulture) + ", chop fade " + DriftLineMath.ChopFade(_seaState).ToString("0.00", invariantCulture) + "), ");
			stringBuilder.Append("fog lum " + _fogLum.ToString("0.00", invariantCulture) + " -> day " + _day.ToString("0.00", invariantCulture) + " (ambient lum " + _ambientLum.ToString("0.00", invariantCulture) + "), tint (" + _tintR.ToString("0.00", invariantCulture) + "," + _tintG.ToString("0.00", invariantCulture) + "," + _tintB.ToString("0.00", invariantCulture) + "), ");
			stringBuilder.Append((_fogMode == 0) ? "fog off" : string.Format("fog mode {0} density {1}", _fogMode, _fogDensity.ToString("0.0000", invariantCulture)));
			stringBuilder.Append(string.Format(" | volumes cached {0} (Instances {1}), rebuilt {2}s ago\n", _cache.Count, _cache.InstancesCount, _cache.RebuiltAgo(time).ToString("0.0", invariantCulture)));
			stringBuilder.Append("cost " + _lastCost.ToString("0.00", invariantCulture) + " ms last frame (" + _costEma.ToString("0.00", invariantCulture) + " EMA, budget " + ModConfig.DriftLineBudgetMs.Value.ToString("0.00", invariantCulture) + "), ");
			stringBuilder.Append($"surface reads {_surfaceReads}, field evals {_fieldEvals} (memo {MemoLiveCells(time)} cells) | ");
			stringBuilder.Append("last error: " + _lastError + " (latched: " + (_latched ? "yes" : "no") + ")");
			return stringBuilder.ToString();
		}

		private static byte ToByte(float v)
		{
			if (float.IsNaN(v) || v <= 0f)
			{
				return 0;
			}
			if (v >= 1f)
			{
				return byte.MaxValue;
			}
			return (byte)(v * 255f + 0.5f);
		}
	}
	internal static class ParticleKit
	{
		private static readonly string[] CandidateShaders = new string[6] { "Particles/Standard Unlit", "Legacy Shaders/Particles/Alpha Blended", "Sprites/Default", "UI/Default", "Particles/Standard Surface", "Legacy Shaders/Particles/Additive" };

		public const int StreakTextureWidth = 128;

		public const int StreakTextureHeight = 32;

		public const int StreakTextureSeed = 7;

		public const int RenderQueue = 3100;

		public static Shader FindShader(string owner, out string chosenName)
		{
			string[] candidateShaders = CandidateShaders;
			foreach (string text in candidateShaders)
			{
				Shader val = Shader.Find(text);
				if ((Object)(object)val != (Object)null)
				{
					chosenName = text;
					Undertow.Log.LogInfo((object)(owner + ": using shader '" + text + "'."));
					return val;
				}
			}
			throw new InvalidOperationException("no particle shader available");
		}

		public static bool ShaderHasFog(string shaderName)
		{
			if (shaderName != null)
			{
				if (!shaderName.StartsWith("Legacy Shaders/Particles/", StringComparison.Ordinal))
				{
					return shaderName.StartsWith("Particles/", StringComparison.Ordinal);
				}
				return true;
			}
			return false;
		}

		public static Material BuildStreakMaterial(string owner, out string shaderName, out bool shaderHasFog)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: 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_0039: Expected O, but got Unknown
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Expected O, but got Unknown
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			Shader val = FindShader(owner, out shaderName);
			shaderHasFog = ShaderHasFog(shaderName);
			Texture2D val2 = new Texture2D(128, 32, (TextureFormat)4, true)
			{
				wrapMode = (TextureWrapMode)1,
				filterMode = (FilterMode)1,
				name = "undertow_drift_line"
			};
			for (int i = 0; i < 32; i++)
			{
				for (int j = 0; j < 128; j++)
				{
					float num = DriftLineMath.StreakAlphaAt(j, i, 128, 32, 7);
					val2.SetPixel(j, i, new Color(1f, 1f, 1f, num));
				}
			}
			val2.Apply();
			return new Material(val)
			{
				mainTexture = (Texture)(object)val2,
				name = "undertow_drift_line",
				renderQueue = 3100
			};
		}
	}
	internal sealed class WaterSurfaceCache
	{
		private struct Entry
		{
			public WaterVolume Volume;

			public Bounds Bounds;
		}

		public const float RebuildSeconds = 2f;

		private readonly List<Entry> _entries = new List<Entry>(128);

		private Dictionary<WaterVolume, Bounds> _boundsMemo = new Dictionary<WaterVolume, Bounds>(128);

		private int _cachedCount = -1;

		private float _lastRebuild = -1f;

		public int Count => _entries.Count;

		public int InstancesCount => WaterVolume.Instances.Count;

		public int Rebuilds { get; private set; }

		public float RebuiltAgo(float now)
		{
			if (!(_lastRebuild < 0f))
			{
				return now - _lastRebuild;
			}
			return -1f;
		}

		public void Refresh(float now, bool force)
		{
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			List<WaterVolume> instances = WaterVolume.Instances;
			if (!force && instances.Count == _cachedCount && !(_lastRebuild < 0f) && !(now - _lastRebuild >= 2f))
			{
				return;
			}
			_entries.Clear();
			for (int i = 0; i < instances.Count; i++)
			{
				WaterVolume val = instances[i];
				if ((Object)(object)val == (Object)null || (int)val.GetLiquidType() != 0)
				{
					continue;
				}
				if (!_boundsMemo.TryGetValue(val, out var value))
				{
					Collider component = ((Component)val).GetComponent<Collider>();
					if ((Object)(object)component == (Object)null)
					{
						continue;
					}
					value = component.bounds;
					_boundsMemo[val] = value;
				}
				_entries.Add(new Entry
				{
					Volume = val,
					Bounds = value
				});
			}
			if (_boundsMemo.Count > _entries.Count * 2 + 32)
			{
				Dictionary<WaterVolume, Bounds> dictionary = new Dictionary<WaterVolume, Bounds>(_entries.Count * 2);
				for (int j = 0; j < _entries.Count; j++)
				{
					dictionary[_entries[j].Volume] = _entries[j].Bounds;
				}
				_boundsMemo = dictionary;
			}
			_cachedCount = instances.Count;
			_lastRebuild = now;
			Rebuilds++;
		}

		public void Clear()
		{
			_entries.Clear();
			_boundsMemo.Clear();
			_cachedCount = -1;
			_lastRebuild = -1f;
		}

		public static bool ContainsXZ(Bounds b, float x, float z)
		{
			//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_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: 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)
			Vector3 min = ((Bounds)(ref b)).min;
			Vector3 max = ((Bounds)(ref b)).max;
			if (x >= min.x && x <= max.x && z >= min.z)
			{
				return z <= max.z;
			}
			return false;
		}

		public bool TryGetSurface(float x, float z, float waterLevel, ref WaterVolume volume, ref Bounds bounds, out float surface)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = default(Vector3);
			((Vector3)(ref val))..ctor(x, waterLevel, z);
			if ((Object)(object)volume != (Object)null && ContainsXZ(bounds, x, z))
			{
				surface = volume.GetWaterSurface(val, 1f);
				return true;
			}
			volume = null;
			surface = 0f;
			bool flag = false;
			for (int i = 0; i < _entries.Count; i++)
			{
				Entry entry = _entries[i];
				if (ContainsXZ(entry.Bounds, x, z) && !((Object)(object)entry.Volume == (Object)null))
				{
					float waterSurface = entry.Volume.GetWaterSurface(val, 1f);
					if (!flag || waterSurface > surface)
					{
						surface = waterSurface;
						volume = entry.Volume;
						bounds = entry.Bounds;
						flag = true;
					}
				}
			}
			return flag;
		}
	}
}
namespace RavenIron.Undertow.Systems
{
	public class FlotsamSystem : IWorldSystem
	{
		private struct Tracked
		{
			public ZDOID Id;

			public float SpawnedAt;
		}

		private string[] _common;

		private string[] _rare;

		private string[] _wreck;

		private bool _warnedMissingPrefab;

		private readonly List<Tracked> _alive = new List<Tracked>();

		public string Name => "FlotsamSystem";

		public bool Enabled => ModConfig.EnableFlotsam.Value;

		public float IntervalSeconds => ModConfig.FlotsamIntervalSeconds.Value;

		public void Initialise()
		{
			_common = Split(ModConfig.FlotsamCommon.Value);
			_rare = Split(ModConfig.FlotsamRare.Value);
			_wreck = Split(ModConfig.FlotsamWreckage.Value);
			Undertow.Log.LogInfo((object)($"[{Name}] {ModConfig.FlotsamPerHour.Value:0.##}/hour per player in slack water, " + $"{ModConfig.FlotsamRingMinMeters.Value:0}-{ModConfig.FlotsamRingMaxMeters.Value:0}m out, " + $"cap {ModConfig.FlotsamMaxAlive.Value}, TTL {ModConfig.FlotsamTtlSeconds.Value:0}s. " + $"Palette: {_common.Length} common, {_rare.Length} rare, {_wreck.Length} wreckage."));
		}

		public void Tick(float deltaSeconds)
		{
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: 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_0102: Unknown result type (might be due to invalid IL or missing references)
			//IL_0104: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0143: Unknown result type (might be due to invalid IL or missing references)
			Prune();
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			List<ZNetPeer> peers = instance.GetPeers();
			int num = peers?.Count ?? 0;
			Player localPlayer = Player.m_localPlayer;
			Player val = (FlotsamMath.HostPlayerIsOrigin(instance.IsServer(), instance.IsDedicated(), (Object)(object)localPlayer != (Object)null) ? localPlayer : null);
			int num2 = FlotsamMath.OriginCount(num, (Object)(object)val != (Object)null);
			if (num2 == 0 || _alive.Count >= ModConfig.FlotsamMaxAlive.Value)
			{
				return;
			}
			float maxSpeed = ModConfig.MaxCurrentSpeed.Live();
			float value = ModConfig.FlotsamMinDepth.Value;
			float value2 = ModConfig.FlotsamPerHour.Value;
			for (int i = 0; i < num2 && _alive.Count < ModConfig.FlotsamMaxAlive.Value; i++)
			{
				Vector3 position;
				if (i < num)
				{
					if (!TryGetPeerPosition(peers[i], out position))
					{
						continue;
					}
				}
				else
				{
					position = ((Component)val).transform.position;
				}
				Vector3 val2 = RingPoint(position, ModConfig.FlotsamRingMinMeters.Value, ModConfig.FlotsamRingMaxMeters.Value);
				if (SeaContext.TryEvaluate(val2.x, val2.z, out var sample))
				{
					float num3 = FlotsamMath.GatherWeight(in sample, maxSpeed, value);
					if (!(num3 <= 0f) && FlotsamMath.ShouldSpawn(num3, value2, deltaSeconds, Random.value))
					{
						Spawn(val2, in sample);
					}
				}
			}
		}

		private static bool TryGetPeerPosition(ZNetPeer peer, out Vector3 position)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_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)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			position = Vector3.zero;
			if (peer == null)
			{
				return false;
			}
			try
			{
				if (peer.m_characterID != ZDOID.None && ZDOMan.instance != null)
				{
					ZDO zDO = ZDOMan.instance.GetZDO(peer.m_characterID);
					if (zDO != null)
					{
						position = zDO.GetPosition();
						return true;
					}
				}
				if (peer.m_refPos != Vector3.zero)
				{
					position = peer.m_refPos;
					return true;
				}
			}
			catch
			{
			}
			return false;
		}

		private static int ZdoTotal()
		{
			try
			{
				ZDOMan instance = ZDOMan.instance;
				return (instance != null) ? instance.NrOfObjects() : (-1);
			}
			catch
			{
				return -1;
			}
		}

		private static Vector3 RingPoint(Vector3 origin, float min, float max)
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			if (max < min)
			{
				max = min;
			}
			float num = Random.value * (float)Math.PI * 2f;
			float num2 = Mathf.Lerp(min, max, Random.value);
			return new Vector3(origin.x + Mathf.Cos(num) * num2, 0f, origin.z + Mathf.Sin(num) * num2);
		}

		private void Spawn(Vector3 point, in FieldSample sample)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_0183: Unknown result type (might be due to invalid IL or missing references)
			//IL_0188: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dc: Unknown result type (might be due to invalid IL or missing references)
			ZNetScene instance = ZNetScene.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			bool flag = WrathBridge.IsStormAt(point);
			string[] array = ((flag && _wreck.Length != 0) ? _wreck : _common);
			if (Random.value < ModConfig.FlotsamRareChance.Value && _rare.Length != 0)
			{
				array = _rare;
			}
			if (array.Length == 0)
			{
				return;
			}
			string text = array[Random.Range(0, array.Length)];
			GameObject prefab = instance.GetPrefab(text);
			if ((Object)(object)prefab == (Object)null)
			{
				if (!_warnedMissingPrefab)
				{
					_warnedMissingPrefab = true;
					Undertow.Log.LogWarning((object)("[" + Name + "] prefab '" + text + "' not found — check the palette in config. Use `wake floats` for the list of prefabs that actually float."));
				}
				return;
			}
			try
			{
				Vector3 val = default(Vector3);
				((Vector3)(ref val))..ctor(point.x, SeaContext.WaterLevel, point.z);
				GameObject val2 = Object.Instantiate<GameObject>(prefab, val, Quaternion.identity);
				if ((Object)(object)val2 == (Object)null)
				{
					return;
				}
				ZNetView component = val2.GetComponent<ZNetView>();
				ZDO val3 = (((Object)(object)component != (Object)null) ? component.GetZDO() : null);
				if (val3 == null)
				{
					Object.Destroy((Object)(object)val2);
					if (!_warnedMissingPrefab)
					{
						_warnedMissingPrefab = true;
						Undertow.Log.LogWarning((object)("[" + Name + "] '" + text + "' spawned without a ZNetView/ZDO — cannot be capped or reclaimed, so it was removed. Drop it from the palette."));
					}
				}
				else
				{
					_alive.Add(new Tracked
					{
						Id = val3.m_uid,
						SpawnedAt = Time.realtimeSinceStartup
					});
					if (ModConfig.VerboseLogging.Value)
					{
						Undertow.Log.LogInfo((object)($"[{Name}] {text} at ({point.x:0}, {point.z:0}) — " + $"depth {sample.Depth:0.#}m, water {sample.Speed:0.###} m/s {sample.Dominant}" + (flag ? ", STORM wreckage" : "") + $" [{_alive.Count}/{ModConfig.FlotsamMaxAlive.Value} alive, {ZdoTotal()} ZDOs]"));
					}
				}
			}
			catch (Exception ex)
			{
				Undertow.Log.LogWarning((object)("[" + Name + "] could not spawn '" + text + "': " + ex.Message));
			}
		}

		private void Prune()
		{
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_016e: Unknown result type (might be due to invalid IL or missing references)
			ZDOMan instance = ZDOMan.instance;
			if (instance == null)
			{
				return;
			}
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			float value = ModConfig.FlotsamTtlSeconds.Value;
			for (int num = _alive.Count - 1; num >= 0; num--)
			{
				Tracked tracked = _alive[num];
				ZDO val;
				try
				{
					val = instance.GetZDO(tracked.Id);
				}
				catch
				{
					val = null;
				}
				if (val == null || !val.IsValid())
				{
					_alive.RemoveAt(num);
					if (ModConfig.VerboseLogging.Value)
					{
						Undertow.Log.LogInfo((object)($"[{Name}] {tracked.Id} gone (picked up, or removed by the world) " + $"[{_alive.Count}/{ModConfig.FlotsamMaxAlive.Value} alive, {ZdoTotal()} ZDOs]"));
					}
				}
				else
				{
					if (!(value > 0f) || !(realtimeSinceStartup - tracked.SpawnedAt > value))
					{
						continue;
					}
					try
					{
						if (!val.IsOwner())
						{
							val.SetOwner(ZDOMan.GetSessionID());
						}
						instance.DestroyZDO(val);
					}
					catch (Exception ex)
					{
						Undertow.Log.LogWarning((object)("[" + Name + "] could not reclaim flotsam; it stays counted against the cap and will be retried: " + ex.Message));
						continue;
					}
					_alive.RemoveAt(num);
					if (ModConfig.VerboseLogging.Value)
					{
						Undertow.Log.LogInfo((object)($"[{Name}] reclaimed {tracked.Id} after {realtimeSinceStartup - tracked.SpawnedAt:0}s " + $"[{_alive.Count}/{ModConfig.FlotsamMaxAlive.Value} alive, {ZdoTotal()} ZDOs]"));
					}
				}
			}
		}

		private static string[] Split(string csv)
		{
			if (string.IsNullOrEmpty(csv))
			{
				return new string[0];
			}
			string[] array = csv.Split(new char[1] { ',' });
			List<string> list = new List<string>(array.Length);
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].Trim();
				if (text.Length > 0)
				{
					list.Add(text);
				}
			}
			return list.ToArray();
		}
	}
}
namespace RavenIron.Undertow.Patches
{
	[HarmonyPatch(typeof(Character), "UpdateSwimming")]
	public static class Patch_Character_Swim
	{
		private sealed class Cached
		{
			public float NextEvalTime;

			public FieldSample Sample;

			public bool Valid;
		}

		public static long PushCount;

		public static float LastWaterSpeed;

		public static float LastDrift;

		public static bool EverRan;

		private const float LogIntervalSeconds = 3f;

		private static float _nextLogTime;

		private static readonly ConditionalWeakTable<Character, Cached> _cache = new ConditionalWeakTable<Character, Cached>();

		private const float ErrorIntervalSeconds = 30f;

		private static float _nextErrorLogTime;

		private static long _suppressedErrors;

		private static bool TryGetSample(Character who, Vector3 position, out FieldSample sample)
		{
			//IL_0050: 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)
			Cached value = _cache.GetValue(who, (Character _) => new Cached());
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (value.Valid && realtimeSinceStartup < value.NextEvalTime)
			{
				sample = value.Sample;
				return true;
			}
			if (!SeaContext.TryEvaluate(position.x, position.z, out sample))
			{
				value.Valid = false;
				return false;
			}
			value.Sample = sample;
			value.Valid = true;
			value.NextEvalTime = realtimeSinceStartup + Mathf.Max(0f, ModConfig.FieldRefreshSeconds.Value);
			return true;
		}

		private static void Postfix(Character __instance, ZNetView ___m_nview, ref Vector3 ___m_currentVel)
		{
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_0177: Unknown result type (might be due to invalid IL or missing references)
			//IL_017c: Unknown result type (might be due to invalid IL or missing references)
			//IL_017e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0185: Unknown result type (might be due to invalid IL or missing references)
			//IL_018d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0194: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b9: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (!ModConfig.EnableSwimmers.Live() || (Object)(object)__instance == (Object)null || !__instance.IsPlayer() || (Object)(object)___m_nview == (Object)null || !___m_nview.IsValid() || !___m_nview.IsOwner())
				{
					return;
				}
				Vector3 position = ((Component)__instance).transform.position;
				if (!TryGetSample(__instance, position, out var sample) || sample.Speed <= 0f)
				{
					return;
				}
				float num = DriftForce.EdgeFade(Mathf.Sqrt(position.x * position.x + position.z * position.z));
				if (num <= 0f)
				{
					return;
				}
				SwimDrift.Compute(sample.X * num, sample.Z * num, ModConfig.SwimmerDriftFactor.Live(), __instance.m_swimSpeed, ModConfig.SwimmerMaxShareOfSwimSpeed.Live(), __instance.m_swimAcceleration, out var dvx, out var dvz);
				if (dvx != 0f || dvz != 0f)
				{
					___m_currentVel.x += dvx;
					___m_currentVel.z += dvz;
					EverRan = true;
					PushCount++;
					LastWaterSpeed = sample.Speed;
					LastDrift = SwimDrift.SteadyStateDrift(Mathf.Sqrt(dvx * dvx + dvz * dvz), __instance.m_swimAcceleration);
					if (ModConfig.VerboseLogging.Value && Time.realtimeSinceStartup >= _nextLogTime)
					{
						_nextLogTime = Time.realtimeSinceStartup + 3f;
						Vector3 velocity = __instance.GetVelocity();
						float num2 = Mathf.Sqrt(velocity.x * velocity.x + velocity.z * velocity.z);
						Undertow.Log.LogInfo((object)($"swim drift @ ({position.x:0},{position.z:0}) | water {sample.Speed:0.###} " + $"drift {LastDrift:0.###} (cap {__instance.m_swimSpeed * ModConfig.SwimmerMaxShareOfSwimSpeed.Live():0.###}) " + $"| swimmer {num2:0.###} m/s, swimSpeed {__instance.m_swimSpeed:0.##}"));
					}
				}
			}
			catch (Exception ex)
			{
				ReportError("swim postfix", ex);
			}
		}

		private static void ReportError(string where, Exception ex)
		{
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (realtimeSinceStartup < _nextErrorLogTime)
			{
				_suppressedErrors++;
				return;
			}
			string text = ((_suppressedErrors > 0) ? (" (and " + _suppressedErrors.ToString(CultureInfo.InvariantCulture) + " more like it since the last line)") : "");
			_suppressedErrors = 0L;
			_nextErrorLogTime = realtimeSinceStartup + 30f;
			Undertow.Log.LogWarning((object)(where + ": " + ex.Message + text));
		}
	}
	[HarmonyPatch(typeof(ZRoutedRpc), "RPC_RoutedRPC")]
	public static class Patch_RoutedRpc_Sender
	{
		[HarmonyPrefix]
		[HarmonyPriority(200)]
		private static bool Prefix(ZRpc rpc, ZPackage pkg, bool __runOriginal)
		{
			if (!__runOriginal)
			{
				return false;
			}
			return ConfigSync.AllowRoutedPacket(rpc, pkg);
		}
	}
	[HarmonyPatch(typeof(Ship), "CustomFixedUpdate")]
	public static class Patch_Ship_Drift
	{
		private sealed class Cached
		{
			public float NextEvalTime;

			public FieldSample Sample;

			public bool Valid;

			public WaterVolume WvCentre;

			public WaterVolume WvLeft;

			public WaterVolume WvRight;

			public WaterVolume WvFore;

			public WaterVolume WvAft;
		}

		public static long PushCount;

		public static float LastWaterSpeed;

		public static float LastAppliedDv;

		private static Ship _lastShip;

		public static bool EverRan;

		private const float LogIntervalSeconds = 2f;

		private static float _nextLogTime;

		private const float ErrorIntervalSeconds = 30f;

		private static float _nextErrorLogTime;

		private static long _suppressedErrors;

		public static string LastMode = "(none)";

		public static float LastSubmergence;

		private static readonly ConditionalWeakTable<Ship, Cached> _cache = new ConditionalWeakTable<Ship, Cached>();

		public static string LastShip
		{
			get
			{
				if (!((Object)(object)_lastShip != (Object)null))
				{
					return "(none)";
				}
				return ((Object)_lastShip).name;
			}
		}

		private static void Postfix(Ship __instance, float fixedDeltaTime, ZNetView ___m_nview, Rigidbody ___m_body, List<Player> ___m_players, Speed ___m_speed)
		{
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: 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_0099: 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_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			//IL_012a: Invalid comparison between Unknown and I4
			//IL_01b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_022e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0235: Unknown result type (might be due to invalid IL or missing references)
			//IL_023d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0244: Unknown result type (might be due to invalid IL or missing references)
			//IL_029a: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a5: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (!ModConfig.EnableDrift.Live() || (Object)(object)__instance == (Object)null || (Object)(object)___m_body == (Object)null || (Object)(object)___m_nview == (Object)null || !___m_nview.IsValid() || !___m_nview.IsOwner())
				{
					return;
				}
				Vector3 position = ((Component)__instance).transform.position;
				bool flag = ___m_players != null && ___m_players.Count > 0;
				float num = (flag ? 1f : ModConfig.UnattendedDriftFactor.Live());
				if (num <= 0f)
				{
					return;
				}
				float num2 = DriftForce.EdgeFade(Mathf.Sqrt(position.x * position.x + position.z * position.z));
				if (num2 <= 0f || !TryGetSample(__instance, position, out var sample) || sample.Speed <= 0f)
				{
					return;
				}
				float edgeFade = num2;
				Vector3 linearVelocity = ___m_body.linearVelocity;
				float speed = sample.Speed;
				float num3 = ((speed > 1E-05f) ? ((linearVelocity.x * sample.X + linearVelocity.z * sample.Z) / speed) : 0f);
				float dvx;
				float dvz;
				if (flag && (int)___m_speed > 0)
				{
					if (!TryUnderWay(__instance, ___m_body, sample, ModConfig.UnderWayDragFactor.Live(), num, edgeFade, out dvx, out dvz))
					{
						return;
					}
					LastMode = "underway";
				}
				else
				{
					DriftForce.Compute(sample.X, sample.Z, num3, ModConfig.DriftStrength.Live(), fixedDeltaTime, num, edgeFade, out dvx, out dvz);
					LastMode = "adrift";
				}
				if (dvx != 0f || dvz != 0f)
				{
					___m_body.AddForce(new Vector3(dvx, 0f, dvz) * ___m_body.mass, (ForceMode)1);
					EverRan = true;
					PushCount++;
					LastWaterSpeed = sample.Speed;
					LastAppliedDv = Mathf.Sqrt(dvx * dvx + dvz * dvz);
					_lastShip = __instance;
					if (ModConfig.VerboseLogging.Value && Time.realtimeSinceStartup >= _nextLogTime)
					{
						_nextLogTime = Time.realtimeSinceStartup + 2f;
						float num4 = Mathf.Sqrt(linearVelocity.x * linearVelocity.x + linearVelocity.z * linearVelocity.z);
						float num5 = ((speed > 0.001f) ? (num3 / speed) : 0f);
						float num6 = ((speed > 0.001f) ? (num4 / speed) : 0f);
						Undertow.Log.LogInfo((object)($"drift {((Object)__instance).name} @ ({position.x:0},{position.z:0}) " + $"depth {sample.Depth:0.#}m {sample.Dominant} | " + $"water {speed:0.###} along {num3:0.###} " + $"ALONG-RATIO {num5:0.##} (total {num6:0.##}) | " + $"dv {LastAppliedDv:0.#####} crew {num:0.##} " + $"mode {LastMode} sub {LastSubmergence:0.##}"));
					}
				}
			}
			catch (Exception ex)
			{
				ReportError("drift postfix", ex);
			}
		}

		private static bool TryUnderWay(Ship ship, Rigidbody body, FieldSample sample, float strength, float crewFactor, float edgeFade, out float dvx, out float dvz)
		{
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: 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_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0180: Unknown result type (might be due to invalid IL or missing references)
			//IL_0185: Unknown result type (might be due to invalid IL or missing references)
			//IL_018d: 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_0195: Unknown result type (might be due to invalid IL or missing references)
			//IL_019a: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0201: Unknown result type (might be due to invalid IL or missing references)
			//IL_0206: Unknown result type (might be due to invalid IL or missing references)
			//IL_020b: Unknown result type (might be due to invalid IL or missing references)
			//IL_020f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0219: Unknown result type (might be due to invalid IL or missing references)
			dvx = 0f;
			dvz = 0f;
			BoxCollider floatCollider = ship.m_floatCollider;
			if ((Object)(object)floatCollider == (Object)null)
			{
				return false;
			}
			Cached value = _cache.GetValue(ship, (Ship _) => new Cached());
			Transform transform = ((Component)floatCollider).transform;
			Vector3 size = floatCollider.size;
			Vector3 position = transform.position;
			Vector3 forward = transform.forward;
			Vector3 right = transform.right;
			Vector3 worldCenterOfMass = body.worldCenterOfMass;
			float num = Floating.GetWaterLevel(worldCenterOfMass, ref value.WvCentre) + Floating.GetWaterLevel(position - right * (size.x * 0.5f), ref value.WvLeft) + Floating.GetWaterLevel(position + right * (size.x * 0.5f), ref value.WvRight) + Floating.GetWaterLevel(position + forward * (size.z * 0.5f), ref value.WvFore) + Floating.GetWaterLevel(position - forward * (size.z * 0.5f), ref value.WvAft);
			num *= 0.2f;
			float num2 = worldCenterOfMass.y - num - ship.m_waterLevelOffset;
			if (num2 > ship.m_disableLevel)
			{
				return false;
			}
			float num3 = ((ship.m_forceDistance > 0.01f) ? ship.m_forceDistance : 0.01f);
			float submergence = (LastSubmergence = Mathf.Clamp01(Mathf.Abs(num2) / num3));
			Vector3 forward2 = ((Component)ship).transform.forward;
			Vector3 right2 = ((Component)ship).transform.right;
			Vector3 linearVelocity = body.linearVelocity;
			Vector3 val = default(Vector3);
			((Vector3)(ref val))..ctor(sample.X, 0f, sample.Z);
			DriftForce.ComputeUnderWay(Vector3.Dot(val, forward2), Vector3.Dot(val, right2), Vector3.Dot(linearVelocity, forward2), Vector3.Dot(linearVelocity, right2), ship.m_dampingForward, ship.m_dampingSideway, submergence, strength, crewFactor, edgeFade, out var dvForward, out var dvRight);
			Vector3 val2 = forward2 * dvForward + right2 * dvRight;
			dvx = val2.x;
			dvz = val2.z;
			return true;
		}

		private static bool TryGetSample(Ship ship, Vector3 position, out FieldSample sample)
		{
			//IL_0050: 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)
			Cached value = _cache.GetValue(ship, (Ship _) => new Cached());
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (value.Valid && realtimeSinceStartup < value.NextEvalTime)
			{
				sample = value.Sample;
				return true;
			}
			if (!SeaContext.TryEvaluate(position.x, position.z, out sample))
			{
				value.Valid = false;
				return false;
			}
			value.Sample = sample;
			value.Valid = true;
			value.NextEvalTime = realtimeSinceStartup + Mathf.Max(0f, ModConfig.FieldRefreshSeconds.Value);
			return true;
		}

		private static void ReportError(string where, Exception ex)
		{
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (realtimeSinceStartup < _nextErrorLogTime)
			{
				_suppressedErrors++;
				return;
			}
			string text = ((_suppressedErrors > 0) ? (" (and " + _suppressedErrors.ToString(CultureInfo.InvariantCulture) + " more like it since the last line)") : "");
			_suppressedErrors = 0L;
			_nextErrorLogTime = realtimeSinceStartup + 30f;
			Undertow.Log.LogWarning((object)(where + ": " + ex.Message + text));
		}
	}
}
namespace RavenIron.Undertow.Net
{
	public static class ConfigSync
	{
		private const string RpcPublish = "com.raveniron.undertow.cfg";

		private const string RpcPush = "com.raveniron.undertow.cfgpush";

		private const string RpcRequest = "com.raveniron.undertow.cfgwant";

		private const string RpcAck = "com.raveniron.undertow.cfgack";

		private const float BroadcastIntervalSeconds = 30f;

		private const float RequestFloorSeconds = 1f;

		private static ZRoutedRpc _registeredWith;

		private static float _nextBroadcast;

		private static float _lastBroadcast = float.NegativeInfinity;

		private static float _lastRequestAnswered = float.NegativeInfinity;

		private static int _lastPeerCount = -1;

		private static bool _hooked;

		private static bool _asked;

		private static bool _applyingPush;

		private static readonly Dictionary<ConfigEntryBase, object> _overrides = new Dictionary<ConfigEntryBase, object>();

		private static bool _any;

		private static readonly Dictionary<string, ConfigEntryBase> _bound = new Dictionary<string, ConfigEntryBase>(StringComparer.Ordinal);

		public static string LastEvent = "no config has arrived from a server";

		private static bool _refusalLogged;

		private static readonly int[] _ourHashes = new int[4]
		{
			StringExtensionMethods.GetStableHashCode("com.raveniron.undertow.cfg"),
			StringExtensionMethods.GetStableHashCode("com.raveniron.undertow.cfgpush"),
			StringExtensionMethods.GetStableHashCode("com.raveniron.undertow.cfgwant"),
			StringExtensionMethods.GetStableHashCode("com.raveniron.undertow.cfgack")
		};

		private static readonly HashSet<long> _forgeryWarned = new HashSet<long>();

		public static int AdoptedCount => _overrides.Count;

		public static int BoundCount => _bound.Count;

		public static void Bind()
		{
			_bound.Clear();
			Add((ConfigEntryBase)(object)ModConfig.MaxCurrentSpeed);
			Add((ConfigEntryBase)(object)ModConfig.TidePeriodSeconds);
			Add((ConfigEntryBase)(object)ModConfig.TideAmplitude);
			Add((ConfigEntryBase)(object)ModConfig.CoastalStrength);
			Add((ConfigEntryBase)(object)ModConfig.StormSurgeMultiplier);
			Add((ConfigEntryBase)(object)ModConfig.EnableWrathBridge);
			Add((ConfigEntryBase)(object)ModConfig.EnableDrift);
			Add((ConfigEntryBase)(object)ModConfig.DriftStrength);
			Add((ConfigEntryBase)(object)ModConfig.UnderWayDragFactor);
			Add((ConfigEntryBase)(object)ModConfig.UnattendedDriftFactor);
			Add((ConfigEntryBase)(object)ModConfig.EnableSwimmers);
			Add((ConfigEntryBase)(object)ModConfig.SwimmerDriftFactor);
			Add((ConfigEntryBase)(object)ModConfig.SwimmerMaxShareOfSwimSpeed);
			for (int i = 0; i < ConfigWire.SyncedKeys.Length; i++)
			{
				if (!_bound.ContainsKey(ConfigWire.SyncedKeys[i]))
				{
					Undertow.Log.LogError((object)("ConfigSync: '" + ConfigWire.SyncedKeys[i] + "' is on the wire but nothing binds it — that key will never sync in either direction. Fix ConfigWire.SyncedKeys."));
				}
			}
		}

		private static void Add(ConfigEntryBase entry)
		{
			if (!(((entry != null) ? entry.Definition : null) == (ConfigDefinition)null))
			{
				string text = ConfigWire.Address(entry.Definition.Section, entry.Definition.Key);
				if (!ConfigWire.IsSynced(entry.Definition.Section, entry.Definition.Key))
				{
					Undertow.Log.LogError((object)("ConfigSync: '" + text + "' is bound for sync but is absent from ConfigWire.SyncedKeys — it will never travel. Fix one of the two."));
				}
				else
				{
					_bound[text] = entry;
				}
			}
		}

		public static float Live(this ConfigEntry<float> entry)
		{
			if (!_any || entry == null)
			{
				return entry?.Value ?? 0f;
			}
			if (_overrides.TryGetValue((ConfigEntryBase)(object)entry, out var value) && value is float)
			{
				return (float)value;
			}
			return entry.Value;
		}

		public static bool Live(this ConfigEntry<bool> entry)
		{
			if (!_any || entry == null)
			{
				return entry?.Value ?? false;
			}
			if (_overrides.TryGetValue((ConfigEntryBase)(object)entry, out var value) && value is bool)
			{
				return (bool)value;
			}
			return entry.Value;
		}

		public static void Tick(ZNet znet)
		{
			if ((Object)(object)znet == (Object)null)
			{
				return;
			}
			try
			{
				EnsureRegistered();
				HookLocalEdits();
				if (!znet.IsServer())
				{
					ZRoutedRpc instance = ZRoutedRpc.instance;
					if (instance != null)
					{
						MaybeAsk(znet, instance);
					}
					return;
				}
				int peerConnections = znet.GetPeerConnections();
				float realtimeSinceStartup = Time.realtimeSinceStartup;
				if (peerConnections != _lastPeerCount)
				{
					_lastPeerCount = peerConnections;
					if (peerConnections > 0)
					{
						Broadcast("peer list changed");
					}
				}
				else if (!(realtimeSinceStartup < _nextBroadcast))
				{
					_nextBroadcast = realtimeSinceStartup + 30f;
					if (peerConnections > 0)
					{
						Broadcast("heartbeat");
					}
				}
			}
			catch (Exception ex)
			{
				Undertow.Log.LogWarning((object)("ConfigSync: tick failed — " + ex.Message));
			}
		}

		public static void Reset()
		{
			_registeredWith = null;
			_lastPeerCount = -1;
			_nextBroadcast = 0f;
			_lastRequestAnswered = float.NegativeInfinity;
			_asked = false;
			_refusalLogged = false;
			_forgeryWarned.Clear();
			if (_overrides.Count == 0)
			{
				LastEvent = "no config has arrived from a server";
				return;
			}
			_overrides.Clear();
			_any = false;
			LastEvent = "left the server — back on local values";
			Undertow.Log.LogInfo((object)"ConfigSync: disconnected — local config back in force.");
		}

		public static bool AllowRoutedPacket(ZRpc rpc, ZPackage pkg)
		{
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null || !instance.IsServer() || rpc == null || pkg == null)
			{
				return true;
			}
			int pos = pkg.GetPos();
			long claimedSender;
			int value;
			try
			{
				pkg.ReadLong();
				claimedSender = pkg.ReadLong();
				pkg.ReadLong();
				pkg.ReadZDOID();
				value = pkg.ReadInt();
			}
			catch
			{
				return true;
			}
			finally
			{
				pkg.SetPos(pos);
			}
			if (Array.IndexOf(_ourHashes, value) < 0)
			{
				return true;
			}
			ZNetPeer val = null;
			List<ZNetPeer> peers = instance.GetPeers();
			if (peers != null)
			{
				for (int i = 0; i < peers.Count; i++)
				{
					if (peers[i] != null && peers[i].m_rpc == rpc)
					{
						val = peers[i];
						break;
					}
				}
			}
			long num = val?.m_uid ?? 0;
			if (ConfigWire.RoutedSenderIsGenuine(val != null, num, claimedSender))
			{
				return true;
			}
			if (_forgeryWarned.Add(num))
			{
				Undertow.Log.LogWarning((object)("ConfigSync: dropped a config message from peer " + num + " (" + (val?.m_playerName ?? "unknown connection") + ") that claimed to come from peer " + claimedSender + ". Only a modified client does that. Nothing was changed or relayed; said once per connection."));
			}
			return false;
		}

		private static void EnsureRegistered()
		{
			ZRoutedRpc instance = ZRoutedRpc.instance;
			if (instance != null && instance != _registeredWith)
			{
				instance.Register<string>("com.raveniron.undertow.cfg", (Action<long, string>)OnPublished);
				instance.Register<string>("com.raveniron.undertow.cfgpush", (Action<long, string>)OnPushed);
				instance.Register<string>("com.raveniron.undertow.cfgwant", (Action<long, string>)OnRequested);
				instance.Register<string>("com.raveniron.undertow.cfgack", (Action<long, string>)OnAcknowledged);
				_registeredWith = instance;
				Undertow.Log.LogInfo((object)"ConfigSync: RPCs registered (undertow-cfg/2).");
			}
		}

		private static void MaybeAsk(ZNet znet, ZRoutedRpc rpc)
		{
			if (!_asked && !znet.IsServer() && znet.GetServerPeer() != null)
			{
				_asked = true;
				rpc.InvokeRoutedRPC("com.raveniron.undertow.cfgwant", new object[1] { "undertow-cfg/2" });
				if (ModConfig.VerboseLogging.Value)
				{
					Undertow.Log.LogInfo((object)"ConfigSync: asked the server for its config.");
				}
			}
		}

		private static void OnRequested(long sender, string payload)
		{
			try
			{
				ZNet instance = ZNet.instance;
				if ((Object)(object)instance == (Object)null || !instance.IsServer())
				{
					return;
				}
				float realtimeSinceStartup = Time.realtimeSinceStartup;
				if (realtimeSinceStartup - _lastRequestAnswered < 1f)
				{
					if (ModConfig.VerboseLogging.Value)
					{
						Undertow.Log.LogInfo((object)("ConfigSync: peer " + sender + " asked again within " + 1f + "s — not answered twice."));
					}
				}
				else
				{
					_lastRequestAnswered = realtimeSinceStartup;
					Broadcast("peer " + sender + " asked");
				}
			}
			catch (Exception ex)
			{
				Undertow.Log.LogWarning((object)("ConfigSync: could not answer a config request — " + ex.Message));
			}
		}

		private static void HookLocalEdits()
		{
			if (!_hooked && _bound.Count != 0)
			{
				ConfigEntry<float> maxCurrentSpeed = ModConfig.MaxCurrentSpeed;
				ConfigFile val = ((maxCurrentSpeed != null) ? ((ConfigEntryBase)maxCurrentSpeed).ConfigFile : null);
				if (val != null)
				{
					val.SettingChanged += OnLocalSettingChanged;
					_hooked = true;
				}
			}
		}

		private static void Broadcast(string why)
		{
			ZRoutedRpc instance = ZRoutedRpc.instance;
			if (instance == null || _bound.Count == 0)
			{
				return;
			}
			List<KeyValuePair<string, string>> list = new List<KeyValuePair<string, string>>(_bound.Count);
			foreach (KeyValuePair<string, ConfigEntryBase> item in _bound)
			{
				string text = Serialise(item.Value);
				if (text != null)
				{
					list.Add(new KeyValuePair<string, string>(item.Key, text));
				}
			}
			instance.InvokeRoutedRPC(0L, "com.raveniron.undertow.cfg", new object[1] { ConfigWire.Format(list) });
			_lastBroadcast = Time.realtimeSinceStartup;
			_nextBroadcast = _lastBroadcast + 30f;
			if (ModConfig.VerboseLogging.Value)
			{
				Undertow.Log.LogInfo((object)("ConfigSync: published (" + why + ") — " + ConfigWire.Describe(list)));
			}
		}

		private static void OnPublished(long sender, string payload)
		{
			try
			{
				ZNet instance = ZNet.instance;
				if ((Object)(object)instance == (Object)null || instance.IsServer())
				{
					return;
				}
				ZNetPeer serverPeer = instance.GetServerPeer();
				if (serverPeer == null)
				{
					return;
				}
				if (serverPeer.m_uid != sender)
				{
					Undertow.Log.LogWarning((object)("ConfigSync: ignored a config payload from peer " + sender + ", which is not this server. Local values stand."));
					return;
				}
				List<KeyValuePair<string, string>> list = ConfigWire.Parse(payload);
				if (list == null)
				{
					LastEvent = "refused a payload this build cannot read";
					if (!_refusalLogged)
					{
						_refusalLogged = true;
						Undertow.Log.LogWarning((object)"ConfigSync: the server's config payload is not 'undertow-cfg/2' — the server runs a different Undertow, and its sea may differ from this build's. Sailing on local values. Said once per session; `wake status` keeps reporting it. Update both ends to the same version.");
					}
				}
				else
				{
					Adopt(list);
				}
			}
			catch (Exception ex)
			{
				Undertow.Log.LogWarning((object)("ConfigSync: could not read the server's config — " + ex.Message));
			}
		}

		private static void Adopt(List<KeyValuePair<string, string>> pairs)
		{
			List<string> list = new List<string>();
			int num = 0;
			for (int i = 0; i < pairs.Count; i++)
			{
				string key = pairs[i].Key;
				string before;
				string after;
				if (!_bound.TryGetValue(key, out var value))
				{
					num++;
				}
				else if (TryOverride(value, key, pairs[i].Value, out before, out after))
				{
					list.Add(ShortName(key) + " " + before + " -> " + after);
				}
			}
			_any = _overrides.Count > 0;
			if (_any)
			{
				LastEvent = "the server set " + _overrides.Count + " value(s)";
			}
			if (list.Count > 0)
			{
				Undertow.Log.LogInfo((object)("ConfigSync: the server's sea is in force — " + string.Join(", ", list.ToArray()) + "."));
			}
			if (num > 0 && ModConfig.VerboseLogging.Value)
			{
				Undertow.Log.LogInfo((object)("ConfigSync: ignored " + num + " value(s) this build does not know. That is the server running a different Undertow, and is harmless."));
			}
		}

		private static bool TryOverride(ConfigEntryBase entry, string address, string text, out string before, out string after)
		{
			before = null;
			after = null;
			object obj;
			if (entry is ConfigEntry<float> entry2)
			{
				if (!ConfigWire.TryReadFloat(text, out var value))
				{
					return Reject(address, text);
				}
				before = ConfigWire.Write(entry2.Live());
				obj = Clamp(entry, value);
				after = ConfigWire.Write((float)obj);
				if (Math.Abs(value - (float)obj) > 1E-06f)
				{
					Undertow.Log.LogWarning((object)("ConfigSync: the server set " + ShortName(address) + " to " + ConfigWire.Write(value) + ", which is outside what this build allows — using " + after + " instead. The sea here is NOT what that server intended; the two ends are probably running different versions of Undertow."));
				}
			}
			else
			{
				if (!(entry is ConfigEntry<bool> entry3))
				{
					Undertow.Log.LogWarning((object)("ConfigSync: '" + address + "' is a " + entry.SettingType.Name + ", which this build cannot carry. Local value stands."));
					return false;
				}
				if (!ConfigWire.TryReadBool(text, out var value2))
				{
					return Reject(address, text);
				}
				before = ConfigWire.Write(entry3.Live());
				obj = value2;
				after = ConfigWire.Write(value2);
			}
			_overrides[entry] = obj;
			return !string.Equals(before, after, StringComparison.Ordinal);
		}

		private static bool Reject(string address, string text)
		{
			Undertow.Log.LogWarning((object)("ConfigSync: could not read '" + text + "' for " + address + ". Local value stands."));
			return false;
		}

		private static object Clamp(ConfigEntryBase entry, object value)
		{
			ConfigDescription description = entry.Description;
			AcceptableValueBase val = ((description != null) ? description.AcceptableValues : null);
			if (val != null)
			{
				return val.Clamp(value);
			}
			return value;
		}

		private static void OnLocalSettingChanged(object _, SettingChangedEventArgs args)
		{
			try
			{
				if (_applyingPush)
				{
					return;
				}
				ConfigEntryBase val = ((args != null) ? args.ChangedSetting : null);
				if (((val != null) ? val.Definition : null) == (ConfigDefinition)null || !ConfigWire.IsSynced(val.Definition.Section, val.Definition.Key))
				{
					return;
				}
				ZNet instance = ZNet.instance;
				if ((Object)(object)instance == (Object)null)
				{
					return;
				}
				string text = ConfigWire.Address(val.Definition.Section, val.Definition.Key);
				string text2 = Serialise(val);
				if (text2 == null)
				{
					return;
				}
				if (instance.IsServer())
				{
					Broadcast("config edited on the server");
					return;
				}
				ZRoutedRpc instance2 = ZRoutedRpc.instance;
				if (instance2 != null)
				{
					List<KeyValuePair<string, string>> pairs = new List<KeyValuePair<string, string>>
					{
						new KeyValuePair<string, string>(text, text2)
					};
					instance2.InvokeRoutedRPC("com.raveniron.undertow.cfgpush", new object[1] { ConfigWire.Format(pairs) });
					Undertow.Log.LogInfo((object)("ConfigSync: sent " + ShortName(text) + "=" + text2 + " to the server; waiting for it to say whether that was allowed."));
				}
			}
			catch (Exception ex)
			{
				Undertow.Log.LogWarning((object)("ConfigSync: could not push a config change — " + ex.Message));
			}
		}

		private static void OnPushed(long sender, string payload)
		{
			try
			{
				ZNet instance = ZNet.instance;
				if ((Object)(object)instance == (Object)null || !instance.IsServer())
				{
					return;
				}
				if (!SenderIsAdmin(instance, sender))
				{
					Undertow.Log.LogWarning((object)("ConfigSync: refused a config push from peer " + sender + " — not an admin."));
					Acknowledge(sender, "the server refused it — you are not an admin on this server. Your own config file keeps the value; the sea does not.");
					return;
				}
				List<KeyValuePair<string, string>> list = ConfigWire.Parse(payload);
				if (list == null || list.Count == 0)
				{
					Undertow.Log.LogWarning((object)("ConfigSync: a config push arrived that this build cannot read — " + ((list == null) ? "it is not 'undertow-cfg/2'." : "it carried no usable values.") + " Nothing was changed. Check that both ends run the same Undertow."));
					return;
				}
				bool flag = false;
				_applyingPush = true;
				try
				{
					for (int i = 0; i < list.Count; i++)
					{
						if (!_bound.TryGetValue(list[i].Key, out var value))
						{
							Undertow.Log.LogWarning((object)("ConfigSync: an admin pushed '" + list[i].Key + "', which this build does not bind. Ignored."));
							continue;
						}
						string serializedValue = value.GetSerializedValue();
						value.SetSerializedValue(list[i].Value);
						string serializedValue2 = value.GetSerializedValue();
						if (string.Equals(serializedValue2, serializedValue, StringComparison.Ordinal) && !string.Equals(list[i].Value, serializedValue, StringComparison.Ordinal))
						{
							Undertow.Log.LogWarning((object)("ConfigSync: '" + list[i].Value + "' did not take on " + ShortName(list[i].Key) + " — it is still " + serializedValue + ". Out of range, or not a " + value.SettingType.Name + "."));
						}
						else
						{
							flag = true;
							Undertow.Log.LogInfo((object)("ConfigSync: admin (peer " + sender + ") set " + ShortName(list[i].Key) + " " + serializedValue + " -> " + serializedValue2 + "."));
						}
					}
				}
				finally
				{
					_applyingPush = false;
				}
				if (flag)
				{
					Broadcast("an admin changed a value");
					Acknowledge(sender, "the server accepted it and told every client.");
				}
				else
				{
					Acknowledge(sender, "the server read it but nothing changed — the value was already set, or it would not take. See the server log.");
				}
			}
			catch (Exception ex)
			{
				Undertow.Log.LogWarning((object)("ConfigSync: could not apply a config push — " + ex.Message));
			}
		}

		private static bool SenderIsAdmin(ZNet znet, long sender)
		{
			ZNetPeer peer = znet.GetPeer(sender);
			object obj;
			if (peer == null)
			{
				obj = null;
			}
			else
			{
				ISocket socket = peer.m_socket;
				obj = ((socket != null) ? socket.GetHostName() : null);
			}
			string text = (string)obj;
			if (!string.IsNullOrEmpty(text))
			{
				return znet.IsAdmin(text);
			}
			return false;
		}

		private static void Acknowledge(long peer, string what)
		{
			try
			{
				ZRoutedRpc instance = ZRoutedRpc.instance;
				if (instance != null)
				{
					instance.InvokeRoutedRPC(peer, "com.raveniron.undertow.cfgack", new object[1] { what ?? "" });
				}
			}
			catch (Exception ex)
			{
				Undertow.Log.LogWarning((object)("ConfigSync: could not acknowledge peer " + peer + " — " + ex.Message));
			}
		}

		private static void OnAcknowledged(long sender, string what)
		{
			try
			{
				ZNet instance = ZNet.instance;
				if (!((Object)(object)instance == (Object)null) && !instance.IsServer())
				{
					ZNetPeer serverPeer = instance.GetServerPeer();
					if (serverPeer != null && serverPeer.m_uid == sender)
					{
						LastEvent = "the server answered a pushed value";
						Undertow.Log.LogInfo((object)("ConfigSync: " + what));
					}
				}
			}
			catch (Exception ex)
			{
				Undertow.Log.LogWarning((object)("ConfigSync: could not read the server's answer — " + ex.Message));
			}
		}

		private static string Serialise(ConfigEntryBase entry)
		{
			if (entry is ConfigEntry<float> val)
			{
				return ConfigWire.Write(val.Value);
			}
			if (entry is ConfigEntry<bool> val2)
			{
				return ConfigWire.Write(val2.Value);
			}
			return null;
		}

		private static string ShortName(string address)
		{
			int num = address.IndexOf('|');
			if (num < 0)
			{
				return address;
			}
			return address.Substring(num + 1);
		}
	}
}
namespace RavenIron.Undertow.Core
{
	public static class ConfigLedger
	{
		public sealed class Rebase
		{
			public string Section;

			public string Key;

			public string[] OldDefaults;

			public string Because;
		}

		public sealed class Backfill
		{
			public string Section;

			public string Key;

			public string LegacyValue;

			public string Because;
		}

		public sealed class Retire
		{
			public string Section;

			public string Key;

			public string Because;
		}

		public struct KeptSlot
		{
			public string Slot;

			public string Value;
		}

		public struct BackfilledSlot
		{
			public string Slot;

			public string Value;

			public string Because;
		}

		public sealed class MigrationPlan
		{
			public int FromVersion;

			public int ToVersion;

			public List<string> ResetToDefault = new List<string>();

			public List<KeptSlot> Kept = new List<KeptSlot>();

			public List<BackfilledSlot> Backfilled = new List<BackfilledSlot>();

			public List<string> Retired = new List<string>();

			public bool IsEmpty
			{
				get
				{
					if (ResetToDefault.Count == 0 && Kept.Count == 0 && Backfilled.Count == 0)
					{
						return Retired.Count == 0;
					}
					return false;
				}
			}

			public bool IsDestructive
			{
				get
				{
					if (ResetToDefault.Count <= 0)
					{
						return Retired.Count > 0;
					}
					return true;
				}
			}
		}

		public const string MetaSection = "0 - Meta";

		public const string VersionKey = "ConfigVersion";

		public const int CurrentVersion = 2;

		private static readonly Dictionary<int, Rebase[]> Rebases = new Dictionary<int, Rebase[]> { 
		{
			2,
			new Rebase[1]
			{
				new Rebase
				{
					Section = "7 - Drift lines",
					Key = "DriftLineMinDepth",
					OldDefaults = new string[1] { "10" },
					Because = "0.7 shipped this at 10 m, which excluded every race, strait, shelf and coastal set — the open ocean is a flat 30 m, so the 'fast water between islands' the drift lines exist to show was gated out of its own habitat. The new floor of 2 m ends its ramp exactly where the current's own shallow fade ends. A value you chose yourself is kept and named."
				}
			}
		} };

		private static readonly Dictionary<int, Backfill[]> Backfills = new Dictionary<int, Backfill[]>();

		private static readonly Dictionary<int, Retire[]> Retirements = new Dictionary<int, Retire[]>();

		public static string Slot(string section, string key)
		{
			return section + "::" + key;
		}

		public static bool SplitSlot(string slot, out string section, out string key)
		{
			section = null;
			key = null;
			if (slot == null)
			{
				return false;
			}
			int num = slot.IndexOf("::", StringComparison.Ordinal);
			if (num <= 0 || num + 2 >= slot.Length)
			{
				return false;
			}
			section = slot.Substring(0, num);
			key = slot.Substring(num + 2);
			return true;
		}

		public static Dictionary<string, string> ParseIni(IEnumerable<string> lines)
		{
			Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal);
			if (lines == null)
			{
				return dictionary;
			}
			string section = "";
			foreach (string line in lines)
			{
				if (line == null)
				{
					continue;
				}
				string text = line.Trim();
				if (text.Length == 0 || text[0] == '#')
				{
					continue;
				}
				if (text[0] == '[' && text[text.Length - 1] == ']')