first commit
This commit is contained in:
167
NitroxClient/ClientAutoFacRegistrar.cs
Normal file
167
NitroxClient/ClientAutoFacRegistrar.cs
Normal file
@@ -0,0 +1,167 @@
|
||||
using System.Reflection;
|
||||
using Autofac;
|
||||
using Autofac.Core;
|
||||
using NitroxClient.Communication;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxClient.Communication.MultiplayerSession;
|
||||
using NitroxClient.Communication.NetworkingLayer.LiteNetLib;
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.Debuggers;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxClient.GameLogic.ChatUI;
|
||||
using NitroxClient.GameLogic.FMOD;
|
||||
using NitroxClient.GameLogic.HUD;
|
||||
using NitroxClient.GameLogic.InitialSync.Abstract;
|
||||
using NitroxClient.GameLogic.PlayerLogic;
|
||||
using NitroxClient.GameLogic.PlayerLogic.PlayerModel;
|
||||
using NitroxClient.GameLogic.PlayerLogic.PlayerModel.Abstract;
|
||||
using NitroxClient.GameLogic.PlayerLogic.PlayerPreferences;
|
||||
using NitroxClient.GameLogic.Settings;
|
||||
using NitroxClient.GameLogic.Spawning.Metadata;
|
||||
using NitroxClient.GameLogic.Spawning.Metadata.Extractor.Abstract;
|
||||
using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
|
||||
using NitroxModel;
|
||||
using NitroxModel.Core;
|
||||
using NitroxModel.GameLogic.FMOD;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxModel.Networking;
|
||||
using NitroxModel_Subnautica.Helper;
|
||||
|
||||
namespace NitroxClient
|
||||
{
|
||||
public class ClientAutoFacRegistrar : IAutoFacRegistrar
|
||||
{
|
||||
private static readonly Assembly currentAssembly = Assembly.GetExecutingAssembly();
|
||||
private readonly IModule[] modules;
|
||||
|
||||
public ClientAutoFacRegistrar(params IModule[] modules)
|
||||
{
|
||||
this.modules = modules;
|
||||
}
|
||||
|
||||
public void RegisterDependencies(ContainerBuilder containerBuilder)
|
||||
{
|
||||
foreach (IModule module in modules)
|
||||
{
|
||||
containerBuilder.RegisterModule(module);
|
||||
}
|
||||
|
||||
RegisterCoreDependencies(containerBuilder);
|
||||
RegisterMetadataDependencies(containerBuilder);
|
||||
RegisterPacketProcessors(containerBuilder);
|
||||
RegisterColorSwapManagers(containerBuilder);
|
||||
RegisterInitialSyncProcessors(containerBuilder);
|
||||
}
|
||||
|
||||
private void RegisterCoreDependencies(ContainerBuilder containerBuilder)
|
||||
{
|
||||
#if DEBUG
|
||||
containerBuilder.RegisterAssemblyTypes(currentAssembly)
|
||||
.AssignableTo<BaseDebugger>()
|
||||
.As<BaseDebugger>()
|
||||
.AsImplementedInterfaces()
|
||||
.AsSelf()
|
||||
.SingleInstance();
|
||||
#endif
|
||||
containerBuilder.Register(c => new NitroxProtobufSerializer($"{nameof(NitroxModel)}.dll"));
|
||||
|
||||
containerBuilder.RegisterType<UnityPreferenceStateProvider>()
|
||||
.As<IPreferenceStateProvider>()
|
||||
.SingleInstance();
|
||||
|
||||
containerBuilder.RegisterType<PlayerPreferenceManager>().SingleInstance();
|
||||
|
||||
containerBuilder.RegisterType<MultiplayerSessionManager>()
|
||||
.As<IMultiplayerSession>()
|
||||
.As<IPacketSender>()
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
containerBuilder.RegisterType<LiteNetLibClient>()
|
||||
.As<IClient>()
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
containerBuilder.RegisterType<LocalPlayer>()
|
||||
.AsSelf() //Would like to deprecate this registration at some point and just work through an abstraction.
|
||||
.As<ILocalNitroxPlayer>()
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
containerBuilder.RegisterType<SubnauticaMap>()
|
||||
.As<IMap>()
|
||||
.InstancePerLifetimeScope();
|
||||
|
||||
containerBuilder.RegisterType<PlayerManager>().InstancePerLifetimeScope();
|
||||
containerBuilder.RegisterType<PlayerModelManager>().InstancePerLifetimeScope();
|
||||
containerBuilder.RegisterType<PlayerVitalsManager>().InstancePerLifetimeScope();
|
||||
containerBuilder.RegisterType<PacketReceiver>().InstancePerLifetimeScope();
|
||||
containerBuilder.RegisterType<Vehicles>().InstancePerLifetimeScope();
|
||||
containerBuilder.RegisterType<AI>().InstancePerLifetimeScope();
|
||||
containerBuilder.RegisterType<PlayerChatManager>().InstancePerLifetimeScope();
|
||||
containerBuilder.RegisterType<SimulationOwnership>().InstancePerLifetimeScope();
|
||||
containerBuilder.RegisterType<LiveMixinManager>().InstancePerLifetimeScope();
|
||||
containerBuilder.RegisterType<Entities>().InstancePerLifetimeScope();
|
||||
containerBuilder.RegisterType<MedkitFabricator>().InstancePerLifetimeScope();
|
||||
containerBuilder.RegisterType<Items>().InstancePerLifetimeScope();
|
||||
containerBuilder.RegisterType<EquipmentSlots>().InstancePerLifetimeScope();
|
||||
containerBuilder.RegisterType<ItemContainers>().InstancePerLifetimeScope();
|
||||
containerBuilder.RegisterType<Cyclops>().InstancePerLifetimeScope();
|
||||
containerBuilder.RegisterType<Rockets>().InstancePerLifetimeScope();
|
||||
containerBuilder.RegisterType<MobileVehicleBay>().InstancePerLifetimeScope();
|
||||
containerBuilder.RegisterType<Interior>().InstancePerLifetimeScope();
|
||||
containerBuilder.RegisterType<NitroxConsole>().InstancePerLifetimeScope();
|
||||
containerBuilder.RegisterType<Terrain>().InstancePerLifetimeScope();
|
||||
containerBuilder.RegisterType<ExosuitModuleEvent>().InstancePerLifetimeScope();
|
||||
containerBuilder.RegisterType<SeamothModulesEvent>().InstancePerLifetimeScope();
|
||||
containerBuilder.RegisterType<Fires>().InstancePerLifetimeScope();
|
||||
containerBuilder.Register(_ => FMODWhitelist.Load(GameInfo.Subnautica)).InstancePerLifetimeScope();
|
||||
containerBuilder.RegisterType<FMODSystem>().InstancePerLifetimeScope();
|
||||
containerBuilder.RegisterType<NitroxSettingsManager>().InstancePerLifetimeScope();
|
||||
containerBuilder.RegisterType<ThrottledPacketSender>().InstancePerLifetimeScope();
|
||||
containerBuilder.RegisterType<PlayerCinematics>().InstancePerLifetimeScope();
|
||||
containerBuilder.RegisterType<NitroxPDATabManager>().InstancePerLifetimeScope();
|
||||
containerBuilder.RegisterType<TimeManager>().InstancePerLifetimeScope();
|
||||
containerBuilder.RegisterType<BulletManager>().InstancePerLifetimeScope();
|
||||
containerBuilder.RegisterType<NtpSyncer>().InstancePerLifetimeScope();
|
||||
}
|
||||
|
||||
private void RegisterMetadataDependencies(ContainerBuilder containerBuilder)
|
||||
{
|
||||
containerBuilder.RegisterAssemblyTypes(currentAssembly)
|
||||
.AssignableTo<IEntityMetadataExtractor>()
|
||||
.As<IEntityMetadataExtractor>()
|
||||
.AsSelf()
|
||||
.SingleInstance();
|
||||
containerBuilder.RegisterAssemblyTypes(currentAssembly)
|
||||
.AssignableTo<IEntityMetadataProcessor>()
|
||||
.As<IEntityMetadataProcessor>()
|
||||
.AsSelf()
|
||||
.SingleInstance();
|
||||
containerBuilder.RegisterType<EntityMetadataManager>().InstancePerLifetimeScope();
|
||||
}
|
||||
|
||||
private void RegisterPacketProcessors(ContainerBuilder containerBuilder)
|
||||
{
|
||||
containerBuilder
|
||||
.RegisterAssemblyTypes(currentAssembly)
|
||||
.AsClosedTypesOf(typeof(ClientPacketProcessor<>))
|
||||
.InstancePerLifetimeScope();
|
||||
}
|
||||
|
||||
private void RegisterColorSwapManagers(ContainerBuilder containerBuilder)
|
||||
{
|
||||
containerBuilder
|
||||
.RegisterAssemblyTypes(currentAssembly)
|
||||
.AssignableTo<IColorSwapManager>()
|
||||
.As<IColorSwapManager>()
|
||||
.SingleInstance();
|
||||
}
|
||||
|
||||
private void RegisterInitialSyncProcessors(ContainerBuilder containerBuilder)
|
||||
{
|
||||
containerBuilder
|
||||
.RegisterAssemblyTypes(currentAssembly)
|
||||
.AssignableTo<IInitialSyncProcessor>()
|
||||
.As<IInitialSyncProcessor>()
|
||||
.InstancePerLifetimeScope();
|
||||
}
|
||||
}
|
||||
}
|
19
NitroxClient/Communication/Abstract/IClient.cs
Normal file
19
NitroxClient/Communication/Abstract/IClient.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System.Threading.Tasks;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Abstract
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstracted IClient in order to give us options in the underlying protocol that we use to communicate with the server.
|
||||
/// Ex: We may want to also roll a UDP client in the future to handle packets where we don't necessarily care
|
||||
/// about transmission order or error recovery.
|
||||
/// </summary>
|
||||
public interface IClient
|
||||
{
|
||||
bool IsConnected { get; }
|
||||
Task StartAsync(string ipAddress, int serverPort);
|
||||
void Stop();
|
||||
void PollEvents();
|
||||
void Send(Packet packet);
|
||||
}
|
||||
}
|
21
NitroxClient/Communication/Abstract/IMultiplayerSession.cs
Normal file
21
NitroxClient/Communication/Abstract/IMultiplayerSession.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.Threading.Tasks;
|
||||
using NitroxModel.MultiplayerSession;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Abstract
|
||||
{
|
||||
public delegate void MultiplayerSessionConnectionStateChangedEventHandler(IMultiplayerSessionConnectionState newState);
|
||||
|
||||
public interface IMultiplayerSession : IPacketSender, IMultiplayerSessionState
|
||||
{
|
||||
IMultiplayerSessionConnectionState CurrentState { get; }
|
||||
event MultiplayerSessionConnectionStateChangedEventHandler ConnectionStateChanged;
|
||||
|
||||
Task ConnectAsync(string ipAddress, int port);
|
||||
void ProcessSessionPolicy(MultiplayerSessionPolicy policy);
|
||||
void RequestSessionReservation(PlayerSettings playerSettings, AuthenticationContext authenticationContext);
|
||||
void ProcessReservationResponsePacket(MultiplayerSessionReservation reservation);
|
||||
void JoinSession();
|
||||
void Disconnect();
|
||||
}
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
namespace NitroxClient.Communication.Abstract
|
||||
{
|
||||
public interface IMultiplayerSessionConnectionContext : IMultiplayerSessionState
|
||||
{
|
||||
void UpdateConnectionState(IMultiplayerSessionConnectionState sessionConnectionState);
|
||||
void ClearSessionState();
|
||||
}
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
using System.Threading.Tasks;
|
||||
using NitroxClient.Communication.MultiplayerSession;
|
||||
|
||||
namespace NitroxClient.Communication.Abstract
|
||||
{
|
||||
public interface IMultiplayerSessionConnectionState
|
||||
{
|
||||
MultiplayerSessionConnectionStage CurrentStage { get; }
|
||||
|
||||
Task NegotiateReservationAsync(IMultiplayerSessionConnectionContext sessionConnectionContext);
|
||||
void JoinSession(IMultiplayerSessionConnectionContext sessionConnectionContext);
|
||||
void Disconnect(IMultiplayerSessionConnectionContext sessionConnectionContext);
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
using NitroxModel.MultiplayerSession;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Abstract
|
||||
{
|
||||
public interface IMultiplayerSessionState
|
||||
{
|
||||
IClient Client { get; }
|
||||
string IpAddress { get; }
|
||||
int ServerPort { get; }
|
||||
MultiplayerSessionPolicy SessionPolicy { get; }
|
||||
PlayerSettings PlayerSettings { get; }
|
||||
AuthenticationContext AuthenticationContext { get; }
|
||||
MultiplayerSessionReservation Reservation { get; }
|
||||
}
|
||||
}
|
13
NitroxClient/Communication/Abstract/IPacketSender.cs
Normal file
13
NitroxClient/Communication/Abstract/IPacketSender.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Abstract;
|
||||
|
||||
public interface IPacketSender
|
||||
{
|
||||
/// <summary>
|
||||
/// Sends the packet. Returns true if packet was not suppressed.
|
||||
/// </summary>
|
||||
/// <param name="packet">The packet to send.</param>
|
||||
/// <returns>True if not suppressed and actually sent.</returns>
|
||||
bool Send<T>(T packet) where T : Packet;
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace NitroxClient.Communication.Exceptions
|
||||
{
|
||||
public class ClientConnectionFailedException : Exception
|
||||
{
|
||||
public ClientConnectionFailedException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public ClientConnectionFailedException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
148
NitroxClient/Communication/LANBroadcastClient.cs
Normal file
148
NitroxClient/Communication/LANBroadcastClient.cs
Normal file
@@ -0,0 +1,148 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LiteNetLib;
|
||||
using LiteNetLib.Utils;
|
||||
using NitroxModel.Constants;
|
||||
|
||||
namespace NitroxClient.Communication;
|
||||
|
||||
public static class LANBroadcastClient
|
||||
{
|
||||
private static event Action<IPEndPoint> serverFound;
|
||||
|
||||
public static event Action<IPEndPoint> ServerFound
|
||||
{
|
||||
add
|
||||
{
|
||||
serverFound += value;
|
||||
|
||||
// Trigger event for servers already found.
|
||||
foreach (IPEndPoint server in DiscoveredServers)
|
||||
{
|
||||
value?.Invoke(server);
|
||||
}
|
||||
}
|
||||
remove => serverFound -= value;
|
||||
}
|
||||
|
||||
private static Task<IEnumerable<IPEndPoint>> lastTask;
|
||||
public static ConcurrentQueue<IPEndPoint> DiscoveredServers = [];
|
||||
|
||||
public static async Task<IEnumerable<IPEndPoint>> SearchAsync(bool force = false, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!force && lastTask != null)
|
||||
{
|
||||
DiscoveredServers = [];
|
||||
foreach (IPEndPoint ipEndPoint in await lastTask)
|
||||
{
|
||||
DiscoveredServers.Enqueue(ipEndPoint);
|
||||
}
|
||||
return DiscoveredServers;
|
||||
}
|
||||
|
||||
return await (lastTask = SearchInternalAsync(cancellationToken));
|
||||
}
|
||||
|
||||
private static async Task<IEnumerable<IPEndPoint>> SearchInternalAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
static void ReceivedResponse(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType)
|
||||
{
|
||||
if (messageType != UnconnectedMessageType.Broadcast)
|
||||
{
|
||||
return;
|
||||
}
|
||||
string responseString = reader.GetString();
|
||||
if (responseString != LANDiscoveryConstants.BROADCAST_RESPONSE_STRING)
|
||||
{
|
||||
return;
|
||||
}
|
||||
int serverPort = reader.GetInt();
|
||||
IPEndPoint serverEndPoint = new(remoteEndPoint.Address, serverPort);
|
||||
if (DiscoveredServers.Contains(serverEndPoint))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Debug($"Found LAN server at {serverEndPoint}.");
|
||||
DiscoveredServers.Enqueue(serverEndPoint);
|
||||
OnServerFound(serverEndPoint);
|
||||
}
|
||||
|
||||
cancellationToken = cancellationToken == default ? new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token : cancellationToken;
|
||||
EventBasedNetListener listener = new();
|
||||
NetManager client = new(listener)
|
||||
{
|
||||
AutoRecycle = true,
|
||||
BroadcastReceiveEnabled = true,
|
||||
UnconnectedMessagesEnabled = true
|
||||
};
|
||||
// Try start client on an available, predefined, port
|
||||
foreach (int port in LANDiscoveryConstants.BROADCAST_PORTS)
|
||||
{
|
||||
if (client.Start(port))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!client.IsRunning)
|
||||
{
|
||||
Log.Warn("Failed to start LAN discover client: none of the defined ports are available");
|
||||
return [];
|
||||
}
|
||||
|
||||
Log.Info("Searching for LAN servers...");
|
||||
listener.NetworkReceiveUnconnectedEvent += ReceivedResponse;
|
||||
Task broadcastTask = Task.Run(async () =>
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
NetDataWriter writer = new();
|
||||
writer.Put(LANDiscoveryConstants.BROADCAST_REQUEST_STRING);
|
||||
foreach (int port in LANDiscoveryConstants.BROADCAST_PORTS)
|
||||
{
|
||||
client.SendBroadcast(writer, port);
|
||||
}
|
||||
try
|
||||
{
|
||||
await Task.Delay(5000, cancellationToken);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}, cancellationToken);
|
||||
Task receiveTask = Task.Run(async () =>
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
client.PollEvents();
|
||||
try
|
||||
{
|
||||
await Task.Delay(100, cancellationToken);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}, cancellationToken);
|
||||
|
||||
await Task.WhenAll(broadcastTask, receiveTask);
|
||||
// Cleanup
|
||||
listener.ClearNetworkReceiveUnconnectedEvent();
|
||||
client.Stop();
|
||||
listener.NetworkReceiveUnconnectedEvent -= ReceivedResponse;
|
||||
return DiscoveredServers;
|
||||
}
|
||||
|
||||
private static void OnServerFound(IPEndPoint obj)
|
||||
{
|
||||
serverFound?.Invoke(obj);
|
||||
}
|
||||
}
|
@@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxModel.MultiplayerSession;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.MultiplayerSession.ConnectionState
|
||||
{
|
||||
public class AwaitingReservationCredentials : ConnectionNegotiatingState
|
||||
{
|
||||
public override MultiplayerSessionConnectionStage CurrentStage => MultiplayerSessionConnectionStage.AWAITING_RESERVATION_CREDENTIALS;
|
||||
|
||||
public override Task NegotiateReservationAsync(IMultiplayerSessionConnectionContext sessionConnectionContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
ValidateState(sessionConnectionContext);
|
||||
|
||||
string reservationCorrelationId = Guid.NewGuid().ToString();
|
||||
RequestSessionReservation(sessionConnectionContext, reservationCorrelationId);
|
||||
AwaitSessionReservation(sessionConnectionContext, reservationCorrelationId);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Disconnect(sessionConnectionContext);
|
||||
throw;
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void RequestSessionReservation(IMultiplayerSessionConnectionContext sessionConnectionContext, string reservationCorrelationId)
|
||||
{
|
||||
IClient client = sessionConnectionContext.Client;
|
||||
PlayerSettings playerSettings = sessionConnectionContext.PlayerSettings;
|
||||
AuthenticationContext authenticationContext = sessionConnectionContext.AuthenticationContext;
|
||||
|
||||
MultiplayerSessionReservationRequest requestPacket = new(reservationCorrelationId, playerSettings, authenticationContext);
|
||||
client.Send(requestPacket);
|
||||
}
|
||||
|
||||
private void AwaitSessionReservation(IMultiplayerSessionConnectionContext sessionConnectionContext, string reservationCorrelationId)
|
||||
{
|
||||
AwaitingSessionReservation nextState = new AwaitingSessionReservation(reservationCorrelationId);
|
||||
sessionConnectionContext.UpdateConnectionState(nextState);
|
||||
}
|
||||
|
||||
private static void ValidateState(IMultiplayerSessionConnectionContext sessionConnectionContext)
|
||||
{
|
||||
ClientIsConnected(sessionConnectionContext);
|
||||
PlayerSettingsIsNotNull(sessionConnectionContext);
|
||||
AuthenticationContextIsNotNull(sessionConnectionContext);
|
||||
}
|
||||
|
||||
private static void ClientIsConnected(IMultiplayerSessionConnectionContext sessionConnectionContext)
|
||||
{
|
||||
if (!sessionConnectionContext.Client.IsConnected)
|
||||
{
|
||||
throw new InvalidOperationException("The client is not connected.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void PlayerSettingsIsNotNull(IMultiplayerSessionConnectionContext sessionConnectionContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
Validate.NotNull(sessionConnectionContext.PlayerSettings);
|
||||
}
|
||||
catch (ArgumentNullException ex)
|
||||
{
|
||||
throw new InvalidOperationException("The context does not contain player settings.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static void AuthenticationContextIsNotNull(IMultiplayerSessionConnectionContext sessionConnectionContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
Validate.NotNull(sessionConnectionContext.AuthenticationContext);
|
||||
}
|
||||
catch (ArgumentNullException ex)
|
||||
{
|
||||
throw new InvalidOperationException("The context does not contain an authentication context.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxModel.MultiplayerSession;
|
||||
using NitroxModel.Packets.Exceptions;
|
||||
|
||||
namespace NitroxClient.Communication.MultiplayerSession.ConnectionState
|
||||
{
|
||||
public class AwaitingSessionReservation : ConnectionNegotiatingState
|
||||
{
|
||||
private readonly string reservationCorrelationId;
|
||||
|
||||
public AwaitingSessionReservation(string reservationCorrelationId)
|
||||
{
|
||||
Validate.NotNull(reservationCorrelationId);
|
||||
this.reservationCorrelationId = reservationCorrelationId;
|
||||
}
|
||||
|
||||
public override MultiplayerSessionConnectionStage CurrentStage => MultiplayerSessionConnectionStage.AWAITING_SESSION_RESERVATION;
|
||||
|
||||
public override Task NegotiateReservationAsync(IMultiplayerSessionConnectionContext sessionConnectionContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
ValidateState(sessionConnectionContext);
|
||||
HandleReservation(sessionConnectionContext);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Disconnect(sessionConnectionContext);
|
||||
throw;
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static void HandleReservation(IMultiplayerSessionConnectionContext sessionConnectionContext)
|
||||
{
|
||||
IMultiplayerSessionConnectionState nextState = sessionConnectionContext.Reservation.ReservationState switch
|
||||
{
|
||||
MultiplayerSessionReservationState.RESERVED => new SessionReserved(),
|
||||
_ => new SessionReservationRejected(),
|
||||
};
|
||||
|
||||
sessionConnectionContext.UpdateConnectionState(nextState);
|
||||
}
|
||||
|
||||
private void ValidateState(IMultiplayerSessionConnectionContext sessionConnectionContext)
|
||||
{
|
||||
ReservationIsNotNull(sessionConnectionContext);
|
||||
ReservationPacketIsCorrelated(sessionConnectionContext);
|
||||
}
|
||||
|
||||
private static void ReservationIsNotNull(IMultiplayerSessionConnectionContext sessionConnectionContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
Validate.NotNull(sessionConnectionContext.Reservation);
|
||||
}
|
||||
catch (ArgumentNullException ex)
|
||||
{
|
||||
throw new InvalidOperationException("The context does not have a reservation.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void ReservationPacketIsCorrelated(IMultiplayerSessionConnectionContext sessionConnectionContext)
|
||||
{
|
||||
if (!reservationCorrelationId.Equals(sessionConnectionContext.Reservation.CorrelationId))
|
||||
{
|
||||
throw new UncorrelatedPacketException(sessionConnectionContext.Reservation, reservationCorrelationId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
using System.Threading.Tasks;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
|
||||
namespace NitroxClient.Communication.MultiplayerSession.ConnectionState
|
||||
{
|
||||
public abstract class CommunicatingState : IMultiplayerSessionConnectionState
|
||||
{
|
||||
public abstract MultiplayerSessionConnectionStage CurrentStage { get; }
|
||||
public abstract Task NegotiateReservationAsync(IMultiplayerSessionConnectionContext sessionConnectionContext);
|
||||
|
||||
public abstract void JoinSession(IMultiplayerSessionConnectionContext sessionConnectionContext);
|
||||
|
||||
public virtual void Disconnect(IMultiplayerSessionConnectionContext sessionConnectionContext)
|
||||
{
|
||||
sessionConnectionContext.ClearSessionState();
|
||||
sessionConnectionContext.Client.Stop();
|
||||
|
||||
Disconnected newConnectionState = new Disconnected();
|
||||
sessionConnectionContext.UpdateConnectionState(newConnectionState);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
|
||||
namespace NitroxClient.Communication.MultiplayerSession.ConnectionState
|
||||
{
|
||||
public abstract class ConnectionNegotiatedState : CommunicatingState
|
||||
{
|
||||
public override Task NegotiateReservationAsync(IMultiplayerSessionConnectionContext sessionConnectionContext)
|
||||
{
|
||||
throw new InvalidOperationException("Unable to negotiate a session connection in the current state.");
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
|
||||
namespace NitroxClient.Communication.MultiplayerSession.ConnectionState
|
||||
{
|
||||
public abstract class ConnectionNegotiatingState : CommunicatingState
|
||||
{
|
||||
public override void JoinSession(IMultiplayerSessionConnectionContext sessionConnectionContext)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot join a session until a reservation has been negotiated with the server.");
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,85 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxClient.Communication.Exceptions;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.MultiplayerSession.ConnectionState
|
||||
{
|
||||
public class Disconnected : IMultiplayerSessionConnectionState
|
||||
{
|
||||
public MultiplayerSessionConnectionStage CurrentStage => MultiplayerSessionConnectionStage.DISCONNECTED;
|
||||
|
||||
public async Task NegotiateReservationAsync(IMultiplayerSessionConnectionContext sessionConnectionContext)
|
||||
{
|
||||
ValidateState(sessionConnectionContext);
|
||||
|
||||
IClient client = sessionConnectionContext.Client;
|
||||
string ipAddress = sessionConnectionContext.IpAddress;
|
||||
int port = sessionConnectionContext.ServerPort;
|
||||
await StartClientAsync(ipAddress, client, port);
|
||||
EstablishSessionPolicy(sessionConnectionContext, client);
|
||||
}
|
||||
|
||||
private void ValidateState(IMultiplayerSessionConnectionContext sessionConnectionContext)
|
||||
{
|
||||
ValidateClient(sessionConnectionContext);
|
||||
|
||||
try
|
||||
{
|
||||
Validate.NotNull(sessionConnectionContext.IpAddress);
|
||||
}
|
||||
catch (ArgumentNullException ex)
|
||||
{
|
||||
throw new InvalidOperationException("The context is missing an IP address.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateClient(IMultiplayerSessionConnectionContext sessionConnectionContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
Validate.NotNull(sessionConnectionContext.Client);
|
||||
}
|
||||
catch (ArgumentNullException ex)
|
||||
{
|
||||
throw new InvalidOperationException("The client must be set on the connection context before trying to negotiate a session reservation.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task StartClientAsync(string ipAddress, IClient client, int port)
|
||||
{
|
||||
if (!client.IsConnected)
|
||||
{
|
||||
await client.StartAsync(ipAddress, port);
|
||||
|
||||
if (!client.IsConnected)
|
||||
{
|
||||
throw new ClientConnectionFailedException("The client failed to connect without providing a reason why.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void EstablishSessionPolicy(IMultiplayerSessionConnectionContext sessionConnectionContext, IClient client)
|
||||
{
|
||||
string policyRequestCorrelationId = Guid.NewGuid().ToString();
|
||||
|
||||
MultiplayerSessionPolicyRequest requestPacket = new MultiplayerSessionPolicyRequest(policyRequestCorrelationId);
|
||||
client.Send(requestPacket);
|
||||
|
||||
EstablishingSessionPolicy nextState = new EstablishingSessionPolicy(policyRequestCorrelationId);
|
||||
sessionConnectionContext.UpdateConnectionState(nextState);
|
||||
}
|
||||
|
||||
public void JoinSession(IMultiplayerSessionConnectionContext sessionConnectionContext)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot join a session until a reservation has been negotiated with the server.");
|
||||
}
|
||||
|
||||
public void Disconnect(IMultiplayerSessionConnectionContext sessionConnectionContext)
|
||||
{
|
||||
throw new InvalidOperationException("Not connected to a multiplayer server.");
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxModel.Packets.Exceptions;
|
||||
|
||||
namespace NitroxClient.Communication.MultiplayerSession.ConnectionState
|
||||
{
|
||||
public class EstablishingSessionPolicy : ConnectionNegotiatingState
|
||||
{
|
||||
private readonly string policyRequestCorrelationId;
|
||||
|
||||
public EstablishingSessionPolicy(string policyRequestCorrelationId)
|
||||
{
|
||||
Validate.NotNull(policyRequestCorrelationId);
|
||||
this.policyRequestCorrelationId = policyRequestCorrelationId;
|
||||
}
|
||||
|
||||
public override MultiplayerSessionConnectionStage CurrentStage => MultiplayerSessionConnectionStage.ESTABLISHING_SERVER_POLICY;
|
||||
|
||||
public override Task NegotiateReservationAsync(IMultiplayerSessionConnectionContext sessionConnectionContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
ValidateState(sessionConnectionContext);
|
||||
AwaitReservationCredentials(sessionConnectionContext);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Disconnect(sessionConnectionContext);
|
||||
throw;
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void ValidateState(IMultiplayerSessionConnectionContext sessionConnectionContext)
|
||||
{
|
||||
SessionPolicyIsNotNull(sessionConnectionContext);
|
||||
SessionPolicyPacketCorrelation(sessionConnectionContext);
|
||||
}
|
||||
|
||||
private static void SessionPolicyIsNotNull(IMultiplayerSessionConnectionContext sessionConnectionContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
Validate.NotNull(sessionConnectionContext.SessionPolicy);
|
||||
}
|
||||
catch (ArgumentNullException ex)
|
||||
{
|
||||
throw new InvalidOperationException("The context is missing a session policy.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void SessionPolicyPacketCorrelation(IMultiplayerSessionConnectionContext sessionConnectionContext)
|
||||
{
|
||||
if (!policyRequestCorrelationId.Equals(sessionConnectionContext.SessionPolicy.CorrelationId))
|
||||
{
|
||||
throw new UncorrelatedPacketException(sessionConnectionContext.SessionPolicy, policyRequestCorrelationId);
|
||||
}
|
||||
}
|
||||
|
||||
private void AwaitReservationCredentials(IMultiplayerSessionConnectionContext sessionConnectionContext)
|
||||
{
|
||||
AwaitingReservationCredentials nextState = new AwaitingReservationCredentials();
|
||||
sessionConnectionContext.UpdateConnectionState(nextState);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
|
||||
namespace NitroxClient.Communication.MultiplayerSession.ConnectionState
|
||||
{
|
||||
public class SessionJoined : ConnectionNegotiatedState
|
||||
{
|
||||
public override MultiplayerSessionConnectionStage CurrentStage => MultiplayerSessionConnectionStage.SESSION_JOINED;
|
||||
|
||||
public override void JoinSession(IMultiplayerSessionConnectionContext sessionConnectionContext)
|
||||
{
|
||||
throw new InvalidOperationException("The session is already in progress.");
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
|
||||
namespace NitroxClient.Communication.MultiplayerSession.ConnectionState
|
||||
{
|
||||
public class SessionReservationRejected : ConnectionNegotiatedState
|
||||
{
|
||||
public override MultiplayerSessionConnectionStage CurrentStage => MultiplayerSessionConnectionStage.SESSION_RESERVATION_REJECTED;
|
||||
|
||||
public override void JoinSession(IMultiplayerSessionConnectionContext sessionConnectionContext)
|
||||
{
|
||||
throw new InvalidOperationException("The session has rejected the reserveration request.");
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.MultiplayerSession.ConnectionState
|
||||
{
|
||||
public class SessionReserved : ConnectionNegotiatedState
|
||||
{
|
||||
public override MultiplayerSessionConnectionStage CurrentStage => MultiplayerSessionConnectionStage.SESSION_RESERVED;
|
||||
|
||||
public override void JoinSession(IMultiplayerSessionConnectionContext sessionConnectionContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
ValidateState(sessionConnectionContext);
|
||||
EnterMultiplayerSession(sessionConnectionContext);
|
||||
ChangeState(sessionConnectionContext);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Disconnect(sessionConnectionContext);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateState(IMultiplayerSessionConnectionContext sessionConnectionContext)
|
||||
{
|
||||
if (!sessionConnectionContext.Client.IsConnected)
|
||||
{
|
||||
throw new InvalidOperationException("The client is not connected.");
|
||||
}
|
||||
}
|
||||
|
||||
private void EnterMultiplayerSession(IMultiplayerSessionConnectionContext sessionConnectionContext)
|
||||
{
|
||||
IClient client = sessionConnectionContext.Client;
|
||||
MultiplayerSessionReservation reservation = sessionConnectionContext.Reservation;
|
||||
string correlationId = reservation.CorrelationId;
|
||||
string reservationKey = reservation.ReservationKey;
|
||||
|
||||
PlayerJoiningMultiplayerSession packet = new PlayerJoiningMultiplayerSession(correlationId, reservationKey);
|
||||
client.Send(packet);
|
||||
}
|
||||
|
||||
private void ChangeState(IMultiplayerSessionConnectionContext sessionConnectionContext)
|
||||
{
|
||||
SessionJoined nextState = new SessionJoined();
|
||||
sessionConnectionContext.UpdateConnectionState(nextState);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
namespace NitroxClient.Communication.MultiplayerSession
|
||||
{
|
||||
public enum MultiplayerSessionConnectionStage
|
||||
{
|
||||
DISCONNECTED,
|
||||
ESTABLISHING_SERVER_POLICY,
|
||||
AWAITING_RESERVATION_CREDENTIALS,
|
||||
AWAITING_SESSION_RESERVATION,
|
||||
SESSION_RESERVED,
|
||||
SESSION_RESERVATION_REJECTED,
|
||||
SESSION_JOINED
|
||||
}
|
||||
}
|
@@ -0,0 +1,163 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxClient.Communication.MultiplayerSession.ConnectionState;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxModel.MultiplayerSession;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxModel.Serialization;
|
||||
|
||||
namespace NitroxClient.Communication.MultiplayerSession
|
||||
{
|
||||
public class MultiplayerSessionManager : IMultiplayerSession, IMultiplayerSessionConnectionContext
|
||||
{
|
||||
private static readonly Task initSerializerTask;
|
||||
|
||||
static MultiplayerSessionManager()
|
||||
{
|
||||
initSerializerTask = Task.Run(Packet.InitSerializer);
|
||||
}
|
||||
|
||||
public IClient Client { get; }
|
||||
public string IpAddress { get; private set; }
|
||||
public int ServerPort { get; private set; }
|
||||
public MultiplayerSessionPolicy SessionPolicy { get; private set; }
|
||||
public PlayerSettings PlayerSettings { get; private set; }
|
||||
public AuthenticationContext AuthenticationContext { get; private set; }
|
||||
public IMultiplayerSessionConnectionState CurrentState { get; private set; }
|
||||
public MultiplayerSessionReservation Reservation { get; private set; }
|
||||
|
||||
public MultiplayerSessionManager(IClient client)
|
||||
{
|
||||
Log.Info("Initializing MultiplayerSessionManager...");
|
||||
Client = client;
|
||||
CurrentState = new Disconnected();
|
||||
}
|
||||
|
||||
// Testing entry point
|
||||
internal MultiplayerSessionManager(IClient client, IMultiplayerSessionConnectionState initialState)
|
||||
{
|
||||
Client = client;
|
||||
CurrentState = initialState;
|
||||
}
|
||||
|
||||
public event MultiplayerSessionConnectionStateChangedEventHandler ConnectionStateChanged;
|
||||
|
||||
public async Task ConnectAsync(string ipAddress, int port)
|
||||
{
|
||||
IpAddress = ipAddress;
|
||||
ServerPort = port;
|
||||
await initSerializerTask;
|
||||
await CurrentState.NegotiateReservationAsync(this);
|
||||
}
|
||||
|
||||
public void ProcessSessionPolicy(MultiplayerSessionPolicy policy)
|
||||
{
|
||||
SessionPolicy = policy;
|
||||
NitroxConsole.DisableConsole = SessionPolicy.DisableConsole;
|
||||
Version localVersion = NitroxEnvironment.Version;
|
||||
NitroxVersion nitroxVersion = new(localVersion.Major, localVersion.Minor);
|
||||
switch (nitroxVersion.CompareTo(SessionPolicy.NitroxVersionAllowed))
|
||||
{
|
||||
case -1:
|
||||
Log.Error($"Client is out of date. Server: {SessionPolicy.NitroxVersionAllowed}, Client: {localVersion}");
|
||||
Log.InGame(Language.main.Get("Nitrox_OutOfDateClient")
|
||||
.Replace("{serverVersion}", SessionPolicy.NitroxVersionAllowed.ToString())
|
||||
.Replace("{localVersion}", localVersion.ToString()));
|
||||
CurrentState.Disconnect(this);
|
||||
return;
|
||||
case 1:
|
||||
Log.Error($"Server is out of date. Server: {SessionPolicy.NitroxVersionAllowed}, Client: {localVersion}");
|
||||
Log.InGame(Language.main.Get("Nitrox_OutOfDateServer")
|
||||
.Replace("{serverVersion}", SessionPolicy.NitroxVersionAllowed.ToString())
|
||||
.Replace("{localVersion}", localVersion.ToString()));
|
||||
CurrentState.Disconnect(this);
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentState.NegotiateReservationAsync(this);
|
||||
}
|
||||
|
||||
public void RequestSessionReservation(PlayerSettings playerSettings, AuthenticationContext authenticationContext)
|
||||
{
|
||||
// If a reservation has already been sent (in which case the client is enqueued in the join queue)
|
||||
if (CurrentState.CurrentStage == MultiplayerSessionConnectionStage.AWAITING_SESSION_RESERVATION)
|
||||
{
|
||||
Log.Info("Waiting in join queue…");
|
||||
Log.InGame(Language.main.Get("Nitrox_Waiting"));
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerSettings = playerSettings;
|
||||
AuthenticationContext = authenticationContext;
|
||||
CurrentState.NegotiateReservationAsync(this);
|
||||
}
|
||||
|
||||
public void ProcessReservationResponsePacket(MultiplayerSessionReservation reservation)
|
||||
{
|
||||
if (reservation.ReservationState == MultiplayerSessionReservationState.ENQUEUED_IN_JOIN_QUEUE)
|
||||
{
|
||||
Log.Info("Waiting in join queue…");
|
||||
Log.InGame(Language.main.Get("Nitrox_Waiting"));
|
||||
return;
|
||||
}
|
||||
|
||||
Reservation = reservation;
|
||||
CurrentState.NegotiateReservationAsync(this);
|
||||
}
|
||||
|
||||
public void JoinSession()
|
||||
{
|
||||
CurrentState.JoinSession(this);
|
||||
}
|
||||
|
||||
public void Disconnect()
|
||||
{
|
||||
if (CurrentState.CurrentStage != MultiplayerSessionConnectionStage.DISCONNECTED)
|
||||
{
|
||||
CurrentState.Disconnect(this);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Send<T>(T packet) where T : Packet
|
||||
{
|
||||
if (!PacketSuppressor<T>.IsSuppressed)
|
||||
{
|
||||
Client.Send(packet);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void UpdateConnectionState(IMultiplayerSessionConnectionState sessionConnectionState)
|
||||
{
|
||||
Validate.NotNull(sessionConnectionState);
|
||||
|
||||
string fromStage = CurrentState == null ? "null" : CurrentState.CurrentStage.ToString();
|
||||
string username = AuthenticationContext == null ? "" : AuthenticationContext.Username;
|
||||
Log.Debug($"Updating session stage from '{fromStage}' to '{sessionConnectionState.CurrentStage}' for '{username}'");
|
||||
|
||||
CurrentState = sessionConnectionState;
|
||||
|
||||
// Last connection state changed will not have any handlers
|
||||
ConnectionStateChanged?.Invoke(CurrentState);
|
||||
|
||||
if (sessionConnectionState.CurrentStage == MultiplayerSessionConnectionStage.SESSION_RESERVED)
|
||||
{
|
||||
Log.PlayerName = username;
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearSessionState()
|
||||
{
|
||||
IpAddress = null;
|
||||
ServerPort = ServerList.DEFAULT_PORT;
|
||||
SessionPolicy = null;
|
||||
PlayerSettings = null;
|
||||
AuthenticationContext = null;
|
||||
Reservation = null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace NitroxClient.Communication.NetworkingLayer.LiteNetLib;
|
||||
|
||||
public sealed class ClockSyncProcedure(LiteNetLibClient liteNetLibClient) : IDisposable
|
||||
{
|
||||
private readonly LiteNetLibClient liteNetLibClient = liteNetLibClient;
|
||||
private readonly int previousPingInterval = liteNetLibClient.PingInterval;
|
||||
private readonly List<long> deltas = [];
|
||||
|
||||
public static ClockSyncProcedure Start(LiteNetLibClient liteNetLibClient, int procedureDuration)
|
||||
{
|
||||
ClockSyncProcedure clockSyncProcedure = new(liteNetLibClient);
|
||||
liteNetLibClient.PingInterval = (int)TimeSpan.FromSeconds(procedureDuration).TotalMilliseconds;
|
||||
liteNetLibClient.LatencyUpdateCallback += clockSyncProcedure.LatencyUpdate;
|
||||
liteNetLibClient.ForceUpdate();
|
||||
return clockSyncProcedure;
|
||||
}
|
||||
|
||||
public void LatencyUpdate(long remoteTimeDelta)
|
||||
{
|
||||
deltas.Add(remoteTimeDelta);
|
||||
liteNetLibClient.ForceUpdate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get an average of remote delta times gathered until now without the ones which are not in the standard deviation range.
|
||||
/// </summary>
|
||||
public bool TryGetSafeAverageRTD(out long average)
|
||||
{
|
||||
if (deltas.Count == 0)
|
||||
{
|
||||
Log.Error($"[{nameof(ClockSyncProcedure)}] No delta received !");
|
||||
average = 0;
|
||||
return false; // abnormal situation
|
||||
}
|
||||
|
||||
double avg = deltas.Average();
|
||||
average = (long)avg; // Need to assign from another variable since you can't give a ref to the below lambda
|
||||
long standardDeviation = (long)Math.Sqrt(deltas.Average(v => Math.Pow(v - avg, 2)));
|
||||
|
||||
List<long> validValues = [];
|
||||
foreach (long delta in deltas)
|
||||
{
|
||||
if (Math.Abs(delta - average) <= standardDeviation)
|
||||
{
|
||||
validValues.Add(delta);
|
||||
}
|
||||
}
|
||||
|
||||
if (validValues.Count == 0)
|
||||
{
|
||||
Log.Warn($"[{nameof(ClockSyncProcedure)}] No valid value out of {deltas.Count} deltas. Using regular average without filtering.");
|
||||
return true; // value is not really meaningful ...
|
||||
}
|
||||
|
||||
average = (long)validValues.Average();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
liteNetLibClient.LatencyUpdateCallback -= LatencyUpdate;
|
||||
liteNetLibClient.PingInterval = previousPingInterval;
|
||||
}
|
||||
}
|
@@ -0,0 +1,146 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LiteNetLib;
|
||||
using LiteNetLib.Utils;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxClient.Debuggers;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxClient.MonoBehaviours.Gui.Modals;
|
||||
using NitroxModel.Networking;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.NetworkingLayer.LiteNetLib;
|
||||
|
||||
public class LiteNetLibClient : IClient
|
||||
{
|
||||
private readonly NetManager client;
|
||||
|
||||
private readonly AutoResetEvent connectedEvent = new(false);
|
||||
private readonly NetDataWriter dataWriter = new();
|
||||
private readonly INetworkDebugger networkDebugger;
|
||||
private readonly PacketReceiver packetReceiver;
|
||||
private readonly FieldInfo manualModeFieldInfo = typeof(NetManager).GetField("_manualMode", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
|
||||
public bool IsConnected { get; private set; }
|
||||
public int PingInterval
|
||||
{
|
||||
get => client.PingInterval;
|
||||
set => client.PingInterval = value;
|
||||
}
|
||||
public Action<long> LatencyUpdateCallback;
|
||||
|
||||
public LiteNetLibClient(PacketReceiver packetReceiver, INetworkDebugger networkDebugger = null)
|
||||
{
|
||||
this.packetReceiver = packetReceiver;
|
||||
this.networkDebugger = networkDebugger;
|
||||
EventBasedNetListener listener = new();
|
||||
listener.PeerConnectedEvent += Connected;
|
||||
listener.PeerDisconnectedEvent += Disconnected;
|
||||
listener.NetworkReceiveEvent += ReceivedNetworkData;
|
||||
listener.NetworkLatencyUpdateEvent += (peer, _) =>
|
||||
{
|
||||
LatencyUpdateCallback?.Invoke(peer.RemoteTimeDelta);
|
||||
};
|
||||
|
||||
|
||||
client = new NetManager(listener)
|
||||
{
|
||||
UpdateTime = 15,
|
||||
ChannelsCount = (byte)typeof(Packet.UdpChannelId).GetEnumValues().Length,
|
||||
#if DEBUG
|
||||
DisconnectTimeout = 300000 //Disables Timeout (for 5 min) for debug purpose (like if you jump though the server code)
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
||||
public async Task StartAsync(string ipAddress, int serverPort)
|
||||
{
|
||||
Log.Info("Initializing LiteNetLibClient...");
|
||||
|
||||
// ConfigureAwait(false) is needed because Unity uses a custom "UnitySynchronizationContext". Which makes async/await work like Unity coroutines.
|
||||
// Because this Task.Run is async-over-sync this would otherwise blocks the main thread as it wants to, without ConfigureAwait(false), continue on the same thread (i.e. main thread).
|
||||
await Task.Run(() =>
|
||||
{
|
||||
client.Start();
|
||||
client.Connect(ipAddress, serverPort, "nitrox");
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
connectedEvent.WaitOne(2000);
|
||||
connectedEvent.Reset();
|
||||
}
|
||||
|
||||
public void Send(Packet packet)
|
||||
{
|
||||
byte[] packetData = packet.Serialize();
|
||||
dataWriter.Reset();
|
||||
dataWriter.Put(packetData.Length);
|
||||
dataWriter.Put(packetData);
|
||||
|
||||
networkDebugger?.PacketSent(packet, dataWriter.Length);
|
||||
client.SendToAll(dataWriter, (byte)packet.UdpChannel, NitroxDeliveryMethod.ToLiteNetLib(packet.DeliveryMethod));
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
IsConnected = false;
|
||||
client.Stop();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This should be called <b>once</b> each game tick
|
||||
/// </summary>
|
||||
public void PollEvents() => client.PollEvents();
|
||||
|
||||
private void ReceivedNetworkData(NetPeer peer, NetDataReader reader, byte channel, DeliveryMethod deliveryMethod)
|
||||
{
|
||||
int packetDataLength = reader.GetInt();
|
||||
byte[] packetData = ArrayPool<byte>.Shared.Rent(packetDataLength);
|
||||
try
|
||||
{
|
||||
reader.GetBytes(packetData, packetDataLength);
|
||||
Packet packet = Packet.Deserialize(packetData);
|
||||
packetReceiver.Add(packet);
|
||||
networkDebugger?.PacketReceived(packet, packetDataLength);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(packetData, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void Connected(NetPeer peer)
|
||||
{
|
||||
// IsConnected must happen before Set() so that its state is noticed WHEN we unblock the thread (cf. connectedEvent.WaitOne(...))
|
||||
IsConnected = true;
|
||||
connectedEvent.Set();
|
||||
Log.Info("Connected to server");
|
||||
}
|
||||
|
||||
private void Disconnected(NetPeer peer, DisconnectInfo disconnectInfo)
|
||||
{
|
||||
// Check must happen before IsConnected is set to false, so that it doesn't send an exception when we aren't even ingame
|
||||
if (Multiplayer.Active)
|
||||
{
|
||||
Modal.Get<LostConnectionModal>()?.Show();
|
||||
}
|
||||
|
||||
IsConnected = false;
|
||||
Log.Info("Disconnected from server");
|
||||
}
|
||||
|
||||
internal void ForceUpdate()
|
||||
{
|
||||
int pingInterval = PingInterval;
|
||||
// Set PingInterval to 0 so another ping is sent immediately
|
||||
PingInterval = 0;
|
||||
// ManualUpdate requires the client to have _manualMode set to true so we temporarily do so
|
||||
manualModeFieldInfo.SetValue(client, true);
|
||||
client.ManualUpdate(0);
|
||||
manualModeFieldInfo.SetValue(client, false);
|
||||
// We set it back to its high value so another ping isn't sent while we're waiting for the previous one
|
||||
PingInterval = pingInterval;
|
||||
}
|
||||
}
|
40
NitroxClient/Communication/PacketReceiver.cs
Normal file
40
NitroxClient/Communication/PacketReceiver.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication;
|
||||
|
||||
public class PacketReceiver
|
||||
{
|
||||
private readonly Queue<Packet> receivedPackets = new(16);
|
||||
private readonly object receivedPacketsLock = new();
|
||||
|
||||
public void Add(Packet packet)
|
||||
{
|
||||
lock (receivedPacketsLock)
|
||||
{
|
||||
receivedPackets.Enqueue(packet);
|
||||
}
|
||||
}
|
||||
|
||||
public Packet GetNextPacket()
|
||||
{
|
||||
lock (receivedPacketsLock)
|
||||
{
|
||||
return receivedPackets.Count == 0 ? null : receivedPackets.Dequeue();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies an operation on each packet waiting to be processed and removes it from the queue.
|
||||
/// </summary>
|
||||
public void ConsumePackets<TExtra>(Action<Packet, TExtra> consumer, TExtra extraParameter)
|
||||
{
|
||||
Packet packet = GetNextPacket();
|
||||
while (packet != null)
|
||||
{
|
||||
consumer(packet, extraParameter);
|
||||
packet = GetNextPacket();
|
||||
}
|
||||
}
|
||||
}
|
66
NitroxClient/Communication/PacketSuppressor.cs
Normal file
66
NitroxClient/Communication/PacketSuppressor.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication;
|
||||
|
||||
/// <summary>
|
||||
/// Suppresses the given packet type from being sent. Disables the suppression when disposed.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The packet type to suppress.</typeparam>
|
||||
public readonly struct PacketSuppressor<T> : IDisposable where T : Packet
|
||||
{
|
||||
private static bool isSuppressed;
|
||||
public static bool IsSuppressed => isSuppressed;
|
||||
|
||||
private static readonly PacketSuppressor<T> instance = new();
|
||||
|
||||
public static PacketSuppressor<T> Suppress()
|
||||
{
|
||||
isSuppressed = true;
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
isSuppressed = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="PacketSuppressor{T}"/>
|
||||
/// <typeparam name="T1">First packet type to suppress.</typeparam>
|
||||
/// <typeparam name="T2">Second packet type to suppress.</typeparam>
|
||||
/// <typeparam name="T3">Third packet type to suppress.</typeparam>
|
||||
/// <typeparam name="T4">Fourth packet type to suppress.</typeparam>
|
||||
/// <typeparam name="T5">Fifth packet type to suppress.</typeparam>
|
||||
public readonly struct PacketSuppressor<T1, T2, T3, T4, T5> : IDisposable
|
||||
where T1 : Packet
|
||||
where T2 : Packet
|
||||
where T3 : Packet
|
||||
where T4 : Packet
|
||||
where T5 : Packet
|
||||
{
|
||||
private static readonly PacketSuppressor<T1> instance1 = new();
|
||||
private static readonly PacketSuppressor<T2> instance2 = new();
|
||||
private static readonly PacketSuppressor<T3> instance3 = new();
|
||||
private static readonly PacketSuppressor<T4> instance4 = new();
|
||||
private static readonly PacketSuppressor<T5> instance5 = new();
|
||||
|
||||
public static PacketSuppressor<T1, T2, T3, T4, T5> Suppress()
|
||||
{
|
||||
PacketSuppressor<T1>.Suppress();
|
||||
PacketSuppressor<T2>.Suppress();
|
||||
PacketSuppressor<T3>.Suppress();
|
||||
PacketSuppressor<T4>.Suppress();
|
||||
PacketSuppressor<T5>.Suppress();
|
||||
return new PacketSuppressor<T1, T2, T3, T4, T5>();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
instance1.Dispose();
|
||||
instance2.Dispose();
|
||||
instance3.Dispose();
|
||||
instance4.Dispose();
|
||||
instance5.Dispose();
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxModel.Packets.Processors.Abstract;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors.Abstract
|
||||
{
|
||||
public abstract class ClientPacketProcessor<T> : PacketProcessor where T : Packet
|
||||
{
|
||||
public override void ProcessPacket(Packet packet, IProcessorContext context)
|
||||
{
|
||||
Process((T)packet);
|
||||
}
|
||||
|
||||
public abstract void Process(T packet);
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
|
||||
public class KeepInventoryChangedProcessor : ClientPacketProcessor<KeepInventoryChanged>
|
||||
{
|
||||
private readonly LocalPlayer localPlayer;
|
||||
|
||||
public KeepInventoryChangedProcessor(LocalPlayer localPlayer)
|
||||
{
|
||||
this.localPlayer = localPlayer;
|
||||
}
|
||||
|
||||
public override void Process(KeepInventoryChanged packet)
|
||||
{
|
||||
localPlayer.KeepInventoryOnDeath = packet.KeepInventoryOnDeath;
|
||||
}
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class AggressiveWhenSeeTargetChangedProcessor : ClientPacketProcessor<AggressiveWhenSeeTargetChanged>
|
||||
{
|
||||
public override void Process(AggressiveWhenSeeTargetChanged packet)
|
||||
{
|
||||
AI.AggressiveWhenSeeTargetChanged(packet.CreatureId, packet.TargetId, packet.Locked, packet.AggressionAmount);
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors
|
||||
{
|
||||
public class AnimationProcessor : ClientPacketProcessor<AnimationChangeEvent>
|
||||
{
|
||||
private readonly PlayerManager remotePlayerManager;
|
||||
|
||||
public AnimationProcessor(PlayerManager remotePlayerManager)
|
||||
{
|
||||
this.remotePlayerManager = remotePlayerManager;
|
||||
}
|
||||
|
||||
public override void Process(AnimationChangeEvent animEvent)
|
||||
{
|
||||
Optional<RemotePlayer> opPlayer = remotePlayerManager.Find(animEvent.PlayerId);
|
||||
if (opPlayer.HasValue)
|
||||
{
|
||||
opPlayer.Value.UpdateAnimationAndCollider((AnimChangeType)animEvent.Type, (AnimChangeState)animEvent.State);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class AttackCyclopsTargetChangedProcessor : ClientPacketProcessor<AttackCyclopsTargetChanged>
|
||||
{
|
||||
public override void Process(AttackCyclopsTargetChanged packet)
|
||||
{
|
||||
AI.AttackCyclopsTargetChanged(packet.CreatureId, packet.TargetId, packet.AggressiveToNoiseAmount);
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class AuroraAndTimeUpdateProcessor : ClientPacketProcessor<AuroraAndTimeUpdate>
|
||||
{
|
||||
private readonly TimeManager timeManager;
|
||||
|
||||
public AuroraAndTimeUpdateProcessor(TimeManager timeManager)
|
||||
{
|
||||
this.timeManager = timeManager;
|
||||
}
|
||||
|
||||
public override void Process(AuroraAndTimeUpdate packet)
|
||||
{
|
||||
timeManager.ProcessUpdate(packet.TimeData.TimePacket);
|
||||
StoryManager.UpdateAuroraData(packet.TimeData.AuroraEventData);
|
||||
timeManager.AuroraRealExplosionTime = packet.TimeData.AuroraEventData.AuroraRealExplosionTime;
|
||||
if (packet.Restore)
|
||||
{
|
||||
StoryManager.RestoreAurora();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors
|
||||
{
|
||||
class BedEnterProcessor : ClientPacketProcessor<BedEnter>
|
||||
{
|
||||
public override void Process(BedEnter packet)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic.Bases;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public abstract class BuildProcessor<T> : ClientPacketProcessor<T> where T : Packet
|
||||
{
|
||||
public override void Process(T packet)
|
||||
{
|
||||
BuildingHandler.Main.BuildQueue.Enqueue(packet);
|
||||
}
|
||||
}
|
||||
|
||||
public class PlaceGhostProcessor : BuildProcessor<PlaceGhost> { }
|
||||
|
||||
public class PlaceModuleProcessor : BuildProcessor<PlaceModule> { }
|
||||
|
||||
public class ModifyConstructedAmountProcessor : BuildProcessor<ModifyConstructedAmount> { }
|
||||
|
||||
public class PlaceBaseProcessor : BuildProcessor<PlaceBase> { }
|
||||
|
||||
public class UpdateBaseProcessor : BuildProcessor<UpdateBase> { }
|
||||
|
||||
public class BaseDeconstructedProcessor : BuildProcessor<BaseDeconstructed> { }
|
||||
|
||||
public class PieceDeconstructedProcessor : BuildProcessor<PieceDeconstructed> { }
|
||||
|
||||
public class WaterParkDeconstructedProcessor : BuildProcessor<WaterParkDeconstructed> { }
|
||||
|
||||
public class LargeWaterParkDeconstructedProcessor : BuildProcessor<LargeWaterParkDeconstructed> { }
|
@@ -0,0 +1,31 @@
|
||||
using System.Collections.Generic;
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic.Bases;
|
||||
using NitroxClient.GameLogic.Settings;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class BuildingDesyncWarningProcessor : ClientPacketProcessor<BuildingDesyncWarning>
|
||||
{
|
||||
public override void Process(BuildingDesyncWarning packet)
|
||||
{
|
||||
if (!BuildingHandler.Main)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<NitroxId, int> operation in packet.Operations)
|
||||
{
|
||||
OperationTracker tracker = BuildingHandler.Main.EnsureTracker(operation.Key);
|
||||
tracker.LastOperationId = operation.Value;
|
||||
tracker.FailedOperations++;
|
||||
}
|
||||
|
||||
if (NitroxPrefs.SafeBuildingLog.Value)
|
||||
{
|
||||
Log.InGame(Language.main.Get("Nitrox_BuildingDesyncDetected"));
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,179 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxClient.GameLogic.Bases;
|
||||
using NitroxClient.GameLogic.Spawning.Bases;
|
||||
using NitroxClient.GameLogic.Spawning.Metadata;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxClient.Unity.Helper;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities.Bases;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class BuildingResyncProcessor : ClientPacketProcessor<BuildingResync>
|
||||
{
|
||||
private readonly Entities entities;
|
||||
private readonly EntityMetadataManager entityMetadataManager;
|
||||
|
||||
public BuildingResyncProcessor(Entities entities, EntityMetadataManager entityMetadataManager)
|
||||
{
|
||||
this.entities = entities;
|
||||
this.entityMetadataManager = entityMetadataManager;
|
||||
}
|
||||
|
||||
public override void Process(BuildingResync packet)
|
||||
{
|
||||
if (!BuildingHandler.Main)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
BuildingHandler.Main.StartCoroutine(ResyncBuildingEntities(packet.BuildEntities, packet.ModuleEntities));
|
||||
}
|
||||
|
||||
public IEnumerator ResyncBuildingEntities(Dictionary<BuildEntity, int> buildEntities, Dictionary<ModuleEntity, int> moduleEntities)
|
||||
{
|
||||
Stopwatch stopwatch = Stopwatch.StartNew();
|
||||
BuildingHandler.Main.StartResync(buildEntities);
|
||||
yield return UpdateEntities<Base, BuildEntity>(buildEntities.Keys.ToList(), OverwriteBase, IsInCloseProximity).OnYieldError(exception => Log.Error(exception, $"Encountered an exception while resyncing BuildEntities"));
|
||||
|
||||
BuildingHandler.Main.StartResync(moduleEntities);
|
||||
yield return UpdateEntities<Constructable, ModuleEntity>(moduleEntities.Keys.ToList(), OverwriteModule, IsInCloseProximity).OnYieldError(exception => Log.Error(exception, $"Encountered an exception while resyncing ModuleEntities"));
|
||||
BuildingHandler.Main.StopResync();
|
||||
|
||||
stopwatch.Stop();
|
||||
|
||||
int totalEntities = buildEntities.Count + moduleEntities.Count;
|
||||
Log.InGame(Language.main.Get("Nitrox_FinishedResyncRequest").Replace("{TIME}", stopwatch.ElapsedMilliseconds.ToString()).Replace("{COUNT}", totalEntities.ToString()));
|
||||
}
|
||||
|
||||
private bool IsInCloseProximity<C>(WorldEntity entity, C componentInWorld) where C : Component
|
||||
{
|
||||
return Vector3.Distance(entity.Transform.Position.ToUnity(), componentInWorld.transform.position) < 0.001f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to overwrite components of the provided type found in GlobalRoot's hierarchy by the provided list of entities to update.
|
||||
/// If no component is found to be corresponding to a provided entity, the entity will be spawned independently.
|
||||
/// Other components of the provided type which weren't updated shall be destroyed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The provided list is modified by the function. Make sure it's not used somewhere else.
|
||||
/// </remarks>
|
||||
/// <typeparam name="C">The Unity component to be looked for</typeparam>
|
||||
/// <typeparam name="E">The GlobalRootEntity type which will be updated</typeparam>
|
||||
/// <param name="overwrite">A function to overwrite a given component by a given entity</param>
|
||||
/// <param name="correspondingPredicate">
|
||||
/// Predicate to determine if an entity can overwrite the GameObject of the provided component.
|
||||
/// </param>
|
||||
public IEnumerator UpdateEntities<C,E>(List<E> entitiesToUpdate, Func<C, E, IEnumerator> overwrite, Func<E, C, bool> correspondingPredicate) where C : Component where E : GlobalRootEntity
|
||||
{
|
||||
List<C> unmarkedComponents = new();
|
||||
Dictionary<NitroxId, E> entitiesToUpdateById = entitiesToUpdate.ToDictionary(e => e.Id);
|
||||
|
||||
foreach (Transform childTransform in LargeWorldStreamer.main.globalRoot.transform)
|
||||
{
|
||||
if (childTransform.TryGetComponent(out C component))
|
||||
{
|
||||
if (component.TryGetNitroxId(out NitroxId id) && entitiesToUpdateById.TryGetValue(id, out E correspondingEntity))
|
||||
{
|
||||
yield return overwrite(component, correspondingEntity).OnYieldError(Log.Error);
|
||||
entitiesToUpdate.Remove(correspondingEntity);
|
||||
continue;
|
||||
}
|
||||
unmarkedComponents.Add(component);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = entitiesToUpdate.Count - 1; i >= 0; i--)
|
||||
{
|
||||
E entity = entitiesToUpdate[i];
|
||||
C associatedComponent = unmarkedComponents.Find(c =>
|
||||
correspondingPredicate(entity, c));
|
||||
yield return overwrite(associatedComponent, entity).OnYieldError(Log.Error);
|
||||
|
||||
unmarkedComponents.Remove(associatedComponent);
|
||||
entitiesToUpdate.RemoveAt(i);
|
||||
}
|
||||
|
||||
for (int i = unmarkedComponents.Count - 1; i >= 0; i--)
|
||||
{
|
||||
Log.Info($"[{typeof(E)} RESYNC] Destroyed GameObject {unmarkedComponents[i].gameObject}");
|
||||
GameObject.Destroy(unmarkedComponents[i].gameObject);
|
||||
}
|
||||
foreach (E entity in entitiesToUpdate)
|
||||
{
|
||||
Log.Info($"[{typeof(E)} RESYNC] spawning entity {entity.Id}");
|
||||
yield return entities.SpawnEntityAsync(entity).OnYieldError(Log.Error);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator OverwriteBase(Base @base, BuildEntity buildEntity)
|
||||
{
|
||||
Log.Info($"[Base RESYNC] Overwriting base with id {buildEntity.Id}");
|
||||
ClearBaseChildren(@base);
|
||||
// Frame to let all children be deleted properly
|
||||
yield return Yielders.WaitForEndOfFrame;
|
||||
|
||||
yield return BuildEntitySpawner.SetupBase(buildEntity, @base, entities);
|
||||
yield return MoonpoolManager.RestoreMoonpools(buildEntity.ChildEntities.OfType<MoonpoolEntity>(), @base);
|
||||
yield return entities.SpawnBatchAsync(buildEntity.ChildEntities.OfType<PlayerWorldEntity>().ToList<Entity>(), false, false);
|
||||
|
||||
foreach (Entity childEntity in buildEntity.ChildEntities)
|
||||
{
|
||||
switch (childEntity)
|
||||
{
|
||||
case MapRoomEntity mapRoomEntity:
|
||||
yield return InteriorPieceEntitySpawner.RestoreMapRoom(@base, mapRoomEntity);
|
||||
break;
|
||||
case BaseLeakEntity baseLeakEntity:
|
||||
yield return entities.SpawnEntityAsync(baseLeakEntity, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator OverwriteModule(Constructable constructable, ModuleEntity moduleEntity)
|
||||
{
|
||||
Log.Info($"[Module RESYNC] Overwriting module with id {moduleEntity.Id}");
|
||||
ModuleEntitySpawner.ApplyModuleData(moduleEntity, constructable.gameObject);
|
||||
entityMetadataManager.ApplyMetadata(constructable.gameObject, moduleEntity.Metadata);
|
||||
yield break;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroys manually ghosts, modules, interior pieces and vehicles of a base
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is the destructive way of clearing the base, if the base isn't modified consequently, IBaseModuleGeometry under the base cells may start spamming errors.
|
||||
/// </remarks>
|
||||
public static void ClearBaseChildren(Base @base)
|
||||
{
|
||||
for (int i = @base.transform.childCount - 1; i >= 0; i--)
|
||||
{
|
||||
Transform child = @base.transform.GetChild(i);
|
||||
if (child.GetComponent<IBaseModule>().AliveOrNull() || child.GetComponent<Constructable>())
|
||||
{
|
||||
UnityEngine.Object.Destroy(child.gameObject);
|
||||
}
|
||||
}
|
||||
foreach (VehicleDockingBay vehicleDockingBay in @base.GetComponentsInChildren<VehicleDockingBay>(true))
|
||||
{
|
||||
if (vehicleDockingBay.dockedVehicle)
|
||||
{
|
||||
UnityEngine.Object.Destroy(vehicleDockingBay.dockedVehicle.gameObject);
|
||||
vehicleDockingBay.SetVehicleUndocked();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxClient.GameLogic.ChatUI;
|
||||
using NitroxClient.GameLogic.Settings;
|
||||
using NitroxModel.DataStructures.Unity;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors
|
||||
{
|
||||
class ChatMessageProcessor : ClientPacketProcessor<ChatMessage>
|
||||
{
|
||||
private readonly PlayerManager remotePlayerManager;
|
||||
private readonly LocalPlayer localPlayer;
|
||||
private readonly PlayerChatManager playerChatManager;
|
||||
|
||||
private readonly Color32 serverMessageColor = new Color32(0x8c, 0x00, 0xFF, 0xFF);
|
||||
|
||||
public ChatMessageProcessor(PlayerManager remotePlayerManager, LocalPlayer localPlayer, PlayerChatManager playerChatManager)
|
||||
{
|
||||
this.remotePlayerManager = remotePlayerManager;
|
||||
this.localPlayer = localPlayer;
|
||||
this.playerChatManager = playerChatManager;
|
||||
}
|
||||
|
||||
public override void Process(ChatMessage message)
|
||||
{
|
||||
if (message.PlayerId != ChatMessage.SERVER_ID)
|
||||
{
|
||||
LogClientMessage(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogServerMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
private void LogClientMessage(ChatMessage message)
|
||||
{
|
||||
// The message can come from either the local player or other players
|
||||
string playerName;
|
||||
NitroxColor color;
|
||||
if (localPlayer.PlayerId == message.PlayerId)
|
||||
{
|
||||
playerName = localPlayer.PlayerName;
|
||||
color = localPlayer.PlayerSettings.PlayerColor;
|
||||
}
|
||||
else
|
||||
{
|
||||
Optional<RemotePlayer> remotePlayer = remotePlayerManager.Find(message.PlayerId);
|
||||
if (!remotePlayer.HasValue)
|
||||
{
|
||||
string playerTableFormatted = string.Join("\n", remotePlayerManager.GetAll().Select(ply => $"Name: '{ply.PlayerName}', Id: {ply.PlayerId}"));
|
||||
Log.Error($"Tried to add chat message for remote player that could not be found with id '${message.PlayerId}' and message: '{message.Text}'.\nAll remote players right now:\n{playerTableFormatted}");
|
||||
throw new Exception($"Tried to add chat message for remote player that could not be found with id '${message.PlayerId}' and message: '{message.Text}'.\nAll remote players right now:\n{playerTableFormatted}");
|
||||
}
|
||||
|
||||
playerName = remotePlayer.Value.PlayerName;
|
||||
color = remotePlayer.Value.PlayerSettings.PlayerColor;
|
||||
}
|
||||
|
||||
playerChatManager.AddMessage(playerName, message.Text, color.ToUnity());
|
||||
if (!NitroxPrefs.SilenceChat.Value)
|
||||
{
|
||||
playerChatManager.ShowChat();
|
||||
}
|
||||
}
|
||||
|
||||
private void LogServerMessage(ChatMessage message)
|
||||
{
|
||||
playerChatManager.AddMessage("Server", message.Text, serverMessageColor);
|
||||
if (!NitroxPrefs.SilenceChat.Value)
|
||||
{
|
||||
playerChatManager.ShowChat();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class CreatureActionProcessor : ClientPacketProcessor<CreatureActionChanged>
|
||||
{
|
||||
private readonly AI ai;
|
||||
|
||||
public CreatureActionProcessor(AI ai)
|
||||
{
|
||||
this.ai = ai;
|
||||
}
|
||||
|
||||
public override void Process(CreatureActionChanged packet)
|
||||
{
|
||||
ai.CreatureActionChanged(packet.CreatureId, packet.CreatureActionType);
|
||||
}
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class CreaturePoopPerformedProcessor : ClientPacketProcessor<CreaturePoopPerformed>
|
||||
{
|
||||
public override void Process(CreaturePoopPerformed packet)
|
||||
{
|
||||
AI.CreaturePoopPerformed(packet.CreatureId);
|
||||
}
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxClient.Unity.Helper;
|
||||
using NitroxModel_Subnautica.Packets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors
|
||||
{
|
||||
public class CyclopsDamagePointHealthChangedProcessor : ClientPacketProcessor<CyclopsDamagePointRepaired>
|
||||
{
|
||||
private readonly IPacketSender packetSender;
|
||||
|
||||
public CyclopsDamagePointHealthChangedProcessor(IPacketSender packetSender)
|
||||
{
|
||||
this.packetSender = packetSender;
|
||||
}
|
||||
|
||||
public override void Process(CyclopsDamagePointRepaired packet)
|
||||
{
|
||||
GameObject gameObject = NitroxEntity.RequireObjectFrom(packet.Id);
|
||||
SubRoot cyclops = gameObject.RequireComponent<SubRoot>();
|
||||
|
||||
using (PacketSuppressor<CyclopsDamage>.Suppress())
|
||||
using (PacketSuppressor<CyclopsDamagePointRepaired>.Suppress())
|
||||
{
|
||||
cyclops.damageManager.damagePoints[packet.DamagePointIndex].liveMixin.AddHealth(packet.RepairAmount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,202 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxClient.Unity.Helper;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxModel_Subnautica.DataStructures.GameLogic;
|
||||
using NitroxModel_Subnautica.Packets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors
|
||||
{
|
||||
/// <summary>
|
||||
/// Add/remove <see cref="CyclopsDamagePoint"/>s and <see cref="Fire"/>s to match the <see cref="CyclopsDamage"/> packet received
|
||||
/// </summary>
|
||||
public class CyclopsDamageProcessor : ClientPacketProcessor<CyclopsDamage>
|
||||
{
|
||||
private readonly IPacketSender packetSender;
|
||||
private readonly Fires fires;
|
||||
|
||||
public CyclopsDamageProcessor(IPacketSender packetSender, Fires fires)
|
||||
{
|
||||
this.packetSender = packetSender;
|
||||
this.fires = fires;
|
||||
}
|
||||
|
||||
public override void Process(CyclopsDamage packet)
|
||||
{
|
||||
SubRoot subRoot = NitroxEntity.RequireObjectFrom(packet.Id).GetComponent<SubRoot>();
|
||||
|
||||
using (PacketSuppressor<CyclopsDamagePointRepaired>.Suppress())
|
||||
{
|
||||
SetActiveDamagePoints(subRoot, packet.DamagePointIndexes);
|
||||
}
|
||||
|
||||
using (PacketSuppressor<FireDoused>.Suppress())
|
||||
{
|
||||
SetActiveRoomFires(subRoot, packet.RoomFires);
|
||||
}
|
||||
|
||||
LiveMixin subHealth = subRoot.gameObject.RequireComponent<LiveMixin>();
|
||||
|
||||
float oldHPPercent = subRoot.oldHPPercent;
|
||||
|
||||
// Client side noises. Not necessary for keeping the health synced
|
||||
if (subHealth.GetHealthFraction() < 0.5f && oldHPPercent >= 0.5f)
|
||||
{
|
||||
subRoot.voiceNotificationManager.PlayVoiceNotification(subRoot.hullLowNotification, true, false);
|
||||
}
|
||||
else if (subHealth.GetHealthFraction() < 0.25f && oldHPPercent >= 0.25f)
|
||||
{
|
||||
subRoot.voiceNotificationManager.PlayVoiceNotification(subRoot.hullCriticalNotification, true, false);
|
||||
}
|
||||
|
||||
using (PacketSuppressor<CyclopsDamage>.Suppress())
|
||||
{
|
||||
// Not necessary, but used by above code whenever damage is done
|
||||
subRoot.oldHPPercent = subHealth.GetHealthFraction();
|
||||
|
||||
// Apply the actual health changes
|
||||
subRoot.gameObject.RequireComponent<LiveMixin>().health = packet.SubHealth;
|
||||
subRoot.gameObject.RequireComponentInChildren<CyclopsExternalDamageManager>().subLiveMixin.health = packet.DamageManagerHealth;
|
||||
subRoot.gameObject.RequireComponent<SubFire>().liveMixin.health = packet.SubFireHealth;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add/remove <see cref="CyclopsDamagePoint"/>s until it matches the <paramref name="damagePointIndexes"/> array passed. Can trigger <see cref="CyclopsDamagePointRepaired"/> packets
|
||||
/// </summary>
|
||||
private void SetActiveDamagePoints(SubRoot cyclops, int[] damagePointIndexes)
|
||||
{
|
||||
CyclopsExternalDamageManager damageManager = cyclops.gameObject.RequireComponentInChildren<CyclopsExternalDamageManager>();
|
||||
List<CyclopsDamagePoint> unusedDamagePoints = damageManager.unusedDamagePoints;
|
||||
|
||||
// CyclopsExternalDamageManager.damagePoints is an unchanged list. It will never have items added/removed from it. Since packet.DamagePointIndexes is also an array
|
||||
// generated in an ordered manner, we can match them without worrying about unordered items.
|
||||
if (damagePointIndexes != null && damagePointIndexes.Length > 0)
|
||||
{
|
||||
int packetDamagePointsIndex = 0;
|
||||
|
||||
for (int damagePointsIndex = 0; damagePointsIndex < damageManager.damagePoints.Length; damagePointsIndex++)
|
||||
{
|
||||
// Loop over all of the packet.DamagePointIndexes as long as there's more to match
|
||||
if (packetDamagePointsIndex < damagePointIndexes.Length
|
||||
&& damagePointIndexes[packetDamagePointsIndex] == damagePointsIndex)
|
||||
{
|
||||
if (!damageManager.damagePoints[damagePointsIndex].gameObject.activeSelf)
|
||||
{
|
||||
// Copied from CyclopsExternalDamageManager.CreatePoint(), except without the random index pick.
|
||||
damageManager.damagePoints[damagePointsIndex].gameObject.SetActive(true);
|
||||
damageManager.damagePoints[damagePointsIndex].RestoreHealth();
|
||||
GameObject prefabGo = damageManager.fxPrefabs[UnityEngine.Random.Range(0, damageManager.fxPrefabs.Length - 1)];
|
||||
damageManager.damagePoints[damagePointsIndex].SpawnFx(prefabGo);
|
||||
unusedDamagePoints.Remove(damageManager.damagePoints[damagePointsIndex]);
|
||||
}
|
||||
|
||||
packetDamagePointsIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If it's active, but not in the list, it must have been repaired.
|
||||
if (damageManager.damagePoints[damagePointsIndex].gameObject.activeSelf)
|
||||
{
|
||||
RepairDamagePoint(cyclops, damagePointsIndex, 999);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Looks like the list came in unordered. I've uttered "That shouldn't happen" enough to do sanity checks for what should be impossible.
|
||||
if (packetDamagePointsIndex < damagePointIndexes.Length)
|
||||
{
|
||||
Log.Error($"[CyclopsDamageProcessor packet.DamagePointIds did not fully iterate! Id: {damagePointIndexes[packetDamagePointsIndex]} had no matching Id in damageManager.damagePoints, or the order is incorrect!]");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// None should be active.
|
||||
for (int i = 0; i < damageManager.damagePoints.Length; i++)
|
||||
{
|
||||
if (damageManager.damagePoints[i].gameObject.activeSelf)
|
||||
{
|
||||
RepairDamagePoint(cyclops, i, 999);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// unusedDamagePoints is checked against damagePoints to determine if there's enough damage points. Failing to set the new list
|
||||
// of unusedDamagePoints will cause random DamagePoints to appear.
|
||||
damageManager.unusedDamagePoints = unusedDamagePoints;
|
||||
// Visual update only to show the water leaking through the window and various hull points based on missing health.
|
||||
damageManager.ToggleLeakPointsBasedOnDamage();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add/remove fires until it matches the <paramref name="roomFires"/> array. Can trigger <see cref="FireDoused"/> packets
|
||||
/// </summary>
|
||||
private void SetActiveRoomFires(SubRoot subRoot, CyclopsFireData[] roomFires)
|
||||
{
|
||||
SubFire subFire = subRoot.gameObject.RequireComponent<SubFire>();
|
||||
Dictionary<CyclopsRooms, SubFire.RoomFire> roomFiresDict = subFire.roomFires;
|
||||
|
||||
if (!subRoot.TryGetIdOrWarn(out NitroxId subRootId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (roomFires != null && roomFires.Length > 0)
|
||||
{
|
||||
// Removing and adding fires will happen in the same loop
|
||||
foreach (KeyValuePair<CyclopsRooms, SubFire.RoomFire> keyValuePair in roomFiresDict)
|
||||
{
|
||||
for (int nodeIndex = 0; nodeIndex < keyValuePair.Value.spawnNodes.Length; nodeIndex++)
|
||||
{
|
||||
CyclopsFireData fireNode = roomFires.SingleOrDefault(x => x.Room == keyValuePair.Key && x.NodeIndex == nodeIndex);
|
||||
|
||||
// If there's a matching node index, add a fire if there isn't one already. Otherwise remove a fire if there is one
|
||||
if (fireNode == null)
|
||||
{
|
||||
if (keyValuePair.Value.spawnNodes[nodeIndex].childCount > 0)
|
||||
{
|
||||
keyValuePair.Value.spawnNodes[nodeIndex].GetComponentInChildren<Fire>().Douse(10000);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (keyValuePair.Value.spawnNodes[nodeIndex].childCount < 1)
|
||||
{
|
||||
fires.Create(new CyclopsFireData(fireNode.FireId, subRootId, fireNode.Room, fireNode.NodeIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Clear out the fires, there should be none active
|
||||
else
|
||||
{
|
||||
foreach (KeyValuePair<CyclopsRooms, SubFire.RoomFire> keyValuePair in roomFiresDict)
|
||||
{
|
||||
foreach (Transform spawnNode in keyValuePair.Value.spawnNodes)
|
||||
{
|
||||
if (spawnNode.childCount > 0)
|
||||
{
|
||||
spawnNode.GetComponentInChildren<Fire>().Douse(10000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the health of a <see cref="CyclopsDamagePoint"/>. This can trigger sending <see cref="CyclopsDamagePointRepaired"/> packets
|
||||
/// </summary>
|
||||
/// <param name="repairAmount">The max health of the point is 1. 999 is passed to trigger a full repair of the <see cref="CyclopsDamagePoint"/></param>
|
||||
private void RepairDamagePoint(SubRoot subRoot, int damagePointIndex, float repairAmount)
|
||||
{
|
||||
subRoot.damageManager.damagePoints[damagePointIndex].liveMixin.AddHealth(repairAmount);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel_Subnautica.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors
|
||||
{
|
||||
class CyclopsDecoyLaunchProcessor : ClientPacketProcessor<CyclopsDecoyLaunch>
|
||||
{
|
||||
private readonly IPacketSender packetSender;
|
||||
private readonly Cyclops cyclops;
|
||||
|
||||
public CyclopsDecoyLaunchProcessor(IPacketSender packetSender, Cyclops cyclops)
|
||||
{
|
||||
this.packetSender = packetSender;
|
||||
this.cyclops = cyclops;
|
||||
}
|
||||
|
||||
public override void Process(CyclopsDecoyLaunch decoyLaunchPacket)
|
||||
{
|
||||
cyclops.LaunchDecoy(decoyLaunchPacket.Id);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel_Subnautica.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors
|
||||
{
|
||||
public class CyclopsFireCreatedProcessor : ClientPacketProcessor<CyclopsFireCreated>
|
||||
{
|
||||
private readonly IPacketSender packetSender;
|
||||
private readonly Fires fires;
|
||||
|
||||
public CyclopsFireCreatedProcessor(IPacketSender packetSender, Fires fires)
|
||||
{
|
||||
this.packetSender = packetSender;
|
||||
this.fires = fires;
|
||||
}
|
||||
|
||||
public override void Process(CyclopsFireCreated packet)
|
||||
{
|
||||
fires.Create(packet.FireCreatedData);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel_Subnautica.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors
|
||||
{
|
||||
public class CyclopsFireSuppressionProcessor : ClientPacketProcessor<CyclopsFireSuppression>
|
||||
{
|
||||
private readonly IPacketSender packetSender;
|
||||
private readonly Cyclops cyclops;
|
||||
|
||||
public CyclopsFireSuppressionProcessor(IPacketSender packetSender, Cyclops cyclops)
|
||||
{
|
||||
this.packetSender = packetSender;
|
||||
this.cyclops = cyclops;
|
||||
}
|
||||
|
||||
public override void Process(CyclopsFireSuppression fireSuppressionPacket)
|
||||
{
|
||||
cyclops.StartFireSuppression(fireSuppressionPacket.Id);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxModel.DataStructures.Unity;
|
||||
using NitroxModel.Packets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors
|
||||
{
|
||||
class DebugStartMapProcessor : ClientPacketProcessor<DebugStartMapPacket>
|
||||
{
|
||||
public override void Process(DebugStartMapPacket packet)
|
||||
{
|
||||
foreach (NitroxVector3 position in packet.StartPositions)
|
||||
{
|
||||
GameObject prim = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||||
prim.transform.position = new Vector3(position.X, position.Y + 10, position.Z);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic.Helper;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.Packets;
|
||||
using UnityEngine;
|
||||
using static NitroxClient.GameLogic.Helper.TransientLocalObjectManager;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors
|
||||
{
|
||||
public class DeconstructionBeginProcessor : ClientPacketProcessor<DeconstructionBegin>
|
||||
{
|
||||
private readonly IPacketSender packetSender;
|
||||
|
||||
public DeconstructionBeginProcessor(IPacketSender packetSender)
|
||||
{
|
||||
this.packetSender = packetSender;
|
||||
}
|
||||
|
||||
public override void Process(DeconstructionBegin packet)
|
||||
{
|
||||
Log.Info($"Received deconstruction packet for id: {packet.Id}");
|
||||
|
||||
GameObject deconstructing = NitroxEntity.RequireObjectFrom(packet.Id);
|
||||
|
||||
Constructable constructable = deconstructing.GetComponent<Constructable>();
|
||||
BaseDeconstructable baseDeconstructable = deconstructing.GetComponent<BaseDeconstructable>();
|
||||
|
||||
using (PacketSuppressor<DeconstructionBegin>.Suppress())
|
||||
{
|
||||
if (baseDeconstructable != null)
|
||||
{
|
||||
TransientLocalObjectManager.Add(TransientObjectType.LATEST_DECONSTRUCTED_BASE_PIECE_GUID, packet.Id);
|
||||
baseDeconstructable.Deconstruct();
|
||||
}
|
||||
else if (constructable != null)
|
||||
{
|
||||
constructable.SetState(false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxClient.GameLogic.HUD;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors
|
||||
{
|
||||
class DisconnectProcessor : ClientPacketProcessor<Disconnect>
|
||||
{
|
||||
private readonly PlayerManager remotePlayerManager;
|
||||
private readonly PlayerVitalsManager vitalsManager;
|
||||
|
||||
public DisconnectProcessor(PlayerManager remotePlayerManager, PlayerVitalsManager vitalsManager)
|
||||
{
|
||||
this.remotePlayerManager = remotePlayerManager;
|
||||
this.vitalsManager = vitalsManager;
|
||||
}
|
||||
|
||||
public override void Process(Disconnect disconnect)
|
||||
{
|
||||
// TODO: don't remove right away... maybe grey out and start
|
||||
// a coroutine to finally remove.
|
||||
vitalsManager.RemoveForPlayer(disconnect.PlayerId);
|
||||
|
||||
Optional<RemotePlayer> remotePlayer = remotePlayerManager.Find(disconnect.PlayerId);
|
||||
if (remotePlayer.HasValue)
|
||||
{
|
||||
remotePlayer.Value.PlayerDisconnectEvent.Trigger(remotePlayer.Value);
|
||||
remotePlayerManager.RemovePlayer(disconnect.PlayerId);
|
||||
Log.Info($"{remotePlayer.Value.PlayerName} disconnected");
|
||||
Log.InGame(Language.main.Get("Nitrox_PlayerDisconnected").Replace("{PLAYER}", remotePlayer.Value.PlayerName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.MonoBehaviours.Discord;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class DiscordRequestIPProcessor : ClientPacketProcessor<DiscordRequestIP>
|
||||
{
|
||||
public override void Process(DiscordRequestIP packet)
|
||||
{
|
||||
DiscordClient.UpdateIpPort(packet.IpPort);
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class DropSimulationOwnershipProcessor : ClientPacketProcessor<DropSimulationOwnership>
|
||||
{
|
||||
private readonly SimulationOwnership simulationOwnershipManager;
|
||||
|
||||
public DropSimulationOwnershipProcessor(SimulationOwnership simulationOwnershipManager)
|
||||
{
|
||||
this.simulationOwnershipManager = simulationOwnershipManager;
|
||||
}
|
||||
|
||||
public override void Process(DropSimulationOwnership packet)
|
||||
{
|
||||
simulationOwnershipManager.DropSimulationFrom(packet.EntityId);
|
||||
}
|
||||
}
|
@@ -0,0 +1,95 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxClient.GameLogic.PlayerLogic;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.Packets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class EntityDestroyedProcessor : ClientPacketProcessor<EntityDestroyed>
|
||||
{
|
||||
public const DamageType DAMAGE_TYPE_RUN_ORIGINAL = (DamageType)100;
|
||||
|
||||
private readonly Entities entities;
|
||||
|
||||
public EntityDestroyedProcessor(Entities entities)
|
||||
{
|
||||
this.entities = entities;
|
||||
}
|
||||
|
||||
public override void Process(EntityDestroyed packet)
|
||||
{
|
||||
entities.RemoveEntity(packet.Id);
|
||||
if (!NitroxEntity.TryGetObjectFrom(packet.Id, out GameObject gameObject))
|
||||
{
|
||||
entities.MarkForDeletion(packet.Id);
|
||||
Log.Warn($"[{nameof(EntityDestroyedProcessor)}] Could not find entity with id: {packet.Id} to destroy.");
|
||||
return;
|
||||
}
|
||||
|
||||
using (PacketSuppressor<EntityDestroyed>.Suppress())
|
||||
{
|
||||
// This type of check could get out of control if there are many types with custom destroy logic. If we get a few more, move to separate processors.
|
||||
if (gameObject.TryGetComponent(out Vehicle vehicle))
|
||||
{
|
||||
DestroyVehicle(vehicle);
|
||||
}
|
||||
else if (gameObject.TryGetComponent(out SubRoot subRoot))
|
||||
{
|
||||
DestroySubroot(subRoot);
|
||||
}
|
||||
else
|
||||
{
|
||||
Entities.DestroyObject(gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DestroySubroot(SubRoot subRoot)
|
||||
{
|
||||
DamageInfo damageInfo = new() { type = DAMAGE_TYPE_RUN_ORIGINAL };
|
||||
if (subRoot.live.health > 0f)
|
||||
{
|
||||
// oldHPPercent must be in the interval [0; 0.25[ because else, SubRoot.OnTakeDamage will end up in the wrong else condition
|
||||
subRoot.oldHPPercent = 0f;
|
||||
subRoot.live.health = 0f;
|
||||
subRoot.live.NotifyAllAttachedDamageReceivers(damageInfo);
|
||||
subRoot.live.Kill();
|
||||
}
|
||||
|
||||
// We use a specific DamageType so that the Prefix on this method will accept this call
|
||||
subRoot.OnTakeDamage(damageInfo);
|
||||
}
|
||||
|
||||
private void DestroyVehicle(Vehicle vehicle)
|
||||
{
|
||||
if (vehicle.GetPilotingMode()) //Check Local Object Have Player inside
|
||||
{
|
||||
vehicle.OnPilotModeEnd();
|
||||
|
||||
if (!Player.main.ToNormalMode(true))
|
||||
{
|
||||
Player.main.ToNormalMode(false);
|
||||
Player.main.transform.parent = null;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (RemotePlayerIdentifier identifier in vehicle.GetComponentsInChildren<RemotePlayerIdentifier>(true))
|
||||
{
|
||||
identifier.RemotePlayer.ResetStates();
|
||||
}
|
||||
|
||||
if (vehicle.gameObject)
|
||||
{
|
||||
if (vehicle.destructionEffect)
|
||||
{
|
||||
GameObject gameObject = Object.Instantiate(vehicle.destructionEffect);
|
||||
gameObject.transform.position = vehicle.transform.position;
|
||||
gameObject.transform.rotation = vehicle.transform.rotation;
|
||||
}
|
||||
|
||||
Object.Destroy(vehicle.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic.Spawning.Metadata;
|
||||
using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxModel.Packets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class EntityMetadataUpdateProcessor : ClientPacketProcessor<EntityMetadataUpdate>
|
||||
{
|
||||
private readonly EntityMetadataManager entityMetadataManager;
|
||||
|
||||
public EntityMetadataUpdateProcessor(EntityMetadataManager entityMetadataManager)
|
||||
{
|
||||
this.entityMetadataManager = entityMetadataManager;
|
||||
}
|
||||
|
||||
public override void Process(EntityMetadataUpdate update)
|
||||
{
|
||||
if (!NitroxEntity.TryGetObjectFrom(update.Id, out GameObject gameObject))
|
||||
{
|
||||
entityMetadataManager.RegisterNewerMetadata(update.Id, update.NewValue);
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<IEntityMetadataProcessor> metadataProcessor = entityMetadataManager.FromMetaData(update.NewValue);
|
||||
Validate.IsTrue(metadataProcessor.HasValue, $"No processor found for EntityMetadata of type {update.NewValue.GetType()}");
|
||||
|
||||
metadataProcessor.Value.ProcessMetadata(gameObject, update.NewValue);
|
||||
}
|
||||
}
|
@@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxClient.GameLogic.Helper;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxClient.Unity.Helper;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel.Packets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class EntityReparentedProcessor : ClientPacketProcessor<EntityReparented>
|
||||
{
|
||||
private readonly Entities entities;
|
||||
|
||||
public EntityReparentedProcessor(Entities entities)
|
||||
{
|
||||
this.entities = entities;
|
||||
}
|
||||
|
||||
public override void Process(EntityReparented packet)
|
||||
{
|
||||
Optional<GameObject> entity = NitroxEntity.GetObjectFrom(packet.Id);
|
||||
|
||||
if (!entity.HasValue)
|
||||
{
|
||||
// In some cases, the affected entity may be pending spawning or out of range.
|
||||
// we only require the parent (in this case, the visible entity is undergoing
|
||||
// some change that must be shown, and if not is an error).
|
||||
return;
|
||||
}
|
||||
|
||||
GameObject newParent = NitroxEntity.RequireObjectFrom(packet.NewParentId);
|
||||
|
||||
if (entity.Value.TryGetComponent(out Pickupable pickupable))
|
||||
{
|
||||
WaterParkItem waterParkItem = pickupable.GetComponent<WaterParkItem>();
|
||||
// If the entity is being parented to a WaterPark
|
||||
if (newParent.TryGetComponent(out WaterPark waterPark))
|
||||
{
|
||||
// If the entity is already in a WaterPark
|
||||
if (waterParkItem.currentWaterPark)
|
||||
{
|
||||
waterParkItem.SetWaterPark(waterPark);
|
||||
return;
|
||||
}
|
||||
pickupable.SetVisible(false);
|
||||
pickupable.Activate(false);
|
||||
waterPark.AddItem(pickupable);
|
||||
// The reparenting is automatic here so we don't need to continue
|
||||
return;
|
||||
}
|
||||
// If the entity was parented to a WaterPark but is picked up by someone
|
||||
else if (waterParkItem)
|
||||
{
|
||||
pickupable.Deactivate();
|
||||
waterParkItem.SetWaterPark(null);
|
||||
}
|
||||
}
|
||||
|
||||
using (PacketSuppressor<EntityReparented>.Suppress())
|
||||
{
|
||||
Type entityType = entities.RequireEntityType(packet.Id);
|
||||
|
||||
// Move this to a resolver if there ends up being a lot of custom reparenting logic
|
||||
if (entityType == typeof(InventoryItemEntity))
|
||||
{
|
||||
InventoryItemReparented(entity.Value, newParent);
|
||||
}
|
||||
else
|
||||
{
|
||||
PerformDefaultReparenting(entity.Value, newParent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InventoryItemReparented(GameObject entity, GameObject newParent)
|
||||
{
|
||||
Optional<ItemsContainer> opContainer = InventoryContainerHelper.TryGetContainerByOwner(newParent);
|
||||
|
||||
if (!opContainer.HasValue)
|
||||
{
|
||||
Log.Error($"Could not find container field on GameObject {newParent.GetFullHierarchyPath()}");
|
||||
return;
|
||||
}
|
||||
|
||||
Pickupable pickupable = entity.RequireComponent<Pickupable>();
|
||||
|
||||
ItemsContainer container = opContainer.Value;
|
||||
container.UnsafeAdd(new InventoryItem(pickupable));
|
||||
}
|
||||
|
||||
private void PerformDefaultReparenting(GameObject entity, GameObject newParent)
|
||||
{
|
||||
entity.transform.SetParent(newParent.transform, false);
|
||||
}
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
using UnityEngine;
|
||||
using static NitroxModel.Packets.EntityTransformUpdates;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class EntityTransformUpdatesProcessor : ClientPacketProcessor<EntityTransformUpdates>
|
||||
{
|
||||
private readonly SimulationOwnership simulationOwnership;
|
||||
|
||||
public EntityTransformUpdatesProcessor(SimulationOwnership simulationOwnership)
|
||||
{
|
||||
this.simulationOwnership = simulationOwnership;
|
||||
}
|
||||
|
||||
public override void Process(EntityTransformUpdates packet)
|
||||
{
|
||||
foreach (EntityTransformUpdate update in packet.Updates)
|
||||
{
|
||||
// We will cancel any position update attempt at one of our locked entities
|
||||
if (!NitroxEntity.TryGetObjectFrom(update.Id, out GameObject gameObject) ||
|
||||
simulationOwnership.HasAnyLockType(update.Id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
RemotelyControlled remotelyControlled = gameObject.EnsureComponent<RemotelyControlled>();
|
||||
|
||||
Vector3 position = update.Position.ToUnity();
|
||||
Quaternion rotation = update.Rotation.ToUnity();
|
||||
|
||||
if (update is SplineTransformUpdate splineUpdate)
|
||||
{
|
||||
remotelyControlled.UpdateKnownSplineUser(position, rotation, splineUpdate.DestinationPosition.ToUnity(), splineUpdate.DestinationDirection.ToUnity(), splineUpdate.Velocity);
|
||||
}
|
||||
else
|
||||
{
|
||||
remotelyControlled.UpdateOrientation(position, rotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel.Packets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors
|
||||
{
|
||||
public class EscapePodChangedProcessor : ClientPacketProcessor<EscapePodChanged>
|
||||
{
|
||||
private readonly PlayerManager remotePlayerManager;
|
||||
|
||||
public EscapePodChangedProcessor(PlayerManager remotePlayerManager)
|
||||
{
|
||||
this.remotePlayerManager = remotePlayerManager;
|
||||
}
|
||||
|
||||
public override void Process(EscapePodChanged packet)
|
||||
{
|
||||
Optional<RemotePlayer> remotePlayer = remotePlayerManager.Find(packet.PlayerId);
|
||||
|
||||
if (remotePlayer.HasValue)
|
||||
{
|
||||
EscapePod escapePod = null;
|
||||
|
||||
if (packet.EscapePodId.HasValue)
|
||||
{
|
||||
GameObject sub = NitroxEntity.RequireObjectFrom(packet.EscapePodId.Value);
|
||||
escapePod = sub.GetComponent<EscapePod>();
|
||||
}
|
||||
|
||||
remotePlayer.Value.SetEscapePod(escapePod);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,36 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
using NitroxModel_Subnautica.Packets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class ExosuitArmActionProcessor : ClientPacketProcessor<ExosuitArmActionPacket>
|
||||
{
|
||||
public override void Process(ExosuitArmActionPacket packet)
|
||||
{
|
||||
if (!NitroxEntity.TryGetObjectFrom(packet.ArmId, out GameObject gameObject))
|
||||
{
|
||||
Log.Error("Could not find exosuit arm");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (packet.TechType)
|
||||
{
|
||||
case TechType.ExosuitClawArmModule:
|
||||
ExosuitModuleEvent.UseClaw(gameObject.GetComponent<ExosuitClawArm>(), packet.ArmAction);
|
||||
break;
|
||||
case TechType.ExosuitDrillArmModule:
|
||||
ExosuitModuleEvent.UseDrill(gameObject.GetComponent<ExosuitDrillArm>(), packet.ArmAction);
|
||||
break;
|
||||
case TechType.ExosuitGrapplingArmModule:
|
||||
ExosuitModuleEvent.UseGrappling(gameObject.GetComponent<ExosuitGrapplingArm>(), packet.ArmAction, packet.OpVector?.ToUnity());
|
||||
break;
|
||||
default:
|
||||
Log.Error($"Got an arm tech that is not handled: {packet.TechType} with action: {packet.ArmAction} for id {packet.ArmId}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
using NitroxModel.GameLogic.FMOD;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class FMODAssetProcessor : ClientPacketProcessor<FMODAssetPacket>
|
||||
{
|
||||
private readonly FMODWhitelist fmodWhitelist;
|
||||
|
||||
public FMODAssetProcessor(FMODWhitelist fmodWhitelist)
|
||||
{
|
||||
this.fmodWhitelist = fmodWhitelist;
|
||||
}
|
||||
|
||||
public override void Process(FMODAssetPacket packet)
|
||||
{
|
||||
if (!fmodWhitelist.TryGetSoundData(packet.AssetPath, out SoundData soundData))
|
||||
{
|
||||
Log.ErrorOnce($"[{nameof(FMODAssetProcessor)}] Whitelist has no item for {packet.AssetPath}.");
|
||||
return;
|
||||
}
|
||||
|
||||
FMODEmitterController.PlayEventOneShot(packet.AssetPath, soundData.Radius, packet.Position.ToUnity(), packet.Volume);
|
||||
}
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.Packets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class FMODCustomEmitterProcessor : ClientPacketProcessor<FMODCustomEmitterPacket>
|
||||
{
|
||||
public override void Process(FMODCustomEmitterPacket packet)
|
||||
{
|
||||
if (!NitroxEntity.TryGetObjectFrom(packet.Id, out GameObject emitterControllerEntity))
|
||||
{
|
||||
Log.ErrorOnce($"[{nameof(FMODCustomEmitterProcessor)}] Couldn't find entity {packet.Id}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!emitterControllerEntity.TryGetComponent(out FMODEmitterController fmodEmitterController))
|
||||
{
|
||||
fmodEmitterController = emitterControllerEntity.AddComponent<FMODEmitterController>();
|
||||
fmodEmitterController.LateRegisterEmitter();
|
||||
}
|
||||
|
||||
using (PacketSuppressor<FMODCustomEmitterPacket>.Suppress())
|
||||
using (PacketSuppressor<FMODCustomLoopingEmitterPacket>.Suppress())
|
||||
{
|
||||
if (packet.Play)
|
||||
{
|
||||
fmodEmitterController.PlayCustomEmitter(packet.AssetPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
fmodEmitterController.StopCustomEmitter(packet.AssetPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.Packets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class FMODCustomLoopingEmitterProcessor : ClientPacketProcessor<FMODCustomLoopingEmitterPacket>
|
||||
{
|
||||
public override void Process(FMODCustomLoopingEmitterPacket packet)
|
||||
{
|
||||
if (!NitroxEntity.TryGetObjectFrom(packet.Id, out GameObject emitterControllerObject))
|
||||
{
|
||||
Log.ErrorOnce($"[{nameof(FMODCustomLoopingEmitterProcessor)}] Couldn't find entity {packet.Id}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!emitterControllerObject.TryGetComponent(out FMODEmitterController fmodEmitterController))
|
||||
{
|
||||
fmodEmitterController = emitterControllerObject.AddComponent<FMODEmitterController>();
|
||||
fmodEmitterController.LateRegisterEmitter();
|
||||
}
|
||||
|
||||
fmodEmitterController.PlayCustomLoopingEmitter(packet.AssetPath);
|
||||
}
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.Packets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class FMODEventInstanceProcessor : ClientPacketProcessor<FMODEventInstancePacket>
|
||||
{
|
||||
public override void Process(FMODEventInstancePacket packet)
|
||||
{
|
||||
if (!NitroxEntity.TryGetObjectFrom(packet.Id, out GameObject emitterControllerObject))
|
||||
{
|
||||
Log.ErrorOnce($"[{nameof(FMODEventInstanceProcessor)}] Couldn't find entity {packet.Id}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!emitterControllerObject.TryGetComponent(out FMODEmitterController fmodEmitterController))
|
||||
{
|
||||
fmodEmitterController = emitterControllerObject.AddComponent<FMODEmitterController>();
|
||||
fmodEmitterController.LateRegisterEmitter();
|
||||
}
|
||||
|
||||
if (packet.Play)
|
||||
{
|
||||
fmodEmitterController.PlayEventInstance(packet.AssetPath, packet.Volume);
|
||||
}
|
||||
else
|
||||
{
|
||||
fmodEmitterController.StopEventInstance(packet.AssetPath);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.Packets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class FMODStudioEventEmitterProcessor : ClientPacketProcessor<FMODStudioEmitterPacket>
|
||||
{
|
||||
public override void Process(FMODStudioEmitterPacket packet)
|
||||
{
|
||||
if (!NitroxEntity.TryGetObjectFrom(packet.Id, out GameObject emitterControllerObject))
|
||||
{
|
||||
Log.ErrorOnce($"[{nameof(FMODStudioEventEmitterProcessor)}] Couldn't find entity {packet.Id}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!emitterControllerObject.TryGetComponent(out FMODEmitterController fmodEmitterController))
|
||||
{
|
||||
fmodEmitterController = emitterControllerObject.AddComponent<FMODEmitterController>();
|
||||
fmodEmitterController.LateRegisterEmitter();
|
||||
}
|
||||
|
||||
using (PacketSuppressor<FMODStudioEmitterPacket>.Suppress())
|
||||
{
|
||||
if (packet.Play)
|
||||
{
|
||||
fmodEmitterController.PlayStudioEmitter(packet.AssetPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
fmodEmitterController.StopStudioEmitter(packet.AssetPath, packet.AllowFadeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class FastCheatChangedProcessor : ClientPacketProcessor<FastCheatChanged>
|
||||
{
|
||||
public override void Process(FastCheatChanged packet)
|
||||
{
|
||||
switch (packet.Cheat)
|
||||
{
|
||||
case FastCheatChanged.FastCheat.HATCH:
|
||||
NoCostConsoleCommand.main.fastHatchCheat = packet.Value;
|
||||
break;
|
||||
|
||||
case FastCheatChanged.FastCheat.GROW:
|
||||
NoCostConsoleCommand.main.fastGrowCheat = packet.Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxClient.Unity.Helper;
|
||||
using NitroxModel.Packets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors
|
||||
{
|
||||
public class FireDousedProcessor : ClientPacketProcessor<FireDoused>
|
||||
{
|
||||
private readonly IPacketSender packetSender;
|
||||
|
||||
public FireDousedProcessor(IPacketSender packetSender)
|
||||
{
|
||||
this.packetSender = packetSender;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds and executes <see cref="Fire.Douse(float)"/>. If the fire is extinguished, it will pass a large float to trigger the private
|
||||
/// <see cref="Fire.Extinguish()"/> method.
|
||||
/// </summary>
|
||||
public override void Process(FireDoused packet)
|
||||
{
|
||||
GameObject fireGameObject = NitroxEntity.RequireObjectFrom(packet.Id);
|
||||
|
||||
using (PacketSuppressor<FireDoused>.Suppress())
|
||||
{
|
||||
fireGameObject.RequireComponent<Fire>().Douse(packet.DouseAmount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using FMOD;
|
||||
using FMOD.Studio;
|
||||
using FMODUnity;
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxClient.GameLogic.FMOD;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel.GameLogic.FMOD;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class FootstepPacketProcessor : ClientPacketProcessor<FootstepPacket>
|
||||
{
|
||||
private readonly PlayerManager remotePlayerManager;
|
||||
private readonly Lazy<FootstepSounds> localFootstepSounds = new(() => Player.mainObject.GetComponent<FootstepSounds>());
|
||||
private PARAMETER_ID fmodIndexSpeed = FMODUWE.invalidParameterId;
|
||||
private readonly float footstepAudioRadius; // To modify this value, modify the last value in the SoundWhitelist_Subnautica.csv file
|
||||
private const float FOOTSTEP_AUDIO_MAX_VOLUME = 0.5f;
|
||||
|
||||
public FootstepPacketProcessor(PlayerManager remotePlayerManager, FMODWhitelist whitelist)
|
||||
{
|
||||
this.remotePlayerManager = remotePlayerManager;
|
||||
whitelist.TryGetSoundData("event:/player/footstep_precursor_base", out SoundData soundData);
|
||||
footstepAudioRadius = soundData.Radius;
|
||||
}
|
||||
|
||||
public override void Process(FootstepPacket packet)
|
||||
{
|
||||
Optional<RemotePlayer> player = remotePlayerManager.Find(packet.PlayerID);
|
||||
if (player.HasValue)
|
||||
{
|
||||
FMODAsset asset = packet.AssetIndex switch
|
||||
{
|
||||
FootstepPacket.StepSounds.PRECURSOR => localFootstepSounds.Value.precursorInteriorSound,
|
||||
FootstepPacket.StepSounds.METAL => localFootstepSounds.Value.metalSound,
|
||||
FootstepPacket.StepSounds.LAND => localFootstepSounds.Value.landSound,
|
||||
_ => null
|
||||
};
|
||||
EventInstance evt = FMODUWE.GetEvent(asset);
|
||||
if (evt.isValid())
|
||||
{
|
||||
if (FMODUWE.IsInvalidParameterId(fmodIndexSpeed))
|
||||
{
|
||||
fmodIndexSpeed = FMODUWE.GetEventInstanceParameterIndex(evt, "speed");
|
||||
}
|
||||
ATTRIBUTES_3D attributes = player.Value.Body.To3DAttributes();
|
||||
evt.set3DAttributes(attributes);
|
||||
evt.setParameterValueByIndex(fmodIndexSpeed, player.Value.AnimationController.Velocity.magnitude);
|
||||
evt.setVolume(FMODSystem.CalculateVolume(Player.mainObject.transform.position, player.Value.Body.transform.position, footstepAudioRadius, FOOTSTEP_AUDIO_MAX_VOLUME));
|
||||
evt.start();
|
||||
evt.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class GameModeChangedProcessor : ClientPacketProcessor<GameModeChanged>
|
||||
{
|
||||
private readonly LocalPlayer localPlayer;
|
||||
private readonly PlayerManager playerManager;
|
||||
|
||||
public GameModeChangedProcessor(LocalPlayer localPlayer, PlayerManager playerManager)
|
||||
{
|
||||
this.localPlayer = localPlayer;
|
||||
this.playerManager = playerManager;
|
||||
}
|
||||
|
||||
public override void Process(GameModeChanged packet)
|
||||
{
|
||||
if (packet.AllPlayers || packet.PlayerId == localPlayer.PlayerId)
|
||||
{
|
||||
GameModeUtils.SetGameMode((GameModeOption)(int)packet.GameMode, GameModeOption.None);
|
||||
}
|
||||
if (packet.AllPlayers)
|
||||
{
|
||||
foreach (RemotePlayer remotePlayer in playerManager.GetAll())
|
||||
{
|
||||
remotePlayer.SetGameMode(packet.GameMode);
|
||||
}
|
||||
}
|
||||
else if (playerManager.TryFind(packet.PlayerId, out RemotePlayer remotePlayer))
|
||||
{
|
||||
remotePlayer.SetGameMode(packet.GameMode);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,118 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic.InitialSync.Abstract;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors
|
||||
{
|
||||
public class InitialPlayerSyncProcessor : ClientPacketProcessor<InitialPlayerSync>
|
||||
{
|
||||
private readonly IPacketSender packetSender;
|
||||
private readonly HashSet<IInitialSyncProcessor> processors;
|
||||
private readonly HashSet<Type> alreadyRan = new();
|
||||
private InitialPlayerSync packet;
|
||||
|
||||
private WaitScreen.ManualWaitItem loadingMultiplayerWaitItem;
|
||||
private WaitScreen.ManualWaitItem subWaitScreenItem;
|
||||
|
||||
private int cumulativeProcessorsRan;
|
||||
private int processorsRanLastCycle;
|
||||
|
||||
public InitialPlayerSyncProcessor(IPacketSender packetSender, IEnumerable<IInitialSyncProcessor> processors)
|
||||
{
|
||||
this.packetSender = packetSender;
|
||||
this.processors = processors.ToSet();
|
||||
}
|
||||
|
||||
public override void Process(InitialPlayerSync packet)
|
||||
{
|
||||
this.packet = packet;
|
||||
loadingMultiplayerWaitItem = WaitScreen.Add(Language.main.Get("Nitrox_SyncingWorld"));
|
||||
cumulativeProcessorsRan = 0;
|
||||
Multiplayer.Main.StartCoroutine(ProcessInitialSyncPacket(this, null));
|
||||
}
|
||||
|
||||
private IEnumerator ProcessInitialSyncPacket(object sender, EventArgs eventArgs)
|
||||
{
|
||||
bool moreProcessorsToRun;
|
||||
do
|
||||
{
|
||||
yield return Multiplayer.Main.StartCoroutine(RunPendingProcessors());
|
||||
|
||||
moreProcessorsToRun = alreadyRan.Count < processors.Count;
|
||||
if (moreProcessorsToRun && processorsRanLastCycle == 0)
|
||||
{
|
||||
throw new Exception($"Detected circular dependencies in initial packet sync between: {GetRemainingProcessorsText()}");
|
||||
}
|
||||
} while (moreProcessorsToRun);
|
||||
|
||||
WaitScreen.Remove(loadingMultiplayerWaitItem);
|
||||
Multiplayer.Main.InitialSyncCompleted = true;
|
||||
|
||||
// When the player finishes loading, we can take back his invincibility
|
||||
Player.main.liveMixin.invincible = false;
|
||||
Player.main.UnfreezeStats();
|
||||
|
||||
packetSender.Send(new PlayerSyncFinished());
|
||||
}
|
||||
|
||||
private IEnumerator RunPendingProcessors()
|
||||
{
|
||||
processorsRanLastCycle = 0;
|
||||
|
||||
foreach (IInitialSyncProcessor processor in processors)
|
||||
{
|
||||
if (IsWaitingToRun(processor.GetType()) && HasDependenciesSatisfied(processor))
|
||||
{
|
||||
loadingMultiplayerWaitItem.SetProgress(cumulativeProcessorsRan, processors.Count);
|
||||
|
||||
alreadyRan.Add(processor.GetType());
|
||||
processorsRanLastCycle++;
|
||||
cumulativeProcessorsRan++;
|
||||
|
||||
Log.Info($"Running {processor.GetType()}");
|
||||
subWaitScreenItem = WaitScreen.Add($"Running {processor.GetType().Name}");
|
||||
yield return Multiplayer.Main.StartCoroutine(processor.Process(packet, subWaitScreenItem));
|
||||
WaitScreen.Remove(subWaitScreenItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool HasDependenciesSatisfied(IInitialSyncProcessor processor)
|
||||
{
|
||||
foreach (Type dependentType in processor.DependentProcessors)
|
||||
{
|
||||
if (IsWaitingToRun(dependentType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsWaitingToRun(Type processor)
|
||||
{
|
||||
return alreadyRan.Contains(processor) == false;
|
||||
}
|
||||
|
||||
private string GetRemainingProcessorsText()
|
||||
{
|
||||
string remaining = "";
|
||||
|
||||
foreach (IInitialSyncProcessor processor in processors)
|
||||
{
|
||||
if (IsWaitingToRun(processor.GetType()))
|
||||
{
|
||||
remaining += $" {processor.GetType()}";
|
||||
}
|
||||
}
|
||||
|
||||
return remaining;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors
|
||||
{
|
||||
class ItemPositionProcessor : ClientPacketProcessor<ItemPosition>
|
||||
{
|
||||
private const float ITEM_TRANSFORM_SMOOTH_PERIOD = 0.25f;
|
||||
|
||||
public override void Process(ItemPosition drop)
|
||||
{
|
||||
Optional<GameObject> opItem = NitroxEntity.GetObjectFrom(drop.Id);
|
||||
|
||||
if (opItem.HasValue)
|
||||
{
|
||||
MovementHelper.MoveRotateGameObject(opItem.Value, drop.Position.ToUnity(), drop.Rotation.ToUnity(), ITEM_TRANSFORM_SMOOTH_PERIOD);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class KnownTechEntryProcessorAdd : ClientPacketProcessor<KnownTechEntryAdd>
|
||||
{
|
||||
private readonly IPacketSender packetSender;
|
||||
|
||||
public KnownTechEntryProcessorAdd(IPacketSender packetSender)
|
||||
{
|
||||
this.packetSender = packetSender;
|
||||
}
|
||||
|
||||
public override void Process(KnownTechEntryAdd packet)
|
||||
{
|
||||
using (PacketSuppressor<KnownTechEntryAdd>.Suppress())
|
||||
{
|
||||
switch (packet.Category)
|
||||
{
|
||||
case KnownTechEntryAdd.EntryCategory.KNOWN:
|
||||
KnownTech.Add(packet.TechType.ToUnity(), packet.Verbose);
|
||||
break;
|
||||
case KnownTechEntryAdd.EntryCategory.ANALYZED:
|
||||
KnownTech.Analyze(packet.TechType.ToUnity(), packet.Verbose);
|
||||
break;
|
||||
default:
|
||||
string categoryName = Enum.GetName(typeof(KnownTechEntryAdd.EntryCategory), packet.Category);
|
||||
Log.Error($"Received an unknown category type for {nameof(KnownTechEntryAdd)} packet: {categoryName}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class LeakRepairedProcessor : ClientPacketProcessor<LeakRepaired>
|
||||
{
|
||||
public override void Process(LeakRepaired packet)
|
||||
{
|
||||
if (NitroxEntity.TryGetComponentFrom(packet.BaseId, out BaseLeakManager baseLeakManager))
|
||||
{
|
||||
baseLeakManager.HealLeakToMax(packet.RelativeCell.ToUnity());
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic.FMOD;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxClient.Unity.Helper;
|
||||
using NitroxModel.Packets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class MedicalCabinetClickedProcessor : ClientPacketProcessor<MedicalCabinetClicked>
|
||||
{
|
||||
public override void Process(MedicalCabinetClicked packet)
|
||||
{
|
||||
GameObject gameObject = NitroxEntity.RequireObjectFrom(packet.Id);
|
||||
MedicalCabinet cabinet = gameObject.RequireComponent<MedicalCabinet>();
|
||||
|
||||
bool medkitPickedUp = !packet.HasMedKit && cabinet.hasMedKit;
|
||||
bool doorChangedState = cabinet.doorOpen != packet.DoorOpen;
|
||||
|
||||
cabinet.hasMedKit = packet.HasMedKit;
|
||||
cabinet.timeSpawnMedKit = packet.NextSpawnTime;
|
||||
|
||||
using (PacketSuppressor<FMODCustomEmitterPacket>.Suppress())
|
||||
using (FMODSystem.SuppressSubnauticaSounds())
|
||||
{
|
||||
if (doorChangedState)
|
||||
{
|
||||
cabinet.Invoke(nameof(MedicalCabinet.ToggleDoorState), 0f);
|
||||
}
|
||||
else if (medkitPickedUp)
|
||||
{
|
||||
cabinet.Invoke(nameof(MedicalCabinet.ToggleDoorState), 2f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors
|
||||
{
|
||||
public class MultiplayerSessionPolicyProcessor : ClientPacketProcessor<MultiplayerSessionPolicy>
|
||||
{
|
||||
private readonly IMultiplayerSession multiplayerSession;
|
||||
|
||||
public MultiplayerSessionPolicyProcessor(IMultiplayerSession multiplayerSession)
|
||||
{
|
||||
this.multiplayerSession = multiplayerSession;
|
||||
}
|
||||
|
||||
public override void Process(MultiplayerSessionPolicy packet)
|
||||
{
|
||||
Log.Info("Processing session policy information.");
|
||||
multiplayerSession.ProcessSessionPolicy(packet);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors
|
||||
{
|
||||
public class MultiplayerSessionReservationProcessor : ClientPacketProcessor<MultiplayerSessionReservation>
|
||||
{
|
||||
private readonly IMultiplayerSession multiplayerSession;
|
||||
|
||||
public MultiplayerSessionReservationProcessor(IMultiplayerSession multiplayerSession)
|
||||
{
|
||||
this.multiplayerSession = multiplayerSession;
|
||||
}
|
||||
|
||||
public override void Process(MultiplayerSessionReservation packet)
|
||||
{
|
||||
multiplayerSession.ProcessReservationResponsePacket(packet);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class MutePlayerProcessor : ClientPacketProcessor<MutePlayer>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
|
||||
public delegate void PlayerMuted(ushort playerId, bool muted);
|
||||
public PlayerMuted OnPlayerMuted;
|
||||
|
||||
public MutePlayerProcessor(PlayerManager playerManager)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
}
|
||||
|
||||
public override void Process(MutePlayer packet)
|
||||
{
|
||||
// We only need to notice if that's another player than local player
|
||||
Optional<RemotePlayer> player = playerManager.Find(packet.PlayerId);
|
||||
if (player.HasValue)
|
||||
{
|
||||
player.Value.PlayerContext.IsMuted = packet.Muted;
|
||||
}
|
||||
OnPlayerMuted(packet.PlayerId, packet.Muted);
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxClient.Unity.Helper;
|
||||
using NitroxModel.Packets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors
|
||||
{
|
||||
public class OpenableStateChangedProcessor : ClientPacketProcessor<OpenableStateChanged>
|
||||
{
|
||||
private readonly IPacketSender packetSender;
|
||||
|
||||
public OpenableStateChangedProcessor(IPacketSender packetSender)
|
||||
{
|
||||
this.packetSender = packetSender;
|
||||
}
|
||||
|
||||
public override void Process(OpenableStateChanged packet)
|
||||
{
|
||||
GameObject gameObject = NitroxEntity.RequireObjectFrom(packet.Id);
|
||||
Openable openable = gameObject.RequireComponent<Openable>();
|
||||
|
||||
using (PacketSuppressor<OpenableStateChanged>.Suppress())
|
||||
{
|
||||
openable.PlayOpenAnimation(packet.IsOpen, packet.Duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class PDAEncyclopediaEntryAddProcessor : ClientPacketProcessor<PDAEncyclopediaEntryAdd>
|
||||
{
|
||||
private readonly IPacketSender packetSender;
|
||||
|
||||
public PDAEncyclopediaEntryAddProcessor(IPacketSender packetSender)
|
||||
{
|
||||
this.packetSender = packetSender;
|
||||
}
|
||||
|
||||
public override void Process(PDAEncyclopediaEntryAdd packet)
|
||||
{
|
||||
using (PacketSuppressor<PDAEncyclopediaEntryAdd>.Suppress())
|
||||
{
|
||||
PDAEncyclopedia.Add(packet.Key, packet.Verbose);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors
|
||||
{
|
||||
public class PDALogEntryAddProcessor : ClientPacketProcessor<PDALogEntryAdd>
|
||||
{
|
||||
private readonly IPacketSender packetSender;
|
||||
|
||||
public PDALogEntryAddProcessor(IPacketSender packetSender)
|
||||
{
|
||||
this.packetSender = packetSender;
|
||||
}
|
||||
|
||||
public override void Process(PDALogEntryAdd packet)
|
||||
{
|
||||
using (PacketSuppressor<PDALogEntryAdd>.Suppress())
|
||||
{
|
||||
PDALog.Add(packet.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class PDAScanFinishedProcessor : ClientPacketProcessor<PDAScanFinished>
|
||||
{
|
||||
public override void Process(PDAScanFinished packet)
|
||||
{
|
||||
if (packet.Id != null)
|
||||
{
|
||||
StoryManager.ScanCompleted(packet.Id, packet.Destroy);
|
||||
}
|
||||
if (packet.WasAlreadyResearched)
|
||||
{
|
||||
return;
|
||||
}
|
||||
TechType packetTechType = packet.TechType.ToUnity();
|
||||
if (packet.FullyResearched)
|
||||
{
|
||||
PDAScanner.partial.RemoveAllFast(packetTechType, static (item, techType) => item.techType == techType);
|
||||
PDAScanner.complete.Add(packetTechType);
|
||||
return;
|
||||
}
|
||||
if (PDAScanner.GetPartialEntryByKey(packetTechType, out PDAScanner.Entry entry))
|
||||
{
|
||||
entry.unlocked = packet.UnlockedAmount;
|
||||
}
|
||||
else
|
||||
{
|
||||
PDAScanner.Add(packetTechType, packet.UnlockedAmount);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class PermsChangedProcessor : ClientPacketProcessor<PermsChanged>
|
||||
{
|
||||
private LocalPlayer localPlayer;
|
||||
|
||||
public delegate void PermissionsChanged(Perms perms);
|
||||
public PermissionsChanged OnPermissionsChanged;
|
||||
|
||||
public PermsChangedProcessor(LocalPlayer localPlayer)
|
||||
{
|
||||
this.localPlayer = localPlayer;
|
||||
}
|
||||
|
||||
public override void Process(PermsChanged packet)
|
||||
{
|
||||
localPlayer.Permissions = packet.NewPerms;
|
||||
OnPermissionsChanged(packet.NewPerms);
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxModel;
|
||||
using NitroxModel.Packets;
|
||||
using Story;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class PlaySunbeamEventProcessor : ClientPacketProcessor<PlaySunbeamEvent>
|
||||
{
|
||||
public override void Process(PlaySunbeamEvent packet)
|
||||
{
|
||||
// TODO: Look into compound goals and OnUnlock goals to bring back the necessary ones
|
||||
int beginIndex = PlaySunbeamEvent.SunbeamGoals.GetIndex(packet.EventKey);
|
||||
if (beginIndex == -1)
|
||||
{
|
||||
Log.Error($"Couldn't find the corresponding sunbeam event in {nameof(PlaySunbeamEvent.SunbeamGoals)} for key {packet.EventKey}");
|
||||
return;
|
||||
}
|
||||
for (int i = beginIndex; i < PlaySunbeamEvent.SunbeamGoals.Length; i++)
|
||||
{
|
||||
StoryGoalManager.main.completedGoals.Remove(PlaySunbeamEvent.SunbeamGoals[i]);
|
||||
}
|
||||
// Same execution as for StoryGoalCustomEventHandler commands
|
||||
StoryGoalManager.main.OnGoalComplete(packet.EventKey);
|
||||
}
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxClient.MonoBehaviours.CinematicController;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxModel.Packets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class PlayerCinematicControllerCallProcessor : ClientPacketProcessor<PlayerCinematicControllerCall>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
|
||||
public PlayerCinematicControllerCallProcessor(PlayerManager playerManager)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
}
|
||||
|
||||
public override void Process(PlayerCinematicControllerCall packet)
|
||||
{
|
||||
if (!NitroxEntity.TryGetObjectFrom(packet.ControllerID, out GameObject entity))
|
||||
{
|
||||
return; // Entity can be not spawned yet bc async.
|
||||
}
|
||||
|
||||
if (!entity.TryGetComponent(out MultiplayerCinematicReference reference))
|
||||
{
|
||||
Log.Warn($"Couldn't find {nameof(MultiplayerCinematicReference)} on {entity.name}:{packet.ControllerID}");
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<RemotePlayer> opPlayer = playerManager.Find(packet.PlayerId);
|
||||
Validate.IsPresent(opPlayer);
|
||||
|
||||
if (packet.StartPlaying)
|
||||
{
|
||||
reference.CallStartCinematicMode(packet.Key, packet.ControllerNameHash, opPlayer.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
reference.CallCinematicModeEnd(packet.Key, packet.ControllerNameHash, opPlayer.Value);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class PlayerDeathProcessor : ClientPacketProcessor<PlayerDeathEvent>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
|
||||
public PlayerDeathProcessor(PlayerManager playerManager)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
}
|
||||
|
||||
public override void Process(PlayerDeathEvent playerDeath)
|
||||
{
|
||||
RemotePlayer player = Validate.IsPresent(playerManager.Find(playerDeath.PlayerId));
|
||||
Log.Debug($"{player.PlayerName} died");
|
||||
Log.InGame(Language.main.Get("Nitrox_PlayerDied").Replace("{PLAYER}", player.PlayerName));
|
||||
player.PlayerDeathEvent.Trigger(player);
|
||||
|
||||
// TODO: Add any death related triggers (i.e. scoreboard updates, rewards, etc.)
|
||||
}
|
||||
}
|
@@ -0,0 +1,119 @@
|
||||
using System;
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxModel.Packets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class PlayerHeldItemChangedProcessor : ClientPacketProcessor<PlayerHeldItemChanged>
|
||||
{
|
||||
private int defaultLayer;
|
||||
private int viewModelLayer;
|
||||
private readonly PlayerManager playerManager;
|
||||
|
||||
public PlayerHeldItemChangedProcessor(PlayerManager playerManager)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
|
||||
if (NitroxEnvironment.IsNormal)
|
||||
{
|
||||
SetupLayers();
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupLayers()
|
||||
{
|
||||
defaultLayer = LayerMask.NameToLayer("Default");
|
||||
viewModelLayer = LayerMask.NameToLayer("Viewmodel");
|
||||
}
|
||||
|
||||
public override void Process(PlayerHeldItemChanged packet)
|
||||
{
|
||||
Optional<RemotePlayer> opPlayer = playerManager.Find(packet.PlayerId);
|
||||
Validate.IsPresent(opPlayer);
|
||||
|
||||
if (!NitroxEntity.TryGetObjectFrom(packet.ItemId, out GameObject item))
|
||||
{
|
||||
return; // Item can be not spawned yet bc async.
|
||||
}
|
||||
|
||||
Pickupable pickupable = item.GetComponent<Pickupable>();
|
||||
Validate.IsTrue(pickupable);
|
||||
|
||||
Validate.NotNull(pickupable.inventoryItem);
|
||||
|
||||
ItemsContainer inventory = opPlayer.Value.Inventory;
|
||||
PlayerTool tool = item.GetComponent<PlayerTool>();
|
||||
|
||||
// Copied from QuickSlots
|
||||
switch (packet.Type)
|
||||
{
|
||||
case PlayerHeldItemChanged.ChangeType.DRAW_AS_TOOL:
|
||||
Validate.IsTrue(tool);
|
||||
ModelPlug.PlugIntoSocket(tool, opPlayer.Value.ItemAttachPoint);
|
||||
Utils.SetLayerRecursively(item, viewModelLayer);
|
||||
foreach (Animator componentsInChild in tool.GetComponentsInChildren<Animator>())
|
||||
{
|
||||
componentsInChild.cullingMode = AnimatorCullingMode.AlwaysAnimate;
|
||||
}
|
||||
if (tool.mainCollider)
|
||||
{
|
||||
tool.mainCollider.enabled = false;
|
||||
}
|
||||
tool.GetComponent<Rigidbody>().isKinematic = true;
|
||||
item.SetActive(true);
|
||||
tool.SetHandIKTargetsEnabled(true);
|
||||
SafeAnimator.SetBool(opPlayer.Value.ArmsController.GetComponent<Animator>(), $"holding_{tool.animToolName}", true);
|
||||
opPlayer.Value.AnimationController["using_tool_first"] = packet.IsFirstTime == null;
|
||||
|
||||
if (item.TryGetComponent(out FPModel fpModelDraw)) //FPModel needs to be updated
|
||||
{
|
||||
fpModelDraw.OnEquip(null, null);
|
||||
}
|
||||
break;
|
||||
|
||||
case PlayerHeldItemChanged.ChangeType.HOLSTER_AS_TOOL:
|
||||
Validate.IsTrue(tool);
|
||||
item.SetActive(false);
|
||||
Utils.SetLayerRecursively(item, defaultLayer);
|
||||
if (tool.mainCollider)
|
||||
{
|
||||
tool.mainCollider.enabled = true;
|
||||
}
|
||||
tool.GetComponent<Rigidbody>().isKinematic = false;
|
||||
pickupable.inventoryItem.item.Reparent(inventory.tr);
|
||||
foreach (Animator componentsInChild in tool.GetComponentsInChildren<Animator>())
|
||||
{
|
||||
componentsInChild.cullingMode = AnimatorCullingMode.CullUpdateTransforms;
|
||||
}
|
||||
SafeAnimator.SetBool(opPlayer.Value.ArmsController.GetComponent<Animator>(), $"holding_{tool.animToolName}", false);
|
||||
opPlayer.Value.AnimationController["using_tool_first"] = false;
|
||||
|
||||
if (item.TryGetComponent(out FPModel fpModelHolster)) //FPModel needs to be updated
|
||||
{
|
||||
fpModelHolster.OnUnequip(null, null);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case PlayerHeldItemChanged.ChangeType.DRAW_AS_ITEM:
|
||||
pickupable.inventoryItem.item.Reparent(opPlayer.Value.ItemAttachPoint);
|
||||
pickupable.inventoryItem.item.SetVisible(true);
|
||||
Utils.SetLayerRecursively(pickupable.inventoryItem.item.gameObject, viewModelLayer);
|
||||
break;
|
||||
|
||||
case PlayerHeldItemChanged.ChangeType.HOLSTER_AS_ITEM:
|
||||
pickupable.inventoryItem.item.Reparent(inventory.tr);
|
||||
pickupable.inventoryItem.item.SetVisible(false);
|
||||
Utils.SetLayerRecursively(pickupable.inventoryItem.item.gameObject, defaultLayer);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(packet.Type));
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class PlayerInCyclopsMovementProcessor : ClientPacketProcessor<PlayerInCyclopsMovement>
|
||||
{
|
||||
private readonly PlayerManager remotePlayerManager;
|
||||
|
||||
public PlayerInCyclopsMovementProcessor(PlayerManager remotePlayerManager)
|
||||
{
|
||||
this.remotePlayerManager = remotePlayerManager;
|
||||
}
|
||||
|
||||
public override void Process(PlayerInCyclopsMovement movement)
|
||||
{
|
||||
if (remotePlayerManager.TryFind(movement.PlayerId, out RemotePlayer remotePlayer) && remotePlayer.Pawn != null)
|
||||
{
|
||||
remotePlayer.UpdatePositionInCyclops(movement.LocalPosition.ToUnity(), movement.LocalRotation.ToUnity());
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
using System.Collections;
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.Packets;
|
||||
using UWE;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class PlayerJoinedMultiplayerSessionProcessor : ClientPacketProcessor<PlayerJoinedMultiplayerSession>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
private readonly Entities entities;
|
||||
|
||||
public PlayerJoinedMultiplayerSessionProcessor(PlayerManager playerManager, Entities entities)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
this.entities = entities;
|
||||
}
|
||||
|
||||
public override void Process(PlayerJoinedMultiplayerSession packet)
|
||||
{
|
||||
CoroutineHost.StartCoroutine(SpawnRemotePlayer(packet));
|
||||
}
|
||||
|
||||
private IEnumerator SpawnRemotePlayer(PlayerJoinedMultiplayerSession packet)
|
||||
{
|
||||
playerManager.Create(packet.PlayerContext);
|
||||
yield return entities.SpawnEntityAsync(packet.PlayerWorldEntity, true, true);
|
||||
|
||||
Log.Info($"{packet.PlayerContext.PlayerName} joined the game");
|
||||
Log.InGame(Language.main.Get("Nitrox_PlayerJoined").Replace("{PLAYER}", packet.PlayerContext.PlayerName));
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.MonoBehaviours.Gui.Modals;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors
|
||||
{
|
||||
public class UserKickedProcessor : ClientPacketProcessor<PlayerKicked>
|
||||
{
|
||||
private readonly IMultiplayerSession session;
|
||||
|
||||
public UserKickedProcessor(IMultiplayerSession session)
|
||||
{
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
public override void Process(PlayerKicked packet)
|
||||
{
|
||||
string message = Language.main.Get("Nitrox_PlayerKicked");
|
||||
|
||||
if (!string.IsNullOrEmpty(packet.Reason))
|
||||
{
|
||||
message += $"\n {packet.Reason}";
|
||||
}
|
||||
|
||||
session.Disconnect();
|
||||
Modal.Get<KickedModal>()?.Show(message);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class PlayerMovementProcessor : ClientPacketProcessor<PlayerMovement>
|
||||
{
|
||||
private readonly PlayerManager remotePlayerManager;
|
||||
|
||||
public PlayerMovementProcessor(PlayerManager remotePlayerManager)
|
||||
{
|
||||
this.remotePlayerManager = remotePlayerManager;
|
||||
}
|
||||
|
||||
public override void Process(PlayerMovement movement)
|
||||
{
|
||||
if (remotePlayerManager.TryFind(movement.PlayerId, out RemotePlayer remotePlayer))
|
||||
{
|
||||
remotePlayer.UpdatePosition(movement.Position.ToUnity(),
|
||||
movement.Velocity.ToUnity(),
|
||||
movement.BodyRotation.ToUnity(),
|
||||
movement.AimingRotation.ToUnity());
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxClient.MonoBehaviours.Gui.HUD;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class PlayerStatsProcessor : ClientPacketProcessor<PlayerStats>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
|
||||
public PlayerStatsProcessor(PlayerManager playerManager)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
}
|
||||
|
||||
public override void Process(PlayerStats playerStats)
|
||||
{
|
||||
if (playerManager.TryFind(playerStats.PlayerId, out RemotePlayer remotePlayer))
|
||||
{
|
||||
RemotePlayerVitals vitals = remotePlayer.vitals;
|
||||
vitals.SetOxygen(playerStats.Oxygen, playerStats.MaxOxygen);
|
||||
vitals.SetHealth(playerStats.Health);
|
||||
vitals.SetFood(playerStats.Food);
|
||||
vitals.SetWater(playerStats.Water);
|
||||
remotePlayer.UpdateHealthAndInfection(playerStats.Health, playerStats.InfectionAmount);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxClient.Unity.Helper;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
using UWE;
|
||||
using Terrain = NitroxClient.GameLogic.Terrain;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class PlayerTeleportedProcessor : ClientPacketProcessor<PlayerTeleported>
|
||||
{
|
||||
public override void Process(PlayerTeleported packet)
|
||||
{
|
||||
Player.main.OnPlayerPositionCheat();
|
||||
|
||||
Vehicle currentVehicle = Player.main.currentMountedVehicle;
|
||||
if (currentVehicle)
|
||||
{
|
||||
currentVehicle.TeleportVehicle(packet.DestinationTo.ToUnity(), currentVehicle.transform.rotation);
|
||||
Player.main.WaitForTeleportation();
|
||||
return;
|
||||
}
|
||||
|
||||
Player.main.SetPosition(packet.DestinationTo.ToUnity());
|
||||
|
||||
if (packet.SubRootID.HasValue && NitroxEntity.TryGetComponentFrom(packet.SubRootID.Value, out SubRoot subRoot))
|
||||
{
|
||||
Player.main.SetCurrentSub(subRoot, true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Freeze the player while it's loading its new position
|
||||
Player.main.cinematicModeActive = true;
|
||||
Player.main.WaitForTeleportation();
|
||||
|
||||
CoroutineHost.StartCoroutine(Terrain.WaitForWorldLoad().OnYieldError(e =>
|
||||
{
|
||||
Player.main.cinematicModeActive = false;
|
||||
Log.Warn($"Something wrong happened while waiting for the terrain to load.\n{e}");
|
||||
}));
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class PvPAttackProcessor : ClientPacketProcessor<PvPAttack>
|
||||
{
|
||||
public override void Process(PvPAttack packet)
|
||||
{
|
||||
if (Player.main && Player.main.liveMixin)
|
||||
{
|
||||
Player.main.liveMixin.TakeDamage(packet.Damage);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxModel.Packets;
|
||||
using Story;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors
|
||||
{
|
||||
public class RadioPlayPendingMessageProcessor : ClientPacketProcessor<RadioPlayPendingMessage>
|
||||
{
|
||||
public override void Process(RadioPlayPendingMessage packet)
|
||||
{
|
||||
StoryGoalManager.main.ExecutePendingRadioMessage();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class RangedAttackLastTargetUpdateProcessor : ClientPacketProcessor<RangedAttackLastTargetUpdate>
|
||||
{
|
||||
public override void Process(RangedAttackLastTargetUpdate packet)
|
||||
{
|
||||
AI.RangedAttackLastTargetUpdate(packet.CreatureId, packet.TargetId, packet.AttackTypeIndex, packet.State);
|
||||
}
|
||||
}
|
@@ -0,0 +1,71 @@
|
||||
using System.Collections;
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
using UWE;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class RemoveCreatureCorpseProcessor : ClientPacketProcessor<RemoveCreatureCorpse>
|
||||
{
|
||||
private readonly Entities entities;
|
||||
private readonly LiveMixinManager liveMixinManager;
|
||||
private readonly SimulationOwnership simulationOwnership;
|
||||
|
||||
public RemoveCreatureCorpseProcessor(Entities entities, LiveMixinManager liveMixinManager, SimulationOwnership simulationOwnership)
|
||||
{
|
||||
this.entities = entities;
|
||||
this.liveMixinManager = liveMixinManager;
|
||||
this.simulationOwnership = simulationOwnership;
|
||||
}
|
||||
|
||||
public override void Process(RemoveCreatureCorpse packet)
|
||||
{
|
||||
entities.RemoveEntity(packet.CreatureId);
|
||||
if (!NitroxEntity.TryGetComponentFrom(packet.CreatureId, out CreatureDeath creatureDeath))
|
||||
{
|
||||
entities.MarkForDeletion(packet.CreatureId);
|
||||
Log.Warn($"[{nameof(RemoveCreatureCorpseProcessor)}] Could not find entity with id: {packet.CreatureId} to remove corpse from.");
|
||||
return;
|
||||
}
|
||||
|
||||
creatureDeath.transform.localPosition = packet.DeathPosition.ToUnity();
|
||||
creatureDeath.transform.localRotation = packet.DeathRotation.ToUnity();
|
||||
CoroutineHost.StartCoroutine(SimplerOnKillAsync(creatureDeath, packet.CreatureId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls only some parts from <see cref="CreatureDeath.OnKillAsync"/> to avoid sending packets from it
|
||||
/// or already synced behaviour (like spawning another respawner from the remote clients)
|
||||
/// </summary>
|
||||
public IEnumerator SimplerOnKillAsync(CreatureDeath creatureDeath, NitroxId creatureId)
|
||||
{
|
||||
// Ensure we don't broadcast anything from this kill event
|
||||
simulationOwnership.StopSimulatingEntity(creatureId);
|
||||
|
||||
// Remove the position broadcasting stuff from it
|
||||
EntityPositionBroadcaster.RemoveEntityMovementControl(creatureDeath.gameObject, creatureId);
|
||||
|
||||
// Receiving this packet means the creature is dead
|
||||
liveMixinManager.SyncRemoteHealth(creatureDeath.liveMixin, 0);
|
||||
|
||||
// To avoid SpawnRespawner to be called
|
||||
creatureDeath.respawn = false;
|
||||
creatureDeath.hasSpawnedRespawner = true;
|
||||
|
||||
// To avoid the cooked data section
|
||||
bool lastDamageWasHeat = creatureDeath.lastDamageWasHeat;
|
||||
creatureDeath.lastDamageWasHeat = false;
|
||||
|
||||
using (PacketSuppressor<EntityMetadataUpdate>.Suppress())
|
||||
{
|
||||
yield return creatureDeath.OnKillAsync();
|
||||
}
|
||||
|
||||
// Restore the field in case it was useful
|
||||
creatureDeath.lastDamageWasHeat = lastDamageWasHeat;
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxModel_Subnautica.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class RocketLaunchProcessor : ClientPacketProcessor<RocketLaunch>
|
||||
{
|
||||
private readonly Rockets rockets;
|
||||
|
||||
public RocketLaunchProcessor(Rockets rockets)
|
||||
{
|
||||
this.rockets = rockets;
|
||||
}
|
||||
|
||||
public override void Process(RocketLaunch rocketLaunch)
|
||||
{
|
||||
rockets.RocketLaunch(rocketLaunch.RocketId);
|
||||
}
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
using System.Linq;
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxModel.Packets;
|
||||
using Story;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class ScheduleProcessor : ClientPacketProcessor<Schedule>
|
||||
{
|
||||
public override void Process(Schedule schedulePacket)
|
||||
{
|
||||
ScheduledGoal goal = new()
|
||||
{
|
||||
goalKey = schedulePacket.Key,
|
||||
goalType = (Story.GoalType)schedulePacket.Type,
|
||||
timeExecute = schedulePacket.TimeExecute
|
||||
};
|
||||
if (ShouldSchedule(goal))
|
||||
{
|
||||
StoryGoalScheduler.main.schedule.Add(goal);
|
||||
}
|
||||
Log.Debug($"Processed a Schedule packet [Key: {goal.goalKey}, Type: {goal.goalType}, TimeExecute: {goal.timeExecute}]");
|
||||
}
|
||||
|
||||
private bool ShouldSchedule(ScheduledGoal goal)
|
||||
{
|
||||
return goal.timeExecute >= DayNightCycle.main.timePassedAsDouble && !IsAlreadyKnown(goal.goalKey);
|
||||
}
|
||||
|
||||
private bool IsAlreadyKnown(string goalKey)
|
||||
{
|
||||
return StoryGoalScheduler.main.schedule.Any(g => g.goalKey == goalKey) || // Scheduled
|
||||
StoryGoalManager.main.completedGoals.Contains(goalKey); // Completed
|
||||
}
|
||||
}
|
@@ -0,0 +1,55 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic.PlayerLogic;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.Packets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class SeaDragonAttackTargetProcessor : ClientPacketProcessor<SeaDragonAttackTarget>
|
||||
{
|
||||
public override void Process(SeaDragonAttackTarget packet)
|
||||
{
|
||||
if (!NitroxEntity.TryGetComponentFrom(packet.SeaDragonId, out SeaDragonMeleeAttack seaDragonMeleeAttack) ||
|
||||
!NitroxEntity.TryGetObjectFrom(packet.TargetId, out GameObject target))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
seaDragonMeleeAttack.seaDragon.Aggression.Value = packet.Aggression;
|
||||
if (target.GetComponent<SubControl>())
|
||||
{
|
||||
// SeaDragonMeleeAttack.OnTouchFront's useful part about Cyclops attack
|
||||
seaDragonMeleeAttack.animator.SetTrigger("shove");
|
||||
seaDragonMeleeAttack.SendMessage("OnMeleeAttack", target, SendMessageOptions.DontRequireReceiver);
|
||||
seaDragonMeleeAttack.timeLastBite = Time.time;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// SeaDragonMeleeAttack.OnTouchFront's useful part about local player attack
|
||||
Collider collider;
|
||||
if (target.TryGetComponent(out RemotePlayerIdentifier remotePlayerIdentifier))
|
||||
{
|
||||
collider = remotePlayerIdentifier.RemotePlayer.Collider;
|
||||
}
|
||||
else if (target.GetComponent<Player>())
|
||||
{
|
||||
collider = Player.mainCollider;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
seaDragonMeleeAttack.timeLastBite = Time.time;
|
||||
if (seaDragonMeleeAttack.attackSound)
|
||||
{
|
||||
using (PacketSuppressor<FMODAssetPacket>.Suppress())
|
||||
{
|
||||
Utils.PlayEnvSound(seaDragonMeleeAttack.attackSound, collider.transform.position, 20f);
|
||||
}
|
||||
}
|
||||
seaDragonMeleeAttack.OnTouch(collider);
|
||||
}
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class SeaDragonGrabExosuitProcessor : ClientPacketProcessor<SeaDragonGrabExosuit>
|
||||
{
|
||||
public override void Process(SeaDragonGrabExosuit packet)
|
||||
{
|
||||
if (!NitroxEntity.TryGetComponentFrom(packet.SeaDragonId, out SeaDragon seaDragon) ||
|
||||
!NitroxEntity.TryGetComponentFrom(packet.TargetId, out Exosuit exosuit))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (PacketSuppressor<SeaDragonGrabExosuit>.Suppress())
|
||||
{
|
||||
seaDragon.GrabExosuit(exosuit);
|
||||
seaDragon.CancelInvoke(nameof(SeaDragon.DamageExosuit));
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.Packets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class SeaDragonSwatAttackProcessor : ClientPacketProcessor<SeaDragonSwatAttack>
|
||||
{
|
||||
public override void Process(SeaDragonSwatAttack packet)
|
||||
{
|
||||
if (!NitroxEntity.TryGetComponentFrom(packet.SeaDragonId, out SeaDragonMeleeAttack seaDragonMeleeAttack) ||
|
||||
!NitroxEntity.TryGetObjectFrom(packet.TargetId, out GameObject targetObject))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (PacketSuppressor<SeaDragonSwatAttack>.Suppress())
|
||||
{
|
||||
seaDragonMeleeAttack.seaDragon.Aggression.Value = packet.Aggression;
|
||||
seaDragonMeleeAttack.SwatAttack(targetObject, packet.IsRightHand);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.Packets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class SeaTreaderChunkPickedUpProcessor : ClientPacketProcessor<SeaTreaderChunkPickedUp>
|
||||
{
|
||||
public override void Process(SeaTreaderChunkPickedUp packet)
|
||||
{
|
||||
if (NitroxEntity.TryGetComponentFrom(packet.ChunkId, out SinkingGroundChunk sinkingGroundChunk))
|
||||
{
|
||||
GameObject.Destroy(sinkingGroundChunk.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxClient.Unity.Helper;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class SeaTreaderSpawnedChunkProcessor : ClientPacketProcessor<SeaTreaderSpawnedChunk>
|
||||
{
|
||||
public override void Process(SeaTreaderSpawnedChunk packet)
|
||||
{
|
||||
if (NitroxEntity.TryGetComponentFrom(packet.CreatureId, out SeaTreader seaTreader) &&
|
||||
seaTreader.TryGetComponentInChildren(out SeaTreaderSounds seaTreaderSounds))
|
||||
{
|
||||
GameObject chunkObject = GameObjectHelper.InstantiateWithId(seaTreaderSounds.stepChunkPrefab, packet.ChunkId);
|
||||
chunkObject.transform.position = packet.Position.ToUnity();
|
||||
chunkObject.transform.rotation = packet.Rotation.ToUnity();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors
|
||||
{
|
||||
public class SeamothModuleActionProcessor : ClientPacketProcessor<SeamothModulesAction>
|
||||
{
|
||||
private readonly IPacketSender packetSender;
|
||||
|
||||
public SeamothModuleActionProcessor(IPacketSender packetSender)
|
||||
{
|
||||
this.packetSender = packetSender;
|
||||
}
|
||||
public override void Process(SeamothModulesAction packet)
|
||||
{
|
||||
using (PacketSuppressor<SeamothModulesAction>.Suppress())
|
||||
{
|
||||
GameObject _gameObject = NitroxEntity.RequireObjectFrom(packet.Id);
|
||||
SeaMoth seamoth = _gameObject.GetComponent<SeaMoth>();
|
||||
if (seamoth != null)
|
||||
{
|
||||
TechType techType = packet.TechType.ToUnity();
|
||||
|
||||
if (techType == TechType.SeamothElectricalDefense)
|
||||
{
|
||||
float[] chargearray = seamoth.quickSlotCharge;
|
||||
float charge = chargearray[packet.SlotID];
|
||||
float slotCharge = seamoth.GetSlotCharge(packet.SlotID);
|
||||
GameObject gameObject = global::Utils.SpawnZeroedAt(seamoth.seamothElectricalDefensePrefab, seamoth.transform, false);
|
||||
ElectricalDefense component = gameObject.GetComponent<ElectricalDefense>();
|
||||
component.charge = charge;
|
||||
component.chargeScalar = slotCharge;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
using NitroxClient.Communication.Abstract;
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.MonoBehaviours.Gui.Modals;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class ServerStoppedProcessor : ClientPacketProcessor<ServerStopped>
|
||||
{
|
||||
private readonly IClient client;
|
||||
|
||||
public ServerStoppedProcessor(IClient client)
|
||||
{
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public override void Process(ServerStopped packet)
|
||||
{
|
||||
// We can send the stop instruction right now instead of waiting for the timeout
|
||||
client.Stop();
|
||||
Modal.Get<ServerStoppedModal>()?.Show();
|
||||
}
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
using NitroxClient.Communication.Packets.Processors.Abstract;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxClient.GameLogic.PlayerLogic;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxClient.Communication.Packets.Processors;
|
||||
|
||||
public class SetIntroCinematicModeProcessor : ClientPacketProcessor<SetIntroCinematicMode>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
private readonly PlayerCinematics playerCinematics;
|
||||
private readonly LocalPlayer localPlayer;
|
||||
|
||||
public SetIntroCinematicModeProcessor(PlayerManager playerManager, PlayerCinematics playerCinematics, LocalPlayer localPlayer)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
this.playerCinematics = playerCinematics;
|
||||
this.localPlayer = localPlayer;
|
||||
}
|
||||
|
||||
public override void Process(SetIntroCinematicMode packet)
|
||||
{
|
||||
if (localPlayer.PlayerId == packet.PlayerId)
|
||||
{
|
||||
if (packet.PartnerId.HasValue)
|
||||
{
|
||||
playerCinematics.IntroCinematicPartnerId = packet.PartnerId;
|
||||
}
|
||||
|
||||
localPlayer.IntroCinematicMode = packet.Mode;
|
||||
return;
|
||||
}
|
||||
|
||||
if (playerManager.TryFind(packet.PlayerId, out RemotePlayer remotePlayer))
|
||||
{
|
||||
remotePlayer.PlayerContext.IntroCinematicMode = packet.Mode;
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Debug($"SetIntroCinematicMode couldn't find Player with id {packet.PlayerId}. This is normal if player has not yet officially joined.");
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user