diff --git a/Data/Scripts/001_Technical/011_Errors.rb b/Data/Scripts/001_Technical/011_Errors.rb new file mode 100644 index 000000000..238af5a35 --- /dev/null +++ b/Data/Scripts/001_Technical/011_Errors.rb @@ -0,0 +1,88 @@ +#=============================================================================== +# Exceptions and critical code +#=============================================================================== +class Reset < Exception +end + +def pbGetExceptionMessage(e,_script="") + emessage = e.message + if e.is_a?(Hangup) + emessage = "The script is taking too long. The game will restart." + elsif e.is_a?(Errno::ENOENT) + filename = emessage.sub("No such file or directory - ", "") + emessage = "File #{filename} not found." + end + if emessage && !safeExists?("Game.rgssad") && !safeExists?("Game.rgss2a") + emessage = emessage.gsub(/uninitialized constant PBItems\:\:(\S+)/) { + "The item '#{$1}' is not valid. Please add the item\r\nto the list of items in the editor. See the wiki for more information." } + emessage = emessage.gsub(/undefined method `(\S+?)' for PBItems\:Module/) { + "The item '#{$1}' is not valid. Please add the item\r\nto the list of items in the editor. See the wiki for more information." } + emessage = emessage.gsub(/uninitialized constant PBTypes\:\:(\S+)/) { + "The type '#{$1}' is not valid. Please add the type\r\nto the PBS/types.txt file." } + emessage = emessage.gsub(/undefined method `(\S+?)' for PBTypes\:Module/) { + "The type '#{$1}' is not valid. Please add the type\r\nto the PBS/types.txt file." } + emessage = emessage.gsub(/uninitialized constant PBTrainers\:\:(\S+)$/) { + "The trainer type '#{$1}' is not valid. Please add the trainer\r\nto the list of trainer types in the Editor. See the wiki for\r\nmore information." } + emessage = emessage.gsub(/undefined method `(\S+?)' for PBTrainers\:Module/) { + "The trainer type '#{$1}' is not valid. Please add the trainer\r\nto the list of trainer types in the Editor. See the wiki for\r\nmore information." } + emessage = emessage.gsub(/uninitialized constant PBSpecies\:\:(\S+)$/) { + "The Pokemon species '#{$1}' is not valid. Please\r\nadd the species to the PBS/pokemon.txt file.\r\nSee the wiki for more information." } + emessage = emessage.gsub(/undefined method `(\S+?)' for PBSpecies\:Module/) { + "The Pokemon species '#{$1}' is not valid. Please\r\nadd the species to the PBS/pokemon.txt file.\r\nSee the wiki for more information." } + end + emessage.gsub!(/Section(\d+)/) { $RGSS_SCRIPTS[$1.to_i][1] } + return emessage +end + +def pbPrintException(e) + premessage = "\r\n=================\r\n\r\n[#{Time.now}]\r\n" + emessage = "" + if $EVENTHANGUPMSG && $EVENTHANGUPMSG!="" + emessage = $EVENTHANGUPMSG # Message with map/event ID generated elsewhere + $EVENTHANGUPMSG = nil + else + emessage = pbGetExceptionMessage(e) + end + btrace = "" + if e.backtrace + maxlength = ($INTERNAL) ? 25 : 10 + e.backtrace[0,maxlength].each { |i| btrace += "#{i}\r\n" } + end + btrace.gsub!(/Section(\d+)/) { $RGSS_SCRIPTS[$1.to_i][1] } + message = "[Pokémon Essentials version #{ESSENTIALS_VERSION}]\r\n" + message += "#{ERROR_TEXT}" # For third party scripts to add to + message += "Exception: #{e.class}\r\n" + message += "Message: #{emessage}\r\n" + message += "\r\nBacktrace:\r\n#{btrace}" + errorlog = "errorlog.txt" + if (Object.const_defined?(:RTP) rescue false) + errorlog = RTP.getSaveFileName("errorlog.txt") + end + File.open(errorlog,"ab") { |f| f.write(premessage); f.write(message) } + errorlogline = errorlog.sub("/", "\\") + errorlogline.sub!(Dir.pwd + "\\", "") + errorlogline.sub!(pbGetUserName, "USERNAME") + errorlogline = "\r\n" + errorlogline if errorlogline.length > 20 + errorlogline.gsub!("/", "\\") + print("#{message}\r\nThis exception was logged in #{errorlogline}.\r\nPress Ctrl+C to copy this message to the clipboard.") +end + +def pbCriticalCode + ret = 0 + begin + yield + ret = 1 + rescue Exception + e = $! + if e.is_a?(Reset) || e.is_a?(SystemExit) + raise + else + pbPrintException(e) + if e.is_a?(Hangup) + ret = 2 + raise Reset.new + end + end + end + return ret +end diff --git a/Data/Scripts/009_Objects and windows/001_BitmapCache.rb b/Data/Scripts/009_Objects and windows/001_BitmapCache.rb index 516236d12..96ecb6e61 100644 --- a/Data/Scripts/009_Objects and windows/001_BitmapCache.rb +++ b/Data/Scripts/009_Objects and windows/001_BitmapCache.rb @@ -21,10 +21,8 @@ def canonicalize(c) for x in csplit if x=="." elsif x==".." - if pos>=0 - ret.delete_at(pos) - pos -= 1 - end + ret.delete_at(pos) if pos>=0 + pos -= 1 else ret.push(x) pos += 1 diff --git a/Data/Scripts/020_System and utilities/002_PSystem_System.rb b/Data/Scripts/020_System and utilities/002_PSystem_System.rb index b24d36972..e8891f414 100644 --- a/Data/Scripts/020_System and utilities/002_PSystem_System.rb +++ b/Data/Scripts/020_System and utilities/002_PSystem_System.rb @@ -131,7 +131,3 @@ module Input end end end - - - -pbSetUpSystem diff --git a/Data/Scripts/020_System and utilities/005_PSystem_Utilities.rb b/Data/Scripts/020_System and utilities/005_PSystem_Utilities.rb index 0e7cda3aa..492b1de99 100644 --- a/Data/Scripts/020_System and utilities/005_PSystem_Utilities.rb +++ b/Data/Scripts/020_System and utilities/005_PSystem_Utilities.rb @@ -1180,7 +1180,6 @@ end - class PokemonGlobalMetadata attr_accessor :trainerRecording end diff --git a/Data/Scripts/021_Debug/001_Debug_Menu.rb b/Data/Scripts/021_Debug/001_Debug_Menu.rb index 672f34851..524403ee5 100644 --- a/Data/Scripts/021_Debug/001_Debug_Menu.rb +++ b/Data/Scripts/021_Debug/001_Debug_Menu.rb @@ -790,7 +790,7 @@ def pbDebugMenuActions(cmd="",sprites=nil,viewport=nil) pbCompileTextUI when "compiledata" msgwindow = pbCreateMessageWindow - pbCompileAllData(true) { |msg| pbMessageDisplay(msgwindow,msg,false) } + Compiler.compile_all(true) { |msg| pbMessageDisplay(msgwindow,msg,false) } pbMessageDisplay(msgwindow,_INTL("All game data was compiled.")) pbDisposeMessageWindow(msgwindow) when "debugconsole" diff --git a/Data/Scripts/021_Debug/006_Editor_DataFormatting.rb b/Data/Scripts/021_Debug/006_Editor_DataFormatting.rb new file mode 100644 index 000000000..56df87b7d --- /dev/null +++ b/Data/Scripts/021_Debug/006_Editor_DataFormatting.rb @@ -0,0 +1,122 @@ +#=============================================================================== +# String formatting, conversion of value to string +#=============================================================================== +def csvQuote(str,always=false) + return "" if !str || str=="" + if always || str[/[,\"]/] # || str[/^\s/] || str[/\s$/] || str[/^#/] + str = str.gsub(/[\"]/,"\\\"") + str = "\"#{str}\"" + end + return str +end + +def csvQuoteAlways(str) + return csvQuote(str,true) +end + +def pbWriteCsvRecord(record,file,schema) + rec = (record.is_a?(Array)) ? record.clone : [record] + for i in 0...schema[1].length + chr = schema[1][i,1] + file.write(",") if i>0 + if rec[i].nil? + # do nothing + elsif rec[i].is_a?(String) + file.write(csvQuote(rec[i])) + elsif rec[i]==true + file.write("true") + elsif rec[i]==false + file.write("false") + elsif rec[i].is_a?(Numeric) + case chr + when "e", "E" # Enumerable + enumer = schema[2+i] + if enumer.is_a?(Array) + file.write(enumer[rec[i]]) + elsif enumer.is_a?(Symbol) || enumer.is_a?(String) + mod = Object.const_get(enumer.to_sym) + if enumer.to_s=="PBTrainers" && !mod.respond_to?("getCount") + file.write((getConstantName(mod,rec[i]) rescue pbGetTrainerConst(rec[i]))) + else + file.write(getConstantName(mod,rec[i])) + end + elsif enumer.is_a?(Module) + file.write(getConstantName(enumer,rec[i])) + elsif enumer.is_a?(Hash) + for key in enumer.keys + if enumer[key]==rec[i] + file.write(key) + break + end + end + end + when "y", "Y" # Enumerable or integer + enumer = schema[2+i] + if enumer.is_a?(Array) + if enumer[rec[i]]!=nil + file.write(enumer[rec[i]]) + else + file.write(rec[i]) + end + elsif enumer.is_a?(Symbol) || enumer.is_a?(String) + mod = Object.const_get(enumer.to_sym) + if enumer.to_s=="PBTrainers" && !mod.respond_to?("getCount") + file.write((getConstantNameOrValue(mod,rec[i]) rescue pbGetTrainerConst(rec[i]))) + else + file.write(getConstantNameOrValue(mod,rec[i])) + end + elsif enumer.is_a?(Module) + file.write(getConstantNameOrValue(enumer,rec[i])) + elsif enumer.is_a?(Hash) + hasenum = false + for key in enumer.keys + if enumer[key]==rec[i] + file.write(key) + hasenum = true; break + end + end + file.write(rec[i]) unless hasenum + end + else # Any other record type + file.write(rec[i].inspect) + end + else + file.write(rec[i].inspect) + end + end + return record +end + +#=============================================================================== +# Enum const manipulators and parsers +#=============================================================================== +def getConstantName(mod,value) + mod = Object.const_get(mod) if mod.is_a?(Symbol) + for c in mod.constants + return c if mod.const_get(c.to_sym)==value + end + raise _INTL("Value {1} not defined by a constant in {2}",value,mod.name) +end + +def getConstantNameOrValue(mod,value) + mod = Object.const_get(mod) if mod.is_a?(Symbol) + for c in mod.constants + return c if mod.const_get(c.to_sym)==value + end + return value.inspect +end + +def setConstantName(mod,value,name) + mod = Object.const_get(mod) if mod.is_a?(Symbol) + for c in mod.constants + mod.send(:remove_const,c.to_sym) if mod.const_get(c.to_sym)==value + end + mod.const_set(name,value) +end + +def removeConstantValue(mod,value) + mod = Object.const_get(mod) if mod.is_a?(Symbol) + for c in mod.constants + mod.send(:remove_const,c.to_sym) if mod.const_get(c.to_sym)==value + end +end diff --git a/Data/Scripts/021_Debug/006_Editor_DataTypes.rb b/Data/Scripts/021_Debug/007_Editor_DataTypes.rb similarity index 100% rename from Data/Scripts/021_Debug/006_Editor_DataTypes.rb rename to Data/Scripts/021_Debug/007_Editor_DataTypes.rb diff --git a/Data/Scripts/021_Debug/007_Editor_Listers.rb b/Data/Scripts/021_Debug/008_Editor_Listers.rb similarity index 100% rename from Data/Scripts/021_Debug/007_Editor_Listers.rb rename to Data/Scripts/021_Debug/008_Editor_Listers.rb diff --git a/Data/Scripts/021_Debug/008_Editor_Utilities.rb b/Data/Scripts/021_Debug/009_Editor_Utilities.rb similarity index 100% rename from Data/Scripts/021_Debug/008_Editor_Utilities.rb rename to Data/Scripts/021_Debug/009_Editor_Utilities.rb diff --git a/Data/Scripts/021_Debug/009_Editor_TilesetEditor.rb b/Data/Scripts/021_Debug/010_Editor_TilesetEditor.rb similarity index 100% rename from Data/Scripts/021_Debug/009_Editor_TilesetEditor.rb rename to Data/Scripts/021_Debug/010_Editor_TilesetEditor.rb diff --git a/Data/Scripts/021_Debug/010_Editor_MapConnectionEditor.rb b/Data/Scripts/021_Debug/011_Editor_MapConnectionEditor.rb similarity index 100% rename from Data/Scripts/021_Debug/010_Editor_MapConnectionEditor.rb rename to Data/Scripts/021_Debug/011_Editor_MapConnectionEditor.rb diff --git a/Data/Scripts/021_Debug/011_Editor_SpritePosEditor.rb b/Data/Scripts/021_Debug/012_Editor_SpritePosEditor.rb similarity index 100% rename from Data/Scripts/021_Debug/011_Editor_SpritePosEditor.rb rename to Data/Scripts/021_Debug/012_Editor_SpritePosEditor.rb diff --git a/Data/Scripts/021_Debug/012_Editor_BattleAnimationEditor.rb b/Data/Scripts/021_Debug/013_Editor_BattleAnimationEditor.rb similarity index 100% rename from Data/Scripts/021_Debug/012_Editor_BattleAnimationEditor.rb rename to Data/Scripts/021_Debug/013_Editor_BattleAnimationEditor.rb diff --git a/Data/Scripts/022_Compiler/001_Compiler.rb b/Data/Scripts/022_Compiler/001_Compiler.rb deleted file mode 100644 index 31b42a15b..000000000 --- a/Data/Scripts/022_Compiler/001_Compiler.rb +++ /dev/null @@ -1,1297 +0,0 @@ -#=============================================================================== -# Exceptions and critical code -#=============================================================================== -class Reset < Exception -end - - - -def pbGetExceptionMessage(e,_script="") - emessage = e.message - if e.is_a?(Hangup) - emessage = "The script is taking too long. The game will restart." - elsif e.is_a?(Errno::ENOENT) - filename = emessage.sub("No such file or directory - ", "") - emessage = "File #{filename} not found." - end - if emessage && !safeExists?("Game.rgssad") && !safeExists?("Game.rgss2a") - emessage = emessage.gsub(/uninitialized constant PBItems\:\:(\S+)/) { - "The item '#{$1}' is not valid. Please add the item\r\nto the list of items in the editor. See the wiki for more information." } - emessage = emessage.gsub(/undefined method `(\S+?)' for PBItems\:Module/) { - "The item '#{$1}' is not valid. Please add the item\r\nto the list of items in the editor. See the wiki for more information." } - emessage = emessage.gsub(/uninitialized constant PBTypes\:\:(\S+)/) { - "The type '#{$1}' is not valid. Please add the type\r\nto the PBS/types.txt file." } - emessage = emessage.gsub(/undefined method `(\S+?)' for PBTypes\:Module/) { - "The type '#{$1}' is not valid. Please add the type\r\nto the PBS/types.txt file." } - emessage = emessage.gsub(/uninitialized constant PBTrainers\:\:(\S+)$/) { - "The trainer type '#{$1}' is not valid. Please add the trainer\r\nto the list of trainer types in the Editor. See the wiki for\r\nmore information." } - emessage = emessage.gsub(/undefined method `(\S+?)' for PBTrainers\:Module/) { - "The trainer type '#{$1}' is not valid. Please add the trainer\r\nto the list of trainer types in the Editor. See the wiki for\r\nmore information." } - emessage = emessage.gsub(/uninitialized constant PBSpecies\:\:(\S+)$/) { - "The Pokemon species '#{$1}' is not valid. Please\r\nadd the species to the PBS/pokemon.txt file.\r\nSee the wiki for more information." } - emessage = emessage.gsub(/undefined method `(\S+?)' for PBSpecies\:Module/) { - "The Pokemon species '#{$1}' is not valid. Please\r\nadd the species to the PBS/pokemon.txt file.\r\nSee the wiki for more information." } - end - emessage.gsub!(/Section(\d+)/) { $RGSS_SCRIPTS[$1.to_i][1] } - return emessage -end - -def pbPrintException(e) - premessage = "\r\n=================\r\n\r\n[#{Time.now}]\r\n" - emessage = "" - if $EVENTHANGUPMSG && $EVENTHANGUPMSG!="" - emessage = $EVENTHANGUPMSG # Message with map/event ID generated elsewhere - $EVENTHANGUPMSG = nil - else - emessage = pbGetExceptionMessage(e) - end - btrace = "" - if e.backtrace - maxlength = ($INTERNAL) ? 25 : 10 - e.backtrace[0,maxlength].each { |i| btrace += "#{i}\r\n" } - end - btrace.gsub!(/Section(\d+)/) { $RGSS_SCRIPTS[$1.to_i][1] } - message = "[Pokémon Essentials version #{ESSENTIALS_VERSION}]\r\n" - message += "#{ERROR_TEXT}" # For third party scripts to add to - message += "Exception: #{e.class}\r\n" - message += "Message: #{emessage}\r\n" - message += "\r\nBacktrace:\r\n#{btrace}" - errorlog = "errorlog.txt" - if (Object.const_defined?(:RTP) rescue false) - errorlog = RTP.getSaveFileName("errorlog.txt") - end - File.open(errorlog,"ab") { |f| f.write(premessage); f.write(message) } - errorlogline = errorlog.sub("/", "\\") - errorlogline.sub!(Dir.pwd + "\\", "") - errorlogline.sub!(pbGetUserName, "USERNAME") - errorlogline = "\r\n" + errorlogline if errorlogline.length > 20 - errorlogline.gsub!("/", "\\") - print("#{message}\r\nThis exception was logged in #{errorlogline}.\r\nPress Ctrl+C to copy this message to the clipboard.") -end - -def pbCriticalCode - ret = 0 - begin - yield - ret = 1 - rescue Exception - e = $! - if e.is_a?(Reset) || e.is_a?(SystemExit) - raise - else - pbPrintException(e) - if e.is_a?(Hangup) - ret = 2 - raise Reset.new - end - end - end - return ret -end - - - -#=============================================================================== -# File reading -#=============================================================================== -module FileLineData - @file = "" - @linedata = "" - @lineno = 0 - @section = nil - @key = nil - @value = nil - - def self.file; return @file; end - - def self.file=(value); @file = value; end - - def self.clear - @file = "" - @linedata = "" - @lineno = "" - @section = nil - @key = nil - @value = nil - end - - def self.linereport - if @section - if @key!=nil - return _INTL("File {1}, section {2}, key {3}\r\n{4}\r\n\r\n",@file,@section,@key,@value) - else - return _INTL("File {1}, section {2}\r\n{3}\r\n\r\n",@file,@section,@value) - end - else - return _INTL("File {1}, line {2}\r\n{3}\r\n\r\n",@file,@lineno,@linedata) - end - end - - def self.setSection(section,key,value) - @section = section - @key = key - if value && value.length>200 - @value = _INTL("{1}...",value[0,200]) - else - @value = (value) ? value.clone : "" - end - end - - def self.setLine(line,lineno) - @section = nil - @linedata = (line && line.length>200) ? sprintf("%s...",line[0,200]) : line.clone - @lineno = lineno - end -end - - - -def findIndex(a) - index = -1 - count = 0 - a.each { |i| - if yield i - index = count - break - end - count += 1 - } - return index -end - -def prepline(line) - line.sub!(/\s*\#.*$/,"") - line.sub!(/^\s+/,"") - line.sub!(/\s+$/,"") - return line -end - -def pbEachFileSectionEx(f) - lineno = 1 - havesection = false - sectionname = nil - lastsection = {} - f.each_line { |line| - if lineno==1 && line[0]==0xEF && line[1]==0xBB && line[2]==0xBF - line = line[3,line.length-3] - end - if !line[/^\#/] && !line[/^\s*$/] - if line[/^\s*\[\s*(.*)\s*\]\s*$/] # Of the format: [something] - yield lastsection,sectionname if havesection - sectionname = $~[1] - havesection = true - lastsection = {} - else - if sectionname==nil - FileLineData.setLine(line,lineno) - raise _INTL("Expected a section at the beginning of the file. This error may also occur if the file was not saved in UTF-8.\r\n{1}",FileLineData.linereport) - end - if !line[/^\s*(\w+)\s*=\s*(.*)$/] - FileLineData.setSection(sectionname,nil,line) - raise _INTL("Bad line syntax (expected syntax like XXX=YYY)\r\n{1}",FileLineData.linereport) - end - r1 = $~[1] - r2 = $~[2] - lastsection[r1] = r2.gsub(/\s+$/,"") - end - end - lineno += 1 - Graphics.update if lineno%500==0 - Win32API.SetWindowText(_INTL("Processing {1} line {2}",FileLineData.file,lineno)) if lineno%50==0 - } - yield lastsection,sectionname if havesection -end - -def pbEachFileSection(f) - pbEachFileSectionEx(f) { |section,name| - yield section,name.to_i if block_given? && name[/^\d+$/] - } -end - -def pbEachFileSection2(f) - pbEachFileSectionEx(f) { |section,name| - yield section,name if block_given? && name[/^\w+[-,\s]{1}\d+$/] - } -end - -def pbEachSection(f) - lineno = 1 - havesection = false - sectionname = nil - lastsection = [] - f.each_line { |line| - if lineno==1 && line[0]==0xEF && line[1]==0xBB && line[2]==0xBF - line = line[3,line.length-3] - end - if !line[/^\#/] && !line[/^\s*$/] - if line[/^\s*\[\s*(.+?)\s*\]\s*$/] - yield lastsection,sectionname if havesection - sectionname = $~[1] - lastsection = [] - havesection = true - else - if sectionname==nil - raise _INTL("Expected a section at the beginning of the file (line {1}). Sections begin with '[name of section]'",lineno) - end - lastsection.push(line.gsub(/^\s+/,"").gsub(/\s+$/,"")) - end - end - lineno += 1 - Graphics.update if lineno%500==0 - } - yield lastsection,sectionname if havesection -end - -def pbEachCommentedLine(f) - lineno = 1 - f.each_line { |line| - if lineno==1 && line[0]==0xEF && line[1]==0xBB && line[2]==0xBF - line = line[3,line.length-3] - end - yield line, lineno if !line[/^\#/] && !line[/^\s*$/] - lineno += 1 - } -end - -def pbCompilerEachCommentedLine(filename) - File.open(filename,"rb") { |f| - FileLineData.file = filename - lineno = 1 - f.each_line { |line| - if lineno==1 && line[0]==0xEF && line[1]==0xBB && line[2]==0xBF - line = line[3,line.length-3] - end - if !line[/^\#/] && !line[/^\s*$/] - FileLineData.setLine(line,lineno) - yield line, lineno - end - lineno += 1 - } - } -end - -def pbEachPreppedLine(f) - lineno = 1 - f.each_line { |line| - if lineno==1 && line[0]==0xEF && line[1]==0xBB && line[2]==0xBF - line = line[3,line.length-3] - end - line = prepline(line) - yield line, lineno if !line[/^\#/] && !line[/^\s*$/] - lineno += 1 - } -end - -def pbCompilerEachPreppedLine(filename) - File.open(filename,"rb") { |f| - FileLineData.file = filename - lineno = 1 - f.each_line { |line| - if lineno==1 && line[0]==0xEF && line[1]==0xBB && line[2]==0xBF - line = line[3,line.length-3] - end - line = prepline(line) - if !line[/^\#/] && !line[/^\s*$/] - FileLineData.setLine(line,lineno) - yield line, lineno - end - lineno += 1 - } - } -end - -#=============================================================================== -# Valid value checks -#=============================================================================== -def pbCheckByte(x,valuename) - if x<0 || x>255 - raise _INTL("The value \"{1}\" must be from 0 through 255 (00-FF in hex), got a value of {2}\r\n{3}",valuename,x,FileLineData.linereport) - end -end - -def pbCheckSignedByte(x,valuename) - if x<-128 || x>127 - raise _INTL("The value \"{1}\" must be from -128 through 127, got a value of {2}\r\n{3}",valuename,x,FileLineData.linereport) - end -end - -def pbCheckWord(x,valuename) - if x<0 || x>65535 - raise _INTL("The value \"{1}\" must be from 0 through 65535 (0000-FFFF in hex), got a value of {2}\r\n{3}",valuename,x,FileLineData.linereport) - end -end - -def pbCheckSignedWord(x,valuename) - if x<-32768 || x>32767 - raise _INTL("The value \"{1}\" must be from -32768 through 32767, got a value of {2}\r\n{3}",valuename,x,FileLineData.linereport) - end -end - -#=============================================================================== -# Csv parsing -#=============================================================================== -def csvfield!(str) - ret = "" - str.sub!(/^\s*/,"") - if str[0,1]=="\"" - str[0,1] = "" - escaped = false - fieldbytes = 0 - str.scan(/./) do |s| - fieldbytes += s.length - break if s=="\"" && !escaped - if s=="\\" && !escaped - escaped = true - else - ret += s - escaped = false - end - end - str[0,fieldbytes] = "" - if !str[/^\s*,/] && !str[/^\s*$/] - raise _INTL("Invalid quoted field (in: {1})\r\n{2}",str,FileLineData.linereport) - end - str[0,str.length] = $~.post_match - else - if str[/,/] - str[0,str.length] = $~.post_match - ret = $~.pre_match - else - ret = str.clone - str[0,str.length] = "" - end - ret.gsub!(/\s+$/,"") - end - return ret -end - -def csvBoolean!(str,_line=-1) - field = csvfield!(str) - if field[/^1|[Tt][Rr][Uu][Ee]|[Yy][Ee][Ss]|[Yy]$/] - return true - elsif field[/^0|[Ff][Aa][Ll][Ss][Ee]|[Nn][Oo]|[Nn]$/] - return false - end - raise _INTL("Field {1} is not a Boolean value (true, false, 1, 0)\r\n{2}",field,FileLineData.linereport) -end - -def csvInt!(str,_line=-1) - ret = csvfield!(str) - if !ret[/^\-?\d+$/] - raise _INTL("Field {1} is not an integer\r\n{2}",ret,FileLineData.linereport) - end - return ret.to_i -end - -def csvPosInt!(str,_line=-1) - ret = csvfield!(str) - if !ret[/^\d+$/] - raise _INTL("Field {1} is not a positive integer\r\n{2}",ret,FileLineData.linereport) - end - return ret.to_i -end - -def csvFloat!(str,_line=-1) - ret = csvfield!(str) - return Float(ret) rescue raise _INTL("Field {1} is not a number\r\n{2}",ret,FileLineData.linereport) -end - -def csvEnumField!(value,enumer,_key,_section) - ret = csvfield!(value) - return checkEnumField(ret,enumer) -end - -def csvEnumFieldOrInt!(value,enumer,_key,_section) - ret = csvfield!(value) - return ret.to_i if ret[/\-?\d+/] - return checkEnumField(ret,enumer) -end - -def checkEnumField(ret,enumer) - if enumer.is_a?(Module) - begin - if ret=="" || !enumer.const_defined?(ret) - raise _INTL("Undefined value {1} in {2}\r\n{3}",ret,enumer.name,FileLineData.linereport) - end - rescue NameError - raise _INTL("Incorrect value {1} in {2}\r\n{3}",ret,enumer.name,FileLineData.linereport) - end - return enumer.const_get(ret.to_sym) - elsif enumer.is_a?(Symbol) || enumer.is_a?(String) - enumer = Object.const_get(enumer.to_sym) - begin - if ret=="" || !enumer.const_defined?(ret) - raise _INTL("Undefined value {1} in {2}\r\n{3}",ret,enumer.name,FileLineData.linereport) - end - rescue NameError - raise _INTL("Incorrect value {1} in {2}\r\n{3}",ret,enumer.name,FileLineData.linereport) - end - return enumer.const_get(ret.to_sym) - elsif enumer.is_a?(Array) - idx = findIndex(enumer) { |item| ret==item } - if idx<0 - raise _INTL("Undefined value {1} (expected one of: {2})\r\n{3}",ret,enumer.inspect,FileLineData.linereport) - end - return idx - elsif enumer.is_a?(Hash) - value = enumer[ret] - if value==nil - raise _INTL("Undefined value {1} (expected one of: {2})\r\n{3}",ret,enumer.keys.inspect,FileLineData.linereport) - end - return value - end - raise _INTL("Enumeration not defined\r\n{1}",FileLineData.linereport) -end - -def checkEnumFieldOrNil(ret,enumer) - if enumer.is_a?(Module) - return nil if ret=="" || !(enumer.const_defined?(ret) rescue false) - return enumer.const_get(ret.to_sym) - elsif enumer.is_a?(Symbol) || enumer.is_a?(String) - enumer = Object.const_get(enumer.to_sym) - return nil if ret=="" || !(enumer.const_defined?(ret) rescue false) - return enumer.const_get(ret.to_sym) - elsif enumer.is_a?(Array) - idx = findIndex(enumer) { |item| ret==item } - return nil if idx<0 - return idx - elsif enumer.is_a?(Hash) - return enumer[ret] - end - return nil -end - -#=============================================================================== -# Csv record readin -#=============================================================================== -def pbGetCsvRecord(rec,lineno,schema) - record = [] - repeat = false - start = 0 - if schema[1][0,1]=="*" - repeat = true - start = 1 - end - begin - for i in start...schema[1].length - chr = schema[1][i,1] - case chr - when "i" # Integer - record.push(csvInt!(rec,lineno)) - when "I" # Optional integer - field = csvfield!(rec) - if field=="" - record.push(nil) - elsif !field[/^\-?\d+$/] - raise _INTL("Field {1} is not an integer\r\n{2}",field,FileLineData.linereport) - else - record.push(field.to_i) - end - when "u" # Positive integer or zero - record.push(csvPosInt!(rec,lineno)) - when "U" # Optional positive integer or zero - field = csvfield!(rec) - if field=="" - record.push(nil) - elsif !field[/^\d+$/] - raise _INTL("Field '{1}' must be 0 or greater\r\n{2}",field,FileLineData.linereport) - else - record.push(field.to_i) - end - when "v" # Positive integer - field = csvPosInt!(rec,lineno) - raise _INTL("Field '{1}' must be greater than 0\r\n{2}",field,FileLineData.linereport) if field==0 - record.push(field) - when "V" # Optional positive integer - field = csvfield!(rec) - if field=="" - record.push(nil) - elsif !field[/^\d+$/] - raise _INTL("Field '{1}' must be greater than 0\r\n{2}",field,FileLineData.linereport) - elsif field.to_i==0 - raise _INTL("Field '{1}' must be greater than 0\r\n{2}",field,FileLineData.linereport) - else - record.push(field.to_i) - end - when "x" # Hexadecimal number - field = csvfield!(rec) - if !field[/^[A-Fa-f0-9]+$/] - raise _INTL("Field '{1}' is not a hexadecimal number\r\n{2}",field,FileLineData.linereport) - end - record.push(field.hex) - when "X" # Optional hexadecimal number - field = csvfield!(rec) - if field=="" - record.push(nil) - elsif !field[/^[A-Fa-f0-9]+$/] - raise _INTL("Field '{1}' is not a hexadecimal number\r\n{2}",field,FileLineData.linereport) - else - record.push(field.hex) - end - when "f" # Floating point number - record.push(csvFloat!(rec,lineno)) - when "F" # Optional floating point number - field = csvfield!(rec) - if field=="" - record.push(nil) - elsif !field[/^\-?^\d*\.?\d*$/] - raise _INTL("Field {1} is not a floating point number\r\n{2}",field,FileLineData.linereport) - else - record.push(field.to_f) - end - when "b" # Boolean - record.push(csvBoolean!(rec,lineno)) - when "B" # Optional Boolean - field = csvfield!(rec) - if field=="" - record.push(nil) - elsif field[/^1|[Tt][Rr][Uu][Ee]|[Yy][Ee][Ss]|[Tt]|[Yy]$/] - record.push(true) - else - record.push(false) - end - when "n" # Name - field = csvfield!(rec) - if !field[/^(?![0-9])\w+$/] - raise _INTL("Field '{1}' must contain only letters, digits, and\r\nunderscores and can't begin with a number.\r\n{2}",field,FileLineData.linereport) - end - record.push(field) - when "N" # Optional name - field = csvfield!(rec) - if field=="" - record.push(nil) - elsif !field[/^(?![0-9])\w+$/] - raise _INTL("Field '{1}' must contain only letters, digits, and\r\nunderscores and can't begin with a number.\r\n{2}",field,FileLineData.linereport) - else - record.push(field) - end - when "s" # String - record.push(csvfield!(rec)) - when "S" # Optional string - field = csvfield!(rec) - record.push((field=="") ? nil : field) - when "q" # Unformatted text - record.push(rec) - rec = "" - when "Q" # Optional unformatted text - if !rec || rec=="" - record.push(nil) - else - record.push(rec) - rec = "" - end - when "e" # Enumerable - record.push(csvEnumField!(rec,schema[2+i-start],"",FileLineData.linereport)) - when "E" # Optional enumerable - field = csvfield!(rec) - record.push(checkEnumFieldOrNil(field,schema[2+i-start])) - when "y" # Enumerable or integer - field = csvfield!(rec) - record.push(csvEnumFieldOrInt!(field,schema[2+i-start],"",FileLineData.linereport)) - when "Y" # Optional enumerable or integer - field = csvfield!(rec) - if field=="" - record.push(nil) - elsif field[/^\-?\d+$/] - record.push(field.to_i) - else - record.push(checkEnumFieldOrNil(field,schema[2+i-start])) - end - end - end - break if repeat && rec=="" - end while repeat - return (schema[1].length==1) ? record[0] : record -end - -#=============================================================================== -# Csv record writing -#=============================================================================== -def csvQuote(str,always=false) - return "" if !str || str=="" - if always || str[/[,\"]/] # || str[/^\s/] || str[/\s$/] || str[/^#/] - str = str.gsub(/[\"]/,"\\\"") - str = "\"#{str}\"" - end - return str -end - -def csvQuoteAlways(str) - return csvQuote(str,true) -end - -def pbWriteCsvRecord(record,file,schema) - rec = (record.is_a?(Array)) ? record.clone : [record] - for i in 0...schema[1].length - chr = schema[1][i,1] - file.write(",") if i>0 - if rec[i].nil? - # do nothing - elsif rec[i].is_a?(String) - file.write(csvQuote(rec[i])) - elsif rec[i]==true - file.write("true") - elsif rec[i]==false - file.write("false") - elsif rec[i].is_a?(Numeric) - case chr - when "e", "E" # Enumerable - enumer = schema[2+i] - if enumer.is_a?(Array) - file.write(enumer[rec[i]]) - elsif enumer.is_a?(Symbol) || enumer.is_a?(String) - mod = Object.const_get(enumer.to_sym) - if enumer.to_s=="PBTrainers" && !mod.respond_to?("getCount") - file.write((getConstantName(mod,rec[i]) rescue pbGetTrainerConst(rec[i]))) - else - file.write(getConstantName(mod,rec[i])) - end - elsif enumer.is_a?(Module) - file.write(getConstantName(enumer,rec[i])) - elsif enumer.is_a?(Hash) - for key in enumer.keys - if enumer[key]==rec[i] - file.write(key) - break - end - end - end - when "y", "Y" # Enumerable or integer - enumer = schema[2+i] - if enumer.is_a?(Array) - if enumer[rec[i]]!=nil - file.write(enumer[rec[i]]) - else - file.write(rec[i]) - end - elsif enumer.is_a?(Symbol) || enumer.is_a?(String) - mod = Object.const_get(enumer.to_sym) - if enumer.to_s=="PBTrainers" && !mod.respond_to?("getCount") - file.write((getConstantNameOrValue(mod,rec[i]) rescue pbGetTrainerConst(rec[i]))) - else - file.write(getConstantNameOrValue(mod,rec[i])) - end - elsif enumer.is_a?(Module) - file.write(getConstantNameOrValue(enumer,rec[i])) - elsif enumer.is_a?(Hash) - hasenum = false - for key in enumer.keys - if enumer[key]==rec[i] - file.write(key) - hasenum = true; break - end - end - file.write(rec[i]) unless hasenum - end - else # Any other record type - file.write(rec[i].inspect) - end - else - file.write(rec[i].inspect) - end - end - return record -end - -#=============================================================================== -# Encoding and decoding -#=============================================================================== -def intSize(value) - return 1 if value<0x80 - return 2 if value<0x4000 - return 3 if value<0x200000 - return 4 if value<0x10000000 - return 5 -end - -def encodeInt(strm,value) - num = 0 - loop do - if value<0x80 - strm.fputb(value) - return num+1 - end - strm.fputb(0x80|(value&0x7F)) - value >>= 7 - num += 1 - end -end - -def decodeInt(strm) - bits = 0 - curbyte = 0 - ret = 0 - begin - curbyte = strm.fgetb - ret += (curbyte&0x7F)<0)&&bits<0x1d - return ret -end - -def strSize(str) - return str.length+intSize(str.length) -end - -def encodeString(strm,str) - encodeInt(strm,str.length) - strm.write(str) -end - -def decodeString(strm) - len = decodeInt(strm) - return strm.read(len) -end - -def strsplit(str,re) - ret = [] - tstr = str - while re=~tstr - ret[ret.length] = $~.pre_match - tstr = $~.post_match - end - ret[ret.length] = tstr if ret.length - return ret -end - -def canonicalize(c) - csplit = strsplit(c,/[\/\\]/) - pos = -1 - ret = [] - retstr = "" - for x in csplit - if x=="." - elsif x==".." - ret.delete_at(pos) if pos>=0 - pos -= 1 - else - ret.push(x) - pos += 1 - end - end - for i in 0...ret.length - retstr += "/" if i>0 - retstr += ret[i] - end - return retstr -end - -def frozenArrayValue(arr) - typestring = "" - for i in 0...arr.length - if i>0 - typestring += ((i%20)==0) ? ",\r\n" : "," - end - typestring += arr[i].to_s - end - return "["+typestring+"].freeze" -end - -#=============================================================================== -# Enum const manipulators and parsers -#=============================================================================== -def pbGetConst(mod,item,err) - isDef = false - begin - mod = Object.const_get(mod) if mod.is_a?(Symbol) - isDef = mod.const_defined?(item.to_sym) - rescue - raise sprintf(err,item) - end - raise sprintf(err,item) if !isDef - return mod.const_get(item.to_sym) -end - -def getConstantName(mod,value) - mod = Object.const_get(mod) if mod.is_a?(Symbol) - for c in mod.constants - return c if mod.const_get(c.to_sym)==value - end - raise _INTL("Value {1} not defined by a constant in {2}",value,mod.name) -end - -def getConstantNameOrValue(mod,value) - mod = Object.const_get(mod) if mod.is_a?(Symbol) - for c in mod.constants - return c if mod.const_get(c.to_sym)==value - end - return value.inspect -end - -def setConstantName(mod,value,name) - mod = Object.const_get(mod) if mod.is_a?(Symbol) - for c in mod.constants - mod.send(:remove_const,c.to_sym) if mod.const_get(c.to_sym)==value - end - mod.const_set(name,value) -end - -def removeConstantValue(mod,value) - mod = Object.const_get(mod) if mod.is_a?(Symbol) - for c in mod.constants - mod.send(:remove_const,c.to_sym) if mod.const_get(c.to_sym)==value - end -end - -def parseItem(item) - clonitem = item.upcase - clonitem.sub!(/^\s*/,"") - clonitem.sub!(/\s*$/,"") - return pbGetConst(PBItems,clonitem,_INTL("Undefined item constant name: %s\r\nName must consist only of letters, numbers, and\r\nunderscores and can't begin with a number.\r\nMake sure the item is defined in\r\nPBS/items.txt.\r\n{1}",FileLineData.linereport)) -end - -def parseSpecies(item) - clonitem = item.upcase - clonitem.gsub!(/^[\s\n]*/,"") - clonitem.gsub!(/[\s\n]*$/,"") - clonitem = "NIDORANmA" if clonitem=="NIDORANMA" - clonitem = "NIDORANfE" if clonitem=="NIDORANFE" - return pbGetConst(PBSpecies,clonitem,_INTL("Undefined species constant name: [%s]\r\nName must consist only of letters, numbers, and\r\nunderscores and can't begin with a number.\r\nMake sure the name is defined in\r\nPBS/pokemon.txt.\r\n{1}",FileLineData.linereport)) -end - -def parseMove(item) - clonitem = item.upcase - clonitem.sub!(/^\s*/,"") - clonitem.sub!(/\s*$/,"") - return pbGetConst(PBMoves,clonitem,_INTL("Undefined move constant name: %s\r\nName must consist only of letters, numbers, and\r\nunderscores and can't begin with a number.\r\nMake sure the name is defined in\r\nPBS/moves.txt.\r\n{1}",FileLineData.linereport)) -end - -def parseNature(item) - clonitem = item.upcase - clonitem.sub!(/^\s*/,"") - clonitem.sub!(/\s*$/,"") - return pbGetConst(PBNatures,clonitem,_INTL("Undefined nature constant name: %s\r\nName must consist only of letters, numbers, and\r\nunderscores and can't begin with a number.\r\nMake sure the name is defined in\r\nthe script section PBNatures.\r\n{1}",FileLineData.linereport)) -end - -def parseTrainer(item) - clonitem = item.clone - clonitem.sub!(/^\s*/,"") - clonitem.sub!(/\s*$/,"") - return pbGetConst(PBTrainers,clonitem,_INTL("Undefined Trainer constant name: %s\r\nName must consist only of letters, numbers, and\r\nunderscores and can't begin with a number.\r\nIn addition, the name must be defined\r\nin trainertypes.txt.\r\n{1}",FileLineData.linereport)) -end - -#=============================================================================== -# Scripted constants -#=============================================================================== -def pbFindScript(a,name) - a.each { |i| - next if !i - return i if i[1]==name - } - return nil -end - -def pbAddScript(script,sectionname) - begin - scripts = load_data("Data/Constants.rxdata") - scripts = [] if !scripts - rescue - scripts = [] - end - if false # s - s = pbFindScript(scripts,sectionname) - s[2]+=Zlib::Deflate.deflate("#{script}\r\n") - else - scripts.push([rand(100000000),sectionname,Zlib::Deflate.deflate("#{script}\r\n")]) - end - save_data(scripts,"Data/Constants.rxdata") -end - - - -#=============================================================================== -# Serial record -#=============================================================================== -class SerialRecord < Array - def bytesize - return SerialRecord.bytesize(self) - end - - def encode(strm) - return SerialRecord.encode(self,strm) - end - - def self.bytesize(arr) - ret = 0 - return 0 if !arr - for field in arr - if field==nil || field==true || field==false - ret += 1 - elsif field.is_a?(String) - ret += strSize(field)+1 - elsif field.is_a?(Numeric) - ret += intSize(field)+1 - end - end - return ret - end - - def self.encode(arr,strm) - return if !arr - for field in arr - if field==nil - strm.write("0") - elsif field==true - strm.write("T") - elsif field==false - strm.write("F") - elsif field.is_a?(String) - strm.write("\"") - encodeString(strm,field) - elsif field.is_a?(Numeric) - strm.write("i") - encodeInt(strm,field) - end - end - end - - def self.decode(strm,offset,length) - ret = SerialRecord.new - strm.pos = offset - while strm.pos>3 - curpos = 0 - numrec.times do - file.pos = curpos - offset = file.fgetdw - length = file.fgetdw - record = SerialRecord.decode(file,offset,length) - ret.push(record) - curpos += 8 - end - } - return ret -end - -def writeSerialRecords(filename,records) - File.open(filename,"wb") { |file| - totalsize = records.length*8 - for record in records - file.fputdw(totalsize) - bytesize = record.bytesize - file.fputdw(bytesize) - totalsize += bytesize - end - for record in records - record.encode(file) - end - } -end - - - -#=============================================================================== -# Data structures -#=============================================================================== -class ByteArray - include Enumerable - - def initialize(data=nil) - @a = (data) ? data.unpack("C*") : [] - end - - def [](i); return @a[i]; end - def []=(i,value); @a[i] = value; end - - def length; @a.length; end - def size; @a.size; end - - def fillNils(length,value) - for i in 0...length - @a[i] = value if !@a[i] - end - end - - def each - @a.each { |i| yield i} - end - - def self._load(str) - return self.new(str) - end - - def _dump(_depth=100) - return @a.pack("C*") - end -end - - - -class WordArray - include Enumerable - - def initialize(data=nil) - @a = (data) ? data.unpack("v*") : [] - end - - def [](i); return @a[i]; end - def []=(i,value); @a[i] = value; end - - def length; @a.length; end - def size; @a.size; end - - def fillNils(length,value) - for i in 0...length - @a[i] = value if !@a[i] - end - end - - def each - @a.each { |i| yield i} - end - - def self._load(str) - return self.new(str) - end - - def _dump(_depth=100) - return @a.pack("v*") - end -end - - - -class SignedWordArray - include Enumerable - - def initialize(data=nil) - @a = (data) ? data.unpack("v*") : [] - end - - def []=(i,value) - @a[i] = value - end - - def [](i) - v = @a[i] - return 0 if !v - return (v<0x8000) ? v : -((~v)&0xFFFF)-1 - end - - def length; @a.length; end - def size; @a.size; end - - def fillNils(length,value) - for i in 0...length - @a[i] = value if !@a[i] - end - end - - def each - @a.each { |i| yield i} - end - - def self._load(str) - return self.new(str) - end - - def _dump(_depth=100) - return @a.pack("v*") - end -end - - - -#=============================================================================== -# Compile all data -#=============================================================================== -def pbCompileAllData(mustCompile) - FileLineData.clear - if mustCompile - if (!$INEDITOR || LANGUAGES.length<2) && pbRgssExists?("Data/messages.dat") - MessageTypes.loadMessageFile("Data/messages.dat") - end - # No dependencies - yield(_INTL("Compiling type data")) - pbCompileTypes - # No dependencies - yield(_INTL("Compiling town map data")) - pbCompileTownMap - # No dependencies - yield(_INTL("Compiling map connection data")) - pbCompileConnections - # No dependencies - yield(_INTL("Compiling ability data")) - pbCompileAbilities - # Depends on PBTypes - yield(_INTL("Compiling move data")) - pbCompileMoves - # Depends on PBMoves - yield(_INTL("Compiling item data")) - pbCompileItems - # Depends on PBItems - yield(_INTL("Compiling berry plant data")) - pbCompileBerryPlants - # Depends on PBMoves, PBItems, PBTypes, PBAbilities - yield(_INTL("Compiling Pokémon data")) - pbCompilePokemonData - # Depends on PBSpecies, PBMoves - yield(_INTL("Compiling Pokémon forms data")) - pbCompilePokemonForms - # Depends on PBSpecies, PBMoves - yield(_INTL("Compiling machine data")) - pbCompileMachines - # No dependencies - yield(_INTL("Compiling Trainer type data")) - pbCompileTrainerTypes - # Depends on PBSpecies, PBItems, PBMoves - yield(_INTL("Compiling Trainer data")) - pbCompileTrainers - # Depends on PBTrainers - yield(_INTL("Compiling phone data")) - pbCompilePhoneData - # Depends on PBTrainers - yield(_INTL("Compiling metadata")) - pbCompileMetadata - # Depends on PBTrainers - yield(_INTL("Compiling battle Trainer data")) - pbCompileTrainerLists - # Depends on PBSpecies - yield(_INTL("Compiling encounter data")) - pbCompileEncounters - # Depends on PBSpecies, PBMoves - yield(_INTL("Compiling shadow move data")) - pbCompileShadowMoves - yield(_INTL("Compiling messages")) - pbCompileAnimations - pbCompileTrainerEvents(mustCompile) - pbSetTextMessages - MessageTypes.saveMessages - else - if (!$INEDITOR || LANGUAGES.length<2) && safeExists?("Data/messages.dat") - MessageTypes.loadMessageFile("Data/messages.dat") - end - end - if !$INEDITOR && LANGUAGES.length>=2 - pbLoadMessages("Data/"+LANGUAGES[$PokemonSystem.language][1]) - end -end - -def pbCompiler - return if !$DEBUG - begin - dataFiles = [ - "berry_plants.dat", - "encounters.dat", - "form2species.dat", - "items.dat", - "map_connections.dat", - "metadata.dat", - "moves.dat", - "phone.dat", - "regional_dexes.dat", - "shadow_movesets.dat", - "species.dat", - "species_eggmoves.dat", - "species_evolutions.dat", - "species_metrics.dat", - "species_movesets.dat", - "tm.dat", - "town_map.dat", - "trainer_lists.dat", - "trainer_types.dat", - "trainers.dat", - "types.dat", - "Constants.rxdata" - ] - textFiles = [ - "abilities.txt", - "berryplants.txt", - "connections.txt", - "encounters.txt", - "items.txt", - "metadata.txt", - "moves.txt", - "phone.txt", - "pokemon.txt", - "pokemonforms.txt", - "shadowmoves.txt", - "tm.txt", - "townmap.txt", - "trainerlists.txt", - "trainers.txt", - "trainertypes.txt", - "types.txt" - ] - latestDataTime = 0 - latestTextTime = 0 - mustCompile = false - # Should recompile if new maps were imported - mustCompile |= pbImportNewMaps - # Should recompile if no existing data is found - mustCompile |= !(PBSpecies.respond_to?("maxValue") rescue false) - # If no PBS file, create one and fill it, then recompile - if !safeIsDirectory?("PBS") - Dir.mkdir("PBS") rescue nil - pbSaveAllData - mustCompile = true - end - # Check data files and PBS files, and recompile if any PBS file was edited - # more recently than the data files were last created - for i in 0...dataFiles.length - begin - File.open("Data/#{dataFiles[i]}") { |file| - latestDataTime = [latestDataTime,file.mtime.to_i].max - } - rescue SystemCallError - mustCompile = true - end - end - for i in 0...textFiles.length - begin - File.open("PBS/#{textFiles[i]}") { |file| - latestTextTime = [latestTextTime,file.mtime.to_i].max - } - rescue SystemCallError - end - end - mustCompile |= (latestTextTime>=latestDataTime) - # Should recompile if holding Ctrl - Input.update - mustCompile = true if Input.press?(Input::CTRL) - # Delete old data files in preparation for recompiling - if mustCompile - for i in 0...dataFiles.length - begin - File.delete("Data/#{dataFiles[i]}") - rescue SystemCallError - end - end - end - # Recompile all data - pbCompileAllData(mustCompile) { |msg| Win32API.SetWindowText(msg) } - rescue Exception - e = $! - raise e if "#{e.class}"=="Reset" || e.is_a?(Reset) || e.is_a?(SystemExit) - pbPrintException(e) - for i in 0...dataFiles.length - begin - File.delete("Data/#{dataFiles[i]}") - rescue SystemCallError - end - end - raise Reset.new if e.is_a?(Hangup) - loop do - Graphics.update - end - end -end diff --git a/Data/Scripts/022_Compiler/001_Data_storage.rb b/Data/Scripts/022_Compiler/001_Data_storage.rb new file mode 100644 index 000000000..7c825b249 --- /dev/null +++ b/Data/Scripts/022_Compiler/001_Data_storage.rb @@ -0,0 +1,296 @@ +#=============================================================================== +# Serial record +#=============================================================================== +# Unused +module SerialRecords + class SerialRecord < Array + def bytesize + return SerialRecord.bytesize(self) + end + + def encode(strm) + return SerialRecord.encode(self,strm) + end + + def self.bytesize(arr) + ret = 0 + return 0 if !arr + for field in arr + if field==nil || field==true || field==false + ret += 1 + elsif field.is_a?(String) + ret += strSize(field)+1 + elsif field.is_a?(Numeric) + ret += intSize(field)+1 + end + end + return ret + end + + def self.encode(arr,strm) + return if !arr + for field in arr + if field==nil + strm.write("0") + elsif field==true + strm.write("T") + elsif field==false + strm.write("F") + elsif field.is_a?(String) + strm.write("\"") + encodeString(strm,field) + elsif field.is_a?(Numeric) + strm.write("i") + encodeInt(strm,field) + end + end + end + + def self.decode(strm,offset,length) + ret = SerialRecord.new + strm.pos = offset + while strm.pos>3 + curpos = 0 + numrec.times do + file.pos = curpos + offset = file.fgetdw + length = file.fgetdw + record = SerialRecord.decode(file,offset,length) + ret.push(record) + curpos += 8 + end + } + return ret + end + + def self.writeSerialRecords(filename,records) + File.open(filename,"wb") { |file| + totalsize = records.length*8 + for record in records + file.fputdw(totalsize) + bytesize = record.bytesize + file.fputdw(bytesize) + totalsize += bytesize + end + for record in records + record.encode(file) + end + } + end +end + +#=============================================================================== +# Data structures +#=============================================================================== +# Unused +class ByteArray + include Enumerable + + def initialize(data=nil) + @a = (data) ? data.unpack("C*") : [] + end + + def [](i); return @a[i]; end + def []=(i,value); @a[i] = value; end + + def length; @a.length; end + def size; @a.size; end + + def fillNils(length,value) + for i in 0...length + @a[i] = value if !@a[i] + end + end + + def each + @a.each { |i| yield i} + end + + def self._load(str) + return self.new(str) + end + + def _dump(_depth=100) + return @a.pack("C*") + end +end + +# Used for tm.txt data +class WordArray + include Enumerable + + def initialize(data=nil) + @a = (data) ? data.unpack("v*") : [] + end + + def [](i); return @a[i]; end + def []=(i,value); @a[i] = value; end + + def length; @a.length; end + def size; @a.size; end + + def fillNils(length,value) + for i in 0...length + @a[i] = value if !@a[i] + end + end + + def each + @a.each { |i| yield i} + end + + def self._load(str) + return self.new(str) + end + + def _dump(_depth=100) + return @a.pack("v*") + end +end + +# Unused +class SignedWordArray + include Enumerable + + def initialize(data=nil) + @a = (data) ? data.unpack("v*") : [] + end + + def []=(i,value) + @a[i] = value + end + + def [](i) + v = @a[i] + return 0 if !v + return (v<0x8000) ? v : -((~v)&0xFFFF)-1 + end + + def length; @a.length; end + def size; @a.size; end + + def fillNils(length,value) + for i in 0...length + @a[i] = value if !@a[i] + end + end + + def each + @a.each { |i| yield i} + end + + def self._load(str) + return self.new(str) + end + + def _dump(_depth=100) + return @a.pack("v*") + end +end + +#=============================================================================== +# Encoding and decoding +#=============================================================================== +def intSize(value) + return 1 if value<0x80 + return 2 if value<0x4000 + return 3 if value<0x200000 + return 4 if value<0x10000000 + return 5 +end + +def encodeInt(strm,value) + num = 0 + loop do + if value<0x80 + strm.fputb(value) + return num+1 + end + strm.fputb(0x80|(value&0x7F)) + value >>= 7 + num += 1 + end +end + +def decodeInt(strm) + bits = 0 + curbyte = 0 + ret = 0 + begin + curbyte = strm.fgetb + ret += (curbyte&0x7F)<0)&&bits<0x1d + return ret +end + +def strSize(str) + return str.length+intSize(str.length) +end + +def encodeString(strm,str) + encodeInt(strm,str.length) + strm.write(str) +end + +def decodeString(strm) + len = decodeInt(strm) + return strm.read(len) +end + +# Unused +def frozenArrayValue(arr) + typestring = "" + for i in 0...arr.length + if i>0 + typestring += ((i%20)==0) ? ",\r\n" : "," + end + typestring += arr[i].to_s + end + return "["+typestring+"].freeze" +end + +#=============================================================================== +# Scripted constants +#=============================================================================== +def pbFindScript(a,name) + a.each { |i| + next if !i + return i if i[1]==name + } + return nil +end + +def pbAddScript(script,sectionname) + begin + scripts = load_data("Data/Constants.rxdata") + scripts = [] if !scripts + rescue + scripts = [] + end + if false # s + s = pbFindScript(scripts,sectionname) + s[2]+=Zlib::Deflate.deflate("#{script}\r\n") + else + scripts.push([rand(100000000),sectionname,Zlib::Deflate.deflate("#{script}\r\n")]) + end + save_data(scripts,"Data/Constants.rxdata") +end diff --git a/Data/Scripts/022_Compiler/002_Compiler.rb b/Data/Scripts/022_Compiler/002_Compiler.rb new file mode 100644 index 000000000..af2747346 --- /dev/null +++ b/Data/Scripts/022_Compiler/002_Compiler.rb @@ -0,0 +1,755 @@ +#=============================================================================== +# Records which file, section and line are currently being read +#=============================================================================== +module FileLineData + @file = "" + @linedata = "" + @lineno = 0 + @section = nil + @key = nil + @value = nil + + def self.file; return @file; end + def self.file=(value); @file = value; end + + def self.clear + @file = "" + @linedata = "" + @lineno = "" + @section = nil + @key = nil + @value = nil + end + + def self.setSection(section,key,value) + @section = section + @key = key + if value && value.length>200 + @value = _INTL("{1}...",value[0,200]) + else + @value = (value) ? value.clone : "" + end + end + + def self.setLine(line,lineno) + @section = nil + @linedata = (line && line.length>200) ? sprintf("%s...",line[0,200]) : line.clone + @lineno = lineno + end + + def self.linereport + if @section + if @key!=nil + return _INTL("File {1}, section {2}, key {3}\r\n{4}\r\n\r\n",@file,@section,@key,@value) + else + return _INTL("File {1}, section {2}\r\n{3}\r\n\r\n",@file,@section,@value) + end + else + return _INTL("File {1}, line {2}\r\n{3}\r\n\r\n",@file,@lineno,@linedata) + end + end +end + +#=============================================================================== +# Compiler +#=============================================================================== +module Compiler + extend self + + def findIndex(a) + index = -1 + count = 0 + a.each { |i| + if yield i + index = count + break + end + count += 1 + } + return index + end + + def prepline(line) + line.sub!(/\s*\#.*$/,"") + line.sub!(/^\s+/,"") + line.sub!(/\s+$/,"") + return line + end + + #============================================================================= + # PBS file readers + #============================================================================= + def pbEachFileSectionEx(f) + lineno = 1 + havesection = false + sectionname = nil + lastsection = {} + f.each_line { |line| + if lineno==1 && line[0]==0xEF && line[1]==0xBB && line[2]==0xBF + line = line[3,line.length-3] + end + if !line[/^\#/] && !line[/^\s*$/] + if line[/^\s*\[\s*(.*)\s*\]\s*$/] # Of the format: [something] + yield lastsection,sectionname if havesection + sectionname = $~[1] + havesection = true + lastsection = {} + else + if sectionname==nil + FileLineData.setLine(line,lineno) + raise _INTL("Expected a section at the beginning of the file. This error may also occur if the file was not saved in UTF-8.\r\n{1}",FileLineData.linereport) + end + if !line[/^\s*(\w+)\s*=\s*(.*)$/] + FileLineData.setSection(sectionname,nil,line) + raise _INTL("Bad line syntax (expected syntax like XXX=YYY)\r\n{1}",FileLineData.linereport) + end + r1 = $~[1] + r2 = $~[2] + lastsection[r1] = r2.gsub(/\s+$/,"") + end + end + lineno += 1 + Graphics.update if lineno%500==0 + Win32API.SetWindowText(_INTL("Processing {1} line {2}",FileLineData.file,lineno)) if lineno%50==0 + } + yield lastsection,sectionname if havesection + end + + # Used for pokemon.txt + def pbEachFileSection(f) + pbEachFileSectionEx(f) { |section,name| + yield section,name.to_i if block_given? && name[/^\d+$/] + } + end + + # Used for pokemonforms.txt + def pbEachFileSection2(f) + pbEachFileSectionEx(f) { |section,name| + yield section,name if block_given? && name[/^\w+[-,\s]{1}\d+$/] + } + end + + # Used for phone.txt + def pbEachSection(f) + lineno = 1 + havesection = false + sectionname = nil + lastsection = [] + f.each_line { |line| + if lineno==1 && line[0]==0xEF && line[1]==0xBB && line[2]==0xBF + line = line[3,line.length-3] + end + if !line[/^\#/] && !line[/^\s*$/] + if line[/^\s*\[\s*(.+?)\s*\]\s*$/] + yield lastsection,sectionname if havesection + sectionname = $~[1] + lastsection = [] + havesection = true + else + if sectionname==nil + raise _INTL("Expected a section at the beginning of the file (line {1}). Sections begin with '[name of section]'",lineno) + end + lastsection.push(line.gsub(/^\s+/,"").gsub(/\s+$/,"")) + end + end + lineno += 1 + Graphics.update if lineno%500==0 + } + yield lastsection,sectionname if havesection + end + + # Unused + def pbEachCommentedLine(f) + lineno = 1 + f.each_line { |line| + if lineno==1 && line[0]==0xEF && line[1]==0xBB && line[2]==0xBF + line = line[3,line.length-3] + end + yield line, lineno if !line[/^\#/] && !line[/^\s*$/] + lineno += 1 + } + end + + # Used for many PBS files + def pbCompilerEachCommentedLine(filename) + File.open(filename,"rb") { |f| + FileLineData.file = filename + lineno = 1 + f.each_line { |line| + if lineno==1 && line[0]==0xEF && line[1]==0xBB && line[2]==0xBF + line = line[3,line.length-3] + end + if !line[/^\#/] && !line[/^\s*$/] + FileLineData.setLine(line,lineno) + yield line, lineno + end + lineno += 1 + } + } + end + + # Unused + def pbEachPreppedLine(f) + lineno = 1 + f.each_line { |line| + if lineno==1 && line[0]==0xEF && line[1]==0xBB && line[2]==0xBF + line = line[3,line.length-3] + end + line = prepline(line) + yield line, lineno if !line[/^\#/] && !line[/^\s*$/] + lineno += 1 + } + end + + # Used for connections.txt, abilities.txt, moves.txt, trainertypes.txt + def pbCompilerEachPreppedLine(filename) + File.open(filename,"rb") { |f| + FileLineData.file = filename + lineno = 1 + f.each_line { |line| + if lineno==1 && line[0]==0xEF && line[1]==0xBB && line[2]==0xBF + line = line[3,line.length-3] + end + line = prepline(line) + if !line[/^\#/] && !line[/^\s*$/] + FileLineData.setLine(line,lineno) + yield line, lineno + end + lineno += 1 + } + } + end + + #============================================================================= + # Convert a string to certain kinds of values + #============================================================================= + def csvfield!(str) + ret = "" + str.sub!(/^\s*/,"") + if str[0,1]=="\"" + str[0,1] = "" + escaped = false + fieldbytes = 0 + str.scan(/./) do |s| + fieldbytes += s.length + break if s=="\"" && !escaped + if s=="\\" && !escaped + escaped = true + else + ret += s + escaped = false + end + end + str[0,fieldbytes] = "" + if !str[/^\s*,/] && !str[/^\s*$/] + raise _INTL("Invalid quoted field (in: {1})\r\n{2}",str,FileLineData.linereport) + end + str[0,str.length] = $~.post_match + else + if str[/,/] + str[0,str.length] = $~.post_match + ret = $~.pre_match + else + ret = str.clone + str[0,str.length] = "" + end + ret.gsub!(/\s+$/,"") + end + return ret + end + + def csvBoolean!(str,_line=-1) + field = csvfield!(str) + if field[/^1|[Tt][Rr][Uu][Ee]|[Yy][Ee][Ss]|[Yy]$/] + return true + elsif field[/^0|[Ff][Aa][Ll][Ss][Ee]|[Nn][Oo]|[Nn]$/] + return false + end + raise _INTL("Field {1} is not a Boolean value (true, false, 1, 0)\r\n{2}",field,FileLineData.linereport) + end + + def csvInt!(str,_line=-1) + ret = csvfield!(str) + if !ret[/^\-?\d+$/] + raise _INTL("Field {1} is not an integer\r\n{2}",ret,FileLineData.linereport) + end + return ret.to_i + end + + def csvPosInt!(str,_line=-1) + ret = csvfield!(str) + if !ret[/^\d+$/] + raise _INTL("Field {1} is not a positive integer\r\n{2}",ret,FileLineData.linereport) + end + return ret.to_i + end + + def csvFloat!(str,_line=-1) + ret = csvfield!(str) + return Float(ret) rescue raise _INTL("Field {1} is not a number\r\n{2}",ret,FileLineData.linereport) + end + + def csvEnumField!(value,enumer,_key,_section) + ret = csvfield!(value) + return checkEnumField(ret,enumer) + end + + def csvEnumFieldOrInt!(value,enumer,_key,_section) + ret = csvfield!(value) + return ret.to_i if ret[/\-?\d+/] + return checkEnumField(ret,enumer) + end + + def checkEnumField(ret,enumer) + if enumer.is_a?(Module) + begin + if ret=="" || !enumer.const_defined?(ret) + raise _INTL("Undefined value {1} in {2}\r\n{3}",ret,enumer.name,FileLineData.linereport) + end + rescue NameError + raise _INTL("Incorrect value {1} in {2}\r\n{3}",ret,enumer.name,FileLineData.linereport) + end + return enumer.const_get(ret.to_sym) + elsif enumer.is_a?(Symbol) || enumer.is_a?(String) + enumer = Object.const_get(enumer.to_sym) + begin + if ret=="" || !enumer.const_defined?(ret) + raise _INTL("Undefined value {1} in {2}\r\n{3}",ret,enumer.name,FileLineData.linereport) + end + rescue NameError + raise _INTL("Incorrect value {1} in {2}\r\n{3}",ret,enumer.name,FileLineData.linereport) + end + return enumer.const_get(ret.to_sym) + elsif enumer.is_a?(Array) + idx = findIndex(enumer) { |item| ret==item } + if idx<0 + raise _INTL("Undefined value {1} (expected one of: {2})\r\n{3}",ret,enumer.inspect,FileLineData.linereport) + end + return idx + elsif enumer.is_a?(Hash) + value = enumer[ret] + if value==nil + raise _INTL("Undefined value {1} (expected one of: {2})\r\n{3}",ret,enumer.keys.inspect,FileLineData.linereport) + end + return value + end + raise _INTL("Enumeration not defined\r\n{1}",FileLineData.linereport) + end + + def checkEnumFieldOrNil(ret,enumer) + if enumer.is_a?(Module) + return nil if ret=="" || !(enumer.const_defined?(ret) rescue false) + return enumer.const_get(ret.to_sym) + elsif enumer.is_a?(Symbol) || enumer.is_a?(String) + enumer = Object.const_get(enumer.to_sym) + return nil if ret=="" || !(enumer.const_defined?(ret) rescue false) + return enumer.const_get(ret.to_sym) + elsif enumer.is_a?(Array) + idx = findIndex(enumer) { |item| ret==item } + return nil if idx<0 + return idx + elsif enumer.is_a?(Hash) + return enumer[ret] + end + return nil + end + + #============================================================================= + # Convert a string to values using a schema + #============================================================================= + def pbGetCsvRecord(rec,lineno,schema) + record = [] + repeat = false + start = 0 + if schema[1][0,1]=="*" + repeat = true + start = 1 + end + begin + for i in start...schema[1].length + chr = schema[1][i,1] + case chr + when "i" # Integer + record.push(csvInt!(rec,lineno)) + when "I" # Optional integer + field = csvfield!(rec) + if field=="" + record.push(nil) + elsif !field[/^\-?\d+$/] + raise _INTL("Field {1} is not an integer\r\n{2}",field,FileLineData.linereport) + else + record.push(field.to_i) + end + when "u" # Positive integer or zero + record.push(csvPosInt!(rec,lineno)) + when "U" # Optional positive integer or zero + field = csvfield!(rec) + if field=="" + record.push(nil) + elsif !field[/^\d+$/] + raise _INTL("Field '{1}' must be 0 or greater\r\n{2}",field,FileLineData.linereport) + else + record.push(field.to_i) + end + when "v" # Positive integer + field = csvPosInt!(rec,lineno) + raise _INTL("Field '{1}' must be greater than 0\r\n{2}",field,FileLineData.linereport) if field==0 + record.push(field) + when "V" # Optional positive integer + field = csvfield!(rec) + if field=="" + record.push(nil) + elsif !field[/^\d+$/] + raise _INTL("Field '{1}' must be greater than 0\r\n{2}",field,FileLineData.linereport) + elsif field.to_i==0 + raise _INTL("Field '{1}' must be greater than 0\r\n{2}",field,FileLineData.linereport) + else + record.push(field.to_i) + end + when "x" # Hexadecimal number + field = csvfield!(rec) + if !field[/^[A-Fa-f0-9]+$/] + raise _INTL("Field '{1}' is not a hexadecimal number\r\n{2}",field,FileLineData.linereport) + end + record.push(field.hex) + when "X" # Optional hexadecimal number + field = csvfield!(rec) + if field=="" + record.push(nil) + elsif !field[/^[A-Fa-f0-9]+$/] + raise _INTL("Field '{1}' is not a hexadecimal number\r\n{2}",field,FileLineData.linereport) + else + record.push(field.hex) + end + when "f" # Floating point number + record.push(csvFloat!(rec,lineno)) + when "F" # Optional floating point number + field = csvfield!(rec) + if field=="" + record.push(nil) + elsif !field[/^\-?^\d*\.?\d*$/] + raise _INTL("Field {1} is not a floating point number\r\n{2}",field,FileLineData.linereport) + else + record.push(field.to_f) + end + when "b" # Boolean + record.push(csvBoolean!(rec,lineno)) + when "B" # Optional Boolean + field = csvfield!(rec) + if field=="" + record.push(nil) + elsif field[/^1|[Tt][Rr][Uu][Ee]|[Yy][Ee][Ss]|[Tt]|[Yy]$/] + record.push(true) + else + record.push(false) + end + when "n" # Name + field = csvfield!(rec) + if !field[/^(?![0-9])\w+$/] + raise _INTL("Field '{1}' must contain only letters, digits, and\r\nunderscores and can't begin with a number.\r\n{2}",field,FileLineData.linereport) + end + record.push(field) + when "N" # Optional name + field = csvfield!(rec) + if field=="" + record.push(nil) + elsif !field[/^(?![0-9])\w+$/] + raise _INTL("Field '{1}' must contain only letters, digits, and\r\nunderscores and can't begin with a number.\r\n{2}",field,FileLineData.linereport) + else + record.push(field) + end + when "s" # String + record.push(csvfield!(rec)) + when "S" # Optional string + field = csvfield!(rec) + record.push((field=="") ? nil : field) + when "q" # Unformatted text + record.push(rec) + rec = "" + when "Q" # Optional unformatted text + if !rec || rec=="" + record.push(nil) + else + record.push(rec) + rec = "" + end + when "e" # Enumerable + record.push(csvEnumField!(rec,schema[2+i-start],"",FileLineData.linereport)) + when "E" # Optional enumerable + field = csvfield!(rec) + record.push(checkEnumFieldOrNil(field,schema[2+i-start])) + when "y" # Enumerable or integer + field = csvfield!(rec) + record.push(csvEnumFieldOrInt!(field,schema[2+i-start],"",FileLineData.linereport)) + when "Y" # Optional enumerable or integer + field = csvfield!(rec) + if field=="" + record.push(nil) + elsif field[/^\-?\d+$/] + record.push(field.to_i) + else + record.push(checkEnumFieldOrNil(field,schema[2+i-start])) + end + end + end + break if repeat && rec=="" + end while repeat + return (schema[1].length==1) ? record[0] : record + end + + #============================================================================= + # Check whether a number fits in a given numerical range (all unused) + #============================================================================= + def pbCheckByte(x,valuename) + if x<0 || x>255 + raise _INTL("The value \"{1}\" must be from 0 through 255 (00-FF in hex), got a value of {2}\r\n{3}", + valuename,x,FileLineData.linereport) + end + end + + def pbCheckSignedByte(x,valuename) + if x<-128 || x>127 + raise _INTL("The value \"{1}\" must be from -128 through 127, got a value of {2}\r\n{3}", + valuename,x,FileLineData.linereport) + end + end + + def pbCheckWord(x,valuename) + if x<0 || x>65535 + raise _INTL("The value \"{1}\" must be from 0 through 65535 (0000-FFFF in hex), got a value of {2}\r\n{3}", + valuename,x,FileLineData.linereport) + end + end + + def pbCheckSignedWord(x,valuename) + if x<-32768 || x>32767 + raise _INTL("The value \"{1}\" must be from -32768 through 32767, got a value of {2}\r\n{3}", + valuename,x,FileLineData.linereport) + end + end + + #============================================================================= + # Parse string into a likely constant name and return its ID number (if any). + # Last ditch attempt to figure out whether a constant is defined. + #============================================================================= + def pbGetConst(mod,item,err) + isDef = false + begin + mod = Object.const_get(mod) if mod.is_a?(Symbol) + isDef = mod.const_defined?(item.to_sym) + rescue + raise sprintf(err,item) + end + raise sprintf(err,item) if !isDef + return mod.const_get(item.to_sym) + end + + def parseItem(item) + clonitem = item.upcase + clonitem.sub!(/^\s*/,"") + clonitem.sub!(/\s*$/,"") + return pbGetConst(PBItems,clonitem,_INTL("Undefined item constant name: %s\r\nName must consist only of letters, numbers, and\r\nunderscores and can't begin with a number.\r\nMake sure the item is defined in\r\nPBS/items.txt.\r\n{1}",FileLineData.linereport)) + end + + def parseSpecies(item) + clonitem = item.upcase + clonitem.gsub!(/^[\s\n]*/,"") + clonitem.gsub!(/[\s\n]*$/,"") + clonitem = "NIDORANmA" if clonitem=="NIDORANMA" + clonitem = "NIDORANfE" if clonitem=="NIDORANFE" + return pbGetConst(PBSpecies,clonitem,_INTL("Undefined species constant name: [%s]\r\nName must consist only of letters, numbers, and\r\nunderscores and can't begin with a number.\r\nMake sure the name is defined in\r\nPBS/pokemon.txt.\r\n{1}",FileLineData.linereport)) + end + + def parseMove(item) + clonitem = item.upcase + clonitem.sub!(/^\s*/,"") + clonitem.sub!(/\s*$/,"") + return pbGetConst(PBMoves,clonitem,_INTL("Undefined move constant name: %s\r\nName must consist only of letters, numbers, and\r\nunderscores and can't begin with a number.\r\nMake sure the name is defined in\r\nPBS/moves.txt.\r\n{1}",FileLineData.linereport)) + end + + # Unused + def parseNature(item) + clonitem = item.upcase + clonitem.sub!(/^\s*/,"") + clonitem.sub!(/\s*$/,"") + return pbGetConst(PBNatures,clonitem,_INTL("Undefined nature constant name: %s\r\nName must consist only of letters, numbers, and\r\nunderscores and can't begin with a number.\r\nMake sure the name is defined in\r\nthe script section PBNatures.\r\n{1}",FileLineData.linereport)) + end + + # Unused + def parseTrainer(item) + clonitem = item.clone + clonitem.sub!(/^\s*/,"") + clonitem.sub!(/\s*$/,"") + return pbGetConst(PBTrainers,clonitem,_INTL("Undefined Trainer constant name: %s\r\nName must consist only of letters, numbers, and\r\nunderscores and can't begin with a number.\r\nIn addition, the name must be defined\r\nin trainertypes.txt.\r\n{1}",FileLineData.linereport)) + end + + #============================================================================= + # Compile all data + #============================================================================= + def compile_all(mustCompile) + FileLineData.clear + if mustCompile + if (!$INEDITOR || LANGUAGES.length<2) && pbRgssExists?("Data/messages.dat") + MessageTypes.loadMessageFile("Data/messages.dat") + end + yield(_INTL("Compiling type data")) + compile_types # No dependencies + yield(_INTL("Compiling town map data")) + compile_town_map # No dependencies + yield(_INTL("Compiling map connection data")) + compile_connections # No dependencies + yield(_INTL("Compiling ability data")) + compile_abilities # No dependencies + yield(_INTL("Compiling move data")) + compile_moves # Depends on PBTypes + yield(_INTL("Compiling item data")) + compile_items # Depends on PBMoves + yield(_INTL("Compiling berry plant data")) + compile_berry_plants # Depends on PBItems + yield(_INTL("Compiling Pokémon data")) + compile_pokemon # Depends on PBMoves, PBItems, PBTypes, PBAbilities + yield(_INTL("Compiling Pokémon forms data")) + compile_pokemon_forms # Depends on PBSpecies, PBMoves, PBItems, PBTypes, PBAbilities + yield(_INTL("Compiling machine data")) + compile_move_compatibilities # Depends on PBSpecies, PBMoves + yield(_INTL("Compiling Trainer type data")) + compile_trainer_types # No dependencies + yield(_INTL("Compiling Trainer data")) + compile_trainers # Depends on PBSpecies, PBItems, PBMoves + yield(_INTL("Compiling phone data")) + compile_phone # Depends on PBTrainers + yield(_INTL("Compiling metadata")) + compile_metadata # Depends on PBTrainers + yield(_INTL("Compiling battle Trainer data")) + compile_trainer_lists # Depends on PBTrainers + yield(_INTL("Compiling encounter data")) + compile_encounters # Depends on PBSpecies + yield(_INTL("Compiling shadow moveset data")) + compile_shadow_movesets # Depends on PBSpecies, PBMoves + yield(_INTL("Compiling animations")) + compile_animations + yield(_INTL("Converting events")) + compile_trainer_events(mustCompile) + yield(_INTL("Saving messages")) + pbSetTextMessages + MessageTypes.saveMessages + else + if (!$INEDITOR || LANGUAGES.length<2) && safeExists?("Data/messages.dat") + MessageTypes.loadMessageFile("Data/messages.dat") + end + end + if !$INEDITOR && LANGUAGES.length>=2 + pbLoadMessages("Data/"+LANGUAGES[$PokemonSystem.language][1]) + end + end + + def main + return if !$DEBUG + begin + dataFiles = [ + "berry_plants.dat", + "encounters.dat", + "form2species.dat", + "items.dat", + "map_connections.dat", + "metadata.dat", + "moves.dat", + "phone.dat", + "regional_dexes.dat", + "shadow_movesets.dat", + "species.dat", + "species_eggmoves.dat", + "species_evolutions.dat", + "species_metrics.dat", + "species_movesets.dat", + "tm.dat", + "town_map.dat", + "trainer_lists.dat", + "trainer_types.dat", + "trainers.dat", + "types.dat", + "Constants.rxdata" + ] + textFiles = [ + "abilities.txt", + "berryplants.txt", + "connections.txt", + "encounters.txt", + "items.txt", + "metadata.txt", + "moves.txt", + "phone.txt", + "pokemon.txt", + "pokemonforms.txt", + "shadowmoves.txt", + "tm.txt", + "townmap.txt", + "trainerlists.txt", + "trainers.txt", + "trainertypes.txt", + "types.txt" + ] + latestDataTime = 0 + latestTextTime = 0 + mustCompile = false + # Should recompile if new maps were imported + mustCompile |= import_new_maps + # Should recompile if no existing data is found + mustCompile |= !(PBSpecies.respond_to?("maxValue") rescue false) + # If no PBS file, create one and fill it, then recompile + if !safeIsDirectory?("PBS") + Dir.mkdir("PBS") rescue nil + pbSaveAllData + mustCompile = true + end + # Check data files and PBS files, and recompile if any PBS file was edited + # more recently than the data files were last created + for i in 0...dataFiles.length + begin + File.open("Data/#{dataFiles[i]}") { |file| + latestDataTime = [latestDataTime,file.mtime.to_i].max + } + rescue SystemCallError + mustCompile = true + end + end + for i in 0...textFiles.length + begin + File.open("PBS/#{textFiles[i]}") { |file| + latestTextTime = [latestTextTime,file.mtime.to_i].max + } + rescue SystemCallError + end + end + mustCompile |= (latestTextTime>=latestDataTime) + # Should recompile if holding Ctrl + Input.update + mustCompile = true if Input.press?(Input::CTRL) + # Delete old data files in preparation for recompiling + if mustCompile + for i in 0...dataFiles.length + begin + File.delete("Data/#{dataFiles[i]}") + rescue SystemCallError + end + end + end + # Recompile all data + compile_all(mustCompile) { |msg| Win32API.SetWindowText(msg) } + rescue Exception + e = $! + raise e if "#{e.class}"=="Reset" || e.is_a?(Reset) || e.is_a?(SystemExit) + pbPrintException(e) + for i in 0...dataFiles.length + begin + File.delete("Data/#{dataFiles[i]}") + rescue SystemCallError + end + end + raise Reset.new if e.is_a?(Hangup) + loop do + Graphics.update + end + end + end +end diff --git a/Data/Scripts/022_Compiler/002_Compiler_PBS.rb b/Data/Scripts/022_Compiler/002_Compiler_PBS.rb deleted file mode 100644 index 1e80c6aca..000000000 --- a/Data/Scripts/022_Compiler/002_Compiler_PBS.rb +++ /dev/null @@ -1,1694 +0,0 @@ -#=============================================================================== -# Compile metadata -#=============================================================================== -class PBTrainers; end - - - -def pbCompileMetadata - sections = [] - currentmap = -1 - pbCompilerEachCommentedLine("PBS/metadata.txt") { |line,lineno| - if line[/^\s*\[\s*(\d+)\s*\]\s*$/] - sectionname = $~[1] - if currentmap==0 - if sections[currentmap][Metadata::HOME]==nil - raise _INTL("The entry Home is required in metadata.txt section [{1}]",sectionname) - end - if sections[currentmap][Metadata::PLAYER_A]==nil - raise _INTL("The entry PlayerA is required in metadata.txt section [{1}]",sectionname) - end - end - currentmap = sectionname.to_i - sections[currentmap] = [] - else - if currentmap<0 - raise _INTL("Expected a section at the beginning of the file\r\n{1}",FileLineData.linereport) - end - if !line[/^\s*(\w+)\s*=\s*(.*)$/] - raise _INTL("Bad line syntax (expected syntax like XXX=YYY)\r\n{1}",FileLineData.linereport) - end - matchData = $~ - schema = nil - FileLineData.setSection(currentmap,matchData[1],matchData[2]) - if currentmap==0 - schema = Metadata::SCHEMA[matchData[1]] - else - schema = MapMetadata::SCHEMA[matchData[1]] - end - if schema - record = pbGetCsvRecord(matchData[2],lineno,schema) - sections[currentmap][schema[0]] = record - end - end - } - save_data(sections,"Data/metadata.dat") -end - -#=============================================================================== -# Compile town map points -#=============================================================================== -def pbCompileTownMap - nonglobaltypes = { - "Name" => [0, "s"], - "Filename" => [1, "s"], - "Point" => [2, "uussUUUU"] - } - currentmap = -1 - rgnnames = [] - placenames = [] - placedescs = [] - sections = [] - pbCompilerEachCommentedLine("PBS/townmap.txt") { |line,lineno| - if line[/^\s*\[\s*(\d+)\s*\]\s*$/] - currentmap = $~[1].to_i - sections[currentmap] = [] - else - if currentmap<0 - raise _INTL("Expected a section at the beginning of the file\r\n{1}",FileLineData.linereport) - end - if !line[/^\s*(\w+)\s*=\s*(.*)$/] - raise _INTL("Bad line syntax (expected syntax like XXX=YYY)\r\n{1}",FileLineData.linereport) - end - settingname = $~[1] - schema = nonglobaltypes[settingname] - if schema - record = pbGetCsvRecord($~[2],lineno,schema) - if settingname=="Name" - rgnnames[currentmap] = record - elsif settingname=="Point" - placenames.push(record[2]) - placedescs.push(record[3]) - sections[currentmap][schema[0]] = [] if !sections[currentmap][schema[0]] - sections[currentmap][schema[0]].push(record) - else # Filename - sections[currentmap][schema[0]] = record - end - end - end - } - save_data(sections,"Data/town_map.dat") - MessageTypes.setMessages(MessageTypes::RegionNames,rgnnames) - MessageTypes.setMessagesAsHash(MessageTypes::PlaceNames,placenames) - MessageTypes.setMessagesAsHash(MessageTypes::PlaceDescriptions,placedescs) -end - -#=============================================================================== -# Compile map connections -#=============================================================================== -def pbCompileConnections - records = [] - pbCompilerEachPreppedLine("PBS/connections.txt") { |line,lineno| - hashenum = { - "N" => "N","North" => "N", - "E" => "E","East" => "E", - "S" => "S","South" => "S", - "W" => "W","West" => "W" - } - record = [] - thisline = line.dup - record.push(csvInt!(thisline,lineno)) - record.push(csvEnumFieldOrInt!(thisline,hashenum,"",sprintf("(line %d)",lineno))) - record.push(csvInt!(thisline,lineno)) - record.push(csvInt!(thisline,lineno)) - record.push(csvEnumFieldOrInt!(thisline,hashenum,"",sprintf("(line %d)",lineno))) - record.push(csvInt!(thisline,lineno)) - if !pbRgssExists?(sprintf("Data/Map%03d.rxdata",record[0])) && - !pbRgssExists?(sprintf("Data/Map%03d.rvdata",record[0])) - print _INTL("Warning: Map {1}, as mentioned in the map connection data, was not found.\r\n{2}",record[0],FileLineData.linereport) - end - if !pbRgssExists?(sprintf("Data/Map%03d.rxdata",record[3])) && - !pbRgssExists?(sprintf("Data/Map%03d.rvdata",record[3])) - print _INTL("Warning: Map {1}, as mentioned in the map connection data, was not found.\r\n{2}",record[3],FileLineData.linereport) - end - case record[1] - when "N"; raise _INTL("North side of first map must connect with south side of second map\r\n{1}",FileLineData.linereport) if record[4]!="S" - when "S"; raise _INTL("South side of first map must connect with north side of second map\r\n{1}",FileLineData.linereport) if record[4]!="N" - when "E"; raise _INTL("East side of first map must connect with west side of second map\r\n{1}",FileLineData.linereport) if record[4]!="W" - when "W"; raise _INTL("West side of first map must connect with east side of second map\r\n{1}",FileLineData.linereport) if record[4]!="E" - end - records.push(record) - } - save_data(records,"Data/map_connections.dat") - Graphics.update -end - -#=============================================================================== -# Compile berry plants -#=============================================================================== -def pbCompileBerryPlants - sections = [] - if File.exists?("PBS/berryplants.txt") - pbCompilerEachCommentedLine("PBS/berryplants.txt") { |line,_lineno| - if line[ /^\s*(\w+)\s*=\s*(.*)$/ ] - key = $1 - value = $2 - value = value.split(",") - for i in 0...value.length - value[i].sub!(/^\s*/,"") - value[i].sub!(/\s*$/,"") - value[i] = value[i].to_i - end - item = parseItem(key) - sections[item] = value - end - } - end - save_data(sections,"Data/berry_plants.dat") -end - -#=============================================================================== -# Compile phone messages -#=============================================================================== -def pbCompilePhoneData - return if !safeExists?("PBS/phone.txt") - database = PhoneDatabase.new - sections = [] - File.open("PBS/phone.txt","rb") { |f| - pbEachSection(f) { |section,name| - case name - when "" - database.generics=section - sections.concat(section) - when "" - database.battleRequests=section - sections.concat(section) - when "" - database.greetingsMorning=section - sections.concat(section) - when "" - database.greetingsEvening=section - sections.concat(section) - when "" - database.greetings=section - sections.concat(section) - when "" - database.bodies1=section - sections.concat(section) - when "" - database.bodies2=section - sections.concat(section) - end - } - } - MessageTypes.setMessagesAsHash(MessageTypes::PhoneMessages,sections) - save_data(database,"Data/phone.dat") -end - -#=============================================================================== -# Compile types -#=============================================================================== -def pbWriteDefaultTypes - if !safeExists?("PBS/types.txt") - File.open("PBS/types.txt","w") { |f| - f.write(0xEF.chr) - f.write(0xBB.chr) - f.write(0xBF.chr) -fx=< [1, "s"], - "InternalName" => [2, "s"], - } - optionaltypes = { - "IsPseudoType" => [3, "b"], - "IsSpecialType" => [4, "b"], - "Weaknesses" => [5, "*s"], - "Resistances" => [6, "*s"], - "Immunities" => [7, "*s"] - } - currentmap = -1 - foundtypes = [] - pbCompilerEachCommentedLine("PBS/types.txt") { |line,lineno| - if line[/^\s*\[\s*(\d+)\s*\]\s*$/] - sectionname = $~[1] - if currentmap>=0 - for reqtype in requiredtypes.keys - if !foundtypes.include?(reqtype) - raise _INTL("Required value '{1}' not given in section '{2}'\r\n{3}",reqtype,currentmap,FileLineData.linereport) - end - end - foundtypes.clear - end - currentmap = sectionname.to_i - types[currentmap] = [currentmap,nil,nil,false,false,[],[],[]] - else - if currentmap<0 - raise _INTL("Expected a section at the beginning of the file\r\n{1}",FileLineData.linereport) - end - if !line[/^\s*(\w+)\s*=\s*(.*)$/] - raise _INTL("Bad line syntax (expected syntax like XXX=YYY)\r\n{1}",FileLineData.linereport) - end - matchData = $~ - schema = nil - FileLineData.setSection(currentmap,matchData[1],matchData[2]) - if requiredtypes.keys.include?(matchData[1]) - schema = requiredtypes[matchData[1]] - foundtypes.push(matchData[1]) - else - schema = optionaltypes[matchData[1]] - end - if schema - record = pbGetCsvRecord(matchData[2],lineno,schema) - types[currentmap][schema[0]] = record - end - end - } - types.compact! - maxValue = 0 - for type in types; maxValue = [maxValue,type[0]].max; end - pseudotypes = [] - specialtypes = [] - typenames = [] - typeinames = [] - typehash = {} - for type in types - pseudotypes.push(type[0]) if type[3] - typenames[type[0]] = type[1] - typeinames[type[0]] = type[2] - typehash[type[0]] = type - end - for type in types - n = type[1] - for w in type[5] - if !typeinames.include?(w) - raise _INTL("'{1}' is not a defined type (PBS/types.txt, {2}, Weaknesses)",w,n) - end - end - for w in type[6] - if !typeinames.include?(w) - raise _INTL("'{1}' is not a defined type (PBS/types.txt, {2}, Resistances)",w,n) - end - end - for w in type[7] - if !typeinames.include?(w) - raise _INTL("'{1}' is not a defined type (PBS/types.txt, {2}, Immunities)",w,n) - end - end - end - for i in 0..maxValue - pseudotypes.push(i) if !typehash[i] - end - pseudotypes.sort! - types.each { |type| specialtypes.push(type[0]) if type[4] } - specialtypes.sort! - count = maxValue+1 - for i in 0...count - type = typehash[i] - j = 0; k = i - while j>3 - curpos = 0 - numrec.times do - file.pos = curpos - offset = file.fgetdw - length = file.fgetdw - record = SerialRecord.decode(file,offset,length) - ret[record[0]] = record - curpos += 8 - end - } - return ret -end - -def pbCompileItems - records = [] - constants = "" - itemnames = [] - itempluralnames = [] - itemdescs = [] - maxValue = 0 - pbCompilerEachCommentedLine("PBS/items.txt") { |line,lineno| - linerecord = pbGetCsvRecord(line,lineno,[0,"vnssuusuuUN"]) - id = linerecord[0] - record = [] - record[ItemData::ID] = id - constant = linerecord[1] - constants += "#{constant}=#{id}\r\n" - record[ItemData::NAME] = linerecord[2] - itemnames[id] = linerecord[2] - record[ItemData::NAME_PLURAL] = linerecord[3] - itempluralnames[id] = linerecord[3] - record[ItemData::POCKET] = linerecord[4] - record[ItemData::PRICE] = linerecord[5] - record[ItemData::DESCRIPTION] = linerecord[6] - itemdescs[id] = linerecord[6] - record[ItemData::FIELD_USE] = linerecord[7] - record[ItemData::BATTLE_USE] = linerecord[8] - record[ItemData::TYPE] = linerecord[9] - if record[ItemData::TYPE]!="" && linerecord[10] - record[ItemData::MOVE] = parseMove(linerecord[10]) - else - record[ItemData::MOVE] = 0 - end - maxValue = [maxValue,id].max - records[id] = record - } - MessageTypes.setMessages(MessageTypes::Items,itemnames) - MessageTypes.setMessages(MessageTypes::ItemPlurals,itempluralnames) - MessageTypes.setMessages(MessageTypes::ItemDescriptions,itemdescs) - save_data(records,"Data/items.dat") - code = "class PBItems\r\n" - code += constants - code += "def self.getName(id)\r\n" - code += "id=getID(PBItems,id)\r\n" - code += "return pbGetMessage(MessageTypes::Items,id); end\r\n" - code += "def self.getNamePlural(id)\r\n" - code += "id=getID(PBItems,id)\r\n" - code += "return pbGetMessage(MessageTypes::ItemPlurals,id); end\r\n" - code += "def self.getCount; return #{records.length}; end\r\n" - code += "def self.maxValue; return #{maxValue}; end\r\n" - code += "end\r\n" - eval(code) - pbAddScript(code,"PBItems") - Graphics.update -end - -#=============================================================================== -# Compile move data -#=============================================================================== -def pbCompileMoves - records = [] - moveNames = [] - moveDescs = [] - maxValue = 0 - count = 0 - pbCompilerEachPreppedLine("PBS/moves.txt") { |line,lineno| - record = [] - lineRecord = pbGetCsvRecord(line,lineno,[0,"vnssueeuuuyiss", - nil,nil,nil,nil,nil,PBTypes,["Physical","Special","Status"], - nil,nil,nil,PBTargets,nil,nil,nil - ]) - if lineRecord[6]==2 && lineRecord[4]!=0 - raise _INTL("Status moves must have a base damage of 0, use either Physical or Special\r\n{1}",FileLineData.linereport) - end - if lineRecord[6]!=2 && lineRecord[4]==0 - print _INTL("Warning: Physical and special moves can't have a base damage of 0, changing to a Status move\r\n{1}",FileLineData.linereport) - lineRecord[6] = 2 - end - record[MoveData::ID] = lineRecord[0] - record[MoveData::INTERNAL_NAME] = lineRecord[1] - record[MoveData::NAME] = lineRecord[2] - record[MoveData::FUNCTION_CODE] = lineRecord[3] - record[MoveData::BASE_DAMAGE] = lineRecord[4] - record[MoveData::TYPE] = lineRecord[5] - record[MoveData::CATEGORY] = lineRecord[6] - record[MoveData::ACCURACY] = lineRecord[7] - record[MoveData::TOTAL_PP] = lineRecord[8] - record[MoveData::EFFECT_CHANCE] = lineRecord[9] - record[MoveData::TARGET] = lineRecord[10] - record[MoveData::PRIORITY] = lineRecord[11] - record[MoveData::FLAGS] = lineRecord[12] - record[MoveData::DESCRIPTION] = lineRecord[13] - maxValue = [maxValue,lineRecord[0]].max - count += 1 - moveNames[lineRecord[0]] = lineRecord[2] # Name - moveDescs[lineRecord[0]] = lineRecord[13] # Description - records[lineRecord[0]] = record - } - save_data(records,"Data/moves.dat") - MessageTypes.setMessages(MessageTypes::Moves,moveNames) - MessageTypes.setMessages(MessageTypes::MoveDescriptions,moveDescs) - code = "class PBMoves\r\n" - for rec in records - code += "#{rec[MoveData::INTERNAL_NAME]}=#{rec[MoveData::ID]}\r\n" if rec - end - code += "def self.getName(id)\r\n" - code += "id=getID(PBMoves,id)\r\n" - code += "return pbGetMessage(MessageTypes::Moves,id); end\r\n" - code += "def self.getCount; return #{count}; end\r\n" - code += "def self.maxValue; return #{maxValue}; end\r\n" - code += "end\r\n" - eval(code) - pbAddScript(code,"PBMoves") -end - -#=============================================================================== -# Compile battle animations -#=============================================================================== -def pbCompileAnimations - begin - if $RPGVX - pbanims = load_data("Data/PkmnAnimations.rvdata") - else - pbanims = load_data("Data/PkmnAnimations.rxdata") - end - rescue - pbanims = PBAnimations.new - end - move2anim = [[],[]] -=begin - if $RPGVX - anims = load_data("Data/Animations.rvdata") - else - anims = load_data("Data/Animations.rxdata") - end - for anim in anims - next if !anim || anim.frames.length==1 - found = false - for i in 0...pbanims.length - if pbanims[i] && pbanims[i].id==anim.id - found = true if pbanims[i].array.length>1 - break - end - end - pbanims[anim.id] = pbConvertRPGAnimation(anim) if !found - end -=end - for i in 0...pbanims.length - next if !pbanims[i] - if pbanims[i].name[/^OppMove\:\s*(.*)$/] - if hasConst?(PBMoves,$~[1]) - moveid = PBMoves.const_get($~[1]) - move2anim[1][moveid] = i - end - elsif pbanims[i].name[/^Move\:\s*(.*)$/] - if hasConst?(PBMoves,$~[1]) - moveid = PBMoves.const_get($~[1]) - move2anim[0][moveid] = i - end - end - end - save_data(move2anim,"Data/move2anim.dat") - save_data(pbanims,"Data/PkmnAnimations.rxdata") -end - -#=============================================================================== -# Compile Pokémon -#=============================================================================== -def pbCompilePokemonData - # Get schemas. - requiredValues = SpeciesData.requiredValues - optionalValues = SpeciesData.optionalValues - # Prepare arrays for compiled data. - speciesData = [] - movesets = [] - eggMoves = [] - regionalDexes = [] - spriteMetrics = [] - evolutions = [] - speciesNames = [] - formNames = [] - pokedexKinds = [] - pokedexEntries = [] - # Prepare variables used to record scripted constants. - constants = "" - maxValue = 0 # Highest species ID - # Read from PBS file. - File.open("PBS/pokemon.txt","rb") { |f| - FileLineData.file = "PBS/pokemon.txt" # For error reporting - # Read a whole section's lines at once, then run through this code. - # contents is a hash containing all the XXX=YYY lines in that section, where - # the keys are the XXX and the values are the YYY (as unprocessed strings). - pbEachFileSection(f) { |contents,speciesID| - # Create array to store compiled data in. - speciesData[speciesID] = [] - # Copy Type1 into Type2 if Type2 is undefined. (All species must have two - # defined types; if both are the same, it is treated as single typed.) - if !contents["Type2"] || contents["Type2"]=="" - if !contents["Type1"] || contents["Type1"]=="" - raise _INTL("No Pokémon type is defined in section {1} (PBS/pokemon.txt)",speciesID.to_s) - end - contents["Type2"] = contents["Type1"].clone - end - # Go through hashes of compilable data and compile this section. - [requiredValues,optionalValues].each do |hash| - for key in hash.keys - FileLineData.setSection(speciesID,key,contents[key]) # For error reporting - maxValue = [maxValue,speciesID].max # Set highest species ID - next if hash[key][0]<0 # Property is not to be compiled; skip it - # Raise an error if the species ID is 0. - if speciesID==0 - raise _INTL("A Pokémon species can't be numbered 0 (PBS/pokemon.txt)") - end - # Skip empty optional properties, or raise an error if a required - # property is empty. - if !contents[key] || contents[key]=="" - raise _INTL("Required entry {1} is missing or empty in section {2} (PBS/pokemon.txt)", - key,speciesID.to_s) if hash==requiredValues - next - end - # Compile value for key. - schema = hash[key] - value = pbGetCsvRecord(contents[key],key,schema) - # Modify value as required. - case key - when "Height", "Weight" - # Convert height/weight to 1 decimal place and multiply by 10. - value = (value*10).round - if value<=0 - raise _INTL("Value for '{1}' can't be less than or close to 0 (section {2}, PBS/pokemon.txt)",key,speciesID) - end - end - # Add value to appropriate array for saving. - case key - when "Moves" - speciesMoves = [] - for i in 0...value.length/2 - speciesMoves.push([value[i*2],value[i*2+1],i]) - end - speciesMoves.sort! { |a,b| (a[0]==b[0]) ? a[2]<=>b[2] : a[0]<=>b[0] } - for i in speciesMoves; i.pop; end - movesets[speciesID] = speciesMoves - when "EggMoves" - if value.is_a?(Array); eggMoves[speciesID] = value - else; eggMoves[speciesID] = [value] - end - when "RegionalNumbers" - if value.is_a?(Array) - value.each_with_index do |num,dexID| - regionalDexes[dexID] = [] if !regionalDexes[dexID] - regionalDexes[dexID][speciesID] = num - end - else - regionalDexes[0] = [] if !regionalDexes[0] - regionalDexes[0][speciesID] = value - end - when "BattlerPlayerX", "BattlerPlayerY", - "BattlerEnemyX", "BattlerEnemyY", - "BattlerAltitude", "BattlerShadowX", "BattlerShadowSize" - spriteMetrics[schema[0]] = [] if !spriteMetrics[schema[0]] - spriteMetrics[schema[0]][speciesID] = value - when "Evolutions" - speciesEvolutions = [] - for i in 0...value.length/3 - speciesEvolutions.push([value[i*3],value[i*3+1],value[i*3+2],false]) - end - evolutions[speciesID] = speciesEvolutions - when "Name" - speciesNames[speciesID] = value - when "FormName" - formNames[speciesID] = value - when "Kind" - pokedexKinds[speciesID] = value - when "Pokedex" - pokedexEntries[speciesID] = value - when "InternalName" - constants += "#{value}=#{speciesID}\r\n" - else # All other data - speciesData[speciesID][schema[0]] = value - end - end - end - } - } - # All data is compiled now, just need to save it. - raise _INTL("No Pokémon species are defined (PBS/pokemon.txt)") if speciesData.length==0 - # Write all constants and some helpful code for PBSpecies. - count = speciesData.compact.length - code = "module PBSpecies\r\n#{constants}" - code += "def PBSpecies.getName(id)\r\n" - code += "id=getID(PBSpecies,id)\r\n" - code += "return pbGetMessage(MessageTypes::Species,id); end\r\n" - code += "def PBSpecies.getCount; return #{count}; end\r\n" - code += "def PBSpecies.maxValue; return #{maxValue}; end\r\n" - code += "end\r\n" - eval(code) - pbAddScript(code,"PBSpecies") - # Save main species data. - save_data(speciesData,"Data/species.dat") - # Save movesets data. - save_data(movesets,"Data/species_movesets.dat") - # Save egg moves data. - save_data(eggMoves,"Data/species_eggmoves.dat") - # Save regional dexes data. - save_data(regionalDexes,"Data/regional_dexes.dat") - # Save metrics data. - for i in 0...7 - defaultValue = (i==SpeciesData::METRIC_SHADOW_SIZE) ? 2 : 0 # Shadow size 2, other metrics 0 - for j in 0..maxValue - spriteMetrics[i] = [] if !spriteMetrics[i] - spriteMetrics[i][j] ||= defaultValue - end - end - save_data(spriteMetrics,"Data/species_metrics.dat") - # Evaluate evolution data (has to be done after all species are read). - for e in 0...evolutions.length - next if !evolutions[e] - evolutions[e].each_with_index do |evo,i| - FileLineData.setSection(i,"Evolutions","") - evo[0] = csvEnumField!(evo[0],PBSpecies,"Evolutions",i) # Species - param_type = PBEvolution.getFunction(evo[1], "parameterType") - if param_type - evo[2] = csvEnumField!(evo[2], param_type, "Evolutions", i) - else - evo[2] = csvInt!(evo[2]) if evo[2] && evo[2] != "" - end - end - end - # Add prevolution data to all species as the first "evolution method". - for sp in 1..maxValue - preSpecies = -1 - evoData = nil - # Check for another species that evolves into sp. - for f in 0...evolutions.length - next if !evolutions[f] || f==sp - evolutions[f].each do |evo| - next if evo[0]!=sp || evo[3] # Evolved species isn't sp or is a prevolution - preSpecies = f # f evolves into sp - evoData = evo - break - end - break if evoData - end - next if !evoData # evoData[1]=method, evoData[2]=level - both are unused - # Found a species that evolves into e, record it as a prevolution. - evolutions[sp] = [] if !evolutions[sp] - evolutions[sp] = [[preSpecies,evoData[1],evoData[2],true]].concat(evolutions[sp]) - end - # Save evolutions data. - save_data(evolutions,"Data/species_evolutions.dat") - # Save all messages. - speciesNames.map! { |name| name || "????????" } - MessageTypes.setMessages(MessageTypes::Species,speciesNames) - MessageTypes.setMessages(MessageTypes::FormNames,formNames) - MessageTypes.setMessages(MessageTypes::Kinds,pokedexKinds) - MessageTypes.setMessages(MessageTypes::Entries,pokedexEntries) -end - -#=============================================================================== -# Compile Pokémon forms -#=============================================================================== -def pbCompilePokemonForms - # Get schemas. - requiredValues = SpeciesData.requiredValues(true) - optionalValues = SpeciesData.optionalValues(true) - # Prepare arrays for compiled data. - speciesData = pbLoadSpeciesData - movesets = [] - eggMoves = [] - spriteMetrics = [] - evolutions = [] - formNames = [] - pokedexKinds = [] - pokedexEntries = [] - formToSpecies = [] # Saved - speciesToForm = [] # Only used in this method - for i in 1..PBSpecies.maxValue - formToSpecies[i] = [i] - speciesToForm[i] = i - end - # Prepare variables used to record scripted constants. - constants = "" - maxValue = PBSpecies.maxValue # Highest species ID - # Read from PBS file. - File.open("PBS/pokemonforms.txt","rb") { |f| - FileLineData.file = "PBS/pokemonforms.txt" # For error reporting - # Read a whole section's lines at once, then run through this code. - # contents is a hash containing all the XXX=YYY lines in that section, where - # the keys are the XXX and the values are the YYY (as unprocessed strings). - pbEachFileSection2(f) { |contents,sectionName| - # Split sectionName into a species number and form number. - splitSectionName = sectionName.split(/[-,\s]/) - if splitSectionName.length!=2 - raise _INTL("Section name {1} is invalid (PBS/pokemonforms.txt). Expected syntax like [XXX,Y] (XXX=internal name, Y=form number).",sectionName) - end - baseSpeciesID = parseSpecies(splitSectionName[0]) - form = csvInt!(splitSectionName[1]) - # Ensure this is a valid form and not a duplicate. - if form==0 - raise _INTL("Form {1} is invalid (PBS/pokemonforms.txt). Form 0 data should be defined in \"PBS/pokemon.txt\".",sectionName) - end - if formToSpecies[baseSpeciesID] && formToSpecies[baseSpeciesID][form] - raise _INTL("Form {1} is defined at least twice (PBS/pokemonforms.txt). It should only be defined once.",sectionName) - end - # Record new species number in formToSpecies. - speciesID = baseSpeciesID - if form>0 - maxValue += 1 - speciesID = maxValue - formToSpecies[baseSpeciesID] = [] if !formToSpecies[baseSpeciesID] - formToSpecies[baseSpeciesID][form] = speciesID - speciesToForm[speciesID] = baseSpeciesID - end - # Generate internal name for this form. - cName = getConstantName(PBSpecies,baseSpeciesID).to_s+"_"+form.to_s - constants += "#{cName}=#{speciesID}\r\n" - # Create array to store compiled data in. - speciesData[speciesID] = [] - # Clone data from base form as a starting point. - speciesData[baseSpeciesID].each_with_index do |val,i| - speciesData[speciesID][i] = (val.is_a?(Array)) ? val.clone : val - end - # Copy Type1 into Type2 if if Type1 is defined but Type2 isn't. (Shouldn't - # inherit either of the base form's types if Type1 is defined for a form.) - if contents["Type1"] && contents["Type1"]!="" - if !contents["Type2"] || contents["Type2"]=="" - contents["Type2"] = contents["Type1"].clone - end - end - # If any held item is defined for this form, clear default data for all - # three held items. - if (contents["WildItemCommon"] && contents["WildItemCommon"]!="") || - (contents["WildItemUncommon"] && contents["WildItemUncommon"]!="") || - (contents["WildItemRare"] && contents["WildItemRare"]!="") - speciesData[speciesID][SpeciesData::WILD_ITEM_COMMON] = nil - speciesData[speciesID][SpeciesData::WILD_ITEM_UNCOMMON] = nil - speciesData[speciesID][SpeciesData::WILD_ITEM_RARE] = nil - end - # Go through hashes of compilable data and compile this section. - [requiredValues,optionalValues].each do |hash| - for key in hash.keys - FileLineData.setSection(speciesID,key,contents[key]) # For error reporting - next if hash[key][0]<0 # Property is not to be compiled; skip it - # Skip empty properties (none are required). - next if !contents[key] || contents[key]=="" - # Compile value for key. - schema = hash[key] - value = pbGetCsvRecord(contents[key],key,schema) - # Modify value as required. - case key - when "Height", "Weight" - # Convert height/weight to 1 decimal place and multiply by 10. - value = (value*10).round - if value<=0 - raise _INTL("Value for '{1}' can't be less than or close to 0 (section {2}, PBS/pokemonforms.txt)",key,speciesID) - end - end - # Add value to appropriate array for saving. - case key - when "Moves" - speciesMoves = [] - for i in 0...value.length/2 - speciesMoves.push([value[i*2],value[i*2+1],i]) - end - speciesMoves.sort! { |a,b| (a[0]==b[0]) ? a[2]<=>b[2] : a[0]<=>b[0] } - for i in speciesMoves; i.pop; end - movesets[speciesID] = speciesMoves - when "EggMoves" - if value.is_a?(Array); eggMoves[speciesID] = value - else; eggMoves[speciesID] = [value] - end - when "BattlerPlayerX", "BattlerPlayerY", - "BattlerEnemyX", "BattlerEnemyY", - "BattlerAltitude", "BattlerShadowX", "BattlerShadowSize" - spriteMetrics[schema[0]] = [] if !spriteMetrics[schema[0]] - spriteMetrics[schema[0]][speciesID] = value - when "Evolutions" - speciesEvolutions = [] - for i in 0...value.length/3 - speciesEvolutions.push([value[i*3],value[i*3+1],value[i*3+2],false]) - end - evolutions[speciesID] = speciesEvolutions - when "FormName" - formNames[speciesID] = value - when "Kind" - pokedexKinds[speciesID] = value - when "Pokedex" - pokedexEntries[speciesID] = value - else # All other data - speciesData[speciesID][schema[0]] = value - end - end - end - } - } - # All data is compiled now, just need to save it. - # Write all constants and some helpful code for PBSpecies. - code = "module PBSpecies\r\n#{constants}" - code += "def PBSpecies.maxValueF; return #{maxValue}; end\r\n" - code += "end\r\n" - eval(code) - pbAddScript(code,"PBSpecies") - # Save main species data. - save_data(speciesData,"Data/species.dat") - # Save conversions of form to species data. - save_data(formToSpecies,"Data/form2species.dat") - # Inherit base form moveset. - newMovesets = pbLoadMovesetsData - pbAppendToBaseFormData(PBSpecies.maxValue+1,maxValue,newMovesets,movesets,speciesToForm,true) - save_data(newMovesets,"Data/species_movesets.dat") - $PokemonTemp.speciesMovesets = nil if $PokemonTemp - # Inherit base form egg moves. - newEggMoves = pbLoadEggMovesData - pbAppendToBaseFormData(PBSpecies.maxValue+1,maxValue,newEggMoves,eggMoves,speciesToForm,false) - save_data(newEggMoves,"Data/species_eggmoves.dat") - $PokemonTemp.speciesEggMoves = nil if $PokemonTemp - # Inherit base form metrics data. - newSpriteMetrics = pbLoadSpeciesMetrics - for i in 0...7 - defaultValue = (i==SpeciesData::METRIC_SHADOW_SIZE) ? 2 : 0 # Shadow size 2, other metrics 0 - pbAppendToBaseFormData(PBSpecies.maxValue+1,maxValue,newSpriteMetrics[i], - spriteMetrics[i] || [],speciesToForm,false,defaultValue) - end - save_data(newSpriteMetrics,"Data/species_metrics.dat") - # Evaluate evolution data (has to be done after all species are read). - for e in 0...evolutions.length - next if !evolutions[e] - evolutions[e].each_with_index do |evo,i| - FileLineData.setSection(i,"Evolutions","") - evo[0] = csvEnumField!(evo[0],PBSpecies,"Evolutions",i) # Species - param_type = PBEvolution.getFunction(evo[1], "parameterType") - if param_type - evo[2] = csvEnumField!(evo[2], param_type, "Evolutions", i) - else - evo[2] = csvPosInt!(evo[2]) if evo[2] && evo[2] != "" - end - end - end - # Inherit base form evolution methods. - newEvolutions = pbLoadEvolutionsData - pbAppendToBaseFormData(PBSpecies.maxValue+1,maxValue,newEvolutions,evolutions,speciesToForm,true) - # Add prevolution data to all species as the first "evolution method". - for i in (PBSpecies.maxValue+1)..maxValue - baseSpecies = speciesToForm[i] - preSpecies = -1 - evoData = nil - # Check for another species that evolves into baseSpecies. - for f in 0...newEvolutions.length - next if !newEvolutions[f] || speciesToForm[f]==baseSpecies - newEvolutions[f].each do |evo| - next if evo[0]!=baseSpecies || evo[3] # Evolved species isn't baseSpecies or is a prevolution - preSpecies = speciesToForm[f] # f evolves into baseSpecies - evoData = evo - break - end - break if evoData - end - next if !evoData # evoData[1]=method, evoData[2]=level - both are unused - # Found a species that evolves into e, record it as a prevolution. - if newEvolutions[i] - newEvolutions[i] = [[preSpecies,evoData[1],evoData[2],true]].concat(newEvolutions[i]) - else - newEvolutions[i] = [[preSpecies,evoData[1],evoData[2],true]] - end - end - # Save evolutions data. - save_data(newEvolutions,"Data/species_evolutions.dat") - $PokemonTemp.evolutionsData = nil if $PokemonTemp - # Save all messages. - MessageTypes.addMessages(MessageTypes::FormNames,formNames) - MessageTypes.addMessages(MessageTypes::Kinds,pokedexKinds) - MessageTypes.addMessages(MessageTypes::Entries,pokedexEntries) -end - -def pbAppendToBaseFormData(idxStart,idxEnd,baseData,extraData,speciesToForm,clone=false,defaultValue=nil) - for i in idxStart..idxEnd - if extraData[i] - baseData[i] = extraData[i] - else - species = speciesToForm[i] - if baseData[species] - if clone - baseData[i] = [] - baseData[species].each { |datum| baseData[i].push(datum.clone) } - elsif baseData[species].is_a?(Array) - baseData[i] = baseData[species].clone - else - baseData[i] = baseData[species] - end - else - baseData[i] = defaultValue - end - end - end -end - -#=============================================================================== -# Compile TM/TM/Move Tutor compatibilities -#=============================================================================== -def pbTMRS # Backup Gen 3 TM list - rstm = [ - # TMs - :FOCUSPUNCH,:DRAGONCLAW,:WATERPULSE,:CALMMIND,:ROAR, - :TOXIC,:HAIL,:BULKUP,:BULLETSEED,:HIDDENPOWER, - :SUNNYDAY,:TAUNT,:ICEBEAM,:BLIZZARD,:HYPERBEAM, - :LIGHTSCREEN,:PROTECT,:RAINDANCE,:GIGADRAIN,:SAFEGUARD, - :FRUSTRATION,:SOLARBEAM,:IRONTAIL,:THUNDERBOLT,:THUNDER, - :EARTHQUAKE,:RETURN,:DIG,:PSYCHIC,:SHADOWBALL, - :BRICKBREAK,:DOUBLETEAM,:REFLECT,:SHOCKWAVE,:FLAMETHROWER, - :SLUDGEBOMB,:SANDSTORM,:FIREBLAST,:ROCKTOMB,:AERIALACE, - :TORMENT,:FACADE,:SECRETPOWER,:REST,:ATTRACT, - :THIEF,:STEELWING,:SKILLSWAP,:SNATCH,:OVERHEAT, - # HMs - :CUT,:FLY,:SURF,:STRENGTH,:FLASH,:ROCKSMASH,:WATERFALL,:DIVE - ] - ret = [] - rstm.length.times do - ret.push((parseMove(rstm.to_s) rescue 0)) - end - return ret -end - -def pbCompileMachines - lineno = 1 - havesection = false - sectionname = nil - sections = [] - if safeExists?("PBS/tm.txt") - f = File.open("PBS/tm.txt","rb") - FileLineData.file="PBS/tm.txt" - f.each_line { |line| - if lineno==1 && line[0]==0xEF && line[1]==0xBB && line[2]==0xBF - line = line[3,line.length-3] - end - FileLineData.setLine(line,lineno) - if !line[/^\#/] && !line[/^\s*$/] - if line[/^\s*\[\s*(.*)\s*\]\s*$/] - sectionname = parseMove($~[1]) - sections[sectionname] = WordArray.new - havesection = true - else - if sectionname==nil - raise _INTL("Expected a section at the beginning of the file. This error may also occur if the file was not saved in UTF-8.\r\n{1}", - FileLineData.linereport) - end - specieslist = line.sub(/\s+$/,"").split(",") - for species in specieslist - next if !species || species=="" - sec = sections[sectionname] - sec[sec.length] = parseSpecies(species) - end - end - end - lineno += 1 - Graphics.update if lineno%50==0 - Win32API.SetWindowText(_INTL("Processing {1} line {2}",FileLineData.file,lineno)) if lineno%50==0 - } - f.close - elsif safeExists?("Data/tmRS.dat") - tmrs = pbTMRS() - for i in 0...tmrs.length - next if !tmrs[i] || tmrs[i]==0 - sections[tmrs[i]] = [] - end - File.open("Data/tmRS.dat","rb") { |f| - species = 1 - while !f.eof? - data = f.read(8)+"\0\0\0\0\0\0\0\0" - for i in 0...58 - next if !tmrs[i] || tmrs[i]==0 - if (data[i>>3]&(1<<(i&7)))!=0 - sections[tmrs[i]].push(species) - end - end - species += 1 - end - } - end - save_data(sections,"Data/tm.dat") -end - -#=============================================================================== -# Compile Shadow moves -#=============================================================================== -def pbCompileShadowMoves - sections = [] - if File.exists?("PBS/shadowmoves.txt") - pbCompilerEachCommentedLine("PBS/shadowmoves.txt") { |line,_lineno| - if line[ /^\s*(\w+)\s*=\s*(.*)$/ ] - key = $1 - value = $2 - value = value.split(",") - species = parseSpecies(key) - moves = [] - for i in 0...[4,value.length].min - moves.push((parseMove(value[i]) rescue nil)) - end - moves.compact! - sections[species] = moves if moves.length>0 - end - } - end - save_data(sections,"Data/shadow_movesets.dat") -end - -#=============================================================================== -# Compile wild encounters -#=============================================================================== -def pbCompileEncounters - lines = [] - linenos = [] - FileLineData.file = "PBS/encounters.txt" - File.open("PBS/encounters.txt","rb") { |f| - lineno = 1 - f.each_line { |line| - if lineno==1 && line[0]==0xEF && line[1]==0xBB && line[2]==0xBF - line = line[3,line.length-3] - end - line = prepline(line) - if line.length!=0 - lines[lines.length] = line - linenos[linenos.length] = lineno - end - lineno += 1 - } - } - encounters = {} - thisenc = nil - needdensity = false - lastmapid = -1 - i = 0 - while i=0 - needdensity = false - enclines = EncounterTypes::EnctypeChances[enc].length - encarray = [] - j = i+1; k = 0 - while jmaxlevel - raise _INTL("Level number is not valid: {1}\r\n{2}",splitarr[1],FileLineData.linereport) - end - if splitarr[2]<=0 || splitarr[2]>maxlevel - raise _INTL("Level number is not valid: {1}\r\n{2}",splitarr[2],FileLineData.linereport) - end - if splitarr[1]>splitarr[2] - raise _INTL("Minimum level is greater than maximum level: {1}\r\n{2}",line,FileLineData.linereport) - end - splitarr[0] = parseSpecies(splitarr[0]) - encarray.push(splitarr) - thisenc[1][enc] = encarray - j += 1; k += 1 - end - if j==lines.length && k=3 - for j in 0...EncounterTypes::EnctypeChances.length - next if !EncounterTypes::EnctypeChances[j] || - EncounterTypes::EnctypeChances[j].length==0 - next if EncounterTypes::EnctypeCompileDens[j]==0 - thisenc[0][j] = nums[EncounterTypes::EnctypeCompileDens[j]-1].to_i - end - else - raise _INTL("Wrong syntax for densities in encounters.txt; got \"{1}\"\r\n{2}",line,FileLineData.linereport) - end - i += 1 - else - raise _INTL("Undefined encounter type {1}, expected one of the following:\r\n{2}\r\n{3}",line,EncounterTypes::Names.inspect,FileLineData.linereport) - end - end - save_data(encounters,"Data/encounters.dat") -end - -#=============================================================================== -# Compile trainer types -#=============================================================================== -def pbCompileTrainerTypes - # Trainer types - records = [] - trainernames = [] - maxValue = 0 - pbCompilerEachPreppedLine("PBS/trainertypes.txt") { |line,lineno| - record=pbGetCsvRecord(line,lineno,[0,"unsUSSSeUS", # ID can be 0 - nil,nil,nil,nil,nil,nil,nil,{ - "" => 2, - "Male" => 0,"M" => 0,"0" => 0, - "Female" => 1,"F" => 1,"1" => 1, - "Mixed" => 2,"X" => 2,"2" => 2 - },nil,nil] - ) - if records[record[0]] - raise _INTL("Two trainer types ({1} and {2}) have the same ID ({3}), which is not allowed.\r\n{4}", - records[record[0]][1],record[1],record[0],FileLineData.linereport) - end - trainernames[record[0]] = record[2] - records[record[0]] = record - maxValue = [maxValue,record[0]].max - } - count = records.compact.length - MessageTypes.setMessages(MessageTypes::TrainerTypes,trainernames) - code = "class PBTrainers\r\n" - for rec in records - next if !rec - code += "#{rec[1]}=#{rec[0]}\r\n" - end - code += "def self.getName(id)\r\n" - code += "id=getID(PBTrainers,id)\r\n" - code += "return pbGetMessage(MessageTypes::TrainerTypes,id); end\r\n" - code += "def self.getCount; return #{count}; end\r\n" - code += "def self.maxValue; return #{maxValue}; end\r\n" - code += "end\r\n" - eval(code) - pbAddScript(code,"PBTrainers") - save_data(records,"Data/trainer_types.dat") -end - -#=============================================================================== -# Compile individual trainers -#=============================================================================== -def pbCompileTrainers - trainer_info_types = TrainerData::SCHEMA - mLevel = PBExperience.maxLevel - trainerindex = -1 - trainers = [] - trainernames = [] - trainerlosetext = [] - pokemonindex = -2 - oldcompilerline = 0 - oldcompilerlength = 0 - pbCompilerEachCommentedLine("PBS/trainers.txt") { |line,lineno| - if line[/^\s*\[\s*(.+)\s*\]\s*$/] - # Section [trainertype,trainername] or [trainertype,trainername,partyid] - if oldcompilerline>0 - raise _INTL("Previous trainer not defined with as many Pokémon as expected\r\n{1}",FileLineData.linereport) - end - if pokemonindex==-1 - raise _INTL("Started new trainer while previous trainer has no Pokémon\r\n{1}",FileLineData.linereport) - end - section = pbGetCsvRecord($~[1],lineno,[0,"esU",PBTrainers]) - trainerindex += 1 - trainertype = section[0] - trainername = section[1] - partyid = section[2] || 0 - trainers[trainerindex] = [trainertype,trainername,[],[],partyid,nil] - trainernames[trainerindex] = trainername - pokemonindex = -1 - elsif line[/^\s*(\w+)\s*=\s*(.*)$/] - # XXX=YYY lines - if trainerindex<0 - raise _INTL("Expected a section at the beginning of the file\r\n{1}",FileLineData.linereport) - end - if oldcompilerline>0 - raise _INTL("Previous trainer not defined with as many Pokémon as expected\r\n{1}",FileLineData.linereport) - end - settingname = $~[1] - schema = trainer_info_types[settingname] - next if !schema - record = pbGetCsvRecord($~[2],lineno,schema) - # Error checking in XXX=YYY lines - case settingname - when "Pokemon" - if record[1]>mLevel - raise _INTL("Bad level: {1} (must be 1-{2})\r\n{3}",record[1],mLevel,FileLineData.linereport) - end - when "Moves" - record = [record] if record.is_a?(Integer) - record.compact! - when "Ability" - if record>5 - raise _INTL("Bad ability flag: {1} (must be 0 or 1 or 2-5)\r\n{2}",record,FileLineData.linereport) - end - when "IV" - record = [record] if record.is_a?(Integer) - record.compact! - for i in record - next if i<=Pokemon::IV_STAT_LIMIT - raise _INTL("Bad IV: {1} (must be 0-{2})\r\n{3}", i, Pokemon::IV_STAT_LIMIT, FileLineData.linereport) - end - when "EV" - record = [record] if record.is_a?(Integer) - record.compact! - for i in record - next if i<=Pokemon::EV_STAT_LIMIT - raise _INTL("Bad EV: {1} (must be 0-{2})\r\n{3}", i, Pokemon::EV_STAT_LIMIT, FileLineData.linereport) - end - evtotal = 0 - for i in 0...6 - evtotal += (iPokemon::EV_LIMIT - raise _INTL("Total EVs are greater than allowed ({1})\r\n{2}", Pokemon::EV_LIMIT, FileLineData.linereport) - end - when "Happiness" - if record>255 - raise _INTL("Bad happiness: {1} (must be 0-255)\r\n{2}",record,FileLineData.linereport) - end - when "Name" - if record.length>Pokemon::MAX_NAME_SIZE - raise _INTL("Bad nickname: {1} (must be 1-{2} characters)\r\n{3}", record, Pokemon::MAX_NAME_SIZE, FileLineData.linereport) - end - end - # Record XXX=YYY setting - case settingname - when "Items" # Items in the trainer's Bag, not the held item - record = [record] if record.is_a?(Integer) - record.compact! - trainers[trainerindex][2] = record - when "LoseText" - trainerlosetext[trainerindex] = record - trainers[trainerindex][5] = record - when "Pokemon" - pokemonindex += 1 - trainers[trainerindex][3][pokemonindex] = [] - trainers[trainerindex][3][pokemonindex][TrainerData::SPECIES] = record[0] - trainers[trainerindex][3][pokemonindex][TrainerData::LEVEL] = record[1] - else - if pokemonindex<0 - raise _INTL("Pokémon hasn't been defined yet!\r\n{1}",FileLineData.linereport) - end - trainers[trainerindex][3][pokemonindex][schema[0]] = record - end - else - # Old compiler - backwards compatibility is SUCH fun! - if pokemonindex==-1 && oldcompilerline==0 - raise _INTL("Unexpected line format, started new trainer while previous trainer has no Pokémon\r\n{1}",FileLineData.linereport) - end - if oldcompilerline==0 # Started an old trainer section - oldcompilerlength = 3 - oldcompilerline = 0 - trainerindex += 1 - trainers[trainerindex] = [0,"",[],[],0] - pokemonindex = -1 - end - oldcompilerline += 1 - case oldcompilerline - when 1 # Trainer type - record = pbGetCsvRecord(line,lineno,[0,"e",PBTrainers]) - trainers[trainerindex][0] = record - when 2 # Trainer name, version number - record = pbGetCsvRecord(line,lineno,[0,"sU"]) - record = [record] if record.is_a?(Integer) - trainers[trainerindex][1] = record[0] - trainernames[trainerindex] = record[0] - trainers[trainerindex][4] = record[1] if record[1] - when 3 # Number of Pokémon, items - record = pbGetCsvRecord(line,lineno,[0,"vEEEEEEEE",nil,PBItems,PBItems, - PBItems,PBItems,PBItems,PBItems,PBItems,PBItems]) - record = [record] if record.is_a?(Integer) - record.compact! - oldcompilerlength += record[0] - record.shift - trainers[trainerindex][2] = record if record - else # Pokémon lines - pokemonindex += 1 - trainers[trainerindex][3][pokemonindex] = [] - record = pbGetCsvRecord(line,lineno, - [0,"evEEEEEUEUBEUUSBU",PBSpecies,nil, PBItems,PBMoves,PBMoves,PBMoves, - PBMoves,nil,{"M"=>0,"m"=>0,"Male"=>0,"male"=>0, - "0"=>0,"F"=>1,"f"=>1,"Female"=>1,"female"=>1, - "1"=>1},nil,nil,PBNatures,nil,nil,nil,nil,nil]) - # Error checking (the +3 is for properties after the four moves) - for i in 0...record.length - next if record[i]==nil - case i - when TrainerData::LEVEL - if record[i]>mLevel - raise _INTL("Bad level: {1} (must be 1-{2})\r\n{3}",record[i],mLevel,FileLineData.linereport) - end - when TrainerData::ABILITY+3 - if record[i]>5 - raise _INTL("Bad ability flag: {1} (must be 0 or 1 or 2-5)\r\n{2}",record[i],FileLineData.linereport) - end - when TrainerData::IV+3 - if record[i]>31 - raise _INTL("Bad IV: {1} (must be 0-31)\r\n{2}",record[i],FileLineData.linereport) - end - record[i] = [record[i]] - when TrainerData::EV+3 - if record[i]>Pokemon::EV_STAT_LIMIT - raise _INTL("Bad EV: {1} (must be 0-{2})\r\n{3}", record[i], Pokemon::EV_STAT_LIMIT, FileLineData.linereport) - end - record[i] = [record[i]] - when TrainerData::HAPPINESS+3 - if record[i]>255 - raise _INTL("Bad happiness: {1} (must be 0-255)\r\n{2}",record[i],FileLineData.linereport) - end - when TrainerData::NAME+3 - if record[i].length>Pokemon::MAX_NAME_SIZE - raise _INTL("Bad nickname: {1} (must be 1-{2} characters)\r\n{3}", record[i], Pokemon::MAX_NAME_SIZE, FileLineData.linereport) - end - end - end - # Write data to trainer array - for i in 0...record.length - next if record[i]==nil - if i>=TrainerData::MOVES && i=TrainerData::MOVES+4) ? i-3 : i - trainers[trainerindex][3][pokemonindex][d] = record[i] - end - end - end - oldcompilerline = 0 if oldcompilerline>=oldcompilerlength - end - } - save_data(trainers,"Data/trainers.dat") - MessageTypes.setMessagesAsHash(MessageTypes::TrainerNames,trainernames) - MessageTypes.setMessagesAsHash(MessageTypes::TrainerLoseText,trainerlosetext) -end - -#=============================================================================== -# Compile Battle Tower and other Cups trainers/Pokémon -#=============================================================================== -def pbCompileBTTrainers(filename) - sections = [] - requiredtypes = { - "Type" => [0, "e",PBTrainers], - "Name" => [1, "s"], - "BeginSpeech" => [2, "s"], - "EndSpeechWin" => [3, "s"], - "EndSpeechLose" => [4, "s"], - "PokemonNos" => [5, "*u"] - } - trainernames = [] - beginspeech = [] - endspeechwin = [] - endspeechlose = [] - if safeExists?(filename) - File.open(filename,"rb") { |f| - FileLineData.file = filename - pbEachFileSectionEx(f) { |section,name| - rsection = [] - for key in section.keys - FileLineData.setSection(name,key,section[key]) - schema = requiredtypes[key] - next if !schema - record = pbGetCsvRecord(section[key],0,schema) - rsection[schema[0]] = record - end - trainernames.push(rsection[1]) - beginspeech.push(rsection[2]) - endspeechwin.push(rsection[3]) - endspeechlose.push(rsection[4]) - sections.push(rsection) - } - } - end - MessageTypes.addMessagesAsHash(MessageTypes::TrainerNames,trainernames) - MessageTypes.addMessagesAsHash(MessageTypes::BeginSpeech,beginspeech) - MessageTypes.addMessagesAsHash(MessageTypes::EndSpeechWin,endspeechwin) - MessageTypes.addMessagesAsHash(MessageTypes::EndSpeechLose,endspeechlose) - return sections -end - -def pbCompileTrainerLists - btTrainersRequiredTypes = { - "Trainers" => [0, "s"], - "Pokemon" => [1, "s"], - "Challenges" => [2, "*s"] - } - if !safeExists?("PBS/trainerlists.txt") - File.open("PBS/trainerlists.txt","wb") { |f| - f.write(0xEF.chr) - f.write(0xBB.chr) - f.write(0xBF.chr) - f.write("[DefaultTrainerList]\r\n") - f.write("Trainers = bttrainers.txt\r\n") - f.write("Pokemon = btpokemon.txt\r\n") - } - end - sections = [] - MessageTypes.setMessagesAsHash(MessageTypes::BeginSpeech,[]) - MessageTypes.setMessagesAsHash(MessageTypes::EndSpeechWin,[]) - MessageTypes.setMessagesAsHash(MessageTypes::EndSpeechLose,[]) - File.open("PBS/trainerlists.txt","rb") { |f| - FileLineData.file = "PBS/trainerlists.txt" - pbEachFileSectionEx(f) { |section,name| - next if name!="DefaultTrainerList" && name!="TrainerList" - rsection = [] - for key in section.keys - FileLineData.setSection(name,key,section[key]) - schema = btTrainersRequiredTypes[key] - next if key=="Challenges" && name=="DefaultTrainerList" - next if !schema - record = pbGetCsvRecord(section[key],0,schema) - rsection[schema[0]] = record - end - if !rsection[0] - raise _INTL("No trainer data file given in section {1}\r\n{2}",name,FileLineData.linereport) - end - if !rsection[1] - raise _INTL("No trainer data file given in section {1}\r\n{2}",name,FileLineData.linereport) - end - rsection[3] = rsection[0] - rsection[4] = rsection[1] - rsection[5] = (name=="DefaultTrainerList") - if safeExists?("PBS/"+rsection[0]) - rsection[0] = pbCompileBTTrainers("PBS/"+rsection[0]) - else - rsection[0] = [] - end - if safeExists?("PBS/"+rsection[1]) - filename = "PBS/"+rsection[1] - rsection[1] = [] - pbCompilerEachCommentedLine(filename) { |line,_lineno| - rsection[1].push(PBPokemon.fromInspected(line)) - } - else - rsection[1] = [] - end - rsection[2] = [] if !rsection[2] - while rsection[2].include?("") - rsection[2].delete("") - end - rsection[2].compact! - sections.push(rsection) - } - } - save_data(sections,"Data/trainer_lists.dat") -end diff --git a/Data/Scripts/022_Compiler/003_Compiler_MapsAndEvents.rb b/Data/Scripts/022_Compiler/003_Compiler_MapsAndEvents.rb deleted file mode 100644 index 0ec07f932..000000000 --- a/Data/Scripts/022_Compiler/003_Compiler_MapsAndEvents.rb +++ /dev/null @@ -1,1465 +0,0 @@ -#=============================================================================== -# Add new map files to the map tree. -#=============================================================================== -def pbImportNewMaps - return false if !$DEBUG - mapfiles = {} - # Get IDs of all maps in the Data folder - Dir.chdir("Data") { - mapData = sprintf("Map*.%s",$RPGVX ? "rvdata" : "rxdata") - for map in Dir.glob(mapData) - if $RPGVX - mapfiles[$1.to_i(10)] = true if map[/map(\d+)\.rvdata/i] - else - mapfiles[$1.to_i(10)] = true if map[/map(\d+)\.rxdata/i] - end - end - } - mapinfos = pbLoadRxData("Data/MapInfos") - maxOrder = 0 - # Exclude maps found in mapinfos - for id in mapinfos.keys - next if !mapinfos[id] - mapfiles.delete(id) if mapfiles[id] - maxOrder = [maxOrder,mapinfos[id].order].max - end - # Import maps not found in mapinfos - maxOrder += 1 - imported = false - count = 0 - for id in mapfiles.keys - next if id==999 # Ignore 999 (random dungeon map) - mapinfo = RPG::MapInfo.new - mapinfo.order = maxOrder - mapinfo.name = sprintf("MAP%03d",id) - maxOrder += 1 - mapinfos[id] = mapinfo - imported = true - count += 1 - end - if imported - if $RPGVX - save_data(mapinfos,"Data/MapInfos.rvdata") - else - save_data(mapinfos,"Data/MapInfos.rxdata") - end - pbMessage(_INTL("{1} new map(s) copied to the Data folder were successfully imported.",count)) - end - return imported -end - -#=============================================================================== -# Generate and modify event commands. -#=============================================================================== -def pbGenerateMoveRoute(commands) - route = RPG::MoveRoute.new - route.repeat = false - route.skippable = true - route.list.clear - i = 0 - while i=map.width || y<0 || y>=map.height - passages = getTilesetPassages(map,mapID) - priorities = getTilesetPriorities(map,mapID) - for i in [2, 1, 0] - tile_id = map.data[x, y, i] - return false if tile_id==nil - passage = passages[tile_id] - if !passage - raise "The tile used on map #{mapID} at coordinates (#{x}, #{y}) on layer #{i+1} doesn't exist in the tileset. " + - "It should be deleted to prevent errors." - end - return false if passage&0x0f==0x0f - return true if priorities[tile_id]==0 - end - return true - end - - def isCounterTile?(mapID,x,y) - return false if $RPGVX - map = getMap(mapID) - return false if !map - passages = getTilesetPassages(map,mapID) - for i in [2, 1, 0] - tile_id = map.data[x, y, i] - return false if tile_id==nil - passage = passages[tile_id] - if !passage - raise "The tile used on map #{mapID} at coordinates (#{x}, #{y}) on layer #{i+1} doesn't exist in the tileset. " + - "It should be deleted to prevent errors." - end - return true if passage&0x80==0x80 - end - return false - end - - def setCounterTile(mapID,x,y) - return if $RPGVX - map = getMap(mapID) - return if !map - passages = getTilesetPassages(map,mapID) - for i in [2, 1, 0] - tile_id = map.data[x, y, i] - next if tile_id==0 - passages[tile_id] |= 0x80 - break - end - end - - def registerSwitch(switch) - return @registeredSwitches[switch] if @registeredSwitches[switch] - for id in 1..5000 - name = @system.switches[id] - next if name && name!="" && name!=switch - @system.switches[id] = switch - @registeredSwitches[switch] = id - return id - end - return 1 - end - - def saveMap(mapID) - save_data(getMap(mapID),mapFilename(mapID)) rescue nil - end - - def saveTilesets - filename = "Data/Tilesets" - filename += ($RPGVX) ? ".rvdata" : ".rxdata" - save_data(@tilesets,filename) - filename = "Data/System" - filename += ($RPGVX) ? ".rvdata" : ".rxdata" - save_data(@system,filename) - end -end - - - -#=============================================================================== -# -#=============================================================================== -class TrainerChecker - def initialize - @trainers = nil - @trainertypes = nil - @dontaskagain = false - end - - def pbTrainerTypeCheck(symbol) - ret = true - if $DEBUG - return if @dontaskagain - if !hasConst?(PBTrainers,symbol) - ret = false - else - trtype = PBTrainers.const_get(symbol) - @trainertypes = load_data("Data/trainer_types.dat") if !@trainertypes - ret = false if !@trainertypes || !@trainertypes[trtype] - end - if !ret - if pbConfirmMessage(_INTL("Add new trainer named {1}?",symbol)) - pbTrainerTypeEditorNew(symbol.to_s) - @trainers = nil - @trainertypes = nil - end -# if pbMapInterpreter -# pbMapInterpreter.command_end rescue nil -# end - end - end - return ret - end - - def pbTrainerBattleCheck(trtype,trname,trid) - return if !$DEBUG || @dontaskagain - if trtype.is_a?(String) || trtype.is_a?(Symbol) - pbTrainerTypeCheck(trtype) - return if !hasConst?(PBTrainers,trtype) - trtype = PBTrainers.const_get(trtype) - end - @trainers = load_data("Data/trainers.dat") if !@trainers - if @trainers - for trainer in @trainers - return if trainer[0]==trtype && trainer[1]==trname && trainer[4]==trid - end - end - cmd = pbMissingTrainer(trtype,trname,trid) - if cmd==2 - @dontaskagain = true - Graphics.update - end - @trainers = nil - @trainertypes = nil - end -end - - - -#=============================================================================== -# Convert trainer comments to trainer event. -#=============================================================================== -def pbConvertToTrainerEvent(event,trainerChecker) - return nil if !event || event.pages.length==0 - list = event.pages[0].list - return nil if list.length<2 - commands = [] - isFirstCommand = false - # Find all the trainer comments in the event - for i in 0...list.length - next if list[i].code!=108 # Comment (first line) - command = list[i].parameters[0] - for j in (i+1)...list.length - break if list[j].code!=408 # Comment (continuation line) - command += "\r\n"+list[j].parameters[0] - end - if command[/^(Battle\:|Type\:|Name\:|BattleID\:|DoubleBattle\:|Backdrop\:|EndSpeech\:|Outcome\:|Continue\:|EndBattle\:|EndIfSwitch\:|VanishIfSwitch\:|RegSpeech\:)/i] - commands.push(command) - isFirstCommand = true if i==0 - end - end - return nil if commands.length==0 - # Found trainer comments; create a new Event object to replace this event - ret = RPG::Event.new(event.x,event.y) - ret.name = event.name - ret.id = event.id - firstpage = Marshal::load(Marshal.dump(event.pages[0])) # Copy event's first page - firstpage.trigger = 2 # On event touch - firstpage.list = [] # Clear page's commands - # Rename the event if there's nothing above the trainer comments - if isFirstCommand - if !event.name[/trainer/i] - ret.name = "Trainer(3)" - elsif event.name[/^\s*trainer\s+\((\d+)\)\s*$/i] - ret.name = "Trainer(#{$1})" - end - end - # Compile the trainer comments - rewriteComments = false # You can change this - battles = [] - trtype = nil - trname = nil - battleid = 0 - doublebattle = false - backdrop = nil - endspeeches = [] - outcome = 0 - continue = false - endbattles = [] - endifswitch = [] - vanishifswitch = [] - regspeech = nil - for command in commands - if command[/^Battle\:\s*([\s\S]+)$/i] - battles.push($~[1]) - pbPushComment(firstpage.list,command) if rewriteComments - elsif command[/^Type\:\s*([\s\S]+)$/i] - trtype = $~[1].gsub(/^\s+/,"").gsub(/\s+$/,"") - pbPushComment(firstpage.list,command) if rewriteComments - elsif command[/^Name\:\s*([\s\S]+)$/i] - trname = $~[1].gsub(/^\s+/,"").gsub(/\s+$/,"") - pbPushComment(firstpage.list,command) if rewriteComments - elsif command[/^BattleID\:\s*(\d+)$/i] - battleid = $~[1].to_i - pbPushComment(firstpage.list,command) if rewriteComments - elsif command[/^DoubleBattle\:\s*([\s\S]+)$/i] - value = $~[1].gsub(/^\s+/,"").gsub(/\s+$/,"") - doublebattle = true if value.upcase=="TRUE" || value.upcase=="YES" - pbPushComment(firstpage.list,command) if rewriteComments - elsif command[/^Backdrop\:\s*([\s\S]+)$/i] - backdrop = $~[1].gsub(/^\s+/,"").gsub(/\s+$/,"") - pbPushComment(firstpage.list,command) if rewriteComments - elsif command[/^EndSpeech\:\s*([\s\S]+)$/i] - endspeeches.push($~[1].gsub(/^\s+/,"").gsub(/\s+$/,"")) - pbPushComment(firstpage.list,command) if rewriteComments - elsif command[/^Outcome\:\s*(\d+)$/i] - outcome = $~[1].to_i - pbPushComment(firstpage.list,command) if rewriteComments - elsif command[/^Continue\:\s*([\s\S]+)$/i] - value = $~[1].gsub(/^\s+/,"").gsub(/\s+$/,"") - continue = true if value.upcase=="TRUE" || value.upcase=="YES" - pbPushComment(firstpage.list,command) if rewriteComments - elsif command[/^EndBattle\:\s*([\s\S]+)$/i] - endbattles.push($~[1].gsub(/^\s+/,"").gsub(/\s+$/,"")) - pbPushComment(firstpage.list,command) if rewriteComments - elsif command[/^EndIfSwitch\:\s*([\s\S]+)$/i] - endifswitch.push(($~[1].gsub(/^\s+/,"").gsub(/\s+$/,"")).to_i) - pbPushComment(firstpage.list,command) if rewriteComments - elsif command[/^VanishIfSwitch\:\s*([\s\S]+)$/i] - vanishifswitch.push(($~[1].gsub(/^\s+/,"").gsub(/\s+$/,"")).to_i) - pbPushComment(firstpage.list,command) if rewriteComments - elsif command[/^RegSpeech\:\s*([\s\S]+)$/i] - regspeech = $~[1].gsub(/^\s+/,"").gsub(/\s+$/,"") - pbPushComment(firstpage.list,command) if rewriteComments - end - end - return nil if battles.length<=0 - # Run trainer check now, except in editor - trainerChecker.pbTrainerBattleCheck(trtype,trname,battleid) if !$INEDITOR - # Set the event's charset to one depending on the trainer type if the event - # doesn't have a charset - if firstpage.graphic.character_name=="" && hasConst?(PBTrainers,trtype) - trainerid = getConst(PBTrainers,trtype) - if trainerid - filename = pbTrainerCharNameFile(trainerid) - if FileTest.image_exist?("Graphics/Characters/"+filename) - firstpage.graphic.character_name = sprintf(filename) - end - end - end - # Create strings that will be used repeatedly - safetrcombo = sprintf(":%s,\"%s\"",trtype,safequote(trname)) # :YOUNGSTER,"Joey" - introplay = sprintf("pbTrainerIntro(:%s)",trtype) - # Write first page - pbPushScript(firstpage.list,introplay) # pbTrainerIntro - pbPushScript(firstpage.list,"pbNoticePlayer(get_character(0))") - pbPushText(firstpage.list,battles[0]) - if battles.length>1 # Has rematches - pbPushScript(firstpage.list,sprintf("pbTrainerCheck(%s,%d,%d)",safetrcombo,battles.length,battleid)) - end - pbPushScript(firstpage.list,"setBattleRule(\"double\")") if doublebattle - pbPushScript(firstpage.list,sprintf("setBattleRule(\"backdrop\",\"%s\")",safequote(backdrop))) if backdrop - pbPushScript(firstpage.list,sprintf("setBattleRule(\"outcomeVar\",%d)",outcomeVar)) if outcome>1 - pbPushScript(firstpage.list,"setBattleRule(\"canLose\")") if continue - espeech = (endspeeches[0]) ? sprintf("_I(\"%s\")",safequote2(endspeeches[0])) : "nil" - if battleid>0 - battleString = sprintf("pbTrainerBattle(%s,%s,nil,%d)",safetrcombo,espeech,battleid) - elsif endspeeches[0] - battleString = sprintf("pbTrainerBattle(%s,%s)",safetrcombo,espeech) - else - battleString = sprintf("pbTrainerBattle(%s)",safetrcombo) - end - pbPushBranch(firstpage.list,battleString) - if battles.length>1 # Has rematches - pbPushScript(firstpage.list, - sprintf("pbPhoneRegisterBattle(_I(\"%s\"),get_character(0),%s,%d)", - regspeech,safetrcombo,battles.length),1) - end - pbPushSelfSwitch(firstpage.list,"A",true,1) - pbPushBranchEnd(firstpage.list,1) - pbPushScript(firstpage.list,"pbTrainerEnd",0) - pbPushEnd(firstpage.list) - # Copy first page to last page and make changes to its properties - lastpage = Marshal::load(Marshal.dump(firstpage)) - lastpage.trigger = 0 # On action - lastpage.list = [] # Clear page's commands - lastpage.condition = firstpage.condition.clone - lastpage.condition.self_switch_valid = true - lastpage.condition.self_switch_ch = "A" - # Copy last page to rematch page - rematchpage = Marshal::load(Marshal.dump(lastpage)) - rematchpage.list = lastpage.list.clone # Copy the last page's commands - rematchpage.condition = lastpage.condition.clone - rematchpage.condition.self_switch_valid = true - rematchpage.condition.self_switch_ch = "B" - # Write rematch and last pages - for i in 1...battles.length - # Run trainer check now, except in editor - trainerChecker.pbTrainerBattleCheck(trtype,trname,battleid+i) if !$INEDITOR - if i==battles.length-1 - pbPushBranch(rematchpage.list,sprintf("pbPhoneBattleCount(%s)>=%d",safetrcombo,i)) - pbPushBranch(lastpage.list,sprintf("pbPhoneBattleCount(%s)>%d",safetrcombo,i)) - else - pbPushBranch(rematchpage.list,sprintf("pbPhoneBattleCount(%s)==%d",safetrcombo,i)) - pbPushBranch(lastpage.list,sprintf("pbPhoneBattleCount(%s)==%d",safetrcombo,i)) - end - # Rematch page - pbPushScript(rematchpage.list,introplay,1) # pbTrainerIntro - pbPushText(rematchpage.list,battles[i],1) - pbPushScript(rematchpage.list,"setBattleRule(\"double\")",1) if doublebattle - pbPushScript(rematchpage.list,sprintf("setBattleRule(\"backdrop\",%s)",safequote(backdrop)),1) if backdrop - pbPushScript(rematchpage.list,sprintf("setBattleRule(\"outcomeVar\",%d)",outcomeVar),1) if outcome>1 - pbPushScript(rematchpage.list,"setBattleRule(\"canLose\")",1) if continue - espeech = nil - if endspeeches.length>0 - espeech = (endspeeches[i]) ? endspeeches[i] : endspeeches[endspeeches.length-1] - end - espeech = (espeech) ? sprintf("_I(\"%s\")",safequote2(espeech)) : "nil" - battleString = sprintf("pbTrainerBattle(%s,%s,nil,%d)",safetrcombo,espeech,battleid+i) - pbPushBranch(rematchpage.list,battleString,1) - pbPushScript(rematchpage.list,sprintf("pbPhoneIncrement(%s,%d)",safetrcombo,battles.length),2) - pbPushSelfSwitch(rematchpage.list,"A",true,2) - pbPushSelfSwitch(rematchpage.list,"B",false,2) - pbPushScript(rematchpage.list,"pbTrainerEnd",2) - pbPushBranchEnd(rematchpage.list,2) - pbPushExit(rematchpage.list,1) # Exit Event Processing - pbPushBranchEnd(rematchpage.list,1) - # Last page - if endbattles.length>0 - ebattle = (endbattles[i]) ? endbattles[i] : endbattles[endbattles.length-1] - pbPushText(lastpage.list,ebattle,1) - end - pbPushScript(lastpage.list, - sprintf("pbPhoneRegisterBattle(_I(\"%s\"),get_character(0),%s,%d)", - regspeech,safetrcombo,battles.length),1) - pbPushExit(lastpage.list,1) # Exit Event Processing - pbPushBranchEnd(lastpage.list,1) - end - # Finish writing rematch page - pbPushEnd(rematchpage.list) - # Finish writing last page - ebattle = (endbattles[0]) ? endbattles[0] : "..." - pbPushText(lastpage.list,ebattle) - if battles.length>1 - pbPushScript(lastpage.list, - sprintf("pbPhoneRegisterBattle(_I(\"%s\"),get_character(0),%s,%d)", - regspeech,safetrcombo,battles.length)) - end - pbPushEnd(lastpage.list) - # Add pages to the new event - if battles.length==1 # Only one battle - ret.pages = [firstpage,lastpage] - else # Has rematches - ret.pages = [firstpage,rematchpage,lastpage] - end - # Copy last page to endIfSwitch page - for endswitch in endifswitch - endIfSwitchPage = Marshal::load(Marshal.dump(lastpage)) - endIfSwitchPage.condition = lastpage.condition.clone - if endIfSwitchPage.condition.switch1_valid # Add another page condition - endIfSwitchPage.condition.switch2_valid = true - endIfSwitchPage.condition.switch2_id = endswitch - else - endIfSwitchPage.condition.switch1_valid = true - endIfSwitchPage.condition.switch1_id = endswitch - end - endIfSwitchPage.condition.self_switch_valid = false - endIfSwitchPage.list = [] # Clear page's commands - ebattle = (endbattles[0]) ? endbattles[0] : "..." - pbPushText(endIfSwitchPage.list,ebattle) - pbPushEnd(endIfSwitchPage.list) - ret.pages.push(endIfSwitchPage) - end - # Copy last page to vanishIfSwitch page - for vanishswitch in vanishifswitch - vanishIfSwitchPage = Marshal::load(Marshal.dump(lastpage)) - vanishIfSwitchPage.graphic.character_name = "" # No charset - vanishIfSwitchPage.condition = lastpage.condition.clone - if vanishIfSwitchPage.condition.switch1_valid # Add another page condition - vanishIfSwitchPage.condition.switch2_valid = true - vanishIfSwitchPage.condition.switch2_id = vanishswitch - else - vanishIfSwitchPage.condition.switch1_valid = true - vanishIfSwitchPage.condition.switch1_id = vanishswitch - end - vanishIfSwitchPage.condition.self_switch_valid = false - vanishIfSwitchPage.list = [] # Clear page's commands - pbPushEnd(vanishIfSwitchPage.list) - ret.pages.push(vanishIfSwitchPage) - end - return ret -end - -#=============================================================================== -# Convert event name to item event. -# Checks if the event's name is "Item:POTION" or "HiddenItem:POTION". If so, -# rewrites the whole event into one now named "Item"/"HiddenItem" which gives -# that item when interacted with. -#=============================================================================== -def pbConvertToItemEvent(event) - return nil if !event || event.pages.length==0 - name = event.name - ret = RPG::Event.new(event.x,event.y) - ret.name = event.name - ret.id = event.id - ret.pages = [] - itemName = "" - hidden = false - if name[/^HiddenItem\:\s*(\w+)\s*$/] - itemName = $1 - return nil if !hasConst?(PBItems,itemName) - ret.name = "HiddenItem" - hidden = true - elsif name[/^Item\:\s*(\w+)\s*$/] - itemName = $1 - return nil if !hasConst?(PBItems,itemName) - ret.name = "Item" - else - return nil - end - # Event page 1 - page = RPG::Event::Page.new - page.graphic.character_name = "Object ball" if !hidden - page.list = [] - pbPushBranch(page.list,sprintf("pbItemBall(:%s)",itemName)) - pbPushSelfSwitch(page.list,"A",true,1) - pbPushElse(page.list,1) - pbPushBranchEnd(page.list,1) - pbPushEnd(page.list) - ret.pages.push(page) - # Event page 2 - page = RPG::Event::Page.new - page.condition.self_switch_valid = true - page.condition.self_switch_ch = "A" - ret.pages.push(page) - return ret -end - -#=============================================================================== -# Checks whether a given event is likely to be a door. If so, rewrite it to -# include animating the event as though it was a door opening and closing as the -# player passes through. -#=============================================================================== -def pbUpdateDoor(event,mapData) - changed = false - return false if event.is_a?(RPG::CommonEvent) - # Check if event has 2+ pages and the last page meets all of these criteria: - # - Has a condition of a Switch being ON - # - The event has a charset graphic - # - There are more than 5 commands in that page, the first of which is a - # Conditional Branch - lastPage = event.pages[event.pages.length-1] - if event.pages.length>=2 && - lastPage.condition.switch1_valid && - lastPage.graphic.character_name!="" && - lastPage.list.length>5 && - lastPage.list[0].code==111 - # This bit of code is just in case Switch 22 has been renamed/repurposed, - # which is highly unlikely. It changes the Switch used in the condition to - # whichever is named 's:tsOff?("A")'. - if lastPage.condition.switch1_id==22 && - mapData.switchName(lastPage.condition.switch1_id)!='s:tsOff?("A")' - lastPage.condition.switch1_id = mapData.registerSwitch('s:tsOff?("A")') - changed = true - end - # If the last page's Switch condition uses a Switch named 's:tsOff?("A")', - # check the penultimate page. If it contains exactly 1 "Transfer Player" - # command and does NOT contain a "Change Transparent Flag" command, rewrite - # both the penultimate page and the last page. - if mapData.switchName(lastPage.condition.switch1_id)=='s:tsOff?("A")' - list = event.pages[event.pages.length-2].list - transferCommand = list.find_all { |cmd| cmd.code==201 } # Transfer Player - if transferCommand.length==1 && !list.any? { |cmd| cmd.code==208 } # Change Transparent Flag - # Rewrite penultimate page - list.clear - pbPushMoveRouteAndWait(list,0,[ # Move Route for door opening - PBMoveRoute::PlaySE,RPG::AudioFile.new("Door enter"),PBMoveRoute::Wait,2, - PBMoveRoute::TurnLeft,PBMoveRoute::Wait,2, - PBMoveRoute::TurnRight,PBMoveRoute::Wait,2, - PBMoveRoute::TurnUp,PBMoveRoute::Wait,2]) - pbPushMoveRouteAndWait(list,-1,[ # Move Route for player entering door - PBMoveRoute::ThroughOn,PBMoveRoute::Up,PBMoveRoute::ThroughOff]) - pbPushEvent(list,208,[0]) # Change Transparent Flag (invisible) - pbPushMoveRouteAndWait(list,0,[PBMoveRoute::Wait,2, # Move Route for door closing - PBMoveRoute::TurnRight,PBMoveRoute::Wait,2, - PBMoveRoute::TurnLeft,PBMoveRoute::Wait,2, - PBMoveRoute::TurnDown,PBMoveRoute::Wait,2]) - pbPushEvent(list,223,[Tone.new(-255,-255,-255),6]) # Change Screen Color Tone - pbPushWait(list,8) # Wait - pbPushEvent(list,208,[1]) # Change Transparent Flag (visible) - pbPushEvent(list,transferCommand[0].code,transferCommand[0].parameters) # Transfer Player - pbPushEvent(list,223,[Tone.new(0,0,0),6]) # Change Screen Color Tone - pbPushEnd(list) - # Rewrite last page - list = lastPage.list - list.clear - pbPushBranch(list,"get_character(0).onEvent?") # Conditional Branch - pbPushEvent(list,208,[0],1) # Change Transparent Flag (invisible) - pbPushMoveRouteAndWait(list,0,[ # Move Route for setting door to open - PBMoveRoute::TurnLeft,PBMoveRoute::Wait,6],1) - pbPushEvent(list,208,[1],1) # Change Transparent Flag (visible) - pbPushMoveRouteAndWait(list,-1,[PBMoveRoute::Down],1) # Move Route for player exiting door - pbPushMoveRouteAndWait(list,0,[ # Move Route for door closing - PBMoveRoute::TurnUp,PBMoveRoute::Wait,2, - PBMoveRoute::TurnRight,PBMoveRoute::Wait,2, - PBMoveRoute::TurnDown,PBMoveRoute::Wait,2],1) - pbPushBranchEnd(list,1) - pbPushScript(list,"setTempSwitchOn(\"A\")") - pbPushEnd(list) - changed = true - end - end - end - return changed -end - -#=============================================================================== -# Fix up standard code snippets -#=============================================================================== -def pbEventIsEmpty?(e) - return true if !e - return false if e.is_a?(RPG::CommonEvent) - return e.pages.length==0 -end - -# Checks if the event has exactly 1 page, said page has no graphic, it has less -# than 12 commands and at least one is a Transfer Player, and the tiles to the -# left/right/upper left/upper right are not passable but the event's tile is. -# Causes a second page to be added to the event which is the "is player on me?" -# check that occurs when the map is entered. -def isLikelyPassage?(thisEvent,mapID,mapData) - return false if !thisEvent || thisEvent.pages.length==0 - return false if thisEvent.pages.length!=1 - if thisEvent.pages[0].graphic.character_name=="" && - thisEvent.pages[0].list.length<=12 && - thisEvent.pages[0].list.any? { |cmd| cmd.code==201 } && # Transfer Player -# mapData.isPassable?(mapID,thisEvent.x,thisEvent.y+1) && - mapData.isPassable?(mapID,thisEvent.x,thisEvent.y) && - !mapData.isPassable?(mapID,thisEvent.x-1,thisEvent.y) && - !mapData.isPassable?(mapID,thisEvent.x+1,thisEvent.y) && - !mapData.isPassable?(mapID,thisEvent.x-1,thisEvent.y-1) && - !mapData.isPassable?(mapID,thisEvent.x+1,thisEvent.y-1) - return true - end - return false -end - -def pbChangeScript(script,re) - tmp = script[0].gsub(re) { yield($~) } - if script[0]!=tmp - script[0] = tmp; return true - end - return false -end - -def pbChangeScripts(script) - changed = false - changed |= pbChangeScript(script,/\$game_variables\[(\d+)\](?!\s*(?:\=|\!|<|>))/) { |m| "pbGet("+m[1]+")" } - changed |= pbChangeScript(script,/\$Trainer\.party\[\s*pbGet\((\d+)\)\s*\]/) { |m| "pbGetPokemon("+m[1]+")" } - return changed -end - -def pbFixEventUse(event,_mapID,mapData) - return nil if pbEventIsEmpty?(event) - changed = false - trainerMoneyRE = /^\s*\$Trainer\.money\s*(<|<=|>|>=)\s*(\d+)\s*$/ - itemBallRE = /^\s*(Kernel\.)?pbItemBall/ - # Rewrite event if it looks like a door - changed = true if pbUpdateDoor(event,mapData) - # Check through each page of the event in turn - pbEachPage(event) do |page| - i = 0 - list = page.list - while i=2 && - e.pages[e.pages.length-1].condition.switch1_valid && - e.pages[e.pages.length-1].condition.switch1_id==22 && - mapData.switchName(e.pages[e.pages.length-1].condition.switch1_id)!='s:tsOff?("A")' && - e.pages[e.pages.length-1].list.length>5 && - e.pages[e.pages.length-1].list[0].code==111 # Conditional Branch - e.pages[e.pages.length-1].condition.switch1_id = mapData.registerSwitch('s:tsOff?("A")') - mapData.saveMap(params[1]) - changed = true - end - # Checks if the found event is a simple Transfer Player one nestled - # between tiles that aren't passable - it is likely a door, so give - # it a second page with an "is player on me?" check. - if isLikelyPassage?(e,params[1],mapData) # Checks the first page - pbAddPassageList(e,mapData) - mapData.saveMap(params[1]) - changed = true - end - # If the found event's last page's Switch condition uses a Switch - # named 's:tsOff?("A")', it really does look like a door. Make this - # command transfer the player on top of it rather than in front of - # it. - if e && e.pages.length>=2 && - e.pages[e.pages.length-1].condition.switch1_valid && - mapData.switchName(e.pages[e.pages.length-1].condition.switch1_id)=='s:tsOff?("A")' - # If this is really a door, move transfer target to it - params[3] -= 1 # Move this command's destination up 1 tile (onto the found event) - params[5] = 1 # No fade (the found event should take care of that) - changed = true - end - deletedRoute = nil - deleteMoveRouteAt = proc { |list,i| - arr = [] - if list[i] && list[i].code==209 # Set Move Route - arr.push(list[i]); list.delete_at(i) - while i=0 - list.insert(i,route[j]) - j -= 1 - end - } - # If the next event command is a Move Route that moves the player, - # check whether all it does is turn the player in a direction (or - # its first item is to move the player in a direction). If so, this - # Transfer Player command may as well set the player's direction - # instead; make it do so and delete that Move Route. - if params[4]==0 && # Retain direction - i+13 # Retain direction -# for j in 0...i -# if list[j].code==209 && list[j].parameters[0]==-1 # Set Move Route -# route = list[j].parameters[1] -# if route && route.list.length<=2 -# oldlistlength = list.length -# # Delete superfluous move route command if necessary -# if route.list[0].code==16 # Player Turn Down -# deleteMoveRouteAt.call(list,j); params[4] = 2; changed = true; i -= (oldlistlength-list.length) -# elsif route.list[0].code==17 # Player Turn Left -# deleteMoveRouteAt.call(list,j); params[4] = 4; changed = true; i -= (oldlistlength-list.length) -# elsif route.list[0].code==18 # Player Turn Right -# deleteMoveRouteAt.call(list,j); params[4] = 6; changed = true; i -= (oldlistlength-list.length) -# elsif route.list[0].code==19 # Player Turn Up -# deleteMoveRouteAt.call(list,j); params[4] = 8; changed = true; i -= (oldlistlength-list.length) -# end -# end -# end -# end - # If the next event command changes the screen color, and the one - # after that is a Move Route which only turns the player in a - # direction, this Transfer Player command may as well set the - # player's direction instead; make it do so and delete that Move - # Route. - elsif params[4]==0 && # Retain direction - i+2=2 && list[i].parameters[0].length>0 && list[i].parameters[0].length<=20 && - !list[i].parameters[0][/\\n/] - # Very short line - list[i].parameters[0] += "\\n"+list[i+1].parameters[0] - list.delete_at(i+1) - i -= 1 # revisit this text command - changed = true - # Check whether this Show Text command has 3+ lines and the next command - # is also a Show Text - elsif lines>=3 && list[i+lines] && list[i+lines].code==101 # Show Text - # Check whether a sentence is being broken midway between two Text - # commands (i.e. the first Show Text doesn't end in certain punctuation) - lastLine = list[i+lines-1].parameters[0].sub(/\s+$/,"") - if lastLine.length>0 && !lastLine[/[\\<]/] && lastLine[/[^\.,\!\?\;\-\"]$/] - message = list[i].parameters[0] - j = i+1 - while j=0 - list.insert(i,RPG::EventCommand.new((j==0) ? 101 : 401,indent,[nextMessage[j]])) - j-=1 - end - j = newMessage.length-1 - while j>=0 - list.insert(i,RPG::EventCommand.new((j==0) ? 101 : 401,indent,[newMessage[j]])) - j -= 1 - end - changed = true - i += 1 - next - end - end - end - when 111 # Conditional Branch - if list[i].parameters[0]==12 # script - x = [list[i].parameters[1]] - changed |= pbChangeScripts(x) - list[i].parameters[1] = x[0] - script = x[0] - if script[trainerMoneyRE] # Compares $Trainer.money with a value - # Checking money directly - operator = $1 - amount = $2.to_i - if operator=="<" - params[0] = 7 # gold - params[2] = 1 - params[1] = amount-1 - changed = true - elsif operator=="<=" - params[0] = 7 # gold - params[2] = 1 - params[1] = amount - changed = true - elsif operator==">" - params[0] = 7 # gold - params[2] = 0 - params[1] = amount+1 - changed = true - elsif operator==">=" - params[0] = 7 # gold - params[2] = 0 - params[1] = amount - changed = true - end - elsif script[itemBallRE] && i>0 # Contains pbItemBall after another command - # Using pbItemBall on non-item events, change it - list[i].parameters[1] = script.sub(/pbItemBall/,"pbReceiveItem") - changed = true - elsif script[/^\s*(Kernel\.)?(pbTrainerBattle|pbDoubleTrainerBattle)/] - # Check if trainer battle conditional branch is empty - j = i+1 - isempty = true - elseIndex = -1 - # Check if page is empty - while j=0 - list.insert(elseIndex+1, - RPG::EventCommand.new(115,list[i].indent+1,[]) # Exit Event Processing - ) - else - list.insert(i+1, - RPG::EventCommand.new(0,list[i].indent+1,[]), # Empty Event - RPG::EventCommand.new(411,list[i].indent,[]), # Else - RPG::EventCommand.new(115,list[i].indent+1,[]) # Exit Event Processing - ) - end - changed = true - end - end - end - end - i += 1 - end - end - return (changed) ? event : nil -end - -#=============================================================================== -# Convert events used as counters into proper counters. -#=============================================================================== -# Checks if the event has just 1 page, which has no conditions and no commands -# and whose movement type is "Fixed". -def isPlainEvent?(event) - return false unless event - return false if event.pages.length>1 - return false if event.pages[0].move_type!=0 - return false if event.pages[0].condition.switch1_valid || - event.pages[0].condition.switch2_valid || - event.pages[0].condition.variable_valid || - event.pages[0].condition.self_switch_valid - return true if event.pages[0].list.length<=1 - return false -end - -# Checks if the event has just 1 page, which has no conditions and whose -# movement type is "Fixed". Then checks if there are no commands, or it looks -# like a simple Mart or a Poké Center nurse event. -def isPlainEventOrMart?(event) - return false unless event - return false if event.pages.length>1 - return false if event.pages[0].move_type!=0 - return false if event.pages[0].condition.switch1_valid || - event.pages[0].condition.switch2_valid || - event.pages[0].condition.variable_valid || - event.pages[0].condition.self_switch_valid - # No commands in the event - return true if event.pages[0].list.length<=1 - # pbPokemonMart events - return true if event.pages[0].list.length<=12 && - event.pages[0].graphic.character_name!="" && # Has charset - event.pages[0].list[0].code==355 && # First line is Script - event.pages[0].list[0].parameters[0][/^pbPokemonMart/] - # pbSetPokemonCenter events - return true if event.pages[0].list.length>8 && - event.pages[0].graphic.character_name!="" && # Has charset - event.pages[0].list[0].code==355 && # First line is Script - event.pages[0].list[0].parameters[0][/^pbSetPokemonCenter/] - return false -end - -# Given two events that are next to each other, decides whether otherEvent is -# likely to be a "counter event", i.e. is placed on a tile with the Counter -# flag, or is on a non-passable tile between two passable tiles (e.g. a desk) -# where one of those two tiles is occupied by thisEvent. -def isLikelyCounter?(thisEvent,otherEvent,mapID,mapData) - # Check whether otherEvent is on a counter tile - return true if mapData.isCounterTile?(mapID,otherEvent.x,otherEvent.y) - # Check whether otherEvent is between an event with a graphic (e.g. an NPC) - # and a spot where the player can be - yonderX = otherEvent.x + (otherEvent.x - thisEvent.x) - yonderY = otherEvent.y + (otherEvent.y - thisEvent.y) - return thisEvent.pages[0].graphic.character_name!="" && # Has charset - otherEvent.pages[0].graphic.character_name=="" && # Has no charset - otherEvent.pages[0].trigger==0 && # Action trigger - mapData.isPassable?(mapID,thisEvent.x,thisEvent.y) && - !mapData.isPassable?(mapID,otherEvent.x,otherEvent.y) && - mapData.isPassable?(mapID,yonderX,yonderY) -end - -# Checks all events in the given map to see if any look like they've been placed -# on a desk with an NPC behind it, where the event on the desk is the actual -# interaction with the NPC. In other words, it's not making proper use of the -# counter flag (which lets the player interact with an event on the other side -# of counter tiles). -# Any events found to be like this have their contents merged into the NPC event -# and the counter event itself is deleted. The tile below the counter event gets -# its counter flag set (if it isn't already). -def pbCheckCounters(map,mapID,mapData) - toDelete = [] - changed = false - for key in map.events.keys - event = map.events[key] - next if !isPlainEventOrMart?(event) - # Found an event that is empty or looks like a simple Mart or a Poké Center - # nurse. Check adjacent events to see if they are "counter events". - neighbors = [] - neighbors.push(mapData.getEventFromXY(mapID,event.x,event.y-1)) - neighbors.push(mapData.getEventFromXY(mapID,event.x,event.y+1)) - neighbors.push(mapData.getEventFromXY(mapID,event.x-1,event.y)) - neighbors.push(mapData.getEventFromXY(mapID,event.x+1,event.y)) - neighbors.compact! - for otherEvent in neighbors - next if isPlainEvent?(otherEvent) # Blank/cosmetic-only event - next if !isLikelyCounter?(event,otherEvent,mapID,mapData) - # Found an adjacent event that looks like it's supposed to be a counter. - # Set the counter flag of the tile beneath the counter event, copy the - # counter event's pages to the NPC event, and delete the counter event. - mapData.setCounterTile(mapID,otherEvent.x,otherEvent.y) - savedPage = event.pages[0] - event.pages = otherEvent.pages - applyPages(savedPage,event.pages) # Apply NPC's visuals to new event pages - toDelete.push(otherEvent.id) - changed = true - end - end - toDelete.each { |key| map.events.delete(key) } - return changed -end - -#=============================================================================== -# Main compiler method for events -#=============================================================================== -def pbCompileTrainerEvents(_mustcompile) - mapData = MapData.new - t = Time.now.to_i - Graphics.update - trainerChecker = TrainerChecker.new - for id in mapData.mapinfos.keys.sort - changed = false - map = mapData.getMap(id) - next if !map || !mapData.mapinfos[id] - Win32API.SetWindowText(_INTL("Processing map {1} ({2})",id,mapData.mapinfos[id].name)) - for key in map.events.keys - if Time.now.to_i-t>=5 - Graphics.update - t = Time.now.to_i - end - newevent = pbConvertToTrainerEvent(map.events[key],trainerChecker) - if newevent - map.events[key] = newevent; changed = true - end - newevent = pbConvertToItemEvent(map.events[key]) - if newevent - map.events[key] = newevent; changed = true - end - newevent = pbFixEventUse(map.events[key],id,mapData) - if newevent - map.events[key] = newevent; changed = true - end - end - if Time.now.to_i-t>=5 - Graphics.update - t = Time.now.to_i - end - changed = true if pbCheckCounters(map,id,mapData) - if changed - mapData.saveMap(id) - mapData.saveTilesets - end - end - changed = false - Graphics.update - commonEvents = pbLoadRxData("Data/CommonEvents") - Win32API.SetWindowText(_INTL("Processing common events")) - for key in 0...commonEvents.length - newevent = pbFixEventUse(commonEvents[key],0,mapData) - if newevent - commonEvents[key] = newevent; changed = true - end - end - if changed - if $RPGVX - save_data(commonEvents,"Data/CommonEvents.rvdata") - else - save_data(commonEvents,"Data/CommonEvents.rxdata") - end - end -end diff --git a/Data/Scripts/022_Compiler/003_Compiler_PBS.rb b/Data/Scripts/022_Compiler/003_Compiler_PBS.rb new file mode 100644 index 000000000..237b0ca4d --- /dev/null +++ b/Data/Scripts/022_Compiler/003_Compiler_PBS.rb @@ -0,0 +1,1517 @@ +class PBTrainers; end + +module Compiler + #============================================================================= + # Compile metadata + #============================================================================= + def compile_metadata + sections = [] + currentmap = -1 + pbCompilerEachCommentedLine("PBS/metadata.txt") { |line,lineno| + if line[/^\s*\[\s*(\d+)\s*\]\s*$/] + sectionname = $~[1] + if currentmap==0 + if sections[currentmap][Metadata::HOME]==nil + raise _INTL("The entry Home is required in metadata.txt section [{1}]",sectionname) + end + if sections[currentmap][Metadata::PLAYER_A]==nil + raise _INTL("The entry PlayerA is required in metadata.txt section [{1}]",sectionname) + end + end + currentmap = sectionname.to_i + sections[currentmap] = [] + else + if currentmap<0 + raise _INTL("Expected a section at the beginning of the file\r\n{1}",FileLineData.linereport) + end + if !line[/^\s*(\w+)\s*=\s*(.*)$/] + raise _INTL("Bad line syntax (expected syntax like XXX=YYY)\r\n{1}",FileLineData.linereport) + end + matchData = $~ + schema = nil + FileLineData.setSection(currentmap,matchData[1],matchData[2]) + if currentmap==0 + schema = Metadata::SCHEMA[matchData[1]] + else + schema = MapMetadata::SCHEMA[matchData[1]] + end + if schema + record = pbGetCsvRecord(matchData[2],lineno,schema) + sections[currentmap][schema[0]] = record + end + end + } + save_data(sections,"Data/metadata.dat") + end + + #============================================================================= + # Compile town map points + #============================================================================= + def compile_town_map + nonglobaltypes = { + "Name" => [0, "s"], + "Filename" => [1, "s"], + "Point" => [2, "uussUUUU"] + } + currentmap = -1 + rgnnames = [] + placenames = [] + placedescs = [] + sections = [] + pbCompilerEachCommentedLine("PBS/townmap.txt") { |line,lineno| + if line[/^\s*\[\s*(\d+)\s*\]\s*$/] + currentmap = $~[1].to_i + sections[currentmap] = [] + else + if currentmap<0 + raise _INTL("Expected a section at the beginning of the file\r\n{1}",FileLineData.linereport) + end + if !line[/^\s*(\w+)\s*=\s*(.*)$/] + raise _INTL("Bad line syntax (expected syntax like XXX=YYY)\r\n{1}",FileLineData.linereport) + end + settingname = $~[1] + schema = nonglobaltypes[settingname] + if schema + record = pbGetCsvRecord($~[2],lineno,schema) + if settingname=="Name" + rgnnames[currentmap] = record + elsif settingname=="Point" + placenames.push(record[2]) + placedescs.push(record[3]) + sections[currentmap][schema[0]] = [] if !sections[currentmap][schema[0]] + sections[currentmap][schema[0]].push(record) + else # Filename + sections[currentmap][schema[0]] = record + end + end + end + } + save_data(sections,"Data/town_map.dat") + MessageTypes.setMessages(MessageTypes::RegionNames,rgnnames) + MessageTypes.setMessagesAsHash(MessageTypes::PlaceNames,placenames) + MessageTypes.setMessagesAsHash(MessageTypes::PlaceDescriptions,placedescs) + end + + #============================================================================= + # Compile map connections + #============================================================================= + def compile_connections + records = [] + pbCompilerEachPreppedLine("PBS/connections.txt") { |line,lineno| + hashenum = { + "N" => "N","North" => "N", + "E" => "E","East" => "E", + "S" => "S","South" => "S", + "W" => "W","West" => "W" + } + record = [] + thisline = line.dup + record.push(csvInt!(thisline,lineno)) + record.push(csvEnumFieldOrInt!(thisline,hashenum,"",sprintf("(line %d)",lineno))) + record.push(csvInt!(thisline,lineno)) + record.push(csvInt!(thisline,lineno)) + record.push(csvEnumFieldOrInt!(thisline,hashenum,"",sprintf("(line %d)",lineno))) + record.push(csvInt!(thisline,lineno)) + if !pbRgssExists?(sprintf("Data/Map%03d.rxdata",record[0])) && + !pbRgssExists?(sprintf("Data/Map%03d.rvdata",record[0])) + print _INTL("Warning: Map {1}, as mentioned in the map connection data, was not found.\r\n{2}",record[0],FileLineData.linereport) + end + if !pbRgssExists?(sprintf("Data/Map%03d.rxdata",record[3])) && + !pbRgssExists?(sprintf("Data/Map%03d.rvdata",record[3])) + print _INTL("Warning: Map {1}, as mentioned in the map connection data, was not found.\r\n{2}",record[3],FileLineData.linereport) + end + case record[1] + when "N"; raise _INTL("North side of first map must connect with south side of second map\r\n{1}",FileLineData.linereport) if record[4]!="S" + when "S"; raise _INTL("South side of first map must connect with north side of second map\r\n{1}",FileLineData.linereport) if record[4]!="N" + when "E"; raise _INTL("East side of first map must connect with west side of second map\r\n{1}",FileLineData.linereport) if record[4]!="W" + when "W"; raise _INTL("West side of first map must connect with east side of second map\r\n{1}",FileLineData.linereport) if record[4]!="E" + end + records.push(record) + } + save_data(records,"Data/map_connections.dat") + Graphics.update + end + + #============================================================================= + # Compile berry plants + #============================================================================= + def compile_berry_plants + sections = [] + if File.exists?("PBS/berryplants.txt") + pbCompilerEachCommentedLine("PBS/berryplants.txt") { |line,_lineno| + if line[ /^\s*(\w+)\s*=\s*(.*)$/ ] + key = $1 + value = $2 + value = value.split(",") + for i in 0...value.length + value[i].sub!(/^\s*/,"") + value[i].sub!(/\s*$/,"") + value[i] = value[i].to_i + end + item = parseItem(key) + sections[item] = value + end + } + end + save_data(sections,"Data/berry_plants.dat") + end + + #============================================================================= + # Compile phone messages + #============================================================================= + def compile_phone + return if !safeExists?("PBS/phone.txt") + database = PhoneDatabase.new + sections = [] + File.open("PBS/phone.txt","rb") { |f| + pbEachSection(f) { |section,name| + case name + when "" + database.generics=section + sections.concat(section) + when "" + database.battleRequests=section + sections.concat(section) + when "" + database.greetingsMorning=section + sections.concat(section) + when "" + database.greetingsEvening=section + sections.concat(section) + when "" + database.greetings=section + sections.concat(section) + when "" + database.bodies1=section + sections.concat(section) + when "" + database.bodies2=section + sections.concat(section) + end + } + } + MessageTypes.setMessagesAsHash(MessageTypes::PhoneMessages,sections) + save_data(database,"Data/phone.dat") + end + + #============================================================================= + # Compile types + #============================================================================= + def compile_types + typechart = [] + types = [] + requiredtypes = { + "Name" => [1, "s"], + "InternalName" => [2, "s"], + } + optionaltypes = { + "IsPseudoType" => [3, "b"], + "IsSpecialType" => [4, "b"], + "Weaknesses" => [5, "*s"], + "Resistances" => [6, "*s"], + "Immunities" => [7, "*s"] + } + currentmap = -1 + foundtypes = [] + pbCompilerEachCommentedLine("PBS/types.txt") { |line,lineno| + if line[/^\s*\[\s*(\d+)\s*\]\s*$/] + sectionname = $~[1] + if currentmap>=0 + for reqtype in requiredtypes.keys + if !foundtypes.include?(reqtype) + raise _INTL("Required value '{1}' not given in section '{2}'\r\n{3}",reqtype,currentmap,FileLineData.linereport) + end + end + foundtypes.clear + end + currentmap = sectionname.to_i + types[currentmap] = [currentmap,nil,nil,false,false,[],[],[]] + else + if currentmap<0 + raise _INTL("Expected a section at the beginning of the file\r\n{1}",FileLineData.linereport) + end + if !line[/^\s*(\w+)\s*=\s*(.*)$/] + raise _INTL("Bad line syntax (expected syntax like XXX=YYY)\r\n{1}",FileLineData.linereport) + end + matchData = $~ + schema = nil + FileLineData.setSection(currentmap,matchData[1],matchData[2]) + if requiredtypes.keys.include?(matchData[1]) + schema = requiredtypes[matchData[1]] + foundtypes.push(matchData[1]) + else + schema = optionaltypes[matchData[1]] + end + if schema + record = pbGetCsvRecord(matchData[2],lineno,schema) + types[currentmap][schema[0]] = record + end + end + } + types.compact! + maxValue = 0 + for type in types; maxValue = [maxValue,type[0]].max; end + pseudotypes = [] + specialtypes = [] + typenames = [] + typeinames = [] + typehash = {} + for type in types + pseudotypes.push(type[0]) if type[3] + typenames[type[0]] = type[1] + typeinames[type[0]] = type[2] + typehash[type[0]] = type + end + for type in types + n = type[1] + for w in type[5] + if !typeinames.include?(w) + raise _INTL("'{1}' is not a defined type (PBS/types.txt, {2}, Weaknesses)",w,n) + end + end + for w in type[6] + if !typeinames.include?(w) + raise _INTL("'{1}' is not a defined type (PBS/types.txt, {2}, Resistances)",w,n) + end + end + for w in type[7] + if !typeinames.include?(w) + raise _INTL("'{1}' is not a defined type (PBS/types.txt, {2}, Immunities)",w,n) + end + end + end + for i in 0..maxValue + pseudotypes.push(i) if !typehash[i] + end + pseudotypes.sort! + types.each { |type| specialtypes.push(type[0]) if type[4] } + specialtypes.sort! + count = maxValue+1 + for i in 0...count + type = typehash[i] + j = 0; k = i + while j>3 + curpos = 0 + numrec.times do + file.pos = curpos + offset = file.fgetdw + length = file.fgetdw + record = SerialRecords::SerialRecord.decode(file,offset,length) + ret[record[0]] = record + curpos += 8 + end + } + return ret + end +=end + + def compile_items + records = [] + constants = "" + itemnames = [] + itempluralnames = [] + itemdescs = [] + maxValue = 0 + pbCompilerEachCommentedLine("PBS/items.txt") { |line,lineno| + linerecord = pbGetCsvRecord(line,lineno,[0,"vnssuusuuUN"]) + id = linerecord[0] + record = [] + record[ItemData::ID] = id + constant = linerecord[1] + constants += "#{constant}=#{id}\r\n" + record[ItemData::NAME] = linerecord[2] + itemnames[id] = linerecord[2] + record[ItemData::NAME_PLURAL] = linerecord[3] + itempluralnames[id] = linerecord[3] + record[ItemData::POCKET] = linerecord[4] + record[ItemData::PRICE] = linerecord[5] + record[ItemData::DESCRIPTION] = linerecord[6] + itemdescs[id] = linerecord[6] + record[ItemData::FIELD_USE] = linerecord[7] + record[ItemData::BATTLE_USE] = linerecord[8] + record[ItemData::TYPE] = linerecord[9] + if record[ItemData::TYPE]!="" && linerecord[10] + record[ItemData::MOVE] = parseMove(linerecord[10]) + else + record[ItemData::MOVE] = 0 + end + maxValue = [maxValue,id].max + records[id] = record + } + MessageTypes.setMessages(MessageTypes::Items,itemnames) + MessageTypes.setMessages(MessageTypes::ItemPlurals,itempluralnames) + MessageTypes.setMessages(MessageTypes::ItemDescriptions,itemdescs) + save_data(records,"Data/items.dat") + code = "class PBItems\r\n" + code += constants + code += "def self.getName(id)\r\n" + code += "id=getID(PBItems,id)\r\n" + code += "return pbGetMessage(MessageTypes::Items,id); end\r\n" + code += "def self.getNamePlural(id)\r\n" + code += "id=getID(PBItems,id)\r\n" + code += "return pbGetMessage(MessageTypes::ItemPlurals,id); end\r\n" + code += "def self.getCount; return #{records.length}; end\r\n" + code += "def self.maxValue; return #{maxValue}; end\r\n" + code += "end\r\n" + eval(code, TOPLEVEL_BINDING) + pbAddScript(code,"PBItems") + Graphics.update + end + + #============================================================================= + # Compile move data + #============================================================================= + def compile_moves + records = [] + moveNames = [] + moveDescs = [] + maxValue = 0 + count = 0 + pbCompilerEachPreppedLine("PBS/moves.txt") { |line,lineno| + record = [] + lineRecord = pbGetCsvRecord(line,lineno,[0,"vnssueeuuuyiss", + nil,nil,nil,nil,nil,PBTypes,["Physical","Special","Status"], + nil,nil,nil,PBTargets,nil,nil,nil + ]) + if lineRecord[6]==2 && lineRecord[4]!=0 + raise _INTL("Status moves must have a base damage of 0, use either Physical or Special\r\n{1}",FileLineData.linereport) + end + if lineRecord[6]!=2 && lineRecord[4]==0 + print _INTL("Warning: Physical and special moves can't have a base damage of 0, changing to a Status move\r\n{1}",FileLineData.linereport) + lineRecord[6] = 2 + end + record[MoveData::ID] = lineRecord[0] + record[MoveData::INTERNAL_NAME] = lineRecord[1] + record[MoveData::NAME] = lineRecord[2] + record[MoveData::FUNCTION_CODE] = lineRecord[3] + record[MoveData::BASE_DAMAGE] = lineRecord[4] + record[MoveData::TYPE] = lineRecord[5] + record[MoveData::CATEGORY] = lineRecord[6] + record[MoveData::ACCURACY] = lineRecord[7] + record[MoveData::TOTAL_PP] = lineRecord[8] + record[MoveData::EFFECT_CHANCE] = lineRecord[9] + record[MoveData::TARGET] = lineRecord[10] + record[MoveData::PRIORITY] = lineRecord[11] + record[MoveData::FLAGS] = lineRecord[12] + record[MoveData::DESCRIPTION] = lineRecord[13] + maxValue = [maxValue,lineRecord[0]].max + count += 1 + moveNames[lineRecord[0]] = lineRecord[2] # Name + moveDescs[lineRecord[0]] = lineRecord[13] # Description + records[lineRecord[0]] = record + } + save_data(records,"Data/moves.dat") + MessageTypes.setMessages(MessageTypes::Moves,moveNames) + MessageTypes.setMessages(MessageTypes::MoveDescriptions,moveDescs) + code = "class PBMoves\r\n" + for rec in records + code += "#{rec[MoveData::INTERNAL_NAME]}=#{rec[MoveData::ID]}\r\n" if rec + end + code += "def self.getName(id)\r\n" + code += "id=getID(PBMoves,id)\r\n" + code += "return pbGetMessage(MessageTypes::Moves,id); end\r\n" + code += "def self.getCount; return #{count}; end\r\n" + code += "def self.maxValue; return #{maxValue}; end\r\n" + code += "end\r\n" + eval(code, TOPLEVEL_BINDING) + pbAddScript(code,"PBMoves") + end + + #============================================================================= + # Compile battle animations + #============================================================================= + def compile_animations + begin + if $RPGVX + pbanims = load_data("Data/PkmnAnimations.rvdata") + else + pbanims = load_data("Data/PkmnAnimations.rxdata") + end + rescue + pbanims = PBAnimations.new + end + move2anim = [[],[]] +=begin + if $RPGVX + anims = load_data("Data/Animations.rvdata") + else + anims = load_data("Data/Animations.rxdata") + end + for anim in anims + next if !anim || anim.frames.length==1 + found = false + for i in 0...pbanims.length + if pbanims[i] && pbanims[i].id==anim.id + found = true if pbanims[i].array.length>1 + break + end + end + pbanims[anim.id] = pbConvertRPGAnimation(anim) if !found + end +=end + for i in 0...pbanims.length + next if !pbanims[i] + if pbanims[i].name[/^OppMove\:\s*(.*)$/] + if hasConst?(PBMoves,$~[1]) + moveid = PBMoves.const_get($~[1]) + move2anim[1][moveid] = i + end + elsif pbanims[i].name[/^Move\:\s*(.*)$/] + if hasConst?(PBMoves,$~[1]) + moveid = PBMoves.const_get($~[1]) + move2anim[0][moveid] = i + end + end + end + save_data(move2anim,"Data/move2anim.dat") + save_data(pbanims,"Data/PkmnAnimations.rxdata") + end + + #============================================================================= + # Compile Pokémon + #============================================================================= + def compile_pokemon + # Get schemas. + requiredValues = SpeciesData.requiredValues + optionalValues = SpeciesData.optionalValues + # Prepare arrays for compiled data. + speciesData = [] + movesets = [] + eggMoves = [] + regionalDexes = [] + spriteMetrics = [] + evolutions = [] + speciesNames = [] + formNames = [] + pokedexKinds = [] + pokedexEntries = [] + # Prepare variables used to record scripted constants. + constants = "" + maxValue = 0 # Highest species ID + # Read from PBS file. + File.open("PBS/pokemon.txt","rb") { |f| + FileLineData.file = "PBS/pokemon.txt" # For error reporting + # Read a whole section's lines at once, then run through this code. + # contents is a hash containing all the XXX=YYY lines in that section, where + # the keys are the XXX and the values are the YYY (as unprocessed strings). + pbEachFileSection(f) { |contents,speciesID| + # Create array to store compiled data in. + speciesData[speciesID] = [] + # Copy Type1 into Type2 if Type2 is undefined. (All species must have two + # defined types; if both are the same, it is treated as single typed.) + if !contents["Type2"] || contents["Type2"]=="" + if !contents["Type1"] || contents["Type1"]=="" + raise _INTL("No Pokémon type is defined in section {1} (PBS/pokemon.txt)",speciesID.to_s) + end + contents["Type2"] = contents["Type1"].clone + end + # Go through hashes of compilable data and compile this section. + [requiredValues,optionalValues].each do |hash| + for key in hash.keys + FileLineData.setSection(speciesID,key,contents[key]) # For error reporting + maxValue = [maxValue,speciesID].max # Set highest species ID + next if hash[key][0]<0 # Property is not to be compiled; skip it + # Raise an error if the species ID is 0. + if speciesID==0 + raise _INTL("A Pokémon species can't be numbered 0 (PBS/pokemon.txt)") + end + # Skip empty optional properties, or raise an error if a required + # property is empty. + if !contents[key] || contents[key]=="" + raise _INTL("Required entry {1} is missing or empty in section {2} (PBS/pokemon.txt)", + key,speciesID.to_s) if hash==requiredValues + next + end + # Compile value for key. + schema = hash[key] + value = pbGetCsvRecord(contents[key],key,schema) + # Modify value as required. + case key + when "Height", "Weight" + # Convert height/weight to 1 decimal place and multiply by 10. + value = (value*10).round + if value<=0 + raise _INTL("Value for '{1}' can't be less than or close to 0 (section {2}, PBS/pokemon.txt)",key,speciesID) + end + end + # Add value to appropriate array for saving. + case key + when "Moves" + speciesMoves = [] + for i in 0...value.length/2 + speciesMoves.push([value[i*2],value[i*2+1],i]) + end + speciesMoves.sort! { |a,b| (a[0]==b[0]) ? a[2]<=>b[2] : a[0]<=>b[0] } + for i in speciesMoves; i.pop; end + movesets[speciesID] = speciesMoves + when "EggMoves" + if value.is_a?(Array); eggMoves[speciesID] = value + else; eggMoves[speciesID] = [value] + end + when "RegionalNumbers" + if value.is_a?(Array) + value.each_with_index do |num,dexID| + regionalDexes[dexID] = [] if !regionalDexes[dexID] + regionalDexes[dexID][speciesID] = num + end + else + regionalDexes[0] = [] if !regionalDexes[0] + regionalDexes[0][speciesID] = value + end + when "BattlerPlayerX", "BattlerPlayerY", + "BattlerEnemyX", "BattlerEnemyY", + "BattlerAltitude", "BattlerShadowX", "BattlerShadowSize" + spriteMetrics[schema[0]] = [] if !spriteMetrics[schema[0]] + spriteMetrics[schema[0]][speciesID] = value + when "Evolutions" + speciesEvolutions = [] + for i in 0...value.length/3 + speciesEvolutions.push([value[i*3],value[i*3+1],value[i*3+2],false]) + end + evolutions[speciesID] = speciesEvolutions + when "Name" + speciesNames[speciesID] = value + when "FormName" + formNames[speciesID] = value + when "Kind" + pokedexKinds[speciesID] = value + when "Pokedex" + pokedexEntries[speciesID] = value + when "InternalName" + constants += "#{value}=#{speciesID}\r\n" + else # All other data + speciesData[speciesID][schema[0]] = value + end + end + end + } + } + # All data is compiled now, just need to save it. + raise _INTL("No Pokémon species are defined (PBS/pokemon.txt)") if speciesData.length==0 + # Write all constants and some helpful code for PBSpecies. + count = speciesData.compact.length + code = "module PBSpecies\r\n#{constants}" + code += "def PBSpecies.getName(id)\r\n" + code += "id=getID(PBSpecies,id)\r\n" + code += "return pbGetMessage(MessageTypes::Species,id); end\r\n" + code += "def PBSpecies.getCount; return #{count}; end\r\n" + code += "def PBSpecies.maxValue; return #{maxValue}; end\r\n" + code += "end\r\n" + eval(code, TOPLEVEL_BINDING) + pbAddScript(code,"PBSpecies") + # Save main species data. + save_data(speciesData,"Data/species.dat") + # Save movesets data. + save_data(movesets,"Data/species_movesets.dat") + # Save egg moves data. + save_data(eggMoves,"Data/species_eggmoves.dat") + # Save regional dexes data. + save_data(regionalDexes,"Data/regional_dexes.dat") + # Save metrics data. + for i in 0...7 + defaultValue = (i==SpeciesData::METRIC_SHADOW_SIZE) ? 2 : 0 # Shadow size 2, other metrics 0 + for j in 0..maxValue + spriteMetrics[i] = [] if !spriteMetrics[i] + spriteMetrics[i][j] ||= defaultValue + end + end + save_data(spriteMetrics,"Data/species_metrics.dat") + # Evaluate evolution data (has to be done after all species are read). + for e in 0...evolutions.length + next if !evolutions[e] + evolutions[e].each_with_index do |evo,i| + FileLineData.setSection(i,"Evolutions","") + evo[0] = csvEnumField!(evo[0],PBSpecies,"Evolutions",i) # Species + param_type = PBEvolution.getFunction(evo[1], "parameterType") + if param_type + evo[2] = csvEnumField!(evo[2], param_type, "Evolutions", i) + else + evo[2] = csvInt!(evo[2]) if evo[2] && evo[2] != "" + end + end + end + # Add prevolution data to all species as the first "evolution method". + for sp in 1..maxValue + preSpecies = -1 + evoData = nil + # Check for another species that evolves into sp. + for f in 0...evolutions.length + next if !evolutions[f] || f==sp + evolutions[f].each do |evo| + next if evo[0]!=sp || evo[3] # Evolved species isn't sp or is a prevolution + preSpecies = f # f evolves into sp + evoData = evo + break + end + break if evoData + end + next if !evoData # evoData[1]=method, evoData[2]=level - both are unused + # Found a species that evolves into e, record it as a prevolution. + evolutions[sp] = [] if !evolutions[sp] + evolutions[sp] = [[preSpecies,evoData[1],evoData[2],true]].concat(evolutions[sp]) + end + # Save evolutions data. + save_data(evolutions,"Data/species_evolutions.dat") + # Save all messages. + speciesNames.map! { |name| name || "????????" } + MessageTypes.setMessages(MessageTypes::Species,speciesNames) + MessageTypes.setMessages(MessageTypes::FormNames,formNames) + MessageTypes.setMessages(MessageTypes::Kinds,pokedexKinds) + MessageTypes.setMessages(MessageTypes::Entries,pokedexEntries) + end + + #============================================================================= + # Compile Pokémon forms + #============================================================================= + def compile_pokemon_forms + # Get schemas. + requiredValues = SpeciesData.requiredValues(true) + optionalValues = SpeciesData.optionalValues(true) + # Prepare arrays for compiled data. + speciesData = pbLoadSpeciesData + movesets = [] + eggMoves = [] + spriteMetrics = [] + evolutions = [] + formNames = [] + pokedexKinds = [] + pokedexEntries = [] + formToSpecies = [] # Saved + speciesToForm = [] # Only used in this method + for i in 1..PBSpecies.maxValue + formToSpecies[i] = [i] + speciesToForm[i] = i + end + # Prepare variables used to record scripted constants. + constants = "" + maxValue = PBSpecies.maxValue # Highest species ID + # Read from PBS file. + File.open("PBS/pokemonforms.txt","rb") { |f| + FileLineData.file = "PBS/pokemonforms.txt" # For error reporting + # Read a whole section's lines at once, then run through this code. + # contents is a hash containing all the XXX=YYY lines in that section, where + # the keys are the XXX and the values are the YYY (as unprocessed strings). + pbEachFileSection2(f) { |contents,sectionName| + # Split sectionName into a species number and form number. + splitSectionName = sectionName.split(/[-,\s]/) + if splitSectionName.length!=2 + raise _INTL("Section name {1} is invalid (PBS/pokemonforms.txt). Expected syntax like [XXX,Y] (XXX=internal name, Y=form number).",sectionName) + end + baseSpeciesID = parseSpecies(splitSectionName[0]) + form = csvInt!(splitSectionName[1]) + # Ensure this is a valid form and not a duplicate. + if form==0 + raise _INTL("Form {1} is invalid (PBS/pokemonforms.txt). Form 0 data should be defined in \"PBS/pokemon.txt\".",sectionName) + end + if formToSpecies[baseSpeciesID] && formToSpecies[baseSpeciesID][form] + raise _INTL("Form {1} is defined at least twice (PBS/pokemonforms.txt). It should only be defined once.",sectionName) + end + # Record new species number in formToSpecies. + speciesID = baseSpeciesID + if form>0 + maxValue += 1 + speciesID = maxValue + formToSpecies[baseSpeciesID] = [] if !formToSpecies[baseSpeciesID] + formToSpecies[baseSpeciesID][form] = speciesID + speciesToForm[speciesID] = baseSpeciesID + end + # Generate internal name for this form. + cName = getConstantName(PBSpecies,baseSpeciesID).to_s+"_"+form.to_s + constants += "#{cName}=#{speciesID}\r\n" + # Create array to store compiled data in. + speciesData[speciesID] = [] + # Clone data from base form as a starting point. + speciesData[baseSpeciesID].each_with_index do |val,i| + speciesData[speciesID][i] = (val.is_a?(Array)) ? val.clone : val + end + # Copy Type1 into Type2 if if Type1 is defined but Type2 isn't. (Shouldn't + # inherit either of the base form's types if Type1 is defined for a form.) + if contents["Type1"] && contents["Type1"]!="" + if !contents["Type2"] || contents["Type2"]=="" + contents["Type2"] = contents["Type1"].clone + end + end + # If any held item is defined for this form, clear default data for all + # three held items. + if (contents["WildItemCommon"] && contents["WildItemCommon"]!="") || + (contents["WildItemUncommon"] && contents["WildItemUncommon"]!="") || + (contents["WildItemRare"] && contents["WildItemRare"]!="") + speciesData[speciesID][SpeciesData::WILD_ITEM_COMMON] = nil + speciesData[speciesID][SpeciesData::WILD_ITEM_UNCOMMON] = nil + speciesData[speciesID][SpeciesData::WILD_ITEM_RARE] = nil + end + # Go through hashes of compilable data and compile this section. + [requiredValues,optionalValues].each do |hash| + for key in hash.keys + FileLineData.setSection(speciesID,key,contents[key]) # For error reporting + next if hash[key][0]<0 # Property is not to be compiled; skip it + # Skip empty properties (none are required). + next if !contents[key] || contents[key]=="" + # Compile value for key. + schema = hash[key] + value = pbGetCsvRecord(contents[key],key,schema) + # Modify value as required. + case key + when "Height", "Weight" + # Convert height/weight to 1 decimal place and multiply by 10. + value = (value*10).round + if value<=0 + raise _INTL("Value for '{1}' can't be less than or close to 0 (section {2}, PBS/pokemonforms.txt)",key,speciesID) + end + end + # Add value to appropriate array for saving. + case key + when "Moves" + speciesMoves = [] + for i in 0...value.length/2 + speciesMoves.push([value[i*2],value[i*2+1],i]) + end + speciesMoves.sort! { |a,b| (a[0]==b[0]) ? a[2]<=>b[2] : a[0]<=>b[0] } + for i in speciesMoves; i.pop; end + movesets[speciesID] = speciesMoves + when "EggMoves" + if value.is_a?(Array); eggMoves[speciesID] = value + else; eggMoves[speciesID] = [value] + end + when "BattlerPlayerX", "BattlerPlayerY", + "BattlerEnemyX", "BattlerEnemyY", + "BattlerAltitude", "BattlerShadowX", "BattlerShadowSize" + spriteMetrics[schema[0]] = [] if !spriteMetrics[schema[0]] + spriteMetrics[schema[0]][speciesID] = value + when "Evolutions" + speciesEvolutions = [] + for i in 0...value.length/3 + speciesEvolutions.push([value[i*3],value[i*3+1],value[i*3+2],false]) + end + evolutions[speciesID] = speciesEvolutions + when "FormName" + formNames[speciesID] = value + when "Kind" + pokedexKinds[speciesID] = value + when "Pokedex" + pokedexEntries[speciesID] = value + else # All other data + speciesData[speciesID][schema[0]] = value + end + end + end + } + } + # All data is compiled now, just need to save it. + # Write all constants and some helpful code for PBSpecies. + code = "module PBSpecies\r\n#{constants}" + code += "def PBSpecies.maxValueF; return #{maxValue}; end\r\n" + code += "end\r\n" + eval(code, TOPLEVEL_BINDING) + pbAddScript(code,"PBSpecies") + # Save main species data. + save_data(speciesData,"Data/species.dat") + # Save conversions of form to species data. + save_data(formToSpecies,"Data/form2species.dat") + # Inherit base form moveset. + newMovesets = pbLoadMovesetsData + append_to_base_form_data(PBSpecies.maxValue+1,maxValue,newMovesets,movesets,speciesToForm,true) + save_data(newMovesets,"Data/species_movesets.dat") + $PokemonTemp.speciesMovesets = nil if $PokemonTemp + # Inherit base form egg moves. + newEggMoves = pbLoadEggMovesData + append_to_base_form_data(PBSpecies.maxValue+1,maxValue,newEggMoves,eggMoves,speciesToForm,false) + save_data(newEggMoves,"Data/species_eggmoves.dat") + $PokemonTemp.speciesEggMoves = nil if $PokemonTemp + # Inherit base form metrics data. + newSpriteMetrics = pbLoadSpeciesMetrics + for i in 0...7 + defaultValue = (i==SpeciesData::METRIC_SHADOW_SIZE) ? 2 : 0 # Shadow size 2, other metrics 0 + append_to_base_form_data(PBSpecies.maxValue+1,maxValue,newSpriteMetrics[i], + spriteMetrics[i] || [],speciesToForm,false,defaultValue) + end + save_data(newSpriteMetrics,"Data/species_metrics.dat") + # Evaluate evolution data (has to be done after all species are read). + for e in 0...evolutions.length + next if !evolutions[e] + evolutions[e].each_with_index do |evo,i| + FileLineData.setSection(i,"Evolutions","") + evo[0] = csvEnumField!(evo[0],PBSpecies,"Evolutions",i) # Species + param_type = PBEvolution.getFunction(evo[1], "parameterType") + if param_type + evo[2] = csvEnumField!(evo[2], param_type, "Evolutions", i) + else + evo[2] = csvPosInt!(evo[2]) if evo[2] && evo[2] != "" + end + end + end + # Inherit base form evolution methods. + newEvolutions = pbLoadEvolutionsData + append_to_base_form_data(PBSpecies.maxValue+1,maxValue,newEvolutions,evolutions,speciesToForm,true) + # Add prevolution data to all species as the first "evolution method". + for i in (PBSpecies.maxValue+1)..maxValue + baseSpecies = speciesToForm[i] + preSpecies = -1 + evoData = nil + # Check for another species that evolves into baseSpecies. + for f in 0...newEvolutions.length + next if !newEvolutions[f] || speciesToForm[f]==baseSpecies + newEvolutions[f].each do |evo| + next if evo[0]!=baseSpecies || evo[3] # Evolved species isn't baseSpecies or is a prevolution + preSpecies = speciesToForm[f] # f evolves into baseSpecies + evoData = evo + break + end + break if evoData + end + next if !evoData # evoData[1]=method, evoData[2]=level - both are unused + # Found a species that evolves into e, record it as a prevolution. + if newEvolutions[i] + newEvolutions[i] = [[preSpecies,evoData[1],evoData[2],true]].concat(newEvolutions[i]) + else + newEvolutions[i] = [[preSpecies,evoData[1],evoData[2],true]] + end + end + # Save evolutions data. + save_data(newEvolutions,"Data/species_evolutions.dat") + $PokemonTemp.evolutionsData = nil if $PokemonTemp + # Save all messages. + MessageTypes.addMessages(MessageTypes::FormNames,formNames) + MessageTypes.addMessages(MessageTypes::Kinds,pokedexKinds) + MessageTypes.addMessages(MessageTypes::Entries,pokedexEntries) + end + + def append_to_base_form_data(idxStart,idxEnd,baseData,extraData,speciesToForm,clone=false,defaultValue=nil) + for i in idxStart..idxEnd + if extraData[i] + baseData[i] = extraData[i] + else + species = speciesToForm[i] + if baseData[species] + if clone + baseData[i] = [] + baseData[species].each { |datum| baseData[i].push(datum.clone) } + elsif baseData[species].is_a?(Array) + baseData[i] = baseData[species].clone + else + baseData[i] = baseData[species] + end + else + baseData[i] = defaultValue + end + end + end + end + + #============================================================================= + # Compile TM/TM/Move Tutor compatibilities + #============================================================================= + def compile_move_compatibilities + lineno = 1 + havesection = false + sectionname = nil + sections = [] + if safeExists?("PBS/tm.txt") + f = File.open("PBS/tm.txt","rb") + FileLineData.file = "PBS/tm.txt" + f.each_line { |line| + if lineno==1 && line[0]==0xEF && line[1]==0xBB && line[2]==0xBF + line = line[3,line.length-3] + end + FileLineData.setLine(line,lineno) + if !line[/^\#/] && !line[/^\s*$/] + if line[/^\s*\[\s*(.*)\s*\]\s*$/] + sectionname = parseMove($~[1]) + sections[sectionname] = WordArray.new + havesection = true + else + if sectionname==nil + raise _INTL("Expected a section at the beginning of the file. This error may also occur if the file was not saved in UTF-8.\r\n{1}", + FileLineData.linereport) + end + specieslist = line.sub(/\s+$/,"").split(",") + for species in specieslist + next if !species || species=="" + sec = sections[sectionname] + sec[sec.length] = parseSpecies(species) + end + end + end + lineno += 1 + Graphics.update if lineno%50==0 + Win32API.SetWindowText(_INTL("Processing {1} line {2}",FileLineData.file,lineno)) if lineno%50==0 + } + f.close + end + save_data(sections,"Data/tm.dat") + end + + #============================================================================= + # Compile Shadow movesets + #============================================================================= + def compile_shadow_movesets + sections = [] + if File.exists?("PBS/shadowmoves.txt") + pbCompilerEachCommentedLine("PBS/shadowmoves.txt") { |line,_lineno| + if line[ /^\s*(\w+)\s*=\s*(.*)$/ ] + key = $1 + value = $2 + value = value.split(",") + species = parseSpecies(key) + moves = [] + for i in 0...[4,value.length].min + moves.push((parseMove(value[i]) rescue nil)) + end + moves.compact! + sections[species] = moves if moves.length>0 + end + } + end + save_data(sections,"Data/shadow_movesets.dat") + end + + #============================================================================= + # Compile wild encounters + #============================================================================= + def compile_encounters + lines = [] + linenos = [] + FileLineData.file = "PBS/encounters.txt" + File.open("PBS/encounters.txt","rb") { |f| + lineno = 1 + f.each_line { |line| + if lineno==1 && line[0]==0xEF && line[1]==0xBB && line[2]==0xBF + line = line[3,line.length-3] + end + line = prepline(line) + if line.length!=0 + lines[lines.length] = line + linenos[linenos.length] = lineno + end + lineno += 1 + } + } + encounters = {} + thisenc = nil + needdensity = false + lastmapid = -1 + i = 0 + while i=0 + needdensity = false + enclines = EncounterTypes::EnctypeChances[enc].length + encarray = [] + j = i+1; k = 0 + while jmaxlevel + raise _INTL("Level number is not valid: {1}\r\n{2}",splitarr[1],FileLineData.linereport) + end + if splitarr[2]<=0 || splitarr[2]>maxlevel + raise _INTL("Level number is not valid: {1}\r\n{2}",splitarr[2],FileLineData.linereport) + end + if splitarr[1]>splitarr[2] + raise _INTL("Minimum level is greater than maximum level: {1}\r\n{2}",line,FileLineData.linereport) + end + splitarr[0] = parseSpecies(splitarr[0]) + encarray.push(splitarr) + thisenc[1][enc] = encarray + j += 1; k += 1 + end + if j==lines.length && k=3 + for j in 0...EncounterTypes::EnctypeChances.length + next if !EncounterTypes::EnctypeChances[j] || + EncounterTypes::EnctypeChances[j].length==0 + next if EncounterTypes::EnctypeCompileDens[j]==0 + thisenc[0][j] = nums[EncounterTypes::EnctypeCompileDens[j]-1].to_i + end + else + raise _INTL("Wrong syntax for densities in encounters.txt; got \"{1}\"\r\n{2}",line,FileLineData.linereport) + end + i += 1 + else + raise _INTL("Undefined encounter type {1}, expected one of the following:\r\n{2}\r\n{3}",line,EncounterTypes::Names.inspect,FileLineData.linereport) + end + end + save_data(encounters,"Data/encounters.dat") + end + + #============================================================================= + # Compile trainer types + #============================================================================= + def compile_trainer_types + records = [] + trainernames = [] + maxValue = 0 + pbCompilerEachPreppedLine("PBS/trainertypes.txt") { |line,lineno| + record=pbGetCsvRecord(line,lineno,[0,"unsUSSSeUS", # ID can be 0 + nil,nil,nil,nil,nil,nil,nil,{ + "" => 2, + "Male" => 0,"M" => 0,"0" => 0, + "Female" => 1,"F" => 1,"1" => 1, + "Mixed" => 2,"X" => 2,"2" => 2 + },nil,nil] + ) + if records[record[0]] + raise _INTL("Two trainer types ({1} and {2}) have the same ID ({3}), which is not allowed.\r\n{4}", + records[record[0]][1],record[1],record[0],FileLineData.linereport) + end + trainernames[record[0]] = record[2] + records[record[0]] = record + maxValue = [maxValue,record[0]].max + } + count = records.compact.length + MessageTypes.setMessages(MessageTypes::TrainerTypes,trainernames) + code = "class PBTrainers\r\n" + for rec in records + next if !rec + code += "#{rec[1]}=#{rec[0]}\r\n" + end + code += "def self.getName(id)\r\n" + code += "id=getID(PBTrainers,id)\r\n" + code += "return pbGetMessage(MessageTypes::TrainerTypes,id); end\r\n" + code += "def self.getCount; return #{count}; end\r\n" + code += "def self.maxValue; return #{maxValue}; end\r\n" + code += "end\r\n" + eval(code, TOPLEVEL_BINDING) + pbAddScript(code,"PBTrainers") + save_data(records,"Data/trainer_types.dat") + end + + #============================================================================= + # Compile individual trainers + #============================================================================= + def compile_trainers + trainer_info_types = TrainerData::SCHEMA + mLevel = PBExperience.maxLevel + trainerindex = -1 + trainers = [] + trainernames = [] + trainerlosetext = [] + pokemonindex = -2 + oldcompilerline = 0 + oldcompilerlength = 0 + pbCompilerEachCommentedLine("PBS/trainers.txt") { |line,lineno| + if line[/^\s*\[\s*(.+)\s*\]\s*$/] + # Section [trainertype,trainername] or [trainertype,trainername,partyid] + if oldcompilerline>0 + raise _INTL("Previous trainer not defined with as many Pokémon as expected\r\n{1}",FileLineData.linereport) + end + if pokemonindex==-1 + raise _INTL("Started new trainer while previous trainer has no Pokémon\r\n{1}",FileLineData.linereport) + end + section = pbGetCsvRecord($~[1],lineno,[0,"esU",PBTrainers]) + trainerindex += 1 + trainertype = section[0] + trainername = section[1] + partyid = section[2] || 0 + trainers[trainerindex] = [trainertype,trainername,[],[],partyid,nil] + trainernames[trainerindex] = trainername + pokemonindex = -1 + elsif line[/^\s*(\w+)\s*=\s*(.*)$/] + # XXX=YYY lines + if trainerindex<0 + raise _INTL("Expected a section at the beginning of the file\r\n{1}",FileLineData.linereport) + end + if oldcompilerline>0 + raise _INTL("Previous trainer not defined with as many Pokémon as expected\r\n{1}",FileLineData.linereport) + end + settingname = $~[1] + schema = trainer_info_types[settingname] + next if !schema + record = pbGetCsvRecord($~[2],lineno,schema) + # Error checking in XXX=YYY lines + case settingname + when "Pokemon" + if record[1]>mLevel + raise _INTL("Bad level: {1} (must be 1-{2})\r\n{3}",record[1],mLevel,FileLineData.linereport) + end + when "Moves" + record = [record] if record.is_a?(Integer) + record.compact! + when "Ability" + if record>5 + raise _INTL("Bad ability flag: {1} (must be 0 or 1 or 2-5)\r\n{2}",record,FileLineData.linereport) + end + when "IV" + record = [record] if record.is_a?(Integer) + record.compact! + for i in record + next if i<=Pokemon::IV_STAT_LIMIT + raise _INTL("Bad IV: {1} (must be 0-{2})\r\n{3}", i, Pokemon::IV_STAT_LIMIT, FileLineData.linereport) + end + when "EV" + record = [record] if record.is_a?(Integer) + record.compact! + for i in record + next if i<=Pokemon::EV_STAT_LIMIT + raise _INTL("Bad EV: {1} (must be 0-{2})\r\n{3}", i, Pokemon::EV_STAT_LIMIT, FileLineData.linereport) + end + evtotal = 0 + for i in 0...6 + evtotal += (iPokemon::EV_LIMIT + raise _INTL("Total EVs are greater than allowed ({1})\r\n{2}", Pokemon::EV_LIMIT, FileLineData.linereport) + end + when "Happiness" + if record>255 + raise _INTL("Bad happiness: {1} (must be 0-255)\r\n{2}",record,FileLineData.linereport) + end + when "Name" + if record.length>Pokemon::MAX_NAME_SIZE + raise _INTL("Bad nickname: {1} (must be 1-{2} characters)\r\n{3}", record, Pokemon::MAX_NAME_SIZE, FileLineData.linereport) + end + end + # Record XXX=YYY setting + case settingname + when "Items" # Items in the trainer's Bag, not the held item + record = [record] if record.is_a?(Integer) + record.compact! + trainers[trainerindex][2] = record + when "LoseText" + trainerlosetext[trainerindex] = record + trainers[trainerindex][5] = record + when "Pokemon" + pokemonindex += 1 + trainers[trainerindex][3][pokemonindex] = [] + trainers[trainerindex][3][pokemonindex][TrainerData::SPECIES] = record[0] + trainers[trainerindex][3][pokemonindex][TrainerData::LEVEL] = record[1] + else + if pokemonindex<0 + raise _INTL("Pokémon hasn't been defined yet!\r\n{1}",FileLineData.linereport) + end + trainers[trainerindex][3][pokemonindex][schema[0]] = record + end + else + # Old compiler - backwards compatibility is SUCH fun! + if pokemonindex==-1 && oldcompilerline==0 + raise _INTL("Unexpected line format, started new trainer while previous trainer has no Pokémon\r\n{1}",FileLineData.linereport) + end + if oldcompilerline==0 # Started an old trainer section + oldcompilerlength = 3 + oldcompilerline = 0 + trainerindex += 1 + trainers[trainerindex] = [0,"",[],[],0] + pokemonindex = -1 + end + oldcompilerline += 1 + case oldcompilerline + when 1 # Trainer type + record = pbGetCsvRecord(line,lineno,[0,"e",PBTrainers]) + trainers[trainerindex][0] = record + when 2 # Trainer name, version number + record = pbGetCsvRecord(line,lineno,[0,"sU"]) + record = [record] if record.is_a?(Integer) + trainers[trainerindex][1] = record[0] + trainernames[trainerindex] = record[0] + trainers[trainerindex][4] = record[1] if record[1] + when 3 # Number of Pokémon, items + record = pbGetCsvRecord(line,lineno,[0,"vEEEEEEEE",nil,PBItems,PBItems, + PBItems,PBItems,PBItems,PBItems,PBItems,PBItems]) + record = [record] if record.is_a?(Integer) + record.compact! + oldcompilerlength += record[0] + record.shift + trainers[trainerindex][2] = record if record + else # Pokémon lines + pokemonindex += 1 + trainers[trainerindex][3][pokemonindex] = [] + record = pbGetCsvRecord(line,lineno, + [0,"evEEEEEUEUBEUUSBU",PBSpecies,nil, PBItems,PBMoves,PBMoves,PBMoves, + PBMoves,nil,{"M"=>0,"m"=>0,"Male"=>0,"male"=>0, + "0"=>0,"F"=>1,"f"=>1,"Female"=>1,"female"=>1, + "1"=>1},nil,nil,PBNatures,nil,nil,nil,nil,nil]) + # Error checking (the +3 is for properties after the four moves) + for i in 0...record.length + next if record[i]==nil + case i + when TrainerData::LEVEL + if record[i]>mLevel + raise _INTL("Bad level: {1} (must be 1-{2})\r\n{3}",record[i],mLevel,FileLineData.linereport) + end + when TrainerData::ABILITY+3 + if record[i]>5 + raise _INTL("Bad ability flag: {1} (must be 0 or 1 or 2-5)\r\n{2}",record[i],FileLineData.linereport) + end + when TrainerData::IV+3 + if record[i]>31 + raise _INTL("Bad IV: {1} (must be 0-31)\r\n{2}",record[i],FileLineData.linereport) + end + record[i] = [record[i]] + when TrainerData::EV+3 + if record[i]>Pokemon::EV_STAT_LIMIT + raise _INTL("Bad EV: {1} (must be 0-{2})\r\n{3}", record[i], Pokemon::EV_STAT_LIMIT, FileLineData.linereport) + end + record[i] = [record[i]] + when TrainerData::HAPPINESS+3 + if record[i]>255 + raise _INTL("Bad happiness: {1} (must be 0-255)\r\n{2}",record[i],FileLineData.linereport) + end + when TrainerData::NAME+3 + if record[i].length>Pokemon::MAX_NAME_SIZE + raise _INTL("Bad nickname: {1} (must be 1-{2} characters)\r\n{3}", record[i], Pokemon::MAX_NAME_SIZE, FileLineData.linereport) + end + end + end + # Write data to trainer array + for i in 0...record.length + next if record[i]==nil + if i>=TrainerData::MOVES && i=TrainerData::MOVES+4) ? i-3 : i + trainers[trainerindex][3][pokemonindex][d] = record[i] + end + end + end + oldcompilerline = 0 if oldcompilerline>=oldcompilerlength + end + } + save_data(trainers,"Data/trainers.dat") + MessageTypes.setMessagesAsHash(MessageTypes::TrainerNames,trainernames) + MessageTypes.setMessagesAsHash(MessageTypes::TrainerLoseText,trainerlosetext) + end + + #============================================================================= + # Compile Battle Tower and other Cups trainers/Pokémon + #============================================================================= + def compile_battle_tower_trainers(filename) + sections = [] + requiredtypes = { + "Type" => [0, "e",PBTrainers], + "Name" => [1, "s"], + "BeginSpeech" => [2, "s"], + "EndSpeechWin" => [3, "s"], + "EndSpeechLose" => [4, "s"], + "PokemonNos" => [5, "*u"] + } + trainernames = [] + beginspeech = [] + endspeechwin = [] + endspeechlose = [] + if safeExists?(filename) + File.open(filename,"rb") { |f| + FileLineData.file = filename + pbEachFileSectionEx(f) { |section,name| + rsection = [] + for key in section.keys + FileLineData.setSection(name,key,section[key]) + schema = requiredtypes[key] + next if !schema + record = pbGetCsvRecord(section[key],0,schema) + rsection[schema[0]] = record + end + trainernames.push(rsection[1]) + beginspeech.push(rsection[2]) + endspeechwin.push(rsection[3]) + endspeechlose.push(rsection[4]) + sections.push(rsection) + } + } + end + MessageTypes.addMessagesAsHash(MessageTypes::TrainerNames,trainernames) + MessageTypes.addMessagesAsHash(MessageTypes::BeginSpeech,beginspeech) + MessageTypes.addMessagesAsHash(MessageTypes::EndSpeechWin,endspeechwin) + MessageTypes.addMessagesAsHash(MessageTypes::EndSpeechLose,endspeechlose) + return sections + end + + def compile_trainer_lists + btTrainersRequiredTypes = { + "Trainers" => [0, "s"], + "Pokemon" => [1, "s"], + "Challenges" => [2, "*s"] + } + if !safeExists?("PBS/trainerlists.txt") + File.open("PBS/trainerlists.txt","wb") { |f| + f.write(0xEF.chr) + f.write(0xBB.chr) + f.write(0xBF.chr) + f.write("[DefaultTrainerList]\r\n") + f.write("Trainers = bttrainers.txt\r\n") + f.write("Pokemon = btpokemon.txt\r\n") + } + end + sections = [] + MessageTypes.setMessagesAsHash(MessageTypes::BeginSpeech,[]) + MessageTypes.setMessagesAsHash(MessageTypes::EndSpeechWin,[]) + MessageTypes.setMessagesAsHash(MessageTypes::EndSpeechLose,[]) + File.open("PBS/trainerlists.txt","rb") { |f| + FileLineData.file = "PBS/trainerlists.txt" + pbEachFileSectionEx(f) { |section,name| + next if name!="DefaultTrainerList" && name!="TrainerList" + rsection = [] + for key in section.keys + FileLineData.setSection(name,key,section[key]) + schema = btTrainersRequiredTypes[key] + next if key=="Challenges" && name=="DefaultTrainerList" + next if !schema + record = pbGetCsvRecord(section[key],0,schema) + rsection[schema[0]] = record + end + if !rsection[0] + raise _INTL("No trainer data file given in section {1}\r\n{2}",name,FileLineData.linereport) + end + if !rsection[1] + raise _INTL("No trainer data file given in section {1}\r\n{2}",name,FileLineData.linereport) + end + rsection[3] = rsection[0] + rsection[4] = rsection[1] + rsection[5] = (name=="DefaultTrainerList") + if safeExists?("PBS/"+rsection[0]) + rsection[0] = compile_battle_tower_trainers("PBS/"+rsection[0]) + else + rsection[0] = [] + end + if safeExists?("PBS/"+rsection[1]) + filename = "PBS/"+rsection[1] + rsection[1] = [] + pbCompilerEachCommentedLine(filename) { |line,_lineno| + rsection[1].push(PBPokemon.fromInspected(line)) + } + else + rsection[1] = [] + end + rsection[2] = [] if !rsection[2] + while rsection[2].include?("") + rsection[2].delete("") + end + rsection[2].compact! + sections.push(rsection) + } + } + save_data(sections,"Data/trainer_lists.dat") + end +end diff --git a/Data/Scripts/022_Compiler/004_Compiler_MapsAndEvents.rb b/Data/Scripts/022_Compiler/004_Compiler_MapsAndEvents.rb new file mode 100644 index 000000000..afa2133fe --- /dev/null +++ b/Data/Scripts/022_Compiler/004_Compiler_MapsAndEvents.rb @@ -0,0 +1,1466 @@ +module Compiler + #============================================================================= + # Add new map files to the map tree. + #============================================================================= + def import_new_maps + return false if !$DEBUG + mapfiles = {} + # Get IDs of all maps in the Data folder + Dir.chdir("Data") { + mapData = sprintf("Map*.%s",$RPGVX ? "rvdata" : "rxdata") + for map in Dir.glob(mapData) + if $RPGVX + mapfiles[$1.to_i(10)] = true if map[/map(\d+)\.rvdata/i] + else + mapfiles[$1.to_i(10)] = true if map[/map(\d+)\.rxdata/i] + end + end + } + mapinfos = pbLoadRxData("Data/MapInfos") + maxOrder = 0 + # Exclude maps found in mapinfos + for id in mapinfos.keys + next if !mapinfos[id] + mapfiles.delete(id) if mapfiles[id] + maxOrder = [maxOrder,mapinfos[id].order].max + end + # Import maps not found in mapinfos + maxOrder += 1 + imported = false + count = 0 + for id in mapfiles.keys + next if id==999 # Ignore 999 (random dungeon map) + mapinfo = RPG::MapInfo.new + mapinfo.order = maxOrder + mapinfo.name = sprintf("MAP%03d",id) + maxOrder += 1 + mapinfos[id] = mapinfo + imported = true + count += 1 + end + if imported + if $RPGVX + save_data(mapinfos,"Data/MapInfos.rvdata") + else + save_data(mapinfos,"Data/MapInfos.rxdata") + end + pbMessage(_INTL("{1} new map(s) copied to the Data folder were successfully imported.",count)) + end + return imported + end + + #============================================================================= + # Generate and modify event commands. + #============================================================================= + def generate_move_route(commands) + route = RPG::MoveRoute.new + route.repeat = false + route.skippable = true + route.list.clear + i = 0 + while i=map.width || y<0 || y>=map.height + passages = getTilesetPassages(map,mapID) + priorities = getTilesetPriorities(map,mapID) + for i in [2, 1, 0] + tile_id = map.data[x, y, i] + return false if tile_id==nil + passage = passages[tile_id] + if !passage + raise "The tile used on map #{mapID} at coordinates (#{x}, #{y}) on layer #{i+1} doesn't exist in the tileset. " + + "It should be deleted to prevent errors." + end + return false if passage&0x0f==0x0f + return true if priorities[tile_id]==0 + end + return true + end + + def isCounterTile?(mapID,x,y) + return false if $RPGVX + map = getMap(mapID) + return false if !map + passages = getTilesetPassages(map,mapID) + for i in [2, 1, 0] + tile_id = map.data[x, y, i] + return false if tile_id==nil + passage = passages[tile_id] + if !passage + raise "The tile used on map #{mapID} at coordinates (#{x}, #{y}) on layer #{i+1} doesn't exist in the tileset. " + + "It should be deleted to prevent errors." + end + return true if passage&0x80==0x80 + end + return false + end + + def setCounterTile(mapID,x,y) + return if $RPGVX + map = getMap(mapID) + return if !map + passages = getTilesetPassages(map,mapID) + for i in [2, 1, 0] + tile_id = map.data[x, y, i] + next if tile_id==0 + passages[tile_id] |= 0x80 + break + end + end + + def registerSwitch(switch) + return @registeredSwitches[switch] if @registeredSwitches[switch] + for id in 1..5000 + name = @system.switches[id] + next if name && name!="" && name!=switch + @system.switches[id] = switch + @registeredSwitches[switch] = id + return id + end + return 1 + end + + def saveMap(mapID) + save_data(getMap(mapID),mapFilename(mapID)) rescue nil + end + + def saveTilesets + filename = "Data/Tilesets" + filename += ($RPGVX) ? ".rvdata" : ".rxdata" + save_data(@tilesets,filename) + filename = "Data/System" + filename += ($RPGVX) ? ".rvdata" : ".rxdata" + save_data(@system,filename) + end + end + + #============================================================================= + # + #============================================================================= + class TrainerChecker + def initialize + @trainers = nil + @trainertypes = nil + @dontaskagain = false + end + + def pbTrainerTypeCheck(symbol) + ret = true + if $DEBUG + return if @dontaskagain + if !hasConst?(PBTrainers,symbol) + ret = false + else + trtype = PBTrainers.const_get(symbol) + @trainertypes = load_data("Data/trainer_types.dat") if !@trainertypes + ret = false if !@trainertypes || !@trainertypes[trtype] + end + if !ret + if pbConfirmMessage(_INTL("Add new trainer named {1}?",symbol)) + pbTrainerTypeEditorNew(symbol.to_s) + @trainers = nil + @trainertypes = nil + end +# if pbMapInterpreter +# pbMapInterpreter.command_end rescue nil +# end + end + end + return ret + end + + def pbTrainerBattleCheck(trtype,trname,trid) + return if !$DEBUG || @dontaskagain + if trtype.is_a?(String) || trtype.is_a?(Symbol) + pbTrainerTypeCheck(trtype) + return if !hasConst?(PBTrainers,trtype) + trtype = PBTrainers.const_get(trtype) + end + @trainers = load_data("Data/trainers.dat") if !@trainers + if @trainers + for trainer in @trainers + return if trainer[0]==trtype && trainer[1]==trname && trainer[4]==trid + end + end + cmd = pbMissingTrainer(trtype,trname,trid) + if cmd==2 + @dontaskagain = true + Graphics.update + end + @trainers = nil + @trainertypes = nil + end + end + + #============================================================================= + # Convert trainer comments to trainer event. + #============================================================================= + def convert_to_trainer_event(event,trainerChecker) + return nil if !event || event.pages.length==0 + list = event.pages[0].list + return nil if list.length<2 + commands = [] + isFirstCommand = false + # Find all the trainer comments in the event + for i in 0...list.length + next if list[i].code!=108 # Comment (first line) + command = list[i].parameters[0] + for j in (i+1)...list.length + break if list[j].code!=408 # Comment (continuation line) + command += "\r\n"+list[j].parameters[0] + end + if command[/^(Battle\:|Type\:|Name\:|BattleID\:|DoubleBattle\:|Backdrop\:|EndSpeech\:|Outcome\:|Continue\:|EndBattle\:|EndIfSwitch\:|VanishIfSwitch\:|RegSpeech\:)/i] + commands.push(command) + isFirstCommand = true if i==0 + end + end + return nil if commands.length==0 + # Found trainer comments; create a new Event object to replace this event + ret = RPG::Event.new(event.x,event.y) + ret.name = event.name + ret.id = event.id + firstpage = Marshal::load(Marshal.dump(event.pages[0])) # Copy event's first page + firstpage.trigger = 2 # On event touch + firstpage.list = [] # Clear page's commands + # Rename the event if there's nothing above the trainer comments + if isFirstCommand + if !event.name[/trainer/i] + ret.name = "Trainer(3)" + elsif event.name[/^\s*trainer\s+\((\d+)\)\s*$/i] + ret.name = "Trainer(#{$1})" + end + end + # Compile the trainer comments + rewriteComments = false # You can change this + battles = [] + trtype = nil + trname = nil + battleid = 0 + doublebattle = false + backdrop = nil + endspeeches = [] + outcome = 0 + continue = false + endbattles = [] + endifswitch = [] + vanishifswitch = [] + regspeech = nil + for command in commands + if command[/^Battle\:\s*([\s\S]+)$/i] + battles.push($~[1]) + push_comment(firstpage.list,command) if rewriteComments + elsif command[/^Type\:\s*([\s\S]+)$/i] + trtype = $~[1].gsub(/^\s+/,"").gsub(/\s+$/,"") + push_comment(firstpage.list,command) if rewriteComments + elsif command[/^Name\:\s*([\s\S]+)$/i] + trname = $~[1].gsub(/^\s+/,"").gsub(/\s+$/,"") + push_comment(firstpage.list,command) if rewriteComments + elsif command[/^BattleID\:\s*(\d+)$/i] + battleid = $~[1].to_i + push_comment(firstpage.list,command) if rewriteComments + elsif command[/^DoubleBattle\:\s*([\s\S]+)$/i] + value = $~[1].gsub(/^\s+/,"").gsub(/\s+$/,"") + doublebattle = true if value.upcase=="TRUE" || value.upcase=="YES" + push_comment(firstpage.list,command) if rewriteComments + elsif command[/^Backdrop\:\s*([\s\S]+)$/i] + backdrop = $~[1].gsub(/^\s+/,"").gsub(/\s+$/,"") + push_comment(firstpage.list,command) if rewriteComments + elsif command[/^EndSpeech\:\s*([\s\S]+)$/i] + endspeeches.push($~[1].gsub(/^\s+/,"").gsub(/\s+$/,"")) + push_comment(firstpage.list,command) if rewriteComments + elsif command[/^Outcome\:\s*(\d+)$/i] + outcome = $~[1].to_i + push_comment(firstpage.list,command) if rewriteComments + elsif command[/^Continue\:\s*([\s\S]+)$/i] + value = $~[1].gsub(/^\s+/,"").gsub(/\s+$/,"") + continue = true if value.upcase=="TRUE" || value.upcase=="YES" + push_comment(firstpage.list,command) if rewriteComments + elsif command[/^EndBattle\:\s*([\s\S]+)$/i] + endbattles.push($~[1].gsub(/^\s+/,"").gsub(/\s+$/,"")) + push_comment(firstpage.list,command) if rewriteComments + elsif command[/^EndIfSwitch\:\s*([\s\S]+)$/i] + endifswitch.push(($~[1].gsub(/^\s+/,"").gsub(/\s+$/,"")).to_i) + push_comment(firstpage.list,command) if rewriteComments + elsif command[/^VanishIfSwitch\:\s*([\s\S]+)$/i] + vanishifswitch.push(($~[1].gsub(/^\s+/,"").gsub(/\s+$/,"")).to_i) + push_comment(firstpage.list,command) if rewriteComments + elsif command[/^RegSpeech\:\s*([\s\S]+)$/i] + regspeech = $~[1].gsub(/^\s+/,"").gsub(/\s+$/,"") + push_comment(firstpage.list,command) if rewriteComments + end + end + return nil if battles.length<=0 + # Run trainer check now, except in editor + trainerChecker.pbTrainerBattleCheck(trtype,trname,battleid) if !$INEDITOR + # Set the event's charset to one depending on the trainer type if the event + # doesn't have a charset + if firstpage.graphic.character_name=="" && hasConst?(PBTrainers,trtype) + trainerid = getConst(PBTrainers,trtype) + if trainerid + filename = pbTrainerCharNameFile(trainerid) + if FileTest.image_exist?("Graphics/Characters/"+filename) + firstpage.graphic.character_name = sprintf(filename) + end + end + end + # Create strings that will be used repeatedly + safetrcombo = sprintf(":%s,\"%s\"",trtype,safequote(trname)) # :YOUNGSTER,"Joey" + introplay = sprintf("pbTrainerIntro(:%s)",trtype) + # Write first page + push_script(firstpage.list,introplay) # pbTrainerIntro + push_script(firstpage.list,"pbNoticePlayer(get_character(0))") + push_text(firstpage.list,battles[0]) + if battles.length>1 # Has rematches + push_script(firstpage.list,sprintf("pbTrainerCheck(%s,%d,%d)",safetrcombo,battles.length,battleid)) + end + push_script(firstpage.list,"setBattleRule(\"double\")") if doublebattle + push_script(firstpage.list,sprintf("setBattleRule(\"backdrop\",\"%s\")",safequote(backdrop))) if backdrop + push_script(firstpage.list,sprintf("setBattleRule(\"outcomeVar\",%d)",outcomeVar)) if outcome>1 + push_script(firstpage.list,"setBattleRule(\"canLose\")") if continue + espeech = (endspeeches[0]) ? sprintf("_I(\"%s\")",safequote2(endspeeches[0])) : "nil" + if battleid>0 + battleString = sprintf("pbTrainerBattle(%s,%s,nil,%d)",safetrcombo,espeech,battleid) + elsif endspeeches[0] + battleString = sprintf("pbTrainerBattle(%s,%s)",safetrcombo,espeech) + else + battleString = sprintf("pbTrainerBattle(%s)",safetrcombo) + end + push_branch(firstpage.list,battleString) + if battles.length>1 # Has rematches + push_script(firstpage.list, + sprintf("pbPhoneRegisterBattle(_I(\"%s\"),get_character(0),%s,%d)", + regspeech,safetrcombo,battles.length),1) + end + push_self_switch(firstpage.list,"A",true,1) + push_branch_end(firstpage.list,1) + push_script(firstpage.list,"pbTrainerEnd",0) + push_end(firstpage.list) + # Copy first page to last page and make changes to its properties + lastpage = Marshal::load(Marshal.dump(firstpage)) + lastpage.trigger = 0 # On action + lastpage.list = [] # Clear page's commands + lastpage.condition = firstpage.condition.clone + lastpage.condition.self_switch_valid = true + lastpage.condition.self_switch_ch = "A" + # Copy last page to rematch page + rematchpage = Marshal::load(Marshal.dump(lastpage)) + rematchpage.list = lastpage.list.clone # Copy the last page's commands + rematchpage.condition = lastpage.condition.clone + rematchpage.condition.self_switch_valid = true + rematchpage.condition.self_switch_ch = "B" + # Write rematch and last pages + for i in 1...battles.length + # Run trainer check now, except in editor + trainerChecker.pbTrainerBattleCheck(trtype,trname,battleid+i) if !$INEDITOR + if i==battles.length-1 + push_branch(rematchpage.list,sprintf("pbPhoneBattleCount(%s)>=%d",safetrcombo,i)) + push_branch(lastpage.list,sprintf("pbPhoneBattleCount(%s)>%d",safetrcombo,i)) + else + push_branch(rematchpage.list,sprintf("pbPhoneBattleCount(%s)==%d",safetrcombo,i)) + push_branch(lastpage.list,sprintf("pbPhoneBattleCount(%s)==%d",safetrcombo,i)) + end + # Rematch page + push_script(rematchpage.list,introplay,1) # pbTrainerIntro + push_text(rematchpage.list,battles[i],1) + push_script(rematchpage.list,"setBattleRule(\"double\")",1) if doublebattle + push_script(rematchpage.list,sprintf("setBattleRule(\"backdrop\",%s)",safequote(backdrop)),1) if backdrop + push_script(rematchpage.list,sprintf("setBattleRule(\"outcomeVar\",%d)",outcomeVar),1) if outcome>1 + push_script(rematchpage.list,"setBattleRule(\"canLose\")",1) if continue + espeech = nil + if endspeeches.length>0 + espeech = (endspeeches[i]) ? endspeeches[i] : endspeeches[endspeeches.length-1] + end + espeech = (espeech) ? sprintf("_I(\"%s\")",safequote2(espeech)) : "nil" + battleString = sprintf("pbTrainerBattle(%s,%s,nil,%d)",safetrcombo,espeech,battleid+i) + push_branch(rematchpage.list,battleString,1) + push_script(rematchpage.list,sprintf("pbPhoneIncrement(%s,%d)",safetrcombo,battles.length),2) + push_self_switch(rematchpage.list,"A",true,2) + push_self_switch(rematchpage.list,"B",false,2) + push_script(rematchpage.list,"pbTrainerEnd",2) + push_branch_end(rematchpage.list,2) + push_exit(rematchpage.list,1) # Exit Event Processing + push_branch_end(rematchpage.list,1) + # Last page + if endbattles.length>0 + ebattle = (endbattles[i]) ? endbattles[i] : endbattles[endbattles.length-1] + push_text(lastpage.list,ebattle,1) + end + push_script(lastpage.list, + sprintf("pbPhoneRegisterBattle(_I(\"%s\"),get_character(0),%s,%d)", + regspeech,safetrcombo,battles.length),1) + push_exit(lastpage.list,1) # Exit Event Processing + push_branch_end(lastpage.list,1) + end + # Finish writing rematch page + push_end(rematchpage.list) + # Finish writing last page + ebattle = (endbattles[0]) ? endbattles[0] : "..." + push_text(lastpage.list,ebattle) + if battles.length>1 + push_script(lastpage.list, + sprintf("pbPhoneRegisterBattle(_I(\"%s\"),get_character(0),%s,%d)", + regspeech,safetrcombo,battles.length)) + end + push_end(lastpage.list) + # Add pages to the new event + if battles.length==1 # Only one battle + ret.pages = [firstpage,lastpage] + else # Has rematches + ret.pages = [firstpage,rematchpage,lastpage] + end + # Copy last page to endIfSwitch page + for endswitch in endifswitch + endIfSwitchPage = Marshal::load(Marshal.dump(lastpage)) + endIfSwitchPage.condition = lastpage.condition.clone + if endIfSwitchPage.condition.switch1_valid # Add another page condition + endIfSwitchPage.condition.switch2_valid = true + endIfSwitchPage.condition.switch2_id = endswitch + else + endIfSwitchPage.condition.switch1_valid = true + endIfSwitchPage.condition.switch1_id = endswitch + end + endIfSwitchPage.condition.self_switch_valid = false + endIfSwitchPage.list = [] # Clear page's commands + ebattle = (endbattles[0]) ? endbattles[0] : "..." + push_text(endIfSwitchPage.list,ebattle) + push_end(endIfSwitchPage.list) + ret.pages.push(endIfSwitchPage) + end + # Copy last page to vanishIfSwitch page + for vanishswitch in vanishifswitch + vanishIfSwitchPage = Marshal::load(Marshal.dump(lastpage)) + vanishIfSwitchPage.graphic.character_name = "" # No charset + vanishIfSwitchPage.condition = lastpage.condition.clone + if vanishIfSwitchPage.condition.switch1_valid # Add another page condition + vanishIfSwitchPage.condition.switch2_valid = true + vanishIfSwitchPage.condition.switch2_id = vanishswitch + else + vanishIfSwitchPage.condition.switch1_valid = true + vanishIfSwitchPage.condition.switch1_id = vanishswitch + end + vanishIfSwitchPage.condition.self_switch_valid = false + vanishIfSwitchPage.list = [] # Clear page's commands + push_end(vanishIfSwitchPage.list) + ret.pages.push(vanishIfSwitchPage) + end + return ret + end + + #============================================================================= + # Convert event name to item event. + # Checks if the event's name is "Item:POTION" or "HiddenItem:POTION". If so, + # rewrites the whole event into one now named "Item"/"HiddenItem" which gives + # that item when interacted with. + #============================================================================= + def convert_to_item_event(event) + return nil if !event || event.pages.length==0 + name = event.name + ret = RPG::Event.new(event.x,event.y) + ret.name = event.name + ret.id = event.id + ret.pages = [] + itemName = "" + hidden = false + if name[/^HiddenItem\:\s*(\w+)\s*$/] + itemName = $1 + return nil if !hasConst?(PBItems,itemName) + ret.name = "HiddenItem" + hidden = true + elsif name[/^Item\:\s*(\w+)\s*$/] + itemName = $1 + return nil if !hasConst?(PBItems,itemName) + ret.name = "Item" + else + return nil + end + # Event page 1 + page = RPG::Event::Page.new + page.graphic.character_name = "Object ball" if !hidden + page.list = [] + push_branch(page.list,sprintf("pbItemBall(:%s)",itemName)) + push_self_switch(page.list,"A",true,1) + push_else(page.list,1) + push_branch_end(page.list,1) + push_end(page.list) + ret.pages.push(page) + # Event page 2 + page = RPG::Event::Page.new + page.condition.self_switch_valid = true + page.condition.self_switch_ch = "A" + ret.pages.push(page) + return ret + end + + #============================================================================= + # Checks whether a given event is likely to be a door. If so, rewrite it to + # include animating the event as though it was a door opening and closing as the + # player passes through. + #============================================================================= + def update_door_event(event,mapData) + changed = false + return false if event.is_a?(RPG::CommonEvent) + # Check if event has 2+ pages and the last page meets all of these criteria: + # - Has a condition of a Switch being ON + # - The event has a charset graphic + # - There are more than 5 commands in that page, the first of which is a + # Conditional Branch + lastPage = event.pages[event.pages.length-1] + if event.pages.length>=2 && + lastPage.condition.switch1_valid && + lastPage.graphic.character_name!="" && + lastPage.list.length>5 && + lastPage.list[0].code==111 + # This bit of code is just in case Switch 22 has been renamed/repurposed, + # which is highly unlikely. It changes the Switch used in the condition to + # whichever is named 's:tsOff?("A")'. + if lastPage.condition.switch1_id==22 && + mapData.switchName(lastPage.condition.switch1_id)!='s:tsOff?("A")' + lastPage.condition.switch1_id = mapData.registerSwitch('s:tsOff?("A")') + changed = true + end + # If the last page's Switch condition uses a Switch named 's:tsOff?("A")', + # check the penultimate page. If it contains exactly 1 "Transfer Player" + # command and does NOT contain a "Change Transparent Flag" command, rewrite + # both the penultimate page and the last page. + if mapData.switchName(lastPage.condition.switch1_id)=='s:tsOff?("A")' + list = event.pages[event.pages.length-2].list + transferCommand = list.find_all { |cmd| cmd.code==201 } # Transfer Player + if transferCommand.length==1 && !list.any? { |cmd| cmd.code==208 } # Change Transparent Flag + # Rewrite penultimate page + list.clear + push_move_route_and_wait(list,0,[ # Move Route for door opening + PBMoveRoute::PlaySE,RPG::AudioFile.new("Door enter"),PBMoveRoute::Wait,2, + PBMoveRoute::TurnLeft,PBMoveRoute::Wait,2, + PBMoveRoute::TurnRight,PBMoveRoute::Wait,2, + PBMoveRoute::TurnUp,PBMoveRoute::Wait,2]) + push_move_route_and_wait(list,-1,[ # Move Route for player entering door + PBMoveRoute::ThroughOn,PBMoveRoute::Up,PBMoveRoute::ThroughOff]) + push_event(list,208,[0]) # Change Transparent Flag (invisible) + push_move_route_and_wait(list,0,[PBMoveRoute::Wait,2, # Move Route for door closing + PBMoveRoute::TurnRight,PBMoveRoute::Wait,2, + PBMoveRoute::TurnLeft,PBMoveRoute::Wait,2, + PBMoveRoute::TurnDown,PBMoveRoute::Wait,2]) + push_event(list,223,[Tone.new(-255,-255,-255),6]) # Change Screen Color Tone + push_wait(list,8) # Wait + push_event(list,208,[1]) # Change Transparent Flag (visible) + push_event(list,transferCommand[0].code,transferCommand[0].parameters) # Transfer Player + push_event(list,223,[Tone.new(0,0,0),6]) # Change Screen Color Tone + push_end(list) + # Rewrite last page + list = lastPage.list + list.clear + push_branch(list,"get_character(0).onEvent?") # Conditional Branch + push_event(list,208,[0],1) # Change Transparent Flag (invisible) + push_move_route_and_wait(list,0,[ # Move Route for setting door to open + PBMoveRoute::TurnLeft,PBMoveRoute::Wait,6],1) + push_event(list,208,[1],1) # Change Transparent Flag (visible) + push_move_route_and_wait(list,-1,[PBMoveRoute::Down],1) # Move Route for player exiting door + push_move_route_and_wait(list,0,[ # Move Route for door closing + PBMoveRoute::TurnUp,PBMoveRoute::Wait,2, + PBMoveRoute::TurnRight,PBMoveRoute::Wait,2, + PBMoveRoute::TurnDown,PBMoveRoute::Wait,2],1) + push_branch_end(list,1) + push_script(list,"setTempSwitchOn(\"A\")") + push_end(list) + changed = true + end + end + end + return changed + end + + #============================================================================= + # Fix up standard code snippets + #============================================================================= + def event_is_empty?(e) + return true if !e + return false if e.is_a?(RPG::CommonEvent) + return e.pages.length==0 + end + + # Checks if the event has exactly 1 page, said page has no graphic, it has + # less than 12 commands and at least one is a Transfer Player, and the tiles + # to the left/right/upper left/upper right are not passable but the event's + # tile is. Causes a second page to be added to the event which is the "is + # player on me?" check that occurs when the map is entered. + def likely_passage?(thisEvent,mapID,mapData) + return false if !thisEvent || thisEvent.pages.length==0 + return false if thisEvent.pages.length!=1 + if thisEvent.pages[0].graphic.character_name=="" && + thisEvent.pages[0].list.length<=12 && + thisEvent.pages[0].list.any? { |cmd| cmd.code==201 } && # Transfer Player +# mapData.isPassable?(mapID,thisEvent.x,thisEvent.y+1) && + mapData.isPassable?(mapID,thisEvent.x,thisEvent.y) && + !mapData.isPassable?(mapID,thisEvent.x-1,thisEvent.y) && + !mapData.isPassable?(mapID,thisEvent.x+1,thisEvent.y) && + !mapData.isPassable?(mapID,thisEvent.x-1,thisEvent.y-1) && + !mapData.isPassable?(mapID,thisEvent.x+1,thisEvent.y-1) + return true + end + return false + end + + def change_script(script,re) + tmp = script[0].gsub(re) { yield($~) } + if script[0]!=tmp + script[0] = tmp + return true + end + return false + end + + def change_scripts(script) + changed = false + changed |= change_script(script,/\$game_variables\[(\d+)\](?!\s*(?:\=|\!|<|>))/) { |m| "pbGet("+m[1]+")" } + changed |= change_script(script,/\$Trainer\.party\[\s*pbGet\((\d+)\)\s*\]/) { |m| "pbGetPokemon("+m[1]+")" } + return changed + end + + def fix_event_use(event,_mapID,mapData) + return nil if event_is_empty?(event) + changed = false + trainerMoneyRE = /^\s*\$Trainer\.money\s*(<|<=|>|>=)\s*(\d+)\s*$/ + itemBallRE = /^\s*(Kernel\.)?pbItemBall/ + # Rewrite event if it looks like a door + changed = true if update_door_event(event,mapData) + # Check through each page of the event in turn + pbEachPage(event) do |page| + i = 0 + list = page.list + while i=2 && + e.pages[e.pages.length-1].condition.switch1_valid && + e.pages[e.pages.length-1].condition.switch1_id==22 && + mapData.switchName(e.pages[e.pages.length-1].condition.switch1_id)!='s:tsOff?("A")' && + e.pages[e.pages.length-1].list.length>5 && + e.pages[e.pages.length-1].list[0].code==111 # Conditional Branch + e.pages[e.pages.length-1].condition.switch1_id = mapData.registerSwitch('s:tsOff?("A")') + mapData.saveMap(params[1]) + changed = true + end + # Checks if the found event is a simple Transfer Player one nestled + # between tiles that aren't passable - it is likely a door, so give + # it a second page with an "is player on me?" check. + if likely_passage?(e,params[1],mapData) # Checks the first page + add_passage_list(e,mapData) + mapData.saveMap(params[1]) + changed = true + end + # If the found event's last page's Switch condition uses a Switch + # named 's:tsOff?("A")', it really does look like a door. Make this + # command transfer the player on top of it rather than in front of + # it. + if e && e.pages.length>=2 && + e.pages[e.pages.length-1].condition.switch1_valid && + mapData.switchName(e.pages[e.pages.length-1].condition.switch1_id)=='s:tsOff?("A")' + # If this is really a door, move transfer target to it + params[3] -= 1 # Move this command's destination up 1 tile (onto the found event) + params[5] = 1 # No fade (the found event should take care of that) + changed = true + end + deletedRoute = nil + deleteMoveRouteAt = proc { |list,i| + arr = [] + if list[i] && list[i].code==209 # Set Move Route + arr.push(list[i]); list.delete_at(i) + while i=0 + list.insert(i,route[j]) + j -= 1 + end + } + # If the next event command is a Move Route that moves the player, + # check whether all it does is turn the player in a direction (or + # its first item is to move the player in a direction). If so, this + # Transfer Player command may as well set the player's direction + # instead; make it do so and delete that Move Route. + if params[4]==0 && # Retain direction + i+13 # Retain direction +# for j in 0...i +# if list[j].code==209 && list[j].parameters[0]==-1 # Set Move Route +# route = list[j].parameters[1] +# if route && route.list.length<=2 +# oldlistlength = list.length +# # Delete superfluous move route command if necessary +# if route.list[0].code==16 # Player Turn Down +# deleteMoveRouteAt.call(list,j); params[4] = 2; changed = true; i -= (oldlistlength-list.length) +# elsif route.list[0].code==17 # Player Turn Left +# deleteMoveRouteAt.call(list,j); params[4] = 4; changed = true; i -= (oldlistlength-list.length) +# elsif route.list[0].code==18 # Player Turn Right +# deleteMoveRouteAt.call(list,j); params[4] = 6; changed = true; i -= (oldlistlength-list.length) +# elsif route.list[0].code==19 # Player Turn Up +# deleteMoveRouteAt.call(list,j); params[4] = 8; changed = true; i -= (oldlistlength-list.length) +# end +# end +# end +# end + # If the next event command changes the screen color, and the one + # after that is a Move Route which only turns the player in a + # direction, this Transfer Player command may as well set the + # player's direction instead; make it do so and delete that Move + # Route. + elsif params[4]==0 && # Retain direction + i+2=2 && list[i].parameters[0].length>0 && list[i].parameters[0].length<=20 && + !list[i].parameters[0][/\\n/] + # Very short line + list[i].parameters[0] += "\\n"+list[i+1].parameters[0] + list.delete_at(i+1) + i -= 1 # revisit this text command + changed = true + # Check whether this Show Text command has 3+ lines and the next command + # is also a Show Text + elsif lines>=3 && list[i+lines] && list[i+lines].code==101 # Show Text + # Check whether a sentence is being broken midway between two Text + # commands (i.e. the first Show Text doesn't end in certain punctuation) + lastLine = list[i+lines-1].parameters[0].sub(/\s+$/,"") + if lastLine.length>0 && !lastLine[/[\\<]/] && lastLine[/[^\.,\!\?\;\-\"]$/] + message = list[i].parameters[0] + j = i+1 + while j=0 + list.insert(i,RPG::EventCommand.new((j==0) ? 101 : 401,indent,[nextMessage[j]])) + j-=1 + end + j = newMessage.length-1 + while j>=0 + list.insert(i,RPG::EventCommand.new((j==0) ? 101 : 401,indent,[newMessage[j]])) + j -= 1 + end + changed = true + i += 1 + next + end + end + end + when 111 # Conditional Branch + if list[i].parameters[0]==12 # script + x = [list[i].parameters[1]] + changed |= change_scripts(x) + list[i].parameters[1] = x[0] + script = x[0] + if script[trainerMoneyRE] # Compares $Trainer.money with a value + # Checking money directly + operator = $1 + amount = $2.to_i + if operator=="<" + params[0] = 7 # gold + params[2] = 1 + params[1] = amount-1 + changed = true + elsif operator=="<=" + params[0] = 7 # gold + params[2] = 1 + params[1] = amount + changed = true + elsif operator==">" + params[0] = 7 # gold + params[2] = 0 + params[1] = amount+1 + changed = true + elsif operator==">=" + params[0] = 7 # gold + params[2] = 0 + params[1] = amount + changed = true + end + elsif script[itemBallRE] && i>0 # Contains pbItemBall after another command + # Using pbItemBall on non-item events, change it + list[i].parameters[1] = script.sub(/pbItemBall/,"pbReceiveItem") + changed = true + elsif script[/^\s*(Kernel\.)?(pbTrainerBattle|pbDoubleTrainerBattle)/] + # Check if trainer battle conditional branch is empty + j = i+1 + isempty = true + elseIndex = -1 + # Check if page is empty + while j=0 + list.insert(elseIndex+1, + RPG::EventCommand.new(115,list[i].indent+1,[]) # Exit Event Processing + ) + else + list.insert(i+1, + RPG::EventCommand.new(0,list[i].indent+1,[]), # Empty Event + RPG::EventCommand.new(411,list[i].indent,[]), # Else + RPG::EventCommand.new(115,list[i].indent+1,[]) # Exit Event Processing + ) + end + changed = true + end + end + end + end + i += 1 + end + end + return (changed) ? event : nil + end + + #============================================================================= + # Convert events used as counters into proper counters. + #============================================================================= + # Checks if the event has just 1 page, which has no conditions and no commands + # and whose movement type is "Fixed". + def plain_event?(event) + return false unless event + return false if event.pages.length>1 + return false if event.pages[0].move_type!=0 + return false if event.pages[0].condition.switch1_valid || + event.pages[0].condition.switch2_valid || + event.pages[0].condition.variable_valid || + event.pages[0].condition.self_switch_valid + return true if event.pages[0].list.length<=1 + return false + end + + # Checks if the event has just 1 page, which has no conditions and whose + # movement type is "Fixed". Then checks if there are no commands, or it looks + # like a simple Mart or a Poké Center nurse event. + def plain_event_or_mart?(event) + return false unless event + return false if event.pages.length>1 + return false if event.pages[0].move_type!=0 + return false if event.pages[0].condition.switch1_valid || + event.pages[0].condition.switch2_valid || + event.pages[0].condition.variable_valid || + event.pages[0].condition.self_switch_valid + # No commands in the event + return true if event.pages[0].list.length<=1 + # pbPokemonMart events + return true if event.pages[0].list.length<=12 && + event.pages[0].graphic.character_name!="" && # Has charset + event.pages[0].list[0].code==355 && # First line is Script + event.pages[0].list[0].parameters[0][/^pbPokemonMart/] + # pbSetPokemonCenter events + return true if event.pages[0].list.length>8 && + event.pages[0].graphic.character_name!="" && # Has charset + event.pages[0].list[0].code==355 && # First line is Script + event.pages[0].list[0].parameters[0][/^pbSetPokemonCenter/] + return false + end + + # Given two events that are next to each other, decides whether otherEvent is + # likely to be a "counter event", i.e. is placed on a tile with the Counter + # flag, or is on a non-passable tile between two passable tiles (e.g. a desk) + # where one of those two tiles is occupied by thisEvent. + def likely_counter?(thisEvent,otherEvent,mapID,mapData) + # Check whether otherEvent is on a counter tile + return true if mapData.isCounterTile?(mapID,otherEvent.x,otherEvent.y) + # Check whether otherEvent is between an event with a graphic (e.g. an NPC) + # and a spot where the player can be + yonderX = otherEvent.x + (otherEvent.x - thisEvent.x) + yonderY = otherEvent.y + (otherEvent.y - thisEvent.y) + return thisEvent.pages[0].graphic.character_name!="" && # Has charset + otherEvent.pages[0].graphic.character_name=="" && # Has no charset + otherEvent.pages[0].trigger==0 && # Action trigger + mapData.isPassable?(mapID,thisEvent.x,thisEvent.y) && + !mapData.isPassable?(mapID,otherEvent.x,otherEvent.y) && + mapData.isPassable?(mapID,yonderX,yonderY) + end + + # Checks all events in the given map to see if any look like they've been + # placed on a desk with an NPC behind it, where the event on the desk is the + # actual interaction with the NPC. In other words, it's not making proper use + # of the counter flag (which lets the player interact with an event on the + # other side of counter tiles). + # Any events found to be like this have their contents merged into the NPC + # event and the counter event itself is deleted. The tile below the counter + # event gets its counter flag set (if it isn't already). + def check_counters(map,mapID,mapData) + toDelete = [] + changed = false + for key in map.events.keys + event = map.events[key] + next if !plain_event_or_mart?(event) + # Found an event that is empty or looks like a simple Mart or a Poké + # Center nurse. Check adjacent events to see if they are "counter events". + neighbors = [] + neighbors.push(mapData.getEventFromXY(mapID,event.x,event.y-1)) + neighbors.push(mapData.getEventFromXY(mapID,event.x,event.y+1)) + neighbors.push(mapData.getEventFromXY(mapID,event.x-1,event.y)) + neighbors.push(mapData.getEventFromXY(mapID,event.x+1,event.y)) + neighbors.compact! + for otherEvent in neighbors + next if plain_event?(otherEvent) # Blank/cosmetic-only event + next if !likely_counter?(event,otherEvent,mapID,mapData) + # Found an adjacent event that looks like it's supposed to be a counter. + # Set the counter flag of the tile beneath the counter event, copy the + # counter event's pages to the NPC event, and delete the counter event. + mapData.setCounterTile(mapID,otherEvent.x,otherEvent.y) + savedPage = event.pages[0] + event.pages = otherEvent.pages + apply_pages(savedPage,event.pages) # Apply NPC's visuals to new event pages + toDelete.push(otherEvent.id) + changed = true + end + end + toDelete.each { |key| map.events.delete(key) } + return changed + end + + #============================================================================= + # Main compiler method for events + #============================================================================= + def compile_trainer_events(_mustcompile) + mapData = MapData.new + t = Time.now.to_i + Graphics.update + trainerChecker = TrainerChecker.new + for id in mapData.mapinfos.keys.sort + changed = false + map = mapData.getMap(id) + next if !map || !mapData.mapinfos[id] + Win32API.SetWindowText(_INTL("Processing map {1} ({2})",id,mapData.mapinfos[id].name)) + for key in map.events.keys + if Time.now.to_i-t>=5 + Graphics.update + t = Time.now.to_i + end + newevent = convert_to_trainer_event(map.events[key],trainerChecker) + if newevent + map.events[key] = newevent + changed = true + end + newevent = convert_to_item_event(map.events[key]) + if newevent + map.events[key] = newevent + changed = true + end + newevent = fix_event_use(map.events[key],id,mapData) + if newevent + map.events[key] = newevent + changed = true + end + end + if Time.now.to_i-t>=5 + Graphics.update + t = Time.now.to_i + end + changed = true if check_counters(map,id,mapData) + if changed + mapData.saveMap(id) + mapData.saveTilesets + end + end + changed = false + Graphics.update + commonEvents = pbLoadRxData("Data/CommonEvents") + Win32API.SetWindowText(_INTL("Processing common events")) + for key in 0...commonEvents.length + newevent = fix_event_use(commonEvents[key],0,mapData) + if newevent + commonEvents[key] = newevent + changed = true + end + end + if changed + if $RPGVX + save_data(commonEvents,"Data/CommonEvents.rvdata") + else + save_data(commonEvents,"Data/CommonEvents.rxdata") + end + end + end +end diff --git a/Data/Scripts/999_Main/999_Main.rb b/Data/Scripts/999_Main/999_Main.rb index 34c5d0b9a..5b9865924 100644 --- a/Data/Scripts/999_Main/999_Main.rb +++ b/Data/Scripts/999_Main/999_Main.rb @@ -1,4 +1,5 @@ -pbCompiler +pbSetUpSystem +Compiler.main