Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=
- # ▼ 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 <choice start> and <choice finish>
- # # 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
- # # <face: filename index>
- # # 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 <new page>
- # #
- # # You can have a list of choices inside <choice start> and <choice finish>
- # # 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 <face: filename index>, but if you know your regex
- # feel free to change it to suit your own dialogue file preferences.
- # ----------------------------------------------------------------------------
- :face_pattern => /^<face:[ ]?(.*) (\d)>/i,
- # ----------------------------------------------------------------------------
- # :np_pattern - A regex used to add a new page to the message.
- # By default, this is <new page>, but if you know your regex
- # feel free to change it to suit your own dialogue file preferences.
- # ----------------------------------------------------------------------------
- :np_pattern => /^<new page>/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 <choice start> (ON ITS OWN LINE), but if you know your
- # regex feel free to change it to suit your own dialogue file preferences.
- # ----------------------------------------------------------------------------
- :choice_start => /^<choice start>$/i,
- # ----------------------------------------------------------------------------
- # :choice_finish - A regex used to end a set of choices.
- # By default, this is <choice finish> (ON ITS OWN LINE), but if you know your
- # regex feel free to change it to suit your own dialogue file preferences.
- # ----------------------------------------------------------------------------
- :choice_finish => /^<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 <<self; alias_method :tri_dff_load_normal_database_3ou9, :load_normal_database; end
- def self.load_normal_database
- tri_dff_load_normal_database_3ou9
- # Before we set $data_dialogue, we need to check whether any of the
- # configured dialogue files are present in the Data folder. Obviously
- # it may be that none of the files are there but there also isn't a
- # cached dialogue data file, so we only want to set $data_dialogue if
- # we had dialogue to load in the first place.
- cache_dialogue
- # Initialise the dialogue data as an empty hash
- $data_dialogue = {}
- # Run through each language, setting the data variable if file exists
- CONFIG[:languages].each { |language, data|
- $data_dialogue[data[:suffix]] = load_data("Data/Dialogue-#{data[:suffix]}.rvdata2") if FileTest.exist?("Data/Dialogue-#{data[:suffix]}.rvdata2")
- } # .each
- end # def
- def self.cache_dialogue
- # Iterate through each configured language.
- CONFIG[:languages].each { |language, data|
- dialogue = {}
- # Flag to check whether files have changed since the last cache. Default
- # to false so that if nothing changed we don't recreate the .rvdata2 file.
- files_changed = false
- # Iterate through each configured dialogue file.
- data[:dialogue_files].each { |file|
- # Point the file to the Data folder.
- filename = "Data/#{file}"
- # Create a cached filename consisting of "filename_cached.ext"
- cached_name = "Data/" + File.basename(file, ".*") + "_cached" + File.extname(file)
- # If the current file exists
- if FileTest.exist?(filename)
- # Set files_changed to true; if a dialogue file is there that means
- # it has changed since the last cache.
- files_changed = true
- File.open(filename, "r") do |f|
- # Set an array containing all lines of the file
- ary = f.readlines
- # Initialise @key as nil; if we don't find an identifier no dialogue
- # will be saved.
- @key = nil
- # Iterate through each element of the array
- ary.each { |line|
- # If the current line matches our identifier pattern...
- if line =~ CONFIG[:identifier_pattern]
- # Set @key to the group matched (which in this case is whatever
- # is inside the square brackets)
- @key = $1
- # Go to the next line; we don't want NPCs to say the identifier!
- next
- end # if
- # If there's a key (which means we found an identifier)...
- if @key
- # Add the line, stripped of whitespace characters, to the hash
- # with @key as the key
- (dialogue[@key] ||= []) << line.strip
- end # if
- } # .each
- end # File.open
- # If we cached a file, rename it to its cached name so it won't get
- # cached again in subsequent plays. Display a helpful message in the
- # console pointing out that the file was cached.
- File.rename(filename, cached_name)
- puts "Cached dialogue file #{filename} for language #{data[:suffix]}"
- else
- # The error will only really be relevant if we haven't created a
- # dialogue data file already; we don't really want to imply an error
- # if the developer just removed cached files afterwards.
- unless FileTest.exist?("Data/Dialogue-#{data[:suffix]}.rvdata2")
- puts "Dialogue error: File #{filename} not found"
- end # unless
- end # if
- } # .each
- # If any of the files have changed...
- if files_changed
- # Recreate our .rvdata2 file.
- File.open("Data/Dialogue-#{data[:suffix]}.rvdata2", "wb") do |file|
- Marshal.dump(dialogue, file)
- end # File.open
- end # if
- } # .each
- end # def
- end # class
- class Game_Interpreter
- def get_dialogue(identifier, lang = "EN")
- # $data_dialogue might not exist if there was no data file to load. Better
- # check that first!
- if $data_dialogue[lang]
- dialogue = $data_dialogue[lang][identifier]
- # Even though the file exists at this point, the identifier might not.
- # Need to check that too.
- if dialogue
- # Initialise choices as an empty array
- choices = []
- # Initialise the flag for determining whether we're reading choices
- getting_choices = false
- # Iterate through each line
- dialogue.each { |line|
- # If the line is the pattern for finishing choices...
- if line =~ LINE_READER::CONFIG[:choice_finish]
- # Turn the flag off and look at the next line.
- getting_choices = false
- next
- # Otherwise, if we're gathering choices...
- elsif getting_choices
- # Add the line to the choices array and look at the next line.
- choices << line
- next
- # Otherwise, if the line is the pattern for starting choices...
- elsif line =~ LINE_READER::CONFIG[:choice_start]
- # Turn the flag on and look at the next line.
- getting_choices = true
- next
- # If the line matches the face pattern...
- elsif line =~ LINE_READER::CONFIG[:face_pattern]
- # Set the face and face index, and remove the pattern from the line
- $game_message.face_name = $1
- $game_message.face_index = $2.to_i
- amended_line = line.gsub(LINE_READER::CONFIG[:face_pattern]) { "" }
- next if amended_line == ""
- elsif line =~ LINE_READER::CONFIG[:np_pattern]
- wait_for_message
- $game_message.new_page
- amended_line = line.gsub(LINE_READER::CONFIG[:np_pattern]) { "" }
- next if amended_line == ""
- end # if
- # Add the line to the message window
- $game_message.add(amended_line || line)
- # Allow normal processing of choices, number input and items
- # alongside the message window
- case next_event_code
- when 102 # Show Choices
- @index += 1
- # This will override the choice strings with the ones in your
- # tags but will keep the cancel choice set in the command.
- setup_choices([choices, @list[@index].parameters[1]])
- when 103 # Input Number
- @index += 1
- setup_num_input(@list[@index].parameters)
- when 104 # Select Item
- @index += 1
- setup_item_choice(@list[@index].parameters)
- end
- } # dialogue.each
- # At this point we've read all of the section, so let's wait for the
- # player to continue.
- wait_for_message
- else
- puts "DEVELOPER ERROR: Key #{identifier} not found in dialogue file."
- end # if
- else
- puts "DEVELOPER ERROR: No dialogue data file found. You may have set up your files incorrectly."
- end # if
- end # def
- def get_random_dialogue(identifier, lang = "EN")
- # $data_dialogue might not exist if there was no data file to load. Better
- # check that first!
- if $data_dialogue[lang]
- dialogue = $data_dialogue[lang][identifier]
- # Even though the file exists at this point, the identifier might not.
- # Need to check that too.
- if dialogue
- # Pick a line, any line
- rnd = rand(dialogue.size-1)
- line = dialogue[rnd]
- if line =~ LINE_READER::CONFIG[:face_pattern]
- # Set the face and face index, and remove the pattern from the line
- $game_message.face_name = $1
- $game_message.face_index = $2.to_i
- amended_line = line.gsub(LINE_READER::CONFIG[:face_pattern]) { "" }
- # Note that we don't have the "next" from the get_dialogue method.
- # The assumption is that if you're using this, you're going to have
- # the presence of mind not to use a face pattern on its own line, or
- # you might just end up with a face and no message.
- end
- # The rest is business as usual.
- $game_message.add(amended_line || line)
- wait_for_message
- else
- puts "DEVELOPER ERROR: Key #{identifier} not found in dialogue file."
- end # if
- else
- puts "DEVELOPER ERROR: No dialogue data file found. You may have set up your files incorrectly."
- end # if
- end # def
- end # class
- # Adding new functionality to Window_Base
- Window_Base.class_eval do
- alias_method :tri_dff_convert_escape_characters_3hs4, :convert_escape_characters
- # Evaluation method for actors
- def actor_eval(n, property)
- actor = n >= 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
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement