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 MovementSync v2.6.1
MovementSync.dll
Decompiled 4 hours agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using GameNetcodeStuff; using Microsoft.CodeAnalysis; using Unity.Netcode; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyVersion("0.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace MovementSync { [BepInPlugin("local.movementsync", "MovementSync", "2.6.1")] public class MovementSyncPlugin : BaseUnityPlugin { private class PlayerWatch { public Vector3 LastPosition; public bool HasLastPosition; public Transform LastFrameOfReference; public float LastDistance; public bool HasLastDistance; public readonly List<float> RecentSpeeds = new List<float>(); public Vector3 WindowStartPosition; public float WindowStartTime; public bool HasWindowStart; public float StalledSeconds; public readonly List<float> TeleportTimes = new List<float>(); public readonly List<float> CatchUpTimes = new List<float>(); public string GuardNote; public float GuardNoteUntil; public float IgnoreUntil; public float NextLogTime; public float SprintSeconds; public float BelowThresholdFor; public readonly List<float> MovingSpeeds = new List<float>(); public float WalkBaseline = -1f; public float NextBaselineCalc; public float LastStaminaSampleTime = -1f; public float LastSpeed; public float PeakSpeed; public string LastSkipReason; public float SkipSince; public bool WarnedStuck; public void ForgetHistory() { RecentSpeeds.Clear(); HasLastDistance = false; } public void ArmWindow(Vector3 where, float now) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) WindowStartPosition = where; WindowStartTime = now; HasWindowStart = true; } public void Note(string text, float now) { GuardNote = text; GuardNoteUntil = now + 5f; } public void ResetSprintStreak() { SprintSeconds = 0f; BelowThresholdFor = 0f; } } public const string PluginGuid = "local.movementsync"; public const string PluginName = "MovementSync"; public const string PluginVersion = "2.6.1"; private const int MinBaselineSamples = 40; private ManualLogSource _log; private ConfigEntry<float> _speedLimit; private ConfigEntry<float> _sustainSeconds; private ConfigEntry<float> _minFractionOverLimit; private ConfigEntry<float> _extremeSpeedLimit; private ConfigEntry<float> _extremeSustainSeconds; private ConfigEntry<float> _sampleInterval; private ConfigEntry<bool> _ignoreVerticalMovement; private ConfigEntry<bool> _useReportedPosition; private ConfigEntry<float> _netDisplacementSeconds; private ConfigEntry<float> _plausibleCatchUpSpeed; private ConfigEntry<float> _guardWindowSeconds; private ConfigEntry<float> _teleportDistance; private ConfigEntry<int> _teleportsInARowToCount; private ConfigEntry<float> _stallDistance; private ConfigEntry<float> _catchUpDistance; private ConfigEntry<float> _gracePeriodSeconds; private ConfigEntry<float> _hitchToleranceMultiplier; private ConfigEntry<bool> _actWhenHosting; private ConfigEntry<bool> _actWhenNotHosting; private ConfigEntry<bool> _testModeLogOnly; private ConfigEntry<float> _disconnectDelay; private ConfigEntry<bool> _discreetMessages; private ConfigEntry<bool> _announceKickInChat; private ConfigEntry<string> _neverActOnUsernames; private ConfigEntry<string> _neverActOnSteamIds; private ConfigEntry<bool> _requireConfirmation; private ConfigEntry<string> _confirmKey; private ConfigEntry<string> _rejectKey; private ConfigEntry<float> _confirmTimeoutSeconds; private ConfigEntry<float> _rejectGraceMinutes; private ConfigEntry<bool> _staminaCheckEnabled; private ConfigEntry<float> _sprintSpeedThreshold; private ConfigEntry<float> _sprintBaselineMultiplier; private ConfigEntry<float> _maxSprintSeconds; private ConfigEntry<float> _sprintBreakSeconds; private ConfigEntry<bool> _showHud; private ConfigEntry<string> _hudKey; private ConfigEntry<int> _hudFontSize; private ConfigEntry<bool> _logAllSpeeds; private ConfigEntry<bool> _logSkipReasons; private ConfigEntry<float> _stuckFlagWarnSeconds; private readonly Dictionary<int, PlayerWatch> _watches = new Dictionary<int, PlayerWatch>(); private float _timeSinceLastSample; private float _ignoreEverythingUntil; private bool _alreadyLeaving; private readonly HashSet<string> _allowedNames = new HashSet<string>(); private readonly HashSet<ulong> _allowedSteamIds = new HashSet<ulong>(); private Key _hudKeyParsed = (Key)102; private Key _confirmKeyParsed = (Key)103; private Key _rejectKeyParsed = (Key)104; private bool _pendingKick; private int _pendingSlot; private string _pendingName; private ulong _pendingSteamId; private string _pendingReason; private float _pendingSpeed; private float _pendingExpires; private readonly Dictionary<ulong, float> _clearedUntil = new Dictionary<ulong, float>(); private bool _hudVisible; private string _hudText = ""; private GUIStyle _hudStyle; private GUIStyle _confirmStyle; private string _lastParsedNames; private string _lastParsedIds; private void Awake() { //IL_073c: Unknown result type (might be due to invalid IL or missing references) //IL_0741: Unknown result type (might be due to invalid IL or missing references) //IL_0755: Unknown result type (might be due to invalid IL or missing references) //IL_075a: Unknown result type (might be due to invalid IL or missing references) //IL_076e: Unknown result type (might be due to invalid IL or missing references) //IL_0773: Unknown result type (might be due to invalid IL or missing references) _log = ((BaseUnityPlugin)this).Logger; _speedLimit = ((BaseUnityPlugin)this).Config.Bind<float>("1 - Detection", "SpeedLimit", 35f, Ranged("Speed in units per second that counts as cheating when held for SustainSeconds. Vanilla sprinting is roughly 8-10, so anything below about 15 WILL flag innocent players. Speed hacks are usually 40-200. Turn on LogAllSpeeds for one round and read the real numbers before lowering this.", 1f, 500f)); _sustainSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("1 - Detection", "SustainSeconds", 3f, Ranged("Seconds of UNBROKEN over-the-limit movement before acting. The most important anti-false-positive setting: any lag spike, teleport or odd frame wipes the streak.", 0.5f, 30f)); _minFractionOverLimit = ((BaseUnityPlugin)this).Config.Bind<float>("1 - Detection", "MinFractionOverLimit", 0.6f, Ranged("What fraction of readings in the window must EACH be over the limit, on top of the average being over it. Stops one freak reading dragging the average over on its own. 0 disables.", 0f, 1f)); _extremeSpeedLimit = ((BaseUnityPlugin)this).Config.Bind<float>("1 - Detection", "ExtremeSpeedLimit", 100f, Ranged("A second, much higher limit needing only ExtremeSustainSeconds to trigger, so blatant hacks are caught in under a second.", 5f, 1000f)); _extremeSustainSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("1 - Detection", "ExtremeSustainSeconds", 0.75f, Ranged("Seconds of unbroken movement above ExtremeSpeedLimit before acting. Keep smaller than SustainSeconds.", 0.25f, 30f)); _sampleInterval = ((BaseUnityPlugin)this).Config.Bind<float>("1 - Detection", "SampleInterval", 0.25f, Ranged("Seconds between measurements. 0.25 means four a second.", 0.05f, 2f)); _ignoreVerticalMovement = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - Detection", "IgnoreVerticalMovement", true, "true = only measure movement across the ground. Long falls and jetpacks are genuinely fast vertically, so ignoring up and down removes both as false positives and costs nothing, because speed hacks move you sideways."); _useReportedPosition = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - Detection", "UseReportedPosition", true, "true = judge other players by the position their own game reported over the network, which is the exact thing a speed hack fakes and the only figure guaranteed to be up to date for them. false = judge by the visible body instead, which is second-hand and smoothed. Leave this true unless the log shows reported speeds staying at zero."); _netDisplacementSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("2 - False positive protection", "NetDisplacementSeconds", 2.5f, Ranged("Every few seconds the mod also checks the straight line between where a player WAS and where they are now. This cannot be dodged by breaking movement into small hops, because it only uses the two end points - and no innocent player can fake it, because it is simply how far they really got.", 1f, 30f)); _plausibleCatchUpSpeed = ((BaseUnityPlugin)this).Config.Bind<float>("2 - False positive protection", "PlausibleCatchUpSpeed", 14f, Ranged("When forgiving a jump as 'the network catching up', this is the fastest a player could really have been moving while their updates were missing. A jump bigger than this speed multiplied by the time they were silent is not catch-up, and gets measured normally. Stops a cheat disguising itself as lag.", 5f, 100f)); _guardWindowSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("2 - False positive protection", "GuardWindowSeconds", 10f, Ranged("Teleport-sized jumps and catch-up events are counted within this many seconds of each other, rather than needing to be exactly back to back. A cheat that alternates hops with normal steps can no longer reset the count.", 2f, 120f)); _teleportDistance = ((BaseUnityPlugin)this).Config.Bind<float>("2 - False positive protection", "TeleportDistance", 40f, Ranged("More than this many units in ONE measurement is treated as a teleport, not running: the ship teleporter, the inverse teleporter, entering the facility and the ship landing all do this. It is a distance per measurement, not a speed - 40 units per 0.25s is about 160 u/s.", 5f, 500f)); _teleportsInARowToCount = ((BaseUnityPlugin)this).Config.Bind<int>("2 - False positive protection", "TeleportsInARowToCount", 5, Ranged("A real teleport happens once. This many teleport-sized jumps WITHIN GuardWindowSeconds counts as cheating - they no longer have to be back to back, so alternating hops with normal steps cannot reset the count. 0 disables.", 0, 100)); _stallDistance = ((BaseUnityPlugin)this).Config.Bind<float>("2 - False positive protection", "StallDistance", 0.05f, Ranged("Movement below this counts as 'did not move at all', which is what a dropped packet looks like. Works with CatchUpDistance.", 0f, 5f)); _catchUpDistance = ((BaseUnityPlugin)this).Config.Bind<float>("2 - False positive protection", "CatchUpDistance", 3f, Ranged("A player who appears frozen for one measurement then jumps further than this is the network catching up, not real speed. The reading is binned and the streak reset. This is what protects a laggy friend.", 0.5f, 100f)); _gracePeriodSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("2 - False positive protection", "GracePeriodSeconds", 5f, Ranged("Seconds of measuring nothing after the ship lands, takes off or changes moon.", 0f, 60f)); _hitchToleranceMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("2 - False positive protection", "HitchToleranceMultiplier", 2.5f, Ranged("If YOUR game stutters and a measurement takes more than this many times longer than SampleInterval, every reading from it is discarded.", 1.1f, 20f)); _actWhenHosting = ((BaseUnityPlugin)this).Config.Bind<bool>("3 - Action", "ActWhenHosting", true, "true = when YOU host, kick the cheater. This is the main way to use the mod: the host's copy protects everybody and nobody else needs it installed."); _actWhenNotHosting = ((BaseUnityPlugin)this).Config.Bind<bool>("3 - Action", "ActWhenNotHosting", true, "true = when you are not the host, disconnect yourself once a cheater is confirmed. A non-host cannot kick anybody, so leaving is the only option the game allows."); _testModeLogOnly = ((BaseUnityPlugin)this).Config.Bind<bool>("3 - Action", "TestModeLogOnly", false, "true = never kick and never disconnect, only write what WOULD have happened. Use this with LogAllSpeeds for a calibration round."); _disconnectDelay = ((BaseUnityPlugin)this).Config.Bind<float>("3 - Action", "DisconnectDelay", 2f, Ranged("When not hosting, seconds to show the warning before leaving the lobby.", 0f, 30f)); _discreetMessages = ((BaseUnityPlugin)this).Config.Bind<bool>("3 - Action", "DiscreetMessages", true, "true = the on-screen message only mentions a connection desync, so anyone watching your screen cannot tell you run a detector. The log always records the real reason."); _announceKickInChat = ((BaseUnityPlugin)this).Config.Bind<bool>("3 - Action", "AnnounceKickInChat", false, "true = when hosting, post a line in chat naming who was removed. Off by default because it tells the lobby you are running a detector."); _neverActOnUsernames = ((BaseUnityPlugin)this).Config.Bind<string>("4 - Never act on", "Usernames", "", "Comma separated player names never to act on. Example: Jim,Bob. Names can be faked, so prefer SteamIds."); _neverActOnSteamIds = ((BaseUnityPlugin)this).Config.Bind<string>("4 - Never act on", "SteamIds", "", "Comma separated Steam IDs never to act on. Harder to fake. LogAllSpeeds prints each player's ID."); _requireConfirmation = ((BaseUnityPlugin)this).Config.Bind<bool>("8 - Ask before kicking", "RequireConfirmation", false, "true = never kick on your own. Instead a box appears telling you who was flagged and why, and you decide. Worth turning on if you are worried about false positives, or when you want to watch somebody you already suspect. Only applies while you are the host."); _confirmKey = ((BaseUnityPlugin)this).Config.Bind<string>("8 - Ask before kicking", "ConfirmKey", "F10", "Key that confirms the kick shown in the box. Write it like F10, K, Numpad1."); _rejectKey = ((BaseUnityPlugin)this).Config.Bind<string>("8 - Ask before kicking", "RejectKey", "F11", "Key that clears the player and dismisses the box."); _confirmTimeoutSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("8 - Ask before kicking", "ConfirmTimeoutSeconds", 30f, Ranged("How long the box stays up before it gives up and does nothing. Doing nothing is the safe outcome, so an unanswered prompt never kicks anybody.", 5f, 600f)); _rejectGraceMinutes = ((BaseUnityPlugin)this).Config.Bind<float>("8 - Ask before kicking", "RejectGraceMinutes", 5f, Ranged("After you clear somebody, how many minutes to leave them alone before flagging them again. Stops the same box reappearing every few seconds for a player who is simply being thrown around by a monster.", 0.5f, 120f)); _staminaCheckEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("6 - Stamina", "StaminaCheckEnabled", true, "true = also watch for unlimited stamina. A player who holds a sprint for longer than any real stamina bar allows, and never once runs out of breath, is not playing fair."); _sprintSpeedThreshold = ((BaseUnityPlugin)this).Config.Bind<float>("6 - Stamina", "SprintSpeedThreshold", 6.5f, Ranged("Speed in units per second at or above which somebody counts as sprinting rather than walking. Vanilla walking is about 4 and sprinting about 8-10, so 6.5 sits between them. Raise it if your speed upgrades make walking fast.", 1f, 100f)); _sprintBaselineMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("6 - Stamina", "SprintBaselineMultiplier", 1.6f, Ranged("How much faster than a player's OWN ordinary pace counts as sprinting. Sprinting in this game is a bit over twice walking speed, so 1.6 sits safely between the two whatever movement-speed upgrades are in play. This is what stops a fast walker being mistaken for someone with unlimited stamina.", 1.1f, 5f)); _maxSprintSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("6 - Stamina", "MaxSprintSeconds", 25f, Ranged("How many seconds somebody may hold that pace UNBROKEN, without ever becoming exhausted, before it counts as cheating. Vanilla stamina runs out in roughly 11 seconds; 25 leaves generous room for the stamina upgrades in More Ship Upgrades.", 5f, 300f)); _sprintBreakSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("6 - Stamina", "SprintBreakSeconds", 0.75f, Ranged("How long they must drop below the sprint pace for the streak to reset. A short value means squeezing past a doorway does not wipe the count; a long one is more forgiving.", 0.1f, 10f)); _showHud = ((BaseUnityPlugin)this).Config.Bind<bool>("7 - On-screen display", "ShowHud", false, "true = draw a small table on screen listing every player's current pace. Far easier to read than the log, which scrolls away. Toggle it in game with HudKey."); _hudKey = ((BaseUnityPlugin)this).Config.Bind<string>("7 - On-screen display", "HudKey", "F9", "Key that shows and hides the on-screen pace table. Write it like F9, F10, K, Numpad0. Set to None to disable the key."); _hudFontSize = ((BaseUnityPlugin)this).Config.Bind<int>("7 - On-screen display", "HudFontSize", 15, Ranged("Text size of the on-screen table.", 8, 40)); _logAllSpeeds = ((BaseUnityPlugin)this).Config.Bind<bool>("5 - Debug", "LogAllSpeeds", false, "true = print every player's speed continuously, showing BOTH the reported figure and the visible-body figure so you can see which one is live. Noisy - use for one round."); _logSkipReasons = ((BaseUnityPlugin)this).Config.Bind<bool>("5 - Debug", "LogSkipReasons", true, "true = when a player is excluded from checking, say why in the log instead of skipping them silently. This is how you tell 'nobody is cheating' apart from 'the mod is not looking at anybody'."); _stuckFlagWarnSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("5 - Debug", "StuckFlagWarnSeconds", 20f, Ranged("If a player is left unchecked for this many seconds straight for the same reason, warn in the log. Catches a game flag that has jammed on and is quietly hiding somebody from the mod.", 5f, 600f)); _hudVisible = _showHud.Value; _hudKeyParsed = ParseKey(_hudKey.Value, (Key)102); _confirmKeyParsed = ParseKey(_confirmKey.Value, (Key)103); _rejectKeyParsed = ParseKey(_rejectKey.Value, (Key)104); RefreshWhitelists(); _log.LogInfo((object)("MovementSync 2.6.1 loaded. Host action: " + (_actWhenHosting.Value ? "kick" : "off") + ", client action: " + (_actWhenNotHosting.Value ? "disconnect self" : "off") + (_testModeLogOnly.Value ? " -- TEST MODE, nothing will actually happen." : "."))); if (_speedLimit.Value < 15f) { _log.LogWarning((object)(string.Format("[{0}] SpeedLimit is {1:F1} u/s, which is at or below normal ", "MovementSync", _speedLimit.Value) + "sprinting. Innocent players will be flagged. Raise it to about 35 unless you have measured your lobby with LogAllSpeeds.")); } } private Key ParseKey(string text, Key fallback) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) if (Enum.TryParse<Key>(text, ignoreCase: true, out Key result)) { return result; } _log.LogWarning((object)string.Format("[{0}] '{1}' is not a key name I recognise, using {2} instead.", "MovementSync", text, fallback)); return fallback; } private static ConfigDescription Ranged(string text, float min, float max) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Expected O, but got Unknown return new ConfigDescription(text, (AcceptableValueBase)(object)new AcceptableValueRange<float>(min, max), Array.Empty<object>()); } private static ConfigDescription Ranged(string text, int min, int max) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Expected O, but got Unknown return new ConfigDescription(text, (AcceptableValueBase)(object)new AcceptableValueRange<int>(min, max), Array.Empty<object>()); } private void Update() { HandleHudKey(); HandlePendingKick(); StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null || instance.allPlayerScripts == null) { _watches.Clear(); _timeSinceLastSample = 0f; _alreadyLeaving = false; _hudText = ""; return; } _timeSinceLastSample += Time.deltaTime; if (_timeSinceLastSample < _sampleInterval.Value) { return; } float timeSinceLastSample = _timeSinceLastSample; _timeSinceLastSample = 0f; if (timeSinceLastSample > _sampleInterval.Value * _hitchToleranceMultiplier.Value) { ForgetAllHistory(); } else if (instance.inShipPhase || instance.shipIsLeaving || instance.travellingToNewLevel) { ForgetAllHistory(); foreach (KeyValuePair<int, PlayerWatch> watch in _watches) { watch.Value.PeakSpeed = 0f; } _ignoreEverythingUntil = Time.time + _gracePeriodSeconds.Value; } else if (Time.time < _ignoreEverythingUntil) { ForgetAllHistory(); } else { MeasureEveryone(instance, timeSinceLastSample); } } private void MeasureEveryone(StartOfRound round, float elapsed) { //IL_00d4: 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_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: 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_0118: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) PlayerControllerB[] allPlayerScripts = round.allPlayerScripts; for (int i = 0; i < allPlayerScripts.Length; i++) { PlayerControllerB val = allPlayerScripts[i]; if ((Object)(object)val == (Object)null) { continue; } if (!_watches.ContainsKey(i)) { _watches[i] = new PlayerWatch(); } PlayerWatch playerWatch = _watches[i]; bool isLocal = (Object)(object)val == (Object)(object)round.localPlayerController; string skipReason = GetSkipReason(val, isLocal); if (skipReason != null) { playerWatch.HasLastPosition = false; playerWatch.HasWindowStart = false; playerWatch.ForgetHistory(); NoteSkip(val, playerWatch, skipReason); continue; } playerWatch.LastSkipReason = null; playerWatch.WarnedStuck = false; Transform parent = ((Component)val).transform.parent; if ((Object)(object)parent != (Object)(object)playerWatch.LastFrameOfReference) { playerWatch.LastFrameOfReference = parent; playerWatch.HasLastPosition = false; playerWatch.HasWindowStart = false; playerWatch.ForgetHistory(); } Vector3 val2 = MeasuredPosition(val, isLocal); if (!playerWatch.HasLastPosition) { playerWatch.LastPosition = val2; playerWatch.HasLastPosition = true; continue; } float num = FlatDistance(playerWatch.LastPosition, val2); playerWatch.LastPosition = val2; if (!playerWatch.HasWindowStart) { playerWatch.ArmWindow(val2, Time.time); } else if (Time.time - playerWatch.WindowStartTime >= _netDisplacementSeconds.Value) { float num2 = Time.time - playerWatch.WindowStartTime; float num3 = FlatDistance(playerWatch.WindowStartPosition, val2) / num2; playerWatch.ArmWindow(val2, Time.time); if (num3 > _speedLimit.Value && CanActOn(val, playerWatch)) { Act(val, i, num3, $"covered ground in a straight line at {num3:F0} u/s over {num2:F1}s"); continue; } } if (num > _teleportDistance.Value) { PruneOlderThan(playerWatch.TeleportTimes, Time.time, _guardWindowSeconds.Value); playerWatch.TeleportTimes.Add(Time.time); playerWatch.Note($"teleport-sized jump x{playerWatch.TeleportTimes.Count}", Time.time); if (_teleportsInARowToCount.Value > 0 && playerWatch.TeleportTimes.Count >= _teleportsInARowToCount.Value && CanActOn(val, playerWatch)) { Act(val, i, num / elapsed, $"{playerWatch.TeleportTimes.Count} teleport-sized jumps within {_guardWindowSeconds.Value:F0}s"); } playerWatch.RecentSpeeds.Clear(); playerWatch.HasLastDistance = false; playerWatch.StalledSeconds = 0f; continue; } if (playerWatch.HasLastDistance && playerWatch.LastDistance < _stallDistance.Value && num > _catchUpDistance.Value) { float num4 = _plausibleCatchUpSpeed.Value * (playerWatch.StalledSeconds + elapsed); if (num <= num4) { PruneOlderThan(playerWatch.CatchUpTimes, Time.time, _guardWindowSeconds.Value); playerWatch.CatchUpTimes.Add(Time.time); playerWatch.Note($"lag catch-up x{playerWatch.CatchUpTimes.Count}", Time.time); playerWatch.RecentSpeeds.Clear(); playerWatch.LastDistance = num; playerWatch.HasLastDistance = true; playerWatch.StalledSeconds = 0f; continue; } playerWatch.Note("jump too big to be lag", Time.time); } if (num < _stallDistance.Value) { playerWatch.StalledSeconds += elapsed; } else { playerWatch.StalledSeconds = 0f; } playerWatch.LastDistance = num; playerWatch.HasLastDistance = true; float num5 = num / elapsed; playerWatch.RecentSpeeds.Add(num5); int num6 = Mathf.Max(SamplesFor(_sustainSeconds.Value), SamplesFor(_extremeSustainSeconds.Value)); while (playerWatch.RecentSpeeds.Count > num6) { playerWatch.RecentSpeeds.RemoveAt(0); } playerWatch.LastSpeed = num5; if (num5 > playerWatch.PeakSpeed) { playerWatch.PeakSpeed = num5; } if (playerWatch.LastStaminaSampleTime >= 0f && Time.time - playerWatch.LastStaminaSampleTime > _sprintBreakSeconds.Value) { playerWatch.ResetSprintStreak(); } playerWatch.LastStaminaSampleTime = Time.time; if (num5 > 1f) { playerWatch.MovingSpeeds.Add(num5); int num7 = Mathf.Max(40, Mathf.RoundToInt(30f / _sampleInterval.Value)); while (playerWatch.MovingSpeeds.Count > num7) { playerWatch.MovingSpeeds.RemoveAt(0); } } if (Time.time >= playerWatch.NextBaselineCalc && playerWatch.MovingSpeeds.Count >= 40) { playerWatch.NextBaselineCalc = Time.time + 2f; List<float> list = new List<float>(playerWatch.MovingSpeeds); list.Sort(); playerWatch.WalkBaseline = list[list.Count / 4]; } bool flag = playerWatch.WalkBaseline > 0f && playerWatch.MovingSpeeds.Count >= 40; if (val.isExhausted && num5 < _sprintSpeedThreshold.Value) { playerWatch.ResetSprintStreak(); } else if (flag) { float num8 = Mathf.Max(_sprintSpeedThreshold.Value, playerWatch.WalkBaseline * _sprintBaselineMultiplier.Value); if (num5 >= num8) { playerWatch.SprintSeconds += elapsed; playerWatch.BelowThresholdFor = 0f; } else { playerWatch.BelowThresholdFor += elapsed; if (playerWatch.BelowThresholdFor >= _sprintBreakSeconds.Value) { playerWatch.ResetSprintStreak(); } } } if (_logAllSpeeds.Value) { LogSpeeds(val, playerWatch, isLocal, num5, parent); } if (CanActOn(val, playerWatch)) { float average; float average2; if (_staminaCheckEnabled.Value && playerWatch.WalkBaseline > 0f && playerWatch.MovingSpeeds.Count >= 40 && playerWatch.SprintSeconds > _maxSprintSeconds.Value) { float sprintSeconds = playerWatch.SprintSeconds; playerWatch.ResetSprintStreak(); Act(val, i, num5, $"held a sprint for {sprintSeconds:F0}s without ever running out of stamina " + $"(limit {_maxSprintSeconds.Value:F0}s)"); } else if (WindowExceeds(playerWatch.RecentSpeeds, _sustainSeconds.Value, _speedLimit.Value, out average)) { Act(val, i, average, $"{_sustainSeconds.Value:F1}s above {_speedLimit.Value:F0} u/s"); } else if (WindowExceeds(playerWatch.RecentSpeeds, _extremeSustainSeconds.Value, _extremeSpeedLimit.Value, out average2)) { Act(val, i, average2, $"{_extremeSustainSeconds.Value:F2}s above {_extremeSpeedLimit.Value:F0} u/s"); } } } if (_hudVisible) { BuildHud(round); } } private void BuildHud(StartOfRound round) { StringBuilder stringBuilder = new StringBuilder(600); stringBuilder.Append("MovementSync - limit ").Append(_speedLimit.Value.ToString("F0")).Append(" u/s held ") .Append(_sustainSeconds.Value.ToString("F1")) .Append("s") .Append(" stamina limit ") .Append(_maxSprintSeconds.Value.ToString("F0")) .Append("s"); if (_testModeLogOnly.Value) { stringBuilder.Append(" [TEST MODE - no one is kicked]"); } stringBuilder.Append('\n'); stringBuilder.Append("player now avg peak sprint state\n"); PlayerControllerB[] allPlayerScripts = round.allPlayerScripts; for (int i = 0; i < allPlayerScripts.Length; i++) { PlayerControllerB val = allPlayerScripts[i]; if ((Object)(object)val == (Object)null || !val.isPlayerControlled || !_watches.TryGetValue(i, out var value)) { continue; } string text = val.playerUsername ?? "?"; if (text.Length > 15) { text = text.Substring(0, 15); } stringBuilder.Append(text.PadRight(16)); if (value.LastSkipReason != null) { stringBuilder.Append(" - - - - ").Append(value.LastSkipReason); } else { float num = AverageOf(value.RecentSpeeds, SamplesFor(_sustainSeconds.Value)); stringBuilder.Append(value.LastSpeed.ToString("F1").PadLeft(5)); stringBuilder.Append((num >= 0f) ? num.ToString("F1").PadLeft(7) : " -"); stringBuilder.Append(value.PeakSpeed.ToString("F1").PadLeft(7)); stringBuilder.Append((value.SprintSeconds.ToString("F0") + "s").PadLeft(8)); stringBuilder.Append(" "); if (value.GuardNote != null && Time.time < value.GuardNoteUntil) { stringBuilder.Append(value.GuardNote); } else if ((Object)(object)val == (Object)(object)round.localPlayerController) { stringBuilder.Append("you"); } else if (val.isExhausted) { stringBuilder.Append("out of breath"); } else { stringBuilder.Append("ok"); } } stringBuilder.Append('\n'); } _hudText = stringBuilder.ToString(); } private float AverageOf(List<float> speeds, int needed) { if (needed <= 0 || speeds.Count < needed) { return -1f; } float num = 0f; for (int i = speeds.Count - needed; i < speeds.Count; i++) { num += speeds[i]; } return num / (float)needed; } private void HandlePendingKick() { //IL_0063: 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_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) if (!_pendingKick) { return; } if (Time.time > _pendingExpires) { _pendingKick = false; _log.LogWarning((object)("[MovementSync] No answer about " + _pendingName + " within " + $"{_confirmTimeoutSeconds.Value:F0}s, so nothing was done. They were NOT kicked.")); return; } Keyboard current = Keyboard.current; if (current == null) { return; } KeyControl val = (((int)_confirmKeyParsed == 0) ? null : current[_confirmKeyParsed]); KeyControl val2 = (((int)_rejectKeyParsed == 0) ? null : current[_rejectKeyParsed]); if (val != null && ((ButtonControl)val).wasPressedThisFrame) { _pendingKick = false; StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null || !((NetworkBehaviour)instance).IsServer) { _log.LogWarning((object)("[MovementSync] Cannot kick " + _pendingName + ": no longer hosting.")); return; } PlayerControllerB val3 = ((_pendingSlot >= 0 && _pendingSlot < instance.allPlayerScripts.Length) ? instance.allPlayerScripts[_pendingSlot] : null); if ((Object)(object)val3 == (Object)null || val3.playerSteamId != _pendingSteamId) { _log.LogWarning((object)("[MovementSync] " + _pendingName + " is no longer in that slot, so nothing was done.")); return; } _log.LogError((object)("[MovementSync] KICKING " + _pendingName + " - confirmed by you.")); if (_announceKickInChat.Value && (Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.AddTextToChatOnServer(_pendingName + " was removed: abnormal movement speed.", -1); } instance.KickPlayer(_pendingSlot); } else if (val2 != null && ((ButtonControl)val2).wasPressedThisFrame) { _pendingKick = false; float value = Time.time + _rejectGraceMinutes.Value * 60f; _clearedUntil[_pendingSteamId] = value; _log.LogInfo((object)("[MovementSync] You cleared " + _pendingName + ". They will not be flagged again " + $"for {_rejectGraceMinutes.Value:F0} minutes.")); } } private void HandleHudKey() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) if ((int)_hudKeyParsed == 0) { return; } Keyboard current = Keyboard.current; if (current != null) { KeyControl val = current[_hudKeyParsed]; if (val != null && ((ButtonControl)val).wasPressedThisFrame) { _hudVisible = !_hudVisible; } } } private void OnGUI() { //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_0065: 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_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Expected O, but got Unknown //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Expected O, but got Unknown //IL_00a5: 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_00b7: 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_00d4: 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_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) if (_pendingKick) { DrawPendingKickBox(); } if (_hudVisible && _hudText.Length != 0) { if (_hudStyle == null || _hudStyle.fontSize != _hudFontSize.Value) { _hudStyle = new GUIStyle(GUI.skin.label) { fontSize = _hudFontSize.Value, alignment = (TextAnchor)0, wordWrap = false, richText = false }; _hudStyle.normal.textColor = Color.white; } Vector2 val = _hudStyle.CalcSize(new GUIContent(_hudText)); Rect val2 = default(Rect); ((Rect)(ref val2))..ctor(12f, 12f, val.x + 18f, val.y + 14f); GUI.Box(val2, GUIContent.none); GUI.Label(new Rect(((Rect)(ref val2)).x + 9f, ((Rect)(ref val2)).y + 7f, val.x, val.y), _hudText, _hudStyle); } } private void DrawPendingKickBox() { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: 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) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Expected O, but got Unknown //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) if (_confirmStyle == null || _confirmStyle.fontSize != _hudFontSize.Value + 3) { _confirmStyle = new GUIStyle(GUI.skin.label) { fontSize = _hudFontSize.Value + 3, alignment = (TextAnchor)0, wordWrap = true, richText = false }; _confirmStyle.normal.textColor = Color.white; } float num = Mathf.Max(0f, _pendingExpires - Time.time); string text = "MovementSync needs your decision\n\nPlayer : " + _pendingName + "\n" + $"Steam : {_pendingSteamId}\n" + $"Reading: {_pendingSpeed:F1} u/s\n" + "Reason : " + _pendingReason + "\n\n" + $"[{_confirmKeyParsed}] kick them [{_rejectKeyParsed}] they are fine\n" + $"Doing nothing kicks nobody ({num:F0}s left)"; float num2 = 460f; float num3 = 210f; Rect val = default(Rect); ((Rect)(ref val))..ctor(((float)Screen.width - num2) * 0.5f, (float)Screen.height * 0.18f, num2, num3); GUI.Box(val, GUIContent.none); GUI.Box(val, GUIContent.none); GUI.Label(new Rect(((Rect)(ref val)).x + 14f, ((Rect)(ref val)).y + 12f, num2 - 28f, num3 - 24f), text, _confirmStyle); } private Vector3 MeasuredPosition(PlayerControllerB player, bool isLocal) { //IL_001d: 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) if (_useReportedPosition.Value && !isLocal) { return player.serverPlayerPosition; } return ((Component)player).transform.localPosition; } private static void PruneOlderThan(List<float> times, float now, float window) { for (int num = times.Count - 1; num >= 0; num--) { if (now - times[num] > window) { times.RemoveAt(num); } } } private float FlatDistance(Vector3 from, Vector3 to) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) if (!_ignoreVerticalMovement.Value) { return Vector3.Distance(from, to); } float num = to.x - from.x; float num2 = to.z - from.z; return Mathf.Sqrt(num * num + num2 * num2); } private bool WindowExceeds(List<float> speeds, float seconds, float limit, out float average) { average = 0f; int num = SamplesFor(seconds); if (speeds.Count < num) { return false; } float num2 = 0f; int num3 = 0; for (int i = speeds.Count - num; i < speeds.Count; i++) { num2 += speeds[i]; if (speeds[i] > limit) { num3++; } } average = num2 / (float)num; if (average <= limit) { return false; } return (float)num3 / (float)num >= _minFractionOverLimit.Value; } private bool ReallyInsideShip(PlayerControllerB player, bool isLocal) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_003e: 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_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0057: 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_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) StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance.shipBounds == (Object)null) { return true; } Vector3 val = MeasuredPosition(player, isLocal); Transform parent = ((Component)player).transform.parent; Vector3 val2 = (((Object)(object)parent == (Object)null) ? val : parent.TransformPoint(val)); Bounds bounds = instance.shipBounds.bounds; return ((Bounds)(ref bounds)).Contains(val2 + Vector3.up * 0.25f); } private string GetSkipReason(PlayerControllerB player, bool isLocal) { if (!player.isPlayerControlled) { return "slot is empty or they have left"; } if (player.isPlayerDead) { return "they are dead"; } if ((player.isInElevator || player.isInHangarShipRoom) && ReallyInsideShip(player, isLocal)) { return "they are inside the ship"; } if (player.inSpecialInteractAnimation) { return "they are in a scripted animation"; } if ((Object)(object)player.inAnimationWithEnemy != (Object)null) { return "a monster has hold of them"; } if (player.inVehicleAnimation) { return "they are in a driver's seat"; } if (player.jetpackControls || player.disablingJetpackControls) { return "they are using a jetpack"; } return null; } private bool CanActOn(PlayerControllerB player, PlayerWatch watch) { if ((Object)(object)StartOfRound.Instance != (Object)null && (Object)(object)player == (Object)(object)StartOfRound.Instance.localPlayerController) { return false; } if (Time.time < watch.IgnoreUntil) { return false; } if (_alreadyLeaving) { return false; } if (IsProtected(player)) { return false; } if (_pendingKick) { return false; } if (_clearedUntil.TryGetValue(player.playerSteamId, out var value) && Time.time < value) { return false; } return true; } private void RefreshWhitelists() { string[] array; if (_lastParsedNames != _neverActOnUsernames.Value) { _lastParsedNames = _neverActOnUsernames.Value; _allowedNames.Clear(); array = _lastParsedNames.Split(','); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length > 0) { _allowedNames.Add(text); } } } if (!(_lastParsedIds != _neverActOnSteamIds.Value)) { return; } _lastParsedIds = _neverActOnSteamIds.Value; _allowedSteamIds.Clear(); array = _lastParsedIds.Split(','); for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim(); if (text2.Length != 0) { if (ulong.TryParse(text2, out var result)) { _allowedSteamIds.Add(result); } else { _log.LogWarning((object)("[MovementSync] '" + text2 + "' in SteamIds is not a number, ignoring it.")); } } } } private bool IsProtected(PlayerControllerB player) { RefreshWhitelists(); if (_allowedSteamIds.Count > 0 && _allowedSteamIds.Contains(player.playerSteamId)) { return true; } if (_allowedNames.Count > 0 && _allowedNames.Contains(player.playerUsername)) { return true; } return false; } private int SamplesFor(float seconds) { int num = Mathf.RoundToInt(seconds / _sampleInterval.Value); return Mathf.Max(2, num); } private void Act(PlayerControllerB player, int playerSlot, float speed, string reason) { //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) PlayerWatch playerWatch = _watches[playerSlot]; playerWatch.IgnoreUntil = Time.time + 15f; playerWatch.ForgetHistory(); string playerUsername = player.playerUsername; bool isServer = ((NetworkBehaviour)StartOfRound.Instance).IsServer; if (_testModeLogOnly.Value) { _log.LogWarning((object)("[MovementSync] TEST MODE - would have " + (isServer ? "kicked" : "left the lobby over") + " " + $"{playerUsername} (steam {player.playerSteamId}) at {speed:F1} u/s, {reason}.")); } else if (isServer && _requireConfirmation.Value && _actWhenHosting.Value) { _pendingKick = true; _pendingSlot = playerSlot; _pendingName = playerUsername; _pendingSteamId = player.playerSteamId; _pendingReason = reason; _pendingSpeed = speed; _pendingExpires = Time.time + _confirmTimeoutSeconds.Value; _log.LogWarning((object)(string.Format("[{0}] WAITING FOR YOU: {1} flagged - {2:F1} u/s, {3}. ", "MovementSync", playerUsername, speed, reason) + $"Press {_confirmKeyParsed} to kick, {_rejectKeyParsed} to clear them.")); ShowMessage("Review needed", playerUsername + " was flagged. Check your screen."); } else if (isServer) { if (!_actWhenHosting.Value) { _log.LogWarning((object)string.Format("[{0}] {1} at {2:F1} u/s, {3}. ActWhenHosting is false, so no kick.", "MovementSync", playerUsername, speed, reason)); return; } _log.LogError((object)string.Format("[{0}] KICKING {1} (steam {2}) - {3:F1} u/s, {4}.", "MovementSync", playerUsername, player.playerSteamId, speed, reason)); if (_announceKickInChat.Value && (Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.AddTextToChatOnServer(playerUsername + " was removed: abnormal movement speed.", -1); } ShowMessage(_discreetMessages.Value ? "Connection" : "Speed hack", _discreetMessages.Value ? (playerUsername + " was removed (desync).") : $"{playerUsername} kicked: {speed:F0} u/s."); StartOfRound.Instance.KickPlayer(playerSlot); } else if (!_actWhenNotHosting.Value) { _log.LogWarning((object)(string.Format("[{0}] {1} at {2:F1} u/s, {3}. ", "MovementSync", playerUsername, speed, reason) + "You are not the host and ActWhenNotHosting is false, so nothing was done.")); } else { _log.LogError((object)(string.Format("[{0}] LEAVING LOBBY - {1} (steam {2}) at {3:F1} u/s, {4}. ", "MovementSync", playerUsername, player.playerSteamId, speed, reason) + "(You are not the host, so kicking is not possible.)")); ShowMessage(_discreetMessages.Value ? "Connection lost" : "Speed hack detected", _discreetMessages.Value ? "Desync detected. Disconnecting." : $"{playerUsername} at {speed:F0} u/s. Disconnecting."); _alreadyLeaving = true; ((MonoBehaviour)this).StartCoroutine(LeaveLobbyAfterDelay(_disconnectDelay.Value)); } } private IEnumerator LeaveLobbyAfterDelay(float delaySeconds) { yield return (object)new WaitForSeconds(delaySeconds); if ((Object)(object)GameNetworkManager.Instance != (Object)null) { GameNetworkManager.Instance.Disconnect(); } _alreadyLeaving = false; _watches.Clear(); } private void ShowMessage(string header, string body) { if (!((Object)(object)HUDManager.Instance == (Object)null)) { HUDManager.Instance.DisplayTip(header, body, true, false, "LC_Tip1"); } } private void ForgetAllHistory() { foreach (KeyValuePair<int, PlayerWatch> watch in _watches) { watch.Value.HasLastPosition = false; watch.Value.HasWindowStart = false; watch.Value.ForgetHistory(); } } private bool ReadyToLog(PlayerWatch watch) { if (Time.time < watch.NextLogTime) { return false; } watch.NextLogTime = Time.time + 0.5f; return true; } private void NoteSkip(PlayerControllerB player, PlayerWatch watch, string reason) { if (watch.LastSkipReason != reason) { watch.LastSkipReason = reason; watch.SkipSince = Time.time; watch.WarnedStuck = false; } else if (!watch.WarnedStuck && Time.time - watch.SkipSince > _stuckFlagWarnSeconds.Value) { watch.WarnedStuck = true; _log.LogWarning((object)("[MovementSync] " + player.playerUsername + " has been left unchecked for " + $"{Time.time - watch.SkipSince:F0} seconds straight because {reason}. " + "If that is not actually true of them right now, the game flag behind it is stuck for players other than you, and they cannot be detected at all.")); } if (_logSkipReasons.Value && player.isPlayerControlled && ReadyToLog(watch)) { _log.LogInfo((object)("[MovementSync] " + player.playerUsername + ": not checked - " + reason + ".")); } } private void LogSpeeds(PlayerControllerB player, PlayerWatch watch, bool isLocal, float speed, Transform frame) { //IL_00ae: 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) if (ReadyToLog(watch)) { string arg = ((_useReportedPosition.Value && !isLocal) ? "reported" : "body"); string text = (((Object)(object)frame != (Object)null) ? ((Object)frame).name : "world"); _log.LogInfo((object)(string.Format("[{0}] {1} (steam {2}): ", "MovementSync", player.playerUsername, player.playerSteamId) + $"{speed:F1} u/s [{arg}]" + (isLocal ? " (this is you)" : "") + " | attached to: " + text + " | reported pos " + Fmt(player.serverPlayerPosition) + " | body pos " + Fmt(((Component)player).transform.localPosition))); } } private static string Fmt(Vector3 v) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) return $"({v.x:F1},{v.y:F1},{v.z:F1})"; } } }