From b84d94a404b8dd046731e7f1e49c125929cd4e75 Mon Sep 17 00:00:00 2001 From: Joshua Thome Date: Tue, 1 Jul 2025 12:28:12 -0500 Subject: [PATCH 1/7] 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 2/7] 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 3/7] 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 4/7] 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 5/7] 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 6/7] 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 7/7] 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