first commit

This commit is contained in:
2025-07-06 00:23:46 +02:00
commit 38f50c8819
1788 changed files with 112878 additions and 0 deletions

View File

@@ -0,0 +1,41 @@
using NitroxClient.MonoBehaviours;
using UnityEngine;
namespace NitroxClient.Helpers;
public static class GameObjectReferenceHolderExtensions
{
public static ReferenceHolder AddReference<T>(this Component component, T reference)
{
return AddReference(component.gameObject, reference);
}
public static ReferenceHolder AddReference<T>(this GameObject gameObject, T reference)
{
ReferenceHolder referenceHolder = gameObject.EnsureComponent<ReferenceHolder>();
referenceHolder.AddReference(reference);
return referenceHolder;
}
public static bool TryGetReference<T>(this GameObject gameObject, out T reference)
{
if (gameObject.TryGetComponent(out ReferenceHolder holder))
{
return holder.TryGetReference(out reference);
}
reference = default;
return false;
}
public static bool TryGetReference<T>(this Component component, out T reference)
{
if (component.TryGetComponent(out ReferenceHolder holder))
{
return holder.TryGetReference(out reference);
}
reference = default;
return false;
}
}

View File

@@ -0,0 +1,116 @@
using System;
using System.Runtime.CompilerServices;
using NitroxClient.MonoBehaviours;
using NitroxClient.Unity.Helper;
using NitroxModel.DataStructures;
using NitroxModel.DataStructures.Util;
using UnityEngine;
namespace NitroxClient.Helpers;
public static class NitroxEntityExtensions
{
public static bool TryGetNitroxEntity(this Component component, out NitroxEntity nitroxEntity)
{
nitroxEntity = null;
return component && component.TryGetComponent(out nitroxEntity);
}
public static bool TryGetNitroxEntity(this GameObject gameObject, out NitroxEntity nitroxEntity)
{
nitroxEntity = null;
return gameObject && gameObject.TryGetComponent(out nitroxEntity);
}
public static bool TryGetNitroxId(this GameObject gameObject, out NitroxId nitroxId)
{
if (!gameObject || !gameObject.TryGetComponent(out NitroxEntity nitroxEntity))
{
nitroxId = null;
return false;
}
nitroxId = nitroxEntity.Id;
return nitroxId != null;
}
public static bool TryGetNitroxId(this Component component, out NitroxId nitroxId)
{
if (!component || !component.TryGetComponent(out NitroxEntity nitroxEntity))
{
nitroxId = null;
return false;
}
nitroxId = nitroxEntity.Id;
return nitroxId != null;
}
public static bool TryGetIdOrWarn(
this GameObject gameObject,
out NitroxId nitroxId,
[CallerMemberName] string methodName = "",
[CallerFilePath] string filePath = "",
[CallerLineNumber] int lineNumber = 0)
{
// Since a destroyed but non-null object is normal behavior for Unity, we don't want to warn about it.
if (!gameObject)
{
nitroxId = null;
return false;
}
if (!gameObject.TryGetComponent(out NitroxEntity nitroxEntity))
{
Log.Warn($"[{filePath[(filePath.LastIndexOf("\\", StringComparison.Ordinal) + 1)..^2] + methodName}():L{lineNumber}] Couldn't find an id on {gameObject.GetFullHierarchyPath()}");
nitroxId = null;
return false;
}
nitroxId = nitroxEntity.Id;
return nitroxId != null;
}
public static bool TryGetIdOrWarn(
this Component component,
out NitroxId nitroxId,
[CallerMemberName] string methodName = "",
[CallerFilePath] string filePath = "",
[CallerLineNumber] int lineNumber = 0)
{
// Since a destroyed but non-null object is normal behavior for Unity, we don't want to warn about it.
if (!component)
{
nitroxId = null;
return false;
}
if (!component.TryGetComponent(out NitroxEntity nitroxEntity))
{
Log.WarnOnce($"[{filePath[(filePath.LastIndexOf("\\", StringComparison.Ordinal) + 1)..^2] + methodName}():L{lineNumber}] Couldn't find an id on {component.GetFullHierarchyPath()}");
nitroxId = null;
return false;
}
nitroxId = nitroxEntity.Id;
return nitroxId != null;
}
public static Optional<NitroxId> GetId(this GameObject gameObject)
{
if (gameObject && gameObject.TryGetComponent(out NitroxEntity nitroxEntity))
{
return Optional.Of(nitroxEntity.Id);
}
return Optional.Empty;
}
public static Optional<NitroxId> GetId(this Component component)
{
if (component && component.TryGetComponent(out NitroxEntity nitroxEntity))
{
return Optional.Of(nitroxEntity.Id);
}
return Optional.Empty;
}
}

View File

@@ -0,0 +1,116 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using ProtoBuf;
using ProtoBuf.Meta;
using UnityEngine;
namespace NitroxClient.Helpers
{
public class NitroxProtobufSerializer
{
public readonly RuntimeTypeModel model;
public readonly Dictionary<Type, int> NitroxTypes = new Dictionary<Type, int>();
protected RuntimeTypeModel Model => model;
public NitroxProtobufSerializer(params string[] assemblies)
{
model = TypeModel.Create();
foreach (string assembly in assemblies)
{
RegisterAssemblyClasses(assembly);
}
foreach (Type type in Assembly.GetExecutingAssembly().GetTypes())
{
bool hasUweProtobuf = (type.GetCustomAttributes(typeof(ProtoContractAttribute), true).Length > 0);
if (hasUweProtobuf)
{
AddType(type);
}
}
}
private void AddType(Type type)
{
// As of the latest protobuf update they will automatically register detected attributes.
model.Add(type, true);
ProtobufSerializerPrecompiled.knownTypes[type] = int.MaxValue; // UWE precompiled is going to pass everything to us
NitroxTypes[type] = int.MaxValue;
if (type.IsSubclassOf(typeof(MonoBehaviour))) // Add Nitrox MonoBehaviours to the Component whitelist
{
ProtobufSerializer.componentWhitelist.Add(type.FullName);
}
}
public void Serialize(Stream stream, object o)
{
model.SerializeWithLengthPrefix(stream, o, o.GetType(), PrefixStyle.Base128, 0);
}
public T Deserialize<T>(Stream stream)
{
T t = (T)Activator.CreateInstance(typeof(T));
return (T)Deserialize(stream, t, typeof(T));
}
public object Deserialize(Stream stream, object o, Type t)
{
return model.DeserializeWithLengthPrefix(stream, o, t, PrefixStyle.Base128, 0);
}
private void RegisterAssemblyClasses(string assemblyName)
{
foreach (Type type in Assembly.Load(assemblyName).GetTypes())
{
bool hasUweProtobuf = (type.GetCustomAttributes(typeof(ProtoContractAttribute), true).Length > 0);
if (hasUweProtobuf)
{
AddType(type);
}
else if (HasNitroxProtoContract(type))
{
AddType(type);
ManuallyRegisterNitroxProtoMembers(type.GetMembers(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance), type);
}
}
}
private bool HasNitroxProtoContract(Type type)
{
foreach (object o in type.GetCustomAttributes(true))
{
if (o.GetType().ToString().Contains("ProtoContractAttribute"))
{
return true;
}
}
return false;
}
private void ManuallyRegisterNitroxProtoMembers(MemberInfo[] info, Type type)
{
foreach (MemberInfo property in info)
{
foreach (object customAttribute in property.GetCustomAttributes(false))
{
Type attributeType = customAttribute.GetType();
if (attributeType.ToString().Contains("ProtoMemberAttribute"))
{
int tag = (int)attributeType.GetProperty("Tag", BindingFlags.Public | BindingFlags.Instance).GetValue(customAttribute, new object[] { });
model[type].Add(tag, property.Name);
}
}
}
}
}
}

View File

@@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using NitroxClient.Communication;
using NitroxClient.Communication.Abstract;
using NitroxModel.Packets;
namespace NitroxClient.Helpers
{
public class ThrottledPacketSender
{
private readonly Dictionary<object, ThrottledPacket> throttledPackets = new();
private readonly IPacketSender packetSender;
public ThrottledPacketSender(IPacketSender packetSender)
{
this.packetSender = packetSender;
}
public void Update()
{
foreach (ThrottledPacket throttledPacket in throttledPackets.Values)
{
if (throttledPacket.WasSend || throttledPacket.SendTime > DateTime.UtcNow)
{
continue;
}
if (packetSender.Send(throttledPacket.Packet))
{
throttledPacket.WasSend = true;
}
}
}
public bool SendThrottled<T>(T packet, Func<T, object> dedupeMethod, float throttleTime = 0.2f) where T : Packet
{
if (PacketSuppressor<T>.IsSuppressed)
{
return false;
}
object dedupeKey = dedupeMethod.Invoke(packet);
if (throttledPackets.TryGetValue(dedupeKey, out ThrottledPacket throttledPacket))
{
throttledPacket.ReplacePacket(packet, throttleTime);
return true;
}
throttledPackets.Add(dedupeKey, new ThrottledPacket(packet, throttleTime));
packetSender.Send(packet);
return true;
}
public bool RemovePendingPackets(object dedupeKey)
{
return throttledPackets.Remove(dedupeKey);
}
private class ThrottledPacket
{
public DateTime SendTime { get; private set; }
public Packet Packet { get; private set; }
public bool WasSend { get; set; }
public ThrottledPacket(Packet packet, float throttleTime)
{
SendTime = DateTime.UtcNow.AddSeconds(throttleTime);
Packet = packet;
}
public void ReplacePacket(Packet packet, float throttleTime)
{
Packet = packet;
if (WasSend)
{
SendTime = DateTime.UtcNow.AddSeconds(throttleTime);
WasSend = false;
}
}
}
}
}

View File

@@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using NitroxModel.Core;
using UnityEngine;
namespace NitroxClient.Helpers;
public static class UnityObjectExtensions
{
/// <summary>
/// Resolves a type using <see cref="NitroxServiceLocator.LocateService{T}" />. If the result is not null it will cache and return the same type on future calls.
/// </summary>
/// <remarks>
/// Dependency Injection should be limited to UnityEngine object types as in other cases it should be injected as constructor parameter.
/// This is the reason for having UnityEngine.Object as first parameter.
/// </remarks>
/// <typeparam name="T">Type to get and cache from <see cref="NitroxServiceLocator" /></typeparam>
/// <returns>The requested type or null if not available.</returns>
public static T Resolve<T>(this UnityEngine.Object _, bool prelifeTime = false) where T : class
{
return prelifeTime ? NitroxServiceLocator.Cache<T>.ValuePreLifetime : NitroxServiceLocator.Cache<T>.Value;
}
/// <summary>
/// Copies a whole component by using reflection. Please note this takes considerable time and every use of this should be thoughtful.
/// </summary>
public static Component CopyComponent(this Component original, GameObject destination)
{
Type type = original.GetType();
Component copy = destination.AddComponent(type);
FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (FieldInfo field in fields)
{
field.SetValue(copy, field.GetValue(original));
}
PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (PropertyInfo property in properties)
{
if (property.GetSetMethod(true) != null)
{
property.SetValue(copy, property.GetValue(original));
}
}
return copy;
}
public static bool TryFind(string name, out GameObject gameObject)
{
gameObject = GameObject.Find(name);
return gameObject;
}
}

View File

@@ -0,0 +1,24 @@
namespace NitroxClient.Helpers;
public static class VFXConstructingHelper
{
public static void EndGracefully(this VFXConstructing vfxConstructing)
{
// tell it we aren't done processing the OnSubComplete side-effects.
vfxConstructing.isDone = false;
// When vehicles are spawned they intentionally have a delay to remove the animation to give it a cinematic effect.
// setting to 0 is not enough, value must be negative.
vfxConstructing.delay = -1;
// We only set the constructed amount and don't call any of the end methods. For some reason, if the vfx doesn't
// end organically it can bug out the vehicle. Users won't notice a difference.
vfxConstructing.constructed = 1;
// Height is only used to calculate splash effects (vehicle falling into the water). This will disable it.
vfxConstructing.heightOffset = -999;
// Force an update to lock in these changes before anything stateful happens.
vfxConstructing.Update();
}
}