Ship log api (#666)

<!-- A new module or something else important -->
## Major features
- New API methods to: Add ship logs from xml, add dialogue from xml,
create a new planet from json string. All of these methods work directly
with strings or XElements and don't load files on their own.

<!-- A new parameter added to a module, or API feature -->
## Minor features
-

<!-- Some improvement that requires no action on the part of add-on
creators i.e., improved star graphics -->
## Improvements
-

<!-- Be sure to reference the existing issue if it exists -->
## Bug fixes
-
This commit is contained in:
Nick 2023-07-22 14:41:36 -04:00 committed by GitHub
commit be10760356
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 239 additions and 73 deletions

View File

@ -7,6 +7,7 @@ using NewHorizons.Utility.OWML;
using OWML.Common;
using System.IO;
using System.Xml;
using System.Xml.Linq;
using UnityEngine;
namespace NewHorizons.Builder.Props
@ -15,6 +16,14 @@ namespace NewHorizons.Builder.Props
{
// Returns the character dialogue tree and remote dialogue trigger, if applicable.
public static (CharacterDialogueTree, RemoteDialogueTrigger) Make(GameObject go, Sector sector, DialogueInfo info, IModBehaviour mod)
{
var xml = File.ReadAllText(Path.Combine(mod.ModHelper.Manifest.ModFolderPath, info.xmlFile));
var dialogueName = Path.GetFileNameWithoutExtension(info.xmlFile);
return Make(go, sector, info, xml, dialogueName);
}
// Create dialogue directly from xml string instead of loading it from a file
public static (CharacterDialogueTree, RemoteDialogueTrigger) Make(GameObject go, Sector sector, DialogueInfo info, string xml, string dialogueName)
{
NHLogger.LogVerbose($"[DIALOGUE] Created a new character dialogue [{info.rename}] on [{info.parentPath}]");
@ -26,7 +35,7 @@ namespace NewHorizons.Builder.Props
return (null, null);
}
var dialogue = MakeConversationZone(go, sector, info, mod.ModHelper);
var dialogue = MakeConversationZone(go, sector, info, xml, dialogueName);
RemoteDialogueTrigger remoteTrigger = null;
if (info.remoteTrigger != null)
@ -76,7 +85,7 @@ namespace NewHorizons.Builder.Props
return remoteDialogueTrigger;
}
private static CharacterDialogueTree MakeConversationZone(GameObject planetGO, Sector sector, DialogueInfo info, IModHelper mod)
private static CharacterDialogueTree MakeConversationZone(GameObject planetGO, Sector sector, DialogueInfo info, string xml, string dialogueName)
{
var conversationZone = GeneralPropBuilder.MakeNew("ConversationZone", planetGO, sector, info, defaultParentPath: info.pathToAnimController);
@ -100,13 +109,11 @@ namespace NewHorizons.Builder.Props
var dialogueTree = conversationZone.AddComponent<NHCharacterDialogueTree>();
var xml = File.ReadAllText(Path.Combine(mod.Manifest.ModFolderPath, info.xmlFile));
var text = new TextAsset(xml)
{
// Text assets need a name to be used with VoiceMod
name = Path.GetFileNameWithoutExtension(info.xmlFile)
name = dialogueName
};
dialogueTree.SetTextXml(text);
AddTranslation(xml);

View File

@ -55,15 +55,20 @@ namespace NewHorizons.Builder.ShipLog
{
string systemName = body.Config.starSystem;
XElement astroBodyFile = XElement.Load(Path.Combine(body.Mod.ModHelper.Manifest.ModFolderPath, body.Config.ShipLog.xmlFile));
XElement astroBodyId = astroBodyFile.Element("ID");
AddShipLogXML(manager, astroBodyFile, body);
}
public static void AddShipLogXML(ShipLogManager manager, XElement xml, NewHorizonsBody body)
{
XElement astroBodyId = xml.Element("ID");
if (astroBodyId == null)
{
NHLogger.LogError("Failed to load ship logs for " + systemName + "!");
NHLogger.LogError("Failed to load ship logs for " + body.Config.name + "!");
}
else
{
var entryIDs = new List<string>();
foreach (XElement entryElement in astroBodyFile.DescendantsAndSelf("Entry"))
foreach (XElement entryElement in xml.DescendantsAndSelf("Entry"))
{
XElement curiosityName = entryElement.Element("Curiosity");
XElement id = entryElement.Element("ID");
@ -98,8 +103,8 @@ namespace NewHorizons.Builder.ShipLog
}
AddTranslation(entryElement);
}
TextAsset newAsset = new TextAsset(astroBodyFile.ToString());
List<TextAsset> newBodies = new List<TextAsset>(manager._shipLogXmlAssets) { newAsset };
var newAsset = new TextAsset(xml.ToString());
var newBodies = new List<TextAsset>(manager._shipLogXmlAssets) { newAsset };
manager._shipLogXmlAssets = newBodies.ToArray();
ShipLogHandler.AddConfig(astroBodyId.Value, entryIDs, body);
}

View File

@ -14,6 +14,11 @@ namespace NewHorizons.External.SerializableData
this.a = a;
}
public static MColor FromColor(Color color)
{
return new MColor((int)(color.r * 255), (int)(color.g * 255), (int)(color.b * 255), (int)(color.a * 255));
}
/// <summary>
/// The red component of this colour from 0-255, higher values will make the colour glow if applicable.
/// </summary>

View File

@ -1,6 +1,7 @@
using OWML.Common;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
using UnityEngine;
using UnityEngine.Events;
@ -8,11 +9,13 @@ namespace NewHorizons
{
public interface INewHorizons
{
#region Obsolete
[Obsolete("Create(Dictionary<string, object> config) is deprecated, please use LoadConfigs(IModBehaviour mod) instead")]
void Create(Dictionary<string, object> config);
[Obsolete("Create(Dictionary<string, object> config) is deprecated, please use LoadConfigs(IModBehaviour mod) instead")]
void Create(Dictionary<string, object> config, IModBehaviour mod);
#endregion
/// <summary>
/// Will load all configs in the regular folders (planets, systems, translations, etc) for this mod.
@ -26,11 +29,30 @@ namespace NewHorizons
/// </summary>
GameObject GetPlanet(string name);
/// <summary>
/// Returns the uniqueIDs of each installed NH addon.
/// </summary>
string[] GetInstalledAddons();
#region Get/set/change star system
/// <summary>
/// The name of the current star system loaded.
/// </summary>
string GetCurrentStarSystem();
/// <summary>
/// Allows you to overwrite the default system. This is where the player is respawned after dying.
/// </summary>
bool SetDefaultSystem(string name);
/// <summary>
/// Allows you to instantly begin a warp to a new star system.
/// Will return false if that system does not exist (cannot be warped to).
/// </summary>
bool ChangeCurrentStarSystem(string name);
#endregion
#region events
/// <summary>
/// An event invoked when the player begins loading the new star system, before the scene starts to load.
/// Gives the name of the star system being switched to.
@ -48,7 +70,9 @@ namespace NewHorizons
/// Gives the name of the planet that was just loaded.
/// </summary>
UnityEvent<string> GetBodyLoadedEvent();
#endregion
#region Querying configs
/// <summary>
/// Uses JSONPath to query a body
/// </summary>
@ -68,23 +92,9 @@ namespace NewHorizons
/// Uses JSONPath to query the current star system
///</summary>
T QuerySystem<T>(string path);
#endregion
/// <summary>
/// Allows you to overwrite the default system. This is where the player is respawned after dying.
/// </summary>
bool SetDefaultSystem(string name);
/// <summary>
/// Allows you to instantly begin a warp to a new star system.
/// Will return false if that system does not exist (cannot be warped to).
/// </summary>
bool ChangeCurrentStarSystem(string name);
/// <summary>
/// Returns the uniqueIDs of each installed NH addon.
/// </summary>
string[] GetInstalledAddons();
#region Spawn props
/// <summary>
/// Allows you to spawn a copy of a prop by specifying its path.
/// This is the same as using Props->details in a config, but also returns the spawned gameObject to you.
@ -109,5 +119,44 @@ namespace NewHorizons
(CharacterDialogueTree, RemoteDialogueTrigger) SpawnDialogue(IModBehaviour mod, GameObject root, string xmlFile, float radius = 1f,
float range = 1f, string blockAfterPersistentCondition = null, float lookAtRadius = 1f, string pathToAnimController = null,
float remoteTriggerRadius = 0f);
#endregion
#region Load json/xml directly
/// <summary>
/// Allows creation of a planet by directly passing the json contents as a string.
/// </summary>
/// <param name="config"></param>
/// <param name="mod"></param>
void CreatePlanet(string config, IModBehaviour mod);
/// <summary>
/// Allows defining details of a star system by directly passing the json contents as a string.
/// </summary>
/// <param name="name"></param>
/// <param name="config"></param>
/// <param name="mod"></param>
void DefineStarSystem(string name, string config, IModBehaviour mod);
/// <summary>
/// Allows creation of dialogue by directly passing the xml and dialogueInfo json contents as strings
/// </summary>
/// <param name="textAssetID">TextAsset name used for compatibility with voice mod. Just has to be a unique identifier.</param>
/// <param name="xml">The contents of the dialogue xml file as a string</param>
/// <param name="dialogueInfo">The json dialogue info as a string. See the documentation/schema for what this can contain.</param>
/// <param name="planetGO">The root planet rigidbody that this dialogue is attached to. Any paths in the dialogueInfo are relative to this body.</param>
/// <returns></returns>
(CharacterDialogueTree, RemoteDialogueTrigger) CreateDialogueFromXML(string textAssetID, string xml, string dialogueInfo, GameObject planetGO);
/// <summary>
/// Directly add ship logs from XML. Call this method right before ShipLogManager awake.
/// </summary>
/// <param name="mod">The mod this method is being called from. This is required for loading files.</param>
/// <param name="xml">The ship log xml contents</param>
/// <param name="planetName">The planet that these ship logs correspond to in the map mode</param>
/// <param name="imageFolder">The relative path from your mod manifest.json where the ship log images are located. The filename must be the same as the fact id. Optional.</param>
/// <param name="entryPositions">A dictionary of each fact id to its 2D position in the ship log. Optional.</param>
/// <param name="curiousityColours">A dictionary of each curiousity ID to its colour and highlight colour in the ship log. Optional.</param>
void AddShipLogXML(IModBehaviour mod, XElement xml, string planetName, string imageFolder = null, Dictionary<string, Vector2> entryPositions = null, Dictionary<string, (Color colour, Color highlight)> curiousityColours = null);
#endregion
}
}

View File

@ -33,6 +33,7 @@ using NewHorizons.Utility.DebugTools;
using NewHorizons.Utility.DebugTools.Menu;
using NewHorizons.Components.Ship;
using NewHorizons.Builder.Props.Audio;
using Epic.OnlineServices;
namespace NewHorizons
{
@ -600,6 +601,33 @@ namespace NewHorizons
#region Load
public void LoadStarSystemConfig(string starSystemName, StarSystemConfig starSystemConfig, string relativePath, IModBehaviour mod)
{
starSystemConfig.Migrate();
starSystemConfig.FixCoordinates();
if (starSystemConfig.startHere)
{
// We always want to allow mods to overwrite setting the main SolarSystem as default but not the other way around
if (starSystemName != "SolarSystem")
{
SetDefaultSystem(starSystemName);
_currentStarSystem = starSystemName;
}
}
if (SystemDict.ContainsKey(starSystemName))
{
if (string.IsNullOrEmpty(SystemDict[starSystemName].Config.travelAudio) && SystemDict[starSystemName].Config.Skybox == null)
SystemDict[starSystemName].Mod = mod;
SystemDict[starSystemName].Config.Merge(starSystemConfig);
}
else
{
SystemDict[starSystemName] = new NewHorizonsSystem(starSystemName, starSystemConfig, relativePath, mod);
}
}
public void LoadConfigs(IModBehaviour mod)
{
try
@ -627,35 +655,13 @@ namespace NewHorizons
foreach (var file in systemFiles)
{
var name = Path.GetFileNameWithoutExtension(file);
var starSystemName = Path.GetFileNameWithoutExtension(file);
NHLogger.LogVerbose($"Loading system {name}");
NHLogger.LogVerbose($"Loading system {starSystemName}");
var relativePath = file.Replace(folder, "");
var starSystemConfig = mod.ModHelper.Storage.Load<StarSystemConfig>(relativePath, false);
starSystemConfig.Migrate();
starSystemConfig.FixCoordinates();
if (starSystemConfig.startHere)
{
// We always want to allow mods to overwrite setting the main SolarSystem as default but not the other way around
if (name != "SolarSystem")
{
SetDefaultSystem(name);
_currentStarSystem = name;
}
}
if (SystemDict.ContainsKey(name))
{
if (string.IsNullOrEmpty(SystemDict[name].Config.travelAudio) && SystemDict[name].Config.Skybox == null)
SystemDict[name].Mod = mod;
SystemDict[name].Config.Merge(starSystemConfig);
}
else
{
SystemDict[name] = new NewHorizonsSystem(name, starSystemConfig, relativePath, mod);
}
LoadStarSystemConfig(starSystemName, starSystemConfig, relativePath, mod);
}
}
if (Directory.Exists(planetsFolder))
@ -766,28 +772,7 @@ namespace NewHorizons
NHLogger.LogVerbose($"Loaded {config.name}");
if (!SystemDict.ContainsKey(config.starSystem))
{
// Since we didn't load it earlier there shouldn't be a star system config
var starSystemConfig = mod.ModHelper.Storage.Load<StarSystemConfig>(Path.Combine("systems", config.starSystem + ".json"), false);
if (starSystemConfig == null) starSystemConfig = new StarSystemConfig();
else NHLogger.LogWarning($"Loaded system config for {config.starSystem}. Why wasn't this loaded earlier?");
starSystemConfig.Migrate();
starSystemConfig.FixCoordinates();
var system = new NewHorizonsSystem(config.starSystem, starSystemConfig, $"", mod);
SystemDict.Add(config.starSystem, system);
BodyDict.Add(config.starSystem, new List<NewHorizonsBody>());
}
// Has to happen after we make sure theres a system config
config.Validate();
config.Migrate();
body = new NewHorizonsBody(config, mod, relativePath);
body = RegisterPlanetConfig(config, mod, relativePath);
}
catch (Exception e)
{
@ -798,6 +783,32 @@ namespace NewHorizons
return body;
}
public NewHorizonsBody RegisterPlanetConfig(PlanetConfig config, IModBehaviour mod, string relativePath)
{
if (!SystemDict.ContainsKey(config.starSystem))
{
// Since we didn't load it earlier there shouldn't be a star system config
var starSystemConfig = mod.ModHelper.Storage.Load<StarSystemConfig>(Path.Combine("systems", config.starSystem + ".json"), false);
if (starSystemConfig == null) starSystemConfig = new StarSystemConfig();
else NHLogger.LogWarning($"Loaded system config for {config.starSystem}. Why wasn't this loaded earlier?");
starSystemConfig.Migrate();
starSystemConfig.FixCoordinates();
var system = new NewHorizonsSystem(config.starSystem, starSystemConfig, $"", mod);
SystemDict.Add(config.starSystem, system);
BodyDict.Add(config.starSystem, new List<NewHorizonsBody>());
}
// Has to happen after we make sure theres a system config
config.Validate();
config.Migrate();
return new NewHorizonsBody(config, mod, relativePath);
}
public void SetDefaultSystem(string defaultSystem)
{
_defaultStarSystem = defaultSystem;

View File

@ -1,10 +1,14 @@
using NewHorizons.Builder.Props;
using NewHorizons.Builder.Props.Audio;
using NewHorizons.Builder.ShipLog;
using NewHorizons.External;
using NewHorizons.External.Configs;
using NewHorizons.External.Modules;
using NewHorizons.External.Modules.Props;
using NewHorizons.External.Modules.Props.Audio;
using NewHorizons.External.Modules.Props.Dialogue;
using NewHorizons.External.SerializableData;
using NewHorizons.OtherMods.MenuFramework;
using NewHorizons.Utility;
using NewHorizons.Utility.OWML;
using Newtonsoft.Json;
@ -15,8 +19,10 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using UnityEngine;
using UnityEngine.Events;
using static NewHorizons.External.Modules.ShipLogModule;
namespace NewHorizons
{
@ -215,5 +221,88 @@ namespace NewHorizons
return DialogueBuilder.Make(root, null, info, mod);
}
public void CreatePlanet(string config, IModBehaviour mod)
{
try
{
var planet = JsonConvert.DeserializeObject<PlanetConfig>(config);
if (planet == null)
{
NHLogger.LogError($"Couldn't load planet via API. Is your Json formatted correctly? {config}");
return;
}
var body = Main.Instance.RegisterPlanetConfig(planet, mod, null);
if (!Main.BodyDict.ContainsKey(body.Config.starSystem)) Main.BodyDict.Add(body.Config.starSystem, new List<NewHorizonsBody>());
Main.BodyDict[body.Config.starSystem].Add(body);
}
catch (Exception ex)
{
NHLogger.LogError($"Error in CreatePlanet API:\n{ex}");
}
}
public void DefineStarSystem(string name, string config, IModBehaviour mod)
{
var starSystemConfig = JsonConvert.DeserializeObject<StarSystemConfig>(config);
Main.Instance.LoadStarSystemConfig(name, starSystemConfig, null, mod);
}
public (CharacterDialogueTree, RemoteDialogueTrigger) CreateDialogueFromXML(string textAssetID, string xml, string dialogueInfo, GameObject planetGO)
{
var info = JsonConvert.DeserializeObject<DialogueInfo>(dialogueInfo);
return DialogueBuilder.Make(planetGO, null, info, xml, textAssetID);
}
public void AddShipLogXML(IModBehaviour mod, XElement xml, string planetName, string imageFolder, Dictionary<string, Vector2> entryPositions, Dictionary<string, (Color colour, Color highlight)> curiousityColours)
{
// This method has to be called each time the ship log manager is created, i.e. each time a system loads so it will only ever be relevant to the current one.
var starSystem = Main.Instance.CurrentStarSystem;
var body = new NewHorizonsBody(new PlanetConfig()
{
name = planetName,
starSystem = starSystem,
ShipLog = new ShipLogModule()
{
spriteFolder = imageFolder
}
}, mod);
if (!Main.BodyDict.ContainsKey(starSystem))
{
Main.BodyDict.Add(starSystem, new List<NewHorizonsBody>());
Main.BodyDict[starSystem].Add(body);
}
else
{
var existingBody = Main.BodyDict[starSystem]
.FirstOrDefault(x => x.Config.name == planetName && x.Mod.ModHelper.Manifest.UniqueName == mod.ModHelper.Manifest.UniqueName);
if (existingBody != null)
{
body = existingBody;
}
else
{
Main.BodyDict[starSystem].Add(body);
}
}
var system = new StarSystemConfig()
{
entryPositions = entryPositions?
.Select((pair) => new EntryPositionInfo() { id = pair.Key, position = pair.Value })
.ToArray(),
curiosities = curiousityColours?
.Select((pair) => new CuriosityColorInfo() { id = pair.Key, color = MColor.FromColor(pair.Value.colour), highlightColor = MColor.FromColor(pair.Value.highlight) })
.ToArray()
};
Main.Instance.LoadStarSystemConfig(starSystem, system, null, mod);
RumorModeBuilder.AddShipLogXML(GameObject.FindObjectOfType<ShipLogManager>(), xml, body);
}
}
}