using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; using VVStabilizer.Patches; [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("VVStabilizer")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Freezes unattended ValheimRAFT vehicles and smooths control-handoff to stop the load-drift and takeover-jolt bugs.")] [assembly: AssemblyFileVersion("0.1.0.0")] [assembly: AssemblyInformationalVersion("0.1.0+9e4758b82ea2b91637b737b6793f2466ef6f1c9a")] [assembly: AssemblyProduct("ValheimVehicles Stabilizer")] [assembly: AssemblyTitle("VVStabilizer")] [assembly: AssemblyVersion("0.1.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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 VVStabilizer { internal static class Config { internal static ConfigEntry Enabled; internal static ConfigEntry FreezeUndriven; internal static ConfigEntry SettleSeconds; internal static ConfigEntry ReseatInterval; internal static ConfigEntry MisseatedEpsilon; internal static ConfigEntry ReseatOnLogin; internal static ConfigEntry DebugLogging; internal static void Init(ConfigFile cfg) { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Expected O, but got Unknown //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Expected O, but got Unknown Enabled = cfg.Bind("General", "Enabled", true, "Master switch. If false, the plugin patches nothing and the game runs vanilla-ValheimRAFT."); FreezeUndriven = cfg.Bind("Stabilizer", "FreezeUndriven", true, "When the vehicle has no controlling driver, hold it kinematic and re-seated at the water/ground line so it cannot drift (fixes the fly-away). Runs only on the ZDO owner (the dedicated server for idle boats)."); SettleSeconds = cfg.Bind("Stabilizer", "SettleSeconds", 0.4f, new ConfigDescription("After a player takes the wheel, keep the vehicle kinematic + re-seated for this many seconds before releasing to normal dynamics (gentle wake-up; fixes the takeover jolt).", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 2f), Array.Empty())); ReseatInterval = cfg.Bind("Stabilizer", "ReseatInterval", 5f, new ConfigDescription("While undriven, re-run the (throttled) re-seat at most this often when the boat looks mis-seated. The cheap per-frame kinematic hold is unaffected.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 60f), Array.Empty())); MisseatedEpsilon = cfg.Bind("Stabilizer", "MisseatedHeightEpsilon", 0.5f, "How far (metres) the vehicle Y may sit above max(waterLevel, groundHeight) before the undriven loop considers it mis-seated and triggers a throttled re-seat."); ReseatOnLogin = cfg.Bind("Stabilizer", "ReseatOnLogin", true, "Re-seat the vehicle when a player logs in / spawns onto it (DynamicLocations), so players land on a seated, upright boat. Client-side."); DebugLogging = cfg.Bind("General", "DebugLogging", false, "Verbose logging of freeze/settle/reseat decisions. Leave off for production."); } } internal sealed class CtrlState { internal bool WasDriven; internal bool EverReseated; internal float SettleUntilTime; internal float LastReseatTime; } internal static class State { private static readonly ConditionalWeakTable _table = new ConditionalWeakTable(); internal static CtrlState Get(object controller) { if (controller == null) { return null; } return _table.GetValue(controller, (object _) => new CtrlState()); } } internal static class Log { private static ManualLogSource _log; private static readonly HashSet _seen = new HashSet(); internal static void Init(ManualLogSource src) { _log = src; } internal static void Info(string msg) { ManualLogSource log = _log; if (log != null) { log.LogInfo((object)msg); } } internal static void Warn(string msg) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)msg); } } internal static void Error(string msg) { ManualLogSource log = _log; if (log != null) { log.LogError((object)msg); } } internal static void Debug(string msg) { if (Config.DebugLogging != null && Config.DebugLogging.Value) { ManualLogSource log = _log; if (log != null) { log.LogInfo((object)("[dbg] " + msg)); } } } internal static void ErrorOnce(string key, Exception e) { if (_seen.Add(key)) { ManualLogSource log = _log; if (log != null) { log.LogError((object)$"[{key}] {e}"); } } } } [BepInPlugin("com.bigelowliam.vvstabilizer", "ValheimVehicles Stabilizer", "0.1.1")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { internal const string Guid = "com.bigelowliam.vvstabilizer"; internal const string Ver = "0.1.1"; private Harmony _harmony; private void Awake() { //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Expected O, but got Unknown Log.Init(((BaseUnityPlugin)this).Logger); Config.Init(((BaseUnityPlugin)this).Config); if (!Config.Enabled.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"VVStabilizer disabled via config; no patches applied."); return; } try { VV.Init(); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("VVStabilizer API bind failed; running inert: " + ex)); return; } if (!VV.Ready) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"VVStabilizer: required ValheimVehicles API not found (version drift?). Plugin is inert — game runs normally."); return; } _harmony = new Harmony("com.bigelowliam.vvstabilizer"); int num = 0; num += TryPatch(VV.GuardedFixedUpdate, typeof(UndrivenFreezePatch), "Postfix", "GuardedFixedUpdate (undriven freeze)"); num += TryPatch(VV.OnControlsHandOff, typeof(HandoffSettlePatch), "Postfix", "OnControlsHandOff (takeover settle)"); if (Config.ReseatOnLogin.Value) { num += TryPatch(VV.OnLoginMoveToZDO, typeof(LoginReseatPatch), "Postfix", "OnLoginMoveToZDO (login reseat)"); } ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("VVStabilizer {0} active — {1} patch(es) applied.", "0.1.1", num)); } private int TryPatch(MethodBase target, Type patchType, string postfixName, string label) { //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Expected O, but got Unknown try { if (target == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("VVStabilizer: patch target not found, skipping: " + label)); return 0; } MethodInfo method = patchType.GetMethod(postfixName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (method == null) { ((BaseUnityPlugin)this).Logger.LogError((object)("VVStabilizer: postfix method missing for " + label)); return 0; } _harmony.Patch(target, (HarmonyMethod)null, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)("VVStabilizer: patched " + label)); return 1; } catch (Exception arg) { ((BaseUnityPlugin)this).Logger.LogError((object)$"VVStabilizer: failed to patch {label}: {arg}"); return 0; } } private void OnDestroy() { try { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } catch { } } } internal sealed class Member { private readonly FieldInfo _field; private readonly MethodInfo _getter; private readonly MethodInfo _setter; internal readonly string Name; internal bool Ok { get { if (!(_field != null)) { return _getter != null; } return true; } } private Member(string name, FieldInfo f, MethodInfo g, MethodInfo s) { Name = name; _field = f; _getter = g; _setter = s; } internal static Member Resolve(Type type, params string[] names) { if (type != null && names != null) { foreach (string text in names) { PropertyInfo propertyInfo = AccessTools.Property(type, text); if (propertyInfo != null && propertyInfo.GetMethod != null) { return new Member(text, null, propertyInfo.GetMethod, propertyInfo.SetMethod); } FieldInfo fieldInfo = AccessTools.Field(type, text); if (fieldInfo != null) { return new Member(text, fieldInfo, null, null); } } } return new Member((names != null && names.Length != 0) ? names[0] : "?", null, null, null); } internal object Get(object instance) { try { if (_field != null) { return _field.GetValue(_field.IsStatic ? null : instance); } if (_getter != null) { return _getter.Invoke(_getter.IsStatic ? null : instance, null); } } catch (Exception e) { Log.ErrorOnce("get:" + Name, e); } return null; } internal void Set(object instance, object value) { try { if (_field != null) { _field.SetValue(_field.IsStatic ? null : instance, value); } else if (_setter != null) { _setter.Invoke(_setter.IsStatic ? null : instance, new object[1] { value }); } } catch (Exception e) { Log.ErrorOnce("set:" + Name, e); } } } internal static class Reflect { internal static Type Type(string fullName) { return AccessTools.TypeByName(fullName); } internal static MethodInfo Method(Type type, string name, Type[] args = null) { if (!(type == null)) { return AccessTools.Method(type, name, args, (Type[])null); } return null; } internal static bool AsBool(object o, bool fallback = false) { if (o is bool) { return (bool)o; } return fallback; } internal static int AsInt(object o, int fallback = 0) { if (o is int) { return (int)o; } return fallback; } } internal static class U { private static PropertyInfo _velProp; private static bool _velResolved; private static PropertyInfo VelocityProp(object rb) { if (_velResolved) { return _velProp; } _velResolved = true; Type type = rb.GetType(); _velProp = type.GetProperty("velocity") ?? type.GetProperty("linearVelocity"); return _velProp; } internal static void ZeroVelocity(Rigidbody rb) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) if (rb == null || rb.isKinematic) { return; } try { rb.angularVelocity = Vector3.zero; } catch { } try { PropertyInfo propertyInfo = VelocityProp(rb); if (propertyInfo != null && propertyInfo.CanWrite) { propertyInfo.SetValue(rb, Vector3.zero, null); } } catch { } } } internal static class VV { private const float HeightEps = 0.2f; private const float DefaultWaterLevel = 30f; internal static Type TVMC; internal static Type TManager; internal static Type TZSync; internal static Type TCommands; internal static Type TLogin; internal static Type TPlayer; internal static Type TZoneSystem; internal static Type TZNetScene; internal static Type TZDO; private static MethodInfo _haveControllingPlayer; private static MethodInfo _haveValidUser; private static MethodInfo _isOwner; private static MethodInfo _isInvalid; private static Member _manager; private static Member _body; private static Member _zsync; private static Member _hauling; private static Member _persistentId; private static Member _isLandVehicle; private static Member _movementController; private static MethodInfo _setKinematic; private static int _setKinematicArgc; private static MethodInfo _fixVehiclePosition; private static FieldInfo _localPlayer; private static Member _zoneInstance; private static Member _waterLevel; private static Member _znsInstance; private static MethodInfo _findInstance; private static MethodInfo _goGetComponent; private static MethodInfo _goGetComponentInParent; private static object _allowFlightEntry; private static object _waterBallastEntry; private static PropertyInfo _entryValueProp; internal static MethodInfo GuardedFixedUpdate { get; private set; } internal static MethodInfo OnControlsHandOff { get; private set; } internal static MethodInfo OnLoginMoveToZDO { get; private set; } internal static bool Ready { get; private set; } internal static bool CanReseat { get { if (_fixVehiclePosition != null) { return _persistentId.Ok; } return false; } } internal static void Init() { TVMC = Reflect.Type("ValheimVehicles.Controllers.VehicleMovementController"); TManager = Reflect.Type("ValheimVehicles.Components.VehicleManager"); TZSync = Reflect.Type("ValheimVehicles.Controllers.VehicleZSyncTransform"); TCommands = Reflect.Type("ValheimVehicles.ConsoleCommands.VehicleCommands"); TLogin = Reflect.Type("ValheimVehicles.ModSupport.DynamicLocationsLoginIntegration"); TPlayer = Reflect.Type("Player"); TZoneSystem = Reflect.Type("ZoneSystem"); TZNetScene = Reflect.Type("ZNetScene"); TZDO = Reflect.Type("ZDO"); GuardedFixedUpdate = Reflect.Method(TVMC, "GuardedFixedUpdate", new Type[1] { typeof(float) }); OnControlsHandOff = ((TPlayer != null) ? Reflect.Method(TVMC, "OnControlsHandOff", new Type[2] { TPlayer, TPlayer }) : null); OnLoginMoveToZDO = ((TLogin != null) ? AccessTools.Method(TLogin, "OnLoginMoveToZDO", (Type[])null, (Type[])null) : null); _haveControllingPlayer = Reflect.Method(TVMC, "HaveControllingPlayer"); _haveValidUser = Reflect.Method(TVMC, "HaveValidUser"); _isOwner = Reflect.Method(TVMC, "IsOwner"); _isInvalid = Reflect.Method(TVMC, "IsInvalid"); _manager = Member.Resolve(TVMC, "Manager"); _body = Member.Resolve(TVMC, "m_body", "rigidbody"); _zsync = Member.Resolve(TVMC, "zsyncTransform"); _hauling = Member.Resolve(TVMC, "isPlayerHaulingVehicle"); _persistentId = Member.Resolve(TManager, "PersistentZdoId"); _isLandVehicle = Member.Resolve(TManager, "IsLandVehicle"); _movementController = Member.Resolve(TManager, "MovementController"); _setKinematic = Reflect.Method(TZSync, "SetKinematic", new Type[2] { typeof(bool), typeof(bool) }); if (_setKinematic == null) { _setKinematic = Reflect.Method(TZSync, "SetKinematic", new Type[1] { typeof(bool) }); } MethodInfo setKinematic = _setKinematic; _setKinematicArgc = (((object)setKinematic != null) ? setKinematic.GetParameters().Length : 0); _fixVehiclePosition = Reflect.Method(TCommands, "FixVehiclePosition", new Type[3] { typeof(int), typeof(float?), typeof(float?) }); _localPlayer = ((TPlayer != null) ? AccessTools.Field(TPlayer, "m_localPlayer") : null); _zoneInstance = Member.Resolve(TZoneSystem, "instance", "m_instance"); _waterLevel = Member.Resolve(TZoneSystem, "m_waterLevel"); _znsInstance = Member.Resolve(TZNetScene, "instance", "m_instance"); _findInstance = ((TZDO != null) ? Reflect.Method(TZNetScene, "FindInstance", new Type[1] { TZDO }) : null); _allowFlightEntry = StaticValue("ValheimVehicles.BepInExConfig.PropulsionConfig", "AllowFlight"); _waterBallastEntry = StaticValue("ValheimVehicles.BepInExConfig.WaterConfig", "WaterBallastEnabled"); Ready = TVMC != null && GuardedFixedUpdate != null && _isOwner != null && _haveControllingPlayer != null && _body.Ok; Log.Info($"API bind: VMC={TVMC != null} GFU={GuardedFixedUpdate != null} " + $"HandOff={OnControlsHandOff != null} Login={OnLoginMoveToZDO != null} " + $"body={_body.Ok} zsync={_zsync.Ok} setKinematic={_setKinematic != null} " + $"isOwner={_isOwner != null} isInvalid={_isInvalid != null} " + $"haveDriver={_haveControllingPlayer != null} manager={_manager.Ok} " + $"persistentId={_persistentId.Ok} isLand={_isLandVehicle.Ok} " + $"fixPos={_fixVehiclePosition != null} localPlayer={_localPlayer != null} " + $"water={_zoneInstance.Ok && _waterLevel.Ok} flightCfg={_allowFlightEntry != null} " + $"ballastCfg={_waterBallastEntry != null}"); } internal static bool IsOwner(object vmc) { if (_isOwner != null) { return Reflect.AsBool(SafeInvoke(_isOwner, vmc, null)); } return false; } internal static bool HaveControllingPlayer(object vmc) { if (_haveControllingPlayer != null) { return Reflect.AsBool(SafeInvoke(_haveControllingPlayer, vmc, null)); } return false; } internal static bool HaveValidUser(object vmc) { if (_haveValidUser != null) { return Reflect.AsBool(SafeInvoke(_haveValidUser, vmc, null)); } return false; } internal static bool IsDriven(object vmc) { if (!HaveValidUser(vmc)) { return HaveControllingPlayer(vmc); } return true; } internal static bool IsInvalid(object vmc) { if (_isInvalid != null) { return Reflect.AsBool(SafeInvoke(_isInvalid, vmc, null)); } return false; } internal static bool IsHauling(object vmc) { if (_hauling.Ok) { return Reflect.AsBool(_hauling.Get(vmc)); } return false; } internal static object Manager(object vmc) { if (!_manager.Ok) { return null; } return _manager.Get(vmc); } internal static bool IsLandVehicle(object vmc) { object obj = Manager(vmc); if (obj != null && _isLandVehicle.Ok) { return Reflect.AsBool(_isLandVehicle.Get(obj)); } return false; } internal static int PersistentId(object vmc) { object obj = Manager(vmc); if (obj == null || !_persistentId.Ok) { return 0; } return Reflect.AsInt(_persistentId.Get(obj)); } internal static Rigidbody Body(object vmc) { object obj = _body.Get(vmc); return (Rigidbody)((obj is Rigidbody) ? obj : null); } internal static object LocalPlayer() { try { return _localPlayer?.GetValue(null); } catch { return null; } } private static bool ReadEntryBool(object entry) { if (entry == null) { return false; } try { if (_entryValueProp == null || _entryValueProp.DeclaringType != entry.GetType()) { _entryValueProp = entry.GetType().GetProperty("Value"); } object obj = _entryValueProp?.GetValue(entry); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } catch { return false; } } internal static bool FlightOrBallastOn() { if (!ReadEntryBool(_allowFlightEntry)) { return ReadEntryBool(_waterBallastEntry); } return true; } internal static void FreezeBody(object vmc) { Rigidbody val = Body(vmc); if (val != null) { U.ZeroVelocity(val); try { val.isKinematic = true; } catch { } } if (!(_setKinematic != null) || !_zsync.Ok) { return; } object obj2 = _zsync.Get(vmc); if (obj2 != null) { try { SafeInvoke(_setKinematic, obj2, (_setKinematicArgc != 2) ? new object[1] { true } : new object[2] { true, true }); } catch { } } } internal static float WaterLevel() { try { if (_zoneInstance.Ok && _waterLevel.Ok) { object obj = _zoneInstance.Get(null); if (obj != null) { object obj2 = _waterLevel.Get(obj); if (obj2 is float) { return (float)obj2; } } } } catch { } return 30f; } internal static void Reseat(object vmc, bool zeroVelocity) { //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0068: 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_0085: Unknown result type (might be due to invalid IL or missing references) try { int num = PersistentId(vmc); if (num != 0 && _fixVehiclePosition != null) { float num2 = WaterLevel(); object obj = num2 - 10000f; object obj2 = num2 + 0.2f; SafeInvoke(_fixVehiclePosition, null, new object[3] { num, obj, obj2 }); } Rigidbody val = Body(vmc); if (val != null) { try { Quaternion rotation = val.rotation; float y = ((Quaternion)(ref rotation)).eulerAngles.y; val.rotation = Quaternion.Euler(0f, y, 0f); } catch { } if (zeroVelocity) { U.ZeroVelocity(val); } } try { Physics.SyncTransforms(); } catch { } } catch (Exception e) { Log.ErrorOnce("Reseat", e); } } internal static IEnumerator WrapLoginReseat(IEnumerator inner, object zdo) { if (inner != null) { while (true) { object obj = null; bool flag; try { flag = inner.MoveNext(); if (flag) { obj = inner.Current; } } catch { flag = false; } if (!flag) { break; } yield return obj; } } object obj3 = null; try { obj3 = ResolveControllerFromZdo(zdo); } catch (Exception e) { Log.ErrorOnce("LoginResolve", e); } if (obj3 == null) { yield break; } try { if (!IsLandVehicle(obj3) && !FlightOrBallastOn()) { Reseat(obj3, zeroVelocity: true); } } catch (Exception e2) { Log.ErrorOnce("LoginReseat", e2); } } private static object ResolveControllerFromZdo(object zdo) { if (zdo == null || _findInstance == null || !_znsInstance.Ok) { return null; } object obj = _znsInstance.Get(null); if (obj == null) { return null; } object obj2 = SafeInvoke(_findInstance, obj, new object[1] { zdo }); if (obj2 == null) { return null; } object componentOf = GetComponentOf(obj2, TManager); if (componentOf == null) { return null; } if (!_movementController.Ok) { return null; } return _movementController.Get(componentOf); } private static object GetComponentOf(object go, Type compType) { if (go == null || compType == null) { return null; } try { if (_goGetComponent == null) { _goGetComponent = go.GetType().GetMethod("GetComponent", new Type[1] { typeof(Type) }); } object obj = _goGetComponent?.Invoke(go, new object[1] { compType }); if (obj != null && !obj.Equals(null)) { return obj; } if (_goGetComponentInParent == null) { _goGetComponentInParent = go.GetType().GetMethod("GetComponentInParent", new Type[1] { typeof(Type) }); } object obj2 = _goGetComponentInParent?.Invoke(go, new object[1] { compType }); if (obj2 != null && !obj2.Equals(null)) { return obj2; } } catch { } return null; } private static object SafeInvoke(MethodInfo mi, object instance, object[] args) { try { return mi.Invoke(instance, args); } catch (TargetInvocationException ex) { Log.ErrorOnce(mi.Name, ex.InnerException ?? ex); return null; } catch (Exception e) { Log.ErrorOnce(mi.Name, e); return null; } } private static object StaticValue(string typeName, string memberName) { try { Type type = Reflect.Type(typeName); if (type == null) { return null; } Member member = Member.Resolve(type, memberName); return member.Ok ? member.Get(null) : null; } catch { return null; } } } } namespace VVStabilizer.Patches { internal static class HandoffSettlePatch { internal static void Postfix(object __instance, object __0) { if (__instance == null || !Config.Enabled.Value || !VV.Ready) { return; } try { object obj = VV.LocalPlayer(); if (obj != null && __0 == obj && !VV.IsLandVehicle(__instance) && !VV.FlightOrBallastOn()) { if (VV.CanReseat) { VV.Reseat(__instance, zeroVelocity: true); } VV.FreezeBody(__instance); CtrlState ctrlState = State.Get(__instance); float num = Config.SettleSeconds.Value; if (num < 0f) { num = 0f; } if (num > 2f) { num = 2f; } ctrlState.SettleUntilTime = Time.time + num; ctrlState.WasDriven = true; Log.Debug($"handoff settle {num}s"); } } catch (Exception e) { Log.ErrorOnce("HandoffSettle", e); } } } internal static class LoginReseatPatch { internal static void Postfix(ref IEnumerator __result, object __0) { if (!Config.Enabled.Value || !Config.ReseatOnLogin.Value || !VV.Ready || !VV.CanReseat || __result == null) { return; } try { __result = VV.WrapLoginReseat(__result, __0); } catch (Exception e) { Log.ErrorOnce("LoginReseatPatch", e); } } } internal static class UndrivenFreezePatch { internal static void Postfix(object __instance) { if (__instance == null || !Config.Enabled.Value || !Config.FreezeUndriven.Value || !VV.Ready) { return; } try { if (VV.IsInvalid(__instance) || !VV.IsOwner(__instance) || VV.IsLandVehicle(__instance) || VV.IsHauling(__instance) || VV.FlightOrBallastOn()) { return; } CtrlState ctrlState = State.Get(__instance); float time = Time.time; if (time < ctrlState.SettleUntilTime) { VV.FreezeBody(__instance); return; } if (VV.IsDriven(__instance)) { ctrlState.WasDriven = true; return; } bool wasDriven = ctrlState.WasDriven; bool flag = !ctrlState.EverReseated; bool flag2 = time - ctrlState.LastReseatTime > Config.ReseatInterval.Value && IsMisseated(__instance); if (VV.CanReseat && (wasDriven || flag || flag2)) { VV.Reseat(__instance, zeroVelocity: true); ctrlState.LastReseatTime = time; ctrlState.EverReseated = true; Log.Debug($"reseat undriven (transition={wasDriven} first={flag} periodic={flag2})"); } VV.FreezeBody(__instance); ctrlState.WasDriven = false; } catch (Exception e) { Log.ErrorOnce("UndrivenFreeze", e); } } private static bool IsMisseated(object vmc) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) Rigidbody val = VV.Body(vmc); if (val == null) { return false; } try { float y = val.position.y; float num = VV.WaterLevel(); return y > num + Config.MisseatedEpsilon.Value; } catch { return false; } } } }