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._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)
{
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>();
pvc._densityByHeight = new AnimationCurve(new Keyframe[]
{
new Keyframe(surfaceSize - 0.5f, 0),
new Keyframe(surfaceSize, 10f),
new Keyframe(config.Atmosphere.size, 0f)
new Keyframe(minHeight - 0.5f, 0),
new Keyframe(minHeight, 10f),
new Keyframe(maxHeight, 0f)
});
rainGO.GetComponent<PlanetaryVectionController>()._activeInSector = sector;
@ -53,9 +63,9 @@ namespace NewHorizons.Builder.Atmosphere
var pvc = snowEmitter.GetComponent<PlanetaryVectionController>();
pvc._densityByHeight = new AnimationCurve(new Keyframe[]
{
new Keyframe(surfaceSize - 0.5f, 0),
new Keyframe(surfaceSize, 10f),
new Keyframe(config.Atmosphere.size, 0f)
new Keyframe(minHeight - 0.5f, 0),
new Keyframe(minHeight, 10f),
new Keyframe(maxHeight, 0f)
});
snowEmitter.GetComponent<PlanetaryVectionController>()._activeInSector = sector;

View File

@ -192,8 +192,12 @@ namespace NewHorizons.Builder.Body
cloak.GetComponent<Renderer>().enabled = true;
// Cull stuff
var cullController = go.AddComponent<BrambleSectorController>();
cullController.SetSector(sector);
// Do next update so other nodes can be built first
Delay.FireOnNextUpdate(() =>
{
var cullController = go.AddComponent<BrambleSectorController>();
cullController.SetSector(sector);
});
// finalize
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)
{
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)
{
foreach (var signal in config.Props.signals)

View File

@ -7,18 +7,18 @@ namespace NewHorizons.Builder.ShipLog
{
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);
switch (info.revealOn)
{
case PropModule.RevealInfo.RevealVolumeType.Enter:
case VolumesModule.RevealVolumeInfo.RevealVolumeType.Enter:
MakeTrigger(newRevealGO, sector, info, mod);
break;
case PropModule.RevealInfo.RevealVolumeType.Observe:
case VolumesModule.RevealVolumeInfo.RevealVolumeType.Observe:
MakeObservable(newRevealGO, sector, info, mod);
break;
case PropModule.RevealInfo.RevealVolumeType.Snapshot:
case VolumesModule.RevealVolumeInfo.RevealVolumeType.Snapshot:
MakeSnapshot(newRevealGO, sector, info, mod);
break;
default:
@ -28,7 +28,7 @@ namespace NewHorizons.Builder.ShipLog
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>();
newShape.radius = info.radius;
@ -36,7 +36,7 @@ namespace NewHorizons.Builder.ShipLog
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 + ")");
revealTriggerVolume.SetActive(false);
@ -45,7 +45,7 @@ namespace NewHorizons.Builder.ShipLog
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);
@ -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");
@ -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);

View File

@ -10,11 +10,11 @@ using System.Threading.Tasks;
using UnityEngine;
using Logger = NewHorizons.Utility.Logger;
namespace NewHorizons.Builder.Props
namespace NewHorizons.Builder.Volumes
{
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");
go.SetActive(false);
@ -27,7 +27,7 @@ namespace NewHorizons.Builder.Props
var owAudioSource = go.AddComponent<OWAudioSource>();
owAudioSource._audioSource = audioSource;
owAudioSource.loop = true;
owAudioSource.loop = info.loop;
owAudioSource.SetTrack(EnumUtils.Parse<OWAudioMixer.TrackName>(info.track.ToString()));
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>
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()
{
// Always have to have a base module
@ -312,6 +322,20 @@ namespace NewHorizons.External.Configs
if (tornado.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;
// 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>
public CuriosityColorInfo[] curiosities;
/// <summary>
/// Extra data that may be used by extension mods
/// </summary>
public object extras;
public class NomaiCoordinates
{
[MinLength(2)]

View File

@ -48,11 +48,6 @@ namespace NewHorizons.External.Modules
/// </summary>
public RaftInfo[] rafts;
/// <summary>
/// Add triggers that reveal parts of the ship log on this planet
/// </summary>
public RevealInfo[] reveal;
/// <summary>
/// Scatter props around this planet's surface
/// </summary>
@ -83,11 +78,6 @@ namespace NewHorizons.External.Modules
/// </summary>
public SingularityModule[] singularities;
/// <summary>
/// Add audio volumes to this planet
/// </summary>
public AudioVolumeInfo[] audioVolumes;
/// <summary>
/// Add signalscope signals to this planet
/// </summary>
@ -98,6 +88,10 @@ namespace NewHorizons.External.Modules
/// </summary>
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]
public class ScatterInfo
{
@ -433,55 +427,6 @@ namespace NewHorizons.External.Modules
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]
public class EntryLocationInfo
{
@ -818,30 +763,6 @@ namespace NewHorizons.External.Modules
[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]
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 NewHorizons.Utility;
@ -11,9 +11,12 @@ namespace NewHorizons.External
private static string _activeProfileName;
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()
{
_activeProfileName = StandaloneProfileManager.SharedInstance?.currentProfile?.profileName;
_activeProfileName = GetProfileName();
if (_activeProfileName == null)
{
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.Orbital;
using NewHorizons.Builder.Props;
using NewHorizons.Builder.Volumes;
using NewHorizons.Components;
using NewHorizons.Components.Orbital;
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;
}
@ -598,6 +609,11 @@ namespace NewHorizons.Handlers
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)
{
FunnelBuilder.Make(go, go.GetComponentInChildren<ConstantForceDetector>(), rb, body.Config.Funnel);

View File

@ -43,6 +43,22 @@ namespace NewHorizons
/// </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>

View File

@ -69,6 +69,7 @@ namespace NewHorizons
public class StarSystemEvent : UnityEvent<string> { }
public StarSystemEvent OnChangeStarSystem;
public StarSystemEvent OnStarSystemLoaded;
public StarSystemEvent OnPlanetLoaded;
// For warping to the eye system
private GameObject _ship;
@ -127,7 +128,7 @@ namespace NewHorizons
BodyDict["SolarSystem"] = new List<NewHorizonsBody>();
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 =
{
@ -143,7 +144,7 @@ namespace NewHorizons
}
}
};
SystemDict["EyeOfTheUniverse"] = new NewHorizonsSystem("EyeOfTheUniverse", new StarSystemConfig(), Instance)
SystemDict["EyeOfTheUniverse"] = new NewHorizonsSystem("EyeOfTheUniverse", new StarSystemConfig(), "", Instance)
{
Config =
{
@ -171,6 +172,7 @@ namespace NewHorizons
OnChangeStarSystem = new StarSystemEvent();
OnStarSystemLoaded = new StarSystemEvent();
OnPlanetLoaded = new StarSystemEvent();
SceneManager.sceneLoaded += OnSceneLoaded;
SceneManager.sceneUnloaded += OnSceneUnloaded;
@ -517,7 +519,7 @@ namespace NewHorizons
}
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.FixCoordinates();
var system = new NewHorizonsSystem(config.starSystem, starSystemConfig, mod);
var system = new NewHorizonsSystem(config.starSystem, starSystemConfig, $"", mod);
SystemDict.Add(config.starSystem, system);
@ -649,6 +651,13 @@ namespace NewHorizons
#region Change star system
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;
IsWarpingFromShip = warp;
@ -660,9 +669,6 @@ namespace NewHorizons
IsChangingStarSystem = true;
WearingSuit = PlayerState.IsWearingSuit();
// We kill them so they don't move as much
Locator.GetDeathManager().KillPlayer(DeathType.Meditation);
OWScene sceneToLoad;
if (newStarSystem == "EyeOfTheUniverse")
@ -680,12 +686,15 @@ namespace NewHorizons
_currentStarSystem = newStarSystem;
// Freeze player inputs
OWInput.ChangeInputMode(InputMode.None);
LoadManager.LoadSceneAsync(sceneToLoad, !vessel, LoadManager.FadeType.ToBlack, 0.1f, true);
}
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 the override is a valid system then we go there

View File

@ -7,12 +7,15 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using UnityEngine;
using UnityEngine.Events;
using Logger = NewHorizons.Utility.Logger;
namespace NewHorizons
{
public class NewHorizonsApi : INewHorizons
{
[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;
}
public string GetCurrentStarSystem()
{
return Main.Instance.CurrentStarSystem;
}
public UnityEvent<string> GetChangeStarSystemEvent()
{
return Main.Instance.OnChangeStarSystem;
}
public UnityEvent<string> GetStarSystemLoadedEvent()
{
return Main.Instance.OnStarSystemLoaded;
}
public string GetCurrentStarSystem() => Main.Instance.CurrentStarSystem;
public UnityEvent<string> GetChangeStarSystemEvent() => Main.Instance.OnChangeStarSystem;
public UnityEvent<string> GetStarSystemLoadedEvent() => Main.Instance.OnStarSystemLoaded;
public UnityEvent<string> GetBodyLoadedEvent() => Main.Instance.OnPlanetLoaded;
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,
float scale, bool alignWithNormal)
{

View File

@ -47,7 +47,7 @@ namespace NewHorizons.OtherMods.OWRichPresence
var localizedName = TranslationHandler.GetTranslation(name, TranslationHandler.TextType.UI);
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)

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",
"$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": {
"type": "string",
"description": "The schema to validate with"
@ -925,13 +936,6 @@
"$ref": "#/definitions/RaftInfo"
}
},
"reveal": {
"type": "array",
"description": "Add triggers that reveal parts of the ship log on this planet",
"items": {
"$ref": "#/definitions/RevealInfo"
}
},
"scatter": {
"type": "array",
"description": "Scatter props around this planet's surface",
@ -974,13 +978,6 @@
"$ref": "#/definitions/SingularityModule"
}
},
"audioVolumes": {
"type": "array",
"description": "Add audio volumes to this planet",
"items": {
"$ref": "#/definitions/AudioVolumeInfo"
}
},
"signals": {
"type": "array",
"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": {
"type": "object",
"additionalProperties": false,
@ -1801,70 +1743,6 @@
"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": {
"type": "object",
"additionalProperties": false,
@ -2503,6 +2381,296 @@
"$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": {

View File

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

View File

@ -69,7 +69,13 @@ namespace NewHorizons.Utility.DebugMenu
PauseMenuInitHook();
Main.Instance.OnChangeStarSystem.AddListener((string s) => SaveLoadedConfigsForRecentSystem());
Main.Instance.OnChangeStarSystem.AddListener((string s) => {
if (saveButtonUnlocked)
{
SaveLoadedConfigsForRecentSystem();
saveButtonUnlocked = false;
}
});
}
else
{
@ -227,6 +233,24 @@ namespace NewHorizons.Utility.DebugMenu
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
{
Logger.Log($"Saving... {relativePath} to {filePath}");
@ -240,19 +264,6 @@ namespace NewHorizons.Utility.DebugMenu
{
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 string UniqueID;
public string RelativePath;
public SpawnModule Spawn = null;
public SpawnPoint SpawnPoint = null;
public StarSystemConfig Config;
public IModBehaviour Mod;
public NewHorizonsSystem(string uniqueID, StarSystemConfig config, IModBehaviour mod)
public NewHorizonsSystem(string uniqueID, StarSystemConfig config, string relativePath, IModBehaviour mod)
{
UniqueID = uniqueID;
Config = config;
RelativePath = relativePath;
Mod = mod;
}
}

View File

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

View File

@ -78,16 +78,29 @@ public static class SchemaExporter
{"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.Definitions["NomaiCoordinates"].Properties["y"].UniqueItems = true;
schema.Definitions["NomaiCoordinates"].Properties["z"].UniqueItems = true;
schema.Properties["extras"] = new JsonSchemaProperty {
Type = JsonObjectType.Object,
Description = "Extra data that may be used by extension mods",
AllowAdditionalProperties = true,
AdditionalPropertiesSchema = new JsonSchema
{
Type = JsonObjectType.Object
}
};
}
return schema;

View File

@ -9,21 +9,99 @@ First create the following interface in your mod:
```cs
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();
/// <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> GetChangeStarSystemEvent();
/// <summary>
/// Retrieve the root GameObject of a custom planet made by creating configs.
/// Will only work if the planet has been created (see GetStarSystemLoadedEvent)
/// </summary>
GameObject GetPlanet(string name);
UnityEvent<string> GetStarSystemLoadedEvent();
/// <summary>
/// The name of the current star system loaded.
/// </summary>
string GetCurrentStarSystem();
GameObject SpawnObject(GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles, float scale, bool alignWithNormal);
/// <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();
string[] GetInstalledAddons();
}
/// <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:
@ -33,7 +111,7 @@ public class MyMod : ModBehaviour
{
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"}!");
```