## Minor features

- Added `conditionTriggerVolumes` to set a dialogue condition when the
player (or scout or ship) enters an area.
- Added `interactionVolumes` for interactable objects that set a
dialogue condition, play a sound, and/or trigger an animation.
- Added `condition` fields to `dreamCandles` and `projectionTotems` to
set dialogue conditions when they are lit or extinguished.

## Improvements

- no longer loads unnecessary bundles in custom systems. saves VRAM.
does not affect RAM.

## Bug fixes

- Fix custom items not having sound when you make socket with the same
custom type first
- Dialogue should now work by default in the ship.
This commit is contained in:
Noah Pilarski 2025-07-20 20:26:12 -04:00 committed by GitHub
commit ecdc0c8af4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 735 additions and 14 deletions

View File

@ -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;

View File

@ -1,3 +1,4 @@
using NewHorizons.Components.EOTE;
using NewHorizons.External.Modules.Props;
using NewHorizons.External.Modules.Props.EchoesOfTheEye;
using NewHorizons.Handlers;
@ -70,6 +71,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<DreamLightConditionController>();
conditionController.SetFromInfo(info.condition);
}
return candleObj;
}
}

View File

@ -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<DreamLightConditionController>();
conditionController.SetFromInfo(info.condition);
}
/*
Delay.FireOnNextUpdate(() =>
{

View File

@ -16,13 +16,6 @@ namespace NewHorizons.Builder.Props
internal static void Init()
{
if (_itemTypes != null)
{
foreach (var value in _itemTypes.Values)
{
EnumUtils.Remove<ItemType>(value);
}
}
_itemTypes = new Dictionary<string, ItemType>();
}
@ -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<ItemType>(info.itemType);
}
var itemType = GetOrCreateItemType(info.itemType);
var socket = go.GetAddComponent<NHItemSocket>();
socket._sector = sector;
@ -205,7 +194,7 @@ namespace NewHorizons.Builder.Props
}
else if (!string.IsNullOrEmpty(name))
{
itemType = EnumUtils.Create<ItemType>(name);
itemType = EnumUtilities.Create<ItemType>(name);
_itemTypes.Add(name, itemType);
}
return itemType;

View File

@ -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<ConditionTriggerVolume>(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;
}
}
}

View File

@ -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<InteractReceiver>(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<NHInteractionVolume>();
volume.Reusable = info.reusable;
volume.Condition = info.condition;
volume.Persistent = info.persistent;
if (!string.IsNullOrEmpty(info.audio))
{
var audioSource = receiver.gameObject.AddComponent<AudioSource>();
// 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>();
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<Animator>();
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;
}
}
}

View File

@ -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)
@ -63,6 +70,13 @@ namespace NewHorizons.Builder.Volumes
VolumeBuilder.MakeAndEnable<MapRestrictionVolume>(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)

View File

@ -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<DreamObjectProjector>();
_projector.OnProjectorLit.AddListener(OnProjectorLit);
_projector.OnProjectorExtinguished.AddListener(OnProjectorExtinguished);
_dreamCandle = GetComponent<DreamCandle>();
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);
}
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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<InteractReceiver>();
_audioSource = GetComponent<OWAudioSource>();
_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();
}
}
}
}

View File

@ -20,5 +20,10 @@ namespace NewHorizons.External.Modules.Props.EchoesOfTheEye
/// Whether the candle should start lit or extinguished.
/// </summary>
public bool startLit;
/// <summary>
/// A condition to set when the candle is lit.
/// </summary>
public DreamLightConditionInfo condition;
}
}

View File

@ -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
{
/// <summary>
/// The name of the dialogue condition or persistent condition to set when the light is lit.
/// </summary>
public string condition;
/// <summary>
/// If true, the condition will persist across all future loops until unset.
/// </summary>
public bool persistent;
/// <summary>
/// Whether to unset the condition when the light is extinguished again.
/// </summary>
public bool reversible;
/// <summary>
/// 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.
/// </summary>
public bool onExtinguish;
}
}

View File

@ -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.
/// </summary>
public bool toggleProjectedObjectsActive;
/// <summary>
/// A condition to set when the totem is lit.
/// </summary>
public DreamLightConditionInfo condition;
}
}

View File

@ -0,0 +1,39 @@
using Newtonsoft.Json;
using System.ComponentModel;
namespace NewHorizons.External.Modules.Volumes.VolumeInfos
{
[JsonObject]
public class ConditionTriggerVolumeInfo : VolumeInfo
{
/// <summary>
/// The name of the dialogue condition or persistent condition to set when entering the volume.
/// </summary>
public string condition;
/// <summary>
/// If true, the condition will persist across all future loops until unset.
/// </summary>
public bool persistent;
/// <summary>
/// Whether to unset the condition when existing the volume.
/// </summary>
public bool reversible;
/// <summary>
/// Whether to set the condition when the player enters this volume. Defaults to true.
/// </summary>
[DefaultValue(true)] public bool player = true;
/// <summary>
/// Whether to set the condition when the scout probe enters this volume.
/// </summary>
public bool probe;
/// <summary>
/// Whether to set the condition when the ship enters this volume.
/// </summary>
public bool ship;
}
}

View File

@ -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
{
/// <summary>
/// The prompt to display when the volume is interacted with.
/// </summary>
public string prompt;
/// <summary>
/// The range at which the volume can be interacted with.
/// </summary>
[DefaultValue(2f)] public float range = 2f;
/// <summary>
/// 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.
/// </summary>
public float? maxViewAngle;
/// <summary>
/// Whether the volume can be interacted with while in the ship.
/// </summary>
public bool usableInShip;
/// <summary>
/// Whether the volume can be interacted with multiple times.
/// </summary>
public bool reusable;
/// <summary>
/// The name of the dialogue condition or persistent condition to set when the volume is interacted with.
/// </summary>
public string condition;
/// <summary>
/// If true, the condition will persist across all future loops until unset.
/// </summary>
public bool persistent;
/// <summary>
/// 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.
/// </summary>
public string audio;
/// <summary>
/// A path to an animator component where an animation will be triggered when the volume is interacted with.
/// </summary>
public string pathToAnimator;
/// <summary>
/// The name of an animation trigger to set on the animator when the volume is interacted with.
/// </summary>
public string animationTrigger;
}
}

View File

@ -11,6 +11,11 @@ namespace NewHorizons.External.Modules.Volumes
/// </summary>
public AudioVolumeInfo[] audioVolumes;
/// <summary>
/// Add condition trigger volumes to this planet. Sets a condition when the player, scout, or ship enters this volume.
/// </summary>
public ConditionTriggerVolumeInfo[] conditionTriggerVolumes;
/// <summary>
/// Add day night audio volumes to this planet. These volumes play a different clip depending on the time of day.
/// </summary>
@ -38,6 +43,12 @@ namespace NewHorizons.External.Modules.Volumes
/// </summary>
public HazardVolumeInfo[] hazardVolumes;
/// <summary>
/// Add interaction volumes to this planet.
/// They can be interacted with by the player to trigger various effects.
/// </summary>
public InteractionVolumeInfo[] interactionVolumes;
/// <summary>
/// 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.

View File

@ -42,6 +42,16 @@ namespace NewHorizons.Handlers
public static void Init(List<NewHorizonsBody> 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)
{
// save memory NOW instead of next frame when other stuff has loaded and taken memory
bundle.UnloadImmediate();
}
}
// Start by destroying all planets if need be
if (Main.SystemDict[Main.Instance.CurrentStarSystem].Config.destroyStockPlanets)
{

View File

@ -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"
}
}
},
@ -5365,6 +5395,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.",
@ -5397,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.",
@ -5724,6 +5768,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,
@ -6528,6 +6640,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,

View File

@ -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" ],