#:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:= # ▼ Dialogue from file # Author: Trihan # Version 3.00 # Release date: 24/05/2015 # #:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:= #------------------------------------------------------------------------------- # ▼ UPDATES #------------------------------------------------------------------------------- # # 24/05/2015. Messages now support choices, number input and item select! # # You can create a choice list between and # # tags and it will overwrite the captions you entered in the event command. # # MAJOR NEW FEATURE: evaluated message control codes! Check the instructions # # for full details. The short version is you can now enter any property from # # most database tabs in the database in a message using a simple code. # # 23/05/2015. Fixed a bug where dynamic wordwrap was too generous when the # # message had a face showing. # # 23/05/2015. Made the wordwrap dynamic, so you don't have to worry about # # font size. # # 23/05/2015. Added the get_random_dialogue method, which, well, gets a # # random line from the given identifier. # # 23/05/2015. Added word wrapping. It will affect all messages, not just # # dialogue. It should handle control codes pretty well but there may be # # bugs so let me know if anything goes wrong there. # # 22/05/2015. Didn't realise I had removed the new page thing when I made my # # overhaul on the 10th. Readded it as a regex pattern. # # 18/05/2015. Fixed a bug where showing a message with a face only worked # # once. # # 11/05/2015. Added language options to make localisation easier. This also # # supports in-game language choice if the developer adds one. # # 10/05/2015. Major overhaul of dialogue storage code, added config options. # # 09/05/2015. Added the facility to type "new page" to make a new page in the # # middle of a dialogue section. # # 09/05/2015. First release #------------------------------------------------------------------------------- # ▼ TERMS OF USAGE #------------------------------------------------------------------------------- # # You are free to adapt this work to suit your needs. # # You can use this work for commercial purposes if you like it. # # Credit is appreciated. # # # # For support: # # rpgmaker.net # # rmrk.net # # rpgmakervxace.net # # rpgrevolution.com #------------------------------------------------------------------------------- # ▼ INTRODUCTION #------------------------------------------------------------------------------- # # This script allows you to place NPC dialogue in text files to make it # # easier to add, spellcheck and edit. #------------------------------------------------------------------------------- # ▼ INSTRUCTIONS # # Within the file, the line to start a section of dialogue should start with # # an identifier (as set in LINE_READER::CONFIG, by default this is # # [section name]. # # # # If you want to add a face to the message, enter the face pattern set in the # # config at the beginning of the line. By default the pattern is # # # # You only have to do this at the beginning of the section, whether by # # itself or at the beginning of the first spoken line. # # # # You can start a new page mid-section at any time with # # # # You can have a list of choices inside and # # tags. Any strings appearing inside these tags will be added to the choice # # menu. Note that the choice has to be BEFORE the message you want it to # # appear alongside. # # # # You can use evaluation control codes to show any property of an actor, # # skill, item, weapon, armor, enemy, troop or state using the codes \ac, \sk, # # \it, \wp, \ar, \en, \tr and \st, followed by square brackets containing the # # ID of the database slot and property name separated by commas. You can # # access one level of arrays in the property by supplying an optional index. # # There is also a \voc code for displaying vocab strings. # # EXAMPLE: Say I wanted to display the MP cost of skill 26. I would put # # \sk[26, mp_cost] \voc[mp] # # # # Note that you can have as many dialogue files as you like; the first time # # you run the game with the script active, all files will be consolidated # # into an .rvdata2 file; the original will be renamed to "name_cached.ext" # # so it won't be cached again unless you rename it to its original form. # # Once you're happy with the data file containing your dialogue, you don't # # need to keep the cached files in the project folder any more. # # HOWEVER, if you're using multiple files and make a change to any of them # # you will have to recache every file or you'll lose the ones that haven't # # changed. This is because any time one of your named dialogue files is # # present in the Data directory the .rvdata2 will be recreated using only # # the files which are there at the time. # # # # When you want to show dialogue in an event, call # # get_dialogue(identifier, OPTIONAL language suffix: defaults to EN.) # # There's also get_random_dialogue(identifier, optional language suffix) # # which as the name suggests pulls out a random line instead of reading them # # all. #------------------------------------------------------------------------------- # # How to use it. #------------------------------------------------------------------------------- # ▼ COMPATIBILITY #------------------------------------------------------------------------------- # # List of aliases and overwrites: # # # # DataManager # # self.load_normal_database (alias) # # self.cache_dialogue (new method) # # # # Game_Interpreter # # get_dialogue (new method) # # # # Window_Base # # convert_escape_characters (alias) # # actor_eval (new method) # # skills/items/weapons/armors/enemies/troops/states_eval (new methods) # # vocab_eval (new method) # # actor_equips (new method) # # # # Window_Message # # process_all_text (overwrite) # # wrap_text (new method) # # # # Compatibility issues # # Scripts which alias process_all_text in Window_Message will need to be # # placed below this one. Scripts which overwrite it will be incompatible. #------------------------------------------------------------------------------- # This module holds our configuration. module LINE_READER CONFIG = { # ---------------------------------------------------------------------------- # EDITABLE REGION BEGINS HERE # ---------------------------------------------------------------------------- # :languages - A hash of languages for which dialogue files have been created. # ---------------------------------------------------------------------------- :languages => { # ---------------------------------------------------------------------------- # Format: # :languagename => { # :dialogue_files => [array of filenames with dialogue for the language], # :suffix => "suffix to use for the language", # }, # ---------------------------------------------------------------------------- :english => { :dialogue_files => ["dialogue.txt"] , :suffix => "EN", }, }, # ---------------------------------------------------------------------------- # :identifier_pattern - A regex used to identify where a section of dialogue # starts. By default, this is [section name], but if you know your regex # feel free to change it to suit your own dialogue file preferences. # ---------------------------------------------------------------------------- :identifier_pattern => /^\[(.*)\]/i, # ---------------------------------------------------------------------------- # :face_pattern - A regex used to insert a face graphic into the message. # By default, this is , but if you know your regex # feel free to change it to suit your own dialogue file preferences. # ---------------------------------------------------------------------------- :face_pattern => /^/i, # ---------------------------------------------------------------------------- # :np_pattern - A regex used to add a new page to the message. # By default, this is , but if you know your regex # feel free to change it to suit your own dialogue file preferences. # ---------------------------------------------------------------------------- :np_pattern => /^/i, # ---------------------------------------------------------------------------- # :choice_start - A regex used to start a set of choices. Note that although # the script will let you define the strings for the choices (would be a # pretty lousy localisation-friendly script otherwise) the actual choice # command still needs to follow the call to get_dialogue and is still # handled by the event. Everything between this and the choice stop pattern # will be added as a choice, but note that the script doesn't yet support # processing for more than 4 choices, even though it's capable of displaying # more in the window. # By default, this is (ON ITS OWN LINE), but if you know your # regex feel free to change it to suit your own dialogue file preferences. # ---------------------------------------------------------------------------- :choice_start => /^$/i, # ---------------------------------------------------------------------------- # :choice_finish - A regex used to end a set of choices. # By default, this is (ON ITS OWN LINE), but if you know your # regex feel free to change it to suit your own dialogue file preferences. # ---------------------------------------------------------------------------- :choice_finish => /^$/i # ---------------------------------------------------------------------------- # EDITING ANYTHING BEYOND THIS POINT WILL RESULT IN PREMATURE BALDING AND # LOSS OF EARNINGS. #----------------------------------------------------------------------------- } end # module LINE_READER # $imported, as with many other scripts, is used to assist other scripters # if they ever have to make compatibility patches. I have chosen to use the # relatively new convention of storing the script's version number as the hash # value. $imported ||= {}['TriDFF'] = "3.00" module DataManager # Include the LINE_READER module so we can just refer to CONFIG. include LINE_READER class <= 1 ? $game_actors[n] : nil actor ? eval("actor.#{property}") : "" end # Evaluation method for vocab def vocab_eval(property) eval("Vocab::#{property}") end # Dynamically create evaluation methods for skills, items, weapons, armors, # enemies, troops and states [:skills, :items, :weapons, :armors, :enemies, :troops, :states].each { |method| define_method("#{method}_eval") { |n, property, index = nil| var_name = n >= 1 ? "$data_#{method}[#{n}]" : nil var_name ? eval("#{var_name}.#{property}#{index ? '[index.to_i]' : ''}") : "" } } # Add new escape codes for the evaluation methods def convert_escape_characters(text) result = text.to_s.clone result.gsub!(/\\/) { "\e" } result.gsub!(/\eAC\[(\d+)(?:, ?(.*?))(?:, ?(\d+))?\]/i) { actor_eval($1.to_i, $2, $3) } result.gsub!(/\eEQ\[(\d+)(?:, ?(.*?))(?:, ?(\d+))?\]/i) { actor_equips($1.to_i, $2) } result.gsub!(/\eSK\[(\d+)(?:, ?(.*?))(?:, ?(\d+))?\]/i) { skills_eval($1.to_i, $2, $3) } result.gsub!(/\eIT\[(\d+)(?:, ?(.*?))(?:, ?(\d+))?\]/i) { items_eval($1.to_i, $2, $3) } result.gsub!(/\eWP\[(\d+)(?:, ?(.*?))(?:, ?(\d+))?\]/i) { weapons_eval($1.to_i, $2, $3) } result.gsub!(/\eAR\[(\d+)(?:, ?(.*?))(?:, ?(\d+))?\]/i) { armors_eval($1.to_i, $2, $3) } result.gsub!(/\eEN\[(\d+)(?:, ?(.*?))(?:, ?(\d+))?\]/i) { enemies_eval($1.to_i, $2, $3) } result.gsub!(/\eTR\[(\d+)(?:, ?(.*?))(?:, ?(\d+))?\]/i) { troops_eval($1.to_i, $2, $3) } result.gsub!(/\eST\[(\d+)(?:, ?(.*?))(?:, ?(\d+))?\]/i) { states_eval($1.to_i, $2, $3) } result.gsub!(/\eVOC\[(.*?)\]/i) { vocab_eval($1) } tri_dff_convert_escape_characters_3hs4(result) end # Special case for an actor's initial equips so they can be named in the # text file instead of just using numbers def actor_equips(actor_id, equipslot) case equipslot when "weapon" slot = 0 when "shield" slot = 1 when "head" slot = 2 when "body" slot = 3 when "accessory" slot = 4 end actor = actor_id >= 1 ? $game_actors[actor_id] : nil actor ? actor.equips[slot || equipslot.to_i].name : "" end end # Adding additional functionality to Window_Message Window_Message.class_eval do # Method overwrite to wrap text def process_all_text open_and_wait text = convert_escape_characters($game_message.all_text) # We want to limit the number of characters shown to the maximum number # of uppercase Ws that can fit into the contents width, since that's the # widest letter of the alphabet. The chances of someone wanting a bunch # of Ws in a row is remote, but I'm nothing if not thorough when it comes # to fringe case handling. If there's a face, lower the available width by # 106 pixels (to allow for padding) col = (contents.width - ($game_message.face_name == "" ? 0 : 106)) / text_size("W").width # Convert icon and colour control codes to a shorter string so they won't # throw off the wrapping so much; we'll insert spaces before and after so # that the control code is wrapped as well. text.gsub!(/\e([ic])\[(\d+)\]/," \\1\\2 ") # Wrap the text! text = wrap_text(text, col) # Okay, now that we've wrapped the text based on a string of lowercase Ws, # it's time to properly figure out what can be shifted up to the previous # line. First, we split the text by newline characters to an array called # lines... lines = text.split("\n") # We also set up an empty output array, which will store our lines after # the positioning has been sorted. output = [] # Loop through each line in the array, providing an index. lines.each_with_index { |line, index| # Infinite looooop loop do # If there's a line for the index after the current one... if lines[index+1] # Set the next line to the one after the current one. next_line = lines[index+1] # If the next line is blank, it's useless to us. if next_line == "" # Delete it from the array, and... lines.delete(next_line) # Go to the next one. next end # if # To get the words in the next line, we split it by spaces. words = next_line.split(" ") # We only care about the first word. word = words[0] # If there isn't a word there, break out of the loop. break unless word # If there's a face, give a width of 106. 0 otherwise. face_width = $game_message.face_name == "" ? 0 : 106 # The width of the line line_width = text_size(line).width # The width of the word word_width = text_size(" " + word).width # If all of these widths combined are less than the contents width, # the word is able to fit on the previous line. if face_width + line_width + word_width < contents.width # Add the word to the end of the previous line, with a space if # there are already words on it. line << (line == "" ? "" : " ") + word # Remove the word plus a space from the next line. next_line.slice!(0, (word + " ").length) else # if the word doesn't fit... break end else # If there isn't a line for the index after the current one... # Break out of the loop. break end # if end # loop # Now that we've got a properly formatted line, append it to output. output << line } # each_with_index # Recreate the text as the output array joined with newline characters. text = output.join("\n") # And now we can return the control codes to their original form. text.gsub!(/ ?([ic])(\d+) ?/,"\e\\1\[\\2\]") pos = {} new_page(text, pos) process_character(text.slice!(0, 1), text, pos) until text.empty? end # This method wraps our text. def wrap_text(txt, col = 80) # Okay, so we want to match (1 to col characters, captured as group 1) # followed by (one or more spaces | the end of the line with an optional # newline character, captured as group 2) | (1 to col characters followed # by an optional group of an optional space followed by an optional i or c # followed by an optional set of digits followed by an optional space, # captured as group 3. The match will be replaced by group 1, then group 3, # then a newline character. txt.gsub(/(.{1,#{col}})( +|$\n?)|(.{1,#{col}}(?: ?[ic]?\d+? )?)/,"\\1\\3\n") end end