From 427cc4562967ce00fed240db6c553f67154385b6 Mon Sep 17 00:00:00 2001 From: Maruno17 Date: Wed, 23 Nov 2022 22:44:15 +0000 Subject: [PATCH] Generalised compiler and writer methods for trainers.txt --- .../021_Compiler/002_Compiler_CompilePBS.rb | 211 ++++++++++-------- .../021_Compiler/003_Compiler_WritePBS.rb | 59 ++--- 2 files changed, 148 insertions(+), 122 deletions(-) diff --git a/Data/Scripts/021_Compiler/002_Compiler_CompilePBS.rb b/Data/Scripts/021_Compiler/002_Compiler_CompilePBS.rb index 168a590d8..f36bf8207 100644 --- a/Data/Scripts/021_Compiler/002_Compiler_CompilePBS.rb +++ b/Data/Scripts/021_Compiler/002_Compiler_CompilePBS.rb @@ -749,132 +749,157 @@ module Compiler def compile_trainers(*paths) GameData::Trainer::DATA.clear schema = GameData::Trainer.schema - max_level = GameData::GrowthRate.max_level - trainer_names = [] - trainer_lose_texts = [] + 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] || "" - trainer_hash = nil + 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 - idx = 0 pbCompilerEachPreppedLine(path) { |line, line_no| echo "." if idx % 50 == 0 idx += 1 Graphics.update if idx % 250 == 0 + FileLineData.setSection(section_name, nil, section_line) if line[/^\s*\[\s*(.+)\s*\]\s*$/] # New section [trainer_type, name] or [trainer_type, name, version] - if trainer_hash - if !current_pkmn - raise _INTL("Started new trainer while previous trainer has no Pokémon.\r\n{1}", FileLineData.linereport) - end - # Add trainer's data to records - trainer_hash[:id] = [trainer_hash[:trainer_type], trainer_hash[:name], trainer_hash[:version]] - GameData::Trainer.register(trainer_hash) + section_name = $~[1] + section_line = line + if data_hash + validate_compiled_trainer(data_hash) + GameData::Trainer.register(data_hash) end - line_data = pbGetCsvRecord($~[1], line_no, [0, "esU", :TrainerType]) - # Construct trainer hash - trainer_hash = { - :trainer_type => line_data[0], - :name => line_data[1], - :version => line_data[2] || 0, - :pokemon => [], + FileLineData.setSection(section_name, nil, section_line) + # Construct data hash + data_hash = { :pbs_file_suffix => file_suffix } + data_hash[schema["SectionName"][0]] = pbGetCsvRecord(section_name.clone, line_no, schema["SectionName"]) + data_hash[schema["Pokemon"][0]] = [] current_pkmn = nil - trainer_names.push(trainer_hash[:name]) elsif line[/^\s*(\w+)\s*=\s*(.*)$/] # XXX=YYY lines - if !trainer_hash + if !data_hash raise _INTL("Expected a section at the beginning of the file.\r\n{1}", FileLineData.linereport) end - property_name = $~[1] - line_schema = schema[property_name] - next if !line_schema - property_value = pbGetCsvRecord($~[2], line_no, line_schema) - # Error checking in XXX=YYY lines - case property_name - when "Pokemon" - if property_value[1] > max_level - raise _INTL("Bad level: {1} (must be 1-{2}).\r\n{3}", property_value[1], max_level, FileLineData.linereport) + key = $~[1] + if schema[key] # Property of the trainer + property_value = pbGetCsvRecord($~[2], line_no, 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 - when "Name" - if property_value.length > Pokemon::MAX_NAME_SIZE - raise _INTL("Bad nickname: {1} (must be 1-{2} characters).\r\n{3}", property_value, Pokemon::MAX_NAME_SIZE, FileLineData.linereport) - end - when "Moves" - property_value.uniq! - when "IV" - property_value.each do |iv| - next if iv <= Pokemon::IV_STAT_LIMIT - raise _INTL("Bad IV: {1} (must be 0-{2}).\r\n{3}", iv, Pokemon::IV_STAT_LIMIT, FileLineData.linereport) - end - when "EV" - property_value.each do |ev| - next if ev <= Pokemon::EV_STAT_LIMIT - raise _INTL("Bad EV: {1} (must be 0-{2}).\r\n{3}", ev, Pokemon::EV_STAT_LIMIT, FileLineData.linereport) - end - ev_total = 0 - GameData::Stat.each_main do |s| - next if s.pbs_order < 0 - ev_total += (property_value[s.pbs_order] || property_value[0]) - end - if ev_total > Pokemon::EV_LIMIT - raise _INTL("Total EVs are greater than allowed ({1}).\r\n{2}", Pokemon::EV_LIMIT, FileLineData.linereport) - end - when "Happiness" - if property_value > 255 - raise _INTL("Bad happiness: {1} (must be 0-255).\r\n{2}", property_value, FileLineData.linereport) - end - when "Ball" - if !GameData::Item.get(property_value).is_poke_ball? - raise _INTL("Value {1} isn't a defined Poké Ball.\r\n{2}", property_value, FileLineData.linereport) - end - end - # Record XXX=YYY setting - case property_name - when "Items", "LoseText" - trainer_hash[line_schema[0]] = property_value - trainer_lose_texts.push(property_value) if property_name == "LoseText" - when "Pokemon" - current_pkmn = { - :species => property_value[0], - :level => property_value[1] - } - trainer_hash[line_schema[0]].push(current_pkmn) - else + elsif sub_schema[key] # Property of a Pokémon if !current_pkmn raise _INTL("Pokémon hasn't been defined yet!\r\n{1}", FileLineData.linereport) end - case property_name - when "IV", "EV" - value_hash = {} - GameData::Stat.each_main do |s| - next if s.pbs_order < 0 - value_hash[s.id] = property_value[s.pbs_order] || property_value[0] - end - current_pkmn[line_schema[0]] = value_hash - else - current_pkmn[line_schema[0]] = property_value - end + current_pkmn[sub_schema[key][0]] = pbGetCsvRecord($~[2], line_no, sub_schema[key]) end end } # Add last trainer's data to records - if trainer_hash - if !current_pkmn - raise _INTL("End of file reached while last trainer has no Pokémon.\r\n{1}", FileLineData.linereport) - end - trainer_hash[:id] = [trainer_hash[:trainer_type], trainer_hash[:name], trainer_hash[:version]] - GameData::Trainer.register(trainer_hash) + 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.\r\n{2}", hash[:id], 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}).\r\n{3}", + pkmn[:level], max_level, FileLineData.linereport) + end + # Ensure valid name length + if pkmn[:name] && pkmn[:name].length > Pokemon::MAX_NAME_SIZE + raise _INTL("Invalid Pokémon nickname: {1} (must be 1-{2} characters).\r\n{3}", + pkmn[:name], Pokemon::MAX_NAME_SIZE, 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}).\r\n{3}", + iv_hash[s.id], Pokemon::IV_STAT_LIMIT, 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}).\r\n{3}", + ev_hash[s.id], Pokemon::EV_STAT_LIMIT, 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).\r\n{2}", + Pokemon::EV_LIMIT, FileLineData.linereport) + end + end + # Ensure valid happiness + if pkmn[:happiness] + if pkmn[:happiness] > 255 + raise _INTL("Bad happiness: {1} (must be 0-255).\r\n{2}", pkmn[:happiness], 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.\r\n{2}", pkmn[:poke_ball], FileLineData.linereport) + end + end + end + end + + def validate_all_compiled_trainers + # Get trainer names and lose texts for translating + trainer_names = [] + lose_texts = [] + GameData::Trainer.each do |trainer| + trainer_names.push(trainer.real_name) + lose_texts.push(trainer.real_lose_text) + end MessageTypes.setMessagesAsHash(MessageTypes::TrainerNames, trainer_names) - MessageTypes.setMessagesAsHash(MessageTypes::TrainerLoseText, trainer_lose_texts) + MessageTypes.setMessagesAsHash(MessageTypes::TrainerLoseText, lose_texts) end #============================================================================= diff --git a/Data/Scripts/021_Compiler/003_Compiler_WritePBS.rb b/Data/Scripts/021_Compiler/003_Compiler_WritePBS.rb index 0c09085a8..d4ebd5a65 100644 --- a/Data/Scripts/021_Compiler/003_Compiler_WritePBS.rb +++ b/Data/Scripts/021_Compiler/003_Compiler_WritePBS.rb @@ -452,50 +452,51 @@ module Compiler #============================================================================= def write_trainers paths = get_all_PBS_file_paths(GameData::Trainer) + schema = GameData::Trainer.schema + sub_schema = GameData::Trainer.sub_schema idx = 0 paths.each do |path| write_pbs_file_message_start(path[0]) File.open(path[0], "wb") { |f| add_PBS_header_to_file(f) + # Write each element in turn GameData::Trainer.each do |element| next if element.pbs_file_suffix != path[1] echo "." if idx % 50 == 0 Graphics.update if idx % 250 == 0 idx += 1 f.write("\#-------------------------------\r\n") - if element.version > 0 - f.write(sprintf("[%s,%s,%d]\r\n", element.trainer_type, element.real_name, element.version)) + if schema["SectionName"] + f.write("[") + pbWriteCsvRecord(element.get_property_for_PBS("SectionName"), f, schema["SectionName"]) + f.write("]\r\n") else - f.write(sprintf("[%s,%s]\r\n", element.trainer_type, element.real_name)) + f.write("[#{element.id}]\r\n") end - f.write(sprintf("Items = %s\r\n", element.items.join(","))) if element.items.length > 0 - if element.real_lose_text && !element.real_lose_text.empty? - f.write(sprintf("LoseText = %s\r\n", element.real_lose_text)) + # Write each trainer property + schema.each_key do |key| + next if key == "SectionName" || key == "Pokemon" + val = element.get_property_for_PBS(key) + next if val.nil? + f.write(sprintf("%s = ", key)) + pbWriteCsvRecord(val, f, schema[key]) + f.write("\r\n") end - element.pokemon.each do |pkmn| - f.write(sprintf("Pokemon = %s,%d\r\n", pkmn[:species], pkmn[:level])) - f.write(sprintf(" Name = %s\r\n", pkmn[:name])) if pkmn[:name] && !pkmn[:name].empty? - f.write(sprintf(" Form = %d\r\n", pkmn[:form])) if pkmn[:form] && pkmn[:form] > 0 - f.write(sprintf(" Gender = %s\r\n", (pkmn[:gender] == 1) ? "female" : "male")) if pkmn[:gender] - f.write(" Shiny = yes\r\n") if pkmn[:shininess] && !pkmn[:super_shininess] - f.write(" SuperShiny = yes\r\n") if pkmn[:super_shininess] - f.write(" Shadow = yes\r\n") if pkmn[:shadowness] - f.write(sprintf(" Moves = %s\r\n", pkmn[:moves].join(","))) if pkmn[:moves] && pkmn[:moves].length > 0 - f.write(sprintf(" Ability = %s\r\n", pkmn[:ability])) if pkmn[:ability] - f.write(sprintf(" AbilityIndex = %d\r\n", pkmn[:ability_index])) if pkmn[:ability_index] - f.write(sprintf(" Item = %s\r\n", pkmn[:item])) if pkmn[:item] - f.write(sprintf(" Nature = %s\r\n", pkmn[:nature])) if pkmn[:nature] - ivs_array = [] - evs_array = [] - GameData::Stat.each_main do |s| - next if s.pbs_order < 0 - ivs_array[s.pbs_order] = pkmn[:iv][s.id] if pkmn[:iv] - evs_array[s.pbs_order] = pkmn[:ev][s.id] if pkmn[:ev] + # Write each Pokémon in turn + element.pokemon.each_with_index do |pkmn, i| + # Write species/level + val = element.get_pokemon_property_for_PBS("Pokemon", i) + f.write("Pokemon = ") + pbWriteCsvRecord(val, f, schema["Pokemon"]) + f.write("\r\n") + # Write other Pokémon properties + sub_schema.each_key do |key| + val = element.get_pokemon_property_for_PBS(key, i) + next if val.nil? + f.write(sprintf(" %s = ", key)) + pbWriteCsvRecord(val, f, sub_schema[key]) + f.write("\r\n") end - f.write(sprintf(" IV = %s\r\n", ivs_array.join(","))) if pkmn[:iv] - f.write(sprintf(" EV = %s\r\n", evs_array.join(","))) if pkmn[:ev] - f.write(sprintf(" Happiness = %d\r\n", pkmn[:happiness])) if pkmn[:happiness] - f.write(sprintf(" Ball = %s\r\n", pkmn[:poke_ball])) if pkmn[:poke_ball] end end }