Files
infinitefusion-e18/Data/Scripts/021_Compiler/002_Compiler_CompilePBS.rb
2025-04-25 22:06:46 -04:00

1491 lines
61 KiB
Ruby

#===============================================================================
#
#===============================================================================
module Compiler
@@categories[:pbs_files] = {
:should_compile => proc { |compiling| next should_compile_pbs_files? },
:header_text => proc { next _INTL("Compiling PBS files") },
:skipped_text => proc { next _INTL("Not compiled") },
:compile => proc {
# Delete old data files in preparation for recompiling
get_all_pbs_data_filenames_to_compile.each do |filename|
begin
File.delete("Data/#{filename[0]}") if FileTest.exist?("Data/#{filename[0]}")
rescue SystemCallError
end
end
text_files = get_all_pbs_files_to_compile
compile_pbs_files(text_files)
}
}
module_function
def get_all_pbs_data_filenames_to_compile
ret = GameData.get_all_data_filenames
ret += [ # Extra .dat files for data that isn't a GameData class
["map_connections.dat", true],
["regional_dexes.dat", true],
["trainer_lists.dat", true]
]
return ret
end
def get_all_pbs_files_to_compile
# Get the GameData classes and their respective base PBS filenames
ret = GameData.get_all_pbs_base_filenames
ret.merge!({
:BattleFacility => "battle_facility_lists",
:Connection => "map_connections",
:RegionalDex => "regional_dexes"
})
ret.each { |key, val| ret[key] = [val] } # [base_filename, ["PBS/file.txt", etc.]]
# Look through all PBS files and match them to a GameData class based on
# their base filenames
text_files_keys = ret.keys.sort! { |a, b| ret[b][0].length <=> ret[a][0].length }
Dir.chdir("PBS/") do
Dir.glob("*.txt") do |f|
base_name = File.basename(f, ".txt")
text_files_keys.each do |key|
next if base_name != ret[key][0] && !f.start_with?(ret[key][0] + "_")
ret[key][1] ||= []
ret[key][1].push("PBS/" + f)
break
end
end
end
return ret
end
def should_compile_pbs_files?
# If no PBS folder exists, create one and fill it, then recompile
if !FileTest.directory?("PBS")
Dir.mkdir("PBS") rescue nil
GameData.load_all
write_all_pbs_files
return true
end
# Get all data files and PBS files to be checked for their last modified times
data_files = get_all_pbs_data_filenames_to_compile
text_files = get_all_pbs_files_to_compile
# Check data files for their latest modify time
latest_data_write_time = 0
data_files.each do |filename| # filename = [string, boolean (whether mandatory)]
if FileTest.exist?("Data/" + filename[0])
begin
File.open("Data/#{filename[0]}") do |file|
latest_data_write_time = [latest_data_write_time, file.mtime.to_i].max
end
rescue SystemCallError
return true
end
elsif filename[1]
return true
end
end
# Check PBS files for their latest modify time
latest_text_edit_time = 0
text_files.each_value do |value|
next if !value || !value[1].is_a?(Array)
value[1].each do |filepath|
begin
File.open(filepath) { |file| latest_text_edit_time = [latest_text_edit_time, file.mtime.to_i].max }
rescue SystemCallError
end
end
end
# Decide to compile if a PBS file was edited more recently than any .dat files
return (latest_text_edit_time >= latest_data_write_time)
end
def compile_pbs_files(text_files)
modify_pbs_file_contents_before_compiling
compile_town_map(*text_files[:TownMap][1])
compile_connections(*text_files[:Connection][1])
compile_types(*text_files[:Type][1])
compile_abilities(*text_files[:Ability][1])
compile_moves(*text_files[:Move][1]) # Depends on Type
compile_items(*text_files[:Item][1]) # Depends on Move
compile_berry_plants(*text_files[:BerryPlant][1]) # Depends on Item
compile_pokemon(*text_files[:Species][1]) # Depends on Move, Item, Type, Ability
compile_pokemon_forms(*text_files[:Species1][1]) # Depends on Species, Move, Item, Type, Ability
compile_pokemon_metrics(*text_files[:SpeciesMetrics][1]) # Depends on Species
compile_shadow_pokemon(*text_files[:ShadowPokemon][1]) # Depends on Species
compile_regional_dexes(*text_files[:RegionalDex][1]) # Depends on Species
compile_ribbons(*text_files[:Ribbon][1])
compile_encounters(*text_files[:Encounter][1]) # Depends on Species
compile_trainer_types(*text_files[:TrainerType][1])
compile_trainers(*text_files[:Trainer][1]) # Depends on Species, Item, Move
compile_trainer_lists # Depends on TrainerType
compile_metadata(*text_files[:Metadata][1]) # Depends on TrainerType
compile_map_metadata(*text_files[:MapMetadata][1])
compile_dungeon_tilesets(*text_files[:DungeonTileset][1])
compile_dungeon_parameters(*text_files[:DungeonParameters][1])
compile_phone(*text_files[:PhoneMessage][1]) # Depends on TrainerType
end
#-----------------------------------------------------------------------------
# Generic methods used when compiling PBS files.
#-----------------------------------------------------------------------------
def compile_pbs_file_message_start(filename)
# The `` around the file's name turns it cyan
Console.echo_li(_INTL("Compiling PBS file `{1}`...", filename.split("/").last))
end
def process_pbs_file_message_end
Console.echo_done(true)
Graphics.update
end
def compile_PBS_file_generic(game_data, *paths)
if game_data.const_defined?(:OPTIONAL) && game_data::OPTIONAL
return if paths.none? { |p| FileTest.exist?(p) }
end
game_data::DATA.clear
schema = game_data.schema
# Read from PBS file(s)
paths.each do |path|
compile_pbs_file_message_start(path)
base_filename = game_data::PBS_BASE_FILENAME
base_filename = base_filename[0] if base_filename.is_a?(Array) # For Species
file_suffix = File.basename(path, ".txt")[base_filename.length + 1, path.length] || ""
File.open(path, "rb") do |f|
FileLineData.file = path # 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).
idx = 0
pbEachFileSection(f, schema) do |contents, section_name|
echo "." if idx % 100 == 0
Graphics.update if idx % 500 == 0
idx += 1
data_hash = {
:id => section_name.to_sym,
:pbs_file_suffix => file_suffix
}
# Go through schema hash of compilable data and compile this section
schema.each_key do |key|
FileLineData.setSection(section_name, key, contents[key]) # For error reporting
if key == "SectionName"
data_hash[schema[key][0]] = get_csv_record(section_name, schema[key])
next
end
# Skip empty properties
next if contents[key].nil?
# Compile value for key
if schema[key][1][0] == "^"
contents[key].each do |val|
value = get_csv_record(val, schema[key])
value = nil if value.is_a?(Array) && value.empty?
data_hash[schema[key][0]] ||= []
data_hash[schema[key][0]].push(value)
end
data_hash[schema[key][0]].compact!
else
value = get_csv_record(contents[key], schema[key])
value = nil if value.is_a?(Array) && value.empty?
data_hash[schema[key][0]] = value
end
end
# Validate and modify the compiled data
yield false, data_hash if block_given?
if game_data.exists?(data_hash[:id])
raise _INTL("Section name '{1}' is used twice.", data_hash[:id]) + "\n" + FileLineData.linereport
end
# Add section's data to records
game_data.register(data_hash)
end
end
process_pbs_file_message_end
end
yield true, nil if block_given?
# Save all data
game_data.save
end
#-----------------------------------------------------------------------------
# Compile Town Map data.
#-----------------------------------------------------------------------------
def compile_town_map(*paths)
GameData::TownMap::DATA.clear
schema = GameData::TownMap.schema
sub_schema = GameData::TownMap.sub_schema
idx = 0
# Read from PBS file(s)
paths.each do |path|
compile_pbs_file_message_start(path)
file_suffix = File.basename(path, ".txt")[GameData::TownMap::PBS_BASE_FILENAME.length + 1, path.length] || ""
data_hash = nil
current_point = nil
section_name = nil
section_line = nil
# Read each line of town_map.txt at a time and compile it as a town map property
pbCompilerEachPreppedLine(path) do |line, line_no|
echo "." if idx % 100 == 0
idx += 1
Graphics.update if idx % 500 == 0
FileLineData.setSection(section_name, nil, section_line)
if line[/^\s*\[\s*(.+)\s*\]\s*$/]
# New section [region_number]
section_name = $~[1]
section_line = line
if data_hash
validate_compiled_town_map(data_hash)
GameData::TownMap.register(data_hash)
end
FileLineData.setSection(section_name, nil, section_line)
# Construct data hash
data_hash = {
:pbs_file_suffix => file_suffix
}
data_hash[schema["SectionName"][0]] = get_csv_record(section_name.clone, schema["SectionName"])
data_hash[schema["Point"][0]] = []
current_point = nil
elsif line[/^\s*(\w+)\s*=\s*(.*)$/]
# XXX=YYY lines
if !data_hash
raise _INTL("Expected a section at the beginning of the file.") + "\n" + FileLineData.linereport
end
key = $~[1]
if schema[key] # Property of the town map
property_value = get_csv_record($~[2], schema[key])
if key == "Point"
current_point = {
:position => [property_value[0], property_value[1]],
:real_name => property_value[2]
}
current_point[:real_description] = property_value[3] if property_value[3]
current_point[:fly_spot] = [property_value[4], property_value[5], property_value[6]] if property_value[4]
current_point[:switch] = property_value[7] if property_value[7]
data_hash[schema[key][0]].push(current_point)
else
data_hash[schema[key][0]] = property_value
end
elsif sub_schema[key] # Property of a point
if !current_point
raise _INTL("Property \"{1}\" is point-specific, but a point hasn't been defined yet.", key) + "\n" + FileLineData.linereport
end
current_point[sub_schema[key][0]] = get_csv_record($~[2], sub_schema[key])
end
end
end
# Add last town map's data to records
if data_hash
FileLineData.setSection(section_name, nil, section_line)
validate_compiled_town_map(data_hash)
GameData::TownMap.register(data_hash)
end
process_pbs_file_message_end
end
validate_all_compiled_town_maps
# Save all data
GameData::TownMap.save
end
def validate_compiled_town_map(hash)
end
def validate_all_compiled_town_maps
# Get town map names and descriptions for translating
region_names = []
point_names = []
point_descriptions = []
GameData::TownMap.each do |town_map|
region_names[town_map.id] = town_map.real_name
town_map.points.each do |point|
point_names.push(point[:real_name])
point_descriptions.push(point[:real_description])
end
end
point_names.uniq!
point_descriptions.uniq!
MessageTypes.setMessagesAsHash(MessageTypes::REGION_NAMES, region_names)
MessageTypes.setMessagesAsHash(MessageTypes::REGION_LOCATION_NAMES, point_names)
MessageTypes.setMessagesAsHash(MessageTypes::REGION_LOCATION_DESCRIPTIONS, point_descriptions)
end
#-----------------------------------------------------------------------------
# Compile map connections.
#-----------------------------------------------------------------------------
def compile_connections(*paths)
hashenum = {
"N" => "N", "North" => "N",
"E" => "E", "East" => "E",
"S" => "S", "South" => "S",
"W" => "W", "West" => "W"
}
schema = [nil, "iyiiyi", nil, hashenum, nil, nil, hashenum]
records = []
paths.each do |path|
compile_pbs_file_message_start(path)
pbCompilerEachPreppedLine(path) do |line, lineno|
FileLineData.setLine(line, lineno)
record = get_csv_record(line, schema)
if !pbRgssExists?(sprintf("Data/Map%03d.rxdata", record[0]))
print _INTL("Warning: Map {1}, as mentioned in the map connection data, was not found.", record[0]) + "\n" + FileLineData.linereport
elsif !pbRgssExists?(sprintf("Data/Map%03d.rxdata", record[3]))
print _INTL("Warning: Map {1}, as mentioned in the map connection data, was not found.", record[3]) + "\n" + FileLineData.linereport
end
case record[1]
when "N"
raise _INTL("North side of first map must connect with south side of second map.") + "\n" + FileLineData.linereport if record[4] != "S"
when "S"
raise _INTL("South side of first map must connect with north side of second map.") + "\n" + FileLineData.linereport if record[4] != "N"
when "E"
raise _INTL("East side of first map must connect with west side of second map.") + "\n" + FileLineData.linereport if record[4] != "W"
when "W"
raise _INTL("West side of first map must connect with east side of second map.") + "\n" + FileLineData.linereport if record[4] != "E"
end
records.push(record)
end
process_pbs_file_message_end
end
save_data(records, "Data/map_connections.dat")
end
#-----------------------------------------------------------------------------
# Compile type data.
#-----------------------------------------------------------------------------
def compile_types(*paths)
compile_PBS_file_generic(GameData::Type, *paths) do |final_validate, hash|
(final_validate) ? validate_all_compiled_types : validate_compiled_type(hash)
end
end
def validate_compiled_type(hash)
# Remove duplicate weaknesses/resistances/immunities
hash[:weaknesses].uniq! if hash[:weaknesses].is_a?(Array)
hash[:resistances].uniq! if hash[:resistances].is_a?(Array)
hash[:immunities].uniq! if hash[:immunities].is_a?(Array)
end
def validate_all_compiled_types
type_names = []
GameData::Type.each do |type|
# Ensure all weaknesses/resistances/immunities are valid types
type.weaknesses.each do |other_type|
next if GameData::Type.exists?(other_type)
raise _INTL("'{1}' is not a defined type (type {2}, Weaknesses).", other_type.to_s, type.id)
end
type.resistances.each do |other_type|
next if GameData::Type.exists?(other_type)
raise _INTL("'{1}' is not a defined type (type {2}, Resistances).", other_type.to_s, type.id)
end
type.immunities.each do |other_type|
next if GameData::Type.exists?(other_type)
raise _INTL("'{1}' is not a defined type (type {2}, Immunities).", other_type.to_s, type.id)
end
# Get type names for translating
type_names.push(type.real_name)
end
MessageTypes.setMessagesAsHash(MessageTypes::TYPE_NAMES, type_names)
end
#-----------------------------------------------------------------------------
# Compile ability data.
#-----------------------------------------------------------------------------
def compile_abilities(*paths)
compile_PBS_file_generic(GameData::Ability, *paths) do |final_validate, hash|
(final_validate) ? validate_all_compiled_abilities : validate_compiled_ability(hash)
end
end
def validate_compiled_ability(hash)
end
def validate_all_compiled_abilities
# Get abilty names/descriptions for translating
ability_names = []
ability_descriptions = []
GameData::Ability.each do |ability|
ability_names.push(ability.real_name)
ability_descriptions.push(ability.real_description)
end
MessageTypes.setMessagesAsHash(MessageTypes::ABILITY_NAMES, ability_names)
MessageTypes.setMessagesAsHash(MessageTypes::ABILITY_DESCRIPTIONS, ability_descriptions)
end
#-----------------------------------------------------------------------------
# Compile move data.
#-----------------------------------------------------------------------------
def compile_moves(*paths)
compile_PBS_file_generic(GameData::Move, *paths) do |final_validate, hash|
(final_validate) ? validate_all_compiled_moves : validate_compiled_move(hash)
end
end
def validate_compiled_move(hash)
if (hash[:category] || 2) == 2 && (hash[:power] || 0) != 0
raise _INTL("Move {1} is defined as a Status move with a non-zero base damage.", hash[:real_name]) + "\n" + FileLineData.linereport
elsif (hash[:category] || 2) != 2 && (hash[:power] || 0) == 0
print _INTL("Warning: Move {1} is defined as Physical or Special but has a base damage of 0. Changing it to a Status move.", hash[:real_name]) + "\n" + FileLineData.linereport
hash[:category] = 2
end
end
def validate_all_compiled_moves
# Get move names/descriptions for translating
move_names = []
move_descriptions = []
GameData::Move.each do |move|
move_names.push(move.real_name)
move_descriptions.push(move.real_description)
end
MessageTypes.setMessagesAsHash(MessageTypes::MOVE_NAMES, move_names)
MessageTypes.setMessagesAsHash(MessageTypes::MOVE_DESCRIPTIONS, move_descriptions)
end
#-----------------------------------------------------------------------------
# Compile item data.
#-----------------------------------------------------------------------------
def compile_items(*paths)
compile_PBS_file_generic(GameData::Item, *paths) do |final_validate, hash|
(final_validate) ? validate_all_compiled_items : validate_compiled_item(hash)
end
end
def validate_compiled_item(hash)
# Support for pockets still being numbers
if hash[:pocket] && hash[:pocket].is_a?(Integer)
all_pockets = GameData::BagPocket.all_pockets
if hash[:pocket] <= 0 || !all_pockets[hash[:pocket] - 1]
raise _INTL("Invalid pocket number {1} for item {2}.", hash[:pocket], hash[:id])
end
hash[:pocket] = all_pockets[hash[:pocket] - 1]
end
end
def validate_all_compiled_items
# Get item names/descriptions for translating
item_names = []
item_names_plural = []
item_portion_names = []
item_portion_names_plural = []
item_descriptions = []
GameData::Item.each do |item|
item_names.push(item.real_name)
item_names_plural.push(item.real_name_plural)
item_portion_names.push(item.real_portion_name)
item_portion_names_plural.push(item.real_portion_name_plural)
item_descriptions.push(item.real_description)
end
MessageTypes.setMessagesAsHash(MessageTypes::ITEM_NAMES, item_names)
MessageTypes.setMessagesAsHash(MessageTypes::ITEM_NAME_PLURALS, item_names_plural)
MessageTypes.setMessagesAsHash(MessageTypes::ITEM_PORTION_NAMES, item_portion_names)
MessageTypes.setMessagesAsHash(MessageTypes::ITEM_PORTION_NAME_PLURALS, item_portion_names_plural)
MessageTypes.setMessagesAsHash(MessageTypes::ITEM_DESCRIPTIONS, item_descriptions)
end
#-----------------------------------------------------------------------------
# Compile berry plant data.
#-----------------------------------------------------------------------------
def compile_berry_plants(*paths)
compile_PBS_file_generic(GameData::BerryPlant, *paths) do |final_validate, hash|
(final_validate) ? validate_all_compiled_berry_plants : validate_compiled_berry_plant(hash)
end
end
def validate_compiled_berry_plant(hash)
end
def validate_all_compiled_berry_plants
end
#-----------------------------------------------------------------------------
# Compile Pokémon data.
#-----------------------------------------------------------------------------
def compile_pokemon(*paths)
compile_PBS_file_generic(GameData::Species, *paths) do |final_validate, hash|
(final_validate) ? validate_all_compiled_pokemon : validate_compiled_pokemon(hash)
end
end
# NOTE: This method is also called by def validate_compiled_pokemon_form
# below, and since a form's hash can contain very little data, don't
# assume any data exists.
def validate_compiled_pokemon(hash)
# Convert base stats array to a hash
if hash[:base_stats].is_a?(Array)
new_stats = {}
GameData::Stat.each_main do |s|
new_stats[s.id] = (hash[:base_stats][s.pbs_order] || 1) if s.pbs_order >= 0
end
hash[:base_stats] = new_stats
end
# Convert EVs array to a hash
if hash[:evs].is_a?(Array)
new_evs = {}
hash[:evs].each { |val| new_evs[val[0]] = val[1] }
GameData::Stat.each_main { |s| new_evs[s.id] ||= 0 }
hash[:evs] = new_evs
end
# Convert height and weight to integer values of tenths of a unit
hash[:height] = [(hash[:height] * 10).round, 1].max if hash[:height]
hash[:weight] = [(hash[:weight] * 10).round, 1].max if hash[:weight]
# Ensure evolutions have a parameter if they need one (don't need to ensure
# the parameter makes sense; that happens below)
if hash[:evolutions]
hash[:evolutions].each do |evo|
FileLineData.setSection(hash[:id].to_s, "Evolution", "Evolution = #{evo[0]},#{evo[1]}") # For error reporting
param_type = GameData::Evolution.get(evo[1]).parameter
next if evo[2] || param_type.nil?
raise _INTL("Evolution method {1} requires a parameter, but none was given.", evo[1]) + "\n" + FileLineData.linereport
end
end
# Record all evolutions as not being prevolutions
if hash[:evolutions].is_a?(Array)
hash[:evolutions].each { |evo| evo[3] = false }
end
# Remove duplicate types
if hash[:types].is_a?(Array)
hash[:types].uniq!
hash[:types].compact!
end
end
def validate_all_compiled_pokemon
# Enumerate all offspring species (this couldn't be done earlier)
GameData::Species.each do |species|
FileLineData.setSection(species.id.to_s, "Offspring", nil) # For error reporting
offspring = species.offspring
offspring.each_with_index do |sp, i|
offspring[i] = cast_csv_value(sp, "e", :Species)
end
end
# Enumerate all evolution species and parameters (this couldn't be done earlier)
GameData::Species.each do |species|
FileLineData.setSection(species.id.to_s, "Evolutions", nil) # For error reporting
species.evolutions.each do |evo|
evo[0] = cast_csv_value(evo[0], "e", :Species)
param_type = GameData::Evolution.get(evo[1]).parameter
if param_type.nil?
evo[2] = nil
elsif param_type == Integer
evo[2] = cast_csv_value(evo[2], "u")
elsif param_type != String
evo[2] = cast_csv_value(evo[2], "e", param_type)
end
end
end
# Add prevolution "evolution" entry for all evolved species
all_evos = {}
GameData::Species.each do |species| # Build a hash of prevolutions for each species
species.evolutions.each do |evo|
all_evos[evo[0]] = [species.species, evo[1], evo[2], true] if !all_evos[evo[0]]
end
end
GameData::Species.each do |species| # Distribute prevolutions
species.evolutions.push(all_evos[species.species].clone) if all_evos[species.species]
end
# Get species names/descriptions for translating
species_names = []
species_form_names = []
species_categories = []
species_pokedex_entries = []
GameData::Species.each do |species|
species_names.push(species.real_name)
species_form_names.push(species.real_form_name)
species_categories.push(species.real_category)
species_pokedex_entries.push(species.real_pokedex_entry)
end
MessageTypes.setMessagesAsHash(MessageTypes::SPECIES_NAMES, species_names)
MessageTypes.setMessagesAsHash(MessageTypes::SPECIES_FORM_NAMES, species_form_names)
MessageTypes.setMessagesAsHash(MessageTypes::SPECIES_CATEGORIES, species_categories)
MessageTypes.setMessagesAsHash(MessageTypes::POKEDEX_ENTRIES, species_pokedex_entries)
end
#-----------------------------------------------------------------------------
# Compile Pokémon forms data.
# NOTE: Doesn't use compile_PBS_file_generic because it needs its own schema
# and shouldn't clear GameData::Species at the start.
#-----------------------------------------------------------------------------
def compile_pokemon_forms(*paths)
schema = GameData::Species.schema(true)
# Read from PBS file(s)
paths.each do |path|
compile_pbs_file_message_start(path)
file_suffix = File.basename(path, ".txt")[GameData::Species::PBS_BASE_FILENAME[1].length + 1, path.length] || ""
File.open(path, "rb") do |f|
FileLineData.file = path # 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).
idx = 0
pbEachFileSection(f, schema) do |contents, section_name|
echo "." if idx % 100 == 0
Graphics.update if idx % 500 == 0
idx += 1
data_hash = {
:id => section_name.to_sym,
:pbs_file_suffix => file_suffix
}
# Go through schema hash of compilable data and compile this section
schema.each_key do |key|
FileLineData.setSection(section_name, key, contents[key]) # For error reporting
if key == "SectionName"
data_hash[schema[key][0]] = get_csv_record(section_name, schema[key])
next
end
# Skip empty properties
next if contents[key].nil?
# Compile value for key
if schema[key][1][0] == "^"
contents[key].each do |val|
value = get_csv_record(val, schema[key])
value = nil if value.is_a?(Array) && value.empty?
data_hash[schema[key][0]] ||= []
data_hash[schema[key][0]].push(value)
end
data_hash[schema[key][0]].compact!
else
value = get_csv_record(contents[key], schema[key])
value = nil if value.is_a?(Array) && value.empty?
data_hash[schema[key][0]] = value
end
end
# Validate and modify the compiled data
validate_compiled_pokemon_form(data_hash)
if GameData::Species.exists?(data_hash[:id])
raise _INTL("Section name '{1}' is used twice.", data_hash[:id]) + "\n" + FileLineData.linereport
end
# Add section's data to records
GameData::Species.register(data_hash)
end
end
process_pbs_file_message_end
end
validate_all_compiled_pokemon_forms
# Save all data
GameData::Species.save
end
def validate_compiled_pokemon_form(hash)
# Split species and form into their own values, generate compound ID from them
hash[:species] = hash[:id][0]
hash[:form] = hash[:id][1]
hash[:id] = sprintf("%s_%d", hash[:species].to_s, hash[:form]).to_sym
if !GameData::Species.exists?(hash[:species])
raise _INTL("Undefined species ID '{1}'.", hash[:species]) + "\n" + FileLineData.linereport
elsif GameData::Species.exists?(hash[:id])
raise _INTL("Form {1} for species ID '{2}' is defined twice.", hash[:form], hash[:species]) + "\n" + FileLineData.linereport
end
# Perform the same validations on this form as for a regular species
validate_compiled_pokemon(hash)
# Inherit undefined properties from base species
base_data = GameData::Species.get(hash[:species])
[:real_name, :real_category, :real_pokedex_entry, :base_exp, :growth_rate,
:gender_ratio, :catch_rate, :happiness, :hatch_steps, :incense, :height,
:weight, :color, :shape, :habitat, :generation].each do |property|
hash[property] = base_data.send(property) if hash[property].nil?
end
[:types, :base_stats, :evs, :tutor_moves, :egg_moves, :abilities,
:hidden_abilities, :egg_groups, :offspring, :flags].each do |property|
hash[property] = base_data.send(property).clone if hash[property].nil?
end
if !hash[:moves].is_a?(Array) || hash[:moves].length == 0
hash[:moves] ||= []
base_data.moves.each { |m| hash[:moves].push(m.clone) }
end
if !hash[:evolutions].is_a?(Array) || hash[:evolutions].length == 0
hash[:evolutions] ||= []
base_data.evolutions.each { |e| hash[:evolutions].push(e.clone) }
end
if hash[:wild_item_common].nil? && hash[:wild_item_uncommon].nil? &&
hash[:wild_item_rare].nil?
hash[:wild_item_common] = base_data.wild_item_common.clone
hash[:wild_item_uncommon] = base_data.wild_item_uncommon.clone
hash[:wild_item_rare] = base_data.wild_item_rare.clone
end
end
def validate_all_compiled_pokemon_forms
# Enumerate all evolution parameters (this couldn't be done earlier)
GameData::Species.each do |species|
FileLineData.setSection(species.id.to_s, "Evolutions", nil) # For error reporting
species.evolutions.each do |evo|
param_type = GameData::Evolution.get(evo[1]).parameter
if param_type.nil?
evo[2] = nil
elsif param_type == Integer
evo[2] = cast_csv_value(evo[2], "u") if evo[2].is_a?(String)
elsif param_type != String
evo[2] = cast_csv_value(evo[2], "e", param_type) if evo[2].is_a?(String)
end
end
end
# Add prevolution "evolution" entry for all evolved species
all_evos = {}
GameData::Species.each do |species| # Build a hash of prevolutions for each species
species.evolutions.each do |evo|
next if evo[3]
all_evos[evo[0]] = [species.species, evo[1], evo[2], true] if !all_evos[evo[0]]
if species.form > 0
all_evos[[evo[0], species.form]] = [species.species, evo[1], evo[2], true] if !all_evos[[evo[0], species.form]]
end
end
end
GameData::Species.each do |species| # Distribute prevolutions
prevo_data = all_evos[[species.species, species.base_form]] || all_evos[species.species]
next if !prevo_data
# Record what species evolves from
species.evolutions.delete_if { |evo| evo[3] }
species.evolutions.push(prevo_data.clone)
# Record that the prevolution can evolve into species
prevo = GameData::Species.get(prevo_data[0])
if prevo.evolutions.none? { |evo| !evo[3] && evo[0] == species.species }
prevo.evolutions.push([species.species, :None, nil])
end
end
# Get species names/descriptions for translating
species_form_names = []
species_categories = []
species_pokedex_entries = []
GameData::Species.each do |species|
next if species.form == 0
species_form_names.push(species.real_form_name)
species_categories.push(species.real_category)
species_pokedex_entries.push(species.real_pokedex_entry)
end
MessageTypes.addMessagesAsHash(MessageTypes::SPECIES_FORM_NAMES, species_form_names)
MessageTypes.addMessagesAsHash(MessageTypes::SPECIES_CATEGORIES, species_categories)
MessageTypes.addMessagesAsHash(MessageTypes::POKEDEX_ENTRIES, species_pokedex_entries)
end
#-----------------------------------------------------------------------------
# Compile Pokémon metrics data.
#-----------------------------------------------------------------------------
def compile_pokemon_metrics(*paths)
compile_PBS_file_generic(GameData::SpeciesMetrics, *paths) do |final_validate, hash|
(final_validate) ? validate_all_compiled_pokemon_metrics : validate_compiled_pokemon_metrics(hash)
end
end
def validate_compiled_pokemon_metrics(hash)
# Split species and form into their own values, generate compound ID from them
if hash[:id].is_a?(Array)
hash[:species] = hash[:id][0]
hash[:form] = hash[:id][1] || 0
if hash[:form] == 0
hash[:id] = hash[:species]
else
hash[:id] = sprintf("%s_%d", hash[:species].to_s, hash[:form]).to_sym
end
end
end
def validate_all_compiled_pokemon_metrics
end
#-----------------------------------------------------------------------------
# Compile Shadow Pokémon data.
#-----------------------------------------------------------------------------
def compile_shadow_pokemon(*paths)
compile_PBS_file_generic(GameData::ShadowPokemon, *paths) do |final_validate, hash|
(final_validate) ? validate_all_compiled_shadow_pokemon : validate_compiled_shadow_pokemon(hash)
end
end
def validate_compiled_shadow_pokemon(hash)
# Split species and form into their own values, generate compound ID from them
if hash[:id].is_a?(Array)
hash[:species] = hash[:id][0]
hash[:form] = hash[:id][1] || 0
if hash[:form] == 0
hash[:id] = hash[:species]
else
hash[:id] = sprintf("%s_%d", hash[:species].to_s, hash[:form]).to_sym
end
end
end
def validate_all_compiled_shadow_pokemon
end
#-----------------------------------------------------------------------------
# Compile Regional Dexes.
#-----------------------------------------------------------------------------
def compile_regional_dexes(*paths)
dex_lists = []
paths.each do |path|
compile_pbs_file_message_start(path)
section = nil
pbCompilerEachPreppedLine(path) do |line, line_no|
Graphics.update if line_no % 200 == 0
if line[/^\s*\[\s*(\d+)\s*\]\s*$/]
section = $~[1].to_i
if dex_lists[section]
raise _INTL("Dex list number {1} is defined at least twice.", section) + "\n" + FileLineData.linereport
end
dex_lists[section] = []
else
raise _INTL("Expected a section at the beginning of the file.") + "\n" + FileLineData.linereport if !section
species_list = line.split(",")
species_list.each do |species|
next if !species || species.empty?
s = parseSpecies(species)
dex_lists[section].push(s)
end
end
end
process_pbs_file_message_end
end
# Check for duplicate species in a Regional Dex
dex_lists.each_with_index do |list, index|
unique_list = list.uniq
next if list == unique_list
list.each_with_index do |s, i|
next if unique_list[i] == s
raise _INTL("Dex list number {1} has species {2} listed twice.", index, s) + "\n" + FileLineData.linereport
end
end
# Save all data
save_data(dex_lists, "Data/regional_dexes.dat")
end
#-----------------------------------------------------------------------------
# Compile ribbon data.
#-----------------------------------------------------------------------------
def compile_ribbons(*paths)
compile_PBS_file_generic(GameData::Ribbon, *paths) do |final_validate, hash|
(final_validate) ? validate_all_compiled_ribbons : validate_compiled_ribbon(hash)
end
end
def validate_compiled_ribbon(hash)
end
def validate_all_compiled_ribbons
# Get ribbon names/descriptions for translating
ribbon_names = []
ribbon_descriptions = []
GameData::Ribbon.each do |ribbon|
ribbon_names.push(ribbon.real_name)
ribbon_descriptions.push(ribbon.real_description)
end
MessageTypes.setMessagesAsHash(MessageTypes::RIBBON_NAMES, ribbon_names)
MessageTypes.setMessagesAsHash(MessageTypes::RIBBON_DESCRIPTIONS, ribbon_descriptions)
end
#-----------------------------------------------------------------------------
# Compile wild encounter data.
#-----------------------------------------------------------------------------
def compile_encounters(*paths)
GameData::Encounter::DATA.clear
max_level = GameData::GrowthRate.max_level
paths.each do |path|
compile_pbs_file_message_start(path)
file_suffix = File.basename(path, ".txt")[GameData::Encounter::PBS_BASE_FILENAME.length + 1, path.length] || ""
encounter_hash = nil
step_chances = nil
current_type = nil
idx = 0
pbCompilerEachPreppedLine(path) do |line, line_no|
echo "." if idx % 100 == 0
idx += 1
Graphics.update if idx % 500 == 0
next if line.length == 0
if current_type && line[/^\d+,/] # Species line
values = line.split(",").collect! { |v| v.strip }
if !values || values.length < 3
raise _INTL("Expected a species entry line for encounter type {1} for map {2}.",
GameData::EncounterType.get(current_type).real_name, encounter_hash[:map]) + "\n" + FileLineData.linereport
end
values = get_csv_record(line, [nil, "vevV", nil, :Species])
values[3] = values[2] if !values[3]
if values[2] > max_level
raise _INTL("Level number {1} is not valid (max. {2}).", values[2], max_level) + "\n" + FileLineData.linereport
elsif values[3] > max_level
raise _INTL("Level number {1} is not valid (max. {2}).", values[3], max_level) + "\n" + FileLineData.linereport
elsif values[2] > values[3]
raise _INTL("Minimum level is greater than maximum level.") + "\n" + FileLineData.linereport
end
encounter_hash[:types][current_type].push(values)
elsif line[/^\[\s*(.+)\s*\]$/] # Map ID line
values = $~[1].split(",").collect! { |v| v.strip.to_i }
values[1] = 0 if !values[1]
map_number = values[0]
map_version = values[1]
# Add map encounter's data to records
if encounter_hash
encounter_hash[:types].each_value do |slots|
next if !slots || slots.length == 0
slots.each_with_index do |slot, i|
next if !slot
slots.each_with_index do |other_slot, j|
next if i == j || !other_slot
next if slot[1] != other_slot[1] || slot[2] != other_slot[2] || slot[3] != other_slot[3]
slot[0] += other_slot[0]
slots[j] = nil
end
end
slots.compact!
slots.sort! { |a, b| (a[0] == b[0]) ? a[1].to_s <=> b[1].to_s : b[0] <=> a[0] }
end
GameData::Encounter.register(encounter_hash)
end
# Raise an error if a map/version combo is used twice
key = sprintf("%s_%d", map_number, map_version).to_sym
if GameData::Encounter::DATA[key]
raise _INTL("Encounters for map '{1}' are defined twice.", map_number) + "\n" + FileLineData.linereport
end
step_chances = {}
# Construct encounter hash
encounter_hash = {
:id => key,
:map => map_number,
:version => map_version,
:step_chances => step_chances,
:types => {},
:pbs_file_suffix => file_suffix
}
current_type = nil
elsif !encounter_hash # File began with something other than a map ID line
raise _INTL("Expected a map number, got \"{1}\" instead.", line) + "\n" + FileLineData.linereport
else
# Check if line is an encounter method name or not
values = line.split(",").collect! { |v| v.strip }
current_type = (values[0] && !values[0].empty?) ? values[0].to_sym : nil
if current_type && GameData::EncounterType.exists?(current_type) # Start of a new encounter method
step_chances[current_type] = values[1].to_i if values[1] && !values[1].empty?
step_chances[current_type] ||= GameData::EncounterType.get(current_type).trigger_chance
encounter_hash[:types][current_type] = []
else
raise _INTL("Undefined encounter type \"{1}\" for map '{2}'.", line, encounter_hash[:map]) + "\n" + FileLineData.linereport
end
end
end
# Add last map's encounter data to records
if encounter_hash
encounter_hash[:types].each_value do |slots|
next if !slots || slots.length == 0
slots.each_with_index do |slot, i|
next if !slot
slots.each_with_index do |other_slot, j|
next if i == j || !other_slot
next if slot[1] != other_slot[1] || slot[2] != other_slot[2] || slot[3] != other_slot[3]
slot[0] += other_slot[0]
slots[j] = nil
end
end
slots.compact!
slots.sort! { |a, b| (a[0] == b[0]) ? a[1].to_s <=> b[1].to_s : b[0] <=> a[0] }
end
GameData::Encounter.register(encounter_hash)
end
process_pbs_file_message_end
end
# Save all data
GameData::Encounter.save
end
#-----------------------------------------------------------------------------
# Compile trainer type data.
#-----------------------------------------------------------------------------
def compile_trainer_types(*paths)
compile_PBS_file_generic(GameData::TrainerType, *paths) do |final_validate, hash|
(final_validate) ? validate_all_compiled_trainer_types : validate_compiled_trainer_type(hash)
end
end
def validate_compiled_trainer_type(hash)
# Ensure valid Poké Ball
if hash[:poke_ball]
if !GameData::Item.get(hash[:poke_ball]).is_poke_ball?
raise _INTL("Value '{1}' isn't a defined Poké Ball.", hash[:poke_ball]) + "\n" + FileLineData.linereport
end
end
end
def validate_all_compiled_trainer_types
# Get trainer type names for translating
trainer_type_names = []
GameData::TrainerType.each do |tr_type|
trainer_type_names.push(tr_type.real_name)
end
MessageTypes.setMessagesAsHash(MessageTypes::TRAINER_TYPE_NAMES, trainer_type_names)
end
#-----------------------------------------------------------------------------
# Compile individual trainer data.
#-----------------------------------------------------------------------------
def compile_trainers(*paths)
GameData::Trainer::DATA.clear
schema = GameData::Trainer.schema
sub_schema = GameData::Trainer.sub_schema
idx = 0
# Read from PBS file(s)
paths.each do |path|
compile_pbs_file_message_start(path)
file_suffix = File.basename(path, ".txt")[GameData::Trainer::PBS_BASE_FILENAME.length + 1, path.length] || ""
data_hash = nil
current_pkmn = nil
section_name = nil
section_line = nil
# Read each line of trainers.txt at a time and compile it as a trainer property
pbCompilerEachPreppedLine(path) do |line, line_no|
echo "." if idx % 100 == 0
idx += 1
Graphics.update if idx % 500 == 0
FileLineData.setSection(section_name, nil, section_line)
if line[/^\s*\[\s*(.+)\s*\]\s*$/]
# New section [trainer_type, name] or [trainer_type, name, version]
section_name = $~[1]
section_line = line
if data_hash
validate_compiled_trainer(data_hash)
GameData::Trainer.register(data_hash)
end
FileLineData.setSection(section_name, nil, section_line)
# Construct data hash
data_hash = {
:pbs_file_suffix => file_suffix
}
data_hash[schema["SectionName"][0]] = get_csv_record(section_name.clone, schema["SectionName"])
data_hash[schema["Pokemon"][0]] = []
current_pkmn = nil
elsif line[/^\s*(\w+)\s*=\s*(.*)$/]
# XXX=YYY lines
if !data_hash
raise _INTL("Expected a section at the beginning of the file.") + "\n" + FileLineData.linereport
end
key = $~[1]
if schema[key] # Property of the trainer
property_value = get_csv_record($~[2], schema[key])
if key == "Pokemon"
current_pkmn = {
:species => property_value[0],
:level => property_value[1]
}
data_hash[schema[key][0]].push(current_pkmn)
else
data_hash[schema[key][0]] = property_value
end
elsif sub_schema[key] # Property of a Pokémon
if !current_pkmn
raise _INTL("Property \"{1}\" is Pokémon-specific, but a Pokémon hasn't been defined yet.", key) + "\n" + FileLineData.linereport
end
current_pkmn[sub_schema[key][0]] = get_csv_record($~[2], sub_schema[key])
end
end
end
# Add last trainer's data to records
if data_hash
FileLineData.setSection(section_name, nil, section_line)
validate_compiled_trainer(data_hash)
GameData::Trainer.register(data_hash)
end
process_pbs_file_message_end
end
validate_all_compiled_trainers
# Save all data
GameData::Trainer.save
end
def validate_compiled_trainer(hash)
# Split trainer type, name and version into their own values, generate compound ID from them
hash[:id][2] ||= 0
hash[:trainer_type] = hash[:id][0]
hash[:real_name] = hash[:id][1]
hash[:version] = hash[:id][2]
# Ensure the trainer has at least one Pokémon
if hash[:pokemon].empty?
raise _INTL("Trainer with ID '{1}' has no Pokémon.", hash[:id]) + "\n" + FileLineData.linereport
end
max_level = GameData::GrowthRate.max_level
hash[:pokemon].each do |pkmn|
# Ensure valid level
if pkmn[:level] > max_level
raise _INTL("Invalid Pokémon level {1} (must be 1-{2}).", pkmn[:level], max_level) + "\n" + FileLineData.linereport
end
# Ensure valid name length
if pkmn[:real_name] && pkmn[:real_name].length > Pokemon::MAX_NAME_SIZE
raise _INTL("Invalid Pokémon nickname: {1} (must be 1-{2} characters).",
pkmn[:real_name], Pokemon::MAX_NAME_SIZE) + "\n" + FileLineData.linereport
end
# Ensure no duplicate moves
pkmn[:moves].uniq! if pkmn[:moves]
# Ensure valid IVs, convert IVs to hash format
if pkmn[:iv]
iv_hash = {}
GameData::Stat.each_main do |s|
next if s.pbs_order < 0
iv_hash[s.id] = pkmn[:iv][s.pbs_order] || pkmn[:iv][0]
if iv_hash[s.id] > Pokemon::IV_STAT_LIMIT
raise _INTL("Invalid IV: {1} (must be 0-{2}).", iv_hash[s.id], Pokemon::IV_STAT_LIMIT) + "\n" + FileLineData.linereport
end
end
pkmn[:iv] = iv_hash
end
# Ensure valid EVs, convert EVs to hash format
if pkmn[:ev]
ev_hash = {}
ev_total = 0
GameData::Stat.each_main do |s|
next if s.pbs_order < 0
ev_hash[s.id] = pkmn[:ev][s.pbs_order] || pkmn[:ev][0]
ev_total += ev_hash[s.id]
if ev_hash[s.id] > Pokemon::EV_STAT_LIMIT
raise _INTL("Invalid EV: {1} (must be 0-{2}).", ev_hash[s.id], Pokemon::EV_STAT_LIMIT) + "\n" + FileLineData.linereport
end
end
pkmn[:ev] = ev_hash
if ev_total > Pokemon::EV_LIMIT
raise _INTL("Invalid EV set (must sum to {1} or less).", Pokemon::EV_LIMIT) + "\n" + FileLineData.linereport
end
end
# Ensure valid happiness
if pkmn[:happiness]
if pkmn[:happiness] > 255
raise _INTL("Bad happiness: {1} (must be 0-255).", pkmn[:happiness]) + "\n" + FileLineData.linereport
end
end
# Ensure valid Poké Ball
if pkmn[:poke_ball]
if !GameData::Item.get(pkmn[:poke_ball]).is_poke_ball?
raise _INTL("Value '{1}' isn't a defined Poké Ball.", pkmn[:poke_ball]) + "\n" + FileLineData.linereport
end
end
end
end
def validate_all_compiled_trainers
# Get trainer names and lose texts for translating
trainer_names = []
lose_texts = []
pokemon_nicknames = []
GameData::Trainer.each do |trainer|
trainer_names.push(trainer.real_name)
lose_texts.push(trainer.real_lose_text)
trainer.pokemon.each do |pkmn|
pokemon_nicknames.push(pkmn[:real_name]) if !nil_or_empty?(pkmn[:real_name])
end
end
MessageTypes.setMessagesAsHash(MessageTypes::TRAINER_NAMES, trainer_names)
MessageTypes.setMessagesAsHash(MessageTypes::TRAINER_SPEECHES_LOSE, lose_texts)
MessageTypes.setMessagesAsHash(MessageTypes::POKEMON_NICKNAMES, pokemon_nicknames)
end
#-----------------------------------------------------------------------------
# Compile Battle Tower and other Cups trainers/Pokémon.
#-----------------------------------------------------------------------------
def compile_trainer_lists(path = "PBS/battle_facility_lists.txt")
compile_pbs_file_message_start(path)
btTrainersRequiredTypes = {
"Trainers" => [0, "s"],
"Pokemon" => [1, "s"],
"Challenges" => [2, "*s"]
}
if !FileTest.exist?(path)
File.open(path, "wb") do |f|
f.write(0xEF.chr)
f.write(0xBB.chr)
f.write(0xBF.chr)
f.write("[DefaultTrainerList]\r\n")
f.write("Trainers = battle_tower_trainers.txt\r\n")
f.write("Pokemon = battle_tower_pokemon.txt\r\n")
end
end
sections = []
MessageTypes.setMessagesAsHash(MessageTypes::FRONTIER_INTRO_SPEECHES, [])
MessageTypes.setMessagesAsHash(MessageTypes::FRONTIER_END_SPEECHES_WIN, [])
MessageTypes.setMessagesAsHash(MessageTypes::FRONTIER_END_SPEECHES_LOSE, [])
File.open(path, "rb") do |f|
FileLineData.file = path
idx = 0
pbEachFileSection(f) do |section, name|
echo "."
idx += 1
Graphics.update
next if name != "DefaultTrainerList" && name != "TrainerList"
rsection = []
section.each_key do |key|
FileLineData.setSection(name, key, section[key])
schema = btTrainersRequiredTypes[key]
next if key == "Challenges" && name == "DefaultTrainerList"
next if !schema
record = get_csv_record(section[key], schema)
rsection[schema[0]] = record
end
if !rsection[0]
raise _INTL("No trainer data file given in section {1}.", name) + "\n" + FileLineData.linereport
end
if !rsection[1]
raise _INTL("No trainer data file given in section {1}.", name) + "\n" + FileLineData.linereport
end
rsection[3] = rsection[0]
rsection[4] = rsection[1]
rsection[5] = (name == "DefaultTrainerList")
if FileTest.exist?("PBS/" + rsection[0])
rsection[0] = compile_battle_tower_trainers("PBS/" + rsection[0])
else
rsection[0] = []
end
if FileTest.exist?("PBS/" + rsection[1])
filename = "PBS/" + rsection[1]
rsection[1] = []
pbCompilerEachCommentedLine(filename) do |line, _lineno|
rsection[1].push(PBPokemon.fromInspected(line))
end
else
rsection[1] = []
end
rsection[2] = [] if !rsection[2]
while rsection[2].include?("")
rsection[2].delete("")
end
rsection[2].compact!
sections.push(rsection)
end
end
save_data(sections, "Data/trainer_lists.dat")
process_pbs_file_message_end
end
def compile_battle_tower_trainers(filename)
sections = []
requiredtypes = {
"Type" => [0, "e", :TrainerType],
"Name" => [1, "s"],
"BeginSpeech" => [2, "s"],
"EndSpeechWin" => [3, "s"],
"EndSpeechLose" => [4, "s"],
"PokemonNos" => [5, "*u"]
}
trainernames = []
beginspeech = []
endspeechwin = []
endspeechlose = []
if FileTest.exist?(filename)
File.open(filename, "rb") do |f|
FileLineData.file = filename
pbEachFileSection(f) do |section, name|
rsection = []
section.each_key do |key|
FileLineData.setSection(name, key, section[key])
schema = requiredtypes[key]
next if !schema
record = get_csv_record(section[key], 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
end
end
MessageTypes.addMessagesAsHash(MessageTypes::TRAINER_NAMES, trainernames)
MessageTypes.addMessagesAsHash(MessageTypes::FRONTIER_INTRO_SPEECHES, beginspeech)
MessageTypes.addMessagesAsHash(MessageTypes::FRONTIER_END_SPEECHES_WIN, endspeechwin)
MessageTypes.addMessagesAsHash(MessageTypes::FRONTIER_END_SPEECHES_LOSE, endspeechlose)
return sections
end
#-----------------------------------------------------------------------------
# Compile metadata.
# NOTE: Doesn't use compile_PBS_file_generic because it contains data for two
# different GameData classes.
#-----------------------------------------------------------------------------
def compile_metadata(*paths)
GameData::Metadata::DATA.clear
GameData::PlayerMetadata::DATA.clear
global_schema = GameData::Metadata.schema
player_schema = GameData::PlayerMetadata.schema
paths.each do |path|
compile_pbs_file_message_start(path)
file_suffix = File.basename(path, ".txt")[GameData::Metadata::PBS_BASE_FILENAME.length + 1, path.length] || ""
# Read from PBS file
File.open(path, "rb") do |f|
FileLineData.file = path # 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).
idx = 0
pbEachFileSection(f) do |contents, section_name|
echo "." if idx % 100 == 0
Graphics.update if idx % 500 == 0
idx += 1
schema = (section_name.to_i == 0) ? global_schema : player_schema
data_hash = {
:id => section_name.to_sym,
:pbs_file_suffix => file_suffix
}
# Go through schema hash of compilable data and compile this section
schema.each_key do |key|
FileLineData.setSection(section_name, key, contents[key]) # For error reporting
if key == "SectionName"
data_hash[schema[key][0]] = get_csv_record(section_name, schema[key])
next
end
# Skip empty properties
next if contents[key].nil?
# Compile value for key
if schema[key][1][0] == "^"
contents[key].each do |val|
value = get_csv_record(val, schema[key])
value = nil if value.is_a?(Array) && value.empty?
data_hash[schema[key][0]] ||= []
data_hash[schema[key][0]].push(value)
end
data_hash[schema[key][0]].compact!
else
value = get_csv_record(contents[key], schema[key])
value = nil if value.is_a?(Array) && value.empty?
data_hash[schema[key][0]] = value
end
end
# Validate and modify the compiled data
if data_hash[:id] == 0
validate_compiled_global_metadata(data_hash)
if GameData::Metadata.exists?(data_hash[:id])
raise _INTL("Global metadata ID '{1}' is used twice.", data_hash[:id]) + "\n" + FileLineData.linereport
end
else
validate_compiled_player_metadata(data_hash)
if GameData::PlayerMetadata.exists?(data_hash[:id])
raise _INTL("Overrides metadata ID '{1}' is used twice.", data_hash[:id]) + "\n" + FileLineData.linereport
end
end
# Add section's data to records
if data_hash[:id] == 0
GameData::Metadata.register(data_hash)
else
GameData::PlayerMetadata.register(data_hash)
end
end
end
process_pbs_file_message_end
end
validate_all_compiled_metadata
# Save all data
GameData::Metadata.save
GameData::PlayerMetadata.save
end
def validate_compiled_global_metadata(hash)
if hash[:home].nil?
raise _INTL("The entry 'Home' is required in metadata.txt section 0.") + "\n" + FileLineData.linereport
end
end
def validate_compiled_player_metadata(hash)
end
# Should be used to check both global metadata and player character metadata.
def validate_all_compiled_metadata
# Ensure global metadata is defined
if !GameData::Metadata.exists?(0)
raise _INTL("Global metadata is not defined in metadata.txt but should be.") + "\n" + FileLineData.linereport
end
# Ensure player character 1's metadata is defined
if !GameData::PlayerMetadata.exists?(1)
raise _INTL("Metadata for player character 1 is not defined in metadata.txt but should be.") + "\n" + FileLineData.linereport
end
# Get storage creator's name for translating
storage_creator = [GameData::Metadata.get.real_storage_creator]
MessageTypes.setMessagesAsHash(MessageTypes::STORAGE_CREATOR_NAME, storage_creator)
end
#-----------------------------------------------------------------------------
# Compile map metadata.
#-----------------------------------------------------------------------------
def compile_map_metadata(*paths)
compile_PBS_file_generic(GameData::MapMetadata, *paths) do |final_validate, hash|
(final_validate) ? validate_all_compiled_map_metadata : validate_compiled_map_metadata(hash)
end
end
def validate_compiled_map_metadata(hash)
# Give the map its RMXP map name if it doesn't define its own
if nil_or_empty?(hash[:real_name])
hash[:real_name] = pbLoadMapInfos[hash[:id]].name
end
end
def validate_all_compiled_map_metadata
# Get map names for translating
map_names = []
GameData::MapMetadata.each { |map| map_names[map.id] = map.real_name }
MessageTypes.setMessagesAsHash(MessageTypes::MAP_NAMES, map_names)
end
#-----------------------------------------------------------------------------
# Compile dungeon tileset data.
#-----------------------------------------------------------------------------
def compile_dungeon_tilesets(*paths)
compile_PBS_file_generic(GameData::DungeonTileset, *paths) do |final_validate, hash|
(final_validate) ? validate_all_compiled_dungeon_tilesets : validate_compiled_dungeon_tileset(hash)
end
end
def validate_compiled_dungeon_tileset(hash)
end
def validate_all_compiled_dungeon_tilesets
end
#-----------------------------------------------------------------------------
# Compile dungeon parameters data.
#-----------------------------------------------------------------------------
def compile_dungeon_parameters(*paths)
compile_PBS_file_generic(GameData::DungeonParameters, *paths) do |final_validate, hash|
(final_validate) ? validate_all_compiled_dungeon_parameters : validate_compiled_dungeon_parameters(hash)
end
end
def validate_compiled_dungeon_parameters(hash)
# Split area and version into their own values, generate compound ID from them
hash[:area] = hash[:id][0]
hash[:version] = hash[:id][1] || 0
if hash[:version] == 0
hash[:id] = hash[:area]
else
hash[:id] = sprintf("%s_%d", hash[:area].to_s, hash[:version]).to_sym
end
if GameData::DungeonParameters.exists?(hash[:id])
raise _INTL("Version {1} of dungeon area {2} is defined twice.", hash[:version], hash[:area]) + "\n" + FileLineData.linereport
end
end
def validate_all_compiled_dungeon_parameters
end
#-----------------------------------------------------------------------------
# Compile phone messages.
#-----------------------------------------------------------------------------
def compile_phone(*paths)
compile_PBS_file_generic(GameData::PhoneMessage, *paths) do |final_validate, hash|
(final_validate) ? validate_all_compiled_phone_contacts : validate_compiled_phone_contact(hash)
end
end
def validate_compiled_phone_contact(hash)
# Split trainer type/name/version into their own values, generate compound ID from them
if hash[:id].strip.downcase == "default"
hash[:id] = "default"
hash[:trainer_type] = hash[:id]
else
line_data = get_csv_record(hash[:id], [nil, "esU", :TrainerType])
hash[:trainer_type] = line_data[0]
hash[:real_name] = line_data[1]
hash[:version] = line_data[2] || 0
hash[:id] = [hash[:trainer_type], hash[:real_name], hash[:version]]
end
end
def validate_all_compiled_phone_contacts
# Get all phone messages for translating
messages = []
GameData::PhoneMessage.each do |contact|
[:intro, :intro_morning, :intro_afternoon, :intro_evening, :body, :body1,
:body2, :battle_request, :battle_remind, :end].each do |msg_type|
msgs = contact.send(msg_type)
next if !msgs || msgs.length == 0
msgs.each { |msg| messages.push(msg) }
end
end
MessageTypes.setMessagesAsHash(MessageTypes::PHONE_MESSAGES, messages)
end
end