From 5aace8408a1667cae45a0e3698637910fc17e595 Mon Sep 17 00:00:00 2001 From: Joshua Thome Date: Sat, 22 Feb 2025 11:02:10 -0600 Subject: [PATCH 01/57] Generic shape/collider configs --- NewHorizons/Builder/Props/ShapeBuilder.cs | 140 ++++++++++++++++++ .../External/Modules/Props/ShapeInfo.cs | 92 ++++++++++++ 2 files changed, 232 insertions(+) create mode 100644 NewHorizons/Builder/Props/ShapeBuilder.cs create mode 100644 NewHorizons/External/Modules/Props/ShapeInfo.cs diff --git a/NewHorizons/Builder/Props/ShapeBuilder.cs b/NewHorizons/Builder/Props/ShapeBuilder.cs new file mode 100644 index 00000000..e7594f6e --- /dev/null +++ b/NewHorizons/Builder/Props/ShapeBuilder.cs @@ -0,0 +1,140 @@ +using NewHorizons.Components; +using NewHorizons.External.Modules.Props; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace NewHorizons.Builder.Props +{ + public static class ShapeBuilder + { + public static Component AddShapeOrCollider(GameObject go, ShapeInfo info) + { + if (info.useShape.HasValue) + { + // Explicitly add either a shape or collider if specified + if (info.useShape.Value) + return AddShape(go, info); + else + return AddCollider(go, info); + } + else + { + // Prefer colliders over shapes if no preference is specified + if (info.type is ShapeType.Sphere or ShapeType.Box or ShapeType.Capsule) + return AddCollider(go, info); + else + return AddShape(go, info); + } + } + + public static Shape AddShape(GameObject go, ShapeInfo info) + { + if (info.hasCollision) + { + throw new NotSupportedException($"Shapes do not support collision; set {info.hasCollision} to false and use a supported collider type (sphere, box, or capsule)."); + } + if (info.useShape.HasValue && !info.useShape.Value) + { + throw new NotSupportedException($"{info.useShape} was explicitly set to false but a shape is required here."); + } + switch (info.type) + { + case ShapeType.Sphere: + var sphereShape = go.AddComponent(); + sphereShape._radius = info.radius; + sphereShape._center = info.offset ?? Vector3.zero; + return sphereShape; + case ShapeType.Box: + var boxShape = go.AddComponent(); + boxShape._size = info.size ?? Vector3.one; + boxShape._center = info.offset ?? Vector3.zero; + return boxShape; + case ShapeType.Capsule: + var capsuleShape = go.AddComponent(); + capsuleShape._radius = info.radius; + capsuleShape._direction = (int)info.direction; + capsuleShape._height = info.height; + capsuleShape._center = info.offset ?? Vector3.zero; + return capsuleShape; + case ShapeType.Cylinder: + var cylinderShape = go.AddComponent(); + cylinderShape._radius = info.radius; + cylinderShape._height = info.height; + cylinderShape._center = info.offset ?? Vector3.zero; + return cylinderShape; + case ShapeType.Cone: + var coneShape = go.AddComponent(); + coneShape._topRadius = info.innerRadius; + coneShape._bottomRadius = info.outerRadius; + coneShape._direction = (int)info.direction; + coneShape._height = info.height; + coneShape._center = info.offset ?? Vector3.zero; + return coneShape; + case ShapeType.Hemisphere: + var hemisphereShape = go.AddComponent(); + hemisphereShape._radius = info.radius; + hemisphereShape._direction = (int)info.direction; + hemisphereShape._cap = info.cap; + hemisphereShape._center = info.offset ?? Vector3.zero; + return hemisphereShape; + case ShapeType.Hemicapsule: + var hemicapsuleShape = go.AddComponent(); + hemicapsuleShape._radius = info.radius; + hemicapsuleShape._direction = (int)info.direction; + hemicapsuleShape._height = info.height; + hemicapsuleShape._cap = info.cap; + hemicapsuleShape._center = info.offset ?? Vector3.zero; + return hemicapsuleShape; + case ShapeType.Ring: + var ringShape = go.AddComponent(); + ringShape.innerRadius = info.innerRadius; + ringShape.outerRadius = info.outerRadius; + ringShape.height = info.height; + ringShape.center = info.offset ?? Vector3.zero; + return ringShape; + default: + throw new ArgumentOutOfRangeException(nameof(info.type), info.type, $"Unsupported shape type"); + } + } + + public static Collider AddCollider(GameObject go, ShapeInfo info) + { + if (info.useShape.HasValue && info.useShape.Value) + { + throw new NotSupportedException($"{info.useShape} was explicitly set to true but a non-shape collider is required here."); + } + switch (info.type) + { + case ShapeType.Sphere: + var sphereCollider = go.AddComponent(); + sphereCollider.radius = info.radius; + sphereCollider.center = info.offset ?? Vector3.zero; + sphereCollider.isTrigger = !info.hasCollision; + go.GetAddComponent(); + return sphereCollider; + case ShapeType.Box: + var boxCollider = go.AddComponent(); + boxCollider.size = info.size ?? Vector3.one; + boxCollider.center = info.offset ?? Vector3.zero; + boxCollider.isTrigger = !info.hasCollision; + go.GetAddComponent(); + return boxCollider; + case ShapeType.Capsule: + var capsuleCollider = go.AddComponent(); + capsuleCollider.radius = info.radius; + capsuleCollider.direction = (int)info.direction; + capsuleCollider.height = info.height; + capsuleCollider.center = info.offset ?? Vector3.zero; + capsuleCollider.isTrigger = !info.hasCollision; + go.GetAddComponent(); + return capsuleCollider; + default: + throw new ArgumentOutOfRangeException(nameof(info.type), info.type, $"Unsupported collider type"); + } + } + } +} diff --git a/NewHorizons/External/Modules/Props/ShapeInfo.cs b/NewHorizons/External/Modules/Props/ShapeInfo.cs new file mode 100644 index 00000000..19b81ecb --- /dev/null +++ b/NewHorizons/External/Modules/Props/ShapeInfo.cs @@ -0,0 +1,92 @@ +using NewHorizons.External.SerializableData; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace NewHorizons.External.Modules.Props +{ + [JsonObject] + public class ShapeInfo + { + /// + /// The type of shape or collider to add. Sphere, box, and capsule colliders are more performant and support collision. Defaults to sphere. + /// + public ShapeType type = ShapeType.Sphere; + + /// + /// The radius of the shape or collider. Defaults to 0.5 meters. Only used by spheres, capsules, cylinders, hemispheres, hemicapsules, and rings. + /// + public float radius = 0.5f; + + /// + /// The height of the shape or collider. Defaults to 1 meter. Only used by capsules, cylinders, cones, hemicapsules, and rings. + /// + public float height = 1f; + + /// + /// The axis that the shape or collider is aligned with. Defaults to the Y axis (up). The flat bottom of the shape will be pointing towards the negative axis. Only used by capsules, cones, hemispheres, and hemicapsules. + /// + public ColliderAxis direction = ColliderAxis.Y; + + /// + /// The inner radius of the shape. Defaults to 0 meters. Only used by cones and rings. + /// + public float innerRadius = 0f; + + /// + /// The outer radius of the shape. Defaults to 0.5 meters. Only used by cones and rings. + /// + public float outerRadius = 0.5f; + + /// + /// Whether the shape has an end cap. Defaults to true. Only used by hemispheres and hemicapsules. + /// + public bool cap = true; + + /// + /// The size of the shape or collider. Defaults to (1,1,1). Only used by boxes. + /// + public MVector3 size; + + /// + /// The offset of the shape or collider from the object's origin. Defaults to (0,0,0). Supported by all collider and shape types. + /// + public MVector3 offset; + + /// + /// Whether the collider should have collision enabled. If false, the collider will be a trigger. Defaults to false. Only supported for spheres, boxes, and capsules. + /// + public bool hasCollision = false; + + /// + /// Whether to explicitly use a shape instead of a collider. Shapes do not support collision and are less performant, but support a wider set of shapes and are required by some components. Omit this unless you explicitly want to use a sphere, box, or capsule shape instead of a collider. + /// + public bool? useShape; + } + + [JsonConverter(typeof(StringEnumConverter))] + public enum ShapeType + { + [EnumMember(Value = @"sphere")] Sphere, + [EnumMember(Value = @"box")] Box, + [EnumMember(Value = @"capsule")] Capsule, + [EnumMember(Value = @"cylinder")] Cylinder, + [EnumMember(Value = @"cone")] Cone, + [EnumMember(Value = @"hemisphere")] Hemisphere, + [EnumMember(Value = @"hemicapsule")] Hemicapsule, + [EnumMember(Value = @"ring")] Ring, + } + + [JsonConverter(typeof(StringEnumConverter))] + public enum ColliderAxis + { + [EnumMember(Value = @"x")] X = 0, + [EnumMember(Value = @"y")] Y = 1, + [EnumMember(Value = @"z")] Z = 2, + } +} From 4a817980781b2ba795a917a3c1372c6e9f65b388 Mon Sep 17 00:00:00 2001 From: Joshua Thome Date: Sat, 22 Feb 2025 11:03:32 -0600 Subject: [PATCH 02/57] Add force volumes and give volumes rotation --- .../Builder/Volumes/ForceVolumeBuilder.cs | 119 ++++++++++++++++++ .../Builder/Volumes/VolumesBuildManager.cs | 38 ++++++ .../External/Modules/Volumes/ForceModule.cs | 40 ++++++ .../VolumeInfos/CylindricalForceVolumeInfo.cs | 24 ++++ .../VolumeInfos/DirectionalForceVolumeInfo.cs | 35 ++++++ .../Volumes/VolumeInfos/ForceVolumeInfo.cs | 40 ++++++ .../Volumes/VolumeInfos/GravityVolumeInfo.cs | 44 +++++++ .../VolumeInfos/PolarForceVolumeInfo.cs | 23 ++++ .../VolumeInfos/RadialForceVolumeInfo.cs | 32 +++++ .../Modules/Volumes/VolumeInfos/VolumeInfo.cs | 2 +- .../External/Modules/Volumes/VolumesModule.cs | 5 + 11 files changed, 401 insertions(+), 1 deletion(-) create mode 100644 NewHorizons/Builder/Volumes/ForceVolumeBuilder.cs create mode 100644 NewHorizons/External/Modules/Volumes/ForceModule.cs create mode 100644 NewHorizons/External/Modules/Volumes/VolumeInfos/CylindricalForceVolumeInfo.cs create mode 100644 NewHorizons/External/Modules/Volumes/VolumeInfos/DirectionalForceVolumeInfo.cs create mode 100644 NewHorizons/External/Modules/Volumes/VolumeInfos/ForceVolumeInfo.cs create mode 100644 NewHorizons/External/Modules/Volumes/VolumeInfos/GravityVolumeInfo.cs create mode 100644 NewHorizons/External/Modules/Volumes/VolumeInfos/PolarForceVolumeInfo.cs create mode 100644 NewHorizons/External/Modules/Volumes/VolumeInfos/RadialForceVolumeInfo.cs diff --git a/NewHorizons/Builder/Volumes/ForceVolumeBuilder.cs b/NewHorizons/Builder/Volumes/ForceVolumeBuilder.cs new file mode 100644 index 00000000..12e1f83f --- /dev/null +++ b/NewHorizons/Builder/Volumes/ForceVolumeBuilder.cs @@ -0,0 +1,119 @@ +using NewHorizons.Builder.Props; +using NewHorizons.External; +using NewHorizons.External.Modules; +using NewHorizons.External.Modules.Volumes.VolumeInfos; +using NewHorizons.Utility.OuterWilds; +using OWML.Common; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace NewHorizons.Builder.Volumes +{ + public static class ForceVolumeBuilder + { + public static CylindricalForceVolume Make(GameObject planetGO, Sector sector, CylindricalForceVolumeInfo info) + { + var forceVolume = Make(planetGO, sector, info); + forceVolume._acceleration = info.force; + forceVolume._localAxis = info.normal ?? Vector3.up; + forceVolume._playGravityCrystalAudio = info.playGravityCrystalAudio; + forceVolume.gameObject.SetActive(true); + return forceVolume; + } + + public static DirectionalForceVolume Make(GameObject planetGO, Sector sector, DirectionalForceVolumeInfo info) + { + var forceVolume = Make(planetGO, sector, info); + forceVolume._fieldDirection = info.normal ?? Vector3.up; + forceVolume._fieldMagnitude = info.force; + forceVolume._affectsAlignment = info.affectsAlignment; + forceVolume._offsetCentripetalForce = info.offsetCentripetalForce; + forceVolume._playGravityCrystalAudio = info.playGravityCrystalAudio; + forceVolume.gameObject.SetActive(true); + return forceVolume; + } + + public static GravityVolume Make(GameObject planetGO, Sector sector, GravityVolumeInfo info) + { + var forceVolume = Make(planetGO, sector, info); + forceVolume._isPlanetGravityVolume = false; + forceVolume._setMass = false; + forceVolume._surfaceAcceleration = info.force; + forceVolume._upperSurfaceRadius = info.upperRadius; + forceVolume._lowerSurfaceRadius = info.lowerRadius; + forceVolume._cutoffAcceleration = info.minForce; + forceVolume._cutoffRadius = info.minRadius; + forceVolume._alignmentRadius = info.alignmentRadius ?? info.upperRadius * 1.5f; + forceVolume._falloffType = info.fallOff switch + { + GravityFallOff.Linear => GravityVolume.FalloffType.linear, + GravityFallOff.InverseSquared => GravityVolume.FalloffType.inverseSquared, + _ => throw new NotImplementedException(), + }; + forceVolume.gameObject.SetActive(true); + return forceVolume; + } + + public static PolarForceVolume Make(GameObject planetGO, Sector sector, PolarForceVolumeInfo info) + { + var forceVolume = Make(planetGO, sector, info); + forceVolume._acceleration = info.force; + forceVolume._localAxis = info.normal ?? Vector3.up; + forceVolume._fieldMode = info.tangential ? PolarForceVolume.ForceMode.Tangential : PolarForceVolume.ForceMode.Polar; + forceVolume.gameObject.SetActive(true); + return forceVolume; + } + + public static RadialForceVolume Make(GameObject planetGO, Sector sector, RadialForceVolumeInfo info) + { + var forceVolume = Make(planetGO, sector, info); + forceVolume._acceleration = info.force; + forceVolume._falloff = info.fallOff switch + { + RadialForceVolumeInfo.FallOff.Constant => RadialForceVolume.Falloff.Constant, + RadialForceVolumeInfo.FallOff.Linear => RadialForceVolume.Falloff.Linear, + RadialForceVolumeInfo.FallOff.InverseSquared => RadialForceVolume.Falloff.InvSqr, + _ => throw new NotImplementedException(), + }; + forceVolume.gameObject.SetActive(true); + return forceVolume; + } + + public static T Make(GameObject planetGO, Sector sector, ForceVolumeInfo info) where T : ForceVolume + { + var go = GeneralPropBuilder.MakeNew(typeof(T).Name, planetGO, sector, info); + go.layer = Layer.BasicEffectVolume; + + var owTriggerVolume = go.AddComponent(); + + if (info.shape != null) + { + var shapeOrCol = ShapeBuilder.AddShapeOrCollider(go, info.shape); + if (shapeOrCol is Shape shape) + owTriggerVolume._shape = shape; + else if (shapeOrCol is Collider col) + owTriggerVolume._owCollider = col.GetComponent(); + } + else + { + var col = go.AddComponent(); + col.radius = info.radius; + col.isTrigger = true; + var owCollider = go.GetAddComponent(); + + owTriggerVolume._owCollider = owCollider; + } + + var forceVolume = go.AddComponent(); + forceVolume._priority = info.priority; + forceVolume._alignmentPriority = info.alignmentPriority; + forceVolume._inheritable = info.inheritable; + + return forceVolume; + } + } +} diff --git a/NewHorizons/Builder/Volumes/VolumesBuildManager.cs b/NewHorizons/Builder/Volumes/VolumesBuildManager.cs index bdaa4a5a..acd48bf1 100644 --- a/NewHorizons/Builder/Volumes/VolumesBuildManager.cs +++ b/NewHorizons/Builder/Volumes/VolumesBuildManager.cs @@ -112,6 +112,44 @@ namespace NewHorizons.Builder.Volumes FluidVolumeBuilder.Make(go, sector, fluidVolume); } } + if (config.Volumes.forces != null) + { + if (config.Volumes.forces.cylindricalVolumes != null) + { + foreach (var cylindricalVolume in config.Volumes.forces.cylindricalVolumes) + { + ForceVolumeBuilder.Make(go, sector, cylindricalVolume); + } + } + if (config.Volumes.forces.directionalVolumes != null) + { + foreach (var directionalVolume in config.Volumes.forces.directionalVolumes) + { + ForceVolumeBuilder.Make(go, sector, directionalVolume); + } + } + if (config.Volumes.forces.gravityVolumes != null) + { + foreach (var gravityVolume in config.Volumes.forces.gravityVolumes) + { + ForceVolumeBuilder.Make(go, sector, gravityVolume); + } + } + if (config.Volumes.forces.polarVolumes != null) + { + foreach (var polarVolume in config.Volumes.forces.polarVolumes) + { + ForceVolumeBuilder.Make(go, sector, polarVolume); + } + } + if (config.Volumes.forces.radialVolumes != null) + { + foreach (var radialVolume in config.Volumes.forces.radialVolumes) + { + ForceVolumeBuilder.Make(go, sector, radialVolume); + } + } + } if (config.Volumes.probe != null) { if (config.Volumes.probe.destructionVolumes != null) diff --git a/NewHorizons/External/Modules/Volumes/ForceModule.cs b/NewHorizons/External/Modules/Volumes/ForceModule.cs new file mode 100644 index 00000000..8250d3e1 --- /dev/null +++ b/NewHorizons/External/Modules/Volumes/ForceModule.cs @@ -0,0 +1,40 @@ +using NewHorizons.External.Modules.Volumes.VolumeInfos; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NewHorizons.External.Modules.Volumes +{ + [JsonObject] + public class ForceModule + { + /// + /// Applies a constant force along the volume's XZ plane towards the volume's center. Affects alignment. + /// + public CylindricalForceVolumeInfo[] cylindricalVolumes; + + /// + /// Applies a constant force in the direction of the volume's Y axis. May affect alignment. + /// + public DirectionalForceVolumeInfo[] directionalVolumes; + + /// + /// Applies planet-like gravity towards the volume's center with falloff by distance. May affect alignment. + /// For actual planetary body gravity, use the properties in the Base module. + /// + public GravityVolumeInfo[] gravityVolumes; + + /// + /// Applies a constant force towards the volume's center. Affects alignment. + /// + public PolarForceVolumeInfo[] polarVolumes; + + /// + /// Applies a force towards the volume's center with falloff by distance. Affects alignment. + /// + public RadialForceVolumeInfo[] radialVolumes; + } +} diff --git a/NewHorizons/External/Modules/Volumes/VolumeInfos/CylindricalForceVolumeInfo.cs b/NewHorizons/External/Modules/Volumes/VolumeInfos/CylindricalForceVolumeInfo.cs new file mode 100644 index 00000000..5926056b --- /dev/null +++ b/NewHorizons/External/Modules/Volumes/VolumeInfos/CylindricalForceVolumeInfo.cs @@ -0,0 +1,24 @@ +using NewHorizons.External.SerializableData; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NewHorizons.External.Modules.Volumes.VolumeInfos +{ + [JsonObject] + public class CylindricalForceVolumeInfo : ForceVolumeInfo + { + /// + /// The direction that the force applied by this volume will be perpendicular to. Defaults to up (0, 1, 0). + /// + public MVector3 normal; + + /// + /// Whether to play the gravity crystal audio when the player is in this volume. + /// + public bool playGravityCrystalAudio; + } +} diff --git a/NewHorizons/External/Modules/Volumes/VolumeInfos/DirectionalForceVolumeInfo.cs b/NewHorizons/External/Modules/Volumes/VolumeInfos/DirectionalForceVolumeInfo.cs new file mode 100644 index 00000000..82af81d2 --- /dev/null +++ b/NewHorizons/External/Modules/Volumes/VolumeInfos/DirectionalForceVolumeInfo.cs @@ -0,0 +1,35 @@ +using NewHorizons.External.SerializableData; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NewHorizons.External.Modules.Volumes.VolumeInfos +{ + [JsonObject] + public class DirectionalForceVolumeInfo : ForceVolumeInfo + { + /// + /// The direction of the force applied by this volume. Defaults to up (0, 1, 0). + /// + public MVector3 normal; + + /// + /// Whether this force volume affects alignment. Defaults to true. + /// + [DefaultValue(true)] public bool affectsAlignment = true; + + /// + /// Whether the force applied by this volume takes the centripetal force of the volume's parent body into account. Defaults to false. + /// + public bool offsetCentripetalForce; + + /// + /// Whether to play the gravity crystal audio when the player is in this volume. + /// + public bool playGravityCrystalAudio; + } +} diff --git a/NewHorizons/External/Modules/Volumes/VolumeInfos/ForceVolumeInfo.cs b/NewHorizons/External/Modules/Volumes/VolumeInfos/ForceVolumeInfo.cs new file mode 100644 index 00000000..4d5fe636 --- /dev/null +++ b/NewHorizons/External/Modules/Volumes/VolumeInfos/ForceVolumeInfo.cs @@ -0,0 +1,40 @@ +using NewHorizons.External.Modules.Props; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NewHorizons.External.Modules.Volumes.VolumeInfos +{ + [JsonObject] + public class ForceVolumeInfo : PriorityVolumeInfo + { + /// + /// The shape of this volume. Defaults to a sphere shape with a radius of `radius`. + /// + public ShapeInfo shape; + + /// + /// The force applied by this volume. Can be negative to reverse the direction. + /// + public float force; + + /// + /// The priority of this force volume for the purposes of alignment. + /// + /// Volumes of higher priority will override volumes of lower priority. Volumes of the same priority will stack like normal. + /// Ex: A player in a gravity volume with priority 0, and zero-gravity volume with priority 1, will feel zero gravity. + /// + /// Default value here is 1 instead of 0 so it automatically overrides planet gravity, which is 0 by default. + /// + [DefaultValue(1)] public int alignmentPriority = 1; + + /// + /// Whether this force volume is inheritable. The most recently activated inheritable force volume will stack with other force volumes even if their priorities differ. + /// + public bool inheritable; + } +} diff --git a/NewHorizons/External/Modules/Volumes/VolumeInfos/GravityVolumeInfo.cs b/NewHorizons/External/Modules/Volumes/VolumeInfos/GravityVolumeInfo.cs new file mode 100644 index 00000000..44b226fc --- /dev/null +++ b/NewHorizons/External/Modules/Volumes/VolumeInfos/GravityVolumeInfo.cs @@ -0,0 +1,44 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NewHorizons.External.Modules.Volumes.VolumeInfos +{ + [JsonObject] + public class GravityVolumeInfo : ForceVolumeInfo + { + /// + /// The upper bounds of the volume's "surface". Above this radius, the force applied by this volume will have falloff applied. + /// + public float upperRadius; + + /// + /// The lower bounds of the volume's "surface". Above this radius and below the `upperRadius`, the force applied by this volume will be constant. Defaults to 0. + /// + [DefaultValue(0f)] public float lowerRadius; + + /// + /// The volume's force will decrease linearly from `force` to `minForce` as distance decreases from `lowerRadius` to `minRadius`. Defaults to 0. + /// + [DefaultValue(0f)] public float minRadius; + + /// + /// The minimum force applied by this volume between `lowerRadius` and `minRadius`. Defaults to 0. + /// + [DefaultValue(0f)] public float minForce; + + /// + /// How the force falls off with distance. Most planets use linear but the sun and some moons use inverseSquared. + /// + [DefaultValue("linear")] public GravityFallOff fallOff = GravityFallOff.Linear; + + /// + /// The radius where objects will be aligned to the volume's force. Defaults to 1.5x the `upperRadius`. Set to 0 to disable alignment. + /// + public float? alignmentRadius; + } +} diff --git a/NewHorizons/External/Modules/Volumes/VolumeInfos/PolarForceVolumeInfo.cs b/NewHorizons/External/Modules/Volumes/VolumeInfos/PolarForceVolumeInfo.cs new file mode 100644 index 00000000..bdf1ee0d --- /dev/null +++ b/NewHorizons/External/Modules/Volumes/VolumeInfos/PolarForceVolumeInfo.cs @@ -0,0 +1,23 @@ +using NewHorizons.External.SerializableData; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NewHorizons.External.Modules.Volumes.VolumeInfos +{ + [JsonObject] + public class PolarForceVolumeInfo : ForceVolumeInfo + { + /// + /// Tangential mode only. The force applied by this volume will be perpendicular to this direction and the direction to the other body. Defaults to up (0, 1, 0). + /// + public MVector3 normal; + /// + /// Enables tangential mode. The force applied by this volume will be perpendicular to the normal and the direction to the other body. Defaults to false. + /// + public bool tangential; + } +} diff --git a/NewHorizons/External/Modules/Volumes/VolumeInfos/RadialForceVolumeInfo.cs b/NewHorizons/External/Modules/Volumes/VolumeInfos/RadialForceVolumeInfo.cs new file mode 100644 index 00000000..eefe58e4 --- /dev/null +++ b/NewHorizons/External/Modules/Volumes/VolumeInfos/RadialForceVolumeInfo.cs @@ -0,0 +1,32 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace NewHorizons.External.Modules.Volumes.VolumeInfos +{ + [JsonObject] + public class RadialForceVolumeInfo : ForceVolumeInfo + { + /// + /// How the force falls off with distance. Defaults to linear. + /// + [DefaultValue("linear")] public FallOff fallOff = FallOff.Linear; + + [JsonConverter(typeof(StringEnumConverter))] + public enum FallOff + { + [EnumMember(Value = @"constant")] Constant = 0, + + [EnumMember(Value = @"linear")] Linear = 1, + + [EnumMember(Value = @"inverseSquared")] + InverseSquared = 2 + } + } +} diff --git a/NewHorizons/External/Modules/Volumes/VolumeInfos/VolumeInfo.cs b/NewHorizons/External/Modules/Volumes/VolumeInfos/VolumeInfo.cs index 7b106614..17cee4bb 100644 --- a/NewHorizons/External/Modules/Volumes/VolumeInfos/VolumeInfo.cs +++ b/NewHorizons/External/Modules/Volumes/VolumeInfos/VolumeInfo.cs @@ -4,7 +4,7 @@ using System.ComponentModel; namespace NewHorizons.External.Modules.Volumes.VolumeInfos { [JsonObject] - public class VolumeInfo : GeneralPointPropInfo + public class VolumeInfo : GeneralPropInfo { /// /// The radius of this volume. diff --git a/NewHorizons/External/Modules/Volumes/VolumesModule.cs b/NewHorizons/External/Modules/Volumes/VolumesModule.cs index 6e3afe19..6b5fc30a 100644 --- a/NewHorizons/External/Modules/Volumes/VolumesModule.cs +++ b/NewHorizons/External/Modules/Volumes/VolumesModule.cs @@ -27,6 +27,11 @@ namespace NewHorizons.External.Modules.Volumes /// public FluidVolumeInfo[] fluidVolumes; + /// + /// Add force volumes to this planet. + /// + public ForceModule forces; + /// /// Add hazard volumes to this planet. /// Causes damage to player when inside this volume. From be1b408da34cae6ad8c4e560e4085d3632d574e0 Mon Sep 17 00:00:00 2001 From: Joshua Thome Date: Sat, 22 Feb 2025 11:03:47 -0600 Subject: [PATCH 03/57] Clarify RevealVolume maxAngle --- .../External/Modules/Volumes/VolumeInfos/RevealVolumeInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NewHorizons/External/Modules/Volumes/VolumeInfos/RevealVolumeInfo.cs b/NewHorizons/External/Modules/Volumes/VolumeInfos/RevealVolumeInfo.cs index 2f577918..49a917c7 100644 --- a/NewHorizons/External/Modules/Volumes/VolumeInfos/RevealVolumeInfo.cs +++ b/NewHorizons/External/Modules/Volumes/VolumeInfos/RevealVolumeInfo.cs @@ -29,7 +29,7 @@ namespace NewHorizons.External.Modules.Volumes.VolumeInfos } /// - /// The max view angle (in degrees) the player can see the volume with to unlock the fact (`observe` only) + /// The max view angle (in degrees) the player can see the volume with to unlock the fact (`observe` only). This will effectively be a cone extending from the volume's center forwards (along the Z axis) based on the volume's rotation. /// [DefaultValue(180f)] public float maxAngle = 180f; // Observe Only From a9a06cfb70fbcf43dd4ce696a93941d1372f32a6 Mon Sep 17 00:00:00 2001 From: Ben C Date: Sat, 22 Feb 2025 17:06:24 +0000 Subject: [PATCH 04/57] Updated Schemas --- NewHorizons/Schemas/body_schema.json | 800 ++++++++++++++++++++++++++- 1 file changed, 793 insertions(+), 7 deletions(-) diff --git a/NewHorizons/Schemas/body_schema.json b/NewHorizons/Schemas/body_schema.json index f8c63685..32f57b50 100644 --- a/NewHorizons/Schemas/body_schema.json +++ b/NewHorizons/Schemas/body_schema.json @@ -1121,6 +1121,11 @@ "format": "float", "default": 0.5 }, + "colliderIsTrigger": { + "type": "boolean", + "description": "Whether the added sphere collider will be a trigger (interactible but does not collide). Defaults to true.", + "default": true + }, "droppable": { "type": "boolean", "description": "Whether the item can be dropped. Defaults to true.", @@ -1230,6 +1235,17 @@ "format": "float", "default": 2.0 }, + "colliderRadius": { + "type": "number", + "description": "Default collider radius when interacting with the socket", + "format": "float", + "default": 0.0 + }, + "colliderIsTrigger": { + "type": "boolean", + "description": "Whether the added sphere collider will be a trigger (interactible but does not collide). Defaults to true.", + "default": true + }, "useGiveTakePrompts": { "type": "boolean", "description": "Whether to use \"Give Item\" / \"Take Item\" prompts instead of \"Insert Item\" / \"Remove Item\"." @@ -1259,12 +1275,6 @@ "removalFact": { "type": "string", "description": "A ship log fact to reveal when removing an item from this socket, or when the socket is empty." - }, - "colliderRadius": { - "type": "number", - "description": "Default collider radius when interacting with the socket", - "format": "float", - "default": 0.0 } } }, @@ -5328,6 +5338,10 @@ "$ref": "#/definitions/FluidVolumeInfo" } }, + "forces": { + "description": "Add force volumes to this planet.", + "$ref": "#/definitions/ForceModule" + }, "hazardVolumes": { "type": "array", "description": "Add hazard volumes to this planet.\nCauses damage to player when inside this volume.", @@ -5462,6 +5476,17 @@ "format": "float", "default": 1.0 }, + "rotation": { + "description": "Rotation of the object", + "$ref": "#/definitions/MVector3" + }, + "alignRadial": { + "type": [ + "boolean", + "null" + ], + "description": "Do we try to automatically align this object to stand upright relative to the body's center? Stacks with rotation.\nDefaults to true for geysers, tornados, and volcanoes, and false for everything else." + }, "position": { "description": "Position of the object", "$ref": "#/definitions/MVector3" @@ -5560,6 +5585,17 @@ "format": "float", "default": 1.0 }, + "rotation": { + "description": "Rotation of the object", + "$ref": "#/definitions/MVector3" + }, + "alignRadial": { + "type": [ + "boolean", + "null" + ], + "description": "Do we try to automatically align this object to stand upright relative to the body's center? Stacks with rotation.\nDefaults to true for geysers, tornados, and volcanoes, and false for everything else." + }, "position": { "description": "Position of the object", "$ref": "#/definitions/MVector3" @@ -5630,6 +5666,17 @@ "format": "float", "default": 1.0 }, + "rotation": { + "description": "Rotation of the object", + "$ref": "#/definitions/MVector3" + }, + "alignRadial": { + "type": [ + "boolean", + "null" + ], + "description": "Do we try to automatically align this object to stand upright relative to the body's center? Stacks with rotation.\nDefaults to true for geysers, tornados, and volcanoes, and false for everything else." + }, "position": { "description": "Position of the object", "$ref": "#/definitions/MVector3" @@ -5713,6 +5760,17 @@ "format": "float", "default": 1.0 }, + "rotation": { + "description": "Rotation of the object", + "$ref": "#/definitions/MVector3" + }, + "alignRadial": { + "type": [ + "boolean", + "null" + ], + "description": "Do we try to automatically align this object to stand upright relative to the body's center? Stacks with rotation.\nDefaults to true for geysers, tornados, and volcanoes, and false for everything else." + }, "position": { "description": "Position of the object", "$ref": "#/definitions/MVector3" @@ -5754,6 +5812,580 @@ } } }, + "ForceModule": { + "type": "object", + "additionalProperties": false, + "properties": { + "cylindricalVolumes": { + "type": "array", + "description": "Applies a constant force along the volume's XZ plane towards the volume's center. Affects alignment.", + "items": { + "$ref": "#/definitions/CylindricalForceVolumeInfo" + } + }, + "directionalVolumes": { + "type": "array", + "description": "Applies a constant force in the direction of the volume's Y axis. May affect alignment.", + "items": { + "$ref": "#/definitions/DirectionalForceVolumeInfo" + } + }, + "gravityVolumes": { + "type": "array", + "description": "Applies planet-like gravity towards the volume's center with falloff by distance. May affect alignment.\nFor actual planetary body gravity, use the properties in the Base module.", + "items": { + "$ref": "#/definitions/GravityVolumeInfo" + } + }, + "polarVolumes": { + "type": "array", + "description": "Applies a constant force towards the volume's center. Affects alignment.", + "items": { + "$ref": "#/definitions/PolarForceVolumeInfo" + } + }, + "radialVolumes": { + "type": "array", + "description": "Applies a force towards the volume's center with falloff by distance. Affects alignment.", + "items": { + "$ref": "#/definitions/RadialForceVolumeInfo" + } + } + } + }, + "CylindricalForceVolumeInfo": { + "type": "object", + "additionalProperties": false, + "properties": { + "shape": { + "description": "The shape of this volume. Defaults to a sphere shape with a radius of `radius`.", + "$ref": "#/definitions/ShapeInfo" + }, + "force": { + "type": "number", + "description": "The force applied by this volume. Can be negative to reverse the direction.", + "format": "float" + }, + "alignmentPriority": { + "type": "integer", + "description": "The priority of this force volume for the purposes of alignment.\n\nVolumes of higher priority will override volumes of lower priority. Volumes of the same priority will stack like normal.\nEx: A player in a gravity volume with priority 0, and zero-gravity volume with priority 1, will feel zero gravity.\n \nDefault value here is 1 instead of 0 so it automatically overrides planet gravity, which is 0 by default. ", + "format": "int32", + "default": 1 + }, + "inheritable": { + "type": "boolean", + "description": "Whether this force volume is inheritable. The most recently activated inheritable force volume will stack with other force volumes even if their priorities differ." + }, + "layer": { + "type": "integer", + "description": "The layer of this volume.\n\nLayers separate the priority system. The priority of volumes in one layer will not affect or override volumes in another. The highest priority volume in each layer will stack like normal.\nThe exception is layer 0. A higher-priority volume in layer 0 will override lower-priority volumes in ALL other layers. A lower-priority volume in layer 0 will stack with other layers like normal.\n \nEx: A player could be affected by the sun on layer 9 priority 0 and planet gravity on layer 3 priority 2. They would experience the gravity of both volumes since they are on different layers.\nIf there was a zero-g volume on layer 0 priority 1, since it is on layer 0 it will override the gravity from the sun (priority 0 which is less than 1) but they will still feel the \ngravity of the planet (priority 2 is greater than 1). The zero-g volume will also still be applied because it is on a different layer.\n \nDefault value here is 0 which means this volume's priority will be evaluated against all other priority volumes regardless of their layer.", + "format": "int32", + "default": 0 + }, + "priority": { + "type": "integer", + "description": "The priority of this volume.\n\nVolumes of higher priority will override volumes of lower priority. Volumes of the same priority will stack like normal.\nEx: A player in a gravity volume with priority 0, and zero-gravity volume with priority 1, will feel zero gravity.\n \nDefault value here is 1 instead of 0 so it automatically overrides planet gravity, which is 0 by default. ", + "format": "int32", + "default": 1 + }, + "radius": { + "type": "number", + "description": "The radius of this volume.", + "format": "float", + "default": 1.0 + }, + "rotation": { + "description": "Rotation of the object", + "$ref": "#/definitions/MVector3" + }, + "alignRadial": { + "type": [ + "boolean", + "null" + ], + "description": "Do we try to automatically align this object to stand upright relative to the body's center? Stacks with rotation.\nDefaults to true for geysers, tornados, and volcanoes, and false for everything else." + }, + "position": { + "description": "Position of the object", + "$ref": "#/definitions/MVector3" + }, + "isRelativeToParent": { + "type": "boolean", + "description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object." + }, + "parentPath": { + "type": "string", + "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)." + }, + "rename": { + "type": "string", + "description": "An optional rename of this object" + }, + "normal": { + "description": "The direction that the force applied by this volume will be perpendicular to. Defaults to up (0, 1, 0).", + "$ref": "#/definitions/MVector3" + }, + "playGravityCrystalAudio": { + "type": "boolean", + "description": "Whether to play the gravity crystal audio when the player is in this volume." + } + } + }, + "ShapeInfo": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "description": "The type of shape or collider to add. Sphere, box, and capsule colliders are more performant and support collision. Defaults to sphere.", + "$ref": "#/definitions/ShapeType" + }, + "radius": { + "type": "number", + "description": "The radius of the shape or collider. Defaults to 0.5 meters. Only used by spheres, capsules, cylinders, hemispheres, hemicapsules, and rings.", + "format": "float" + }, + "height": { + "type": "number", + "description": "The height of the shape or collider. Defaults to 1 meter. Only used by capsules, cylinders, cones, hemicapsules, and rings.", + "format": "float" + }, + "direction": { + "description": "The axis that the shape or collider is aligned with. Defaults to the Y axis (up). The flat bottom of the shape will be pointing towards the negative axis. Only used by capsules, cones, hemispheres, and hemicapsules.", + "$ref": "#/definitions/ColliderAxis" + }, + "innerRadius": { + "type": "number", + "description": "The inner radius of the shape. Defaults to 0 meters. Only used by cones and rings.", + "format": "float" + }, + "outerRadius": { + "type": "number", + "description": "The outer radius of the shape. Defaults to 0.5 meters. Only used by cones and rings.", + "format": "float" + }, + "cap": { + "type": "boolean", + "description": "Whether the shape has an end cap. Defaults to true. Only used by hemispheres and hemicapsules." + }, + "size": { + "description": "The size of the shape or collider. Defaults to (1,1,1). Only used by boxes.", + "$ref": "#/definitions/MVector3" + }, + "offset": { + "description": "The offset of the shape or collider from the object's origin. Defaults to (0,0,0). Supported by all collider and shape types.", + "$ref": "#/definitions/MVector3" + }, + "hasCollision": { + "type": "boolean", + "description": "Whether the collider should have collision enabled. If false, the collider will be a trigger. Defaults to false. Only supported for spheres, boxes, and capsules." + }, + "useShape": { + "type": [ + "boolean", + "null" + ], + "description": "Whether to explicitly use a shape instead of a collider. Shapes do not support collision and are less performant, but support a wider set of shapes and are required by some components. Omit this unless you explicitly want to use a sphere, box, or capsule shape instead of a collider." + } + } + }, + "ShapeType": { + "type": "string", + "description": "", + "x-enumNames": [ + "Sphere", + "Box", + "Capsule", + "Cylinder", + "Cone", + "Hemisphere", + "Hemicapsule", + "Ring" + ], + "enum": [ + "sphere", + "box", + "capsule", + "cylinder", + "cone", + "hemisphere", + "hemicapsule", + "ring" + ] + }, + "ColliderAxis": { + "type": "string", + "description": "", + "x-enumNames": [ + "X", + "Y", + "Z" + ], + "enum": [ + "x", + "y", + "z" + ] + }, + "DirectionalForceVolumeInfo": { + "type": "object", + "additionalProperties": false, + "properties": { + "shape": { + "description": "The shape of this volume. Defaults to a sphere shape with a radius of `radius`.", + "$ref": "#/definitions/ShapeInfo" + }, + "force": { + "type": "number", + "description": "The force applied by this volume. Can be negative to reverse the direction.", + "format": "float" + }, + "alignmentPriority": { + "type": "integer", + "description": "The priority of this force volume for the purposes of alignment.\n\nVolumes of higher priority will override volumes of lower priority. Volumes of the same priority will stack like normal.\nEx: A player in a gravity volume with priority 0, and zero-gravity volume with priority 1, will feel zero gravity.\n \nDefault value here is 1 instead of 0 so it automatically overrides planet gravity, which is 0 by default. ", + "format": "int32", + "default": 1 + }, + "inheritable": { + "type": "boolean", + "description": "Whether this force volume is inheritable. The most recently activated inheritable force volume will stack with other force volumes even if their priorities differ." + }, + "layer": { + "type": "integer", + "description": "The layer of this volume.\n\nLayers separate the priority system. The priority of volumes in one layer will not affect or override volumes in another. The highest priority volume in each layer will stack like normal.\nThe exception is layer 0. A higher-priority volume in layer 0 will override lower-priority volumes in ALL other layers. A lower-priority volume in layer 0 will stack with other layers like normal.\n \nEx: A player could be affected by the sun on layer 9 priority 0 and planet gravity on layer 3 priority 2. They would experience the gravity of both volumes since they are on different layers.\nIf there was a zero-g volume on layer 0 priority 1, since it is on layer 0 it will override the gravity from the sun (priority 0 which is less than 1) but they will still feel the \ngravity of the planet (priority 2 is greater than 1). The zero-g volume will also still be applied because it is on a different layer.\n \nDefault value here is 0 which means this volume's priority will be evaluated against all other priority volumes regardless of their layer.", + "format": "int32", + "default": 0 + }, + "priority": { + "type": "integer", + "description": "The priority of this volume.\n\nVolumes of higher priority will override volumes of lower priority. Volumes of the same priority will stack like normal.\nEx: A player in a gravity volume with priority 0, and zero-gravity volume with priority 1, will feel zero gravity.\n \nDefault value here is 1 instead of 0 so it automatically overrides planet gravity, which is 0 by default. ", + "format": "int32", + "default": 1 + }, + "radius": { + "type": "number", + "description": "The radius of this volume.", + "format": "float", + "default": 1.0 + }, + "rotation": { + "description": "Rotation of the object", + "$ref": "#/definitions/MVector3" + }, + "alignRadial": { + "type": [ + "boolean", + "null" + ], + "description": "Do we try to automatically align this object to stand upright relative to the body's center? Stacks with rotation.\nDefaults to true for geysers, tornados, and volcanoes, and false for everything else." + }, + "position": { + "description": "Position of the object", + "$ref": "#/definitions/MVector3" + }, + "isRelativeToParent": { + "type": "boolean", + "description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object." + }, + "parentPath": { + "type": "string", + "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)." + }, + "rename": { + "type": "string", + "description": "An optional rename of this object" + }, + "normal": { + "description": "The direction of the force applied by this volume. Defaults to up (0, 1, 0).", + "$ref": "#/definitions/MVector3" + }, + "affectsAlignment": { + "type": "boolean", + "description": "Whether this force volume affects alignment. Defaults to true.", + "default": true + }, + "offsetCentripetalForce": { + "type": "boolean", + "description": "Whether the force applied by this volume takes the centripetal force of the volume's parent body into account. Defaults to false." + }, + "playGravityCrystalAudio": { + "type": "boolean", + "description": "Whether to play the gravity crystal audio when the player is in this volume." + } + } + }, + "GravityVolumeInfo": { + "type": "object", + "additionalProperties": false, + "properties": { + "shape": { + "description": "The shape of this volume. Defaults to a sphere shape with a radius of `radius`.", + "$ref": "#/definitions/ShapeInfo" + }, + "force": { + "type": "number", + "description": "The force applied by this volume. Can be negative to reverse the direction.", + "format": "float" + }, + "alignmentPriority": { + "type": "integer", + "description": "The priority of this force volume for the purposes of alignment.\n\nVolumes of higher priority will override volumes of lower priority. Volumes of the same priority will stack like normal.\nEx: A player in a gravity volume with priority 0, and zero-gravity volume with priority 1, will feel zero gravity.\n \nDefault value here is 1 instead of 0 so it automatically overrides planet gravity, which is 0 by default. ", + "format": "int32", + "default": 1 + }, + "inheritable": { + "type": "boolean", + "description": "Whether this force volume is inheritable. The most recently activated inheritable force volume will stack with other force volumes even if their priorities differ." + }, + "layer": { + "type": "integer", + "description": "The layer of this volume.\n\nLayers separate the priority system. The priority of volumes in one layer will not affect or override volumes in another. The highest priority volume in each layer will stack like normal.\nThe exception is layer 0. A higher-priority volume in layer 0 will override lower-priority volumes in ALL other layers. A lower-priority volume in layer 0 will stack with other layers like normal.\n \nEx: A player could be affected by the sun on layer 9 priority 0 and planet gravity on layer 3 priority 2. They would experience the gravity of both volumes since they are on different layers.\nIf there was a zero-g volume on layer 0 priority 1, since it is on layer 0 it will override the gravity from the sun (priority 0 which is less than 1) but they will still feel the \ngravity of the planet (priority 2 is greater than 1). The zero-g volume will also still be applied because it is on a different layer.\n \nDefault value here is 0 which means this volume's priority will be evaluated against all other priority volumes regardless of their layer.", + "format": "int32", + "default": 0 + }, + "priority": { + "type": "integer", + "description": "The priority of this volume.\n\nVolumes of higher priority will override volumes of lower priority. Volumes of the same priority will stack like normal.\nEx: A player in a gravity volume with priority 0, and zero-gravity volume with priority 1, will feel zero gravity.\n \nDefault value here is 1 instead of 0 so it automatically overrides planet gravity, which is 0 by default. ", + "format": "int32", + "default": 1 + }, + "radius": { + "type": "number", + "description": "The radius of this volume.", + "format": "float", + "default": 1.0 + }, + "rotation": { + "description": "Rotation of the object", + "$ref": "#/definitions/MVector3" + }, + "alignRadial": { + "type": [ + "boolean", + "null" + ], + "description": "Do we try to automatically align this object to stand upright relative to the body's center? Stacks with rotation.\nDefaults to true for geysers, tornados, and volcanoes, and false for everything else." + }, + "position": { + "description": "Position of the object", + "$ref": "#/definitions/MVector3" + }, + "isRelativeToParent": { + "type": "boolean", + "description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object." + }, + "parentPath": { + "type": "string", + "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)." + }, + "rename": { + "type": "string", + "description": "An optional rename of this object" + }, + "upperRadius": { + "type": "number", + "description": "The upper bounds of the volume's \"surface\". Above this radius, the force applied by this volume will have falloff applied.", + "format": "float" + }, + "lowerRadius": { + "type": "number", + "description": "The lower bounds of the volume's \"surface\". Above this radius and below the `upperRadius`, the force applied by this volume will be constant. Defaults to 0.", + "format": "float", + "default": 0.0 + }, + "minRadius": { + "type": "number", + "description": "The volume's force will decrease linearly from `force` to `minForce` as distance decreases from `lowerRadius` to `minRadius`. Defaults to 0.", + "format": "float", + "default": 0.0 + }, + "minForce": { + "type": "number", + "description": "The minimum force applied by this volume between `lowerRadius` and `minRadius`. Defaults to 0.", + "format": "float", + "default": 0.0 + }, + "fallOff": { + "description": "How the force falls off with distance. Most planets use linear but the sun and some moons use inverseSquared.", + "default": "linear", + "$ref": "#/definitions/GravityFallOff" + }, + "alignmentRadius": { + "type": [ + "null", + "number" + ], + "description": "The radius where objects will be aligned to the volume's force. Defaults to 1.5x the `upperRadius`. Set to 0 to disable alignment.", + "format": "float" + } + } + }, + "PolarForceVolumeInfo": { + "type": "object", + "additionalProperties": false, + "properties": { + "shape": { + "description": "The shape of this volume. Defaults to a sphere shape with a radius of `radius`.", + "$ref": "#/definitions/ShapeInfo" + }, + "force": { + "type": "number", + "description": "The force applied by this volume. Can be negative to reverse the direction.", + "format": "float" + }, + "alignmentPriority": { + "type": "integer", + "description": "The priority of this force volume for the purposes of alignment.\n\nVolumes of higher priority will override volumes of lower priority. Volumes of the same priority will stack like normal.\nEx: A player in a gravity volume with priority 0, and zero-gravity volume with priority 1, will feel zero gravity.\n \nDefault value here is 1 instead of 0 so it automatically overrides planet gravity, which is 0 by default. ", + "format": "int32", + "default": 1 + }, + "inheritable": { + "type": "boolean", + "description": "Whether this force volume is inheritable. The most recently activated inheritable force volume will stack with other force volumes even if their priorities differ." + }, + "layer": { + "type": "integer", + "description": "The layer of this volume.\n\nLayers separate the priority system. The priority of volumes in one layer will not affect or override volumes in another. The highest priority volume in each layer will stack like normal.\nThe exception is layer 0. A higher-priority volume in layer 0 will override lower-priority volumes in ALL other layers. A lower-priority volume in layer 0 will stack with other layers like normal.\n \nEx: A player could be affected by the sun on layer 9 priority 0 and planet gravity on layer 3 priority 2. They would experience the gravity of both volumes since they are on different layers.\nIf there was a zero-g volume on layer 0 priority 1, since it is on layer 0 it will override the gravity from the sun (priority 0 which is less than 1) but they will still feel the \ngravity of the planet (priority 2 is greater than 1). The zero-g volume will also still be applied because it is on a different layer.\n \nDefault value here is 0 which means this volume's priority will be evaluated against all other priority volumes regardless of their layer.", + "format": "int32", + "default": 0 + }, + "priority": { + "type": "integer", + "description": "The priority of this volume.\n\nVolumes of higher priority will override volumes of lower priority. Volumes of the same priority will stack like normal.\nEx: A player in a gravity volume with priority 0, and zero-gravity volume with priority 1, will feel zero gravity.\n \nDefault value here is 1 instead of 0 so it automatically overrides planet gravity, which is 0 by default. ", + "format": "int32", + "default": 1 + }, + "radius": { + "type": "number", + "description": "The radius of this volume.", + "format": "float", + "default": 1.0 + }, + "rotation": { + "description": "Rotation of the object", + "$ref": "#/definitions/MVector3" + }, + "alignRadial": { + "type": [ + "boolean", + "null" + ], + "description": "Do we try to automatically align this object to stand upright relative to the body's center? Stacks with rotation.\nDefaults to true for geysers, tornados, and volcanoes, and false for everything else." + }, + "position": { + "description": "Position of the object", + "$ref": "#/definitions/MVector3" + }, + "isRelativeToParent": { + "type": "boolean", + "description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object." + }, + "parentPath": { + "type": "string", + "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)." + }, + "rename": { + "type": "string", + "description": "An optional rename of this object" + }, + "normal": { + "description": "Tangential mode only. The force applied by this volume will be perpendicular to this direction and the direction to the other body. Defaults to up (0, 1, 0).", + "$ref": "#/definitions/MVector3" + }, + "tangential": { + "type": "boolean", + "description": "Enables tangential mode. The force applied by this volume will be perpendicular to the normal and the direction to the other body. Defaults to false." + } + } + }, + "RadialForceVolumeInfo": { + "type": "object", + "additionalProperties": false, + "properties": { + "shape": { + "description": "The shape of this volume. Defaults to a sphere shape with a radius of `radius`.", + "$ref": "#/definitions/ShapeInfo" + }, + "force": { + "type": "number", + "description": "The force applied by this volume. Can be negative to reverse the direction.", + "format": "float" + }, + "alignmentPriority": { + "type": "integer", + "description": "The priority of this force volume for the purposes of alignment.\n\nVolumes of higher priority will override volumes of lower priority. Volumes of the same priority will stack like normal.\nEx: A player in a gravity volume with priority 0, and zero-gravity volume with priority 1, will feel zero gravity.\n \nDefault value here is 1 instead of 0 so it automatically overrides planet gravity, which is 0 by default. ", + "format": "int32", + "default": 1 + }, + "inheritable": { + "type": "boolean", + "description": "Whether this force volume is inheritable. The most recently activated inheritable force volume will stack with other force volumes even if their priorities differ." + }, + "layer": { + "type": "integer", + "description": "The layer of this volume.\n\nLayers separate the priority system. The priority of volumes in one layer will not affect or override volumes in another. The highest priority volume in each layer will stack like normal.\nThe exception is layer 0. A higher-priority volume in layer 0 will override lower-priority volumes in ALL other layers. A lower-priority volume in layer 0 will stack with other layers like normal.\n \nEx: A player could be affected by the sun on layer 9 priority 0 and planet gravity on layer 3 priority 2. They would experience the gravity of both volumes since they are on different layers.\nIf there was a zero-g volume on layer 0 priority 1, since it is on layer 0 it will override the gravity from the sun (priority 0 which is less than 1) but they will still feel the \ngravity of the planet (priority 2 is greater than 1). The zero-g volume will also still be applied because it is on a different layer.\n \nDefault value here is 0 which means this volume's priority will be evaluated against all other priority volumes regardless of their layer.", + "format": "int32", + "default": 0 + }, + "priority": { + "type": "integer", + "description": "The priority of this volume.\n\nVolumes of higher priority will override volumes of lower priority. Volumes of the same priority will stack like normal.\nEx: A player in a gravity volume with priority 0, and zero-gravity volume with priority 1, will feel zero gravity.\n \nDefault value here is 1 instead of 0 so it automatically overrides planet gravity, which is 0 by default. ", + "format": "int32", + "default": 1 + }, + "radius": { + "type": "number", + "description": "The radius of this volume.", + "format": "float", + "default": 1.0 + }, + "rotation": { + "description": "Rotation of the object", + "$ref": "#/definitions/MVector3" + }, + "alignRadial": { + "type": [ + "boolean", + "null" + ], + "description": "Do we try to automatically align this object to stand upright relative to the body's center? Stacks with rotation.\nDefaults to true for geysers, tornados, and volcanoes, and false for everything else." + }, + "position": { + "description": "Position of the object", + "$ref": "#/definitions/MVector3" + }, + "isRelativeToParent": { + "type": "boolean", + "description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object." + }, + "parentPath": { + "type": "string", + "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)." + }, + "rename": { + "type": "string", + "description": "An optional rename of this object" + }, + "fallOff": { + "description": "How the force falls off with distance. Defaults to linear.", + "default": "linear", + "$ref": "#/definitions/FallOff" + } + } + }, + "FallOff": { + "type": "string", + "description": "", + "x-enumNames": [ + "Constant", + "Linear", + "InverseSquared" + ], + "enum": [ + "constant", + "linear", + "inverseSquared" + ] + }, "HazardVolumeInfo": { "type": "object", "additionalProperties": false, @@ -5764,6 +6396,17 @@ "format": "float", "default": 1.0 }, + "rotation": { + "description": "Rotation of the object", + "$ref": "#/definitions/MVector3" + }, + "alignRadial": { + "type": [ + "boolean", + "null" + ], + "description": "Do we try to automatically align this object to stand upright relative to the body's center? Stacks with rotation.\nDefaults to true for geysers, tornados, and volcanoes, and false for everything else." + }, "position": { "description": "Position of the object", "$ref": "#/definitions/MVector3" @@ -5807,6 +6450,17 @@ "type": "object", "additionalProperties": false, "properties": { + "rotation": { + "description": "Rotation of the object", + "$ref": "#/definitions/MVector3" + }, + "alignRadial": { + "type": [ + "boolean", + "null" + ], + "description": "Do we try to automatically align this object to stand upright relative to the body's center? Stacks with rotation.\nDefaults to true for geysers, tornados, and volcanoes, and false for everything else." + }, "position": { "description": "Position of the object", "$ref": "#/definitions/MVector3" @@ -5841,6 +6495,17 @@ "format": "float", "default": 1.0 }, + "rotation": { + "description": "Rotation of the object", + "$ref": "#/definitions/MVector3" + }, + "alignRadial": { + "type": [ + "boolean", + "null" + ], + "description": "Do we try to automatically align this object to stand upright relative to the body's center? Stacks with rotation.\nDefaults to true for geysers, tornados, and volcanoes, and false for everything else." + }, "position": { "description": "Position of the object", "$ref": "#/definitions/MVector3" @@ -5912,6 +6577,17 @@ "format": "float", "default": 1.0 }, + "rotation": { + "description": "Rotation of the object", + "$ref": "#/definitions/MVector3" + }, + "alignRadial": { + "type": [ + "boolean", + "null" + ], + "description": "Do we try to automatically align this object to stand upright relative to the body's center? Stacks with rotation.\nDefaults to true for geysers, tornados, and volcanoes, and false for everything else." + }, "position": { "description": "Position of the object", "$ref": "#/definitions/MVector3" @@ -5969,6 +6645,17 @@ "format": "float", "default": 1.0 }, + "rotation": { + "description": "Rotation of the object", + "$ref": "#/definitions/MVector3" + }, + "alignRadial": { + "type": [ + "boolean", + "null" + ], + "description": "Do we try to automatically align this object to stand upright relative to the body's center? Stacks with rotation.\nDefaults to true for geysers, tornados, and volcanoes, and false for everything else." + }, "position": { "description": "Position of the object", "$ref": "#/definitions/MVector3" @@ -5987,7 +6674,7 @@ }, "maxAngle": { "type": "number", - "description": "The max view angle (in degrees) the player can see the volume with to unlock the fact (`observe` only)", + "description": "The max view angle (in degrees) the player can see the volume with to unlock the fact (`observe` only). This will effectively be a cone extending from the volume's center forwards (along the Z axis) based on the volume's rotation.", "format": "float", "default": 180.0 }, @@ -6092,6 +6779,17 @@ "format": "float", "default": 1.0 }, + "rotation": { + "description": "Rotation of the object", + "$ref": "#/definitions/MVector3" + }, + "alignRadial": { + "type": [ + "boolean", + "null" + ], + "description": "Do we try to automatically align this object to stand upright relative to the body's center? Stacks with rotation.\nDefaults to true for geysers, tornados, and volcanoes, and false for everything else." + }, "position": { "description": "Position of the object", "$ref": "#/definitions/MVector3" @@ -6132,6 +6830,17 @@ "format": "float", "default": 1.0 }, + "rotation": { + "description": "Rotation of the object", + "$ref": "#/definitions/MVector3" + }, + "alignRadial": { + "type": [ + "boolean", + "null" + ], + "description": "Do we try to automatically align this object to stand upright relative to the body's center? Stacks with rotation.\nDefaults to true for geysers, tornados, and volcanoes, and false for everything else." + }, "position": { "description": "Position of the object", "$ref": "#/definitions/MVector3" @@ -6182,6 +6891,17 @@ "format": "float", "default": 1.0 }, + "rotation": { + "description": "Rotation of the object", + "$ref": "#/definitions/MVector3" + }, + "alignRadial": { + "type": [ + "boolean", + "null" + ], + "description": "Do we try to automatically align this object to stand upright relative to the body's center? Stacks with rotation.\nDefaults to true for geysers, tornados, and volcanoes, and false for everything else." + }, "position": { "description": "Position of the object", "$ref": "#/definitions/MVector3" @@ -6226,6 +6946,17 @@ "format": "float", "default": 1.0 }, + "rotation": { + "description": "Rotation of the object", + "$ref": "#/definitions/MVector3" + }, + "alignRadial": { + "type": [ + "boolean", + "null" + ], + "description": "Do we try to automatically align this object to stand upright relative to the body's center? Stacks with rotation.\nDefaults to true for geysers, tornados, and volcanoes, and false for everything else." + }, "position": { "description": "Position of the object", "$ref": "#/definitions/MVector3" @@ -6298,6 +7029,17 @@ "format": "float", "default": 1.0 }, + "rotation": { + "description": "Rotation of the object", + "$ref": "#/definitions/MVector3" + }, + "alignRadial": { + "type": [ + "boolean", + "null" + ], + "description": "Do we try to automatically align this object to stand upright relative to the body's center? Stacks with rotation.\nDefaults to true for geysers, tornados, and volcanoes, and false for everything else." + }, "position": { "description": "Position of the object", "$ref": "#/definitions/MVector3" @@ -6352,6 +7094,17 @@ "format": "float", "default": 1.0 }, + "rotation": { + "description": "Rotation of the object", + "$ref": "#/definitions/MVector3" + }, + "alignRadial": { + "type": [ + "boolean", + "null" + ], + "description": "Do we try to automatically align this object to stand upright relative to the body's center? Stacks with rotation.\nDefaults to true for geysers, tornados, and volcanoes, and false for everything else." + }, "position": { "description": "Position of the object", "$ref": "#/definitions/MVector3" @@ -6392,6 +7145,17 @@ "format": "float", "default": 1.0 }, + "rotation": { + "description": "Rotation of the object", + "$ref": "#/definitions/MVector3" + }, + "alignRadial": { + "type": [ + "boolean", + "null" + ], + "description": "Do we try to automatically align this object to stand upright relative to the body's center? Stacks with rotation.\nDefaults to true for geysers, tornados, and volcanoes, and false for everything else." + }, "position": { "description": "Position of the object", "$ref": "#/definitions/MVector3" @@ -6432,6 +7196,17 @@ "format": "float", "default": 1.0 }, + "rotation": { + "description": "Rotation of the object", + "$ref": "#/definitions/MVector3" + }, + "alignRadial": { + "type": [ + "boolean", + "null" + ], + "description": "Do we try to automatically align this object to stand upright relative to the body's center? Stacks with rotation.\nDefaults to true for geysers, tornados, and volcanoes, and false for everything else." + }, "position": { "description": "Position of the object", "$ref": "#/definitions/MVector3" @@ -6469,6 +7244,17 @@ "format": "float", "default": 1.0 }, + "rotation": { + "description": "Rotation of the object", + "$ref": "#/definitions/MVector3" + }, + "alignRadial": { + "type": [ + "boolean", + "null" + ], + "description": "Do we try to automatically align this object to stand upright relative to the body's center? Stacks with rotation.\nDefaults to true for geysers, tornados, and volcanoes, and false for everything else." + }, "position": { "description": "Position of the object", "$ref": "#/definitions/MVector3" From 9fe04aad7d76362ec6ccdf03dca75374813223ea Mon Sep 17 00:00:00 2001 From: Joshua Thome Date: Sat, 22 Feb 2025 14:12:59 -0600 Subject: [PATCH 05/57] Unify volume builders and support shapes on all volume types --- .../Builder/Body/SingularityBuilder.cs | 1 + NewHorizons/Builder/Props/ShapeBuilder.cs | 25 +++++++++ .../Builder/Volumes/AudioVolumeBuilder.cs | 9 ++-- .../Volumes/ChangeStarSystemVolumeBuilder.cs | 2 + .../Builder/Volumes/CreditsVolumeBuilder.cs | 2 + .../Volumes/DayNightAudioVolumeBuilder.cs | 11 ++-- .../Volumes/DestructionVolumeBuilder.cs | 2 + .../Builder/Volumes/FluidVolumeBuilder.cs | 2 + .../Builder/Volumes/ForceVolumeBuilder.cs | 42 ++++++--------- .../Builder/Volumes/HazardVolumeBuilder.cs | 54 ++++++++++--------- .../Volumes/NotificationVolumeBuilder.cs | 13 ++--- .../Builder/Volumes/OxygenVolumeBuilder.cs | 2 + .../Builder/Volumes/PriorityVolumeBuilder.cs | 11 ++++ .../Rulesets/PlayerImpactRulesetBuilder.cs | 2 + .../Volumes/Rulesets/ProbeRulesetBuilder.cs | 2 + .../Volumes/Rulesets/ThrustRulesetBuilder.cs | 2 + .../Builder/Volumes/SpeedTrapVolumeBuilder.cs | 2 + .../Builder/Volumes/VanishVolumeBuilder.cs | 20 +------ .../VisorFrostEffectVolumeBuilder.cs | 2 + .../VisorRainEffectVolumeBuilder.cs | 2 + NewHorizons/Builder/Volumes/VolumeBuilder.cs | 37 +++++++++---- .../Builder/Volumes/VolumesBuildManager.cs | 18 +++---- .../Builder/Volumes/ZeroGVolumeBuilder.cs | 2 + .../Volumes/VolumeInfos/ForceVolumeInfo.cs | 5 -- .../Modules/Volumes/VolumeInfos/VolumeInfo.cs | 8 ++- 25 files changed, 161 insertions(+), 117 deletions(-) diff --git a/NewHorizons/Builder/Body/SingularityBuilder.cs b/NewHorizons/Builder/Body/SingularityBuilder.cs index eb8863c8..f7d303ef 100644 --- a/NewHorizons/Builder/Body/SingularityBuilder.cs +++ b/NewHorizons/Builder/Body/SingularityBuilder.cs @@ -150,6 +150,7 @@ namespace NewHorizons.Builder.Body streamingVolume.streamingGroup = streamingGroup; streamingVolume.transform.parent = blackHoleVolume.transform; streamingVolume.transform.localPosition = Vector3.zero; + streamingVolume.gameObject.SetActive(true); } } catch (Exception e) diff --git a/NewHorizons/Builder/Props/ShapeBuilder.cs b/NewHorizons/Builder/Props/ShapeBuilder.cs index e7594f6e..7e224e98 100644 --- a/NewHorizons/Builder/Props/ShapeBuilder.cs +++ b/NewHorizons/Builder/Props/ShapeBuilder.cs @@ -11,6 +11,31 @@ namespace NewHorizons.Builder.Props { public static class ShapeBuilder { + public static OWTriggerVolume AddTriggerVolume(GameObject go, ShapeInfo info, float defaultRadius) + { + var owTriggerVolume = go.AddComponent(); + + if (info != null) + { + var shapeOrCol = AddShapeOrCollider(go, info); + if (shapeOrCol is Shape shape) + owTriggerVolume._shape = shape; + else if (shapeOrCol is Collider col) + owTriggerVolume._owCollider = col.GetComponent(); + } + else + { + var col = go.AddComponent(); + col.radius = defaultRadius; + col.isTrigger = true; + var owCollider = go.GetAddComponent(); + + owTriggerVolume._owCollider = owCollider; + } + + return owTriggerVolume; + } + public static Component AddShapeOrCollider(GameObject go, ShapeInfo info) { if (info.useShape.HasValue) diff --git a/NewHorizons/Builder/Volumes/AudioVolumeBuilder.cs b/NewHorizons/Builder/Volumes/AudioVolumeBuilder.cs index 6b961a79..0c209154 100644 --- a/NewHorizons/Builder/Volumes/AudioVolumeBuilder.cs +++ b/NewHorizons/Builder/Volumes/AudioVolumeBuilder.cs @@ -25,7 +25,8 @@ namespace NewHorizons.Builder.Volumes owAudioSource.SetTrack(info.track.ConvertToOW()); AudioUtilities.SetAudioClip(owAudioSource, info.audio, mod); - var audioVolume = go.AddComponent(); + var audioVolume = PriorityVolumeBuilder.MakeExisting(go, planetGO, sector, info); + audioVolume._layer = info.layer; audioVolume.SetPriority(info.priority); audioVolume._fadeSeconds = info.fadeSeconds; @@ -33,11 +34,7 @@ namespace NewHorizons.Builder.Volumes audioVolume._randomizePlayhead = info.randomizePlayhead; audioVolume._pauseOnFadeOut = info.pauseOnFadeOut; - var shape = go.AddComponent(); - shape.radius = info.radius; - - var owTriggerVolume = go.AddComponent(); - owTriggerVolume._shape = shape; + var owTriggerVolume = go.GetComponent(); audioVolume._triggerVolumeOverride = owTriggerVolume; go.SetActive(true); diff --git a/NewHorizons/Builder/Volumes/ChangeStarSystemVolumeBuilder.cs b/NewHorizons/Builder/Volumes/ChangeStarSystemVolumeBuilder.cs index 5557da98..54eddc3d 100644 --- a/NewHorizons/Builder/Volumes/ChangeStarSystemVolumeBuilder.cs +++ b/NewHorizons/Builder/Volumes/ChangeStarSystemVolumeBuilder.cs @@ -13,6 +13,8 @@ namespace NewHorizons.Builder.Volumes volume.TargetSolarSystem = info.targetStarSystem; volume.TargetSpawnID = info.spawnPointID; + volume.gameObject.SetActive(true); + return volume; } } diff --git a/NewHorizons/Builder/Volumes/CreditsVolumeBuilder.cs b/NewHorizons/Builder/Volumes/CreditsVolumeBuilder.cs index 81c3c6ce..5692c3e5 100644 --- a/NewHorizons/Builder/Volumes/CreditsVolumeBuilder.cs +++ b/NewHorizons/Builder/Volumes/CreditsVolumeBuilder.cs @@ -14,6 +14,8 @@ namespace NewHorizons.Builder.Volumes volume.gameOver = info.gameOver; volume.deathType = info.deathType == null ? null : EnumUtils.Parse(info.deathType.ToString(), DeathType.Default); + volume.gameObject.SetActive(true); + return volume; } } diff --git a/NewHorizons/Builder/Volumes/DayNightAudioVolumeBuilder.cs b/NewHorizons/Builder/Volumes/DayNightAudioVolumeBuilder.cs index b2ed1ce1..875bba75 100644 --- a/NewHorizons/Builder/Volumes/DayNightAudioVolumeBuilder.cs +++ b/NewHorizons/Builder/Volumes/DayNightAudioVolumeBuilder.cs @@ -15,7 +15,8 @@ namespace NewHorizons.Builder.Volumes var go = GeneralPropBuilder.MakeNew("DayNightAudioVolume", planetGO, sector, info); go.layer = Layer.AdvancedEffectVolume; - var audioVolume = go.AddComponent(); + var audioVolume = PriorityVolumeBuilder.MakeExisting(go, planetGO, sector, info); + audioVolume.sunName = info.sun; audioVolume.dayWindow = info.dayWindow; audioVolume.dayAudio = info.dayAudio; @@ -24,13 +25,7 @@ namespace NewHorizons.Builder.Volumes audioVolume.volume = info.volume; audioVolume.SetTrack(info.track.ConvertToOW()); - var shape = go.AddComponent(); - shape.radius = info.radius; - - var owTriggerVolume = go.AddComponent(); - owTriggerVolume._shape = shape; - - go.SetActive(true); + audioVolume.gameObject.SetActive(true); return audioVolume; } diff --git a/NewHorizons/Builder/Volumes/DestructionVolumeBuilder.cs b/NewHorizons/Builder/Volumes/DestructionVolumeBuilder.cs index ad93ad7f..d633d27c 100644 --- a/NewHorizons/Builder/Volumes/DestructionVolumeBuilder.cs +++ b/NewHorizons/Builder/Volumes/DestructionVolumeBuilder.cs @@ -12,6 +12,8 @@ namespace NewHorizons.Builder.Volumes volume._deathType = EnumUtils.Parse(info.deathType.ToString(), DeathType.Default); + volume.gameObject.SetActive(true); + return volume; } } diff --git a/NewHorizons/Builder/Volumes/FluidVolumeBuilder.cs b/NewHorizons/Builder/Volumes/FluidVolumeBuilder.cs index e2839bd3..70b6d399 100644 --- a/NewHorizons/Builder/Volumes/FluidVolumeBuilder.cs +++ b/NewHorizons/Builder/Volumes/FluidVolumeBuilder.cs @@ -35,6 +35,8 @@ namespace NewHorizons.Builder.Volumes volume._allowShipAutoroll = info.allowShipAutoroll; volume._disableOnStart = info.disableOnStart; + volume.gameObject.SetActive(true); + return volume; } } diff --git a/NewHorizons/Builder/Volumes/ForceVolumeBuilder.cs b/NewHorizons/Builder/Volumes/ForceVolumeBuilder.cs index 12e1f83f..ebc5c360 100644 --- a/NewHorizons/Builder/Volumes/ForceVolumeBuilder.cs +++ b/NewHorizons/Builder/Volumes/ForceVolumeBuilder.cs @@ -18,28 +18,35 @@ namespace NewHorizons.Builder.Volumes public static CylindricalForceVolume Make(GameObject planetGO, Sector sector, CylindricalForceVolumeInfo info) { var forceVolume = Make(planetGO, sector, info); + forceVolume._acceleration = info.force; forceVolume._localAxis = info.normal ?? Vector3.up; forceVolume._playGravityCrystalAudio = info.playGravityCrystalAudio; + forceVolume.gameObject.SetActive(true); + return forceVolume; } public static DirectionalForceVolume Make(GameObject planetGO, Sector sector, DirectionalForceVolumeInfo info) { var forceVolume = Make(planetGO, sector, info); + forceVolume._fieldDirection = info.normal ?? Vector3.up; forceVolume._fieldMagnitude = info.force; forceVolume._affectsAlignment = info.affectsAlignment; forceVolume._offsetCentripetalForce = info.offsetCentripetalForce; forceVolume._playGravityCrystalAudio = info.playGravityCrystalAudio; + forceVolume.gameObject.SetActive(true); + return forceVolume; } public static GravityVolume Make(GameObject planetGO, Sector sector, GravityVolumeInfo info) { var forceVolume = Make(planetGO, sector, info); + forceVolume._isPlanetGravityVolume = false; forceVolume._setMass = false; forceVolume._surfaceAcceleration = info.force; @@ -54,23 +61,29 @@ namespace NewHorizons.Builder.Volumes GravityFallOff.InverseSquared => GravityVolume.FalloffType.inverseSquared, _ => throw new NotImplementedException(), }; + forceVolume.gameObject.SetActive(true); + return forceVolume; } public static PolarForceVolume Make(GameObject planetGO, Sector sector, PolarForceVolumeInfo info) { var forceVolume = Make(planetGO, sector, info); + forceVolume._acceleration = info.force; forceVolume._localAxis = info.normal ?? Vector3.up; forceVolume._fieldMode = info.tangential ? PolarForceVolume.ForceMode.Tangential : PolarForceVolume.ForceMode.Polar; + forceVolume.gameObject.SetActive(true); + return forceVolume; } public static RadialForceVolume Make(GameObject planetGO, Sector sector, RadialForceVolumeInfo info) { var forceVolume = Make(planetGO, sector, info); + forceVolume._acceleration = info.force; forceVolume._falloff = info.fallOff switch { @@ -79,37 +92,16 @@ namespace NewHorizons.Builder.Volumes RadialForceVolumeInfo.FallOff.InverseSquared => RadialForceVolume.Falloff.InvSqr, _ => throw new NotImplementedException(), }; + forceVolume.gameObject.SetActive(true); + return forceVolume; } - public static T Make(GameObject planetGO, Sector sector, ForceVolumeInfo info) where T : ForceVolume + public static TVolume Make(GameObject planetGO, Sector sector, ForceVolumeInfo info) where TVolume : ForceVolume { - var go = GeneralPropBuilder.MakeNew(typeof(T).Name, planetGO, sector, info); - go.layer = Layer.BasicEffectVolume; + var forceVolume = PriorityVolumeBuilder.Make(planetGO, sector, info); - var owTriggerVolume = go.AddComponent(); - - if (info.shape != null) - { - var shapeOrCol = ShapeBuilder.AddShapeOrCollider(go, info.shape); - if (shapeOrCol is Shape shape) - owTriggerVolume._shape = shape; - else if (shapeOrCol is Collider col) - owTriggerVolume._owCollider = col.GetComponent(); - } - else - { - var col = go.AddComponent(); - col.radius = info.radius; - col.isTrigger = true; - var owCollider = go.GetAddComponent(); - - owTriggerVolume._owCollider = owCollider; - } - - var forceVolume = go.AddComponent(); - forceVolume._priority = info.priority; forceVolume._alignmentPriority = info.alignmentPriority; forceVolume._inheritable = info.inheritable; diff --git a/NewHorizons/Builder/Volumes/HazardVolumeBuilder.cs b/NewHorizons/Builder/Volumes/HazardVolumeBuilder.cs index f26f0c2e..1d5fe013 100644 --- a/NewHorizons/Builder/Volumes/HazardVolumeBuilder.cs +++ b/NewHorizons/Builder/Volumes/HazardVolumeBuilder.cs @@ -3,6 +3,7 @@ using NewHorizons.External.Modules.Volumes.VolumeInfos; using NewHorizons.Utility.OuterWilds; using OWML.Common; using OWML.Utils; +using System; using System.Linq; using UnityEngine; @@ -13,35 +14,28 @@ namespace NewHorizons.Builder.Volumes public static HazardVolume Make(GameObject planetGO, Sector sector, OWRigidbody owrb, HazardVolumeInfo info, IModBehaviour mod) { var go = GeneralPropBuilder.MakeNew("HazardVolume", planetGO, sector, info); - go.layer = Layer.BasicEffectVolume; - - var shape = go.AddComponent(); - shape.radius = info.radius; - - var owTriggerVolume = go.AddComponent(); - owTriggerVolume._shape = shape; - - var volume = AddHazardVolume(go, sector, owrb, info.type, info.firstContactDamageType, info.firstContactDamage, info.damagePerSecond); + + var volume = MakeExisting(go, planetGO, sector, owrb, info); go.SetActive(true); return volume; } - public static HazardVolume AddHazardVolume(GameObject go, Sector sector, OWRigidbody owrb, HazardVolumeInfo.HazardType? type, HazardVolumeInfo.InstantDamageType? firstContactDamageType, float firstContactDamage, float damagePerSecond) + public static HazardVolume MakeExisting(GameObject go, GameObject planetGO, Sector sector, OWRigidbody owrb, HazardVolumeInfo info) { HazardVolume hazardVolume = null; - if (type == HazardVolumeInfo.HazardType.RIVERHEAT) + if (info.type == HazardVolumeInfo.HazardType.RIVERHEAT) { - hazardVolume = go.AddComponent(); + hazardVolume = VolumeBuilder.MakeExisting(go, planetGO, sector, info); } - else if (type == HazardVolumeInfo.HazardType.HEAT) + else if (info.type == HazardVolumeInfo.HazardType.HEAT) { - hazardVolume = go.AddComponent(); + hazardVolume = VolumeBuilder.MakeExisting(go, planetGO, sector, info); } - else if (type == HazardVolumeInfo.HazardType.DARKMATTER) + else if (info.type == HazardVolumeInfo.HazardType.DARKMATTER) { - hazardVolume = go.AddComponent(); + hazardVolume = VolumeBuilder.MakeExisting(go, planetGO, sector, info); var visorFrostEffectVolume = go.AddComponent(); visorFrostEffectVolume._frostRate = 0.5f; visorFrostEffectVolume._maxFrost = 0.91f; @@ -67,28 +61,38 @@ namespace NewHorizons.Builder.Volumes submerge._fluidDetector = detector; } } - else if (type == HazardVolumeInfo.HazardType.ELECTRICITY) + else if (info.type == HazardVolumeInfo.HazardType.ELECTRICITY) { - var electricityVolume = go.AddComponent(); + var electricityVolume = VolumeBuilder.MakeExisting(go, planetGO, sector, info); electricityVolume._shockAudioPool = new OWAudioSource[0]; hazardVolume = electricityVolume; } else { var simpleHazardVolume = go.AddComponent(); - simpleHazardVolume._type = EnumUtils.Parse(type.ToString(), HazardVolume.HazardType.GENERAL); + simpleHazardVolume._type = EnumUtils.Parse(info.type.ToString(), HazardVolume.HazardType.GENERAL); hazardVolume = simpleHazardVolume; } hazardVolume._attachedBody = owrb; - hazardVolume._damagePerSecond = type == null ? 0f : damagePerSecond; + hazardVolume._damagePerSecond = info.type == HazardVolumeInfo.HazardType.NONE ? 0f : info.damagePerSecond; - if (firstContactDamageType != null) - { - hazardVolume._firstContactDamageType = EnumUtils.Parse(firstContactDamageType.ToString(), InstantDamageType.Impact); - hazardVolume._firstContactDamage = firstContactDamage; - } + hazardVolume._firstContactDamageType = EnumUtils.Parse(info.firstContactDamageType.ToString(), InstantDamageType.Impact); + hazardVolume._firstContactDamage = info.firstContactDamage; return hazardVolume; } + + public static HazardVolume AddHazardVolume(GameObject go, Sector sector, OWRigidbody owrb, HazardVolumeInfo.HazardType? type, HazardVolumeInfo.InstantDamageType? firstContactDamageType, float firstContactDamage, float damagePerSecond) + { + var planetGO = sector.transform.root.gameObject; + return MakeExisting(go, planetGO, sector, owrb, new HazardVolumeInfo + { + radius = 0f, // Volume builder should skip creating an extra trigger volume and collider if radius is 0 + type = type ?? HazardVolumeInfo.HazardType.NONE, + firstContactDamageType = firstContactDamageType ?? HazardVolumeInfo.InstantDamageType.Impact, + firstContactDamage = firstContactDamage, + damagePerSecond = damagePerSecond + }); + } } } diff --git a/NewHorizons/Builder/Volumes/NotificationVolumeBuilder.cs b/NewHorizons/Builder/Volumes/NotificationVolumeBuilder.cs index 18f51e69..bf4bbe3f 100644 --- a/NewHorizons/Builder/Volumes/NotificationVolumeBuilder.cs +++ b/NewHorizons/Builder/Volumes/NotificationVolumeBuilder.cs @@ -11,21 +11,16 @@ namespace NewHorizons.Builder.Volumes { public static NHNotificationVolume Make(GameObject planetGO, Sector sector, NotificationVolumeInfo info, IModBehaviour mod) { - var go = GeneralPropBuilder.MakeNew("NotificationVolume", planetGO, sector, info); - go.layer = Layer.BasicEffectVolume; + var notificationVolume = VolumeBuilder.Make(planetGO, sector, info); - var shape = go.AddComponent(); - shape.radius = info.radius; + // Preserving name for backwards compatibility + notificationVolume.gameObject.name = string.IsNullOrEmpty(info.rename) ? "NotificationVolume" : info.rename; - var owTriggerVolume = go.AddComponent(); - owTriggerVolume._shape = shape; - - var notificationVolume = go.AddComponent(); notificationVolume.SetTarget(info.target); if (info.entryNotification != null) notificationVolume.SetEntryNotification(info.entryNotification.displayMessage, info.entryNotification.duration); if (info.exitNotification != null) notificationVolume.SetExitNotification(info.exitNotification.displayMessage, info.exitNotification.duration); - go.SetActive(true); + notificationVolume.gameObject.SetActive(true); return notificationVolume; } diff --git a/NewHorizons/Builder/Volumes/OxygenVolumeBuilder.cs b/NewHorizons/Builder/Volumes/OxygenVolumeBuilder.cs index 3fa34dfe..bcd1aa77 100644 --- a/NewHorizons/Builder/Volumes/OxygenVolumeBuilder.cs +++ b/NewHorizons/Builder/Volumes/OxygenVolumeBuilder.cs @@ -12,6 +12,8 @@ namespace NewHorizons.Builder.Volumes volume._treeVolume = info.treeVolume; volume._playRefillAudio = info.playRefillAudio; + volume.gameObject.SetActive(true); + return volume; } } diff --git a/NewHorizons/Builder/Volumes/PriorityVolumeBuilder.cs b/NewHorizons/Builder/Volumes/PriorityVolumeBuilder.cs index f3cce567..fc76910f 100644 --- a/NewHorizons/Builder/Volumes/PriorityVolumeBuilder.cs +++ b/NewHorizons/Builder/Volumes/PriorityVolumeBuilder.cs @@ -5,6 +5,17 @@ namespace NewHorizons.Builder.Volumes { public static class PriorityVolumeBuilder { + public static TVolume MakeExisting(GameObject go, GameObject planetGO, Sector sector, PriorityVolumeInfo info) where TVolume : PriorityVolume + { + var volume = VolumeBuilder.MakeExisting(go, planetGO, sector, info); + + volume._layer = info.layer; + volume.SetPriority(info.priority); + + return volume; + } + + public static TVolume Make(GameObject planetGO, Sector sector, PriorityVolumeInfo info) where TVolume : PriorityVolume { var volume = VolumeBuilder.Make(planetGO, sector, info); diff --git a/NewHorizons/Builder/Volumes/Rulesets/PlayerImpactRulesetBuilder.cs b/NewHorizons/Builder/Volumes/Rulesets/PlayerImpactRulesetBuilder.cs index f892ea07..988e9da8 100644 --- a/NewHorizons/Builder/Volumes/Rulesets/PlayerImpactRulesetBuilder.cs +++ b/NewHorizons/Builder/Volumes/Rulesets/PlayerImpactRulesetBuilder.cs @@ -12,6 +12,8 @@ namespace NewHorizons.Builder.Volumes.Rulesets volume.minImpactSpeed = info.minImpactSpeed; volume.maxImpactSpeed = info.maxImpactSpeed; + volume.gameObject.SetActive(true); + return volume; } } diff --git a/NewHorizons/Builder/Volumes/Rulesets/ProbeRulesetBuilder.cs b/NewHorizons/Builder/Volumes/Rulesets/ProbeRulesetBuilder.cs index 98823ffa..b0768324 100644 --- a/NewHorizons/Builder/Volumes/Rulesets/ProbeRulesetBuilder.cs +++ b/NewHorizons/Builder/Volumes/Rulesets/ProbeRulesetBuilder.cs @@ -15,6 +15,8 @@ namespace NewHorizons.Builder.Volumes.Rulesets volume._lanternRange = info.lanternRange; volume._ignoreAnchor = info.ignoreAnchor; + volume.gameObject.SetActive(true); + return volume; } } diff --git a/NewHorizons/Builder/Volumes/Rulesets/ThrustRulesetBuilder.cs b/NewHorizons/Builder/Volumes/Rulesets/ThrustRulesetBuilder.cs index 8a49808d..5e158ecd 100644 --- a/NewHorizons/Builder/Volumes/Rulesets/ThrustRulesetBuilder.cs +++ b/NewHorizons/Builder/Volumes/Rulesets/ThrustRulesetBuilder.cs @@ -13,6 +13,8 @@ namespace NewHorizons.Builder.Volumes.Rulesets volume._nerfJetpackBooster = info.nerfJetpackBooster; volume._nerfDuration = info.nerfDuration; + volume.gameObject.SetActive(true); + return volume; } } diff --git a/NewHorizons/Builder/Volumes/SpeedTrapVolumeBuilder.cs b/NewHorizons/Builder/Volumes/SpeedTrapVolumeBuilder.cs index fa1ed9bc..1b9319ed 100644 --- a/NewHorizons/Builder/Volumes/SpeedTrapVolumeBuilder.cs +++ b/NewHorizons/Builder/Volumes/SpeedTrapVolumeBuilder.cs @@ -12,6 +12,8 @@ namespace NewHorizons.Builder.Volumes volume._speedLimit = info.speedLimit; volume._acceleration = info.acceleration; + volume.gameObject.SetActive(true); + return volume; } } diff --git a/NewHorizons/Builder/Volumes/VanishVolumeBuilder.cs b/NewHorizons/Builder/Volumes/VanishVolumeBuilder.cs index 9bc2a514..4b312a60 100644 --- a/NewHorizons/Builder/Volumes/VanishVolumeBuilder.cs +++ b/NewHorizons/Builder/Volumes/VanishVolumeBuilder.cs @@ -1,6 +1,4 @@ -using NewHorizons.Builder.Props; using NewHorizons.External.Modules.Volumes.VolumeInfos; -using NewHorizons.Utility.OuterWilds; using UnityEngine; namespace NewHorizons.Builder.Volumes @@ -9,27 +7,13 @@ namespace NewHorizons.Builder.Volumes { public static TVolume Make(GameObject planetGO, Sector sector, VanishVolumeInfo info) where TVolume : VanishVolume { - var go = GeneralPropBuilder.MakeNew(typeof(TVolume).Name, planetGO, sector, info); - go.layer = Layer.BasicEffectVolume; - - var collider = go.AddComponent(); - collider.isTrigger = true; - collider.radius = info.radius; - - var owCollider = go.AddComponent(); - owCollider._collider = collider; - - var owTriggerVolume = go.AddComponent(); - owTriggerVolume._owCollider = owCollider; - - var volume = go.AddComponent(); + var volume = VolumeBuilder.Make(planetGO, sector, info); + var collider = volume.gameObject.GetComponent(); volume._collider = collider; volume._shrinkBodies = info.shrinkBodies; volume._onlyAffectsPlayerAndShip = info.onlyAffectsPlayerRelatedBodies; - go.SetActive(true); - return volume; } } diff --git a/NewHorizons/Builder/Volumes/VisorEffects/VisorFrostEffectVolumeBuilder.cs b/NewHorizons/Builder/Volumes/VisorEffects/VisorFrostEffectVolumeBuilder.cs index 793d72b4..c33886e2 100644 --- a/NewHorizons/Builder/Volumes/VisorEffects/VisorFrostEffectVolumeBuilder.cs +++ b/NewHorizons/Builder/Volumes/VisorEffects/VisorFrostEffectVolumeBuilder.cs @@ -12,6 +12,8 @@ namespace NewHorizons.Builder.Volumes.VisorEffects volume._frostRate = info.frostRate; volume._maxFrost = info.maxFrost; + volume.gameObject.SetActive(true); + return volume; } } diff --git a/NewHorizons/Builder/Volumes/VisorEffects/VisorRainEffectVolumeBuilder.cs b/NewHorizons/Builder/Volumes/VisorEffects/VisorRainEffectVolumeBuilder.cs index e07c95e6..6f7c9c01 100644 --- a/NewHorizons/Builder/Volumes/VisorEffects/VisorRainEffectVolumeBuilder.cs +++ b/NewHorizons/Builder/Volumes/VisorEffects/VisorRainEffectVolumeBuilder.cs @@ -13,6 +13,8 @@ namespace NewHorizons.Builder.Volumes.VisorEffects volume._dropletRate = info.dropletRate; volume._streakRate = info.streakRate; + volume.gameObject.SetActive(true); + return volume; } } diff --git a/NewHorizons/Builder/Volumes/VolumeBuilder.cs b/NewHorizons/Builder/Volumes/VolumeBuilder.cs index 0ee5d0b7..061ea559 100644 --- a/NewHorizons/Builder/Volumes/VolumeBuilder.cs +++ b/NewHorizons/Builder/Volumes/VolumeBuilder.cs @@ -7,21 +7,36 @@ namespace NewHorizons.Builder.Volumes { public static class VolumeBuilder { + public static TVolume MakeExisting(GameObject go, GameObject planetGO, Sector sector, VolumeInfo info) where TVolume : MonoBehaviour + { + // Respect existing layer if set to a valid volume layer + if (go.layer != Layer.AdvancedEffectVolume) + { + go.layer = Layer.BasicEffectVolume; + } + + // Skip creating a trigger volume if one already exists and has a shape set and we aren't overriding it + var trigger = go.GetComponent(); + if (trigger == null || (trigger._shape == null && trigger._owCollider == null) || info.shape != null || info.radius > 0f) + { + ShapeBuilder.AddTriggerVolume(go, info.shape, info.radius); + } + + var volume = go.AddComponent(); + + return volume; + } + public static TVolume Make(GameObject planetGO, Sector sector, VolumeInfo info) where TVolume : MonoBehaviour //Could be BaseVolume but I need to create vanilla volumes too. { var go = GeneralPropBuilder.MakeNew(typeof(TVolume).Name, planetGO, sector, info); - go.layer = Layer.BasicEffectVolume; - - var shape = go.AddComponent(); - shape.radius = info.radius; - - var owTriggerVolume = go.AddComponent(); - owTriggerVolume._shape = shape; - - var volume = go.AddComponent(); - - go.SetActive(true); + return MakeExisting(go, planetGO, sector, info); + } + public static TVolume MakeAndEnable(GameObject planetGO, Sector sector, VolumeInfo info) where TVolume : MonoBehaviour + { + var volume = Make(planetGO, sector, info); + volume.gameObject.SetActive(true); return volume; } } diff --git a/NewHorizons/Builder/Volumes/VolumesBuildManager.cs b/NewHorizons/Builder/Volumes/VolumesBuildManager.cs index acd48bf1..66c33c49 100644 --- a/NewHorizons/Builder/Volumes/VolumesBuildManager.cs +++ b/NewHorizons/Builder/Volumes/VolumesBuildManager.cs @@ -60,28 +60,28 @@ namespace NewHorizons.Builder.Volumes { foreach (var mapRestrictionVolume in config.Volumes.mapRestrictionVolumes) { - VolumeBuilder.Make(go, sector, mapRestrictionVolume); + VolumeBuilder.MakeAndEnable(go, sector, mapRestrictionVolume); } } if (config.Volumes.interferenceVolumes != null) { foreach (var interferenceVolume in config.Volumes.interferenceVolumes) { - VolumeBuilder.Make(go, sector, interferenceVolume); + VolumeBuilder.MakeAndEnable(go, sector, interferenceVolume); } } if (config.Volumes.reverbVolumes != null) { foreach (var reverbVolume in config.Volumes.reverbVolumes) { - VolumeBuilder.Make(go, sector, reverbVolume); + VolumeBuilder.MakeAndEnable(go, sector, reverbVolume); } } if (config.Volumes.insulatingVolumes != null) { foreach (var insulatingVolume in config.Volumes.insulatingVolumes) { - VolumeBuilder.Make(go, sector, insulatingVolume); + VolumeBuilder.MakeAndEnable(go, sector, insulatingVolume); } } if (config.Volumes.zeroGravityVolumes != null) @@ -156,14 +156,14 @@ namespace NewHorizons.Builder.Volumes { foreach (var destructionVolume in config.Volumes.probe.destructionVolumes) { - VolumeBuilder.Make(go, sector, destructionVolume); + VolumeBuilder.MakeAndEnable(go, sector, destructionVolume); } } if (config.Volumes.probe.safetyVolumes != null) { foreach (var safetyVolume in config.Volumes.probe.safetyVolumes) { - VolumeBuilder.Make(go, sector, safetyVolume); + VolumeBuilder.MakeAndEnable(go, sector, safetyVolume); } } } @@ -190,7 +190,7 @@ namespace NewHorizons.Builder.Volumes { foreach (var antiTravelMusicRuleset in config.Volumes.rulesets.antiTravelMusicRulesets) { - VolumeBuilder.Make(go, sector, antiTravelMusicRuleset); + VolumeBuilder.MakeAndEnable(go, sector, antiTravelMusicRuleset); } } if (config.Volumes.rulesets.playerImpactRulesets != null) @@ -219,7 +219,7 @@ namespace NewHorizons.Builder.Volumes { foreach (var referenceFrameBlockerVolume in config.Volumes.referenceFrameBlockerVolumes) { - VolumeBuilder.Make(go, sector, referenceFrameBlockerVolume); + VolumeBuilder.MakeAndEnable(go, sector, referenceFrameBlockerVolume); } } if (config.Volumes.speedTrapVolumes != null) @@ -233,7 +233,7 @@ namespace NewHorizons.Builder.Volumes { foreach (var lightSourceVolume in config.Volumes.lightSourceVolumes) { - VolumeBuilder.Make(go, sector, lightSourceVolume); + VolumeBuilder.MakeAndEnable(go, sector, lightSourceVolume); } } if (config.Volumes.solarSystemVolume != null) diff --git a/NewHorizons/Builder/Volumes/ZeroGVolumeBuilder.cs b/NewHorizons/Builder/Volumes/ZeroGVolumeBuilder.cs index afdde0b8..70ecc7e1 100644 --- a/NewHorizons/Builder/Volumes/ZeroGVolumeBuilder.cs +++ b/NewHorizons/Builder/Volumes/ZeroGVolumeBuilder.cs @@ -11,6 +11,8 @@ namespace NewHorizons.Builder.Volumes volume._inheritable = true; + volume.gameObject.SetActive(true); + return volume; } } diff --git a/NewHorizons/External/Modules/Volumes/VolumeInfos/ForceVolumeInfo.cs b/NewHorizons/External/Modules/Volumes/VolumeInfos/ForceVolumeInfo.cs index 4d5fe636..e56a3ca5 100644 --- a/NewHorizons/External/Modules/Volumes/VolumeInfos/ForceVolumeInfo.cs +++ b/NewHorizons/External/Modules/Volumes/VolumeInfos/ForceVolumeInfo.cs @@ -12,11 +12,6 @@ namespace NewHorizons.External.Modules.Volumes.VolumeInfos [JsonObject] public class ForceVolumeInfo : PriorityVolumeInfo { - /// - /// The shape of this volume. Defaults to a sphere shape with a radius of `radius`. - /// - public ShapeInfo shape; - /// /// The force applied by this volume. Can be negative to reverse the direction. /// diff --git a/NewHorizons/External/Modules/Volumes/VolumeInfos/VolumeInfo.cs b/NewHorizons/External/Modules/Volumes/VolumeInfos/VolumeInfo.cs index 17cee4bb..94f7a41b 100644 --- a/NewHorizons/External/Modules/Volumes/VolumeInfos/VolumeInfo.cs +++ b/NewHorizons/External/Modules/Volumes/VolumeInfos/VolumeInfo.cs @@ -1,3 +1,4 @@ +using NewHorizons.External.Modules.Props; using Newtonsoft.Json; using System.ComponentModel; @@ -7,8 +8,13 @@ namespace NewHorizons.External.Modules.Volumes.VolumeInfos public class VolumeInfo : GeneralPropInfo { /// - /// The radius of this volume. + /// The radius of this volume, if a shape is not specified. /// [DefaultValue(1f)] public float radius = 1f; + + /// + /// The shape of this volume. Defaults to a sphere with a radius of `radius` if not specified. + /// + public ShapeInfo shape; } } From 3d2634811375bfe293dd33ca0b52c2a9d87b539b Mon Sep 17 00:00:00 2001 From: Ben C Date: Sat, 22 Feb 2025 20:14:32 +0000 Subject: [PATCH 06/57] Updated Schemas --- NewHorizons/Schemas/body_schema.json | 902 +++++++++++++++------------ 1 file changed, 487 insertions(+), 415 deletions(-) diff --git a/NewHorizons/Schemas/body_schema.json b/NewHorizons/Schemas/body_schema.json index 32f57b50..f3e7871e 100644 --- a/NewHorizons/Schemas/body_schema.json +++ b/NewHorizons/Schemas/body_schema.json @@ -5472,10 +5472,14 @@ }, "radius": { "type": "number", - "description": "The radius of this volume.", + "description": "The radius of this volume, if a shape is not specified.", "format": "float", "default": 1.0 }, + "shape": { + "description": "The shape of this volume. Defaults to a sphere with a radius of `radius` if not specified.", + "$ref": "#/definitions/ShapeInfo" + }, "rotation": { "description": "Rotation of the object", "$ref": "#/definitions/MVector3" @@ -5549,388 +5553,6 @@ } } }, - "NHClipSelectionType": { - "type": "string", - "description": "", - "x-enumNames": [ - "RANDOM", - "SEQUENTIAL", - "MANUAL" - ], - "enum": [ - "random", - "sequential", - "manual" - ] - }, - "DayNightAudioVolumeInfo": { - "type": "object", - "additionalProperties": false, - "properties": { - "layer": { - "type": "integer", - "description": "The layer of this volume.\n\nLayers separate the priority system. The priority of volumes in one layer will not affect or override volumes in another. The highest priority volume in each layer will stack like normal.\nThe exception is layer 0. A higher-priority volume in layer 0 will override lower-priority volumes in ALL other layers. A lower-priority volume in layer 0 will stack with other layers like normal.\n \nEx: A player could be affected by the sun on layer 9 priority 0 and planet gravity on layer 3 priority 2. They would experience the gravity of both volumes since they are on different layers.\nIf there was a zero-g volume on layer 0 priority 1, since it is on layer 0 it will override the gravity from the sun (priority 0 which is less than 1) but they will still feel the \ngravity of the planet (priority 2 is greater than 1). The zero-g volume will also still be applied because it is on a different layer.\n \nDefault value here is 0 which means this volume's priority will be evaluated against all other priority volumes regardless of their layer.", - "format": "int32", - "default": 0 - }, - "priority": { - "type": "integer", - "description": "The priority of this volume.\n\nVolumes of higher priority will override volumes of lower priority. Volumes of the same priority will stack like normal.\nEx: A player in a gravity volume with priority 0, and zero-gravity volume with priority 1, will feel zero gravity.\n \nDefault value here is 1 instead of 0 so it automatically overrides planet gravity, which is 0 by default. ", - "format": "int32", - "default": 1 - }, - "radius": { - "type": "number", - "description": "The radius of this volume.", - "format": "float", - "default": 1.0 - }, - "rotation": { - "description": "Rotation of the object", - "$ref": "#/definitions/MVector3" - }, - "alignRadial": { - "type": [ - "boolean", - "null" - ], - "description": "Do we try to automatically align this object to stand upright relative to the body's center? Stacks with rotation.\nDefaults to true for geysers, tornados, and volcanoes, and false for everything else." - }, - "position": { - "description": "Position of the object", - "$ref": "#/definitions/MVector3" - }, - "isRelativeToParent": { - "type": "boolean", - "description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object." - }, - "parentPath": { - "type": "string", - "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)." - }, - "rename": { - "type": "string", - "description": "An optional rename of this object" - }, - "dayAudio": { - "type": "string", - "description": "The audio to use during the day. Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list. Leave empty for no daytime audio." - }, - "nightAudio": { - "type": "string", - "description": "The audio to use during the day. Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list. Leave empty for no nightime audio." - }, - "sun": { - "type": "string", - "description": "The name of the astro object used to determine if it is day or night." - }, - "dayWindow": { - "type": "number", - "description": "Angle in degrees defining daytime. Inside this window it will be day and outside it will be night.", - "format": "float", - "default": 180.0, - "maximum": 360.0, - "minimum": 0.0 - }, - "volume": { - "type": "number", - "description": "The loudness of the audio", - "format": "float", - "default": 1.0, - "maximum": 1.0, - "minimum": 0.0 - }, - "track": { - "description": "The audio track of this audio volume.\nMost of the time you'll use environment (the default) for sound effects and music for music. ", - "default": "environment", - "$ref": "#/definitions/NHAudioMixerTrackName" - } - } - }, - "DestructionVolumeInfo": { - "type": "object", - "additionalProperties": false, - "properties": { - "shrinkBodies": { - "type": "boolean", - "description": "Whether the bodies will shrink when they enter this volume or just disappear instantly.", - "default": true - }, - "onlyAffectsPlayerRelatedBodies": { - "type": "boolean", - "description": "Whether this volume only affects the player, ship, probe/scout, model rocket ship, and nomai shuttle." - }, - "radius": { - "type": "number", - "description": "The radius of this volume.", - "format": "float", - "default": 1.0 - }, - "rotation": { - "description": "Rotation of the object", - "$ref": "#/definitions/MVector3" - }, - "alignRadial": { - "type": [ - "boolean", - "null" - ], - "description": "Do we try to automatically align this object to stand upright relative to the body's center? Stacks with rotation.\nDefaults to true for geysers, tornados, and volcanoes, and false for everything else." - }, - "position": { - "description": "Position of the object", - "$ref": "#/definitions/MVector3" - }, - "isRelativeToParent": { - "type": "boolean", - "description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object." - }, - "parentPath": { - "type": "string", - "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)." - }, - "rename": { - "type": "string", - "description": "An optional rename of this object" - }, - "deathType": { - "description": "The type of death the player will have if they enter this volume.", - "default": "default", - "$ref": "#/definitions/NHDeathType" - } - } - }, - "NHDeathType": { - "type": "string", - "description": "Some special death types are:\n\nSupernova: Special death type used when the supernova hits you. You will not wake up if in the Dreamworld. \n\nDigestion: Death type used by anglerfish (and cut-content ghosts and water monster)\n\nBig bang: Special death type used at the end of the game\n\nMeditation: Special death type used when skipping to the next loop. You will not wake up if in the Dreamworld. \n\nTimeloop: Special death type used when the time loop ends. You will not wake up if in the Dreamworld. \n\nBlackhole: Special death type used by the ATP blackhole (and custom NH blackholes without whitehole destinations)\n\nDream: Special DLC death type used to kill a player in the real world while in the Dreamworld (i.e., you will loop not wake up)\n\nDreamExplosion: Special DLC death type used by the prototype artifact to kill a player in the real world while in the Dreamworld (i.e., you will loop not wake up)\n\nCrushedByElevator: Similar to the Crushed death type, but much faster", - "x-enumNames": [ - "Default", - "Impact", - "Asphyxiation", - "Energy", - "Supernova", - "Digestion", - "BigBang", - "Crushed", - "Meditation", - "TimeLoop", - "Lava", - "BlackHole", - "Dream", - "DreamExplosion", - "CrushedByElevator" - ], - "enum": [ - "default", - "impact", - "asphyxiation", - "energy", - "supernova", - "digestion", - "bigBang", - "crushed", - "meditation", - "timeLoop", - "lava", - "blackHole", - "dream", - "dreamExplosion", - "crushedByElevator" - ] - }, - "FluidVolumeInfo": { - "type": "object", - "additionalProperties": false, - "properties": { - "layer": { - "type": "integer", - "description": "The layer of this volume.\n\nLayers separate the priority system. The priority of volumes in one layer will not affect or override volumes in another. The highest priority volume in each layer will stack like normal.\nThe exception is layer 0. A higher-priority volume in layer 0 will override lower-priority volumes in ALL other layers. A lower-priority volume in layer 0 will stack with other layers like normal.\n \nEx: A player could be affected by the sun on layer 9 priority 0 and planet gravity on layer 3 priority 2. They would experience the gravity of both volumes since they are on different layers.\nIf there was a zero-g volume on layer 0 priority 1, since it is on layer 0 it will override the gravity from the sun (priority 0 which is less than 1) but they will still feel the \ngravity of the planet (priority 2 is greater than 1). The zero-g volume will also still be applied because it is on a different layer.\n \nDefault value here is 0 which means this volume's priority will be evaluated against all other priority volumes regardless of their layer.", - "format": "int32", - "default": 0 - }, - "priority": { - "type": "integer", - "description": "The priority of this volume.\n\nVolumes of higher priority will override volumes of lower priority. Volumes of the same priority will stack like normal.\nEx: A player in a gravity volume with priority 0, and zero-gravity volume with priority 1, will feel zero gravity.\n \nDefault value here is 1 instead of 0 so it automatically overrides planet gravity, which is 0 by default. ", - "format": "int32", - "default": 1 - }, - "radius": { - "type": "number", - "description": "The radius of this volume.", - "format": "float", - "default": 1.0 - }, - "rotation": { - "description": "Rotation of the object", - "$ref": "#/definitions/MVector3" - }, - "alignRadial": { - "type": [ - "boolean", - "null" - ], - "description": "Do we try to automatically align this object to stand upright relative to the body's center? Stacks with rotation.\nDefaults to true for geysers, tornados, and volcanoes, and false for everything else." - }, - "position": { - "description": "Position of the object", - "$ref": "#/definitions/MVector3" - }, - "isRelativeToParent": { - "type": "boolean", - "description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object." - }, - "parentPath": { - "type": "string", - "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)." - }, - "rename": { - "type": "string", - "description": "An optional rename of this object" - }, - "density": { - "type": "number", - "description": "Density of the fluid. The higher the density, the harder it is to go through this fluid.", - "format": "float", - "default": 1.2 - }, - "type": { - "description": "The type of fluid for this volume.", - "$ref": "#/definitions/NHFluidType" - }, - "alignmentFluid": { - "type": "boolean", - "description": "Should the player and rafts align to this fluid.", - "default": true - }, - "allowShipAutoroll": { - "type": "boolean", - "description": "Should the ship align to the fluid by rolling." - }, - "disableOnStart": { - "type": "boolean", - "description": "Disable this fluid volume immediately?" - } - } - }, - "ForceModule": { - "type": "object", - "additionalProperties": false, - "properties": { - "cylindricalVolumes": { - "type": "array", - "description": "Applies a constant force along the volume's XZ plane towards the volume's center. Affects alignment.", - "items": { - "$ref": "#/definitions/CylindricalForceVolumeInfo" - } - }, - "directionalVolumes": { - "type": "array", - "description": "Applies a constant force in the direction of the volume's Y axis. May affect alignment.", - "items": { - "$ref": "#/definitions/DirectionalForceVolumeInfo" - } - }, - "gravityVolumes": { - "type": "array", - "description": "Applies planet-like gravity towards the volume's center with falloff by distance. May affect alignment.\nFor actual planetary body gravity, use the properties in the Base module.", - "items": { - "$ref": "#/definitions/GravityVolumeInfo" - } - }, - "polarVolumes": { - "type": "array", - "description": "Applies a constant force towards the volume's center. Affects alignment.", - "items": { - "$ref": "#/definitions/PolarForceVolumeInfo" - } - }, - "radialVolumes": { - "type": "array", - "description": "Applies a force towards the volume's center with falloff by distance. Affects alignment.", - "items": { - "$ref": "#/definitions/RadialForceVolumeInfo" - } - } - } - }, - "CylindricalForceVolumeInfo": { - "type": "object", - "additionalProperties": false, - "properties": { - "shape": { - "description": "The shape of this volume. Defaults to a sphere shape with a radius of `radius`.", - "$ref": "#/definitions/ShapeInfo" - }, - "force": { - "type": "number", - "description": "The force applied by this volume. Can be negative to reverse the direction.", - "format": "float" - }, - "alignmentPriority": { - "type": "integer", - "description": "The priority of this force volume for the purposes of alignment.\n\nVolumes of higher priority will override volumes of lower priority. Volumes of the same priority will stack like normal.\nEx: A player in a gravity volume with priority 0, and zero-gravity volume with priority 1, will feel zero gravity.\n \nDefault value here is 1 instead of 0 so it automatically overrides planet gravity, which is 0 by default. ", - "format": "int32", - "default": 1 - }, - "inheritable": { - "type": "boolean", - "description": "Whether this force volume is inheritable. The most recently activated inheritable force volume will stack with other force volumes even if their priorities differ." - }, - "layer": { - "type": "integer", - "description": "The layer of this volume.\n\nLayers separate the priority system. The priority of volumes in one layer will not affect or override volumes in another. The highest priority volume in each layer will stack like normal.\nThe exception is layer 0. A higher-priority volume in layer 0 will override lower-priority volumes in ALL other layers. A lower-priority volume in layer 0 will stack with other layers like normal.\n \nEx: A player could be affected by the sun on layer 9 priority 0 and planet gravity on layer 3 priority 2. They would experience the gravity of both volumes since they are on different layers.\nIf there was a zero-g volume on layer 0 priority 1, since it is on layer 0 it will override the gravity from the sun (priority 0 which is less than 1) but they will still feel the \ngravity of the planet (priority 2 is greater than 1). The zero-g volume will also still be applied because it is on a different layer.\n \nDefault value here is 0 which means this volume's priority will be evaluated against all other priority volumes regardless of their layer.", - "format": "int32", - "default": 0 - }, - "priority": { - "type": "integer", - "description": "The priority of this volume.\n\nVolumes of higher priority will override volumes of lower priority. Volumes of the same priority will stack like normal.\nEx: A player in a gravity volume with priority 0, and zero-gravity volume with priority 1, will feel zero gravity.\n \nDefault value here is 1 instead of 0 so it automatically overrides planet gravity, which is 0 by default. ", - "format": "int32", - "default": 1 - }, - "radius": { - "type": "number", - "description": "The radius of this volume.", - "format": "float", - "default": 1.0 - }, - "rotation": { - "description": "Rotation of the object", - "$ref": "#/definitions/MVector3" - }, - "alignRadial": { - "type": [ - "boolean", - "null" - ], - "description": "Do we try to automatically align this object to stand upright relative to the body's center? Stacks with rotation.\nDefaults to true for geysers, tornados, and volcanoes, and false for everything else." - }, - "position": { - "description": "Position of the object", - "$ref": "#/definitions/MVector3" - }, - "isRelativeToParent": { - "type": "boolean", - "description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object." - }, - "parentPath": { - "type": "string", - "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)." - }, - "rename": { - "type": "string", - "description": "An optional rename of this object" - }, - "normal": { - "description": "The direction that the force applied by this volume will be perpendicular to. Defaults to up (0, 1, 0).", - "$ref": "#/definitions/MVector3" - }, - "playGravityCrystalAudio": { - "type": "boolean", - "description": "Whether to play the gravity crystal audio when the player is in this volume." - } - } - }, "ShapeInfo": { "type": "object", "additionalProperties": false, @@ -6026,14 +5648,326 @@ "z" ] }, - "DirectionalForceVolumeInfo": { + "NHClipSelectionType": { + "type": "string", + "description": "", + "x-enumNames": [ + "RANDOM", + "SEQUENTIAL", + "MANUAL" + ], + "enum": [ + "random", + "sequential", + "manual" + ] + }, + "DayNightAudioVolumeInfo": { "type": "object", "additionalProperties": false, "properties": { + "layer": { + "type": "integer", + "description": "The layer of this volume.\n\nLayers separate the priority system. The priority of volumes in one layer will not affect or override volumes in another. The highest priority volume in each layer will stack like normal.\nThe exception is layer 0. A higher-priority volume in layer 0 will override lower-priority volumes in ALL other layers. A lower-priority volume in layer 0 will stack with other layers like normal.\n \nEx: A player could be affected by the sun on layer 9 priority 0 and planet gravity on layer 3 priority 2. They would experience the gravity of both volumes since they are on different layers.\nIf there was a zero-g volume on layer 0 priority 1, since it is on layer 0 it will override the gravity from the sun (priority 0 which is less than 1) but they will still feel the \ngravity of the planet (priority 2 is greater than 1). The zero-g volume will also still be applied because it is on a different layer.\n \nDefault value here is 0 which means this volume's priority will be evaluated against all other priority volumes regardless of their layer.", + "format": "int32", + "default": 0 + }, + "priority": { + "type": "integer", + "description": "The priority of this volume.\n\nVolumes of higher priority will override volumes of lower priority. Volumes of the same priority will stack like normal.\nEx: A player in a gravity volume with priority 0, and zero-gravity volume with priority 1, will feel zero gravity.\n \nDefault value here is 1 instead of 0 so it automatically overrides planet gravity, which is 0 by default. ", + "format": "int32", + "default": 1 + }, + "radius": { + "type": "number", + "description": "The radius of this volume, if a shape is not specified.", + "format": "float", + "default": 1.0 + }, "shape": { - "description": "The shape of this volume. Defaults to a sphere shape with a radius of `radius`.", + "description": "The shape of this volume. Defaults to a sphere with a radius of `radius` if not specified.", "$ref": "#/definitions/ShapeInfo" }, + "rotation": { + "description": "Rotation of the object", + "$ref": "#/definitions/MVector3" + }, + "alignRadial": { + "type": [ + "boolean", + "null" + ], + "description": "Do we try to automatically align this object to stand upright relative to the body's center? Stacks with rotation.\nDefaults to true for geysers, tornados, and volcanoes, and false for everything else." + }, + "position": { + "description": "Position of the object", + "$ref": "#/definitions/MVector3" + }, + "isRelativeToParent": { + "type": "boolean", + "description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object." + }, + "parentPath": { + "type": "string", + "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)." + }, + "rename": { + "type": "string", + "description": "An optional rename of this object" + }, + "dayAudio": { + "type": "string", + "description": "The audio to use during the day. Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list. Leave empty for no daytime audio." + }, + "nightAudio": { + "type": "string", + "description": "The audio to use during the day. Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list. Leave empty for no nightime audio." + }, + "sun": { + "type": "string", + "description": "The name of the astro object used to determine if it is day or night." + }, + "dayWindow": { + "type": "number", + "description": "Angle in degrees defining daytime. Inside this window it will be day and outside it will be night.", + "format": "float", + "default": 180.0, + "maximum": 360.0, + "minimum": 0.0 + }, + "volume": { + "type": "number", + "description": "The loudness of the audio", + "format": "float", + "default": 1.0, + "maximum": 1.0, + "minimum": 0.0 + }, + "track": { + "description": "The audio track of this audio volume.\nMost of the time you'll use environment (the default) for sound effects and music for music. ", + "default": "environment", + "$ref": "#/definitions/NHAudioMixerTrackName" + } + } + }, + "DestructionVolumeInfo": { + "type": "object", + "additionalProperties": false, + "properties": { + "shrinkBodies": { + "type": "boolean", + "description": "Whether the bodies will shrink when they enter this volume or just disappear instantly.", + "default": true + }, + "onlyAffectsPlayerRelatedBodies": { + "type": "boolean", + "description": "Whether this volume only affects the player, ship, probe/scout, model rocket ship, and nomai shuttle." + }, + "radius": { + "type": "number", + "description": "The radius of this volume, if a shape is not specified.", + "format": "float", + "default": 1.0 + }, + "shape": { + "description": "The shape of this volume. Defaults to a sphere with a radius of `radius` if not specified.", + "$ref": "#/definitions/ShapeInfo" + }, + "rotation": { + "description": "Rotation of the object", + "$ref": "#/definitions/MVector3" + }, + "alignRadial": { + "type": [ + "boolean", + "null" + ], + "description": "Do we try to automatically align this object to stand upright relative to the body's center? Stacks with rotation.\nDefaults to true for geysers, tornados, and volcanoes, and false for everything else." + }, + "position": { + "description": "Position of the object", + "$ref": "#/definitions/MVector3" + }, + "isRelativeToParent": { + "type": "boolean", + "description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object." + }, + "parentPath": { + "type": "string", + "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)." + }, + "rename": { + "type": "string", + "description": "An optional rename of this object" + }, + "deathType": { + "description": "The type of death the player will have if they enter this volume.", + "default": "default", + "$ref": "#/definitions/NHDeathType" + } + } + }, + "NHDeathType": { + "type": "string", + "description": "Some special death types are:\n\nSupernova: Special death type used when the supernova hits you. You will not wake up if in the Dreamworld. \n\nDigestion: Death type used by anglerfish (and cut-content ghosts and water monster)\n\nBig bang: Special death type used at the end of the game\n\nMeditation: Special death type used when skipping to the next loop. You will not wake up if in the Dreamworld. \n\nTimeloop: Special death type used when the time loop ends. You will not wake up if in the Dreamworld. \n\nBlackhole: Special death type used by the ATP blackhole (and custom NH blackholes without whitehole destinations)\n\nDream: Special DLC death type used to kill a player in the real world while in the Dreamworld (i.e., you will loop not wake up)\n\nDreamExplosion: Special DLC death type used by the prototype artifact to kill a player in the real world while in the Dreamworld (i.e., you will loop not wake up)\n\nCrushedByElevator: Similar to the Crushed death type, but much faster", + "x-enumNames": [ + "Default", + "Impact", + "Asphyxiation", + "Energy", + "Supernova", + "Digestion", + "BigBang", + "Crushed", + "Meditation", + "TimeLoop", + "Lava", + "BlackHole", + "Dream", + "DreamExplosion", + "CrushedByElevator" + ], + "enum": [ + "default", + "impact", + "asphyxiation", + "energy", + "supernova", + "digestion", + "bigBang", + "crushed", + "meditation", + "timeLoop", + "lava", + "blackHole", + "dream", + "dreamExplosion", + "crushedByElevator" + ] + }, + "FluidVolumeInfo": { + "type": "object", + "additionalProperties": false, + "properties": { + "layer": { + "type": "integer", + "description": "The layer of this volume.\n\nLayers separate the priority system. The priority of volumes in one layer will not affect or override volumes in another. The highest priority volume in each layer will stack like normal.\nThe exception is layer 0. A higher-priority volume in layer 0 will override lower-priority volumes in ALL other layers. A lower-priority volume in layer 0 will stack with other layers like normal.\n \nEx: A player could be affected by the sun on layer 9 priority 0 and planet gravity on layer 3 priority 2. They would experience the gravity of both volumes since they are on different layers.\nIf there was a zero-g volume on layer 0 priority 1, since it is on layer 0 it will override the gravity from the sun (priority 0 which is less than 1) but they will still feel the \ngravity of the planet (priority 2 is greater than 1). The zero-g volume will also still be applied because it is on a different layer.\n \nDefault value here is 0 which means this volume's priority will be evaluated against all other priority volumes regardless of their layer.", + "format": "int32", + "default": 0 + }, + "priority": { + "type": "integer", + "description": "The priority of this volume.\n\nVolumes of higher priority will override volumes of lower priority. Volumes of the same priority will stack like normal.\nEx: A player in a gravity volume with priority 0, and zero-gravity volume with priority 1, will feel zero gravity.\n \nDefault value here is 1 instead of 0 so it automatically overrides planet gravity, which is 0 by default. ", + "format": "int32", + "default": 1 + }, + "radius": { + "type": "number", + "description": "The radius of this volume, if a shape is not specified.", + "format": "float", + "default": 1.0 + }, + "shape": { + "description": "The shape of this volume. Defaults to a sphere with a radius of `radius` if not specified.", + "$ref": "#/definitions/ShapeInfo" + }, + "rotation": { + "description": "Rotation of the object", + "$ref": "#/definitions/MVector3" + }, + "alignRadial": { + "type": [ + "boolean", + "null" + ], + "description": "Do we try to automatically align this object to stand upright relative to the body's center? Stacks with rotation.\nDefaults to true for geysers, tornados, and volcanoes, and false for everything else." + }, + "position": { + "description": "Position of the object", + "$ref": "#/definitions/MVector3" + }, + "isRelativeToParent": { + "type": "boolean", + "description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object." + }, + "parentPath": { + "type": "string", + "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)." + }, + "rename": { + "type": "string", + "description": "An optional rename of this object" + }, + "density": { + "type": "number", + "description": "Density of the fluid. The higher the density, the harder it is to go through this fluid.", + "format": "float", + "default": 1.2 + }, + "type": { + "description": "The type of fluid for this volume.", + "$ref": "#/definitions/NHFluidType" + }, + "alignmentFluid": { + "type": "boolean", + "description": "Should the player and rafts align to this fluid.", + "default": true + }, + "allowShipAutoroll": { + "type": "boolean", + "description": "Should the ship align to the fluid by rolling." + }, + "disableOnStart": { + "type": "boolean", + "description": "Disable this fluid volume immediately?" + } + } + }, + "ForceModule": { + "type": "object", + "additionalProperties": false, + "properties": { + "cylindricalVolumes": { + "type": "array", + "description": "Applies a constant force along the volume's XZ plane towards the volume's center. Affects alignment.", + "items": { + "$ref": "#/definitions/CylindricalForceVolumeInfo" + } + }, + "directionalVolumes": { + "type": "array", + "description": "Applies a constant force in the direction of the volume's Y axis. May affect alignment.", + "items": { + "$ref": "#/definitions/DirectionalForceVolumeInfo" + } + }, + "gravityVolumes": { + "type": "array", + "description": "Applies planet-like gravity towards the volume's center with falloff by distance. May affect alignment.\nFor actual planetary body gravity, use the properties in the Base module.", + "items": { + "$ref": "#/definitions/GravityVolumeInfo" + } + }, + "polarVolumes": { + "type": "array", + "description": "Applies a constant force towards the volume's center. Affects alignment.", + "items": { + "$ref": "#/definitions/PolarForceVolumeInfo" + } + }, + "radialVolumes": { + "type": "array", + "description": "Applies a force towards the volume's center with falloff by distance. Affects alignment.", + "items": { + "$ref": "#/definitions/RadialForceVolumeInfo" + } + } + } + }, + "CylindricalForceVolumeInfo": { + "type": "object", + "additionalProperties": false, + "properties": { "force": { "type": "number", "description": "The force applied by this volume. Can be negative to reverse the direction.", @@ -6063,10 +5997,92 @@ }, "radius": { "type": "number", - "description": "The radius of this volume.", + "description": "The radius of this volume, if a shape is not specified.", "format": "float", "default": 1.0 }, + "shape": { + "description": "The shape of this volume. Defaults to a sphere with a radius of `radius` if not specified.", + "$ref": "#/definitions/ShapeInfo" + }, + "rotation": { + "description": "Rotation of the object", + "$ref": "#/definitions/MVector3" + }, + "alignRadial": { + "type": [ + "boolean", + "null" + ], + "description": "Do we try to automatically align this object to stand upright relative to the body's center? Stacks with rotation.\nDefaults to true for geysers, tornados, and volcanoes, and false for everything else." + }, + "position": { + "description": "Position of the object", + "$ref": "#/definitions/MVector3" + }, + "isRelativeToParent": { + "type": "boolean", + "description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object." + }, + "parentPath": { + "type": "string", + "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)." + }, + "rename": { + "type": "string", + "description": "An optional rename of this object" + }, + "normal": { + "description": "The direction that the force applied by this volume will be perpendicular to. Defaults to up (0, 1, 0).", + "$ref": "#/definitions/MVector3" + }, + "playGravityCrystalAudio": { + "type": "boolean", + "description": "Whether to play the gravity crystal audio when the player is in this volume." + } + } + }, + "DirectionalForceVolumeInfo": { + "type": "object", + "additionalProperties": false, + "properties": { + "force": { + "type": "number", + "description": "The force applied by this volume. Can be negative to reverse the direction.", + "format": "float" + }, + "alignmentPriority": { + "type": "integer", + "description": "The priority of this force volume for the purposes of alignment.\n\nVolumes of higher priority will override volumes of lower priority. Volumes of the same priority will stack like normal.\nEx: A player in a gravity volume with priority 0, and zero-gravity volume with priority 1, will feel zero gravity.\n \nDefault value here is 1 instead of 0 so it automatically overrides planet gravity, which is 0 by default. ", + "format": "int32", + "default": 1 + }, + "inheritable": { + "type": "boolean", + "description": "Whether this force volume is inheritable. The most recently activated inheritable force volume will stack with other force volumes even if their priorities differ." + }, + "layer": { + "type": "integer", + "description": "The layer of this volume.\n\nLayers separate the priority system. The priority of volumes in one layer will not affect or override volumes in another. The highest priority volume in each layer will stack like normal.\nThe exception is layer 0. A higher-priority volume in layer 0 will override lower-priority volumes in ALL other layers. A lower-priority volume in layer 0 will stack with other layers like normal.\n \nEx: A player could be affected by the sun on layer 9 priority 0 and planet gravity on layer 3 priority 2. They would experience the gravity of both volumes since they are on different layers.\nIf there was a zero-g volume on layer 0 priority 1, since it is on layer 0 it will override the gravity from the sun (priority 0 which is less than 1) but they will still feel the \ngravity of the planet (priority 2 is greater than 1). The zero-g volume will also still be applied because it is on a different layer.\n \nDefault value here is 0 which means this volume's priority will be evaluated against all other priority volumes regardless of their layer.", + "format": "int32", + "default": 0 + }, + "priority": { + "type": "integer", + "description": "The priority of this volume.\n\nVolumes of higher priority will override volumes of lower priority. Volumes of the same priority will stack like normal.\nEx: A player in a gravity volume with priority 0, and zero-gravity volume with priority 1, will feel zero gravity.\n \nDefault value here is 1 instead of 0 so it automatically overrides planet gravity, which is 0 by default. ", + "format": "int32", + "default": 1 + }, + "radius": { + "type": "number", + "description": "The radius of this volume, if a shape is not specified.", + "format": "float", + "default": 1.0 + }, + "shape": { + "description": "The shape of this volume. Defaults to a sphere with a radius of `radius` if not specified.", + "$ref": "#/definitions/ShapeInfo" + }, "rotation": { "description": "Rotation of the object", "$ref": "#/definitions/MVector3" @@ -6117,10 +6133,6 @@ "type": "object", "additionalProperties": false, "properties": { - "shape": { - "description": "The shape of this volume. Defaults to a sphere shape with a radius of `radius`.", - "$ref": "#/definitions/ShapeInfo" - }, "force": { "type": "number", "description": "The force applied by this volume. Can be negative to reverse the direction.", @@ -6150,10 +6162,14 @@ }, "radius": { "type": "number", - "description": "The radius of this volume.", + "description": "The radius of this volume, if a shape is not specified.", "format": "float", "default": 1.0 }, + "shape": { + "description": "The shape of this volume. Defaults to a sphere with a radius of `radius` if not specified.", + "$ref": "#/definitions/ShapeInfo" + }, "rotation": { "description": "Rotation of the object", "$ref": "#/definitions/MVector3" @@ -6223,10 +6239,6 @@ "type": "object", "additionalProperties": false, "properties": { - "shape": { - "description": "The shape of this volume. Defaults to a sphere shape with a radius of `radius`.", - "$ref": "#/definitions/ShapeInfo" - }, "force": { "type": "number", "description": "The force applied by this volume. Can be negative to reverse the direction.", @@ -6256,10 +6268,14 @@ }, "radius": { "type": "number", - "description": "The radius of this volume.", + "description": "The radius of this volume, if a shape is not specified.", "format": "float", "default": 1.0 }, + "shape": { + "description": "The shape of this volume. Defaults to a sphere with a radius of `radius` if not specified.", + "$ref": "#/definitions/ShapeInfo" + }, "rotation": { "description": "Rotation of the object", "$ref": "#/definitions/MVector3" @@ -6301,10 +6317,6 @@ "type": "object", "additionalProperties": false, "properties": { - "shape": { - "description": "The shape of this volume. Defaults to a sphere shape with a radius of `radius`.", - "$ref": "#/definitions/ShapeInfo" - }, "force": { "type": "number", "description": "The force applied by this volume. Can be negative to reverse the direction.", @@ -6334,10 +6346,14 @@ }, "radius": { "type": "number", - "description": "The radius of this volume.", + "description": "The radius of this volume, if a shape is not specified.", "format": "float", "default": 1.0 }, + "shape": { + "description": "The shape of this volume. Defaults to a sphere with a radius of `radius` if not specified.", + "$ref": "#/definitions/ShapeInfo" + }, "rotation": { "description": "Rotation of the object", "$ref": "#/definitions/MVector3" @@ -6392,10 +6408,14 @@ "properties": { "radius": { "type": "number", - "description": "The radius of this volume.", + "description": "The radius of this volume, if a shape is not specified.", "format": "float", "default": 1.0 }, + "shape": { + "description": "The shape of this volume. Defaults to a sphere with a radius of `radius` if not specified.", + "$ref": "#/definitions/ShapeInfo" + }, "rotation": { "description": "Rotation of the object", "$ref": "#/definitions/MVector3" @@ -6479,9 +6499,13 @@ }, "radius": { "type": "number", - "description": "The radius of this volume.", + "description": "The radius of this volume, if a shape is not specified.", "format": "float", "default": 1.0 + }, + "shape": { + "description": "The shape of this volume. Defaults to a sphere with a radius of `radius` if not specified.", + "$ref": "#/definitions/ShapeInfo" } } }, @@ -6491,10 +6515,14 @@ "properties": { "radius": { "type": "number", - "description": "The radius of this volume.", + "description": "The radius of this volume, if a shape is not specified.", "format": "float", "default": 1.0 }, + "shape": { + "description": "The shape of this volume. Defaults to a sphere with a radius of `radius` if not specified.", + "$ref": "#/definitions/ShapeInfo" + }, "rotation": { "description": "Rotation of the object", "$ref": "#/definitions/MVector3" @@ -6573,10 +6601,14 @@ "properties": { "radius": { "type": "number", - "description": "The radius of this volume.", + "description": "The radius of this volume, if a shape is not specified.", "format": "float", "default": 1.0 }, + "shape": { + "description": "The shape of this volume. Defaults to a sphere with a radius of `radius` if not specified.", + "$ref": "#/definitions/ShapeInfo" + }, "rotation": { "description": "Rotation of the object", "$ref": "#/definitions/MVector3" @@ -6641,10 +6673,14 @@ "properties": { "radius": { "type": "number", - "description": "The radius of this volume.", + "description": "The radius of this volume, if a shape is not specified.", "format": "float", "default": 1.0 }, + "shape": { + "description": "The shape of this volume. Defaults to a sphere with a radius of `radius` if not specified.", + "$ref": "#/definitions/ShapeInfo" + }, "rotation": { "description": "Rotation of the object", "$ref": "#/definitions/MVector3" @@ -6775,10 +6811,14 @@ "properties": { "radius": { "type": "number", - "description": "The radius of this volume.", + "description": "The radius of this volume, if a shape is not specified.", "format": "float", "default": 1.0 }, + "shape": { + "description": "The shape of this volume. Defaults to a sphere with a radius of `radius` if not specified.", + "$ref": "#/definitions/ShapeInfo" + }, "rotation": { "description": "Rotation of the object", "$ref": "#/definitions/MVector3" @@ -6826,10 +6866,14 @@ "properties": { "radius": { "type": "number", - "description": "The radius of this volume.", + "description": "The radius of this volume, if a shape is not specified.", "format": "float", "default": 1.0 }, + "shape": { + "description": "The shape of this volume. Defaults to a sphere with a radius of `radius` if not specified.", + "$ref": "#/definitions/ShapeInfo" + }, "rotation": { "description": "Rotation of the object", "$ref": "#/definitions/MVector3" @@ -6887,10 +6931,14 @@ "properties": { "radius": { "type": "number", - "description": "The radius of this volume.", + "description": "The radius of this volume, if a shape is not specified.", "format": "float", "default": 1.0 }, + "shape": { + "description": "The shape of this volume. Defaults to a sphere with a radius of `radius` if not specified.", + "$ref": "#/definitions/ShapeInfo" + }, "rotation": { "description": "Rotation of the object", "$ref": "#/definitions/MVector3" @@ -6942,10 +6990,14 @@ "properties": { "radius": { "type": "number", - "description": "The radius of this volume.", + "description": "The radius of this volume, if a shape is not specified.", "format": "float", "default": 1.0 }, + "shape": { + "description": "The shape of this volume. Defaults to a sphere with a radius of `radius` if not specified.", + "$ref": "#/definitions/ShapeInfo" + }, "rotation": { "description": "Rotation of the object", "$ref": "#/definitions/MVector3" @@ -7025,10 +7077,14 @@ }, "radius": { "type": "number", - "description": "The radius of this volume.", + "description": "The radius of this volume, if a shape is not specified.", "format": "float", "default": 1.0 }, + "shape": { + "description": "The shape of this volume. Defaults to a sphere with a radius of `radius` if not specified.", + "$ref": "#/definitions/ShapeInfo" + }, "rotation": { "description": "Rotation of the object", "$ref": "#/definitions/MVector3" @@ -7090,10 +7146,14 @@ }, "radius": { "type": "number", - "description": "The radius of this volume.", + "description": "The radius of this volume, if a shape is not specified.", "format": "float", "default": 1.0 }, + "shape": { + "description": "The shape of this volume. Defaults to a sphere with a radius of `radius` if not specified.", + "$ref": "#/definitions/ShapeInfo" + }, "rotation": { "description": "Rotation of the object", "$ref": "#/definitions/MVector3" @@ -7141,10 +7201,14 @@ "properties": { "radius": { "type": "number", - "description": "The radius of this volume.", + "description": "The radius of this volume, if a shape is not specified.", "format": "float", "default": 1.0 }, + "shape": { + "description": "The shape of this volume. Defaults to a sphere with a radius of `radius` if not specified.", + "$ref": "#/definitions/ShapeInfo" + }, "rotation": { "description": "Rotation of the object", "$ref": "#/definitions/MVector3" @@ -7192,10 +7256,14 @@ "properties": { "radius": { "type": "number", - "description": "The radius of this volume.", + "description": "The radius of this volume, if a shape is not specified.", "format": "float", "default": 1.0 }, + "shape": { + "description": "The shape of this volume. Defaults to a sphere with a radius of `radius` if not specified.", + "$ref": "#/definitions/ShapeInfo" + }, "rotation": { "description": "Rotation of the object", "$ref": "#/definitions/MVector3" @@ -7240,10 +7308,14 @@ "properties": { "radius": { "type": "number", - "description": "The radius of this volume.", + "description": "The radius of this volume, if a shape is not specified.", "format": "float", "default": 1.0 }, + "shape": { + "description": "The shape of this volume. Defaults to a sphere with a radius of `radius` if not specified.", + "$ref": "#/definitions/ShapeInfo" + }, "rotation": { "description": "Rotation of the object", "$ref": "#/definitions/MVector3" From d759f1843bfde2105dc02656fa63c10169652c35 Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Sat, 15 Mar 2025 02:14:56 -0400 Subject: [PATCH 07/57] Allow comet tails on vanilla bodies --- NewHorizons/Builder/Body/CometTailBuilder.cs | 18 ++++++++++++------ NewHorizons/Builder/Body/ProxyBuilder.cs | 2 +- NewHorizons/Handlers/PlanetCreationHandler.cs | 2 +- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/NewHorizons/Builder/Body/CometTailBuilder.cs b/NewHorizons/Builder/Body/CometTailBuilder.cs index 9633b316..781c44bc 100644 --- a/NewHorizons/Builder/Body/CometTailBuilder.cs +++ b/NewHorizons/Builder/Body/CometTailBuilder.cs @@ -60,14 +60,22 @@ namespace NewHorizons.Builder.Body } } - public static void Make(GameObject planetGO, Sector sector, CometTailModule cometTailModule, PlanetConfig config) + public static void Make(GameObject planetGO, Sector sector, CometTailModule cometTailModule, PlanetConfig config, AstroObject ao) { - if (config.Orbit.primaryBody == null) + var primaryBody = ao.GetPrimaryBody(); + + if (!string.IsNullOrEmpty(config.Orbit.primaryBody)) primaryBody = AstroObjectLocator.GetAstroObject(config.Orbit.primaryBody); + + if (primaryBody == null) { NHLogger.LogError($"Comet {planetGO.name} does not orbit anything. That makes no sense"); return; } + if (string.IsNullOrEmpty(cometTailModule.primaryBody)) + cometTailModule.primaryBody = !string.IsNullOrEmpty(config.Orbit.primaryBody) ? config.Orbit.primaryBody + : (primaryBody._name == AstroObject.Name.CustomString ? primaryBody.GetCustomName() : primaryBody._name.ToString()); + var rootObj = new GameObject("CometRoot"); rootObj.SetActive(false); rootObj.transform.parent = sector?.transform ?? planetGO.transform; @@ -79,13 +87,11 @@ namespace NewHorizons.Builder.Body if (cometTailModule.rotationOverride != null) controller.SetRotationOverride(cometTailModule.rotationOverride); - if (string.IsNullOrEmpty(cometTailModule.primaryBody)) cometTailModule.primaryBody = config.Orbit.primaryBody; - Delay.FireOnNextUpdate(() => { controller.SetPrimaryBody( - AstroObjectLocator.GetAstroObject(cometTailModule.primaryBody).transform, - AstroObjectLocator.GetAstroObject(config.Orbit.primaryBody).GetAttachedOWRigidbody() + AstroObjectLocator.GetAstroObject(cometTailModule.primaryBody).transform, + primaryBody.GetAttachedOWRigidbody() ); }); diff --git a/NewHorizons/Builder/Body/ProxyBuilder.cs b/NewHorizons/Builder/Body/ProxyBuilder.cs index 0b4cafc3..943726eb 100644 --- a/NewHorizons/Builder/Body/ProxyBuilder.cs +++ b/NewHorizons/Builder/Body/ProxyBuilder.cs @@ -203,7 +203,7 @@ namespace NewHorizons.Builder.Body if (body.Config.CometTail != null) { - CometTailBuilder.Make(proxy, null, body.Config.CometTail, body.Config); + CometTailBuilder.Make(proxy, null, body.Config.CometTail, body.Config, planetGO.GetComponent()); } if (body.Config.Props?.proxyDetails != null) diff --git a/NewHorizons/Handlers/PlanetCreationHandler.cs b/NewHorizons/Handlers/PlanetCreationHandler.cs index 858131e9..8b0c01f2 100644 --- a/NewHorizons/Handlers/PlanetCreationHandler.cs +++ b/NewHorizons/Handlers/PlanetCreationHandler.cs @@ -657,7 +657,7 @@ namespace NewHorizons.Handlers if (body.Config.CometTail != null) { - CometTailBuilder.Make(go, sector, body.Config.CometTail, body.Config); + CometTailBuilder.Make(go, sector, body.Config.CometTail, body.Config, go.GetComponent()); } if (body.Config.Lava != null) From 4519d2f25727b1078954858a31f70b27072639f3 Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Sat, 15 Mar 2025 02:58:34 -0400 Subject: [PATCH 08/57] Move to an extension --- NewHorizons/Builder/Body/CometTailBuilder.cs | 2 +- NewHorizons/Utility/NewHorizonExtensions.cs | 5 +++++ NewHorizons/Utility/OuterWilds/AstroObjectLocator.cs | 4 ++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/NewHorizons/Builder/Body/CometTailBuilder.cs b/NewHorizons/Builder/Body/CometTailBuilder.cs index 781c44bc..2af94def 100644 --- a/NewHorizons/Builder/Body/CometTailBuilder.cs +++ b/NewHorizons/Builder/Body/CometTailBuilder.cs @@ -74,7 +74,7 @@ namespace NewHorizons.Builder.Body if (string.IsNullOrEmpty(cometTailModule.primaryBody)) cometTailModule.primaryBody = !string.IsNullOrEmpty(config.Orbit.primaryBody) ? config.Orbit.primaryBody - : (primaryBody._name == AstroObject.Name.CustomString ? primaryBody.GetCustomName() : primaryBody._name.ToString()); + : primaryBody.GetKey(); var rootObj = new GameObject("CometRoot"); rootObj.SetActive(false); diff --git a/NewHorizons/Utility/NewHorizonExtensions.cs b/NewHorizons/Utility/NewHorizonExtensions.cs index 9f4da941..216ea14a 100644 --- a/NewHorizons/Utility/NewHorizonExtensions.cs +++ b/NewHorizons/Utility/NewHorizonExtensions.cs @@ -445,6 +445,11 @@ namespace NewHorizons.Utility return globalMusicController._endTimesSource.clip.length; } + public static string GetKey(this AstroObject ao) + { + return ao._name == AstroObject.Name.CustomString ? ao.GetCustomName() : ao._name.ToString(); + } + public static CodeMatcher LogInstructions(this CodeMatcher matcher, string prefix) { matcher.InstructionEnumeration().LogInstructions(prefix); diff --git a/NewHorizons/Utility/OuterWilds/AstroObjectLocator.cs b/NewHorizons/Utility/OuterWilds/AstroObjectLocator.cs index 9e3a8a01..dd3c9564 100644 --- a/NewHorizons/Utility/OuterWilds/AstroObjectLocator.cs +++ b/NewHorizons/Utility/OuterWilds/AstroObjectLocator.cs @@ -65,7 +65,7 @@ namespace NewHorizons.Utility.OuterWilds public static void RegisterCustomAstroObject(AstroObject ao) { - var key = ao._name == AstroObject.Name.CustomString ? ao.GetCustomName() : ao._name.ToString(); + var key = ao.GetKey(); if (_customAstroObjectDictionary.ContainsKey(key)) { @@ -81,7 +81,7 @@ namespace NewHorizons.Utility.OuterWilds public static void DeregisterCustomAstroObject(AstroObject ao) { - var key = ao._name == AstroObject.Name.CustomString ? ao.GetCustomName() : ao._name.ToString(); + var key = ao.GetKey(); _customAstroObjectDictionary.Remove(key); } From c04905193fd25a55a32a04d135b271f71b86d233 Mon Sep 17 00:00:00 2001 From: Will Corby Date: Sun, 16 Mar 2025 15:14:20 -0700 Subject: [PATCH 09/57] Update details.md fix broken link to wiki --- docs/src/content/docs/guides/details.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/content/docs/guides/details.md b/docs/src/content/docs/guides/details.md index faff0389..78211fb1 100644 --- a/docs/src/content/docs/guides/details.md +++ b/docs/src/content/docs/guides/details.md @@ -25,7 +25,7 @@ There is an [old unity template](https://github.com/xen-42/outer-wilds-unity-tem The project contains ripped versions of all the game scripts, meaning you can put things like DirectionalForceVolumes in your Unity project to have artificial gravity volumes loaded right into the game.\ Either one works, but the new one has more tools and better versions of the scripts (in exchange for being invite-only). -Read [this guide](https://github.com/ow-mods/outer-wilds-unity-wiki/wiki/Tutorials-%E2%80%90-Using-asset-bundles) on how to work with asset bundles in editor. +Read [this guide](https://github.com/ow-mods/outer-wilds-unity-wiki/wiki/Tutorials-%E2%80%90-Using-AssetBundles) on how to work with asset bundles in editor. ## Importing a planet's surface from Unity From 5ce402363fb44364eada4a21500c051d30cecf8b Mon Sep 17 00:00:00 2001 From: Will Corby Date: Sun, 16 Mar 2025 15:15:06 -0700 Subject: [PATCH 10/57] Update details.md fix OTHER broken link --- docs/src/content/docs/guides/details.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/content/docs/guides/details.md b/docs/src/content/docs/guides/details.md index 78211fb1..bb0ee020 100644 --- a/docs/src/content/docs/guides/details.md +++ b/docs/src/content/docs/guides/details.md @@ -20,7 +20,7 @@ You can use [Unity Explorer](https://outerwildsmods.com/mods/unityexplorer) to t ## Asset Bundles -There is an [old unity template](https://github.com/xen-42/outer-wilds-unity-template) and a [new one](https://github.com/ow-mods/outer-wilds-unity-wiki/wiki#outer-wilds-unity-assets) +There is an [old unity template](https://github.com/xen-42/outer-wilds-unity-template) and a [new one](https://github.com/ow-mods/outer-wilds-unity-wiki/wiki/Tools-%E2%80%90-Outer-Wilds-Unity-Assets-repository) The project contains ripped versions of all the game scripts, meaning you can put things like DirectionalForceVolumes in your Unity project to have artificial gravity volumes loaded right into the game.\ Either one works, but the new one has more tools and better versions of the scripts (in exchange for being invite-only). From 6f75a69d24e8a7f71d9554f39b71bf812da803fc Mon Sep 17 00:00:00 2001 From: Will Corby Date: Sun, 16 Mar 2025 15:16:31 -0700 Subject: [PATCH 11/57] Update troubleshooting.md point to prop placer mod --- docs/src/content/docs/guides/troubleshooting.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/content/docs/guides/troubleshooting.md b/docs/src/content/docs/guides/troubleshooting.md index d4650d40..09ab375d 100644 --- a/docs/src/content/docs/guides/troubleshooting.md +++ b/docs/src/content/docs/guides/troubleshooting.md @@ -19,5 +19,5 @@ which interact poorly with the fluid detector and can mess up the movement of th Either clear the .nhcache files or enable Debug mode to always regenerate the text cache. ## Prop placer is gone! -This is not a bug, actually. We removed prop placer because it was inconsistent and buggy, and no one in years cared enough to fix it. -Use the debug raycast button and Unity Explorer to place your props, or otherwise work in unity editor. +It has been moved to a [separate mod](https://outerwildsmods.com/mods/propplacer/) +Use it in addition to the debug raycast button and Unity Explorer to place your props, or otherwise work in unity editor. From 6971a3b040c91828ca7b5b638036159f483869f8 Mon Sep 17 00:00:00 2001 From: Will Corby Date: Sun, 16 Mar 2025 15:16:44 -0700 Subject: [PATCH 12/57] Update troubleshooting.md --- docs/src/content/docs/guides/troubleshooting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/content/docs/guides/troubleshooting.md b/docs/src/content/docs/guides/troubleshooting.md index 09ab375d..8f6b4592 100644 --- a/docs/src/content/docs/guides/troubleshooting.md +++ b/docs/src/content/docs/guides/troubleshooting.md @@ -19,5 +19,5 @@ which interact poorly with the fluid detector and can mess up the movement of th Either clear the .nhcache files or enable Debug mode to always regenerate the text cache. ## Prop placer is gone! -It has been moved to a [separate mod](https://outerwildsmods.com/mods/propplacer/) +It has been moved to a [separate mod](https://outerwildsmods.com/mods/propplacer/). Use it in addition to the debug raycast button and Unity Explorer to place your props, or otherwise work in unity editor. From dd7bcbd5b1eeaf887b1f132a242f3d1e01ed0c8b Mon Sep 17 00:00:00 2001 From: xen-42 Date: Mon, 17 Mar 2025 11:49:11 -0400 Subject: [PATCH 13/57] Fix atmospherebuilder cull group (affected hazydreams) --- NewHorizons/Builder/Atmosphere/AtmosphereBuilder.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/NewHorizons/Builder/Atmosphere/AtmosphereBuilder.cs b/NewHorizons/Builder/Atmosphere/AtmosphereBuilder.cs index 29854385..ac38cd76 100644 --- a/NewHorizons/Builder/Atmosphere/AtmosphereBuilder.cs +++ b/NewHorizons/Builder/Atmosphere/AtmosphereBuilder.cs @@ -100,6 +100,15 @@ namespace NewHorizons.Builder.Atmosphere atmoGO.transform.position = planetGO.transform.TransformPoint(Vector3.zero); atmoGO.SetActive(true); + // CullGroups have already set up their renderers when this is done so we need to add ourself to it + // TODO: There are probably other builders where this is relevant + // This in particular was a bug affecting hazy dreams + if (sector != null && sector.gameObject.GetComponent() is CullGroup cullGroup) + { + cullGroup.RecursivelyAddRenderers(atmoGO.transform, true); + cullGroup.SetVisible(cullGroup.IsVisible()); + } + return atmoGO; } } From 93c427b5e64f37aaaa23883ff8c3f6616ebe2d35 Mon Sep 17 00:00:00 2001 From: josshmot Date: Fri, 4 Apr 2025 15:16:18 +1000 Subject: [PATCH 14/57] Added function for custom credits music and duration --- .../Builder/Volumes/CreditsVolumeBuilder.cs | 4 +- .../Builder/Volumes/VolumesBuildManager.cs | 2 +- NewHorizons/Components/NHGameOverManager.cs | 40 ++++++++++++++++--- .../Components/Volumes/LoadCreditsVolume.cs | 4 +- .../External/Modules/GameOverModule.cs | 35 ++++++++++++++++ .../SerializableEnums/NHCreditsType.cs | 4 +- NewHorizons/NewHorizons.csproj.user | 2 +- 7 files changed, 81 insertions(+), 10 deletions(-) diff --git a/NewHorizons/Builder/Volumes/CreditsVolumeBuilder.cs b/NewHorizons/Builder/Volumes/CreditsVolumeBuilder.cs index 81c3c6ce..4f1c0ecb 100644 --- a/NewHorizons/Builder/Volumes/CreditsVolumeBuilder.cs +++ b/NewHorizons/Builder/Volumes/CreditsVolumeBuilder.cs @@ -1,5 +1,6 @@ using NewHorizons.Components.Volumes; using NewHorizons.External.Modules.Volumes.VolumeInfos; +using OWML.Common; using OWML.Utils; using UnityEngine; @@ -7,12 +8,13 @@ namespace NewHorizons.Builder.Volumes { internal static class CreditsVolumeBuilder { - public static LoadCreditsVolume Make(GameObject planetGO, Sector sector, LoadCreditsVolumeInfo info) + public static LoadCreditsVolume Make(GameObject planetGO, Sector sector, LoadCreditsVolumeInfo info, IModBehaviour mod) { var volume = VolumeBuilder.Make(planetGO, sector, info); volume.gameOver = info.gameOver; volume.deathType = info.deathType == null ? null : EnumUtils.Parse(info.deathType.ToString(), DeathType.Default); + volume.mod = mod; return volume; } diff --git a/NewHorizons/Builder/Volumes/VolumesBuildManager.cs b/NewHorizons/Builder/Volumes/VolumesBuildManager.cs index bdaa4a5a..128717f6 100644 --- a/NewHorizons/Builder/Volumes/VolumesBuildManager.cs +++ b/NewHorizons/Builder/Volumes/VolumesBuildManager.cs @@ -209,7 +209,7 @@ namespace NewHorizons.Builder.Volumes { foreach (var creditsVolume in config.Volumes.creditsVolume) { - CreditsVolumeBuilder.Make(go, sector, creditsVolume); + CreditsVolumeBuilder.Make(go, sector, creditsVolume, mod); } } } diff --git a/NewHorizons/Components/NHGameOverManager.cs b/NewHorizons/Components/NHGameOverManager.cs index 93a7339a..354b8319 100644 --- a/NewHorizons/Components/NHGameOverManager.cs +++ b/NewHorizons/Components/NHGameOverManager.cs @@ -1,7 +1,9 @@ using NewHorizons.External.Modules; using NewHorizons.External.SerializableEnums; using NewHorizons.Handlers; +using NewHorizons.Utility.Files; using NewHorizons.Utility.OWML; +using OWML.Common; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -48,13 +50,13 @@ namespace NewHorizons.Components } } - public void StartGameOverSequence(GameOverModule gameOver, DeathType? deathType) + public void StartGameOverSequence(GameOverModule gameOver, DeathType? deathType, IModBehaviour mod = null) { _gameOverSequenceStarted = true; - Delay.StartCoroutine(GameOver(gameOver, deathType)); + Delay.StartCoroutine(GameOver(gameOver, deathType, mod)); } - private IEnumerator GameOver(GameOverModule gameOver, DeathType? deathType) + private IEnumerator GameOver(GameOverModule gameOver, DeathType? deathType, IModBehaviour mod) { OWInput.ChangeInputMode(InputMode.None); ReticleController.Hide(); @@ -104,15 +106,17 @@ namespace NewHorizons.Components yield return new WaitUntil(ReadytoLoadCreditsScene); } - LoadCreditsScene(gameOver); + LoadCreditsScene(gameOver, mod); } private bool ReadytoLoadCreditsScene() => _gameOverController._fadedOutText && _gameOverController._textAnimator.IsComplete(); - private void LoadCreditsScene(GameOverModule gameOver) + private void LoadCreditsScene(GameOverModule gameOver, IModBehaviour mod) { NHLogger.LogVerbose($"Load credits {gameOver.creditsType}"); + + switch (gameOver.creditsType) { case NHCreditsType.Fast: @@ -124,6 +128,32 @@ namespace NewHorizons.Components case NHCreditsType.Kazoo: TimelineObliterationController.s_hasRealityEnded = true; LoadManager.LoadScene(OWScene.Credits_Fast, LoadManager.FadeType.ToBlack); + break; + case NHCreditsType.Custom: + // We can't load in custom music if an IModBehaviour cannot be provided. This should only happen if called via TryHijackDeathSequence(). + if (mod is null) + NHLogger.LogWarning("Credits called using TryHijackDeathSequence(), custom credits audio cannot not be loaded."); + + LoadManager.LoadScene(OWScene.Credits_Fast, LoadManager.FadeType.ToBlack); + + // Patch new music + var musicSource = Locator.FindObjectsOfType().Where(x => x.name == "AudioSource").Single(); + musicSource.Stop(); + if (mod is not null) + { + AudioUtilities.SetAudioClip(musicSource, gameOver.audio, mod); + } + musicSource.SetMaxVolume(gameOver.audioVolume); + musicSource.loop = gameOver.audioLooping; + + // Patch scroll duration + var creditsScroll = Locator.FindObjectOfType(); + creditsScroll._scrollDuration = gameOver.scrollDuration; + + // Restart credits scroll + creditsScroll.Start(); + musicSource.FadeIn(gameOver.audioFadeInLength); + break; default: // GameOverController disables post processing diff --git a/NewHorizons/Components/Volumes/LoadCreditsVolume.cs b/NewHorizons/Components/Volumes/LoadCreditsVolume.cs index 26f75831..4f2dbfeb 100644 --- a/NewHorizons/Components/Volumes/LoadCreditsVolume.cs +++ b/NewHorizons/Components/Volumes/LoadCreditsVolume.cs @@ -1,4 +1,5 @@ using NewHorizons.External.Modules; +using OWML.Common; using UnityEngine; @@ -8,12 +9,13 @@ namespace NewHorizons.Components.Volumes { public GameOverModule gameOver; public DeathType? deathType; + public IModBehaviour mod; public override void OnTriggerVolumeEntry(GameObject hitObj) { if (hitObj.CompareTag("PlayerDetector") && enabled && (string.IsNullOrEmpty(gameOver.condition) || DialogueConditionManager.SharedInstance.GetConditionState(gameOver.condition))) { - NHGameOverManager.Instance.StartGameOverSequence(gameOver, deathType); + NHGameOverManager.Instance.StartGameOverSequence(gameOver, deathType, mod); } } diff --git a/NewHorizons/External/Modules/GameOverModule.cs b/NewHorizons/External/Modules/GameOverModule.cs index 00d029bd..dee5f302 100644 --- a/NewHorizons/External/Modules/GameOverModule.cs +++ b/NewHorizons/External/Modules/GameOverModule.cs @@ -2,6 +2,7 @@ using NewHorizons.External.SerializableData; using NewHorizons.External.SerializableEnums; using Newtonsoft.Json; using System.ComponentModel; +using System.ComponentModel.DataAnnotations; namespace NewHorizons.External.Modules { @@ -24,6 +25,40 @@ namespace NewHorizons.External.Modules /// public string condition; + /// + /// Path to the audio file to use as custom music for the credits. + /// Note: only applies when creditsType is set to "custom". + /// + public string audio; + + /// + /// The length of the fade in and out for the credits music. + /// Note: only applies when creditsType is set to "custom". + /// + [DefaultValue(1f)] + public float audioVolume; + + /// + /// Determines if the credits music should loop. + /// Note: only applies when creditsType is set to "custom". + /// + [DefaultValue(false)] + public bool audioLooping; + + /// + /// The length of the fade in for the credits music. + /// Note: only applies when creditsType is set to "custom". + /// + [DefaultValue(0f)] + public float audioFadeInLength; + + /// + /// Duration of the credits scroll in seconds. + /// Note: only applies when creditsType is set to "custom". + /// + [DefaultValue(120f)] + public float scrollDuration; + /// /// The type of credits that will run after the game over message is shown /// diff --git a/NewHorizons/External/SerializableEnums/NHCreditsType.cs b/NewHorizons/External/SerializableEnums/NHCreditsType.cs index 83c1dc51..f4d0f6c4 100644 --- a/NewHorizons/External/SerializableEnums/NHCreditsType.cs +++ b/NewHorizons/External/SerializableEnums/NHCreditsType.cs @@ -13,6 +13,8 @@ namespace NewHorizons.External.SerializableEnums [EnumMember(Value = @"kazoo")] Kazoo = 2, - [EnumMember(Value = @"none")] None = 3 + [EnumMember(Value = @"custom")] Custom = 3, + + [EnumMember(Value = @"none")] None = 4 } } diff --git a/NewHorizons/NewHorizons.csproj.user b/NewHorizons/NewHorizons.csproj.user index 5e39a9dd..44729020 100644 --- a/NewHorizons/NewHorizons.csproj.user +++ b/NewHorizons/NewHorizons.csproj.user @@ -1,4 +1,4 @@ - + $(AppData)\OuterWildsModManager\OWML\Mods\xen.NewHorizons From 803d2d6e2dccb89a063875043ef8a6a9888a3c7e Mon Sep 17 00:00:00 2001 From: josshmot Date: Fri, 4 Apr 2025 16:49:39 +1000 Subject: [PATCH 15/57] Fixed it so it actually works --- NewHorizons/Components/NHGameOverManager.cs | 71 +++++++++++++------ .../External/Modules/GameOverModule.cs | 4 +- 2 files changed, 51 insertions(+), 24 deletions(-) diff --git a/NewHorizons/Components/NHGameOverManager.cs b/NewHorizons/Components/NHGameOverManager.cs index 354b8319..2a8496ee 100644 --- a/NewHorizons/Components/NHGameOverManager.cs +++ b/NewHorizons/Components/NHGameOverManager.cs @@ -7,6 +7,7 @@ using OWML.Common; using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using UnityEngine; namespace NewHorizons.Components @@ -115,8 +116,6 @@ namespace NewHorizons.Components { NHLogger.LogVerbose($"Load credits {gameOver.creditsType}"); - - switch (gameOver.creditsType) { case NHCreditsType.Fast: @@ -132,28 +131,10 @@ namespace NewHorizons.Components case NHCreditsType.Custom: // We can't load in custom music if an IModBehaviour cannot be provided. This should only happen if called via TryHijackDeathSequence(). if (mod is null) - NHLogger.LogWarning("Credits called using TryHijackDeathSequence(), custom credits audio cannot not be loaded."); - - LoadManager.LoadScene(OWScene.Credits_Fast, LoadManager.FadeType.ToBlack); - - // Patch new music - var musicSource = Locator.FindObjectsOfType().Where(x => x.name == "AudioSource").Single(); - musicSource.Stop(); - if (mod is not null) { - AudioUtilities.SetAudioClip(musicSource, gameOver.audio, mod); + NHLogger.LogWarning("Credits called using TryHijackDeathSequence(), custom credits audio cannot not be loaded."); } - musicSource.SetMaxVolume(gameOver.audioVolume); - musicSource.loop = gameOver.audioLooping; - - // Patch scroll duration - var creditsScroll = Locator.FindObjectOfType(); - creditsScroll._scrollDuration = gameOver.scrollDuration; - - // Restart credits scroll - creditsScroll.Start(); - musicSource.FadeIn(gameOver.audioFadeInLength); - + LoadCustomCreditsScene(gameOver, mod); break; default: // GameOverController disables post processing @@ -164,5 +145,51 @@ namespace NewHorizons.Components break; } } + + private void LoadCustomCreditsScene(GameOverModule gameOver, IModBehaviour mod) + { + var fromScene = LoadManager.GetCurrentScene(); + var toScene = OWScene.Credits_Fast; + + LoadManager.LoadScene(toScene, LoadManager.FadeType.ToBlack); + + // We need to do this so we can unsubscribe from within the lambda. + LoadManager.SceneLoadEvent completeCreditsLoad = null; + + completeCreditsLoad = (fromScene, toScene) => + { + // Patch new music + var musicSource = Locator.FindObjectsOfType().Where(x => x.name == "AudioSource").Single(); + musicSource.Stop(); + if (mod is not null) + { + AudioUtilities.SetAudioClip(musicSource, gameOver.audio, mod); + } + musicSource.SetMaxVolume(gameOver.audioVolume); + musicSource.loop = gameOver.audioLooping; + musicSource.FadeIn(gameOver.audioFadeInLength); + + // Janky wait until credits are built + Task.Run( () => + { + var startTime = Time.time; + while (Locator.FindObjectsOfType().Length == 0) { + if (Time.time > startTime + 0.1f) + { + NHLogger.LogError("Timeout while waiting for credits to be built. Scroll duration couldn't be changed."); + return; + } + } + + // Patch scroll duration + var creditsScroll = Locator.FindObjectOfType(); + creditsScroll._scrollDuration = gameOver.scrollDuration; + }); + + LoadManager.OnCompleteSceneLoad -= completeCreditsLoad; + }; + + LoadManager.OnCompleteSceneLoad += completeCreditsLoad; + } } } diff --git a/NewHorizons/External/Modules/GameOverModule.cs b/NewHorizons/External/Modules/GameOverModule.cs index dee5f302..eb0fd911 100644 --- a/NewHorizons/External/Modules/GameOverModule.cs +++ b/NewHorizons/External/Modules/GameOverModule.cs @@ -23,7 +23,7 @@ namespace NewHorizons.External.Modules /// Condition that must be true for this game over to trigger. If this is on a LoadCreditsVolume, leave empty to always trigger this game over. /// Note this is a regular dialogue condition, not a persistent condition. /// - public string condition; + public string condition; /// /// Path to the audio file to use as custom music for the credits. @@ -46,7 +46,7 @@ namespace NewHorizons.External.Modules public bool audioLooping; /// - /// The length of the fade in for the credits music. + /// Whether the credits music should fade in. /// Note: only applies when creditsType is set to "custom". /// [DefaultValue(0f)] From e94aced8346f427745bb24b867d00dbe662ef647 Mon Sep 17 00:00:00 2001 From: josshmot Date: Fri, 4 Apr 2025 20:19:17 +1000 Subject: [PATCH 16/57] Made it explicitly required to provide IModeBehaviour for StartGameOverSequence() --- NewHorizons/Components/NHGameOverManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NewHorizons/Components/NHGameOverManager.cs b/NewHorizons/Components/NHGameOverManager.cs index 2a8496ee..3fb7f560 100644 --- a/NewHorizons/Components/NHGameOverManager.cs +++ b/NewHorizons/Components/NHGameOverManager.cs @@ -47,11 +47,11 @@ namespace NewHorizons.Components var gameOver = _gameOvers.FirstOrDefault(x => !string.IsNullOrEmpty(x.condition) && DialogueConditionManager.SharedInstance.GetConditionState(x.condition)); if (!_gameOverSequenceStarted && gameOver != null && !Locator.GetDeathManager()._finishedDLC) { - StartGameOverSequence(gameOver, null); + StartGameOverSequence(gameOver, null, null); } } - public void StartGameOverSequence(GameOverModule gameOver, DeathType? deathType, IModBehaviour mod = null) + public void StartGameOverSequence(GameOverModule gameOver, DeathType? deathType, IModBehaviour mod) { _gameOverSequenceStarted = true; Delay.StartCoroutine(GameOver(gameOver, deathType, mod)); From 29f1c4ab8cee0a55b825152a6d0bdf3d25dd1915 Mon Sep 17 00:00:00 2001 From: josshmot Date: Fri, 4 Apr 2025 20:23:15 +1000 Subject: [PATCH 17/57] Moved an if statement to a more appropriate place --- NewHorizons/Components/NHGameOverManager.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/NewHorizons/Components/NHGameOverManager.cs b/NewHorizons/Components/NHGameOverManager.cs index 3fb7f560..467d3a66 100644 --- a/NewHorizons/Components/NHGameOverManager.cs +++ b/NewHorizons/Components/NHGameOverManager.cs @@ -129,11 +129,6 @@ namespace NewHorizons.Components LoadManager.LoadScene(OWScene.Credits_Fast, LoadManager.FadeType.ToBlack); break; case NHCreditsType.Custom: - // We can't load in custom music if an IModBehaviour cannot be provided. This should only happen if called via TryHijackDeathSequence(). - if (mod is null) - { - NHLogger.LogWarning("Credits called using TryHijackDeathSequence(), custom credits audio cannot not be loaded."); - } LoadCustomCreditsScene(gameOver, mod); break; default: @@ -165,6 +160,11 @@ namespace NewHorizons.Components { AudioUtilities.SetAudioClip(musicSource, gameOver.audio, mod); } + else + { + // We can't load in custom music if an IModBehaviour cannot be provided. This should only happen if called via TryHijackDeathSequence(). + NHLogger.LogWarning("Credits called using TryHijackDeathSequence(), custom credits audio cannot not be loaded."); + } musicSource.SetMaxVolume(gameOver.audioVolume); musicSource.loop = gameOver.audioLooping; musicSource.FadeIn(gameOver.audioFadeInLength); From 5cf7c18a511a05bbbf815c8fad0493f9737edb9f Mon Sep 17 00:00:00 2001 From: josshmot Date: Sat, 5 Apr 2025 10:47:54 +1000 Subject: [PATCH 18/57] Renamed a few things --- NewHorizons/Components/NHGameOverManager.cs | 2 +- NewHorizons/External/Modules/GameOverModule.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/NewHorizons/Components/NHGameOverManager.cs b/NewHorizons/Components/NHGameOverManager.cs index 467d3a66..f1055799 100644 --- a/NewHorizons/Components/NHGameOverManager.cs +++ b/NewHorizons/Components/NHGameOverManager.cs @@ -183,7 +183,7 @@ namespace NewHorizons.Components // Patch scroll duration var creditsScroll = Locator.FindObjectOfType(); - creditsScroll._scrollDuration = gameOver.scrollDuration; + creditsScroll._scrollDuration = gameOver.length; }); LoadManager.OnCompleteSceneLoad -= completeCreditsLoad; diff --git a/NewHorizons/External/Modules/GameOverModule.cs b/NewHorizons/External/Modules/GameOverModule.cs index eb0fd911..2fae76ee 100644 --- a/NewHorizons/External/Modules/GameOverModule.cs +++ b/NewHorizons/External/Modules/GameOverModule.cs @@ -46,7 +46,7 @@ namespace NewHorizons.External.Modules public bool audioLooping; /// - /// Whether the credits music should fade in. + /// Length of the credits music fade in. /// Note: only applies when creditsType is set to "custom". /// [DefaultValue(0f)] @@ -57,7 +57,7 @@ namespace NewHorizons.External.Modules /// Note: only applies when creditsType is set to "custom". /// [DefaultValue(120f)] - public float scrollDuration; + public float length; /// /// The type of credits that will run after the game over message is shown From 8fcf7d303163b995433b76882fb3c574974c9d12 Mon Sep 17 00:00:00 2001 From: josshmot Date: Sat, 5 Apr 2025 12:18:08 +1000 Subject: [PATCH 19/57] Removed audio fade in feature that wasn't working --- NewHorizons/Components/NHGameOverManager.cs | 11 +++++------ NewHorizons/External/Modules/GameOverModule.cs | 7 ------- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/NewHorizons/Components/NHGameOverManager.cs b/NewHorizons/Components/NHGameOverManager.cs index f1055799..6dd351ad 100644 --- a/NewHorizons/Components/NHGameOverManager.cs +++ b/NewHorizons/Components/NHGameOverManager.cs @@ -153,9 +153,8 @@ namespace NewHorizons.Components completeCreditsLoad = (fromScene, toScene) => { - // Patch new music + // Patch new music clip var musicSource = Locator.FindObjectsOfType().Where(x => x.name == "AudioSource").Single(); - musicSource.Stop(); if (mod is not null) { AudioUtilities.SetAudioClip(musicSource, gameOver.audio, mod); @@ -164,10 +163,10 @@ namespace NewHorizons.Components { // We can't load in custom music if an IModBehaviour cannot be provided. This should only happen if called via TryHijackDeathSequence(). NHLogger.LogWarning("Credits called using TryHijackDeathSequence(), custom credits audio cannot not be loaded."); + return; } - musicSource.SetMaxVolume(gameOver.audioVolume); musicSource.loop = gameOver.audioLooping; - musicSource.FadeIn(gameOver.audioFadeInLength); + musicSource._maxSourceVolume = gameOver.audioVolume; // Janky wait until credits are built Task.Run( () => @@ -176,11 +175,11 @@ namespace NewHorizons.Components while (Locator.FindObjectsOfType().Length == 0) { if (Time.time > startTime + 0.1f) { - NHLogger.LogError("Timeout while waiting for credits to be built. Scroll duration couldn't be changed."); + NHLogger.LogError("Timeout while waiting for credits to be built. Scroll duration won't be changed."); return; } } - + // Patch scroll duration var creditsScroll = Locator.FindObjectOfType(); creditsScroll._scrollDuration = gameOver.length; diff --git a/NewHorizons/External/Modules/GameOverModule.cs b/NewHorizons/External/Modules/GameOverModule.cs index 2fae76ee..4c10830e 100644 --- a/NewHorizons/External/Modules/GameOverModule.cs +++ b/NewHorizons/External/Modules/GameOverModule.cs @@ -45,13 +45,6 @@ namespace NewHorizons.External.Modules [DefaultValue(false)] public bool audioLooping; - /// - /// Length of the credits music fade in. - /// Note: only applies when creditsType is set to "custom". - /// - [DefaultValue(0f)] - public float audioFadeInLength; - /// /// Duration of the credits scroll in seconds. /// Note: only applies when creditsType is set to "custom". From 5e0d44d911c3e639737f08eca17f687744876e45 Mon Sep 17 00:00:00 2001 From: josshmot Date: Sat, 5 Apr 2025 13:07:30 +1000 Subject: [PATCH 20/57] Fixed credits music fade in still sometimes occuring --- NewHorizons/Components/NHGameOverManager.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NewHorizons/Components/NHGameOverManager.cs b/NewHorizons/Components/NHGameOverManager.cs index 6dd351ad..2dc57d36 100644 --- a/NewHorizons/Components/NHGameOverManager.cs +++ b/NewHorizons/Components/NHGameOverManager.cs @@ -180,6 +180,10 @@ namespace NewHorizons.Components } } + // Override fade in + musicSource.Stop(); + musicSource.Play(); + // Patch scroll duration var creditsScroll = Locator.FindObjectOfType(); creditsScroll._scrollDuration = gameOver.length; From 6d77b0b1d1e38fe70c9d0c50018075055bdc55d5 Mon Sep 17 00:00:00 2001 From: josshmot Date: Tue, 8 Apr 2025 16:50:31 +1000 Subject: [PATCH 21/57] Fixed incorrect log level in NHGameOverManager.LoadCustomCreditsScene() --- NewHorizons/Components/NHGameOverManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NewHorizons/Components/NHGameOverManager.cs b/NewHorizons/Components/NHGameOverManager.cs index 2dc57d36..3da1b8cf 100644 --- a/NewHorizons/Components/NHGameOverManager.cs +++ b/NewHorizons/Components/NHGameOverManager.cs @@ -162,7 +162,7 @@ namespace NewHorizons.Components else { // We can't load in custom music if an IModBehaviour cannot be provided. This should only happen if called via TryHijackDeathSequence(). - NHLogger.LogWarning("Credits called using TryHijackDeathSequence(), custom credits audio cannot not be loaded."); + NHLogger.Log("Credits called using TryHijackDeathSequence(), custom credits audio cannot not be loaded."); return; } musicSource.loop = gameOver.audioLooping; From 0e6555d9336b2bbebc32f64bcf188ea49fe3e4c3 Mon Sep 17 00:00:00 2001 From: josshmot Date: Tue, 8 Apr 2025 17:01:06 +1000 Subject: [PATCH 22/57] Neatened up NHGameOverManager.LoadCustomCreditsScene() and added some comments to explain things --- NewHorizons/Components/NHGameOverManager.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/NewHorizons/Components/NHGameOverManager.cs b/NewHorizons/Components/NHGameOverManager.cs index 3da1b8cf..25f3549e 100644 --- a/NewHorizons/Components/NHGameOverManager.cs +++ b/NewHorizons/Components/NHGameOverManager.cs @@ -148,13 +148,12 @@ namespace NewHorizons.Components LoadManager.LoadScene(toScene, LoadManager.FadeType.ToBlack); - // We need to do this so we can unsubscribe from within the lambda. - LoadManager.SceneLoadEvent completeCreditsLoad = null; - + // Unfortunately we can't make this a private method, as LoadManager.SceneLoadEvent enforces the (fromScene, toScene) parameters, which prevents us from passing in gameOver and mod, which we need. + LoadManager.SceneLoadEvent completeCreditsLoad = null; // needs to be done so we can unsubscribe from within the lambda. completeCreditsLoad = (fromScene, toScene) => { // Patch new music clip - var musicSource = Locator.FindObjectsOfType().Where(x => x.name == "AudioSource").Single(); + var musicSource = Locator.FindObjectsOfType().Where(x => x.name == "AudioSource").Single(); // AudioSource that plays the credits music is literally called "AudioSource", luckily it's the only one called that. Lazy OW devs do be lazy. if (mod is not null) { AudioUtilities.SetAudioClip(musicSource, gameOver.audio, mod); From 5d680277ecb7a989bf649611d5a473f6a072e2f2 Mon Sep 17 00:00:00 2001 From: josshmot Date: Tue, 8 Apr 2025 17:30:43 +1000 Subject: [PATCH 23/57] Patched in event for CreditsBuilt and refactored NHGameOverManager.LoadCustomCreditsScene() to implement it and remove the janky task --- NewHorizons/Components/NHGameOverManager.cs | 43 +++++++------------ .../CreditsScenePatches/CreditsPatches.cs | 13 ++++++ 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/NewHorizons/Components/NHGameOverManager.cs b/NewHorizons/Components/NHGameOverManager.cs index 25f3549e..087205f6 100644 --- a/NewHorizons/Components/NHGameOverManager.cs +++ b/NewHorizons/Components/NHGameOverManager.cs @@ -1,9 +1,11 @@ using NewHorizons.External.Modules; using NewHorizons.External.SerializableEnums; using NewHorizons.Handlers; +using NewHorizons.Patches.CreditsScenePatches; using NewHorizons.Utility.Files; using NewHorizons.Utility.OWML; using OWML.Common; +using System; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -143,15 +145,13 @@ namespace NewHorizons.Components private void LoadCustomCreditsScene(GameOverModule gameOver, IModBehaviour mod) { - var fromScene = LoadManager.GetCurrentScene(); - var toScene = OWScene.Credits_Fast; + LoadManager.LoadScene(OWScene.Credits_Fast, LoadManager.FadeType.ToBlack); - LoadManager.LoadScene(toScene, LoadManager.FadeType.ToBlack); - - // Unfortunately we can't make this a private method, as LoadManager.SceneLoadEvent enforces the (fromScene, toScene) parameters, which prevents us from passing in gameOver and mod, which we need. - LoadManager.SceneLoadEvent completeCreditsLoad = null; // needs to be done so we can unsubscribe from within the lambda. - completeCreditsLoad = (fromScene, toScene) => + // Unfortunately we can't make this a private method, as EventArgs/EventHandler enforces the (sender, e) parameters, which prevents us from passing in gameOver and mod, which we need. + EventHandler onCreditsBuilt = null; // needs to be done so we can unsubscribe from within the lambda. + onCreditsBuilt = (sender, e) => { + // Patch new music clip var musicSource = Locator.FindObjectsOfType().Where(x => x.name == "AudioSource").Single(); // AudioSource that plays the credits music is literally called "AudioSource", luckily it's the only one called that. Lazy OW devs do be lazy. if (mod is not null) @@ -167,31 +167,18 @@ namespace NewHorizons.Components musicSource.loop = gameOver.audioLooping; musicSource._maxSourceVolume = gameOver.audioVolume; - // Janky wait until credits are built - Task.Run( () => - { - var startTime = Time.time; - while (Locator.FindObjectsOfType().Length == 0) { - if (Time.time > startTime + 0.1f) - { - NHLogger.LogError("Timeout while waiting for credits to be built. Scroll duration won't be changed."); - return; - } - } + // Override fade in + musicSource.Stop(); + musicSource.Play(); - // Override fade in - musicSource.Stop(); - musicSource.Play(); + // Patch scroll duration + var creditsScroll = Locator.FindObjectOfType(); + creditsScroll._scrollDuration = gameOver.length; - // Patch scroll duration - var creditsScroll = Locator.FindObjectOfType(); - creditsScroll._scrollDuration = gameOver.length; - }); - - LoadManager.OnCompleteSceneLoad -= completeCreditsLoad; + CreditsPatches.CreditsBuilt -= onCreditsBuilt; }; - LoadManager.OnCompleteSceneLoad += completeCreditsLoad; + CreditsPatches.CreditsBuilt += onCreditsBuilt; } } } diff --git a/NewHorizons/Patches/CreditsScenePatches/CreditsPatches.cs b/NewHorizons/Patches/CreditsScenePatches/CreditsPatches.cs index 8892f334..037b30bf 100644 --- a/NewHorizons/Patches/CreditsScenePatches/CreditsPatches.cs +++ b/NewHorizons/Patches/CreditsScenePatches/CreditsPatches.cs @@ -1,17 +1,30 @@ using HarmonyLib; using NewHorizons.Handlers; +using System; namespace NewHorizons.Patches.CreditsScenePatches { [HarmonyPatch(typeof(Credits))] public static class CreditsPatches { + public static event EventHandler CreditsBuilt; // Used in NHGameOverManager to patch credits music and scroll speed + [HarmonyPrefix] [HarmonyPatch(nameof(Credits.Start))] public static void Credits_Start(Credits __instance) { CreditsHandler.AddCredits(__instance); } + + [HarmonyPostfix] + [HarmonyPatch(nameof(Credits.BuildCredits))] + public static void Credits_BuildCredits_Post(Credits __instance) + { + // Do things BuildCredits() normally does + + // Fire event once finished + CreditsBuilt?.Invoke(__instance, new EventArgs()); + } } } From ee776e384b4d5dcb263be13aa95d07c3b574b5d1 Mon Sep 17 00:00:00 2001 From: xen-42 Date: Tue, 8 Apr 2025 11:17:45 -0400 Subject: [PATCH 24/57] Rewrite to support hijackdeathsequence, but haven't tested --- NewHorizons/Components/NHGameOverManager.cs | 41 +++++++++++---------- NewHorizons/Main.cs | 2 +- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/NewHorizons/Components/NHGameOverManager.cs b/NewHorizons/Components/NHGameOverManager.cs index 087205f6..0fe3dc08 100644 --- a/NewHorizons/Components/NHGameOverManager.cs +++ b/NewHorizons/Components/NHGameOverManager.cs @@ -9,7 +9,6 @@ using System; using System.Collections; using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; using UnityEngine; namespace NewHorizons.Components @@ -20,14 +19,14 @@ namespace NewHorizons.Components /// Mod unique id to game over module list /// Done as a dictionary so that Reload Configs can overwrite entries per mod /// - public static Dictionary gameOvers = new(); + public static Dictionary gameOvers = new(); public static NHGameOverManager Instance { get; private set; } private GameOverController _gameOverController; private PlayerCameraEffectController _playerCameraEffectController; - private GameOverModule[] _gameOvers; + private (IModBehaviour mod, GameOverModule gameOver)[] _gameOvers; private bool _gameOverSequenceStarted; @@ -41,15 +40,25 @@ namespace NewHorizons.Components _gameOverController = FindObjectOfType(); _playerCameraEffectController = FindObjectOfType(); - _gameOvers = gameOvers.SelectMany(x => x.Value).ToArray(); + var gameOverList = new List<(IModBehaviour, GameOverModule)>(); + foreach (var gameOverPair in gameOvers) + { + var mod = gameOverPair.Key; + foreach (var gameOver in gameOverPair.Value) + { + gameOverList.Add((mod, gameOver)); + } + } + _gameOvers = gameOverList.ToArray(); } public void TryHijackDeathSequence() { - var gameOver = _gameOvers.FirstOrDefault(x => !string.IsNullOrEmpty(x.condition) && DialogueConditionManager.SharedInstance.GetConditionState(x.condition)); - if (!_gameOverSequenceStarted && gameOver != null && !Locator.GetDeathManager()._finishedDLC) + var gameOver = _gameOvers.FirstOrDefault(x => !string.IsNullOrEmpty(x.gameOver.condition) + && DialogueConditionManager.SharedInstance.GetConditionState(x.gameOver.condition)); + if (!_gameOverSequenceStarted && gameOver != default && !Locator.GetDeathManager()._finishedDLC) { - StartGameOverSequence(gameOver, null, null); + StartGameOverSequence(gameOver.gameOver, null, gameOver.mod); } } @@ -151,19 +160,13 @@ namespace NewHorizons.Components EventHandler onCreditsBuilt = null; // needs to be done so we can unsubscribe from within the lambda. onCreditsBuilt = (sender, e) => { - + // Unsubscribe first, playing it safe in case it NREs + CreditsPatches.CreditsBuilt -= onCreditsBuilt; + // Patch new music clip var musicSource = Locator.FindObjectsOfType().Where(x => x.name == "AudioSource").Single(); // AudioSource that plays the credits music is literally called "AudioSource", luckily it's the only one called that. Lazy OW devs do be lazy. - if (mod is not null) - { - AudioUtilities.SetAudioClip(musicSource, gameOver.audio, mod); - } - else - { - // We can't load in custom music if an IModBehaviour cannot be provided. This should only happen if called via TryHijackDeathSequence(). - NHLogger.Log("Credits called using TryHijackDeathSequence(), custom credits audio cannot not be loaded."); - return; - } + AudioUtilities.SetAudioClip(musicSource, gameOver.audio, mod); + musicSource.loop = gameOver.audioLooping; musicSource._maxSourceVolume = gameOver.audioVolume; @@ -174,8 +177,6 @@ namespace NewHorizons.Components // Patch scroll duration var creditsScroll = Locator.FindObjectOfType(); creditsScroll._scrollDuration = gameOver.length; - - CreditsPatches.CreditsBuilt -= onCreditsBuilt; }; CreditsPatches.CreditsBuilt += onCreditsBuilt; diff --git a/NewHorizons/Main.cs b/NewHorizons/Main.cs index f81400a9..113be63b 100644 --- a/NewHorizons/Main.cs +++ b/NewHorizons/Main.cs @@ -849,7 +849,7 @@ namespace NewHorizons } if (addonConfig.gameOver != null) { - NHGameOverManager.gameOvers[mod.ModHelper.Manifest.UniqueName] = addonConfig.gameOver; + NHGameOverManager.gameOvers[mod] = addonConfig.gameOver; } AddonConfigs[mod] = addonConfig; From e02dd94cad0366c917ea634ee522231e66b2f684 Mon Sep 17 00:00:00 2001 From: xen-42 Date: Tue, 8 Apr 2025 11:54:07 -0400 Subject: [PATCH 25/57] Fixed a bug where changing an existing AudioSource to use a specific AudioClip resource didn't work --- NewHorizons/External/Modules/GameOverModule.cs | 1 - NewHorizons/Utility/Files/AudioUtilities.cs | 17 ++++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/NewHorizons/External/Modules/GameOverModule.cs b/NewHorizons/External/Modules/GameOverModule.cs index 4c10830e..7bb884e1 100644 --- a/NewHorizons/External/Modules/GameOverModule.cs +++ b/NewHorizons/External/Modules/GameOverModule.cs @@ -2,7 +2,6 @@ using NewHorizons.External.SerializableData; using NewHorizons.External.SerializableEnums; using Newtonsoft.Json; using System.ComponentModel; -using System.ComponentModel.DataAnnotations; namespace NewHorizons.External.Modules { diff --git a/NewHorizons/Utility/Files/AudioUtilities.cs b/NewHorizons/Utility/Files/AudioUtilities.cs index fe09d46e..f3c2924d 100644 --- a/NewHorizons/Utility/Files/AudioUtilities.cs +++ b/NewHorizons/Utility/Files/AudioUtilities.cs @@ -27,23 +27,34 @@ namespace NewHorizons.Utility.Files source._clipArrayLength = 0; source._clipSelectionOnPlay = OWAudioSource.ClipSelectionOnPlay.MANUAL; source.clip = clip; + NHLogger.LogVerbose($"[{nameof(AudioUtilities)}] : Audio {audio} was loaded from a file"); return; } catch { - NHLogger.LogError($"Could not load file {audio}"); + NHLogger.LogError($"[{nameof(AudioUtilities)}] : Could not load file {audio}"); } } if (EnumUtils.TryParse(audio, out AudioType type)) { source._audioLibraryClip = type; + NHLogger.LogVerbose($"[{nameof(AudioUtilities)}] : Audio {audio} was an AudioType enum"); } else { var audioClip = SearchUtilities.FindResourceOfTypeAndName(audio); - if (audioClip == null) NHLogger.Log($"Couldn't find audio clip {audio}"); - else source.clip = audioClip; + if (audioClip == null) + { + NHLogger.LogError($"[{nameof(AudioUtilities)}] : Couldn't find audio clip {audio}"); + } + else + { + NHLogger.LogVerbose($"[{nameof(AudioUtilities)}] : Audio {audio} was an AudioClip resource"); + // Else if this is set it will try to change the clip back when it starts playing + source._audioLibraryClip = AudioType.None; + source.clip = audioClip; + } } } From 6767ab3548a820b64939adc9887fb00887040eef Mon Sep 17 00:00:00 2001 From: xen-42 Date: Tue, 8 Apr 2025 12:46:36 -0400 Subject: [PATCH 26/57] Destroy flood sensors on docks --- NewHorizons/Builder/Props/DetailBuilder.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/NewHorizons/Builder/Props/DetailBuilder.cs b/NewHorizons/Builder/Props/DetailBuilder.cs index 825af0c1..d7400937 100644 --- a/NewHorizons/Builder/Props/DetailBuilder.cs +++ b/NewHorizons/Builder/Props/DetailBuilder.cs @@ -473,10 +473,14 @@ namespace NewHorizons.Builder.Props { // These flood toggles are to disable flooded docks on the Stranger // Presumably the user isn't making one of those - foreach (var toggle in dock.GetComponents()) + foreach (var toggle in dock.GetComponents().Concat(dock.GetComponentsInChildren())) { Component.DestroyImmediate(toggle); } + foreach (var floodSensor in dock.GetComponents().Concat(dock.GetComponentsInChildren())) + { + Component.DestroyImmediate(floodSensor); + } } } From ca1bc3701446e99cd9fc1add7a80d61a97c0d017 Mon Sep 17 00:00:00 2001 From: xen-42 Date: Tue, 8 Apr 2025 13:00:50 -0400 Subject: [PATCH 27/57] Update manifest.json --- NewHorizons/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NewHorizons/manifest.json b/NewHorizons/manifest.json index eff475cf..3311a8fb 100644 --- a/NewHorizons/manifest.json +++ b/NewHorizons/manifest.json @@ -4,7 +4,7 @@ "author": "xen, Bwc9876, JohnCorby, MegaPiggy, and friends", "name": "New Horizons", "uniqueName": "xen.NewHorizons", - "version": "1.27.3", + "version": "1.27.4", "owmlVersion": "2.12.1", "dependencies": [ "JohnCorby.VanillaFix", "xen.CommonCameraUtility", "dgarro.CustomShipLogModes" ], "conflicts": [ "PacificEngine.OW_CommonResources" ], From 8f06cab729961bb6dd109fe647e10c76a1668873 Mon Sep 17 00:00:00 2001 From: xen-42 Date: Tue, 8 Apr 2025 13:02:43 -0400 Subject: [PATCH 28/57] Mention that audio clip and audio type also work --- NewHorizons/External/Modules/GameOverModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NewHorizons/External/Modules/GameOverModule.cs b/NewHorizons/External/Modules/GameOverModule.cs index 7bb884e1..33cf2269 100644 --- a/NewHorizons/External/Modules/GameOverModule.cs +++ b/NewHorizons/External/Modules/GameOverModule.cs @@ -25,7 +25,7 @@ namespace NewHorizons.External.Modules public string condition; /// - /// Path to the audio file to use as custom music for the credits. + /// Path to the audio file to use as custom music for the credits. Also supports AudioClips and AudioType like other audio settings. /// Note: only applies when creditsType is set to "custom". /// public string audio; From a6efef5ee392aee84f81ece1a9e71813acd6a20a Mon Sep 17 00:00:00 2001 From: xen-42 Date: Tue, 8 Apr 2025 13:03:55 -0400 Subject: [PATCH 29/57] Reword --- NewHorizons/External/Modules/GameOverModule.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NewHorizons/External/Modules/GameOverModule.cs b/NewHorizons/External/Modules/GameOverModule.cs index 33cf2269..a844a190 100644 --- a/NewHorizons/External/Modules/GameOverModule.cs +++ b/NewHorizons/External/Modules/GameOverModule.cs @@ -22,10 +22,10 @@ namespace NewHorizons.External.Modules /// Condition that must be true for this game over to trigger. If this is on a LoadCreditsVolume, leave empty to always trigger this game over. /// Note this is a regular dialogue condition, not a persistent condition. /// - public string condition; + public string condition; /// - /// Path to the audio file to use as custom music for the credits. Also supports AudioClips and AudioType like other audio settings. + /// The audio to use for the credits music. Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list. /// Note: only applies when creditsType is set to "custom". /// public string audio; From 50ccd32b8e15135d9c16bb85a8e323ee46418708 Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Mon, 7 Apr 2025 18:32:44 -0400 Subject: [PATCH 30/57] add docks --- .../Props/{ => EchoesOfTheEye}/RaftBuilder.cs | 17 ++++++- .../Props/EchoesOfTheEye/RaftDockBuilder.cs | 51 +++++++++++++++++++ NewHorizons/Builder/Props/PropBuildManager.cs | 1 + NewHorizons/External/Modules/PropModule.cs | 5 ++ .../Props/EchoesOfTheEye/RaftDockInfo.cs | 10 ++++ .../Modules/Props/EchoesOfTheEye/RaftInfo.cs | 5 ++ NewHorizons/Main.cs | 1 + 7 files changed, 89 insertions(+), 1 deletion(-) rename NewHorizons/Builder/Props/{ => EchoesOfTheEye}/RaftBuilder.cs (85%) create mode 100644 NewHorizons/Builder/Props/EchoesOfTheEye/RaftDockBuilder.cs create mode 100644 NewHorizons/External/Modules/Props/EchoesOfTheEye/RaftDockInfo.cs diff --git a/NewHorizons/Builder/Props/RaftBuilder.cs b/NewHorizons/Builder/Props/EchoesOfTheEye/RaftBuilder.cs similarity index 85% rename from NewHorizons/Builder/Props/RaftBuilder.cs rename to NewHorizons/Builder/Props/EchoesOfTheEye/RaftBuilder.cs index 59f734bf..5747c71b 100644 --- a/NewHorizons/Builder/Props/RaftBuilder.cs +++ b/NewHorizons/Builder/Props/EchoesOfTheEye/RaftBuilder.cs @@ -5,7 +5,7 @@ using NewHorizons.Utility; using NewHorizons.Utility.OWML; using UnityEngine; -namespace NewHorizons.Builder.Props +namespace NewHorizons.Builder.Props.EchoesOfTheEye { public static class RaftBuilder { @@ -91,6 +91,21 @@ namespace NewHorizons.Builder.Props raftObject.SetActive(true); + if (planetGO != null && !string.IsNullOrEmpty(info.dockPath)) + { + var dockTransform = planetGO.transform.Find(info.dockPath); + if (dockTransform != null && dockTransform.TryGetComponent(out RaftDock raftDock)) + { + raftController.SkipSuspendOnStart(); + raftDock._startRaft = raftController; + raftDock._raft = raftController; + } + else + { + NHLogger.LogError($"Cannot find raft dock object at path: {planetGO.name}/{info.dockPath}"); + } + } + return raftObject; } } diff --git a/NewHorizons/Builder/Props/EchoesOfTheEye/RaftDockBuilder.cs b/NewHorizons/Builder/Props/EchoesOfTheEye/RaftDockBuilder.cs new file mode 100644 index 00000000..834e82ad --- /dev/null +++ b/NewHorizons/Builder/Props/EchoesOfTheEye/RaftDockBuilder.cs @@ -0,0 +1,51 @@ +using NewHorizons.Components.Props; +using NewHorizons.External.Modules.Props; +using NewHorizons.External.Modules.Props.EchoesOfTheEye; +using NewHorizons.Handlers; +using NewHorizons.Utility; +using NewHorizons.Utility.OWML; +using OWML.Common; +using UnityEngine; + +namespace NewHorizons.Builder.Props.EchoesOfTheEye +{ + public static class RaftDockBuilder + { + private static GameObject _prefab; + + internal static void InitPrefab() + { + if (_prefab == null) + { + _prefab = SearchUtilities.Find("RingWorld_Body/Sector_RingInterior/Sector_Zone1/Structures_Zone1/RaftHouse_ArrivalPatio_Zone1/Interactables_RaftHouse_ArrivalPatio_Zone1/Prefab_IP_RaftDock").InstantiateInactive().Rename("Prefab_RaftDock").DontDestroyOnLoad(); + if (_prefab == null) + { + NHLogger.LogWarning($"Tried to make a raft dock but couldn't. Do you have the DLC installed?"); + return; + } + else + { + _prefab.AddComponent()._destroyOnDLCNotOwned = true; + var raftDock = _prefab.GetComponent(); + raftDock._startRaft = null; + raftDock._floodSensor = null; + Object.DestroyImmediate(_prefab.FindChild("FloodSensor")); + Object.DestroyImmediate(_prefab.FindChild("FloodSensor_RaftHouseArrivalPatio_NoDelay")); + } + } + } + + public static GameObject Make(GameObject planetGO, Sector sector, RaftDockInfo info, IModBehaviour mod) + { + InitPrefab(); + + if (_prefab == null || sector == null) return null; + + var dockObject = DetailBuilder.Make(planetGO, sector, mod, _prefab, new DetailInfo(info)); + + //var raftDock = dockObject.GetComponent(); + + return dockObject; + } + } +} diff --git a/NewHorizons/Builder/Props/PropBuildManager.cs b/NewHorizons/Builder/Props/PropBuildManager.cs index 579d5e21..fb8d21c7 100644 --- a/NewHorizons/Builder/Props/PropBuildManager.cs +++ b/NewHorizons/Builder/Props/PropBuildManager.cs @@ -114,6 +114,7 @@ namespace NewHorizons.Builder.Props MakeGeneralProps(go, config.Props.grappleTotems, (totem) => GrappleTotemBuilder.Make(go, sector, totem, mod)); MakeGeneralProps(go, config.Props.dreamCampfires, (campfire) => DreamCampfireBuilder.Make(go, sector, campfire, mod), (campfire) => campfire.id); MakeGeneralProps(go, config.Props.dreamArrivalPoints, (point) => DreamArrivalPointBuilder.Make(go, sector, point, mod), (point) => point.id); + MakeGeneralProps(go, config.Props.raftDocks, (dock) => RaftDockBuilder.Make(go, sector, dock, mod)); MakeGeneralProps(go, config.Props.rafts, (raft) => RaftBuilder.Make(go, sector, raft, planetBody)); } MakeGeneralProps(go, config.Props.tornados, (tornado) => TornadoBuilder.Make(go, sector, tornado, config.Atmosphere?.clouds != null)); diff --git a/NewHorizons/External/Modules/PropModule.cs b/NewHorizons/External/Modules/PropModule.cs index 4a162bd8..1f7806ef 100644 --- a/NewHorizons/External/Modules/PropModule.cs +++ b/NewHorizons/External/Modules/PropModule.cs @@ -58,6 +58,11 @@ namespace NewHorizons.External.Modules /// public RaftInfo[] rafts; + /// + /// Add raft docks to this planet (requires Echoes of the Eye DLC) + /// + public RaftDockInfo[] raftDocks; + /// /// Scatter props around this planet's surface /// diff --git a/NewHorizons/External/Modules/Props/EchoesOfTheEye/RaftDockInfo.cs b/NewHorizons/External/Modules/Props/EchoesOfTheEye/RaftDockInfo.cs new file mode 100644 index 00000000..37730991 --- /dev/null +++ b/NewHorizons/External/Modules/Props/EchoesOfTheEye/RaftDockInfo.cs @@ -0,0 +1,10 @@ +using Newtonsoft.Json; +using System.ComponentModel; + +namespace NewHorizons.External.Modules.Props.EchoesOfTheEye +{ + [JsonObject] + public class RaftDockInfo : GeneralPropInfo + { + } +} diff --git a/NewHorizons/External/Modules/Props/EchoesOfTheEye/RaftInfo.cs b/NewHorizons/External/Modules/Props/EchoesOfTheEye/RaftInfo.cs index d248679e..61c3a1dd 100644 --- a/NewHorizons/External/Modules/Props/EchoesOfTheEye/RaftInfo.cs +++ b/NewHorizons/External/Modules/Props/EchoesOfTheEye/RaftInfo.cs @@ -10,6 +10,11 @@ namespace NewHorizons.External.Modules.Props.EchoesOfTheEye /// Acceleration of the raft. Default acceleration is 5. /// [DefaultValue(5f)] public float acceleration = 5f; + + /// + /// Path to the dock this raft will start attached to. + /// + public string dockPath; } } diff --git a/NewHorizons/Main.cs b/NewHorizons/Main.cs index f81400a9..855372a2 100644 --- a/NewHorizons/Main.cs +++ b/NewHorizons/Main.cs @@ -382,6 +382,7 @@ namespace NewHorizons ProjectionBuilder.InitPrefabs(); CloakBuilder.InitPrefab(); RaftBuilder.InitPrefab(); + RaftDockBuilder.InitPrefab(); DreamCampfireBuilder.InitPrefab(); DreamArrivalPointBuilder.InitPrefab(); } From 92b7c70c16e00fe77b95161636f8e97d96e97fbf Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Tue, 8 Apr 2025 14:19:08 -0400 Subject: [PATCH 31/57] change patch so that it doesn't invoke twice --- .../RaftControllerPatches.cs | 49 ++++++++++++------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/NewHorizons/Patches/EchoesOfTheEyePatches/RaftControllerPatches.cs b/NewHorizons/Patches/EchoesOfTheEyePatches/RaftControllerPatches.cs index 48a08f08..f437e4a8 100644 --- a/NewHorizons/Patches/EchoesOfTheEyePatches/RaftControllerPatches.cs +++ b/NewHorizons/Patches/EchoesOfTheEyePatches/RaftControllerPatches.cs @@ -75,30 +75,41 @@ namespace NewHorizons.Patches.EchoesOfTheEyePatches return false; } - [HarmonyPostfix] + [HarmonyPrefix] [HarmonyPatch(nameof(RaftController.UpdateMoveToTarget))] - public static void UpdateMoveToTarget(RaftController __instance) + public static bool UpdateMoveToTarget(RaftController __instance) { + OWRigidbody raftBody = __instance._raftBody; + OWRigidbody origParentBody = raftBody.GetOrigParentBody(); + Transform transform = origParentBody.transform; + Vector3 position = transform.TransformPoint(__instance._targetLocalPosition); + Vector3 distance = position - raftBody.GetPosition(); + float speed = Mathf.Min(__instance._targetSpeed, distance.magnitude / Time.deltaTime); + Vector3 pointVelocity = raftBody.GetOrigParentBody().GetPointVelocity(raftBody.GetPosition()); + raftBody.SetVelocity(pointVelocity + (distance.normalized * speed)); + float t = (__instance.currentDistanceLerp = Mathf.InverseLerp(__instance._startDistance, 0.001f, distance.magnitude)); + var st = Mathf.SmoothStep(0f, 1f, t); // If it has a riverFluid volume then its a regular stranger one - if (__instance._movingToTarget && __instance._riverFluid == null) + var isStranger = __instance._riverFluid != null; + // Base game threshold has this at 1f (after doing smoothstep on it) + // For whatever reason it never hits that for NH planets (probably since they're moving so much compared to the steady velocity of the Stranger) + // Might break for somebody with a wacky spinning planet in which case we can adjust this or add some kind of fallback (i.e., wait x seconds and then just say its there) + // Fixes #1005 + if (isStranger ? (st >= 1f) : (t > 0.999f)) { - OWRigidbody raftBody = __instance._raftBody; - OWRigidbody origParentBody = __instance._raftBody.GetOrigParentBody(); - Transform transform = origParentBody.transform; - Vector3 vector = transform.TransformPoint(__instance._targetLocalPosition); - - // Base game threshold has this at 1f (after doing smoothstep on it) - // For whatever reason it never hits that for NH planets (probably since they're moving so much compared to the steady velocity of the Stranger) - // Might break for somebody with a wacky spinning planet in which case we can adjust this or add some kind of fallback (i.e., wait x seconds and then just say its there) - // Fixes #1005 - if (__instance.currentDistanceLerp > 0.999f) - { - raftBody.SetPosition(vector); - raftBody.SetRotation(transform.rotation * __instance._targetLocalRotation); - __instance.StopMovingToTarget(); - __instance.OnArriveAtTarget.Invoke(); - } + raftBody.SetPosition(position); + raftBody.SetRotation(transform.rotation * __instance._targetLocalRotation); + __instance.StopMovingToTarget(); + __instance.OnArriveAtTarget.Invoke(); } + else + { + Quaternion quaternion = Quaternion.Slerp(__instance._startLocalRotation, __instance._targetLocalRotation, st); + Quaternion toRotation = transform.rotation * quaternion; + Vector3 vector4 = OWPhysics.FromToAngularVelocity(raftBody.GetRotation(), toRotation); + raftBody.SetAngularVelocity(origParentBody.GetAngularVelocity() + vector4); + } + return false; } } } From fe3196dac93bb0e98c94ce710a5916ddc5114778 Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Tue, 8 Apr 2025 15:05:47 -0400 Subject: [PATCH 32/57] destroy flood toggles too --- NewHorizons/Builder/Props/EchoesOfTheEye/RaftDockBuilder.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NewHorizons/Builder/Props/EchoesOfTheEye/RaftDockBuilder.cs b/NewHorizons/Builder/Props/EchoesOfTheEye/RaftDockBuilder.cs index 834e82ad..b8cc22fa 100644 --- a/NewHorizons/Builder/Props/EchoesOfTheEye/RaftDockBuilder.cs +++ b/NewHorizons/Builder/Props/EchoesOfTheEye/RaftDockBuilder.cs @@ -29,6 +29,10 @@ namespace NewHorizons.Builder.Props.EchoesOfTheEye var raftDock = _prefab.GetComponent(); raftDock._startRaft = null; raftDock._floodSensor = null; + foreach (var floodToggle in _prefab.GetComponents()) + { + Component.DestroyImmediate(floodToggle); + } Object.DestroyImmediate(_prefab.FindChild("FloodSensor")); Object.DestroyImmediate(_prefab.FindChild("FloodSensor_RaftHouseArrivalPatio_NoDelay")); } From 2f53572d7582eac150c7617098ccd1cdf83bdeea Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Tue, 8 Apr 2025 16:37:43 -0400 Subject: [PATCH 33/57] change to dreamworld standing torch --- NewHorizons/Builder/Props/ProjectionBuilder.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/NewHorizons/Builder/Props/ProjectionBuilder.cs b/NewHorizons/Builder/Props/ProjectionBuilder.cs index 2ebf64fc..38bedc5d 100644 --- a/NewHorizons/Builder/Props/ProjectionBuilder.cs +++ b/NewHorizons/Builder/Props/ProjectionBuilder.cs @@ -92,11 +92,14 @@ namespace NewHorizons.Builder.Props if (_standingVisionTorchPrefab == null) { - _standingVisionTorchPrefab = SearchUtilities.Find("RingWorld_Body/Sector_RingWorld/Sector_SecretEntrance/Interactibles_SecretEntrance/Experiment_1/VisionTorchApparatus/VisionTorchRoot/Prefab_IP_VisionTorchProjector")?.gameObject?.InstantiateInactive()?.Rename("Prefab_IP_VisionTorchProjector")?.DontDestroyOnLoad(); + _standingVisionTorchPrefab = SearchUtilities.Find("DreamWorld_Body/Sector_DreamWorld/Sector_DreamZone_4/Interactibles_DreamZone_4_Upper/Prefab_IP_VisionTorchProjector")?.gameObject?.InstantiateInactive()?.Rename("Prefab_IP_VisionTorchProjector")?.DontDestroyOnLoad(); if (_standingVisionTorchPrefab == null) NHLogger.LogWarning($"Tried to make standing vision torch prefab but couldn't. Do you have the DLC installed?"); else + { _standingVisionTorchPrefab.AddComponent()._destroyOnDLCNotOwned = true; + GameObject.DestroyImmediate(_standingVisionTorchPrefab.FindChild("Prefab_IP_Reel_PrisonPeephole_Vision")); + } } } From e734ada4b09a1cf11ae849c4e6988f69072e60a5 Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Tue, 8 Apr 2025 21:59:55 -0400 Subject: [PATCH 34/57] allow dream lanterns to interact with these rafts --- NewHorizons/Builder/Props/EchoesOfTheEye/RaftBuilder.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NewHorizons/Builder/Props/EchoesOfTheEye/RaftBuilder.cs b/NewHorizons/Builder/Props/EchoesOfTheEye/RaftBuilder.cs index 5747c71b..c24c0106 100644 --- a/NewHorizons/Builder/Props/EchoesOfTheEye/RaftBuilder.cs +++ b/NewHorizons/Builder/Props/EchoesOfTheEye/RaftBuilder.cs @@ -40,6 +40,10 @@ namespace NewHorizons.Builder.Props.EchoesOfTheEye lightSensor._sector.OnSectorOccupantsUpdated -= lightSensor.OnSectorOccupantsUpdated; lightSensor._sector = null; } + lightSensor._detectDreamLanterns = true; + lightSensor._lanternFocusThreshold = 0.9f; + lightSensor._illuminatingDreamLanternList = new List(); + lightSensor._lightSourceMask |= LightSourceType.DREAM_LANTERN; } } } From 9365a4845df1bf5181f35f78806e2430c7884b80 Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Tue, 8 Apr 2025 22:06:34 -0400 Subject: [PATCH 35/57] Add tronworld model for raft --- NewHorizons/Builder/Props/EchoesOfTheEye/RaftBuilder.cs | 9 +++++++++ NewHorizons/Utility/NewHorizonExtensions.cs | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/NewHorizons/Builder/Props/EchoesOfTheEye/RaftBuilder.cs b/NewHorizons/Builder/Props/EchoesOfTheEye/RaftBuilder.cs index c24c0106..28550681 100644 --- a/NewHorizons/Builder/Props/EchoesOfTheEye/RaftBuilder.cs +++ b/NewHorizons/Builder/Props/EchoesOfTheEye/RaftBuilder.cs @@ -45,6 +45,15 @@ namespace NewHorizons.Builder.Props.EchoesOfTheEye lightSensor._illuminatingDreamLanternList = new List(); lightSensor._lightSourceMask |= LightSourceType.DREAM_LANTERN; } + + // TODO: Change to one mesh + var twRaftRoot = new GameObject("Effects_IP_SIM_Raft"); + twRaftRoot.transform.SetParent(_prefab.transform, false); + var twRaft = SearchUtilities.Find("DreamWorld_Body/Sector_DreamWorld/Interactibles_Dreamworld/DreamRaft_Body/Effects_IP_SIM_Raft_1Way") + .Instantiate(Vector3.zero, Quaternion.identity, twRaftRoot.transform).Rename("Effects_IP_SIM_Raft_1Way"); + twRaft.Instantiate(Vector3.zero, Quaternion.Euler(0, 180, 0), twRaftRoot.transform).Rename(twRaft.name); + twRaft.Instantiate(Vector3.zero, Quaternion.Euler(0, 90, 0), twRaftRoot.transform).Rename(twRaft.name); + twRaft.Instantiate(Vector3.zero, Quaternion.Euler(0, -90, 0), twRaftRoot.transform).Rename(twRaft.name); } } } diff --git a/NewHorizons/Utility/NewHorizonExtensions.cs b/NewHorizons/Utility/NewHorizonExtensions.cs index 216ea14a..10ca7570 100644 --- a/NewHorizons/Utility/NewHorizonExtensions.cs +++ b/NewHorizons/Utility/NewHorizonExtensions.cs @@ -274,6 +274,14 @@ namespace NewHorizons.Utility return UnityEngine.Object.Instantiate(original); } + public static GameObject Instantiate(this GameObject original, Vector3 localPosition, Quaternion localRotation, Transform parent) + { + var copy = UnityEngine.Object.Instantiate(original, parent, false); + copy.transform.localPosition = localPosition; + copy.transform.localRotation = localRotation; + return copy; + } + public static T DontDestroyOnLoad(this T target) where T : UnityEngine.Object { UnityEngine.Object.DontDestroyOnLoad(target); From a69fd7206ef2f205936d0ec99a9d372f137d454e Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Tue, 8 Apr 2025 22:06:49 -0400 Subject: [PATCH 36/57] Add clean parameter --- .../Props/EchoesOfTheEye/RaftBuilder.cs | 59 ++++++++++++++++++- .../Modules/Props/EchoesOfTheEye/RaftInfo.cs | 5 ++ 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/NewHorizons/Builder/Props/EchoesOfTheEye/RaftBuilder.cs b/NewHorizons/Builder/Props/EchoesOfTheEye/RaftBuilder.cs index 28550681..aa1aa110 100644 --- a/NewHorizons/Builder/Props/EchoesOfTheEye/RaftBuilder.cs +++ b/NewHorizons/Builder/Props/EchoesOfTheEye/RaftBuilder.cs @@ -3,6 +3,7 @@ using NewHorizons.External.Modules.Props.EchoesOfTheEye; using NewHorizons.Handlers; using NewHorizons.Utility; using NewHorizons.Utility.OWML; +using System.Collections.Generic; using UnityEngine; namespace NewHorizons.Builder.Props.EchoesOfTheEye @@ -10,6 +11,7 @@ namespace NewHorizons.Builder.Props.EchoesOfTheEye public static class RaftBuilder { private static GameObject _prefab; + private static GameObject _cleanPrefab; internal static void InitPrefab() { @@ -56,15 +58,68 @@ namespace NewHorizons.Builder.Props.EchoesOfTheEye twRaft.Instantiate(Vector3.zero, Quaternion.Euler(0, -90, 0), twRaftRoot.transform).Rename(twRaft.name); } } + if (_cleanPrefab == null && _prefab != null) + { + _cleanPrefab = _prefab?.InstantiateInactive()?.Rename("Raft_Body_Prefab_Clean")?.DontDestroyOnLoad(); + if (_cleanPrefab == null) + { + NHLogger.LogWarning($"Tried to make a raft but couldn't. Do you have the DLC installed?"); + return; + } + else + { + var raftController = _cleanPrefab.GetComponent(); + var rwRaft = _cleanPrefab.FindChild("Structure_IP_Raft"); + var rwRaftAnimator = rwRaft.GetComponent(); + var rac = rwRaftAnimator.runtimeAnimatorController; + Object.DestroyImmediate(rwRaft); + + var dwRaft = SearchUtilities.Find("DreamWorld_Body/Sector_DreamWorld/Interactibles_Dreamworld/DreamRaft_Body/Structure_IP_Raft") + .Instantiate(Vector3.zero, Quaternion.identity, _cleanPrefab.transform).Rename("Structure_IP_DreamRaft"); + dwRaft.transform.SetSiblingIndex(3); + foreach (var child in dwRaft.GetAllChildren()) + { + child.SetActive(true); + } + + var dwRaftAnimator = dwRaft.AddComponent(); + dwRaftAnimator.runtimeAnimatorController = rac; + raftController._railingAnimator = dwRaftAnimator; + + var dwLightSensorForward = SearchUtilities.Find("DreamWorld_Body/Sector_DreamWorld/Interactibles_Dreamworld/DreamRaft_Body/LightSensor_Forward"); + var dwLightSensorOrigMaterial = dwLightSensorForward.GetComponent()._origMaterial; + var dwLightSensor = dwLightSensorForward.FindChild("Structure_IP_Raft_Sensor"); + ChangeSensor(_cleanPrefab.FindChild("LightSensorRoot/LightSensor_Forward"), dwLightSensorOrigMaterial, dwLightSensor); + ChangeSensor(_cleanPrefab.FindChild("LightSensorRoot/LightSensor_Right"), dwLightSensorOrigMaterial, dwLightSensor); + ChangeSensor(_cleanPrefab.FindChild("LightSensorRoot/LightSensor_Rear"), dwLightSensorOrigMaterial, dwLightSensor, true); + ChangeSensor(_cleanPrefab.FindChild("LightSensorRoot/LightSensor_Left"), dwLightSensorOrigMaterial, dwLightSensor); + } + } + } + + private static void ChangeSensor(GameObject lightSensor, Material origMaterial, GameObject newLightSensor, bool reverse = false) + { + var singleLightSensor = lightSensor.GetComponent(); + var lightSensorEffects = lightSensor.GetComponent(); + lightSensorEffects._lightSensor = singleLightSensor; + Object.DestroyImmediate(lightSensor.FindChild("Structure_IP_Raft_Sensor")); + lightSensorEffects._origMaterial = origMaterial; + var copiedLightSensor = newLightSensor + .Instantiate(reverse ? new Vector3(0, -1.5f, -0.1303f) : new Vector3(0, -1.5f, -0.0297f), + reverse ? Quaternion.identity : Quaternion.Euler(0, 180, 0), + lightSensor.transform).Rename("Structure_IP_DreamRaft_Sensor"); + var bulb = copiedLightSensor.FindChild("Props_IP_Raft_Lamp_geoBulb"); + lightSensorEffects._renderer = bulb.GetComponent(); + lightSensorEffects._lightRenderer = bulb.GetComponent(); } public static GameObject Make(GameObject planetGO, Sector sector, RaftInfo info, OWRigidbody planetBody) { InitPrefab(); - if (_prefab == null || sector == null) return null; + if (_prefab == null || _cleanPrefab == null || sector == null) return null; - GameObject raftObject = GeneralPropBuilder.MakeFromPrefab(_prefab, "Raft_Body", planetGO, sector, info); + GameObject raftObject = GeneralPropBuilder.MakeFromPrefab(info.clean ? _cleanPrefab : _prefab, "Raft_Body", planetGO, sector, info); StreamingHandler.SetUpStreaming(raftObject, sector); diff --git a/NewHorizons/External/Modules/Props/EchoesOfTheEye/RaftInfo.cs b/NewHorizons/External/Modules/Props/EchoesOfTheEye/RaftInfo.cs index 61c3a1dd..ddf6cac1 100644 --- a/NewHorizons/External/Modules/Props/EchoesOfTheEye/RaftInfo.cs +++ b/NewHorizons/External/Modules/Props/EchoesOfTheEye/RaftInfo.cs @@ -15,6 +15,11 @@ namespace NewHorizons.External.Modules.Props.EchoesOfTheEye /// Path to the dock this raft will start attached to. /// public string dockPath; + + /// + /// Uses the raft model from the dreamworld + /// + public bool clean; } } From 0b65c852d2626c321e5394dcb804ca8ae477de09 Mon Sep 17 00:00:00 2001 From: josshmot Date: Thu, 10 Apr 2025 17:34:19 +1000 Subject: [PATCH 37/57] Fixed undefined behaviour when custom credits attributes aren't specified --- NewHorizons/Components/NHGameOverManager.cs | 9 ++++++++- NewHorizons/External/Modules/GameOverModule.cs | 13 +++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/NewHorizons/Components/NHGameOverManager.cs b/NewHorizons/Components/NHGameOverManager.cs index 0fe3dc08..acc3080d 100644 --- a/NewHorizons/Components/NHGameOverManager.cs +++ b/NewHorizons/Components/NHGameOverManager.cs @@ -165,7 +165,14 @@ namespace NewHorizons.Components // Patch new music clip var musicSource = Locator.FindObjectsOfType().Where(x => x.name == "AudioSource").Single(); // AudioSource that plays the credits music is literally called "AudioSource", luckily it's the only one called that. Lazy OW devs do be lazy. - AudioUtilities.SetAudioClip(musicSource, gameOver.audio, mod); + if (gameOver.audio != string.Empty) // string.Empty is default value for "audio" in GameOverModule, means no audio is specified. + { + AudioUtilities.SetAudioClip(musicSource, gameOver.audio, mod); // Load audio if specified + } + else + { + musicSource.AssignAudioLibraryClip(AudioType.PLACEHOLDER); // Otherwise default custom credits are silent + } musicSource.loop = gameOver.audioLooping; musicSource._maxSourceVolume = gameOver.audioVolume; diff --git a/NewHorizons/External/Modules/GameOverModule.cs b/NewHorizons/External/Modules/GameOverModule.cs index 7bb884e1..d9dba70b 100644 --- a/NewHorizons/External/Modules/GameOverModule.cs +++ b/NewHorizons/External/Modules/GameOverModule.cs @@ -22,34 +22,31 @@ namespace NewHorizons.External.Modules /// Condition that must be true for this game over to trigger. If this is on a LoadCreditsVolume, leave empty to always trigger this game over. /// Note this is a regular dialogue condition, not a persistent condition. /// - public string condition; + public string condition; /// /// Path to the audio file to use as custom music for the credits. /// Note: only applies when creditsType is set to "custom". /// - public string audio; + public string audio = string.Empty; // Explicitly declaring this for condition in NHGameOverManager /// /// The length of the fade in and out for the credits music. /// Note: only applies when creditsType is set to "custom". /// - [DefaultValue(1f)] - public float audioVolume; + [DefaultValue(1f)] public float audioVolume = 1f; /// /// Determines if the credits music should loop. /// Note: only applies when creditsType is set to "custom". /// - [DefaultValue(false)] - public bool audioLooping; + [DefaultValue(false)] public bool audioLooping = false; /// /// Duration of the credits scroll in seconds. /// Note: only applies when creditsType is set to "custom". /// - [DefaultValue(120f)] - public float length; + [DefaultValue(120f)] public float length = 120f; /// /// The type of credits that will run after the game over message is shown From 105e03a3efc55a616f9b2d7ae65ef104680c3cb3 Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Thu, 10 Apr 2025 10:49:51 -0400 Subject: [PATCH 38/57] Change so that you can still use ringworld model --- .../Builder/Props/ProjectionBuilder.cs | 22 +++++++++++++++---- .../Props/EchoesOfTheEye/ProjectionInfo.cs | 2 +- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/NewHorizons/Builder/Props/ProjectionBuilder.cs b/NewHorizons/Builder/Props/ProjectionBuilder.cs index 38bedc5d..8639d3c7 100644 --- a/NewHorizons/Builder/Props/ProjectionBuilder.cs +++ b/NewHorizons/Builder/Props/ProjectionBuilder.cs @@ -43,6 +43,7 @@ namespace NewHorizons.Builder.Props private static GameObject _autoPrefab; private static GameObject _visionTorchDetectorPrefab; private static GameObject _standingVisionTorchPrefab; + private static GameObject _standingVisionTorchCleanPrefab; private static readonly int EmissionMap = Shader.PropertyToID("_EmissionMap"); private static bool _isInit; @@ -90,15 +91,27 @@ namespace NewHorizons.Builder.Props _visionTorchDetectorPrefab.AddComponent()._destroyOnDLCNotOwned = true; } + if (_standingVisionTorchCleanPrefab == null) + { + _standingVisionTorchCleanPrefab = SearchUtilities.Find("DreamWorld_Body/Sector_DreamWorld/Sector_DreamZone_4/Interactibles_DreamZone_4_Upper/Prefab_IP_VisionTorchProjector")?.gameObject?.InstantiateInactive()?.Rename("Prefab_DW_VisionTorchProjector")?.DontDestroyOnLoad(); + if (_standingVisionTorchCleanPrefab == null) + NHLogger.LogWarning($"Tried to make standing vision torch prefab but couldn't. Do you have the DLC installed?"); + else + { + _standingVisionTorchCleanPrefab.AddComponent()._destroyOnDLCNotOwned = true; + GameObject.DestroyImmediate(_standingVisionTorchCleanPrefab.FindChild("Prefab_IP_Reel_PrisonPeephole_Vision")); + } + } + if (_standingVisionTorchPrefab == null) { - _standingVisionTorchPrefab = SearchUtilities.Find("DreamWorld_Body/Sector_DreamWorld/Sector_DreamZone_4/Interactibles_DreamZone_4_Upper/Prefab_IP_VisionTorchProjector")?.gameObject?.InstantiateInactive()?.Rename("Prefab_IP_VisionTorchProjector")?.DontDestroyOnLoad(); + _standingVisionTorchPrefab = SearchUtilities.Find("RingWorld_Body/Sector_RingWorld/Sector_SecretEntrance/Interactibles_SecretEntrance/Experiment_1/VisionTorchApparatus/VisionTorchRoot/Prefab_IP_VisionTorchProjector")?.gameObject?.InstantiateInactive()?.Rename("Prefab_IP_VisionTorchProjector")?.DontDestroyOnLoad(); if (_standingVisionTorchPrefab == null) NHLogger.LogWarning($"Tried to make standing vision torch prefab but couldn't. Do you have the DLC installed?"); else { _standingVisionTorchPrefab.AddComponent()._destroyOnDLCNotOwned = true; - GameObject.DestroyImmediate(_standingVisionTorchPrefab.FindChild("Prefab_IP_Reel_PrisonPeephole_Vision")); + GameObject.Instantiate(_standingVisionTorchCleanPrefab.FindChild("Effects_IP_SIM_VisionTorch"), _standingVisionTorchPrefab.transform, false).Rename("Effects_IP_SIM_VisionTorch"); } } } @@ -449,10 +462,11 @@ namespace NewHorizons.Builder.Props { InitPrefabs(); - if (_standingVisionTorchPrefab == null) return null; + if (_standingVisionTorchPrefab == null || _standingVisionTorchCleanPrefab == null) return null; // Spawn the torch itself - var standingTorch = DetailBuilder.Make(planetGO, sector, mod, _standingVisionTorchPrefab, new DetailInfo(info)); + var prefab = info.reelCondition == ProjectionInfo.SlideReelCondition.Pristine ? _standingVisionTorchCleanPrefab : _standingVisionTorchPrefab; + var standingTorch = DetailBuilder.Make(planetGO, sector, mod, prefab, new DetailInfo(info)); if (standingTorch == null) { diff --git a/NewHorizons/External/Modules/Props/EchoesOfTheEye/ProjectionInfo.cs b/NewHorizons/External/Modules/Props/EchoesOfTheEye/ProjectionInfo.cs index e059c310..43424ec0 100644 --- a/NewHorizons/External/Modules/Props/EchoesOfTheEye/ProjectionInfo.cs +++ b/NewHorizons/External/Modules/Props/EchoesOfTheEye/ProjectionInfo.cs @@ -82,7 +82,7 @@ namespace NewHorizons.External.Modules.Props.EchoesOfTheEye [DefaultValue("sevenSlides")] public SlideReelType reelModel = SlideReelType.SevenSlides; /// - /// Exclusive to the slide reel type. Condition/material of the reel. Antique is the Stranger, Pristine is the Dreamworld, Rusted is a burned reel. + /// Exclusive to the slide reel and standing vision torch type. Condition/material of the reel. Antique is the Stranger, Pristine is the Dreamworld, Rusted (exclusive to slide reels) is a burned reel. /// [DefaultValue("antique")] public SlideReelCondition reelCondition = SlideReelCondition.Antique; From 264bcb24460d46657d3af542787cc0f53e2e73a4 Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Thu, 10 Apr 2025 11:12:02 -0400 Subject: [PATCH 39/57] make name consistent with reels --- NewHorizons/Builder/Props/EchoesOfTheEye/RaftBuilder.cs | 2 +- NewHorizons/External/Modules/Props/EchoesOfTheEye/RaftInfo.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NewHorizons/Builder/Props/EchoesOfTheEye/RaftBuilder.cs b/NewHorizons/Builder/Props/EchoesOfTheEye/RaftBuilder.cs index aa1aa110..2ae3435f 100644 --- a/NewHorizons/Builder/Props/EchoesOfTheEye/RaftBuilder.cs +++ b/NewHorizons/Builder/Props/EchoesOfTheEye/RaftBuilder.cs @@ -119,7 +119,7 @@ namespace NewHorizons.Builder.Props.EchoesOfTheEye if (_prefab == null || _cleanPrefab == null || sector == null) return null; - GameObject raftObject = GeneralPropBuilder.MakeFromPrefab(info.clean ? _cleanPrefab : _prefab, "Raft_Body", planetGO, sector, info); + GameObject raftObject = GeneralPropBuilder.MakeFromPrefab(info.pristine ? _cleanPrefab : _prefab, "Raft_Body", planetGO, sector, info); StreamingHandler.SetUpStreaming(raftObject, sector); diff --git a/NewHorizons/External/Modules/Props/EchoesOfTheEye/RaftInfo.cs b/NewHorizons/External/Modules/Props/EchoesOfTheEye/RaftInfo.cs index ddf6cac1..af35da12 100644 --- a/NewHorizons/External/Modules/Props/EchoesOfTheEye/RaftInfo.cs +++ b/NewHorizons/External/Modules/Props/EchoesOfTheEye/RaftInfo.cs @@ -19,7 +19,7 @@ namespace NewHorizons.External.Modules.Props.EchoesOfTheEye /// /// Uses the raft model from the dreamworld /// - public bool clean; + public bool pristine; } } From 5374448514b6495f26dab0e0c634488f570a5cea Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Thu, 10 Apr 2025 14:30:56 -0400 Subject: [PATCH 40/57] Speed Limiter --- .../Volumes/SpeedLimiterVolumeBuilder.cs | 20 +++ .../Builder/Volumes/VolumesBuildManager.cs | 7 + .../Components/Volumes/SpeedLimiterVolume.cs | 166 ++++++++++++++++++ .../VolumeInfos/SpeedLimiterVolumeInfo.cs | 28 +++ .../External/Modules/Volumes/VolumesModule.cs | 7 + 5 files changed, 228 insertions(+) create mode 100644 NewHorizons/Builder/Volumes/SpeedLimiterVolumeBuilder.cs create mode 100644 NewHorizons/Components/Volumes/SpeedLimiterVolume.cs create mode 100644 NewHorizons/External/Modules/Volumes/VolumeInfos/SpeedLimiterVolumeInfo.cs 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. /// From 3cae688e2c14ece815b63ca77d00d3877317d74d Mon Sep 17 00:00:00 2001 From: josshmot Date: Fri, 11 Apr 2025 04:58:30 +1000 Subject: [PATCH 41/57] Replaced != string.Empty with string.IsNullOrEmpty() in NHGameOverManager --- NewHorizons/Components/NHGameOverManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NewHorizons/Components/NHGameOverManager.cs b/NewHorizons/Components/NHGameOverManager.cs index acc3080d..1586755d 100644 --- a/NewHorizons/Components/NHGameOverManager.cs +++ b/NewHorizons/Components/NHGameOverManager.cs @@ -165,13 +165,13 @@ namespace NewHorizons.Components // Patch new music clip var musicSource = Locator.FindObjectsOfType().Where(x => x.name == "AudioSource").Single(); // AudioSource that plays the credits music is literally called "AudioSource", luckily it's the only one called that. Lazy OW devs do be lazy. - if (gameOver.audio != string.Empty) // string.Empty is default value for "audio" in GameOverModule, means no audio is specified. + if (string.IsNullOrEmpty(gameOver.audio)) // string.Empty is default value for "audio" in GameOverModule, means no audio is specified. { AudioUtilities.SetAudioClip(musicSource, gameOver.audio, mod); // Load audio if specified } else { - musicSource.AssignAudioLibraryClip(AudioType.PLACEHOLDER); // Otherwise default custom credits are silent + musicSource.AssignAudioLibraryClip(AudioType.PLACEHOLDER); // Otherwise default custom credits are silent - AudioType.PLACEHOLDER is silence (apparently) } musicSource.loop = gameOver.audioLooping; From ad3802d383febc14efa43abfcc57a8920c602011 Mon Sep 17 00:00:00 2001 From: josshmot Date: Fri, 11 Apr 2025 05:07:49 +1000 Subject: [PATCH 42/57] Added intended behaviour when unspecified to json description for GameOverModule.audio --- NewHorizons/External/Modules/GameOverModule.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NewHorizons/External/Modules/GameOverModule.cs b/NewHorizons/External/Modules/GameOverModule.cs index d9dba70b..2f62a5b6 100644 --- a/NewHorizons/External/Modules/GameOverModule.cs +++ b/NewHorizons/External/Modules/GameOverModule.cs @@ -25,7 +25,7 @@ namespace NewHorizons.External.Modules public string condition; /// - /// Path to the audio file to use as custom music for the credits. + /// Path to the audio file to use as custom music for the credits. When creditsType is set to "custom", credits will be silent unless this is specified. /// Note: only applies when creditsType is set to "custom". /// public string audio = string.Empty; // Explicitly declaring this for condition in NHGameOverManager @@ -37,7 +37,7 @@ namespace NewHorizons.External.Modules [DefaultValue(1f)] public float audioVolume = 1f; /// - /// Determines if the credits music should loop. + /// Determines if the credits music should loop () /// Note: only applies when creditsType is set to "custom". /// [DefaultValue(false)] public bool audioLooping = false; From a8d944a4c9e4bf867b344b7b1d55c76db69234f8 Mon Sep 17 00:00:00 2001 From: josshmot Date: Fri, 11 Apr 2025 05:09:46 +1000 Subject: [PATCH 43/57] Fixed typo in GameOverModule --- NewHorizons/External/Modules/GameOverModule.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NewHorizons/External/Modules/GameOverModule.cs b/NewHorizons/External/Modules/GameOverModule.cs index 2f62a5b6..859e9d3b 100644 --- a/NewHorizons/External/Modules/GameOverModule.cs +++ b/NewHorizons/External/Modules/GameOverModule.cs @@ -25,7 +25,7 @@ namespace NewHorizons.External.Modules public string condition; /// - /// Path to the audio file to use as custom music for the credits. When creditsType is set to "custom", credits will be silent unless this is specified. + /// Path to the audio file to use as custom music for the credits. When creditsType is set to "custom", credits will be silent unless this attribute is specified. /// Note: only applies when creditsType is set to "custom". /// public string audio = string.Empty; // Explicitly declaring this for condition in NHGameOverManager @@ -37,7 +37,7 @@ namespace NewHorizons.External.Modules [DefaultValue(1f)] public float audioVolume = 1f; /// - /// Determines if the credits music should loop () + /// Determines if the credits music should loop. /// Note: only applies when creditsType is set to "custom". /// [DefaultValue(false)] public bool audioLooping = false; From 539ef61c534be2feccc2d48b4992143f5b2c7642 Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Thu, 10 Apr 2025 15:11:34 -0400 Subject: [PATCH 44/57] correction --- NewHorizons/Components/NHGameOverManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NewHorizons/Components/NHGameOverManager.cs b/NewHorizons/Components/NHGameOverManager.cs index 1586755d..37661562 100644 --- a/NewHorizons/Components/NHGameOverManager.cs +++ b/NewHorizons/Components/NHGameOverManager.cs @@ -165,7 +165,7 @@ namespace NewHorizons.Components // Patch new music clip var musicSource = Locator.FindObjectsOfType().Where(x => x.name == "AudioSource").Single(); // AudioSource that plays the credits music is literally called "AudioSource", luckily it's the only one called that. Lazy OW devs do be lazy. - if (string.IsNullOrEmpty(gameOver.audio)) // string.Empty is default value for "audio" in GameOverModule, means no audio is specified. + if (!string.IsNullOrEmpty(gameOver.audio)) // string.Empty is default value for "audio" in GameOverModule, means no audio is specified. { AudioUtilities.SetAudioClip(musicSource, gameOver.audio, mod); // Load audio if specified } From f1246810c73f1fae8d985d0fce3e93e7eb91d336 Mon Sep 17 00:00:00 2001 From: Joshua Thome Date: Tue, 15 Apr 2025 22:57:12 -0500 Subject: [PATCH 45/57] Minor typo in error message --- NewHorizons/Builder/Props/ShapeBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NewHorizons/Builder/Props/ShapeBuilder.cs b/NewHorizons/Builder/Props/ShapeBuilder.cs index 7e224e98..924ea135 100644 --- a/NewHorizons/Builder/Props/ShapeBuilder.cs +++ b/NewHorizons/Builder/Props/ShapeBuilder.cs @@ -60,7 +60,7 @@ namespace NewHorizons.Builder.Props { if (info.hasCollision) { - throw new NotSupportedException($"Shapes do not support collision; set {info.hasCollision} to false and use a supported collider type (sphere, box, or capsule)."); + throw new NotSupportedException($"Shapes do not support collision; set {nameof(info.hasCollision)} to false or use a supported collider type (sphere, box, or capsule)."); } if (info.useShape.HasValue && !info.useShape.Value) { From c9b1f91c7b6e7921fde00a865c451f738efc0964 Mon Sep 17 00:00:00 2001 From: Joshua Thome Date: Wed, 16 Apr 2025 21:21:41 -0500 Subject: [PATCH 46/57] Disable collision checks for complex shapes --- NewHorizons/Builder/Props/ShapeBuilder.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/NewHorizons/Builder/Props/ShapeBuilder.cs b/NewHorizons/Builder/Props/ShapeBuilder.cs index 924ea135..1496e6e0 100644 --- a/NewHorizons/Builder/Props/ShapeBuilder.cs +++ b/NewHorizons/Builder/Props/ShapeBuilder.cs @@ -90,6 +90,7 @@ namespace NewHorizons.Builder.Props cylinderShape._radius = info.radius; cylinderShape._height = info.height; cylinderShape._center = info.offset ?? Vector3.zero; + cylinderShape._pointChecksOnly = true; return cylinderShape; case ShapeType.Cone: var coneShape = go.AddComponent(); @@ -98,6 +99,7 @@ namespace NewHorizons.Builder.Props coneShape._direction = (int)info.direction; coneShape._height = info.height; coneShape._center = info.offset ?? Vector3.zero; + coneShape._pointChecksOnly = true; return coneShape; case ShapeType.Hemisphere: var hemisphereShape = go.AddComponent(); @@ -105,6 +107,7 @@ namespace NewHorizons.Builder.Props hemisphereShape._direction = (int)info.direction; hemisphereShape._cap = info.cap; hemisphereShape._center = info.offset ?? Vector3.zero; + hemisphereShape._pointChecksOnly = true; return hemisphereShape; case ShapeType.Hemicapsule: var hemicapsuleShape = go.AddComponent(); @@ -113,6 +116,7 @@ namespace NewHorizons.Builder.Props hemicapsuleShape._height = info.height; hemicapsuleShape._cap = info.cap; hemicapsuleShape._center = info.offset ?? Vector3.zero; + hemicapsuleShape._pointChecksOnly = true; return hemicapsuleShape; case ShapeType.Ring: var ringShape = go.AddComponent(); @@ -120,6 +124,7 @@ namespace NewHorizons.Builder.Props ringShape.outerRadius = info.outerRadius; ringShape.height = info.height; ringShape.center = info.offset ?? Vector3.zero; + ringShape._pointChecksOnly = true; return ringShape; default: throw new ArgumentOutOfRangeException(nameof(info.type), info.type, $"Unsupported shape type"); From 8084425a23ac3f543627c4081b36c6fd171abf01 Mon Sep 17 00:00:00 2001 From: Joshua Thome Date: Wed, 16 Apr 2025 21:53:17 -0500 Subject: [PATCH 47/57] Volume docs page --- docs/src/content/docs/guides/volumes.md | 72 +++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 docs/src/content/docs/guides/volumes.md diff --git a/docs/src/content/docs/guides/volumes.md b/docs/src/content/docs/guides/volumes.md new file mode 100644 index 00000000..192b2180 --- /dev/null +++ b/docs/src/content/docs/guides/volumes.md @@ -0,0 +1,72 @@ +--- +title: Volumes +description: Guide to making volumes in New Horizons +--- + +Volumes are invisible 3D "zones" or "triggers" that cause various effects when objects enter or leave them. For example, `oxygenVolumes` refill the player's oxygen when they enter (used for the various oxygen-generating trees in the game), `forces.directionalVolumes` push players and other physics objects in a specific direction (used by both Nomai artificial gravity surfaces and tractor beams), `revealVolumes` unlock ship log facts when the player enters or observes them (used everywhere in the game), and more. + +New Horizons makes adding volumes to your planets easy; just specify them like you would [for a prop](/guides/details/) but under `Volumes` instead of `Props`. For example, to add an oxygen volume at certain location: + +```json title="planets/My Cool Planet.json" +{ + "$schema": "https://raw.githubusercontent.com/Outer-Wilds-New-Horizons/new-horizons/main/NewHorizons/Schemas/body_schema.json", + "name" : "My Cool Planet", + "Volumes": { + "oxygenVolumes": [ + { + "position": {"x": 399.4909, "y": -1.562098, "z": 20.11444}, + "radius": 30, + "treeVolume": true, + "playRefillAudio": true + } + ] + } +} +``` + +Listing out every type of volume is outside the scope of this guide, but you can see every supported type of volume and the properties they need in [the VolumesModule schema](/schemas/body-schema/defs/volumesmodule/). + +## Volume Shapes + +By default, volumes are spherical, and you can specify the radius of that sphere with the `radius` property. If you want to use a different shape for your volume, such as a box or capsule, you can specify your volume's `shape` like so: + +```json title="planets/My Cool Planet.json" +{ + "$schema": "https://raw.githubusercontent.com/Outer-Wilds-New-Horizons/new-horizons/main/NewHorizons/Schemas/body_schema.json", + "name" : "My Cool Planet", + "Volumes": { + "forces": { + "directionalVolumes": [ + { + "rename": "ArtificialGravitySurface", + "force": 8, + "playGravityCrystalAudio": true, + "shape": { + "type": "box", + "size": { + "x": 15.0, + "y": 10.0, + "z": 5.0 + }, + "offset": { + "x": 0, + "y": 5.0, + "z": 0 + } + }, + "position": { "x": 0, "y": -110, "z": 0 }, + "rotation": { "x": 180, "y": 0, "z": 0 } + } + ] + } + } +} +``` + +The supported shape types are: `sphere`, `box`, `capsule`, `cylinder`, `cone`, `hemisphere`, `hemicapsule`, and `ring`. See [the ShapeInfo schema](/schemas/body-schema/defs/shapeinfo/) for the full list of properties available to define each shape. + +Note that `sphere`, `box`, and `capsule` shapes are more reliable and efficient than other shapes, so prefer using them whenever possible. + +### Debugging + +To visualize the shapes of your volumes in-game, use the [Collider Visualizer mod](https://outerwildsmods.com/mods/collidervisualizer/). It will display a wireframe of the shapes around you so you can see precisely where they are and reposition or resize them as needed. From 5cde777159b45a244cb1118dbb485bd6f3c987ec Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Fri, 18 Apr 2025 05:53:41 -0400 Subject: [PATCH 48/57] log xml file --- .../Builder/Props/TranslatorText/TranslatorTextBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NewHorizons/Builder/Props/TranslatorText/TranslatorTextBuilder.cs b/NewHorizons/Builder/Props/TranslatorText/TranslatorTextBuilder.cs index cd967809..f5cf3b2b 100644 --- a/NewHorizons/Builder/Props/TranslatorText/TranslatorTextBuilder.cs +++ b/NewHorizons/Builder/Props/TranslatorText/TranslatorTextBuilder.cs @@ -471,7 +471,7 @@ namespace NewHorizons.Builder.Props.TranslatorText if (info.arcInfo != null && info.arcInfo.Count() != dict.Values.Count()) { - NHLogger.LogError($"Can't make NomaiWallText, arcInfo length [{info.arcInfo.Count()}] doesn't equal number of TextBlocks [{dict.Values.Count()}] in the xml"); + NHLogger.LogError($"Can't make NomaiWallText [{info.xmlFile}], arcInfo length [{info.arcInfo.Count()}] doesn't equal number of TextBlocks [{dict.Values.Count()}] in the xml"); return; } From 8c4f22eab3476d3710c7add1138046c5fa97391f Mon Sep 17 00:00:00 2001 From: xen-42 Date: Fri, 18 Apr 2025 23:35:53 -0400 Subject: [PATCH 49/57] Why did we stop using this --- .github/workflows/build.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 49c55427..c56ab4f4 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -62,7 +62,8 @@ jobs: name: NewHorizons-Schemas-${{ inputs.build_type }} path: .\NewHorizons\Schemas - - name: Check Changed Schemas + - name: Verify Changed Schemas + uses: tj-actions/verify-changed-files@v20 id: changed_files - run: | - echo "files_changed=$(git diff --exit-code NewHorizons/Schemas 2>&1>$null && echo false || echo true)" >> $Env:GITHUB_OUTPUT \ No newline at end of file + with: + files: NewHorizons/Schemas/** \ No newline at end of file From dd90af13148adac5d639a6903604058d54c6ccfb Mon Sep 17 00:00:00 2001 From: Ben C Date: Sat, 19 Apr 2025 03:39:03 +0000 Subject: [PATCH 50/57] Updated Schemas --- .../Schemas/addon_manifest_schema.json | 23 +++++++++++++++++ NewHorizons/Schemas/body_schema.json | 25 ++++++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/NewHorizons/Schemas/addon_manifest_schema.json b/NewHorizons/Schemas/addon_manifest_schema.json index d68f545c..401db544 100644 --- a/NewHorizons/Schemas/addon_manifest_schema.json +++ b/NewHorizons/Schemas/addon_manifest_schema.json @@ -103,6 +103,27 @@ "type": "string", "description": "Condition that must be true for this game over to trigger. If this is on a LoadCreditsVolume, leave empty to always trigger this game over.\nNote this is a regular dialogue condition, not a persistent condition." }, + "audio": { + "type": "string", + "description": "The audio to use for the credits music. Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list.\nCredits will be silent unless this attribute is specified.\nNote: only applies when creditsType is set to \"custom\"." + }, + "audioVolume": { + "type": "number", + "description": "The length of the fade in and out for the credits music.\nNote: only applies when creditsType is set to \"custom\".", + "format": "float", + "default": 1.0 + }, + "audioLooping": { + "type": "boolean", + "description": "Determines if the credits music should loop.\nNote: only applies when creditsType is set to \"custom\".", + "default": false + }, + "length": { + "type": "number", + "description": "Duration of the credits scroll in seconds.\nNote: only applies when creditsType is set to \"custom\".", + "format": "float", + "default": 120.0 + }, "creditsType": { "description": "The type of credits that will run after the game over message is shown", "default": "fast", @@ -152,12 +173,14 @@ "Fast", "Final", "Kazoo", + "Custom", "None" ], "enum": [ "fast", "final", "kazoo", + "custom", "none" ] } diff --git a/NewHorizons/Schemas/body_schema.json b/NewHorizons/Schemas/body_schema.json index c3f289b1..421029c5 100644 --- a/NewHorizons/Schemas/body_schema.json +++ b/NewHorizons/Schemas/body_schema.json @@ -2964,7 +2964,7 @@ "$ref": "#/definitions/SlideReelType" }, "reelCondition": { - "description": "Exclusive to the slide reel type. Condition/material of the reel. Antique is the Stranger, Pristine is the Dreamworld, Rusted is a burned reel.", + "description": "Exclusive to the slide reel and standing vision torch type. Condition/material of the reel. Antique is the Stranger, Pristine is the Dreamworld, Rusted (exclusive to slide reels) is a burned reel.", "default": "antique", "$ref": "#/definitions/SlideReelCondition" }, @@ -6529,6 +6529,27 @@ "type": "string", "description": "Condition that must be true for this game over to trigger. If this is on a LoadCreditsVolume, leave empty to always trigger this game over.\nNote this is a regular dialogue condition, not a persistent condition." }, + "audio": { + "type": "string", + "description": "The audio to use for the credits music. Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list.\nCredits will be silent unless this attribute is specified.\nNote: only applies when creditsType is set to \"custom\"." + }, + "audioVolume": { + "type": "number", + "description": "The length of the fade in and out for the credits music.\nNote: only applies when creditsType is set to \"custom\".", + "format": "float", + "default": 1.0 + }, + "audioLooping": { + "type": "boolean", + "description": "Determines if the credits music should loop.\nNote: only applies when creditsType is set to \"custom\".", + "default": false + }, + "length": { + "type": "number", + "description": "Duration of the credits scroll in seconds.\nNote: only applies when creditsType is set to \"custom\".", + "format": "float", + "default": 120.0 + }, "creditsType": { "description": "The type of credits that will run after the game over message is shown", "default": "fast", @@ -6543,12 +6564,14 @@ "Fast", "Final", "Kazoo", + "Custom", "None" ], "enum": [ "fast", "final", "kazoo", + "custom", "none" ] }, From f4f78f5aaae2aeb2c8e25db78323424d820747e5 Mon Sep 17 00:00:00 2001 From: xen-42 Date: Sat, 19 Apr 2025 00:06:00 -0400 Subject: [PATCH 51/57] Get components in children also checks self for components --- NewHorizons/Builder/Props/DetailBuilder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NewHorizons/Builder/Props/DetailBuilder.cs b/NewHorizons/Builder/Props/DetailBuilder.cs index d7400937..82a22d05 100644 --- a/NewHorizons/Builder/Props/DetailBuilder.cs +++ b/NewHorizons/Builder/Props/DetailBuilder.cs @@ -473,11 +473,11 @@ namespace NewHorizons.Builder.Props { // These flood toggles are to disable flooded docks on the Stranger // Presumably the user isn't making one of those - foreach (var toggle in dock.GetComponents().Concat(dock.GetComponentsInChildren())) + foreach (var toggle in dock.GetComponentsInChildren()) { Component.DestroyImmediate(toggle); } - foreach (var floodSensor in dock.GetComponents().Concat(dock.GetComponentsInChildren())) + foreach (var floodSensor in dock.GetComponentsInChildren()) { Component.DestroyImmediate(floodSensor); } From 07da94959168cfee490e4918c566adb7eb0ee7c6 Mon Sep 17 00:00:00 2001 From: Ben C Date: Sat, 19 Apr 2025 04:06:57 +0000 Subject: [PATCH 52/57] Updated Schemas --- NewHorizons/Schemas/body_schema.json | 99 ++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/NewHorizons/Schemas/body_schema.json b/NewHorizons/Schemas/body_schema.json index 421029c5..758d2254 100644 --- a/NewHorizons/Schemas/body_schema.json +++ b/NewHorizons/Schemas/body_schema.json @@ -2231,6 +2231,13 @@ "$ref": "#/definitions/RaftInfo" } }, + "raftDocks": { + "type": "array", + "description": "Add raft docks to this planet (requires Echoes of the Eye DLC)", + "items": { + "$ref": "#/definitions/RaftDockInfo" + } + }, "scatter": { "type": "array", "description": "Scatter props around this planet's surface", @@ -2813,6 +2820,47 @@ "description": "Acceleration of the raft. Default acceleration is 5.", "format": "float", "default": 5.0 + }, + "dockPath": { + "type": "string", + "description": "Path to the dock this raft will start attached to." + }, + "pristine": { + "type": "boolean", + "description": "Uses the raft model from the dreamworld" + } + } + }, + "RaftDockInfo": { + "type": "object", + "additionalProperties": false, + "properties": { + "rotation": { + "description": "Rotation of the object", + "$ref": "#/definitions/MVector3" + }, + "alignRadial": { + "type": [ + "boolean", + "null" + ], + "description": "Do we try to automatically align this object to stand upright relative to the body's center? Stacks with rotation.\nDefaults to true for geysers, tornados, and volcanoes, and false for everything else." + }, + "position": { + "description": "Position of the object", + "$ref": "#/definitions/MVector3" + }, + "isRelativeToParent": { + "type": "boolean", + "description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object." + }, + "parentPath": { + "type": "string", + "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)." + }, + "rename": { + "type": "string", + "description": "An optional rename of this object" } } }, @@ -5423,6 +5471,13 @@ "$ref": "#/definitions/SpeedTrapVolumeInfo" } }, + "speedLimiterVolumes": { + "type": "array", + "description": "Add speed limiter volumes to this planet.\nSlows down the player, ship, and probe when they enter this volume.\nUsed on the Stranger in DLC.", + "items": { + "$ref": "#/definitions/SpeedLimiterVolumeInfo" + } + }, "visorEffects": { "description": "Add visor effect volumes to this planet.", "$ref": "#/definitions/VisorEffectModule" @@ -6266,6 +6321,50 @@ } } }, + "SpeedLimiterVolumeInfo": { + "type": "object", + "additionalProperties": false, + "properties": { + "radius": { + "type": "number", + "description": "The radius of this volume.", + "format": "float", + "default": 1.0 + }, + "position": { + "description": "Position of the object", + "$ref": "#/definitions/MVector3" + }, + "isRelativeToParent": { + "type": "boolean", + "description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object." + }, + "parentPath": { + "type": "string", + "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)." + }, + "rename": { + "type": "string", + "description": "An optional rename of this object" + }, + "maxSpeed": { + "type": "number", + "description": "The speed the volume will slow you down to when you enter it.", + "format": "float", + "default": 10.0 + }, + "stoppingDistance": { + "type": "number", + "format": "float", + "default": 100.0 + }, + "maxEntryAngle": { + "type": "number", + "format": "float", + "default": 60.0 + } + } + }, "VisorEffectModule": { "type": "object", "additionalProperties": false, From 781bb64e8593b8ce93889e2c6eb1a203038cc887 Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Sat, 19 Apr 2025 00:08:33 -0400 Subject: [PATCH 53/57] Comments --- NewHorizons/Components/Volumes/SpeedLimiterVolume.cs | 2 -- .../Modules/Volumes/VolumeInfos/SpeedLimiterVolumeInfo.cs | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/NewHorizons/Components/Volumes/SpeedLimiterVolume.cs b/NewHorizons/Components/Volumes/SpeedLimiterVolume.cs index f5d1e4af..478589ff 100644 --- a/NewHorizons/Components/Volumes/SpeedLimiterVolume.cs +++ b/NewHorizons/Components/Volumes/SpeedLimiterVolume.cs @@ -142,8 +142,6 @@ namespace NewHorizons.Components.Volumes public float deceleration; - public bool decelerated; - public TrackedBody(OWRigidbody body, Detector.Name name, float deceleration) { this.body = body; diff --git a/NewHorizons/External/Modules/Volumes/VolumeInfos/SpeedLimiterVolumeInfo.cs b/NewHorizons/External/Modules/Volumes/VolumeInfos/SpeedLimiterVolumeInfo.cs index f6f2fca9..ef341d57 100644 --- a/NewHorizons/External/Modules/Volumes/VolumeInfos/SpeedLimiterVolumeInfo.cs +++ b/NewHorizons/External/Modules/Volumes/VolumeInfos/SpeedLimiterVolumeInfo.cs @@ -14,13 +14,13 @@ namespace NewHorizons.External.Modules.Volumes.VolumeInfos public float maxSpeed = 10f; /// - /// + /// The distance from the outside of the volume that the limiter slows you down to max speed at. /// [DefaultValue(100f)] public float stoppingDistance = 100f; /// - /// + /// The maximum angle (in degrees) between the direction the incoming object is moving relative to the volume's center and the line from the object toward the center of the volume, within which the speed limiter will activate. /// [DefaultValue(60f)] public float maxEntryAngle = 60f; From 33f468a50af3dcd9006dc8fd99b020d5556f51e9 Mon Sep 17 00:00:00 2001 From: Ben C Date: Sat, 19 Apr 2025 04:10:42 +0000 Subject: [PATCH 54/57] Updated Schemas --- NewHorizons/Schemas/body_schema.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NewHorizons/Schemas/body_schema.json b/NewHorizons/Schemas/body_schema.json index 758d2254..19669bff 100644 --- a/NewHorizons/Schemas/body_schema.json +++ b/NewHorizons/Schemas/body_schema.json @@ -6355,11 +6355,13 @@ }, "stoppingDistance": { "type": "number", + "description": "The distance from the outside of the volume that the limiter slows you down to max speed at.", "format": "float", "default": 100.0 }, "maxEntryAngle": { "type": "number", + "description": "The maximum angle (in degrees) between the direction the incoming object is moving relative to the volume's center and the line from the object toward the center of the volume, within which the speed limiter will activate.", "format": "float", "default": 60.0 } From 54d27bedebfb5b18ce2308512c5e8ce5d5b8b3e6 Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Sat, 19 Apr 2025 00:21:07 -0400 Subject: [PATCH 55/57] match with others --- NewHorizons/Builder/Volumes/SpeedLimiterVolumeBuilder.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NewHorizons/Builder/Volumes/SpeedLimiterVolumeBuilder.cs b/NewHorizons/Builder/Volumes/SpeedLimiterVolumeBuilder.cs index d28b0a24..0bd94b87 100644 --- a/NewHorizons/Builder/Volumes/SpeedLimiterVolumeBuilder.cs +++ b/NewHorizons/Builder/Volumes/SpeedLimiterVolumeBuilder.cs @@ -14,6 +14,8 @@ namespace NewHorizons.Builder.Volumes volume.stoppingDistance = info.stoppingDistance; volume.maxEntryAngle = info.maxEntryAngle; + volume.gameObject.SetActive(true); + return volume; } } From 4ac782faa3718e79a6142267426e3c194d66243e Mon Sep 17 00:00:00 2001 From: Ben C Date: Sat, 19 Apr 2025 04:23:11 +0000 Subject: [PATCH 56/57] Updated Schemas --- NewHorizons/Schemas/body_schema.json | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/NewHorizons/Schemas/body_schema.json b/NewHorizons/Schemas/body_schema.json index 90abc434..27da29db 100644 --- a/NewHorizons/Schemas/body_schema.json +++ b/NewHorizons/Schemas/body_schema.json @@ -7100,10 +7100,25 @@ "properties": { "radius": { "type": "number", - "description": "The radius of this volume.", + "description": "The radius of this volume, if a shape is not specified.", "format": "float", "default": 1.0 }, + "shape": { + "description": "The shape of this volume. Defaults to a sphere with a radius of `radius` if not specified.", + "$ref": "#/definitions/ShapeInfo" + }, + "rotation": { + "description": "Rotation of the object", + "$ref": "#/definitions/MVector3" + }, + "alignRadial": { + "type": [ + "boolean", + "null" + ], + "description": "Do we try to automatically align this object to stand upright relative to the body's center? Stacks with rotation.\nDefaults to true for geysers, tornados, and volcanoes, and false for everything else." + }, "position": { "description": "Position of the object", "$ref": "#/definitions/MVector3" From ea5857497b534ee160ac80491d3133edbefb1a0b Mon Sep 17 00:00:00 2001 From: xen-42 Date: Sat, 19 Apr 2025 00:34:29 -0400 Subject: [PATCH 57/57] Update manifest.json --- NewHorizons/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NewHorizons/manifest.json b/NewHorizons/manifest.json index 3311a8fb..99694510 100644 --- a/NewHorizons/manifest.json +++ b/NewHorizons/manifest.json @@ -4,7 +4,7 @@ "author": "xen, Bwc9876, JohnCorby, MegaPiggy, and friends", "name": "New Horizons", "uniqueName": "xen.NewHorizons", - "version": "1.27.4", + "version": "1.28.0", "owmlVersion": "2.12.1", "dependencies": [ "JohnCorby.VanillaFix", "xen.CommonCameraUtility", "dgarro.CustomShipLogModes" ], "conflicts": [ "PacificEngine.OW_CommonResources" ],