Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Undertow v1.0.4
plugins/Undertow.dll
Decompiled 17 hours ago
The result has been truncated due to the large size, download it to view full contents!
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] == ']')