first commit
This commit is contained in:
23
NitroxServer/App.config
Normal file
23
NitroxServer/App.config
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0" />
|
||||
</startup>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<probing privatePath="lib;libs;uwe_lib" />
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
<system.web>
|
||||
<membership defaultProvider="ClientAuthenticationMembershipProvider">
|
||||
<providers>
|
||||
<add name="ClientAuthenticationMembershipProvider" type="System.Web.ClientServices.Providers.ClientFormsAuthenticationMembershipProvider, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" serviceUri="" />
|
||||
</providers>
|
||||
</membership>
|
||||
<roleManager defaultProvider="ClientRoleProvider" enabled="true">
|
||||
<providers>
|
||||
<add name="ClientRoleProvider" type="System.Web.ClientServices.Providers.ClientRoleProvider, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" serviceUri="" cacheTimeout="86400" />
|
||||
</providers>
|
||||
</roleManager>
|
||||
</system.web>
|
||||
</configuration>
|
30
NitroxServer/Communication/ConnectionState.cs
Normal file
30
NitroxServer/Communication/ConnectionState.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using LiteNetLib;
|
||||
|
||||
namespace NitroxServer.Communication;
|
||||
|
||||
public enum NitroxConnectionState
|
||||
{
|
||||
Unknown,
|
||||
Disconnected,
|
||||
Connected,
|
||||
Reserved,
|
||||
InGame
|
||||
}
|
||||
|
||||
public static class NitroxConnectionStateExtensions
|
||||
{
|
||||
public static NitroxConnectionState ToNitrox(this ConnectionState connectionState)
|
||||
{
|
||||
if ((connectionState & ConnectionState.Connected) == ConnectionState.Connected)
|
||||
{
|
||||
return NitroxConnectionState.Connected;
|
||||
}
|
||||
|
||||
if ((connectionState & ConnectionState.Disconnected) == ConnectionState.Disconnected)
|
||||
{
|
||||
return NitroxConnectionState.Disconnected;
|
||||
}
|
||||
|
||||
return NitroxConnectionState.Unknown;
|
||||
}
|
||||
}
|
60
NitroxServer/Communication/LANBroadcastServer.cs
Normal file
60
NitroxServer/Communication/LANBroadcastServer.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using LiteNetLib;
|
||||
using LiteNetLib.Utils;
|
||||
using NitroxModel.Constants;
|
||||
|
||||
namespace NitroxServer.Communication;
|
||||
|
||||
public static class LANBroadcastServer
|
||||
{
|
||||
private static NetManager server;
|
||||
private static EventBasedNetListener listener;
|
||||
private static Timer pollTimer;
|
||||
|
||||
public static void Start(CancellationToken ct)
|
||||
{
|
||||
listener = new EventBasedNetListener();
|
||||
listener.NetworkReceiveUnconnectedEvent += NetworkReceiveUnconnected;
|
||||
server = new NetManager(listener);
|
||||
server.AutoRecycle = true;
|
||||
server.BroadcastReceiveEnabled = true;
|
||||
server.UnconnectedMessagesEnabled = true;
|
||||
|
||||
foreach (int port in LANDiscoveryConstants.BROADCAST_PORTS)
|
||||
{
|
||||
if (server.Start(port))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pollTimer = new Timer(_ => server.PollEvents());
|
||||
pollTimer.Change(0, 100);
|
||||
Log.Debug($"{nameof(LANBroadcastServer)} started");
|
||||
}
|
||||
|
||||
public static void Stop()
|
||||
{
|
||||
listener?.ClearNetworkReceiveUnconnectedEvent();
|
||||
server?.Stop();
|
||||
pollTimer?.Dispose();
|
||||
Log.Debug($"{nameof(LANBroadcastServer)} stopped");
|
||||
}
|
||||
|
||||
private static void NetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType)
|
||||
{
|
||||
if (messageType == UnconnectedMessageType.Broadcast)
|
||||
{
|
||||
string requestString = reader.GetString();
|
||||
if (requestString == LANDiscoveryConstants.BROADCAST_REQUEST_STRING)
|
||||
{
|
||||
NetDataWriter writer = new();
|
||||
writer.Put(LANDiscoveryConstants.BROADCAST_RESPONSE_STRING);
|
||||
writer.Put(Server.Instance.Port);
|
||||
|
||||
server.SendBroadcast(writer, remoteEndPoint.Port);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using LiteNetLib;
|
||||
using LiteNetLib.Utils;
|
||||
using NitroxModel.Networking;
|
||||
using NitroxModel.Packets;
|
||||
|
||||
namespace NitroxServer.Communication.LiteNetLib;
|
||||
|
||||
public class LiteNetLibConnection : INitroxConnection, IEquatable<LiteNetLibConnection>
|
||||
{
|
||||
private readonly NetDataWriter dataWriter = new();
|
||||
private readonly NetPeer peer;
|
||||
|
||||
public IPEndPoint Endpoint => peer;
|
||||
public NitroxConnectionState State => peer.ConnectionState.ToNitrox();
|
||||
|
||||
public LiteNetLibConnection(NetPeer peer)
|
||||
{
|
||||
this.peer = peer;
|
||||
}
|
||||
|
||||
public void SendPacket(Packet packet)
|
||||
{
|
||||
if (peer.ConnectionState == ConnectionState.Connected)
|
||||
{
|
||||
byte[] packetData = packet.Serialize();
|
||||
dataWriter.Reset();
|
||||
dataWriter.Put(packetData.Length);
|
||||
dataWriter.Put(packetData);
|
||||
|
||||
peer.Send(dataWriter, (byte)packet.UdpChannel, NitroxDeliveryMethod.ToLiteNetLib(packet.DeliveryMethod));
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warn($"Cannot send packet {packet?.GetType()} to a closed connection {peer as IPEndPoint}");
|
||||
}
|
||||
}
|
||||
|
||||
public static bool operator ==(LiteNetLibConnection left, LiteNetLibConnection right)
|
||||
{
|
||||
return Equals(left, right);
|
||||
}
|
||||
|
||||
public static bool operator !=(LiteNetLibConnection left, LiteNetLibConnection right)
|
||||
{
|
||||
return !Equals(left, right);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ReferenceEquals(this, obj))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (obj.GetType() != GetType())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Equals((LiteNetLibConnection)obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return peer?.Id.GetHashCode() ?? 0;
|
||||
}
|
||||
|
||||
public bool Equals(LiteNetLibConnection other)
|
||||
{
|
||||
return peer?.Id == other?.peer?.Id;
|
||||
}
|
||||
}
|
166
NitroxServer/Communication/LiteNetLib/LiteNetLibServer.cs
Normal file
166
NitroxServer/Communication/LiteNetLib/LiteNetLibServer.cs
Normal file
@@ -0,0 +1,166 @@
|
||||
using System.Buffers;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LiteNetLib;
|
||||
using LiteNetLib.Utils;
|
||||
using Mono.Nat;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxModel.Serialization;
|
||||
using NitroxServer.Communication.Packets;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
|
||||
namespace NitroxServer.Communication.LiteNetLib;
|
||||
|
||||
public class LiteNetLibServer : NitroxServer
|
||||
{
|
||||
private readonly EventBasedNetListener listener;
|
||||
private readonly NetManager server;
|
||||
|
||||
public LiteNetLibServer(PacketHandler packetHandler, PlayerManager playerManager, EntitySimulation entitySimulation, SubnauticaServerConfig serverConfig) : base(packetHandler, playerManager, entitySimulation, serverConfig)
|
||||
{
|
||||
listener = new EventBasedNetListener();
|
||||
server = new NetManager(listener);
|
||||
}
|
||||
|
||||
public override bool Start(CancellationToken ct = default)
|
||||
{
|
||||
listener.PeerConnectedEvent += PeerConnected;
|
||||
listener.PeerDisconnectedEvent += PeerDisconnected;
|
||||
listener.NetworkReceiveEvent += NetworkDataReceived;
|
||||
listener.ConnectionRequestEvent += OnConnectionRequest;
|
||||
|
||||
server.ChannelsCount = (byte)typeof(Packet.UdpChannelId).GetEnumValues().Length;
|
||||
server.BroadcastReceiveEnabled = true;
|
||||
server.UnconnectedMessagesEnabled = true;
|
||||
server.UpdateTime = 15;
|
||||
server.UnsyncedEvents = true;
|
||||
#if DEBUG
|
||||
server.DisconnectTimeout = 300000; //Disables Timeout (for 5 min) for debug purpose (like if you jump though the server code)
|
||||
#endif
|
||||
|
||||
if (!server.Start(portNumber))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (useUpnpPortForwarding)
|
||||
{
|
||||
_ = PortForwardAsync((ushort)portNumber, ct);
|
||||
}
|
||||
if (useLANBroadcast)
|
||||
{
|
||||
LANBroadcastServer.Start(ct);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task PortForwardAsync(ushort port, CancellationToken ct = default)
|
||||
{
|
||||
if (await NatHelper.GetPortMappingAsync(port, Protocol.Udp, ct) != null)
|
||||
{
|
||||
Log.Info($"Port {port} UDP is already port forwarded");
|
||||
return;
|
||||
}
|
||||
|
||||
NatHelper.ResultCodes mappingResult = await NatHelper.AddPortMappingAsync(port, Protocol.Udp, ct);
|
||||
if (!ct.IsCancellationRequested)
|
||||
{
|
||||
switch (mappingResult)
|
||||
{
|
||||
case NatHelper.ResultCodes.SUCCESS:
|
||||
Log.Info($"Server port {port} UDP has been automatically opened on your router (port is closed when server closes)");
|
||||
break;
|
||||
case NatHelper.ResultCodes.CONFLICT_IN_MAPPING_ENTRY:
|
||||
Log.Warn($"Port forward for {port} UDP failed. It appears to already be port forwarded or it conflicts with another port forward rule.");
|
||||
break;
|
||||
case NatHelper.ResultCodes.UNKNOWN_ERROR:
|
||||
Log.Warn($"Failed to port forward {port} UDP through UPnP. If using Hamachi or you've manually port-forwarded, please disregard this warning. To disable this feature you can go into the server settings.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Stop()
|
||||
{
|
||||
if (!server.IsRunning)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
playerManager.SendPacketToAllPlayers(new ServerStopped());
|
||||
// We want every player to receive this packet
|
||||
Thread.Sleep(500);
|
||||
server.Stop();
|
||||
if (useUpnpPortForwarding)
|
||||
{
|
||||
if (NatHelper.DeletePortMappingAsync((ushort)portNumber, Protocol.Udp, CancellationToken.None).GetAwaiter().GetResult())
|
||||
{
|
||||
Log.Debug($"Port forward rule removed for {portNumber} UDP");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warn($"Failed to remove port forward rule {portNumber} UDP");
|
||||
}
|
||||
}
|
||||
if (useLANBroadcast)
|
||||
{
|
||||
LANBroadcastServer.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnConnectionRequest(ConnectionRequest request)
|
||||
{
|
||||
if (server.ConnectedPeersCount < maxConnections)
|
||||
{
|
||||
request.AcceptIfKey("nitrox");
|
||||
}
|
||||
else
|
||||
{
|
||||
request.Reject();
|
||||
}
|
||||
}
|
||||
|
||||
private void PeerConnected(NetPeer peer)
|
||||
{
|
||||
LiteNetLibConnection connection = new(peer);
|
||||
lock (connectionsByRemoteIdentifier)
|
||||
{
|
||||
connectionsByRemoteIdentifier[peer.Id] = connection;
|
||||
}
|
||||
}
|
||||
|
||||
private void PeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo)
|
||||
{
|
||||
ClientDisconnected(GetConnection(peer.Id));
|
||||
}
|
||||
|
||||
private void NetworkDataReceived(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);
|
||||
INitroxConnection connection = GetConnection(peer.Id);
|
||||
ProcessIncomingData(connection, packet);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(packetData, true);
|
||||
}
|
||||
}
|
||||
|
||||
private INitroxConnection GetConnection(int remoteIdentifier)
|
||||
{
|
||||
INitroxConnection connection;
|
||||
lock (connectionsByRemoteIdentifier)
|
||||
{
|
||||
connectionsByRemoteIdentifier.TryGetValue(remoteIdentifier, out connection);
|
||||
}
|
||||
|
||||
return connection;
|
||||
}
|
||||
}
|
14
NitroxServer/Communication/NitroxConnection.cs
Normal file
14
NitroxServer/Communication/NitroxConnection.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.Net;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxModel.Packets.Processors.Abstract;
|
||||
|
||||
namespace NitroxServer.Communication;
|
||||
|
||||
public interface INitroxConnection : IProcessorContext
|
||||
{
|
||||
IPEndPoint Endpoint { get; }
|
||||
|
||||
NitroxConnectionState State { get; }
|
||||
|
||||
void SendPacket(Packet packet);
|
||||
}
|
83
NitroxServer/Communication/NitroxServer.cs
Normal file
83
NitroxServer/Communication/NitroxServer.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxModel.Serialization;
|
||||
using NitroxServer.Communication.Packets;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
|
||||
namespace NitroxServer.Communication
|
||||
{
|
||||
public abstract class NitroxServer
|
||||
{
|
||||
static NitroxServer()
|
||||
{
|
||||
Packet.InitSerializer();
|
||||
}
|
||||
|
||||
protected readonly int portNumber;
|
||||
protected readonly int maxConnections;
|
||||
protected readonly bool useUpnpPortForwarding;
|
||||
protected readonly bool useLANBroadcast;
|
||||
|
||||
protected readonly PacketHandler packetHandler;
|
||||
protected readonly EntitySimulation entitySimulation;
|
||||
protected readonly Dictionary<int, INitroxConnection> connectionsByRemoteIdentifier = new();
|
||||
protected readonly PlayerManager playerManager;
|
||||
|
||||
public NitroxServer(PacketHandler packetHandler, PlayerManager playerManager, EntitySimulation entitySimulation, SubnauticaServerConfig serverConfig)
|
||||
{
|
||||
this.packetHandler = packetHandler;
|
||||
this.playerManager = playerManager;
|
||||
this.entitySimulation = entitySimulation;
|
||||
|
||||
portNumber = serverConfig.ServerPort;
|
||||
maxConnections = serverConfig.MaxConnections;
|
||||
useUpnpPortForwarding = serverConfig.AutoPortForward;
|
||||
useLANBroadcast = serverConfig.LANDiscoveryEnabled;
|
||||
}
|
||||
|
||||
public abstract bool Start(CancellationToken ct = default);
|
||||
|
||||
public abstract void Stop();
|
||||
|
||||
protected void ClientDisconnected(INitroxConnection connection)
|
||||
{
|
||||
Player player = playerManager.GetPlayer(connection);
|
||||
|
||||
if (player != null)
|
||||
{
|
||||
playerManager.PlayerDisconnected(connection);
|
||||
|
||||
Disconnect disconnect = new(player.Id);
|
||||
playerManager.SendPacketToAllPlayers(disconnect);
|
||||
|
||||
List<SimulatedEntity> ownershipChanges = entitySimulation.CalculateSimulationChangesFromPlayerDisconnect(player);
|
||||
|
||||
if (ownershipChanges.Count > 0)
|
||||
{
|
||||
SimulationOwnershipChange ownershipChange = new(ownershipChanges);
|
||||
playerManager.SendPacketToAllPlayers(ownershipChange);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
playerManager.NonPlayerDisconnected(connection);
|
||||
}
|
||||
}
|
||||
|
||||
protected void ProcessIncomingData(INitroxConnection connection, Packet packet)
|
||||
{
|
||||
try
|
||||
{
|
||||
packetHandler.Process(packet, connection);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Exception while processing packet: {packet}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
88
NitroxServer/Communication/Packets/PacketHandler.cs
Normal file
88
NitroxServer/Communication/Packets/PacketHandler.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NitroxModel.Core;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxModel.Packets.Processors.Abstract;
|
||||
using NitroxServer.Communication.Packets.Processors;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
|
||||
namespace NitroxServer.Communication.Packets
|
||||
{
|
||||
public class PacketHandler
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
private readonly DefaultServerPacketProcessor defaultServerPacketProcessor;
|
||||
private readonly Dictionary<Type, PacketProcessor> packetProcessorAuthCache = new();
|
||||
private readonly Dictionary<Type, PacketProcessor> packetProcessorUnauthCache = new();
|
||||
|
||||
public PacketHandler(PlayerManager playerManager, DefaultServerPacketProcessor packetProcessor)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
defaultServerPacketProcessor = packetProcessor;
|
||||
}
|
||||
|
||||
public void Process(Packet packet, INitroxConnection connection)
|
||||
{
|
||||
Player player = playerManager.GetPlayer(connection);
|
||||
if (player == null)
|
||||
{
|
||||
ProcessUnauthenticated(packet, connection);
|
||||
}
|
||||
else
|
||||
{
|
||||
ProcessAuthenticated(packet, player);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessAuthenticated(Packet packet, Player player)
|
||||
{
|
||||
Type packetType = packet.GetType();
|
||||
if (!packetProcessorAuthCache.TryGetValue(packetType, out PacketProcessor processor))
|
||||
{
|
||||
Type packetProcessorType = typeof(AuthenticatedPacketProcessor<>).MakeGenericType(packetType);
|
||||
packetProcessorAuthCache[packetType] = processor = NitroxServiceLocator.LocateOptionalService(packetProcessorType).Value as PacketProcessor;
|
||||
}
|
||||
|
||||
if (processor != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
processor.ProcessPacket(packet, player);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Error in packet processor {processor.GetType()}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
defaultServerPacketProcessor.ProcessPacket(packet, player);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessUnauthenticated(Packet packet, INitroxConnection connection)
|
||||
{
|
||||
Type packetType = packet.GetType();
|
||||
if (!packetProcessorUnauthCache.TryGetValue(packetType, out PacketProcessor processor))
|
||||
{
|
||||
Type packetProcessorType = typeof(UnauthenticatedPacketProcessor<>).MakeGenericType(packetType);
|
||||
packetProcessorUnauthCache[packetType] = processor = NitroxServiceLocator.LocateOptionalService(packetProcessorType).Value as PacketProcessor;
|
||||
}
|
||||
if (processor == null)
|
||||
{
|
||||
Log.Warn($"Received invalid, unauthenticated packet: {packet}");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
processor.ProcessPacket(packet, connection);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Error in packet processor {processor.GetType()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxModel.Packets.Processors.Abstract;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors.Abstract
|
||||
{
|
||||
public abstract class AuthenticatedPacketProcessor<T> : PacketProcessor where T : Packet
|
||||
{
|
||||
public override void ProcessPacket(Packet packet, IProcessorContext player)
|
||||
{
|
||||
Process((T)packet, (Player)player);
|
||||
}
|
||||
|
||||
public abstract void Process(T packet, Player player);
|
||||
}
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
|
||||
public abstract class TransmitIfCanSeePacketProcessor<T> : AuthenticatedPacketProcessor<T> where T : Packet
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
private readonly EntityRegistry entityRegistry;
|
||||
|
||||
public TransmitIfCanSeePacketProcessor(PlayerManager playerManager, EntityRegistry entityRegistry)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
this.entityRegistry = entityRegistry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transmits the provided <paramref name="packet"/> to all other players (excluding <paramref name="senderPlayer"/>)
|
||||
/// who can see (<see cref="Player.CanSee"/>) entities corresponding to the provided <paramref name="entityIds"/> only if all those entities are registered.
|
||||
/// </summary>
|
||||
public void TransmitIfCanSeeEntities(Packet packet, Player senderPlayer, List<NitroxId> entityIds)
|
||||
{
|
||||
List<Entity> entities = [];
|
||||
foreach (NitroxId entityId in entityIds)
|
||||
{
|
||||
if (entityRegistry.TryGetEntityById(entityId, out Entity entity))
|
||||
{
|
||||
entities.Add(entity);
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Player player in playerManager.GetConnectedPlayersExcept(senderPlayer))
|
||||
{
|
||||
if (entities.All(player.CanSee))
|
||||
{
|
||||
player.SendPacket(packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxModel.Packets.Processors.Abstract;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors.Abstract
|
||||
{
|
||||
public abstract class UnauthenticatedPacketProcessor<T> : PacketProcessor where T : Packet
|
||||
{
|
||||
public override void ProcessPacket(Packet packet, IProcessorContext connection)
|
||||
{
|
||||
Process((T)packet, (INitroxConnection)connection);
|
||||
}
|
||||
|
||||
public abstract void Process(T packet, INitroxConnection connection);
|
||||
}
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class AggressiveWhenSeeTargetChangedProcessor(
|
||||
PlayerManager playerManager,
|
||||
EntityRegistry entityRegistry
|
||||
) : TransmitIfCanSeePacketProcessor<AggressiveWhenSeeTargetChanged>(playerManager, entityRegistry)
|
||||
{
|
||||
public override void Process(AggressiveWhenSeeTargetChanged packet, Player sender) => TransmitIfCanSeeEntities(packet, sender, [packet.CreatureId, packet.TargetId]);
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class AttackCyclopsTargetChangedProcessor(
|
||||
PlayerManager playerManager,
|
||||
EntityRegistry entityRegistry
|
||||
) : TransmitIfCanSeePacketProcessor<AttackCyclopsTargetChanged>(playerManager, entityRegistry)
|
||||
{
|
||||
public override void Process(AttackCyclopsTargetChanged packet, Player sender) => TransmitIfCanSeeEntities(packet, sender, [packet.CreatureId, packet.TargetId]);
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Bases;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class BaseDeconstructedProcessor : BuildingProcessor<BaseDeconstructed>
|
||||
{
|
||||
public BaseDeconstructedProcessor(BuildingManager buildingManager, PlayerManager playerManager) : base(buildingManager, playerManager) { }
|
||||
|
||||
public override void Process(BaseDeconstructed packet, Player player)
|
||||
{
|
||||
if (buildingManager.ReplaceBaseByGhost(packet))
|
||||
{
|
||||
playerManager.SendPacketToOtherPlayers(packet, player);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors
|
||||
{
|
||||
public class BedEnterProcessor : AuthenticatedPacketProcessor<BedEnter>
|
||||
{
|
||||
private readonly StoryManager storyManager;
|
||||
|
||||
public BedEnterProcessor(StoryManager storyManager)
|
||||
{
|
||||
this.storyManager = storyManager;
|
||||
}
|
||||
|
||||
public override void Process(BedEnter packet, Player player)
|
||||
{
|
||||
// TODO: Needs repair since the new time implementation only relies on server-side time.
|
||||
// storyManager.ChangeTime(StoryManager.TimeModification.SKIP);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Bases;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public abstract class BuildingProcessor<T> : AuthenticatedPacketProcessor<T> where T : Packet
|
||||
{
|
||||
internal readonly BuildingManager buildingManager;
|
||||
internal readonly PlayerManager playerManager;
|
||||
internal readonly EntitySimulation entitySimulation;
|
||||
|
||||
public BuildingProcessor(BuildingManager buildingManager, PlayerManager playerManager, EntitySimulation entitySimulation = null)
|
||||
{
|
||||
this.buildingManager = buildingManager;
|
||||
this.playerManager = playerManager;
|
||||
this.entitySimulation = entitySimulation;
|
||||
}
|
||||
|
||||
public void SendToOtherPlayersWithOperationId(T packet, Player player, int operationId)
|
||||
{
|
||||
if (packet is OrderedBuildPacket buildPacket)
|
||||
{
|
||||
buildPacket.OperationId = operationId;
|
||||
}
|
||||
playerManager.SendPacketToOtherPlayers(packet, player);
|
||||
}
|
||||
|
||||
public void ClaimBuildPiece(Entity entity, Player player)
|
||||
{
|
||||
SimulatedEntity simulatedEntity = entitySimulation.AssignNewEntityToPlayer(entity, player, false);
|
||||
SimulationOwnershipChange ownershipChangePacket = new(simulatedEntity);
|
||||
playerManager.SendPacketToAllPlayers(ownershipChangePacket);
|
||||
}
|
||||
}
|
@@ -0,0 +1,53 @@
|
||||
using System.Collections.Generic;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities.Bases;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class BuildingResyncRequestProcessor : AuthenticatedPacketProcessor<BuildingResyncRequest>
|
||||
{
|
||||
private readonly EntityRegistry entityRegistry;
|
||||
private readonly WorldEntityManager worldEntityManager;
|
||||
|
||||
public BuildingResyncRequestProcessor(EntityRegistry entityRegistry, WorldEntityManager worldEntityManager)
|
||||
{
|
||||
this.entityRegistry = entityRegistry;
|
||||
this.worldEntityManager = worldEntityManager;
|
||||
}
|
||||
|
||||
public override void Process(BuildingResyncRequest packet, Player player)
|
||||
{
|
||||
Dictionary<BuildEntity, int> buildEntities = new();
|
||||
Dictionary<ModuleEntity, int> moduleEntities = new();
|
||||
void AddEntityToResync(Entity entity)
|
||||
{
|
||||
switch (entity)
|
||||
{
|
||||
case BuildEntity buildEntity:
|
||||
buildEntities.Add(buildEntity, buildEntity.OperationId);
|
||||
break;
|
||||
case ModuleEntity moduleEntity:
|
||||
moduleEntities.Add(moduleEntity, -1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (packet.ResyncEverything)
|
||||
{
|
||||
foreach (GlobalRootEntity globalRootEntity in worldEntityManager.GetGlobalRootEntities(true))
|
||||
{
|
||||
AddEntityToResync(globalRootEntity);
|
||||
}
|
||||
}
|
||||
else if (packet.EntityId != null && entityRegistry.TryGetEntityById(packet.EntityId, out Entity entity))
|
||||
{
|
||||
AddEntityToResync(entity);
|
||||
}
|
||||
|
||||
player.SendPacket(new BuildingResync(buildEntities, moduleEntities));
|
||||
}
|
||||
}
|
@@ -0,0 +1,54 @@
|
||||
using System.Collections.Generic;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class CellVisibilityChangedProcessor : AuthenticatedPacketProcessor<CellVisibilityChanged>
|
||||
{
|
||||
private readonly EntitySimulation entitySimulation;
|
||||
private readonly WorldEntityManager worldEntityManager;
|
||||
|
||||
public CellVisibilityChangedProcessor(EntitySimulation entitySimulation, WorldEntityManager worldEntityManager)
|
||||
{
|
||||
this.entitySimulation = entitySimulation;
|
||||
this.worldEntityManager = worldEntityManager;
|
||||
}
|
||||
|
||||
public override void Process(CellVisibilityChanged packet, Player player)
|
||||
{
|
||||
player.AddCells(packet.Added);
|
||||
player.RemoveCells(packet.Removed);
|
||||
|
||||
List<Entity> totalEntities = [];
|
||||
List<SimulatedEntity> totalSimulationChanges = [];
|
||||
|
||||
foreach (AbsoluteEntityCell addedCell in packet.Added)
|
||||
{
|
||||
worldEntityManager.LoadUnspawnedEntities(addedCell.BatchId, false);
|
||||
|
||||
totalSimulationChanges.AddRange(entitySimulation.GetSimulationChangesForCell(player, addedCell));
|
||||
totalEntities.AddRange(worldEntityManager.GetEntities(addedCell));
|
||||
}
|
||||
|
||||
foreach (AbsoluteEntityCell removedCell in packet.Removed)
|
||||
{
|
||||
entitySimulation.FillWithRemovedCells(player, removedCell, totalSimulationChanges);
|
||||
}
|
||||
|
||||
// Simulation update must be broadcasted before the entities are spawned
|
||||
if (totalSimulationChanges.Count > 0)
|
||||
{
|
||||
entitySimulation.BroadcastSimulationChanges(new(totalSimulationChanges));
|
||||
}
|
||||
|
||||
if (totalEntities.Count > 0)
|
||||
{
|
||||
SpawnEntities batchEntities = new(totalEntities);
|
||||
player.SendPacket(batchEntities);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors
|
||||
{
|
||||
public class ChatMessageProcessor : AuthenticatedPacketProcessor<ChatMessage>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
|
||||
public ChatMessageProcessor(PlayerManager playerManager)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
}
|
||||
|
||||
public override void Process(ChatMessage packet, Player player)
|
||||
{
|
||||
if (player.PlayerContext.IsMuted)
|
||||
{
|
||||
player.SendPacket(new ChatMessage(ChatMessage.SERVER_ID, "You're currently muted"));
|
||||
return;
|
||||
}
|
||||
Log.Info($"<{player.Name}>: {packet.Text}");
|
||||
playerManager.SendPacketToAllPlayers(packet);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class ClearPlanterProcessor : AuthenticatedPacketProcessor<ClearPlanter>
|
||||
{
|
||||
private readonly EntityRegistry entityRegistry;
|
||||
|
||||
public ClearPlanterProcessor(EntityRegistry entityRegistry)
|
||||
{
|
||||
this.entityRegistry = entityRegistry;
|
||||
}
|
||||
|
||||
public override void Process(ClearPlanter packet, Player player)
|
||||
{
|
||||
if (entityRegistry.TryGetEntityById(packet.PlanterId, out PlanterEntity planterEntity))
|
||||
{
|
||||
// No need to transmit this packet since the operation is automatically done on remote clients
|
||||
entityRegistry.CleanChildren(planterEntity);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.ErrorOnce($"[{nameof(ClearPlanterProcessor)}] Could not find PlanterEntity with id {packet.PlanterId}");
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class CreatureActionChangedProcessor(
|
||||
PlayerManager playerManager,
|
||||
EntityRegistry entityRegistry
|
||||
) : TransmitIfCanSeePacketProcessor<CreatureActionChanged>(playerManager, entityRegistry)
|
||||
{
|
||||
public override void Process(CreatureActionChanged packet, Player sender) => TransmitIfCanSeeEntities(packet, sender, [packet.CreatureId]);
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class CreaturePoopPerformedProcessor(
|
||||
PlayerManager playerManager,
|
||||
EntityRegistry entityRegistry
|
||||
) : TransmitIfCanSeePacketProcessor<CreaturePoopPerformed>(playerManager, entityRegistry)
|
||||
{
|
||||
public override void Process(CreaturePoopPerformed packet, Player sender) => TransmitIfCanSeeEntities(packet, sender, [packet.CreatureId]);
|
||||
}
|
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class DefaultServerPacketProcessor : AuthenticatedPacketProcessor<Packet>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
|
||||
private readonly HashSet<Type> loggingPacketBlackList = new()
|
||||
{
|
||||
typeof(AnimationChangeEvent),
|
||||
typeof(PlayerMovement),
|
||||
typeof(ItemPosition),
|
||||
typeof(PlayerStats),
|
||||
typeof(StoryGoalExecuted),
|
||||
typeof(FMODAssetPacket),
|
||||
typeof(FMODCustomEmitterPacket),
|
||||
typeof(FMODCustomLoopingEmitterPacket),
|
||||
typeof(FMODStudioEmitterPacket),
|
||||
typeof(PlayerCinematicControllerCall),
|
||||
typeof(TorpedoShot),
|
||||
typeof(TorpedoHit),
|
||||
typeof(TorpedoTargetAcquired),
|
||||
typeof(StasisSphereShot),
|
||||
typeof(StasisSphereHit),
|
||||
typeof(SeaTreaderChunkPickedUp)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Packet types which don't have a server packet processor but should not be transmitted
|
||||
/// </summary>
|
||||
private readonly HashSet<Type> defaultPacketProcessorBlacklist = new()
|
||||
{
|
||||
typeof(GameModeChanged), typeof(DropSimulationOwnership),
|
||||
};
|
||||
|
||||
public DefaultServerPacketProcessor(PlayerManager playerManager)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
}
|
||||
|
||||
public override void Process(Packet packet, Player player)
|
||||
{
|
||||
if (!loggingPacketBlackList.Contains(packet.GetType()))
|
||||
{
|
||||
Log.Debug($"Using default packet processor for: {packet} and player {player.Id}");
|
||||
}
|
||||
|
||||
if (defaultPacketProcessorBlacklist.Contains(packet.GetType()))
|
||||
{
|
||||
Log.ErrorOnce($"Player {player.Name} [{player.Id}] sent a packet which is blacklisted by the server. It's likely that the said player is using a modified version of Nitrox and action could be taken accordingly.");
|
||||
return;
|
||||
}
|
||||
playerManager.SendPacketToOtherPlayers(packet, player);
|
||||
}
|
||||
}
|
@@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxModel.Serialization;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class DiscordRequestIPProcessor : AuthenticatedPacketProcessor<DiscordRequestIP>
|
||||
{
|
||||
private readonly SubnauticaServerConfig serverConfig;
|
||||
|
||||
private string ipPort;
|
||||
|
||||
public DiscordRequestIPProcessor(SubnauticaServerConfig serverConfig)
|
||||
{
|
||||
this.serverConfig = serverConfig;
|
||||
}
|
||||
|
||||
public override void Process(DiscordRequestIP packet, Player player)
|
||||
{
|
||||
if (string.IsNullOrEmpty(ipPort))
|
||||
{
|
||||
Task.Run(() => ProcessPacketAsync(packet, player));
|
||||
return;
|
||||
}
|
||||
|
||||
packet.IpPort = ipPort;
|
||||
player.SendPacket(packet);
|
||||
}
|
||||
|
||||
private async Task ProcessPacketAsync(DiscordRequestIP packet, Player player)
|
||||
{
|
||||
string result = await GetIpAsync();
|
||||
if (result == "")
|
||||
{
|
||||
Log.Error("Couldn't get external Ip for discord request.");
|
||||
return;
|
||||
}
|
||||
|
||||
packet.IpPort = ipPort = $"{result}:{serverConfig.ServerPort}";
|
||||
player.SendPacket(packet);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the WAN IP address or the Hamachi IP address if the WAN IP address is not available.
|
||||
/// </summary>
|
||||
/// <returns>Found IP or blank string if none found</returns>
|
||||
private static async Task<string> GetIpAsync()
|
||||
{
|
||||
Task<IPAddress> wanIp = NetHelper.GetWanIpAsync();
|
||||
Task<IPAddress> hamachiIp = Task.Run(NetHelper.GetHamachiIp);
|
||||
if (await wanIp != null)
|
||||
{
|
||||
return wanIp.Result.ToString();
|
||||
}
|
||||
|
||||
if (await hamachiIp != null)
|
||||
{
|
||||
return hamachiIp.Result.ToString();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class EntityDestroyedPacketProcessor : AuthenticatedPacketProcessor<EntityDestroyed>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
private readonly EntitySimulation entitySimulation;
|
||||
private readonly WorldEntityManager worldEntityManager;
|
||||
|
||||
public EntityDestroyedPacketProcessor(PlayerManager playerManager, EntitySimulation entitySimulation, WorldEntityManager worldEntityManager)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
this.worldEntityManager = worldEntityManager;
|
||||
this.entitySimulation = entitySimulation;
|
||||
}
|
||||
|
||||
public override void Process(EntityDestroyed packet, Player destroyingPlayer)
|
||||
{
|
||||
entitySimulation.EntityDestroyed(packet.Id);
|
||||
|
||||
if (worldEntityManager.TryDestroyEntity(packet.Id, out Entity entity))
|
||||
{
|
||||
if (entity is VehicleWorldEntity vehicleWorldEntity)
|
||||
{
|
||||
worldEntityManager.MovePlayerChildrenToRoot(vehicleWorldEntity);
|
||||
}
|
||||
|
||||
foreach (Player player in playerManager.GetConnectedPlayers())
|
||||
{
|
||||
bool isOtherPlayer = player != destroyingPlayer;
|
||||
if (isOtherPlayer && player.CanSee(entity))
|
||||
{
|
||||
player.SendPacket(packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,90 @@
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class EntityMetadataUpdateProcessor : AuthenticatedPacketProcessor<EntityMetadataUpdate>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
private readonly EntityRegistry entityRegistry;
|
||||
|
||||
public EntityMetadataUpdateProcessor(PlayerManager playerManager, EntityRegistry entityRegistry)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
this.entityRegistry = entityRegistry;
|
||||
}
|
||||
|
||||
public override void Process(EntityMetadataUpdate packet, Player sendingPlayer)
|
||||
{
|
||||
if (!entityRegistry.TryGetEntityById(packet.Id, out Entity entity))
|
||||
{
|
||||
Log.Error($"Entity metadata {packet.NewValue.GetType()} updated on an entity unknown to the server {packet.Id}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (TryProcessMetadata(sendingPlayer, entity, packet.NewValue))
|
||||
{
|
||||
entity.Metadata = packet.NewValue;
|
||||
SendUpdateToVisiblePlayers(packet, sendingPlayer, entity);
|
||||
}
|
||||
}
|
||||
|
||||
private void SendUpdateToVisiblePlayers(EntityMetadataUpdate packet, Player sendingPlayer, Entity entity)
|
||||
{
|
||||
foreach (Player player in playerManager.GetConnectedPlayers())
|
||||
{
|
||||
bool updateVisibleToPlayer = player.CanSee(entity);
|
||||
|
||||
// Always sync container/storage metadata to all visible players
|
||||
bool isContainerMetadata = IsContainerRelatedMetadata(packet.NewValue);
|
||||
|
||||
if (player != sendingPlayer && (updateVisibleToPlayer || isContainerMetadata))
|
||||
{
|
||||
player.SendPacket(packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsContainerRelatedMetadata(EntityMetadata metadata)
|
||||
{
|
||||
// Check if metadata is related to containers/storage
|
||||
return metadata.GetType().Name.Contains("Container") ||
|
||||
metadata.GetType().Name.Contains("Storage") ||
|
||||
metadata.GetType().Name.Contains("Inventory");
|
||||
}
|
||||
|
||||
private bool TryProcessMetadata(Player sendingPlayer, Entity entity, EntityMetadata metadata)
|
||||
{
|
||||
return metadata switch
|
||||
{
|
||||
PlayerMetadata playerMetadata => ProcessPlayerMetadata(sendingPlayer, entity, playerMetadata),
|
||||
|
||||
// Always allow container/storage metadata updates for proper sync
|
||||
_ when IsContainerRelatedMetadata(metadata) => true,
|
||||
|
||||
// Allow metadata updates from any player by default
|
||||
_ => true
|
||||
};
|
||||
}
|
||||
|
||||
private bool ProcessPlayerMetadata(Player sendingPlayer, Entity entity, PlayerMetadata metadata)
|
||||
{
|
||||
if (sendingPlayer.GameObjectId == entity.Id)
|
||||
{
|
||||
sendingPlayer.EquippedItems.Clear();
|
||||
foreach (PlayerMetadata.EquippedItem item in metadata.EquippedItems)
|
||||
{
|
||||
sendingPlayer.EquippedItems.Add(item.Slot, item.Id);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Log.WarnOnce($"Player {sendingPlayer.Name} tried updating metadata of another player's entity {entity.Id}");
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class EntityReparentedProcessor : AuthenticatedPacketProcessor<EntityReparented>
|
||||
{
|
||||
private readonly EntityRegistry entityRegistry;
|
||||
private readonly PlayerManager playerManager;
|
||||
|
||||
public EntityReparentedProcessor(EntityRegistry entityRegistry, PlayerManager playerManager)
|
||||
{
|
||||
this.entityRegistry = entityRegistry;
|
||||
this.playerManager = playerManager;
|
||||
}
|
||||
|
||||
public override void Process(EntityReparented packet, Player player)
|
||||
{
|
||||
if (!entityRegistry.TryGetEntityById(packet.Id, out Entity entity))
|
||||
{
|
||||
Log.Error($"Couldn't find entity for {packet.Id}");
|
||||
return;
|
||||
}
|
||||
if (!entityRegistry.TryGetEntityById(packet.NewParentId, out Entity parentEntity))
|
||||
{
|
||||
Log.Error($"Couldn't find parent entity for {packet.NewParentId}");
|
||||
return;
|
||||
}
|
||||
|
||||
entityRegistry.ReparentEntity(packet.Id, packet.NewParentId);
|
||||
playerManager.SendPacketToOtherPlayers(packet, player);
|
||||
}
|
||||
}
|
@@ -0,0 +1,59 @@
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors
|
||||
{
|
||||
class EntitySpawnedByClientProcessor : AuthenticatedPacketProcessor<EntitySpawnedByClient>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
private readonly EntityRegistry entityRegistry;
|
||||
private readonly WorldEntityManager worldEntityManager;
|
||||
private readonly EntitySimulation entitySimulation;
|
||||
|
||||
public EntitySpawnedByClientProcessor(PlayerManager playerManager, EntityRegistry entityRegistry, WorldEntityManager worldEntityManager, EntitySimulation entitySimulation)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
this.entityRegistry = entityRegistry;
|
||||
this.worldEntityManager = worldEntityManager;
|
||||
this.entitySimulation = entitySimulation;
|
||||
}
|
||||
|
||||
public override void Process(EntitySpawnedByClient packet, Player playerWhoSpawned)
|
||||
{
|
||||
Entity entity = packet.Entity;
|
||||
|
||||
// If the entity already exists in the registry, it is fine to update. This is a normal case as the player
|
||||
// may have an item in their inventory (that the registry knows about) then wants to spawn it into the world.
|
||||
entityRegistry.AddOrUpdate(entity);
|
||||
|
||||
SimulatedEntity simulatedEntity = null;
|
||||
if (entity is WorldEntity worldEntity)
|
||||
{
|
||||
worldEntityManager.TrackEntityInTheWorld(worldEntity);
|
||||
|
||||
if (packet.RequireSimulation)
|
||||
{
|
||||
simulatedEntity = entitySimulation.AssignNewEntityToPlayer(entity, playerWhoSpawned);
|
||||
|
||||
SimulationOwnershipChange ownershipChangePacket = new SimulationOwnershipChange(simulatedEntity);
|
||||
playerManager.SendPacketToAllPlayers(ownershipChangePacket);
|
||||
}
|
||||
}
|
||||
|
||||
SpawnEntities spawnEntities = new(entity, simulatedEntity, packet.RequireRespawn);
|
||||
foreach (Player player in playerManager.GetConnectedPlayers())
|
||||
{
|
||||
bool isOtherPlayer = player != playerWhoSpawned;
|
||||
if (isOtherPlayer && player.CanSee(entity))
|
||||
{
|
||||
player.SendPacket(spawnEntities);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,89 @@
|
||||
using System.Collections.Generic;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
using static NitroxModel.Packets.EntityTransformUpdates;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors
|
||||
{
|
||||
class EntityTransformUpdatesProcessor : AuthenticatedPacketProcessor<EntityTransformUpdates>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
private readonly WorldEntityManager worldEntityManager;
|
||||
private readonly SimulationOwnershipData simulationOwnershipData;
|
||||
|
||||
public EntityTransformUpdatesProcessor(PlayerManager playerManager, WorldEntityManager worldEntityManager, SimulationOwnershipData simulationOwnershipData)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
this.worldEntityManager = worldEntityManager;
|
||||
this.simulationOwnershipData = simulationOwnershipData;
|
||||
}
|
||||
|
||||
public override void Process(EntityTransformUpdates packet, Player simulatingPlayer)
|
||||
{
|
||||
Dictionary<Player, List<EntityTransformUpdate>> visibleUpdatesByPlayer = InitializeVisibleUpdateMapWithOtherPlayers(simulatingPlayer);
|
||||
AssignVisibleUpdatesToPlayers(simulatingPlayer, packet.Updates, visibleUpdatesByPlayer);
|
||||
SendUpdatesToPlayers(visibleUpdatesByPlayer);
|
||||
}
|
||||
|
||||
private Dictionary<Player, List<EntityTransformUpdate>> InitializeVisibleUpdateMapWithOtherPlayers(Player simulatingPlayer)
|
||||
{
|
||||
Dictionary<Player, List<EntityTransformUpdate>> visibleUpdatesByPlayer = new Dictionary<Player, List<EntityTransformUpdate>>();
|
||||
|
||||
foreach (Player player in playerManager.GetConnectedPlayers())
|
||||
{
|
||||
if (!player.Equals(simulatingPlayer))
|
||||
{
|
||||
visibleUpdatesByPlayer[player] = new List<EntityTransformUpdate>();
|
||||
}
|
||||
}
|
||||
|
||||
return visibleUpdatesByPlayer;
|
||||
}
|
||||
|
||||
private void AssignVisibleUpdatesToPlayers(Player sendingPlayer, List<EntityTransformUpdate> updates, Dictionary<Player, List<EntityTransformUpdate>> visibleUpdatesByPlayer)
|
||||
{
|
||||
foreach (EntityTransformUpdate update in updates)
|
||||
{
|
||||
if (!simulationOwnershipData.TryGetLock(update.Id, out SimulationOwnershipData.PlayerLock playerLock) || playerLock.Player != sendingPlayer)
|
||||
{
|
||||
// This will happen pretty frequently when a player moves very fast (swimfast or maybe some more edge cases) so we can just ignore this
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!worldEntityManager.TryUpdateEntityPosition(update.Id, update.Position, update.Rotation, out AbsoluteEntityCell currentCell, out WorldEntity worldEntity))
|
||||
{
|
||||
// Normal behaviour if the entity was removed at the same time as someone trying to simulate a postion update.
|
||||
// we log an info inside entityManager.UpdateEntityPosition just in case.
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<Player, List<EntityTransformUpdate>> playerUpdates in visibleUpdatesByPlayer)
|
||||
{
|
||||
if (playerUpdates.Key.CanSee(worldEntity))
|
||||
{
|
||||
playerUpdates.Value.Add(update);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SendUpdatesToPlayers(Dictionary<Player, List<EntityTransformUpdate>> visibleUpdatesByPlayer)
|
||||
{
|
||||
foreach (KeyValuePair<Player, List<EntityTransformUpdate>> playerUpdates in visibleUpdatesByPlayer)
|
||||
{
|
||||
Player player = playerUpdates.Key;
|
||||
List<EntityTransformUpdate> updates = playerUpdates.Value;
|
||||
|
||||
if (updates.Count > 0)
|
||||
{
|
||||
EntityTransformUpdates updatesPacket = new EntityTransformUpdates(updates);
|
||||
player.SendPacket(updatesPacket);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors
|
||||
{
|
||||
public class EscapePodChangedPacketProcessor : AuthenticatedPacketProcessor<EscapePodChanged>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
|
||||
public EscapePodChangedPacketProcessor(PlayerManager playerManager)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
}
|
||||
|
||||
public override void Process(EscapePodChanged packet, Player player)
|
||||
{
|
||||
Log.Debug(packet);
|
||||
player.SubRootId = packet.EscapePodId;
|
||||
playerManager.SendPacketToOtherPlayers(packet, player);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
using NitroxModel.DataStructures.Unity;
|
||||
using NitroxModel.GameLogic.FMOD;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class FMODAssetProcessor : AuthenticatedPacketProcessor<FMODAssetPacket>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
private readonly FMODWhitelist fmodWhitelist;
|
||||
|
||||
public FMODAssetProcessor(PlayerManager playerManager, FMODWhitelist fmodWhitelist)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
this.fmodWhitelist = fmodWhitelist;
|
||||
}
|
||||
|
||||
public override void Process(FMODAssetPacket packet, Player sendingPlayer)
|
||||
{
|
||||
if (!fmodWhitelist.TryGetSoundData(packet.AssetPath, out SoundData soundData))
|
||||
{
|
||||
Log.Error($"[{nameof(FMODAssetProcessor)}] Whitelist has no item for {packet.AssetPath}.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (Player player in playerManager.GetConnectedPlayers())
|
||||
{
|
||||
float distance = NitroxVector3.Distance(player.Position, packet.Position);
|
||||
if (player != sendingPlayer && (soundData.IsGlobal || player.SubRootId.Equals(sendingPlayer.SubRootId)) && distance <= soundData.Radius)
|
||||
{
|
||||
packet.Volume = SoundHelper.CalculateVolume(distance, soundData.Radius, packet.Volume);
|
||||
player.SendPacket(packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
using NitroxModel.DataStructures.Unity;
|
||||
using NitroxModel.GameLogic.FMOD;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class FMODEventInstanceProcessor : AuthenticatedPacketProcessor<FMODEventInstancePacket>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
private readonly FMODWhitelist fmodWhitelist;
|
||||
|
||||
public FMODEventInstanceProcessor(PlayerManager playerManager, FMODWhitelist fmodWhitelist)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
this.fmodWhitelist = fmodWhitelist;
|
||||
}
|
||||
|
||||
public override void Process(FMODEventInstancePacket packet, Player sendingPlayer)
|
||||
{
|
||||
if (!fmodWhitelist.TryGetSoundData(packet.AssetPath, out SoundData soundData))
|
||||
{
|
||||
Log.Error($"[{nameof(FMODEventInstanceProcessor)}] Whitelist has no item for {packet.AssetPath}.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (Player player in playerManager.GetConnectedPlayers())
|
||||
{
|
||||
float distance = NitroxVector3.Distance(player.Position, packet.Position);
|
||||
if (player != sendingPlayer &&
|
||||
(soundData.IsGlobal || player.SubRootId.Equals(sendingPlayer.SubRootId)) &&
|
||||
distance < soundData.Radius)
|
||||
{
|
||||
packet.Volume = SoundHelper.CalculateVolume(distance, soundData.Radius, packet.Volume);
|
||||
player.SendPacket(packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors
|
||||
{
|
||||
class FireDousedProcessor : AuthenticatedPacketProcessor<FireDoused>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
|
||||
public FireDousedProcessor(PlayerManager playerManager)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
}
|
||||
|
||||
public override void Process(FireDoused packet, Player simulatingPlayer)
|
||||
{
|
||||
playerManager.SendPacketToOtherPlayers(packet, simulatingPlayer);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
using NitroxModel.DataStructures.Unity;
|
||||
using NitroxModel.GameLogic.FMOD;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class FootstepPacketProcessor : AuthenticatedPacketProcessor<FootstepPacket>
|
||||
{
|
||||
private readonly float footstepAudioRange; // To modify this value, modify the last value of the event:/player/footstep_precursor_base sound in the SoundWhitelist_Subnautica.csv file
|
||||
private readonly PlayerManager playerManager;
|
||||
|
||||
public FootstepPacketProcessor(PlayerManager playerManager, FMODWhitelist whitelist)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
whitelist.TryGetSoundData("event:/player/footstep_precursor_base", out SoundData soundData);
|
||||
footstepAudioRange = soundData.Radius;
|
||||
}
|
||||
|
||||
public override void Process(FootstepPacket footstepPacket, Player sendingPlayer)
|
||||
{
|
||||
foreach (Player player in playerManager.GetConnectedPlayers())
|
||||
{
|
||||
if (NitroxVector3.Distance(player.Position, sendingPlayer.Position) >= footstepAudioRange ||
|
||||
player == sendingPlayer)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if(player.SubRootId.Equals(sendingPlayer.SubRootId))
|
||||
{
|
||||
player.SendPacket(footstepPacket);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class GoalCompletedProcessor : AuthenticatedPacketProcessor<GoalCompleted>
|
||||
{
|
||||
public override void Process(GoalCompleted packet, Player player)
|
||||
{
|
||||
player.PersonalCompletedGoalsWithTimestamp.Add(packet.CompletedGoal, packet.CompletionTime);
|
||||
}
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Unlockables;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors
|
||||
{
|
||||
public class KnownTechEntryAddProcessor : AuthenticatedPacketProcessor<KnownTechEntryAdd>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
private readonly PDAStateData pdaStateData;
|
||||
|
||||
public KnownTechEntryAddProcessor(PlayerManager playerManager, PDAStateData pdaStateData)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
this.pdaStateData = pdaStateData;
|
||||
}
|
||||
|
||||
public override void Process(KnownTechEntryAdd packet, Player player)
|
||||
{
|
||||
switch (packet.Category)
|
||||
{
|
||||
case KnownTechEntryAdd.EntryCategory.KNOWN:
|
||||
pdaStateData.AddKnownTechType(packet.TechType, packet.PartialTechTypesToRemove);
|
||||
break;
|
||||
case KnownTechEntryAdd.EntryCategory.ANALYZED:
|
||||
pdaStateData.AddAnalyzedTechType(packet.TechType);
|
||||
break;
|
||||
}
|
||||
|
||||
playerManager.SendPacketToOtherPlayers(packet, player);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Bases;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class LargeWaterParkDeconstructedProcessor : BuildingProcessor<LargeWaterParkDeconstructed>
|
||||
{
|
||||
public LargeWaterParkDeconstructedProcessor(BuildingManager buildingManager, PlayerManager playerManager) : base(buildingManager, playerManager) { }
|
||||
|
||||
public override void Process(LargeWaterParkDeconstructed packet, Player player)
|
||||
{
|
||||
// SeparateChildrenToWaterParks must happen before ReplacePieceByGhost
|
||||
// so the water park's children can be moved before it being removed
|
||||
if (buildingManager.SeparateChildrenToWaterParks(packet) &&
|
||||
buildingManager.ReplacePieceByGhost(player, packet, out _, out int operationId))
|
||||
{
|
||||
packet.BaseData = null;
|
||||
SendToOtherPlayersWithOperationId(packet, player, operationId);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class LeakRepairedProcessor : AuthenticatedPacketProcessor<LeakRepaired>
|
||||
{
|
||||
private readonly WorldEntityManager worldEntityManager;
|
||||
private readonly PlayerManager playerManager;
|
||||
|
||||
public LeakRepairedProcessor(WorldEntityManager worldEntityManager, PlayerManager playerManager)
|
||||
{
|
||||
this.worldEntityManager = worldEntityManager;
|
||||
this.playerManager = playerManager;
|
||||
}
|
||||
|
||||
public override void Process(LeakRepaired packet, Player player)
|
||||
{
|
||||
if (worldEntityManager.TryDestroyEntity(packet.LeakId, out _))
|
||||
{
|
||||
playerManager.SendPacketToOtherPlayers(packet, player);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Bases;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class ModifyConstructedAmountProcessor : BuildingProcessor<ModifyConstructedAmount>
|
||||
{
|
||||
public ModifyConstructedAmountProcessor(BuildingManager buildingManager, PlayerManager playerManager) : base(buildingManager, playerManager) { }
|
||||
|
||||
public override void Process(ModifyConstructedAmount packet, Player player)
|
||||
{
|
||||
if (buildingManager.ModifyConstructedAmount(packet))
|
||||
{
|
||||
playerManager.SendPacketToOtherPlayers(packet, player);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors
|
||||
{
|
||||
class ModuleAddedProcessor : AuthenticatedPacketProcessor<ModuleAdded>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
private readonly EntityRegistry entityRegistry;
|
||||
|
||||
public ModuleAddedProcessor(PlayerManager playerManager, EntityRegistry entityRegistry)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
this.entityRegistry = entityRegistry;
|
||||
}
|
||||
|
||||
public override void Process(ModuleAdded packet, Player player)
|
||||
{
|
||||
Optional<Entity> entity = entityRegistry.GetEntityById(packet.Id);
|
||||
|
||||
if (!entity.HasValue)
|
||||
{
|
||||
Log.Error($"Could not find entity {packet.Id} module added to a vehicle.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (entity.Value is InventoryItemEntity inventoryItem)
|
||||
{
|
||||
InstalledModuleEntity moduleEntity = new(packet.Slot, inventoryItem.ClassId, inventoryItem.Id, inventoryItem.TechType, inventoryItem.Metadata, packet.ParentId, inventoryItem.ChildEntities);
|
||||
|
||||
// Convert the world entity into an inventory item
|
||||
entityRegistry.AddOrUpdate(moduleEntity);
|
||||
|
||||
// Have other players respawn the item inside the inventory.
|
||||
playerManager.SendPacketToOtherPlayers(new SpawnEntities(moduleEntity, forceRespawn: true), player);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors
|
||||
{
|
||||
class ModuleRemovedProcessor : AuthenticatedPacketProcessor<ModuleRemoved>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
private readonly EntityRegistry entityRegistry;
|
||||
|
||||
public ModuleRemovedProcessor(PlayerManager playerManager, EntityRegistry entityRegistry)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
this.entityRegistry = entityRegistry;
|
||||
}
|
||||
|
||||
public override void Process(ModuleRemoved packet, Player player)
|
||||
{
|
||||
Optional<Entity> entity = entityRegistry.GetEntityById(packet.Id);
|
||||
|
||||
if (!entity.HasValue)
|
||||
{
|
||||
Log.Error($"Could not find entity {packet.Id} module added to a vehicle.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (entity.Value is InstalledModuleEntity installedModule)
|
||||
{
|
||||
InventoryItemEntity inventoryEntity = new(installedModule.Id, installedModule.ClassId, installedModule.TechType, installedModule.Metadata, packet.NewParentId, installedModule.ChildEntities);
|
||||
|
||||
// Convert the world entity into an inventory item
|
||||
entityRegistry.AddOrUpdate(inventoryEntity);
|
||||
|
||||
// Have other players respawn the item inside the inventory.
|
||||
playerManager.SendPacketToOtherPlayers(new SpawnEntities(inventoryEntity, forceRespawn: true), player);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxModel.Serialization;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors
|
||||
{
|
||||
public class MultiplayerSessionPolicyRequestProcessor : UnauthenticatedPacketProcessor<MultiplayerSessionPolicyRequest>
|
||||
{
|
||||
private readonly SubnauticaServerConfig config;
|
||||
|
||||
public MultiplayerSessionPolicyRequestProcessor(SubnauticaServerConfig config)
|
||||
{
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
// This will extend in the future when we look into different options for auth
|
||||
public override void Process(MultiplayerSessionPolicyRequest packet, INitroxConnection connection)
|
||||
{
|
||||
Log.Info("Providing session policies...");
|
||||
connection.SendPacket(new MultiplayerSessionPolicy(packet.CorrelationId, config.DisableConsole, config.MaxConnections, config.IsPasswordRequired()));
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
using NitroxModel.MultiplayerSession;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors
|
||||
{
|
||||
public class MultiplayerSessionReservationRequestProcessor : UnauthenticatedPacketProcessor<MultiplayerSessionReservationRequest>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
|
||||
public MultiplayerSessionReservationRequestProcessor(PlayerManager playerManager)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
}
|
||||
|
||||
public override void Process(MultiplayerSessionReservationRequest packet, INitroxConnection connection)
|
||||
{
|
||||
Log.Info($"Processing reservation request from {packet.AuthenticationContext.Username}");
|
||||
|
||||
string correlationId = packet.CorrelationId;
|
||||
PlayerSettings playerSettings = packet.PlayerSettings;
|
||||
AuthenticationContext authenticationContext = packet.AuthenticationContext;
|
||||
MultiplayerSessionReservation reservation = playerManager.ReservePlayerContext(
|
||||
connection,
|
||||
playerSettings,
|
||||
authenticationContext,
|
||||
correlationId);
|
||||
|
||||
Log.Info($"Reservation processed successfully: Username: {packet.AuthenticationContext.Username} - {reservation}");
|
||||
|
||||
connection.SendPacket(reservation);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Unlockables;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors
|
||||
{
|
||||
public class PDAEncyclopediaEntryAddProcessor : AuthenticatedPacketProcessor<PDAEncyclopediaEntryAdd>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
private readonly PDAStateData pdaStateData;
|
||||
|
||||
public PDAEncyclopediaEntryAddProcessor(PlayerManager playerManager, PDAStateData pdaStateData)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
this.pdaStateData = pdaStateData;
|
||||
}
|
||||
|
||||
public override void Process(PDAEncyclopediaEntryAdd packet, Player player)
|
||||
{
|
||||
pdaStateData.AddEncyclopediaEntry(packet.Key);
|
||||
playerManager.SendPacketToOtherPlayers(packet, player);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Unlockables;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors
|
||||
{
|
||||
public class PDALogEntryAddProcessor : AuthenticatedPacketProcessor<PDALogEntryAdd>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
private readonly PDAStateData pdaState;
|
||||
private readonly ScheduleKeeper scheduleKeeper;
|
||||
|
||||
public PDALogEntryAddProcessor(PlayerManager playerManager, PDAStateData pdaState, ScheduleKeeper scheduleKeeper)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
this.pdaState = pdaState;
|
||||
this.scheduleKeeper = scheduleKeeper;
|
||||
}
|
||||
|
||||
public override void Process(PDALogEntryAdd packet, Player player)
|
||||
{
|
||||
pdaState.AddPDALogEntry(new PDALogEntry(packet.Key, packet.Timestamp));
|
||||
if (scheduleKeeper.ContainsScheduledGoal(packet.Key))
|
||||
{
|
||||
scheduleKeeper.UnScheduleGoal(packet.Key);
|
||||
}
|
||||
playerManager.SendPacketToOtherPlayers(packet, player);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
using NitroxServer.GameLogic.Unlockables;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class PDAScanFinishedPacketProcessor : AuthenticatedPacketProcessor<PDAScanFinished>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
private readonly PDAStateData pdaStateData;
|
||||
private readonly WorldEntityManager worldEntityManager;
|
||||
|
||||
public PDAScanFinishedPacketProcessor(PlayerManager playerManager, PDAStateData pdaStateData, WorldEntityManager worldEntityManager)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
this.pdaStateData = pdaStateData;
|
||||
this.worldEntityManager = worldEntityManager;
|
||||
}
|
||||
|
||||
public override void Process(PDAScanFinished packet, Player player)
|
||||
{
|
||||
if (!packet.WasAlreadyResearched)
|
||||
{
|
||||
pdaStateData.UpdateEntryUnlockedProgress(packet.TechType, packet.UnlockedAmount, packet.FullyResearched);
|
||||
}
|
||||
playerManager.SendPacketToOtherPlayers(packet, player);
|
||||
|
||||
|
||||
if (packet.Id != null)
|
||||
{
|
||||
if (packet.Destroy)
|
||||
{
|
||||
worldEntityManager.TryDestroyEntity(packet.Id, out _);
|
||||
}
|
||||
else
|
||||
{
|
||||
pdaStateData.AddScannerFragment(packet.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,55 @@
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class PickupItemPacketProcessor : AuthenticatedPacketProcessor<PickupItem>
|
||||
{
|
||||
private readonly EntityRegistry entityRegistry;
|
||||
private readonly WorldEntityManager worldEntityManager;
|
||||
private readonly PlayerManager playerManager;
|
||||
private readonly SimulationOwnershipData simulationOwnershipData;
|
||||
|
||||
public PickupItemPacketProcessor(EntityRegistry entityRegistry, WorldEntityManager worldEntityManager, PlayerManager playerManager, SimulationOwnershipData simulationOwnershipData)
|
||||
{
|
||||
this.entityRegistry = entityRegistry;
|
||||
this.worldEntityManager = worldEntityManager;
|
||||
this.playerManager = playerManager;
|
||||
this.simulationOwnershipData = simulationOwnershipData;
|
||||
}
|
||||
|
||||
public override void Process(PickupItem packet, Player player)
|
||||
{
|
||||
NitroxId id = packet.Item.Id;
|
||||
if (simulationOwnershipData.RevokeOwnerOfId(id))
|
||||
{
|
||||
ushort serverId = ushort.MaxValue;
|
||||
SimulationOwnershipChange simulationOwnershipChange = new(id, serverId, SimulationLockType.TRANSIENT);
|
||||
playerManager.SendPacketToAllPlayers(simulationOwnershipChange);
|
||||
}
|
||||
|
||||
StopTrackingExistingWorldEntity(id);
|
||||
|
||||
entityRegistry.AddOrUpdate(packet.Item);
|
||||
|
||||
// Have other players respawn the item inside the inventory.
|
||||
playerManager.SendPacketToOtherPlayers(new SpawnEntities(packet.Item, forceRespawn: true), player);
|
||||
}
|
||||
|
||||
private void StopTrackingExistingWorldEntity(NitroxId id)
|
||||
{
|
||||
Optional<Entity> entity = entityRegistry.GetEntityById(id);
|
||||
|
||||
if (entity.HasValue && entity.Value is WorldEntity worldEntity)
|
||||
{
|
||||
// Do not track this entity in the open world anymore.
|
||||
worldEntityManager.StopTrackingEntity(worldEntity);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Bases;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class PieceDeconstructedProcessor : BuildingProcessor<PieceDeconstructed>
|
||||
{
|
||||
public PieceDeconstructedProcessor(BuildingManager buildingManager, PlayerManager playerManager) : base(buildingManager, playerManager) { }
|
||||
|
||||
public override void Process(PieceDeconstructed packet, Player player)
|
||||
{
|
||||
if (buildingManager.ReplacePieceByGhost(player, packet, out _, out int operationId))
|
||||
{
|
||||
packet.BaseData = null;
|
||||
SendToOtherPlayersWithOperationId(packet, player, operationId);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class PinnedRecipeMovedProcessor : AuthenticatedPacketProcessor<PinnedRecipeMoved>
|
||||
{
|
||||
public override void Process(PinnedRecipeMoved packet, Player player)
|
||||
{
|
||||
player.PinnedRecipePreferences.Clear();
|
||||
player.PinnedRecipePreferences.AddRange(packet.RecipePins);
|
||||
}
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Bases;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class PlaceBaseProcessor : BuildingProcessor<PlaceBase>
|
||||
{
|
||||
public PlaceBaseProcessor(BuildingManager buildingManager, PlayerManager playerManager, EntitySimulation entitySimulation) : base(buildingManager, playerManager, entitySimulation){ }
|
||||
|
||||
public override void Process(PlaceBase packet, Player player)
|
||||
{
|
||||
if (buildingManager.CreateBase(packet))
|
||||
{
|
||||
ClaimBuildPiece(packet.BuildEntity, player);
|
||||
|
||||
// End-players can process elementary operations without this data (packet would be heavier for no reason)
|
||||
packet.Deflate();
|
||||
playerManager.SendPacketToOtherPlayers(packet, player);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Bases;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class PlaceGhostProcessor : BuildingProcessor<PlaceGhost>
|
||||
{
|
||||
public PlaceGhostProcessor(BuildingManager buildingManager, PlayerManager playerManager) : base(buildingManager, playerManager) { }
|
||||
|
||||
public override void Process(PlaceGhost packet, Player player)
|
||||
{
|
||||
if (buildingManager.AddGhost(packet))
|
||||
{
|
||||
playerManager.SendPacketToOtherPlayers(packet, player);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Bases;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class PlaceModuleProcessor : BuildingProcessor<PlaceModule>
|
||||
{
|
||||
public PlaceModuleProcessor(BuildingManager buildingManager, PlayerManager playerManager, EntitySimulation entitySimulation) : base(buildingManager, playerManager, entitySimulation) { }
|
||||
|
||||
public override void Process(PlaceModule packet, Player player)
|
||||
{
|
||||
if (buildingManager.AddModule(packet))
|
||||
{
|
||||
if (packet.ModuleEntity.ParentId == null)
|
||||
{
|
||||
ClaimBuildPiece(packet.ModuleEntity, player);
|
||||
}
|
||||
playerManager.SendPacketToOtherPlayers(packet, player);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxModel.Serialization;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors
|
||||
{
|
||||
class PlayerDeathEventProcessor : AuthenticatedPacketProcessor<PlayerDeathEvent>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
private readonly SubnauticaServerConfig serverConfig;
|
||||
|
||||
public PlayerDeathEventProcessor(PlayerManager playerManager, SubnauticaServerConfig config)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
this.serverConfig = config;
|
||||
}
|
||||
|
||||
public override void Process(PlayerDeathEvent packet, Player player)
|
||||
{
|
||||
if (serverConfig.IsHardcore())
|
||||
{
|
||||
player.IsPermaDeath = true;
|
||||
PlayerKicked playerKicked = new PlayerKicked("Permanent death from hardcore mode");
|
||||
player.SendPacket(playerKicked);
|
||||
}
|
||||
|
||||
player.LastStoredPosition = packet.DeathPosition;
|
||||
player.LastStoredSubRootID = player.SubRootId;
|
||||
|
||||
if (player.Permissions > Perms.MODERATOR)
|
||||
{
|
||||
player.SendPacket(new ChatMessage(ChatMessage.SERVER_ID, "You can use /back to go to your death location"));
|
||||
}
|
||||
|
||||
playerManager.SendPacketToOtherPlayers(packet, player);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors
|
||||
{
|
||||
public class PlayerHeldItemChangedProcessor : AuthenticatedPacketProcessor<PlayerHeldItemChanged>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
|
||||
public PlayerHeldItemChangedProcessor(PlayerManager playerManager)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
}
|
||||
|
||||
public override void Process(PlayerHeldItemChanged packet, Player player)
|
||||
{
|
||||
if (packet.IsFirstTime != null && !player.UsedItems.Contains(packet.IsFirstTime))
|
||||
{
|
||||
player.UsedItems.Add(packet.IsFirstTime);
|
||||
}
|
||||
|
||||
playerManager.SendPacketToOtherPlayers(packet, player);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class PlayerInCyclopsMovementProcessor : AuthenticatedPacketProcessor<PlayerInCyclopsMovement>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
private readonly EntityRegistry entityRegistry;
|
||||
|
||||
public PlayerInCyclopsMovementProcessor(PlayerManager playerManager, EntityRegistry entityRegistry)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
this.entityRegistry = entityRegistry;
|
||||
}
|
||||
|
||||
public override void Process(PlayerInCyclopsMovement packet, Player player)
|
||||
{
|
||||
if (entityRegistry.TryGetEntityById(player.PlayerContext.PlayerNitroxId, out PlayerWorldEntity playerWorldEntity))
|
||||
{
|
||||
playerWorldEntity.Transform.LocalPosition = packet.LocalPosition;
|
||||
playerWorldEntity.Transform.LocalRotation = packet.LocalRotation;
|
||||
|
||||
player.Position = playerWorldEntity.Transform.Position;
|
||||
player.Rotation = playerWorldEntity.Transform.Rotation;
|
||||
playerManager.SendPacketToOtherPlayers(packet, player);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.ErrorOnce($"{nameof(PlayerWorldEntity)} couldn't be found for player {player.Name}. It is adviced the player reconnects before losing too much progression.");
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,128 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.DataStructures.Unity;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel.MultiplayerSession;
|
||||
using NitroxModel.Networking;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxModel.Serialization;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Bases;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
using NitroxServer.Serialization.World;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors
|
||||
{
|
||||
public class PlayerJoiningMultiplayerSessionProcessor : UnauthenticatedPacketProcessor<PlayerJoiningMultiplayerSession>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
private readonly ScheduleKeeper scheduleKeeper;
|
||||
private readonly StoryManager storyManager;
|
||||
private readonly World world;
|
||||
private readonly EntityRegistry entityRegistry;
|
||||
private readonly SubnauticaServerConfig serverConfig;
|
||||
private readonly NtpSyncer ntpSyncer;
|
||||
private readonly SessionSettings sessionSettings;
|
||||
|
||||
public PlayerJoiningMultiplayerSessionProcessor(ScheduleKeeper scheduleKeeper, StoryManager storyManager, PlayerManager playerManager, World world, EntityRegistry entityRegistry, SubnauticaServerConfig serverConfig, NtpSyncer ntpSyncer, SessionSettings sessionSettings)
|
||||
{
|
||||
this.scheduleKeeper = scheduleKeeper;
|
||||
this.storyManager = storyManager;
|
||||
this.playerManager = playerManager;
|
||||
this.world = world;
|
||||
this.entityRegistry = entityRegistry;
|
||||
this.serverConfig = serverConfig;
|
||||
this.ntpSyncer = ntpSyncer;
|
||||
this.sessionSettings = sessionSettings;
|
||||
}
|
||||
|
||||
public override void Process(PlayerJoiningMultiplayerSession packet, INitroxConnection connection)
|
||||
{
|
||||
Player player = playerManager.PlayerConnected(connection, packet.ReservationKey, out bool wasBrandNewPlayer);
|
||||
NitroxId assignedEscapePodId = world.EscapePodManager.AssignPlayerToEscapePod(player.Id, out Optional<EscapePodWorldEntity> newlyCreatedEscapePod);
|
||||
|
||||
if (wasBrandNewPlayer)
|
||||
{
|
||||
player.SubRootId = assignedEscapePodId;
|
||||
}
|
||||
|
||||
if (newlyCreatedEscapePod.HasValue)
|
||||
{
|
||||
SpawnEntities spawnNewEscapePod = new(newlyCreatedEscapePod.Value);
|
||||
playerManager.SendPacketToOtherPlayers(spawnNewEscapePod, player);
|
||||
}
|
||||
|
||||
// Make players on localhost admin by default.
|
||||
if (connection.Endpoint.Address.IsLocalhost())
|
||||
{
|
||||
Log.Info($"Granted admin to '{player.Name}' because they're playing on the host machine");
|
||||
player.Permissions = Perms.ADMIN;
|
||||
}
|
||||
|
||||
List<SimulatedEntity> simulations = world.EntitySimulation.AssignGlobalRootEntitiesAndGetData(player);
|
||||
|
||||
player.Entity = wasBrandNewPlayer ? SetupPlayerEntity(player) : RespawnExistingEntity(player);
|
||||
|
||||
List<GlobalRootEntity> globalRootEntities = world.WorldEntityManager.GetGlobalRootEntities(true);
|
||||
bool isFirstPlayer = playerManager.GetConnectedPlayers().Count == 1;
|
||||
|
||||
InitialPlayerSync initialPlayerSync = new(player.GameObjectId,
|
||||
wasBrandNewPlayer,
|
||||
assignedEscapePodId,
|
||||
player.EquippedItems,
|
||||
player.UsedItems,
|
||||
player.QuickSlotsBindingIds,
|
||||
world.GameData.PDAState.GetInitialPDAData(),
|
||||
world.GameData.StoryGoals.GetInitialStoryGoalData(scheduleKeeper, player),
|
||||
player.Position,
|
||||
player.Rotation,
|
||||
player.SubRootId,
|
||||
player.Stats,
|
||||
GetOtherPlayers(player),
|
||||
globalRootEntities,
|
||||
simulations,
|
||||
player.GameMode,
|
||||
player.Permissions,
|
||||
wasBrandNewPlayer ? IntroCinematicMode.LOADING : IntroCinematicMode.COMPLETED,
|
||||
new(new(player.PingInstancePreferences), player.PinnedRecipePreferences.ToList()),
|
||||
storyManager.GetTimeData(),
|
||||
isFirstPlayer,
|
||||
BuildingManager.GetEntitiesOperations(globalRootEntities),
|
||||
serverConfig.KeepInventoryOnDeath,
|
||||
sessionSettings
|
||||
);
|
||||
|
||||
player.SendPacket(initialPlayerSync);
|
||||
}
|
||||
|
||||
private IEnumerable<PlayerContext> GetOtherPlayers(Player player)
|
||||
{
|
||||
return playerManager.GetConnectedPlayers().Where(p => p != player)
|
||||
.Select(p => p.PlayerContext);
|
||||
}
|
||||
|
||||
private PlayerWorldEntity SetupPlayerEntity(Player player)
|
||||
{
|
||||
NitroxTransform transform = new(player.Position, player.Rotation, NitroxVector3.One);
|
||||
|
||||
PlayerWorldEntity playerEntity = new PlayerWorldEntity(transform, 0, null, false, player.GameObjectId, NitroxTechType.None, null, null, new List<Entity>());
|
||||
entityRegistry.AddOrUpdate(playerEntity);
|
||||
world.WorldEntityManager.TrackEntityInTheWorld(playerEntity);
|
||||
return playerEntity;
|
||||
}
|
||||
|
||||
private PlayerWorldEntity RespawnExistingEntity(Player player)
|
||||
{
|
||||
if (entityRegistry.TryGetEntityById(player.PlayerContext.PlayerNitroxId, out PlayerWorldEntity playerWorldEntity))
|
||||
{
|
||||
return playerWorldEntity;
|
||||
}
|
||||
Log.Error($"Unable to find player entity for {player.Name}. Re-creating one");
|
||||
return SetupPlayerEntity(player);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors
|
||||
{
|
||||
class PlayerMovementProcessor : AuthenticatedPacketProcessor<PlayerMovement>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
private readonly EntityRegistry entityRegistry;
|
||||
|
||||
public PlayerMovementProcessor(PlayerManager playerManager, EntityRegistry entityRegistry)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
this.entityRegistry = entityRegistry;
|
||||
}
|
||||
|
||||
public override void Process(PlayerMovement packet, Player player)
|
||||
{
|
||||
Optional<PlayerWorldEntity> playerEntity = entityRegistry.GetEntityById<PlayerWorldEntity>(player.PlayerContext.PlayerNitroxId);
|
||||
|
||||
if (playerEntity.HasValue)
|
||||
{
|
||||
playerEntity.Value.Transform.Position = packet.Position;
|
||||
playerEntity.Value.Transform.Rotation = packet.BodyRotation;
|
||||
}
|
||||
|
||||
player.Position = packet.Position;
|
||||
player.Rotation = packet.BodyRotation;
|
||||
playerManager.SendPacketToOtherPlayers(packet, player);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors
|
||||
{
|
||||
public class PlayerQuickSlotsBindingChangedProcessor : AuthenticatedPacketProcessor<PlayerQuickSlotsBindingChanged>
|
||||
{
|
||||
public override void Process(PlayerQuickSlotsBindingChanged packet, Player player)
|
||||
{
|
||||
player.QuickSlotsBindingIds = packet.SlotItemIds;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class PlayerSeeOutOfCellEntityProcessor : AuthenticatedPacketProcessor<PlayerSeeOutOfCellEntity>
|
||||
{
|
||||
private readonly EntityRegistry entityRegistry;
|
||||
|
||||
public PlayerSeeOutOfCellEntityProcessor(EntityRegistry entityRegistry)
|
||||
{
|
||||
this.entityRegistry = entityRegistry;
|
||||
}
|
||||
|
||||
public override void Process(PlayerSeeOutOfCellEntity packet, Player player)
|
||||
{
|
||||
if (entityRegistry.GetEntityById(packet.EntityId).HasValue)
|
||||
{
|
||||
player.OutOfCellVisibleEntities.Add(packet.EntityId);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class PlayerStatsProcessor : AuthenticatedPacketProcessor<PlayerStats>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
|
||||
public PlayerStatsProcessor(PlayerManager playerManager)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
}
|
||||
|
||||
public override void Process(PlayerStats packet, Player player)
|
||||
{
|
||||
if (packet.PlayerId != player.Id)
|
||||
{
|
||||
Log.WarnOnce($"[{nameof(PlayerStatsProcessor)}] Player ID mismatch (received: {packet.PlayerId}, real: {player.Id})");
|
||||
packet.PlayerId = player.Id;
|
||||
}
|
||||
player.Stats = new PlayerStatsData(packet.Oxygen, packet.MaxOxygen, packet.Health, packet.Food, packet.Water, packet.InfectionAmount);
|
||||
playerManager.SendPacketToOtherPlayers(packet, player);
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors
|
||||
{
|
||||
public class PlayerSyncFinishedProcessor : AuthenticatedPacketProcessor<PlayerSyncFinished>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
|
||||
public PlayerSyncFinishedProcessor(PlayerManager playerManager)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
}
|
||||
|
||||
public override void Process(PlayerSyncFinished packet, Player player)
|
||||
{
|
||||
// If this is the first player connecting we need to restart time at this exact moment
|
||||
if (playerManager.GetConnectedPlayers().Count == 1)
|
||||
{
|
||||
Server.Instance.ResumeServer();
|
||||
}
|
||||
|
||||
playerManager.FinishProcessingReservation(player);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,58 @@
|
||||
using System.Collections.Generic;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class PlayerUnseeOutOfCellEntityProcessor : AuthenticatedPacketProcessor<PlayerUnseeOutOfCellEntity>
|
||||
{
|
||||
private readonly SimulationOwnershipData simulationOwnershipData;
|
||||
private readonly PlayerManager playerManager;
|
||||
private readonly EntitySimulation entitySimulation;
|
||||
private readonly EntityRegistry entityRegistry;
|
||||
|
||||
public PlayerUnseeOutOfCellEntityProcessor(SimulationOwnershipData simulationOwnershipData, PlayerManager playerManager, EntitySimulation entitySimulation, EntityRegistry entityRegistry)
|
||||
{
|
||||
this.simulationOwnershipData = simulationOwnershipData;
|
||||
this.playerManager = playerManager;
|
||||
this.entitySimulation = entitySimulation;
|
||||
this.entityRegistry = entityRegistry;
|
||||
}
|
||||
|
||||
public override void Process(PlayerUnseeOutOfCellEntity packet, Player player)
|
||||
{
|
||||
// Most of this packet's utility is in the below Remove
|
||||
if (!player.OutOfCellVisibleEntities.Remove(packet.EntityId) ||
|
||||
!entityRegistry.TryGetEntityById(packet.EntityId, out Entity entity))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If player can still see the entity even after removing it from the OutOfCellVisibleEntities, then we don't need to change anything
|
||||
if (player.CanSee(entity))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If the player doesn't own the entity's simulation then we don't need to do anything
|
||||
if (!simulationOwnershipData.RevokeIfOwner(packet.EntityId, player))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<Player> otherPlayers = playerManager.GetConnectedPlayersExcept(player);
|
||||
if (entitySimulation.TryAssignEntityToPlayers(otherPlayers, entity, out SimulatedEntity simulatedEntity))
|
||||
{
|
||||
entitySimulation.BroadcastSimulationChanges([simulatedEntity]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// No player has taken simulation on the entity
|
||||
playerManager.SendPacketToAllPlayers(new DropSimulationOwnership(packet.EntityId));
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
using System.Collections.Generic;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxModel.Serialization;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.Serialization;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class PvPAttackProcessor : AuthenticatedPacketProcessor<PvPAttack>
|
||||
{
|
||||
private readonly SubnauticaServerConfig serverConfig;
|
||||
private readonly PlayerManager playerManager;
|
||||
|
||||
// TODO: In the future, do a whole config for damage sources
|
||||
private static readonly Dictionary<PvPAttack.AttackType, float> damageMultiplierByType = new()
|
||||
{
|
||||
{ PvPAttack.AttackType.KnifeHit, 0.5f },
|
||||
{ PvPAttack.AttackType.HeatbladeHit, 1f }
|
||||
};
|
||||
|
||||
public PvPAttackProcessor(SubnauticaServerConfig serverConfig, PlayerManager playerManager)
|
||||
{
|
||||
this.serverConfig = serverConfig;
|
||||
this.playerManager = playerManager;
|
||||
}
|
||||
|
||||
public override void Process(PvPAttack packet, Player player)
|
||||
{
|
||||
if (!serverConfig.PvPEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!playerManager.TryGetPlayerById(packet.TargetPlayerId, out Player targetPlayer))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!damageMultiplierByType.TryGetValue(packet.Type, out float multiplier))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
packet.Damage *= multiplier;
|
||||
targetPlayer.SendPacket(packet);
|
||||
}
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Unlockables;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors
|
||||
{
|
||||
public class RadioPlayPendingMessageProcessor : AuthenticatedPacketProcessor<RadioPlayPendingMessage>
|
||||
{
|
||||
private readonly StoryGoalData storyGoalData;
|
||||
private readonly PlayerManager playerManager;
|
||||
|
||||
public RadioPlayPendingMessageProcessor(StoryGoalData storyGoalData, PlayerManager playerManager)
|
||||
{
|
||||
this.storyGoalData = storyGoalData;
|
||||
this.playerManager = playerManager;
|
||||
}
|
||||
|
||||
public override void Process(RadioPlayPendingMessage packet, Player player)
|
||||
{
|
||||
if (!storyGoalData.RemovedLatestRadioMessage())
|
||||
{
|
||||
Log.Warn($"Tried to remove the latest radio message but the radio queue is empty: {packet}");
|
||||
}
|
||||
playerManager.SendPacketToOtherPlayers(packet, player);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class RangedAttackLastTargetUpdateProcessor(
|
||||
PlayerManager playerManager,
|
||||
EntityRegistry entityRegistry
|
||||
) : TransmitIfCanSeePacketProcessor<RangedAttackLastTargetUpdate>(playerManager, entityRegistry)
|
||||
{
|
||||
public override void Process(RangedAttackLastTargetUpdate packet, Player sender) => TransmitIfCanSeeEntities(packet, sender, [packet.CreatureId, packet.TargetId]);
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class PinnedRecipeProcessor : AuthenticatedPacketProcessor<RecipePinned>
|
||||
{
|
||||
public override void Process(RecipePinned packet, Player player)
|
||||
{
|
||||
if (packet.Pinned)
|
||||
{
|
||||
player.PinnedRecipePreferences.Add(packet.TechType);
|
||||
}
|
||||
else
|
||||
{
|
||||
player.PinnedRecipePreferences.Remove(packet.TechType);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class RemoveCreatureCorpseProcessor : AuthenticatedPacketProcessor<RemoveCreatureCorpse>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
private readonly EntitySimulation entitySimulation;
|
||||
private readonly WorldEntityManager worldEntityManager;
|
||||
|
||||
public RemoveCreatureCorpseProcessor(PlayerManager playerManager, EntitySimulation entitySimulation, WorldEntityManager worldEntityManager)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
this.worldEntityManager = worldEntityManager;
|
||||
this.entitySimulation = entitySimulation;
|
||||
}
|
||||
|
||||
public override void Process(RemoveCreatureCorpse packet, Player destroyingPlayer)
|
||||
{
|
||||
// TODO: In the future, for more immersion (though that's a neglectable +), have a corpse entity on server-side or a dedicated metadata for this entity (CorpseMetadata)
|
||||
// So that even players rejoining can see it (before it despawns)
|
||||
entitySimulation.EntityDestroyed(packet.CreatureId);
|
||||
|
||||
if (worldEntityManager.TryDestroyEntity(packet.CreatureId, out Entity entity))
|
||||
{
|
||||
foreach (Player player in playerManager.GetConnectedPlayers())
|
||||
{
|
||||
bool isOtherPlayer = player != destroyingPlayer;
|
||||
if (isOtherPlayer && player.CanSee(entity))
|
||||
{
|
||||
player.SendPacket(packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors
|
||||
{
|
||||
public class ScheduleProcessor : AuthenticatedPacketProcessor<Schedule>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
private readonly ScheduleKeeper scheduleKeeper;
|
||||
|
||||
public ScheduleProcessor(PlayerManager playerManager, ScheduleKeeper scheduleKeeper)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
this.scheduleKeeper = scheduleKeeper;
|
||||
}
|
||||
|
||||
public override void Process(Schedule packet, Player player)
|
||||
{
|
||||
scheduleKeeper.ScheduleGoal(NitroxScheduledGoal.From(packet.TimeExecute, packet.Key, packet.Type));
|
||||
playerManager.SendPacketToOtherPlayers(packet, player);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class SeaDragonAttackTargetProcessor(
|
||||
PlayerManager playerManager,
|
||||
EntityRegistry entityRegistry
|
||||
) : TransmitIfCanSeePacketProcessor<SeaDragonAttackTarget>(playerManager, entityRegistry)
|
||||
{
|
||||
public override void Process(SeaDragonAttackTarget packet, Player sender) => TransmitIfCanSeeEntities(packet, sender, [packet.SeaDragonId, packet.TargetId]);
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class SeaDragonGrabExosuitProcessor(
|
||||
PlayerManager playerManager,
|
||||
EntityRegistry entityRegistry
|
||||
) : TransmitIfCanSeePacketProcessor<SeaDragonGrabExosuit>(playerManager, entityRegistry)
|
||||
{
|
||||
public override void Process(SeaDragonGrabExosuit packet, Player sender) => TransmitIfCanSeeEntities(packet, sender, [packet.SeaDragonId, packet.TargetId]);
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class SeaDragonSwatAttackProcessor(
|
||||
PlayerManager playerManager,
|
||||
EntityRegistry entityRegistry
|
||||
) : TransmitIfCanSeePacketProcessor<SeaDragonSwatAttack>(playerManager, entityRegistry)
|
||||
{
|
||||
public override void Process(SeaDragonSwatAttack packet, Player sender) => TransmitIfCanSeeEntities(packet, sender, [packet.SeaDragonId, packet.TargetId]);
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class SeaTreaderSpawnedChunkProcessor(
|
||||
PlayerManager playerManager,
|
||||
EntityRegistry entityRegistry
|
||||
) : TransmitIfCanSeePacketProcessor<SeaTreaderSpawnedChunk>(playerManager, entityRegistry)
|
||||
{
|
||||
public override void Process(SeaTreaderSpawnedChunk packet, Player sender) => TransmitIfCanSeeEntities(packet, sender, [packet.CreatureId]);
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.ConsoleCommands.Processor;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors
|
||||
{
|
||||
public class ServerCommandProcessor : AuthenticatedPacketProcessor<ServerCommand>
|
||||
{
|
||||
private readonly ConsoleCommandProcessor cmdProcessor;
|
||||
|
||||
public ServerCommandProcessor(ConsoleCommandProcessor cmdProcessor)
|
||||
{
|
||||
this.cmdProcessor = cmdProcessor;
|
||||
}
|
||||
|
||||
public override void Process(ServerCommand packet, Player player)
|
||||
{
|
||||
Log.Info($"{player.Name} issued command: /{packet.Cmd}");
|
||||
cmdProcessor.ProcessCommand(packet.Cmd, Optional.Of(player), player.Permissions);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
using System.Linq;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class SetIntroCinematicModeProcessor : AuthenticatedPacketProcessor<SetIntroCinematicMode>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
|
||||
public SetIntroCinematicModeProcessor(PlayerManager playerManager)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
}
|
||||
|
||||
public override void Process(SetIntroCinematicMode packet, Player player)
|
||||
{
|
||||
if (packet.PlayerId != player.Id)
|
||||
{
|
||||
Log.Warn($"Received {nameof(SetIntroCinematicMode)} packet where packet.{nameof(SetIntroCinematicMode.PlayerId)} was not equal to sending playerId");
|
||||
return;
|
||||
}
|
||||
|
||||
packet.PartnerId = null; // Resetting incoming packets just to be safe we don't relay any PartnerId. Server has only authority.
|
||||
player.PlayerContext.IntroCinematicMode = packet.Mode;
|
||||
playerManager.SendPacketToOtherPlayers(packet, player);
|
||||
Log.Debug($"Set IntroCinematicMode to {packet.Mode} for {player.PlayerContext.PlayerName}");
|
||||
|
||||
Player[] allWaitingPlayers = playerManager.ConnectedPlayers().Where(p => p.PlayerContext.IntroCinematicMode == IntroCinematicMode.WAITING).ToArray();
|
||||
if (allWaitingPlayers.Length >= 2)
|
||||
{
|
||||
Log.Info($"Starting IntroCinematic for {allWaitingPlayers[0].PlayerContext.PlayerName} and {allWaitingPlayers[1].PlayerContext.PlayerName}");
|
||||
|
||||
allWaitingPlayers[0].PlayerContext.IntroCinematicMode = allWaitingPlayers[1].PlayerContext.IntroCinematicMode = IntroCinematicMode.START;
|
||||
|
||||
playerManager.SendPacketToAllPlayers(new SetIntroCinematicMode(allWaitingPlayers[0].Id, IntroCinematicMode.START, allWaitingPlayers[1].Id));
|
||||
playerManager.SendPacketToAllPlayers(new SetIntroCinematicMode(allWaitingPlayers[1].Id, IntroCinematicMode.START, allWaitingPlayers[0].Id));
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class SignalPingPreferenceChangedProcessor : AuthenticatedPacketProcessor<SignalPingPreferenceChanged>
|
||||
{
|
||||
public override void Process(SignalPingPreferenceChanged packet, Player player)
|
||||
{
|
||||
player.PingInstancePreferences[packet.PingKey] = new(packet.Color, packet.Visible);
|
||||
}
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class SimulationOwnershipRequestProcessor : AuthenticatedPacketProcessor<SimulationOwnershipRequest>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
private readonly SimulationOwnershipData simulationOwnershipData;
|
||||
private readonly EntitySimulation entitySimulation;
|
||||
|
||||
public SimulationOwnershipRequestProcessor(PlayerManager playerManager, SimulationOwnershipData simulationOwnershipData, EntitySimulation entitySimulation)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
this.simulationOwnershipData = simulationOwnershipData;
|
||||
this.entitySimulation = entitySimulation;
|
||||
}
|
||||
|
||||
public override void Process(SimulationOwnershipRequest ownershipRequest, Player player)
|
||||
{
|
||||
bool aquiredLock = simulationOwnershipData.TryToAcquire(ownershipRequest.Id, player, ownershipRequest.LockType);
|
||||
|
||||
if (aquiredLock)
|
||||
{
|
||||
bool shouldEntityMove = entitySimulation.ShouldSimulateEntityMovement(ownershipRequest.Id);
|
||||
SimulationOwnershipChange simulationOwnershipChange = new(ownershipRequest.Id, player.Id, ownershipRequest.LockType, shouldEntityMove);
|
||||
playerManager.SendPacketToOtherPlayers(simulationOwnershipChange, player);
|
||||
}
|
||||
|
||||
SimulationOwnershipResponse responseToPlayer = new(ownershipRequest.Id, aquiredLock, ownershipRequest.LockType);
|
||||
player.SendPacket(responseToPlayer);
|
||||
}
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Unlockables;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class StoryGoalExecutedProcessor : AuthenticatedPacketProcessor<StoryGoalExecuted>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
private readonly StoryGoalData storyGoalData;
|
||||
private readonly ScheduleKeeper scheduleKeeper;
|
||||
private readonly PDAStateData pdaStateData;
|
||||
|
||||
public StoryGoalExecutedProcessor(PlayerManager playerManager, StoryGoalData storyGoalData, ScheduleKeeper scheduleKeeper, PDAStateData pdaStateData)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
this.storyGoalData = storyGoalData;
|
||||
this.scheduleKeeper = scheduleKeeper;
|
||||
this.pdaStateData = pdaStateData;
|
||||
}
|
||||
|
||||
public override void Process(StoryGoalExecuted packet, Player player)
|
||||
{
|
||||
Log.Debug($"Processing StoryGoalExecuted: {packet}");
|
||||
// The switch is structure is similar to StoryGoal.Execute()
|
||||
bool added = storyGoalData.CompletedGoals.Add(packet.Key);
|
||||
switch (packet.Type)
|
||||
{
|
||||
case StoryGoalExecuted.EventType.RADIO:
|
||||
if (added)
|
||||
{
|
||||
storyGoalData.RadioQueue.Enqueue(packet.Key);
|
||||
}
|
||||
break;
|
||||
case StoryGoalExecuted.EventType.PDA:
|
||||
if (packet.Timestamp.HasValue)
|
||||
{
|
||||
pdaStateData.AddPDALogEntry(new(packet.Key, packet.Timestamp.Value));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
scheduleKeeper.UnScheduleGoal(packet.Key);
|
||||
|
||||
playerManager.SendPacketToOtherPlayers(packet, player);
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors
|
||||
{
|
||||
class SubRootChangedPacketProcessor : AuthenticatedPacketProcessor<SubRootChanged>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
private readonly EntityRegistry entityRegistry;
|
||||
|
||||
public SubRootChangedPacketProcessor(PlayerManager playerManager, EntityRegistry entityRegistry)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
this.entityRegistry = entityRegistry;
|
||||
}
|
||||
|
||||
public override void Process(SubRootChanged packet, Player player)
|
||||
{
|
||||
entityRegistry.ReparentEntity(player.GameObjectId, packet.SubRootId.OrNull());
|
||||
player.SubRootId = packet.SubRootId;
|
||||
playerManager.SendPacketToOtherPlayers(packet, player);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Bases;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class UpdateBaseProcessor : BuildingProcessor<UpdateBase>
|
||||
{
|
||||
public UpdateBaseProcessor(BuildingManager buildingManager, PlayerManager playerManager, EntitySimulation entitySimulation) : base(buildingManager, playerManager, entitySimulation) { }
|
||||
|
||||
public override void Process(UpdateBase packet, Player player)
|
||||
{
|
||||
if (buildingManager.UpdateBase(player, packet, out int operationId))
|
||||
{
|
||||
if (packet.BuiltPieceEntity is GlobalRootEntity entity)
|
||||
{
|
||||
ClaimBuildPiece(entity, player);
|
||||
}
|
||||
// End-players can process elementary operations without this data (packet would be heavier for no reason)
|
||||
packet.Deflate();
|
||||
SendToOtherPlayersWithOperationId(packet, player, operationId);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class VehicleDockingProcessor : AuthenticatedPacketProcessor<VehicleDocking>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
private readonly EntityRegistry entityRegistry;
|
||||
|
||||
public VehicleDockingProcessor(PlayerManager playerManager, EntityRegistry entityRegistry)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
this.entityRegistry = entityRegistry;
|
||||
}
|
||||
|
||||
public override void Process(VehicleDocking packet, Player player)
|
||||
{
|
||||
if (!entityRegistry.TryGetEntityById(packet.VehicleId, out Entity vehicleEntity))
|
||||
{
|
||||
Log.Error($"Unable to find vehicle to dock {packet.VehicleId}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!entityRegistry.TryGetEntityById(packet.DockId, out Entity dockEntity))
|
||||
{
|
||||
Log.Error($"Unable to find dock {packet.DockId} for docking vehicle {packet.VehicleId}");
|
||||
return;
|
||||
}
|
||||
|
||||
entityRegistry.ReparentEntity(vehicleEntity, dockEntity);
|
||||
|
||||
playerManager.SendPacketToOtherPlayers(packet, player);
|
||||
}
|
||||
}
|
@@ -0,0 +1,63 @@
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.DataStructures.Unity;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class VehicleMovementsPacketProcessor : AuthenticatedPacketProcessor<VehicleMovements>
|
||||
{
|
||||
private static readonly NitroxVector3 CyclopsSteeringWheelRelativePosition = new(-0.05f, 0.97f, -23.54f);
|
||||
|
||||
private readonly PlayerManager playerManager;
|
||||
private readonly EntityRegistry entityRegistry;
|
||||
private readonly SimulationOwnershipData simulationOwnershipData;
|
||||
|
||||
public VehicleMovementsPacketProcessor(PlayerManager playerManager, EntityRegistry entityRegistry, SimulationOwnershipData simulationOwnershipData)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
this.entityRegistry = entityRegistry;
|
||||
this.simulationOwnershipData = simulationOwnershipData;
|
||||
}
|
||||
|
||||
public override void Process(VehicleMovements packet, Player player)
|
||||
{
|
||||
for (int i = packet.Data.Count - 1; i >= 0; i--)
|
||||
{
|
||||
MovementData movementData = packet.Data[i];
|
||||
if (simulationOwnershipData.GetPlayerForLock(movementData.Id) != player)
|
||||
{
|
||||
Log.WarnOnce($"Player {player.Name} tried updating {movementData.Id}'s position but they don't have the lock on it");
|
||||
// TODO: In the future, add "packet.Data.RemoveAt(i);" and "continue;" to prevent those abnormal situations
|
||||
}
|
||||
|
||||
if (entityRegistry.TryGetEntityById(movementData.Id, out WorldEntity worldEntity))
|
||||
{
|
||||
worldEntity.Transform.Position = movementData.Position;
|
||||
worldEntity.Transform.Rotation = movementData.Rotation;
|
||||
|
||||
if (movementData is DrivenVehicleMovementData)
|
||||
{
|
||||
// Cyclops' driving wheel is at a known position so we need to adapt the position of the player accordingly
|
||||
if (worldEntity.TechType.Name.Equals("Cyclops"))
|
||||
{
|
||||
player.Entity.Transform.LocalPosition = CyclopsSteeringWheelRelativePosition;
|
||||
player.Position = player.Entity.Transform.Position;
|
||||
}
|
||||
else
|
||||
{
|
||||
player.Position = movementData.Position;
|
||||
player.Rotation = movementData.Rotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (packet.Data.Count > 0)
|
||||
{
|
||||
playerManager.SendPacketToOtherPlayers(packet, player);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class VehicleOnPilotModeChangedProcessor : AuthenticatedPacketProcessor<VehicleOnPilotModeChanged>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
|
||||
public VehicleOnPilotModeChangedProcessor(PlayerManager playerManager)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
}
|
||||
|
||||
public override void Process(VehicleOnPilotModeChanged packet, Player player)
|
||||
{
|
||||
player.PlayerContext.DrivingVehicle = packet.IsPiloting ? packet.VehicleId : null;
|
||||
|
||||
playerManager.SendPacketToOtherPlayers(packet, player);
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Entities;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class VehicleUndockingProcessor : AuthenticatedPacketProcessor<VehicleUndocking>
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
private readonly EntityRegistry entityRegistry;
|
||||
|
||||
public VehicleUndockingProcessor(PlayerManager playerManager, EntityRegistry entityRegistry)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
this.entityRegistry = entityRegistry;
|
||||
}
|
||||
|
||||
public override void Process(VehicleUndocking packet, Player player)
|
||||
{
|
||||
if (packet.UndockingStart)
|
||||
{
|
||||
if (!entityRegistry.TryGetEntityById(packet.VehicleId, out Entity vehicleEntity))
|
||||
{
|
||||
Log.Error($"Unable to find vehicle to undock {packet.VehicleId}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!entityRegistry.GetEntityById(vehicleEntity.ParentId).HasValue)
|
||||
{
|
||||
Log.Error($"Unable to find docked vehicles parent {vehicleEntity.ParentId} to undock from");
|
||||
return;
|
||||
}
|
||||
|
||||
entityRegistry.RemoveFromParent(vehicleEntity);
|
||||
}
|
||||
|
||||
playerManager.SendPacketToOtherPlayers(packet, player);
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.GameLogic;
|
||||
using NitroxServer.GameLogic.Bases;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors;
|
||||
|
||||
public class WaterParkDeconstructedProcessor : BuildingProcessor<WaterParkDeconstructed>
|
||||
{
|
||||
public WaterParkDeconstructedProcessor(BuildingManager buildingManager, PlayerManager playerManager) : base(buildingManager, playerManager) { }
|
||||
|
||||
public override void Process(WaterParkDeconstructed packet, Player player)
|
||||
{
|
||||
if (buildingManager.ReplacePieceByGhost(player, packet, out Entity removedEntity, out int operationId) &&
|
||||
buildingManager.CreateWaterParkPiece(packet, removedEntity))
|
||||
{
|
||||
packet.BaseData = null;
|
||||
SendToOtherPlayersWithOperationId(packet, player, operationId);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.Communication.Packets.Processors.Abstract;
|
||||
using NitroxServer.GameLogic;
|
||||
|
||||
namespace NitroxServer.Communication.Packets.Processors
|
||||
{
|
||||
class WeldActionProcessor : AuthenticatedPacketProcessor<WeldAction>
|
||||
{
|
||||
private readonly SimulationOwnershipData simulationOwnershipData;
|
||||
|
||||
public WeldActionProcessor(SimulationOwnershipData simulationOwnershipData)
|
||||
{
|
||||
this.simulationOwnershipData = simulationOwnershipData;
|
||||
}
|
||||
|
||||
public override void Process(WeldAction packet, Player player)
|
||||
{
|
||||
Player simulatingPlayer = simulationOwnershipData.GetPlayerForLock(packet.Id);
|
||||
|
||||
if (simulatingPlayer != null)
|
||||
{
|
||||
Log.Debug($"Send WeldAction to simulating player {simulatingPlayer.Name} for entity {packet.Id}");
|
||||
simulatingPlayer.SendPacket(packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
64
NitroxServer/ConsoleCommands/Abstract/CallArgs.cs
Normal file
64
NitroxServer/ConsoleCommands/Abstract/CallArgs.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
|
||||
namespace NitroxServer.ConsoleCommands.Abstract
|
||||
{
|
||||
public abstract partial class Command
|
||||
{
|
||||
public ref struct CallArgs
|
||||
{
|
||||
public Command Command { get; }
|
||||
public Optional<Player> Sender { get; }
|
||||
public Span<string> Args { get; }
|
||||
|
||||
public bool IsConsole => !Sender.HasValue;
|
||||
public string SenderName => Sender.HasValue ? Sender.Value.Name : "SERVER";
|
||||
|
||||
public CallArgs(Command command, Optional<Player> sender, Span<string> args)
|
||||
{
|
||||
Command = command;
|
||||
Sender = sender;
|
||||
Args = args;
|
||||
}
|
||||
|
||||
public bool IsValid(int index)
|
||||
{
|
||||
return index < Args.Length && index >= 0 && Args.Length != 0;
|
||||
}
|
||||
|
||||
public string GetTillEnd(int startIndex = 0)
|
||||
{
|
||||
// TODO: Proper argument capture/parse instead of this argument join hack
|
||||
if (Args.Length > 0)
|
||||
{
|
||||
return string.Join(" ", Args.Slice(startIndex).ToArray());
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public string Get(int index)
|
||||
{
|
||||
return Get<string>(index);
|
||||
}
|
||||
|
||||
public T Get<T>(int index)
|
||||
{
|
||||
IParameter<object> param = Command.Parameters[index];
|
||||
string arg = IsValid(index) ? Args[index] : null;
|
||||
|
||||
if (arg == null)
|
||||
{
|
||||
return default(T);
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(string))
|
||||
{
|
||||
return (T)(object)arg;
|
||||
}
|
||||
|
||||
return (T)param.Read(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
149
NitroxServer/ConsoleCommands/Abstract/Command.cs
Normal file
149
NitroxServer/ConsoleCommands/Abstract/Command.cs
Normal file
@@ -0,0 +1,149 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using NitroxModel.Core;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxServer.GameLogic;
|
||||
|
||||
namespace NitroxServer.ConsoleCommands.Abstract
|
||||
{
|
||||
public abstract partial class Command
|
||||
{
|
||||
private int optional, required;
|
||||
|
||||
public virtual IEnumerable<string> Aliases { get; }
|
||||
|
||||
public string Name { get; }
|
||||
public string Description { get; }
|
||||
public Perms RequiredPermLevel { get; }
|
||||
public PermsFlag Flags { get; }
|
||||
public bool AllowedArgOverflow { get; set; }
|
||||
public List<IParameter<object>> Parameters { get; }
|
||||
|
||||
protected Command(string name, Perms perms, PermsFlag flag, string description) : this(name, perms, description)
|
||||
{
|
||||
Flags = flag;
|
||||
}
|
||||
|
||||
protected Command(string name, Perms perms, string description)
|
||||
{
|
||||
Validate.NotNull(name);
|
||||
|
||||
Name = name;
|
||||
Flags = PermsFlag.NONE;
|
||||
RequiredPermLevel = perms;
|
||||
AllowedArgOverflow = false;
|
||||
Aliases = Array.Empty<string>();
|
||||
Parameters = new List<IParameter<object>>();
|
||||
Description = string.IsNullOrEmpty(description) ? "No description provided" : description;
|
||||
}
|
||||
|
||||
protected abstract void Execute(CallArgs args);
|
||||
|
||||
public void TryExecute(Optional<Player> sender, Span<string> args)
|
||||
{
|
||||
if (args.Length < required)
|
||||
{
|
||||
SendMessage(sender, $"Error: Invalid Parameters\nUsage: {ToHelpText(false, true)}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!AllowedArgOverflow && args.Length > optional + required)
|
||||
{
|
||||
SendMessage(sender, $"Error: Too many Parameters\nUsage: {ToHelpText(false, true)}");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Execute(new CallArgs(this, sender, args));
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
SendMessage(sender, $"Error: {ex.Message}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Fatal error while trying to execute the command");
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanExecute(Perms treshold)
|
||||
{
|
||||
return RequiredPermLevel <= treshold;
|
||||
}
|
||||
|
||||
public string ToHelpText(bool singleCommand, bool cropText = false)
|
||||
{
|
||||
StringBuilder cmd = new(Name);
|
||||
|
||||
if (Aliases.Any())
|
||||
{
|
||||
cmd.AppendFormat("/{0}", string.Join("/", Aliases));
|
||||
}
|
||||
|
||||
cmd.AppendFormat(" {0}", string.Join(" ", Parameters));
|
||||
|
||||
if (singleCommand)
|
||||
{
|
||||
string parameterPreText = Parameters.Count == 0 ? "" : Environment.NewLine;
|
||||
string parameterText = $"{parameterPreText}{string.Join("\n", Parameters.Select(p => $"{p,-47} - {p.GetDescription()}"))}";
|
||||
|
||||
return cropText ? $"{cmd}" : $"{cmd,-32} - {Description} {parameterText}";
|
||||
}
|
||||
return cropText ? $"{cmd}" : $"{cmd,-32} - {Description}";
|
||||
}
|
||||
|
||||
protected void AddParameter<T>(T param) where T : IParameter<object>
|
||||
{
|
||||
Validate.NotNull(param as object);
|
||||
Parameters.Add(param);
|
||||
|
||||
if (param.IsRequired)
|
||||
{
|
||||
required++;
|
||||
}
|
||||
else
|
||||
{
|
||||
optional++;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a message to an existing player
|
||||
/// </summary>
|
||||
public static void SendMessageToPlayer(Optional<Player> player, string message)
|
||||
{
|
||||
if (player.HasValue)
|
||||
{
|
||||
player.Value.SendPacket(new ChatMessage(ChatMessage.SERVER_ID, message));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a message to an existing player and logs it in the console
|
||||
/// </summary>
|
||||
public static void SendMessage(Optional<Player> player, string message)
|
||||
{
|
||||
SendMessageToPlayer(player, message);
|
||||
if (!player.HasValue)
|
||||
{
|
||||
Log.Info(message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a message to all connected players
|
||||
/// </summary>
|
||||
public static void SendMessageToAllPlayers(string message)
|
||||
{
|
||||
PlayerManager playerManager = NitroxServiceLocator.LocateService<PlayerManager>();
|
||||
playerManager.SendPacketToAllPlayers(new ChatMessage(ChatMessage.SERVER_ID, message));
|
||||
Log.Info($"[BROADCAST] {message}");
|
||||
}
|
||||
}
|
||||
}
|
43
NitroxServer/ConsoleCommands/Abstract/Parameter.cs
Normal file
43
NitroxServer/ConsoleCommands/Abstract/Parameter.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxServer.ConsoleCommands.Abstract
|
||||
{
|
||||
public abstract class Parameter<T> : IParameter<T>
|
||||
{
|
||||
public bool IsRequired { get; }
|
||||
public string Name { get; }
|
||||
private string Description { get; }
|
||||
|
||||
protected Parameter(string name, bool isRequired, string description)
|
||||
{
|
||||
Validate.IsFalse(string.IsNullOrEmpty(name));
|
||||
|
||||
Name = name;
|
||||
IsRequired = isRequired;
|
||||
Description = description;
|
||||
}
|
||||
|
||||
public abstract bool IsValid(string arg);
|
||||
public abstract T Read(string arg);
|
||||
|
||||
public virtual string GetDescription()
|
||||
{
|
||||
return Description;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{(IsRequired ? '{' : '[')}{Name}{(IsRequired ? '}' : ']')}";
|
||||
}
|
||||
}
|
||||
|
||||
public interface IParameter<out T>
|
||||
{
|
||||
bool IsRequired { get; }
|
||||
string Name { get; }
|
||||
|
||||
bool IsValid(string arg);
|
||||
T Read(string arg);
|
||||
string GetDescription();
|
||||
}
|
||||
}
|
41
NitroxServer/ConsoleCommands/Abstract/Type/TypeBoolean.cs
Normal file
41
NitroxServer/ConsoleCommands/Abstract/Type/TypeBoolean.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxServer.ConsoleCommands.Abstract.Type
|
||||
{
|
||||
public class TypeBoolean : Parameter<bool>, IParameter<object>
|
||||
{
|
||||
private static readonly string[] noValues = new string[]
|
||||
{
|
||||
bool.FalseString,
|
||||
"no",
|
||||
"off"
|
||||
};
|
||||
|
||||
private static readonly string[] yesValues = new string[]
|
||||
{
|
||||
bool.TrueString,
|
||||
"yes",
|
||||
"on"
|
||||
};
|
||||
|
||||
public TypeBoolean(string name, bool isRequired, string description) : base(name, isRequired, description) { }
|
||||
|
||||
public override bool IsValid(string arg)
|
||||
{
|
||||
return yesValues.Contains(arg, StringComparer.OrdinalIgnoreCase) || noValues.Contains(arg, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public override bool Read(string arg)
|
||||
{
|
||||
Validate.IsTrue(IsValid(arg), "Invalid boolean value received");
|
||||
return yesValues.Contains(arg, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
object IParameter<object>.Read(string arg)
|
||||
{
|
||||
return Read(arg);
|
||||
}
|
||||
}
|
||||
}
|
29
NitroxServer/ConsoleCommands/Abstract/Type/TypeEnum.cs
Normal file
29
NitroxServer/ConsoleCommands/Abstract/Type/TypeEnum.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxServer.ConsoleCommands.Abstract.Type
|
||||
{
|
||||
public class TypeEnum<T> : Parameter<object> where T : struct, Enum
|
||||
{
|
||||
public TypeEnum(string name, bool required, string description) : base(name, required, description)
|
||||
{
|
||||
Validate.IsTrue(typeof(T).IsEnum, $"Type {typeof(T).FullName} isn't an enum");
|
||||
}
|
||||
|
||||
public override bool IsValid(string arg)
|
||||
{
|
||||
return Enum.TryParse<T>(arg, true, out _);
|
||||
}
|
||||
|
||||
public override object Read(string arg)
|
||||
{
|
||||
Validate.IsTrue(Enum.TryParse(arg, true, out T value), $"Unknown value received (pick from: {string.Join(", ", Enum.GetNames(typeof(T)))})");
|
||||
return value;
|
||||
}
|
||||
|
||||
public override string GetDescription()
|
||||
{
|
||||
return $"{base.GetDescription()} (values: {string.Join(", ", Enum.GetNames(typeof(T)))})";
|
||||
}
|
||||
}
|
||||
}
|
25
NitroxServer/ConsoleCommands/Abstract/Type/TypeFloat.cs
Normal file
25
NitroxServer/ConsoleCommands/Abstract/Type/TypeFloat.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxServer.ConsoleCommands.Abstract.Type
|
||||
{
|
||||
public class TypeFloat : Parameter<float>, IParameter<object>
|
||||
{
|
||||
public TypeFloat(string name, bool isRequired, string description) : base(name, isRequired, description) { }
|
||||
|
||||
public override bool IsValid(string arg)
|
||||
{
|
||||
return float.TryParse(arg, out _);
|
||||
}
|
||||
|
||||
public override float Read(string arg)
|
||||
{
|
||||
Validate.IsTrue(float.TryParse(arg, out float value), "Invalid decimal number received");
|
||||
return value;
|
||||
}
|
||||
|
||||
object IParameter<object>.Read(string arg)
|
||||
{
|
||||
return Read(arg);
|
||||
}
|
||||
}
|
||||
}
|
25
NitroxServer/ConsoleCommands/Abstract/Type/TypeInt.cs
Normal file
25
NitroxServer/ConsoleCommands/Abstract/Type/TypeInt.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxServer.ConsoleCommands.Abstract.Type
|
||||
{
|
||||
public class TypeInt : Parameter<int>, IParameter<object>
|
||||
{
|
||||
public TypeInt(string name, bool isRequired, string description) : base(name, isRequired, description) { }
|
||||
|
||||
public override bool IsValid(string arg)
|
||||
{
|
||||
return int.TryParse(arg, out _);
|
||||
}
|
||||
|
||||
public override int Read(string arg)
|
||||
{
|
||||
Validate.IsTrue(int.TryParse(arg, out int value), "Invalid integer received");
|
||||
return value;
|
||||
}
|
||||
|
||||
object IParameter<object>.Read(string arg)
|
||||
{
|
||||
return Read(arg);
|
||||
}
|
||||
}
|
||||
}
|
24
NitroxServer/ConsoleCommands/Abstract/Type/TypeNitroxId.cs
Normal file
24
NitroxServer/ConsoleCommands/Abstract/Type/TypeNitroxId.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxServer.ConsoleCommands.Abstract.Type;
|
||||
|
||||
public class TypeNitroxId(string name, bool isRequired, string description) : Parameter<NitroxId>(name, isRequired, description)
|
||||
{
|
||||
public override bool IsValid(string arg)
|
||||
{
|
||||
return IsValid(arg, out _);
|
||||
}
|
||||
|
||||
private static bool IsValid(string arg, out Guid result)
|
||||
{
|
||||
return Guid.TryParse(arg, out result);
|
||||
}
|
||||
|
||||
public override NitroxId Read(string arg)
|
||||
{
|
||||
Validate.IsTrue(IsValid(arg, out Guid result), "Received an invalid NitroxId");
|
||||
return new NitroxId(result);
|
||||
}
|
||||
}
|
27
NitroxServer/ConsoleCommands/Abstract/Type/TypePlayer.cs
Normal file
27
NitroxServer/ConsoleCommands/Abstract/Type/TypePlayer.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using NitroxModel.Core;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxServer.GameLogic;
|
||||
|
||||
namespace NitroxServer.ConsoleCommands.Abstract.Type
|
||||
{
|
||||
public class TypePlayer : Parameter<Player>
|
||||
{
|
||||
private static readonly PlayerManager playerManager = NitroxServiceLocator.LocateService<PlayerManager>();
|
||||
|
||||
public TypePlayer(string name, bool required, string description) : base(name, required, description)
|
||||
{
|
||||
Validate.NotNull(playerManager, "PlayerManager can't be null to resolve the command");
|
||||
}
|
||||
|
||||
public override bool IsValid(string arg)
|
||||
{
|
||||
return playerManager.TryGetPlayerByName(arg, out _);
|
||||
}
|
||||
|
||||
public override Player Read(string arg)
|
||||
{
|
||||
Validate.IsTrue(playerManager.TryGetPlayerByName(arg, out Player player), "Player not found");
|
||||
return player;
|
||||
}
|
||||
}
|
||||
}
|
20
NitroxServer/ConsoleCommands/Abstract/Type/TypeString.cs
Normal file
20
NitroxServer/ConsoleCommands/Abstract/Type/TypeString.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxServer.ConsoleCommands.Abstract.Type
|
||||
{
|
||||
public class TypeString : Parameter<string>
|
||||
{
|
||||
public TypeString(string name, bool isRequired, string description) : base(name, isRequired, description) { }
|
||||
|
||||
public override bool IsValid(string arg)
|
||||
{
|
||||
return !string.IsNullOrEmpty(arg);
|
||||
}
|
||||
|
||||
public override string Read(string arg)
|
||||
{
|
||||
Validate.IsTrue(IsValid(arg), "Received null/empty instead of a valid string");
|
||||
return arg;
|
||||
}
|
||||
}
|
||||
}
|
42
NitroxServer/ConsoleCommands/AuroraCommand.cs
Normal file
42
NitroxServer/ConsoleCommands/AuroraCommand.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxServer.ConsoleCommands.Abstract;
|
||||
using NitroxServer.ConsoleCommands.Abstract.Type;
|
||||
using NitroxServer.GameLogic;
|
||||
|
||||
namespace NitroxServer.ConsoleCommands;
|
||||
|
||||
// TODO: When we make the new command system, move this stuff to it
|
||||
public class AuroraCommand : Command
|
||||
{
|
||||
private readonly StoryManager storyManager;
|
||||
|
||||
// We shouldn't let the server use this command because it needs some stuff to happen client-side like goals
|
||||
public AuroraCommand(StoryManager storyManager) : base("aurora", Perms.ADMIN, PermsFlag.NO_CONSOLE, "Manage Aurora's state")
|
||||
{
|
||||
AddParameter(new TypeString("countdown/restore/explode", true, "Which action to apply to Aurora"));
|
||||
|
||||
this.storyManager = storyManager;
|
||||
}
|
||||
|
||||
protected override void Execute(CallArgs args)
|
||||
{
|
||||
string action = args.Get<string>(0);
|
||||
|
||||
switch (action.ToLower())
|
||||
{
|
||||
case "countdown":
|
||||
storyManager.BroadcastExplodeAurora(true);
|
||||
break;
|
||||
case "restore":
|
||||
storyManager.BroadcastRestoreAurora();
|
||||
break;
|
||||
case "explode":
|
||||
storyManager.BroadcastExplodeAurora(false);
|
||||
break;
|
||||
default:
|
||||
// Same message as in the abstract class, in method TryExecute
|
||||
SendMessage(args.Sender, $"Error: Invalid Parameters\nUsage: {ToHelpText(false, true)}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
43
NitroxServer/ConsoleCommands/AutosaveCommand.cs
Normal file
43
NitroxServer/ConsoleCommands/AutosaveCommand.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System.IO;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.Serialization;
|
||||
using NitroxServer.ConsoleCommands.Abstract;
|
||||
using NitroxServer.ConsoleCommands.Abstract.Type;
|
||||
|
||||
namespace NitroxServer.ConsoleCommands
|
||||
{
|
||||
internal class AutoSaveCommand : Command
|
||||
{
|
||||
private readonly Server server;
|
||||
private readonly SubnauticaServerConfig serverConfig;
|
||||
|
||||
public AutoSaveCommand(Server server, SubnauticaServerConfig serverConfig) : base("autosave", Perms.ADMIN, "Toggles the map autosave")
|
||||
{
|
||||
AddParameter(new TypeBoolean("on/off", true, "Whether autosave should be on or off"));
|
||||
|
||||
this.server = server;
|
||||
this.serverConfig = serverConfig;
|
||||
}
|
||||
|
||||
protected override void Execute(CallArgs args)
|
||||
{
|
||||
bool toggle = args.Get<bool>(0);
|
||||
|
||||
using (serverConfig.Update(Path.Combine(KeyValueStore.Instance.GetSavesFolderDir(), server.Name)))
|
||||
{
|
||||
if (toggle)
|
||||
{
|
||||
serverConfig.DisableAutoSave = false;
|
||||
Server.Instance.EnablePeriodicSaving();
|
||||
SendMessage(args.Sender, "Enabled periodical saving");
|
||||
}
|
||||
else
|
||||
{
|
||||
serverConfig.DisableAutoSave = true;
|
||||
Server.Instance.DisablePeriodicSaving();
|
||||
SendMessage(args.Sender, "Disabled periodical saving");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
26
NitroxServer/ConsoleCommands/BackCommand.cs
Normal file
26
NitroxServer/ConsoleCommands/BackCommand.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxServer.ConsoleCommands.Abstract;
|
||||
|
||||
namespace NitroxServer.ConsoleCommands
|
||||
{
|
||||
internal class BackCommand : Command
|
||||
{
|
||||
public BackCommand() : base("back", Perms.MODERATOR, PermsFlag.NO_CONSOLE, "Teleports you back on your last location")
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Execute(CallArgs args)
|
||||
{
|
||||
Player player = args.Sender.Value;
|
||||
|
||||
if (player.LastStoredPosition == null)
|
||||
{
|
||||
SendMessage(args.Sender, "No previous location...");
|
||||
return;
|
||||
}
|
||||
|
||||
player.Teleport(player.LastStoredPosition.Value, player.LastStoredSubRootID);
|
||||
SendMessage(args.Sender, $"Teleported back to {player.LastStoredPosition.Value}");
|
||||
}
|
||||
}
|
||||
}
|
18
NitroxServer/ConsoleCommands/BackupCommand.cs
Normal file
18
NitroxServer/ConsoleCommands/BackupCommand.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxServer.ConsoleCommands.Abstract;
|
||||
|
||||
namespace NitroxServer.ConsoleCommands
|
||||
{
|
||||
internal class BackupCommand : Command
|
||||
{
|
||||
public BackupCommand() : base("backup", Perms.ADMIN, "Creates a backup of the save")
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Execute(CallArgs args)
|
||||
{
|
||||
Server.Instance.BackUp();
|
||||
SendMessageToPlayer(args.Sender, "World has been backed up");
|
||||
}
|
||||
}
|
||||
}
|
24
NitroxServer/ConsoleCommands/BroadcastCommand.cs
Normal file
24
NitroxServer/ConsoleCommands/BroadcastCommand.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Collections.Generic;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxServer.ConsoleCommands.Abstract;
|
||||
using NitroxServer.ConsoleCommands.Abstract.Type;
|
||||
|
||||
namespace NitroxServer.ConsoleCommands
|
||||
{
|
||||
internal class BroadcastCommand : Command
|
||||
{
|
||||
public override IEnumerable<string> Aliases { get; } = new[] { "say" };
|
||||
|
||||
public BroadcastCommand() : base("broadcast", Perms.MODERATOR, "Broadcasts a message on the server")
|
||||
{
|
||||
AddParameter(new TypeString("message", true, "The message to be broadcast"));
|
||||
|
||||
AllowedArgOverflow = true;
|
||||
}
|
||||
|
||||
protected override void Execute(CallArgs args)
|
||||
{
|
||||
SendMessageToAllPlayers(args.GetTillEnd());
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user