diff --git a/BepInEx5Plugins.Ash.Alisa.TextScrolling/BepInEx5Plugins.Ash.Alisa.TextScrolling.csproj b/BepInEx5Plugins.Ash.Alisa.TextScrolling/BepInEx5Plugins.Ash.Alisa.TextScrolling.csproj
new file mode 100644
index 0000000..6ced828
--- /dev/null
+++ b/BepInEx5Plugins.Ash.Alisa.TextScrolling/BepInEx5Plugins.Ash.Alisa.TextScrolling.csproj
@@ -0,0 +1,37 @@
+
+
+
+ net35
+ BepInEx5Plugins.Ash.Alisa.TextScrolling
+ TextScrolling
+ 1.0.0
+ true
+ 9.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ..\lib\SteamRelease\Assembly-CSharp.dll
+ false
+
+
+ ..\lib\UnityEngine\UnityEngine.UI.dll
+ false
+
+
+
+
+
+
+
diff --git a/BepInEx5Plugins.Ash.Alisa.TextScrolling/HarmonyPatches/TextBoxManager_EnableTextBox.cs b/BepInEx5Plugins.Ash.Alisa.TextScrolling/HarmonyPatches/TextBoxManager_EnableTextBox.cs
new file mode 100644
index 0000000..1dce498
--- /dev/null
+++ b/BepInEx5Plugins.Ash.Alisa.TextScrolling/HarmonyPatches/TextBoxManager_EnableTextBox.cs
@@ -0,0 +1,32 @@
+using HarmonyLib;
+using Object = UnityEngine.Object;
+
+namespace BepInEx5Plugins.Ash.Alisa.TextScrolling.HarmonyPatches
+{
+ [HarmonyPatch(typeof(TextBoxManager), "EnableTextBox")]
+ public class TextBoxManager_EnableTextBox
+ {
+ // Replace call to TextScroll.
+ public static bool Prefix(TextBoxManager __instance
+ , ref bool ___cantSkip
+ )
+ {
+ ___cantSkip = true;
+ __instance.isActive = true;
+
+ var interaction = Object.FindObjectOfType();
+
+ if (interaction)
+ {
+ interaction.interactionIsActive = true;
+ }
+
+ if (__instance.isScrollingText)
+ {
+ __instance.StartCoroutine(TextBoxManager_Update.TextScroll(__instance, __instance.textLines[__instance.currentLine]));
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/BepInEx5Plugins.Ash.Alisa.TextScrolling/HarmonyPatches/TextBoxManager_Update.cs b/BepInEx5Plugins.Ash.Alisa.TextScrolling/HarmonyPatches/TextBoxManager_Update.cs
new file mode 100644
index 0000000..555b301
--- /dev/null
+++ b/BepInEx5Plugins.Ash.Alisa.TextScrolling/HarmonyPatches/TextBoxManager_Update.cs
@@ -0,0 +1,227 @@
+using HarmonyLib;
+using System;
+using System.Collections;
+using System.Reflection;
+using System.Text.RegularExpressions;
+using UnityEngine;
+using UnityEngine.UI;
+
+namespace BepInEx5Plugins.Ash.Alisa.TextScrolling.HarmonyPatches
+{
+ [HarmonyPatch(typeof(TextBoxManager), "Update")]
+ public class TextBoxManager_Update
+ {
+ public static float typeSpeed = 1f;
+
+ public static bool allowEscapedLineBreak = false;
+
+ public static bool stripLineBreaks = false;
+
+ public static string lineBreakReplacement = " ";
+
+
+ private static FieldInfo TextBoxManager_cancelTyping;
+
+ private static FieldInfo TextBoxManager_cantSkip;
+
+ private static FieldInfo TextBoxManager_skipTimer;
+
+ private static FieldInfo TextBoxManager_typeSpeed;
+
+ private static MethodInfo TextBoxManager_TextLanguage;
+
+ public static bool Prepare(MethodBase original)
+ {
+ if (original is null)
+ {
+ try
+ {
+ TextBoxManager_cancelTyping = typeof(TextBoxManager).GetField("cancelTyping", BindingFlags.NonPublic | BindingFlags.Instance);
+ TextBoxManager_cantSkip = typeof(TextBoxManager).GetField("cantSkip", BindingFlags.NonPublic | BindingFlags.Instance);
+ TextBoxManager_skipTimer = typeof(TextBoxManager).GetField("skipTimer", BindingFlags.NonPublic | BindingFlags.Instance);
+ TextBoxManager_typeSpeed = typeof(TextBoxManager).GetField("typeSpeed", BindingFlags.NonPublic | BindingFlags.Instance);
+ TextBoxManager_TextLanguage = typeof(TextBoxManager).GetMethod("TextLanguage", BindingFlags.NonPublic | BindingFlags.Instance);
+ }
+ catch (Exception exception)
+ {
+ Console.WriteLine(exception);
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ // Replace call to TextScroll.
+ private static bool Prefix(TextBoxManager __instance
+ , ref Interaction ___interaction
+ , ref Settings ___settings
+ )
+ {
+ TextBoxManager_TextLanguage.Invoke(__instance, null);
+
+ if (!__instance.isActive)
+ {
+ return false;
+ }
+
+ if (!__instance.isScrollingText)
+ {
+ __instance.theText.text = __instance.textLines[__instance.currentLine];
+
+ if (!__instance.isSubtitles
+ && (Input.GetKeyDown(___settings.keys["Action"])
+ || Input.GetKeyDown(___settings.keys["Cancel"]))
+ && !(bool)TextBoxManager_cantSkip.GetValue(__instance))
+ {
+ TextBoxManager_cantSkip.SetValue(__instance, true);
+ ++__instance.currentLine;
+ }
+
+ if (__instance.currentLine > __instance.endAtLine)
+ {
+ __instance.DisableTextBox();
+ }
+ }
+
+ if (__instance.isScrollingText
+ && (Input.GetKeyDown(___settings.keys["Action"])
+ || Input.GetKeyDown(___settings.keys["Cancel"]))
+ )
+ {
+ if (!__instance.isTyping)
+ {
+ TextBoxManager_cantSkip.SetValue(__instance, true);
+ ++__instance.currentLine;
+
+ if (__instance.currentLine > __instance.endAtLine)
+ {
+ __instance.DisableTextBox();
+ }
+ else
+ {
+ __instance.StartCoroutine(TextScroll(__instance, __instance.textLines[__instance.currentLine]));
+ }
+ }
+ else if (__instance.isTyping && !(bool)TextBoxManager_cancelTyping.GetValue(__instance))
+ {
+ TextBoxManager_cancelTyping.SetValue(__instance, true);
+ }
+ }
+
+ if (___interaction.watchingDoor)
+ {
+ __instance.watchDoor = true;
+ }
+ else
+ {
+ __instance.watchDoor = false;
+ }
+
+ if ((bool)TextBoxManager_cantSkip.GetValue(__instance))
+ {
+ TextBoxManager_skipTimer.SetValue(__instance, (float)TextBoxManager_skipTimer.GetValue(__instance) + Time.deltaTime);
+
+ if ((float)TextBoxManager_skipTimer.GetValue(__instance) > 0.4f)
+ {
+ TextBoxManager_cantSkip.SetValue(__instance, false);
+ TextBoxManager_skipTimer.SetValue(__instance, 0f);
+ }
+ }
+
+ return false;
+ }
+
+ // Add support for word breaks.
+ // Add support for color tags.
+ // Add support for custom line replacement.
+ public static IEnumerator TextScroll(TextBoxManager textBox, string lineOfText)
+ {
+ foreach (var outline in textBox.theText.GetComponents())
+ {
+ outline.useGraphicAlpha = true;
+ }
+
+ textBox.isTyping = true;
+ TextBoxManager_cancelTyping.SetValue(textBox, false);
+
+#pragma warning disable Harmony003 // Harmony non-ref patch parameters modified
+ var br = (string)null;
+
+ if (stripLineBreaks)
+ {
+ br = lineBreakReplacement;
+ }
+ else
+ {
+ br = "\n";
+ }
+
+ if (allowEscapedLineBreak)
+ {
+ lineOfText = lineOfText
+ .Replace("\\@", "\0")
+ .Replace("@", br)
+ .Replace("\0", "@");
+ }
+ else
+ {
+ lineOfText = lineOfText.Replace("@", br);
+ }
+#pragma warning restore Harmony003 // Harmony non-ref patch parameters modified
+
+ var typeSpeed = (float)TextBoxManager_typeSpeed.GetValue(textBox) * TextBoxManager_Update.typeSpeed;
+ var colorTagRegex = new Regex(@"(<\/color>|)");
+ var hasColor = false;
+ var letter = 1;
+
+ while (textBox.isTyping
+ && !(bool)TextBoxManager_cancelTyping.GetValue(textBox)
+ && letter < lineOfText.Length - 1)
+ {
+ if (lineOfText[letter] == '<')
+ {
+ var sub = lineOfText.Substring(letter);
+
+ if (sub.StartsWith(""))
+ {
+ hasColor = false;
+ letter += "".Length;
+ sub = lineOfText.Substring(letter);
+
+ textBox.theText.text = lineOfText.Substring(0, letter) + "" + colorTagRegex.Replace(sub, "") + "";
+ }
+ else if (sub.StartsWith("" + "" + colorTagRegex.Replace(sub, "") + "";
+ }
+ }
+ else
+ {
+ if (!hasColor)
+ {
+ textBox.theText.text = lineOfText.Substring(0, letter) + "" + colorTagRegex.Replace(lineOfText.Substring(letter), "") + "";
+ }
+ else
+ {
+ textBox.theText.text = lineOfText.Substring(0, letter) + "" + "" + colorTagRegex.Replace(lineOfText.Substring(letter), "") + "";
+ }
+
+ ++letter;
+ }
+
+ yield return new WaitForSeconds(typeSpeed);
+ }
+
+ textBox.theText.text = lineOfText;
+ textBox.isTyping = false;
+ TextBoxManager_cancelTyping.SetValue(textBox, false);
+ }
+ }
+}
diff --git a/BepInEx5Plugins.Ash.Alisa.TextScrolling/NuGet.Config b/BepInEx5Plugins.Ash.Alisa.TextScrolling/NuGet.Config
new file mode 100644
index 0000000..1864ded
--- /dev/null
+++ b/BepInEx5Plugins.Ash.Alisa.TextScrolling/NuGet.Config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/BepInEx5Plugins.Ash.Alisa.TextScrolling/Plugin.cs b/BepInEx5Plugins.Ash.Alisa.TextScrolling/Plugin.cs
new file mode 100644
index 0000000..55ec853
--- /dev/null
+++ b/BepInEx5Plugins.Ash.Alisa.TextScrolling/Plugin.cs
@@ -0,0 +1,60 @@
+using BepInEx;
+using BepInEx.Configuration;
+using HarmonyLib;
+using System;
+
+namespace BepInEx5Plugins.Ash.Alisa.TextScrolling
+{
+ [BepInPlugin(PluginInfo.PLUGIN_GUID, PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)]
+ public class Plugin : BaseUnityPlugin
+ {
+ private readonly ConfigEntry typeSpeed;
+
+ private readonly ConfigEntry allowEscapedLineBreak;
+
+ private readonly ConfigEntry lineBreakReplacement;
+
+ private readonly ConfigEntry stripLineBreaks;
+
+ private Plugin()
+ {
+ typeSpeed = Config.Bind("TextScrolling", "Type Speed", 1f, "Multiply the speed of the type writer animation by this amount.");
+ allowEscapedLineBreak = Config.Bind("TextScrolling", "Allow Escaped Line Break", true, "If enabled, convert \\@ to @.");
+ stripLineBreaks = Config.Bind("TextScrolling", "Strip Line Breaks", false, "If enabled, remove manually inserted line breaks.");
+ lineBreakReplacement = Config.Bind("TextScrolling", "Line Break Replacement", " ", "If 'Strip Line Breaks' is true, replace line breaks with this value.");
+
+ Config.SettingChanged += Config_SettingChanged;
+
+ ApplySettings();
+ }
+
+ private void Config_SettingChanged(object sender, EventArgs e)
+ {
+ ApplySettings();
+ }
+
+ private void ApplySettings()
+ {
+ HarmonyPatches.TextBoxManager_Update.typeSpeed = typeSpeed.Value;
+ HarmonyPatches.TextBoxManager_Update.allowEscapedLineBreak = allowEscapedLineBreak.Value;
+ HarmonyPatches.TextBoxManager_Update.stripLineBreaks = stripLineBreaks.Value;
+ HarmonyPatches.TextBoxManager_Update.lineBreakReplacement = lineBreakReplacement.Value;
+ }
+
+ private void Awake()
+ {
+ try
+ {
+ Logger.LogInfo($"Plugin {PluginInfo.PLUGIN_GUID} is loaded!");
+
+ var harmony = new Harmony(Info.Metadata.GUID);
+
+ harmony.PatchAll();
+ }
+ catch (Exception exception)
+ {
+ Console.WriteLine(exception);
+ }
+ }
+ }
+}