Advertisement
Trihan

Trihan's Dialogue From File

May 8th, 2015
1,363
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Ruby 25.72 KB | None | 0 0
  1. #:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=
  2. #  ▼ Dialogue from file
  3. #  Author: Trihan
  4. #  Version 3.00
  5. #  Release date: 24/05/2015
  6. #
  7. #:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=
  8.  
  9. #-------------------------------------------------------------------------------
  10. #  ▼ UPDATES
  11. #-------------------------------------------------------------------------------
  12. # # 24/05/2015. Messages now support choices, number input and item select!
  13. # # You can create a choice list between <choice start> and <choice finish>
  14. # # tags and it will overwrite the captions you entered in the event command.
  15. # # MAJOR NEW FEATURE: evaluated message control codes! Check the instructions
  16. # # for full details. The short version is you can now enter any property from
  17. # # most database tabs in the database in a message using a simple code.
  18. # # 23/05/2015. Fixed a bug where dynamic wordwrap was too generous when the
  19. # # message had a face showing.
  20. # # 23/05/2015. Made the wordwrap dynamic, so you don't have to worry about
  21. # # font size.
  22. # # 23/05/2015. Added the get_random_dialogue method, which, well, gets a
  23. # # random line from the given identifier.
  24. # # 23/05/2015. Added word wrapping. It will affect all messages, not just
  25. # # dialogue. It should handle control codes pretty well but there may be
  26. # # bugs so let me know if anything goes wrong there.
  27. # # 22/05/2015. Didn't realise I had removed the new page thing when I made my
  28. # # overhaul on the 10th. Readded it as a regex pattern.
  29. # # 18/05/2015. Fixed a bug where showing a message with a face only worked
  30. # # once.
  31. # # 11/05/2015. Added language options to make localisation easier. This also
  32. # # supports in-game language choice if the developer adds one.
  33. # # 10/05/2015. Major overhaul of dialogue storage code, added config options.
  34. # # 09/05/2015. Added the facility to type "new page" to make a new page in the
  35. # # middle of a dialogue section.
  36. # # 09/05/2015. First release
  37. #-------------------------------------------------------------------------------
  38. #  ▼ TERMS OF USAGE
  39. #-------------------------------------------------------------------------------
  40. # # You are free to adapt this work to suit your needs.
  41. # # You can use this work for commercial purposes if you like it.
  42. # # Credit is appreciated.
  43. # #
  44. # # For support:
  45. # # rpgmaker.net
  46. # # rmrk.net
  47. # # rpgmakervxace.net
  48. # # rpgrevolution.com
  49. #-------------------------------------------------------------------------------
  50. #  ▼ INTRODUCTION
  51. #-------------------------------------------------------------------------------
  52. # # This script allows you to place NPC dialogue in text files to make it
  53. # # easier to add, spellcheck and edit.
  54. #-------------------------------------------------------------------------------
  55. #  ▼ INSTRUCTIONS
  56. # # Within the file, the line to start a section of dialogue should start with
  57. # # an identifier (as set in LINE_READER::CONFIG, by default this is
  58. # # [section name].
  59. # #
  60. # # If you want to add a face to the message, enter the face pattern set in the
  61. # # config at the beginning of the line. By default the pattern is
  62. # # <face: filename index>
  63. # # You only have to do this at the beginning of the section, whether by
  64. # # itself or at the beginning of the first spoken line.
  65. # #
  66. # # You can start a new page mid-section at any time with <new page>
  67. # #
  68. # # You can have a list of choices inside <choice start> and <choice finish>
  69. # # tags. Any strings appearing inside these tags will be added to the choice
  70. # # menu. Note that the choice has to be BEFORE the message you want it to
  71. # # appear alongside.
  72. # #
  73. # # You can use evaluation control codes to show any property of an actor,
  74. # # skill, item, weapon, armor, enemy, troop or state using the codes \ac, \sk,
  75. # # \it, \wp, \ar, \en, \tr and \st, followed by square brackets containing the
  76. # # ID of the database slot and property name separated by commas. You can
  77. # # access one level of arrays in the property by supplying an optional index.
  78. # # There is also a \voc code for displaying vocab strings.
  79. # # EXAMPLE: Say I wanted to display the MP cost of skill 26. I would put
  80. # # \sk[26, mp_cost] \voc[mp]
  81. # #
  82. # # Note that you can have as many dialogue files as you like; the first time
  83. # # you run the game with the script active, all files will be consolidated
  84. # # into an .rvdata2 file; the original will be renamed to "name_cached.ext"
  85. # # so it won't be cached again unless you rename it to its original form.
  86. # # Once you're happy with the data file containing your dialogue, you don't
  87. # # need to keep the cached files in the project folder any more.
  88. # # HOWEVER, if you're using multiple files and make a change to any of them
  89. # # you will have to recache every file or you'll lose the ones that haven't
  90. # # changed. This is because any time one of your named dialogue files is
  91. # # present in the Data directory the .rvdata2 will be recreated using only
  92. # # the files which are there at the time.
  93. # #
  94. # # When you want to show dialogue in an event, call
  95. # # get_dialogue(identifier, OPTIONAL language suffix: defaults to EN.)
  96. # # There's also get_random_dialogue(identifier, optional language suffix)
  97. # # which as the name suggests pulls out a random line instead of reading them
  98. # # all.
  99. #-------------------------------------------------------------------------------
  100. # # How to use it.
  101. #-------------------------------------------------------------------------------
  102. #  ▼ COMPATIBILITY
  103. #-------------------------------------------------------------------------------
  104. # # List of aliases and overwrites:
  105. # #
  106. # # DataManager
  107. # # self.load_normal_database (alias)
  108. # # self.cache_dialogue (new method)
  109. # #
  110. # # Game_Interpreter
  111. # # get_dialogue (new method)
  112. # #
  113. # # Window_Base
  114. # # convert_escape_characters (alias)
  115. # # actor_eval (new method)
  116. # # skills/items/weapons/armors/enemies/troops/states_eval (new methods)
  117. # # vocab_eval (new method)
  118. # # actor_equips (new method)
  119. # #
  120. # # Window_Message
  121. # # process_all_text (overwrite)
  122. # # wrap_text (new method)
  123. # #
  124. # # Compatibility issues
  125. # # Scripts which alias process_all_text in Window_Message will need to be
  126. # # placed below this one. Scripts which overwrite it will be incompatible.
  127. #-------------------------------------------------------------------------------
  128.  
  129. # This module holds our configuration.
  130. module LINE_READER
  131.   CONFIG = {
  132.   # ----------------------------------------------------------------------------
  133.   # EDITABLE REGION BEGINS HERE
  134.   # ----------------------------------------------------------------------------
  135.   # :languages - A hash of languages for which dialogue files have been created.
  136.   # ----------------------------------------------------------------------------
  137.     :languages => {
  138.   # ----------------------------------------------------------------------------
  139.   # Format:
  140.   # :languagename => {
  141.   #   :dialogue_files => [array of filenames with dialogue for the language],
  142.   #   :suffix => "suffix to use for the language",
  143.   # },
  144.   # ----------------------------------------------------------------------------
  145.       :english => {
  146.         :dialogue_files => ["dialogue.txt"] ,
  147.         :suffix => "EN",
  148.       },
  149.     },
  150.   # ----------------------------------------------------------------------------
  151.   # :identifier_pattern - A regex used to identify where a section of dialogue
  152.   # starts. By default, this is [section name], but if you know your regex
  153.   # feel free to change it to suit your own dialogue file preferences.
  154.   # ----------------------------------------------------------------------------
  155.     :identifier_pattern => /^\[(.*)\]/i,
  156.   # ----------------------------------------------------------------------------
  157.   # :face_pattern - A regex used to insert a face graphic into the message.
  158.   # By default, this is <face: filename index>, but if you know your regex
  159.   # feel free to change it to suit your own dialogue file preferences.
  160.   # ----------------------------------------------------------------------------
  161.     :face_pattern => /^<face:[ ]?(.*) (\d)>/i,
  162.   # ----------------------------------------------------------------------------
  163.   # :np_pattern - A regex used to add a new page to the message.
  164.   # By default, this is <new page>, but if you know your regex
  165.   # feel free to change it to suit your own dialogue file preferences.
  166.   # ----------------------------------------------------------------------------
  167.     :np_pattern => /^<new page>/i,
  168.   # ----------------------------------------------------------------------------
  169.   # :choice_start - A regex used to start a set of choices. Note that although
  170.   # the script will let you define the strings for the choices (would be a
  171.   # pretty lousy localisation-friendly script otherwise) the actual choice
  172.   # command still needs to follow the call to get_dialogue and is still
  173.   # handled by the event. Everything between this and the choice stop pattern
  174.   # will be added as a choice, but note that the script doesn't yet support
  175.   # processing for more than 4 choices, even though it's capable of displaying
  176.   # more in the window.
  177.   # By default, this is <choice start> (ON ITS OWN LINE), but if you know your
  178.   # regex feel free to change it to suit your own dialogue file preferences.
  179.   # ----------------------------------------------------------------------------
  180.     :choice_start => /^<choice start>$/i,
  181.   # ----------------------------------------------------------------------------
  182.   # :choice_finish - A regex used to end a set of choices.
  183.   # By default, this is <choice finish> (ON ITS OWN LINE), but if you know your
  184.   # regex feel free to change it to suit your own dialogue file preferences.
  185.   # ----------------------------------------------------------------------------
  186.     :choice_finish => /^<choice finish>$/i
  187.   # ----------------------------------------------------------------------------
  188.   # EDITING ANYTHING BEYOND THIS POINT WILL RESULT IN PREMATURE BALDING AND
  189.   # LOSS OF EARNINGS.
  190.   #-----------------------------------------------------------------------------
  191.   }
  192. end # module LINE_READER
  193.  
  194. # $imported, as with many other scripts, is used to assist other scripters
  195. # if they ever have to make compatibility patches. I have chosen to use the
  196. # relatively new convention of storing the script's version number as the hash
  197. # value.
  198. $imported ||= {}['TriDFF'] = "3.00"
  199.  
  200. module DataManager
  201.   # Include the LINE_READER module so we can just refer to CONFIG.
  202.   include LINE_READER
  203.  
  204.   class <<self; alias_method :tri_dff_load_normal_database_3ou9, :load_normal_database; end
  205.   def self.load_normal_database
  206.     tri_dff_load_normal_database_3ou9
  207.     # Before we set $data_dialogue, we need to check whether any of the
  208.     # configured dialogue files are present in the Data folder. Obviously
  209.     # it may be that none of the files are there but there also isn't a
  210.     # cached dialogue data file, so we only want to set $data_dialogue if
  211.     # we had dialogue to load in the first place.
  212.     cache_dialogue
  213.     # Initialise the dialogue data as an empty hash
  214.     $data_dialogue = {}
  215.     # Run through each language, setting the data variable if file exists
  216.     CONFIG[:languages].each { |language, data|
  217.       $data_dialogue[data[:suffix]] = load_data("Data/Dialogue-#{data[:suffix]}.rvdata2") if FileTest.exist?("Data/Dialogue-#{data[:suffix]}.rvdata2")
  218.     } # .each
  219.   end # def
  220.  
  221.   def self.cache_dialogue
  222.     # Iterate through each configured language.
  223.     CONFIG[:languages].each { |language, data|
  224.       dialogue = {}
  225.       # Flag to check whether files have changed since the last cache. Default
  226.       # to false so that if nothing changed we don't recreate the .rvdata2 file.
  227.       files_changed = false
  228.       # Iterate through each configured dialogue file.
  229.       data[:dialogue_files].each { |file|
  230.         # Point the file to the Data folder.
  231.         filename = "Data/#{file}"
  232.         # Create a cached filename consisting of "filename_cached.ext"
  233.         cached_name = "Data/" + File.basename(file, ".*") + "_cached" + File.extname(file)
  234.         # If the current file exists
  235.         if FileTest.exist?(filename)
  236.           # Set files_changed to true; if a dialogue file is there that means
  237.           # it has changed since the last cache.
  238.           files_changed = true
  239.           File.open(filename, "r") do |f|
  240.             # Set an array containing all lines of the file
  241.             ary = f.readlines
  242.             # Initialise @key as nil; if we don't find an identifier no dialogue
  243.             # will be saved.
  244.             @key = nil
  245.             # Iterate through each element of the array
  246.             ary.each { |line|
  247.               # If the current line matches our identifier pattern...
  248.               if line =~ CONFIG[:identifier_pattern]
  249.                 # Set @key to the group matched (which in this case is whatever
  250.                 # is inside the square brackets)
  251.                 @key = $1
  252.                 # Go to the next line; we don't want NPCs to say the identifier!
  253.                 next
  254.               end # if
  255.               # If there's a key (which means we found an identifier)...
  256.               if @key
  257.                 # Add the line, stripped of whitespace characters, to the hash
  258.                 # with @key as the key
  259.                 (dialogue[@key] ||= []) << line.strip
  260.               end # if
  261.             } # .each
  262.           end # File.open
  263.           # If we cached a file, rename it to its cached name so it won't get
  264.           # cached again in subsequent plays. Display a helpful message in the
  265.           # console pointing out that the file was cached.
  266.           File.rename(filename, cached_name)
  267.           puts "Cached dialogue file #{filename} for language #{data[:suffix]}"
  268.         else
  269.           # The error will only really be relevant if we haven't created a
  270.           # dialogue data file already; we don't really want to imply an error
  271.           # if the developer just removed cached files afterwards.
  272.           unless FileTest.exist?("Data/Dialogue-#{data[:suffix]}.rvdata2")
  273.             puts "Dialogue error: File #{filename} not found"
  274.           end # unless
  275.         end # if
  276.       } # .each
  277.       # If any of the files have changed...
  278.       if files_changed
  279.         # Recreate our .rvdata2 file.
  280.         File.open("Data/Dialogue-#{data[:suffix]}.rvdata2", "wb") do |file|
  281.           Marshal.dump(dialogue, file)
  282.         end # File.open
  283.       end # if
  284.     } # .each
  285.   end # def
  286. end # class
  287.  
  288. class Game_Interpreter
  289.   def get_dialogue(identifier, lang = "EN")
  290.     # $data_dialogue might not exist if there was no data file to load. Better
  291.     # check that first!
  292.     if $data_dialogue[lang]
  293.       dialogue = $data_dialogue[lang][identifier]
  294.       # Even though the file exists at this point, the identifier might not.
  295.       # Need to check that too.
  296.       if dialogue
  297.         # Initialise choices as an empty array
  298.         choices = []
  299.         # Initialise the flag for determining whether we're reading choices
  300.         getting_choices = false
  301.         # Iterate through each line
  302.         dialogue.each { |line|
  303.           # If the line is the pattern for finishing choices...
  304.           if line =~ LINE_READER::CONFIG[:choice_finish]
  305.             # Turn the flag off and look at the next line.
  306.             getting_choices = false
  307.             next
  308.           # Otherwise, if we're gathering choices...
  309.           elsif getting_choices
  310.             # Add the line to the choices array and look at the next line.
  311.             choices << line
  312.             next
  313.           # Otherwise, if the line is the pattern for starting choices...
  314.           elsif line =~ LINE_READER::CONFIG[:choice_start]
  315.             # Turn the flag on and look at the next line.
  316.             getting_choices = true
  317.             next
  318.           # If the line matches the face pattern...
  319.           elsif line =~ LINE_READER::CONFIG[:face_pattern]
  320.             # Set the face and face index, and remove the pattern from the line
  321.             $game_message.face_name = $1
  322.             $game_message.face_index = $2.to_i
  323.             amended_line = line.gsub(LINE_READER::CONFIG[:face_pattern]) { "" }
  324.             next if amended_line == ""
  325.           elsif line =~ LINE_READER::CONFIG[:np_pattern]
  326.             wait_for_message
  327.             $game_message.new_page
  328.             amended_line = line.gsub(LINE_READER::CONFIG[:np_pattern]) { "" }
  329.             next if amended_line == ""
  330.           end # if
  331.           # Add the line to the message window
  332.           $game_message.add(amended_line || line)
  333.           # Allow normal processing of choices, number input and items
  334.           # alongside the message window
  335.           case next_event_code
  336.           when 102  # Show Choices
  337.             @index += 1
  338.             # This will override the choice strings with the ones in your
  339.             # tags but will keep the cancel choice set in the command.
  340.             setup_choices([choices, @list[@index].parameters[1]])
  341.           when 103  # Input Number
  342.             @index += 1
  343.             setup_num_input(@list[@index].parameters)
  344.           when 104  # Select Item
  345.             @index += 1
  346.             setup_item_choice(@list[@index].parameters)
  347.           end
  348.         } # dialogue.each
  349.         # At this point we've read all of the section, so let's wait for the
  350.         # player to continue.
  351.         wait_for_message
  352.       else
  353.         puts "DEVELOPER ERROR: Key #{identifier} not found in dialogue file."
  354.       end # if
  355.     else
  356.       puts "DEVELOPER ERROR: No dialogue data file found. You may have set up your files incorrectly."
  357.     end # if
  358.   end # def
  359.  
  360.   def get_random_dialogue(identifier, lang = "EN")
  361.     # $data_dialogue might not exist if there was no data file to load. Better
  362.     # check that first!
  363.     if $data_dialogue[lang]
  364.       dialogue = $data_dialogue[lang][identifier]
  365.       # Even though the file exists at this point, the identifier might not.
  366.       # Need to check that too.
  367.       if dialogue
  368.         # Pick a line, any line
  369.         rnd = rand(dialogue.size-1)
  370.         line = dialogue[rnd]
  371.         if line =~ LINE_READER::CONFIG[:face_pattern]
  372.           # Set the face and face index, and remove the pattern from the line
  373.           $game_message.face_name = $1
  374.           $game_message.face_index = $2.to_i
  375.           amended_line = line.gsub(LINE_READER::CONFIG[:face_pattern]) { "" }
  376.           # Note that we don't have the "next" from the get_dialogue method.
  377.           # The assumption is that if you're using this, you're going to have
  378.           # the presence of mind not to use a face pattern on its own line, or
  379.           # you might just end up with a face and no message.
  380.         end
  381.         # The rest is business as usual.
  382.         $game_message.add(amended_line || line)
  383.         wait_for_message
  384.       else
  385.         puts "DEVELOPER ERROR: Key #{identifier} not found in dialogue file."
  386.       end # if
  387.     else
  388.       puts "DEVELOPER ERROR: No dialogue data file found. You may have set up your files incorrectly."
  389.     end # if
  390.   end # def
  391. end # class
  392.  
  393. # Adding new functionality to Window_Base
  394. Window_Base.class_eval do
  395.   alias_method :tri_dff_convert_escape_characters_3hs4, :convert_escape_characters
  396.   # Evaluation method for actors
  397.   def actor_eval(n, property)
  398.     actor = n >= 1 ? $game_actors[n] : nil
  399.     actor ? eval("actor.#{property}") : ""
  400.   end
  401.  
  402.   # Evaluation method for vocab
  403.   def vocab_eval(property)
  404.     eval("Vocab::#{property}")
  405.   end
  406.  
  407.   # Dynamically create evaluation methods for skills, items, weapons, armors,
  408.   # enemies, troops and states
  409.   [:skills, :items, :weapons, :armors, :enemies, :troops, :states].each { |method|
  410.     define_method("#{method}_eval") { |n, property, index = nil|
  411.       var_name = n >= 1 ? "$data_#{method}[#{n}]" : nil
  412.       var_name ? eval("#{var_name}.#{property}#{index ? '[index.to_i]' : ''}") : ""
  413.     }
  414.   }
  415.  
  416.   # Add new escape codes for the evaluation methods
  417.   def convert_escape_characters(text)
  418.     result = text.to_s.clone
  419.     result.gsub!(/\\/)            { "\e" }
  420.     result.gsub!(/\eAC\[(\d+)(?:, ?(.*?))(?:, ?(\d+))?\]/i) { actor_eval($1.to_i, $2, $3) }
  421.     result.gsub!(/\eEQ\[(\d+)(?:, ?(.*?))(?:, ?(\d+))?\]/i) { actor_equips($1.to_i, $2) }
  422.     result.gsub!(/\eSK\[(\d+)(?:, ?(.*?))(?:, ?(\d+))?\]/i) { skills_eval($1.to_i, $2, $3) }
  423.     result.gsub!(/\eIT\[(\d+)(?:, ?(.*?))(?:, ?(\d+))?\]/i) { items_eval($1.to_i, $2, $3) }
  424.     result.gsub!(/\eWP\[(\d+)(?:, ?(.*?))(?:, ?(\d+))?\]/i) { weapons_eval($1.to_i, $2, $3) }
  425.     result.gsub!(/\eAR\[(\d+)(?:, ?(.*?))(?:, ?(\d+))?\]/i) { armors_eval($1.to_i, $2, $3) }
  426.     result.gsub!(/\eEN\[(\d+)(?:, ?(.*?))(?:, ?(\d+))?\]/i) { enemies_eval($1.to_i, $2, $3) }
  427.     result.gsub!(/\eTR\[(\d+)(?:, ?(.*?))(?:, ?(\d+))?\]/i) { troops_eval($1.to_i, $2, $3) }
  428.     result.gsub!(/\eST\[(\d+)(?:, ?(.*?))(?:, ?(\d+))?\]/i) { states_eval($1.to_i, $2, $3) }
  429.     result.gsub!(/\eVOC\[(.*?)\]/i) { vocab_eval($1) }
  430.     tri_dff_convert_escape_characters_3hs4(result)
  431.   end
  432.  
  433.   # Special case for an actor's initial equips so they can be named in the
  434.   # text file instead of just using numbers
  435.   def actor_equips(actor_id, equipslot)
  436.     case equipslot
  437.     when "weapon"
  438.       slot = 0
  439.     when "shield"
  440.       slot = 1
  441.     when "head"
  442.       slot = 2
  443.     when "body"
  444.       slot = 3
  445.     when "accessory"
  446.       slot = 4
  447.     end
  448.     actor = actor_id >= 1 ? $game_actors[actor_id] : nil
  449.     actor ? actor.equips[slot || equipslot.to_i].name : ""
  450.   end
  451. end
  452.  
  453. # Adding additional functionality to Window_Message
  454. Window_Message.class_eval do
  455.   # Method overwrite to wrap text
  456.   def process_all_text
  457.     open_and_wait
  458.     text = convert_escape_characters($game_message.all_text)
  459.     # We want to limit the number of characters shown to the maximum number
  460.     # of uppercase Ws that can fit into the contents width, since that's the
  461.     # widest letter of the alphabet. The chances of someone wanting a bunch
  462.     # of Ws in a row is remote, but I'm nothing if not thorough when it comes
  463.     # to fringe case handling. If there's a face, lower the available width by
  464.     # 106 pixels (to allow for padding)
  465.     col = (contents.width - ($game_message.face_name == "" ? 0 : 106)) / text_size("W").width
  466.     # Convert icon and colour control codes to a shorter string so they won't
  467.     # throw off the wrapping so much; we'll insert spaces before and after so
  468.     # that the control code is wrapped as well.
  469.     text.gsub!(/\e([ic])\[(\d+)\]/," \\1\\2 ")
  470.     # Wrap the text!
  471.     text = wrap_text(text, col)
  472.     # Okay, now that we've wrapped the text based on a string of lowercase Ws,
  473.     # it's time to properly figure out what can be shifted up to the previous
  474.     # line. First, we split the text by newline characters to an array called
  475.     # lines...
  476.     lines = text.split("\n")
  477.     # We also set up an empty output array, which will store our lines after
  478.     # the positioning has been sorted.
  479.     output = []
  480.     # Loop through each line in the array, providing an index.
  481.     lines.each_with_index { |line, index|
  482.       # Infinite looooop
  483.       loop do
  484.         # If there's a line for the index after the current one...
  485.         if lines[index+1]
  486.           # Set the next line to the one after the current one.
  487.           next_line = lines[index+1]
  488.           # If the next line is blank, it's useless to us.
  489.           if next_line == ""
  490.             # Delete it from the array, and...
  491.             lines.delete(next_line)
  492.             # Go to the next one.
  493.             next
  494.           end # if
  495.           # To get the words in the next line, we split it by spaces.
  496.           words = next_line.split(" ")
  497.           # We only care about the first word.
  498.           word = words[0]
  499.           # If there isn't a word there, break out of the loop.
  500.           break unless word
  501.           # If there's a face, give a width of 106. 0 otherwise.
  502.           face_width = $game_message.face_name == "" ? 0 : 106
  503.           # The width of the line
  504.           line_width = text_size(line).width
  505.           # The width of the word
  506.           word_width = text_size(" " + word).width
  507.           # If all of these widths combined are less than the contents width,
  508.           # the word is able to fit on the previous line.
  509.           if face_width + line_width + word_width < contents.width
  510.             # Add the word to the end of the previous line, with a space if
  511.             # there are already words on it.
  512.             line << (line == "" ? "" : " ") + word
  513.             # Remove the word plus a space from the next line.
  514.             next_line.slice!(0, (word + " ").length)
  515.           else # if the word doesn't fit...
  516.             break
  517.           end
  518.         else # If there isn't a line for the index after the current one...
  519.           # Break out of the loop.
  520.           break
  521.         end # if
  522.       end # loop
  523.       # Now that we've got a properly formatted line, append it to output.
  524.       output << line
  525.     } # each_with_index
  526.     # Recreate the text as the output array joined with newline characters.
  527.     text = output.join("\n")
  528.     # And now we can return the control codes to their original form.
  529.     text.gsub!(/ ?([ic])(\d+) ?/,"\e\\1\[\\2\]")
  530.     pos = {}
  531.     new_page(text, pos)
  532.     process_character(text.slice!(0, 1), text, pos) until text.empty?
  533.   end
  534.  
  535.   # This method wraps our text.
  536.   def wrap_text(txt, col = 80)
  537.     # Okay, so we want to match (1 to col characters, captured as group 1)
  538.     # followed by (one or more spaces | the end of the line with an optional
  539.     # newline character, captured as group 2) | (1 to col characters followed
  540.     # by an optional group of an optional space followed by an optional i or c
  541.     # followed by an optional set of digits followed by an optional space,
  542.     # captured as group 3. The match will be replaced by group 1, then group 3,
  543.     # then a newline character.
  544.     txt.gsub(/(.{1,#{col}})( +|$\n?)|(.{1,#{col}}(?: ?[ic]?\d+? )?)/,"\\1\\3\n")
  545.   end
  546. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement