Files
Nitrox/NitroxClient/GameLogic/Cyclops.cs
2025-07-06 00:23:46 +02:00

202 lines
9.0 KiB
C#

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using NitroxClient.Communication;
using NitroxClient.Communication.Abstract;
using NitroxClient.MonoBehaviours;
using NitroxClient.Unity.Helper;
using NitroxModel_Subnautica.DataStructures;
using NitroxModel.DataStructures;
using NitroxModel.DataStructures.Util;
using NitroxModel.Packets;
using NitroxModel_Subnautica.DataStructures.GameLogic;
using NitroxModel_Subnautica.Packets;
using UnityEngine;
using static NitroxClient.GameLogic.Spawning.Metadata.Extractor.CyclopsMetadataExtractor;
namespace NitroxClient.GameLogic
{
public class Cyclops
{
private readonly IPacketSender packetSender;
private readonly Vehicles vehicles;
private readonly Entities entities;
public Cyclops(IPacketSender packetSender, Vehicles vehicles, Entities entities)
{
this.packetSender = packetSender;
this.vehicles = vehicles;
this.entities = entities;
}
public void BroadcastMetadataChange(NitroxId id)
{
GameObject gameObject = NitroxEntity.RequireObjectFrom(id);
CyclopsGameObject cyclops = new CyclopsGameObject() { GameObject = gameObject };
entities.EntityMetadataChanged(cyclops, id);
}
public void BroadcastLaunchDecoy(NitroxId id)
{
CyclopsDecoyLaunch packet = new CyclopsDecoyLaunch(id);
packetSender.Send(packet);
}
public void BroadcastActivateFireSuppression(NitroxId id)
{
CyclopsFireSuppression packet = new CyclopsFireSuppression(id);
packetSender.Send(packet);
}
public void LaunchDecoy(NitroxId id)
{
GameObject cyclops = NitroxEntity.RequireObjectFrom(id);
CyclopsDecoyManager decoyManager = cyclops.RequireComponent<CyclopsDecoyManager>();
using (PacketSuppressor<EntityMetadataUpdate>.Suppress())
{
decoyManager.Invoke(nameof(CyclopsDecoyManager.LaunchWithDelay), 3f);
decoyManager.decoyLaunchButton.UpdateText();
decoyManager.subRoot.voiceNotificationManager.PlayVoiceNotification(decoyManager.subRoot.decoyNotification, false, true);
decoyManager.subRoot.BroadcastMessage("UpdateTotalDecoys", decoyManager.decoyCount, SendMessageOptions.DontRequireReceiver);
CyclopsDecoyLaunchButton decoyLaunchButton = cyclops.RequireComponentInChildren<CyclopsDecoyLaunchButton>();
decoyLaunchButton.StartCooldown();
}
}
public void StartFireSuppression(NitroxId id)
{
GameObject cyclops = NitroxEntity.RequireObjectFrom(id);
CyclopsFireSuppressionSystemButton fireSuppButton = cyclops.RequireComponentInChildren<CyclopsFireSuppressionSystemButton>();
using (PacketSuppressor<CyclopsFireSuppression>.Suppress())
{
// Infos from SubFire.StartSystem
fireSuppButton.subFire.StartCoroutine(StartFireSuppressionSystem(fireSuppButton.subFire));
fireSuppButton.StartCooldown();
}
}
// Remake of the StartSystem Coroutine from original player. Some Methods are not used from the original coroutine
// For example no temporaryClose as this will be initiated anyway from the originating Player
// Also the fire extiguishing will not start cause the initial player is already extiguishing the fires. Else this could double/triple/... the extinguishing
private IEnumerator StartFireSuppressionSystem(SubFire fire)
{
fire.subRoot.voiceNotificationManager.PlayVoiceNotification(fire.subRoot.fireSupressionNotification, false, true);
yield return Yielders.WaitFor3Seconds;
fire.fireSuppressionActive = true;
fire.subRoot.fireSuppressionState = true;
fire.subRoot.BroadcastMessage("NewAlarmState", null, SendMessageOptions.DontRequireReceiver);
fire.Invoke(nameof(SubFire.CancelFireSuppression), fire.fireSuppressionSystemDuration);
float doorCloseDuration = 30f;
fire.gameObject.BroadcastMessage("TemporaryLock", doorCloseDuration, SendMessageOptions.DontRequireReceiver);
}
/// <summary>
/// Triggers a <see cref="CyclopsDamage"/> packet
/// </summary>
public void OnCreateDamagePoint(SubRoot subRoot)
{
BroadcastDamageState(subRoot, Optional.Empty);
}
/// <summary>
/// Called when the player repairs a <see cref="CyclopsDamagePoint"/>. Right now it's not possible to partially repair because it would be difficult to implement.
/// <see cref="CyclopsDamagePoint"/>s are coupled with <see cref="LiveMixin"/>, which is used with just about anything that has health.
/// I would need to hook onto <see cref="LiveMixin.AddHealth(float)"/>, or maybe the repair gun event to catch when something repairs a damage point, which I don't
/// believe is worth the effort. A <see cref="CyclopsDamagePoint"/> is already fully repaired in a little over a second. This can trigger sending
/// <see cref="CyclopsDamagePointRepaired"/> and <see cref="CyclopsDamage"/> packets
/// </summary>
public void OnDamagePointRepaired(SubRoot subRoot, CyclopsDamagePoint damagePoint, float repairAmount)
{
if (!subRoot.TryGetIdOrWarn(out NitroxId subId))
{
return;
}
for (int i = 0; i < subRoot.damageManager.damagePoints.Length; i++)
{
if (subRoot.damageManager.damagePoints[i] == damagePoint)
{
CyclopsDamagePointRepaired packet = new(subId, i, repairAmount);
packetSender.Send(packet);
return;
}
}
}
/// <summary>
/// Send out a <see cref="CyclopsDamage"/> packet
/// </summary>
private void BroadcastDamageState(SubRoot subRoot, Optional<DamageInfo> info)
{
if (!subRoot.TryGetIdOrWarn(out NitroxId subId))
{
return;
}
LiveMixin subHealth = subRoot.gameObject.RequireComponent<LiveMixin>();
if (subHealth.health <= 0)
{
return;
}
CyclopsDamageInfoData damageInfo = null;
if (info.HasValue)
{
DamageInfo damage = info.Value;
Optional<NitroxId> dealerId = damage.dealer.GetId();
// Source of the damage. Used if the damage done to the Cyclops was not calculated on other clients. Currently it's just used to figure out what sounds and
// visual effects should be used.
damageInfo = new CyclopsDamageInfoData(subId, dealerId, damage.originalDamage, damage.damage, damage.position.ToDto(), damage.type);
}
int[] damagePointIndexes = GetActiveDamagePoints(subRoot).ToArray();
CyclopsFireData[] firePoints = GetActiveRoomFires(subRoot.GetComponent<SubFire>()).ToArray();
CyclopsDamage packet = new(subId, subRoot.GetComponent<LiveMixin>().health, subRoot.damageManager.subLiveMixin.health, subRoot.GetComponent<SubFire>().liveMixin.health, damagePointIndexes, firePoints, damageInfo);
packetSender.Send(packet);
}
/// <summary>
/// Get all of the index locations of <see cref="CyclopsDamagePoint"/>s in <see cref="CyclopsExternalDamageManager.damagePoints"/>.
/// </summary>
private IEnumerable<int> GetActiveDamagePoints(SubRoot subRoot)
{
for (int i = 0; i < subRoot.damageManager.damagePoints.Length; i++)
{
if (subRoot.damageManager.damagePoints[i].gameObject.activeSelf)
{
yield return i;
}
}
}
/// <summary>
/// Get all of the index locations of all the fires on the <see cref="SubRoot"/>. <see cref="SubFire.RoomFire.spawnNodes"/> contains
/// a static list of all possible fire nodes.
/// </summary>
private IEnumerable<CyclopsFireData> GetActiveRoomFires(SubFire subFire)
{
if (!subFire.subRoot.TryGetIdOrWarn(out NitroxId subRootId))
{
yield break;
}
foreach (KeyValuePair<CyclopsRooms, SubFire.RoomFire> roomFire in subFire.roomFires)
{
for (int i = 0; i < roomFire.Value.spawnNodes.Length; i++)
{
if (roomFire.Value.spawnNodes[i].childCount > 0)
{
if (!roomFire.Value.spawnNodes[i].GetComponentInChildren<Fire>().TryGetIdOrWarn(out NitroxId fireId))
{
yield break;
}
yield return new CyclopsFireData(fireId, subRootId, roomFire.Key, i);
}
}
}
}
}
}