diff --git a/Custom Menu Text/CustomMenuTest.csproj b/Custom Menu Text/CustomMenuText.csproj similarity index 97% rename from Custom Menu Text/CustomMenuTest.csproj rename to Custom Menu Text/CustomMenuText.csproj index bead503..507be5e 100644 --- a/Custom Menu Text/CustomMenuTest.csproj +++ b/Custom Menu Text/CustomMenuText.csproj @@ -9,8 +9,9 @@ Properties CustomMenuText CustomMenuText - v4.6.1 + v4.6 512 + true diff --git a/Custom Menu Text/CustomMenuText.sln b/Custom Menu Text/CustomMenuText.sln new file mode 100644 index 0000000..bb6eece --- /dev/null +++ b/Custom Menu Text/CustomMenuText.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27703.2035 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CustomMenuText", "CustomMenuText.csproj", "{5BF5B462-C110-4C72-A3C2-5E9A29920958}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5BF5B462-C110-4C72-A3C2-5E9A29920958}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5BF5B462-C110-4C72-A3C2-5E9A29920958}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5BF5B462-C110-4C72-A3C2-5E9A29920958}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5BF5B462-C110-4C72-A3C2-5E9A29920958}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {916CA203-6505-444F-BFBF-9DBAA7D5D768} + EndGlobalSection +EndGlobal diff --git a/Custom Menu Text/CustomMenuTextPlugin.cs b/Custom Menu Text/CustomMenuTextPlugin.cs index 1144929..267ef90 100644 --- a/Custom Menu Text/CustomMenuTextPlugin.cs +++ b/Custom Menu Text/CustomMenuTextPlugin.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.IO; using UnityEngine; using UnityEngine.SceneManagement; @@ -13,10 +12,12 @@ namespace TestBSPlugin public class CustomMenuTextPlugin : IPlugin { // path to the file to load text from - private const string FILE_PATH = "/CustomMenuText.txt"; + private const string FILE_PATH = "/UserData/CustomMenuText.txt"; + + public static readonly string[] DEFAULT_TEXT = { "BEAT", "SABER" }; public string Name => "Custom Menu Text"; - public string Version => "1.0.0"; + public string Version => "2.0.0"; public void OnApplicationStart() { SceneManager.activeSceneChanged += SceneManagerOnActiveSceneChanged; @@ -31,50 +32,98 @@ private void SceneManager_sceneLoaded(Scene arg0, LoadSceneMode arg1) { if (arg0.buildIndex == 1) // Menu scene { - // TODO: factor code out into sensible functions rather than one big block of code - - // keep the base game's text in case we don't find the file - string newFirstLine = "BEAT"; - string newSecondLine = "SABER"; - + // Look for the custom text file string gameDirectory = Environment.CurrentDirectory; gameDirectory = gameDirectory.Replace('\\', '/'); if (File.Exists(gameDirectory + FILE_PATH)) { - string dataInFile = File.ReadAllText(gameDirectory + FILE_PATH); - string[] entriesInFile = dataInFile.Split(new string[]{ "\n\n", "\r\n\r\n" }, StringSplitOptions.RemoveEmptyEntries); - System.Random r = new System.Random(); - string[] linesInFile = entriesInFile[r.Next(entriesInFile.Length)].Split(new string[]{ "\n","\r\n"},StringSplitOptions.RemoveEmptyEntries); + var linesInFile = File.ReadLines(gameDirectory + FILE_PATH); + + // Strip comments (all lines beginning with #) + linesInFile = linesInFile.Where(s => s == "" || s[0] != '#'); - // if there's no text in the file, we leave the default values - if (linesInFile.Length > 0) + // Collect entries, splitting on empty lines + List entriesInFile = new List(); + List currentEntry = new List(); + foreach(string line in linesInFile) { - // the first line exists, so use it - newFirstLine = linesInFile[0]; - if (linesInFile.Length > 1) + if (line == "") { - newSecondLine = linesInFile[1]; + entriesInFile.Add(currentEntry.ToArray()); + currentEntry.Clear(); } else { - // if the file has only one line, don't display a second line - newSecondLine = ""; + currentEntry.Add(line); } } + if (currentEntry.Count != 0) + { + // in case the last entry doesn't end in a newline + entriesInFile.Add(currentEntry.ToArray()); + } + + if (entriesInFile.Count == 0) + { + // No entries; warn and default to BEAT SABER + Console.WriteLine("[CustomMenuText] File found, but it contained no entries!"); + setText(DEFAULT_TEXT); + } + else + { + // Choose an entry randomly + + // Unity's random seems to give biased results + // int entryPicked = UnityEngine.Random.Range(0, entriesInFile.Count); + // using System.Random instead + System.Random r = new System.Random(); + int entryPicked = r.Next(entriesInFile.Count); + + // Set the text + setText(entriesInFile[entryPicked]); + } } + else + { + // No custom text file found! + // Print an error in the console + Console.WriteLine("[CustomMenuText] No custom text file found!"); + Console.WriteLine("Make sure the file is in the UserData folder and named CustomMenuText.txt!"); - // make sure text is in all caps - newFirstLine = newFirstLine.ToUpperInvariant(); - newSecondLine = newSecondLine.ToUpperInvariant(); + // Default to BEAT SABER + setText(DEFAULT_TEXT); + } + } + } - TextMeshPro wasB = GameObject.Find("B").GetComponent(); - TextMeshPro wasE = GameObject.Find("E").GetComponent(); - TextMeshPro wasAT = GameObject.Find("AT").GetComponent(); - TextMeshPro line2 = GameObject.Find("SABER").GetComponent(); + /// + /// Sets the text in the main menu (which normally reads BEAT SABER) to + /// the text of your choice. TextMeshPro formatting can be used here. + /// Additionally: + /// - If the text is exactly 2 lines long, the first line will be + /// displayed in blue, and the second will be displayed in red. + /// - If the first line contains exactly 4 characters, the second will + /// flicker (like the E in BEAT SABER). + /// Warning: Only call this function from the main menu scene! + /// + /// + /// The text to display, separated by lines (from top to bottom). + /// + public void setText(string[] lines) + { + TextMeshPro wasB = GameObject.Find("B").GetComponent(); + TextMeshPro wasE = GameObject.Find("E").GetComponent(); + TextMeshPro wasAT = GameObject.Find("AT").GetComponent(); + TextMeshPro wasSABER = GameObject.Find("SABER").GetComponent(); + + if (lines.Length == 2) + { + string newFirstLine = lines[0]; + string newSecondLine = lines[1]; // TODO: put more thought/work into keeping the flicker // currently this relies on the font being monospace, which it's not even - if (newFirstLine.Length == 4) + if (newFirstLine.Length == 4) { // we can fit it onto the existing text meshes perfectly // thereby keeping the flicker effect on the second character @@ -90,20 +139,42 @@ private void SceneManager_sceneLoaded(Scene arg0, LoadSceneMode arg1) // to make sure the text is centered, line up the AT with SABER's position // but keep its y value - Vector3 newPos = line2.transform.position; + Vector3 newPos = wasSABER.transform.position; newPos.y = wasAT.transform.position.y; wasAT.transform.position = newPos; wasAT.text = newFirstLine; } - line2.text = newSecondLine; + wasSABER.text = newSecondLine; // make sure text of any length won't wrap onto multiple lines wasAT.overflowMode = TextOverflowModes.Overflow; - line2.overflowMode = TextOverflowModes.Overflow; + wasSABER.overflowMode = TextOverflowModes.Overflow; wasAT.enableWordWrapping = false; - line2.enableWordWrapping = false; + wasSABER.enableWordWrapping = false; + } + else + { + // Hide "BEAT" entirely; we're just going to use SABER + wasB.text = ""; + wasE.text = ""; + wasAT.text = ""; + + // Center "SABER" vertically (halfway between the original positions) + Vector3 newPos = wasSABER.transform.position; + newPos.y = (newPos.y + wasB.transform.position.y) / 2; + wasSABER.transform.position = newPos; + + // Set text color to white by default (users can change it with formatting anyway) + wasSABER.color = Color.white; + + // Prevent undesired word wrap + wasSABER.overflowMode = TextOverflowModes.Overflow; + wasSABER.enableWordWrapping = false; + + // Set the text + wasSABER.text = String.Join("\n", lines); } }