diff --git a/NewHorizons/Builder/Volumes/SpeedLimiterVolumeBuilder.cs b/NewHorizons/Builder/Volumes/SpeedLimiterVolumeBuilder.cs new file mode 100644 index 00000000..d28b0a24 --- /dev/null +++ b/NewHorizons/Builder/Volumes/SpeedLimiterVolumeBuilder.cs @@ -0,0 +1,20 @@ +using NewHorizons.Components.Volumes; +using NewHorizons.External.Modules.Volumes.VolumeInfos; +using UnityEngine; + +namespace NewHorizons.Builder.Volumes +{ + public static class SpeedLimiterVolumeBuilder + { + public static SpeedLimiterVolume Make(GameObject planetGO, Sector sector, SpeedLimiterVolumeInfo info) + { + var volume = VolumeBuilder.Make(planetGO, sector, info); + + volume.maxSpeed = info.maxSpeed; + volume.stoppingDistance = info.stoppingDistance; + volume.maxEntryAngle = info.maxEntryAngle; + + return volume; + } + } +} diff --git a/NewHorizons/Builder/Volumes/VolumesBuildManager.cs b/NewHorizons/Builder/Volumes/VolumesBuildManager.cs index bdaa4a5a..fa713082 100644 --- a/NewHorizons/Builder/Volumes/VolumesBuildManager.cs +++ b/NewHorizons/Builder/Volumes/VolumesBuildManager.cs @@ -191,6 +191,13 @@ namespace NewHorizons.Builder.Volumes SpeedTrapVolumeBuilder.Make(go, sector, speedTrapVolume); } } + if (config.Volumes.speedLimiterVolumes != null) + { + foreach (var speedLimiterVolume in config.Volumes.speedLimiterVolumes) + { + SpeedLimiterVolumeBuilder.Make(go, sector, speedLimiterVolume); + } + } if (config.Volumes.lightSourceVolumes != null) { foreach (var lightSourceVolume in config.Volumes.lightSourceVolumes) diff --git a/NewHorizons/Components/Volumes/SpeedLimiterVolume.cs b/NewHorizons/Components/Volumes/SpeedLimiterVolume.cs new file mode 100644 index 00000000..f5d1e4af --- /dev/null +++ b/NewHorizons/Components/Volumes/SpeedLimiterVolume.cs @@ -0,0 +1,166 @@ +using System.Collections.Generic; +using System; +using UnityEngine; + +namespace NewHorizons.Components.Volumes +{ + public class SpeedLimiterVolume : BaseVolume + { + public float maxSpeed = 10f; + public float stoppingDistance = 100f; + public float maxEntryAngle = 60f; + + private OWRigidbody _parentBody; + private List _trackedBodies = new List(); + private bool _playerJustExitedDream; + + public override void Awake() + { + _parentBody = GetComponentInParent(); + base.Awake(); + GlobalMessenger.AddListener("ExitDreamWorld", OnExitDreamWorld); + } + + public void Start() + { + enabled = false; + } + + public override void OnDestroy() + { + base.OnDestroy(); + GlobalMessenger.RemoveListener("ExitDreamWorld", OnExitDreamWorld); + } + + public void FixedUpdate() + { + foreach (var trackedBody in _trackedBodies) + { + bool slowed = false; + Vector3 velocity = trackedBody.body.GetVelocity() - _parentBody.GetVelocity(); + float magnitude = velocity.magnitude; + if (magnitude <= maxSpeed) + { + slowed = true; + } + else + { + bool needsSlowing = true; + float velocityReduction = trackedBody.deceleration * Time.deltaTime; + float requiredReduction = maxSpeed - magnitude; + if (requiredReduction > velocityReduction) + { + velocityReduction = requiredReduction; + slowed = true; + } + if (trackedBody.name == Detector.Name.Ship) + { + Autopilot component = Locator.GetShipTransform().GetComponent(); + if (component != null && component.IsFlyingToDestination()) + { + needsSlowing = false; + } + } + if (needsSlowing) + { + Vector3 velocityChange = velocityReduction * velocity.normalized; + trackedBody.body.AddVelocityChange(velocityChange); + if (trackedBody.name == Detector.Name.Ship && PlayerState.IsInsideShip()) + { + Locator.GetPlayerBody().AddVelocityChange(velocityChange); + } + } + } + if (slowed) + { + if (trackedBody.name == Detector.Name.Ship) + GlobalMessenger.FireEvent("ShipExitSpeedLimiter"); + _trackedBodies.Remove(trackedBody); + if (_trackedBodies.Count == 0) + enabled = false; + } + } + } + + private void OnExitDreamWorld() + { + _playerJustExitedDream = true; + } + + public override void OnTriggerVolumeEntry(GameObject hitObj) + { + DynamicForceDetector component = hitObj.GetComponent(); + if (component == null || !component.CompareNameMask(Detector.Name.Player | Detector.Name.Probe | Detector.Name.Ship)) return; + + if (component.GetName() == Detector.Name.Player && (PlayerState.IsInsideShip() || _playerJustExitedDream)) + { + _playerJustExitedDream = false; + } + else + { + OWRigidbody attachedOWRigidbody = component.GetAttachedOWRigidbody(); + Vector3 from = transform.position - attachedOWRigidbody.GetPosition(); + Vector3 to = attachedOWRigidbody.GetVelocity() - _parentBody.GetVelocity(); + float magnitude = to.magnitude; + if (magnitude > maxSpeed && Vector3.Angle(from, to) < maxEntryAngle) + { + float deceleration = (maxSpeed * maxSpeed - magnitude * magnitude) / (2f * stoppingDistance); + TrackedBody trackedBody = new TrackedBody(attachedOWRigidbody, component.GetName(), deceleration); + _trackedBodies.Add(trackedBody); + if (component.GetName() == Detector.Name.Ship) + GlobalMessenger.FireEvent("ShipEnterSpeedLimiter"); + enabled = true; + } + } + } + + public override void OnTriggerVolumeExit(GameObject hitObj) + { + DynamicForceDetector component = hitObj.GetComponent(); + if (component == null) return; + + OWRigidbody body = component.GetAttachedOWRigidbody(); + TrackedBody trackedBody = _trackedBodies.Find((TrackedBody i) => i.body == body); + if (trackedBody != null) + { + if (trackedBody.name == Detector.Name.Ship) + GlobalMessenger.FireEvent("ShipExitSpeedLimiter"); + + _trackedBodies.Remove(trackedBody); + + if (_trackedBodies.Count == 0) + enabled = false; + } + } + + [Serializable] + protected class TrackedBody + { + public OWRigidbody body; + + public Detector.Name name; + + public float deceleration; + + public bool decelerated; + + public TrackedBody(OWRigidbody body, Detector.Name name, float deceleration) + { + this.body = body; + this.name = name; + this.deceleration = deceleration; + } + + public override bool Equals(object obj) + { + if (obj is TrackedBody trackedBody) + { + return trackedBody.body == body && trackedBody.name == name; + } + return base.Equals(obj); + } + + public override int GetHashCode() => body.GetHashCode(); + } + } +} diff --git a/NewHorizons/External/Modules/Volumes/VolumeInfos/SpeedLimiterVolumeInfo.cs b/NewHorizons/External/Modules/Volumes/VolumeInfos/SpeedLimiterVolumeInfo.cs new file mode 100644 index 00000000..f6f2fca9 --- /dev/null +++ b/NewHorizons/External/Modules/Volumes/VolumeInfos/SpeedLimiterVolumeInfo.cs @@ -0,0 +1,28 @@ +using Newtonsoft.Json; +using System.ComponentModel; +using UnityEngine; + +namespace NewHorizons.External.Modules.Volumes.VolumeInfos +{ + [JsonObject] + public class SpeedLimiterVolumeInfo : VolumeInfo + { + /// + /// The speed the volume will slow you down to when you enter it. + /// + [DefaultValue(10f)] + public float maxSpeed = 10f; + + /// + /// + /// + [DefaultValue(100f)] + public float stoppingDistance = 100f; + + /// + /// + /// + [DefaultValue(60f)] + public float maxEntryAngle = 60f; + } +} diff --git a/NewHorizons/External/Modules/Volumes/VolumesModule.cs b/NewHorizons/External/Modules/Volumes/VolumesModule.cs index 6e3afe19..e9791a51 100644 --- a/NewHorizons/External/Modules/Volumes/VolumesModule.cs +++ b/NewHorizons/External/Modules/Volumes/VolumesModule.cs @@ -101,6 +101,13 @@ namespace NewHorizons.External.Modules.Volumes /// public SpeedTrapVolumeInfo[] speedTrapVolumes; + /// + /// Add speed limiter volumes to this planet. + /// Slows down the player, ship, and probe when they enter this volume. + /// Used on the Stranger in DLC. + /// + public SpeedLimiterVolumeInfo[] speedLimiterVolumes; + /// /// Add visor effect volumes to this planet. ///