mirror of
https://github.com/Outer-Wilds-New-Horizons/new-horizons.git
synced 2025-12-11 20:15:44 +01:00
Custom credits (#265)
Adds in scrolling credits support from a credits list written into the addon-manifest.
This commit is contained in:
commit
1bcbb1eb66
19
NewHorizons/Assets/addon-manifest.json
Normal file
19
NewHorizons/Assets/addon-manifest.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"credits": [
|
||||||
|
"xen#Mod Director\n#Programmer",
|
||||||
|
"Bwc9876#Mod Manager\n#Programmer\n#Dev Ops",
|
||||||
|
"FreezeDriedMangos#Programmer\n#Dev Tool Creator",
|
||||||
|
"MegaPiggy#Programmer",
|
||||||
|
"JohnCorby#Programmer",
|
||||||
|
"Hawkbat#Programmer",
|
||||||
|
"Trifid#Tester\n#Programmer",
|
||||||
|
"Nageld#Programmer",
|
||||||
|
"Ernesto#Fish",
|
||||||
|
"With help from#Raicuparta\n#dgarroDC\n#jtsalomo\n#and the modding community",
|
||||||
|
" ",
|
||||||
|
"Based off Marshmallow made by#Mister_Nebula",
|
||||||
|
"With help from#AmazingAlek\n#Raicuparta\n#and the Outer Wilds discord server",
|
||||||
|
" ",
|
||||||
|
"This work is unofficial Fan Content"
|
||||||
|
]
|
||||||
|
}
|
||||||
4
NewHorizons/External/Configs/AddonConfig.cs
vendored
4
NewHorizons/External/Configs/AddonConfig.cs
vendored
@ -24,5 +24,9 @@ namespace NewHorizons.External.Configs
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public AchievementInfo[] achievements;
|
public AchievementInfo[] achievements;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Credits info for this mod. A list of contributors and their roles separated by #. For example: xen#New Horizons dev.
|
||||||
|
/// </summary>
|
||||||
|
public string[] credits;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
218
NewHorizons/Handlers/CreditsHandler.cs
Normal file
218
NewHorizons/Handlers/CreditsHandler.cs
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Xml;
|
||||||
|
using UnityEngine;
|
||||||
|
using Logger = NewHorizons.Utility.Logger;
|
||||||
|
|
||||||
|
namespace NewHorizons.Handlers
|
||||||
|
{
|
||||||
|
public static class CreditsHandler
|
||||||
|
{
|
||||||
|
private static Dictionary<string, string[]> _creditsInfo;
|
||||||
|
|
||||||
|
public static void RegisterCredits(string sectionName, string[] entries)
|
||||||
|
{
|
||||||
|
if (_creditsInfo == null) _creditsInfo = new();
|
||||||
|
|
||||||
|
Logger.LogVerbose($"Registering credits for {sectionName}");
|
||||||
|
|
||||||
|
_creditsInfo[sectionName] = entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddCredits(Credits credits)
|
||||||
|
{
|
||||||
|
Logger.LogVerbose($"Adding to credits");
|
||||||
|
|
||||||
|
var creditsAsset = credits._creditsAsset;
|
||||||
|
|
||||||
|
var xml = new XmlDocument();
|
||||||
|
xml.LoadXml(creditsAsset.xml.text);
|
||||||
|
|
||||||
|
foreach (var pair in _creditsInfo)
|
||||||
|
{
|
||||||
|
AddCreditsSection(pair.Key, pair.Value, ref xml);
|
||||||
|
}
|
||||||
|
|
||||||
|
var outerXml = xml.OuterXml.Replace("/n", "
");
|
||||||
|
|
||||||
|
creditsAsset.xml = new TextAsset(outerXml);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddCreditsSection(string sectionName, string[] entries, ref XmlDocument xml)
|
||||||
|
{
|
||||||
|
var finalCredits = xml.SelectSingleNode("Credits/section");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Looks bad, would need more customization, complicated, messes up music timing, wont do for now
|
||||||
|
var nodeFade = CreateFadeCreditsFromList(xml, sectionName, entries);
|
||||||
|
finalCredits.InsertAfter(nodeFade, finalCredits.ChildNodes[0]);
|
||||||
|
*/
|
||||||
|
|
||||||
|
var fastCredits = NodeWhere(finalCredits.ChildNodes, "MainScrollSection");
|
||||||
|
var nodeScroll = CreateScrollCreditsFromList(xml, sectionName, entries);
|
||||||
|
fastCredits.InsertBefore(nodeScroll, fastCredits.ChildNodes[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static XmlNode NodeWhere(XmlNodeList list, string name)
|
||||||
|
{
|
||||||
|
foreach(XmlNode node in list)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (node.Attributes[0].Value == name) return node;
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Looked bad so not used
|
||||||
|
/*
|
||||||
|
private static XmlNode CreateFadeCreditsFromList(XmlDocument doc, string title, string[] entries)
|
||||||
|
{
|
||||||
|
var rootSection = MakeNode(doc, "section", new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{ "platform", "All" },
|
||||||
|
{ "type", "Fade" },
|
||||||
|
{ "fadeInTime", "1.3" },
|
||||||
|
{ "displayTime", "10" },
|
||||||
|
{ "fadeOutTime", "1.4" },
|
||||||
|
{ "waitTime", "0.5" },
|
||||||
|
{ "padding-bottom", "-8" },
|
||||||
|
{ "spacing", "16" }
|
||||||
|
});
|
||||||
|
|
||||||
|
var titleLayout = MakeNode(doc, "layout", new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{ "type", "SingleColumnFadeCentered" }
|
||||||
|
});
|
||||||
|
|
||||||
|
var titleNode = MakeNode(doc, "title", new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{"text-align", "UpperCenter" },
|
||||||
|
{"height", "122" }
|
||||||
|
});
|
||||||
|
titleNode.InnerText = title;
|
||||||
|
titleLayout.AppendChild(titleNode);
|
||||||
|
|
||||||
|
var type = "SingleColumnFadeCentered";
|
||||||
|
var xmlText = $"<layout type=\"{type}\" spacer-base-height=\"10\">\n";
|
||||||
|
for (int i = 0; i < entries.Length; i++)
|
||||||
|
{
|
||||||
|
var entry = entries[i];
|
||||||
|
|
||||||
|
if (entry.Contains("#"))
|
||||||
|
{
|
||||||
|
// Replace first one with a space
|
||||||
|
entry = RemoveExcessSpaces(entry);
|
||||||
|
var indexOfColon = entry.IndexOf(":");
|
||||||
|
var firstPart = entry.Substring(0, Math.Min(entry.IndexOf("#"), indexOfColon == -1 ? int.MaxValue : indexOfColon));
|
||||||
|
entry = firstPart + ": " + entry.Substring(entry.IndexOf("#") + 1);
|
||||||
|
}
|
||||||
|
entry = entry.Replace("#", ", ").Replace("/n", "");
|
||||||
|
|
||||||
|
xmlText += $"{entry}\n";
|
||||||
|
xmlText += "<spacer />\n";
|
||||||
|
}
|
||||||
|
xmlText += "<spacer height = \"295\" />\n";
|
||||||
|
xmlText += "</layout>";
|
||||||
|
|
||||||
|
rootSection.AppendChild(titleLayout);
|
||||||
|
foreach (var node in StringToNodes(doc, xmlText)) rootSection.AppendChild(node);
|
||||||
|
|
||||||
|
return rootSection;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string RemoveExcessSpaces(string s)
|
||||||
|
{
|
||||||
|
var options = RegexOptions.None;
|
||||||
|
Regex regex = new Regex("[ ]{2,}", options);
|
||||||
|
return regex.Replace(s, " ");
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
private static XmlNode CreateScrollCreditsFromList(XmlDocument doc, string title, string[] entries)
|
||||||
|
{
|
||||||
|
var rootSection = MakeNode(doc, "section", new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{ "platform", "All" },
|
||||||
|
{ "type", "Scroll" },
|
||||||
|
{ "scrollDuration", "214" },
|
||||||
|
{ "spacing", "12" },
|
||||||
|
{ "width", "1590" }
|
||||||
|
});
|
||||||
|
|
||||||
|
var titleLayout = MakeNode(doc, "layout", new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{ "type", "SingleColumnScrollCentered" }
|
||||||
|
});
|
||||||
|
|
||||||
|
var titleNode = MakeNode(doc, "title", new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{"text-align", "UpperCenter" },
|
||||||
|
{"height", "122" }
|
||||||
|
});
|
||||||
|
titleNode.InnerText = title;
|
||||||
|
titleLayout.AppendChild(titleNode);
|
||||||
|
|
||||||
|
|
||||||
|
var xmlText = "";
|
||||||
|
bool? flag = null;
|
||||||
|
for (int i = 0; i < entries.Length; i++)
|
||||||
|
{
|
||||||
|
var entry = entries[i];
|
||||||
|
|
||||||
|
var twoColumn = entry.Contains("#");
|
||||||
|
if (flag != twoColumn)
|
||||||
|
{
|
||||||
|
if (i != 0) xmlText += "</layout>";
|
||||||
|
var type = twoColumn ? "TwoColumnScrollAlignRightLeft" : "SingleColumnScrollCentered";
|
||||||
|
xmlText += $"<layout type=\"{type}\" spacer-base-height=\"10\">\n";
|
||||||
|
flag = twoColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
xmlText += $"{entry}\n";
|
||||||
|
xmlText += "<spacer/>";
|
||||||
|
}
|
||||||
|
xmlText += "<spacer height = \"295\" />\n";
|
||||||
|
xmlText += "</layout>";
|
||||||
|
|
||||||
|
rootSection.AppendChild(titleLayout);
|
||||||
|
foreach(var node in StringToNodes(doc, xmlText)) rootSection.AppendChild(node);
|
||||||
|
|
||||||
|
return rootSection;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static XmlNode[] StringToNodes(XmlDocument docContext, string text)
|
||||||
|
{
|
||||||
|
var doc = new XmlDocument();
|
||||||
|
// Doing this funny thing so that theres a single parent root thing
|
||||||
|
doc.LoadXml("<root>" + text + "</root>");
|
||||||
|
|
||||||
|
// ArgumentException: The node to be inserted is from a different document context.
|
||||||
|
var nodes = new List<XmlNode>();
|
||||||
|
foreach (XmlNode node in doc.DocumentElement.ChildNodes)
|
||||||
|
{
|
||||||
|
nodes.Add(docContext.ImportNode(node, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodes.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static XmlNode MakeNode(XmlDocument doc, string nodeType, Dictionary<string, string> attributes)
|
||||||
|
{
|
||||||
|
var xmlNode = doc.CreateElement(nodeType);
|
||||||
|
|
||||||
|
if (attributes != null)
|
||||||
|
{
|
||||||
|
foreach (var pair in attributes)
|
||||||
|
{
|
||||||
|
var attribute = doc.CreateAttribute(pair.Key);
|
||||||
|
attribute.Value = pair.Value;
|
||||||
|
xmlNode.Attributes.Append(attribute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return xmlNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -198,6 +198,8 @@ namespace NewHorizons
|
|||||||
|
|
||||||
AchievementHandler.Init();
|
AchievementHandler.Init();
|
||||||
VoiceHandler.Init();
|
VoiceHandler.Init();
|
||||||
|
|
||||||
|
LoadAddonManifest("Assets/addon-manifest.json", this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnDestroy()
|
public void OnDestroy()
|
||||||
@ -515,9 +517,7 @@ namespace NewHorizons
|
|||||||
// Has to go before translations for achievements
|
// Has to go before translations for achievements
|
||||||
if (File.Exists(folder + "addon-manifest.json"))
|
if (File.Exists(folder + "addon-manifest.json"))
|
||||||
{
|
{
|
||||||
var addonConfig = mod.ModHelper.Storage.Load<AddonConfig>("addon-manifest.json");
|
LoadAddonManifest("addon-manifest.json", mod);
|
||||||
|
|
||||||
AchievementHandler.RegisterAddon(addonConfig, mod as ModBehaviour);
|
|
||||||
}
|
}
|
||||||
if (Directory.Exists(folder + @"translations\"))
|
if (Directory.Exists(folder + @"translations\"))
|
||||||
{
|
{
|
||||||
@ -531,6 +531,16 @@ namespace NewHorizons
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void LoadAddonManifest(string file, IModBehaviour mod)
|
||||||
|
{
|
||||||
|
Logger.LogVerbose($"Loading addon manifest for {mod.ModHelper.Manifest.Name}");
|
||||||
|
|
||||||
|
var addonConfig = mod.ModHelper.Storage.Load<AddonConfig>(file);
|
||||||
|
|
||||||
|
if (addonConfig.achievements != null) AchievementHandler.RegisterAddon(addonConfig, mod as ModBehaviour);
|
||||||
|
if (addonConfig.credits != null) CreditsHandler.RegisterCredits(mod.ModHelper.Manifest.Name, addonConfig.credits);
|
||||||
|
}
|
||||||
|
|
||||||
private void LoadTranslations(string folder, IModBehaviour mod)
|
private void LoadTranslations(string folder, IModBehaviour mod)
|
||||||
{
|
{
|
||||||
var foundFile = false;
|
var foundFile = false;
|
||||||
|
|||||||
33
NewHorizons/Patches/CreditsScene/CreditsEntryPatches.cs
Normal file
33
NewHorizons/Patches/CreditsScene/CreditsEntryPatches.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
using HarmonyLib;
|
||||||
|
using NewHorizons.Utility;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace NewHorizons.Patches.CreditsScene
|
||||||
|
{
|
||||||
|
[HarmonyPatch]
|
||||||
|
public static class CreditsEntryPatches
|
||||||
|
{
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch(typeof(CreditsEntry), nameof(CreditsEntry.SetContents))]
|
||||||
|
public static bool CreditsEntry_SetContents(CreditsEntry __instance, string[] __0)
|
||||||
|
{
|
||||||
|
var columnTexts = __0;
|
||||||
|
|
||||||
|
for (int i = 0; i < __instance._columns.Length; i++)
|
||||||
|
{
|
||||||
|
// Base method throws out of bounds exception sometimes (_columns length doesn't match columnTexts length)
|
||||||
|
// Trim also NREs sometimes because of the TrimHelper class Mobius has idk
|
||||||
|
try
|
||||||
|
{
|
||||||
|
__instance._columns[i].text = columnTexts[i].Trim();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Error occurs when column 2 is empty
|
||||||
|
__instance._columns[i].text = " ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
NewHorizons/Patches/CreditsScene/CreditsPatches.cs
Normal file
17
NewHorizons/Patches/CreditsScene/CreditsPatches.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
using HarmonyLib;
|
||||||
|
using NewHorizons.Handlers;
|
||||||
|
|
||||||
|
namespace NewHorizons.Patches.CreditsScene
|
||||||
|
{
|
||||||
|
[HarmonyPatch]
|
||||||
|
public static class CreditsPatches
|
||||||
|
{
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch(typeof(Credits), nameof(Credits.Start))]
|
||||||
|
public static void Credits_Start(Credits __instance)
|
||||||
|
{
|
||||||
|
CreditsHandler.AddCredits(__instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -12,6 +12,13 @@
|
|||||||
"$ref": "#/definitions/AchievementInfo"
|
"$ref": "#/definitions/AchievementInfo"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"credits": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Credits info for this mod. A list of contributors and their roles separated by #. For example: xen#New Horizons dev.",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
"$schema": {
|
"$schema": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "The schema to validate with"
|
"description": "The schema to validate with"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user