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