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 { /// /// Add/remove s and s to match the packet received /// public class CyclopsDamageProcessor : ClientPacketProcessor { 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(); using (PacketSuppressor.Suppress()) { SetActiveDamagePoints(subRoot, packet.DamagePointIndexes); } using (PacketSuppressor.Suppress()) { SetActiveRoomFires(subRoot, packet.RoomFires); } LiveMixin subHealth = subRoot.gameObject.RequireComponent(); 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.Suppress()) { // Not necessary, but used by above code whenever damage is done subRoot.oldHPPercent = subHealth.GetHealthFraction(); // Apply the actual health changes subRoot.gameObject.RequireComponent().health = packet.SubHealth; subRoot.gameObject.RequireComponentInChildren().subLiveMixin.health = packet.DamageManagerHealth; subRoot.gameObject.RequireComponent().liveMixin.health = packet.SubFireHealth; } } /// /// Add/remove s until it matches the array passed. Can trigger packets /// private void SetActiveDamagePoints(SubRoot cyclops, int[] damagePointIndexes) { CyclopsExternalDamageManager damageManager = cyclops.gameObject.RequireComponentInChildren(); List 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(); } /// /// Add/remove fires until it matches the array. Can trigger packets /// private void SetActiveRoomFires(SubRoot subRoot, CyclopsFireData[] roomFires) { SubFire subFire = subRoot.gameObject.RequireComponent(); Dictionary 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 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().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 keyValuePair in roomFiresDict) { foreach (Transform spawnNode in keyValuePair.Value.spawnNodes) { if (spawnNode.childCount > 0) { spawnNode.GetComponentInChildren().Douse(10000); } } } } } /// /// Set the health of a . This can trigger sending packets /// /// The max health of the point is 1. 999 is passed to trigger a full repair of the private void RepairDamagePoint(SubRoot subRoot, int damagePointIndex, float repairAmount) { subRoot.damageManager.damagePoints[damagePointIndex].liveMixin.AddHealth(repairAmount); } } }