Decompiled source of NeonTracers v0.3.3

Mods/NeonTracers.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BoneLib;
using BoneLib.BoneMenu;
using HarmonyLib;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSLZ.Marrow;
using Il2CppSLZ.Marrow.AI;
using Il2CppSystem.Collections.Generic;
using LabFusion.Network;
using LabFusion.Player;
using LabFusion.SDK.Modules;
using LabFusion.Scene;
using LabFusion.Utilities;
using MelonLoader;
using MelonLoader.Preferences;
using Microsoft.CodeAnalysis;
using NeonTracers;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Rendering;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(TracerMod), "Neon Tracers", "0.3.3", "NeonTracers", null)]
[assembly: MelonGame("Stress Level Zero", "BONELAB")]
[assembly: MelonAdditionalDependencies(new string[] { "BoneLib", "LabFusion" })]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("NeonTracers")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.3.3.0")]
[assembly: AssemblyInformationalVersion("0.3.3")]
[assembly: AssemblyProduct("NeonTracers")]
[assembly: AssemblyTitle("NeonTracers")]
[assembly: AssemblyVersion("0.3.3.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 NeonTracers
{
	public sealed class TracerFusionModule : Module
	{
		public override string Name => "Neon Tracers";

		public override string Author => "NeonTracers";

		public override Version Version => new Version(0, 3, 3);

		protected override void OnModuleRegistered()
		{
			if (ModuleMessageManager.GetHandlerByType(typeof(TracerFusionMessage)) == null)
			{
				ModuleMessageManager.RegisterHandler<TracerFusionMessage>();
			}
		}
	}
	[SkipHandleWhileLoading]
	public sealed class TracerFusionMessage : ModuleMessageHandler
	{
		protected override void OnHandleMessage(ReceivedMessage received)
		{
			byte? sender = ((ReceivedMessage)(ref received)).Sender;
			if (sender.HasValue)
			{
				byte valueOrDefault = sender.GetValueOrDefault();
				if (valueOrDefault != PlayerIDManager.LocalSmallID && PlayerIDManager.HasPlayerID(valueOrDefault))
				{
					FusionBridge.Receive(valueOrDefault, ((ReceivedMessage)(ref received)).Bytes);
				}
			}
		}
	}
	internal static class FusionBridge
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static ServerEvent <0>__Reset;

			public static PlayerUpdate <1>__Joined;

			public static PlayerUpdate <2>__Left;
		}

		private static TracerMod? mod;

		private static Guid epoch = Guid.NewGuid();

		private static ulong sequence;

		private static bool wasConnected;

		private static float nextFlush;

		private static float nextSettings;

		private static readonly List<VisualCommand> pending = new List<VisualCommand>();

		private static readonly Dictionary<byte, (double Tokens, float At)> budgets = new Dictionary<byte, (double, float)>();

		public static bool Connected
		{
			get
			{
				if (NetworkInfo.HasServer)
				{
					return PlayerIDManager.LocalID != null;
				}
				return false;
			}
		}

		public static byte LocalId => PlayerIDManager.LocalSmallID;

		public static string Scene => FusionSceneManager.Barcode ?? string.Empty;

		public static void Initialize(TracerMod instance)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Expected O, but got Unknown
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Expected O, but got Unknown
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Expected O, but got Unknown
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Expected O, but got Unknown
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Expected O, but got Unknown
			mod = instance;
			ModuleManager.RegisterModule<TracerFusionModule>();
			object obj = <>O.<0>__Reset;
			if (obj == null)
			{
				ServerEvent val = Reset;
				<>O.<0>__Reset = val;
				obj = (object)val;
			}
			MultiplayerHooking.OnStartedServer += (ServerEvent)obj;
			object obj2 = <>O.<0>__Reset;
			if (obj2 == null)
			{
				ServerEvent val2 = Reset;
				<>O.<0>__Reset = val2;
				obj2 = (object)val2;
			}
			MultiplayerHooking.OnJoinedServer += (ServerEvent)obj2;
			object obj3 = <>O.<0>__Reset;
			if (obj3 == null)
			{
				ServerEvent val3 = Reset;
				<>O.<0>__Reset = val3;
				obj3 = (object)val3;
			}
			MultiplayerHooking.OnDisconnected += (ServerEvent)obj3;
			object obj4 = <>O.<1>__Joined;
			if (obj4 == null)
			{
				PlayerUpdate val4 = Joined;
				<>O.<1>__Joined = val4;
				obj4 = (object)val4;
			}
			MultiplayerHooking.OnPlayerJoined += (PlayerUpdate)obj4;
			object obj5 = <>O.<2>__Left;
			if (obj5 == null)
			{
				PlayerUpdate val5 = Left;
				<>O.<2>__Left = val5;
				obj5 = (object)val5;
			}
			MultiplayerHooking.OnPlayerLeft += (PlayerUpdate)obj5;
		}

		public static void Dispose()
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Expected O, but got Unknown
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Expected O, but got Unknown
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Expected O, but got Unknown
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Expected O, but got Unknown
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Expected O, but got Unknown
			object obj = <>O.<0>__Reset;
			if (obj == null)
			{
				ServerEvent val = Reset;
				<>O.<0>__Reset = val;
				obj = (object)val;
			}
			MultiplayerHooking.OnStartedServer -= (ServerEvent)obj;
			object obj2 = <>O.<0>__Reset;
			if (obj2 == null)
			{
				ServerEvent val2 = Reset;
				<>O.<0>__Reset = val2;
				obj2 = (object)val2;
			}
			MultiplayerHooking.OnJoinedServer -= (ServerEvent)obj2;
			object obj3 = <>O.<0>__Reset;
			if (obj3 == null)
			{
				ServerEvent val3 = Reset;
				<>O.<0>__Reset = val3;
				obj3 = (object)val3;
			}
			MultiplayerHooking.OnDisconnected -= (ServerEvent)obj3;
			object obj4 = <>O.<1>__Joined;
			if (obj4 == null)
			{
				PlayerUpdate val4 = Joined;
				<>O.<1>__Joined = val4;
				obj4 = (object)val4;
			}
			MultiplayerHooking.OnPlayerJoined -= (PlayerUpdate)obj4;
			object obj5 = <>O.<2>__Left;
			if (obj5 == null)
			{
				PlayerUpdate val5 = Left;
				<>O.<2>__Left = val5;
				obj5 = (object)val5;
			}
			MultiplayerHooking.OnPlayerLeft -= (PlayerUpdate)obj5;
			pending.Clear();
			budgets.Clear();
			mod = null;
		}

		private static void Joined(PlayerID _)
		{
			Announce();
			Queue(new VisualCommand(VisualOp.RequestSettings));
		}

		private static void Left(PlayerID id)
		{
			budgets.Remove(id.SmallID);
			mod?.RemovePeer(id.SmallID);
		}

		private static void Reset()
		{
			pending.Clear();
			budgets.Clear();
			epoch = Guid.NewGuid();
			sequence = 0uL;
			wasConnected = Connected;
			mod?.ResetNetworkSession();
			Announce();
			Queue(new VisualCommand(VisualOp.RequestSettings));
		}

		public static void SceneChanged()
		{
			pending.Clear();
			epoch = Guid.NewGuid();
			sequence = 0uL;
			mod?.ResetNetworkPeers();
			Announce();
			Queue(new VisualCommand(VisualOp.RequestSettings));
		}

		public static void Announce()
		{
			if (mod != null)
			{
				VisualSettings ownSettings = mod.OwnSettings;
				Queue(new VisualCommand(VisualOp.Settings, 0u, 0f, default(Vector3), ownSettings));
			}
		}

		public static void Queue(VisualCommand command)
		{
			if (Connected && mod != null)
			{
				if (pending.Count >= 512)
				{
					pending.Clear();
					pending.Add(new VisualCommand(VisualOp.Clear));
				}
				pending.Add(command);
			}
		}

		public static void Tick()
		{
			//IL_0123: Unknown result type (might be due to invalid IL or missing references)
			//IL_0128: Unknown result type (might be due to invalid IL or missing references)
			//IL_012a: Unknown result type (might be due to invalid IL or missing references)
			if (Connected != wasConnected)
			{
				Reset();
			}
			if (!Connected)
			{
				return;
			}
			if (Time.unscaledTime >= nextSettings)
			{
				nextSettings = Time.unscaledTime + 5f;
				Announce();
				Queue(new VisualCommand(VisualOp.RequestSettings));
			}
			if (Time.unscaledTime < nextFlush || pending.Count == 0)
			{
				return;
			}
			nextFlush = Time.unscaledTime + 1f / 30f;
			for (int i = 0; i < 4; i++)
			{
				if (pending.Count <= 0)
				{
					break;
				}
				int j = 0;
				int num = 32;
				for (; j < pending.Count && j < 64; j++)
				{
					FlightPoint[]? path = pending[j].Path;
					int num2 = 80 + ((path != null) ? path.Length : 0) * 16;
					if (num + num2 > 8000)
					{
						break;
					}
					num += num2;
				}
				VisualCommand[] commands = pending.GetRange(0, j).ToArray();
				pending.RemoveRange(0, j);
				byte[] array = VisualProtocol.Encode(new VisualPacket(epoch, ++sequence, commands, Scene));
				MessageRoute reliableToOtherClients = CommonMessageRoutes.ReliableToOtherClients;
				NetMessage val = NetMessage.ModuleCreate<TracerFusionMessage>(array, reliableToOtherClients, (byte?)LocalId);
				try
				{
					if (NetworkInfo.IsHost)
					{
						MessageSender.BroadcastMessageExcept(LocalId, (NetworkChannel)0, val, false);
					}
					else
					{
						MessageSender.SendToServer((NetworkChannel)0, val);
					}
				}
				finally
				{
					((IDisposable)val)?.Dispose();
				}
			}
		}

		public static void Receive(byte sender, byte[] bytes)
		{
			if (mod == null || !Connected || bytes.Length > 12000)
			{
				return;
			}
			float unscaledTime = Time.unscaledTime;
			if (!budgets.TryGetValue(sender, out (double, float) value))
			{
				value = (32768.0, unscaledTime);
			}
			double num = Math.Min(32768.0, value.Item1 + (double)(Math.Max(0f, unscaledTime - value.Item2) * 96000f));
			if ((double)bytes.Length > num)
			{
				budgets[sender] = (num, unscaledTime);
				return;
			}
			budgets[sender] = (num - (double)bytes.Length, unscaledTime);
			try
			{
				mod.ReceiveVisuals(sender, VisualProtocol.Decode(bytes));
			}
			catch (Exception ex)
			{
				mod.NetworkWarning(ex.Message);
			}
		}

		public static bool IsLocalShot(Transform source, Rigidbody body, TriggerRefProxy proxy)
		{
			if (!Connected)
			{
				return true;
			}
			RigManager rigManager = Player.RigManager;
			if (!Object.op_Implicit((Object)(object)rigManager))
			{
				return false;
			}
			Gun val = FindSourceGun(source, body);
			if ((Object)(object)val != (Object)null && Object.op_Implicit((Object)(object)val) && Object.op_Implicit((Object)(object)val.triggerGrip))
			{
				List<Hand> attachedHands = val.triggerGrip.attachedHands;
				int num = 0;
				while (attachedHands != null && num < attachedHands.Count)
				{
					if (Object.op_Implicit((Object)(object)attachedHands[num]) && (Object)(object)attachedHands[num].manager == (Object)(object)rigManager)
					{
						return true;
					}
					num++;
				}
				return false;
			}
			RigManager val2 = (Object.op_Implicit((Object)(object)proxy) ? ((Component)proxy).GetComponentInParent<RigManager>() : null);
			if (!Object.op_Implicit((Object)(object)val2) && Object.op_Implicit((Object)(object)proxy) && Object.op_Implicit((Object)(object)proxy.root))
			{
				val2 = proxy.root.GetComponent<RigManager>();
			}
			if (!Object.op_Implicit((Object)(object)val2) && Object.op_Implicit((Object)(object)source))
			{
				val2 = ((Component)source).GetComponentInParent<RigManager>();
			}
			if (Object.op_Implicit((Object)(object)val2))
			{
				return (Object)(object)val2 == (Object)(object)rigManager;
			}
			return false;
		}

		public static string PlayerName(byte id)
		{
			PlayerID playerID = PlayerIDManager.GetPlayerID(id);
			string text = ((playerID != null) ? playerID.Metadata.Username.GetValueOrEmpty() : null);
			if (text == null || text.Length <= 0)
			{
				return $"Player {id}";
			}
			return text;
		}

		public static Gun? FindSourceGun(Transform source, Rigidbody body)
		{
			Gun val = (Object.op_Implicit((Object)(object)source) ? ((Component)source).GetComponentInParent<Gun>() : null);
			if (!Object.op_Implicit((Object)(object)val) && Object.op_Implicit((Object)(object)body))
			{
				val = ((Component)body).GetComponentInParent<Gun>();
			}
			return val;
		}
	}
	public sealed class TracerMod : MelonMod
	{
		private sealed class Trace
		{
			private readonly GameObject root;

			private readonly LineRenderer core;

			private readonly LineRenderer halo;

			private readonly LineRenderer outerGlow;

			private readonly List<Vector3> points = new List<Vector3>(96);

			private Color tint;

			private float length;

			private float ended;

			private const float FadeSeconds = 0.2f;

			private readonly UnityAction<Collider, Vector3, Vector3> collisionListener;

			private UnityEventCollision? collisionEvent;

			private readonly MaterialPropertyBlock properties = new MaterialPropertyBlock();

			private RemotePlayback? playback;

			private int replayed;

			private Vector3 launchDirection;

			private bool customLaunch;

			private float launchDistance;

			public bool IsRemote { get; private set; }

			public uint NetworkId { get; set; }

			public float LastSentAge { get; set; }

			public float LastReceived { get; set; }

			public float Age => Math.Clamp(Time.time - Started, 0f, 14f);

			public Vector3 Head
			{
				get
				{
					//IL_0023: Unknown result type (might be due to invalid IL or missing references)
					//IL_000f: Unknown result type (might be due to invalid IL or missing references)
					if (points.Count <= 0)
					{
						return IncomingPoint;
					}
					List<Vector3> list = points;
					return list[list.Count - 1];
				}
			}

			public Vector3 LaunchOrigin { get; private set; }

			public bool IsRicochet { get; private set; }

			public bool ImpactChecked { get; set; }

			public Vector3 IncomingPoint { get; private set; }

			public Vector3 IncomingDirection { get; private set; }

			public float IncomingSpeed { get; private set; }

			public Projectile? Projectile { get; private set; }

			public int Id { get; private set; }

			public float Started { get; private set; }

			public bool Busy { get; private set; }

			public Trace(Material mat)
			{
				//IL_000e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0018: Expected O, but got Unknown
				//IL_003b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0045: Expected O, but got Unknown
				collisionListener = UnityAction<Collider, Vector3, Vector3>.op_Implicit((Action<Collider, Vector3, Vector3>)delegate(Collider collider, Vector3 _, Vector3 __)
				{
					TracerMod instance = Instance;
					if (instance == null)
					{
						return;
					}
					try
					{
						instance.OnImpact(this, collider);
					}
					catch (Exception e)
					{
						instance.Report(e);
					}
				});
				root = new GameObject("NeonTracers_Visual");
				Object.DontDestroyOnLoad((Object)(object)root);
				outerGlow = MakeLine("OuterGlow", mat);
				halo = MakeLine("Halo", mat);
				core = MakeLine("Core", mat);
				root.SetActive(false);
			}

			private LineRenderer MakeLine(string name, Material mat)
			{
				//IL_0001: Unknown result type (might be due to invalid IL or missing references)
				//IL_0006: Unknown result type (might be due to invalid IL or missing references)
				GameObject val = new GameObject(name);
				val.transform.SetParent(root.transform, false);
				LineRenderer obj = val.AddComponent<LineRenderer>();
				((Renderer)obj).sharedMaterial = mat;
				obj.useWorldSpace = true;
				obj.alignment = (LineAlignment)0;
				((Renderer)obj).shadowCastingMode = (ShadowCastingMode)0;
				((Renderer)obj).receiveShadows = false;
				obj.numCapVertices = 2;
				obj.positionCount = 0;
				return obj;
			}

			public void Start(Projectile p, VisualSettings settings, Vector3 muzzle)
			{
				//IL_005a: Unknown result type (might be due to invalid IL or missing references)
				//IL_0062: Unknown result type (might be due to invalid IL or missing references)
				//IL_0067: Unknown result type (might be due to invalid IL or missing references)
				//IL_0095: Unknown result type (might be due to invalid IL or missing references)
				DetachCollision();
				Projectile = p;
				Id = ((Object)p).GetInstanceID();
				Started = Time.time;
				IsRemote = false;
				NetworkId = 0u;
				LastSentAge = 0f;
				IsRicochet = false;
				ImpactChecked = false;
				playback = null;
				CaptureIncoming(p);
				LaunchOrigin = muzzle;
				launchDirection = IncomingDirection;
				Busy = true;
				ended = -1f;
				points.Clear();
				points.Add(((Component)p).transform.position);
				Configure(settings);
				LineRenderer obj = core;
				LineRenderer obj2 = halo;
				int num = (outerGlow.positionCount = 0);
				int positionCount = (obj2.positionCount = num);
				obj.positionCount = positionCount;
				root.SetActive(true);
				collisionEvent = p.onCollision;
				if (collisionEvent != null)
				{
					((UnityEvent<Collider, Vector3, Vector3>)(object)collisionEvent).AddListener(collisionListener);
				}
			}

			public void Configure(VisualSettings settings)
			{
				//IL_0047: Unknown result type (might be due to invalid IL or missing references)
				//IL_004c: Unknown result type (might be due to invalid IL or missing references)
				//IL_005e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0063: Unknown result type (might be due to invalid IL or missing references)
				//IL_0075: Unknown result type (might be due to invalid IL or missing references)
				//IL_007a: Unknown result type (might be due to invalid IL or missing references)
				//IL_0093: Unknown result type (might be due to invalid IL or missing references)
				//IL_0094: Unknown result type (might be due to invalid IL or missing references)
				//IL_0137: Unknown result type (might be due to invalid IL or missing references)
				//IL_008c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0091: Unknown result type (might be due to invalid IL or missing references)
				customLaunch = settings.CustomStart;
				launchDistance = settings.StartDistance;
				tint = (Color)(settings.Color switch
				{
					1 => new Color(1f, 0.025f, 0.005f), 
					2 => new Color(1f, 0.35f, 0.005f), 
					3 => new Color(0.005f, 0.8f, 1f), 
					_ => new Color(0.025f, 1f, 0.005f), 
				});
				length = settings.Length;
				LineRenderer obj = core;
				float startWidth = (core.endWidth = settings.Width);
				obj.startWidth = startWidth;
				LineRenderer obj2 = halo;
				startWidth = (halo.endWidth = settings.Width * 3.2f);
				obj2.startWidth = startWidth;
				LineRenderer obj3 = outerGlow;
				startWidth = (outerGlow.endWidth = settings.Width * 7f);
				obj3.startWidth = startWidth;
				properties.SetColor("_Color", new Color(settings.Brightness, settings.Brightness, settings.Brightness, 1f));
				((Renderer)core).SetPropertyBlock(properties);
				((Renderer)halo).SetPropertyBlock(properties);
				((Renderer)outerGlow).SetPropertyBlock(properties);
			}

			public void StartRemote(byte owner, uint id, VisualSettings settings, RemotePlayback track)
			{
				//IL_008c: Unknown result type (might be due to invalid IL or missing references)
				//IL_009e: Unknown result type (might be due to invalid IL or missing references)
				//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
				//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
				DetachCollision();
				Projectile = null;
				IsRemote = true;
				NetworkId = id;
				IsRicochet = false;
				ImpactChecked = true;
				Busy = true;
				ended = -1f;
				Started = Time.time + 0.1f;
				LastReceived = Time.unscaledTime;
				playback = track;
				replayed = -1;
				points.Clear();
				points.Add(ToUnity(track.Points[0].Position));
				LaunchOrigin = points[0];
				launchDirection = Vector3.zero;
				Configure(settings);
				LineRenderer obj = core;
				LineRenderer obj2 = halo;
				int num = (outerGlow.positionCount = 0);
				int positionCount = (obj2.positionCount = num);
				obj.positionCount = positionCount;
				root.SetActive(true);
			}

			public void CaptureIncoming(Projectile p)
			{
				//IL_0007: Unknown result type (might be due to invalid IL or missing references)
				//IL_0013: Unknown result type (might be due to invalid IL or missing references)
				//IL_0018: Unknown result type (might be due to invalid IL or missing references)
				//IL_0035: Unknown result type (might be due to invalid IL or missing references)
				//IL_003a: Unknown result type (might be due to invalid IL or missing references)
				//IL_003d: Unknown result type (might be due to invalid IL or missing references)
				//IL_002d: Unknown result type (might be due to invalid IL or missing references)
				IncomingPoint = ((Component)p).transform.position;
				Vector3 direction = p._direction;
				Vector3 incomingDirection;
				if (!(((Vector3)(ref direction)).sqrMagnitude > 1E-06f))
				{
					incomingDirection = ((Component)p).transform.forward;
				}
				else
				{
					direction = p._direction;
					incomingDirection = ((Vector3)(ref direction)).normalized;
				}
				IncomingDirection = incomingDirection;
				IncomingSpeed = Math.Abs(p.currentSpeed);
			}

			private void DetachCollision()
			{
				if (collisionEvent != null)
				{
					((UnityEvent<Collider, Vector3, Vector3>)(object)collisionEvent).RemoveListener(collisionListener);
				}
				collisionEvent = null;
			}

			public void StartRicochet(FlightPoint[] path)
			{
				//IL_003f: Unknown result type (might be due to invalid IL or missing references)
				DetachCollision();
				Projectile = null;
				IsRicochet = true;
				playback = new RemotePlayback();
				playback.Ricochet(path);
				replayed = -1;
				Add(ToUnity(path[0].Position));
			}

			public void AdvancePlayback()
			{
				//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
				//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
				if (playback == null || ended >= 0f || Time.time < Started)
				{
					return;
				}
				if (IsRemote && float.IsPositiveInfinity(playback.EndTime) && Time.unscaledTime - LastReceived > 5f)
				{
					Hide();
					return;
				}
				if (Time.time - Started > 15f)
				{
					Hide();
					return;
				}
				PlaybackFrame playbackFrame = playback.Evaluate(Age);
				if (playbackFrame.Hide)
				{
					Hide();
					return;
				}
				for (int i = replayed + 1; i <= playback.Cursor; i++)
				{
					Add(ToUnity(playback.Points[i].Position));
				}
				replayed = playback.Cursor;
				Add(ToUnity(playbackFrame.Position));
				IsRicochet = playbackFrame.Ricochet;
				if (playbackFrame.Fade)
				{
					Finish();
				}
			}

			public void Add(Vector3 p)
			{
				//IL_0000: Unknown result type (might be due to invalid IL or missing references)
				//IL_000f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0014: Unknown result type (might be due to invalid IL or missing references)
				//IL_0019: Unknown result type (might be due to invalid IL or missing references)
				//IL_002f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0053: Unknown result type (might be due to invalid IL or missing references)
				//IL_0061: Unknown result type (might be due to invalid IL or missing references)
				//IL_0080: Unknown result type (might be due to invalid IL or missing references)
				//IL_008e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0096: Unknown result type (might be due to invalid IL or missing references)
				List<Vector3> list = points;
				Vector3 val = p - list[list.Count - 1];
				if (((Vector3)(ref val)).sqrMagnitude < 1E-06f)
				{
					return;
				}
				points.Add(p);
				float num = length;
				for (int num2 = points.Count - 1; num2 > 0; num2--)
				{
					float num3 = Vector3.Distance(points[num2], points[num2 - 1]);
					if (num3 > num)
					{
						points[num2 - 1] = Vector3.Lerp(points[num2], points[num2 - 1], num / num3);
						if (num2 > 1)
						{
							points.RemoveRange(0, num2 - 1);
						}
						break;
					}
					num -= num3;
				}
				if (points.Count > 96)
				{
					points.RemoveRange(0, points.Count - 96);
				}
			}

			public void Finish()
			{
				if (!(ended >= 0f))
				{
					DetachCollision();
					IsRicochet = false;
					Projectile = null;
					ended = Time.time;
				}
			}

			public void Hide()
			{
				DetachCollision();
				Projectile = null;
				IsRicochet = false;
				Busy = false;
				root.SetActive(false);
			}

			public void Draw()
			{
				//IL_0053: Unknown result type (might be due to invalid IL or missing references)
				//IL_0058: Unknown result type (might be due to invalid IL or missing references)
				//IL_0062: Unknown result type (might be due to invalid IL or missing references)
				//IL_0067: Unknown result type (might be due to invalid IL or missing references)
				//IL_0071: 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)
				//IL_0086: Unknown result type (might be due to invalid IL or missing references)
				//IL_008b: Unknown result type (might be due to invalid IL or missing references)
				//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
				//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
				//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
				//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
				//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
				//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
				//IL_00db: Unknown result type (might be due to invalid IL or missing references)
				//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
				//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
				//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
				//IL_0108: Unknown result type (might be due to invalid IL or missing references)
				//IL_010e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0114: Unknown result type (might be due to invalid IL or missing references)
				//IL_011f: Unknown result type (might be due to invalid IL or missing references)
				//IL_012f: Unknown result type (might be due to invalid IL or missing references)
				//IL_01cf: Unknown result type (might be due to invalid IL or missing references)
				//IL_01da: Unknown result type (might be due to invalid IL or missing references)
				//IL_023e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0243: Unknown result type (might be due to invalid IL or missing references)
				//IL_0247: Unknown result type (might be due to invalid IL or missing references)
				//IL_024c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0255: Unknown result type (might be due to invalid IL or missing references)
				//IL_0263: Unknown result type (might be due to invalid IL or missing references)
				//IL_0271: Unknown result type (might be due to invalid IL or missing references)
				//IL_027f: Unknown result type (might be due to invalid IL or missing references)
				//IL_028d: Unknown result type (might be due to invalid IL or missing references)
				//IL_029b: Unknown result type (might be due to invalid IL or missing references)
				//IL_01a9: Unknown result type (might be due to invalid IL or missing references)
				//IL_01af: Unknown result type (might be due to invalid IL or missing references)
				//IL_01b4: Unknown result type (might be due to invalid IL or missing references)
				//IL_01b9: Unknown result type (might be due to invalid IL or missing references)
				//IL_02b8: Unknown result type (might be due to invalid IL or missing references)
				//IL_02d2: Unknown result type (might be due to invalid IL or missing references)
				//IL_02ec: Unknown result type (might be due to invalid IL or missing references)
				if (!Busy)
				{
					return;
				}
				float num = ((ended < 0f) ? 1f : (1f - (Time.time - ended) / 0.2f));
				if (num <= 0f)
				{
					Busy = false;
					root.SetActive(false);
					return;
				}
				Color val = Color.Lerp(tint, Color.white, 0.3f);
				val.a = num;
				Color val2 = tint;
				val2.a = num * 0.65f;
				Color val3 = tint;
				val3.a = num * 0.3f;
				core.startColor = new Color(val.r, val.g, val.b, num * 0.35f);
				core.endColor = val;
				halo.startColor = new Color(val2.r, val2.g, val2.b, 0f);
				halo.endColor = val2;
				outerGlow.startColor = new Color(val3.r, val3.g, val3.b, 0f);
				outerGlow.endColor = val3;
				LineRenderer obj = core;
				LineRenderer obj2 = halo;
				int num2 = (outerGlow.positionCount = points.Count);
				int positionCount = (obj2.positionCount = num2);
				obj.positionCount = positionCount;
				if (IsRemote && playback != null && playback.Points.Count > 1)
				{
					launchDirection = ToUnity(playback.Points[1].Position) - LaunchOrigin;
				}
				if (ended < 0f && LaunchStreak.TryGetSegment(ToNumerics(LaunchOrigin), ToNumerics(launchDirection), Time.time - Started, IsRicochet, customLaunch, launchDistance, out var tail, out var head))
				{
					LineRenderer obj3 = core;
					LineRenderer obj4 = halo;
					num2 = (outerGlow.positionCount = 2);
					positionCount = (obj4.positionCount = num2);
					obj3.positionCount = positionCount;
					Vector3 val4 = ToUnity(tail);
					Vector3 val5 = ToUnity(head);
					core.SetPosition(0, val4);
					halo.SetPosition(0, val4);
					outerGlow.SetPosition(0, val4);
					core.SetPosition(1, val5);
					halo.SetPosition(1, val5);
					outerGlow.SetPosition(1, val5);
				}
				else
				{
					for (int i = 0; i < points.Count; i++)
					{
						core.SetPosition(i, points[i]);
						halo.SetPosition(i, points[i]);
						outerGlow.SetPosition(i, points[i]);
					}
				}
			}

			public void Dispose()
			{
				DetachCollision();
				Object.Destroy((Object)(object)root);
			}
		}

		private readonly Dictionary<byte, PeerState> peers = new Dictionary<byte, PeerState>();

		private readonly Dictionary<(byte Owner, uint Id), Trace> remoteVisuals = new Dictionary<(byte, uint), Trace>();

		private uint nextVisualId;

		private bool loadingScene;

		private float nextSettingsReply;

		private MelonPreferences_Entry<bool> lastFive;

		private readonly Dictionary<int, (int Reserve, float Captured)> magazineSnapshots = new Dictionary<int, (int, float)>();

		internal static TracerMod? Instance;

		private MelonPreferences_Category prefs;

		private MelonPreferences_Entry<bool> enabled;

		private MelonPreferences_Entry<bool> everyRound;

		private MelonPreferences_Entry<bool> ricochets;

		private MelonPreferences_Entry<int> ricochetChance;

		private readonly Random visualRandom = new Random();

		private MelonPreferences_Entry<TracerColor> color;

		private MelonPreferences_Entry<TracerStyle> style;

		private MelonPreferences_Entry<float> brightness;

		private MelonPreferences_Entry<float> width;

		private MelonPreferences_Entry<bool> customStart;

		private MelonPreferences_Entry<float> startDistance;

		private MelonPreferences_Entry<int> every;

		private readonly List<Trace> traces = new List<Trace>();

		private readonly Dictionary<int, Trace> live = new Dictionary<int, Trace>();

		private Material? material;

		private Texture2D? glowTexture;

		private uint projectileCount;

		private bool warned;

		private const int MaxTraces = 256;

		internal VisualSettings OwnSettings => new VisualSettings(enabled.Value, everyRound.Value, ricochets.Value, (byte)color.Value, (byte)style.Value, brightness.Value, width.Value, (byte)every.Value, (byte)ricochetChance.Value, lastFive.Value, customStart.Value, startDistance.Value);

		private Trace? AllocateTrace()
		{
			CleanRemoteVisuals();
			if (!EnsureMaterial())
			{
				return null;
			}
			Trace trace = traces.Find((Trace t) => !t.Busy);
			if (trace == null && traces.Count < 256)
			{
				trace = new Trace(material);
				traces.Add(trace);
			}
			return trace;
		}

		private void ApplyLocalSettings()
		{
			foreach (Trace trace in traces)
			{
				if (trace.Busy && !trace.IsRemote)
				{
					trace.Configure(OwnSettings);
				}
			}
		}

		private void ClearLocal(bool ricochetsOnly = false)
		{
			foreach (Trace trace in traces)
			{
				if (trace.Busy && !trace.IsRemote && (!ricochetsOnly || trace.IsRicochet))
				{
					live.Remove(trace.Id);
					if (ricochetsOnly)
					{
						StopTrace(trace);
					}
					else
					{
						trace.Hide();
					}
				}
			}
			if (!ricochetsOnly)
			{
				FusionBridge.Queue(new VisualCommand(VisualOp.Clear));
			}
			projectileCount = 0u;
		}

		private void SpawnNetworkVisual(Trace t)
		{
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			if (FusionBridge.Connected)
			{
				if (++nextVisualId == 0)
				{
					nextVisualId++;
				}
				t.NetworkId = nextVisualId;
				Vector3 val = (customStart.Value ? t.LaunchOrigin : t.IncomingPoint);
				FusionBridge.Queue(new VisualCommand(VisualOp.Spawn, t.NetworkId, 0f, ToNumerics(val), OwnSettings));
				if (customStart.Value)
				{
					FusionBridge.Queue(new VisualCommand(VisualOp.Point, t.NetworkId, 0.0005f, ToNumerics(val + t.IncomingDirection * 0.22f)));
				}
			}
		}

		private void SendPoint(Trace t)
		{
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			if (t.NetworkId != 0 && !t.IsRemote && FusionBridge.Connected && !(t.Age - t.LastSentAge < 1f / 30f))
			{
				t.LastSentAge = t.Age;
				FusionBridge.Queue(new VisualCommand(VisualOp.Point, t.NetworkId, t.Age, ToNumerics(t.Head)));
			}
		}

		private void StopTrace(Trace t)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			if (t.NetworkId != 0 && !t.IsRemote)
			{
				FusionBridge.Queue(new VisualCommand(VisualOp.Stop, t.NetworkId, t.Age, ToNumerics(t.Head)));
			}
			t.Hide();
		}

		private void LaunchVisualRicochet(Trace trace, Vector3 point, Vector3 velocity, float lifetime, int mask)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: 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_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			Vector3 position = ToNumerics(point);
			float age = trace.Age;
			float num = 0f;
			List<FlightPoint> list = new List<FlightPoint>
			{
				new FlightPoint(age, position)
			};
			while (num < lifetime && list.Count < 96)
			{
				float num2 = Math.Min(0.02f, lifetime - num);
				Vector3 val = ToUnity(position);
				RicochetMath.Step(ref position, ref velocity, num2);
				Vector3 val2 = ToUnity(position) - val;
				num += num2;
				RaycastHit val3 = default(RaycastHit);
				int num3;
				if (((Vector3)(ref val2)).sqrMagnitude > 1E-06f)
				{
					num3 = (Physics.Raycast(val, ((Vector3)(ref val2)).normalized, ref val3, ((Vector3)(ref val2)).magnitude, mask, (QueryTriggerInteraction)1) ? 1 : 0);
					if (num3 != 0)
					{
						position = ToNumerics(((RaycastHit)(ref val3)).point);
					}
				}
				else
				{
					num3 = 0;
				}
				list.Add(new FlightPoint(age + num, position));
				if (num3 != 0)
				{
					break;
				}
			}
			FlightPoint[] array = list.ToArray();
			trace.StartRicochet(array);
			if (trace.NetworkId != 0)
			{
				uint networkId = trace.NetworkId;
				float time = array[0].Time;
				Vector3 position2 = array[0].Position;
				FlightPoint[] path = array;
				FusionBridge.Queue(new VisualCommand(VisualOp.Ricochet, networkId, time, position2, default(VisualSettings), path));
			}
		}

		internal void NetworkWarning(string message)
		{
			if (!warned)
			{
				warned = true;
				((MelonBase)this).LoggerInstance.Warning("Ignored tracer network data: " + message);
			}
		}

		internal void ResetNetworkSession()
		{
			Clear();
			peers.Clear();
			remoteVisuals.Clear();
			nextVisualId = 0u;
		}

		internal void ResetNetworkPeers()
		{
			foreach (Trace value in remoteVisuals.Values)
			{
				value.Hide();
			}
			remoteVisuals.Clear();
			peers.Clear();
		}

		internal void RemovePeer(byte owner)
		{
			RemovePeerVisuals(owner);
			peers.Remove(owner);
		}

		private void RemovePeerVisuals(byte owner)
		{
			(byte, uint)[] array = remoteVisuals.Keys.Where(((byte Owner, uint Id) k) => k.Owner == owner).ToArray();
			foreach ((byte, uint) key in array)
			{
				remoteVisuals[key].Hide();
				remoteVisuals.Remove(key);
			}
			if (peers.TryGetValue(owner, out PeerState value))
			{
				value.Tracks.Clear();
			}
		}

		internal void ReceiveVisuals(byte sender, VisualPacket packet)
		{
			if (loadingScene || packet.Scene != FusionBridge.Scene)
			{
				return;
			}
			if (!peers.TryGetValue(sender, out PeerState value))
			{
				value = new PeerState();
				peers.Add(sender, value);
			}
			if (value.Epoch != packet.Epoch)
			{
				RemovePeerVisuals(sender);
			}
			if (!value.Accept(packet))
			{
				return;
			}
			VisualCommand[] commands = packet.Commands;
			foreach (VisualCommand visualCommand in commands)
			{
				(byte, uint) key = (sender, visualCommand.Id);
				bool flag = value.Apply(visualCommand);
				if (remoteVisuals.TryGetValue(key, out Trace value2))
				{
					value2.LastReceived = Time.unscaledTime;
				}
				switch (visualCommand.Op)
				{
				case VisualOp.RequestSettings:
					if (Time.unscaledTime >= nextSettingsReply)
					{
						nextSettingsReply = Time.unscaledTime + 0.5f;
						FusionBridge.Announce();
					}
					break;
				case VisualOp.Settings:
					if (!visualCommand.Settings.Enabled)
					{
						RemovePeerVisuals(sender);
						break;
					}
					foreach (KeyValuePair<(byte, uint), Trace> remoteVisual in remoteVisuals)
					{
						if (remoteVisual.Key.Item1 == sender)
						{
							remoteVisual.Value.Configure(visualCommand.Settings);
						}
					}
					break;
				case VisualOp.Clear:
					RemovePeerVisuals(sender);
					break;
				case VisualOp.Spawn:
					if (flag)
					{
						Trace trace = AllocateTrace();
						if (trace == null)
						{
							value.Tracks.Remove(visualCommand.Id);
							break;
						}
						trace.StartRemote(sender, visualCommand.Id, visualCommand.Settings, value.Tracks[visualCommand.Id]);
						remoteVisuals[key] = trace;
					}
					break;
				}
			}
		}

		private void CleanRemoteVisuals()
		{
			(byte, uint)[] array = (from p in remoteVisuals
				where !p.Value.Busy
				select p.Key).ToArray();
			for (int num = 0; num < array.Length; num++)
			{
				(byte, uint) key = array[num];
				remoteVisuals.Remove(key);
				if (peers.TryGetValue(key.Item1, out PeerState value))
				{
					value.Tracks.Remove(key.Item2);
				}
			}
		}

		private void RefreshPlayerSettings(Page page)
		{
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_012c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0196: Unknown result type (might be due to invalid IL or missing references)
			//IL_0239: Unknown result type (might be due to invalid IL or missing references)
			//IL_0297: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ed: Unknown result type (might be due to invalid IL or missing references)
			foreach (Page indexPage in page.IndexPages)
			{
				indexPage.RemoveAll();
			}
			page.RemoveAll();
			page.CreateFunction("Refresh player settings", Color.cyan, (Action)delegate
			{
				RefreshPlayerSettings(page);
			});
			page.CreateFunction(FusionBridge.Connected ? "Fusion connected" : "Solo (Fusion installed)", Color.white, (Action)null);
			foreach (KeyValuePair<byte, PeerState> item in peers.OrderBy<KeyValuePair<byte, PeerState>, byte>((KeyValuePair<byte, PeerState> p) => p.Key))
			{
				VisualSettings? settings = item.Value.Settings;
				if (settings.HasValue)
				{
					VisualSettings valueOrDefault = settings.GetValueOrDefault();
					string text = FusionBridge.PlayerName(item.Key);
					page.CreateFunction(text + ": " + (valueOrDefault.Enabled ? "Tracers on" : "No tracers"), Color.green, (Action)null);
					page.CreateFunction($"{valueOrDefault.Color} / {valueOrDefault.Style} / brightness {valueOrDefault.Brightness:0}", Color.white, (Action)null);
					string value = (valueOrDefault.LastFive ? "last 5 rounds" : (valueOrDefault.EveryRound ? "every round" : $"every {valueOrDefault.EveryN}"));
					page.CreateFunction($"Width {valueOrDefault.Width:0.000} / {value}", Color.white, (Action)null);
					page.CreateFunction(valueOrDefault.CustomStart ? $"Start distance: {valueOrDefault.StartDistance:0.##} m" : "Start distance: Original", Color.white, (Action)null);
					page.CreateFunction("Ricochets: " + (valueOrDefault.Ricochets ? $"{valueOrDefault.Chance}%" : "Off"), Color.white, (Action)null);
				}
			}
		}

		private void CaptureMagazine(Gun gun)
		{
			if (!lastFive.Value || !enabled.Value || !Object.op_Implicit((Object)(object)gun) || gun._magState == null)
			{
				return;
			}
			int instanceID = ((Object)gun).GetInstanceID();
			if (!magazineSnapshots.ContainsKey(instanceID) && magazineSnapshots.Count >= 256)
			{
				magazineSnapshots.Remove(magazineSnapshots.MinBy((KeyValuePair<int, (int Reserve, float Captured)> p) => p.Value.Captured).Key);
			}
			magazineSnapshots[instanceID] = (gun._magState.AmmoCount, Time.time);
		}

		private int? ReadMagazineSnapshot(Gun? gun)
		{
			if ((Object)(object)gun == (Object)null || !Object.op_Implicit((Object)(object)gun) || !magazineSnapshots.TryGetValue(((Object)gun).GetInstanceID(), out (int, float) value) || Time.time - value.Item2 > 2f)
			{
				return null;
			}
			return value.Item1;
		}

		public override void OnInitializeMelon()
		{
			//IL_030b: Unknown result type (might be due to invalid IL or missing references)
			//IL_031d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0348: Unknown result type (might be due to invalid IL or missing references)
			//IL_0370: Unknown result type (might be due to invalid IL or missing references)
			//IL_0398: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_0419: Unknown result type (might be due to invalid IL or missing references)
			//IL_0446: Unknown result type (might be due to invalid IL or missing references)
			//IL_047d: Unknown result type (might be due to invalid IL or missing references)
			//IL_04b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_04dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0513: Unknown result type (might be due to invalid IL or missing references)
			//IL_053e: Unknown result type (might be due to invalid IL or missing references)
			//IL_05b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_05c6: Expected O, but got Unknown
			//IL_05f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0608: Unknown result type (might be due to invalid IL or missing references)
			//IL_0615: Expected O, but got Unknown
			//IL_0615: Expected O, but got Unknown
			Instance = this;
			prefs = MelonPreferences.CreateCategory("NeonTracers");
			enabled = prefs.CreateEntry<bool>("Enabled", true, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			color = prefs.CreateEntry<TracerColor>("Color", TracerColor.Green, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			style = prefs.CreateEntry<TracerStyle>("Style", TracerStyle.Standard, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			brightness = prefs.CreateEntry<float>("Brightness", 12f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			width = prefs.CreateEntry<float>("Width", 0.03f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			customStart = prefs.CreateEntry<bool>("CustomStartDistance", false, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			startDistance = prefs.CreateEntry<float>("StartDistanceMeters", 1.5f, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			startDistance.Value = LaunchStreak.NormalizeDistance(startDistance.Value);
			every = prefs.CreateEntry<int>("EveryNProjectiles", 1, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			if (!Enum.IsDefined(typeof(TracerColor), color.Value))
			{
				color.Value = TracerColor.Green;
			}
			if (!Enum.IsDefined(typeof(TracerStyle), style.Value))
			{
				style.Value = TracerStyle.Standard;
			}
			brightness.Value = ValidFloat(brightness.Value, 12f, 1f, 50f);
			width.Value = ValidFloat(width.Value, 0.03f, 0.004f, 0.1f);
			MelonPreferences_Entry<int> val = prefs.CreateEntry<int>("VisualRevision", 0, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			bool num = val.Value < 1;
			if (num)
			{
				brightness.Value = Math.Max(brightness.Value, 12f);
				width.Value = Math.Max(width.Value, 0.03f);
				val.Value = 1;
			}
			every.Value = Math.Clamp(every.Value, 1, 10);
			everyRound = prefs.CreateEntry<bool>("EveryRoundIsTracer", every.Value == 1, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			lastFive = prefs.CreateEntry<bool>("LastFiveRoundsOnly", false, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			ricochets = prefs.CreateEntry<bool>("VisualRicochets", true, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			ricochetChance = prefs.CreateEntry<int>("RicochetChancePercent", 25, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			ricochetChance.Value = RicochetMath.NormalizeChance(ricochetChance.Value);
			if (num)
			{
				Save();
			}
			Page obj = Page.Root.CreatePage("Neon Tracers", Color.green, 0, true);
			obj.CreateBool("No tracers", Color.white, !enabled.Value, (Action<bool>)delegate(bool v)
			{
				enabled.Value = !v;
				if (v)
				{
					ClearLocal();
				}
				Save();
			});
			obj.CreateBool("Every round is a tracer", Color.green, everyRound.Value, (Action<bool>)delegate(bool v)
			{
				everyRound.Value = v;
				projectileCount = 0u;
				Save();
			});
			obj.CreateBool("Last 5 rounds in magazine", Color.green, lastFive.Value, (Action<bool>)delegate(bool v)
			{
				lastFive.Value = v;
				projectileCount = 0u;
				magazineSnapshots.Clear();
				Save();
			});
			obj.CreateBool("Visual ricochets", Color.green, ricochets.Value, (Action<bool>)delegate(bool v)
			{
				ricochets.Value = v;
				if (!v)
				{
					ClearLocal(ricochetsOnly: true);
				}
				Save();
			});
			obj.CreateInt("Ricochet chance (%)", Color.green, ricochetChance.Value, 5, 0, 100, (Action<int>)delegate(int v)
			{
				ricochetChance.Value = RicochetMath.NormalizeChance(v);
				Save();
			});
			obj.CreateEnum("Color", Color.white, (Enum)color.Value, (Action<Enum>)delegate(Enum v)
			{
				color.Value = (TracerColor)(object)v;
				Save();
			});
			obj.CreateEnum("Style", Color.white, (Enum)style.Value, (Action<Enum>)delegate(Enum v)
			{
				style.Value = (TracerStyle)(object)v;
				Save();
			});
			obj.CreateFloat("Brightness", Color.white, brightness.Value, 1f, 1f, 50f, (Action<float>)delegate(float v)
			{
				brightness.Value = v;
				Save();
			});
			obj.CreateFloat("Width (meters)", Color.white, width.Value, 0.002f, 0.004f, 0.1f, (Action<float>)delegate(float v)
			{
				width.Value = v;
				Save();
			});
			obj.CreateBool("Custom start distance", Color.white, customStart.Value, (Action<bool>)delegate(bool v)
			{
				customStart.Value = v;
				Save();
			});
			obj.CreateFloat("Tracer start distance (m)", Color.white, startDistance.Value, 0.25f, 0f, 10f, (Action<float>)delegate(float v)
			{
				startDistance.Value = LaunchStreak.NormalizeDistance(v);
				Save();
			});
			obj.CreateInt("Every N projectiles", Color.white, every.Value, 1, 1, 10, (Action<int>)delegate(int v)
			{
				every.Value = v;
				projectileCount = 0u;
				Save();
			});
			Page page = obj.CreatePage("Fusion player settings", Color.cyan, 12, true);
			RefreshPlayerSettings(page);
			FusionBridge.Initialize(this);
			Hooking.OnLevelLoading += OnLevelLoading;
			Hooking.OnLevelLoaded += OnLevelLoaded;
			Hooking.OnPreFireGun += CaptureMagazine;
			((MelonBase)this).HarmonyInstance.Patch((MethodBase)AccessTools.Method(typeof(Projectile), "SetBulletObject", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(TracerMod), "SpawnPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			((MelonBase)this).HarmonyInstance.Patch((MethodBase)AccessTools.Method(typeof(Projectile), "Update", (Type[])null, (Type[])null), new HarmonyMethod(typeof(TracerMod), "UpdatePrefix", (Type[])null), new HarmonyMethod(typeof(TracerMod), "UpdatePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			((MelonBase)this).LoggerInstance.Msg("Ready: BoneMenu > Neon Tracers. Fusion per-player sync registered.");
		}

		private static float ValidFloat(float v, float fallback, float min, float max)
		{
			if (!float.IsFinite(v))
			{
				return fallback;
			}
			return Math.Clamp(v, min, max);
		}

		private void Save()
		{
			prefs.SaveToFile(false);
			ApplyLocalSettings();
			FusionBridge.Announce();
		}

		private void OnLevelLoading(LevelInfo _)
		{
			loadingScene = true;
			Clear();
			FusionBridge.SceneChanged();
		}

		private void OnLevelLoaded(LevelInfo _)
		{
			loadingScene = false;
			FusionBridge.SceneChanged();
		}

		private static void SpawnPostfix(Projectile __instance, Transform __1, Rigidbody __4, TriggerRefProxy __5)
		{
			if (Instance == null)
			{
				return;
			}
			try
			{
				Instance.Begin(__instance, FusionBridge.IsLocalShot(__1, __4, __5), FusionBridge.FindSourceGun(__1, __4));
			}
			catch (Exception e)
			{
				Instance.Report(e);
			}
		}

		private static void UpdatePostfix(Projectile __instance)
		{
			if (Instance == null)
			{
				return;
			}
			try
			{
				Instance.Sample(__instance);
			}
			catch (Exception e)
			{
				Instance.Report(e);
			}
		}

		private static void UpdatePrefix(Projectile __instance)
		{
			if (Instance == null)
			{
				return;
			}
			try
			{
				if (Instance.live.TryGetValue(((Object)__instance).GetInstanceID(), out Trace value))
				{
					value.CaptureIncoming(__instance);
				}
			}
			catch (Exception e)
			{
				Instance.Report(e);
			}
		}

		private void OnImpact(Trace trace, Collider collider)
		{
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_012b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0139: Unknown result type (might be due to invalid IL or missing references)
			//IL_0140: Unknown result type (might be due to invalid IL or missing references)
			//IL_014a: Unknown result type (might be due to invalid IL or missing references)
			//IL_014f: Unknown result type (might be due to invalid IL or missing references)
			if (!enabled.Value || !trace.Busy || trace.IsRicochet || trace.ImpactChecked || (Object)(object)trace.Projectile == (Object)null)
			{
				return;
			}
			trace.ImpactChecked = true;
			try
			{
				if (ricochets.Value && Object.op_Implicit((Object)(object)collider) && RicochetMath.ShouldBounce(visualRandom.NextDouble(), ricochetChance.Value))
				{
					Ray val = default(Ray);
					((Ray)(ref val))..ctor(trace.IncomingPoint, trace.IncomingDirection);
					float num = Math.Max(5f, trace.IncomingSpeed * Math.Max(Time.deltaTime, 0.001f) * 2f + 1f);
					RaycastHit val2 = default(RaycastHit);
					if (collider.Raycast(val, ref val2, num))
					{
						Vector3 vector = RicochetMath.LaunchDirection(ToNumerics(trace.IncomingDirection), ToNumerics(((RaycastHit)(ref val2)).normal), Range(0.6f, 1.3f), Range(-0.3f, 0.3f));
						LayerMask collisionLayers = trace.Projectile.collisionLayers;
						int value = ((LayerMask)(ref collisionLayers)).value;
						live.Remove(trace.Id);
						trace.Add(((RaycastHit)(ref val2)).point);
						LaunchVisualRicochet(trace, ((RaycastHit)(ref val2)).point + ((RaycastHit)(ref val2)).normal * 0.025f, vector * Range(55f, 110f), Range(1.1f, 1.8f), value);
					}
				}
			}
			finally
			{
				if (!trace.IsRicochet)
				{
					live.Remove(trace.Id);
					StopTrace(trace);
				}
			}
		}

		private float Range(float min, float max)
		{
			return min + (max - min) * (float)visualRandom.NextDouble();
		}

		private static Vector3 ToNumerics(Vector3 v)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			return new Vector3(v.x, v.y, v.z);
		}

		private static Vector3 ToUnity(Vector3 v)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			return new Vector3(v.X, v.Y, v.Z);
		}

		private void Report(Exception e)
		{
			if (!warned)
			{
				warned = true;
				((MelonBase)this).LoggerInstance.Warning($"Tracer effect error (further errors suppressed): {e}");
			}
		}

		private void Begin(Projectile p, bool ownedLocally, Gun? sourceGun)
		{
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			int instanceID = ((Object)p).GetInstanceID();
			if (live.Remove(instanceID, out Trace value))
			{
				StopTrace(value);
			}
			if (loadingScene || !ownedLocally || !enabled.Value)
			{
				return;
			}
			uint projectileIndex = projectileCount;
			if (!lastFive.Value && !everyRound.Value)
			{
				projectileCount++;
			}
			if (TracerSelection.ShouldTrace(enabled: true, lastFive.Value, everyRound.Value, every.Value, lastFive.Value ? ReadMagazineSnapshot(sourceGun) : ((int?)null), projectileIndex))
			{
				Trace trace = AllocateTrace();
				if (trace != null)
				{
					Vector3 muzzle = (((Object)(object)sourceGun != (Object)null && Object.op_Implicit((Object)(object)sourceGun) && Object.op_Implicit((Object)(object)sourceGun.firePointTransform)) ? sourceGun.firePointTransform.position : ((Component)p).transform.position);
					trace.Start(p, OwnSettings, muzzle);
					live[instanceID] = trace;
					SpawnNetworkVisual(trace);
				}
			}
		}

		private bool EnsureMaterial()
		{
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Expected O, but got Unknown
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Expected O, but got Unknown
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)material != (Object)null)
			{
				return true;
			}
			Shader val = Shader.Find("Sprites/Default");
			if ((Object)(object)val == (Object)null)
			{
				if (!warned)
				{
					((MelonBase)this).LoggerInstance.Warning("Sprites/Default shader unavailable; tracers cannot render.");
				}
				warned = true;
				return false;
			}
			material = new Material(val)
			{
				name = "NeonTracers_Unlit"
			};
			glowTexture = new Texture2D(2, 64, (TextureFormat)4, false, true)
			{
				name = "NeonTracers_SoftGlow",
				wrapMode = (TextureWrapMode)1,
				filterMode = (FilterMode)1
			};
			Color[] array = (Color[])(object)new Color[128];
			for (int i = 0; i < 64; i++)
			{
				float num = Math.Abs((float)i / 63f * 2f - 1f);
				float num2 = MathF.Pow(1f - num * num, 3f);
				array[i * 2] = (array[i * 2 + 1] = new Color(1f, 1f, 1f, num2));
			}
			glowTexture.SetPixels(Il2CppStructArray<Color>.op_Implicit(array));
			glowTexture.Apply(false, true);
			material.mainTexture = (Texture)(object)glowTexture;
			return true;
		}

		private void Sample(Projectile p)
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			if (live.TryGetValue(((Object)p).GetInstanceID(), out Trace value))
			{
				value.Add(((Component)p).transform.position);
				if (p.isFinished || !((Component)p).gameObject.activeInHierarchy)
				{
					live.Remove(((Object)p).GetInstanceID());
					StopTrace(value);
				}
				else
				{
					SendPoint(value);
				}
			}
		}

		public override void OnLateUpdate()
		{
			FusionBridge.Tick();
			foreach (Trace trace in traces)
			{
				if (trace.Busy)
				{
					trace.AdvancePlayback();
					if ((Object)(object)trace.Projectile != (Object)null && (!Object.op_Implicit((Object)(object)trace.Projectile) || !((Component)trace.Projectile).gameObject.activeInHierarchy || Time.time - trace.Started > 10f))
					{
						live.Remove(trace.Id);
						StopTrace(trace);
					}
					trace.Draw();
				}
			}
			CleanRemoteVisuals();
		}

		private void Clear()
		{
			magazineSnapshots.Clear();
			live.Clear();
			remoteVisuals.Clear();
			foreach (Trace trace in traces)
			{
				trace.Dispose();
			}
			traces.Clear();
			if ((Object)(object)material != (Object)null)
			{
				Object.Destroy((Object)(object)material);
			}
			material = null;
			if ((Object)(object)glowTexture != (Object)null)
			{
				Object.Destroy((Object)(object)glowTexture);
			}
			glowTexture = null;
			projectileCount = 0u;
		}

		public override void OnDeinitializeMelon()
		{
			Hooking.OnLevelLoading -= OnLevelLoading;
			Hooking.OnLevelLoaded -= OnLevelLoaded;
			Hooking.OnPreFireGun -= CaptureMagazine;
			FusionBridge.Dispose();
			((MelonBase)this).HarmonyInstance.UnpatchSelf();
			Clear();
			Instance = null;
		}
	}
	internal static class LaunchStreak
	{
		public const float Duration = 0.012f;

		public const float Length = 0.22f;

		public const float SeedTime = 0.0005f;

		public static float NormalizeDistance(float value)
		{
			if (!float.IsFinite(value))
			{
				return 1.5f;
			}
			return Math.Clamp(value, 0f, 10f);
		}

		public static bool TryGetSegment(Vector3 muzzle, Vector3 direction, float age, bool ricochet, bool customStart, float startDistance, out Vector3 tail, out Vector3 head)
		{
			tail = (head = muzzle);
			bool flag = !customStart || ricochet || !float.IsFinite(startDistance);
			if (!flag)
			{
				bool flag2 = ((startDistance < 0f || startDistance > 10f) ? true : false);
				flag = flag2;
			}
			if (flag || !float.IsFinite(age) || age < 0f || age >= 0.012f || !float.IsFinite(direction.LengthSquared()) || direction.LengthSquared() < 1E-06f)
			{
				return false;
			}
			direction = Vector3.Normalize(direction);
			tail = muzzle + direction * (startDistance + 0.24f * age / 0.012f);
			head = tail + direction * 0.22f;
			return true;
		}
	}
	public enum TracerColor
	{
		Green,
		Red,
		Amber,
		Cyan
	}
	public enum TracerStyle
	{
		Short,
		Standard,
		Long
	}
	internal static class RicochetMath
	{
		public static int NormalizeChance(int percent)
		{
			return (Math.Clamp(percent, 0, 100) + 2) / 5 * 5;
		}

		public static bool ShouldBounce(double roll, int percent = 25)
		{
			if (roll >= 0.0 && roll < 1.0)
			{
				return roll < (double)NormalizeChance(percent) / 100.0;
			}
			return false;
		}

		public static Vector3 LaunchDirection(Vector3 incoming, Vector3 normal, float lift, float scatter)
		{
			normal = Vector3.Normalize(normal);
			incoming = ((incoming.LengthSquared() > 1E-06f) ? Vector3.Normalize(incoming) : (-normal));
			Vector3 vector = Vector3.Reflect(incoming, normal);
			Vector3 vector2 = Vector3.Cross(normal, vector);
			if (vector2.LengthSquared() > 1E-06f)
			{
				vector2 = Vector3.Normalize(vector2);
			}
			Vector3 vector3 = vector + Vector3.UnitY * lift + vector2 * scatter;
			if (normal.Y >= 0f)
			{
				vector3.Y = Math.Max(vector3.Y, 0.25f);
			}
			float num = Vector3.Dot(vector3, normal);
			if (num < 0.2f)
			{
				vector3 += normal * (0.2f - num);
			}
			return Vector3.Normalize(vector3);
		}

		public static void Step(ref Vector3 position, ref Vector3 velocity, float dt)
		{
			Vector3 vector = new Vector3(0f, -9.81f, 0f);
			position += velocity * dt + vector * (0.5f * dt * dt);
			velocity = (velocity + vector * dt) * MathF.Exp(-0.3f * dt);
		}
	}
	internal static class TracerSelection
	{
		public static bool ShouldTrace(bool enabled, bool lastFive, bool everyRound, int interval, int? magazineReserve, uint projectileIndex)
		{
			if (!enabled)
			{
				return false;
			}
			if (lastFive)
			{
				if (magazineReserve.HasValue)
				{
					int valueOrDefault = magazineReserve.GetValueOrDefault();
					if (valueOrDefault >= 0)
					{
						return valueOrDefault < 5;
					}
				}
				return false;
			}
			if (!everyRound)
			{
				return projectileIndex % (uint)Math.Max(1, interval) == 0;
			}
			return true;
		}
	}
	internal readonly record struct VisualSettings(bool Enabled, bool EveryRound, bool Ricochets, byte Color, byte Style, float Brightness, float Width, byte EveryN, byte Chance, bool LastFive = false, bool CustomStart = false, float StartDistance = 1.5f)
	{
		public bool Valid
		{
			get
			{
				if (Color <= 3 && Style <= 2 && float.IsFinite(Brightness))
				{
					float brightness = Brightness;
					if (brightness >= 1f && brightness <= 50f && float.IsFinite(Width))
					{
						brightness = Width;
						if (brightness >= 0.004f && brightness <= 0.1f)
						{
							byte everyN = EveryN;
							if (everyN >= 1 && everyN <= 10 && Chance <= 100 && Chance % 5 == 0 && float.IsFinite(StartDistance))
							{
								brightness = StartDistance;
								if (brightness >= 0f)
								{
									return brightness <= 10f;
								}
								return false;
							}
						}
					}
				}
				return false;
			}
		}

		public float Length => Style switch
		{
			0 => 0.7f, 
			2 => 5f, 
			_ => 2f, 
		};
	}
	internal enum VisualOp : byte
	{
		RequestSettings,
		Settings,
		Spawn,
		Point,
		Stop,
		Ricochet,
		Clear
	}
	internal readonly record struct FlightPoint(float Time, Vector3 Position);
	internal sealed record VisualCommand(VisualOp Op, uint Id = 0u, float Time = 0f, Vector3 Position = default(Vector3), VisualSettings Settings = default(VisualSettings), FlightPoint[]? Path = null);
	internal sealed record VisualPacket(Guid Epoch, ulong Sequence, VisualCommand[] Commands, string Scene = "");
	internal static class VisualProtocol
	{
		public const int MaxBytes = 12000;

		public const int MaxCommands = 64;

		public const int MaxPath = 96;

		public static byte[] Encode(VisualPacket packet)
		{
			using MemoryStream memoryStream = new MemoryStream();
			using BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
			binaryWriter.Write((byte)3);
			binaryWriter.Write(packet.Epoch.ToByteArray());
			binaryWriter.Write(packet.Sequence);
			byte[] bytes = Encoding.UTF8.GetBytes(packet.Scene);
			if (bytes.Length > 512)
			{
				throw new InvalidDataException("Scene identifier too long.");
			}
			binaryWriter.Write((ushort)bytes.Length);
			binaryWriter.Write(bytes);
			if (packet.Commands.Length > 64)
			{
				throw new InvalidDataException("Too many commands.");
			}
			binaryWriter.Write((byte)packet.Commands.Length);
			VisualCommand[] commands = packet.Commands;
			foreach (VisualCommand visualCommand in commands)
			{
				binaryWriter.Write((byte)visualCommand.Op);
				VisualOp op = visualCommand.Op;
				if (op - 1 <= VisualOp.Settings)
				{
					WriteSettings(binaryWriter, visualCommand.Settings);
				}
				op = visualCommand.Op;
				if (op - 2 <= VisualOp.Point)
				{
					binaryWriter.Write(visualCommand.Id);
					WritePoint(binaryWriter, new FlightPoint(visualCommand.Time, visualCommand.Position));
				}
				if (visualCommand.Op == VisualOp.Ricochet)
				{
					FlightPoint[] array = visualCommand.Path ?? throw new InvalidDataException("Missing flight.");
					int num = array.Length;
					if ((num < 2 || num > 96) ? true : false)
					{
						throw new InvalidDataException("Invalid flight length.");
					}
					binaryWriter.Write((byte)array.Length);
					FlightPoint[] array2 = array;
					foreach (FlightPoint p in array2)
					{
						WritePoint(binaryWriter, p);
					}
				}
			}
			if (memoryStream.Length > 12000)
			{
				throw new InvalidDataException("Packet too large.");
			}
			return memoryStream.ToArray();
		}

		public static VisualPacket Decode(byte[] bytes)
		{
			if (bytes.Length > 12000)
			{
				throw new InvalidDataException("Packet too large.");
			}
			using MemoryStream memoryStream = new MemoryStream(bytes, writable: false);
			using BinaryReader binaryReader = new BinaryReader(memoryStream);
			if (binaryReader.ReadByte() != 3)
			{
				throw new InvalidDataException("Unsupported protocol; all players need Neon Tracers 0.3.3.");
			}
			Guid epoch = new Guid(binaryReader.ReadBytes(16));
			ulong sequence = binaryReader.ReadUInt64();
			int num = binaryReader.ReadUInt16();
			if (num > 512)
			{
				throw new InvalidDataException("Scene identifier too long.");
			}
			byte[] array = binaryReader.ReadBytes(num);
			if (array.Length != num)
			{
				throw new EndOfStreamException();
			}
			string scene = Encoding.UTF8.GetString(array);
			int num2 = binaryReader.ReadByte();
			if (num2 > 64)
			{
				throw new InvalidDataException("Too many commands.");
			}
			VisualCommand[] array2 = new VisualCommand[num2];
			for (int i = 0; i < num2; i++)
			{
				VisualOp visualOp = (VisualOp)binaryReader.ReadByte();
				if ((int)visualOp > 6)
				{
					throw new InvalidDataException("Unknown operation.");
				}
				bool flag = visualOp - 1 <= VisualOp.Settings;
				VisualSettings settings = (flag ? ReadSettings(binaryReader) : default(VisualSettings));
				uint num3 = 0u;
				FlightPoint flightPoint = default(FlightPoint);
				if (visualOp - 2 <= VisualOp.Point)
				{
					num3 = binaryReader.ReadUInt32();
					flightPoint = ReadPoint(binaryReader);
					if (num3 == 0)
					{
						throw new InvalidDataException("Invalid visual ID.");
					}
				}
				FlightPoint[] array3 = null;
				if (visualOp == VisualOp.Ricochet)
				{
					int num4 = binaryReader.ReadByte();
					if ((num4 < 2 || num4 > 96) ? true : false)
					{
						throw new InvalidDataException("Invalid flight length.");
					}
					array3 = new FlightPoint[num4];
					float num5 = flightPoint.Time - 1E-05f;
					for (int j = 0; j < num4; j++)
					{
						array3[j] = ReadPoint(binaryReader);
						if (array3[j].Time <= num5)
						{
							throw new InvalidDataException("Unordered flight.");
						}
						num5 = array3[j].Time;
					}
				}
				array2[i] = new VisualCommand(visualOp, num3, flightPoint.Time, flightPoint.Position, settings, array3);
			}
			if (memoryStream.Position != memoryStream.Length)
			{
				throw new InvalidDataException("Trailing data.");
			}
			return new VisualPacket(epoch, sequence, array2, scene);
		}

		private static bool ReadBool(BinaryReader r)
		{
			return r.ReadByte() switch
			{
				0 => false, 
				1 => true, 
				_ => throw new InvalidDataException("Invalid boolean."), 
			};
		}

		private static void WriteSettings(BinaryWriter w, VisualSettings s)
		{
			if (!s.Valid)
			{
				throw new InvalidDataException("Invalid settings.");
			}
			w.Write(s.Enabled);
			w.Write(s.EveryRound);
			w.Write(s.Ricochets);
			w.Write(s.Color);
			w.Write(s.Style);
			w.Write(s.Brightness);
			w.Write(s.Width);
			w.Write(s.EveryN);
			w.Write(s.Chance);
			w.Write(s.LastFive);
			w.Write(s.CustomStart);
			w.Write(s.StartDistance);
		}

		private static VisualSettings ReadSettings(BinaryReader r)
		{
			VisualSettings result = new VisualSettings(ReadBool(r), ReadBool(r), ReadBool(r), r.ReadByte(), r.ReadByte(), r.ReadSingle(), r.ReadSingle(), r.ReadByte(), r.ReadByte(), ReadBool(r), ReadBool(r), r.ReadSingle());
			if (!result.Valid)
			{
				throw new InvalidDataException("Invalid settings.");
			}
			return result;
		}

		private static void WritePoint(BinaryWriter w, FlightPoint p)
		{
			ValidatePoint(p);
			w.Write(p.Time);
			w.Write(p.Position.X);
			w.Write(p.Position.Y);
			w.Write(p.Position.Z);
		}

		private static FlightPoint ReadPoint(BinaryReader r)
		{
			FlightPoint flightPoint = new FlightPoint(r.ReadSingle(), new Vector3(r.ReadSingle(), r.ReadSingle(), r.ReadSingle()));
			ValidatePoint(flightPoint);
			return flightPoint;
		}

		private static void ValidatePoint(FlightPoint p)
		{
			bool flag = !float.IsFinite(p.Time);
			if (!flag)
			{
				float time = p.Time;
				bool flag2 = ((time < 0f || time > 15f) ? true : false);
				flag = flag2;
			}
			if (flag || !float.IsFinite(p.Position.X) || !float.IsFinite(p.Position.Y) || !float.IsFinite(p.Position.Z) || p.Position.LengthSquared() > 1E+12f)
			{
				throw new InvalidDataException("Invalid visual position or time.");
			}
		}
	}
	internal sealed class PeerState
	{
		public Guid Epoch;

		public ulong LastSequence;

		public VisualSettings? Settings;

		public readonly Dictionary<uint, RemotePlayback> Tracks = new Dictionary<uint, RemotePlayback>();

		public bool Accept(VisualPacket packet)
		{
			if (Epoch != packet.Epoch)
			{
				Epoch = packet.Epoch;
				LastSequence = 0uL;
				Tracks.Clear();
				Settings = null;
			}
			if (packet.Sequence <= LastSequence)
			{
				return false;
			}
			LastSequence = packet.Sequence;
			return true;
		}

		public bool Apply(VisualCommand command)
		{
			switch (command.Op)
			{
			case VisualOp.Settings:
				Settings = command.Settings;
				if (!command.Settings.Enabled)
				{
					Tracks.Clear();
				}
				break;
			case VisualOp.Clear:
				Tracks.Clear();
				break;
			case VisualOp.Spawn:
			{
				if (!command.Settings.Enabled || Tracks.ContainsKey(command.Id) || Tracks.Count >= 64)
				{
					return false;
				}
				RemotePlayback remotePlayback = new RemotePlayback();
				remotePlayback.Add(new FlightPoint(command.Time, command.Position));
				Tracks.Add(command.Id, remotePlayback);
				return true;
			}
			case VisualOp.Point:
			{
				if (Tracks.TryGetValue(command.Id, out RemotePlayback value3))
				{
					value3.Add(new FlightPoint(command.Time, command.Position));
				}
				break;
			}
			case VisualOp.Stop:
			{
				if (Tracks.TryGetValue(command.Id, out RemotePlayback value2))
				{
					value2.Stop(new FlightPoint(command.Time, command.Position));
				}
				break;
			}
			case VisualOp.Ricochet:
			{
				if (Tracks.TryGetValue(command.Id, out RemotePlayback value))
				{
					value.Ricochet(command.Path);
				}
				break;
			}
			}
			return false;
		}
	}
	internal readonly record struct PlaybackFrame(Vector3 Position, bool Hide, bool Fade, bool Ricochet);
	internal sealed class RemotePlayback
	{
		public readonly List<FlightPoint> Points = new List<FlightPoint>();

		private float bounceTime = float.PositiveInfinity;

		private bool fade;

		public int Cursor { get; private set; }

		public float EndTime { get; private set; } = float.PositiveInfinity;

		public void Add(FlightPoint point)
		{
			if (Points.Count > 0)
			{
				float time = point.Time;
				List<FlightPoint> points = Points;
				if (time < points[points.Count - 1].Time)
				{
					return;
				}
			}
			if (Points.Count > 0)
			{
				float time2 = point.Time;
				List<FlightPoint> points2 = Points;
				if (time2 == points2[points2.Count - 1].Time)
				{
					List<FlightPoint> points3 = Points;
					points3[points3.Count - 1] = point;
					return;
				}
			}
			if (Points.Count < 512)
			{
				Points.Add(point);
			}
		}

		public void Stop(FlightPoint point)
		{
			Add(point);
			EndTime = point.Time;
			fade = false;
		}

		public void Ricochet(FlightPoint[] path)
		{
			bounceTime = path[0].Time;
			foreach (FlightPoint point in path)
			{
				Add(point);
			}
			EndTime = path[^1].Time;
			fade = true;
		}

		public PlaybackFrame Evaluate(float age)
		{
			if (Points.Count == 0)
			{
				return new PlaybackFrame(default(Vector3), Hide: true, Fade: false, Ricochet: false);
			}
			while (Cursor + 1 < Points.Count && Points[Cursor + 1].Time <= age)
			{
				Cursor++;
			}
			Vector3 vector = Points[Cursor].Position;
			if (Cursor + 1 < Points.Count && age > Points[Cursor].Time)
			{
				FlightPoint flightPoint = Points[Cursor + 1];
				vector = Vector3.Lerp(vector, flightPoint.Position, Math.Clamp((age - Points[Cursor].Time) / (flightPoint.Time - Points[Cursor].Time), 0f, 1f));
			}
			bool flag = age >= EndTime;
			return new PlaybackFrame(vector, flag && !fade, flag && fade, age >= bounceTime);
		}
	}
}