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,91 @@
using NitroxClient.GameLogic;
using NitroxModel.Packets;
using UnityEngine;
namespace NitroxClient.MonoBehaviours.Vehicles;
public class CyclopsMovementReplicator : VehicleMovementReplicator
{
protected static readonly int CYCLOPS_YAW = Animator.StringToHash("cyclops_yaw");
protected static readonly int CYCLOPS_PITCH = Animator.StringToHash("cyclops_pitch");
private SubControl subControl;
private RemotePlayer drivingPlayer;
private bool throttleApplied;
private float steeringWheelYaw;
public void Awake()
{
subControl = GetComponent<SubControl>();
}
public new void Update()
{
base.Update();
if (subControl.canAccel && throttleApplied)
{
// See SubControl.Update
var topClamp = subControl.useThrottleIndex switch
{
1 => 0.66f,
2 => 1f,
_ => 0.33f,
};
subControl.engineRPMManager.AccelerateInput(topClamp);
for (int i = 0; i < subControl.throttleHandlers.Length; i++)
{
subControl.throttleHandlers[i].OnSubAppliedThrottle();
}
}
if (Mathf.Abs(steeringWheelYaw) > 0.1f)
{
ShipSide shipSide = steeringWheelYaw > 0 ? ShipSide.Port : ShipSide.Starboard;
for (int i = 0; i < subControl.turnHandlers.Length; i++)
{
subControl.turnHandlers[i].OnSubTurn(shipSide);
}
}
}
public override void ApplyNewMovementData(MovementData newMovementData)
{
if (newMovementData is not DrivenVehicleMovementData vehicleMovementData)
{
return;
}
steeringWheelYaw = vehicleMovementData.SteeringWheelYaw;
float steeringWheelPitch = vehicleMovementData.SteeringWheelPitch;
// See SubControl.UpdateAnimation
subControl.steeringWheelYaw = steeringWheelYaw;
subControl.steeringWheelPitch = steeringWheelPitch;
if (subControl.mainAnimator)
{
subControl.mainAnimator.SetFloat(VIEW_YAW, subControl.steeringWheelYaw);
subControl.mainAnimator.SetFloat(VIEW_PITCH, subControl.steeringWheelPitch);
if (drivingPlayer != null)
{
drivingPlayer.AnimationController.SetFloat(CYCLOPS_YAW, subControl.steeringWheelYaw);
drivingPlayer.AnimationController.SetFloat(CYCLOPS_PITCH, subControl.steeringWheelPitch);
}
}
throttleApplied = vehicleMovementData.ThrottleApplied;
}
public override void Enter(RemotePlayer drivingPlayer)
{
this.drivingPlayer = drivingPlayer;
}
public override void Exit()
{
drivingPlayer = null;
throttleApplied = false;
}
}

View File

@@ -0,0 +1,134 @@
using FMOD.Studio;
using NitroxClient.GameLogic;
using NitroxClient.GameLogic.FMOD;
using NitroxModel.GameLogic.FMOD;
using NitroxModel.Packets;
using UnityEngine;
namespace NitroxClient.MonoBehaviours.Vehicles;
public class ExosuitMovementReplicator : VehicleMovementReplicator
{
private Exosuit exosuit;
public Vector3 velocity;
private float jetLoopingSoundDistance;
private float thrustPower;
private bool jetsActive;
private float timeJetsActiveChanged;
public void Awake()
{
exosuit = GetComponent<Exosuit>();
SetupSound();
}
public new void Update()
{
Vector3 positionBefore = transform.position;
base.Update();
Vector3 positionAfter = transform.position;
velocity = (positionAfter - positionBefore) / Time.deltaTime;
float volume = FMODSystem.CalculateVolume(transform.position, Player.main.transform.position, jetLoopingSoundDistance, 1f);
EventInstance soundHandle = exosuit.loopingJetSound.playing ? exosuit.loopingJetSound.evt : exosuit.loopingJetSound.evtStop;
if (soundHandle.hasHandle())
{
soundHandle.setVolume(volume);
}
// See Exosuit.Update, thrust power simulation
if (jetsActive)
{
thrustPower = Mathf.Clamp01(thrustPower - Time.deltaTime * exosuit.thrustConsumption);
exosuit.thrustIntensity += Time.deltaTime / exosuit.timeForFullVirbation;
}
else
{
float num = Time.deltaTime * exosuit.thrustConsumption * 0.7f;
if (exosuit.onGround)
{
num = Time.deltaTime * exosuit.thrustConsumption * 4f;
}
thrustPower = Mathf.Clamp01(thrustPower + num);
exosuit.thrustIntensity -= Time.deltaTime * 10f;
}
exosuit.thrustIntensity = Mathf.Clamp01(exosuit.thrustIntensity);
if (timeJetsActiveChanged + 0.3f <= Time.time)
{
if (jetsActive && thrustPower > 0f)
{
exosuit.loopingJetSound.Play();
exosuit.fxcontrol.Play(0);
exosuit.areFXPlaying = true;
}
else
{
exosuit.loopingJetSound.Stop();
exosuit.fxcontrol.Stop(0);
exosuit.areFXPlaying = false;
}
}
}
public override void ApplyNewMovementData(MovementData newMovementData)
{
if (newMovementData is not DrivenVehicleMovementData vehicleMovementData)
{
return;
}
float steeringWheelYaw = vehicleMovementData.SteeringWheelYaw;
float steeringWheelPitch = vehicleMovementData.SteeringWheelPitch;
// See Vehicle.Update (reverse operation for vehicle.steeringWheel... = ...)
exosuit.steeringWheelYaw = steeringWheelPitch / 70f;
exosuit.steeringWheelPitch = steeringWheelPitch / 45f;
if (exosuit.mainAnimator)
{
exosuit.mainAnimator.SetFloat(VIEW_YAW, steeringWheelYaw);
exosuit.mainAnimator.SetFloat(VIEW_PITCH, steeringWheelPitch);
}
// See Exosuit.jetsActive setter
if (jetsActive != vehicleMovementData.ThrottleApplied)
{
jetsActive = vehicleMovementData.ThrottleApplied;
timeJetsActiveChanged = Time.time;
}
}
private void SetupSound()
{
this.Resolve<FMODWhitelist>().TryGetSoundData(exosuit.loopingJetSound.asset.path, out SoundData jetSoundData);
jetLoopingSoundDistance = jetSoundData.Radius;
if (FMODUWE.IsInvalidParameterId(exosuit.fmodIndexSpeed))
{
exosuit.fmodIndexSpeed = exosuit.ambienceSound.GetParameterIndex("speed");
}
if (FMODUWE.IsInvalidParameterId(exosuit.fmodIndexRotate))
{
exosuit.fmodIndexRotate = exosuit.ambienceSound.GetParameterIndex("rotate");
}
}
public override void Enter(RemotePlayer remotePlayer)
{
exosuit.SetIKEnabled(true);
exosuit.thrustIntensity = 0;
}
public override void Exit()
{
exosuit.SetIKEnabled(false);
exosuit.loopingJetSound.Stop(STOP_MODE.ALLOWFADEOUT);
exosuit.fxcontrol.Stop(0);
jetsActive = false;
}
}

View File

@@ -0,0 +1,120 @@
using FMOD.Studio;
using NitroxClient.GameLogic;
using NitroxModel.GameLogic.FMOD;
using NitroxModel.Packets;
using UnityEngine;
namespace NitroxClient.MonoBehaviours.Vehicles;
public class SeamothMovementReplicator : VehicleMovementReplicator
{
private SeaMoth seaMoth;
private FMOD_CustomLoopingEmitter rpmSound;
private FMOD_CustomEmitter revSound;
private FMOD_CustomEmitter enterSeamoth;
private float radiusRpmSound;
private float radiusRevSound;
private float radiusEnterSound;
private bool throttleApplied;
public void Awake()
{
seaMoth = GetComponent<SeaMoth>();
SetupSound();
}
public new void Update()
{
base.Update();
if (throttleApplied)
{
seaMoth.engineSound.AccelerateInput(1);
}
}
public override void ApplyNewMovementData(MovementData newMovementData)
{
if (newMovementData is not DrivenVehicleMovementData vehicleMovementData)
{
return;
}
float steeringWheelYaw = vehicleMovementData.SteeringWheelYaw;
float steeringWheelPitch = vehicleMovementData.SteeringWheelPitch;
// See Vehicle.Update (reverse operation for vehicle.steeringWheel... = ...)
seaMoth.steeringWheelYaw = steeringWheelYaw / 70f;
seaMoth.steeringWheelPitch = steeringWheelPitch / 45f;
if (seaMoth.mainAnimator)
{
seaMoth.mainAnimator.SetFloat(VIEW_YAW, steeringWheelYaw);
seaMoth.mainAnimator.SetFloat(VIEW_PITCH, steeringWheelPitch);
}
// Adjusting volume for the engine Sound
float distanceToPlayer = Vector3.Distance(Player.main.transform.position, transform.position);
float volumeRpmSound = SoundHelper.CalculateVolume(distanceToPlayer, radiusRpmSound, 1f);
float volumeRevSound = SoundHelper.CalculateVolume(distanceToPlayer, radiusRevSound, 1f);
rpmSound.GetEventInstance().setVolume(volumeRpmSound);
revSound.GetEventInstance().setVolume(volumeRevSound);
throttleApplied = vehicleMovementData.ThrottleApplied;
}
private void SetupSound()
{
rpmSound = seaMoth.engineSound.engineRpmSFX;
revSound = seaMoth.engineSound.engineRevUp;
enterSeamoth = seaMoth.enterSeamoth;
rpmSound.followParent = true;
revSound.followParent = true;
this.Resolve<FMODWhitelist>().IsWhitelisted(rpmSound.asset.path, out radiusRpmSound);
this.Resolve<FMODWhitelist>().IsWhitelisted(revSound.asset.path, out radiusRevSound);
this.Resolve<FMODWhitelist>().IsWhitelisted(seaMoth.enterSeamoth.asset.path, out radiusEnterSound);
rpmSound.GetEventInstance().setProperty(EVENT_PROPERTY.MINIMUM_DISTANCE, 1f);
revSound.GetEventInstance().setProperty(EVENT_PROPERTY.MINIMUM_DISTANCE, 1f);
enterSeamoth.GetEventInstance().setProperty(EVENT_PROPERTY.MINIMUM_DISTANCE, 1f);
rpmSound.GetEventInstance().setProperty(EVENT_PROPERTY.MAXIMUM_DISTANCE, radiusRpmSound);
revSound.GetEventInstance().setProperty(EVENT_PROPERTY.MAXIMUM_DISTANCE, radiusRevSound);
enterSeamoth.GetEventInstance().setProperty(EVENT_PROPERTY.MAXIMUM_DISTANCE, radiusEnterSound);
if (FMODUWE.IsInvalidParameterId(seaMoth.fmodIndexSpeed))
{
seaMoth.fmodIndexSpeed = seaMoth.ambienceSound.GetParameterIndex("speed");
}
}
public override void Enter(RemotePlayer remotePlayer)
{
seaMoth.bubbles.Play();
if (enterSeamoth)
{
// After first run, this sound will still be in "playing" mode so we need to release it by hand
enterSeamoth.Stop();
enterSeamoth.ReleaseEvent();
enterSeamoth.CacheEventInstance();
float distanceToPlayer = Vector3.Distance(Player.main.transform.position, transform.position);
float sound = SoundHelper.CalculateVolume(distanceToPlayer, radiusEnterSound, 1f);
enterSeamoth.evt.setVolume(sound);
enterSeamoth.Play();
}
}
public override void Exit()
{
seaMoth.bubbles.Stop();
throttleApplied = false;
}
}

View File

@@ -0,0 +1,13 @@
using NitroxClient.GameLogic;
using UnityEngine;
namespace NitroxClient.MonoBehaviours.Vehicles;
public abstract class VehicleMovementReplicator : MovementReplicator
{
protected static readonly int VIEW_YAW = Animator.StringToHash("view_yaw");
protected static readonly int VIEW_PITCH = Animator.StringToHash("view_pitch");
public abstract void Enter(RemotePlayer remotePlayer);
public abstract void Exit();
}

View File

@@ -0,0 +1,147 @@
using NitroxModel.DataStructures;
using NitroxModel.Packets;
using NitroxModel_Subnautica.DataStructures;
using UnityEngine;
namespace NitroxClient.MonoBehaviours.Vehicles;
public class WatchedEntry
{
/// <remarks>
/// In unity position units. Refer to <see cref="ShouldBroadcastMovement"/> for use infos.
/// </remarks>
private const float MINIMAL_MOVEMENT_TRESHOLD = 0.05f;
/// <remarks>
/// In degrees (°). Refer to <see cref="ShouldBroadcastMovement"/> for use infos.
/// </remarks>
private const float MINIMAL_ROTATION_TRESHOLD = 0.05f;
/// <remarks>
/// In seconds. Refer to <see cref="ShouldBroadcastMovement"/> for use infos.
/// </remarks>
private const float MAX_TIME_WITHOUT_BROADCAST = 5f;
/// <inheritdoc cref="MAX_TIME_WITHOUT_BROADCAST"/>
private const float SAFETY_BROADCAST_WINDOW = 0.2f;
private readonly NitroxId Id;
private readonly Transform transform;
private readonly Vehicle vehicle;
private readonly SubControl subControl;
private float latestBroadcastTime;
private Vector3 latestLocalPositionSent;
private Quaternion latestLocalRotationSent;
public WatchedEntry(NitroxId Id, Transform transform)
{
this.Id = Id;
this.transform = transform;
vehicle = transform.GetComponent<Vehicle>();
subControl = transform.GetComponent<SubControl>();
}
private bool IsDrivenVehicle()
{
return vehicle && Player.main.currentMountedVehicle == vehicle;
}
private bool IsDrivenCyclops()
{
return subControl && Player.main.currentSub == subControl.sub && Player.main.mode == Player.Mode.Piloting;
}
public MovementData GetMovementData(NitroxId id)
{
// Packets should be filled with more data if the vehicle is being driven by the local player
if (IsDrivenVehicle())
{
// Those two values are set between -1 and 1 so we can easily scale them up while still in range for sbyte
sbyte steeringWheelYaw = (sbyte)(Mathf.Clamp(vehicle.steeringWheelYaw, -1, 1) * 70f);
sbyte steeringWheelPitch = (sbyte)(Mathf.Clamp(vehicle.steeringWheelPitch, -1, 1) * 45f);
bool throttleApplied = false;
Vector3 input = AvatarInputHandler.main.IsEnabled() ? GameInput.GetMoveDirection() : Vector3.zero;
// See SeaMoth.UpdateSounds
if (vehicle is SeaMoth)
{
throttleApplied = input.magnitude > 0f;
}
// See Exosuit.Update
else if (vehicle is Exosuit)
{
throttleApplied = input.y > 0f;
}
return new DrivenVehicleMovementData(id, transform.position.ToDto(), transform.rotation.ToDto(), steeringWheelYaw, steeringWheelPitch, throttleApplied);
}
if (IsDrivenCyclops())
{
// Cyclop steering wheel's yaw and pitch are between -90 and 90 so they're already in range for sbyte
sbyte steeringWheelYaw = (sbyte)Mathf.Clamp(subControl.steeringWheelYaw, -90, 90);
sbyte steeringWheelPitch = (sbyte)Mathf.Clamp(subControl.steeringWheelPitch, -90, 90);
// See SubControl.Update
bool throttleApplied = subControl.throttle.magnitude > 0.0001f;
return new DrivenVehicleMovementData(id, transform.position.ToDto(), transform.rotation.ToDto(), steeringWheelYaw, steeringWheelPitch, throttleApplied);
}
// Normal case in which the vehicule isn't driven by the local player
return new SimpleMovementData(id, transform.position.ToDto(), transform.rotation.ToDto());
}
public void OnBroadcastPosition()
{
latestLocalPositionSent = transform.localPosition;
latestLocalRotationSent = transform.localRotation;
}
private bool HasVehicleMoved()
{
return Vector3.Distance(latestLocalPositionSent, transform.localPosition) > MINIMAL_MOVEMENT_TRESHOLD ||
Quaternion.Angle(latestLocalRotationSent, transform.localRotation) > MINIMAL_ROTATION_TRESHOLD;
}
/// <summary>
/// Rate limiter which prevents all non-moving vehicles from sending too many packets following some rules:
/// - the driven vehicle is not rate limited
/// - position changes less than <see cref="MINIMAL_MOVEMENT_TRESHOLD"/> are ignored
/// - rotation changes less than <see cref="MINIMAL_ROTATION_TRESHOLD"/> are ignored
/// - every period of <see cref="MAX_TIME_WITHOUT_BROADCAST"/>, there's a <see cref="SAFETY_BROADCAST_WINDOW"/>
/// during which movements packets are sent to avoid any packet drop's bad effect, regardless of <see cref="HasVehicleMoved"/>
/// </summary>
/// <remarks>
/// <see cref="latestBroadcastTime"/> is not updated during the <see cref="SAFETY_BROADCAST_WINDOW"/> so we can recognize this window
/// </remarks>
public bool ShouldBroadcastMovement()
{
// Watched entry validity check (e.g. for vehicle death)
if (!transform)
{
MovementBroadcaster.UnregisterWatched(Id);
return false;
}
float deltaTimeSinceBroadcast = DayNightCycle.main.timePassedAsFloat - latestBroadcastTime;
if (IsDrivenCyclops() || IsDrivenVehicle() || deltaTimeSinceBroadcast < 0 || HasVehicleMoved())
{
// As long as the vehicle has moved, we can reset the broadcast timer
latestBroadcastTime = DayNightCycle.main.timePassedAsFloat;
return true;
}
if (deltaTimeSinceBroadcast > MAX_TIME_WITHOUT_BROADCAST)
{
if (deltaTimeSinceBroadcast > MAX_TIME_WITHOUT_BROADCAST + SAFETY_BROADCAST_WINDOW)
{
// only reset the broadcast timer after the safety window has elapsed
latestBroadcastTime = DayNightCycle.main.timePassedAsFloat;
}
return true;
}
return false;
}
}