mirror of
https://github.com/Outer-Wilds-New-Horizons/new-horizons.git
synced 2025-12-11 20:15:44 +01:00
Title Screen config (#1028)
## Major features - Adds title screen configuration (fixes #1027) Todo list: - [x] menuColor - [x] factRequiredForTitle - [x] conditionRequiredForTitle - [x] skyBox - [x] Music - [x] rotationSpeed - [x] menuPlanet - [x] mergeWithOtherTitles - [x] title screen handler api method - [x] Docs - [x] Multiple title screens for one mod 
This commit is contained in:
commit
144421ca51
BIN
NewHorizons/Assets/textures/MENU_OuterWildsLogo_d.png
Normal file
BIN
NewHorizons/Assets/textures/MENU_OuterWildsLogo_d.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
@ -210,6 +210,7 @@ namespace NewHorizons.Builder.Body
|
|||||||
{
|
{
|
||||||
foreach (var detailInfo in body.Config.Props.proxyDetails)
|
foreach (var detailInfo in body.Config.Props.proxyDetails)
|
||||||
{
|
{
|
||||||
|
// Thought about switching these to SimplifiedDetailInfo but we use AlignRadial with these so we can't
|
||||||
DetailBuilder.Make(proxy, null, body.Mod, detailInfo);
|
DetailBuilder.Make(proxy, null, body.Mod, detailInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
using NewHorizons.External.Configs;
|
using NewHorizons.External.Modules;
|
||||||
using NewHorizons.Utility;
|
using NewHorizons.Utility;
|
||||||
using NewHorizons.Utility.Files;
|
using NewHorizons.Utility.Files;
|
||||||
using NewHorizons.Utility.OWML;
|
using NewHorizons.Utility.OWML;
|
||||||
@ -13,13 +13,13 @@ namespace NewHorizons.Builder.StarSystem
|
|||||||
{
|
{
|
||||||
private static readonly Shader _unlitShader = Shader.Find("Unlit/Texture");
|
private static readonly Shader _unlitShader = Shader.Find("Unlit/Texture");
|
||||||
|
|
||||||
public static void Make(StarSystemConfig.SkyboxModule module, IModBehaviour mod)
|
public static void Make(SkyboxModule module, IModBehaviour mod)
|
||||||
{
|
{
|
||||||
NHLogger.Log("Building Skybox");
|
NHLogger.Log("Building Skybox");
|
||||||
BuildSkySphere(module, mod);
|
BuildSkySphere(module, mod);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GameObject BuildSkySphere(StarSystemConfig.SkyboxModule module, IModBehaviour mod)
|
public static GameObject BuildSkySphere(SkyboxModule module, IModBehaviour mod)
|
||||||
{
|
{
|
||||||
var skybox = SearchUtilities.Find("Skybox");
|
var skybox = SearchUtilities.Find("Skybox");
|
||||||
|
|
||||||
|
|||||||
45
NewHorizons/External/Configs/StarSystemConfig.cs
vendored
45
NewHorizons/External/Configs/StarSystemConfig.cs
vendored
@ -181,51 +181,6 @@ namespace NewHorizons.External.Configs
|
|||||||
public int[] z;
|
public int[] z;
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonObject]
|
|
||||||
public class SkyboxModule
|
|
||||||
{
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether to destroy the star field around the player
|
|
||||||
/// </summary>
|
|
||||||
public bool destroyStarField;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether to use a cube for the skybox instead of a smooth sphere
|
|
||||||
/// </summary>
|
|
||||||
public bool useCube;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Relative filepath to the texture to use for the skybox's positive X direction
|
|
||||||
/// </summary>
|
|
||||||
public string rightPath;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Relative filepath to the texture to use for the skybox's negative X direction
|
|
||||||
/// </summary>
|
|
||||||
public string leftPath;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Relative filepath to the texture to use for the skybox's positive Y direction
|
|
||||||
/// </summary>
|
|
||||||
public string topPath;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Relative filepath to the texture to use for the skybox's negative Y direction
|
|
||||||
/// </summary>
|
|
||||||
public string bottomPath;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Relative filepath to the texture to use for the skybox's positive Z direction
|
|
||||||
/// </summary>
|
|
||||||
public string frontPath;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Relative filepath to the texture to use for the skybox's negative Z direction
|
|
||||||
/// </summary>
|
|
||||||
public string backPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonObject]
|
[JsonObject]
|
||||||
public class GlobalMusicModule
|
public class GlobalMusicModule
|
||||||
{
|
{
|
||||||
|
|||||||
119
NewHorizons/External/Configs/TitleScreenConfig.cs
vendored
Normal file
119
NewHorizons/External/Configs/TitleScreenConfig.cs
vendored
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
using NewHorizons.External.Modules;
|
||||||
|
using NewHorizons.External.Modules.Props;
|
||||||
|
using NewHorizons.External.SerializableData;
|
||||||
|
using NewHorizons.Handlers;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace NewHorizons.External.Configs
|
||||||
|
{
|
||||||
|
[JsonObject]
|
||||||
|
public class TitleScreenConfig
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Create title screens
|
||||||
|
/// </summary>
|
||||||
|
public TitleScreenInfo[] titleScreens = new TitleScreenInfo[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonObject]
|
||||||
|
public class TitleScreenInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Colour of the text on the main menu
|
||||||
|
/// </summary>
|
||||||
|
public MColor menuTextTint;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ship log fact required for this title screen to appear.
|
||||||
|
/// </summary>
|
||||||
|
public string factRequiredForTitle;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Persistent condition required for this title screen to appear.
|
||||||
|
/// </summary>
|
||||||
|
public string persistentConditionRequiredForTitle;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If set to true, NH generated planets will not show on the title screen. If false, this title screen has the same chance as other NH planet title screens to show.
|
||||||
|
/// </summary>
|
||||||
|
public bool disableNHPlanets = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If set to true, this custom title screen will merge with all other custom title screens with shareTitleScreen set to true. If false, NH will randomly select between this and other valid title screens that are loaded.
|
||||||
|
/// </summary>
|
||||||
|
public bool shareTitleScreen = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Customize the skybox for this title screen
|
||||||
|
/// </summary>
|
||||||
|
public SkyboxModule Skybox;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The music audio that will play on the title screen. Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list.
|
||||||
|
/// </summary>
|
||||||
|
public string music;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ambience audio that will play on the title screen. Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list.
|
||||||
|
/// </summary>
|
||||||
|
public string ambience;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Edit properties of the background
|
||||||
|
/// </summary>
|
||||||
|
public BackgroundModule Background;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Edit properties of the main menu planet
|
||||||
|
/// </summary>
|
||||||
|
public MenuPlanetModule MenuPlanet;
|
||||||
|
|
||||||
|
[JsonObject]
|
||||||
|
public class BackgroundModule
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Changes the speed the background rotates (and by extension the main menu planet). This is in degrees per second.
|
||||||
|
/// </summary>
|
||||||
|
public float rotationSpeed = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disables the renderers of objects at the provided paths
|
||||||
|
/// </summary>
|
||||||
|
public string[] removeChildren;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A list of DetailInfos to populate the background with.
|
||||||
|
/// </summary>
|
||||||
|
public SimplifiedDetailInfo[] details;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonObject]
|
||||||
|
public class MenuPlanetModule
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Disables the renderers of the main menu planet and all objects on it (this is to improve compatibility with other mods that don't use the NH title screen json).
|
||||||
|
/// </summary>
|
||||||
|
public bool destroyMenuPlanet = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disables the renderers of objects at the provided paths
|
||||||
|
/// </summary>
|
||||||
|
public string[] removeChildren;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A list of DetailInfos to populate the main menu planet with.
|
||||||
|
/// </summary>
|
||||||
|
public SimplifiedDetailInfo[] details;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Changes the speed the main menu planet. This is in degrees per second.
|
||||||
|
/// </summary>
|
||||||
|
public float rotationSpeed = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extra data that may be used by extension mods
|
||||||
|
/// </summary>
|
||||||
|
public object extras;
|
||||||
|
}
|
||||||
|
}
|
||||||
26
NewHorizons/External/Modules/Props/DetailInfo.cs
vendored
26
NewHorizons/External/Modules/Props/DetailInfo.cs
vendored
@ -6,12 +6,15 @@ using System.ComponentModel;
|
|||||||
|
|
||||||
namespace NewHorizons.External.Modules.Props
|
namespace NewHorizons.External.Modules.Props
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A lesser form of DetailInfo used for the title screen since that supports fewer features
|
||||||
|
/// </summary>
|
||||||
[JsonObject]
|
[JsonObject]
|
||||||
public class DetailInfo : GeneralPropInfo
|
public class SimplifiedDetailInfo : GeneralPropInfo
|
||||||
{
|
{
|
||||||
public DetailInfo() { }
|
public SimplifiedDetailInfo() { }
|
||||||
|
|
||||||
public DetailInfo(GeneralPointPropInfo info)
|
public SimplifiedDetailInfo(GeneralPointPropInfo info)
|
||||||
{
|
{
|
||||||
JsonConvert.PopulateObject(JsonConvert.SerializeObject(info), this);
|
JsonConvert.PopulateObject(JsonConvert.SerializeObject(info), this);
|
||||||
}
|
}
|
||||||
@ -47,6 +50,23 @@ namespace NewHorizons.External.Modules.Props
|
|||||||
/// Scale each axis of the prop. Overrides `scale`.
|
/// Scale each axis of the prop. Overrides `scale`.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MVector3 stretch;
|
public MVector3 stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonObject]
|
||||||
|
public class DetailInfo : SimplifiedDetailInfo
|
||||||
|
{
|
||||||
|
public DetailInfo() { }
|
||||||
|
|
||||||
|
public DetailInfo(GeneralPointPropInfo info)
|
||||||
|
{
|
||||||
|
JsonConvert.PopulateObject(JsonConvert.SerializeObject(info), this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DetailInfo(SimplifiedDetailInfo info)
|
||||||
|
{
|
||||||
|
keepLoaded = true;
|
||||||
|
JsonConvert.PopulateObject(JsonConvert.SerializeObject(info), this);
|
||||||
|
}
|
||||||
|
|
||||||
[Obsolete("Use QuantumDetailInfo")]
|
[Obsolete("Use QuantumDetailInfo")]
|
||||||
public string quantumGroupID;
|
public string quantumGroupID;
|
||||||
|
|||||||
@ -14,7 +14,7 @@ namespace NewHorizons.External.Modules.Props.EchoesOfTheEye
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The type of dream candle this is.
|
/// The type of dream candle this is.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DefaultValue(DreamCandleType.Ground)] public DreamCandleType type = DreamCandleType.Ground;
|
[DefaultValue("ground")] public DreamCandleType type = DreamCandleType.Ground;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the candle should start lit or extinguished.
|
/// Whether the candle should start lit or extinguished.
|
||||||
|
|||||||
48
NewHorizons/External/Modules/SkyboxModule.cs
vendored
Normal file
48
NewHorizons/External/Modules/SkyboxModule.cs
vendored
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace NewHorizons.External.Modules
|
||||||
|
{
|
||||||
|
[JsonObject]
|
||||||
|
public class SkyboxModule
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Whether to destroy the star field around the player
|
||||||
|
/// </summary>
|
||||||
|
public bool destroyStarField;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether to use a cube for the skybox instead of a smooth sphere
|
||||||
|
/// </summary>
|
||||||
|
public bool useCube;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Relative filepath to the texture to use for the skybox's positive X direction
|
||||||
|
/// </summary>
|
||||||
|
public string rightPath;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Relative filepath to the texture to use for the skybox's negative X direction
|
||||||
|
/// </summary>
|
||||||
|
public string leftPath;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Relative filepath to the texture to use for the skybox's positive Y direction
|
||||||
|
/// </summary>
|
||||||
|
public string topPath;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Relative filepath to the texture to use for the skybox's negative Y direction
|
||||||
|
/// </summary>
|
||||||
|
public string bottomPath;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Relative filepath to the texture to use for the skybox's positive Z direction
|
||||||
|
/// </summary>
|
||||||
|
public string frontPath;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Relative filepath to the texture to use for the skybox's negative Z direction
|
||||||
|
/// </summary>
|
||||||
|
public string backPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -15,14 +15,15 @@ namespace NewHorizons.Handlers
|
|||||||
{
|
{
|
||||||
private static Dictionary<string, AudioType> _customAudioTypes;
|
private static Dictionary<string, AudioType> _customAudioTypes;
|
||||||
private static List<AudioLibrary.AudioEntry> _audioEntries;
|
private static List<AudioLibrary.AudioEntry> _audioEntries;
|
||||||
|
private static bool _postInitialized = false;
|
||||||
|
|
||||||
public static void Init()
|
public static void Init()
|
||||||
{
|
{
|
||||||
_customAudioTypes = new Dictionary<string, AudioType>();
|
_customAudioTypes = new Dictionary<string, AudioType>();
|
||||||
_audioEntries = new List<AudioLibrary.AudioEntry>();
|
_audioEntries = new List<AudioLibrary.AudioEntry>();
|
||||||
|
_postInitialized = false;
|
||||||
|
|
||||||
Delay.RunWhen(
|
Delay.RunWhen(() => Locator.GetAudioManager()?._libraryAsset != null && Locator.GetAudioManager()?._audioLibraryDict != null,
|
||||||
() => Locator.GetAudioManager()?._libraryAsset != null,
|
|
||||||
PostInit
|
PostInit
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -30,7 +31,12 @@ namespace NewHorizons.Handlers
|
|||||||
private static void PostInit()
|
private static void PostInit()
|
||||||
{
|
{
|
||||||
NHLogger.LogVerbose($"Adding all custom AudioTypes to the library");
|
NHLogger.LogVerbose($"Adding all custom AudioTypes to the library");
|
||||||
|
_postInitialized = true;
|
||||||
|
ModifyAudioLibrary();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ModifyAudioLibrary()
|
||||||
|
{
|
||||||
var library = Locator.GetAudioManager()._libraryAsset;
|
var library = Locator.GetAudioManager()._libraryAsset;
|
||||||
var audioEntries = library.audioEntries; // store previous array
|
var audioEntries = library.audioEntries; // store previous array
|
||||||
library.audioEntries = library.audioEntries.Concat(_audioEntries).ToArray(); // concat custom entries
|
library.audioEntries = library.audioEntries.Concat(_audioEntries).ToArray(); // concat custom entries
|
||||||
@ -88,6 +94,8 @@ namespace NewHorizons.Handlers
|
|||||||
_audioEntries.Add(new AudioLibrary.AudioEntry(audioType, audioClips));
|
_audioEntries.Add(new AudioLibrary.AudioEntry(audioType, audioClips));
|
||||||
_customAudioTypes.Add(id, audioType);
|
_customAudioTypes.Add(id, audioType);
|
||||||
|
|
||||||
|
if (_postInitialized) ModifyAudioLibrary();
|
||||||
|
|
||||||
return audioType;
|
return audioType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,16 +1,125 @@
|
|||||||
using NewHorizons.Builder.Body;
|
using NewHorizons.Builder.Body;
|
||||||
|
using NewHorizons.Builder.Props;
|
||||||
|
using NewHorizons.Builder.StarSystem;
|
||||||
using NewHorizons.External;
|
using NewHorizons.External;
|
||||||
|
using NewHorizons.External.Configs;
|
||||||
using NewHorizons.External.Modules;
|
using NewHorizons.External.Modules;
|
||||||
|
using NewHorizons.External.Modules.Props;
|
||||||
|
using NewHorizons.Handlers.TitleScreen;
|
||||||
using NewHorizons.Utility;
|
using NewHorizons.Utility;
|
||||||
using NewHorizons.Utility.OWML;
|
using NewHorizons.Utility.OWML;
|
||||||
|
using OWML.Common;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.SceneManagement;
|
||||||
|
using Color = UnityEngine.Color;
|
||||||
|
|
||||||
namespace NewHorizons.Handlers
|
namespace NewHorizons.Handlers
|
||||||
{
|
{
|
||||||
public static class TitleSceneHandler
|
public static class TitleSceneHandler
|
||||||
{
|
{
|
||||||
|
internal static Dictionary<IModBehaviour, TitleScreenBuilderList> TitleScreenBuilders = new();
|
||||||
|
internal static NewHorizonsBody[] eligibleBodies => Main.BodyDict.Values.ToList().SelectMany(x => x).ToList()
|
||||||
|
.Where(b => (b.Config.HeightMap != null || b.Config.Atmosphere?.clouds != null) && b.Config.Star == null && b.Config.canShowOnTitle).ToArray();
|
||||||
|
internal static int eligibleCount => eligibleBodies.Count();
|
||||||
|
internal static bool reloaded = false;
|
||||||
|
internal static bool reopenProfile = false;
|
||||||
|
|
||||||
|
public static void Init()
|
||||||
|
{
|
||||||
|
var scene = SearchUtilities.Find("Scene");
|
||||||
|
var background = SearchUtilities.Find("Scene/Background");
|
||||||
|
var planetPivot = SearchUtilities.Find("Scene/Background/PlanetPivot");
|
||||||
|
|
||||||
|
// Add fake sectors for ocean water component support
|
||||||
|
scene.AddComponent<FakeSector>();
|
||||||
|
background.AddComponent<FakeSector>();
|
||||||
|
planetPivot.AddComponent<FakeSector>();
|
||||||
|
|
||||||
|
// parent ambient light and campfire to the root (idk why they aren't parented in the first place mobius)
|
||||||
|
var planetRoot = SearchUtilities.Find("Scene/Background/PlanetPivot/PlanetRoot");
|
||||||
|
var campfire = SearchUtilities.Find("Scene/Background/PlanetPivot/Prefab_HEA_Campfire");
|
||||||
|
campfire.transform.SetParent(planetRoot.transform, true);
|
||||||
|
var ambientLight = SearchUtilities.Find("Scene/Background/PlanetPivot/AmbientLight_CaveTwin");
|
||||||
|
ambientLight.transform.SetParent(planetRoot.transform, true);
|
||||||
|
|
||||||
|
InitSubtitles();
|
||||||
|
TitleScreenColourHandler.ResetColour(); // reset color at the start
|
||||||
|
AudioTypeHandler.Init(); // init audio for custom music
|
||||||
|
|
||||||
|
// Load player data for fact and persistent condition checking
|
||||||
|
var profileManager = StandaloneProfileManager.SharedInstance;
|
||||||
|
profileManager.OnProfileSignInComplete += OnProfileSignInComplete;
|
||||||
|
profileManager.PreInitialize();
|
||||||
|
profileManager.Initialize();
|
||||||
|
if (profileManager.currentProfile != null) // check if there is even a profile made yet
|
||||||
|
PlayerData.Init(profileManager.currentProfileGameSave,
|
||||||
|
profileManager.currentProfileGameSettings,
|
||||||
|
profileManager.currentProfileGraphicsSettings,
|
||||||
|
profileManager.currentProfileInputJSON);
|
||||||
|
|
||||||
|
// Grab configs and handlers and merge them into one list
|
||||||
|
var validBuilders = TitleScreenBuilders.Values
|
||||||
|
.Where(list => list.IsValid)
|
||||||
|
.Select(list => list.GetRelevantBuilder()).ToList();
|
||||||
|
|
||||||
|
var hasNHPlanets = eligibleCount != 0;
|
||||||
|
|
||||||
|
// Get random index for the main builder
|
||||||
|
var index = UnityEngine.Random.Range(0, validBuilders.Count());
|
||||||
|
var randomBuilder = validBuilders.ElementAtOrDefault(index);
|
||||||
|
if (randomBuilder != null)
|
||||||
|
{
|
||||||
|
validBuilders.RemoveAt(index);
|
||||||
|
|
||||||
|
// display nh planets if not disabled
|
||||||
|
if (!randomBuilder.DisableNHPlanets)
|
||||||
|
{
|
||||||
|
DisplayBodiesOnTitleScreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
// if it can share build extras
|
||||||
|
if (randomBuilder.CanShare)
|
||||||
|
{
|
||||||
|
// only build the ones that also can share and have the same value for disabling nh planet (if there is any nh planets)
|
||||||
|
foreach (var builder in validBuilders.Where(builder => builder.CanShare && (hasNHPlanets ? builder.DisableNHPlanets == randomBuilder.DisableNHPlanets : true)))
|
||||||
|
{
|
||||||
|
builder.Build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build main one last so it overrides the extras
|
||||||
|
randomBuilder.Build();
|
||||||
|
}
|
||||||
|
// default to displaying nh planets if no title screen builders
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DisplayBodiesOnTitleScreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Main.Instance.OnAllTitleScreensLoaded?.Invoke();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
NHLogger.LogError($"Error in event handler for OnAllTitleScreensLoaded: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnProfileSignInComplete(ProfileManagerSignInResult result)
|
||||||
|
{
|
||||||
|
NHLogger.LogVerbose($"OnProfileSignInComplete {result}: {StandaloneProfileManager.SharedInstance.currentProfile.profileName}");
|
||||||
|
reloaded = true;
|
||||||
|
reopenProfile = true;
|
||||||
|
|
||||||
|
// Taken and modified from SubmitActionLoadScene.ConfirmSubmit
|
||||||
|
LoadManager.LoadScene(OWScene.TitleScreen);
|
||||||
|
Locator.GetMenuInputModule().DisableInputs();
|
||||||
|
}
|
||||||
|
|
||||||
public static void InitSubtitles()
|
public static void InitSubtitles()
|
||||||
{
|
{
|
||||||
GameObject subtitleContainer = SearchUtilities.Find("TitleMenu/TitleCanvas/TitleLayoutGroup/Logo_EchoesOfTheEye");
|
GameObject subtitleContainer = SearchUtilities.Find("TitleMenu/TitleCanvas/TitleLayoutGroup/Logo_EchoesOfTheEye");
|
||||||
@ -25,73 +134,184 @@ namespace NewHorizons.Handlers
|
|||||||
subtitleContainer.AddComponent<SubtitlesHandler>();
|
subtitleContainer.AddComponent<SubtitlesHandler>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void DisplayBodyOnTitleScreen(List<NewHorizonsBody> bodies)
|
public static void BuildConfig(IModBehaviour mod, TitleScreenInfo config)
|
||||||
{
|
{
|
||||||
// Try loading one planet why not
|
if (config.menuTextTint != null)
|
||||||
// var eligible = BodyDict.Values.ToList().SelectMany(x => x).ToList().Where(b => (b.Config.HeightMap != null || b.Config.Atmosphere?.Cloud != null) && b.Config.Star == null).ToArray();
|
|
||||||
var eligible = bodies.Where(b => (b.Config.HeightMap != null || b.Config.Atmosphere?.clouds != null) && b.Config.Star == null && b.Config.canShowOnTitle).ToArray();
|
|
||||||
var eligibleCount = eligible.Count();
|
|
||||||
if (eligibleCount == 0) return;
|
|
||||||
|
|
||||||
var selectionCount = Mathf.Min(eligibleCount, 3);
|
|
||||||
var indices = RandomUtility.GetUniqueRandomArray(0, eligible.Count(), selectionCount);
|
|
||||||
|
|
||||||
NHLogger.LogVerbose($"Displaying {selectionCount} bodies on the title screen");
|
|
||||||
|
|
||||||
var planetSizes = new List<(GameObject planet, float size)>();
|
|
||||||
|
|
||||||
var bodyInfo = LoadTitleScreenBody(eligible[indices[0]]);
|
|
||||||
bodyInfo.planet.transform.localRotation = Quaternion.Euler(15, 0, 0);
|
|
||||||
planetSizes.Add(bodyInfo);
|
|
||||||
|
|
||||||
if (selectionCount > 1)
|
|
||||||
{
|
{
|
||||||
bodyInfo.planet.transform.localPosition = new Vector3(0, -15, 0);
|
TitleScreenColourHandler.SetColour(config.menuTextTint.ToColor());
|
||||||
bodyInfo.planet.transform.localRotation = Quaternion.Euler(10f, 0f, 0f);
|
|
||||||
|
|
||||||
var bodyInfo2 = LoadTitleScreenBody(eligible[indices[1]]);
|
|
||||||
bodyInfo2.planet.transform.localPosition = new Vector3(7, 30, 0);
|
|
||||||
bodyInfo2.planet.transform.localRotation = Quaternion.Euler(10f, 0f, 0f);
|
|
||||||
planetSizes.Add(bodyInfo2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectionCount > 2)
|
if (config.Skybox?.destroyStarField ?? false)
|
||||||
{
|
{
|
||||||
var bodyInfo3 = LoadTitleScreenBody(eligible[indices[2]]);
|
UnityEngine.Object.Destroy(SearchUtilities.Find("Skybox/Starfield"));
|
||||||
bodyInfo3.planet.transform.localPosition = new Vector3(-5, 10, 0);
|
|
||||||
bodyInfo3.planet.transform.localRotation = Quaternion.Euler(10f, 0f, 0f);
|
|
||||||
planetSizes.Add(bodyInfo3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SearchUtilities.Find("Scene/Background/PlanetPivot/Prefab_HEA_Campfire").SetActive(false);
|
if (config.Skybox?.rightPath != null ||
|
||||||
SearchUtilities.Find("Scene/Background/PlanetPivot/PlanetRoot").SetActive(false);
|
config.Skybox?.leftPath != null ||
|
||||||
|
config.Skybox?.topPath != null ||
|
||||||
var lightGO = new GameObject("Light");
|
config.Skybox?.bottomPath != null ||
|
||||||
lightGO.transform.parent = SearchUtilities.Find("Scene/Background").transform;
|
config.Skybox?.frontPath != null ||
|
||||||
lightGO.transform.localPosition = new Vector3(-47.9203f, 145.7596f, 43.1802f);
|
config.Skybox?.bottomPath != null)
|
||||||
lightGO.transform.localRotation = Quaternion.Euler(13.1412f, 122.8785f, 169.4302f);
|
|
||||||
var light = lightGO.AddComponent<Light>();
|
|
||||||
light.type = LightType.Directional;
|
|
||||||
light.color = Color.white;
|
|
||||||
light.range = float.PositiveInfinity;
|
|
||||||
light.intensity = 0.8f;
|
|
||||||
|
|
||||||
// Resize planets relative to each other
|
|
||||||
// If there are multiple planets shrink them down to 30% of the size
|
|
||||||
var maxSize = planetSizes.Select(x => x.size).Max();
|
|
||||||
var minSize = planetSizes.Select(x => x.size).Min();
|
|
||||||
var multiplePlanets = planetSizes.Count > 1;
|
|
||||||
foreach (var (planet, size) in planetSizes)
|
|
||||||
{
|
{
|
||||||
var adjustedSize = size / maxSize;
|
SkyboxBuilder.Make(config.Skybox, mod);
|
||||||
// If some planets would be too small we'll do a funny thing
|
}
|
||||||
if (minSize / maxSize < 0.3f)
|
|
||||||
|
if (!string.IsNullOrEmpty(config.music))
|
||||||
|
{
|
||||||
|
var musicSource = SearchUtilities.Find("Scene/AudioSource_Music").GetComponent<OWAudioSource>();
|
||||||
|
var audioType = AudioTypeHandler.GetAudioType(config.music, mod);
|
||||||
|
Delay.FireOnNextUpdate(() => musicSource.AssignAudioLibraryClip(audioType));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(config.ambience))
|
||||||
|
{
|
||||||
|
var ambienceSource = SearchUtilities.Find("Scene/AudioSource_Ambience").GetComponent<OWAudioSource>();
|
||||||
|
var audioType = AudioTypeHandler.GetAudioType(config.ambience, mod);
|
||||||
|
Delay.FireOnNextUpdate(() => ambienceSource.AssignAudioLibraryClip(audioType));
|
||||||
|
}
|
||||||
|
|
||||||
|
var background = SearchUtilities.Find("Scene/Background");
|
||||||
|
var menuPlanet = SearchUtilities.Find("Scene/Background/PlanetPivot");
|
||||||
|
|
||||||
|
if (config.Background != null)
|
||||||
|
{
|
||||||
|
if (config.Background.removeChildren != null)
|
||||||
{
|
{
|
||||||
var t = Mathf.InverseLerp(minSize, maxSize, size);
|
RemoveChildren(background, config.Background.removeChildren);
|
||||||
adjustedSize = Mathf.Lerp(0.3f, 1f, t * t);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
planet.transform.localScale *= adjustedSize * (multiplePlanets ? 0.3f : 1f);
|
if (config.Background.details != null)
|
||||||
|
{
|
||||||
|
foreach (var simplifiedDetail in config.Background.details)
|
||||||
|
{
|
||||||
|
DetailBuilder.Make(background, background.GetComponentInParent<Sector>(), mod, new DetailInfo(simplifiedDetail));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var rotator = background.GetComponent<RotateTransform>();
|
||||||
|
rotator._degreesPerSecond = config.Background.rotationSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.MenuPlanet != null)
|
||||||
|
{
|
||||||
|
if (config.MenuPlanet.removeChildren != null)
|
||||||
|
{
|
||||||
|
RemoveChildren(menuPlanet, config.MenuPlanet.removeChildren);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.MenuPlanet.details != null)
|
||||||
|
{
|
||||||
|
foreach (var simplifiedDetail in config.MenuPlanet.details)
|
||||||
|
{
|
||||||
|
DetailBuilder.Make(menuPlanet, menuPlanet.GetComponentInParent<Sector>(), mod, new DetailInfo(simplifiedDetail));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var rotator = menuPlanet.GetComponent<RotateTransform>();
|
||||||
|
rotator._localAxis = Vector3.up; // fix axis (because there is no reason for it to be negative when degrees were also negative)
|
||||||
|
rotator._degreesPerSecond = config.MenuPlanet.rotationSpeed;
|
||||||
|
|
||||||
|
if (config.MenuPlanet.destroyMenuPlanet)
|
||||||
|
{
|
||||||
|
SearchUtilities.Find("Scene/Background/PlanetPivot/PlanetRoot").SetActive(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RemoveChildren(GameObject go, string[] paths)
|
||||||
|
{
|
||||||
|
foreach (var childPath in paths)
|
||||||
|
{
|
||||||
|
var flag = true;
|
||||||
|
foreach (var childObj in go.transform.FindAll(childPath))
|
||||||
|
{
|
||||||
|
flag = false;
|
||||||
|
// idk why we wait here but we do
|
||||||
|
Delay.FireInNUpdates(() =>
|
||||||
|
{
|
||||||
|
if (childObj != null && childObj.gameObject != null)
|
||||||
|
{
|
||||||
|
childObj.gameObject.SetActive(false);
|
||||||
|
}
|
||||||
|
}, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flag) NHLogger.LogWarning($"Couldn't find \"{childPath}\".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DisplayBodiesOnTitleScreen()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Try loading one planet why not
|
||||||
|
var eligible = eligibleBodies;
|
||||||
|
var eligibleCount = eligible.Count();
|
||||||
|
if (eligibleCount == 0) return;
|
||||||
|
|
||||||
|
var selectionCount = Mathf.Min(eligibleCount, 3);
|
||||||
|
var indices = RandomUtility.GetUniqueRandomArray(0, eligible.Count(), selectionCount);
|
||||||
|
|
||||||
|
NHLogger.LogVerbose($"Displaying {selectionCount} bodies on the title screen");
|
||||||
|
|
||||||
|
var planetSizes = new List<(GameObject planet, float size)>();
|
||||||
|
|
||||||
|
var bodyInfo = LoadTitleScreenBody(eligible[indices[0]]);
|
||||||
|
bodyInfo.planet.transform.localRotation = Quaternion.Euler(15, 0, 0);
|
||||||
|
planetSizes.Add(bodyInfo);
|
||||||
|
|
||||||
|
if (selectionCount > 1)
|
||||||
|
{
|
||||||
|
bodyInfo.planet.transform.localPosition = new Vector3(0, -15, 0);
|
||||||
|
bodyInfo.planet.transform.localRotation = Quaternion.Euler(10f, 0f, 0f);
|
||||||
|
|
||||||
|
var bodyInfo2 = LoadTitleScreenBody(eligible[indices[1]]);
|
||||||
|
bodyInfo2.planet.transform.localPosition = new Vector3(7, 30, 0);
|
||||||
|
bodyInfo2.planet.transform.localRotation = Quaternion.Euler(10f, 0f, 0f);
|
||||||
|
planetSizes.Add(bodyInfo2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectionCount > 2)
|
||||||
|
{
|
||||||
|
var bodyInfo3 = LoadTitleScreenBody(eligible[indices[2]]);
|
||||||
|
bodyInfo3.planet.transform.localPosition = new Vector3(-5, 10, 0);
|
||||||
|
bodyInfo3.planet.transform.localRotation = Quaternion.Euler(10f, 0f, 0f);
|
||||||
|
planetSizes.Add(bodyInfo3);
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchUtilities.Find("Scene/Background/PlanetPivot/PlanetRoot").SetActive(false);
|
||||||
|
|
||||||
|
var lightGO = new GameObject("Light");
|
||||||
|
lightGO.transform.parent = SearchUtilities.Find("Scene/Background").transform;
|
||||||
|
lightGO.transform.localPosition = new Vector3(-47.9203f, 145.7596f, 43.1802f);
|
||||||
|
lightGO.transform.localRotation = Quaternion.Euler(13.1412f, 122.8785f, 169.4302f);
|
||||||
|
var light = lightGO.AddComponent<Light>();
|
||||||
|
light.type = LightType.Directional;
|
||||||
|
light.color = Color.white;
|
||||||
|
light.range = float.PositiveInfinity;
|
||||||
|
light.intensity = 0.8f;
|
||||||
|
|
||||||
|
// Resize planets relative to each other
|
||||||
|
// If there are multiple planets shrink them down to 30% of the size
|
||||||
|
var maxSize = planetSizes.Select(x => x.size).Max();
|
||||||
|
var minSize = planetSizes.Select(x => x.size).Min();
|
||||||
|
var multiplePlanets = planetSizes.Count > 1;
|
||||||
|
foreach (var (planet, size) in planetSizes)
|
||||||
|
{
|
||||||
|
var adjustedSize = size / maxSize;
|
||||||
|
// If some planets would be too small we'll do a funny thing
|
||||||
|
if (minSize / maxSize < 0.3f)
|
||||||
|
{
|
||||||
|
var t = Mathf.InverseLerp(minSize, maxSize, size);
|
||||||
|
adjustedSize = Mathf.Lerp(0.3f, 1f, t * t);
|
||||||
|
}
|
||||||
|
|
||||||
|
planet.transform.localScale *= adjustedSize * (multiplePlanets ? 0.3f : 1f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
NHLogger.LogError($"Failed to make title screen bodies: {e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,11 +373,11 @@ namespace NewHorizons.Handlers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var pivot = Object.Instantiate(SearchUtilities.Find("Scene/Background/PlanetPivot"), SearchUtilities.Find("Scene/Background").transform);
|
var pivot = UnityEngine.Object.Instantiate(SearchUtilities.Find("Scene/Background/PlanetPivot"), SearchUtilities.Find("Scene/Background").transform);
|
||||||
pivot.GetComponent<RotateTransform>()._degreesPerSecond = 10f;
|
pivot.GetComponent<RotateTransform>()._degreesPerSecond = 10f;
|
||||||
foreach (Transform child in pivot.transform)
|
foreach (Transform child in pivot.transform)
|
||||||
{
|
{
|
||||||
Object.Destroy(child.gameObject);
|
UnityEngine.Object.Destroy(child.gameObject);
|
||||||
}
|
}
|
||||||
pivot.name = "Pivot";
|
pivot.name = "Pivot";
|
||||||
|
|
||||||
@ -222,5 +442,195 @@ namespace NewHorizons.Handlers
|
|||||||
|
|
||||||
return meshRenderer;
|
return meshRenderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static void RegisterBuilder(IModBehaviour mod, ITitleScreenBuilder builder)
|
||||||
|
{
|
||||||
|
if (!TitleScreenBuilders.ContainsKey(mod))
|
||||||
|
TitleScreenBuilders.Add(mod, new TitleScreenBuilderList());
|
||||||
|
|
||||||
|
TitleScreenBuilders[mod].Add(builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RegisterBuilder(IModBehaviour mod, TitleScreenInfo config)
|
||||||
|
=> RegisterBuilder(mod,
|
||||||
|
new TitleScreenConfigBuilder(mod, config));
|
||||||
|
|
||||||
|
public static void RegisterBuilder(IModBehaviour mod, Action<GameObject> builder, bool disableNHPlanets, bool shareTitleScreen, string persistentConditionRequired, string factRequired)
|
||||||
|
=> RegisterBuilder(mod,
|
||||||
|
new TitleScreenBuilder(mod, builder,
|
||||||
|
disableNHPlanets, shareTitleScreen,
|
||||||
|
persistentConditionRequired, factRequired));
|
||||||
|
|
||||||
|
internal static void ResetConfigs()
|
||||||
|
{
|
||||||
|
foreach (var builderList in TitleScreenBuilders.Values)
|
||||||
|
{
|
||||||
|
builderList.list.RemoveAll(builder => builder is TitleScreenConfigBuilder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class TitleScreenBuilderList
|
||||||
|
{
|
||||||
|
public List<ITitleScreenBuilder> list = new List<ITitleScreenBuilder>();
|
||||||
|
|
||||||
|
public void Add(ITitleScreenBuilder builder)
|
||||||
|
{
|
||||||
|
list.Add(builder);
|
||||||
|
builder.Index = list.IndexOf(builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsValid => GetRelevantBuilder() != null;
|
||||||
|
|
||||||
|
public ITitleScreenBuilder GetRelevantBuilder()
|
||||||
|
{
|
||||||
|
return list.LastOrDefault(builder => builder.KnowsFact() && builder.HasCondition());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class TitleScreenBuilder : ITitleScreenBuilder
|
||||||
|
{
|
||||||
|
public IModBehaviour mod;
|
||||||
|
public Action<GameObject> builder;
|
||||||
|
public bool disableNHPlanets;
|
||||||
|
public bool shareTitleScreen;
|
||||||
|
public string persistentConditionRequired;
|
||||||
|
public string factRequired;
|
||||||
|
|
||||||
|
public TitleScreenBuilder(IModBehaviour mod, Action<GameObject> builder, bool disableNHPlanets, bool shareTitleScreen, string persistentConditionRequired, string factRequired)
|
||||||
|
{
|
||||||
|
this.mod = mod;
|
||||||
|
this.builder = builder;
|
||||||
|
this.disableNHPlanets = disableNHPlanets;
|
||||||
|
this.shareTitleScreen = shareTitleScreen;
|
||||||
|
this.persistentConditionRequired = persistentConditionRequired;
|
||||||
|
this.factRequired = factRequired;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Build()
|
||||||
|
{
|
||||||
|
NHLogger.LogVerbose($"Building handler {mod.ModHelper.Manifest.UniqueName} #{index}");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
builder.Invoke(SearchUtilities.Find("Scene"));
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
NHLogger.LogError($"Error while building title screen handler {mod.ModHelper.Manifest.UniqueName} #{index}: {e}");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Main.Instance.OnTitleScreenLoaded?.Invoke(mod.ModHelper.Manifest.UniqueName, index);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
NHLogger.LogError($"Error in event handler for OnTitleScreenLoaded on title screen {mod.ModHelper.Manifest.UniqueName} #{index}: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IModBehaviour Mod => mod;
|
||||||
|
|
||||||
|
public bool DisableNHPlanets => disableNHPlanets;
|
||||||
|
|
||||||
|
public bool CanShare => shareTitleScreen;
|
||||||
|
|
||||||
|
public bool KnowsFact() => string.IsNullOrEmpty(factRequired) || StandaloneProfileManager.SharedInstance.currentProfile != null && ShipLogHandler.KnowsFact(factRequired);
|
||||||
|
|
||||||
|
public bool HasCondition() => string.IsNullOrEmpty(persistentConditionRequired) || StandaloneProfileManager.SharedInstance.currentProfile != null && PlayerData.GetPersistentCondition(persistentConditionRequired);
|
||||||
|
|
||||||
|
private int index = -1;
|
||||||
|
public int Index { get => index; set => index = value; }
|
||||||
|
|
||||||
|
public override string ToString() => Mod.ModHelper.Manifest.UniqueName + " #" + Index;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class TitleScreenConfigBuilder : ITitleScreenBuilder
|
||||||
|
{
|
||||||
|
public IModBehaviour mod;
|
||||||
|
public TitleScreenInfo config;
|
||||||
|
|
||||||
|
public TitleScreenConfigBuilder(IModBehaviour mod, TitleScreenInfo config)
|
||||||
|
{
|
||||||
|
this.mod = mod;
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Build()
|
||||||
|
{
|
||||||
|
NHLogger.LogVerbose($"Building config {mod.ModHelper.Manifest.UniqueName} #{index}");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
BuildConfig(mod, config);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
NHLogger.LogError($"Error while building title screen config {mod.ModHelper.Manifest.UniqueName} #{index}: {e}");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Main.Instance.OnTitleScreenLoaded?.Invoke(mod.ModHelper.Manifest.UniqueName, index);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
NHLogger.LogError($"Error in event handler for OnTitleScreenLoaded on title screen {mod.ModHelper.Manifest.UniqueName} #{index}: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IModBehaviour Mod => mod;
|
||||||
|
|
||||||
|
public bool DisableNHPlanets => config.disableNHPlanets;
|
||||||
|
|
||||||
|
public bool CanShare => config.shareTitleScreen;
|
||||||
|
|
||||||
|
public bool KnowsFact() => string.IsNullOrEmpty(config.factRequiredForTitle) || StandaloneProfileManager.SharedInstance.currentProfile != null && ShipLogHandler.KnowsFact(config.factRequiredForTitle);
|
||||||
|
|
||||||
|
public bool HasCondition() => string.IsNullOrEmpty(config.persistentConditionRequiredForTitle) || StandaloneProfileManager.SharedInstance.currentProfile != null && PlayerData.GetPersistentCondition(config.persistentConditionRequiredForTitle);
|
||||||
|
|
||||||
|
private int index = -1;
|
||||||
|
public int Index { get => index; set => index = value; }
|
||||||
|
|
||||||
|
public override string ToString() => Mod.ModHelper.Manifest.UniqueName + " #" + Index;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal interface ITitleScreenBuilder
|
||||||
|
{
|
||||||
|
IModBehaviour Mod { get; }
|
||||||
|
|
||||||
|
bool DisableNHPlanets { get; }
|
||||||
|
|
||||||
|
bool CanShare { get; }
|
||||||
|
|
||||||
|
void Build();
|
||||||
|
bool KnowsFact();
|
||||||
|
bool HasCondition();
|
||||||
|
|
||||||
|
int Index { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// For water and etc (they require a sector or else they will get deleted by detail builder)
|
||||||
|
/// </summary>
|
||||||
|
private class FakeSector : Sector
|
||||||
|
{
|
||||||
|
public override void Awake()
|
||||||
|
{
|
||||||
|
_triggerRoot = gameObject;
|
||||||
|
_subsectors = new List<Sector>();
|
||||||
|
_occupantMask = DynamicOccupant.Player;
|
||||||
|
var parentSector = GetComponentsInParent<Sector>().FirstOrDefault(parentSector => parentSector != this);
|
||||||
|
if (parentSector != null)
|
||||||
|
{
|
||||||
|
_parentSector = parentSector;
|
||||||
|
_parentSector.AddSubsector(this);
|
||||||
|
}
|
||||||
|
SectorManager.RegisterSector(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
OnSectorOccupantsUpdated.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
79
NewHorizons/Handlers/TitleScreen/TitleScreenColourHandler.cs
Normal file
79
NewHorizons/Handlers/TitleScreen/TitleScreenColourHandler.cs
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
using HarmonyLib;
|
||||||
|
using NewHorizons.Utility;
|
||||||
|
using NewHorizons.Utility.Files;
|
||||||
|
using NewHorizons.Utility.OWML;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.SceneManagement;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
|
||||||
|
namespace NewHorizons.Handlers.TitleScreen
|
||||||
|
{
|
||||||
|
[HarmonyPatch]
|
||||||
|
public class TitleScreenColourHandler
|
||||||
|
{
|
||||||
|
public static void SetColour(Color colour)
|
||||||
|
{
|
||||||
|
NHLogger.LogVerbose("Setting title screen colour to " + colour.ToString());
|
||||||
|
colour.a = 1;
|
||||||
|
var buttons = GameObject.FindObjectOfType<TitleScreenManager>()._mainMenu.GetComponentsInChildren<Text>();
|
||||||
|
var footer = GameObject.Find("TitleMenu/TitleCanvas/FooterBlock").GetComponentsInChildren<Text>();
|
||||||
|
foreach (var button in buttons.Concat(footer))
|
||||||
|
{
|
||||||
|
button.color = colour;
|
||||||
|
}
|
||||||
|
_mainMenuColour = colour;
|
||||||
|
var logo = ImageUtilities.TintImage(ImageUtilities.GetTexture(Main.Instance, "Assets\\textures\\MENU_OuterWildsLogo_d.png"), (Color)_mainMenuColour);
|
||||||
|
var animRenderer = GameObject.FindObjectOfType<TitleAnimRenderer>();
|
||||||
|
var colouredLogoMaterial = GameObject.Instantiate(animRenderer._logoMaterial).Rename("MENU_OuterWildsLogoANIM_mat_Coloured");
|
||||||
|
colouredLogoMaterial.mainTexture = logo;
|
||||||
|
animRenderer._logoMaterial = colouredLogoMaterial;
|
||||||
|
animRenderer.Awake();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ResetColour()
|
||||||
|
{
|
||||||
|
_mainMenuColour = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch(typeof(UIStyleApplier), nameof(UIStyleApplier.ChangeColors))]
|
||||||
|
public static bool UIStyleApplier_ChangeColors(UIStyleApplier __instance, UIElementState state)
|
||||||
|
{
|
||||||
|
if (SceneManager.GetActiveScene().name == "TitleScreen" && _mainMenuColour is Color colour && __instance.transform.parent.name == "MainMenuLayoutGroup")
|
||||||
|
{
|
||||||
|
// Wyrm didn't say to account for any of these states I win!
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case UIElementState.INTERMEDIATELY_HIGHLIGHTED:
|
||||||
|
case UIElementState.HIGHLIGHTED:
|
||||||
|
case UIElementState.PRESSED:
|
||||||
|
case UIElementState.ROLLOVER_HIGHLIGHT:
|
||||||
|
Color.RGBToHSV(colour, out var h, out var s, out var v);
|
||||||
|
colour = Color.HSVToRGB(h, s * 0.2f, v * 1.2f);
|
||||||
|
break;
|
||||||
|
case UIElementState.DISABLED:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < __instance._foregroundGraphics.Length; i++)
|
||||||
|
{
|
||||||
|
__instance._foregroundGraphics[i].color = colour;
|
||||||
|
}
|
||||||
|
for (int j = 0; j < __instance._backgroundGraphics.Length; j++)
|
||||||
|
{
|
||||||
|
__instance._backgroundGraphics[j].color = colour;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Color? _mainMenuColour;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -73,6 +73,17 @@ namespace NewHorizons
|
|||||||
/// Gives the name of the planet that was just loaded.
|
/// Gives the name of the planet that was just loaded.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
UnityEvent<string> GetBodyLoadedEvent();
|
UnityEvent<string> GetBodyLoadedEvent();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event invoked when NH has finished building a title screen.
|
||||||
|
/// Gives the unique name of the mod the title screen builder was from and the index for when you have multiple title screens.
|
||||||
|
/// </summary>
|
||||||
|
UnityEvent<string, int> GetTitleScreenLoadedEvent();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event invoked when NH has finished building the title screen.
|
||||||
|
/// </summary>
|
||||||
|
UnityEvent GetAllTitleScreensLoadedEvent();
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Querying configs
|
#region Querying configs
|
||||||
@ -96,6 +107,16 @@ namespace NewHorizons
|
|||||||
///</summary>
|
///</summary>
|
||||||
T QuerySystem<T>(string path);
|
T QuerySystem<T>(string path);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Uses JSONPath to query a title screen config
|
||||||
|
/// </summary>
|
||||||
|
object QueryTitleScreen(Type outType, IModBehaviour mod, string path);
|
||||||
|
|
||||||
|
///<summary>
|
||||||
|
/// Uses JSONPath to query a title screen config
|
||||||
|
/// </summary>
|
||||||
|
T QueryTitleScreen<T>(IModBehaviour mod, string path);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Register your own builder that will act on the given GameObject by reading the json string of its "extras" module
|
/// Register your own builder that will act on the given GameObject by reading the json string of its "extras" module
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -222,5 +243,17 @@ namespace NewHorizons
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id"></param>
|
/// <param name="id"></param>
|
||||||
void SetNextSpawnID(string id);
|
void SetNextSpawnID(string id);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a builder for the main menu.
|
||||||
|
/// Call this once before the main menu finishes loading
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mod"></param>
|
||||||
|
/// <param name="builder">Builder to run when this title screen is selected. The GameObject passed through it is the main scene object containing both the background and menu planet.</param>
|
||||||
|
/// <param name="disableNHPlanets">If set to true, NH generated planets will not show on the title screen. If false, this title screen has the same chance as other NH planet title screens to show.</param>
|
||||||
|
/// <param name="shareTitleScreen">If set to true, this custom title screen will merge with all other custom title screens with shareTitleScreen set to true. If false, NH will randomly select between this and other valid title screens that are loaded.</param>
|
||||||
|
/// <param name="persistentConditionRequired">Persistent condition required for this title screen to appear.</param>
|
||||||
|
/// <param name="factRequired">Ship log fact required for this title screen to appear.</param>
|
||||||
|
void RegisterTitleScreenBuilder(IModBehaviour mod, Action<GameObject> builder, bool disableNHPlanets = true, bool shareTitleScreen = false, string persistentConditionRequired = null, string factRequired = null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -56,6 +56,7 @@ namespace NewHorizons
|
|||||||
public static Dictionary<string, List<NewHorizonsBody>> BodyDict = new();
|
public static Dictionary<string, List<NewHorizonsBody>> BodyDict = new();
|
||||||
public static List<IModBehaviour> MountedAddons = new();
|
public static List<IModBehaviour> MountedAddons = new();
|
||||||
public static Dictionary<IModBehaviour, AddonConfig> AddonConfigs = new();
|
public static Dictionary<IModBehaviour, AddonConfig> AddonConfigs = new();
|
||||||
|
public static Dictionary<IModBehaviour, TitleScreenConfig> TitleScreenConfigs = new();
|
||||||
|
|
||||||
public static float SecondsElapsedInLoop = -1;
|
public static float SecondsElapsedInLoop = -1;
|
||||||
|
|
||||||
@ -107,10 +108,13 @@ namespace NewHorizons
|
|||||||
public ShipWarpController ShipWarpController { get; private set; }
|
public ShipWarpController ShipWarpController { get; private set; }
|
||||||
|
|
||||||
// API events
|
// API events
|
||||||
public class StarSystemEvent : UnityEvent<string> { }
|
public class StringEvent : UnityEvent<string> { }
|
||||||
public StarSystemEvent OnChangeStarSystem = new();
|
public StringEvent OnChangeStarSystem = new();
|
||||||
public StarSystemEvent OnStarSystemLoaded = new();
|
public StringEvent OnStarSystemLoaded = new();
|
||||||
public StarSystemEvent OnPlanetLoaded = new();
|
public StringEvent OnPlanetLoaded = new();
|
||||||
|
public class StringIndexEvent : UnityEvent<string, int> { }
|
||||||
|
public StringIndexEvent OnTitleScreenLoaded = new();
|
||||||
|
public UnityEvent OnAllTitleScreensLoaded = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Depending on platform, the AsyncOwnershipStatus might not be ready by the time we go to check it.
|
/// Depending on platform, the AsyncOwnershipStatus might not be ready by the time we go to check it.
|
||||||
@ -161,7 +165,10 @@ namespace NewHorizons
|
|||||||
if (wasUsingCustomTitleScreen != CustomTitleScreen && SceneManager.GetActiveScene().name == "TitleScreen" && _wasConfigured)
|
if (wasUsingCustomTitleScreen != CustomTitleScreen && SceneManager.GetActiveScene().name == "TitleScreen" && _wasConfigured)
|
||||||
{
|
{
|
||||||
NHLogger.LogVerbose("Reloading");
|
NHLogger.LogVerbose("Reloading");
|
||||||
SceneManager.LoadScene("TitleScreen", LoadSceneMode.Single);
|
TitleSceneHandler.reloaded = true;
|
||||||
|
// Taken and modified from SubmitActionLoadScene.ConfirmSubmit
|
||||||
|
LoadManager.LoadScene(OWScene.TitleScreen);
|
||||||
|
Locator.GetMenuInputModule().DisableInputs();
|
||||||
}
|
}
|
||||||
|
|
||||||
_wasConfigured = true;
|
_wasConfigured = true;
|
||||||
@ -171,6 +178,9 @@ namespace NewHorizons
|
|||||||
{
|
{
|
||||||
BodyDict.Clear();
|
BodyDict.Clear();
|
||||||
SystemDict.Clear();
|
SystemDict.Clear();
|
||||||
|
TitleScreenConfigs.Clear();
|
||||||
|
|
||||||
|
TitleSceneHandler.ResetConfigs();
|
||||||
|
|
||||||
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
|
||||||
@ -423,15 +433,7 @@ namespace NewHorizons
|
|||||||
|
|
||||||
if (isTitleScreen && CustomTitleScreen)
|
if (isTitleScreen && CustomTitleScreen)
|
||||||
{
|
{
|
||||||
try
|
TitleSceneHandler.Init();
|
||||||
{
|
|
||||||
TitleSceneHandler.DisplayBodyOnTitleScreen(BodyDict.Values.ToList().SelectMany(x => x).ToList());
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
NHLogger.LogError($"Failed to make title screen bodies: {e}");
|
|
||||||
}
|
|
||||||
TitleSceneHandler.InitSubtitles();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EOTU fixes
|
// EOTU fixes
|
||||||
@ -795,6 +797,10 @@ namespace NewHorizons
|
|||||||
{
|
{
|
||||||
LoadAddonManifest("addon-manifest.json", mod);
|
LoadAddonManifest("addon-manifest.json", mod);
|
||||||
}
|
}
|
||||||
|
if (File.Exists(Path.Combine(folder, "title-screen.json")))
|
||||||
|
{
|
||||||
|
LoadTitleScreenConfig("title-screen.json", mod);
|
||||||
|
}
|
||||||
if (Directory.Exists(Path.Combine(folder, "translations")))
|
if (Directory.Exists(Path.Combine(folder, "translations")))
|
||||||
{
|
{
|
||||||
LoadTranslations(folder, mod);
|
LoadTranslations(folder, mod);
|
||||||
@ -847,6 +853,25 @@ namespace NewHorizons
|
|||||||
AddonConfigs[mod] = addonConfig;
|
AddonConfigs[mod] = addonConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void LoadTitleScreenConfig(string file, IModBehaviour mod)
|
||||||
|
{
|
||||||
|
NHLogger.LogVerbose($"Loading title screen config for {mod.ModHelper.Manifest.Name}");
|
||||||
|
|
||||||
|
var titleScreenConfig = mod.ModHelper.Storage.Load<TitleScreenConfig>(file, false);
|
||||||
|
|
||||||
|
if (titleScreenConfig == null)
|
||||||
|
{
|
||||||
|
NHLogger.LogError($"Title screen config for {mod.ModHelper.Manifest.Name} could not load, check your JSON");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TitleScreenConfigs[mod] = titleScreenConfig;
|
||||||
|
foreach (var info in titleScreenConfig.titleScreens)
|
||||||
|
{
|
||||||
|
TitleSceneHandler.RegisterBuilder(mod, info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void LoadTranslations(string folder, IModBehaviour mod)
|
private void LoadTranslations(string folder, IModBehaviour mod)
|
||||||
{
|
{
|
||||||
var foundFile = false;
|
var foundFile = false;
|
||||||
|
|||||||
@ -90,6 +90,8 @@ namespace NewHorizons
|
|||||||
public UnityEvent<string> GetChangeStarSystemEvent() => Main.Instance.OnChangeStarSystem;
|
public UnityEvent<string> GetChangeStarSystemEvent() => Main.Instance.OnChangeStarSystem;
|
||||||
public UnityEvent<string> GetStarSystemLoadedEvent() => Main.Instance.OnStarSystemLoaded;
|
public UnityEvent<string> GetStarSystemLoadedEvent() => Main.Instance.OnStarSystemLoaded;
|
||||||
public UnityEvent<string> GetBodyLoadedEvent() => Main.Instance.OnPlanetLoaded;
|
public UnityEvent<string> GetBodyLoadedEvent() => Main.Instance.OnPlanetLoaded;
|
||||||
|
public UnityEvent<string, int> GetTitleScreenLoadedEvent() => Main.Instance.OnTitleScreenLoaded;
|
||||||
|
public UnityEvent GetAllTitleScreensLoadedEvent() => Main.Instance.OnAllTitleScreensLoaded;
|
||||||
|
|
||||||
public bool SetDefaultSystem(string name)
|
public bool SetDefaultSystem(string name)
|
||||||
{
|
{
|
||||||
@ -178,6 +180,24 @@ namespace NewHorizons
|
|||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public object QueryTitleScreen(Type outType, IModBehaviour mod, string jsonPath)
|
||||||
|
{
|
||||||
|
var titleScreenConfig = Main.TitleScreenConfigs[mod];
|
||||||
|
return titleScreenConfig == null
|
||||||
|
? null
|
||||||
|
: QueryJson(outType, Path.Combine(mod.ModHelper.Manifest.ModFolderPath, "title-screen.json"), jsonPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public T QueryTitleScreen<T>(IModBehaviour mod, string jsonPath)
|
||||||
|
{
|
||||||
|
var data = QueryTitleScreen(typeof(T), mod, jsonPath);
|
||||||
|
if (data is T result)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
public GameObject SpawnObject(IModBehaviour mod, GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles,
|
public GameObject SpawnObject(IModBehaviour mod, GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles,
|
||||||
float scale, bool alignRadial)
|
float scale, bool alignRadial)
|
||||||
{
|
{
|
||||||
@ -344,5 +364,8 @@ namespace NewHorizons
|
|||||||
public void AddSubtitle(IModBehaviour mod, string filePath) => SubtitlesHandler.RegisterAdditionalSubtitle(mod, filePath);
|
public void AddSubtitle(IModBehaviour mod, string filePath) => SubtitlesHandler.RegisterAdditionalSubtitle(mod, filePath);
|
||||||
|
|
||||||
public void SetNextSpawnID(string id) => PlayerSpawnHandler.TargetSpawnID = id;
|
public void SetNextSpawnID(string id) => PlayerSpawnHandler.TargetSpawnID = id;
|
||||||
|
|
||||||
|
public void RegisterTitleScreenBuilder(IModBehaviour mod, Action<GameObject> builder, bool disableNHPlanets = true, bool shareTitleScreen = false, string persistentConditionRequired = null, string factRequired = null)
|
||||||
|
=> TitleSceneHandler.RegisterBuilder(mod, builder, disableNHPlanets, shareTitleScreen, persistentConditionRequired, factRequired);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
44
NewHorizons/Patches/TitleScenePatches.cs
Normal file
44
NewHorizons/Patches/TitleScenePatches.cs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
using HarmonyLib;
|
||||||
|
using NewHorizons.Handlers;
|
||||||
|
using NewHorizons.Utility;
|
||||||
|
using NewHorizons.Utility.OWML;
|
||||||
|
using OWML.Utils;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace NewHorizons.Patches;
|
||||||
|
|
||||||
|
[HarmonyPatch]
|
||||||
|
internal static class TitleScenePatches
|
||||||
|
{
|
||||||
|
[HarmonyPrefix, HarmonyPatch(typeof(TitleScreenAnimation), nameof(TitleScreenAnimation.Awake))]
|
||||||
|
public static void TitleScreenAnimation_Awake(TitleScreenAnimation __instance)
|
||||||
|
{
|
||||||
|
if (TitleSceneHandler.reloaded)
|
||||||
|
{
|
||||||
|
TitleSceneHandler.reloaded = false;
|
||||||
|
|
||||||
|
// Skip Splash on title screen reload
|
||||||
|
TitleScreenAnimation titleScreenAnimation = __instance;
|
||||||
|
titleScreenAnimation._fadeDuration = 0;
|
||||||
|
titleScreenAnimation._gamepadSplash = false;
|
||||||
|
titleScreenAnimation._introPan = false;
|
||||||
|
|
||||||
|
TitleAnimationController titleAnimationController = GameObject.FindObjectOfType<TitleAnimationController>();
|
||||||
|
titleAnimationController._logoFadeDelay = 0.001f;
|
||||||
|
titleAnimationController._logoFadeDuration = 0.001f;
|
||||||
|
titleAnimationController._optionsFadeDelay = 0.001f;
|
||||||
|
titleAnimationController._optionsFadeDuration = 0.001f;
|
||||||
|
titleAnimationController._optionsFadeSpacing = 0.001f;
|
||||||
|
titleAnimationController.FadeInTitleLogo();
|
||||||
|
|
||||||
|
// Reopen profile
|
||||||
|
if (TitleSceneHandler.reopenProfile)
|
||||||
|
{
|
||||||
|
TitleSceneHandler.reopenProfile = false;
|
||||||
|
Delay.FireOnNextUpdate(() =>
|
||||||
|
SearchUtilities.Find("TitleMenu/TitleCanvas/TitleLayoutGroup/MainMenuBlock/MainMenuLayoutGroup/Button-Profile")
|
||||||
|
.GetComponent<SubmitActionMenu>().Submit());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -946,35 +946,6 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"assetBundle": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Relative filepath to an asset-bundle to load the prefab defined in `path` from"
|
|
||||||
},
|
|
||||||
"path": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Either the path in the scene hierarchy of the item to copy or the path to the object in the supplied asset bundle. \nIf empty, will make an empty game object. This can be useful for adding other props to it as its children."
|
|
||||||
},
|
|
||||||
"removeChildren": {
|
|
||||||
"type": "array",
|
|
||||||
"description": "A list of children to remove from this detail",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"removeComponents": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Do we reset all the components on this object? Useful for certain props that have dialogue components attached to\nthem."
|
|
||||||
},
|
|
||||||
"scale": {
|
|
||||||
"type": "number",
|
|
||||||
"description": "Scale the prop",
|
|
||||||
"format": "float",
|
|
||||||
"default": 1.0
|
|
||||||
},
|
|
||||||
"stretch": {
|
|
||||||
"description": "Scale each axis of the prop. Overrides `scale`.",
|
|
||||||
"$ref": "#/definitions/MVector3"
|
|
||||||
},
|
|
||||||
"keepLoaded": {
|
"keepLoaded": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Should this detail stay loaded (visible and collideable) even if you're outside the sector (good for very large props)?\nAlso makes this detail visible on the map.\nKeeping many props loaded is bad for performance so use this only when it's actually relevant\nMost logic/behavior scripts will still only work inside the sector, as most of those scripts break if a sector is not provided."
|
"description": "Should this detail stay loaded (visible and collideable) even if you're outside the sector (good for very large props)?\nAlso makes this detail visible on the map.\nKeeping many props loaded is bad for performance so use this only when it's actually relevant\nMost logic/behavior scripts will still only work inside the sector, as most of those scripts break if a sector is not provided."
|
||||||
@ -1025,6 +996,35 @@
|
|||||||
"description": "Should this detail be treated as a socket for an interactible item",
|
"description": "Should this detail be treated as a socket for an interactible item",
|
||||||
"$ref": "#/definitions/ItemSocketInfo"
|
"$ref": "#/definitions/ItemSocketInfo"
|
||||||
},
|
},
|
||||||
|
"assetBundle": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Relative filepath to an asset-bundle to load the prefab defined in `path` from"
|
||||||
|
},
|
||||||
|
"path": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Either the path in the scene hierarchy of the item to copy or the path to the object in the supplied asset bundle. \nIf empty, will make an empty game object. This can be useful for adding other props to it as its children."
|
||||||
|
},
|
||||||
|
"removeChildren": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "A list of children to remove from this detail",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"removeComponents": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Do we reset all the components on this object? Useful for certain props that have dialogue components attached to\nthem."
|
||||||
|
},
|
||||||
|
"scale": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Scale the prop",
|
||||||
|
"format": "float",
|
||||||
|
"default": 1.0
|
||||||
|
},
|
||||||
|
"stretch": {
|
||||||
|
"description": "Scale each axis of the prop. Overrides `scale`.",
|
||||||
|
"$ref": "#/definitions/MVector3"
|
||||||
|
},
|
||||||
"rotation": {
|
"rotation": {
|
||||||
"description": "Rotation of the object",
|
"description": "Rotation of the object",
|
||||||
"$ref": "#/definitions/MVector3"
|
"$ref": "#/definitions/MVector3"
|
||||||
@ -1564,35 +1564,6 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"assetBundle": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Relative filepath to an asset-bundle to load the prefab defined in `path` from"
|
|
||||||
},
|
|
||||||
"path": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Either the path in the scene hierarchy of the item to copy or the path to the object in the supplied asset bundle. \nIf empty, will make an empty game object. This can be useful for adding other props to it as its children."
|
|
||||||
},
|
|
||||||
"removeChildren": {
|
|
||||||
"type": "array",
|
|
||||||
"description": "A list of children to remove from this detail",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"removeComponents": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Do we reset all the components on this object? Useful for certain props that have dialogue components attached to\nthem."
|
|
||||||
},
|
|
||||||
"scale": {
|
|
||||||
"type": "number",
|
|
||||||
"description": "Scale the prop",
|
|
||||||
"format": "float",
|
|
||||||
"default": 1.0
|
|
||||||
},
|
|
||||||
"stretch": {
|
|
||||||
"description": "Scale each axis of the prop. Overrides `scale`.",
|
|
||||||
"$ref": "#/definitions/MVector3"
|
|
||||||
},
|
|
||||||
"keepLoaded": {
|
"keepLoaded": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Should this detail stay loaded (visible and collideable) even if you're outside the sector (good for very large props)?\nAlso makes this detail visible on the map.\nKeeping many props loaded is bad for performance so use this only when it's actually relevant\nMost logic/behavior scripts will still only work inside the sector, as most of those scripts break if a sector is not provided."
|
"description": "Should this detail stay loaded (visible and collideable) even if you're outside the sector (good for very large props)?\nAlso makes this detail visible on the map.\nKeeping many props loaded is bad for performance so use this only when it's actually relevant\nMost logic/behavior scripts will still only work inside the sector, as most of those scripts break if a sector is not provided."
|
||||||
@ -1643,6 +1614,35 @@
|
|||||||
"description": "Should this detail be treated as a socket for an interactible item",
|
"description": "Should this detail be treated as a socket for an interactible item",
|
||||||
"$ref": "#/definitions/ItemSocketInfo"
|
"$ref": "#/definitions/ItemSocketInfo"
|
||||||
},
|
},
|
||||||
|
"assetBundle": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Relative filepath to an asset-bundle to load the prefab defined in `path` from"
|
||||||
|
},
|
||||||
|
"path": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Either the path in the scene hierarchy of the item to copy or the path to the object in the supplied asset bundle. \nIf empty, will make an empty game object. This can be useful for adding other props to it as its children."
|
||||||
|
},
|
||||||
|
"removeChildren": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "A list of children to remove from this detail",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"removeComponents": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Do we reset all the components on this object? Useful for certain props that have dialogue components attached to\nthem."
|
||||||
|
},
|
||||||
|
"scale": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Scale the prop",
|
||||||
|
"format": "float",
|
||||||
|
"default": 1.0
|
||||||
|
},
|
||||||
|
"stretch": {
|
||||||
|
"description": "Scale each axis of the prop. Overrides `scale`.",
|
||||||
|
"$ref": "#/definitions/MVector3"
|
||||||
|
},
|
||||||
"rotation": {
|
"rotation": {
|
||||||
"description": "Rotation of the object",
|
"description": "Rotation of the object",
|
||||||
"$ref": "#/definitions/MVector3"
|
"$ref": "#/definitions/MVector3"
|
||||||
@ -1680,35 +1680,6 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"assetBundle": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Relative filepath to an asset-bundle to load the prefab defined in `path` from"
|
|
||||||
},
|
|
||||||
"path": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Either the path in the scene hierarchy of the item to copy or the path to the object in the supplied asset bundle. \nIf empty, will make an empty game object. This can be useful for adding other props to it as its children."
|
|
||||||
},
|
|
||||||
"removeChildren": {
|
|
||||||
"type": "array",
|
|
||||||
"description": "A list of children to remove from this detail",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"removeComponents": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Do we reset all the components on this object? Useful for certain props that have dialogue components attached to\nthem."
|
|
||||||
},
|
|
||||||
"scale": {
|
|
||||||
"type": "number",
|
|
||||||
"description": "Scale the prop",
|
|
||||||
"format": "float",
|
|
||||||
"default": 1.0
|
|
||||||
},
|
|
||||||
"stretch": {
|
|
||||||
"description": "Scale each axis of the prop. Overrides `scale`.",
|
|
||||||
"$ref": "#/definitions/MVector3"
|
|
||||||
},
|
|
||||||
"keepLoaded": {
|
"keepLoaded": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Should this detail stay loaded (visible and collideable) even if you're outside the sector (good for very large props)?\nAlso makes this detail visible on the map.\nKeeping many props loaded is bad for performance so use this only when it's actually relevant\nMost logic/behavior scripts will still only work inside the sector, as most of those scripts break if a sector is not provided."
|
"description": "Should this detail stay loaded (visible and collideable) even if you're outside the sector (good for very large props)?\nAlso makes this detail visible on the map.\nKeeping many props loaded is bad for performance so use this only when it's actually relevant\nMost logic/behavior scripts will still only work inside the sector, as most of those scripts break if a sector is not provided."
|
||||||
@ -1759,6 +1730,35 @@
|
|||||||
"description": "Should this detail be treated as a socket for an interactible item",
|
"description": "Should this detail be treated as a socket for an interactible item",
|
||||||
"$ref": "#/definitions/ItemSocketInfo"
|
"$ref": "#/definitions/ItemSocketInfo"
|
||||||
},
|
},
|
||||||
|
"assetBundle": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Relative filepath to an asset-bundle to load the prefab defined in `path` from"
|
||||||
|
},
|
||||||
|
"path": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Either the path in the scene hierarchy of the item to copy or the path to the object in the supplied asset bundle. \nIf empty, will make an empty game object. This can be useful for adding other props to it as its children."
|
||||||
|
},
|
||||||
|
"removeChildren": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "A list of children to remove from this detail",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"removeComponents": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Do we reset all the components on this object? Useful for certain props that have dialogue components attached to\nthem."
|
||||||
|
},
|
||||||
|
"scale": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Scale the prop",
|
||||||
|
"format": "float",
|
||||||
|
"default": 1.0
|
||||||
|
},
|
||||||
|
"stretch": {
|
||||||
|
"description": "Scale each axis of the prop. Overrides `scale`.",
|
||||||
|
"$ref": "#/definitions/MVector3"
|
||||||
|
},
|
||||||
"rotation": {
|
"rotation": {
|
||||||
"description": "Rotation of the object",
|
"description": "Rotation of the object",
|
||||||
"$ref": "#/definitions/MVector3"
|
"$ref": "#/definitions/MVector3"
|
||||||
@ -2381,33 +2381,6 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"rotation": {
|
|
||||||
"description": "Rotation of the object",
|
|
||||||
"$ref": "#/definitions/MVector3"
|
|
||||||
},
|
|
||||||
"alignRadial": {
|
|
||||||
"type": [
|
|
||||||
"boolean",
|
|
||||||
"null"
|
|
||||||
],
|
|
||||||
"description": "Do we try to automatically align this object to stand upright relative to the body's center? Stacks with rotation.\nDefaults to true for geysers, tornados, and volcanoes, and false for everything else."
|
|
||||||
},
|
|
||||||
"position": {
|
|
||||||
"description": "Position of the object",
|
|
||||||
"$ref": "#/definitions/MVector3"
|
|
||||||
},
|
|
||||||
"isRelativeToParent": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
|
|
||||||
},
|
|
||||||
"parentPath": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
|
|
||||||
},
|
|
||||||
"rename": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "An optional rename of this object"
|
|
||||||
},
|
|
||||||
"assetBundle": {
|
"assetBundle": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Relative filepath to an asset-bundle to load the prefab defined in `path` from"
|
"description": "Relative filepath to an asset-bundle to load the prefab defined in `path` from"
|
||||||
@ -2437,6 +2410,33 @@
|
|||||||
"description": "Scale each axis of the prop. Overrides `scale`.",
|
"description": "Scale each axis of the prop. Overrides `scale`.",
|
||||||
"$ref": "#/definitions/MVector3"
|
"$ref": "#/definitions/MVector3"
|
||||||
},
|
},
|
||||||
|
"rotation": {
|
||||||
|
"description": "Rotation of the object",
|
||||||
|
"$ref": "#/definitions/MVector3"
|
||||||
|
},
|
||||||
|
"alignRadial": {
|
||||||
|
"type": [
|
||||||
|
"boolean",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"description": "Do we try to automatically align this object to stand upright relative to the body's center? Stacks with rotation.\nDefaults to true for geysers, tornados, and volcanoes, and false for everything else."
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
"description": "Position of the object",
|
||||||
|
"$ref": "#/definitions/MVector3"
|
||||||
|
},
|
||||||
|
"isRelativeToParent": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
|
||||||
|
},
|
||||||
|
"parentPath": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
|
||||||
|
},
|
||||||
|
"rename": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "An optional rename of this object"
|
||||||
|
},
|
||||||
"keepLoaded": {
|
"keepLoaded": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Should this detail stay loaded (visible and collideable) even if you're outside the sector (good for very large props)?\nAlso makes this detail visible on the map.\nKeeping many props loaded is bad for performance so use this only when it's actually relevant\nMost logic/behavior scripts will still only work inside the sector, as most of those scripts break if a sector is not provided."
|
"description": "Should this detail stay loaded (visible and collideable) even if you're outside the sector (good for very large props)?\nAlso makes this detail visible on the map.\nKeeping many props loaded is bad for performance so use this only when it's actually relevant\nMost logic/behavior scripts will still only work inside the sector, as most of those scripts break if a sector is not provided."
|
||||||
@ -3976,35 +3976,6 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"assetBundle": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Relative filepath to an asset-bundle to load the prefab defined in `path` from"
|
|
||||||
},
|
|
||||||
"path": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Either the path in the scene hierarchy of the item to copy or the path to the object in the supplied asset bundle. \nIf empty, will make an empty game object. This can be useful for adding other props to it as its children."
|
|
||||||
},
|
|
||||||
"removeChildren": {
|
|
||||||
"type": "array",
|
|
||||||
"description": "A list of children to remove from this detail",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"removeComponents": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Do we reset all the components on this object? Useful for certain props that have dialogue components attached to\nthem."
|
|
||||||
},
|
|
||||||
"scale": {
|
|
||||||
"type": "number",
|
|
||||||
"description": "Scale the prop",
|
|
||||||
"format": "float",
|
|
||||||
"default": 1.0
|
|
||||||
},
|
|
||||||
"stretch": {
|
|
||||||
"description": "Scale each axis of the prop. Overrides `scale`.",
|
|
||||||
"$ref": "#/definitions/MVector3"
|
|
||||||
},
|
|
||||||
"keepLoaded": {
|
"keepLoaded": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Should this detail stay loaded (visible and collideable) even if you're outside the sector (good for very large props)?\nAlso makes this detail visible on the map.\nKeeping many props loaded is bad for performance so use this only when it's actually relevant\nMost logic/behavior scripts will still only work inside the sector, as most of those scripts break if a sector is not provided."
|
"description": "Should this detail stay loaded (visible and collideable) even if you're outside the sector (good for very large props)?\nAlso makes this detail visible on the map.\nKeeping many props loaded is bad for performance so use this only when it's actually relevant\nMost logic/behavior scripts will still only work inside the sector, as most of those scripts break if a sector is not provided."
|
||||||
@ -4055,6 +4026,35 @@
|
|||||||
"description": "Should this detail be treated as a socket for an interactible item",
|
"description": "Should this detail be treated as a socket for an interactible item",
|
||||||
"$ref": "#/definitions/ItemSocketInfo"
|
"$ref": "#/definitions/ItemSocketInfo"
|
||||||
},
|
},
|
||||||
|
"assetBundle": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Relative filepath to an asset-bundle to load the prefab defined in `path` from"
|
||||||
|
},
|
||||||
|
"path": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Either the path in the scene hierarchy of the item to copy or the path to the object in the supplied asset bundle. \nIf empty, will make an empty game object. This can be useful for adding other props to it as its children."
|
||||||
|
},
|
||||||
|
"removeChildren": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "A list of children to remove from this detail",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"removeComponents": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Do we reset all the components on this object? Useful for certain props that have dialogue components attached to\nthem."
|
||||||
|
},
|
||||||
|
"scale": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Scale the prop",
|
||||||
|
"format": "float",
|
||||||
|
"default": 1.0
|
||||||
|
},
|
||||||
|
"stretch": {
|
||||||
|
"description": "Scale each axis of the prop. Overrides `scale`.",
|
||||||
|
"$ref": "#/definitions/MVector3"
|
||||||
|
},
|
||||||
"rotation": {
|
"rotation": {
|
||||||
"description": "Rotation of the object",
|
"description": "Rotation of the object",
|
||||||
"$ref": "#/definitions/MVector3"
|
"$ref": "#/definitions/MVector3"
|
||||||
@ -4507,7 +4507,7 @@
|
|||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"description": "The type of dream candle this is.",
|
"description": "The type of dream candle this is.",
|
||||||
"default": "Ground",
|
"default": "ground",
|
||||||
"$ref": "#/definitions/DreamCandleType"
|
"$ref": "#/definitions/DreamCandleType"
|
||||||
},
|
},
|
||||||
"startLit": {
|
"startLit": {
|
||||||
|
|||||||
286
NewHorizons/Schemas/title_screen_schema.json
Normal file
286
NewHorizons/Schemas/title_screen_schema.json
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"title": "Title Screen Schema",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"titleScreens": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Create title screens",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/TitleScreenInfo"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"$schema": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The schema to validate with"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"definitions": {
|
||||||
|
"TitleScreenInfo": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"menuTextTint": {
|
||||||
|
"description": "Colour of the text on the main menu",
|
||||||
|
"$ref": "#/definitions/MColor"
|
||||||
|
},
|
||||||
|
"factRequiredForTitle": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Ship log fact required for this title screen to appear."
|
||||||
|
},
|
||||||
|
"persistentConditionRequiredForTitle": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Persistent condition required for this title screen to appear."
|
||||||
|
},
|
||||||
|
"disableNHPlanets": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "If set to true, NH generated planets will not show on the title screen. If false, this title screen has the same chance as other NH planet title screens to show."
|
||||||
|
},
|
||||||
|
"shareTitleScreen": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "If set to true, this custom title screen will merge with all other custom title screens with shareTitleScreen set to true. If false, NH will randomly select between this and other valid title screens that are loaded."
|
||||||
|
},
|
||||||
|
"Skybox": {
|
||||||
|
"description": "Customize the skybox for this title screen",
|
||||||
|
"$ref": "#/definitions/SkyboxModule"
|
||||||
|
},
|
||||||
|
"music": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The music audio that will play on the title screen. Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list."
|
||||||
|
},
|
||||||
|
"ambience": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The ambience audio that will play on the title screen. Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list."
|
||||||
|
},
|
||||||
|
"Background": {
|
||||||
|
"description": "Edit properties of the background",
|
||||||
|
"$ref": "#/definitions/BackgroundModule"
|
||||||
|
},
|
||||||
|
"MenuPlanet": {
|
||||||
|
"description": "Edit properties of the main menu planet",
|
||||||
|
"$ref": "#/definitions/MenuPlanetModule"
|
||||||
|
},
|
||||||
|
"extras": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Extra data that may be used by extension mods",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"MColor": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"r": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "The red component of this colour from 0-255, higher values will make the colour glow if applicable.",
|
||||||
|
"format": "int32",
|
||||||
|
"maximum": 2147483647.0,
|
||||||
|
"minimum": 0.0
|
||||||
|
},
|
||||||
|
"g": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "The green component of this colour from 0-255, higher values will make the colour glow if applicable.",
|
||||||
|
"format": "int32",
|
||||||
|
"maximum": 2147483647.0,
|
||||||
|
"minimum": 0.0
|
||||||
|
},
|
||||||
|
"b": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "The blue component of this colour from 0-255, higher values will make the colour glow if applicable.",
|
||||||
|
"format": "int32",
|
||||||
|
"maximum": 2147483647.0,
|
||||||
|
"minimum": 0.0
|
||||||
|
},
|
||||||
|
"a": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "The alpha (opacity) component of this colour",
|
||||||
|
"format": "int32",
|
||||||
|
"default": 255,
|
||||||
|
"maximum": 255.0,
|
||||||
|
"minimum": 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"SkyboxModule": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"destroyStarField": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether to destroy the star field around the player"
|
||||||
|
},
|
||||||
|
"useCube": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether to use a cube for the skybox instead of a smooth sphere"
|
||||||
|
},
|
||||||
|
"rightPath": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Relative filepath to the texture to use for the skybox's positive X direction"
|
||||||
|
},
|
||||||
|
"leftPath": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Relative filepath to the texture to use for the skybox's negative X direction"
|
||||||
|
},
|
||||||
|
"topPath": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Relative filepath to the texture to use for the skybox's positive Y direction"
|
||||||
|
},
|
||||||
|
"bottomPath": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Relative filepath to the texture to use for the skybox's negative Y direction"
|
||||||
|
},
|
||||||
|
"frontPath": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Relative filepath to the texture to use for the skybox's positive Z direction"
|
||||||
|
},
|
||||||
|
"backPath": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Relative filepath to the texture to use for the skybox's negative Z direction"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"BackgroundModule": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"rotationSpeed": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Changes the speed the background rotates (and by extension the main menu planet). This is in degrees per second.",
|
||||||
|
"format": "float"
|
||||||
|
},
|
||||||
|
"removeChildren": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Disables the renderers of objects at the provided paths",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"details": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "A list of DetailInfos to populate the background with.",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/SimplifiedDetailInfo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"SimplifiedDetailInfo": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "A lesser form of DetailInfo used for the title screen since that supports fewer features",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"rotation": {
|
||||||
|
"description": "Rotation of the object",
|
||||||
|
"$ref": "#/definitions/MVector3"
|
||||||
|
},
|
||||||
|
"alignRadial": {
|
||||||
|
"type": [
|
||||||
|
"boolean",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"description": "Do we try to automatically align this object to stand upright relative to the body's center? Stacks with rotation.\nDefaults to true for geysers, tornados, and volcanoes, and false for everything else."
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
"description": "Position of the object",
|
||||||
|
"$ref": "#/definitions/MVector3"
|
||||||
|
},
|
||||||
|
"isRelativeToParent": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
|
||||||
|
},
|
||||||
|
"parentPath": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
|
||||||
|
},
|
||||||
|
"rename": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "An optional rename of this object"
|
||||||
|
},
|
||||||
|
"assetBundle": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Relative filepath to an asset-bundle to load the prefab defined in `path` from"
|
||||||
|
},
|
||||||
|
"path": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Either the path in the scene hierarchy of the item to copy or the path to the object in the supplied asset bundle. \nIf empty, will make an empty game object. This can be useful for adding other props to it as its children."
|
||||||
|
},
|
||||||
|
"removeChildren": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "A list of children to remove from this detail",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"removeComponents": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Do we reset all the components on this object? Useful for certain props that have dialogue components attached to\nthem."
|
||||||
|
},
|
||||||
|
"scale": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Scale the prop",
|
||||||
|
"format": "float",
|
||||||
|
"default": 1.0
|
||||||
|
},
|
||||||
|
"stretch": {
|
||||||
|
"description": "Scale each axis of the prop. Overrides `scale`.",
|
||||||
|
"$ref": "#/definitions/MVector3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"MVector3": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"x": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "float"
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "float"
|
||||||
|
},
|
||||||
|
"z": {
|
||||||
|
"type": "number",
|
||||||
|
"format": "float"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"MenuPlanetModule": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"destroyMenuPlanet": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Disables the renderers of the main menu planet and all objects on it (this is to improve compatibility with other mods that don't use the NH title screen json)."
|
||||||
|
},
|
||||||
|
"removeChildren": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Disables the renderers of objects at the provided paths",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"details": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "A list of DetailInfos to populate the main menu planet with.",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/SimplifiedDetailInfo"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rotationSpeed": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Changes the speed the main menu planet. This is in degrees per second.",
|
||||||
|
"format": "float"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"$docs": {
|
||||||
|
"title": "Title Screen Schema",
|
||||||
|
"description": "Schema for the title screen config in New Horizons"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -33,6 +33,9 @@ public static class SchemaExporter
|
|||||||
var translationSchema =
|
var translationSchema =
|
||||||
new Schema<TranslationConfig>("Translation Schema", "Schema for a translation file in New Horizons", $"{folderName}/translation_schema", settings);
|
new Schema<TranslationConfig>("Translation Schema", "Schema for a translation file in New Horizons", $"{folderName}/translation_schema", settings);
|
||||||
translationSchema.Output();
|
translationSchema.Output();
|
||||||
|
var titleScreenSchema = new Schema<TitleScreenConfig>("Title Screen Schema",
|
||||||
|
"Schema for the title screen config in New Horizons", $"{folderName}/title_screen_schema", settings);
|
||||||
|
titleScreenSchema.Output();
|
||||||
Console.WriteLine("Done!");
|
Console.WriteLine("Done!");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,6 +111,19 @@ public static class SchemaExporter
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_title is "Title Screen Schema")
|
||||||
|
{
|
||||||
|
schema.Definitions["TitleScreenInfo"].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;
|
return schema;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,6 +14,7 @@ const schemas = [
|
|||||||
"addon_manifest_schema.json",
|
"addon_manifest_schema.json",
|
||||||
"dialogue_schema.xsd",
|
"dialogue_schema.xsd",
|
||||||
"text_schema.xsd",
|
"text_schema.xsd",
|
||||||
|
"title_screen_schema.json",
|
||||||
"shiplog_schema.xsd"
|
"shiplog_schema.xsd"
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -90,6 +91,7 @@ export default defineConfig({
|
|||||||
{ label: "Addon Manifest Schema", link: "schemas/addon-manifest-schema" },
|
{ label: "Addon Manifest Schema", link: "schemas/addon-manifest-schema" },
|
||||||
{ label: "Dialogue Schema", link: "schemas/dialogue-schema" },
|
{ label: "Dialogue Schema", link: "schemas/dialogue-schema" },
|
||||||
{ label: "Text Schema", link: "schemas/text-schema" },
|
{ label: "Text Schema", link: "schemas/text-schema" },
|
||||||
|
{ label: "Title Screen Schema", link: "schemas/title-screen-schema" },
|
||||||
{ label: "Ship Log Schema", link: "schemas/shiplog-schema" }
|
{ label: "Ship Log Schema", link: "schemas/shiplog-schema" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@ -16,7 +16,12 @@ A star system config file will look something like this:
|
|||||||
```json title="my_star_system.json"
|
```json title="my_star_system.json"
|
||||||
{
|
{
|
||||||
"$schema": "https://raw.githubusercontent.com/Outer-Wilds-New-Horizons/new-horizons/main/NewHorizons/Schemas/star_system_schema.json",
|
"$schema": "https://raw.githubusercontent.com/Outer-Wilds-New-Horizons/new-horizons/main/NewHorizons/Schemas/star_system_schema.json",
|
||||||
"travelAudio": "assets/Travel.mp3",
|
"canEnterViaWarpDrive": true,
|
||||||
|
"startHere": false,
|
||||||
|
"respawnHere": true,
|
||||||
|
"GlobalMusic": {
|
||||||
|
"travelAudio": "planets/assets/Travel Audio.mp3"
|
||||||
|
},
|
||||||
"Vessel": {
|
"Vessel": {
|
||||||
"coords": {
|
"coords": {
|
||||||
"x": [4, 0, 3, 1],
|
"x": [4, 0, 3, 1],
|
||||||
|
|||||||
194
docs/src/content/docs/guides/title-screens.md
Normal file
194
docs/src/content/docs/guides/title-screens.md
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
---
|
||||||
|
title: Title Screens
|
||||||
|
description: A guide to creating a custom title screens in New Horizons
|
||||||
|
---
|
||||||
|
|
||||||
|
Welcome! This page outlines how to make a custom title screen.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
Your mod's title screen config is a JSON file named `title-screen.json` that should be placed within your mod folder.
|
||||||
|
|
||||||
|
A title screen config file will look something like this:
|
||||||
|
|
||||||
|
```json title="title-screen.json"
|
||||||
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/Outer-Wilds-New-Horizons/new-horizons/main/NewHorizons/Schemas/title_screen_schema.json",
|
||||||
|
"titleScreens": [
|
||||||
|
{
|
||||||
|
"disableNHPlanets": false,
|
||||||
|
"shareTitleScreen": true,
|
||||||
|
"music": "planets/assets/TitleScreenMusic.mp3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disableNHPlanets": true,
|
||||||
|
"shareTitleScreen": true,
|
||||||
|
"factRequiredForTitle": "EXAMPLES_ARTIFICIAL_GRAVITY",
|
||||||
|
"menuTextTint": {
|
||||||
|
"r": 128,
|
||||||
|
"g": 128,
|
||||||
|
"b": 255
|
||||||
|
},
|
||||||
|
"music": "planets/assets/TitleScreenMusic.mp3",
|
||||||
|
"ambience": "planets/assets/TitleScreenAmbience.mp3",
|
||||||
|
"Skybox": {
|
||||||
|
"destroyStarField": true,
|
||||||
|
"rightPath": "systems/New System Assets/Skybox/Right_Large.png",
|
||||||
|
"leftPath": "systems/New System Assets/Skybox/Left_Large.png",
|
||||||
|
"topPath": "systems/New System Assets/Skybox/Up_Large.png",
|
||||||
|
"bottomPath": "systems/New System Assets/Skybox/Down_Large.png",
|
||||||
|
"frontPath": "systems/New System Assets/Skybox/Front_Large.png",
|
||||||
|
"backPath": "systems/New System Assets/Skybox/Back_Large.png"
|
||||||
|
},
|
||||||
|
"Background": {
|
||||||
|
"details": [
|
||||||
|
{
|
||||||
|
"assetBundle": "assetbundles/test",
|
||||||
|
"path": "Assets/Prefabs/Background.prefab",
|
||||||
|
"position": {"x": 200, "y": 280, "z": -50},
|
||||||
|
"rotation": {"x": 310, "y": 0, "z": 310},
|
||||||
|
"scale": 0.05
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rotationSpeed": 10
|
||||||
|
},
|
||||||
|
"MenuPlanet": {
|
||||||
|
"destroyMenuPlanet": false,
|
||||||
|
"removeChildren": ["PlanetRoot/Props"],
|
||||||
|
"details": [
|
||||||
|
{
|
||||||
|
"assetBundle": "assetbundles/test",
|
||||||
|
"path": "Assets/Prefabs/ArtificialGravity.prefab",
|
||||||
|
"removeChildren": ["Gravity"],
|
||||||
|
"parentPath": "PlanetRoot",
|
||||||
|
"position": {"x": 0, "y": 32, "z": 0},
|
||||||
|
"rotation": {"x": 90, "y": 0, "z": 0},
|
||||||
|
"scale": 10
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rotationSpeed": 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can have multiple title screens but only one will be selected from the list. The last title screen in the list, that is unlocked, will always be selected.
|
||||||
|
|
||||||
|
## Configs
|
||||||
|
|
||||||
|
Title screens from configs are always put first into the list.
|
||||||
|
|
||||||
|
### `disableNHPlanets`
|
||||||
|
|
||||||
|
If set to `true`, prevents NH-generated planets from appearing on the title screen. Defaults to true.
|
||||||
|
|
||||||
|
### `shareTitleScreen`
|
||||||
|
|
||||||
|
If set to `true`, this title screen will merge with others that have the same setting enabled. For more info head to the [sharing section](#sharing) of this page. Defaults to false.
|
||||||
|
|
||||||
|
### `menuTextTint`
|
||||||
|
|
||||||
|
Defines the color of the menu text and logo. Uses RGB values, where `r`, `g`, and `b` range from `0` to `255`.
|
||||||
|
|
||||||
|
### `factRequiredForTitle`
|
||||||
|
|
||||||
|
Specifies a ship log fact that must be discovered for this title screen to appear.
|
||||||
|
|
||||||
|
### `conditionRequiredForTitle`
|
||||||
|
|
||||||
|
Specifies a persistent condition required for this title screen to appear.
|
||||||
|
|
||||||
|
### `music` and `ambience`
|
||||||
|
|
||||||
|
The audio for background music and ambience. Can be a path to a .wav/.ogg/.mp3 file, or taken from the [AudioClip list](/reference/audio-enum).
|
||||||
|
|
||||||
|
### `Background` and `MenuPlanet`
|
||||||
|
|
||||||
|
A module for the background and main menu planet that include object additions, removal, and rotation speed.
|
||||||
|
|
||||||
|
##### `details`
|
||||||
|
|
||||||
|
You can add objects to both the background and menu planet. The menu planet objects spin while the background objects are stationary.
|
||||||
|
These simplified details are just like the details in planet configs except that they only have the basic features.
|
||||||
|
|
||||||
|
## Schema
|
||||||
|
|
||||||
|
To see all the different things you can put into a config file check out the [Title Screen Schema](/schemas/title-screen-schema).
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
New Horizons provides an API method to register and build custom title screens dynamically.
|
||||||
|
|
||||||
|
These will be put at the end of the list for the selection of all your mod's title screens.
|
||||||
|
|
||||||
|
You cannot combine configs with API unfortunately as only the API will be selected.
|
||||||
|
|
||||||
|
```csharp title="INewHorizons.cs"
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a builder for the main menu.
|
||||||
|
/// Call this once before the main menu finishes loading
|
||||||
|
/// </summary>
|
||||||
|
void RegisterTitleScreenBuilder(IModBehaviour mod, Action<GameObject> builder, bool disableNHPlanets = true, bool shareTitleScreen = false, string conditionRequired = null, string factRequired = null);
|
||||||
|
```
|
||||||
|
|
||||||
|
It shares a few values with the configs but also has an exclusive one.
|
||||||
|
|
||||||
|
`builder`: Builder to run when this title screen is selected. The GameObject passed through it is the main scene object containing both the background and menu planet.
|
||||||
|
|
||||||
|
### Example API usage
|
||||||
|
|
||||||
|
You can run `RegisterTitleScreenBuilder` more than once to add multiple title screen builders.
|
||||||
|
|
||||||
|
```csharp title="YourModBehaviour.cs"
|
||||||
|
NewHorizons = ModHelper.Interaction.TryGetModApi<INewHorizons>("xen.NewHorizons");
|
||||||
|
NewHorizons.RegisterTitleScreenBuilder(this, BuildTitleScreen, disableNHPlanets: true, shareTitleScreen: true);
|
||||||
|
```
|
||||||
|
|
||||||
|
```csharp title="YourModBehaviour.cs"
|
||||||
|
public void BuildTitleScreen(GameObject scene)
|
||||||
|
{
|
||||||
|
ModHelper.Console.WriteLine($"Building title screen", MessageType.Success);
|
||||||
|
//Add an object to the title screen or do whatever else you want
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Events
|
||||||
|
|
||||||
|
Additionally, New Horizons provides events in the API for tracking title screen loading:
|
||||||
|
|
||||||
|
```csharp title="INewHorizons.cs"
|
||||||
|
/// <summary>
|
||||||
|
/// An event invoked when NH has finished building a title screen.
|
||||||
|
/// Gives the unique name of the mod the title screen builder was from and the index for when you have multiple title screens.
|
||||||
|
/// </summary>
|
||||||
|
UnityEvent<string, int> GetTitleScreenLoadedEvent();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An event invoked when NH has finished building the title screen.
|
||||||
|
/// </summary>
|
||||||
|
UnityEvent GetAllTitleScreensLoadedEvent();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example event usage
|
||||||
|
|
||||||
|
```csharp title="YourModBehaviour.cs"
|
||||||
|
NewHorizons = ModHelper.Interaction.TryGetModApi<INewHorizons>("xen.NewHorizons");
|
||||||
|
NewHorizons.GetTitleScreenLoadedEvent().AddListener(OnTitleScreenLoaded);
|
||||||
|
NewHorizons.GetAllTitleScreensLoadedEvent().AddListener(OnAllTitleScreensLoaded);
|
||||||
|
```
|
||||||
|
|
||||||
|
```csharp title="YourModBehaviour.cs"
|
||||||
|
public void OnTitleScreenLoaded(string modUniqueName, int index)
|
||||||
|
{
|
||||||
|
ModHelper.Console.WriteLine($"Title screen loaded: {modUniqueName} #{index}", MessageType.Success);
|
||||||
|
}
|
||||||
|
public void OnAllTitleScreensLoaded()
|
||||||
|
{
|
||||||
|
ModHelper.Console.WriteLine("All title screens loaded", MessageType.Success);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sharing
|
||||||
|
|
||||||
|
New Horizons will randomly select a valid title screen each time the user enters the main menu and then if `shareTitleScreen` is set to `true` it will build all the other shareable title screens (that also have matching `disableNHPlanets` values). If it doesn't have share set to true then it will only show the randomly selected.
|
||||||
Loading…
x
Reference in New Issue
Block a user