diff --git a/Assets/Fungus/Scripts/Commands/Menu.cs b/Assets/Fungus/Scripts/Commands/Menu.cs index a64578dc..c8ac058a 100644 --- a/Assets/Fungus/Scripts/Commands/Menu.cs +++ b/Assets/Fungus/Scripts/Commands/Menu.cs @@ -17,6 +17,7 @@ namespace Fungus public class Menu : Command, ILocalizable { [Tooltip("Text to display on the menu button")] + [TextArea()] [SerializeField] protected string text = "Option Text"; [Tooltip("Notes about the option text for other authors, localization, etc.")] diff --git a/Assets/Fungus/Scripts/Utils/TextVariationHandler.cs b/Assets/Fungus/Scripts/Utils/TextVariationHandler.cs index dd305480..6af32241 100644 --- a/Assets/Fungus/Scripts/Utils/TextVariationHandler.cs +++ b/Assets/Fungus/Scripts/Utils/TextVariationHandler.cs @@ -14,6 +14,8 @@ namespace Fungus /// Default behaviour is to show one element after another and hold the final element. Such that [a|b|c] will show /// a the first time it is parsed, b the second and every subsequent time it will show c. /// + /// Empty sections are allowed, such that [a||c], on second showing it will have 0 characters. + /// /// This behaviour can be modified with certain characters at the start of the [], eg. [&a|b|c]; /// - & does not hold the final element it wraps back around to the begining in a looping fashion /// - ! does not hold the final element, it instead returns empty for the varying section @@ -21,95 +23,188 @@ namespace Fungus /// public static class TextVariationHandler { - static Dictionary hashedSections = new Dictionary(); - static StringBuilder sb = new StringBuilder(); - const string pattern = @"\[([^]]+?)\]"; - static Regex r = new Regex(pattern); - - private enum VaryType + public class Section { - Sequence, - Cycle, - Once, - Random + public VaryType type = VaryType.Sequence; + + public enum VaryType + { + Sequence, + Cycle, + Once, + Random + } + + public string entire = string.Empty; + public List elements = new List(); + + public string Select(ref int index) + { + switch (type) + { + case VaryType.Sequence: + index = UnityEngine.Mathf.Min(index, elements.Count - 1); + break; + case VaryType.Cycle: + index = index % elements.Count; + break; + case VaryType.Once: + //clamp to 1 more than options + index = UnityEngine.Mathf.Min(index, elements.Count); + break; + case VaryType.Random: + index = UnityEngine.Random.Range(0, elements.Count); + break; + default: + break; + } + + if (index >= 0 && index < elements.Count) + return elements[index]; + + return string.Empty; + } } - static public string SelectVariations(string input) + static Dictionary hashedSections = new Dictionary(); + //static StringBuilder sb = new StringBuilder(); + //const string pattern = @"\[([^]]+?)\]"; + //static Regex r = new Regex(pattern); + + /// + /// Simple parser to extract depth matched []. + /// + /// Such that a string of "[Hail and well met|Hello|[Good |]Morning] Traveler" will return + /// "[Hail and well met|Hello|[Good |]Morning]" + /// and string of "Hail and well met|Hello|[Good |]Morning" + /// will return [Good |] + /// + /// + /// + /// + static public bool TokenizeVarySections(string input, List
varyingSections) { - sb.Length = 0; - sb.Append(input); + varyingSections.Clear(); + int currentDepth = 0; + int curStartIndex = 0; + int curPipeIndex = 0; + Section curSection = null; - // Match the regular expression pattern against a text string. - var results = r.Matches(input); - for (int i = 0; i < results.Count; i++) + for (int i = 0; i < input.Length; i++) { - Match match = results[i]; + switch (input[i]) + { + case '[': + if (currentDepth == 0) + { + curSection = new Section(); + varyingSections.Add(curSection); - //determine type - VaryType t = VaryType.Sequence; + //determine type and skip control char + var typedIndicatingChar = input[i + 1]; + switch (typedIndicatingChar) + { + case '~': + curSection.type = Section.VaryType.Random; + break; + case '&': + curSection.type = Section.VaryType.Cycle; + break; + case '!': + curSection.type = Section.VaryType.Once; + break; + default: + break; + } - var typedIndicatingChar = match.Value[1]; - switch (typedIndicatingChar) - { - case '~': - t = VaryType.Random; + + //mark start + curStartIndex = i; + curPipeIndex = i + 1; + } + currentDepth++; break; - case '&': - t = VaryType.Cycle; + case ']': + if (currentDepth == 1) + { + + //extract, including the ] + curSection.entire = input.Substring(curStartIndex, i - curStartIndex + 1); + + + //close an element if we started one + if (curStartIndex != curPipeIndex - 1) + { + curSection.elements.Add(input.Substring(curPipeIndex, i - curPipeIndex)); + } + + //if has control var, clean first element + if(curSection.type != Section.VaryType.Sequence) + { + curSection.elements[0] = curSection.elements[0].Substring(1); + } + } + currentDepth--; break; - case '!': - t = VaryType.Once; + case '|': + if (currentDepth == 1) + { + //split + curSection.elements.Add(input.Substring(curPipeIndex, i - curPipeIndex)); + + //over the | on the next one + curPipeIndex = i + 1; + } break; default: break; } + } + + return varyingSections.Count > 0; + } + + + static public string SelectVariations(string input) + { + // Match the regular expression pattern against a text string. + List
sections = new List
(); + bool foundSections = TokenizeVarySections(input, sections); + + if (!foundSections) + return input; + + StringBuilder sb = new StringBuilder(); + sb.Length = 0; + sb.Append(input); - //explode and remove the control char - int startSubStrIndex = t != VaryType.Sequence ? 2 : 1; - int subStrLen = match.Value.Length - 1 - startSubStrIndex; - var exploded = match.Value.Substring(startSubStrIndex, subStrLen).Split('|'); + for (int i = 0; i < sections.Count; i++) + { + var curSection = sections[i]; string selected = string.Empty; //fetched hashed value int index = -1; - int key = input.GetHashCode() ^ match.Value.GetHashCode(); + int key = input.GetHashCode() ^ curSection.entire.GetHashCode(); int foundVal = 0; - if(hashedSections.TryGetValue(key, out foundVal)) + if (hashedSections.TryGetValue(key, out foundVal)) { index = foundVal; } - + index++; - switch (t) - { - case VaryType.Sequence: - index = UnityEngine.Mathf.Min(index, exploded.Length - 1); - break; - case VaryType.Cycle: - index = index % exploded.Length; - break; - case VaryType.Once: - //clamp to 1 more than options - index = UnityEngine.Mathf.Min(index, exploded.Length); - break; - case VaryType.Random: - index = UnityEngine.Random.Range(0, exploded.Length - 1); - break; - default: - break; - } + selected = curSection.Select(ref index); //update hashed value hashedSections[key] = index; - //selected updated if valid - if(index >=0 && index < exploded.Length) - selected = exploded[index]; - + //handle sub vary within selected section + selected = SelectVariations(selected); - sb.Replace(match.Value, selected); + //update with selecton + sb.Replace(curSection.entire, selected); } return sb.ToString(); diff --git a/Assets/FungusExamples/VariationText/TextVariation.unity b/Assets/FungusExamples/VariationText/TextVariation.unity index 26972784..8d58dba4 100644 --- a/Assets/FungusExamples/VariationText/TextVariation.unity +++ b/Assets/FungusExamples/VariationText/TextVariation.unity @@ -942,8 +942,10 @@ MonoBehaviour: y: -340 width: 1114 height: 859 - selectedBlocks: [] - selectedCommands: [] + selectedBlocks: + - {fileID: 1755499608} + selectedCommands: + - {fileID: 1755499607} variables: [] description: stepPause: 0 @@ -973,9 +975,9 @@ MonoBehaviour: stringVal: 'john calling-neutral left: [Good morning, it''s John|Hmm|Yes, still here]. - sherlock explaining right: [Who are you talking to?|Still at it, I see.] We + sherlock explaining right: [!Who are you talking to?||Still at it, I see.] We have a case to [~deal with|work|tend to|solve]. [!I''m right as rain.|Stop fussing - will you.]' + will you.|||Rude.] ' clearPrevious: booleanRef: {fileID: 0} booleanVal: 1