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"