diff --git a/DawnLib/src/API/Terminal/ModifyDisplayText.cs b/DawnLib/src/API/Terminal/ModifyDisplayText.cs new file mode 100644 index 00000000..dd0ad6c6 --- /dev/null +++ b/DawnLib/src/API/Terminal/ModifyDisplayText.cs @@ -0,0 +1,259 @@ +using System.Collections.Generic; +using System.Linq; +using Dawn.Internal; +using Dawn.Utils; + +namespace Dawn; +public class ModifyDisplayText +{ + private static List DawnDisplayTextInserts = []; + + public string Word = string.Empty; + public string TextToLookFor = string.Empty; + private string _textActuallyAdded = string.Empty; + internal TerminalNode ResultNode = null!; + public TerminalNode OriginalNode { get; private set; } + public IProvider AddedText; + + public Style ModifyStyle = Style.InsertLast; + + /// This determines your text modification style. (see remarks for more information) + /// + /// InsertAll - This will INSERT your AddedText at all locations that match your TextToLookFor property. + /// InsertFirst - This will INSERT your AddedText at the FIRST location that matches your TextToLookFor property. + /// InsertLast - This will INSERT your AddedText at the LAST location that matches your TextToLookFor property. + /// ReplaceAll - This will REPLACE all matching instances of your TextToLookFor property with your AddedText property. + /// ReplaceFirst - This will REPLACE the FIRST matching instance of your TextToLookFor property with your AddedText property. + /// ReplaceAll - This will REPLACE the LAST matching instance of your TextToLookFor property with your AddedText property. + /// + public enum Style + { + InsertAll, + InsertFirst, + InsertLast, + ReplaceAll, + ReplaceFirst, + ReplaceLast, + } + + /// Primary constructor for ModifyDisplayText. Takes a given keyword and modifies the resulting node's displaytext + /// This is the keyword a user enters to display the TerminalNode + /// This is the text you plan to add to the resulting TerminalNode. Can be any type of IProvider + /// (Optional) This is the text you are wishing to insert after OR replace, depending on the next parameter + /// (Optional) This determines your modification style. Options are defined under ModifyDisplayText.Style + /// Text modification is done one time at Terminal Start. This does not replace or add to the Terminal.TextPostProcess method + public ModifyDisplayText(string keyword, IProvider textToAdd, string textToLookFor = "", Style replaceStyle = Style.InsertLast) + { + Word = keyword; + AddedText = textToAdd; + TextToLookFor = textToLookFor; + ModifyStyle = replaceStyle; + + DawnDisplayTextInserts.Add(this); + } + + /// Secondary constructor for ModifyDisplayText. Takes a given TerminalNode and modifies the resulting node's displaytext + /// This is the TerminalNode you wish to modify. + /// This is the text you plan to add to the resulting TerminalNode. Can be any type of IProvider + /// (Optional) This is the text you are wishing to insert after OR replace, depending on the next parameter + /// (Optional) This determines your modification style. Options are defined under ModifyDisplayText.Style + /// + /// Text modification is done one time at Terminal Start. This does not replace or add to the Terminal.TextPostProcess method. + /// When utilizing this secondary constructor, if the TerminalNode is deleted and then re-created your text modifications will not take place. + /// You may utilize ManualResultNode(TerminalNode terminalNode) to manually update your resulting terminal node if you do not wish to use the primary constructor. + /// + public ModifyDisplayText(TerminalNode terminalNode, IProvider textToAdd, string textToLookFor = "", Style replaceStyle = Style.InsertLast) + { + ResultNode = terminalNode; + Word = string.Empty; + AddedText = textToAdd; + TextToLookFor = textToLookFor; + ModifyStyle = replaceStyle; + + DawnDisplayTextInserts.Add(this); + } + + /// This allows you to disable or re-enable your text modification + /// + /// If the text has already been modified this will not remove your modifications at runtime. + /// This simply removes your text modification from the list of modifications to run at Terminal Start. + /// + public void ToggleTextInsert(bool value) + { + if (value) + { + if (!DawnDisplayTextInserts.Contains(this)) + DawnDisplayTextInserts.Add(this); + } + else + { + DawnDisplayTextInserts.Remove(this); + } + } + + /// This allows you to manually set your Result TerminalNode + /// + /// Setting this manually to a non-null result will disable keyword to result node matching. + /// NOTE: Changing this after Terminal Start has no affect on the result node. + /// + public void ManualResultNode(TerminalNode terminalNode) + { + ResultNode = terminalNode; + } + + /// This allows you to manually remove your added text during runtime + /// + /// Running this method is not necessary unless there is a desire to remove the modifications before lobby reset. + /// Text modifications are automatically removed at lobby reset by default. + /// + public void RemoveAddedTextNow() + { + ResetNodeText(); + } + + /// This allows you to manually refresh your added text during runtime + /// + /// Running this method is not necessary unless there is a desire to refresh the text modifications before lobby reset. + /// Text modifications are automatically removed at lobby reset by default and refreshed at lobby load. + /// + public void RefreshAddedTextNow() + { + ResetNodeText(); + AddText(); + } + + internal static void AddAllTextInserts() + { + //remove any duplicates from list + DawnDisplayTextInserts = [.. DawnDisplayTextInserts.Distinct()]; + + foreach(ModifyDisplayText insert in DawnDisplayTextInserts) + { + insert.AddText(); + } + } + + //reset terminal nodes to original status + internal static void RemoveAllTextInserts() + { + foreach (ModifyDisplayText insert in DawnDisplayTextInserts) + { + insert.ResetNodeText(); + } + } + + private void AddText() + { + if (ResultNode == null) + { + if (!TryGetResultNode(out ResultNode)) + return; + } + + if (CheckForChanges()) + return; + + _textActuallyAdded = AddedText.Provide(); + + if (string.IsNullOrWhiteSpace(TextToLookFor)) + { + //make sure we're at least adding the new line if someone forgot it + if (!_textActuallyAdded.EndsWith('\n')) + _textActuallyAdded += "\n"; + + DawnPlugin.Logger.LogMessage($"Appending text to end of {ResultNode.name} - {_textActuallyAdded}"); + ResultNode.displayText += AddedText; + } + else + { + if (ResultNode.displayText.Contains(TextToLookFor)) + { + //insert text based on style defined + InsertTextWithStyle(); + } + else + { + //result displayText does not contain our expected text + DawnPlugin.Logger.LogWarning($"Unable to insert text after {TextToLookFor} for {ResultNode.name}, provided TextToLookFor does not exist!"); + } + + } + } + + private void InsertTextWithStyle() + { + switch (ModifyStyle) + { + case Style.InsertAll: + ResultNode.displayText = ResultNode.displayText.Replace(TextToLookFor, TextToLookFor + _textActuallyAdded); + DawnPlugin.Logger.LogMessage($"{ResultNode.name} has successfully replaced all instances of {TextToLookFor} with [{TextToLookFor + _textActuallyAdded}]"); + break; + + case Style.InsertFirst: + ResultNode.displayText = ResultNode.displayText.ReplaceAtFirstIndexOf(TextToLookFor, TextToLookFor + _textActuallyAdded); + DawnPlugin.Logger.LogMessage($"{ResultNode.name} has successfully replaced the FIRST instance of {TextToLookFor} with [{TextToLookFor + _textActuallyAdded}]"); + break; + + case Style.InsertLast: + ResultNode.displayText = ResultNode.displayText.ReplaceAtLastIndexOf(TextToLookFor, TextToLookFor + _textActuallyAdded); + DawnPlugin.Logger.LogMessage($"{ResultNode.name} has successfully replaced the LAST instance of {TextToLookFor} with [{TextToLookFor + _textActuallyAdded}]"); + break; + + case Style.ReplaceAll: + ResultNode.displayText = ResultNode.displayText.Replace(TextToLookFor, _textActuallyAdded); + DawnPlugin.Logger.LogMessage($"{ResultNode.name} has successfully replaced all instances of {TextToLookFor} with [{_textActuallyAdded}]"); + break; + + case Style.ReplaceFirst: + ResultNode.displayText = ResultNode.displayText.ReplaceAtFirstIndexOf(TextToLookFor, _textActuallyAdded); + DawnPlugin.Logger.LogMessage($"{ResultNode.name} has successfully replaced the FIRST instance of {TextToLookFor} with [{TextToLookFor + _textActuallyAdded}]"); + break; + + case Style.ReplaceLast: + ResultNode.displayText = ResultNode.displayText.ReplaceAtLastIndexOf(TextToLookFor, _textActuallyAdded); + DawnPlugin.Logger.LogMessage($"{ResultNode.name} has successfully replaced the LAST instance of {TextToLookFor} with [{TextToLookFor + _textActuallyAdded}]"); + break; + } + } + + private void ResetNodeText() + { + // result doesn't exist anymore, no need to reset anything + // OR no text has been added + if (ResultNode == null || string.IsNullOrEmpty(_textActuallyAdded)) + return; + + DawnPlugin.Logger.LogMessage($"Removing inserted text [{_textActuallyAdded}] from node - {ResultNode.name}"); + ResultNode.displayText = ResultNode.displayText.Replace(_textActuallyAdded, string.Empty); + } + + private bool CheckForChanges() + { + if (string.IsNullOrWhiteSpace(_textActuallyAdded)) + return false; + + return ResultNode.displayText.Contains(_textActuallyAdded); + } + + private bool TryGetResultNode(out TerminalNode result) + { + result = null!; + if (TerminalRefs.Instance.TryGetKeyword(Word, out TerminalKeyword terminalKeyword)) + { + if (terminalKeyword.specialKeywordResult == null) + { + CompatibleNoun compatibleNoun = terminalKeyword.defaultVerb.compatibleNouns.FirstOrDefault(k => k.noun == terminalKeyword); + if (compatibleNoun == null) + return false; + + result = compatibleNoun.result; + } + else + { + result = terminalKeyword.specialKeywordResult; + } + } + + return result != null; + } +} \ No newline at end of file diff --git a/DawnLib/src/DawnTesting.cs b/DawnLib/src/DawnTesting.cs index 5c585526..dd7be810 100644 --- a/DawnLib/src/DawnTesting.cs +++ b/DawnLib/src/DawnTesting.cs @@ -19,8 +19,10 @@ internal static void TestCommands() builder.SetClearTextFlags(TerminalCommandRegistration.ClearText.Result | TerminalCommandRegistration.ClearText.Query); }); + ModifyDisplayText versionAdd = new("help", new SimpleProvider("\n\n>VERSION\nDisplay Dawnlib's current version"), "record.", ModifyDisplayText.Style.InsertFirst); + UnityEvent test = new(); - DawnLib.DefineTerminalCommand(NamespacedKey.From("dawn_lib", "lights_command"), "DawnLibLights", builder => + DawnTerminalCommandInfo lightsCommand = DawnLib.DefineTerminalCommand(NamespacedKey.From("dawn_lib", "lights_command"), "DawnLibLights", builder => { builder.SetEnabled(new SimpleProvider(true)); builder.SetMainText(BasicLightsCommand); @@ -37,6 +39,8 @@ internal static void TestCommands() test.Invoke(); }; + ModifyDisplayText lightsAdd = new("other", new FuncProvider(() => $"\n\n>{ListToString(LightsKeywords(), ", ").ToUpperInvariant()}\nToggle ship interior lights"), "planet."); + DawnLib.DefineTerminalCommand(NamespacedKey.From("dawn_lib", "test_input_command"), "DawnLibInputs", builder => { builder.SetEnabled(new SimpleProvider(true)); @@ -47,6 +51,8 @@ internal static void TestCommands() builder.SetDescription("Takes the player's input and uses it on the next screen."); }); + ModifyDisplayText inputCommand = new("store", new SimpleProvider($"\nThis is a hint to input\n\n")); + DawnLib.DefineTerminalCommand(NamespacedKey.From("dawn_lib", "test_query_command"), "DawnLibQuery", builder => { builder.SetEnabled(new SimpleProvider(true)); @@ -63,6 +69,21 @@ internal static void TestCommands() queryCommandBuilder.SetCancelWord("no"); }); }); + + ModifyDisplayText ReplaceAllTest = new("help", new SimpleProvider("///"), ">", ModifyDisplayText.Style.ReplaceAll); + ModifyDisplayText ReplaceLastTest = new("help", new SimpleProvider("da"), "the", ModifyDisplayText.Style.ReplaceLast); + } + + private static string ListToString(List list, string separator) + { + string result = string.Empty; + + foreach(string item in list) + { + result += item + separator; + } + + return result.RemoveEnd(separator); } private static string BasicLightsCommand() @@ -99,6 +120,6 @@ private static bool ShouldAddVersion() private static List LightsKeywords() { - return ["lights", "dark", "vow", "help"]; + return ["lights", "dark"]; } } diff --git a/DawnLib/src/Internal/Patches/TerminalPatches.cs b/DawnLib/src/Internal/Patches/TerminalPatches.cs index e06b7f7d..3ca09ef4 100644 --- a/DawnLib/src/Internal/Patches/TerminalPatches.cs +++ b/DawnLib/src/Internal/Patches/TerminalPatches.cs @@ -74,12 +74,18 @@ private static void TerminalDisableHook(On.Terminal.orig_OnDisable orig, Termina //still need to run the method orig(self); + + //Remove all InsertToDisplayText changes and return nodes to original values + ModifyDisplayText.RemoveAllTextInserts(); } private static void TerminalStartHook(On.Terminal.orig_Start orig, Terminal self) { orig(self); + //Run all InsertToDisplayText changes + ModifyDisplayText.AddAllTextInserts(); + //assign priorities to any remaining keywords that have not received a value yet //also assign descriptions/category if unassigned //doing this in start to give time after Terminal.Awake where commands are created diff --git a/DawnLib/src/Utils/Extensions/StringExtensions.cs b/DawnLib/src/Utils/Extensions/StringExtensions.cs index d242ba31..2eece1ed 100644 --- a/DawnLib/src/Utils/Extensions/StringExtensions.cs +++ b/DawnLib/src/Utils/Extensions/StringExtensions.cs @@ -25,6 +25,32 @@ public static string RemoveEnd(this string input, string end) return input; } + public static string ReplaceAtFirstIndexOf(this string value, string expectedText, string replacement) + { + int index = value.IndexOf(expectedText); + if(index < 0) + { + DawnPlugin.Logger.LogWarning($"InsertAtFirstIndexOf: Unable to find expected text - {expectedText} in string - {value}. Text is unchanged"); + return value; + } + + return value.Remove(index, expectedText.Length).Insert(index, replacement); + + } + + public static string ReplaceAtLastIndexOf(this string value, string expectedText, string replacement) + { + int index = value.LastIndexOf(expectedText); + if (index < 0) + { + DawnPlugin.Logger.LogWarning($"InsertAtLastIndexOf: Unable to find expected text - {expectedText} in string - {value}. Text is unchanged"); + return value; + } + + return value.Remove(index, expectedText.Length).Insert(index, replacement); + + } + public static string StripSpecialCharacters(this string input) { string returnString = string.Empty; diff --git a/DawnLib/src/Utils/Extensions/TerminalExtensions.cs b/DawnLib/src/Utils/Extensions/TerminalExtensions.cs index 39833ac0..6481e3bf 100644 --- a/DawnLib/src/Utils/Extensions/TerminalExtensions.cs +++ b/DawnLib/src/Utils/Extensions/TerminalExtensions.cs @@ -88,7 +88,6 @@ public static bool TryGetKeyword(this Terminal terminal, string keyWord, out Ter { if (keyWord.CompareStringsInvariant(keyword.word)) { - //Loggers.LogDebug($"Keyword: [{keyWord}] found!"); terminalKeyword = keyword; return true; }