Merge branch 'dev' into enum

This commit is contained in:
Noah Pilarski 2022-09-01 07:19:55 -04:00
commit 422d15af1c
28 changed files with 1223 additions and 337 deletions

View File

@ -20,6 +20,16 @@ namespace NewHorizons.Builder.Atmosphere
SCG._dynamicCullingBounds = false; SCG._dynamicCullingBounds = false;
SCG._waitForStreaming = false; SCG._waitForStreaming = false;
var minHeight = surfaceSize;
var maxHeight = config.Atmosphere.size;
if (config.HeightMap?.minHeight != null)
{
if (config.Water?.size >= config.HeightMap.minHeight) minHeight = config.Water.size; // use sea level if its higher
else minHeight = config.HeightMap.minHeight;
}
else if (config.Water?.size != null) minHeight = config.Water.size;
else if (config.Lava?.size != null) minHeight = config.Lava.size;
if (config.Atmosphere.hasRain) if (config.Atmosphere.hasRain)
{ {
var rainGO = GameObject.Instantiate(SearchUtilities.Find("GiantsDeep_Body/Sector_GD/Sector_GDInterior/Effects_GDInterior/Effects_GD_Rain"), effectsGO.transform); var rainGO = GameObject.Instantiate(SearchUtilities.Find("GiantsDeep_Body/Sector_GD/Sector_GDInterior/Effects_GDInterior/Effects_GD_Rain"), effectsGO.transform);
@ -29,9 +39,9 @@ namespace NewHorizons.Builder.Atmosphere
var pvc = rainGO.GetComponent<PlanetaryVectionController>(); var pvc = rainGO.GetComponent<PlanetaryVectionController>();
pvc._densityByHeight = new AnimationCurve(new Keyframe[] pvc._densityByHeight = new AnimationCurve(new Keyframe[]
{ {
new Keyframe(surfaceSize - 0.5f, 0), new Keyframe(minHeight - 0.5f, 0),
new Keyframe(surfaceSize, 10f), new Keyframe(minHeight, 10f),
new Keyframe(config.Atmosphere.size, 0f) new Keyframe(maxHeight, 0f)
}); });
rainGO.GetComponent<PlanetaryVectionController>()._activeInSector = sector; rainGO.GetComponent<PlanetaryVectionController>()._activeInSector = sector;
@ -53,9 +63,9 @@ namespace NewHorizons.Builder.Atmosphere
var pvc = snowEmitter.GetComponent<PlanetaryVectionController>(); var pvc = snowEmitter.GetComponent<PlanetaryVectionController>();
pvc._densityByHeight = new AnimationCurve(new Keyframe[] pvc._densityByHeight = new AnimationCurve(new Keyframe[]
{ {
new Keyframe(surfaceSize - 0.5f, 0), new Keyframe(minHeight - 0.5f, 0),
new Keyframe(surfaceSize, 10f), new Keyframe(minHeight, 10f),
new Keyframe(config.Atmosphere.size, 0f) new Keyframe(maxHeight, 0f)
}); });
snowEmitter.GetComponent<PlanetaryVectionController>()._activeInSector = sector; snowEmitter.GetComponent<PlanetaryVectionController>()._activeInSector = sector;

View File

@ -192,8 +192,12 @@ namespace NewHorizons.Builder.Body
cloak.GetComponent<Renderer>().enabled = true; cloak.GetComponent<Renderer>().enabled = true;
// Cull stuff // Cull stuff
var cullController = go.AddComponent<BrambleSectorController>(); // Do next update so other nodes can be built first
cullController.SetSector(sector); Delay.FireOnNextUpdate(() =>
{
var cullController = go.AddComponent<BrambleSectorController>();
cullController.SetSector(sector);
});
// finalize // finalize
atmo.SetActive(true); atmo.SetActive(true);

View File

@ -109,20 +109,6 @@ namespace NewHorizons.Builder.Props
} }
} }
} }
if (config.Props.reveal != null)
{
foreach (var revealInfo in config.Props.reveal)
{
try
{
RevealBuilder.Make(go, sector, revealInfo, mod);
}
catch (Exception ex)
{
Logger.LogError($"Couldn't make reveal location [{revealInfo.reveals}] for [{go.name}]:\n{ex}");
}
}
}
if (config.Props.entryLocation != null) if (config.Props.entryLocation != null)
{ {
foreach (var entryLocationInfo in config.Props.entryLocation) foreach (var entryLocationInfo in config.Props.entryLocation)
@ -207,13 +193,6 @@ namespace NewHorizons.Builder.Props
} }
} }
} }
if (config.Props.audioVolumes != null)
{
foreach (var audioVolume in config.Props.audioVolumes)
{
AudioVolumeBuilder.Make(go, sector, audioVolume, mod);
}
}
if (config.Props.signals != null) if (config.Props.signals != null)
{ {
foreach (var signal in config.Props.signals) foreach (var signal in config.Props.signals)

View File

@ -7,18 +7,18 @@ namespace NewHorizons.Builder.ShipLog
{ {
public static class RevealBuilder public static class RevealBuilder
{ {
public static void Make(GameObject go, Sector sector, PropModule.RevealInfo info, IModBehaviour mod) public static void Make(GameObject go, Sector sector, VolumesModule.RevealVolumeInfo info, IModBehaviour mod)
{ {
var newRevealGO = MakeGameObject(go, sector, info, mod); var newRevealGO = MakeGameObject(go, sector, info, mod);
switch (info.revealOn) switch (info.revealOn)
{ {
case PropModule.RevealInfo.RevealVolumeType.Enter: case VolumesModule.RevealVolumeInfo.RevealVolumeType.Enter:
MakeTrigger(newRevealGO, sector, info, mod); MakeTrigger(newRevealGO, sector, info, mod);
break; break;
case PropModule.RevealInfo.RevealVolumeType.Observe: case VolumesModule.RevealVolumeInfo.RevealVolumeType.Observe:
MakeObservable(newRevealGO, sector, info, mod); MakeObservable(newRevealGO, sector, info, mod);
break; break;
case PropModule.RevealInfo.RevealVolumeType.Snapshot: case VolumesModule.RevealVolumeInfo.RevealVolumeType.Snapshot:
MakeSnapshot(newRevealGO, sector, info, mod); MakeSnapshot(newRevealGO, sector, info, mod);
break; break;
default: default:
@ -28,7 +28,7 @@ namespace NewHorizons.Builder.ShipLog
newRevealGO.SetActive(true); newRevealGO.SetActive(true);
} }
private static SphereShape MakeShape(GameObject go, PropModule.RevealInfo info, Shape.CollisionMode collisionMode) private static SphereShape MakeShape(GameObject go, VolumesModule.RevealVolumeInfo info, Shape.CollisionMode collisionMode)
{ {
SphereShape newShape = go.AddComponent<SphereShape>(); SphereShape newShape = go.AddComponent<SphereShape>();
newShape.radius = info.radius; newShape.radius = info.radius;
@ -36,7 +36,7 @@ namespace NewHorizons.Builder.ShipLog
return newShape; return newShape;
} }
private static GameObject MakeGameObject(GameObject planetGO, Sector sector, PropModule.RevealInfo info, IModBehaviour mod) private static GameObject MakeGameObject(GameObject planetGO, Sector sector, VolumesModule.RevealVolumeInfo info, IModBehaviour mod)
{ {
GameObject revealTriggerVolume = new GameObject("Reveal Volume (" + info.revealOn + ")"); GameObject revealTriggerVolume = new GameObject("Reveal Volume (" + info.revealOn + ")");
revealTriggerVolume.SetActive(false); revealTriggerVolume.SetActive(false);
@ -45,7 +45,7 @@ namespace NewHorizons.Builder.ShipLog
return revealTriggerVolume; return revealTriggerVolume;
} }
private static void MakeTrigger(GameObject go, Sector sector, PropModule.RevealInfo info, IModBehaviour mod) private static void MakeTrigger(GameObject go, Sector sector, VolumesModule.RevealVolumeInfo info, IModBehaviour mod)
{ {
var shape = MakeShape(go, info, Shape.CollisionMode.Volume); var shape = MakeShape(go, info, Shape.CollisionMode.Volume);
@ -65,7 +65,7 @@ namespace NewHorizons.Builder.ShipLog
} }
} }
private static void MakeObservable(GameObject go, Sector sector, PropModule.RevealInfo info, IModBehaviour mod) private static void MakeObservable(GameObject go, Sector sector, VolumesModule.RevealVolumeInfo info, IModBehaviour mod)
{ {
go.layer = LayerMask.NameToLayer("Interactible"); go.layer = LayerMask.NameToLayer("Interactible");
@ -96,7 +96,7 @@ namespace NewHorizons.Builder.ShipLog
} }
} }
private static void MakeSnapshot(GameObject go, Sector sector, PropModule.RevealInfo info, IModBehaviour mod) private static void MakeSnapshot(GameObject go, Sector sector, VolumesModule.RevealVolumeInfo info, IModBehaviour mod)
{ {
var shape = MakeShape(go, info, Shape.CollisionMode.Manual); var shape = MakeShape(go, info, Shape.CollisionMode.Manual);

View File

@ -10,11 +10,11 @@ using System.Threading.Tasks;
using UnityEngine; using UnityEngine;
using Logger = NewHorizons.Utility.Logger; using Logger = NewHorizons.Utility.Logger;
namespace NewHorizons.Builder.Props namespace NewHorizons.Builder.Volumes
{ {
public static class AudioVolumeBuilder public static class AudioVolumeBuilder
{ {
public static AudioVolume Make(GameObject planetGO, Sector sector, PropModule.AudioVolumeInfo info, IModBehaviour mod) public static AudioVolume Make(GameObject planetGO, Sector sector, VolumesModule.AudioVolumeInfo info, IModBehaviour mod)
{ {
var go = new GameObject("AudioVolume"); var go = new GameObject("AudioVolume");
go.SetActive(false); go.SetActive(false);
@ -27,7 +27,7 @@ namespace NewHorizons.Builder.Props
var owAudioSource = go.AddComponent<OWAudioSource>(); var owAudioSource = go.AddComponent<OWAudioSource>();
owAudioSource._audioSource = audioSource; owAudioSource._audioSource = audioSource;
owAudioSource.loop = true; owAudioSource.loop = info.loop;
owAudioSource.SetTrack(EnumUtils.Parse<OWAudioMixer.TrackName>(info.track.ToString())); owAudioSource.SetTrack(EnumUtils.Parse<OWAudioMixer.TrackName>(info.track.ToString()));
AudioUtilities.SetAudioClip(owAudioSource, info.audio, mod); AudioUtilities.SetAudioClip(owAudioSource, info.audio, mod);

View File

@ -0,0 +1,40 @@
using NewHorizons.External.Modules;
using OWML.Common;
using OWML.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace NewHorizons.Builder.Volumes
{
public static class HazardVolumeBuilder
{
public static HazardVolume Make(GameObject planetGO, Sector sector, OWRigidbody owrb, VolumesModule.HazardVolumeInfo info, IModBehaviour mod)
{
var go = new GameObject("HazardVolume");
go.SetActive(false);
go.transform.parent = sector?.transform ?? planetGO.transform;
go.transform.position = planetGO.transform.TransformPoint(info.position != null ? (Vector3)info.position : Vector3.zero);
go.layer = LayerMask.NameToLayer("BasicEffectVolume");
var shape = go.AddComponent<SphereShape>();
shape.radius = info.radius;
var owTriggerVolume = go.AddComponent<OWTriggerVolume>();
owTriggerVolume._shape = shape;
var hazardVolume = go.AddComponent<SimpleHazardVolume>();
hazardVolume._attachedBody = owrb;
hazardVolume._type = EnumUtils.Parse<HazardVolume.HazardType>(info.type.ToString(), HazardVolume.HazardType.GENERAL);
hazardVolume._damagePerSecond = info.damagePerSecond;
hazardVolume._firstContactDamageType = EnumUtils.Parse<InstantDamageType>(info.firstContactDamageType.ToString(), InstantDamageType.Impact);
hazardVolume._firstContactDamage = info.firstContactDamage;
go.SetActive(true);
return hazardVolume;
}
}
}

View File

@ -0,0 +1,43 @@
using NewHorizons.Components;
using NewHorizons.External.Modules;
using NewHorizons.Utility;
using OWML.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using Logger = NewHorizons.Utility.Logger;
using NHNotificationVolume = NewHorizons.Components.NotificationVolume;
namespace NewHorizons.Builder.Volumes
{
public static class NotificationVolumeBuilder
{
public static NHNotificationVolume Make(GameObject planetGO, Sector sector, VolumesModule.NotificationVolumeInfo info, IModBehaviour mod)
{
var go = new GameObject("NotificationVolume");
go.SetActive(false);
go.transform.parent = sector?.transform ?? planetGO.transform;
go.transform.position = planetGO.transform.TransformPoint(info.position != null ? (Vector3)info.position : Vector3.zero);
go.layer = LayerMask.NameToLayer("BasicEffectVolume");
var shape = go.AddComponent<SphereShape>();
shape.radius = info.radius;
var owTriggerVolume = go.AddComponent<OWTriggerVolume>();
owTriggerVolume._shape = shape;
var notificationVolume = go.AddComponent<NHNotificationVolume>();
notificationVolume.SetTarget(info.target);
if (info.entryNotification != null) notificationVolume.SetEntryNotification(info.entryNotification.displayMessage, info.entryNotification.duration);
if (info.exitNotification != null) notificationVolume.SetExitNotification(info.exitNotification.displayMessage, info.exitNotification.duration);
go.SetActive(true);
return notificationVolume;
}
}
}

View File

@ -0,0 +1,54 @@
using NewHorizons.Builder.Body;
using NewHorizons.Builder.ShipLog;
using NewHorizons.Builder.Volumes;
using NewHorizons.External.Configs;
using OWML.Common;
using System;
using System.Collections.Generic;
using UnityEngine;
using Logger = NewHorizons.Utility.Logger;
namespace NewHorizons.Builder.Volumes
{
public static class VolumesBuildManager
{
public static void Make(GameObject go, Sector sector, OWRigidbody planetBody, PlanetConfig config, IModBehaviour mod)
{
if (config.Volumes.revealVolumes != null)
{
foreach (var revealInfo in config.Volumes.revealVolumes)
{
try
{
RevealBuilder.Make(go, sector, revealInfo, mod);
}
catch (Exception ex)
{
Logger.LogError($"Couldn't make reveal location [{revealInfo.reveals}] for [{go.name}]:\n{ex}");
}
}
}
if (config.Volumes.audioVolumes != null)
{
foreach (var audioVolume in config.Volumes.audioVolumes)
{
AudioVolumeBuilder.Make(go, sector, audioVolume, mod);
}
}
if (config.Volumes.notificationVolumes != null)
{
foreach (var notificationVolume in config.Volumes.notificationVolumes)
{
NotificationVolumeBuilder.Make(go, sector, notificationVolume, mod);
}
}
if (config.Volumes.hazardVolumes != null)
{
foreach (var hazardVolume in config.Volumes.hazardVolumes)
{
HazardVolumeBuilder.Make(go, sector, planetBody, hazardVolume, mod);
}
}
}
}
}

View File

@ -0,0 +1,129 @@
using NewHorizons.Handlers;
using OWML.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace NewHorizons.Components
{
[RequireComponent(typeof(OWTriggerVolume))]
public class NotificationVolume : MonoBehaviour
{
private NotificationTarget _target = NotificationTarget.All;
private bool _pin = false;
private OWTriggerVolume _triggerVolume;
private NotificationData _entryNotification;
private NotificationData _exitNotification;
public void Awake()
{
_triggerVolume = this.GetRequiredComponent<OWTriggerVolume>();
_triggerVolume.OnEntry += OnTriggerVolumeEntry;
_triggerVolume.OnExit += OnTriggerVolumeExit;
}
public void OnDestroy()
{
if (_triggerVolume == null) return;
_triggerVolume.OnEntry -= OnTriggerVolumeEntry;
_triggerVolume.OnExit -= OnTriggerVolumeExit;
}
public void SetPinned(bool pin) => _pin = pin;
public void SetTarget(External.Modules.VolumesModule.NotificationVolumeInfo.NotificationTarget target) => SetTarget(EnumUtils.Parse<NotificationTarget>(target.ToString(), NotificationTarget.All));
public void SetTarget(NotificationTarget target) => _target = target;
public void SetEntryNotification(string displayMessage, float duration = 5)
{
_entryNotification = new NotificationData(_target, TranslationHandler.GetTranslation(displayMessage, TranslationHandler.TextType.UI), duration);
}
public void SetExitNotification(string displayMessage, float duration = 5)
{
_exitNotification = new NotificationData(_target, TranslationHandler.GetTranslation(displayMessage, TranslationHandler.TextType.UI), duration);
}
public void OnTriggerVolumeEntry(GameObject hitObj)
{
if (_target == NotificationTarget.All)
{
if (hitObj.CompareTag("PlayerDetector") || hitObj.CompareTag("ShipDetector"))
{
PostEntryNotification();
}
}
else if (_target == NotificationTarget.Player)
{
if (hitObj.CompareTag("PlayerDetector"))
{
PostEntryNotification();
}
}
else if (_target == NotificationTarget.Ship)
{
if (hitObj.CompareTag("ShipDetector"))
{
PostEntryNotification();
}
}
}
public void OnTriggerVolumeExit(GameObject hitObj)
{
if (_target == NotificationTarget.All)
{
if (hitObj.CompareTag("PlayerDetector") || hitObj.CompareTag("ShipDetector"))
{
PostExitNotification();
}
}
else if (_target == NotificationTarget.Player)
{
if (hitObj.CompareTag("PlayerDetector"))
{
PostExitNotification();
}
}
else if (_target == NotificationTarget.Ship)
{
if (hitObj.CompareTag("ShipDetector"))
{
PostExitNotification();
}
}
}
public void PostEntryNotification()
{
if (_entryNotification == null) return;
NotificationManager.SharedInstance.PostNotification(_entryNotification, _pin);
}
public void PostExitNotification()
{
if (_exitNotification == null) return;
NotificationManager.SharedInstance.PostNotification(_exitNotification, _pin);
}
public void UnpinEntryNotification()
{
if (_entryNotification == null) return;
if (NotificationManager.SharedInstance.IsPinnedNotification(_entryNotification))
{
NotificationManager.SharedInstance.UnpinNotification(_entryNotification);
}
}
public void UnpinExitNotification()
{
if (_exitNotification == null) return;
if (NotificationManager.SharedInstance.IsPinnedNotification(_exitNotification))
{
NotificationManager.SharedInstance.UnpinNotification(_exitNotification);
}
}
}
}

View File

@ -169,6 +169,16 @@ namespace NewHorizons.External.Configs
/// </summary> /// </summary>
public WaterModule Water; public WaterModule Water;
/// <summary>
/// Add various volumes on this body
/// </summary>
public VolumesModule Volumes;
/// <summary>
/// Extra data that may be used by extension mods
/// </summary>
public object extras;
public PlanetConfig() public PlanetConfig()
{ {
// Always have to have a base module // Always have to have a base module
@ -312,6 +322,20 @@ namespace NewHorizons.External.Configs
if (tornado.downwards) if (tornado.downwards)
tornado.type = PropModule.TornadoInfo.TornadoType.Downwards; tornado.type = PropModule.TornadoInfo.TornadoType.Downwards;
if (Props?.audioVolumes != null)
{
if (Volumes == null) Volumes = new VolumesModule();
if (Volumes.audioVolumes == null) Volumes.audioVolumes = new VolumesModule.AudioVolumeInfo[0];
Volumes.audioVolumes = Volumes.audioVolumes.Concat(Props.audioVolumes).ToArray();
}
if (Props?.reveal != null)
{
if (Volumes == null) Volumes = new VolumesModule();
if (Volumes.revealVolumes == null) Volumes.revealVolumes = new VolumesModule.RevealVolumeInfo[0];
Volumes.revealVolumes = Volumes.revealVolumes.Concat(Props.reveal).ToArray();
}
if (Base.sphereOfInfluence != 0f) Base.soiOverride = Base.sphereOfInfluence; if (Base.sphereOfInfluence != 0f) Base.soiOverride = Base.sphereOfInfluence;
// Moved a bunch of stuff off of shiplog module to star system module because it didnt exist when we made this // Moved a bunch of stuff off of shiplog module to star system module because it didnt exist when we made this

View File

@ -102,6 +102,11 @@ namespace NewHorizons.External.Configs
/// </summary> /// </summary>
public CuriosityColorInfo[] curiosities; public CuriosityColorInfo[] curiosities;
/// <summary>
/// Extra data that may be used by extension mods
/// </summary>
public object extras;
public class NomaiCoordinates public class NomaiCoordinates
{ {
[MinLength(2)] [MinLength(2)]

View File

@ -48,11 +48,6 @@ namespace NewHorizons.External.Modules
/// </summary> /// </summary>
public RaftInfo[] rafts; public RaftInfo[] rafts;
/// <summary>
/// Add triggers that reveal parts of the ship log on this planet
/// </summary>
public RevealInfo[] reveal;
/// <summary> /// <summary>
/// Scatter props around this planet's surface /// Scatter props around this planet's surface
/// </summary> /// </summary>
@ -83,11 +78,6 @@ namespace NewHorizons.External.Modules
/// </summary> /// </summary>
public SingularityModule[] singularities; public SingularityModule[] singularities;
/// <summary>
/// Add audio volumes to this planet
/// </summary>
public AudioVolumeInfo[] audioVolumes;
/// <summary> /// <summary>
/// Add signalscope signals to this planet /// Add signalscope signals to this planet
/// </summary> /// </summary>
@ -98,6 +88,10 @@ namespace NewHorizons.External.Modules
/// </summary> /// </summary>
public RemoteInfo[] remotes; public RemoteInfo[] remotes;
[Obsolete("reveal is deprecated. Use Volumes->revealVolumes instead.")] public VolumesModule.RevealVolumeInfo[] reveal;
[Obsolete("audioVolumes is deprecated. Use Volumes->audioVolumes instead.")] public VolumesModule.AudioVolumeInfo[] audioVolumes;
[JsonObject] [JsonObject]
public class ScatterInfo public class ScatterInfo
{ {
@ -433,55 +427,6 @@ namespace NewHorizons.External.Modules
public string xmlFile; public string xmlFile;
} }
[JsonObject]
public class RevealInfo
{
[JsonConverter(typeof(StringEnumConverter))]
public enum RevealVolumeType
{
[EnumMember(Value = @"enter")] Enter = 0,
[EnumMember(Value = @"observe")] Observe = 1,
[EnumMember(Value = @"snapshot")] Snapshot = 2
}
/// <summary>
/// The max view angle (in degrees) the player can see the volume with to unlock the fact (`observe` only)
/// </summary>
public float maxAngle = 180f; // Observe Only
/// <summary>
/// The max distance the user can be away from the volume to reveal the fact (`snapshot` and `observe` only)
/// </summary>
public float maxDistance = -1f; // Snapshot & Observe Only
/// <summary>
/// The position to place this volume at
/// </summary>
public MVector3 position;
/// <summary>
/// The radius of this reveal volume
/// </summary>
public float radius = 1f;
/// <summary>
/// What needs to be done to the volume to unlock the facts
/// </summary>
[DefaultValue("enter")] public RevealVolumeType revealOn = RevealVolumeType.Enter;
/// <summary>
/// A list of facts to reveal
/// </summary>
public string[] reveals;
/// <summary>
/// An achievement to unlock. Optional.
/// </summary>
public string achievementID;
}
[JsonObject] [JsonObject]
public class EntryLocationInfo public class EntryLocationInfo
{ {
@ -818,30 +763,6 @@ namespace NewHorizons.External.Modules
[DefaultValue(1f)] public float probability = 1f; [DefaultValue(1f)] public float probability = 1f;
} }
[JsonObject]
public class AudioVolumeInfo
{
/// <summary>
/// The location of this audio volume. Optional (will default to 0,0,0).
/// </summary>
public MVector3 position;
/// <summary>
/// The radius of this audio volume
/// </summary>
public float radius;
/// <summary>
/// The audio to use. Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list.
/// </summary>
public string audio;
/// <summary>
/// The audio track of this audio volume
/// </summary>
[DefaultValue("environment")] public AudioMixerTrackName track = AudioMixerTrackName.Environment;
}
[JsonObject] [JsonObject]
public class RemoteInfo public class RemoteInfo
{ {
@ -1002,25 +923,4 @@ namespace NewHorizons.External.Modules
} }
} }
} }
[JsonConverter(typeof(StringEnumConverter))]
public enum AudioMixerTrackName
{
[EnumMember(Value = @"undefined")] Undefined = 0,
[EnumMember(Value = @"menu")] Menu = 1,
[EnumMember(Value = @"music")] Music = 2,
[EnumMember(Value = @"environment")] Environment = 4,
[EnumMember(Value = @"environmentUnfiltered")] Environment_Unfiltered = 5,
[EnumMember(Value = @"endTimesSfx")] EndTimes_SFX = 8,
[EnumMember(Value = @"signal")] Signal = 16,
[EnumMember(Value = @"death")] Death = 32,
[EnumMember(Value = @"player")] Player = 64,
[EnumMember(Value = @"playerExternal")] Player_External = 65,
[EnumMember(Value = @"ship")] Ship = 128,
[EnumMember(Value = @"map")] Map = 256,
[EnumMember(Value = @"endTimesMusic")] EndTimes_Music = 512,
[EnumMember(Value = @"muffleWhileRafting")] MuffleWhileRafting = 1024,
[EnumMember(Value = @"muffleIndoors")] MuffleIndoors = 2048,
[EnumMember(Value = @"slideReelMusic")] SlideReelMusic = 4096,
}
} }

View File

@ -0,0 +1,243 @@
using NewHorizons.Utility;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
namespace NewHorizons.External.Modules
{
[JsonObject]
public class VolumesModule
{
/// <summary>
/// Add audio volumes to this planet
/// </summary>
public AudioVolumeInfo[] audioVolumes;
/// <summary>
/// Add hazard volumes to this planet
/// </summary>
public HazardVolumeInfo[] hazardVolumes;
/// <summary>
/// Add notification volumes to this planet
/// </summary>
public NotificationVolumeInfo[] notificationVolumes;
/// <summary>
/// Add triggers that reveal parts of the ship log on this planet
/// </summary>
public RevealVolumeInfo[] revealVolumes;
[JsonObject]
public class RevealVolumeInfo
{
[JsonConverter(typeof(StringEnumConverter))]
public enum RevealVolumeType
{
[EnumMember(Value = @"enter")] Enter = 0,
[EnumMember(Value = @"observe")] Observe = 1,
[EnumMember(Value = @"snapshot")] Snapshot = 2
}
/// <summary>
/// The max view angle (in degrees) the player can see the volume with to unlock the fact (`observe` only)
/// </summary>
public float maxAngle = 180f; // Observe Only
/// <summary>
/// The max distance the user can be away from the volume to reveal the fact (`snapshot` and `observe` only)
/// </summary>
public float maxDistance = -1f; // Snapshot & Observe Only
/// <summary>
/// The position to place this volume at
/// </summary>
public MVector3 position;
/// <summary>
/// The radius of this reveal volume
/// </summary>
public float radius = 1f;
/// <summary>
/// What needs to be done to the volume to unlock the facts
/// </summary>
[DefaultValue("enter")] public RevealVolumeType revealOn = RevealVolumeType.Enter;
/// <summary>
/// A list of facts to reveal
/// </summary>
public string[] reveals;
/// <summary>
/// An achievement to unlock. Optional.
/// </summary>
public string achievementID;
}
[JsonObject]
public class AudioVolumeInfo
{
/// <summary>
/// The location of this audio volume. Optional (will default to 0,0,0).
/// </summary>
public MVector3 position;
/// <summary>
/// The radius of this audio volume
/// </summary>
public float radius;
/// <summary>
/// The audio to use. Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list.
/// </summary>
public string audio;
/// <summary>
/// The audio track of this audio volume
/// </summary>
[DefaultValue("environment")] public AudioMixerTrackName track = AudioMixerTrackName.Environment;
/// <summary>
/// Whether to loop this audio while in this audio volume or just play it once
/// </summary>
[DefaultValue(true)] public bool loop = true;
}
[JsonObject]
public class NotificationVolumeInfo
{
/// <summary>
/// What the notification will show for.
/// </summary>
[DefaultValue("all")] public NotificationTarget target = NotificationTarget.All;
/// <summary>
/// The location of this notification volume. Optional (will default to 0,0,0).
/// </summary>
public MVector3 position;
/// <summary>
/// The radius of this notification volume.
/// </summary>
public float radius;
/// <summary>
/// The notification that will play when you enter this volume.
/// </summary>
public NotificationInfo entryNotification;
/// <summary>
/// The notification that will play when you exit this volume.
/// </summary>
public NotificationInfo exitNotification;
[JsonObject]
public class NotificationInfo
{
/// <summary>
/// The message that will be displayed.
/// </summary>
public string displayMessage;
/// <summary>
/// The duration this notification will be displayed.
/// </summary>
[DefaultValue(5f)] public float duration = 5f;
}
[JsonConverter(typeof(StringEnumConverter))]
public enum NotificationTarget
{
[EnumMember(Value = @"all")] All = 0,
[EnumMember(Value = @"ship")] Ship = 1,
[EnumMember(Value = @"player")] Player = 2,
}
}
[JsonObject]
public class HazardVolumeInfo
{
/// <summary>
/// The location of this hazard volume. Optional (will default to 0,0,0).
/// </summary>
public MVector3 position;
/// <summary>
/// The radius of this hazard volume.
/// </summary>
public float radius;
/// <summary>
/// The type of hazard for this volume.
/// </summary>
[DefaultValue("general")] public HazardType type = HazardType.GENERAL;
/// <summary>
/// The amount of damage you will take per second while inside this volume.
/// </summary>
[DefaultValue(10f)] public float damagePerSecond = 10f;
/// <summary>
/// The type of damage you will take when you first touch this volume.
/// </summary>
[DefaultValue("impact")] public InstantDamageType firstContactDamageType = InstantDamageType.Impact;
/// <summary>
/// The amount of damage you will take when you first touch this volume.
/// </summary>
public float firstContactDamage;
[JsonConverter(typeof(StringEnumConverter))]
public enum HazardType
{
[EnumMember(Value = @"none")] NONE = 0,
[EnumMember(Value = @"general")] GENERAL = 1,
[EnumMember(Value = @"darkMatter")] DARKMATTER = 2,
[EnumMember(Value = @"heat")] HEAT = 4,
[EnumMember(Value = @"fire")] FIRE = 8,
[EnumMember(Value = @"sandfall")] SANDFALL = 16,
[EnumMember(Value = @"electricity")] ELECTRICITY = 32,
[EnumMember(Value = @"rapids")] RAPIDS = 64
}
[JsonConverter(typeof(StringEnumConverter))]
public enum InstantDamageType
{
[EnumMember(Value = @"impact")] Impact,
[EnumMember(Value = @"puncture")] Puncture,
[EnumMember(Value = @"electrical")] Electrical
}
}
}
[JsonConverter(typeof(StringEnumConverter))]
public enum AudioMixerTrackName
{
[EnumMember(Value = @"undefined")] Undefined = 0,
[EnumMember(Value = @"menu")] Menu = 1,
[EnumMember(Value = @"music")] Music = 2,
[EnumMember(Value = @"environment")] Environment = 4,
[EnumMember(Value = @"environmentUnfiltered")] Environment_Unfiltered = 5,
[EnumMember(Value = @"endTimesSfx")] EndTimes_SFX = 8,
[EnumMember(Value = @"signal")] Signal = 16,
[EnumMember(Value = @"death")] Death = 32,
[EnumMember(Value = @"player")] Player = 64,
[EnumMember(Value = @"playerExternal")] Player_External = 65,
[EnumMember(Value = @"ship")] Ship = 128,
[EnumMember(Value = @"map")] Map = 256,
[EnumMember(Value = @"endTimesMusic")] EndTimes_Music = 512,
[EnumMember(Value = @"muffleWhileRafting")] MuffleWhileRafting = 1024,
[EnumMember(Value = @"muffleIndoors")] MuffleIndoors = 2048,
[EnumMember(Value = @"slideReelMusic")] SlideReelMusic = 4096,
}
}

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using NewHorizons.Utility; using NewHorizons.Utility;
@ -11,9 +11,12 @@ namespace NewHorizons.External
private static string _activeProfileName; private static string _activeProfileName;
private static readonly string FileName = "save.json"; private static readonly string FileName = "save.json";
// This is its own method so it can be patched by NH-QSB compat
public static string GetProfileName() => StandaloneProfileManager.SharedInstance?.currentProfile?.profileName;
public static void Load() public static void Load()
{ {
_activeProfileName = StandaloneProfileManager.SharedInstance?.currentProfile?.profileName; _activeProfileName = GetProfileName();
if (_activeProfileName == null) if (_activeProfileName == null)
{ {
Logger.LogError("Couldn't find active profile, are you on Gamepass?"); Logger.LogError("Couldn't find active profile, are you on Gamepass?");

View File

@ -3,6 +3,7 @@ using NewHorizons.Builder.Body;
using NewHorizons.Builder.General; using NewHorizons.Builder.General;
using NewHorizons.Builder.Orbital; using NewHorizons.Builder.Orbital;
using NewHorizons.Builder.Props; using NewHorizons.Builder.Props;
using NewHorizons.Builder.Volumes;
using NewHorizons.Components; using NewHorizons.Components;
using NewHorizons.Components.Orbital; using NewHorizons.Components.Orbital;
using NewHorizons.OtherMods.OWRichPresence; using NewHorizons.OtherMods.OWRichPresence;
@ -248,6 +249,16 @@ namespace NewHorizons.Handlers
} }
} }
} }
try
{
Main.Instance.OnPlanetLoaded?.Invoke(body.Config.name);
}
catch (Exception e)
{
Logger.LogError($"Error in event handler for OnPlanetLoaded on body {body.Config.name}: {e}");
}
return true; return true;
} }
@ -598,6 +609,11 @@ namespace NewHorizons.Handlers
PropBuildManager.Make(go, sector, rb, body.Config, body.Mod); PropBuildManager.Make(go, sector, rb, body.Config, body.Mod);
} }
if (body.Config.Volumes != null)
{
VolumesBuildManager.Make(go, sector, rb, body.Config, body.Mod);
}
if (body.Config.Funnel != null) if (body.Config.Funnel != null)
{ {
FunnelBuilder.Make(go, go.GetComponentInChildren<ConstantForceDetector>(), rb, body.Config.Funnel); FunnelBuilder.Make(go, go.GetComponentInChildren<ConstantForceDetector>(), rb, body.Config.Funnel);

View File

@ -43,6 +43,22 @@ namespace NewHorizons
/// </summary> /// </summary>
UnityEvent<string> GetStarSystemLoadedEvent(); UnityEvent<string> GetStarSystemLoadedEvent();
/// <summary>
/// An event invoked when NH has finished a planet for a star system.
/// Gives the name of the planet that was just loaded.
/// </summary>
UnityEvent<string> GetBodyLoadedEvent();
/// <summary>
/// Uses JSONPath to query a body
/// </summary>
object QueryBody(Type outType, string bodyName, string path);
/// <summary>
/// Uses JSONPath to query a system
/// </summary>
object QuerySystem(Type outType, string path);
/// <summary> /// <summary>
/// Allows you to overwrite the default system. This is where the player is respawned after dying. /// Allows you to overwrite the default system. This is where the player is respawned after dying.
/// </summary> /// </summary>

View File

@ -69,6 +69,7 @@ namespace NewHorizons
public class StarSystemEvent : UnityEvent<string> { } public class StarSystemEvent : UnityEvent<string> { }
public StarSystemEvent OnChangeStarSystem; public StarSystemEvent OnChangeStarSystem;
public StarSystemEvent OnStarSystemLoaded; public StarSystemEvent OnStarSystemLoaded;
public StarSystemEvent OnPlanetLoaded;
// For warping to the eye system // For warping to the eye system
private GameObject _ship; private GameObject _ship;
@ -127,7 +128,7 @@ namespace NewHorizons
BodyDict["SolarSystem"] = new List<NewHorizonsBody>(); BodyDict["SolarSystem"] = new List<NewHorizonsBody>();
BodyDict["EyeOfTheUniverse"] = new List<NewHorizonsBody>(); // Keep this empty tho fr BodyDict["EyeOfTheUniverse"] = new List<NewHorizonsBody>(); // Keep this empty tho fr
SystemDict["SolarSystem"] = new NewHorizonsSystem("SolarSystem", new StarSystemConfig(), Instance) SystemDict["SolarSystem"] = new NewHorizonsSystem("SolarSystem", new StarSystemConfig(), "", Instance)
{ {
Config = Config =
{ {
@ -143,7 +144,7 @@ namespace NewHorizons
} }
} }
}; };
SystemDict["EyeOfTheUniverse"] = new NewHorizonsSystem("EyeOfTheUniverse", new StarSystemConfig(), Instance) SystemDict["EyeOfTheUniverse"] = new NewHorizonsSystem("EyeOfTheUniverse", new StarSystemConfig(), "", Instance)
{ {
Config = Config =
{ {
@ -171,6 +172,7 @@ namespace NewHorizons
OnChangeStarSystem = new StarSystemEvent(); OnChangeStarSystem = new StarSystemEvent();
OnStarSystemLoaded = new StarSystemEvent(); OnStarSystemLoaded = new StarSystemEvent();
OnPlanetLoaded = new StarSystemEvent();
SceneManager.sceneLoaded += OnSceneLoaded; SceneManager.sceneLoaded += OnSceneLoaded;
SceneManager.sceneUnloaded += OnSceneUnloaded; SceneManager.sceneUnloaded += OnSceneUnloaded;
@ -517,7 +519,7 @@ namespace NewHorizons
} }
else else
{ {
SystemDict[name] = new NewHorizonsSystem(name, starSystemConfig, mod); SystemDict[name] = new NewHorizonsSystem(name, starSystemConfig, relativePath, mod);
} }
} }
} }
@ -618,7 +620,7 @@ namespace NewHorizons
starSystemConfig.Migrate(); starSystemConfig.Migrate();
starSystemConfig.FixCoordinates(); starSystemConfig.FixCoordinates();
var system = new NewHorizonsSystem(config.starSystem, starSystemConfig, mod); var system = new NewHorizonsSystem(config.starSystem, starSystemConfig, $"", mod);
SystemDict.Add(config.starSystem, system); SystemDict.Add(config.starSystem, system);
@ -649,6 +651,13 @@ namespace NewHorizons
#region Change star system #region Change star system
public void ChangeCurrentStarSystem(string newStarSystem, bool warp = false, bool vessel = false) public void ChangeCurrentStarSystem(string newStarSystem, bool warp = false, bool vessel = false)
{ {
// If we're just on the title screen set the system for later
if (LoadManager.GetCurrentScene() == OWScene.TitleScreen)
{
_currentStarSystem = newStarSystem;
return;
}
if (IsChangingStarSystem) return; if (IsChangingStarSystem) return;
IsWarpingFromShip = warp; IsWarpingFromShip = warp;
@ -660,9 +669,6 @@ namespace NewHorizons
IsChangingStarSystem = true; IsChangingStarSystem = true;
WearingSuit = PlayerState.IsWearingSuit(); WearingSuit = PlayerState.IsWearingSuit();
// We kill them so they don't move as much
Locator.GetDeathManager().KillPlayer(DeathType.Meditation);
OWScene sceneToLoad; OWScene sceneToLoad;
if (newStarSystem == "EyeOfTheUniverse") if (newStarSystem == "EyeOfTheUniverse")
@ -680,12 +686,15 @@ namespace NewHorizons
_currentStarSystem = newStarSystem; _currentStarSystem = newStarSystem;
// Freeze player inputs
OWInput.ChangeInputMode(InputMode.None);
LoadManager.LoadSceneAsync(sceneToLoad, !vessel, LoadManager.FadeType.ToBlack, 0.1f, true); LoadManager.LoadSceneAsync(sceneToLoad, !vessel, LoadManager.FadeType.ToBlack, 0.1f, true);
} }
void OnDeath(DeathType _) void OnDeath(DeathType _)
{ {
// We reset the solar system on death (unless we just killed the player) // We reset the solar system on death
if (!IsChangingStarSystem) if (!IsChangingStarSystem)
{ {
// If the override is a valid system then we go there // If the override is a valid system then we go there

View File

@ -7,12 +7,15 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using UnityEngine; using UnityEngine;
using UnityEngine.Events; using UnityEngine.Events;
using Logger = NewHorizons.Utility.Logger; using Logger = NewHorizons.Utility.Logger;
namespace NewHorizons namespace NewHorizons
{ {
public class NewHorizonsApi : INewHorizons public class NewHorizonsApi : INewHorizons
{ {
[Obsolete("Create(Dictionary<string, object> config) is deprecated, please use LoadConfigs(IModBehaviour mod) instead")] [Obsolete("Create(Dictionary<string, object> config) is deprecated, please use LoadConfigs(IModBehaviour mod) instead")]
@ -64,20 +67,10 @@ namespace NewHorizons
return Main.BodyDict.Values.SelectMany(x => x)?.ToList()?.FirstOrDefault(x => x.Config.name == name)?.Object; return Main.BodyDict.Values.SelectMany(x => x)?.ToList()?.FirstOrDefault(x => x.Config.name == name)?.Object;
} }
public string GetCurrentStarSystem() public string GetCurrentStarSystem() => Main.Instance.CurrentStarSystem;
{ public UnityEvent<string> GetChangeStarSystemEvent() => Main.Instance.OnChangeStarSystem;
return Main.Instance.CurrentStarSystem; public UnityEvent<string> GetStarSystemLoadedEvent() => Main.Instance.OnStarSystemLoaded;
} public UnityEvent<string> GetBodyLoadedEvent() => Main.Instance.OnPlanetLoaded;
public UnityEvent<string> GetChangeStarSystemEvent()
{
return Main.Instance.OnChangeStarSystem;
}
public UnityEvent<string> GetStarSystemLoadedEvent()
{
return Main.Instance.OnStarSystemLoaded;
}
public bool SetDefaultSystem(string name) public bool SetDefaultSystem(string name)
{ {
@ -108,6 +101,42 @@ namespace NewHorizons
} }
} }
private static object QueryJson(Type outType, string filePath, string jsonPath)
{
if (filePath == "") return null;
try
{
var jsonText = File.ReadAllText(filePath);
var jsonData = JObject.Parse(jsonText);
return jsonData.SelectToken(jsonPath)?.ToObject(outType);
}
catch (FileNotFoundException)
{
return null;
}
catch (JsonException e)
{
Logger.LogError(e.ToString());
return null;
}
}
public object QueryBody(Type outType, string bodyName, string jsonPath)
{
var planet = Main.BodyDict[Main.Instance.CurrentStarSystem].Find((b) => b.Config.name == bodyName);
return planet == null
? null
: QueryJson(outType, planet.Mod.ModHelper.Manifest.ModFolderPath + planet.RelativePath, jsonPath);
}
public object QuerySystem(Type outType, string jsonPath)
{
var system = Main.SystemDict[Main.Instance.CurrentStarSystem];
return system == null
? null
: QueryJson(outType, system.Mod.ModHelper.Manifest.ModFolderPath + system.RelativePath, jsonPath);
}
public GameObject SpawnObject(GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles, public GameObject SpawnObject(GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles,
float scale, bool alignWithNormal) float scale, bool alignWithNormal)
{ {

View File

@ -47,7 +47,7 @@ namespace NewHorizons.OtherMods.OWRichPresence
var localizedName = TranslationHandler.GetTranslation(name, TranslationHandler.TextType.UI); var localizedName = TranslationHandler.GetTranslation(name, TranslationHandler.TextType.UI);
var message = TranslationHandler.GetTranslation("RICH_PRESENCE_EXPLORING", TranslationHandler.TextType.UI).Replace("{0}", localizedName); var message = TranslationHandler.GetTranslation("RICH_PRESENCE_EXPLORING", TranslationHandler.TextType.UI).Replace("{0}", localizedName);
API.CreateTrigger(go, sector, message, name.Replace(" ", "").Replace("'", "").ToLowerInvariant()); API.CreateTrigger(go, sector, message, name.Replace(" ", "").Replace("'", "").Replace("-", "").ToLowerInvariant());
} }
public static void OnStarSystemLoaded(string name) public static void OnStarSystemLoaded(string name)

View File

@ -0,0 +1,29 @@
using HarmonyLib;
using System.Collections.Generic;
namespace NewHorizons.Patches
{
[HarmonyPatch]
public class ShapePatches
{
[HarmonyPrefix]
[HarmonyPatch(typeof(ShapeManager), nameof(ShapeManager.Initialize))]
public static bool ShapeManager_Initialize()
{
ShapeManager._exists = true;
ShapeManager._detectors = new ShapeManager.Layer(256);
for (int index = 0; index < 256; ++index)
ShapeManager._detectors[index].contacts = new List<ShapeManager.ContactData>(64);
ShapeManager._volumes = new ShapeManager.Layer[4];
for (int index = 0; index < 4; ++index)
ShapeManager._volumes[index] = new ShapeManager.Layer(2048);
ShapeManager._locked = false;
ShapeManager._frameFlag = false;
return false;
}
}
}

View File

@ -128,6 +128,17 @@
"description": "Add water to this planet", "description": "Add water to this planet",
"$ref": "#/definitions/WaterModule" "$ref": "#/definitions/WaterModule"
}, },
"Volumes": {
"description": "Add various volumes on this body",
"$ref": "#/definitions/VolumesModule"
},
"extras": {
"type": "object",
"description": "Extra data that may be used by extension mods",
"additionalProperties": {
"type": "object"
}
},
"$schema": { "$schema": {
"type": "string", "type": "string",
"description": "The schema to validate with" "description": "The schema to validate with"
@ -925,13 +936,6 @@
"$ref": "#/definitions/RaftInfo" "$ref": "#/definitions/RaftInfo"
} }
}, },
"reveal": {
"type": "array",
"description": "Add triggers that reveal parts of the ship log on this planet",
"items": {
"$ref": "#/definitions/RevealInfo"
}
},
"scatter": { "scatter": {
"type": "array", "type": "array",
"description": "Scatter props around this planet's surface", "description": "Scatter props around this planet's surface",
@ -974,13 +978,6 @@
"$ref": "#/definitions/SingularityModule" "$ref": "#/definitions/SingularityModule"
} }
}, },
"audioVolumes": {
"type": "array",
"description": "Add audio volumes to this planet",
"items": {
"$ref": "#/definitions/AudioVolumeInfo"
}
},
"signals": { "signals": {
"type": "array", "type": "array",
"description": "Add signalscope signals to this planet", "description": "Add signalscope signals to this planet",
@ -1337,61 +1334,6 @@
} }
} }
}, },
"RevealInfo": {
"type": "object",
"additionalProperties": false,
"properties": {
"maxAngle": {
"type": "number",
"description": "The max view angle (in degrees) the player can see the volume with to unlock the fact (`observe` only)",
"format": "float"
},
"maxDistance": {
"type": "number",
"description": "The max distance the user can be away from the volume to reveal the fact (`snapshot` and `observe` only)",
"format": "float"
},
"position": {
"description": "The position to place this volume at",
"$ref": "#/definitions/MVector3"
},
"radius": {
"type": "number",
"description": "The radius of this reveal volume",
"format": "float"
},
"revealOn": {
"description": "What needs to be done to the volume to unlock the facts",
"default": "enter",
"$ref": "#/definitions/RevealVolumeType"
},
"reveals": {
"type": "array",
"description": "A list of facts to reveal",
"items": {
"type": "string"
}
},
"achievementID": {
"type": "string",
"description": "An achievement to unlock. Optional."
}
}
},
"RevealVolumeType": {
"type": "string",
"description": "",
"x-enumNames": [
"Enter",
"Observe",
"Snapshot"
],
"enum": [
"enter",
"observe",
"snapshot"
]
},
"ScatterInfo": { "ScatterInfo": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
@ -1801,70 +1743,6 @@
"whiteHole" "whiteHole"
] ]
}, },
"AudioVolumeInfo": {
"type": "object",
"additionalProperties": false,
"properties": {
"position": {
"description": "The location of this audio volume. Optional (will default to 0,0,0).",
"$ref": "#/definitions/MVector3"
},
"radius": {
"type": "number",
"description": "The radius of this audio volume",
"format": "float"
},
"audio": {
"type": "string",
"description": "The audio to use. Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list."
},
"track": {
"description": "The audio track of this audio volume",
"default": "environment",
"$ref": "#/definitions/AudioMixerTrackName"
}
}
},
"AudioMixerTrackName": {
"type": "string",
"description": "",
"x-enumNames": [
"Undefined",
"Menu",
"Music",
"Environment",
"Environment_Unfiltered",
"EndTimes_SFX",
"Signal",
"Death",
"Player",
"Player_External",
"Ship",
"Map",
"EndTimes_Music",
"MuffleWhileRafting",
"MuffleIndoors",
"SlideReelMusic"
],
"enum": [
"undefined",
"menu",
"music",
"environment",
"environmentUnfiltered",
"endTimesSfx",
"signal",
"death",
"player",
"playerExternal",
"ship",
"map",
"endTimesMusic",
"muffleWhileRafting",
"muffleIndoors",
"slideReelMusic"
]
},
"SignalInfo": { "SignalInfo": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
@ -2503,6 +2381,296 @@
"$ref": "#/definitions/MColor" "$ref": "#/definitions/MColor"
} }
} }
},
"VolumesModule": {
"type": "object",
"additionalProperties": false,
"properties": {
"audioVolumes": {
"type": "array",
"description": "Add audio volumes to this planet",
"items": {
"$ref": "#/definitions/AudioVolumeInfo"
}
},
"hazardVolumes": {
"type": "array",
"description": "Add hazard volumes to this planet",
"items": {
"$ref": "#/definitions/HazardVolumeInfo"
}
},
"notificationVolumes": {
"type": "array",
"description": "Add notification volumes to this planet",
"items": {
"$ref": "#/definitions/NotificationVolumeInfo"
}
},
"revealVolumes": {
"type": "array",
"description": "Add triggers that reveal parts of the ship log on this planet",
"items": {
"$ref": "#/definitions/RevealVolumeInfo"
}
}
}
},
"AudioVolumeInfo": {
"type": "object",
"additionalProperties": false,
"properties": {
"position": {
"description": "The location of this audio volume. Optional (will default to 0,0,0).",
"$ref": "#/definitions/MVector3"
},
"radius": {
"type": "number",
"description": "The radius of this audio volume",
"format": "float"
},
"audio": {
"type": "string",
"description": "The audio to use. Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list."
},
"track": {
"description": "The audio track of this audio volume",
"default": "environment",
"$ref": "#/definitions/AudioMixerTrackName"
},
"loop": {
"type": "boolean",
"description": "Whether to loop this audio while in this audio volume or just play it once",
"default": true
}
}
},
"AudioMixerTrackName": {
"type": "string",
"description": "",
"x-enumNames": [
"Undefined",
"Menu",
"Music",
"Environment",
"Environment_Unfiltered",
"EndTimes_SFX",
"Signal",
"Death",
"Player",
"Player_External",
"Ship",
"Map",
"EndTimes_Music",
"MuffleWhileRafting",
"MuffleIndoors",
"SlideReelMusic"
],
"enum": [
"undefined",
"menu",
"music",
"environment",
"environmentUnfiltered",
"endTimesSfx",
"signal",
"death",
"player",
"playerExternal",
"ship",
"map",
"endTimesMusic",
"muffleWhileRafting",
"muffleIndoors",
"slideReelMusic"
]
},
"HazardVolumeInfo": {
"type": "object",
"additionalProperties": false,
"properties": {
"position": {
"description": "The location of this hazard volume. Optional (will default to 0,0,0).",
"$ref": "#/definitions/MVector3"
},
"radius": {
"type": "number",
"description": "The radius of this hazard volume.",
"format": "float"
},
"type": {
"description": "The type of hazard for this volume.",
"default": "general",
"$ref": "#/definitions/HazardType"
},
"damagePerSecond": {
"type": "number",
"description": "The amount of damage you will take per second while inside this volume.",
"format": "float",
"default": 10.0
},
"firstContactDamageType": {
"description": "The type of damage you will take when you first touch this volume.",
"default": "impact",
"$ref": "#/definitions/InstantDamageType"
},
"firstContactDamage": {
"type": "number",
"description": "The amount of damage you will take when you first touch this volume.",
"format": "float"
}
}
},
"HazardType": {
"type": "string",
"description": "",
"x-enumNames": [
"NONE",
"GENERAL",
"DARKMATTER",
"HEAT",
"FIRE",
"SANDFALL",
"ELECTRICITY",
"RAPIDS"
],
"enum": [
"none",
"general",
"darkMatter",
"heat",
"fire",
"sandfall",
"electricity",
"rapids"
]
},
"InstantDamageType": {
"type": "string",
"description": "",
"x-enumNames": [
"Impact",
"Puncture",
"Electrical"
],
"enum": [
"impact",
"puncture",
"electrical"
]
},
"NotificationVolumeInfo": {
"type": "object",
"additionalProperties": false,
"properties": {
"target": {
"description": "What the notification will show for.",
"default": "all",
"$ref": "#/definitions/NotificationTarget"
},
"position": {
"description": "The location of this notification volume. Optional (will default to 0,0,0).",
"$ref": "#/definitions/MVector3"
},
"radius": {
"type": "number",
"description": "The radius of this notification volume.",
"format": "float"
},
"entryNotification": {
"description": "The notification that will play when you enter this volume.",
"$ref": "#/definitions/NotificationInfo"
},
"exitNotification": {
"description": "The notification that will play when you exit this volume.",
"$ref": "#/definitions/NotificationInfo"
}
}
},
"NotificationTarget": {
"type": "string",
"description": "",
"x-enumNames": [
"All",
"Ship",
"Player"
],
"enum": [
"all",
"ship",
"player"
]
},
"NotificationInfo": {
"type": "object",
"additionalProperties": false,
"properties": {
"displayMessage": {
"type": "string",
"description": "The message that will be displayed."
},
"duration": {
"type": "number",
"description": "The duration this notification will be displayed.",
"format": "float",
"default": 5.0
}
}
},
"RevealVolumeInfo": {
"type": "object",
"additionalProperties": false,
"properties": {
"maxAngle": {
"type": "number",
"description": "The max view angle (in degrees) the player can see the volume with to unlock the fact (`observe` only)",
"format": "float"
},
"maxDistance": {
"type": "number",
"description": "The max distance the user can be away from the volume to reveal the fact (`snapshot` and `observe` only)",
"format": "float"
},
"position": {
"description": "The position to place this volume at",
"$ref": "#/definitions/MVector3"
},
"radius": {
"type": "number",
"description": "The radius of this reveal volume",
"format": "float"
},
"revealOn": {
"description": "What needs to be done to the volume to unlock the facts",
"default": "enter",
"$ref": "#/definitions/RevealVolumeType"
},
"reveals": {
"type": "array",
"description": "A list of facts to reveal",
"items": {
"type": "string"
}
},
"achievementID": {
"type": "string",
"description": "An achievement to unlock. Optional."
}
}
},
"RevealVolumeType": {
"type": "string",
"description": "",
"x-enumNames": [
"Enter",
"Observe",
"Snapshot"
],
"enum": [
"enter",
"observe",
"snapshot"
]
} }
}, },
"$docs": { "$docs": {

View File

@ -71,6 +71,13 @@
"$ref": "#/definitions/CuriosityColorInfo" "$ref": "#/definitions/CuriosityColorInfo"
} }
}, },
"extras": {
"type": "object",
"description": "Extra data that may be used by extension mods",
"additionalProperties": {
"type": "object"
}
},
"$schema": { "$schema": {
"type": "string", "type": "string",
"description": "The schema to validate with" "description": "The schema to validate with"

View File

@ -69,7 +69,13 @@ namespace NewHorizons.Utility.DebugMenu
PauseMenuInitHook(); PauseMenuInitHook();
Main.Instance.OnChangeStarSystem.AddListener((string s) => SaveLoadedConfigsForRecentSystem()); Main.Instance.OnChangeStarSystem.AddListener((string s) => {
if (saveButtonUnlocked)
{
SaveLoadedConfigsForRecentSystem();
saveButtonUnlocked = false;
}
});
} }
else else
{ {
@ -227,6 +233,24 @@ namespace NewHorizons.Utility.DebugMenu
var json = loadedConfigFiles[filePath].ToSerializedJson(); var json = loadedConfigFiles[filePath].ToSerializedJson();
try
{
var path = loadedMod.ModHelper.Manifest.ModFolderPath + backupFolderName + relativePath;
Logger.LogVerbose($"Backing up... {relativePath} to {path}");
var oldPath = loadedMod.ModHelper.Manifest.ModFolderPath + relativePath;
var directoryName = Path.GetDirectoryName(path);
Directory.CreateDirectory(directoryName);
if (File.Exists(oldPath))
File.WriteAllBytes(path, File.ReadAllBytes(oldPath));
else
File.WriteAllText(path, json);
}
catch (Exception e)
{
Logger.LogError($"Failed to save backup file {backupFolderName}{relativePath}:\n{e}");
}
try try
{ {
Logger.Log($"Saving... {relativePath} to {filePath}"); Logger.Log($"Saving... {relativePath} to {filePath}");
@ -240,19 +264,6 @@ namespace NewHorizons.Utility.DebugMenu
{ {
Logger.LogError($"Failed to save file {relativePath}:\n{e}"); Logger.LogError($"Failed to save file {relativePath}:\n{e}");
} }
try
{
var path = Main.Instance.ModHelper.Manifest.ModFolderPath + backupFolderName + relativePath;
var directoryName = Path.GetDirectoryName(path);
Directory.CreateDirectory(directoryName);
File.WriteAllText(path, json);
}
catch (Exception e)
{
Logger.LogError($"Failed to save backup file {backupFolderName}{relativePath}:\n{e}");
}
} }
} }

View File

@ -11,15 +11,17 @@ namespace NewHorizons.Utility
public class NewHorizonsSystem public class NewHorizonsSystem
{ {
public string UniqueID; public string UniqueID;
public string RelativePath;
public SpawnModule Spawn = null; public SpawnModule Spawn = null;
public SpawnPoint SpawnPoint = null; public SpawnPoint SpawnPoint = null;
public StarSystemConfig Config; public StarSystemConfig Config;
public IModBehaviour Mod; public IModBehaviour Mod;
public NewHorizonsSystem(string uniqueID, StarSystemConfig config, IModBehaviour mod) public NewHorizonsSystem(string uniqueID, StarSystemConfig config, string relativePath, IModBehaviour mod)
{ {
UniqueID = uniqueID; UniqueID = uniqueID;
Config = config; Config = config;
RelativePath = relativePath;
Mod = mod; Mod = mod;
} }
} }

View File

@ -4,7 +4,7 @@
"author": "xen, Bwc9876, clay, MegaPiggy, John, Hawkbar, Trifid, Book", "author": "xen, Bwc9876, clay, MegaPiggy, John, Hawkbar, Trifid, Book",
"name": "New Horizons", "name": "New Horizons",
"uniqueName": "xen.NewHorizons", "uniqueName": "xen.NewHorizons",
"version": "1.5.0", "version": "1.5.1",
"owmlVersion": "2.6.0", "owmlVersion": "2.6.0",
"dependencies": [ "JohnCorby.VanillaFix" ], "dependencies": [ "JohnCorby.VanillaFix" ],
"conflicts": [ "Raicuparta.QuantumSpaceBuddies", "PacificEngine.OW_Randomizer" ], "conflicts": [ "Raicuparta.QuantumSpaceBuddies", "PacificEngine.OW_Randomizer" ],

View File

@ -78,16 +78,29 @@ public static class SchemaExporter
{"description", _description} {"description", _description}
}); });
if (_title == "Celestial Body Schema") switch (_title)
{ {
schema.Definitions["OrbitModule"].Properties["semiMajorAxis"].Default = 5000f; case "Celestial Body Schema":
schema.Definitions["OrbitModule"].Properties["semiMajorAxis"].Default = 5000f;
break;
case "Star System Schema":
schema.Definitions["NomaiCoordinates"].Properties["x"].UniqueItems = true;
schema.Definitions["NomaiCoordinates"].Properties["y"].UniqueItems = true;
schema.Definitions["NomaiCoordinates"].Properties["z"].UniqueItems = true;
break;
} }
if (_title == "Star System Schema") if (_title is "Star System Schema" or "Celestial Body Schema")
{ {
schema.Definitions["NomaiCoordinates"].Properties["x"].UniqueItems = true; schema.Properties["extras"] = new JsonSchemaProperty {
schema.Definitions["NomaiCoordinates"].Properties["y"].UniqueItems = true; Type = JsonObjectType.Object,
schema.Definitions["NomaiCoordinates"].Properties["z"].UniqueItems = true; Description = "Extra data that may be used by extension mods",
AllowAdditionalProperties = true,
AdditionalPropertiesSchema = new JsonSchema
{
Type = JsonObjectType.Object
}
};
} }
return schema; return schema;

View File

@ -9,21 +9,99 @@ First create the following interface in your mod:
```cs ```cs
public interface INewHorizons public interface INewHorizons
{ {
void LoadConfigs(IModBehaviour mod); [Obsolete("Create(Dictionary<string, object> config) is deprecated, please use LoadConfigs(IModBehaviour mod) instead")]
void Create(Dictionary<string, object> config);
GameObject GetPlanet(string name); [Obsolete("Create(Dictionary<string, object> config) is deprecated, please use LoadConfigs(IModBehaviour mod) instead")]
void Create(Dictionary<string, object> config, IModBehaviour mod);
string GetCurrentStarSystem();
UnityEvent<string> GetChangeStarSystemEvent(); /// <summary>
/// Will load all configs in the regular folders (planets, systems, translations, etc) for this mod.
/// The NH addon config template is just a single call to this API method.
/// </summary>
void LoadConfigs(IModBehaviour mod);
UnityEvent<string> GetStarSystemLoadedEvent(); /// <summary>
/// Retrieve the root GameObject of a custom planet made by creating configs.
GameObject SpawnObject(GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles, float scale, bool alignWithNormal); /// Will only work if the planet has been created (see GetStarSystemLoadedEvent)
/// </summary>
GameObject GetPlanet(string name);
string[] GetInstalledAddons(); /// <summary>
} /// The name of the current star system loaded.
/// </summary>
string GetCurrentStarSystem();
/// <summary>
/// An event invoked when the player begins loading the new star system, before the scene starts to load.
/// Gives the name of the star system being switched to.
/// </summary>
UnityEvent<string> GetChangeStarSystemEvent();
/// <summary>
/// An event invoked when NH has finished generating all planets for a new star system.
/// Gives the name of the star system that was just loaded.
/// </summary>
UnityEvent<string> GetStarSystemLoadedEvent();
/// <summary>
/// An event invoked when NH has finished a planet for a star system.
/// Gives the name of the planet that was just loaded.
/// </summary>
UnityEvent<string> GetBodyLoadedEvent();
/// <summary>
/// Uses JSONPath to query a body
/// </summary>
object QueryBody(Type outType, string bodyName, string path);
/// <summary>
/// Uses JSONPath to query a system
/// </summary>
object QuerySystem(Type outType, string path);
/// <summary>
/// Allows you to overwrite the default system. This is where the player is respawned after dying.
/// </summary>
bool SetDefaultSystem(string name);
/// <summary>
/// Allows you to instantly begin a warp to a new star system.
/// Will return false if that system does not exist (cannot be warped to).
/// </summary>
bool ChangeCurrentStarSystem(string name);
/// <summary>
/// Returns the uniqueIDs of each installed NH addon.
/// </summary>
string[] GetInstalledAddons();
/// <summary>
/// Allows you to spawn a copy of a prop by specifying its path.
/// This is the same as using Props->details in a config, but also returns the spawned gameObject to you.
/// </summary>
GameObject SpawnObject(GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles,
float scale, bool alignWithNormal);
/// <summary>
/// Allows you to spawn an AudioSignal on a planet.
/// This is the same as using Props->signals in a config, but also returns the spawned AudioSignal to you.
/// This method will not set its position. You will have to do that with the returned object.
/// </summary>
AudioSignal SpawnSignal(IModBehaviour mod, GameObject root, string audio, string name, string frequency,
float sourceRadius = 1f, float detectionRadius = 20f, float identificationRadius = 10f, bool insideCloak = false,
bool onlyAudibleToScope = true, string reveals = "");
/// <summary>
/// Allows you to spawn character dialogue on a planet. Also returns the RemoteDialogueTrigger if remoteTriggerRadius is specified.
/// This is the same as using Props->dialogue in a config, but also returns the spawned game objects to you.
/// This method will not set the position of the dialogue or remote trigger. You will have to do that with the returned objects.
/// </summary>
(CharacterDialogueTree, RemoteDialogueTrigger) SpawnDialogue(IModBehaviour mod, GameObject root, string xmlFile, float radius = 1f,
float range = 1f, string blockAfterPersistentCondition = null, float lookAtRadius = 1f, string pathToAnimController = null,
float remoteTriggerRadius = 0f);
}
``` ```
In your main `ModBehaviour` class you can get the NewHorizons API like so: In your main `ModBehaviour` class you can get the NewHorizons API like so:
@ -33,7 +111,7 @@ public class MyMod : ModBehaviour
{ {
void Start() void Start()
{ {
INewHorizons NewHorizonsAPI = ModHelper.Interaction.GetModApi<INewHorizons>("xen.NewHorizons"); INewHorizons NewHorizonsAPI = ModHelper.Interaction.TryGetModApi<INewHorizons>("xen.NewHorizons");
} }
} }
``` ```

View File

@ -0,0 +1,74 @@
---
Title: Extending Configs
Description: A guide on extending config files with the New Horizons API
Sort_Priority: 5
---
# Extending Configs
This guide will explain how to use the API to add new features to New Horizons.
## How Extending Works
Addon developers will add a key to the `extras` object in the root of the config
```json
{
"name": "Wetrock",
"extras": {
"myCoolExtensionData": {
"myCoolExtensionProperty": 2
}
}
}
```
Your mod will then use the API's `QueryBody` method to obtain the `myCoolExtensionData` object.
**It's up to the addon dev to list your mod as a dependency!**
## Extending Planets
You can extend all planets by hooking into the `OnBodyLoaded` event of the API:
```csharp
var api = ModHelper.Interactions.TryGetModApi<INewHorizons>("xen.NewHorizons");
api.GetBodyLoadedEvent().AddListener((name) => {
ModHelper.Console.WriteLine($"Body: {name} Loaded!");
});
```
In order to get your extra module, first define the module as a class:
```csharp
public class MyCoolExtensionData {
int myCoolExtensionProperty;
}
```
Then, use the `QueryBody` method:
```csharp
var api = ModHelper.Interactions.TryGetModApi<INewHorizons>("xen.NewHorizons");
api.GetBodyLoadedEvent().AddListener((name) => {
ModHelper.Console.WriteLine($"Body: {name} Loaded!");
var potentialData = api.QueryBody(typeof(MyCoolExtensionData), "$.extras.myCoolExtensionData", name);
// Makes sure the module is valid and not null
if (potentialData is MyCoolExtensionData data) {
ModHelper.Console.WriteLine($"myCoolExtensionProperty for {name} is {data.myCoolExtensionProperty}!");
}
});
```
## Extending Systems
Extending systems is the exact same as extending planets, except you use the `QuerySystem` method instead.
## Accessing Other Values
You can also use the `QueryBody` method to get values of the config outside of your extension object
```csharp
var primaryBody = api.QueryBody(typeof(string), "Wetrock", "$.Orbit.primaryBody");
ModHelper.Console.WriteLine($"Primary of {bodyName} is {primaryBody ?? "NULL"}!");
```