Decompiled source of Trap Sync v0.6.1

UCHTrapSync.dll

Decompiled 6 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Steamworks;
using UCHTrapSync.Diagnostics;
using UCHTrapSync.Sync;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.SceneManagement;

[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("UCHTrapSync")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.6.1.0")]
[assembly: AssemblyInformationalVersion("0.6.1")]
[assembly: AssemblyProduct("UCHTrapSync")]
[assembly: AssemblyTitle("UCHTrapSync")]
[assembly: AssemblyVersion("0.6.1.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 UCHTrapSync
{
	public enum ModLogLevel
	{
		Off,
		Info,
		Debug,
		Verbose
	}
	[BepInPlugin("mpb27.uch.trapsync", "UCH Trap Sync", "0.6.1")]
	public class Plugin : BaseUnityPlugin
	{
		public const string Guid = "mpb27.uch.trapsync";

		public const string Name = "UCH Trap Sync";

		public const string Version = "0.6.1";

		internal static Plugin Instance;

		internal static ManualLogSource Log;

		internal static ConfigEntry<bool> ModEnabled;

		internal static ConfigEntry<bool> DebugOverlay;

		internal static ConfigEntry<ModLogLevel> LogLevel;

		internal static ConfigEntry<bool> RealtimeSchedule;

		internal static ConfigEntry<bool> AllowOnlineUnsynced;

		internal static ConfigEntry<bool> NetworkSync;

		internal static ConfigEntry<bool> AlignStart;

		internal static ConfigEntry<bool> ClockDiscipline;

		internal static ConfigEntry<float> CatchUpMaxDeltaTime;

		internal static ConfigEntry<float> CharacterSendRate;

		private Harmony harmony;

		private void Awake()
		{
			//IL_017b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0185: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			ModEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Mod an/aus. Aus = es wird nichts gepatcht.");
			DebugOverlay = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "Overlay", false, "Debug-Overlay (Rundenzeit, Ping) anzeigen.");
			LogLevel = ((BaseUnityPlugin)this).Config.Bind<ModLogLevel>("Debug", "LogLevel", ModLogLevel.Debug, "Wie viel der Mod ins BepInEx-Log schreibt.");
			RealtimeSchedule = ((BaseUnityPlugin)this).Config.Bind<bool>("Sync", "RealtimeSchedule", true, "Armbrust schiesst nach Echtzeit-Uhr statt nach Timer und Animation.");
			AllowOnlineUnsynced = ((BaseUnityPlugin)this).Config.Bind<bool>("Sync", "AllowOnlineUnsynced", false, "Taktsteuerung auch online ohne Abgleich mit dem Host erlauben. Nur fuer Tests: weicht dann von ungemoddeten Spielern ab.");
			NetworkSync = ((BaseUnityPlugin)this).Config.Bind<bool>("Sync", "NetworkSync", true, "Zeitabgleich mit dem Host. Wird nur aktiv, wenn alle Spieler der Lobby dieselbe Mod-Version haben.");
			AlignStart = ((BaseUnityPlugin)this).Config.Bind<bool>("Sync", "AlignStart", true, "Alle Rechner schalten die Fallen im selben Moment scharf (statt jeder nach eigenem Countdown).");
			ClockDiscipline = ((BaseUnityPlugin)this).Config.Bind<bool>("Sync", "ClockDiscipline", true, "Spielzeit waehrend des Laufs an die gemeinsame Rundenzeit koppeln. Synchronisiert alle Bloecke, die nach Spielzeit laufen.");
			CatchUpMaxDeltaTime = ((BaseUnityPlugin)this).Config.Bind<float>("Sync", "CatchUpMaxDeltaTime", 0.3f, "Wie viel Zeit Unity nach einem Ruckler pro Bild nachholen darf (Sekunden). Das Spiel selbst nutzt 0.05.");
			CharacterSendRate = ((BaseUnityPlugin)this).Config.Bind<float>("Sync", "CharacterSendRate", 30f, "Wie oft pro Sekunde Spielfiguren ihre Position senden (das Spiel nutzt 1 / sendInterval, siehe [Char]-Zeilen im Log). Hoeher = fremde Figuren weniger verzoegert. 0 = nicht aendern. Greift nur, wenn alle Spieler den Mod haben.");
			if (!ModEnabled.Value)
			{
				Log.LogInfo((object)"UCH Trap Sync 0.6.1 geladen, aber per Config deaktiviert.");
				return;
			}
			harmony = new Harmony("mpb27.uch.trapsync");
			harmony.PatchAll(typeof(Plugin).Assembly);
			((Component)this).gameObject.AddComponent<DebugOverlay>();
			((Component)this).gameObject.AddComponent<NetSync>();
			((Component)this).gameObject.AddComponent<GameClock>();
			((Component)this).gameObject.AddComponent<CharacterSendRate>();
			int num = 0;
			foreach (MethodBase patchedMethod in harmony.GetPatchedMethods())
			{
				_ = patchedMethod;
				num++;
			}
			Log.LogInfo((object)string.Format("{0} {1} geladen, {2} Methode(n) gepatcht.", "UCH Trap Sync", "0.6.1", num));
		}

		private void OnDestroy()
		{
			Harmony obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
		}

		internal static void LogDebug(string msg)
		{
			if (LogLevel.Value >= ModLogLevel.Debug)
			{
				Log.LogInfo((object)msg);
			}
		}

		internal static void LogVerbose(string msg)
		{
			if (LogLevel.Value >= ModLogLevel.Verbose)
			{
				Log.LogInfo((object)msg);
			}
		}
	}
}
namespace UCHTrapSync.Sync
{
	internal class CharacterSendRate : MonoBehaviour
	{
		private const double CheckInterval = 1.0;

		private const float IntervalMargin = 0.003f;

		private readonly Dictionary<NetworkTransform, float> originals = new Dictionary<NetworkTransform, float>();

		private readonly List<NetworkTransform> dead = new List<NetworkTransform>();

		private double nextCheck;

		private bool lastApplied;

		private void Update()
		{
			double now = SyncClock.Now;
			if (now < nextCheck)
			{
				return;
			}
			nextCheck = now + 1.0;
			try
			{
				Check();
			}
			catch (Exception ex)
			{
				Plugin.LogVerbose("[Char] Fehler beim Setzen der Senderate: " + ex.Message);
			}
		}

		private void Check()
		{
			float value = Plugin.CharacterSendRate.Value;
			bool flag = value > 0f && SyncClock.NetworkSynced;
			float num = (flag ? Mathf.Max(1f / value - 0.003f, 0.005f) : 0f);
			Character[] array = Object.FindObjectsOfType<Character>();
			foreach (Character val in array)
			{
				NetworkTransform component = ((Component)val).GetComponent<NetworkTransform>();
				if (!((Object)(object)component == (Object)null))
				{
					if (!originals.TryGetValue(component, out var value2))
					{
						value2 = component.sendInterval;
						originals[component] = value2;
					}
					float num2 = ((flag && num < value2) ? num : value2);
					if (!Mathf.Approximately(component.sendInterval, num2))
					{
						Plugin.LogDebug($"[Char] '{((Object)val).name}' eigene={((NetworkBehaviour)val).hasAuthority} sendInterval {component.sendInterval:F3}s -> {num2:F3}s " + $"(Original {value2:F3}s, interpolation={component.interpolateMovement:F2})");
						component.sendInterval = num2;
					}
				}
			}
			if (flag != lastApplied)
			{
				lastApplied = flag;
				Plugin.LogDebug(flag ? $"[Char] Senderate der Spielfiguren: {value:F0} pro Sekunde" : "[Char] Senderate der Spielfiguren: Original");
			}
			dead.Clear();
			foreach (NetworkTransform key in originals.Keys)
			{
				if ((Object)(object)key == (Object)null)
				{
					dead.Add(key);
				}
			}
			foreach (NetworkTransform item in dead)
			{
				originals.Remove(item);
			}
		}
	}
	internal class GameClock : MonoBehaviour
	{
		internal static GameClock Instance;

		private const double Gain = 1.0;

		private const double MaxSpeedUp = 0.05;

		private const double MaxSlowDown = 0.03;

		private const double DeadBand = 0.001;

		private const double GiveUpError = 3.0;

		private const double LogInterval = 5.0;

		private bool running;

		private TrapControl mode;

		private double zero;

		private float gameZero;

		private double lastNow;

		private float savedMaxDeltaTime;

		private bool touchedTimeScale;

		private double nextLog;

		private double worstError;

		internal bool Running => running;

		internal double Error { get; private set; }

		private static float BaseScale
		{
			get
			{
				float gameSpeed = Modifiers.GetInstance().GameSpeed;
				if (!(gameSpeed > 0f))
				{
					return 1f;
				}
				return gameSpeed;
			}
		}

		private void Awake()
		{
			Instance = this;
			SceneManager.activeSceneChanged += delegate
			{
				StopRun("Szenenwechsel");
			};
		}

		internal void StartRun()
		{
			StopRun(null);
			if (!Plugin.ClockDiscipline.Value)
			{
				return;
			}
			mode = SyncClock.DecideControl();
			double now = SyncClock.Now;
			if (mode == TrapControl.Shared)
			{
				if (!SyncClock.TryGetSharedZero(out zero))
				{
					return;
				}
			}
			else
			{
				if (mode != TrapControl.Local)
				{
					return;
				}
				zero = now;
			}
			gameZero = Time.time;
			lastNow = now;
			savedMaxDeltaTime = Time.maximumDeltaTime;
			float value = Plugin.CatchUpMaxDeltaTime.Value;
			if (value > savedMaxDeltaTime)
			{
				Time.maximumDeltaTime = value;
			}
			running = true;
			touchedTimeScale = false;
			worstError = 0.0;
			nextLog = now + 5.0;
			Plugin.LogDebug($"[Clock] Lauf gestartet modus={mode} startfehler={(now - zero) * 1000.0:F1}ms " + $"maxDt {savedMaxDeltaTime:F3}->{Time.maximumDeltaTime:F3}");
		}

		internal void StopRun(string reason)
		{
			if (running)
			{
				running = false;
				Error = 0.0;
				Time.maximumDeltaTime = savedMaxDeltaTime;
				if (touchedTimeScale && Time.timeScale > 0f)
				{
					Time.timeScale = BaseScale;
				}
				if (reason != null)
				{
					Plugin.LogDebug($"[Clock] Lauf beendet ({reason}), groesster Fehler {worstError * 1000.0:F1}ms");
				}
			}
		}

		private void Update()
		{
			if (!running)
			{
				return;
			}
			double now = SyncClock.Now;
			double num = now - lastNow;
			lastNow = now;
			if (Time.timeScale <= 0f)
			{
				zero += num;
				return;
			}
			float baseScale = BaseScale;
			double num2 = (now - zero) * (double)baseScale;
			double num3 = Time.time - gameZero;
			double num4 = (Error = num2 - num3);
			if (Math.Abs(num4) > worstError)
			{
				worstError = Math.Abs(num4);
			}
			if (Math.Abs(num4) > 3.0)
			{
				Plugin.Log.LogWarning((object)$"[Clock] Fehler {num4:F2}s zu gross, Zeitkorrektur fuer diesen Lauf abgeschaltet");
				StopRun("Fehler zu gross");
				return;
			}
			double num6 = 0.0;
			if (Math.Abs(num4) > 0.001)
			{
				num6 = num4 * 1.0;
				if (num6 > 0.05)
				{
					num6 = 0.05;
				}
				else if (num6 < -0.03)
				{
					num6 = -0.03;
				}
			}
			Time.timeScale = baseScale * (float)(1.0 + num6);
			touchedTimeScale = true;
			if (now >= nextLog)
			{
				nextLog = now + 5.0;
				Plugin.LogDebug($"[Clock] fehler={num4 * 1000.0:F1}ms korrektur={num6 * 100.0:F2}% lauf=+{now - zero:F1}s");
			}
		}
	}
	internal class LauncherScheduler : MonoBehaviour
	{
		private static readonly Dictionary<int, LauncherScheduler> byLauncher = new Dictionary<int, LauncherScheduler>();

		private static readonly Dictionary<string, int> shootStateHashes = new Dictionary<string, int>();

		private static readonly FieldRef<ProjectileLauncher, int> projectileCounter = AccessTools.FieldRefAccess<ProjectileLauncher, int>("projectileCounter");

		private const string ShootEventName = "ShootProjectile";

		private const float ParkedLastShot = -1000000f;

		private ProjectileLauncher launcher;

		private int launcherKey;

		private AnimationClip shootClip;

		private float shootEventTime;

		private TrapControl mode;

		private double sharedZero;

		private double lastNow;

		private double elapsed;

		private int nextTrigger;

		private int nextShot;

		internal bool Controlled => mode != TrapControl.Off;

		internal bool FiringNow { get; private set; }

		private static float RateOfFire
		{
			get
			{
				float rateOfFire = Modifiers.GetInstance().RateOfFire;
				if (!(rateOfFire > 0f))
				{
					return 1f;
				}
				return rateOfFire;
			}
		}

		private double Period => launcher.interval / RateOfFire;

		private double FirstTrigger
		{
			get
			{
				double num = 0f - launcher.initialDelay - launcher.firstShotDelay;
				double num2 = Period - num;
				if (!(num2 > 0.0))
				{
					return 0.0;
				}
				return num2;
			}
		}

		private double EventDelay
		{
			get
			{
				if (!((Object)(object)shootClip != (Object)null))
				{
					return 0.0;
				}
				return shootEventTime / RateOfFire;
			}
		}

		internal static LauncherScheduler For(ProjectileLauncher l)
		{
			byLauncher.TryGetValue(((Object)l).GetInstanceID(), out var value);
			return value;
		}

		internal static void Attach(ProjectileLauncher l)
		{
			if (!((Object)(object)For(l) != (Object)null))
			{
				LauncherScheduler launcherScheduler = ((Component)l).gameObject.AddComponent<LauncherScheduler>();
				launcherScheduler.launcher = l;
				launcherScheduler.launcherKey = ((Object)l).GetInstanceID();
				launcherScheduler.FindShootClip();
				byLauncher[launcherScheduler.launcherKey] = launcherScheduler;
			}
		}

		private void OnDestroy()
		{
			byLauncher.Remove(launcherKey);
		}

		private void FindShootClip()
		{
			Animator shootAnimator = launcher.ShootAnimator;
			if ((Object)(object)shootAnimator == (Object)null || (Object)(object)shootAnimator.runtimeAnimatorController == (Object)null)
			{
				return;
			}
			float num = float.MaxValue;
			AnimationClip[] animationClips = shootAnimator.runtimeAnimatorController.animationClips;
			foreach (AnimationClip val in animationClips)
			{
				AnimationEvent[] events = val.events;
				foreach (AnimationEvent val2 in events)
				{
					float num2 = Mathf.Abs(val.length - launcher.interval);
					if (val2.functionName == "ShootProjectile" && num2 < num)
					{
						num = num2;
						shootClip = val;
						shootEventTime = val2.time;
					}
				}
			}
		}

		internal void OnActivated()
		{
			mode = ((Period > 0.01) ? SyncClock.DecideControl() : TrapControl.Off);
			if (mode == TrapControl.Shared && !SyncClock.TryGetSharedZero(out sharedZero))
			{
				mode = TrapControl.Off;
			}
			lastNow = SyncClock.Now;
			elapsed = 0.0;
			nextTrigger = 0;
			nextShot = 0;
			if (!Controlled && launcher.lastShot <= -500000f)
			{
				launcher.lastShot = 0f - launcher.initialDelay - launcher.firstShotDelay;
			}
			string arg = ((mode == TrapControl.Shared) ? $"{(sharedZero - lastNow) * 1000.0:F1}ms" : "-");
			Plugin.LogDebug($"[Sync] id={((Placeable)launcher).ID} modus={mode} periode={Period:F4} " + string.Format("ersterSchuss=+{0:F4} clip='{1}' eventT={2:F3} ", FirstTrigger + EventDelay, ((Object)(object)shootClip != (Object)null) ? ((Object)shootClip).name : "-", shootEventTime) + $"nullpunkt={arg} allein={SyncClock.PlayingAlone} netzSync={SyncClock.NetworkSynced}");
		}

		internal void ParkOriginalTimer()
		{
			if (Controlled && ((ActiveBlock)launcher).Active)
			{
				launcher.lastShot = -1000000f;
			}
		}

		private void Update()
		{
			double now = SyncClock.Now;
			double num = now - lastNow;
			lastNow = now;
			if (Controlled && !((Object)(object)launcher == (Object)null) && ((ActiveBlock)launcher).Active && !((ActiveBlock)launcher).Paused)
			{
				if (mode == TrapControl.Shared)
				{
					elapsed = (now - sharedZero) * SyncClock.RunScale;
				}
				else
				{
					elapsed += num * SyncClock.RunScale;
				}
				LearnShootState();
				double period = Period;
				double firstTrigger = FirstTrigger;
				double num2 = firstTrigger + EventDelay;
				if (elapsed >= firstTrigger + (double)nextTrigger * period)
				{
					nextTrigger = (int)((elapsed - firstTrigger) / period) + 1;
					StartShootAnimation();
				}
				if (elapsed >= num2 + (double)nextShot * period)
				{
					int num3 = (int)((elapsed - num2) / period);
					double due = num2 + (double)num3 * period;
					nextShot = num3 + 1;
					Fire(num3, due);
				}
			}
		}

		private void StartShootAnimation()
		{
			launcher.shooting = true;
			RoundClock.OnTrigger(launcher);
			Animator shootAnimator = launcher.ShootAnimator;
			if (!((Object)(object)shootAnimator == (Object)null))
			{
				if (shootStateHashes.TryGetValue(((Object)shootAnimator.runtimeAnimatorController).name, out var value))
				{
					shootAnimator.ResetTrigger("Shoot");
					shootAnimator.Play(value, 0, 0f);
				}
				else
				{
					shootAnimator.SetTrigger("Shoot");
				}
			}
		}

		private void LearnShootState()
		{
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			Animator shootAnimator = launcher.ShootAnimator;
			if ((Object)(object)shootAnimator == (Object)null || (Object)(object)shootClip == (Object)null || (Object)(object)shootAnimator.runtimeAnimatorController == (Object)null)
			{
				return;
			}
			string name = ((Object)shootAnimator.runtimeAnimatorController).name;
			if (!shootStateHashes.ContainsKey(name) && !shootAnimator.IsInTransition(0))
			{
				AnimatorClipInfo[] currentAnimatorClipInfo = shootAnimator.GetCurrentAnimatorClipInfo(0);
				if (currentAnimatorClipInfo.Length == 1 && (Object)(object)((AnimatorClipInfo)(ref currentAnimatorClipInfo[0])).clip == (Object)(object)shootClip)
				{
					AnimatorStateInfo currentAnimatorStateInfo = shootAnimator.GetCurrentAnimatorStateInfo(0);
					int shortNameHash = ((AnimatorStateInfo)(ref currentAnimatorStateInfo)).shortNameHash;
					shootStateHashes[name] = shortNameHash;
					Plugin.LogDebug($"[Sync] Schuss-Zustand gelernt controller='{name}' hash={shortNameHash}");
				}
			}
		}

		private void Fire(int index, double due)
		{
			projectileCounter.Invoke(launcher) = index;
			Plugin.LogDebug($"[Sync] id={((Placeable)launcher).ID} n={index + 1} soll=+{due:F4} ist=+{elapsed:F4} spaet={elapsed - due:F4}");
			FiringNow = true;
			try
			{
				launcher.ShootProjectile();
			}
			finally
			{
				FiringNow = false;
			}
		}
	}
	internal static class TsMsg
	{
		internal const short Ping = 20001;

		internal const short Pong = 20002;

		internal const short State = 20003;

		internal const short PhaseRef = 20004;
	}
	internal class MsgTsPing : MessageBase
	{
		public int Seq;

		public double HostTime;

		public override void Serialize(NetworkWriter writer)
		{
			writer.Write(Seq);
			writer.Write(HostTime);
		}

		public override void Deserialize(NetworkReader reader)
		{
			Seq = reader.ReadInt32();
			HostTime = reader.ReadDouble();
		}
	}
	internal class MsgTsPong : MessageBase
	{
		public int Seq;

		public double HostTime;

		public double ClientTime;

		public string Version;

		public override void Serialize(NetworkWriter writer)
		{
			writer.Write(Seq);
			writer.Write(HostTime);
			writer.Write(ClientTime);
			writer.Write(Version);
		}

		public override void Deserialize(NetworkReader reader)
		{
			Seq = reader.ReadInt32();
			HostTime = reader.ReadDouble();
			ClientTime = reader.ReadDouble();
			Version = reader.ReadString();
		}
	}
	internal class MsgTsState : MessageBase
	{
		public string Version;

		public double Offset;

		public double Rtt;

		public int Samples;

		public override void Serialize(NetworkWriter writer)
		{
			writer.Write(Version);
			writer.Write(Offset);
			writer.Write(Rtt);
			writer.Write(Samples);
		}

		public override void Deserialize(NetworkReader reader)
		{
			Version = reader.ReadString();
			Offset = reader.ReadDouble();
			Rtt = reader.ReadDouble();
			Samples = reader.ReadInt32();
		}
	}
	internal class MsgTsPhaseRef : MessageBase
	{
		public int Phase;

		public double HostTime;

		public int Seed;

		public override void Serialize(NetworkWriter writer)
		{
			writer.Write(Phase);
			writer.Write(HostTime);
			writer.Write(Seed);
		}

		public override void Deserialize(NetworkReader reader)
		{
			Phase = reader.ReadInt32();
			HostTime = reader.ReadDouble();
			Seed = reader.ReadInt32();
		}
	}
	internal class NetSync : MonoBehaviour
	{
		private struct Sample
		{
			public double Rtt;

			public double Offset;
		}

		private class Peer
		{
			public readonly List<Sample> Samples = new List<Sample>();

			public double BestRtt;

			public double BestOffset;

			public double LastPong = -1.0;

			public string Version = "";
		}

		internal static NetSync Instance;

		private const string PresenceKey = "uchtrapsync";

		private const double TickInterval = 1.0;

		private const double FreshSeconds = 5.0;

		private const int MinSamples = 3;

		private const int MaxSamples = 8;

		private readonly Dictionary<int, Peer> peers = new Dictionary<int, Peer>();

		private readonly List<int> staleIds = new List<int>();

		private bool allModded;

		private string presenceReason = "";

		private int seq;

		private double clientOffset;

		private double clientRtt;

		private double lastState = -1.0;

		private string hostVersion = "";

		private double nextTick;

		private bool lastLoggedSynced;

		private string lastError = "";

		internal double OffsetToHost
		{
			get
			{
				if (!IsHost)
				{
					return clientOffset;
				}
				return 0.0;
			}
		}

		private static bool IsHost
		{
			get
			{
				LobbyManager instance = LobbyManager.instance;
				if ((Object)(object)instance != (Object)null)
				{
					return instance.IsHost;
				}
				return false;
			}
		}

		internal bool HostSynced
		{
			get
			{
				if (!allModded || !NetworkServer.active)
				{
					return false;
				}
				int num = 0;
				double now = SyncClock.Now;
				foreach (NetworkConnection connection in NetworkServer.connections)
				{
					if (connection != null && connection.hostId != -1)
					{
						num++;
						if (!peers.TryGetValue(connection.connectionId, out var value) || value.Samples.Count < 3 || value.Version != "0.6.1" || now - value.LastPong > 5.0)
						{
							return false;
						}
					}
				}
				return num > 0;
			}
		}

		internal bool ClientSynced
		{
			get
			{
				if (lastState > 0.0 && SyncClock.Now - lastState <= 5.0)
				{
					return hostVersion == "0.6.1";
				}
				return false;
			}
		}

		internal bool Synced
		{
			get
			{
				if (!IsHost)
				{
					return ClientSynced;
				}
				return HostSynced;
			}
		}

		internal string StatusLine
		{
			get
			{
				if (!Plugin.NetworkSync.Value)
				{
					return "Netz-Sync: per Config aus";
				}
				if ((Object)(object)LobbyManager.instance == (Object)null)
				{
					return "Netz-Sync: keine Lobby";
				}
				if (IsHost)
				{
					if (!Synced)
					{
						return "Netz-Sync: aus (" + (allModded ? "Messung läuft" : presenceReason) + ")";
					}
					return $"Netz-Sync: AKTIV (Host, {peers.Count} Client(s))";
				}
				if (!Synced)
				{
					return "Netz-Sync: aus (kein Signal vom Host)";
				}
				return $"Netz-Sync: AKTIV (Offset {clientOffset * 1000.0:F0} ms, RTT {clientRtt * 1000.0:F0} ms)";
			}
		}

		private void Awake()
		{
			Instance = this;
		}

		private void Update()
		{
			if (!Plugin.NetworkSync.Value)
			{
				return;
			}
			double now = SyncClock.Now;
			if (now < nextTick)
			{
				return;
			}
			nextTick = now + 1.0;
			try
			{
				Tick();
			}
			catch (Exception ex)
			{
				if (ex.Message != lastError)
				{
					lastError = ex.Message;
					Plugin.Log.LogWarning((object)$"[Net] Fehler im Sync-Tick, Sync bleibt aus: {ex}");
				}
				allModded = false;
			}
		}

		private void Tick()
		{
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: 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_006c: Expected O, but got Unknown
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Expected O, but got Unknown
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Expected O, but got Unknown
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Expected O, but got Unknown
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			LobbyManager instance = LobbyManager.instance;
			if ((Object)(object)instance == (Object)null)
			{
				peers.Clear();
				allModded = false;
				presenceReason = "keine Lobby";
				lastState = -1.0;
				return;
			}
			CSteamID lobby = FindSteamLobby();
			PublishPresence(lobby);
			if (((NetworkManager)instance).client != null)
			{
				((NetworkManager)instance).client.RegisterHandler((short)20001, new NetworkMessageDelegate(OnPing));
				((NetworkManager)instance).client.RegisterHandler((short)20003, new NetworkMessageDelegate(OnState));
				((NetworkManager)instance).client.RegisterHandler((short)20004, new NetworkMessageDelegate(OnPhaseRef));
			}
			if (instance.IsHost && NetworkServer.active)
			{
				NetworkServer.RegisterHandler((short)20002, new NetworkMessageDelegate(OnPong));
				string reason;
				bool flag = EvaluatePresence(instance, lobby, out reason);
				if (flag != allModded || reason != presenceReason)
				{
					Plugin.LogDebug("[Net] Handshake: " + (flag ? "alle Spieler haben 0.6.1" : ("Sync aus - " + reason)));
				}
				allModded = flag;
				presenceReason = reason;
				PrunePeers();
				if (allModded)
				{
					SendPings();
				}
			}
			else
			{
				allModded = false;
				presenceReason = "kein Host";
			}
			bool synced = Synced;
			if (synced != lastLoggedSynced)
			{
				lastLoggedSynced = synced;
				Plugin.LogDebug("[Net] " + StatusLine);
			}
		}

		private static CSteamID FindSteamLobby()
		{
			//IL_0024: 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)
			SteamMatchmaker val = Object.FindObjectOfType<SteamMatchmaker>();
			if ((Object)(object)val == (Object)null || val.SteamLobby == null || !((MatchmakingLobby)val.SteamLobby).IsValid())
			{
				return CSteamID.Nil;
			}
			return val.SteamLobby.LobbyID;
		}

		private static void PublishPresence(CSteamID lobby)
		{
			//IL_000a: 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)
			//IL_0026: 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)
			if (((CSteamID)(ref lobby)).IsValid() && SteamMatchmaking.GetLobbyMemberData(lobby, SteamUser.GetSteamID(), "uchtrapsync") != "0.6.1")
			{
				SteamMatchmaking.SetLobbyMemberData(lobby, "uchtrapsync", "0.6.1");
				Plugin.LogDebug(string.Format("[Net] Version {0} in Steam-Lobby {1} eingetragen", "0.6.1", lobby.m_SteamID));
			}
		}

		private static bool EvaluatePresence(LobbyManager lm, CSteamID lobby, out string reason)
		{
			//IL_0025: 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_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: 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_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			int num = lm.CountClients();
			if (num == 0)
			{
				reason = "keine Mitspieler verbunden";
				return false;
			}
			if (!((CSteamID)(ref lobby)).IsValid())
			{
				reason = "keine Steam-Lobby";
				return false;
			}
			int numLobbyMembers = SteamMatchmaking.GetNumLobbyMembers(lobby);
			if (numLobbyMembers != num + 1)
			{
				reason = $"Steam-Lobby hat {numLobbyMembers} Mitglied(er), verbunden sind {num + 1}";
				return false;
			}
			for (int i = 0; i < numLobbyMembers; i++)
			{
				CSteamID lobbyMemberByIndex = SteamMatchmaking.GetLobbyMemberByIndex(lobby, i);
				string lobbyMemberData = SteamMatchmaking.GetLobbyMemberData(lobby, lobbyMemberByIndex, "uchtrapsync");
				if (lobbyMemberData != "0.6.1")
				{
					string friendPersonaName = SteamFriends.GetFriendPersonaName(lobbyMemberByIndex);
					reason = (string.IsNullOrEmpty(lobbyMemberData) ? ("'" + friendPersonaName + "' hat den Mod nicht") : ("'" + friendPersonaName + "' hat Version " + lobbyMemberData + " statt 0.6.1"));
					return false;
				}
			}
			reason = "";
			return true;
		}

		private void SendPings()
		{
			MsgTsPing msgTsPing = new MsgTsPing
			{
				Seq = ++seq
			};
			foreach (NetworkConnection connection in NetworkServer.connections)
			{
				if (connection != null && connection.hostId != -1)
				{
					msgTsPing.HostTime = SyncClock.Now;
					connection.Send((short)20001, (MessageBase)(object)msgTsPing);
				}
			}
		}

		private void PrunePeers()
		{
			staleIds.Clear();
			foreach (int key in peers.Keys)
			{
				bool flag = false;
				foreach (NetworkConnection connection in NetworkServer.connections)
				{
					if (connection != null && connection.connectionId == key)
					{
						flag = true;
						break;
					}
				}
				if (!flag)
				{
					staleIds.Add(key);
				}
			}
			foreach (int staleId in staleIds)
			{
				peers.Remove(staleId);
			}
		}

		private void OnPong(NetworkMessage netMsg)
		{
			MsgTsPong msgTsPong = netMsg.ReadMessage<MsgTsPong>();
			double now = SyncClock.Now;
			double num = now - msgTsPong.HostTime;
			if (num < 0.0 || num > 5.0)
			{
				return;
			}
			int connectionId = netMsg.conn.connectionId;
			if (!peers.TryGetValue(connectionId, out var value))
			{
				value = new Peer();
				peers[connectionId] = value;
			}
			value.Samples.Add(new Sample
			{
				Rtt = num,
				Offset = msgTsPong.ClientTime - (msgTsPong.HostTime + num / 2.0)
			});
			if (value.Samples.Count > 8)
			{
				value.Samples.RemoveAt(0);
			}
			value.BestRtt = double.MaxValue;
			foreach (Sample sample in value.Samples)
			{
				if (sample.Rtt < value.BestRtt)
				{
					value.BestRtt = sample.Rtt;
					value.BestOffset = sample.Offset;
				}
			}
			value.LastPong = now;
			value.Version = msgTsPong.Version ?? "";
			Plugin.LogVerbose($"[Net] Pong conn={connectionId} seq={msgTsPong.Seq} rtt={num * 1000.0:F1}ms best={value.BestRtt * 1000.0:F1}ms " + $"offset={value.BestOffset:F4}s n={value.Samples.Count}");
			netMsg.conn.Send((short)20003, (MessageBase)(object)new MsgTsState
			{
				Version = "0.6.1",
				Offset = value.BestOffset,
				Rtt = value.BestRtt,
				Samples = value.Samples.Count
			});
		}

		internal void SendPhaseRef(int phase, double hostTime, int seed)
		{
			MsgTsPhaseRef msgTsPhaseRef = new MsgTsPhaseRef
			{
				Phase = phase,
				HostTime = hostTime,
				Seed = seed
			};
			foreach (NetworkConnection connection in NetworkServer.connections)
			{
				if (connection != null && connection.hostId != -1)
				{
					connection.Send((short)20004, (MessageBase)(object)msgTsPhaseRef);
				}
			}
		}

		private void OnPing(NetworkMessage netMsg)
		{
			MsgTsPing msgTsPing = netMsg.ReadMessage<MsgTsPing>();
			netMsg.conn.Send((short)20002, (MessageBase)(object)new MsgTsPong
			{
				Seq = msgTsPing.Seq,
				HostTime = msgTsPing.HostTime,
				ClientTime = SyncClock.Now,
				Version = "0.6.1"
			});
		}

		private void OnState(NetworkMessage netMsg)
		{
			MsgTsState msgTsState = netMsg.ReadMessage<MsgTsState>();
			hostVersion = msgTsState.Version ?? "";
			if (msgTsState.Samples >= 3)
			{
				clientOffset = msgTsState.Offset;
				clientRtt = msgTsState.Rtt;
				lastState = SyncClock.Now;
				Plugin.LogVerbose($"[Net] State offset={clientOffset:F4}s rtt={clientRtt * 1000.0:F1}ms n={msgTsState.Samples}");
			}
		}

		private void OnPhaseRef(NetworkMessage netMsg)
		{
			MsgTsPhaseRef msgTsPhaseRef = netMsg.ReadMessage<MsgTsPhaseRef>();
			if (!IsHost)
			{
				if (!ClientSynced)
				{
					Plugin.LogDebug("[Net] PhaseRef erhalten, aber kein gültiger Zeitabgleich - Runde läuft im Original");
				}
				else
				{
					SyncClock.SetPhaseRef(msgTsPhaseRef.HostTime + clientOffset, msgTsPhaseRef.Seed, $"vom Host, Offset {clientOffset * 1000.0:F1} ms, RTT {clientRtt * 1000.0:F1} ms");
				}
			}
		}
	}
	internal enum TrapControl
	{
		Off,
		Local,
		Shared
	}
	internal static class SyncClock
	{
		private const double PhaseRefMaxAge = 15.0;

		private static readonly Random rng = new Random();

		private static double phaseRefLocal = -1.0;

		internal static float WaitTime = 1.5f;

		internal static int Seed { get; private set; }

		internal static double Now => Time.realtimeSinceStartupAsDouble;

		internal static bool NetworkSynced
		{
			get
			{
				if ((Object)(object)NetSync.Instance != (Object)null)
				{
					return NetSync.Instance.Synced;
				}
				return false;
			}
		}

		internal static bool PlayingAlone
		{
			get
			{
				LobbyManager instance = LobbyManager.instance;
				if (!((Object)(object)instance == (Object)null))
				{
					if (instance.IsHost)
					{
						return instance.NumActiveConnections <= 1;
					}
					return false;
				}
				return true;
			}
		}

		internal static bool HostMustWaitForPhaseRef
		{
			get
			{
				LobbyManager instance = LobbyManager.instance;
				if (Plugin.RealtimeSchedule.Value && (Object)(object)instance != (Object)null && instance.IsHost && !PlayingAlone && (Object)(object)NetSync.Instance != (Object)null && NetSync.Instance.HostSynced)
				{
					return phaseRefLocal <= 0.0;
				}
				return false;
			}
		}

		internal static double RunScale
		{
			get
			{
				if (Time.timeScale <= 0f)
				{
					return 0.0;
				}
				float gameSpeed = Modifiers.GetInstance().GameSpeed;
				if (!(gameSpeed > 0f))
				{
					return 1.0;
				}
				return gameSpeed;
			}
		}

		internal static bool TryGetSharedZero(out double zero)
		{
			zero = phaseRefLocal + (double)WaitTime;
			if (phaseRefLocal > 0.0)
			{
				return Now - phaseRefLocal <= 15.0;
			}
			return false;
		}

		internal static bool TryGetPhaseAge(out double age)
		{
			age = Now - phaseRefLocal;
			if (age < 0.0)
			{
				age = 0.0;
			}
			if (Plugin.RealtimeSchedule.Value && phaseRefLocal > 0.0)
			{
				return age <= 15.0;
			}
			return false;
		}

		internal static TrapControl DecideControl()
		{
			if (!Plugin.RealtimeSchedule.Value)
			{
				return TrapControl.Off;
			}
			if (PlayingAlone)
			{
				return TrapControl.Local;
			}
			if (TryGetSharedZero(out var _))
			{
				return TrapControl.Shared;
			}
			if (!Plugin.AllowOnlineUnsynced.Value)
			{
				return TrapControl.Off;
			}
			return TrapControl.Local;
		}

		internal static void SetPhaseRef(double localTime, int seed, string source)
		{
			phaseRefLocal = localTime;
			Seed = seed;
			Plugin.LogDebug($"[Net] Runden-Referenz gesetzt ({source}), vor {(Now - localTime) * 1000.0:F1} ms, seed={seed}");
		}

		internal static void ClearPhaseRef()
		{
			phaseRefLocal = -1.0;
		}

		internal static void HostPhaseStarted(GamePhase phase)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Invalid comparison between Unknown and I4
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Invalid comparison between Unknown and I4
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Expected I4, but got Unknown
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			if ((int)phase != 2 && (int)phase != 4)
			{
				ClearPhaseRef();
				GameClock.Instance?.StopRun($"Phase {phase}");
				return;
			}
			NetSync instance = NetSync.Instance;
			if ((Object)(object)instance == (Object)null || !instance.HostSynced)
			{
				ClearPhaseRef();
				if (!PlayingAlone)
				{
					Plugin.LogDebug(string.Format("[Net] Phase {0}: kein Sync in dieser Runde ({1})", phase, ((Object)(object)instance != (Object)null) ? instance.StatusLine : "NetSync fehlt"));
				}
			}
			else
			{
				double now = Now;
				int seed = rng.Next();
				instance.SendPhaseRef((int)phase, now, seed);
				SetPhaseRef(now, seed, $"Host, Phase {phase}");
			}
		}

		internal static void ClientPhaseReceived(GamePhase phase)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Invalid comparison between Unknown and I4
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Invalid comparison between Unknown and I4
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			if ((int)phase != 2 && (int)phase != 4)
			{
				ClearPhaseRef();
				GameClock.Instance?.StopRun($"Phase {phase}");
			}
		}
	}
}
namespace UCHTrapSync.Patches
{
	[HarmonyPatch(typeof(GameControl), "ToPlayMode")]
	internal static class GameControl_ToPlayMode_Patch
	{
		private static void Postfix(GameControl __instance)
		{
			VersusControl val = (VersusControl)(object)((__instance is VersusControl) ? __instance : null);
			if (val != null && val.WaitTime > 0f)
			{
				SyncClock.WaitTime = val.WaitTime;
			}
			RoundClock.OnPlayPhase(((object)__instance).GetType().Name);
		}
	}
	[HarmonyPatch(typeof(GameControl), "CallRpcStartPhase")]
	internal static class GameControl_CallRpcStartPhase_Patch
	{
		private static void Postfix(GamePhase phase)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			if (RoundClock.IsHost())
			{
				SyncClock.HostPhaseStarted(phase);
			}
		}
	}
	[HarmonyPatch(typeof(GameControl), "RpcStartPhase")]
	internal static class GameControl_RpcStartPhase_Patch
	{
		private unsafe static void Postfix(GamePhase phase)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			RoundClock.OnRpcStartPhase(((object)(*(GamePhase*)(&phase))/*cast due to .constrained prefix*/).ToString());
			if (!RoundClock.IsHost())
			{
				SyncClock.ClientPhaseReceived(phase);
			}
		}
	}
	[HarmonyPatch(typeof(HockeyShooter), "ShootProjectile")]
	internal static class HockeyShooter_ShootProjectile_Patch
	{
		private static void Postfix(HockeyShooter __instance)
		{
			RoundClock.OnOtherShot("Hockey", ((Placeable)__instance).ID);
		}
	}
	[HarmonyPatch(typeof(ProjectileLauncher), "Start")]
	internal static class ProjectileLauncher_Start_Patch
	{
		private static void Postfix(ProjectileLauncher __instance)
		{
			Plugin.LogDebug($"[Launcher] Start name='{((Object)__instance).name}' id={((Placeable)__instance).ID} " + $"interval={__instance.interval:F3} firstShotDelay={__instance.firstShotDelay:F3} " + $"initialDelay={__instance.initialDelay:F3} lastShot={__instance.lastShot:F3} " + string.Format("animator={0} ammo='{1}'", (Object)(object)__instance.ShootAnimator != (Object)null, ((Object)(object)__instance.Ammo != (Object)null) ? ((Object)__instance.Ammo).name : "null"));
			AnimatorDump.Dump(__instance);
			LauncherScheduler.Attach(__instance);
		}
	}
	[HarmonyPatch(typeof(ProjectileLauncher), "Activate")]
	internal static class ProjectileLauncher_Activate_Patch
	{
		private static void Postfix(ProjectileLauncher __instance)
		{
			RoundClock.OnLauncherActivated(__instance);
			LauncherScheduler.For(__instance)?.OnActivated();
		}
	}
	[HarmonyPatch(typeof(ProjectileLauncher), "FixedUpdate")]
	internal static class ProjectileLauncher_FixedUpdate_Patch
	{
		private static void Prefix(ProjectileLauncher __instance, out bool __state)
		{
			__state = __instance.shooting;
			LauncherScheduler.For(__instance)?.ParkOriginalTimer();
		}

		private static void Postfix(ProjectileLauncher __instance, bool __state)
		{
			if (!__state && __instance.shooting)
			{
				RoundClock.OnTrigger(__instance);
			}
		}
	}
	[HarmonyPatch(typeof(ProjectileLauncher), "ShootProjectile")]
	internal static class ProjectileLauncher_ShootProjectile_Patch
	{
		private static readonly FieldRef<ProjectileLauncher, int> projectileCounter = AccessTools.FieldRefAccess<ProjectileLauncher, int>("projectileCounter");

		private static bool Prefix(ProjectileLauncher __instance, out int __state)
		{
			__state = projectileCounter.Invoke(__instance);
			LauncherScheduler launcherScheduler = LauncherScheduler.For(__instance);
			if (!((Object)(object)launcherScheduler == (Object)null) && launcherScheduler.Controlled)
			{
				return launcherScheduler.FiringNow;
			}
			return true;
		}

		private static void Postfix(ProjectileLauncher __instance, int __state)
		{
			int num = projectileCounter.Invoke(__instance);
			if (num != __state)
			{
				RoundClock.OnShot(__instance, num);
			}
		}
	}
	[HarmonyPatch]
	internal static class VersusControl_RunStart_Patch
	{
		private static readonly FieldRef<VersusControl, float> readyTimer = AccessTools.FieldRefAccess<VersusControl, float>("readyTimer");

		private static readonly FieldRef<VersusControl, bool> runStarted = AccessTools.FieldRefAccess<VersusControl, bool>("runStarted");

		private static IEnumerable<MethodBase> TargetMethods()
		{
			yield return AccessTools.Method(typeof(VersusControl), "DoPlayMode", (Type[])null, (Type[])null);
			yield return AccessTools.Method(typeof(VersusControl), "DoSuddenDeath", (Type[])null, (Type[])null);
		}

		private static void Prefix(VersusControl __instance, out bool __state)
		{
			__state = runStarted.Invoke(__instance);
			if (!__state && Plugin.AlignStart.Value)
			{
				if (SyncClock.TryGetPhaseAge(out var age))
				{
					readyTimer.Invoke(__instance) = (float)age;
				}
				else if (SyncClock.HostMustWaitForPhaseRef)
				{
					readyTimer.Invoke(__instance) = 0f;
				}
			}
		}

		private static void Postfix(VersusControl __instance, bool __state)
		{
			if (!__state && runStarted.Invoke(__instance))
			{
				RoundClock.BeginRunIfNewFrame();
				GameClock.Instance?.StartRun();
			}
		}
	}
}
namespace UCHTrapSync.Diagnostics
{
	internal static class AnimatorDump
	{
		private static readonly HashSet<string> dumped = new HashSet<string>();

		internal static void Dump(ProjectileLauncher launcher)
		{
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			Animator shootAnimator = launcher.ShootAnimator;
			if ((Object)(object)shootAnimator == (Object)null || (Object)(object)shootAnimator.runtimeAnimatorController == (Object)null)
			{
				return;
			}
			RuntimeAnimatorController runtimeAnimatorController = shootAnimator.runtimeAnimatorController;
			if (!dumped.Add(((Object)runtimeAnimatorController).name))
			{
				return;
			}
			Plugin.LogDebug($"[Anim] controller='{((Object)runtimeAnimatorController).name}' updateMode={shootAnimator.updateMode} speed={shootAnimator.speed:F2} " + $"layers={shootAnimator.layerCount} params={shootAnimator.parameterCount}");
			AnimationClip[] animationClips = runtimeAnimatorController.animationClips;
			foreach (AnimationClip val in animationClips)
			{
				Plugin.LogDebug($"[Anim]   clip='{((Object)val).name}' length={val.length:F4} loop={((Motion)val).isLooping} fps={val.frameRate:F0} events={val.events.Length}");
				AnimationEvent[] events = val.events;
				foreach (AnimationEvent val2 in events)
				{
					Plugin.LogDebug($"[Anim]     event t={val2.time:F4} fn='{val2.functionName}'");
				}
			}
		}
	}
	internal class DebugOverlay : MonoBehaviour
	{
		private GUIStyle style;

		private int ping = -1;

		private float nextPingAt;

		private void OnGUI()
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Expected O, but got Unknown
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f7: 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)
			if (Plugin.DebugOverlay.Value)
			{
				if (style == null)
				{
					style = new GUIStyle(GUI.skin.label)
					{
						fontSize = 14
					};
					style.normal.textColor = Color.white;
				}
				if (Time.realtimeSinceStartup >= nextPingAt)
				{
					nextPingAt = Time.realtimeSinceStartup + 0.5f;
					ping = RoundClock.Ping();
				}
				string text = string.Format("UCH Trap Sync {0}  {1}  Ping {2} ms\n", "0.6.1", RoundClock.IsHost() ? "HOST" : "CLIENT", ping) + string.Format("Runde {0}.{1}  {2}\n", RoundClock.Round, RoundClock.Run, RoundClock.Running ? "läuft" : "wartet") + $"Echtzeit +{RoundClock.RealElapsed:F2}s  Spielzeit +{RoundClock.GameElapsed:F2}s  " + $"Lag {RoundClock.RealElapsed - RoundClock.GameElapsed:F3}s\n" + $"Launcher aktiv: {RoundClock.ActiveLaunchers}  Letzter Schuss: {RoundClock.LastShot}\n" + (((Object)(object)NetSync.Instance != (Object)null) ? NetSync.Instance.StatusLine : "Netz-Sync: -") + "\n" + (((Object)(object)GameClock.Instance != (Object)null && GameClock.Instance.Running) ? $"Spieluhr: Fehler {GameClock.Instance.Error * 1000.0:F1} ms, timeScale {Time.timeScale:F3}" : "Spieluhr: nicht gekoppelt");
				Rect val = default(Rect);
				((Rect)(ref val))..ctor(10f, 10f, 560f, 124f);
				GUI.color = new Color(0f, 0f, 0f, 0.6f);
				GUI.DrawTexture(val, (Texture)(object)Texture2D.whiteTexture);
				GUI.color = Color.white;
				GUI.Label(new Rect(((Rect)(ref val)).x + 6f, ((Rect)(ref val)).y + 4f, ((Rect)(ref val)).width - 12f, ((Rect)(ref val)).height - 8f), text, style);
			}
		}
	}
	internal static class RoundClock
	{
		private static float playPhaseReal;

		private static float runStartReal;

		private static float runStartGame;

		private static int lastActivationFrame = -1;

		private static readonly Dictionary<int, float> triggerReal = new Dictionary<int, float>();

		internal static int Round { get; private set; }

		internal static int Run { get; private set; }

		internal static bool Running { get; private set; }

		internal static int ActiveLaunchers { get; private set; }

		internal static string LastShot { get; private set; } = "-";

		internal static float RealElapsed
		{
			get
			{
				if (!Running)
				{
					return 0f;
				}
				return Time.realtimeSinceStartup - runStartReal;
			}
		}

		internal static float GameElapsed
		{
			get
			{
				if (!Running)
				{
					return 0f;
				}
				return Time.time - runStartGame;
			}
		}

		private static string Utc => DateTime.UtcNow.ToString("HH:mm:ss.fff");

		internal static void OnPlayPhase(string source)
		{
			Round++;
			Run = 0;
			Running = false;
			playPhaseReal = Time.realtimeSinceStartup;
			triggerReal.Clear();
			Plugin.LogDebug($"[Round] r={Round} PLAY-Phase ({source}) host={IsHost()} ping={Ping()}ms lobby={LobbyType()} " + $"fixedDt={Time.fixedDeltaTime:F4} maxDt={Time.maximumDeltaTime:F3} timeScale={Time.timeScale:F2} " + $"frame={Time.frameCount} utc={Utc}");
			LogCharacterSync();
		}

		private static void LogCharacterSync()
		{
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Character[] array = Object.FindObjectsOfType<Character>();
				foreach (Character val in array)
				{
					NetworkTransform component = ((Component)val).GetComponent<NetworkTransform>();
					if (!((Object)(object)component == (Object)null))
					{
						Plugin.LogDebug($"[Char] '{((Object)val).name}' eigene={((NetworkBehaviour)val).hasAuthority} sendInterval={component.sendInterval:F3}s modus={component.transformSyncMode} " + $"interpolation={component.interpolateMovement:F2} bewegungsSchwelle={component.movementTheshold:F3} " + $"snapSchwelle={component.snapThreshold:F2} geschwSchwelle={component.velocityThreshold:F4}");
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.LogDebug("[Char] Auslesen fehlgeschlagen: " + ex.Message);
			}
		}

		internal static void OnRpcStartPhase(string phase)
		{
			Plugin.LogDebug($"[Round] RpcStartPhase empfangen phase={phase} ping={Ping()}ms frame={Time.frameCount} utc={Utc}");
		}

		internal static void BeginRunIfNewFrame()
		{
			if (Time.frameCount != lastActivationFrame)
			{
				lastActivationFrame = Time.frameCount;
				Run++;
				Running = true;
				ActiveLaunchers = 0;
				runStartReal = Time.realtimeSinceStartup;
				runStartGame = Time.time;
				triggerReal.Clear();
				Plugin.LogDebug($"[Run] r={Round}.{Run} Fallen aktiviert, {runStartReal - playPhaseReal:F3}s nach PLAY-Phase " + $"frame={Time.frameCount} utc={Utc}");
			}
		}

		internal static void OnLauncherActivated(ProjectileLauncher launcher)
		{
			BeginRunIfNewFrame();
			ActiveLaunchers++;
			Plugin.LogDebug($"[Launcher] r={Round}.{Run} aktiv id={((Placeable)launcher).ID} name='{((Object)launcher).name}' lastShot={launcher.lastShot:F3} interval={launcher.interval:F3}");
		}

		internal static void OnTrigger(ProjectileLauncher launcher)
		{
			triggerReal[((Placeable)launcher).ID] = Time.realtimeSinceStartup;
			Plugin.LogVerbose($"[Trig] r={Round}.{Run} id={((Placeable)launcher).ID} real=+{RealElapsed:F3} game=+{GameElapsed:F3}");
		}

		internal static void OnShot(ProjectileLauncher launcher, int shotNumber)
		{
			float realElapsed = RealElapsed;
			float gameElapsed = GameElapsed;
			float value;
			string text = (triggerReal.TryGetValue(((Placeable)launcher).ID, out value) ? $"{Time.realtimeSinceStartup - value:F3}" : "?");
			LastShot = $"id={((Placeable)launcher).ID} n={shotNumber} +{realElapsed:F3}s";
			Plugin.LogDebug($"[Shot] r={Round}.{Run} id={((Placeable)launcher).ID} n={shotNumber} real=+{realElapsed:F3} game=+{gameElapsed:F3} " + $"lag={realElapsed - gameElapsed:F3} animDelay={text} frame={Time.frameCount} utc={Utc}");
		}

		internal static string LobbyType()
		{
			try
			{
				MatchmakingLobby currentMatchmakingLobby = Matchmaker.CurrentMatchmakingLobby;
				return (currentMatchmakingLobby != null) ? ((object)currentMatchmakingLobby).GetType().Name : "keine";
			}
			catch (Exception)
			{
				return "?";
			}
		}

		internal static void OnOtherShot(string kind, int id)
		{
			Plugin.LogDebug($"[{kind}] r={Round}.{Run} id={id} real=+{RealElapsed:F3} game=+{GameElapsed:F3} " + $"lag={RealElapsed - GameElapsed:F3} frame={Time.frameCount} utc={Utc}");
		}

		internal static bool IsHost()
		{
			LobbyManager instance = LobbyManager.instance;
			if ((Object)(object)instance != (Object)null)
			{
				return instance.IsHost;
			}
			return false;
		}

		internal static int Ping()
		{
			try
			{
				LobbyManager instance = LobbyManager.instance;
				return ((Object)(object)instance != (Object)null) ? instance.CurrentPing : (-1);
			}
			catch (Exception)
			{
				return -1;
			}
		}
	}
}