diff --git a/NewHorizons/Assets/addon-manifest.json b/NewHorizons/Assets/addon-manifest.json
new file mode 100644
index 00000000..4a7dd87c
--- /dev/null
+++ b/NewHorizons/Assets/addon-manifest.json
@@ -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"
+ ]
+}
diff --git a/NewHorizons/External/Configs/AddonConfig.cs b/NewHorizons/External/Configs/AddonConfig.cs
index bf89193f..2fe85d7b 100644
--- a/NewHorizons/External/Configs/AddonConfig.cs
+++ b/NewHorizons/External/Configs/AddonConfig.cs
@@ -24,5 +24,9 @@ namespace NewHorizons.External.Configs
///
public AchievementInfo[] achievements;
+ ///
+ /// Credits info for this mod. A list of contributors and their roles separated by #. For example: xen#New Horizons dev.
+ ///
+ public string[] credits;
}
}
diff --git a/NewHorizons/Handlers/CreditsHandler.cs b/NewHorizons/Handlers/CreditsHandler.cs
new file mode 100644
index 00000000..9d7e7405
--- /dev/null
+++ b/NewHorizons/Handlers/CreditsHandler.cs
@@ -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 _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()
+ {
+ { "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()
+ {
+ { "type", "SingleColumnFadeCentered" }
+ });
+
+ var titleNode = MakeNode(doc, "title", new Dictionary()
+ {
+ {"text-align", "UpperCenter" },
+ {"height", "122" }
+ });
+ titleNode.InnerText = title;
+ titleLayout.AppendChild(titleNode);
+
+ var type = "SingleColumnFadeCentered";
+ var xmlText = $"\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 += "\n";
+ }
+ xmlText += "\n";
+ xmlText += "";
+
+ 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()
+ {
+ { "platform", "All" },
+ { "type", "Scroll" },
+ { "scrollDuration", "214" },
+ { "spacing", "12" },
+ { "width", "1590" }
+ });
+
+ var titleLayout = MakeNode(doc, "layout", new Dictionary()
+ {
+ { "type", "SingleColumnScrollCentered" }
+ });
+
+ var titleNode = MakeNode(doc, "title", new Dictionary()
+ {
+ {"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 += "";
+ var type = twoColumn ? "TwoColumnScrollAlignRightLeft" : "SingleColumnScrollCentered";
+ xmlText += $"\n";
+ flag = twoColumn;
+ }
+
+ xmlText += $"{entry}\n";
+ xmlText += "";
+ }
+ xmlText += "\n";
+ xmlText += "";
+
+ 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("" + text + "");
+
+ // ArgumentException: The node to be inserted is from a different document context.
+ var nodes = new List();
+ 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 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;
+ }
+ }
+}
diff --git a/NewHorizons/Main.cs b/NewHorizons/Main.cs
index 452201fe..7b51f660 100644
--- a/NewHorizons/Main.cs
+++ b/NewHorizons/Main.cs
@@ -198,6 +198,8 @@ namespace NewHorizons
AchievementHandler.Init();
VoiceHandler.Init();
+
+ LoadAddonManifest("Assets/addon-manifest.json", this);
}
public void OnDestroy()
@@ -515,9 +517,7 @@ namespace NewHorizons
// Has to go before translations for achievements
if (File.Exists(folder + "addon-manifest.json"))
{
- var addonConfig = mod.ModHelper.Storage.Load("addon-manifest.json");
-
- AchievementHandler.RegisterAddon(addonConfig, mod as ModBehaviour);
+ LoadAddonManifest("addon-manifest.json", mod);
}
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(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)
{
var foundFile = false;
diff --git a/NewHorizons/Patches/CreditsScene/CreditsEntryPatches.cs b/NewHorizons/Patches/CreditsScene/CreditsEntryPatches.cs
new file mode 100644
index 00000000..fe255ed6
--- /dev/null
+++ b/NewHorizons/Patches/CreditsScene/CreditsEntryPatches.cs
@@ -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;
+ }
+ }
+}
diff --git a/NewHorizons/Patches/CreditsScene/CreditsPatches.cs b/NewHorizons/Patches/CreditsScene/CreditsPatches.cs
new file mode 100644
index 00000000..724ef696
--- /dev/null
+++ b/NewHorizons/Patches/CreditsScene/CreditsPatches.cs
@@ -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);
+ }
+ }
+}
+
diff --git a/NewHorizons/Schemas/addon_manifest_schema.json b/NewHorizons/Schemas/addon_manifest_schema.json
index 4df4aa0f..bfe88b86 100644
--- a/NewHorizons/Schemas/addon_manifest_schema.json
+++ b/NewHorizons/Schemas/addon_manifest_schema.json
@@ -12,6 +12,13 @@
"$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": {
"type": "string",
"description": "The schema to validate with"