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,20 @@
using NitroxModel.Core;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using UnityEngine;
namespace NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
public abstract class EntityMetadataProcessor<TMetadata> : IEntityMetadataProcessor where TMetadata : EntityMetadata
{
public abstract void ProcessMetadata(GameObject gameObject, TMetadata metadata);
public void ProcessMetadata(GameObject gameObject, EntityMetadata metadata)
{
ProcessMetadata(gameObject, (TMetadata)metadata);
}
protected TService Resolve<TService>() where TService : class
{
return NitroxServiceLocator.Cache<TService>.Value;
}
}

View File

@@ -0,0 +1,9 @@
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using UnityEngine;
namespace NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
public interface IEntityMetadataProcessor
{
public abstract void ProcessMetadata(GameObject gameObject, EntityMetadata metadata);
}

View File

@@ -0,0 +1,24 @@
using NitroxClient.Unity.Helper;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using NitroxModel.DataStructures.Unity;
using UnityEngine;
namespace NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
public abstract class VehicleMetadataProcessor<T> : EntityMetadataProcessor<T> where T : VehicleMetadata
{
private readonly LiveMixinManager liveMixinManager;
public VehicleMetadataProcessor(LiveMixinManager liveMixinManager)
{
this.liveMixinManager = liveMixinManager;
}
protected void SetHealth(GameObject gameObject, float health)
{
LiveMixin liveMixin = gameObject.RequireComponentInChildren<LiveMixin>(true);
liveMixinManager.SyncRemoteHealth(liveMixin, health);
}
protected void SetNameAndColors(SubName subName, string text, NitroxVector3[] nitroxColor) => SubNameInputMetadataProcessor.SetNameAndColors(subName, text, nitroxColor);
}

View File

@@ -0,0 +1,22 @@
using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using UnityEngine;
namespace NitroxClient.GameLogic.Spawning.Metadata.Processor;
public class BatteryMetadataProcessor : EntityMetadataProcessor<BatteryMetadata>
{
public override void ProcessMetadata(GameObject gameObject, BatteryMetadata metadata)
{
Battery battery = gameObject.GetComponent<Battery>();
if (battery)
{
battery._charge = metadata.Charge;
}
else
{
Log.Error($"Could not find Battery on {gameObject.name}");
}
}
}

View File

@@ -0,0 +1,25 @@
using NitroxClient.Communication;
using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using NitroxModel.Packets;
using UnityEngine;
namespace NitroxClient.GameLogic.Spawning.Metadata.Processor;
public class ConstructorMetadataProcessor : EntityMetadataProcessor<ConstructorMetadata>
{
public override void ProcessMetadata(GameObject gameObject, ConstructorMetadata metadata)
{
if (gameObject.TryGetComponent(out Constructor constructor))
{
using (PacketSuppressor<EntityMetadataUpdate>.Suppress())
{
constructor.Deploy(metadata.Deployed);
}
}
else
{
Log.Error($"[{nameof(ConstructorMetadataProcessor)}] Could not find {nameof(Constructor)} on {gameObject.name}");
}
}
}

View File

@@ -0,0 +1,45 @@
using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
using NitroxClient.Unity.Helper;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using NitroxModel_Subnautica.DataStructures;
using UnityEngine;
namespace NitroxClient.GameLogic.Spawning.Metadata.Processor;
public class CrafterMetadataProcessor : EntityMetadataProcessor<CrafterMetadata>
{
// small increase to prevent this player from swiping item from remote player
public const float ANTI_GRIEF_DURATION_BUFFER = 0.2f;
public override void ProcessMetadata(GameObject gameObject, CrafterMetadata metadata)
{
if (metadata.TechType == null)
{
EnsureCrafterReset(gameObject);
}
else
{
SpawnItemInCrafter(gameObject, metadata);
}
}
private void EnsureCrafterReset(GameObject gameObject)
{
CrafterLogic crafterLogic = gameObject.RequireComponentInChildren<CrafterLogic>(true);
crafterLogic.ResetCrafter();
}
private void SpawnItemInCrafter(GameObject gameObject, CrafterMetadata metadata)
{
GhostCrafter ghostCrafter = gameObject.RequireComponentInChildren<GhostCrafter>(true);
float elapsedFromStart = DayNightCycle.main.timePassedAsFloat - metadata.StartTime;
// If a craft started way in the past, set duration to 0.01 (the craft function will not work with 0)
// Keeping track of both the duration and start time allows us to solve use-cases such as reloading
// when an item is being crafted or not picked up yet.
float duration = Mathf.Max(metadata.Duration - elapsedFromStart + ANTI_GRIEF_DURATION_BUFFER, 0.01f);
ghostCrafter.logic.Craft(metadata.TechType.ToUnity(), duration);
}
}

View File

@@ -0,0 +1,31 @@
using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using UnityEngine;
namespace NitroxClient.GameLogic.Spawning.Metadata.Processor;
public class CrashHomeMetadataProcessor : EntityMetadataProcessor<CrashHomeMetadata>
{
public override void ProcessMetadata(GameObject gameObject, CrashHomeMetadata metadata)
{
if (gameObject.TryGetComponent(out CrashHome crashHome))
{
crashHome.spawnTime = metadata.SpawnTime;
UpdateCrashHomeOpen(crashHome);
}
else
{
Log.Error($"[{nameof(CrashHomeMetadataProcessor)}] Could not find {nameof(CrashHome)} on {gameObject}");
}
}
public static void UpdateCrashHomeOpen(CrashHome crashHome)
{
// From CrashHome.Update
// We also add a distance detection to take into account if the crash is still in the home or not
bool isCrashResting = crashHome.crash && crashHome.crash.IsResting() && crashHome &&
Vector3.Distance(crashHome.transform.position, crashHome.crash.transform.position) < 1f;
crashHome.animator.SetBool(AnimatorHashID.attacking, !isCrashResting);
crashHome.prevClosed = isCrashResting;
}
}

View File

@@ -0,0 +1,58 @@
using NitroxClient.Communication;
using NitroxClient.Communication.Abstract;
using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
using NitroxClient.Unity.Helper;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using NitroxModel.Packets;
using UnityEngine;
namespace NitroxClient.GameLogic.Spawning.Metadata.Processor;
public class CyclopsLightingMetadataProcessor : EntityMetadataProcessor<CyclopsLightingMetadata>
{
private readonly IPacketSender packetSender;
public CyclopsLightingMetadataProcessor(IPacketSender packetSender)
{
this.packetSender = packetSender;
}
public override void ProcessMetadata(GameObject gameObject, CyclopsLightingMetadata metadata)
{
CyclopsLightingPanel lighting = gameObject.RequireComponentInChildren<CyclopsLightingPanel>(true);
using (PacketSuppressor<EntityMetadataUpdate>.Suppress())
{
SetInternalLighting(lighting, metadata.InternalLightsOn);
SetFloodLighting(lighting, metadata.FloodLightsOn);
}
}
private void SetInternalLighting(CyclopsLightingPanel lighting, bool isOn)
{
if (lighting.lightingOn == isOn)
{
return;
}
lighting.lightingOn = !lighting.lightingOn;
lighting.cyclopsRoot.ForceLightingState(lighting.lightingOn);
FMODAsset asset = !lighting.lightingOn ? lighting.vn_lightsOff : lighting.vn_lightsOn;
FMODUWE.PlayOneShot(asset, lighting.transform.position, 1f);
lighting.UpdateLightingButtons();
}
private void SetFloodLighting(CyclopsLightingPanel lighting, bool isOn)
{
if (lighting.floodlightsOn == isOn)
{
return;
}
lighting.floodlightsOn = !lighting.floodlightsOn;
lighting.SetExternalLighting(lighting.floodlightsOn);
FMODAsset asset = !lighting.floodlightsOn ? lighting.vn_floodlightsOff : lighting.vn_floodlightsOn;
FMODUWE.PlayOneShot(asset, lighting.transform.position, 1f);
lighting.UpdateLightingButtons();
}
}

View File

@@ -0,0 +1,174 @@
using NitroxClient.Communication;
using NitroxClient.Communication.Abstract;
using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
using NitroxClient.Unity.Helper;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using NitroxModel.Packets;
using UnityEngine;
namespace NitroxClient.GameLogic.Spawning.Metadata.Processor;
public class CyclopsMetadataProcessor : EntityMetadataProcessor<CyclopsMetadata>
{
private readonly IPacketSender packetSender;
private readonly LiveMixinManager liveMixinManager;
public CyclopsMetadataProcessor(IPacketSender packetSender, LiveMixinManager liveMixinManager)
{
this.packetSender = packetSender;
this.liveMixinManager = liveMixinManager;
}
public override void ProcessMetadata(GameObject cyclops, CyclopsMetadata metadata)
{
using (PacketSuppressor<EntityMetadataUpdate>.Suppress())
{
SetEngineMode(cyclops, (CyclopsMotorMode.CyclopsMotorModes)metadata.EngineMode);
ChangeSilentRunning(cyclops, metadata.SilentRunningOn);
ChangeShieldMode(cyclops, metadata.ShieldOn);
ChangeSonarMode(cyclops, metadata.SonarOn);
SetEngineState(cyclops, metadata.EngineOn);
SetHealth(cyclops, metadata.Health);
SetDestroyed(cyclops, metadata.IsDestroyed);
}
}
private void SetEngineState(GameObject cyclops, bool isOn)
{
CyclopsEngineChangeState engineState = cyclops.RequireComponentInChildren<CyclopsEngineChangeState>(true);
if (isOn == engineState.motorMode.engineOn)
{
// engine state is the same - nothing to do.
return;
}
// During initial sync or when the cyclops HUD isn't shown (from outside of the cyclops)
if (Player.main.currentSub != engineState.subRoot)
{
engineState.startEngine = isOn;
engineState.subRoot.BroadcastMessage(nameof(CyclopsMotorMode.InvokeChangeEngineState), isOn, SendMessageOptions.RequireReceiver);
engineState.invalidButton = true;
engineState.Invoke(nameof(CyclopsEngineChangeState.ResetInvalidButton), 2.5f);
}
// When inside of the cyclops, we play the cinematics
else
{
// To invoke the whole OnClick method we need to set the right parameters first
engineState.invalidButton = false;
using (PacketSuppressor<EntityMetadataUpdate>.Suppress())
{
engineState.OnClick();
}
}
}
private void SetEngineMode(GameObject cyclops, CyclopsMotorMode.CyclopsMotorModes mode)
{
foreach (CyclopsMotorModeButton button in cyclops.GetComponentsInChildren<CyclopsMotorModeButton>(true))
{
// At initial sync, this kind of processor is executed before the Start()
if (!button.subRoot)
{
button.Start();
}
button.SetCyclopsMotorMode(mode);
}
}
private void ChangeSilentRunning(GameObject cyclops, bool isOn)
{
CyclopsSilentRunningAbilityButton ability = cyclops.RequireComponentInChildren<CyclopsSilentRunningAbilityButton>(true);
if (isOn == ability.active)
{
return;
}
Log.Debug($"Set silent running to {isOn} for cyclops");
ability.active = isOn;
if (isOn)
{
ability.image.sprite = ability.activeSprite;
ability.subRoot.BroadcastMessage("RigForSilentRunning");
ability.InvokeRepeating(nameof(CyclopsSilentRunningAbilityButton.SilentRunningIteration), 0f, ability.silentRunningIteration);
}
else
{
ability.image.sprite = ability.inactiveSprite;
ability.subRoot.BroadcastMessage("SecureFromSilentRunning");
ability.CancelInvoke(nameof(CyclopsSilentRunningAbilityButton.SilentRunningIteration));
}
}
private void ChangeShieldMode(GameObject cyclops, bool isOn)
{
CyclopsShieldButton shield = cyclops.GetComponentInChildren<CyclopsShieldButton>(true);
if (!shield)
{
// may not have a shield installed.
return;
}
bool isShieldOn = shield.activeSprite == shield.image.sprite;
if (isShieldOn == isOn)
{
return;
}
if (isOn)
{
shield.StartShield();
}
else
{
shield.StopShield();
}
}
private void ChangeSonarMode(GameObject cyclops, bool isOn)
{
CyclopsSonarButton sonarButton = cyclops.GetComponentInChildren<CyclopsSonarButton>(true);
if (sonarButton && sonarButton.sonarActive != isOn)
{
if (isOn)
{
sonarButton.TurnOnSonar();
}
else
{
sonarButton.TurnOffSonar();
}
}
}
private void SetHealth(GameObject gameObject, float health)
{
LiveMixin liveMixin = gameObject.RequireComponentInChildren<LiveMixin>(true);
liveMixinManager.SyncRemoteHealth(liveMixin, health);
}
private void SetDestroyed(GameObject gameObject, bool isDestroyed)
{
CyclopsDestructionEvent destructionEvent = gameObject.RequireComponentInChildren<CyclopsDestructionEvent>(true);
// Don't play VFX and SFX if the Cyclops is already destroyed or was spawned in as destroyed
if (destructionEvent.subRoot.subDestroyed == isDestroyed) return;
if (isDestroyed)
{
// Use packet suppressor as sentinel so the patch callback knows not to spawn loot
using (PacketSuppressor<EntitySpawnedByClient>.Suppress())
{
destructionEvent.DestroyCyclops();
}
}
else
{
destructionEvent.RestoreCyclops();
}
}
}

View File

@@ -0,0 +1,21 @@
using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using UnityEngine;
namespace NitroxClient.GameLogic.Spawning.Metadata.Processor;
public class EatableMetadataProcessor : EntityMetadataProcessor<EatableMetadata>
{
public override void ProcessMetadata(GameObject gameObject, EatableMetadata metadata)
{
if (gameObject.TryGetComponent(out Eatable eatable))
{
eatable.SetDecomposes(true);
eatable.timeDecayStart = metadata.TimeDecayStart;
}
if (gameObject.TryGetComponent(out LiveMixin liveMixin))
{
Resolve<LiveMixinManager>().SyncRemoteHealth(liveMixin, 0);
}
}
}

View File

@@ -0,0 +1,39 @@
using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
using NitroxClient.MonoBehaviours;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using UnityEngine;
namespace NitroxClient.GameLogic.Spawning.Metadata.Processor;
public class EggMetadataProcessor : EntityMetadataProcessor<EggMetadata>
{
public override void ProcessMetadata(GameObject gameObject, EggMetadata metadata)
{
if (!gameObject.TryGetComponent(out CreatureEgg creatureEgg))
{
Log.Error($"[{nameof(EggMetadataProcessor)}] Could not find {nameof(CreatureEgg)} on {gameObject.name}");
return;
}
if (metadata.TimeStartHatching == -1f)
{
// If the egg is not in a water park we only need its progress value
creatureEgg.progress = metadata.Progress;
return;
}
// If the egg is in a water park we only need its time start hatching value
// the current progress will be automatically computed by UpdateProgress()
creatureEgg.timeStartHatching = metadata.TimeStartHatching;
// While being fully loaded, the base is inactive and coroutines shouldn't be started (they'll throw an exception)
// To avoid, that we postpone their execution to 1 more second which is enough because time is frozen during initial sync
if (Multiplayer.Main && !Multiplayer.Main.InitialSyncCompleted &&
creatureEgg.timeStartHatching + creatureEgg.GetHatchDuration() < DayNightCycle.main.timePassedAsFloat)
{
creatureEgg.timeStartHatching = DayNightCycle.main.timePassedAsFloat + 1 - creatureEgg.GetHatchDuration();
}
creatureEgg.UpdateProgress();
}
}

View File

@@ -0,0 +1,21 @@
using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using UnityEngine;
namespace NitroxClient.GameLogic.Spawning.Metadata.Processor;
public class EntitySignMetadataProcessor : EntityMetadataProcessor<EntitySignMetadata>
{
public override void ProcessMetadata(GameObject gameObject, EntitySignMetadata metadata)
{
uGUI_SignInput sign = gameObject.GetComponentInChildren<uGUI_SignInput>(true);
if (sign)
{
sign.text = metadata.Text;
sign.colorIndex = metadata.ColorIndex;
sign.elementsState = metadata.Elements;
sign.scaleIndex = metadata.ScaleIndex;
sign.SetBackground(metadata.Background);
}
}
}

View File

@@ -0,0 +1,75 @@
using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using UnityEngine;
namespace NitroxClient.GameLogic.Spawning.Metadata.Processor;
public class EscapePodMetadataProcessor : EntityMetadataProcessor<EscapePodMetadata>
{
// For metadata changes outside initial sync we only care about broken -> repaired
public override void ProcessMetadata(GameObject gameObject, EscapePodMetadata metadata)
{
if (!gameObject.TryGetComponent(out EscapePod pod))
{
Log.Error($"[{nameof(EscapePodMetadataProcessor)}] Could not get the EscapePod component from the provided gameobject.");
return;
}
if (!pod.liveMixin.IsFullHealth() && metadata.PodRepaired)
{
pod.liveMixin.health = pod.liveMixin.maxHealth;
pod.healthScalar = 1;
pod.damageEffectsShowing = true;
pod.UpdateDamagedEffects();
pod.OnRepair();
}
if (!pod.radioSpawner.spawnedObj.TryGetComponent(out Radio radio))
{
Log.Error($"[{nameof(EscapePodMetadataProcessor)}] Could not get Radio from EscapePod.");
return;
}
if (!radio.liveMixin.IsFullHealth() && metadata.RadioRepaired)
{
radio.liveMixin.AddHealth(radio.liveMixin.maxHealth);
}
}
/// <summary>
/// Applies repaired state without animations and minimal audio playback
/// </summary>
public static void ProcessInitialSyncMetadata(EscapePod pod, Radio radio, EscapePodMetadata metadata)
{
if (metadata.PodRepaired)
{
pod.liveMixin.health = pod.liveMixin.maxHealth;
pod.healthScalar = 1;
pod.damageEffectsShowing = true; // Needs to be set to true for UpdateDamagedEffects() to function
pod.UpdateDamagedEffects();
pod.vfxSpawner.SpawnManual(); // Spawn vfx to instantly disable it so no smoke is fading after player has joined
pod.vfxSpawner.spawnedObj.SetActive(false);
pod.lightingController.SnapToState(0);
}
else
{
IntroLifepodDirector introLifepodDirector = pod.GetComponent<IntroLifepodDirector>();
introLifepodDirector.OnProtoDeserializeObjectTree(null);
introLifepodDirector.ToggleActiveObjects(false);
pod.lightingController.SnapToState(2);
}
if (metadata.RadioRepaired)
{
radio.liveMixin.health = radio.liveMixin.maxHealth;
if (radio.liveMixin.loopingDamageEffectObj)
{
Object.Destroy(radio.liveMixin.loopingDamageEffectObj);
}
}
else
{
pod.DamageRadio();
}
}
}

View File

@@ -0,0 +1,32 @@
using NitroxClient.Communication;
using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using NitroxModel.Packets;
using UnityEngine;
namespace NitroxClient.GameLogic.Spawning.Metadata.Processor;
public class ExosuitMetadataProcessor : VehicleMetadataProcessor<ExosuitMetadata>
{
public ExosuitMetadataProcessor(LiveMixinManager liveMixinManager) : base(liveMixinManager) { }
public override void ProcessMetadata(GameObject gameObject, ExosuitMetadata metadata)
{
if (!gameObject.TryGetComponent(out Exosuit exosuit))
{
Log.ErrorOnce($"[{nameof(ExosuitMetadataProcessor)}] Could not find {nameof(Exosuit)} on {gameObject}");
return;
}
if (!gameObject.TryGetComponent(out SubName subName))
{
Log.ErrorOnce($"[{nameof(ExosuitMetadataProcessor)}] Could not find {nameof(SubName)} on {gameObject}");
return;
}
using (PacketSuppressor<EntityMetadataUpdate>.Suppress())
{
SetHealth(gameObject, metadata.Health);
SetNameAndColors(subName, metadata.Name, metadata.Colors);
}
}
}

View File

@@ -0,0 +1,24 @@
using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using UnityEngine;
namespace NitroxClient.GameLogic.Spawning.Metadata.Processor;
public class FireExtinguisherHolderMetadataProcessor : EntityMetadataProcessor<FireExtinguisherHolderMetadata>
{
public override void ProcessMetadata(GameObject gameObject, FireExtinguisherHolderMetadata metadata)
{
FireExtinguisherHolder holder = gameObject.GetComponent<FireExtinguisherHolder>();
if (holder)
{
holder.fuel = metadata.Fuel;
holder.hasTank = metadata.HasExtinguisher;
holder.tankObject.SetActive(metadata.HasExtinguisher);
}
else
{
Log.Error($"Could not find FireExtinguisherHolder on {gameObject.name}");
}
}
}

View File

@@ -0,0 +1,31 @@
using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using UnityEngine;
namespace NitroxClient.GameLogic.Spawning.Metadata.Processor;
public class FlashlightMetadataProcessor : EntityMetadataProcessor<FlashlightMetadata>
{
public override void ProcessMetadata(GameObject gameObject, FlashlightMetadata metadata)
{
FlashLight flashLight = gameObject.GetComponent<FlashLight>();
if (flashLight)
{
ToggleLights lights = flashLight.gameObject.GetComponent<ToggleLights>();
if (lights)
{
lights.lightsActive = metadata.On;
}
else
{
Log.Error($"Could not find ToggleLights on {flashLight.name}");
}
}
else
{
Log.Error($"Could not find FlashLight on {gameObject.name}");
}
}
}

View File

@@ -0,0 +1,63 @@
using NitroxClient.Communication;
using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using NitroxModel.Packets;
using UnityEngine;
namespace NitroxClient.GameLogic.Spawning.Metadata.Processor;
public class FruitPlantMetadataProcessor : EntityMetadataProcessor<FruitPlantMetadata>
{
public override void ProcessMetadata(GameObject gameObject, FruitPlantMetadata metadata)
{
// Two cases:
// 1. The entity with an id directly has a FruitPlant onto it
if (gameObject.TryGetComponent(out FruitPlant fruitPlant))
{
ProcessMetadata(fruitPlant, metadata);
return;
}
// 2. The entity with an id has a Plantable (located in the plot's storage),
// we want to access the FruitPlant component which is on the spawned plant object
if (!gameObject.TryGetComponent(out Plantable plantable))
{
Log.Error($"[{nameof(FruitPlantMetadataProcessor)}] Could not find {nameof(FruitPlant)} related to {gameObject.name}");
return;
}
if (!plantable.linkedGrownPlant)
{
// This is an error which will happen quite often since this metadata
// is applied from PlantableMetadataProcessor even when linkedGrownPlant isn't available yet
return;
}
if (!plantable.linkedGrownPlant.TryGetComponent(out fruitPlant))
{
Log.Error($"[{nameof(FruitPlantMetadataProcessor)}] Could not find {nameof(FruitPlant)} on {gameObject.name}'s linkedGrownPlant {plantable.linkedGrownPlant.name}");
return;
}
ProcessMetadata(fruitPlant, metadata);
}
private static void ProcessMetadata(FruitPlant fruitPlant, FruitPlantMetadata metadata)
{
// Inspired by FruitPlant.Initialize
fruitPlant.inactiveFruits.Clear();
using (PacketSuppressor<EntityMetadataUpdate>.Suppress())
{
for (int i = 0; i < fruitPlant.fruits.Length; i++)
{
fruitPlant.fruits[i].SetPickedState(metadata.PickedStates[i]);
if (metadata.PickedStates[i])
{
fruitPlant.inactiveFruits.Add(fruitPlant.fruits[i]);
}
}
}
fruitPlant.timeNextFruit = metadata.TimeNextFruit;
}
}

View File

@@ -0,0 +1,26 @@
using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using UnityEngine;
namespace NitroxClient.GameLogic.Spawning.Metadata.Processor;
public class IncubatorMetadataProcessor : EntityMetadataProcessor<IncubatorMetadata>
{
public override void ProcessMetadata(GameObject gameObject, IncubatorMetadata metadata)
{
if (metadata.Powered)
{
IncubatorActivationTerminal terminal = gameObject.GetComponentInChildren<IncubatorActivationTerminal>();
terminal.incubator.SetPowered(true);
terminal.onUseGoal?.Trigger();
terminal.CloseDeck();
}
if (metadata.Hatched)
{
Incubator incubator = gameObject.GetComponentInChildren<Incubator>();
incubator.hatched = true;
incubator.OnHatched();
}
}
}

View File

@@ -0,0 +1,30 @@
using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using UnityEngine;
namespace NitroxClient.GameLogic.Spawning.Metadata.Processor;
public class KeypadMetadataProcessor : EntityMetadataProcessor<KeypadMetadata>
{
public override void ProcessMetadata(GameObject gameObject, KeypadMetadata metadata)
{
Log.Debug($"Received keypad metadata change for {gameObject.name} with data of {metadata}");
KeypadDoorConsole keypad = gameObject.GetComponent<KeypadDoorConsole>();
keypad.unlocked = metadata.Unlocked;
if (metadata.Unlocked)
{
if (keypad.root)
{
keypad.root.BroadcastMessage("UnlockDoor");
}
else
{
keypad.BroadcastMessage("UnlockDoor");
}
keypad.UnlockDoor();
}
}
}

View File

@@ -0,0 +1,45 @@
using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using UnityEngine;
namespace NitroxClient.GameLogic.Spawning.Metadata.Processor;
public class PlantableMetadataProcessor(FruitPlantMetadataProcessor fruitPlantMetadataProcessor) : EntityMetadataProcessor<PlantableMetadata>
{
private readonly FruitPlantMetadataProcessor fruitPlantMetadataProcessor = fruitPlantMetadataProcessor;
public override void ProcessMetadata(GameObject gameObject, PlantableMetadata metadata)
{
if (!gameObject.TryGetComponent(out Plantable plantable))
{
Log.Error($"[{nameof(PlantableMetadataProcessor)}] Could not find {nameof(Plantable)} on {gameObject.name}");
return;
}
// For Plantables which were replaced by the GrowingPlant object
if (plantable.growingPlant)
{
plantable.growingPlant.timeStartGrowth = metadata.TimeStartGrowth;
}
// For regular Plantables
else if (plantable.model.TryGetComponent(out GrowingPlant growingPlant))
{
// Calculation from GrowingPlant.GetProgress
if (metadata.TimeStartGrowth == -1f)
{
plantable.plantAge = 0;
}
else
{
// This is the reversed calculation because we're looking for "progress" while we already know timeStartGrowth
plantable.plantAge = Mathf.Clamp((DayNightCycle.main.timePassedAsFloat - metadata.TimeStartGrowth) / growingPlant.GetGrowthDuration(), 0f, growingPlant.maxProgress);
}
}
// TODO: Refer to the TODO in PlantableMetadata
if (metadata.FruitPlantMetadata != null)
{
fruitPlantMetadataProcessor.ProcessMetadata(gameObject, metadata.FruitPlantMetadata);
}
}
}

View File

@@ -0,0 +1,77 @@
using System.Collections.Generic;
using System.Linq;
using NitroxClient.GameLogic.PlayerLogic;
using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
using NitroxClient.Unity.Helper;
using NitroxModel.DataStructures;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using NitroxModel_Subnautica.DataStructures;
using UnityEngine;
using static NitroxModel.DataStructures.GameLogic.Entities.Metadata.PlayerMetadata;
namespace NitroxClient.GameLogic.Spawning.Metadata.Processor;
public class PlayerMetadataProcessor : EntityMetadataProcessor<PlayerMetadata>
{
private NitroxId localPlayerId = null;
public override void ProcessMetadata(GameObject gameObject, PlayerMetadata metadata)
{
if (!gameObject.TryGetIdOrWarn(out NitroxId id))
{
return;
}
// The local player id should be static, therefor we can cache the id for performance
if (localPlayerId == null && !Player.main.TryGetIdOrWarn(out localPlayerId))
{
return;
}
if (id == localPlayerId)
{
UpdateForLocalPlayer(metadata);
}
else
{
UpdateForRemotePlayer(gameObject, metadata);
}
}
private void UpdateForLocalPlayer(PlayerMetadata metadata)
{
ItemsContainer currentItems = Inventory.Get().container;
Equipment equipment = Inventory.main.equipment;
foreach (EquippedItem equippedItem in metadata.EquippedItems)
{
InventoryItem inventoryItem = currentItems.FirstOrDefault(item => item.item.TryGetNitroxId(out NitroxId id) && equippedItem.Id == id);
// It is OK if we don't find the item, this could be a rebroadcast and we've already equipped the item.
if (inventoryItem != null)
{
Pickupable pickupable = inventoryItem.item;
currentItems.RemoveItem(pickupable, true);
inventoryItem.container = equipment;
pickupable.Reparent(equipment.tr);
equipment.equipment[equippedItem.Slot] = inventoryItem;
equipment.UpdateCount(pickupable.GetTechType(), true);
Equipment.SendEquipmentEvent(pickupable, 0, equipment.owner, equippedItem.Slot);
equipment.NotifyEquip(equippedItem.Slot, inventoryItem);
currentItems.RemoveItem(inventoryItem.item);
}
}
}
private void UpdateForRemotePlayer(GameObject gameObject, PlayerMetadata metadata)
{
Log.Info("Calling UpdateForRemotePlayer");
RemotePlayerIdentifier remotePlayerId = gameObject.RequireComponent<RemotePlayerIdentifier>();
List<TechType> equippedTechTypes = metadata.EquippedItems.Select(x => x.TechType.ToUnity()).ToList();
remotePlayerId.RemotePlayer.UpdateEquipmentVisibility(equippedTechTypes);
}
}

View File

@@ -0,0 +1,25 @@
using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using UnityEngine;
namespace NitroxClient.GameLogic.Spawning.Metadata.Processor;
public class PrecursorDoorwayMetadataProcessor : EntityMetadataProcessor<PrecursorDoorwayMetadata>
{
public override void ProcessMetadata(GameObject gameObject, PrecursorDoorwayMetadata metadata)
{
Log.Info($"Received precursor door metadata change for {gameObject.name} with data of {metadata}");
PrecursorDoorway precursorDoorway = gameObject.GetComponent<PrecursorDoorway>();
precursorDoorway.isOpen = metadata.IsOpen;
if (metadata.IsOpen)
{
precursorDoorway.BroadcastMessage("DisableField");
}
else
{
precursorDoorway.BroadcastMessage("EnableField");
}
}
}

View File

@@ -0,0 +1,19 @@
using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using UnityEngine;
namespace NitroxClient.GameLogic.Spawning.Metadata.Processor;
public class PrecursorKeyTerminalMetadataProcessor : EntityMetadataProcessor<PrecursorKeyTerminalMetadata>
{
public override void ProcessMetadata(GameObject gameObject, PrecursorKeyTerminalMetadata metadata)
{
Log.Debug($"Received precursor key terminal metadata change for {gameObject.name} with data of {metadata}");
PrecursorKeyTerminal precursorKeyTerminal = gameObject.GetComponent<PrecursorKeyTerminal>();
if (precursorKeyTerminal)
{
precursorKeyTerminal.slotted = metadata.Slotted;
}
}
}

View File

@@ -0,0 +1,19 @@
using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using UnityEngine;
namespace NitroxClient.GameLogic.Spawning.Metadata.Processor;
public class PrecursorTeleporterActivationTerminalMetadataProcessor : EntityMetadataProcessor<PrecursorTeleporterActivationTerminalMetadata>
{
public override void ProcessMetadata(GameObject gameObject, PrecursorTeleporterActivationTerminalMetadata metadata)
{
Log.Debug($"Received precursor teleporter activation terminal metadata change for {gameObject.name} with data of {metadata}");
PrecursorTeleporterActivationTerminal precursorTeleporterActivationTerminal = gameObject.GetComponent<PrecursorTeleporterActivationTerminal>();
if (precursorTeleporterActivationTerminal)
{
precursorTeleporterActivationTerminal.unlocked = metadata.Unlocked;
}
}
}

View File

@@ -0,0 +1,19 @@
using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using UnityEngine;
namespace NitroxClient.GameLogic.Spawning.Metadata.Processor;
public class PrecursorTeleporterMetadataProcessor : EntityMetadataProcessor<PrecursorTeleporterMetadata>
{
public override void ProcessMetadata(GameObject gameObject, PrecursorTeleporterMetadata metadata)
{
Log.Debug($"Received precursor teleporter metadata change for {gameObject.name} with data of {metadata}");
PrecursorTeleporter precursorTeleporter = gameObject.GetComponent<PrecursorTeleporter>();
if (precursorTeleporter)
{
precursorTeleporter.ToggleDoor(metadata.IsOpen);
}
}
}

View File

@@ -0,0 +1,23 @@
using NitroxClient.Communication;
using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using NitroxModel.Packets;
using UnityEngine;
namespace NitroxClient.GameLogic.Spawning.Metadata.Processor;
public class RadiationMetadataProcessor : EntityMetadataProcessor<RadiationMetadata>
{
public override void ProcessMetadata(GameObject gameObject, RadiationMetadata metadata)
{
if (!gameObject.TryGetComponent(out LiveMixin liveMixin))
{
Log.Error($"[{nameof(RadiationMetadataProcessor)}] Couldn't find LiveMixin on {gameObject}");
return;
}
using (PacketSuppressor<EntityMetadataUpdate>.Suppress())
{
Resolve<LiveMixinManager>().SyncRemoteHealth(liveMixin, metadata.Health);
}
}
}

View File

@@ -0,0 +1,157 @@
using System.Collections.Generic;
using System.Linq;
using NitroxClient.Communication;
using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
using NitroxClient.Unity.Helper;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using NitroxModel.Packets;
using UnityEngine;
using static Rocket;
namespace NitroxClient.GameLogic.Spawning.Metadata.Processor;
public class RocketMetadataProcessor : EntityMetadataProcessor<RocketMetadata>
{
// For newly connected players, we will only build the previous stage with construction bots for a certain time period.
private const float MAX_ALLOWABLE_TIME_FOR_CONSTRUCTOR_BOTS = 10;
/** Rocket states :
* 0 : Launch Platform
* 1 : Gantry
* 2 : Boosters
* 3 : Fuel Reserve
* 4 : Cockpit
* 5 : Final rocket
**/
public override void ProcessMetadata(GameObject gameObject, RocketMetadata metadata)
{
Rocket rocket = gameObject.GetComponent<Rocket>();
if (!rocket)
{
Log.Error($"Could not find Rocket on {gameObject.name}");
return;
}
using (PacketSuppressor<EntityMetadataUpdate>.Suppress())
{
UpdateElevator(rocket, metadata);
UpdateStage(rocket, metadata);
UpdatePreflightChecks(rocket, metadata);
}
}
private void UpdateElevator(Rocket rocket, RocketMetadata metadata)
{
// elevators will only be present on this model after the gantry (p1) is built
if (rocket.currentRocketStage > 1)
{
rocket.elevatorPosition = metadata.ElevatorPosition;
rocket.elevatorState = (RocketElevatorStates)metadata.ElevatorState;
rocket.SetElevatorPosition();
}
}
private void UpdateStage(Rocket rocket, RocketMetadata metadata)
{
if (rocket.currentRocketStage == metadata.CurrentStage)
{
return;
}
bool allowConstructorBots = DayNightCycle.main.timePassedAsFloat - metadata.LastStageTransitionTime < MAX_ALLOWABLE_TIME_FOR_CONSTRUCTOR_BOTS;
RocketConstructor rocketConstructor = rocket.RequireComponentInChildren<RocketConstructor>(true);
for (int stage = rocket.currentRocketStage; stage < metadata.CurrentStage; stage++)
{
bool lastStageToBuild = stage == metadata.CurrentStage - 1;
GameObject build = rocket.StartRocketConstruction();
// We only want to use construction bots for the last constructed stage (just in case the client is out dated by multiple stages).
// For all others, just force the completion of that stage.
if (lastStageToBuild && allowConstructorBots)
{
rocketConstructor.SendBuildBots(build);
}
else
{
VFXConstructing vfxConstructing = build.GetComponent<VFXConstructing>();
vfxConstructing.EndGracefully();
}
}
}
private void UpdatePreflightChecks(Rocket rocket, RocketMetadata metadata)
{
if (rocket.currentRocketStage < 4)
{
return;
}
IEnumerable<PreflightCheck> completedChecks = metadata.PreflightChecks.Select(i => (PreflightCheck)i);
RocketPreflightCheckManager rocketPreflightCheckManager = rocket.RequireComponent<RocketPreflightCheckManager>();
foreach (PreflightCheck completedCheck in completedChecks)
{
if (!rocketPreflightCheckManager.preflightChecks.Contains(completedCheck))
{
CompletePreflightCheck(rocket, completedCheck);
rocketPreflightCheckManager.CompletePreflightCheck(completedCheck);
}
}
}
private void CompletePreflightCheck(Rocket rocket, PreflightCheck preflightCheck)
{
bool isCockpitCheck = preflightCheck == PreflightCheck.LifeSupport ||
preflightCheck == PreflightCheck.PrimaryComputer;
if (isCockpitCheck)
{
CompleteCockpitPreflightCheck(rocket, preflightCheck);
}
else
{
CompleteBasicPreflightCheck(rocket, preflightCheck);
}
}
private void CompleteCockpitPreflightCheck(Rocket rocket, PreflightCheck preflightCheck)
{
CockpitSwitch[] cockpitSwitches = rocket.GetComponentsInChildren<CockpitSwitch>(true);
foreach (CockpitSwitch cockpitSwitch in cockpitSwitches)
{
if (!cockpitSwitch.completed && cockpitSwitch.preflightCheck == preflightCheck)
{
cockpitSwitch.animator.SetBool("Completed", true);
cockpitSwitch.completed = true;
if (cockpitSwitch.collision)
{
cockpitSwitch.collision.SetActive(false);
}
}
}
}
private void CompleteBasicPreflightCheck(Rocket rocket, PreflightCheck preflightCheck)
{
ThrowSwitch[] throwSwitches = rocket.GetComponentsInChildren<ThrowSwitch>(true);
foreach (ThrowSwitch throwSwitch in throwSwitches)
{
if (!throwSwitch.completed && throwSwitch.preflightCheck == preflightCheck)
{
throwSwitch.animator.AliveOrNull()?.SetTrigger("Throw");
throwSwitch.completed = true;
throwSwitch.cinematicTrigger.showIconOnHandHover = false;
throwSwitch.triggerCollider.enabled = false;
throwSwitch.lamp.GetComponent<SkinnedMeshRenderer>().material = throwSwitch.completeMat;
}
}
}
}

View File

@@ -0,0 +1,37 @@
using System;
using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using NitroxModel_Subnautica.DataStructures;
using UnityEngine;
namespace NitroxClient.GameLogic.Spawning.Metadata.Processor;
public class SeaTreaderMetadataProcessor : EntityMetadataProcessor<SeaTreaderMetadata>
{
public override void ProcessMetadata(GameObject gameObject, SeaTreaderMetadata metadata)
{
if (gameObject.TryGetComponent(out SeaTreader seaTreader))
{
if (!seaTreader.isInitialized)
{
seaTreader.InitializeOnce();
}
seaTreader.reverseDirection = metadata.ReverseDirection;
float grazingTimeLeft = Math.Max(0, metadata.GrazingEndTime - DayNightCycle.main.timePassedAsFloat);
seaTreader.grazing = grazingTimeLeft > 0;
seaTreader.grazingTimeLeft = grazingTimeLeft;
seaTreader.leashPosition = metadata.LeashPosition.ToUnity();
seaTreader.leashPosition.y = gameObject.transform.position.y;
seaTreader.isInitialized = true;
seaTreader.InitializeAgain();
}
else
{
Log.Error($"Could not find {nameof(SeaTreader)} on {gameObject.name}");
}
}
}

View File

@@ -0,0 +1,25 @@
using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using UnityEngine;
namespace NitroxClient.GameLogic.Spawning.Metadata.Processor;
public class SealedDoorMetadataProcessor : EntityMetadataProcessor<SealedDoorMetadata>
{
public override void ProcessMetadata(GameObject gameObject, SealedDoorMetadata metadata)
{
Log.Info($"Received door metadata change for {gameObject.name} with data of {metadata}");
Sealed door = gameObject.GetComponent<Sealed>();
door._sealed = metadata.Sealed;
door.openedAmount = metadata.OpenedAmount;
LaserCutObject laseredObject = gameObject.GetComponent<LaserCutObject>();
if (laseredObject && door._sealed)
{
laseredObject.lastCutValue = door.openedAmount;
laseredObject.ActivateFX();
}
}
}

View File

@@ -0,0 +1,44 @@
using NitroxClient.Communication;
using NitroxClient.GameLogic.FMOD;
using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using NitroxModel.Packets;
using UnityEngine;
namespace NitroxClient.GameLogic.Spawning.Metadata.Processor;
public class SeamothMetadataProcessor : VehicleMetadataProcessor<SeamothMetadata>
{
public SeamothMetadataProcessor(LiveMixinManager liveMixinManager) : base(liveMixinManager)
{ }
public override void ProcessMetadata(GameObject gameObject, SeamothMetadata metadata)
{
if (!gameObject.TryGetComponent(out SeaMoth seamoth))
{
Log.ErrorOnce($"[{nameof(SeamothMetadataProcessor)}] Could not find {nameof(SeaMoth)} on {gameObject}");
return;
}
if (!gameObject.TryGetComponent(out SubName subName))
{
Log.ErrorOnce($"[{nameof(SeamothMetadataProcessor)}] Could not find {nameof(SubName)} on {gameObject}");
return;
}
using (PacketSuppressor<EntityMetadataUpdate>.Suppress())
{
SetLights(seamoth, metadata.LightsOn);
SetHealth(seamoth.gameObject, metadata.Health);
SetNameAndColors(subName, metadata.Name, metadata.Colors);
}
}
private void SetLights(SeaMoth seamoth, bool lightsOn)
{
using (FMODSystem.SuppressSendingSounds())
{
seamoth.toggleLights.SetLightsActive(lightsOn);
}
}
}

View File

@@ -0,0 +1,23 @@
using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using UnityEngine;
namespace NitroxClient.GameLogic.Spawning.Metadata.Processor;
public class StarshipDoorMetadataProcessor : EntityMetadataProcessor<StarshipDoorMetadata>
{
public override void ProcessMetadata(GameObject gameObject, StarshipDoorMetadata metadata)
{
StarshipDoor starshipDoor = gameObject.GetComponent<StarshipDoor>();
starshipDoor.doorOpen = metadata.DoorOpen;
starshipDoor.doorLocked = metadata.DoorLocked;
if (metadata.DoorLocked)
{
starshipDoor.LockDoor();
}
else
{
starshipDoor.UnlockDoor();
}
}
}

View File

@@ -0,0 +1,26 @@
using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using NitroxModel_Subnautica.DataStructures;
using UnityEngine;
namespace NitroxClient.GameLogic.Spawning.Metadata.Processor;
public class StayAtLeashPositionMetadataProcessor : EntityMetadataProcessor<StayAtLeashPositionMetadata>
{
public override void ProcessMetadata(GameObject gameObject, StayAtLeashPositionMetadata metadata)
{
if (!gameObject.TryGetComponent(out Creature creature))
{
Log.Error($"Could not find {nameof(Creature)} on {gameObject.name}");
return;
}
if (!creature.isInitialized)
{
// TODO: When #2137 is merged, only a MetadataHolder to the creature and postfix patch creature.Start to consume it
creature.InitializeOnce();
creature.isInitialized = true;
}
creature.leashPosition = metadata.LeashPosition.ToUnity();
}
}

View File

@@ -0,0 +1,53 @@
using System.Linq;
using NitroxClient.Communication;
using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
using NitroxClient.Unity.Helper;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using NitroxModel.DataStructures.Unity;
using NitroxModel.Packets;
using NitroxModel_Subnautica.DataStructures;
using UnityEngine;
namespace NitroxClient.GameLogic.Spawning.Metadata.Processor;
public class SubNameInputMetadataProcessor : EntityMetadataProcessor<SubNameInputMetadata>
{
public override void ProcessMetadata(GameObject gameObject, SubNameInputMetadata metadata)
{
if (!gameObject.TryGetComponent(out SubNameInput subNameInput))
{
Log.ErrorOnce($"[{nameof(SubNameInputMetadataProcessor)}] Could not find {nameof(SubNameInput)} on {gameObject}");
return;
}
SubName subName = subNameInput.target;
if (!subName && !subNameInput.TryGetComponent(out subName))
{
Log.ErrorOnce($"[{nameof(SubNameInputMetadataProcessor)}] {gameObject}'s {nameof(subNameInput)} doesn't have a target.");
return;
}
// Ensure the SubNameInput's object is active so that it receives events from its SubName
gameObject.SetActive(true);
using (PacketSuppressor<EntityMetadataUpdate>.Suppress())
{
// Name and color applying must be applied before SelectedColorIndex
SetNameAndColors(subName, metadata.Name, metadata.Colors);
subNameInput.SetSelected(metadata.SelectedColorIndex);
}
}
public static void SetNameAndColors(SubName subName, string text, NitroxVector3[] nitroxColors)
{
if (!string.IsNullOrEmpty(text))
{
subName.DeserializeName(text);
}
if (nitroxColors != null)
{
Vector3[] colors = nitroxColors.Select(c => c.ToUnity()).ToArray();
subName.DeserializeColors(colors);
}
}
}

View File

@@ -0,0 +1,55 @@
using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
using NitroxClient.MonoBehaviours;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using UnityEngine;
namespace NitroxClient.GameLogic.Spawning.Metadata.Processor;
public class WaterParkCreatureMetadataProcessor : EntityMetadataProcessor<WaterParkCreatureMetadata>
{
public override void ProcessMetadata(GameObject gameObject, WaterParkCreatureMetadata metadata)
{
if (!gameObject.TryGetComponent(out WaterParkCreature waterParkCreature))
{
Log.Error($"[{nameof(WaterParkCreatureMetadataProcessor)}] Could not find {nameof(WaterParkCreature)} on {gameObject.name}");
return;
}
if (waterParkCreature.currentWaterPark)
{
// MatureTime is important for fishes that are already in a WaterPark, to calculate the right age
waterParkCreature.matureTime = metadata.MatureTime;
double startTime = metadata.MatureTime - waterParkCreature.data.growingPeriod;
waterParkCreature.age = Mathf.InverseLerp((float)startTime, (float)metadata.MatureTime, DayNightCycle.main.timePassedAsFloat);
waterParkCreature.timeNextBreed = metadata.TimeNextBreed;
}
else
{
// Age is the only important constant for fishes that are in an item state
waterParkCreature.matureTime = -1;
waterParkCreature.age = metadata.Age;
waterParkCreature.timeNextBreed = -1;
}
// Scaling according to WaterParkCreature.ManagedUpdate
waterParkCreature.transform.localScale = Mathf.Lerp(waterParkCreature.data.initialSize, waterParkCreature.data.maxSize, waterParkCreature.age) * Vector3.one;
waterParkCreature.isMature = waterParkCreature.age == 1f;
waterParkCreature.bornInside = metadata.BornInside;
// This field is not serialized but is always the exact same so it's supposedly recomputed but it would break with our system
// (calculation from WaterParkCreature.ManagedUpdate)
waterParkCreature.breedInterval = waterParkCreature.data.growingPeriod * 0.5f;
// While being fully loaded, the base is inactive and coroutines shouldn't be started (they'll throw an exception)
// To avoid, that we postpone their execution to 1 more second which is enough because time is frozen during initial sync
// This is the mating condition from WaterParkCreature.ManagedUpdate to postpone mating
if (Multiplayer.Main && !Multiplayer.Main.InitialSyncCompleted && waterParkCreature.currentWaterPark && waterParkCreature.GetCanBreed() &&
waterParkCreature.timeNextBreed != -1 && DayNightCycle.main.timePassedAsFloat > waterParkCreature.timeNextBreed)
{
waterParkCreature.timeNextBreed = DayNightCycle.main.timePassedAsFloat + 1;
}
waterParkCreature.OnProtoDeserialize(null);
}
}

View File

@@ -0,0 +1,14 @@
using NitroxClient.GameLogic.Spawning.Metadata.Processor.Abstract;
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
using UnityEngine;
namespace NitroxClient.GameLogic.Spawning.Metadata.Processor;
public class WeldableWallPanelGenericMetadataProcessor : EntityMetadataProcessor<WeldableWallPanelGenericMetadata>
{
public override void ProcessMetadata(GameObject gameObject, WeldableWallPanelGenericMetadata metadata)
{
WeldableWallPanelGeneric weldableWallPanelGeneric = gameObject.GetComponent<WeldableWallPanelGeneric>();
weldableWallPanelGeneric.liveMixin.health = metadata.LiveMixInHealth;
}
}