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;
}
}
}
}