daily pastebin goal
33%
SHARE
TWEET

PowerText

Powerhoof Mar 20th, 2017 (edited) 123 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. //-----------------------------------------
  2. //          PowerText
  3. // More info/instructions:
  4. //  http://powerhoof.com/PowerText
  5. //  dave@powerhoof.com
  6. //  @DuzzOnDrums
  7. //----------------------------------------
  8.  
  9. using UnityEngine;
  10. using System.Collections;
  11. using System.Collections.Generic;
  12. using System.Text.RegularExpressions;
  13. using System.IO;
  14. using System.Text;
  15. using PowerTools;
  16. using PowerTools.PowerTextUtils;
  17.  
  18. namespace PowerTools
  19. {
  20.  
  21. #region Class: PowerText
  22.  
  23. /** <summary>
  24. Usage:
  25. - Create a new PowerText().
  26. - Call Parse(string inputText); to parse the input text.
  27. - Call GetString(string groupName); to generate a string from the name of a group.
  28.  
  29. See http://tools.powerhoof.com for examples
  30. */
  31. public class PowerText
  32. {
  33.     #endregion
  34.     #region Definitions
  35.  
  36.     // Matches any nonspace character between two semicolons, eg. :ANIMAL:
  37.     static readonly Regex s_regexGroup = new Regex(@"^:(?<group> \S+?):", RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase);  
  38.  
  39.     // Matches a stuff on a line within a group. This is probably insantely inefficient :D
  40.     static readonly Regex s_regexLine = new Regex(
  41.             @"(^//.*) |"                        // Comments are a line starting with '//'
  42.             + @"(^\[(?<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)
  43.             + @"(?<empty>\[\s*\]) | "           // Empty line is ignored unless it has [] on it
  44.             + @"(?<ps> \[\#\]) | "              // Start of pluralized denoted by [#] (eg: I have [#]40 hat[s])
  45.             + @"(?<pe> \[\\\#\]) | "             // End of pluralized denoted by [\#] (eg: I have [#]40 hat[s])
  46.             + @"(?<s>\[s\]) | (?<es>\[es\]) | (\[/(?<plr> \w+)\]) | "  // Plurals are [s], [es] or [/PluralOfPreviousWord]
  47.             + @"(\[(?<ref> \S+?)\]) | "          // Ref (reference to another group) is nonspace character between brackets eg. [ANIMAL] // TODO: Check why that 2nd ? is in there...
  48.             + @"(?<text> [^\[\r]+)",             // Text is anything that's not a new line.
  49.         RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase);
  50.  
  51.     // Matches first letter of line, or first letter after a full stop or equivalent
  52.     static readonly Regex s_regexCapital = new Regex(@"((?<=^\W*)\w) | ((?<=[.:!?]\s*)\w)", RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase);
  53.  
  54.     // Matches a followed by space and a vowel (unless directly after non-whitespace, ie the end of a word)
  55.     static readonly Regex s_regexAn = new Regex(@"(?<!\S)a(?=\s+[aeiou])", RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase);
  56.  
  57.     static readonly char[] LINEDELIM = {'\n'};
  58.  
  59.     static readonly string MATCH_GROUP = "group";
  60.     static readonly string MATCH_EMPTY = "empty";
  61.     static readonly string MATCH_WEIGHT = "weight";
  62.     static readonly string MATCH_REF = "ref";
  63.     static readonly string MATCH_TEXT = "text";
  64.     static readonly string MATCH_PLURALISE_START = "ps";
  65.     static readonly string MATCH_PLURALISE_END = "pe";
  66.     static readonly string MATCH_PLURAL_S = "s";
  67.     static readonly string MATCH_PLURAL_ES = "es";
  68.     static readonly string MATCH_PLURAL = "plr";
  69.  
  70.     #endregion
  71.     #region Vars: private
  72.  
  73.     Dictionary<string, PowerTextNodeGroup> m_groups = new Dictionary<string, PowerTextNodeGroup>();
  74.  
  75.     #endregion
  76.     #region Funcs: public
  77.  
  78.     /// Find a string from the group name (the thing between the colons. eg. :WeaponName: )
  79.     public string GetString(string groupName)
  80.     {
  81.         string result = string.Empty;
  82.         PowerTextNodeGroup group = null;
  83.         if ( m_groups.TryGetValue(groupName.ToUpper(), out group) )
  84.         {
  85.             bool plural = false;
  86.             StringBuilder builder = new StringBuilder();
  87.             group.Build(builder, ref plural);
  88.             result = builder.ToString();   
  89.             result = PostProcess(result);
  90.         }
  91.         return result;
  92.     }
  93.  
  94.  
  95.     /// Takes the input text and parses it. Do this before "GetString"
  96.     public void Parse(string text)
  97.     {
  98.         string[] textLines = text.Split(LINEDELIM,10000);
  99.  
  100.         PowerTextNodeGroup currentGroup = null;
  101.         PowerTextNodeLine currentLine = null;
  102.  
  103.         for (int i = 0; i < textLines.Length; ++i )
  104.         {
  105.             string lineText = textLines[i];            
  106.             Match groupMatch = s_regexGroup.Match(lineText);
  107.             if ( groupMatch.Success )
  108.             {
  109.                 // Parse group name
  110.                     string groupName = groupMatch.Groups[MATCH_GROUP].Value;
  111.                 currentGroup = FindOrCreateGroup(groupName);
  112.             }
  113.             else if ( currentGroup != null )
  114.             {
  115.                 // Add node for line
  116.                 currentLine = new PowerTextNodeLine();
  117.  
  118.                 bool lineHasContent = false;
  119.                 PowerTextNodeString lastStringNode = null;
  120.    
  121.                 MatchCollection lineMatches = s_regexLine.Matches(lineText);
  122.                 foreach( Match lineMatch in lineMatches )
  123.                 {
  124.                     if ( lineMatch.Success && lineMatch.Groups == null )
  125.                         continue;                  
  126.  
  127.                     if ( lineMatch.Groups[MATCH_EMPTY].Success)
  128.                     {
  129.                         lineHasContent = true;
  130.                         currentLine.Append( (PowerTextNodeString)(string.Empty) );
  131.                     }
  132.                     if ( lineMatch.Groups[MATCH_WEIGHT].Success)
  133.                     {
  134.                         lineHasContent = true;
  135.                             float.TryParse(lineMatch.Groups[MATCH_WEIGHT].Value, out currentLine.m_weight);
  136.                     }
  137.                     if ( lineMatch.Groups[MATCH_REF].Success)
  138.                     {
  139.                         lineHasContent = true;
  140.                             currentLine.Append( FindOrCreateGroup(lineMatch.Groups[MATCH_REF].Value) );
  141.                     }
  142.                     if ( lineMatch.Groups[MATCH_TEXT].Success )
  143.                     {
  144.                         string value = lineMatch.Groups[MATCH_TEXT].Value;
  145.                         if ( string.IsNullOrEmpty(value) == false )
  146.                         {
  147.                             lineHasContent = true;
  148.                             lastStringNode = (PowerTextNodeString)value;
  149.                             currentLine.Append( lastStringNode );
  150.                         }
  151.                     }
  152.                     if ( lineMatch.Groups[MATCH_PLURALISE_START].Success )
  153.                     {
  154.                         lineHasContent = true;
  155.                         currentLine.Append( new PowerTextNodePluralFlag(true) );
  156.                     }
  157.                     if ( lineMatch.Groups[MATCH_PLURALISE_END].Success )
  158.                     {
  159.                         currentLine.Append( new PowerTextNodePluralFlag(false) );
  160.                     }
  161.  
  162.                     // Parse "plural" stuff
  163.                     if ( lastStringNode != null )
  164.                     {
  165.                         if ( lineMatch.Groups[MATCH_PLURAL_S].Success ) lastStringNode.SetPlural(PowerTextNodeString.ePluralType.s);
  166.                         if ( lineMatch.Groups[MATCH_PLURAL_ES].Success ) lastStringNode.SetPlural(PowerTextNodeString.ePluralType.es);
  167.                         if ( lineMatch.Groups[MATCH_PLURAL].Success )
  168.                         {
  169.                             string value = lineMatch.Groups[MATCH_PLURAL].Value;
  170.                             if ( string.IsNullOrEmpty(value) == false )
  171.                             {
  172.                                 lastStringNode.SetPlural(PowerTextNodeString.ePluralType.Custom, value);
  173.                             }                              
  174.                         }
  175.                     }
  176.                 }      
  177.                          
  178.                 if ( lineHasContent )
  179.                     currentGroup.AddOption(currentLine);                
  180.             }
  181.         }
  182.     }
  183.  
  184.     /// Sets a text variable by name (in the script, write [name] to use the variable)
  185.     public void SetVariable(string name, string value)
  186.     {
  187.         ParseGroup(name, value);
  188.     }
  189.  
  190.     /// Sets an int variable by name, marking it as plural if it's not 1 (in the script, write [name] to use the variable)
  191.     public void SetVariable(string name, int value)
  192.     {
  193.         if ( value != 1 )            
  194.             ParseGroup(name, value.ToString()+"[#]");
  195.         else
  196.             ParseGroup(name, value.ToString());
  197.     }
  198.  
  199.  
  200.     #endregion
  201.     #region Funcs: Private
  202.  
  203.     PowerTextNodeGroup FindOrCreateGroup(string name)
  204.     {
  205.         PowerTextNodeGroup node = null;
  206.         name = name.ToUpper();
  207.         if ( m_groups.TryGetValue(name, out node) )
  208.         {
  209.             return node;
  210.         }
  211.         node = new PowerTextNodeGroup();
  212.         m_groups.Add(name, node);
  213.         return node;
  214.     }
  215.  
  216.     PowerTextNodeGroup ParseGroup(string name, string lines)
  217.     {
  218.         PowerTextNodeGroup group = FindOrCreateGroup(name);
  219.         return group;
  220.     }    
  221.  
  222.     string PostProcess( string text )
  223.     {
  224.         // Capitalise first alphanumeric of sentance, or after . ; - ! ?
  225.         text = s_regexCapital.Replace(text, ReplaceCapital);
  226.  
  227.         // Change a to an
  228.         return s_regexAn.Replace(text,ReplaceAn);
  229.     }
  230.      
  231.     string ReplaceAn(Match match)
  232.     {
  233.         return string.Concat(match.ToString(),'n');
  234.     }
  235.  
  236.     string ReplaceCapital(Match match)
  237.     {
  238.         return match.ToString().ToUpper();
  239.     }
  240.     #endregion
  241. }
  242.  
  243.  
  244.  
  245. #region Class: PowerTextNodes
  246.  
  247. // Interface to PowerTextNode with generic function to generate the string
  248. public interface IPowerTextNode
  249. {
  250.     void Build( StringBuilder builder, ref bool pluralize );
  251.     float GetWeight();
  252. }
  253.  
  254. // Node that returns random node from list
  255. public class PowerTextNodeGroup: IPowerTextNode
  256. {  
  257.     public List<IPowerTextNode> m_options = null;
  258.     ShuffledIndex m_shuffledIndex;
  259.     float m_maxWeight = 1;
  260.  
  261.     public void Build( StringBuilder builder, ref bool plural )
  262.     {
  263.         if ( m_options == null || m_options.Count <= 0 )
  264.             return;
  265.  
  266.         if ( m_shuffledIndex == null ) m_shuffledIndex = new ShuffledIndex(m_options.Count);
  267.  
  268.         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).
  269.         {
  270.             m_shuffledIndex.Next();
  271.             float weight = m_options[m_shuffledIndex].GetWeight();
  272.             if ( weight >= m_maxWeight || (weight > 0.0f && weight >= Random.value * m_maxWeight) )
  273.             {
  274.                 m_options[m_shuffledIndex].Build(builder, ref plural);
  275.                 break;
  276.             }
  277.         }
  278.  
  279.     }
  280.  
  281.     public float GetWeight() { return 1.0f; }
  282.  
  283.     public void AddOption(IPowerTextNode node)
  284.     {
  285.         if ( m_options == null )
  286.             m_options = new List<IPowerTextNode>();
  287.         m_options.Add(node);
  288.         m_maxWeight = Mathf.Max(m_maxWeight, node.GetWeight());
  289.     }
  290.  
  291. }
  292.  
  293. // Node that returns a bunch of nodes concatenated (eg: "hello " + [name] + " how are you today?" is 3 PowerTextNodeLines)
  294. public class PowerTextNodeLine: IPowerTextNode
  295. {
  296.     public List<IPowerTextNode> m_strings;
  297.  
  298.     public float m_weight = 1;
  299.  
  300.     public void Build( StringBuilder builder, ref bool plural )
  301.     {
  302.         if ( m_strings == null )
  303.             return;
  304.         for ( int i = 0; i < m_strings.Count; ++i )                
  305.             m_strings[i].Build(builder, ref plural);                
  306.     }
  307.  
  308.     public float GetWeight() { return m_weight; }
  309.  
  310.     public void Append(IPowerTextNode node)
  311.     {
  312.         if ( m_strings == null )
  313.             m_strings = new List<IPowerTextNode>();
  314.         m_strings.Add(node);        
  315.     }
  316. }
  317.  
  318. public class PowerTextNodeString : IPowerTextNode
  319. {
  320.     // Text nodes can have an alternate plural, migth j ust append 's' or 'es' or might replace the whole word
  321.     public enum ePluralType
  322.     {
  323.         None,
  324.         s,
  325.         es,
  326.         Custom
  327.     }
  328.  
  329.     string m_string = null;
  330.     ePluralType m_pluralType = ePluralType.None;
  331.     string m_pluralString = null;
  332.  
  333.     public PowerTextNodeString(string str) {m_string = str;}
  334.     public static implicit operator PowerTextNodeString(string str)
  335.     {
  336.         return new PowerTextNodeString(str);
  337.     }
  338.  
  339.     public void SetPlural( ePluralType type, string customString = null )
  340.     {
  341.         m_pluralType = type;
  342.         m_pluralString = customString;
  343.     }
  344.  
  345.     public void Build( StringBuilder builder, ref bool plural )
  346.     {
  347.         // Check if it should make the text end with a plural
  348.         if ( m_pluralType != ePluralType.None && plural )
  349.         {
  350.             if ( m_pluralType == ePluralType.Custom )
  351.             {
  352.                 // Custom plural means it'll replace the entire last word (eg:  life/lives)
  353.                 int pos = m_string.LastIndexOf(' ');
  354.                 if ( pos > 0 && pos < m_string.Length )
  355.                     builder.Append(m_string.Substring(0,pos));
  356.                 builder.Append(m_pluralString);
  357.             }
  358.             else if ( m_pluralType == ePluralType.s )
  359.             {
  360.                 builder.Append(m_string);
  361.                 builder.Append('s');
  362.             }
  363.             else if ( m_pluralType == ePluralType.es )
  364.             {
  365.                 builder.Append(m_string);
  366.                 builder.Append("es");
  367.             }
  368.         }
  369.         else
  370.         {
  371.             builder.Append(m_string);
  372.         }
  373.     }
  374.  
  375.     public float GetWeight() { return 1.0f; }
  376. }
  377.  
  378. public class PowerTextNodePluralFlag : IPowerTextNode
  379. {
  380.     bool m_pluralize = false;
  381.     public PowerTextNodePluralFlag(bool pluralize) { m_pluralize = pluralize; }
  382.     public void Build( StringBuilder builder, ref bool pluralize ) { pluralize = m_pluralize; }
  383.     public float GetWeight() { return 1.0f; }
  384. }
  385.  
  386.  
  387. #endregion
  388.  
  389. }
  390.  
  391. #region Class: Utils
  392.  
  393. // Utils- Normally these would be in seperate file.
  394.  
  395. namespace PowerTools.PowerTextUtils
  396. {
  397.  
  398. public class ShuffledIndex
  399. {
  400.     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
  401.     int[] m_ids = null;
  402.    
  403.     public ShuffledIndex(int count)
  404.     {
  405.         m_ids = new int[count];
  406.         for (int i = 0; i < count; ++i )
  407.         {
  408.             m_ids[i] = i;
  409.         }
  410.     }
  411.    
  412.     public int Next()
  413.     {
  414.         m_current++;
  415.         return (int)this;
  416.     }
  417.  
  418.     public static implicit operator int(ShuffledIndex m)
  419.     {
  420.         if ( m.m_ids.Length == 0 )
  421.             return -1;
  422.         if ( m.m_current < 0 || m.m_current >= m.m_ids.Length )
  423.         {          
  424.             int previousValue = m.m_ids[m.m_ids.Length - 1];
  425.            
  426.             m.m_ids.Shuffle();          
  427.             m.m_current = 0;
  428.            
  429.             // 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)
  430.             if ( m.m_ids.Length > 1 && previousValue == m.m_ids[0] )
  431.                 Utils.Swap(ref m.m_ids[0], ref m.m_ids[1]);
  432.         }
  433.         return m.m_ids[m.m_current];
  434.     }
  435.  
  436.     public int Length { get{ return m_ids.Length; } }
  437. }
  438.  
  439. public static class Utils
  440. {
  441.     // Extention method to shuffle an array
  442.    public static void Shuffle<T>( this T[] list )
  443.    {
  444.         T temp;
  445.         int j = 0;
  446.         int count = list.Length;
  447.         for ( int i = count - 1; i >= 1; --i )
  448.         {
  449.             j = Random.Range(0,i+1);
  450.             // swap
  451.             temp = list[i];
  452.             list[i] = list[j];
  453.             list[j] = temp;
  454.         }
  455.     }
  456.  
  457.     // Usually I have this in a util class it's pretty useful :)
  458.     public static void Swap<T>(ref T lhs, ref T rhs)
  459.     {
  460.         T temp;
  461.         temp = lhs;
  462.         lhs = rhs;
  463.         rhs = temp;
  464.     }
  465. }
  466.  
  467. }
  468. #endregion
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
 
Top