Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- //-----------------------------------------
- // PowerText
- // More info/instructions:
- // http://powerhoof.com/PowerText
- // dave@powerhoof.com
- // @DuzzOnDrums
- //----------------------------------------
- using UnityEngine;
- using System.Collections;
- using System.Collections.Generic;
- using System.Text.RegularExpressions;
- using System.IO;
- using System.Text;
- using PowerTools;
- using PowerTools.PowerTextUtils;
- namespace PowerTools
- {
- #region Class: PowerText
- /** <summary>
- Usage:
- - Create a new PowerText().
- - Call Parse(string inputText); to parse the input text.
- - Call GetString(string groupName); to generate a string from the name of a group.
- See http://tools.powerhoof.com for examples
- */
- public class PowerText
- {
- #endregion
- #region Definitions
- // Matches any nonspace character between two semicolons, eg. :ANIMAL:
- static readonly Regex s_regexGroup = new Regex(@"^:(?<group> \S+?):", RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase);
- // Matches a stuff on a line within a group. This is probably insantely inefficient :D
- static readonly Regex s_regexLine = new Regex(
- @"(^//.*) |" // Comments are a line starting with '//'
- + @"(^\[(?<weight>\d*\.?\d+)\]) | " // Weight is a group that's just got a number in it at the start of a line (this controls how often that string will be used)
- + @"(?<empty>\[\s*\]) | " // Empty line is ignored unless it has [] on it
- + @"(?<ps> \[\#\]) | " // Start of pluralized denoted by [#] (eg: I have [#]40 hat[s])
- + @"(?<pe> \[\\\#\]) | " // End of pluralized denoted by [\#] (eg: I have [#]40 hat[s])
- + @"(?<s>\[s\]) | (?<es>\[es\]) | (\[/(?<plr> \w+)\]) | " // Plurals are [s], [es] or [/PluralOfPreviousWord]
- + @"(\[(?<ref> \S+?)\]) | " // Ref (reference to another group) is nonspace character between brackets eg. [ANIMAL] // TODO: Check why that 2nd ? is in there...
- + @"(?<text> [^\[\r]+)", // Text is anything that's not a new line.
- RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase);
- // Matches first letter of line, or first letter after a full stop or equivalent
- static readonly Regex s_regexCapital = new Regex(@"((?<=^\W*)\w) | ((?<=[.:!?]\s*)\w)", RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase);
- // Matches a followed by space and a vowel (unless directly after non-whitespace, ie the end of a word)
- static readonly Regex s_regexAn = new Regex(@"(?<!\S)a(?=\s+[aeiou])", RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase);
- static readonly char[] LINEDELIM = {'\n'};
- static readonly string MATCH_GROUP = "group";
- static readonly string MATCH_EMPTY = "empty";
- static readonly string MATCH_WEIGHT = "weight";
- static readonly string MATCH_REF = "ref";
- static readonly string MATCH_TEXT = "text";
- static readonly string MATCH_PLURALISE_START = "ps";
- static readonly string MATCH_PLURALISE_END = "pe";
- static readonly string MATCH_PLURAL_S = "s";
- static readonly string MATCH_PLURAL_ES = "es";
- static readonly string MATCH_PLURAL = "plr";
- #endregion
- #region Vars: private
- Dictionary<string, PowerTextNodeGroup> m_groups = new Dictionary<string, PowerTextNodeGroup>();
- #endregion
- #region Funcs: public
- /// Find a string from the group name (the thing between the colons. eg. :WeaponName: )
- public string GetString(string groupName)
- {
- string result = string.Empty;
- PowerTextNodeGroup group = null;
- if ( m_groups.TryGetValue(groupName.ToUpper(), out group) )
- {
- bool plural = false;
- StringBuilder builder = new StringBuilder();
- group.Build(builder, ref plural);
- result = builder.ToString();
- result = PostProcess(result);
- }
- return result;
- }
- /// Takes the input text and parses it. Do this before "GetString"
- public void Parse(string text)
- {
- string[] textLines = text.Split(LINEDELIM,10000);
- PowerTextNodeGroup currentGroup = null;
- PowerTextNodeLine currentLine = null;
- for (int i = 0; i < textLines.Length; ++i )
- {
- string lineText = textLines[i];
- Match groupMatch = s_regexGroup.Match(lineText);
- if ( groupMatch.Success )
- {
- // Parse group name
- string groupName = groupMatch.Groups[MATCH_GROUP].Value;
- currentGroup = FindOrCreateGroup(groupName);
- }
- else if ( currentGroup != null )
- {
- // Add node for line
- currentLine = new PowerTextNodeLine();
- bool lineHasContent = false;
- PowerTextNodeString lastStringNode = null;
- MatchCollection lineMatches = s_regexLine.Matches(lineText);
- foreach( Match lineMatch in lineMatches )
- {
- if ( lineMatch.Success && lineMatch.Groups == null )
- continue;
- if ( lineMatch.Groups[MATCH_EMPTY].Success)
- {
- lineHasContent = true;
- currentLine.Append( (PowerTextNodeString)(string.Empty) );
- }
- if ( lineMatch.Groups[MATCH_WEIGHT].Success)
- {
- lineHasContent = true;
- float.TryParse(lineMatch.Groups[MATCH_WEIGHT].Value, out currentLine.m_weight);
- }
- if ( lineMatch.Groups[MATCH_REF].Success)
- {
- lineHasContent = true;
- currentLine.Append( FindOrCreateGroup(lineMatch.Groups[MATCH_REF].Value) );
- }
- if ( lineMatch.Groups[MATCH_TEXT].Success )
- {
- string value = lineMatch.Groups[MATCH_TEXT].Value;
- if ( string.IsNullOrEmpty(value) == false )
- {
- lineHasContent = true;
- lastStringNode = (PowerTextNodeString)value;
- currentLine.Append( lastStringNode );
- }
- }
- if ( lineMatch.Groups[MATCH_PLURALISE_START].Success )
- {
- lineHasContent = true;
- currentLine.Append( new PowerTextNodePluralFlag(true) );
- }
- if ( lineMatch.Groups[MATCH_PLURALISE_END].Success )
- {
- currentLine.Append( new PowerTextNodePluralFlag(false) );
- }
- // Parse "plural" stuff
- if ( lastStringNode != null )
- {
- if ( lineMatch.Groups[MATCH_PLURAL_S].Success ) lastStringNode.SetPlural(PowerTextNodeString.ePluralType.s);
- if ( lineMatch.Groups[MATCH_PLURAL_ES].Success ) lastStringNode.SetPlural(PowerTextNodeString.ePluralType.es);
- if ( lineMatch.Groups[MATCH_PLURAL].Success )
- {
- string value = lineMatch.Groups[MATCH_PLURAL].Value;
- if ( string.IsNullOrEmpty(value) == false )
- {
- lastStringNode.SetPlural(PowerTextNodeString.ePluralType.Custom, value);
- }
- }
- }
- }
- if ( lineHasContent )
- currentGroup.AddOption(currentLine);
- }
- }
- }
- /// Sets a text variable by name (in the script, write [name] to use the variable)
- public void SetVariable(string name, string value)
- {
- ParseGroup(name, value);
- }
- /// Sets an int variable by name, marking it as plural if it's not 1 (in the script, write [name] to use the variable)
- public void SetVariable(string name, int value)
- {
- if ( value != 1 )
- ParseGroup(name, value.ToString()+"[#]");
- else
- ParseGroup(name, value.ToString());
- }
- #endregion
- #region Funcs: Private
- PowerTextNodeGroup FindOrCreateGroup(string name)
- {
- PowerTextNodeGroup node = null;
- name = name.ToUpper();
- if ( m_groups.TryGetValue(name, out node) )
- {
- return node;
- }
- node = new PowerTextNodeGroup();
- m_groups.Add(name, node);
- return node;
- }
- PowerTextNodeGroup ParseGroup(string name, string lines)
- {
- PowerTextNodeGroup group = FindOrCreateGroup(name);
- return group;
- }
- string PostProcess( string text )
- {
- // Capitalise first alphanumeric of sentance, or after . ; - ! ?
- text = s_regexCapital.Replace(text, ReplaceCapital);
- // Change a to an
- return s_regexAn.Replace(text,ReplaceAn);
- }
- string ReplaceAn(Match match)
- {
- return string.Concat(match.ToString(),'n');
- }
- string ReplaceCapital(Match match)
- {
- return match.ToString().ToUpper();
- }
- #endregion
- }
- #region Class: PowerTextNodes
- // Interface to PowerTextNode with generic function to generate the string
- public interface IPowerTextNode
- {
- void Build( StringBuilder builder, ref bool pluralize );
- float GetWeight();
- }
- // Node that returns random node from list
- public class PowerTextNodeGroup: IPowerTextNode
- {
- public List<IPowerTextNode> m_options = null;
- ShuffledIndex m_shuffledIndex;
- float m_maxWeight = 1;
- public void Build( StringBuilder builder, ref bool plural )
- {
- if ( m_options == null || m_options.Count <= 0 )
- return;
- if ( m_shuffledIndex == null ) m_shuffledIndex = new ShuffledIndex(m_options.Count);
- for ( int i = 0; i <= m_shuffledIndex.Length*2; ++i ) // Loop through shuffled list until find an entry that satisfies weight constraint (only necessary if all have weight of zero).
- {
- m_shuffledIndex.Next();
- float weight = m_options[m_shuffledIndex].GetWeight();
- if ( weight >= m_maxWeight || (weight > 0.0f && weight >= Random.value * m_maxWeight) )
- {
- m_options[m_shuffledIndex].Build(builder, ref plural);
- break;
- }
- }
- }
- public float GetWeight() { return 1.0f; }
- public void AddOption(IPowerTextNode node)
- {
- if ( m_options == null )
- m_options = new List<IPowerTextNode>();
- m_options.Add(node);
- m_maxWeight = Mathf.Max(m_maxWeight, node.GetWeight());
- }
- }
- // Node that returns a bunch of nodes concatenated (eg: "hello " + [name] + " how are you today?" is 3 PowerTextNodeLines)
- public class PowerTextNodeLine: IPowerTextNode
- {
- public List<IPowerTextNode> m_strings;
- public float m_weight = 1;
- public void Build( StringBuilder builder, ref bool plural )
- {
- if ( m_strings == null )
- return;
- for ( int i = 0; i < m_strings.Count; ++i )
- m_strings[i].Build(builder, ref plural);
- }
- public float GetWeight() { return m_weight; }
- public void Append(IPowerTextNode node)
- {
- if ( m_strings == null )
- m_strings = new List<IPowerTextNode>();
- m_strings.Add(node);
- }
- }
- public class PowerTextNodeString : IPowerTextNode
- {
- // Text nodes can have an alternate plural, migth j ust append 's' or 'es' or might replace the whole word
- public enum ePluralType
- {
- None,
- s,
- es,
- Custom
- }
- string m_string = null;
- ePluralType m_pluralType = ePluralType.None;
- string m_pluralString = null;
- public PowerTextNodeString(string str) {m_string = str;}
- public static implicit operator PowerTextNodeString(string str)
- {
- return new PowerTextNodeString(str);
- }
- public void SetPlural( ePluralType type, string customString = null )
- {
- m_pluralType = type;
- m_pluralString = customString;
- }
- public void Build( StringBuilder builder, ref bool plural )
- {
- // Check if it should make the text end with a plural
- if ( m_pluralType != ePluralType.None && plural )
- {
- if ( m_pluralType == ePluralType.Custom )
- {
- // Custom plural means it'll replace the entire last word (eg: life/lives)
- int pos = m_string.LastIndexOf(' ');
- if ( pos > 0 && pos < m_string.Length )
- builder.Append(m_string.Substring(0,pos));
- builder.Append(m_pluralString);
- }
- else if ( m_pluralType == ePluralType.s )
- {
- builder.Append(m_string);
- builder.Append('s');
- }
- else if ( m_pluralType == ePluralType.es )
- {
- builder.Append(m_string);
- builder.Append("es");
- }
- }
- else
- {
- builder.Append(m_string);
- }
- }
- public float GetWeight() { return 1.0f; }
- }
- public class PowerTextNodePluralFlag : IPowerTextNode
- {
- bool m_pluralize = false;
- public PowerTextNodePluralFlag(bool pluralize) { m_pluralize = pluralize; }
- public void Build( StringBuilder builder, ref bool pluralize ) { pluralize = m_pluralize; }
- public float GetWeight() { return 1.0f; }
- }
- #endregion
- }
- #region Class: Utils
- // Utils- Normally these would be in seperate file.
- namespace PowerTools.PowerTextUtils
- {
- public class ShuffledIndex
- {
- int m_current = -2; // -2 so if next() is called first, it'll still reshuffle the first time because the index will still be invalid
- int[] m_ids = null;
- public ShuffledIndex(int count)
- {
- m_ids = new int[count];
- for (int i = 0; i < count; ++i )
- {
- m_ids[i] = i;
- }
- }
- public int Next()
- {
- m_current++;
- return (int)this;
- }
- public static implicit operator int(ShuffledIndex m)
- {
- if ( m.m_ids.Length == 0 )
- return -1;
- if ( m.m_current < 0 || m.m_current >= m.m_ids.Length )
- {
- int previousValue = m.m_ids[m.m_ids.Length - 1];
- m.m_ids.Shuffle();
- m.m_current = 0;
- // Check if it's repeating the last value and if so, swap the first elements around so you don't get 2 in a row (only if more than 1 element)
- if ( m.m_ids.Length > 1 && previousValue == m.m_ids[0] )
- Utils.Swap(ref m.m_ids[0], ref m.m_ids[1]);
- }
- return m.m_ids[m.m_current];
- }
- public int Length { get{ return m_ids.Length; } }
- }
- public static class Utils
- {
- // Extention method to shuffle an array
- public static void Shuffle<T>( this T[] list )
- {
- T temp;
- int j = 0;
- int count = list.Length;
- for ( int i = count - 1; i >= 1; --i )
- {
- j = Random.Range(0,i+1);
- // swap
- temp = list[i];
- list[i] = list[j];
- list[j] = temp;
- }
- }
- // Usually I have this in a util class it's pretty useful :)
- public static void Swap<T>(ref T lhs, ref T rhs)
- {
- T temp;
- temp = lhs;
- lhs = rhs;
- rhs = temp;
- }
- }
- }
- #endregion
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement