diff --git a/src/Episode3/DataIndexes.cc b/src/Episode3/DataIndexes.cc index 582154f5..641fa19b 100644 --- a/src/Episode3/DataIndexes.cc +++ b/src/Episode3/DataIndexes.cc @@ -975,9 +975,25 @@ string CardDefinition::str(bool single_line, const TextSet* text_archive) const } } } + string jp_name_s = this->jp_name.decode(); + string en_name_short_s = this->en_short_name.decode(); + string jp_name_short_s = this->jp_short_name.decode(); + string names_str; + if (!en_name_s.empty()) { + names_str += phosg::string_printf(" EN: \"%s\"", en_name_s.c_str()); + if (!en_name_short_s.empty() && en_name_short_s != en_name_s) { + names_str += phosg::string_printf(" (Abr. \"%s\")", en_name_short_s.c_str()); + } + } + if (!jp_name_s.empty()) { + names_str += phosg::string_printf(" JP: \"%s\"", jp_name_s.c_str()); + if (!jp_name_short_s.empty() && jp_name_short_s != jp_name_s) { + names_str += phosg::string_printf(" (Abr. \"%s\")", jp_name_short_s.c_str()); + } + } return phosg::string_printf( "\ -Card: %04" PRIX32 " \"%s\"\n\ +Card: %04" PRIX32 "%s\n\ Type: %s, class: %s\n\ Usability condition: %s\n\ Rank: %s\n\ @@ -998,7 +1014,7 @@ Card: %04" PRIX32 " \"%s\"\n\ %s\n\ Effects:%s", this->card_id.load(), - en_name_s.c_str(), + names_str.c_str(), type_str.c_str(), card_class_str.c_str(), criterion_str.c_str(), @@ -2590,12 +2606,15 @@ MapIndex::VersionedMap::VersionedMap(std::string&& compressed_data, uint8_t lang : language(language), compressed_data(std::move(compressed_data)) { string decompressed = prs_decompress(this->compressed_data); - if (decompressed.size() != sizeof(MapDefinition)) { + if (decompressed.size() == sizeof(MapDefinitionTrial)) { + this->map = make_shared(*reinterpret_cast(decompressed.data())); + } else if (decompressed.size() == sizeof(MapDefinition)) { + this->map = make_shared(*reinterpret_cast(decompressed.data())); + } else { throw runtime_error(phosg::string_printf( "decompressed data size is incorrect (expected %zu bytes, read %zu bytes)", sizeof(MapDefinition), decompressed.size())); } - this->map = make_shared(*reinterpret_cast(decompressed.data())); } shared_ptr MapIndex::VersionedMap::trial() const { diff --git a/src/Episode3/DataIndexes.hh b/src/Episode3/DataIndexes.hh index f4e7d025..19d72fef 100644 --- a/src/Episode3/DataIndexes.hh +++ b/src/Episode3/DataIndexes.hh @@ -1413,7 +1413,9 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests /* 59DC */ uint8_t map_category; // This field determines block graphics to be used in the Cyber environment. - // There are 10 block types (0-9); if this value is > 9, type 0 is used. + // There are 10 block types (0-9); if this value is > 9, type 0 is used. This + // field has no effect in Ep3 NTE, even though there are 6 different block + // texture files on the NTE disc. /* 59DD */ uint8_t cyber_block_type; /* 59DE */ be_uint16_t unknown_a11; diff --git a/src/Main.cc b/src/Main.cc index f5edba0b..a8d1ae57 100644 --- a/src/Main.cc +++ b/src/Main.cc @@ -2013,17 +2013,22 @@ Action a_show_ep3_cards( Action a_generate_ep3_cards_html( "generate-ep3-cards-html", "\ - generate-ep3-cards-html [--ep3-nte] [--threads=N] [--no-images]\n\ + generate-ep3-cards-html [--ep3-nte] [--compare] [--threads=N] [--no-images]\n\ + [--no-disassembly]\n\ Generate an HTML file describing all Episode 3 card definitions from the\n\ system/ep3 directory. If --ep3-nte is given, use the Trial Edition card\n\ definitions instead. If --no-images is given, omit the card images.\n", +[](phosg::Arguments& args) { size_t num_threads = args.get("threads", 0); - bool is_nte = (get_cli_version(args, Version::GC_EP3) == Version::GC_EP3_NTE); + bool include_nte = (get_cli_version(args, Version::GC_EP3) == Version::GC_EP3_NTE) || args.get("compare"); + bool include_final = (get_cli_version(args, Version::GC_EP3) == Version::GC_EP3) || args.get("compare"); bool no_images = args.get("no-images"); + bool no_large_images = args.get("no-large-images"); + bool no_disassembly = args.get("no-disassembly"); auto s = make_shared(get_config_filename(args)); + s->clear_file_caches(false); s->load_patch_indexes(false); s->load_text_index(false); s->load_ep3_cards(false); @@ -2034,148 +2039,221 @@ Action a_generate_ep3_cards_html( } catch (const out_of_range&) { } - struct CardInfo { - shared_ptr ce; - string small_filename; - string medium_filename; - string large_filename; - string small_data_url; - string medium_data_url; - string large_data_url; + struct VersionInfo { + struct CardInfo { + shared_ptr ce; + string small_filename; + string medium_filename; + string large_filename; + string small_data_url; + string medium_data_url; + string large_data_url; - bool is_empty() const { - return (this->ce == nullptr) && this->small_data_url.empty() && this->medium_data_url.empty() && this->large_data_url.empty(); + bool is_empty() const { + return (this->ce == nullptr) && this->small_data_url.empty() && this->medium_data_url.empty() && this->large_data_url.empty(); + } + }; + + const char* name; + vector card_infos; + bool show_large_column = false; + bool show_medium_column = false; + bool show_small_column = false; + size_t num_output_columns = 2; + + VersionInfo( + const char* name, + shared_ptr card_index, + const char* cardtex_directory, + bool no_large_images, + size_t num_threads, + bool no_disassembly) + : name(name) { + for (uint32_t card_id : card_index->all_ids()) { + if (this->card_infos.size() <= card_id) { + this->card_infos.resize(card_id + 1); + } + this->card_infos[card_id].ce = card_index->definition_for_id(card_id); + } + + if (cardtex_directory) { + for (const auto& filename : phosg::list_directory_sorted(cardtex_directory)) { + if ((filename[0] == 'C' || filename[0] == 'M' || filename[0] == 'L') && (filename[1] == '_')) { + size_t card_id = stoull(filename.substr(2, 3), nullptr, 10); + if (this->card_infos.size() <= card_id) { + this->card_infos.resize(card_id + 1); + } + auto& info = this->card_infos[card_id]; + if (filename[0] == 'C' && !no_large_images) { + info.large_filename = string(cardtex_directory) + "/" + filename; + this->show_large_column = true; + } else if (filename[0] == 'L') { + info.medium_filename = string(cardtex_directory) + "/" + filename; + this->show_medium_column = true; + } else if (filename[0] == 'M') { + info.small_filename = string(cardtex_directory) + "/" + filename; + this->show_small_column = true; + } + } + } + + phosg::parallel_range([&](uint32_t index, size_t) -> bool { + auto& info = this->card_infos[index]; + if (!info.large_filename.empty()) { + phosg::Image img(info.large_filename); + phosg::Image cropped(512, 399); + cropped.blit(img, 0, 0, 512, 399, 0, 0); + info.large_data_url = cropped.png_data_url(); + } + if (!info.medium_filename.empty()) { + phosg::Image img(info.medium_filename); + phosg::Image cropped(184, 144); + cropped.blit(img, 0, 0, 184, 144, 0, 0); + info.medium_data_url = cropped.png_data_url(); + } + if (!info.small_filename.empty()) { + phosg::Image img(info.small_filename); + phosg::Image cropped(58, 43); + cropped.blit(img, 0, 0, 58, 43, 0, 0); + info.small_data_url = cropped.png_data_url(); + } + return false; + }, + 0, this->card_infos.size(), num_threads); + } + + this->num_output_columns = 1 + (!no_disassembly) + this->show_small_column + this->show_medium_column + this->show_large_column; + } + + const CardInfo* get_entry(size_t card_id) const { + if (card_id >= this->card_infos.size()) { + return nullptr; + } + const auto* entry = &this->card_infos[card_id]; + return entry->is_empty() ? nullptr : entry; } }; - auto card_index = is_nte ? s->ep3_card_index_trial : s->ep3_card_index; - vector infos; - for (uint32_t card_id : card_index->all_ids()) { - if (infos.size() <= card_id) { - infos.resize(card_id + 1); - } - infos[card_id].ce = card_index->definition_for_id(card_id); - } - bool show_large_column = false; - bool show_medium_column = false; - bool show_small_column = false; - if (!no_images) { - for (const auto& filename : phosg::list_directory_sorted("system/ep3/cardtex")) { - if ((filename[0] == 'C' || filename[0] == 'M' || filename[0] == 'L') && (filename[1] == '_')) { - size_t card_id = stoull(filename.substr(2, 3), nullptr, 10); - if (infos.size() <= card_id) { - infos.resize(card_id + 1); - } - auto& info = infos[card_id]; - if (filename[0] == 'C') { - info.large_filename = "system/ep3/cardtex/" + filename; - show_large_column = true; - } else if (filename[0] == 'L') { - info.medium_filename = "system/ep3/cardtex/" + filename; - show_medium_column = true; - } else if (filename[0] == 'M') { - info.small_filename = "system/ep3/cardtex/" + filename; - show_small_column = true; - } - } - } - } - phosg::parallel_range([&](uint32_t index, size_t) -> bool { - auto& info = infos[index]; - if (!info.large_filename.empty()) { - phosg::Image img(info.large_filename); - phosg::Image cropped(512, 399); - cropped.blit(img, 0, 0, 512, 399, 0, 0); - info.large_data_url = cropped.png_data_url(); - } - if (!info.medium_filename.empty()) { - phosg::Image img(info.medium_filename); - phosg::Image cropped(184, 144); - cropped.blit(img, 0, 0, 184, 144, 0, 0); - info.medium_data_url = cropped.png_data_url(); - } - if (!info.small_filename.empty()) { - phosg::Image img(info.small_filename); - phosg::Image cropped(58, 43); - cropped.blit(img, 0, 0, 58, 43, 0, 0); - info.small_data_url = cropped.png_data_url(); - } - return false; - }, - 0, infos.size(), num_threads); + vector version_infos; + if (include_nte) { + version_infos.emplace_back("NTE", s->ep3_card_index_trial, no_images ? nullptr : "system/ep3/cardtex-trial", no_large_images, num_threads, no_disassembly); + } + if (include_final) { + version_infos.emplace_back("Final", s->ep3_card_index, no_images ? nullptr : "system/ep3/cardtex", no_large_images, num_threads, no_disassembly); + } deque blocks; blocks.emplace_back("Phantasy Star Online Episode III cards"); blocks.emplace_back("
Legend:
Card has no definition and is obviously incomplete
Card is unobtainable in random draws but may be a quest or event reward
Card is obtainable in random draws


"); - blocks.emplace_back(""); - if (show_small_column) { - blocks.emplace_back(""); + blocks.emplace_back("
IDSmall
"); + + for (const auto& vi : version_infos) { + blocks.emplace_back(phosg::string_printf("", + vi.num_output_columns, vi.name)); } - if (show_medium_column) { - blocks.emplace_back(""); + blocks.emplace_back(""); + for (const auto& vi : version_infos) { + if (vi.show_small_column) { + blocks.emplace_back(""); + } + if (vi.show_medium_column) { + blocks.emplace_back(""); + } + if (vi.show_large_column) { + blocks.emplace_back(""); + } + blocks.emplace_back(""); } - if (show_large_column) { - blocks.emplace_back(""); + blocks.emplace_back(""); + + size_t num_infos = 0; + for (const auto& vi : version_infos) { + num_infos = std::max(num_infos, vi.card_infos.size()); } - blocks.emplace_back(""); - for (size_t card_id = 0; card_id < infos.size(); card_id++) { - const auto& entry = infos[card_id]; - if (entry.is_empty()) { + + for (size_t card_id = 0; card_id < num_infos; card_id++) { + bool any_vi_has_entry = false; + for (const auto& vi : version_infos) { + if (vi.get_entry(card_id)) { + any_vi_has_entry = true; + break; + } + } + if (!any_vi_has_entry) { continue; } - const char* background_color; - if (!entry.ce) { - background_color = "#663333"; - } else if (entry.ce->def.cannot_drop || - ((entry.ce->def.rank == Episode3::CardRank::D1) || (entry.ce->def.rank == Episode3::CardRank::D2) || (entry.ce->def.rank == Episode3::CardRank::D3)) || - ((entry.ce->def.card_class() == Episode3::CardClass::BOSS_ATTACK_ACTION) || (entry.ce->def.card_class() == Episode3::CardClass::BOSS_TECH)) || - ((entry.ce->def.drop_rates[0] == 6) && (entry.ce->def.drop_rates[1] == 6))) { - background_color = "#336633"; - } else { - background_color = "#333333"; - } + blocks.emplace_back(phosg::string_printf("", card_id)); - blocks.emplace_back(phosg::string_printf("", background_color)); - blocks.emplace_back(phosg::string_printf("", card_id)); - if (show_small_column) { - blocks.emplace_back("", + vi.num_output_columns)); + continue; } - blocks.emplace_back(""); - } - if (show_medium_column) { - blocks.emplace_back(""); - } - if (show_large_column) { - blocks.emplace_back(""); + } + if (vi.show_medium_column) { + blocks.emplace_back(td_tag); + if (!entry->medium_data_url.empty()) { + blocks.emplace_back("medium_data_url)); + blocks.emplace_back("\" />"); + } + blocks.emplace_back(""); + } + if (vi.show_large_column) { + blocks.emplace_back(td_tag); + if (!entry->large_data_url.empty()) { + blocks.emplace_back("large_data_url)); + blocks.emplace_back("\" />"); + } + blocks.emplace_back(""); + } + blocks.emplace_back(td_tag); + if (entry->ce) { + blocks.emplace_back("
");
+            blocks.emplace_back(entry->ce->text);
+            blocks.emplace_back("
"); + if (!no_disassembly) { + blocks.emplace_back(td_tag); + blocks.emplace_back("
");
+              blocks.emplace_back(entry->ce->def.str(false, text_english.get()));
+              blocks.emplace_back("
"); + } + } else { + blocks.emplace_back(""); + if (!no_disassembly) { + blocks.emplace_back(td_tag); + blocks.emplace_back("
Definition is missing
"); + blocks.emplace_back(""); + } } - blocks.emplace_back(""); } - blocks.emplace_back(""); + blocks.emplace_back(""); } blocks.emplace_back("
ID%sMedium
SmallMediumLargeTextDisassemblyLarge
TextDisassembly
%04zX
%04zX
"); - if (!entry.small_data_url.empty()) { - blocks.emplace_back(""); + for (const auto& vi : version_infos) { + const VersionInfo::CardInfo* entry = vi.get_entry(card_id); + if (!entry) { + blocks.emplace_back(phosg::string_printf("
No entry
"); - if (!entry.medium_data_url.empty()) { - blocks.emplace_back(""); + + const char* background_color; + if (!entry->ce) { + background_color = "#663333"; + } else if (entry->ce->def.cannot_drop || + ((entry->ce->def.rank == Episode3::CardRank::D1) || (entry->ce->def.rank == Episode3::CardRank::D2) || (entry->ce->def.rank == Episode3::CardRank::D3)) || + ((entry->ce->def.card_class() == Episode3::CardClass::BOSS_ATTACK_ACTION) || (entry->ce->def.card_class() == Episode3::CardClass::BOSS_TECH)) || + ((entry->ce->def.drop_rates[0] == 6) && (entry->ce->def.drop_rates[1] == 6))) { + background_color = "#336633"; + } else { + background_color = "#333333"; } - blocks.emplace_back(""); - if (!entry.large_data_url.empty()) { - blocks.emplace_back(""); + + string td_tag = phosg::string_printf("", background_color); + if (vi.show_small_column) { + blocks.emplace_back(td_tag); + if (!entry->small_data_url.empty()) { + blocks.emplace_back("small_data_url)); + blocks.emplace_back("\" />"); + } + blocks.emplace_back(""); - if (entry.ce) { - blocks.emplace_back("
");
-          blocks.emplace_back(entry.ce->text);
-          blocks.emplace_back("
");
-          blocks.emplace_back(entry.ce->def.str(false, text_english.get()));
-          blocks.emplace_back("
"); - } else { - blocks.emplace_back("
Definition is missing
"); - } - blocks.emplace_back("
");