first commit

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

View File

@@ -0,0 +1,15 @@
using NitroxModel.Packets;
using NitroxModel.Packets.Processors.Abstract;
namespace NitroxClient.Communication.Packets.Processors.Abstract
{
public abstract class ClientPacketProcessor<T> : PacketProcessor where T : Packet
{
public override void ProcessPacket(Packet packet, IProcessorContext context)
{
Process((T)packet);
}
public abstract void Process(T packet);
}
}

View File

@@ -0,0 +1,19 @@
using NitroxClient.GameLogic;
using NitroxModel.Packets;
namespace NitroxClient.Communication.Packets.Processors.Abstract;
public class KeepInventoryChangedProcessor : ClientPacketProcessor<KeepInventoryChanged>
{
private readonly LocalPlayer localPlayer;
public KeepInventoryChangedProcessor(LocalPlayer localPlayer)
{
this.localPlayer = localPlayer;
}
public override void Process(KeepInventoryChanged packet)
{
localPlayer.KeepInventoryOnDeath = packet.KeepInventoryOnDeath;
}
}

View File

@@ -0,0 +1,13 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxModel.Packets;
namespace NitroxClient.Communication.Packets.Processors;
public class AggressiveWhenSeeTargetChangedProcessor : ClientPacketProcessor<AggressiveWhenSeeTargetChanged>
{
public override void Process(AggressiveWhenSeeTargetChanged packet)
{
AI.AggressiveWhenSeeTargetChanged(packet.CreatureId, packet.TargetId, packet.Locked, packet.AggressionAmount);
}
}

View File

@@ -0,0 +1,27 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxClient.MonoBehaviours;
using NitroxModel.DataStructures.Util;
using NitroxModel.Packets;
namespace NitroxClient.Communication.Packets.Processors
{
public class AnimationProcessor : ClientPacketProcessor<AnimationChangeEvent>
{
private readonly PlayerManager remotePlayerManager;
public AnimationProcessor(PlayerManager remotePlayerManager)
{
this.remotePlayerManager = remotePlayerManager;
}
public override void Process(AnimationChangeEvent animEvent)
{
Optional<RemotePlayer> opPlayer = remotePlayerManager.Find(animEvent.PlayerId);
if (opPlayer.HasValue)
{
opPlayer.Value.UpdateAnimationAndCollider((AnimChangeType)animEvent.Type, (AnimChangeState)animEvent.State);
}
}
}
}

View File

@@ -0,0 +1,13 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxModel.Packets;
namespace NitroxClient.Communication.Packets.Processors;
public class AttackCyclopsTargetChangedProcessor : ClientPacketProcessor<AttackCyclopsTargetChanged>
{
public override void Process(AttackCyclopsTargetChanged packet)
{
AI.AttackCyclopsTargetChanged(packet.CreatureId, packet.TargetId, packet.AggressiveToNoiseAmount);
}
}

View File

@@ -0,0 +1,26 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxModel.Packets;
namespace NitroxClient.Communication.Packets.Processors;
public class AuroraAndTimeUpdateProcessor : ClientPacketProcessor<AuroraAndTimeUpdate>
{
private readonly TimeManager timeManager;
public AuroraAndTimeUpdateProcessor(TimeManager timeManager)
{
this.timeManager = timeManager;
}
public override void Process(AuroraAndTimeUpdate packet)
{
timeManager.ProcessUpdate(packet.TimeData.TimePacket);
StoryManager.UpdateAuroraData(packet.TimeData.AuroraEventData);
timeManager.AuroraRealExplosionTime = packet.TimeData.AuroraEventData.AuroraRealExplosionTime;
if (packet.Restore)
{
StoryManager.RestoreAurora();
}
}
}

View File

@@ -0,0 +1,12 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxModel.Packets;
namespace NitroxClient.Communication.Packets.Processors
{
class BedEnterProcessor : ClientPacketProcessor<BedEnter>
{
public override void Process(BedEnter packet)
{
}
}
}

View File

@@ -0,0 +1,31 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic.Bases;
using NitroxModel.Packets;
namespace NitroxClient.Communication.Packets.Processors;
public abstract class BuildProcessor<T> : ClientPacketProcessor<T> where T : Packet
{
public override void Process(T packet)
{
BuildingHandler.Main.BuildQueue.Enqueue(packet);
}
}
public class PlaceGhostProcessor : BuildProcessor<PlaceGhost> { }
public class PlaceModuleProcessor : BuildProcessor<PlaceModule> { }
public class ModifyConstructedAmountProcessor : BuildProcessor<ModifyConstructedAmount> { }
public class PlaceBaseProcessor : BuildProcessor<PlaceBase> { }
public class UpdateBaseProcessor : BuildProcessor<UpdateBase> { }
public class BaseDeconstructedProcessor : BuildProcessor<BaseDeconstructed> { }
public class PieceDeconstructedProcessor : BuildProcessor<PieceDeconstructed> { }
public class WaterParkDeconstructedProcessor : BuildProcessor<WaterParkDeconstructed> { }
public class LargeWaterParkDeconstructedProcessor : BuildProcessor<LargeWaterParkDeconstructed> { }

View File

@@ -0,0 +1,31 @@
using System.Collections.Generic;
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic.Bases;
using NitroxClient.GameLogic.Settings;
using NitroxModel.DataStructures;
using NitroxModel.Packets;
namespace NitroxClient.Communication.Packets.Processors;
public class BuildingDesyncWarningProcessor : ClientPacketProcessor<BuildingDesyncWarning>
{
public override void Process(BuildingDesyncWarning packet)
{
if (!BuildingHandler.Main)
{
return;
}
foreach (KeyValuePair<NitroxId, int> operation in packet.Operations)
{
OperationTracker tracker = BuildingHandler.Main.EnsureTracker(operation.Key);
tracker.LastOperationId = operation.Value;
tracker.FailedOperations++;
}
if (NitroxPrefs.SafeBuildingLog.Value)
{
Log.InGame(Language.main.Get("Nitrox_BuildingDesyncDetected"));
}
}
}

View File

@@ -0,0 +1,179 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxClient.GameLogic.Bases;
using NitroxClient.GameLogic.Spawning.Bases;
using NitroxClient.GameLogic.Spawning.Metadata;
using NitroxClient.MonoBehaviours;
using NitroxClient.Unity.Helper;
using NitroxModel.DataStructures;
using NitroxModel.DataStructures.GameLogic;
using NitroxModel.DataStructures.GameLogic.Entities;
using NitroxModel.DataStructures.GameLogic.Entities.Bases;
using NitroxModel.Packets;
using NitroxModel_Subnautica.DataStructures;
using UnityEngine;
namespace NitroxClient.Communication.Packets.Processors;
public class BuildingResyncProcessor : ClientPacketProcessor<BuildingResync>
{
private readonly Entities entities;
private readonly EntityMetadataManager entityMetadataManager;
public BuildingResyncProcessor(Entities entities, EntityMetadataManager entityMetadataManager)
{
this.entities = entities;
this.entityMetadataManager = entityMetadataManager;
}
public override void Process(BuildingResync packet)
{
if (!BuildingHandler.Main)
{
return;
}
BuildingHandler.Main.StartCoroutine(ResyncBuildingEntities(packet.BuildEntities, packet.ModuleEntities));
}
public IEnumerator ResyncBuildingEntities(Dictionary<BuildEntity, int> buildEntities, Dictionary<ModuleEntity, int> moduleEntities)
{
Stopwatch stopwatch = Stopwatch.StartNew();
BuildingHandler.Main.StartResync(buildEntities);
yield return UpdateEntities<Base, BuildEntity>(buildEntities.Keys.ToList(), OverwriteBase, IsInCloseProximity).OnYieldError(exception => Log.Error(exception, $"Encountered an exception while resyncing BuildEntities"));
BuildingHandler.Main.StartResync(moduleEntities);
yield return UpdateEntities<Constructable, ModuleEntity>(moduleEntities.Keys.ToList(), OverwriteModule, IsInCloseProximity).OnYieldError(exception => Log.Error(exception, $"Encountered an exception while resyncing ModuleEntities"));
BuildingHandler.Main.StopResync();
stopwatch.Stop();
int totalEntities = buildEntities.Count + moduleEntities.Count;
Log.InGame(Language.main.Get("Nitrox_FinishedResyncRequest").Replace("{TIME}", stopwatch.ElapsedMilliseconds.ToString()).Replace("{COUNT}", totalEntities.ToString()));
}
private bool IsInCloseProximity<C>(WorldEntity entity, C componentInWorld) where C : Component
{
return Vector3.Distance(entity.Transform.Position.ToUnity(), componentInWorld.transform.position) < 0.001f;
}
/// <summary>
/// Tries to overwrite components of the provided type found in GlobalRoot's hierarchy by the provided list of entities to update.
/// If no component is found to be corresponding to a provided entity, the entity will be spawned independently.
/// Other components of the provided type which weren't updated shall be destroyed.
/// </summary>
/// <remarks>
/// The provided list is modified by the function. Make sure it's not used somewhere else.
/// </remarks>
/// <typeparam name="C">The Unity component to be looked for</typeparam>
/// <typeparam name="E">The GlobalRootEntity type which will be updated</typeparam>
/// <param name="overwrite">A function to overwrite a given component by a given entity</param>
/// <param name="correspondingPredicate">
/// Predicate to determine if an entity can overwrite the GameObject of the provided component.
/// </param>
public IEnumerator UpdateEntities<C,E>(List<E> entitiesToUpdate, Func<C, E, IEnumerator> overwrite, Func<E, C, bool> correspondingPredicate) where C : Component where E : GlobalRootEntity
{
List<C> unmarkedComponents = new();
Dictionary<NitroxId, E> entitiesToUpdateById = entitiesToUpdate.ToDictionary(e => e.Id);
foreach (Transform childTransform in LargeWorldStreamer.main.globalRoot.transform)
{
if (childTransform.TryGetComponent(out C component))
{
if (component.TryGetNitroxId(out NitroxId id) && entitiesToUpdateById.TryGetValue(id, out E correspondingEntity))
{
yield return overwrite(component, correspondingEntity).OnYieldError(Log.Error);
entitiesToUpdate.Remove(correspondingEntity);
continue;
}
unmarkedComponents.Add(component);
}
}
for (int i = entitiesToUpdate.Count - 1; i >= 0; i--)
{
E entity = entitiesToUpdate[i];
C associatedComponent = unmarkedComponents.Find(c =>
correspondingPredicate(entity, c));
yield return overwrite(associatedComponent, entity).OnYieldError(Log.Error);
unmarkedComponents.Remove(associatedComponent);
entitiesToUpdate.RemoveAt(i);
}
for (int i = unmarkedComponents.Count - 1; i >= 0; i--)
{
Log.Info($"[{typeof(E)} RESYNC] Destroyed GameObject {unmarkedComponents[i].gameObject}");
GameObject.Destroy(unmarkedComponents[i].gameObject);
}
foreach (E entity in entitiesToUpdate)
{
Log.Info($"[{typeof(E)} RESYNC] spawning entity {entity.Id}");
yield return entities.SpawnEntityAsync(entity).OnYieldError(Log.Error);
}
}
public IEnumerator OverwriteBase(Base @base, BuildEntity buildEntity)
{
Log.Info($"[Base RESYNC] Overwriting base with id {buildEntity.Id}");
ClearBaseChildren(@base);
// Frame to let all children be deleted properly
yield return Yielders.WaitForEndOfFrame;
yield return BuildEntitySpawner.SetupBase(buildEntity, @base, entities);
yield return MoonpoolManager.RestoreMoonpools(buildEntity.ChildEntities.OfType<MoonpoolEntity>(), @base);
yield return entities.SpawnBatchAsync(buildEntity.ChildEntities.OfType<PlayerWorldEntity>().ToList<Entity>(), false, false);
foreach (Entity childEntity in buildEntity.ChildEntities)
{
switch (childEntity)
{
case MapRoomEntity mapRoomEntity:
yield return InteriorPieceEntitySpawner.RestoreMapRoom(@base, mapRoomEntity);
break;
case BaseLeakEntity baseLeakEntity:
yield return entities.SpawnEntityAsync(baseLeakEntity, true);
break;
}
}
}
public IEnumerator OverwriteModule(Constructable constructable, ModuleEntity moduleEntity)
{
Log.Info($"[Module RESYNC] Overwriting module with id {moduleEntity.Id}");
ModuleEntitySpawner.ApplyModuleData(moduleEntity, constructable.gameObject);
entityMetadataManager.ApplyMetadata(constructable.gameObject, moduleEntity.Metadata);
yield break;
}
/// <summary>
/// Destroys manually ghosts, modules, interior pieces and vehicles of a base
/// </summary>
/// <remarks>
/// This is the destructive way of clearing the base, if the base isn't modified consequently, IBaseModuleGeometry under the base cells may start spamming errors.
/// </remarks>
public static void ClearBaseChildren(Base @base)
{
for (int i = @base.transform.childCount - 1; i >= 0; i--)
{
Transform child = @base.transform.GetChild(i);
if (child.GetComponent<IBaseModule>().AliveOrNull() || child.GetComponent<Constructable>())
{
UnityEngine.Object.Destroy(child.gameObject);
}
}
foreach (VehicleDockingBay vehicleDockingBay in @base.GetComponentsInChildren<VehicleDockingBay>(true))
{
if (vehicleDockingBay.dockedVehicle)
{
UnityEngine.Object.Destroy(vehicleDockingBay.dockedVehicle.gameObject);
vehicleDockingBay.SetVehicleUndocked();
}
}
}
}

View File

@@ -0,0 +1,82 @@
using System;
using System.Linq;
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxClient.GameLogic.ChatUI;
using NitroxClient.GameLogic.Settings;
using NitroxModel.DataStructures.Unity;
using NitroxModel.DataStructures.Util;
using NitroxModel.Packets;
using NitroxModel_Subnautica.DataStructures;
using UnityEngine;
namespace NitroxClient.Communication.Packets.Processors
{
class ChatMessageProcessor : ClientPacketProcessor<ChatMessage>
{
private readonly PlayerManager remotePlayerManager;
private readonly LocalPlayer localPlayer;
private readonly PlayerChatManager playerChatManager;
private readonly Color32 serverMessageColor = new Color32(0x8c, 0x00, 0xFF, 0xFF);
public ChatMessageProcessor(PlayerManager remotePlayerManager, LocalPlayer localPlayer, PlayerChatManager playerChatManager)
{
this.remotePlayerManager = remotePlayerManager;
this.localPlayer = localPlayer;
this.playerChatManager = playerChatManager;
}
public override void Process(ChatMessage message)
{
if (message.PlayerId != ChatMessage.SERVER_ID)
{
LogClientMessage(message);
}
else
{
LogServerMessage(message);
}
}
private void LogClientMessage(ChatMessage message)
{
// The message can come from either the local player or other players
string playerName;
NitroxColor color;
if (localPlayer.PlayerId == message.PlayerId)
{
playerName = localPlayer.PlayerName;
color = localPlayer.PlayerSettings.PlayerColor;
}
else
{
Optional<RemotePlayer> remotePlayer = remotePlayerManager.Find(message.PlayerId);
if (!remotePlayer.HasValue)
{
string playerTableFormatted = string.Join("\n", remotePlayerManager.GetAll().Select(ply => $"Name: '{ply.PlayerName}', Id: {ply.PlayerId}"));
Log.Error($"Tried to add chat message for remote player that could not be found with id '${message.PlayerId}' and message: '{message.Text}'.\nAll remote players right now:\n{playerTableFormatted}");
throw new Exception($"Tried to add chat message for remote player that could not be found with id '${message.PlayerId}' and message: '{message.Text}'.\nAll remote players right now:\n{playerTableFormatted}");
}
playerName = remotePlayer.Value.PlayerName;
color = remotePlayer.Value.PlayerSettings.PlayerColor;
}
playerChatManager.AddMessage(playerName, message.Text, color.ToUnity());
if (!NitroxPrefs.SilenceChat.Value)
{
playerChatManager.ShowChat();
}
}
private void LogServerMessage(ChatMessage message)
{
playerChatManager.AddMessage("Server", message.Text, serverMessageColor);
if (!NitroxPrefs.SilenceChat.Value)
{
playerChatManager.ShowChat();
}
}
}
}

View File

@@ -0,0 +1,20 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxModel.Packets;
namespace NitroxClient.Communication.Packets.Processors;
public class CreatureActionProcessor : ClientPacketProcessor<CreatureActionChanged>
{
private readonly AI ai;
public CreatureActionProcessor(AI ai)
{
this.ai = ai;
}
public override void Process(CreatureActionChanged packet)
{
ai.CreatureActionChanged(packet.CreatureId, packet.CreatureActionType);
}
}

View File

@@ -0,0 +1,13 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxModel.Packets;
namespace NitroxClient.Communication.Packets.Processors;
public class CreaturePoopPerformedProcessor : ClientPacketProcessor<CreaturePoopPerformed>
{
public override void Process(CreaturePoopPerformed packet)
{
AI.CreaturePoopPerformed(packet.CreatureId);
}
}

View File

@@ -0,0 +1,31 @@
using NitroxClient.Communication.Abstract;
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.MonoBehaviours;
using NitroxClient.Unity.Helper;
using NitroxModel_Subnautica.Packets;
using UnityEngine;
namespace NitroxClient.Communication.Packets.Processors
{
public class CyclopsDamagePointHealthChangedProcessor : ClientPacketProcessor<CyclopsDamagePointRepaired>
{
private readonly IPacketSender packetSender;
public CyclopsDamagePointHealthChangedProcessor(IPacketSender packetSender)
{
this.packetSender = packetSender;
}
public override void Process(CyclopsDamagePointRepaired packet)
{
GameObject gameObject = NitroxEntity.RequireObjectFrom(packet.Id);
SubRoot cyclops = gameObject.RequireComponent<SubRoot>();
using (PacketSuppressor<CyclopsDamage>.Suppress())
using (PacketSuppressor<CyclopsDamagePointRepaired>.Suppress())
{
cyclops.damageManager.damagePoints[packet.DamagePointIndex].liveMixin.AddHealth(packet.RepairAmount);
}
}
}
}

View File

@@ -0,0 +1,202 @@
using System.Collections.Generic;
using System.Linq;
using NitroxClient.Communication.Abstract;
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxClient.MonoBehaviours;
using NitroxClient.Unity.Helper;
using NitroxModel.DataStructures;
using NitroxModel.Packets;
using NitroxModel_Subnautica.DataStructures.GameLogic;
using NitroxModel_Subnautica.Packets;
using UnityEngine;
namespace NitroxClient.Communication.Packets.Processors
{
/// <summary>
/// Add/remove <see cref="CyclopsDamagePoint"/>s and <see cref="Fire"/>s to match the <see cref="CyclopsDamage"/> packet received
/// </summary>
public class CyclopsDamageProcessor : ClientPacketProcessor<CyclopsDamage>
{
private readonly IPacketSender packetSender;
private readonly Fires fires;
public CyclopsDamageProcessor(IPacketSender packetSender, Fires fires)
{
this.packetSender = packetSender;
this.fires = fires;
}
public override void Process(CyclopsDamage packet)
{
SubRoot subRoot = NitroxEntity.RequireObjectFrom(packet.Id).GetComponent<SubRoot>();
using (PacketSuppressor<CyclopsDamagePointRepaired>.Suppress())
{
SetActiveDamagePoints(subRoot, packet.DamagePointIndexes);
}
using (PacketSuppressor<FireDoused>.Suppress())
{
SetActiveRoomFires(subRoot, packet.RoomFires);
}
LiveMixin subHealth = subRoot.gameObject.RequireComponent<LiveMixin>();
float oldHPPercent = subRoot.oldHPPercent;
// Client side noises. Not necessary for keeping the health synced
if (subHealth.GetHealthFraction() < 0.5f && oldHPPercent >= 0.5f)
{
subRoot.voiceNotificationManager.PlayVoiceNotification(subRoot.hullLowNotification, true, false);
}
else if (subHealth.GetHealthFraction() < 0.25f && oldHPPercent >= 0.25f)
{
subRoot.voiceNotificationManager.PlayVoiceNotification(subRoot.hullCriticalNotification, true, false);
}
using (PacketSuppressor<CyclopsDamage>.Suppress())
{
// Not necessary, but used by above code whenever damage is done
subRoot.oldHPPercent = subHealth.GetHealthFraction();
// Apply the actual health changes
subRoot.gameObject.RequireComponent<LiveMixin>().health = packet.SubHealth;
subRoot.gameObject.RequireComponentInChildren<CyclopsExternalDamageManager>().subLiveMixin.health = packet.DamageManagerHealth;
subRoot.gameObject.RequireComponent<SubFire>().liveMixin.health = packet.SubFireHealth;
}
}
/// <summary>
/// Add/remove <see cref="CyclopsDamagePoint"/>s until it matches the <paramref name="damagePointIndexes"/> array passed. Can trigger <see cref="CyclopsDamagePointRepaired"/> packets
/// </summary>
private void SetActiveDamagePoints(SubRoot cyclops, int[] damagePointIndexes)
{
CyclopsExternalDamageManager damageManager = cyclops.gameObject.RequireComponentInChildren<CyclopsExternalDamageManager>();
List<CyclopsDamagePoint> unusedDamagePoints = damageManager.unusedDamagePoints;
// CyclopsExternalDamageManager.damagePoints is an unchanged list. It will never have items added/removed from it. Since packet.DamagePointIndexes is also an array
// generated in an ordered manner, we can match them without worrying about unordered items.
if (damagePointIndexes != null && damagePointIndexes.Length > 0)
{
int packetDamagePointsIndex = 0;
for (int damagePointsIndex = 0; damagePointsIndex < damageManager.damagePoints.Length; damagePointsIndex++)
{
// Loop over all of the packet.DamagePointIndexes as long as there's more to match
if (packetDamagePointsIndex < damagePointIndexes.Length
&& damagePointIndexes[packetDamagePointsIndex] == damagePointsIndex)
{
if (!damageManager.damagePoints[damagePointsIndex].gameObject.activeSelf)
{
// Copied from CyclopsExternalDamageManager.CreatePoint(), except without the random index pick.
damageManager.damagePoints[damagePointsIndex].gameObject.SetActive(true);
damageManager.damagePoints[damagePointsIndex].RestoreHealth();
GameObject prefabGo = damageManager.fxPrefabs[UnityEngine.Random.Range(0, damageManager.fxPrefabs.Length - 1)];
damageManager.damagePoints[damagePointsIndex].SpawnFx(prefabGo);
unusedDamagePoints.Remove(damageManager.damagePoints[damagePointsIndex]);
}
packetDamagePointsIndex++;
}
else
{
// If it's active, but not in the list, it must have been repaired.
if (damageManager.damagePoints[damagePointsIndex].gameObject.activeSelf)
{
RepairDamagePoint(cyclops, damagePointsIndex, 999);
}
}
}
// Looks like the list came in unordered. I've uttered "That shouldn't happen" enough to do sanity checks for what should be impossible.
if (packetDamagePointsIndex < damagePointIndexes.Length)
{
Log.Error($"[CyclopsDamageProcessor packet.DamagePointIds did not fully iterate! Id: {damagePointIndexes[packetDamagePointsIndex]} had no matching Id in damageManager.damagePoints, or the order is incorrect!]");
}
}
else
{
// None should be active.
for (int i = 0; i < damageManager.damagePoints.Length; i++)
{
if (damageManager.damagePoints[i].gameObject.activeSelf)
{
RepairDamagePoint(cyclops, i, 999);
}
}
}
// unusedDamagePoints is checked against damagePoints to determine if there's enough damage points. Failing to set the new list
// of unusedDamagePoints will cause random DamagePoints to appear.
damageManager.unusedDamagePoints = unusedDamagePoints;
// Visual update only to show the water leaking through the window and various hull points based on missing health.
damageManager.ToggleLeakPointsBasedOnDamage();
}
/// <summary>
/// Add/remove fires until it matches the <paramref name="roomFires"/> array. Can trigger <see cref="FireDoused"/> packets
/// </summary>
private void SetActiveRoomFires(SubRoot subRoot, CyclopsFireData[] roomFires)
{
SubFire subFire = subRoot.gameObject.RequireComponent<SubFire>();
Dictionary<CyclopsRooms, SubFire.RoomFire> roomFiresDict = subFire.roomFires;
if (!subRoot.TryGetIdOrWarn(out NitroxId subRootId))
{
return;
}
if (roomFires != null && roomFires.Length > 0)
{
// Removing and adding fires will happen in the same loop
foreach (KeyValuePair<CyclopsRooms, SubFire.RoomFire> keyValuePair in roomFiresDict)
{
for (int nodeIndex = 0; nodeIndex < keyValuePair.Value.spawnNodes.Length; nodeIndex++)
{
CyclopsFireData fireNode = roomFires.SingleOrDefault(x => x.Room == keyValuePair.Key && x.NodeIndex == nodeIndex);
// If there's a matching node index, add a fire if there isn't one already. Otherwise remove a fire if there is one
if (fireNode == null)
{
if (keyValuePair.Value.spawnNodes[nodeIndex].childCount > 0)
{
keyValuePair.Value.spawnNodes[nodeIndex].GetComponentInChildren<Fire>().Douse(10000);
}
}
else
{
if (keyValuePair.Value.spawnNodes[nodeIndex].childCount < 1)
{
fires.Create(new CyclopsFireData(fireNode.FireId, subRootId, fireNode.Room, fireNode.NodeIndex));
}
}
}
}
}
// Clear out the fires, there should be none active
else
{
foreach (KeyValuePair<CyclopsRooms, SubFire.RoomFire> keyValuePair in roomFiresDict)
{
foreach (Transform spawnNode in keyValuePair.Value.spawnNodes)
{
if (spawnNode.childCount > 0)
{
spawnNode.GetComponentInChildren<Fire>().Douse(10000);
}
}
}
}
}
/// <summary>
/// Set the health of a <see cref="CyclopsDamagePoint"/>. This can trigger sending <see cref="CyclopsDamagePointRepaired"/> packets
/// </summary>
/// <param name="repairAmount">The max health of the point is 1. 999 is passed to trigger a full repair of the <see cref="CyclopsDamagePoint"/></param>
private void RepairDamagePoint(SubRoot subRoot, int damagePointIndex, float repairAmount)
{
subRoot.damageManager.damagePoints[damagePointIndex].liveMixin.AddHealth(repairAmount);
}
}
}

View File

@@ -0,0 +1,24 @@
using NitroxClient.Communication.Abstract;
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxModel_Subnautica.Packets;
namespace NitroxClient.Communication.Packets.Processors
{
class CyclopsDecoyLaunchProcessor : ClientPacketProcessor<CyclopsDecoyLaunch>
{
private readonly IPacketSender packetSender;
private readonly Cyclops cyclops;
public CyclopsDecoyLaunchProcessor(IPacketSender packetSender, Cyclops cyclops)
{
this.packetSender = packetSender;
this.cyclops = cyclops;
}
public override void Process(CyclopsDecoyLaunch decoyLaunchPacket)
{
cyclops.LaunchDecoy(decoyLaunchPacket.Id);
}
}
}

View File

@@ -0,0 +1,24 @@
using NitroxClient.Communication.Abstract;
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxModel_Subnautica.Packets;
namespace NitroxClient.Communication.Packets.Processors
{
public class CyclopsFireCreatedProcessor : ClientPacketProcessor<CyclopsFireCreated>
{
private readonly IPacketSender packetSender;
private readonly Fires fires;
public CyclopsFireCreatedProcessor(IPacketSender packetSender, Fires fires)
{
this.packetSender = packetSender;
this.fires = fires;
}
public override void Process(CyclopsFireCreated packet)
{
fires.Create(packet.FireCreatedData);
}
}
}

View File

@@ -0,0 +1,24 @@
using NitroxClient.Communication.Abstract;
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxModel_Subnautica.Packets;
namespace NitroxClient.Communication.Packets.Processors
{
public class CyclopsFireSuppressionProcessor : ClientPacketProcessor<CyclopsFireSuppression>
{
private readonly IPacketSender packetSender;
private readonly Cyclops cyclops;
public CyclopsFireSuppressionProcessor(IPacketSender packetSender, Cyclops cyclops)
{
this.packetSender = packetSender;
this.cyclops = cyclops;
}
public override void Process(CyclopsFireSuppression fireSuppressionPacket)
{
cyclops.StartFireSuppression(fireSuppressionPacket.Id);
}
}
}

View File

@@ -0,0 +1,19 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxModel.DataStructures.Unity;
using NitroxModel.Packets;
using UnityEngine;
namespace NitroxClient.Communication.Packets.Processors
{
class DebugStartMapProcessor : ClientPacketProcessor<DebugStartMapPacket>
{
public override void Process(DebugStartMapPacket packet)
{
foreach (NitroxVector3 position in packet.StartPositions)
{
GameObject prim = GameObject.CreatePrimitive(PrimitiveType.Cube);
prim.transform.position = new Vector3(position.X, position.Y + 10, position.Z);
}
}
}
}

View File

@@ -0,0 +1,43 @@
using NitroxClient.Communication.Abstract;
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic.Helper;
using NitroxClient.MonoBehaviours;
using NitroxModel.Packets;
using UnityEngine;
using static NitroxClient.GameLogic.Helper.TransientLocalObjectManager;
namespace NitroxClient.Communication.Packets.Processors
{
public class DeconstructionBeginProcessor : ClientPacketProcessor<DeconstructionBegin>
{
private readonly IPacketSender packetSender;
public DeconstructionBeginProcessor(IPacketSender packetSender)
{
this.packetSender = packetSender;
}
public override void Process(DeconstructionBegin packet)
{
Log.Info($"Received deconstruction packet for id: {packet.Id}");
GameObject deconstructing = NitroxEntity.RequireObjectFrom(packet.Id);
Constructable constructable = deconstructing.GetComponent<Constructable>();
BaseDeconstructable baseDeconstructable = deconstructing.GetComponent<BaseDeconstructable>();
using (PacketSuppressor<DeconstructionBegin>.Suppress())
{
if (baseDeconstructable != null)
{
TransientLocalObjectManager.Add(TransientObjectType.LATEST_DECONSTRUCTED_BASE_PIECE_GUID, packet.Id);
baseDeconstructable.Deconstruct();
}
else if (constructable != null)
{
constructable.SetState(false, false);
}
}
}
}
}

View File

@@ -0,0 +1,36 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxClient.GameLogic.HUD;
using NitroxModel.DataStructures.Util;
using NitroxModel.Packets;
namespace NitroxClient.Communication.Packets.Processors
{
class DisconnectProcessor : ClientPacketProcessor<Disconnect>
{
private readonly PlayerManager remotePlayerManager;
private readonly PlayerVitalsManager vitalsManager;
public DisconnectProcessor(PlayerManager remotePlayerManager, PlayerVitalsManager vitalsManager)
{
this.remotePlayerManager = remotePlayerManager;
this.vitalsManager = vitalsManager;
}
public override void Process(Disconnect disconnect)
{
// TODO: don't remove right away... maybe grey out and start
// a coroutine to finally remove.
vitalsManager.RemoveForPlayer(disconnect.PlayerId);
Optional<RemotePlayer> remotePlayer = remotePlayerManager.Find(disconnect.PlayerId);
if (remotePlayer.HasValue)
{
remotePlayer.Value.PlayerDisconnectEvent.Trigger(remotePlayer.Value);
remotePlayerManager.RemovePlayer(disconnect.PlayerId);
Log.Info($"{remotePlayer.Value.PlayerName} disconnected");
Log.InGame(Language.main.Get("Nitrox_PlayerDisconnected").Replace("{PLAYER}", remotePlayer.Value.PlayerName));
}
}
}
}

View File

@@ -0,0 +1,13 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.MonoBehaviours.Discord;
using NitroxModel.Packets;
namespace NitroxClient.Communication.Packets.Processors;
public class DiscordRequestIPProcessor : ClientPacketProcessor<DiscordRequestIP>
{
public override void Process(DiscordRequestIP packet)
{
DiscordClient.UpdateIpPort(packet.IpPort);
}
}

View File

@@ -0,0 +1,20 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxModel.Packets;
namespace NitroxClient.Communication.Packets.Processors;
public class DropSimulationOwnershipProcessor : ClientPacketProcessor<DropSimulationOwnership>
{
private readonly SimulationOwnership simulationOwnershipManager;
public DropSimulationOwnershipProcessor(SimulationOwnership simulationOwnershipManager)
{
this.simulationOwnershipManager = simulationOwnershipManager;
}
public override void Process(DropSimulationOwnership packet)
{
simulationOwnershipManager.DropSimulationFrom(packet.EntityId);
}
}

View File

@@ -0,0 +1,95 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxClient.GameLogic.PlayerLogic;
using NitroxClient.MonoBehaviours;
using NitroxModel.Packets;
using UnityEngine;
namespace NitroxClient.Communication.Packets.Processors;
public class EntityDestroyedProcessor : ClientPacketProcessor<EntityDestroyed>
{
public const DamageType DAMAGE_TYPE_RUN_ORIGINAL = (DamageType)100;
private readonly Entities entities;
public EntityDestroyedProcessor(Entities entities)
{
this.entities = entities;
}
public override void Process(EntityDestroyed packet)
{
entities.RemoveEntity(packet.Id);
if (!NitroxEntity.TryGetObjectFrom(packet.Id, out GameObject gameObject))
{
entities.MarkForDeletion(packet.Id);
Log.Warn($"[{nameof(EntityDestroyedProcessor)}] Could not find entity with id: {packet.Id} to destroy.");
return;
}
using (PacketSuppressor<EntityDestroyed>.Suppress())
{
// This type of check could get out of control if there are many types with custom destroy logic. If we get a few more, move to separate processors.
if (gameObject.TryGetComponent(out Vehicle vehicle))
{
DestroyVehicle(vehicle);
}
else if (gameObject.TryGetComponent(out SubRoot subRoot))
{
DestroySubroot(subRoot);
}
else
{
Entities.DestroyObject(gameObject);
}
}
}
private void DestroySubroot(SubRoot subRoot)
{
DamageInfo damageInfo = new() { type = DAMAGE_TYPE_RUN_ORIGINAL };
if (subRoot.live.health > 0f)
{
// oldHPPercent must be in the interval [0; 0.25[ because else, SubRoot.OnTakeDamage will end up in the wrong else condition
subRoot.oldHPPercent = 0f;
subRoot.live.health = 0f;
subRoot.live.NotifyAllAttachedDamageReceivers(damageInfo);
subRoot.live.Kill();
}
// We use a specific DamageType so that the Prefix on this method will accept this call
subRoot.OnTakeDamage(damageInfo);
}
private void DestroyVehicle(Vehicle vehicle)
{
if (vehicle.GetPilotingMode()) //Check Local Object Have Player inside
{
vehicle.OnPilotModeEnd();
if (!Player.main.ToNormalMode(true))
{
Player.main.ToNormalMode(false);
Player.main.transform.parent = null;
}
}
foreach (RemotePlayerIdentifier identifier in vehicle.GetComponentsInChildren<RemotePlayerIdentifier>(true))
{
identifier.RemotePlayer.ResetStates();
}
if (vehicle.gameObject)
{
if (vehicle.destructionEffect)
{
GameObject gameObject = Object.Instantiate(vehicle.destructionEffect);
gameObject.transform.position = vehicle.transform.position;
gameObject.transform.rotation = vehicle.transform.rotation;
}
Object.Destroy(vehicle.gameObject);
}
}
}

View File

@@ -0,0 +1,34 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic.Spawning.Metadata;
using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
using NitroxClient.MonoBehaviours;
using NitroxModel.DataStructures.Util;
using NitroxModel.Helper;
using NitroxModel.Packets;
using UnityEngine;
namespace NitroxClient.Communication.Packets.Processors;
public class EntityMetadataUpdateProcessor : ClientPacketProcessor<EntityMetadataUpdate>
{
private readonly EntityMetadataManager entityMetadataManager;
public EntityMetadataUpdateProcessor(EntityMetadataManager entityMetadataManager)
{
this.entityMetadataManager = entityMetadataManager;
}
public override void Process(EntityMetadataUpdate update)
{
if (!NitroxEntity.TryGetObjectFrom(update.Id, out GameObject gameObject))
{
entityMetadataManager.RegisterNewerMetadata(update.Id, update.NewValue);
return;
}
Optional<IEntityMetadataProcessor> metadataProcessor = entityMetadataManager.FromMetaData(update.NewValue);
Validate.IsTrue(metadataProcessor.HasValue, $"No processor found for EntityMetadata of type {update.NewValue.GetType()}");
metadataProcessor.Value.ProcessMetadata(gameObject, update.NewValue);
}
}

View File

@@ -0,0 +1,99 @@
using System;
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxClient.GameLogic.Helper;
using NitroxClient.MonoBehaviours;
using NitroxClient.Unity.Helper;
using NitroxModel.DataStructures.GameLogic.Entities;
using NitroxModel.DataStructures.Util;
using NitroxModel.Packets;
using UnityEngine;
namespace NitroxClient.Communication.Packets.Processors;
public class EntityReparentedProcessor : ClientPacketProcessor<EntityReparented>
{
private readonly Entities entities;
public EntityReparentedProcessor(Entities entities)
{
this.entities = entities;
}
public override void Process(EntityReparented packet)
{
Optional<GameObject> entity = NitroxEntity.GetObjectFrom(packet.Id);
if (!entity.HasValue)
{
// In some cases, the affected entity may be pending spawning or out of range.
// we only require the parent (in this case, the visible entity is undergoing
// some change that must be shown, and if not is an error).
return;
}
GameObject newParent = NitroxEntity.RequireObjectFrom(packet.NewParentId);
if (entity.Value.TryGetComponent(out Pickupable pickupable))
{
WaterParkItem waterParkItem = pickupable.GetComponent<WaterParkItem>();
// If the entity is being parented to a WaterPark
if (newParent.TryGetComponent(out WaterPark waterPark))
{
// If the entity is already in a WaterPark
if (waterParkItem.currentWaterPark)
{
waterParkItem.SetWaterPark(waterPark);
return;
}
pickupable.SetVisible(false);
pickupable.Activate(false);
waterPark.AddItem(pickupable);
// The reparenting is automatic here so we don't need to continue
return;
}
// If the entity was parented to a WaterPark but is picked up by someone
else if (waterParkItem)
{
pickupable.Deactivate();
waterParkItem.SetWaterPark(null);
}
}
using (PacketSuppressor<EntityReparented>.Suppress())
{
Type entityType = entities.RequireEntityType(packet.Id);
// Move this to a resolver if there ends up being a lot of custom reparenting logic
if (entityType == typeof(InventoryItemEntity))
{
InventoryItemReparented(entity.Value, newParent);
}
else
{
PerformDefaultReparenting(entity.Value, newParent);
}
}
}
private void InventoryItemReparented(GameObject entity, GameObject newParent)
{
Optional<ItemsContainer> opContainer = InventoryContainerHelper.TryGetContainerByOwner(newParent);
if (!opContainer.HasValue)
{
Log.Error($"Could not find container field on GameObject {newParent.GetFullHierarchyPath()}");
return;
}
Pickupable pickupable = entity.RequireComponent<Pickupable>();
ItemsContainer container = opContainer.Value;
container.UnsafeAdd(new InventoryItem(pickupable));
}
private void PerformDefaultReparenting(GameObject entity, GameObject newParent)
{
entity.transform.SetParent(newParent.transform, false);
}
}

View File

@@ -0,0 +1,46 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxClient.MonoBehaviours;
using NitroxModel.Packets;
using NitroxModel_Subnautica.DataStructures;
using UnityEngine;
using static NitroxModel.Packets.EntityTransformUpdates;
namespace NitroxClient.Communication.Packets.Processors;
public class EntityTransformUpdatesProcessor : ClientPacketProcessor<EntityTransformUpdates>
{
private readonly SimulationOwnership simulationOwnership;
public EntityTransformUpdatesProcessor(SimulationOwnership simulationOwnership)
{
this.simulationOwnership = simulationOwnership;
}
public override void Process(EntityTransformUpdates packet)
{
foreach (EntityTransformUpdate update in packet.Updates)
{
// We will cancel any position update attempt at one of our locked entities
if (!NitroxEntity.TryGetObjectFrom(update.Id, out GameObject gameObject) ||
simulationOwnership.HasAnyLockType(update.Id))
{
continue;
}
RemotelyControlled remotelyControlled = gameObject.EnsureComponent<RemotelyControlled>();
Vector3 position = update.Position.ToUnity();
Quaternion rotation = update.Rotation.ToUnity();
if (update is SplineTransformUpdate splineUpdate)
{
remotelyControlled.UpdateKnownSplineUser(position, rotation, splineUpdate.DestinationPosition.ToUnity(), splineUpdate.DestinationDirection.ToUnity(), splineUpdate.Velocity);
}
else
{
remotelyControlled.UpdateOrientation(position, rotation);
}
}
}
}

View File

@@ -0,0 +1,39 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxClient.MonoBehaviours;
using NitroxModel.DataStructures.Util;
using NitroxModel.Packets;
using UnityEngine;
namespace NitroxClient.Communication.Packets.Processors
{
public class EscapePodChangedProcessor : ClientPacketProcessor<EscapePodChanged>
{
private readonly PlayerManager remotePlayerManager;
public EscapePodChangedProcessor(PlayerManager remotePlayerManager)
{
this.remotePlayerManager = remotePlayerManager;
}
public override void Process(EscapePodChanged packet)
{
Optional<RemotePlayer> remotePlayer = remotePlayerManager.Find(packet.PlayerId);
if (remotePlayer.HasValue)
{
EscapePod escapePod = null;
if (packet.EscapePodId.HasValue)
{
GameObject sub = NitroxEntity.RequireObjectFrom(packet.EscapePodId.Value);
escapePod = sub.GetComponent<EscapePod>();
}
remotePlayer.Value.SetEscapePod(escapePod);
}
}
}
}

View File

@@ -0,0 +1,36 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxClient.MonoBehaviours;
using NitroxModel_Subnautica.DataStructures;
using NitroxModel_Subnautica.Packets;
using UnityEngine;
namespace NitroxClient.Communication.Packets.Processors;
public class ExosuitArmActionProcessor : ClientPacketProcessor<ExosuitArmActionPacket>
{
public override void Process(ExosuitArmActionPacket packet)
{
if (!NitroxEntity.TryGetObjectFrom(packet.ArmId, out GameObject gameObject))
{
Log.Error("Could not find exosuit arm");
return;
}
switch (packet.TechType)
{
case TechType.ExosuitClawArmModule:
ExosuitModuleEvent.UseClaw(gameObject.GetComponent<ExosuitClawArm>(), packet.ArmAction);
break;
case TechType.ExosuitDrillArmModule:
ExosuitModuleEvent.UseDrill(gameObject.GetComponent<ExosuitDrillArm>(), packet.ArmAction);
break;
case TechType.ExosuitGrapplingArmModule:
ExosuitModuleEvent.UseGrappling(gameObject.GetComponent<ExosuitGrapplingArm>(), packet.ArmAction, packet.OpVector?.ToUnity());
break;
default:
Log.Error($"Got an arm tech that is not handled: {packet.TechType} with action: {packet.ArmAction} for id {packet.ArmId}");
break;
}
}
}

View File

@@ -0,0 +1,28 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.MonoBehaviours;
using NitroxModel_Subnautica.DataStructures;
using NitroxModel.GameLogic.FMOD;
using NitroxModel.Packets;
namespace NitroxClient.Communication.Packets.Processors;
public class FMODAssetProcessor : ClientPacketProcessor<FMODAssetPacket>
{
private readonly FMODWhitelist fmodWhitelist;
public FMODAssetProcessor(FMODWhitelist fmodWhitelist)
{
this.fmodWhitelist = fmodWhitelist;
}
public override void Process(FMODAssetPacket packet)
{
if (!fmodWhitelist.TryGetSoundData(packet.AssetPath, out SoundData soundData))
{
Log.ErrorOnce($"[{nameof(FMODAssetProcessor)}] Whitelist has no item for {packet.AssetPath}.");
return;
}
FMODEmitterController.PlayEventOneShot(packet.AssetPath, soundData.Radius, packet.Position.ToUnity(), packet.Volume);
}
}

View File

@@ -0,0 +1,37 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.MonoBehaviours;
using NitroxModel.Packets;
using UnityEngine;
namespace NitroxClient.Communication.Packets.Processors;
public class FMODCustomEmitterProcessor : ClientPacketProcessor<FMODCustomEmitterPacket>
{
public override void Process(FMODCustomEmitterPacket packet)
{
if (!NitroxEntity.TryGetObjectFrom(packet.Id, out GameObject emitterControllerEntity))
{
Log.ErrorOnce($"[{nameof(FMODCustomEmitterProcessor)}] Couldn't find entity {packet.Id}");
return;
}
if (!emitterControllerEntity.TryGetComponent(out FMODEmitterController fmodEmitterController))
{
fmodEmitterController = emitterControllerEntity.AddComponent<FMODEmitterController>();
fmodEmitterController.LateRegisterEmitter();
}
using (PacketSuppressor<FMODCustomEmitterPacket>.Suppress())
using (PacketSuppressor<FMODCustomLoopingEmitterPacket>.Suppress())
{
if (packet.Play)
{
fmodEmitterController.PlayCustomEmitter(packet.AssetPath);
}
else
{
fmodEmitterController.StopCustomEmitter(packet.AssetPath);
}
}
}
}

View File

@@ -0,0 +1,26 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.MonoBehaviours;
using NitroxModel.Packets;
using UnityEngine;
namespace NitroxClient.Communication.Packets.Processors;
public class FMODCustomLoopingEmitterProcessor : ClientPacketProcessor<FMODCustomLoopingEmitterPacket>
{
public override void Process(FMODCustomLoopingEmitterPacket packet)
{
if (!NitroxEntity.TryGetObjectFrom(packet.Id, out GameObject emitterControllerObject))
{
Log.ErrorOnce($"[{nameof(FMODCustomLoopingEmitterProcessor)}] Couldn't find entity {packet.Id}");
return;
}
if (!emitterControllerObject.TryGetComponent(out FMODEmitterController fmodEmitterController))
{
fmodEmitterController = emitterControllerObject.AddComponent<FMODEmitterController>();
fmodEmitterController.LateRegisterEmitter();
}
fmodEmitterController.PlayCustomLoopingEmitter(packet.AssetPath);
}
}

View File

@@ -0,0 +1,33 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.MonoBehaviours;
using NitroxModel.Packets;
using UnityEngine;
namespace NitroxClient.Communication.Packets.Processors;
public class FMODEventInstanceProcessor : ClientPacketProcessor<FMODEventInstancePacket>
{
public override void Process(FMODEventInstancePacket packet)
{
if (!NitroxEntity.TryGetObjectFrom(packet.Id, out GameObject emitterControllerObject))
{
Log.ErrorOnce($"[{nameof(FMODEventInstanceProcessor)}] Couldn't find entity {packet.Id}");
return;
}
if (!emitterControllerObject.TryGetComponent(out FMODEmitterController fmodEmitterController))
{
fmodEmitterController = emitterControllerObject.AddComponent<FMODEmitterController>();
fmodEmitterController.LateRegisterEmitter();
}
if (packet.Play)
{
fmodEmitterController.PlayEventInstance(packet.AssetPath, packet.Volume);
}
else
{
fmodEmitterController.StopEventInstance(packet.AssetPath);
}
}
}

View File

@@ -0,0 +1,36 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.MonoBehaviours;
using NitroxModel.Packets;
using UnityEngine;
namespace NitroxClient.Communication.Packets.Processors;
public class FMODStudioEventEmitterProcessor : ClientPacketProcessor<FMODStudioEmitterPacket>
{
public override void Process(FMODStudioEmitterPacket packet)
{
if (!NitroxEntity.TryGetObjectFrom(packet.Id, out GameObject emitterControllerObject))
{
Log.ErrorOnce($"[{nameof(FMODStudioEventEmitterProcessor)}] Couldn't find entity {packet.Id}");
return;
}
if (!emitterControllerObject.TryGetComponent(out FMODEmitterController fmodEmitterController))
{
fmodEmitterController = emitterControllerObject.AddComponent<FMODEmitterController>();
fmodEmitterController.LateRegisterEmitter();
}
using (PacketSuppressor<FMODStudioEmitterPacket>.Suppress())
{
if (packet.Play)
{
fmodEmitterController.PlayStudioEmitter(packet.AssetPath);
}
else
{
fmodEmitterController.StopStudioEmitter(packet.AssetPath, packet.AllowFadeout);
}
}
}
}

View File

@@ -0,0 +1,21 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxModel.Packets;
namespace NitroxClient.Communication.Packets.Processors;
public class FastCheatChangedProcessor : ClientPacketProcessor<FastCheatChanged>
{
public override void Process(FastCheatChanged packet)
{
switch (packet.Cheat)
{
case FastCheatChanged.FastCheat.HATCH:
NoCostConsoleCommand.main.fastHatchCheat = packet.Value;
break;
case FastCheatChanged.FastCheat.GROW:
NoCostConsoleCommand.main.fastGrowCheat = packet.Value;
break;
}
}
}

View File

@@ -0,0 +1,33 @@
using NitroxClient.Communication.Abstract;
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.MonoBehaviours;
using NitroxClient.Unity.Helper;
using NitroxModel.Packets;
using UnityEngine;
namespace NitroxClient.Communication.Packets.Processors
{
public class FireDousedProcessor : ClientPacketProcessor<FireDoused>
{
private readonly IPacketSender packetSender;
public FireDousedProcessor(IPacketSender packetSender)
{
this.packetSender = packetSender;
}
/// <summary>
/// Finds and executes <see cref="Fire.Douse(float)"/>. If the fire is extinguished, it will pass a large float to trigger the private
/// <see cref="Fire.Extinguish()"/> method.
/// </summary>
public override void Process(FireDoused packet)
{
GameObject fireGameObject = NitroxEntity.RequireObjectFrom(packet.Id);
using (PacketSuppressor<FireDoused>.Suppress())
{
fireGameObject.RequireComponent<Fire>().Douse(packet.DouseAmount);
}
}
}
}

View File

@@ -0,0 +1,57 @@
using System;
using FMOD;
using FMOD.Studio;
using FMODUnity;
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxClient.GameLogic.FMOD;
using NitroxModel.DataStructures.Util;
using NitroxModel.GameLogic.FMOD;
using NitroxModel.Packets;
namespace NitroxClient.Communication.Packets.Processors;
public class FootstepPacketProcessor : ClientPacketProcessor<FootstepPacket>
{
private readonly PlayerManager remotePlayerManager;
private readonly Lazy<FootstepSounds> localFootstepSounds = new(() => Player.mainObject.GetComponent<FootstepSounds>());
private PARAMETER_ID fmodIndexSpeed = FMODUWE.invalidParameterId;
private readonly float footstepAudioRadius; // To modify this value, modify the last value in the SoundWhitelist_Subnautica.csv file
private const float FOOTSTEP_AUDIO_MAX_VOLUME = 0.5f;
public FootstepPacketProcessor(PlayerManager remotePlayerManager, FMODWhitelist whitelist)
{
this.remotePlayerManager = remotePlayerManager;
whitelist.TryGetSoundData("event:/player/footstep_precursor_base", out SoundData soundData);
footstepAudioRadius = soundData.Radius;
}
public override void Process(FootstepPacket packet)
{
Optional<RemotePlayer> player = remotePlayerManager.Find(packet.PlayerID);
if (player.HasValue)
{
FMODAsset asset = packet.AssetIndex switch
{
FootstepPacket.StepSounds.PRECURSOR => localFootstepSounds.Value.precursorInteriorSound,
FootstepPacket.StepSounds.METAL => localFootstepSounds.Value.metalSound,
FootstepPacket.StepSounds.LAND => localFootstepSounds.Value.landSound,
_ => null
};
EventInstance evt = FMODUWE.GetEvent(asset);
if (evt.isValid())
{
if (FMODUWE.IsInvalidParameterId(fmodIndexSpeed))
{
fmodIndexSpeed = FMODUWE.GetEventInstanceParameterIndex(evt, "speed");
}
ATTRIBUTES_3D attributes = player.Value.Body.To3DAttributes();
evt.set3DAttributes(attributes);
evt.setParameterValueByIndex(fmodIndexSpeed, player.Value.AnimationController.Velocity.magnitude);
evt.setVolume(FMODSystem.CalculateVolume(Player.mainObject.transform.position, player.Value.Body.transform.position, footstepAudioRadius, FOOTSTEP_AUDIO_MAX_VOLUME));
evt.start();
evt.release();
}
}
}
}

View File

@@ -0,0 +1,36 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxModel.Packets;
namespace NitroxClient.Communication.Packets.Processors;
public class GameModeChangedProcessor : ClientPacketProcessor<GameModeChanged>
{
private readonly LocalPlayer localPlayer;
private readonly PlayerManager playerManager;
public GameModeChangedProcessor(LocalPlayer localPlayer, PlayerManager playerManager)
{
this.localPlayer = localPlayer;
this.playerManager = playerManager;
}
public override void Process(GameModeChanged packet)
{
if (packet.AllPlayers || packet.PlayerId == localPlayer.PlayerId)
{
GameModeUtils.SetGameMode((GameModeOption)(int)packet.GameMode, GameModeOption.None);
}
if (packet.AllPlayers)
{
foreach (RemotePlayer remotePlayer in playerManager.GetAll())
{
remotePlayer.SetGameMode(packet.GameMode);
}
}
else if (playerManager.TryFind(packet.PlayerId, out RemotePlayer remotePlayer))
{
remotePlayer.SetGameMode(packet.GameMode);
}
}
}

View File

@@ -0,0 +1,118 @@
using System;
using System.Collections;
using System.Collections.Generic;
using NitroxClient.Communication.Abstract;
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic.InitialSync.Abstract;
using NitroxClient.MonoBehaviours;
using NitroxModel.Packets;
namespace NitroxClient.Communication.Packets.Processors
{
public class InitialPlayerSyncProcessor : ClientPacketProcessor<InitialPlayerSync>
{
private readonly IPacketSender packetSender;
private readonly HashSet<IInitialSyncProcessor> processors;
private readonly HashSet<Type> alreadyRan = new();
private InitialPlayerSync packet;
private WaitScreen.ManualWaitItem loadingMultiplayerWaitItem;
private WaitScreen.ManualWaitItem subWaitScreenItem;
private int cumulativeProcessorsRan;
private int processorsRanLastCycle;
public InitialPlayerSyncProcessor(IPacketSender packetSender, IEnumerable<IInitialSyncProcessor> processors)
{
this.packetSender = packetSender;
this.processors = processors.ToSet();
}
public override void Process(InitialPlayerSync packet)
{
this.packet = packet;
loadingMultiplayerWaitItem = WaitScreen.Add(Language.main.Get("Nitrox_SyncingWorld"));
cumulativeProcessorsRan = 0;
Multiplayer.Main.StartCoroutine(ProcessInitialSyncPacket(this, null));
}
private IEnumerator ProcessInitialSyncPacket(object sender, EventArgs eventArgs)
{
bool moreProcessorsToRun;
do
{
yield return Multiplayer.Main.StartCoroutine(RunPendingProcessors());
moreProcessorsToRun = alreadyRan.Count < processors.Count;
if (moreProcessorsToRun && processorsRanLastCycle == 0)
{
throw new Exception($"Detected circular dependencies in initial packet sync between: {GetRemainingProcessorsText()}");
}
} while (moreProcessorsToRun);
WaitScreen.Remove(loadingMultiplayerWaitItem);
Multiplayer.Main.InitialSyncCompleted = true;
// When the player finishes loading, we can take back his invincibility
Player.main.liveMixin.invincible = false;
Player.main.UnfreezeStats();
packetSender.Send(new PlayerSyncFinished());
}
private IEnumerator RunPendingProcessors()
{
processorsRanLastCycle = 0;
foreach (IInitialSyncProcessor processor in processors)
{
if (IsWaitingToRun(processor.GetType()) && HasDependenciesSatisfied(processor))
{
loadingMultiplayerWaitItem.SetProgress(cumulativeProcessorsRan, processors.Count);
alreadyRan.Add(processor.GetType());
processorsRanLastCycle++;
cumulativeProcessorsRan++;
Log.Info($"Running {processor.GetType()}");
subWaitScreenItem = WaitScreen.Add($"Running {processor.GetType().Name}");
yield return Multiplayer.Main.StartCoroutine(processor.Process(packet, subWaitScreenItem));
WaitScreen.Remove(subWaitScreenItem);
}
}
}
private bool HasDependenciesSatisfied(IInitialSyncProcessor processor)
{
foreach (Type dependentType in processor.DependentProcessors)
{
if (IsWaitingToRun(dependentType))
{
return false;
}
}
return true;
}
private bool IsWaitingToRun(Type processor)
{
return alreadyRan.Contains(processor) == false;
}
private string GetRemainingProcessorsText()
{
string remaining = "";
foreach (IInitialSyncProcessor processor in processors)
{
if (IsWaitingToRun(processor.GetType()))
{
remaining += $" {processor.GetType()}";
}
}
return remaining;
}
}
}

View File

@@ -0,0 +1,25 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxClient.MonoBehaviours;
using NitroxModel.DataStructures.Util;
using NitroxModel.Packets;
using NitroxModel_Subnautica.DataStructures;
using UnityEngine;
namespace NitroxClient.Communication.Packets.Processors
{
class ItemPositionProcessor : ClientPacketProcessor<ItemPosition>
{
private const float ITEM_TRANSFORM_SMOOTH_PERIOD = 0.25f;
public override void Process(ItemPosition drop)
{
Optional<GameObject> opItem = NitroxEntity.GetObjectFrom(drop.Id);
if (opItem.HasValue)
{
MovementHelper.MoveRotateGameObject(opItem.Value, drop.Position.ToUnity(), drop.Rotation.ToUnity(), ITEM_TRANSFORM_SMOOTH_PERIOD);
}
}
}
}

View File

@@ -0,0 +1,37 @@
using System;
using NitroxClient.Communication.Abstract;
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxModel.Packets;
using NitroxModel_Subnautica.DataStructures;
namespace NitroxClient.Communication.Packets.Processors;
public class KnownTechEntryProcessorAdd : ClientPacketProcessor<KnownTechEntryAdd>
{
private readonly IPacketSender packetSender;
public KnownTechEntryProcessorAdd(IPacketSender packetSender)
{
this.packetSender = packetSender;
}
public override void Process(KnownTechEntryAdd packet)
{
using (PacketSuppressor<KnownTechEntryAdd>.Suppress())
{
switch (packet.Category)
{
case KnownTechEntryAdd.EntryCategory.KNOWN:
KnownTech.Add(packet.TechType.ToUnity(), packet.Verbose);
break;
case KnownTechEntryAdd.EntryCategory.ANALYZED:
KnownTech.Analyze(packet.TechType.ToUnity(), packet.Verbose);
break;
default:
string categoryName = Enum.GetName(typeof(KnownTechEntryAdd.EntryCategory), packet.Category);
Log.Error($"Received an unknown category type for {nameof(KnownTechEntryAdd)} packet: {categoryName}");
break;
}
}
}
}

View File

@@ -0,0 +1,17 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.MonoBehaviours;
using NitroxModel.Packets;
using NitroxModel_Subnautica.DataStructures;
namespace NitroxClient.Communication.Packets.Processors;
public class LeakRepairedProcessor : ClientPacketProcessor<LeakRepaired>
{
public override void Process(LeakRepaired packet)
{
if (NitroxEntity.TryGetComponentFrom(packet.BaseId, out BaseLeakManager baseLeakManager))
{
baseLeakManager.HealLeakToMax(packet.RelativeCell.ToUnity());
}
}
}

View File

@@ -0,0 +1,36 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic.FMOD;
using NitroxClient.MonoBehaviours;
using NitroxClient.Unity.Helper;
using NitroxModel.Packets;
using UnityEngine;
namespace NitroxClient.Communication.Packets.Processors;
public class MedicalCabinetClickedProcessor : ClientPacketProcessor<MedicalCabinetClicked>
{
public override void Process(MedicalCabinetClicked packet)
{
GameObject gameObject = NitroxEntity.RequireObjectFrom(packet.Id);
MedicalCabinet cabinet = gameObject.RequireComponent<MedicalCabinet>();
bool medkitPickedUp = !packet.HasMedKit && cabinet.hasMedKit;
bool doorChangedState = cabinet.doorOpen != packet.DoorOpen;
cabinet.hasMedKit = packet.HasMedKit;
cabinet.timeSpawnMedKit = packet.NextSpawnTime;
using (PacketSuppressor<FMODCustomEmitterPacket>.Suppress())
using (FMODSystem.SuppressSubnauticaSounds())
{
if (doorChangedState)
{
cabinet.Invoke(nameof(MedicalCabinet.ToggleDoorState), 0f);
}
else if (medkitPickedUp)
{
cabinet.Invoke(nameof(MedicalCabinet.ToggleDoorState), 2f);
}
}
}
}

View File

@@ -0,0 +1,22 @@
using NitroxClient.Communication.Abstract;
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxModel.Packets;
namespace NitroxClient.Communication.Packets.Processors
{
public class MultiplayerSessionPolicyProcessor : ClientPacketProcessor<MultiplayerSessionPolicy>
{
private readonly IMultiplayerSession multiplayerSession;
public MultiplayerSessionPolicyProcessor(IMultiplayerSession multiplayerSession)
{
this.multiplayerSession = multiplayerSession;
}
public override void Process(MultiplayerSessionPolicy packet)
{
Log.Info("Processing session policy information.");
multiplayerSession.ProcessSessionPolicy(packet);
}
}
}

View File

@@ -0,0 +1,21 @@
using NitroxClient.Communication.Abstract;
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxModel.Packets;
namespace NitroxClient.Communication.Packets.Processors
{
public class MultiplayerSessionReservationProcessor : ClientPacketProcessor<MultiplayerSessionReservation>
{
private readonly IMultiplayerSession multiplayerSession;
public MultiplayerSessionReservationProcessor(IMultiplayerSession multiplayerSession)
{
this.multiplayerSession = multiplayerSession;
}
public override void Process(MultiplayerSessionReservation packet)
{
multiplayerSession.ProcessReservationResponsePacket(packet);
}
}
}

View File

@@ -0,0 +1,30 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxModel.DataStructures.Util;
using NitroxModel.Packets;
namespace NitroxClient.Communication.Packets.Processors;
public class MutePlayerProcessor : ClientPacketProcessor<MutePlayer>
{
private readonly PlayerManager playerManager;
public delegate void PlayerMuted(ushort playerId, bool muted);
public PlayerMuted OnPlayerMuted;
public MutePlayerProcessor(PlayerManager playerManager)
{
this.playerManager = playerManager;
}
public override void Process(MutePlayer packet)
{
// We only need to notice if that's another player than local player
Optional<RemotePlayer> player = playerManager.Find(packet.PlayerId);
if (player.HasValue)
{
player.Value.PlayerContext.IsMuted = packet.Muted;
}
OnPlayerMuted(packet.PlayerId, packet.Muted);
}
}

View File

@@ -0,0 +1,30 @@
using NitroxClient.Communication.Abstract;
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.MonoBehaviours;
using NitroxClient.Unity.Helper;
using NitroxModel.Packets;
using UnityEngine;
namespace NitroxClient.Communication.Packets.Processors
{
public class OpenableStateChangedProcessor : ClientPacketProcessor<OpenableStateChanged>
{
private readonly IPacketSender packetSender;
public OpenableStateChangedProcessor(IPacketSender packetSender)
{
this.packetSender = packetSender;
}
public override void Process(OpenableStateChanged packet)
{
GameObject gameObject = NitroxEntity.RequireObjectFrom(packet.Id);
Openable openable = gameObject.RequireComponent<Openable>();
using (PacketSuppressor<OpenableStateChanged>.Suppress())
{
openable.PlayOpenAnimation(packet.IsOpen, packet.Duration);
}
}
}
}

View File

@@ -0,0 +1,23 @@
using NitroxClient.Communication.Abstract;
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxModel.Packets;
namespace NitroxClient.Communication.Packets.Processors;
public class PDAEncyclopediaEntryAddProcessor : ClientPacketProcessor<PDAEncyclopediaEntryAdd>
{
private readonly IPacketSender packetSender;
public PDAEncyclopediaEntryAddProcessor(IPacketSender packetSender)
{
this.packetSender = packetSender;
}
public override void Process(PDAEncyclopediaEntryAdd packet)
{
using (PacketSuppressor<PDAEncyclopediaEntryAdd>.Suppress())
{
PDAEncyclopedia.Add(packet.Key, packet.Verbose);
}
}
}

View File

@@ -0,0 +1,24 @@
using NitroxClient.Communication.Abstract;
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxModel.Packets;
namespace NitroxClient.Communication.Packets.Processors
{
public class PDALogEntryAddProcessor : ClientPacketProcessor<PDALogEntryAdd>
{
private readonly IPacketSender packetSender;
public PDALogEntryAddProcessor(IPacketSender packetSender)
{
this.packetSender = packetSender;
}
public override void Process(PDALogEntryAdd packet)
{
using (PacketSuppressor<PDALogEntryAdd>.Suppress())
{
PDALog.Add(packet.Key);
}
}
}
}

View File

@@ -0,0 +1,37 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxModel;
using NitroxModel.Packets;
using NitroxModel_Subnautica.DataStructures;
namespace NitroxClient.Communication.Packets.Processors;
public class PDAScanFinishedProcessor : ClientPacketProcessor<PDAScanFinished>
{
public override void Process(PDAScanFinished packet)
{
if (packet.Id != null)
{
StoryManager.ScanCompleted(packet.Id, packet.Destroy);
}
if (packet.WasAlreadyResearched)
{
return;
}
TechType packetTechType = packet.TechType.ToUnity();
if (packet.FullyResearched)
{
PDAScanner.partial.RemoveAllFast(packetTechType, static (item, techType) => item.techType == techType);
PDAScanner.complete.Add(packetTechType);
return;
}
if (PDAScanner.GetPartialEntryByKey(packetTechType, out PDAScanner.Entry entry))
{
entry.unlocked = packet.UnlockedAmount;
}
else
{
PDAScanner.Add(packetTechType, packet.UnlockedAmount);
}
}
}

View File

@@ -0,0 +1,25 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxModel.DataStructures.GameLogic;
using NitroxModel.Packets;
namespace NitroxClient.Communication.Packets.Processors;
public class PermsChangedProcessor : ClientPacketProcessor<PermsChanged>
{
private LocalPlayer localPlayer;
public delegate void PermissionsChanged(Perms perms);
public PermissionsChanged OnPermissionsChanged;
public PermsChangedProcessor(LocalPlayer localPlayer)
{
this.localPlayer = localPlayer;
}
public override void Process(PermsChanged packet)
{
localPlayer.Permissions = packet.NewPerms;
OnPermissionsChanged(packet.NewPerms);
}
}

View File

@@ -0,0 +1,26 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxModel;
using NitroxModel.Packets;
using Story;
namespace NitroxClient.Communication.Packets.Processors;
public class PlaySunbeamEventProcessor : ClientPacketProcessor<PlaySunbeamEvent>
{
public override void Process(PlaySunbeamEvent packet)
{
// TODO: Look into compound goals and OnUnlock goals to bring back the necessary ones
int beginIndex = PlaySunbeamEvent.SunbeamGoals.GetIndex(packet.EventKey);
if (beginIndex == -1)
{
Log.Error($"Couldn't find the corresponding sunbeam event in {nameof(PlaySunbeamEvent.SunbeamGoals)} for key {packet.EventKey}");
return;
}
for (int i = beginIndex; i < PlaySunbeamEvent.SunbeamGoals.Length; i++)
{
StoryGoalManager.main.completedGoals.Remove(PlaySunbeamEvent.SunbeamGoals[i]);
}
// Same execution as for StoryGoalCustomEventHandler commands
StoryGoalManager.main.OnGoalComplete(packet.EventKey);
}
}

View File

@@ -0,0 +1,46 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxClient.MonoBehaviours;
using NitroxClient.MonoBehaviours.CinematicController;
using NitroxModel.DataStructures.Util;
using NitroxModel.Helper;
using NitroxModel.Packets;
using UnityEngine;
namespace NitroxClient.Communication.Packets.Processors;
public class PlayerCinematicControllerCallProcessor : ClientPacketProcessor<PlayerCinematicControllerCall>
{
private readonly PlayerManager playerManager;
public PlayerCinematicControllerCallProcessor(PlayerManager playerManager)
{
this.playerManager = playerManager;
}
public override void Process(PlayerCinematicControllerCall packet)
{
if (!NitroxEntity.TryGetObjectFrom(packet.ControllerID, out GameObject entity))
{
return; // Entity can be not spawned yet bc async.
}
if (!entity.TryGetComponent(out MultiplayerCinematicReference reference))
{
Log.Warn($"Couldn't find {nameof(MultiplayerCinematicReference)} on {entity.name}:{packet.ControllerID}");
return;
}
Optional<RemotePlayer> opPlayer = playerManager.Find(packet.PlayerId);
Validate.IsPresent(opPlayer);
if (packet.StartPlaying)
{
reference.CallStartCinematicMode(packet.Key, packet.ControllerNameHash, opPlayer.Value);
}
else
{
reference.CallCinematicModeEnd(packet.Key, packet.ControllerNameHash, opPlayer.Value);
}
}
}

View File

@@ -0,0 +1,26 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxModel.Helper;
using NitroxModel.Packets;
namespace NitroxClient.Communication.Packets.Processors;
public class PlayerDeathProcessor : ClientPacketProcessor<PlayerDeathEvent>
{
private readonly PlayerManager playerManager;
public PlayerDeathProcessor(PlayerManager playerManager)
{
this.playerManager = playerManager;
}
public override void Process(PlayerDeathEvent playerDeath)
{
RemotePlayer player = Validate.IsPresent(playerManager.Find(playerDeath.PlayerId));
Log.Debug($"{player.PlayerName} died");
Log.InGame(Language.main.Get("Nitrox_PlayerDied").Replace("{PLAYER}", player.PlayerName));
player.PlayerDeathEvent.Trigger(player);
// TODO: Add any death related triggers (i.e. scoreboard updates, rewards, etc.)
}
}

View File

@@ -0,0 +1,119 @@
using System;
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxClient.MonoBehaviours;
using NitroxModel.DataStructures.Util;
using NitroxModel.Helper;
using NitroxModel.Packets;
using UnityEngine;
namespace NitroxClient.Communication.Packets.Processors;
public class PlayerHeldItemChangedProcessor : ClientPacketProcessor<PlayerHeldItemChanged>
{
private int defaultLayer;
private int viewModelLayer;
private readonly PlayerManager playerManager;
public PlayerHeldItemChangedProcessor(PlayerManager playerManager)
{
this.playerManager = playerManager;
if (NitroxEnvironment.IsNormal)
{
SetupLayers();
}
}
private void SetupLayers()
{
defaultLayer = LayerMask.NameToLayer("Default");
viewModelLayer = LayerMask.NameToLayer("Viewmodel");
}
public override void Process(PlayerHeldItemChanged packet)
{
Optional<RemotePlayer> opPlayer = playerManager.Find(packet.PlayerId);
Validate.IsPresent(opPlayer);
if (!NitroxEntity.TryGetObjectFrom(packet.ItemId, out GameObject item))
{
return; // Item can be not spawned yet bc async.
}
Pickupable pickupable = item.GetComponent<Pickupable>();
Validate.IsTrue(pickupable);
Validate.NotNull(pickupable.inventoryItem);
ItemsContainer inventory = opPlayer.Value.Inventory;
PlayerTool tool = item.GetComponent<PlayerTool>();
// Copied from QuickSlots
switch (packet.Type)
{
case PlayerHeldItemChanged.ChangeType.DRAW_AS_TOOL:
Validate.IsTrue(tool);
ModelPlug.PlugIntoSocket(tool, opPlayer.Value.ItemAttachPoint);
Utils.SetLayerRecursively(item, viewModelLayer);
foreach (Animator componentsInChild in tool.GetComponentsInChildren<Animator>())
{
componentsInChild.cullingMode = AnimatorCullingMode.AlwaysAnimate;
}
if (tool.mainCollider)
{
tool.mainCollider.enabled = false;
}
tool.GetComponent<Rigidbody>().isKinematic = true;
item.SetActive(true);
tool.SetHandIKTargetsEnabled(true);
SafeAnimator.SetBool(opPlayer.Value.ArmsController.GetComponent<Animator>(), $"holding_{tool.animToolName}", true);
opPlayer.Value.AnimationController["using_tool_first"] = packet.IsFirstTime == null;
if (item.TryGetComponent(out FPModel fpModelDraw)) //FPModel needs to be updated
{
fpModelDraw.OnEquip(null, null);
}
break;
case PlayerHeldItemChanged.ChangeType.HOLSTER_AS_TOOL:
Validate.IsTrue(tool);
item.SetActive(false);
Utils.SetLayerRecursively(item, defaultLayer);
if (tool.mainCollider)
{
tool.mainCollider.enabled = true;
}
tool.GetComponent<Rigidbody>().isKinematic = false;
pickupable.inventoryItem.item.Reparent(inventory.tr);
foreach (Animator componentsInChild in tool.GetComponentsInChildren<Animator>())
{
componentsInChild.cullingMode = AnimatorCullingMode.CullUpdateTransforms;
}
SafeAnimator.SetBool(opPlayer.Value.ArmsController.GetComponent<Animator>(), $"holding_{tool.animToolName}", false);
opPlayer.Value.AnimationController["using_tool_first"] = false;
if (item.TryGetComponent(out FPModel fpModelHolster)) //FPModel needs to be updated
{
fpModelHolster.OnUnequip(null, null);
}
break;
case PlayerHeldItemChanged.ChangeType.DRAW_AS_ITEM:
pickupable.inventoryItem.item.Reparent(opPlayer.Value.ItemAttachPoint);
pickupable.inventoryItem.item.SetVisible(true);
Utils.SetLayerRecursively(pickupable.inventoryItem.item.gameObject, viewModelLayer);
break;
case PlayerHeldItemChanged.ChangeType.HOLSTER_AS_ITEM:
pickupable.inventoryItem.item.Reparent(inventory.tr);
pickupable.inventoryItem.item.SetVisible(false);
Utils.SetLayerRecursively(pickupable.inventoryItem.item.gameObject, defaultLayer);
break;
default:
throw new ArgumentOutOfRangeException(nameof(packet.Type));
}
}
}

View File

@@ -0,0 +1,24 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxModel.Packets;
using NitroxModel_Subnautica.DataStructures;
namespace NitroxClient.Communication.Packets.Processors;
public class PlayerInCyclopsMovementProcessor : ClientPacketProcessor<PlayerInCyclopsMovement>
{
private readonly PlayerManager remotePlayerManager;
public PlayerInCyclopsMovementProcessor(PlayerManager remotePlayerManager)
{
this.remotePlayerManager = remotePlayerManager;
}
public override void Process(PlayerInCyclopsMovement movement)
{
if (remotePlayerManager.TryFind(movement.PlayerId, out RemotePlayer remotePlayer) && remotePlayer.Pawn != null)
{
remotePlayer.UpdatePositionInCyclops(movement.LocalPosition.ToUnity(), movement.LocalRotation.ToUnity());
}
}
}

View File

@@ -0,0 +1,33 @@
using System.Collections;
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxModel.Packets;
using UWE;
namespace NitroxClient.Communication.Packets.Processors;
public class PlayerJoinedMultiplayerSessionProcessor : ClientPacketProcessor<PlayerJoinedMultiplayerSession>
{
private readonly PlayerManager playerManager;
private readonly Entities entities;
public PlayerJoinedMultiplayerSessionProcessor(PlayerManager playerManager, Entities entities)
{
this.playerManager = playerManager;
this.entities = entities;
}
public override void Process(PlayerJoinedMultiplayerSession packet)
{
CoroutineHost.StartCoroutine(SpawnRemotePlayer(packet));
}
private IEnumerator SpawnRemotePlayer(PlayerJoinedMultiplayerSession packet)
{
playerManager.Create(packet.PlayerContext);
yield return entities.SpawnEntityAsync(packet.PlayerWorldEntity, true, true);
Log.Info($"{packet.PlayerContext.PlayerName} joined the game");
Log.InGame(Language.main.Get("Nitrox_PlayerJoined").Replace("{PLAYER}", packet.PlayerContext.PlayerName));
}
}

View File

@@ -0,0 +1,30 @@
using NitroxClient.Communication.Abstract;
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.MonoBehaviours.Gui.Modals;
using NitroxModel.Packets;
namespace NitroxClient.Communication.Packets.Processors
{
public class UserKickedProcessor : ClientPacketProcessor<PlayerKicked>
{
private readonly IMultiplayerSession session;
public UserKickedProcessor(IMultiplayerSession session)
{
this.session = session;
}
public override void Process(PlayerKicked packet)
{
string message = Language.main.Get("Nitrox_PlayerKicked");
if (!string.IsNullOrEmpty(packet.Reason))
{
message += $"\n {packet.Reason}";
}
session.Disconnect();
Modal.Get<KickedModal>()?.Show(message);
}
}
}

View File

@@ -0,0 +1,27 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxModel.Packets;
using NitroxModel_Subnautica.DataStructures;
namespace NitroxClient.Communication.Packets.Processors;
public class PlayerMovementProcessor : ClientPacketProcessor<PlayerMovement>
{
private readonly PlayerManager remotePlayerManager;
public PlayerMovementProcessor(PlayerManager remotePlayerManager)
{
this.remotePlayerManager = remotePlayerManager;
}
public override void Process(PlayerMovement movement)
{
if (remotePlayerManager.TryFind(movement.PlayerId, out RemotePlayer remotePlayer))
{
remotePlayer.UpdatePosition(movement.Position.ToUnity(),
movement.Velocity.ToUnity(),
movement.BodyRotation.ToUnity(),
movement.AimingRotation.ToUnity());
}
}
}

View File

@@ -0,0 +1,29 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxClient.MonoBehaviours.Gui.HUD;
using NitroxModel.Packets;
namespace NitroxClient.Communication.Packets.Processors;
public class PlayerStatsProcessor : ClientPacketProcessor<PlayerStats>
{
private readonly PlayerManager playerManager;
public PlayerStatsProcessor(PlayerManager playerManager)
{
this.playerManager = playerManager;
}
public override void Process(PlayerStats playerStats)
{
if (playerManager.TryFind(playerStats.PlayerId, out RemotePlayer remotePlayer))
{
RemotePlayerVitals vitals = remotePlayer.vitals;
vitals.SetOxygen(playerStats.Oxygen, playerStats.MaxOxygen);
vitals.SetHealth(playerStats.Health);
vitals.SetFood(playerStats.Food);
vitals.SetWater(playerStats.Water);
remotePlayer.UpdateHealthAndInfection(playerStats.Health, playerStats.InfectionAmount);
}
}
}

View File

@@ -0,0 +1,43 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.MonoBehaviours;
using NitroxClient.Unity.Helper;
using NitroxModel.Packets;
using NitroxModel_Subnautica.DataStructures;
using UWE;
using Terrain = NitroxClient.GameLogic.Terrain;
namespace NitroxClient.Communication.Packets.Processors;
public class PlayerTeleportedProcessor : ClientPacketProcessor<PlayerTeleported>
{
public override void Process(PlayerTeleported packet)
{
Player.main.OnPlayerPositionCheat();
Vehicle currentVehicle = Player.main.currentMountedVehicle;
if (currentVehicle)
{
currentVehicle.TeleportVehicle(packet.DestinationTo.ToUnity(), currentVehicle.transform.rotation);
Player.main.WaitForTeleportation();
return;
}
Player.main.SetPosition(packet.DestinationTo.ToUnity());
if (packet.SubRootID.HasValue && NitroxEntity.TryGetComponentFrom(packet.SubRootID.Value, out SubRoot subRoot))
{
Player.main.SetCurrentSub(subRoot, true);
return;
}
// Freeze the player while it's loading its new position
Player.main.cinematicModeActive = true;
Player.main.WaitForTeleportation();
CoroutineHost.StartCoroutine(Terrain.WaitForWorldLoad().OnYieldError(e =>
{
Player.main.cinematicModeActive = false;
Log.Warn($"Something wrong happened while waiting for the terrain to load.\n{e}");
}));
}
}

View File

@@ -0,0 +1,15 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxModel.Packets;
namespace NitroxClient.Communication.Packets.Processors;
public class PvPAttackProcessor : ClientPacketProcessor<PvPAttack>
{
public override void Process(PvPAttack packet)
{
if (Player.main && Player.main.liveMixin)
{
Player.main.liveMixin.TakeDamage(packet.Damage);
}
}
}

View File

@@ -0,0 +1,14 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxModel.Packets;
using Story;
namespace NitroxClient.Communication.Packets.Processors
{
public class RadioPlayPendingMessageProcessor : ClientPacketProcessor<RadioPlayPendingMessage>
{
public override void Process(RadioPlayPendingMessage packet)
{
StoryGoalManager.main.ExecutePendingRadioMessage();
}
}
}

View File

@@ -0,0 +1,13 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxModel.Packets;
namespace NitroxClient.Communication.Packets.Processors;
public class RangedAttackLastTargetUpdateProcessor : ClientPacketProcessor<RangedAttackLastTargetUpdate>
{
public override void Process(RangedAttackLastTargetUpdate packet)
{
AI.RangedAttackLastTargetUpdate(packet.CreatureId, packet.TargetId, packet.AttackTypeIndex, packet.State);
}
}

View File

@@ -0,0 +1,71 @@
using System.Collections;
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxClient.MonoBehaviours;
using NitroxModel.DataStructures;
using NitroxModel.Packets;
using NitroxModel_Subnautica.DataStructures;
using UWE;
namespace NitroxClient.Communication.Packets.Processors;
public class RemoveCreatureCorpseProcessor : ClientPacketProcessor<RemoveCreatureCorpse>
{
private readonly Entities entities;
private readonly LiveMixinManager liveMixinManager;
private readonly SimulationOwnership simulationOwnership;
public RemoveCreatureCorpseProcessor(Entities entities, LiveMixinManager liveMixinManager, SimulationOwnership simulationOwnership)
{
this.entities = entities;
this.liveMixinManager = liveMixinManager;
this.simulationOwnership = simulationOwnership;
}
public override void Process(RemoveCreatureCorpse packet)
{
entities.RemoveEntity(packet.CreatureId);
if (!NitroxEntity.TryGetComponentFrom(packet.CreatureId, out CreatureDeath creatureDeath))
{
entities.MarkForDeletion(packet.CreatureId);
Log.Warn($"[{nameof(RemoveCreatureCorpseProcessor)}] Could not find entity with id: {packet.CreatureId} to remove corpse from.");
return;
}
creatureDeath.transform.localPosition = packet.DeathPosition.ToUnity();
creatureDeath.transform.localRotation = packet.DeathRotation.ToUnity();
CoroutineHost.StartCoroutine(SimplerOnKillAsync(creatureDeath, packet.CreatureId));
}
/// <summary>
/// Calls only some parts from <see cref="CreatureDeath.OnKillAsync"/> to avoid sending packets from it
/// or already synced behaviour (like spawning another respawner from the remote clients)
/// </summary>
public IEnumerator SimplerOnKillAsync(CreatureDeath creatureDeath, NitroxId creatureId)
{
// Ensure we don't broadcast anything from this kill event
simulationOwnership.StopSimulatingEntity(creatureId);
// Remove the position broadcasting stuff from it
EntityPositionBroadcaster.RemoveEntityMovementControl(creatureDeath.gameObject, creatureId);
// Receiving this packet means the creature is dead
liveMixinManager.SyncRemoteHealth(creatureDeath.liveMixin, 0);
// To avoid SpawnRespawner to be called
creatureDeath.respawn = false;
creatureDeath.hasSpawnedRespawner = true;
// To avoid the cooked data section
bool lastDamageWasHeat = creatureDeath.lastDamageWasHeat;
creatureDeath.lastDamageWasHeat = false;
using (PacketSuppressor<EntityMetadataUpdate>.Suppress())
{
yield return creatureDeath.OnKillAsync();
}
// Restore the field in case it was useful
creatureDeath.lastDamageWasHeat = lastDamageWasHeat;
}
}

View File

@@ -0,0 +1,20 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxModel_Subnautica.Packets;
namespace NitroxClient.Communication.Packets.Processors;
public class RocketLaunchProcessor : ClientPacketProcessor<RocketLaunch>
{
private readonly Rockets rockets;
public RocketLaunchProcessor(Rockets rockets)
{
this.rockets = rockets;
}
public override void Process(RocketLaunch rocketLaunch)
{
rockets.RocketLaunch(rocketLaunch.RocketId);
}
}

View File

@@ -0,0 +1,35 @@
using System.Linq;
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxModel.Packets;
using Story;
namespace NitroxClient.Communication.Packets.Processors;
public class ScheduleProcessor : ClientPacketProcessor<Schedule>
{
public override void Process(Schedule schedulePacket)
{
ScheduledGoal goal = new()
{
goalKey = schedulePacket.Key,
goalType = (Story.GoalType)schedulePacket.Type,
timeExecute = schedulePacket.TimeExecute
};
if (ShouldSchedule(goal))
{
StoryGoalScheduler.main.schedule.Add(goal);
}
Log.Debug($"Processed a Schedule packet [Key: {goal.goalKey}, Type: {goal.goalType}, TimeExecute: {goal.timeExecute}]");
}
private bool ShouldSchedule(ScheduledGoal goal)
{
return goal.timeExecute >= DayNightCycle.main.timePassedAsDouble && !IsAlreadyKnown(goal.goalKey);
}
private bool IsAlreadyKnown(string goalKey)
{
return StoryGoalScheduler.main.schedule.Any(g => g.goalKey == goalKey) || // Scheduled
StoryGoalManager.main.completedGoals.Contains(goalKey); // Completed
}
}

View File

@@ -0,0 +1,55 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic.PlayerLogic;
using NitroxClient.MonoBehaviours;
using NitroxModel.Packets;
using UnityEngine;
namespace NitroxClient.Communication.Packets.Processors;
public class SeaDragonAttackTargetProcessor : ClientPacketProcessor<SeaDragonAttackTarget>
{
public override void Process(SeaDragonAttackTarget packet)
{
if (!NitroxEntity.TryGetComponentFrom(packet.SeaDragonId, out SeaDragonMeleeAttack seaDragonMeleeAttack) ||
!NitroxEntity.TryGetObjectFrom(packet.TargetId, out GameObject target))
{
return;
}
seaDragonMeleeAttack.seaDragon.Aggression.Value = packet.Aggression;
if (target.GetComponent<SubControl>())
{
// SeaDragonMeleeAttack.OnTouchFront's useful part about Cyclops attack
seaDragonMeleeAttack.animator.SetTrigger("shove");
seaDragonMeleeAttack.SendMessage("OnMeleeAttack", target, SendMessageOptions.DontRequireReceiver);
seaDragonMeleeAttack.timeLastBite = Time.time;
return;
}
// SeaDragonMeleeAttack.OnTouchFront's useful part about local player attack
Collider collider;
if (target.TryGetComponent(out RemotePlayerIdentifier remotePlayerIdentifier))
{
collider = remotePlayerIdentifier.RemotePlayer.Collider;
}
else if (target.GetComponent<Player>())
{
collider = Player.mainCollider;
}
else
{
return;
}
seaDragonMeleeAttack.timeLastBite = Time.time;
if (seaDragonMeleeAttack.attackSound)
{
using (PacketSuppressor<FMODAssetPacket>.Suppress())
{
Utils.PlayEnvSound(seaDragonMeleeAttack.attackSound, collider.transform.position, 20f);
}
}
seaDragonMeleeAttack.OnTouch(collider);
}
}

View File

@@ -0,0 +1,23 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.MonoBehaviours;
using NitroxModel.Packets;
namespace NitroxClient.Communication.Packets.Processors;
public class SeaDragonGrabExosuitProcessor : ClientPacketProcessor<SeaDragonGrabExosuit>
{
public override void Process(SeaDragonGrabExosuit packet)
{
if (!NitroxEntity.TryGetComponentFrom(packet.SeaDragonId, out SeaDragon seaDragon) ||
!NitroxEntity.TryGetComponentFrom(packet.TargetId, out Exosuit exosuit))
{
return;
}
using (PacketSuppressor<SeaDragonGrabExosuit>.Suppress())
{
seaDragon.GrabExosuit(exosuit);
seaDragon.CancelInvoke(nameof(SeaDragon.DamageExosuit));
}
}
}

View File

@@ -0,0 +1,24 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.MonoBehaviours;
using NitroxModel.Packets;
using UnityEngine;
namespace NitroxClient.Communication.Packets.Processors;
public class SeaDragonSwatAttackProcessor : ClientPacketProcessor<SeaDragonSwatAttack>
{
public override void Process(SeaDragonSwatAttack packet)
{
if (!NitroxEntity.TryGetComponentFrom(packet.SeaDragonId, out SeaDragonMeleeAttack seaDragonMeleeAttack) ||
!NitroxEntity.TryGetObjectFrom(packet.TargetId, out GameObject targetObject))
{
return;
}
using (PacketSuppressor<SeaDragonSwatAttack>.Suppress())
{
seaDragonMeleeAttack.seaDragon.Aggression.Value = packet.Aggression;
seaDragonMeleeAttack.SwatAttack(targetObject, packet.IsRightHand);
}
}
}

View File

@@ -0,0 +1,17 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.MonoBehaviours;
using NitroxModel.Packets;
using UnityEngine;
namespace NitroxClient.Communication.Packets.Processors;
public class SeaTreaderChunkPickedUpProcessor : ClientPacketProcessor<SeaTreaderChunkPickedUp>
{
public override void Process(SeaTreaderChunkPickedUp packet)
{
if (NitroxEntity.TryGetComponentFrom(packet.ChunkId, out SinkingGroundChunk sinkingGroundChunk))
{
GameObject.Destroy(sinkingGroundChunk.gameObject);
}
}
}

View File

@@ -0,0 +1,22 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.MonoBehaviours;
using NitroxClient.Unity.Helper;
using NitroxModel.Packets;
using NitroxModel_Subnautica.DataStructures;
using UnityEngine;
namespace NitroxClient.Communication.Packets.Processors;
public class SeaTreaderSpawnedChunkProcessor : ClientPacketProcessor<SeaTreaderSpawnedChunk>
{
public override void Process(SeaTreaderSpawnedChunk packet)
{
if (NitroxEntity.TryGetComponentFrom(packet.CreatureId, out SeaTreader seaTreader) &&
seaTreader.TryGetComponentInChildren(out SeaTreaderSounds seaTreaderSounds))
{
GameObject chunkObject = GameObjectHelper.InstantiateWithId(seaTreaderSounds.stepChunkPrefab, packet.ChunkId);
chunkObject.transform.position = packet.Position.ToUnity();
chunkObject.transform.rotation = packet.Rotation.ToUnity();
}
}
}

View File

@@ -0,0 +1,42 @@
using NitroxClient.Communication.Abstract;
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.MonoBehaviours;
using NitroxModel.Packets;
using NitroxModel_Subnautica.DataStructures;
using UnityEngine;
namespace NitroxClient.Communication.Packets.Processors
{
public class SeamothModuleActionProcessor : ClientPacketProcessor<SeamothModulesAction>
{
private readonly IPacketSender packetSender;
public SeamothModuleActionProcessor(IPacketSender packetSender)
{
this.packetSender = packetSender;
}
public override void Process(SeamothModulesAction packet)
{
using (PacketSuppressor<SeamothModulesAction>.Suppress())
{
GameObject _gameObject = NitroxEntity.RequireObjectFrom(packet.Id);
SeaMoth seamoth = _gameObject.GetComponent<SeaMoth>();
if (seamoth != null)
{
TechType techType = packet.TechType.ToUnity();
if (techType == TechType.SeamothElectricalDefense)
{
float[] chargearray = seamoth.quickSlotCharge;
float charge = chargearray[packet.SlotID];
float slotCharge = seamoth.GetSlotCharge(packet.SlotID);
GameObject gameObject = global::Utils.SpawnZeroedAt(seamoth.seamothElectricalDefensePrefab, seamoth.transform, false);
ElectricalDefense component = gameObject.GetComponent<ElectricalDefense>();
component.charge = charge;
component.chargeScalar = slotCharge;
}
}
}
}
}
}

View File

@@ -0,0 +1,23 @@
using NitroxClient.Communication.Abstract;
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.MonoBehaviours.Gui.Modals;
using NitroxModel.Packets;
namespace NitroxClient.Communication.Packets.Processors;
public class ServerStoppedProcessor : ClientPacketProcessor<ServerStopped>
{
private readonly IClient client;
public ServerStoppedProcessor(IClient client)
{
this.client = client;
}
public override void Process(ServerStopped packet)
{
// We can send the stop instruction right now instead of waiting for the timeout
client.Stop();
Modal.Get<ServerStoppedModal>()?.Show();
}
}

View File

@@ -0,0 +1,42 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxClient.GameLogic.PlayerLogic;
using NitroxModel.Packets;
namespace NitroxClient.Communication.Packets.Processors;
public class SetIntroCinematicModeProcessor : ClientPacketProcessor<SetIntroCinematicMode>
{
private readonly PlayerManager playerManager;
private readonly PlayerCinematics playerCinematics;
private readonly LocalPlayer localPlayer;
public SetIntroCinematicModeProcessor(PlayerManager playerManager, PlayerCinematics playerCinematics, LocalPlayer localPlayer)
{
this.playerManager = playerManager;
this.playerCinematics = playerCinematics;
this.localPlayer = localPlayer;
}
public override void Process(SetIntroCinematicMode packet)
{
if (localPlayer.PlayerId == packet.PlayerId)
{
if (packet.PartnerId.HasValue)
{
playerCinematics.IntroCinematicPartnerId = packet.PartnerId;
}
localPlayer.IntroCinematicMode = packet.Mode;
return;
}
if (playerManager.TryFind(packet.PlayerId, out RemotePlayer remotePlayer))
{
remotePlayer.PlayerContext.IntroCinematicMode = packet.Mode;
return;
}
Log.Debug($"SetIntroCinematicMode couldn't find Player with id {packet.PlayerId}. This is normal if player has not yet officially joined.");
}
}

View File

@@ -0,0 +1,25 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxModel.DataStructures;
using NitroxModel.Packets;
namespace NitroxClient.Communication.Packets.Processors;
public class SimulationOwnershipChangeProcessor : ClientPacketProcessor<SimulationOwnershipChange>
{
private readonly SimulationOwnership simulationOwnershipManager;
public SimulationOwnershipChangeProcessor(SimulationOwnership simulationOwnershipManager)
{
this.simulationOwnershipManager = simulationOwnershipManager;
}
public override void Process(SimulationOwnershipChange simulationOwnershipChange)
{
foreach (SimulatedEntity simulatedEntity in simulationOwnershipChange.Entities)
{
simulationOwnershipManager.TreatSimulatedEntity(simulatedEntity);
}
}
}

View File

@@ -0,0 +1,53 @@
using NitroxClient.Communication.Abstract;
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxClient.MonoBehaviours;
using NitroxModel.DataStructures;
using NitroxModel.DataStructures.Util;
using NitroxModel.Packets;
using UnityEngine;
namespace NitroxClient.Communication.Packets.Processors
{
public class SimulationOwnershipResponseProcessor : ClientPacketProcessor<SimulationOwnershipResponse>
{
private readonly IMultiplayerSession multiplayerSession;
private readonly SimulationOwnership simulationOwnershipManager;
public SimulationOwnershipResponseProcessor(IMultiplayerSession multiplayerSession, SimulationOwnership simulationOwnershipManager)
{
this.multiplayerSession = multiplayerSession;
this.simulationOwnershipManager = simulationOwnershipManager;
}
public override void Process(SimulationOwnershipResponse response)
{
/*
* For now, we expect the simulation lock callback to setup entity broadcasting as
* most items that are requesting an exclusive lock have custom broadcast code, ex:
* vehicles like the cyclops. However, we may one day want to add a watcher here
* to ensure broadcast one day, ex:
*
* EntityPositionBroadcaster.WatchEntity(simulatedEntity.Id, gameObject.Value);
*
*/
simulationOwnershipManager.ReceivedSimulationLockResponse(response.Id, response.LockAcquired, response.LockType);
if (response.LockAcquired)
{
RemoveRemoteController(response.Id);
}
}
private void RemoveRemoteController(NitroxId id)
{
Optional<GameObject> gameObject = NitroxEntity.GetObjectFrom(id);
if (gameObject.HasValue)
{
RemotelyControlled remotelyControlled = gameObject.Value.GetComponent<RemotelyControlled>();
Object.Destroy(remotelyControlled);
}
}
}
}

View File

@@ -0,0 +1,40 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxModel.DataStructures;
using NitroxModel.Packets;
namespace NitroxClient.Communication.Packets.Processors;
public class SpawnEntitiesProcessor : ClientPacketProcessor<SpawnEntities>
{
private readonly Entities entities;
private readonly SimulationOwnership simulationOwnership;
public SpawnEntitiesProcessor(Entities entities, SimulationOwnership simulationOwnership)
{
this.entities = entities;
this.simulationOwnership = simulationOwnership;
}
public override void Process(SpawnEntities packet)
{
if (packet.ForceRespawn)
{
entities.CleanupExistingEntities(packet.Entities);
}
if (packet.Entities.Count > 0)
{
if (packet.Simulations != null)
{
foreach (SimulatedEntity simulatedEntity in packet.Simulations)
{
simulationOwnership.RegisterNewerSimulation(simulatedEntity.Id, simulatedEntity);
}
}
// Packet processing is done in the main thread so there's no issue calling this
entities.EnqueueEntitiesToSpawn(packet.Entities);
}
}
}

View File

@@ -0,0 +1,21 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxModel.Packets;
using NitroxModel_Subnautica.DataStructures;
namespace NitroxClient.Communication.Packets.Processors;
public class StasisSphereHitProcessor : ClientPacketProcessor<StasisSphereHit>
{
private readonly BulletManager bulletManager;
public StasisSphereHitProcessor(BulletManager bulletManager)
{
this.bulletManager = bulletManager;
}
public override void Process(StasisSphereHit packet)
{
bulletManager.StasisSphereHit(packet.PlayerId, packet.Position.ToUnity(), packet.Rotation.ToUnity(), packet.ChargeNormalized, packet.Consumption);
}
}

View File

@@ -0,0 +1,21 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxModel.Packets;
using NitroxModel_Subnautica.DataStructures;
namespace NitroxClient.Communication.Packets.Processors;
public class StasisSphereShotProcessor : ClientPacketProcessor<StasisSphereShot>
{
private readonly BulletManager bulletManager;
public StasisSphereShotProcessor(BulletManager bulletManager)
{
this.bulletManager = bulletManager;
}
public override void Process(StasisSphereShot packet)
{
bulletManager.ShootStasisSphere(packet.PlayerId, packet.Position.ToUnity(), packet.Rotation.ToUnity(), packet.Speed, packet.LifeTime, packet.ChargeNormalized);
}
}

View File

@@ -0,0 +1,31 @@
using NitroxClient.Communication.Abstract;
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxModel;
using NitroxModel.Packets;
using NitroxModel_Subnautica.DataStructures;
using Story;
namespace NitroxClient.Communication.Packets.Processors;
public class StoryGoalExecutedClientProcessor : ClientPacketProcessor<StoryGoalExecuted>
{
private readonly IPacketSender packetSender;
public StoryGoalExecutedClientProcessor(IPacketSender packetSender)
{
this.packetSender = packetSender;
}
public override void Process(StoryGoalExecuted packet)
{
StoryGoalScheduler.main.schedule.RemoveAllFast(packet.Key, static (goal, packetGoalKey) => goal.goalKey == packetGoalKey);
using (PacketSuppressor<StoryGoalExecuted>.Suppress())
using (PacketSuppressor<PDALogEntryAdd>.Suppress())
using (PacketSuppressor<KnownTechEntryAdd>.Suppress()) // StoryGoalManager => OnGoalUnlockTracker => UnlockBlueprintData => KnownTech.Add
using (PacketSuppressor<PDAEncyclopediaEntryAdd>.Suppress())
{
StoryGoal.Execute(packet.Key, packet.Type.ToUnity());
}
}
}

View File

@@ -0,0 +1,37 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxClient.MonoBehaviours;
using NitroxModel.DataStructures.Util;
using NitroxModel.Packets;
using UnityEngine;
namespace NitroxClient.Communication.Packets.Processors
{
public class SubRootChangedProcessor : ClientPacketProcessor<SubRootChanged>
{
private readonly PlayerManager remotePlayerManager;
public SubRootChangedProcessor(PlayerManager remotePlayerManager)
{
this.remotePlayerManager = remotePlayerManager;
}
public override void Process(SubRootChanged packet)
{
Optional<RemotePlayer> remotePlayer = remotePlayerManager.Find(packet.PlayerId);
if (remotePlayer.HasValue)
{
SubRoot subRoot = null;
if (packet.SubRootId.HasValue)
{
GameObject sub = NitroxEntity.RequireObjectFrom(packet.SubRootId.Value);
subRoot = sub.GetComponent<SubRoot>();
}
remotePlayer.Value.SetSubRoot(subRoot);
}
}
}
}

View File

@@ -0,0 +1,20 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxModel.Packets;
namespace NitroxClient.Communication.Packets.Processors;
public class TimeChangeProcessor : ClientPacketProcessor<TimeChange>
{
private readonly TimeManager timeManager;
public TimeChangeProcessor(TimeManager timeManager)
{
this.timeManager = timeManager;
}
public override void Process(TimeChange timeChangePacket)
{
timeManager.ProcessUpdate(timeChangePacket);
}
}

View File

@@ -0,0 +1,54 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic.FMOD;
using NitroxClient.MonoBehaviours;
using NitroxClient.Unity.Helper;
using NitroxModel.GameLogic.FMOD;
using UnityEngine;
namespace NitroxClient.Communication.Packets.Processors;
public class ToggleLightsProcessor : ClientPacketProcessor<NitroxModel.Packets.ToggleLights>
{
private readonly FMODWhitelist fmodWhitelist;
public ToggleLightsProcessor(FMODWhitelist fmodWhitelist)
{
this.fmodWhitelist = fmodWhitelist;
}
public override void Process(NitroxModel.Packets.ToggleLights packet)
{
GameObject gameObject = NitroxEntity.RequireObjectFrom(packet.Id);
ToggleLights toggleLights = gameObject.RequireComponent<ToggleLights>();
if (packet.IsOn == toggleLights.GetLightsActive())
{
return;
}
using (PacketSuppressor<NitroxModel.Packets.ToggleLights>.Suppress())
using (FMODSystem.SuppressSendingSounds())
{
using (FMODSystem.SuppressSubnauticaSounds())
{
toggleLights.SetLightsActive(packet.IsOn);
}
FMODAsset soundAsset;
if (packet.IsOn)
{
soundAsset = toggleLights.lightsOnSound ? toggleLights.lightsOnSound.asset : toggleLights.onSound;
}
else
{
soundAsset = toggleLights.lightsOffSound ? toggleLights.lightsOffSound.asset : toggleLights.offSound;
}
if (soundAsset && fmodWhitelist.TryGetSoundData(soundAsset.path, out SoundData soundData))
{
FMODEmitterController.PlayEventOneShot(soundAsset, soundData.Radius, toggleLights.transform.position);
}
}
}
}

View File

@@ -0,0 +1,21 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxModel.Packets;
using NitroxModel_Subnautica.DataStructures;
namespace NitroxClient.Communication.Packets.Processors;
public class TorpedoHitProcessor : ClientPacketProcessor<TorpedoHit>
{
private readonly BulletManager bulletManager;
public TorpedoHitProcessor(BulletManager bulletManager)
{
this.bulletManager = bulletManager;
}
public override void Process(TorpedoHit packet)
{
bulletManager.TorpedoHit(packet.BulletId, packet.Position.ToUnity(), packet.Rotation.ToUnity());
}
}

View File

@@ -0,0 +1,21 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxModel.Packets;
using NitroxModel_Subnautica.DataStructures;
namespace NitroxClient.Communication.Packets.Processors;
public class TorpedoShotProcessor : ClientPacketProcessor<TorpedoShot>
{
private readonly BulletManager bulletManager;
public TorpedoShotProcessor(BulletManager bulletManager)
{
this.bulletManager = bulletManager;
}
public override void Process(TorpedoShot packet)
{
bulletManager.ShootSeamothTorpedo(packet.BulletId, packet.TechType.ToUnity(), packet.Position.ToUnity(), packet.Rotation.ToUnity(), packet.Speed, packet.LifeTime);
}
}

View File

@@ -0,0 +1,21 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxModel.Packets;
using NitroxModel_Subnautica.DataStructures;
namespace NitroxClient.Communication.Packets.Processors;
public class TorpedoTargetAcquiredProcessor : ClientPacketProcessor<TorpedoTargetAcquired>
{
private readonly BulletManager bulletManager;
public TorpedoTargetAcquiredProcessor(BulletManager bulletManager)
{
this.bulletManager = bulletManager;
}
public override void Process(TorpedoTargetAcquired packet)
{
bulletManager.TorpedoTargetAcquired(packet.BulletId, packet.TargetId, packet.Position.ToUnity(), packet.Rotation.ToUnity());
}
}

View File

@@ -0,0 +1,84 @@
using System.Collections;
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxClient.MonoBehaviours;
using NitroxClient.MonoBehaviours.Vehicles;
using NitroxClient.Unity.Helper;
using NitroxModel.DataStructures;
using NitroxModel.Packets;
using UnityEngine;
namespace NitroxClient.Communication.Packets.Processors;
public class VehicleDockingProcessor : ClientPacketProcessor<VehicleDocking>
{
private readonly Vehicles vehicles;
public VehicleDockingProcessor(Vehicles vehicles)
{
this.vehicles = vehicles;
}
public override void Process(VehicleDocking packet)
{
if (!NitroxEntity.TryGetComponentFrom(packet.VehicleId, out Vehicle vehicle))
{
Log.Error($"[{nameof(VehicleDockingProcessor)}] could not find Vehicle component on {packet.VehicleId}");
return;
}
if (!NitroxEntity.TryGetComponentFrom(packet.DockId, out VehicleDockingBay dockingBay))
{
Log.Error($"[{nameof(VehicleDockingProcessor)}] could not find VehicleDockingBay component on {packet.DockId}");
return;
}
if (vehicle.TryGetComponent(out VehicleMovementReplicator vehicleMovementReplicator))
{
vehicleMovementReplicator.enabled = false;
Log.Debug($"[{nameof(VehicleDockingProcessor)}] Disabled VehicleMovementReplicator on {packet.VehicleId}");
}
vehicle.StartCoroutine(DelayAnimationAndDisablePiloting(vehicle, vehicleMovementReplicator, dockingBay, packet.VehicleId, packet.PlayerId));
}
private IEnumerator DelayAnimationAndDisablePiloting(Vehicle vehicle, VehicleMovementReplicator vehicleMovementReplicator, VehicleDockingBay vehicleDockingBay, NitroxId vehicleId, ushort playerId)
{
// Consider the vehicle movement latency (we don't teleport the vehicle to the docking position)
if (vehicleMovementReplicator)
{
// NB: We don't have a lifetime ahead of us
float waitTime = Mathf.Clamp(vehicleMovementReplicator.maxAllowedLatency, 0f, 2f);
yield return new WaitForSeconds(waitTime);
}
else
{
yield return Yielders.WaitFor1Second;
}
// DockVehicle sets the rigid body kinematic of the vehicle to true, we don't want that behaviour
// Therefore disable kinematic (again) to remove the bouncing behavior
DockRemoteVehicle(vehicleDockingBay, vehicle);
vehicle.useRigidbody.isKinematic = false;
yield return Yielders.WaitFor2Seconds;
vehicles.SetOnPilotMode(vehicleId, playerId, false);
}
/// Copy of <see cref="VehicleDockingBay.DockVehicle"/> without the player centric bits
private void DockRemoteVehicle(VehicleDockingBay bay, Vehicle vehicle)
{
bay.dockedVehicle = vehicle;
LargeWorldStreamer.main.cellManager.UnregisterEntity(bay.dockedVehicle.gameObject);
bay.dockedVehicle.transform.parent = bay.GetSubRoot().transform;
vehicle.docked = true;
bay.vehicle_docked_param = true;
SkyEnvironmentChanged.Broadcast(vehicle.gameObject, bay.subRoot);
bay.GetSubRoot().BroadcastMessage("UnlockDoors", SendMessageOptions.DontRequireReceiver);
// We are only actually adding the health if we have a lock on the vehicle so we're fine to keep this routine going on.
// If vehicle ownership changes then it'll still be fine because the verification will still be on the vehicle ownership.
bay.CancelInvoke(nameof(VehicleDockingBay.RepairVehicle));
bay.InvokeRepeating(nameof(VehicleDockingBay.RepairVehicle), 0.0f, 5f);
}
}

View File

@@ -0,0 +1,24 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.MonoBehaviours;
using NitroxModel.Packets;
namespace NitroxClient.Communication.Packets.Processors;
public class VehicleMovementsProcessor : ClientPacketProcessor<VehicleMovements>
{
public override void Process(VehicleMovements packet)
{
if (!MovementBroadcaster.Instance)
{
return;
}
foreach (MovementData movementData in packet.Data)
{
if (MovementBroadcaster.Instance.Replicators.TryGetValue(movementData.Id, out MovementReplicator movementReplicator))
{
movementReplicator.AddSnapshot(movementData, (float)packet.RealTime);
}
}
}
}

View File

@@ -0,0 +1,36 @@
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxClient.MonoBehaviours;
using NitroxModel.Packets;
using UnityEngine;
namespace NitroxClient.Communication.Packets.Processors;
public class VehicleOnPilotModeChangedProcessor : ClientPacketProcessor<VehicleOnPilotModeChanged>
{
private readonly Vehicles vehicles;
private readonly PlayerManager playerManager;
public VehicleOnPilotModeChangedProcessor(Vehicles vehicles, PlayerManager playerManager)
{
this.vehicles = vehicles;
this.playerManager = playerManager;
}
public override void Process(VehicleOnPilotModeChanged packet)
{
if (NitroxEntity.TryGetObjectFrom(packet.VehicleId, out GameObject gameObject))
{
// If the vehicle is docked, then we will manually set the piloting mode
// once the animations complete. This prevents weird behaviour such as the
// player existing the vehicle while it is about to dock (the event fires
// before the animation completes on the remote player.)
if (gameObject.TryGetComponent(out Vehicle vehicle) && vehicle.docked)
{
return;
}
vehicles.SetOnPilotMode(gameObject, packet.PlayerId, packet.IsPiloting);
}
}
}

View File

@@ -0,0 +1,96 @@
using System.Collections;
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxClient.MonoBehaviours;
using NitroxClient.Unity.Helper;
using NitroxModel.Packets;
using UnityEngine;
namespace NitroxClient.Communication.Packets.Processors;
public class VehicleUndockingProcessor : ClientPacketProcessor<VehicleUndocking>
{
private readonly Vehicles vehicles;
private readonly PlayerManager remotePlayerManager;
public VehicleUndockingProcessor(Vehicles vehicles, PlayerManager remotePlayerManager)
{
this.vehicles = vehicles;
this.remotePlayerManager = remotePlayerManager;
}
public override void Process(VehicleUndocking packet)
{
GameObject vehicleGo = NitroxEntity.RequireObjectFrom(packet.VehicleId);
GameObject vehicleDockingBayGo = NitroxEntity.RequireObjectFrom(packet.DockId);
Vehicle vehicle = vehicleGo.RequireComponent<Vehicle>();
VehicleDockingBay vehicleDockingBay = vehicleDockingBayGo.RequireComponent<VehicleDockingBay>();
using (PacketSuppressor<VehicleUndocking>.Suppress())
{
if (packet.UndockingStart)
{
StartVehicleUndocking(packet, vehicleGo, vehicle, vehicleDockingBay);
}
else
{
FinishVehicleUndocking(packet, vehicle, vehicleDockingBay);
}
}
}
private void StartVehicleUndocking(VehicleUndocking packet, GameObject vehicleGo, Vehicle vehicle, VehicleDockingBay vehicleDockingBay)
{
vehicleDockingBay.subRoot.BroadcastMessage("OnLaunchBayOpening", SendMessageOptions.DontRequireReceiver);
SkyEnvironmentChanged.Broadcast(vehicleGo, (GameObject)null);
if (remotePlayerManager.TryFind(packet.PlayerId, out RemotePlayer player))
{
// It can happen that the player turns in circles around himself in the vehicle. This stops it.
player.RigidBody.angularVelocity = Vector3.zero;
vehicles.SetOnPilotMode(packet.VehicleId, packet.PlayerId, true);
}
vehicleDockingBay.StartCoroutine(StartUndockingAnimation(vehicleDockingBay));
if (vehicle.TryGetComponent(out MovementReplicator vehicleMovementReplicator))
{
vehicleMovementReplicator.ClearBuffer();
Log.Debug($"[{nameof(VehicleDockingProcessor)}] Clear MovementReplicator on {packet.VehicleId}");
}
}
private static IEnumerator StartUndockingAnimation(VehicleDockingBay vehicleDockingBay)
{
yield return Yielders.WaitFor2Seconds;
vehicleDockingBay.vehicle_docked_param = false;
}
private void FinishVehicleUndocking(VehicleUndocking packet, Vehicle vehicle, VehicleDockingBay vehicleDockingBay)
{
if (vehicleDockingBay.GetSubRoot().isCyclops)
{
vehicleDockingBay.SetVehicleUndocked();
}
vehicleDockingBay.dockedVehicle = null;
vehicleDockingBay.CancelInvoke(nameof(VehicleDockingBay.RepairVehicle));
vehicle.docked = false;
if (remotePlayerManager.TryFind(packet.PlayerId, out RemotePlayer player))
{
// Sometimes the player is not set accordingly which stretches the player's model instead of putting them in place
// after undocking. This fixes it (the player rigid body seems to not be set right sometimes)
player.SetSubRoot(null);
player.SetVehicle(null);
player.SetVehicle(vehicle);
}
vehicles.SetOnPilotMode(packet.VehicleId, packet.PlayerId, true);
if (vehicle.TryGetComponent(out MovementReplicator vehicleMovementReplicator))
{
vehicleMovementReplicator.enabled = true;
Log.Debug($"[{nameof(VehicleDockingProcessor)}] Enabled MovementReplicator on {packet.VehicleId}");
}
Log.Debug("Set vehicle undocking complete");
}
}

View File

@@ -0,0 +1,41 @@
using NitroxClient.Communication.Abstract;
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxClient.MonoBehaviours;
using NitroxModel.Packets;
using UnityEngine;
namespace NitroxClient.Communication.Packets.Processors
{
class WeldActionProcessor : ClientPacketProcessor<WeldAction>
{
private IMultiplayerSession multiplayerSession;
private SimulationOwnership simulationOwnership;
public WeldActionProcessor(IMultiplayerSession multiplayerSession, SimulationOwnership simulationOwnership)
{
this.multiplayerSession = multiplayerSession;
this.simulationOwnership = simulationOwnership;
}
public override void Process(WeldAction packet)
{
GameObject gameObject = NitroxEntity.RequireObjectFrom(packet.Id);
if (!simulationOwnership.HasAnyLockType(packet.Id))
{
Log.Error($"Got WeldAction packet for {packet.Id} but did not find the lock corresponding to it");
return;
}
LiveMixin liveMixin = gameObject.GetComponent<LiveMixin>();
if (!liveMixin)
{
Log.Error($"Did not find LiveMixin for GameObject {packet.Id} even though it was welded.");
return;
}
// If we add other player sounds/animations, this is the place to do it for welding
liveMixin.AddHealth(packet.HealthAdded);
}
}
}