From b84d94a404b8dd046731e7f1e49c125929cd4e75 Mon Sep 17 00:00:00 2001 From: Joshua Thome Date: Tue, 1 Jul 2025 12:28:12 -0500 Subject: [PATCH 01/13] Condition trigger volume --- .../Volumes/ConditionTriggerVolumeBuilder.cs | 25 +++++++ .../Builder/Volumes/VolumesBuildManager.cs | 7 ++ .../Volumes/ConditionTriggerVolume.cs | 66 +++++++++++++++++++ .../VolumeInfos/ConditionTriggerVolumeInfo.cs | 39 +++++++++++ .../External/Modules/Volumes/VolumesModule.cs | 5 ++ 5 files changed, 142 insertions(+) create mode 100644 NewHorizons/Builder/Volumes/ConditionTriggerVolumeBuilder.cs create mode 100644 NewHorizons/Components/Volumes/ConditionTriggerVolume.cs create mode 100644 NewHorizons/External/Modules/Volumes/VolumeInfos/ConditionTriggerVolumeInfo.cs diff --git a/NewHorizons/Builder/Volumes/ConditionTriggerVolumeBuilder.cs b/NewHorizons/Builder/Volumes/ConditionTriggerVolumeBuilder.cs new file mode 100644 index 00000000..2f9f270a --- /dev/null +++ b/NewHorizons/Builder/Volumes/ConditionTriggerVolumeBuilder.cs @@ -0,0 +1,25 @@ +using NewHorizons.Components.Volumes; +using NewHorizons.External.Modules.Volumes.VolumeInfos; +using UnityEngine; + +namespace NewHorizons.Builder.Volumes +{ + internal static class ConditionTriggerVolumeBuilder + { + public static ConditionTriggerVolume Make(GameObject planetGO, Sector sector, ConditionTriggerVolumeInfo info) + { + var volume = VolumeBuilder.Make(planetGO, sector, info); + + volume.Condition = info.condition; + volume.Persistent = info.persistent; + volume.Reversible = info.reversible; + volume.Player = info.player; + volume.Probe = info.probe; + volume.Ship = info.ship; + + volume.gameObject.SetActive(true); + + return volume; + } + } +} diff --git a/NewHorizons/Builder/Volumes/VolumesBuildManager.cs b/NewHorizons/Builder/Volumes/VolumesBuildManager.cs index 161f1d6a..58ff8fdb 100644 --- a/NewHorizons/Builder/Volumes/VolumesBuildManager.cs +++ b/NewHorizons/Builder/Volumes/VolumesBuildManager.cs @@ -35,6 +35,13 @@ namespace NewHorizons.Builder.Volumes AudioVolumeBuilder.Make(go, sector, audioVolume, mod); } } + if (config.Volumes.conditionTriggerVolumes != null) + { + foreach (var conditionTriggerVolume in config.Volumes.conditionTriggerVolumes) + { + ConditionTriggerVolumeBuilder.Make(go, sector, conditionTriggerVolume); + } + } if (config.Volumes.dayNightAudioVolumes != null) { foreach (var dayNightAudioVolume in config.Volumes.dayNightAudioVolumes) diff --git a/NewHorizons/Components/Volumes/ConditionTriggerVolume.cs b/NewHorizons/Components/Volumes/ConditionTriggerVolume.cs new file mode 100644 index 00000000..047f8fb2 --- /dev/null +++ b/NewHorizons/Components/Volumes/ConditionTriggerVolume.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace NewHorizons.Components.Volumes +{ + public class ConditionTriggerVolume : BaseVolume + { + public string Condition { get; set; } + public bool Persistent { get; set; } + public bool Reversible { get; set; } + public bool Player { get; set; } = true; + public bool Probe { get; set; } + public bool Ship { get; set; } + + public override void OnTriggerVolumeEntry(GameObject hitObj) + { + if (TestHitObject(hitObj)) + { + if (Persistent) + { + PlayerData.SetPersistentCondition(Condition, true); + } + else + { + DialogueConditionManager.SharedInstance.SetConditionState(Condition, true); + } + } + } + + public override void OnTriggerVolumeExit(GameObject hitObj) + { + if (Reversible && TestHitObject(hitObj)) + { + if (Persistent) + { + PlayerData.SetPersistentCondition(Condition, false); + } + else + { + DialogueConditionManager.SharedInstance.SetConditionState(Condition, false); + } + } + } + + bool TestHitObject(GameObject hitObj) + { + if (Player && hitObj.CompareTag("PlayerDetector")) + { + return true; + } + if (Probe && hitObj.CompareTag("ProbeDetector")) + { + return true; + } + if (Ship && hitObj.CompareTag("ShipDetector")) + { + return true; + } + return false; + } + } +} diff --git a/NewHorizons/External/Modules/Volumes/VolumeInfos/ConditionTriggerVolumeInfo.cs b/NewHorizons/External/Modules/Volumes/VolumeInfos/ConditionTriggerVolumeInfo.cs new file mode 100644 index 00000000..c364d151 --- /dev/null +++ b/NewHorizons/External/Modules/Volumes/VolumeInfos/ConditionTriggerVolumeInfo.cs @@ -0,0 +1,39 @@ +using Newtonsoft.Json; +using System.ComponentModel; + +namespace NewHorizons.External.Modules.Volumes.VolumeInfos +{ + [JsonObject] + public class ConditionTriggerVolumeInfo : VolumeInfo + { + /// + /// The name of the dialogue condition or persistent condition to set when entering the volume. + /// + public string condition; + + /// + /// If true, the condition will persist across all future loops until unset. + /// + public bool persistent; + + /// + /// Whether to unset the condition when existing the volume. + /// + public bool reversible; + + /// + /// Whether to set the condition when the player enters this volume. Defaults to true. + /// + [DefaultValue(true)] public bool player = true; + + /// + /// Whether to set the condition when the scout probe enters this volume. + /// + public bool probe; + + /// + /// Whether to set the condition when the ship enters this volume. + /// + public bool ship; + } +} diff --git a/NewHorizons/External/Modules/Volumes/VolumesModule.cs b/NewHorizons/External/Modules/Volumes/VolumesModule.cs index 34795d15..7f44b911 100644 --- a/NewHorizons/External/Modules/Volumes/VolumesModule.cs +++ b/NewHorizons/External/Modules/Volumes/VolumesModule.cs @@ -11,6 +11,11 @@ namespace NewHorizons.External.Modules.Volumes /// public AudioVolumeInfo[] audioVolumes; + /// + /// Add condition trigger volumes to this planet. Sets a condition when the player, scout, or ship enters this volume. + /// + public ConditionTriggerVolumeInfo[] conditionTriggerVolumes; + /// /// Add day night audio volumes to this planet. These volumes play a different clip depending on the time of day. /// From 330564538a3e996b2f4a90fa4a85fcaef65795d8 Mon Sep 17 00:00:00 2001 From: Ben C Date: Tue, 1 Jul 2025 17:36:52 +0000 Subject: [PATCH 02/13] Updated Schemas --- NewHorizons/Schemas/body_schema.json | 75 ++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/NewHorizons/Schemas/body_schema.json b/NewHorizons/Schemas/body_schema.json index 27da29db..7b35e000 100644 --- a/NewHorizons/Schemas/body_schema.json +++ b/NewHorizons/Schemas/body_schema.json @@ -5365,6 +5365,13 @@ "$ref": "#/definitions/AudioVolumeInfo" } }, + "conditionTriggerVolumes": { + "type": "array", + "description": "Add condition trigger volumes to this planet. Sets a condition when the player, scout, or ship enters this volume.", + "items": { + "$ref": "#/definitions/ConditionTriggerVolumeInfo" + } + }, "dayNightAudioVolumes": { "type": "array", "description": "Add day night audio volumes to this planet. These volumes play a different clip depending on the time of day.", @@ -5717,6 +5724,74 @@ "manual" ] }, + "ConditionTriggerVolumeInfo": { + "type": "object", + "additionalProperties": false, + "properties": { + "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" + }, + "condition": { + "type": "string", + "description": "The name of the dialogue condition or persistent condition to set when entering the volume." + }, + "persistent": { + "type": "boolean", + "description": "If true, the condition will persist across all future loops until unset." + }, + "reversible": { + "type": "boolean", + "description": "Whether to unset the condition when existing the volume." + }, + "player": { + "type": "boolean", + "description": "Whether to set the condition when the player enters this volume. Defaults to true.", + "default": true + }, + "probe": { + "type": "boolean", + "description": "Whether to set the condition when the scout probe enters this volume." + }, + "ship": { + "type": "boolean", + "description": "Whether to set the condition when the ship enters this volume." + } + } + }, "DayNightAudioVolumeInfo": { "type": "object", "additionalProperties": false, From d072a74b5d9f9656a96fd348a3dd6afb75c6a68d Mon Sep 17 00:00:00 2001 From: Joshua Thome Date: Tue, 1 Jul 2025 17:08:13 -0500 Subject: [PATCH 03/13] Dream candle and projection totem conditions --- .../EchoesOfTheEye/DreamCandleBuilder.cs | 8 ++ .../EchoesOfTheEye/ProjectionTotemBuilder.cs | 8 ++ .../EOTE/DreamLightConditionController.cs | 85 +++++++++++++++++++ .../Props/EchoesOfTheEye/DreamCandleInfo.cs | 5 ++ .../EchoesOfTheEye/DreamLightConditionInfo.cs | 31 +++++++ .../EchoesOfTheEye/ProjectionTotemInfo.cs | 5 ++ 6 files changed, 142 insertions(+) create mode 100644 NewHorizons/Components/EOTE/DreamLightConditionController.cs create mode 100644 NewHorizons/External/Modules/Props/EchoesOfTheEye/DreamLightConditionInfo.cs diff --git a/NewHorizons/Builder/Props/EchoesOfTheEye/DreamCandleBuilder.cs b/NewHorizons/Builder/Props/EchoesOfTheEye/DreamCandleBuilder.cs index 38387bf8..be0b2184 100644 --- a/NewHorizons/Builder/Props/EchoesOfTheEye/DreamCandleBuilder.cs +++ b/NewHorizons/Builder/Props/EchoesOfTheEye/DreamCandleBuilder.cs @@ -1,3 +1,4 @@ +using NewHorizons.Components.EOTE; using NewHorizons.External.Modules.Props; using NewHorizons.External.Modules.Props.EchoesOfTheEye; using NewHorizons.Handlers; @@ -9,6 +10,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using TerrainBaker; using UnityEngine; namespace NewHorizons.Builder.Props.EchoesOfTheEye @@ -70,6 +72,12 @@ namespace NewHorizons.Builder.Props.EchoesOfTheEye dreamCandle._startLit = info.startLit; dreamCandle.SetLit(info.startLit, false, true); + if (info.condition != null) + { + var conditionController = dreamCandle.gameObject.AddComponent(); + conditionController.SetFromInfo(info.condition); + } + return candleObj; } } diff --git a/NewHorizons/Builder/Props/EchoesOfTheEye/ProjectionTotemBuilder.cs b/NewHorizons/Builder/Props/EchoesOfTheEye/ProjectionTotemBuilder.cs index 69155147..6b70b567 100644 --- a/NewHorizons/Builder/Props/EchoesOfTheEye/ProjectionTotemBuilder.cs +++ b/NewHorizons/Builder/Props/EchoesOfTheEye/ProjectionTotemBuilder.cs @@ -1,3 +1,4 @@ +using NewHorizons.Components.EOTE; using NewHorizons.External.Modules.Props; using NewHorizons.External.Modules.Props.EchoesOfTheEye; using NewHorizons.Handlers; @@ -116,6 +117,13 @@ namespace NewHorizons.Builder.Props.EchoesOfTheEye projector._lit = info.startLit; projector._startLit = info.startLit; projector._extinguishOnly = info.extinguishOnly; + + if (info.condition != null) + { + var conditionController = projector.gameObject.AddComponent(); + conditionController.SetFromInfo(info.condition); + } + /* Delay.FireOnNextUpdate(() => { diff --git a/NewHorizons/Components/EOTE/DreamLightConditionController.cs b/NewHorizons/Components/EOTE/DreamLightConditionController.cs new file mode 100644 index 00000000..7800f236 --- /dev/null +++ b/NewHorizons/Components/EOTE/DreamLightConditionController.cs @@ -0,0 +1,85 @@ +using NewHorizons.External.Modules.Props.EchoesOfTheEye; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace NewHorizons.Components.EOTE +{ + public class DreamLightConditionController : MonoBehaviour + { + public string Condition { get; set; } + public bool Persistent { get; set; } + public bool Reversible { get; set; } + public bool OnExtinguish { get; set; } + + DreamObjectProjector _projector; + DreamCandle _dreamCandle; + + public void SetFromInfo(DreamLightConditionInfo info) + { + Condition = info.condition; + Persistent = info.persistent; + Reversible = info.reversible; + OnExtinguish = info.onExtinguish; + } + + protected void Awake() + { + _projector = GetComponent(); + _projector.OnProjectorLit.AddListener(OnProjectorLit); + _projector.OnProjectorExtinguished.AddListener(OnProjectorExtinguished); + + _dreamCandle = GetComponent(); + if (_dreamCandle != null) + { + _dreamCandle.OnLitStateChanged.AddListener(OnCandleLitStateChanged); + } + } + + protected void OnDestroy() + { + if (_projector != null) + { + _projector.OnProjectorLit.RemoveListener(OnProjectorLit); + _projector.OnProjectorExtinguished.RemoveListener(OnProjectorExtinguished); + } + if (_dreamCandle != null) + { + _dreamCandle.OnLitStateChanged.RemoveListener(OnCandleLitStateChanged); + } + } + + private void OnProjectorLit() + { + HandleCondition(!OnExtinguish); + } + + private void OnProjectorExtinguished() + { + HandleCondition(OnExtinguish); + } + + private void OnCandleLitStateChanged() + { + HandleCondition(OnExtinguish ? !_dreamCandle._lit : _dreamCandle._lit); + } + + private void HandleCondition(bool shouldSet) + { + if (shouldSet || Reversible) + { + if (Persistent) + { + PlayerData.SetPersistentCondition(Condition, shouldSet); + } + else + { + DialogueConditionManager.SharedInstance.SetConditionState(Condition, shouldSet); + } + } + } + } +} diff --git a/NewHorizons/External/Modules/Props/EchoesOfTheEye/DreamCandleInfo.cs b/NewHorizons/External/Modules/Props/EchoesOfTheEye/DreamCandleInfo.cs index 7f4efb24..95017236 100644 --- a/NewHorizons/External/Modules/Props/EchoesOfTheEye/DreamCandleInfo.cs +++ b/NewHorizons/External/Modules/Props/EchoesOfTheEye/DreamCandleInfo.cs @@ -20,5 +20,10 @@ namespace NewHorizons.External.Modules.Props.EchoesOfTheEye /// Whether the candle should start lit or extinguished. /// public bool startLit; + + /// + /// A condition to set when the candle is lit. + /// + public DreamLightConditionInfo condition; } } diff --git a/NewHorizons/External/Modules/Props/EchoesOfTheEye/DreamLightConditionInfo.cs b/NewHorizons/External/Modules/Props/EchoesOfTheEye/DreamLightConditionInfo.cs new file mode 100644 index 00000000..b1b00583 --- /dev/null +++ b/NewHorizons/External/Modules/Props/EchoesOfTheEye/DreamLightConditionInfo.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NewHorizons.External.Modules.Props.EchoesOfTheEye +{ + public class DreamLightConditionInfo + { + /// + /// The name of the dialogue condition or persistent condition to set when the light is lit. + /// + public string condition; + + /// + /// If true, the condition will persist across all future loops until unset. + /// + public bool persistent; + + /// + /// Whether to unset the condition when the light is extinguished again. + /// + public bool reversible; + + /// + /// Whether to set the condition when the light is extinguished instead. If `reversible` is true, the condition will be unset when the light is lit again. + /// + public bool onExtinguish; + } +} diff --git a/NewHorizons/External/Modules/Props/EchoesOfTheEye/ProjectionTotemInfo.cs b/NewHorizons/External/Modules/Props/EchoesOfTheEye/ProjectionTotemInfo.cs index 0852b7e9..31953579 100644 --- a/NewHorizons/External/Modules/Props/EchoesOfTheEye/ProjectionTotemInfo.cs +++ b/NewHorizons/External/Modules/Props/EchoesOfTheEye/ProjectionTotemInfo.cs @@ -45,5 +45,10 @@ namespace NewHorizons.External.Modules.Props.EchoesOfTheEye /// If set, projected objects will be set to fully active or fully disabled instantly instead of smoothly fading lights/renderers/colliders. Use this if the normal behavior is insufficient for the objects you're using. /// public bool toggleProjectedObjectsActive; + + /// + /// A condition to set when the totem is lit. + /// + public DreamLightConditionInfo condition; } } From b71aa9f84537fbc28582116980aa86553ec9c2fb Mon Sep 17 00:00:00 2001 From: Ben C Date: Tue, 1 Jul 2025 22:10:15 +0000 Subject: [PATCH 04/13] Updated Schemas --- NewHorizons/Schemas/body_schema.json | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/NewHorizons/Schemas/body_schema.json b/NewHorizons/Schemas/body_schema.json index 7b35e000..d8f7036c 100644 --- a/NewHorizons/Schemas/body_schema.json +++ b/NewHorizons/Schemas/body_schema.json @@ -4571,6 +4571,10 @@ "startLit": { "type": "boolean", "description": "Whether the candle should start lit or extinguished." + }, + "condition": { + "description": "A condition to set when the candle is lit.", + "$ref": "#/definitions/DreamLightConditionInfo" } } }, @@ -4600,6 +4604,28 @@ "pile" ] }, + "DreamLightConditionInfo": { + "type": "object", + "additionalProperties": false, + "properties": { + "condition": { + "type": "string", + "description": "The name of the dialogue condition or persistent condition to set when the light is lit." + }, + "persistent": { + "type": "boolean", + "description": "If true, the condition will persist across all future loops until unset." + }, + "reversible": { + "type": "boolean", + "description": "Whether to unset the condition when the light is extinguished again." + }, + "onExtinguish": { + "type": "boolean", + "description": "Whether to set the condition when the light is extinguished instead. If `reversible` is true, the condition will be unset when the light is lit again." + } + } + }, "ProjectionTotemInfo": { "type": "object", "additionalProperties": false, @@ -4667,6 +4693,10 @@ "toggleProjectedObjectsActive": { "type": "boolean", "description": "If set, projected objects will be set to fully active or fully disabled instantly instead of smoothly fading lights/renderers/colliders. Use this if the normal behavior is insufficient for the objects you're using." + }, + "condition": { + "description": "A condition to set when the totem is lit.", + "$ref": "#/definitions/DreamLightConditionInfo" } } }, From 1330df64b48c70e848f49ae138a37d08f8146f46 Mon Sep 17 00:00:00 2001 From: Joshua Thome Date: Fri, 4 Jul 2025 14:21:14 -0500 Subject: [PATCH 05/13] Interaction volumes --- .../Volumes/InteractionVolumeBuilder.cs | 87 +++++++++++++++++++ .../Builder/Volumes/VolumesBuildManager.cs | 7 ++ .../Components/Volumes/NHInteractionVolume.cs | 69 +++++++++++++++ .../VolumeInfos/InteractionVolumeInfo.cs | 65 ++++++++++++++ .../External/Modules/Volumes/VolumesModule.cs | 6 ++ 5 files changed, 234 insertions(+) create mode 100644 NewHorizons/Builder/Volumes/InteractionVolumeBuilder.cs create mode 100644 NewHorizons/Components/Volumes/NHInteractionVolume.cs create mode 100644 NewHorizons/External/Modules/Volumes/VolumeInfos/InteractionVolumeInfo.cs diff --git a/NewHorizons/Builder/Volumes/InteractionVolumeBuilder.cs b/NewHorizons/Builder/Volumes/InteractionVolumeBuilder.cs new file mode 100644 index 00000000..2f5c0122 --- /dev/null +++ b/NewHorizons/Builder/Volumes/InteractionVolumeBuilder.cs @@ -0,0 +1,87 @@ +using NewHorizons.Components.Volumes; +using NewHorizons.External.Modules.Volumes.VolumeInfos; +using NewHorizons.Handlers; +using NewHorizons.Utility; +using NewHorizons.Utility.Files; +using NewHorizons.Utility.OuterWilds; +using NewHorizons.Utility.OWML; +using OWML.Common; +using UnityEngine; + +namespace NewHorizons.Builder.Volumes +{ + internal static class InteractionVolumeBuilder + { + public static InteractReceiver Make(GameObject planetGO, Sector sector, InteractionVolumeInfo info, IModBehaviour mod) + { + // Interaction volumes must use colliders because the first-person interaction system uses raycasting + if (info.shape != null) + { + info.shape.useShape = false; + } + + var receiver = VolumeBuilder.Make(planetGO, sector, info); + receiver.gameObject.layer = Layer.Interactible; + + receiver._interactRange = info.range; + receiver._checkViewAngle = info.maxViewAngle.HasValue; + receiver._maxViewAngle = info.maxViewAngle ?? 180f; + receiver._usableInShip = info.usableInShip; + + var volume = receiver.gameObject.AddComponent(); + + volume.Reusable = info.reusable; + volume.Condition = info.condition; + volume.Persistent = info.persistent; + + if (!string.IsNullOrEmpty(info.audio)) + { + var audioSource = receiver.gameObject.AddComponent(); + + // This could be more configurable but this should cover the most common use cases without bloating the info object + var owAudioSource = receiver.gameObject.AddComponent(); + owAudioSource._audioSource = audioSource; + owAudioSource.playOnAwake = false; + owAudioSource.loop = false; + owAudioSource.SetMaxVolume(1f); + owAudioSource.SetClipSelectionType(OWAudioSource.ClipSelectionOnPlay.RANDOM); + owAudioSource.SetTrack(OWAudioMixer.TrackName.Environment); + AudioUtilities.SetAudioClip(owAudioSource, info.audio, mod); + } + + if (!string.IsNullOrEmpty(info.pathToAnimator)) + { + var animObj = planetGO.transform.Find(info.pathToAnimator); + + if (animObj == null) + { + NHLogger.LogError($"Couldn't find child of {planetGO.transform.GetPath()} at {info.pathToAnimator}"); + } + else + { + var animator = animObj.GetComponent(); + if (animator == null) + { + NHLogger.LogError($"Couldn't find Animator on {animObj.name} at {info.pathToAnimator}"); + } + else + { + volume.TargetAnimator = animator; + volume.AnimationTrigger = info.animationTrigger; + } + } + } + + receiver.gameObject.SetActive(true); + + var text = TranslationHandler.GetTranslation(info.prompt, TranslationHandler.TextType.UI); + Delay.FireOnNextUpdate(() => + { + // This NREs if set immediately + receiver.ChangePrompt(text); + }); + + return receiver; + } + } +} diff --git a/NewHorizons/Builder/Volumes/VolumesBuildManager.cs b/NewHorizons/Builder/Volumes/VolumesBuildManager.cs index 58ff8fdb..1b098f3f 100644 --- a/NewHorizons/Builder/Volumes/VolumesBuildManager.cs +++ b/NewHorizons/Builder/Volumes/VolumesBuildManager.cs @@ -70,6 +70,13 @@ namespace NewHorizons.Builder.Volumes VolumeBuilder.MakeAndEnable(go, sector, mapRestrictionVolume); } } + if (config.Volumes.interactionVolumes != null) + { + foreach (var interactionVolume in config.Volumes.interactionVolumes) + { + InteractionVolumeBuilder.Make(go, sector, interactionVolume, mod); + } + } if (config.Volumes.interferenceVolumes != null) { foreach (var interferenceVolume in config.Volumes.interferenceVolumes) diff --git a/NewHorizons/Components/Volumes/NHInteractionVolume.cs b/NewHorizons/Components/Volumes/NHInteractionVolume.cs new file mode 100644 index 00000000..4e258208 --- /dev/null +++ b/NewHorizons/Components/Volumes/NHInteractionVolume.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace NewHorizons.Components.Volumes +{ + public class NHInteractionVolume : MonoBehaviour + { + public bool Reusable { get; set; } + public string Condition { get; set; } + public bool Persistent { get; set; } + public Animator TargetAnimator { get; set; } + public string AnimationTrigger { get; set; } + + InteractReceiver _interactReceiver; + OWAudioSource _audioSource; + + protected void Awake() + { + _interactReceiver = GetComponent(); + _audioSource = GetComponent(); + + _interactReceiver.OnPressInteract += OnInteract; + } + + protected void OnDestroy() + { + _interactReceiver.OnPressInteract -= OnInteract; + } + + protected void OnInteract() + { + if (!string.IsNullOrEmpty(Condition)) + { + if (Persistent) + { + PlayerData.SetPersistentCondition(Condition, true); + } + else + { + DialogueConditionManager.SharedInstance.SetConditionState(Condition, true); + } + } + + if (_audioSource != null) + { + _audioSource.Play(); + } + + if (TargetAnimator) + { + TargetAnimator.SetTrigger(AnimationTrigger); + } + + if (Reusable) + { + _interactReceiver.ResetInteraction(); + _interactReceiver.EnableInteraction(); + } + else + { + _interactReceiver.DisableInteraction(); + } + } + } +} diff --git a/NewHorizons/External/Modules/Volumes/VolumeInfos/InteractionVolumeInfo.cs b/NewHorizons/External/Modules/Volumes/VolumeInfos/InteractionVolumeInfo.cs new file mode 100644 index 00000000..0a9e7744 --- /dev/null +++ b/NewHorizons/External/Modules/Volumes/VolumeInfos/InteractionVolumeInfo.cs @@ -0,0 +1,65 @@ +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 InteractionVolumeInfo : VolumeInfo + { + /// + /// The prompt to display when the volume is interacted with. + /// + public string prompt; + + /// + /// The range at which the volume can be interacted with. + /// + [DefaultValue(2f)] public float range = 2f; + + /// + /// The max view angle (in degrees) the player can see the volume with to interact with it. This will effectively be a cone extending from the volume's center forwards (along the Z axis) based on the volume's rotation. + /// If not specified, no view angle restriction will be applied. + /// + public float? maxViewAngle; + + /// + /// Whether the volume can be interacted with while in the ship. + /// + public bool usableInShip; + + /// + /// Whether the volume can be interacted with multiple times. + /// + public bool reusable; + + /// + /// The name of the dialogue condition or persistent condition to set when the volume is interacted with. + /// + public string condition; + + /// + /// If true, the condition will persist across all future loops until unset. + /// + public bool persistent; + + /// + /// A sound to play when the volume is interacted with. Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list. + /// + public string audio; + + /// + /// A path to an animator component where an animation will be triggered when the volume is interacted with. + /// + public string pathToAnimator; + + /// + /// The name of an animation trigger to set on the animator when the volume is interacted with. + /// + public string animationTrigger; + } +} diff --git a/NewHorizons/External/Modules/Volumes/VolumesModule.cs b/NewHorizons/External/Modules/Volumes/VolumesModule.cs index 7f44b911..959d8004 100644 --- a/NewHorizons/External/Modules/Volumes/VolumesModule.cs +++ b/NewHorizons/External/Modules/Volumes/VolumesModule.cs @@ -43,6 +43,12 @@ namespace NewHorizons.External.Modules.Volumes /// public HazardVolumeInfo[] hazardVolumes; + /// + /// Add interaction volumes to this planet. + /// They can be interacted with by the player to trigger various effects. + /// + public InteractionVolumeInfo[] interactionVolumes; + /// /// Add interference volumes to this planet. /// Hides HUD markers of ship scout/probe and prevents scout photos if you are not inside the volume together with ship or scout probe. From 2e06de70c272928bb78ed2cc0dc523a6f87012e9 Mon Sep 17 00:00:00 2001 From: Ben C Date: Fri, 4 Jul 2025 19:22:54 +0000 Subject: [PATCH 06/13] Updated Schemas --- NewHorizons/Schemas/body_schema.json | 96 ++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/NewHorizons/Schemas/body_schema.json b/NewHorizons/Schemas/body_schema.json index d8f7036c..265cef6b 100644 --- a/NewHorizons/Schemas/body_schema.json +++ b/NewHorizons/Schemas/body_schema.json @@ -5434,6 +5434,13 @@ "$ref": "#/definitions/HazardVolumeInfo" } }, + "interactionVolumes": { + "type": "array", + "description": "Add interaction volumes to this planet.\nThey can be interacted with by the player to trigger various effects.", + "items": { + "$ref": "#/definitions/InteractionVolumeInfo" + } + }, "interferenceVolumes": { "type": "array", "description": "Add interference volumes to this planet.\nHides HUD markers of ship scout/probe and prevents scout photos if you are not inside the volume together with ship or scout probe.", @@ -6626,6 +6633,95 @@ } } }, + "InteractionVolumeInfo": { + "type": "object", + "additionalProperties": false, + "properties": { + "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" + }, + "prompt": { + "type": "string", + "description": "The prompt to display when the volume is interacted with." + }, + "range": { + "type": "number", + "description": "The range at which the volume can be interacted with.", + "format": "float", + "default": 2.0 + }, + "maxViewAngle": { + "type": [ + "null", + "number" + ], + "description": "The max view angle (in degrees) the player can see the volume with to interact with it. This will effectively be a cone extending from the volume's center forwards (along the Z axis) based on the volume's rotation.\nIf not specified, no view angle restriction will be applied.", + "format": "float" + }, + "usableInShip": { + "type": "boolean", + "description": "Whether the volume can be interacted with while in the ship." + }, + "reusable": { + "type": "boolean", + "description": "Whether the volume can be interacted with multiple times." + }, + "condition": { + "type": "string", + "description": "The name of the dialogue condition or persistent condition to set when the volume is interacted with." + }, + "persistent": { + "type": "boolean", + "description": "If true, the condition will persist across all future loops until unset." + }, + "audio": { + "type": "string", + "description": "A sound to play when the volume is interacted with. Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list." + }, + "pathToAnimator": { + "type": "string", + "description": "A path to an animator component where an animation will be triggered when the volume is interacted with. " + }, + "animationTrigger": { + "type": "string", + "description": "The name of an animation trigger to set on the animator when the volume is interacted with." + } + } + }, "VolumeInfo": { "type": "object", "additionalProperties": false, From e4f89be4e9978ed1090b1503e6dd197edcd6bfcd Mon Sep 17 00:00:00 2001 From: Joshua Thome Date: Fri, 4 Jul 2025 14:27:19 -0500 Subject: [PATCH 07/13] Remove accidental import --- NewHorizons/Builder/Props/EchoesOfTheEye/DreamCandleBuilder.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/NewHorizons/Builder/Props/EchoesOfTheEye/DreamCandleBuilder.cs b/NewHorizons/Builder/Props/EchoesOfTheEye/DreamCandleBuilder.cs index be0b2184..5b648899 100644 --- a/NewHorizons/Builder/Props/EchoesOfTheEye/DreamCandleBuilder.cs +++ b/NewHorizons/Builder/Props/EchoesOfTheEye/DreamCandleBuilder.cs @@ -10,7 +10,6 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -using TerrainBaker; using UnityEngine; namespace NewHorizons.Builder.Props.EchoesOfTheEye From 3403c5da34c7e65a04e675cdc99974e9dcf972ce Mon Sep 17 00:00:00 2001 From: xen-42 Date: Sun, 6 Jul 2025 23:59:09 -0400 Subject: [PATCH 08/13] Make dialogue usable in ship --- NewHorizons/Builder/Props/DialogueBuilder.cs | 4 ++++ NewHorizons/manifest.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/NewHorizons/Builder/Props/DialogueBuilder.cs b/NewHorizons/Builder/Props/DialogueBuilder.cs index 8bd12c86..0878cd87 100644 --- a/NewHorizons/Builder/Props/DialogueBuilder.cs +++ b/NewHorizons/Builder/Props/DialogueBuilder.cs @@ -290,6 +290,10 @@ namespace NewHorizons.Builder.Props interact._interactRange = info.range; + // If a dialogue is on the ship, make sure its usable + // Assumes these are inside the ship and not outside, not sure if thats an issue + interact._usableInShip = planetGO.name == "Ship_Body"; + if (info.radius <= 0) { sphere.enabled = false; diff --git a/NewHorizons/manifest.json b/NewHorizons/manifest.json index e1bfa1ad..75cd8f6b 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.28.4", + "version": "1.28.5", "owmlVersion": "2.12.1", "dependencies": [ "JohnCorby.VanillaFix", "xen.CommonCameraUtility", "dgarro.CustomShipLogModes" ], "conflicts": [ "PacificEngine.OW_CommonResources" ], From ea85e3bab0f48686085eba384dc0cd29ed841ef8 Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Sun, 8 Jun 2025 00:35:06 -0400 Subject: [PATCH 09/13] Fix items not having sound when you make socket first --- NewHorizons/Builder/Props/ItemBuilder.cs | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/NewHorizons/Builder/Props/ItemBuilder.cs b/NewHorizons/Builder/Props/ItemBuilder.cs index 05620329..15b3bc27 100644 --- a/NewHorizons/Builder/Props/ItemBuilder.cs +++ b/NewHorizons/Builder/Props/ItemBuilder.cs @@ -16,13 +16,6 @@ namespace NewHorizons.Builder.Props internal static void Init() { - if (_itemTypes != null) - { - foreach (var value in _itemTypes.Values) - { - EnumUtils.Remove(value); - } - } _itemTypes = new Dictionary(); } @@ -141,11 +134,7 @@ namespace NewHorizons.Builder.Props { go.layer = Layer.Interactible; - var itemType = EnumUtils.TryParse(info.itemType, true, out ItemType result) ? result : ItemType.Invalid; - if (itemType == ItemType.Invalid && !string.IsNullOrEmpty(info.itemType)) - { - itemType = EnumUtilities.Create(info.itemType); - } + var itemType = GetOrCreateItemType(info.itemType); var socket = go.GetAddComponent(); socket._sector = sector; @@ -205,7 +194,7 @@ namespace NewHorizons.Builder.Props } else if (!string.IsNullOrEmpty(name)) { - itemType = EnumUtils.Create(name); + itemType = EnumUtilities.Create(name); _itemTypes.Add(name, itemType); } return itemType; From 20126dfe91d4e4150fd173a5f6620900a33fddf6 Mon Sep 17 00:00:00 2001 From: JohnCorby Date: Sat, 19 Jul 2025 00:59:47 -0700 Subject: [PATCH 10/13] unload streaming in custom systems --- NewHorizons/Handlers/PlanetCreationHandler.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/NewHorizons/Handlers/PlanetCreationHandler.cs b/NewHorizons/Handlers/PlanetCreationHandler.cs index 1528151d..4730e58e 100644 --- a/NewHorizons/Handlers/PlanetCreationHandler.cs +++ b/NewHorizons/Handlers/PlanetCreationHandler.cs @@ -42,6 +42,15 @@ namespace NewHorizons.Handlers public static void Init(List bodies) { + // TH gets preloaded in title screen. custom systems dont need this + if (Main.Instance.CurrentStarSystem is not ("SolarSystem" or "EyeOfTheUniverse")) + { + foreach (var bundle in StreamingManager.s_activeBundles) + { + StreamingManager.UnloadStreamingAssets(bundle.assetBundleName); + } + } + // Start by destroying all planets if need be if (Main.SystemDict[Main.Instance.CurrentStarSystem].Config.destroyStockPlanets) { From d38d9fa1c86323d48376f79ea55efd71e1a50fab Mon Sep 17 00:00:00 2001 From: JohnCorby Date: Sat, 19 Jul 2025 01:07:04 -0700 Subject: [PATCH 11/13] unload immediate --- NewHorizons/Handlers/PlanetCreationHandler.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/NewHorizons/Handlers/PlanetCreationHandler.cs b/NewHorizons/Handlers/PlanetCreationHandler.cs index 4730e58e..e1c4864e 100644 --- a/NewHorizons/Handlers/PlanetCreationHandler.cs +++ b/NewHorizons/Handlers/PlanetCreationHandler.cs @@ -47,7 +47,8 @@ namespace NewHorizons.Handlers { foreach (var bundle in StreamingManager.s_activeBundles) { - StreamingManager.UnloadStreamingAssets(bundle.assetBundleName); + // save memory NOW instead of next frame when other stuff has loaded and taken memory + bundle.UnloadImmediate(); } } From b0f445e415f9e0b19131ee99c0d67f16630235d4 Mon Sep 17 00:00:00 2001 From: JohnCorby Date: Sat, 19 Jul 2025 01:11:15 -0700 Subject: [PATCH 12/13] Revert "unload immediate" This reverts commit d38d9fa1c86323d48376f79ea55efd71e1a50fab. --- NewHorizons/Handlers/PlanetCreationHandler.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/NewHorizons/Handlers/PlanetCreationHandler.cs b/NewHorizons/Handlers/PlanetCreationHandler.cs index e1c4864e..4730e58e 100644 --- a/NewHorizons/Handlers/PlanetCreationHandler.cs +++ b/NewHorizons/Handlers/PlanetCreationHandler.cs @@ -47,8 +47,7 @@ namespace NewHorizons.Handlers { foreach (var bundle in StreamingManager.s_activeBundles) { - // save memory NOW instead of next frame when other stuff has loaded and taken memory - bundle.UnloadImmediate(); + StreamingManager.UnloadStreamingAssets(bundle.assetBundleName); } } From 361fc2aa57688c1ca3629cc32448018941b5f55f Mon Sep 17 00:00:00 2001 From: JohnCorby Date: Sat, 19 Jul 2025 01:14:38 -0700 Subject: [PATCH 13/13] Reapply "unload immediate" This reverts commit b0f445e415f9e0b19131ee99c0d67f16630235d4. --- NewHorizons/Handlers/PlanetCreationHandler.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/NewHorizons/Handlers/PlanetCreationHandler.cs b/NewHorizons/Handlers/PlanetCreationHandler.cs index 4730e58e..e1c4864e 100644 --- a/NewHorizons/Handlers/PlanetCreationHandler.cs +++ b/NewHorizons/Handlers/PlanetCreationHandler.cs @@ -47,7 +47,8 @@ namespace NewHorizons.Handlers { foreach (var bundle in StreamingManager.s_activeBundles) { - StreamingManager.UnloadStreamingAssets(bundle.assetBundleName); + // save memory NOW instead of next frame when other stuff has loaded and taken memory + bundle.UnloadImmediate(); } }