improve ep3 data formatting

This commit is contained in:
Martin Michelsen
2023-08-04 22:40:18 -07:00
parent 308c58e761
commit 911b17df7e
4 changed files with 327 additions and 69 deletions
+299 -48
View File
@@ -18,6 +18,99 @@ using namespace std;
namespace Episode3 {
const char* name_for_link_color(uint8_t color) {
switch (color) {
case 1:
return "blue"; // HP halver
case 2:
return "red"; // Physical attacks
case 3:
return "yellow"; // Techniques
case 4:
return "brown"; // Leukon Knight
case 5:
return "orange"; // Penetrate/confuse
case 6:
return "purple"; // Instant death
case 7:
return "white"; // Castor
case 8:
return "gray"; // Pollux
case 9:
return "green"; // Status effects
default:
throw invalid_argument("unknown color");
}
}
const char* name_for_card_type(CardType type) {
switch (type) {
case CardType::HUNTERS_SC:
return "HUNTERS_SC";
case CardType::ARKZ_SC:
return "ARKZ_SC";
case CardType::ITEM:
return "ITEM";
case CardType::CREATURE:
return "CREATURE";
case CardType::ACTION:
return "ACTION";
case CardType::ASSIST:
return "ASSIST";
case CardType::INVALID_FF:
return "INVALID_FF";
default:
throw invalid_argument("invalid card type");
}
}
const char* name_for_card_class(CardClass cc) {
switch (cc) {
case CardClass::HU_SC:
return "HU_SC";
case CardClass::RA_SC:
return "RA_SC";
case CardClass::FO_SC:
return "FO_SC";
case CardClass::NATIVE_CREATURE:
return "NATIVE_CREATURE";
case CardClass::A_BEAST_CREATURE:
return "A_BEAST_CREATURE";
case CardClass::MACHINE_CREATURE:
return "MACHINE_CREATURE";
case CardClass::DARK_CREATURE:
return "DARK_CREATURE";
case CardClass::GUARD_ITEM:
return "GUARD_ITEM";
case CardClass::MAG_ITEM:
return "MAG_ITEM";
case CardClass::SWORD_ITEM:
return "SWORD_ITEM";
case CardClass::GUN_ITEM:
return "GUN_ITEM";
case CardClass::CANE_ITEM:
return "CANE_ITEM";
case CardClass::ATTACK_ACTION:
return "ATTACK_ACTION";
case CardClass::DEFENSE_ACTION:
return "DEFENSE_ACTION";
case CardClass::TECH:
return "TECH";
case CardClass::PHOTON_BLAST:
return "PHOTON_BLAST";
case CardClass::CONNECT_ONLY_ATTACK_ACTION:
return "CONNECT_ONLY_ATTACK_ACTION";
case CardClass::BOSS_ATTACK_ACTION:
return "BOSS_ATTACK_ACTION";
case CardClass::BOSS_TECH:
return "BOSS_TECH";
case CardClass::ASSIST:
return "ASSIST";
default:
throw invalid_argument("invalid card class");
}
}
const char* name_for_attack_medium(AttackMedium medium) {
switch (medium) {
case AttackMedium::UNKNOWN:
@@ -35,6 +128,83 @@ const char* name_for_attack_medium(AttackMedium medium) {
}
}
const char* name_for_criterion_code(CriterionCode code) {
switch (code) {
case CriterionCode::NONE:
return "NONE";
case CriterionCode::HU_CLASS_SC:
return "HU_CLASS_SC";
case CriterionCode::RA_CLASS_SC:
return "RA_CLASS_SC";
case CriterionCode::FO_CLASS_SC:
return "FO_CLASS_SC";
case CriterionCode::SAME_TEAM:
return "SAME_TEAM";
case CriterionCode::SAME_PLAYER:
return "SAME_PLAYER";
case CriterionCode::SAME_TEAM_NOT_SAME_PLAYER:
return "SAME_TEAM_NOT_SAME_PLAYER";
case CriterionCode::UNKNOWN_07:
return "UNKNOWN_07";
case CriterionCode::NOT_SC:
return "NOT_SC";
case CriterionCode::SC:
return "SC";
case CriterionCode::HU_OR_RA_CLASS_SC:
return "HU_OR_RA_CLASS_SC";
case CriterionCode::HUNTER_HUMAN_SC:
return "HUNTER_HUMAN_SC";
case CriterionCode::HUNTER_HU_CLASS_MALE_SC:
return "HUNTER_HU_CLASS_MALE_SC";
case CriterionCode::HUNTER_FEMALE_SC:
return "HUNTER_FEMALE_SC";
case CriterionCode::HUNTER_HU_OR_FO_CLASS_HUMAN_SC:
return "HUNTER_HU_OR_FO_CLASS_HUMAN_SC";
case CriterionCode::HUNTER_HU_CLASS_ANDROID_SC:
return "HUNTER_HU_CLASS_ANDROID_SC";
case CriterionCode::UNKNOWN_10:
return "UNKNOWN_10";
case CriterionCode::UNKNOWN_11:
return "UNKNOWN_11";
case CriterionCode::HUNTER_HUNEWEARL_CLASS_SC:
return "HUNTER_HUNEWEARL_CLASS_SC";
case CriterionCode::HUNTER_RA_CLASS_MALE_SC:
return "HUNTER_RA_CLASS_MALE_SC";
case CriterionCode::HUNTER_RA_CLASS_FEMALE_SC:
return "HUNTER_RA_CLASS_FEMALE_SC";
case CriterionCode::HUNTER_RA_OR_FO_CLASS_FEMALE_SC:
return "HUNTER_RA_OR_FO_CLASS_FEMALE_SC";
case CriterionCode::HUNTER_HU_OR_RA_CLASS_HUMAN_SC:
return "HUNTER_HU_OR_RA_CLASS_HUMAN_SC";
case CriterionCode::HUNTER_RA_CLASS_ANDROID_SC:
return "HUNTER_RA_CLASS_ANDROID_SC";
case CriterionCode::HUNTER_FO_CLASS_FEMALE_SC:
return "HUNTER_FO_CLASS_FEMALE_SC";
case CriterionCode::HUNTER_FEMALE_HUMAN_SC:
return "HUNTER_FEMALE_HUMAN_SC";
case CriterionCode::HUNTER_ANDROID_SC:
return "HUNTER_ANDROID_SC";
case CriterionCode::HU_OR_FO_CLASS_SC:
return "HU_OR_FO_CLASS_SC";
case CriterionCode::RA_OR_FO_CLASS_SC:
return "RA_OR_FO_CLASS_SC";
case CriterionCode::PHYSICAL_OR_UNKNOWN_ATTACK_MEDIUM:
return "PHYSICAL_OR_UNKNOWN_ATTACK_MEDIUM";
case CriterionCode::TECH_OR_UNKNOWN_ATTACK_MEDIUM:
return "TECH_OR_UNKNOWN_ATTACK_MEDIUM";
case CriterionCode::PHYSICAL_OR_TECH_OR_UNKNOWN_ATTACK_MEDIUM:
return "PHYSICAL_OR_TECH_OR_UNKNOWN_ATTACK_MEDIUM";
case CriterionCode::UNKNOWN_20:
return "UNKNOWN_20";
case CriterionCode::UNKNOWN_21:
return "UNKNOWN_21";
case CriterionCode::UNKNOWN_22:
return "UNKNOWN_22";
default:
throw invalid_argument("invalid criterion code");
}
}
Location::Location() : Location(0, 0) {}
Location::Location(uint8_t x, uint8_t y) : Location(x, y, Direction::RIGHT) {}
Location::Location(uint8_t x, uint8_t y, Direction direction)
@@ -140,15 +310,6 @@ bool card_class_is_tech_like(CardClass cc) {
(cc == CardClass::BOSS_TECH);
}
static const vector<const char*> name_for_card_type({
"HunterSC",
"ArkzSC",
"Item",
"Creature",
"Action",
"Assist",
});
static const unordered_map<string, const char*> description_for_expr_token({
{"f", "Number of FCs controlled by current SC"},
{"d", "Die roll"},
@@ -724,7 +885,14 @@ string string_for_colors(const parray<uint8_t, 8>& colors) {
string ret;
for (size_t x = 0; x < 8; x++) {
if (colors[x]) {
ret += '0' + colors[x];
if (!ret.empty()) {
ret += ",";
}
try {
ret += name_for_link_color(colors[x]);
} catch (const invalid_argument) {
ret += string_printf("%02hhX", colors[x]);
}
}
}
if (ret.empty()) {
@@ -757,16 +925,27 @@ string string_for_range(const parray<be_uint32_t, 6>& range) {
return ret;
}
string CardDefinition::str() const {
string CardDefinition::str(bool single_line) const {
string type_str;
try {
type_str = name_for_card_type.at(static_cast<uint8_t>(this->type));
} catch (const out_of_range&) {
type_str = name_for_card_type(this->type);
} catch (const invalid_argument&) {
type_str = string_printf("%02hhX", static_cast<uint8_t>(this->type));
}
string criterion_str;
try {
criterion_str = name_for_criterion_code(this->usable_criterion);
} catch (const invalid_argument&) {
criterion_str = string_printf("%02hhX", static_cast<uint8_t>(this->usable_criterion));
}
string card_class_str;
try {
card_class_str = name_for_card_class(this->card_class());
} catch (const invalid_argument&) {
card_class_str = string_printf("%04hX", this->be_card_class.load());
}
string rarity_str = name_for_rarity(this->rarity);
string target_mode_str = name_for_target_mode(this->target_mode);
string range_str = string_for_range(this->range);
string assist_turns_str = string_for_assist_turns(this->assist_turns);
string hp_str = this->hp.str();
string ap_str = this->ap.str();
@@ -780,44 +959,115 @@ string CardDefinition::str() const {
if (this->effects[x].is_empty()) {
continue;
}
if (!effects_str.empty()) {
if (!single_line) {
effects_str += "\n ";
} else if (!effects_str.empty()) {
effects_str += ", ";
}
effects_str += this->effects[x].str();
}
return string_printf(
"[Card: %04" PRIX32 " name=%s type=%s usable_condition=%02hhX rare=%s "
"cost=%hhX+%hhX target=%s range=%s assist_turns=%s cannot_move=%s "
"cannot_attack=%s cannot_drop=%s hp=%s ap=%s tp=%s mv=%s left=%s right=%s "
"top=%s a2=%04hX class=%04hX assist_effect=[%hu, %hu] "
"drop_rates=[%hu, %hu] effects=[%s]]",
this->card_id.load(),
this->en_name.data(),
type_str.c_str(),
static_cast<uint8_t>(this->usable_criterion),
rarity_str.c_str(),
this->self_cost,
this->ally_cost,
target_mode_str.c_str(),
range_str.c_str(),
assist_turns_str.c_str(),
this->cannot_move ? "true" : "false",
this->cannot_attack ? "true" : "false",
this->cannot_drop ? "true" : "false",
hp_str.c_str(),
ap_str.c_str(),
tp_str.c_str(),
mv_str.c_str(),
left_str.c_str(),
right_str.c_str(),
top_str.c_str(),
this->unknown_a2.load(),
this->be_card_class.load(),
this->assist_effect[0].load(),
this->assist_effect[1].load(),
this->drop_rates[0].load(),
this->drop_rates[1].load(),
effects_str.c_str());
if (!single_line && effects_str.empty()) {
effects_str = " (none)";
}
if (single_line) {
string range_str = string_for_range(this->range);
return string_printf(
"[Card: %04" PRIX32 " name=%s type=%s usable_condition=%s rare=%s "
"cost=%hhX+%hhX target=%s range=%s assist_turns=%s cannot_move=%s "
"cannot_attack=%s cannot_drop=%s hp=%s ap=%s tp=%s mv=%s left=%s right=%s "
"top=%s a2=%04hX class=%s assist_effect=[%hu, %hu] "
"drop_rates=[%hu, %hu] effects=[%s]]",
this->card_id.load(),
this->en_name.data(),
type_str.c_str(),
criterion_str.c_str(),
rarity_str.c_str(),
this->self_cost,
this->ally_cost,
target_mode_str.c_str(),
range_str.c_str(),
assist_turns_str.c_str(),
this->cannot_move ? "true" : "false",
this->cannot_attack ? "true" : "false",
this->cannot_drop ? "true" : "false",
hp_str.c_str(),
ap_str.c_str(),
tp_str.c_str(),
mv_str.c_str(),
left_str.c_str(),
right_str.c_str(),
top_str.c_str(),
this->unknown_a2.load(),
card_class_str.c_str(),
this->assist_effect[0].load(),
this->assist_effect[1].load(),
this->drop_rates[0].load(),
this->drop_rates[1].load(),
effects_str.c_str());
} else { // Not single-line
string range_str;
if (this->range[0] == 0x000FFFFF) {
range_str = " (entire field)";
} else {
for (size_t x = 0; x < 6; x++) {
range_str += "\n ";
for (size_t z = 0; z < 5; z++) {
bool is_included = ((this->range[x] >> (16 - (z * 4))) & 0xF);
if (x == 4 && z == 2) {
range_str += is_included ? "@" : "#";
} else {
range_str += is_included ? "*" : "-";
}
}
}
}
return string_printf(
"\
Card: %04" PRIX32 " \"%s\"\n\
Type: %s, class: %s\n\
Usability condition: %s\n\
Rarity: %s\n\
Cost: %hhX (self) + %hhX (ally)\n\
Target mode: %s\n\
Range:%s\n\
Assist turns: %s\n\
Capabilities: %s move, %s attack\n\
HP: %s, AP: %s, TP: %s, MV: %s\n\
Left colors: %s; right colors: %s; top colors: %s\n\
Unknown a2: %04hX\n\
Assist effect: [%hu, %hu]\n\
Drop rates: [%hu, %hu] (%s drop)\n\
Effects:%s",
this->card_id.load(),
this->en_name.data(),
type_str.c_str(),
card_class_str.c_str(),
criterion_str.c_str(),
rarity_str.c_str(),
this->self_cost,
this->ally_cost,
target_mode_str.c_str(),
range_str.c_str(),
assist_turns_str.c_str(),
this->cannot_move ? "cannot" : "can",
this->cannot_attack ? "cannot" : "can",
hp_str.c_str(),
ap_str.c_str(),
tp_str.c_str(),
mv_str.c_str(),
left_str.c_str(),
right_str.c_str(),
top_str.c_str(),
this->unknown_a2.load(),
this->assist_effect[0].load(),
this->assist_effect[1].load(),
this->drop_rates[0].load(),
this->drop_rates[1].load(),
this->cannot_drop ? "cannot" : "can",
effects_str.c_str());
}
}
HPType hp_type_for_name(const char* name) {
@@ -1514,6 +1764,7 @@ DataIndex::DataIndex(const string& directory, uint32_t behavior_flags)
tags.emplace_back(std::move(tag));
}
}
strip_leading_whitespace(orig_text);
if (!card_text.emplace(card_id, std::move(orig_text)).second) {
throw runtime_error("duplicate card text id");
+8 -1
View File
@@ -21,6 +21,8 @@ namespace Episode3 {
class DataIndex;
const char* name_for_link_color(uint8_t color);
enum BehaviorFlag {
SKIP_DECK_VERIFY = 0x00000001,
IGNORE_CARD_COUNTS = 0x00000002,
@@ -93,6 +95,8 @@ enum class CriterionCode : uint8_t {
UNKNOWN_22 = 0x22,
};
const char* name_for_criterion_code(CriterionCode code);
enum class CardRarity : uint8_t {
N1 = 0x01,
R1 = 0x02,
@@ -121,6 +125,8 @@ enum class CardType : uint8_t {
END_CARD_LIST = 0xFF,
};
const char* name_for_card_type(CardType type);
enum class CardClass : uint16_t {
HU_SC = 0x0000,
RA_SC = 0x0001,
@@ -144,6 +150,7 @@ enum class CardClass : uint16_t {
ASSIST = 0x0028,
};
const char* name_for_card_class(CardClass cc);
bool card_class_is_tech_like(CardClass cc);
enum class TargetMode : uint8_t {
@@ -552,7 +559,7 @@ struct CardDefinition {
CardClass card_class() const;
void decode_range();
std::string str() const;
std::string str(bool single_line = true) const;
} __attribute__((packed)); // 0x128 bytes in total
struct CardDefinitionsFooter {
+19 -19
View File
@@ -205,9 +205,12 @@ The actions are:\n\
--gc, and --bb options can be used to select the command format and\n\
encryption. If --bb is used, the --key=KEY-NAME option is also required (as\n\
in decrypt-data above).\n\
show-ep3-data\n\
Print the Episode 3 maps and card definitions from the system/ep3 directory\n\
in a (sort of) human-readable format.\n\
show-ep3-maps\n\
Print the Episode 3 maps from the system/ep3 directory in a (sort of)\n\
human-readable format.\n\
show-ep3-cards\n\
Print the Episode 3 card definitions from the system/ep3 directory in a\n\
human-readable format.\n\
describe-item DATA\n\
Print the name of the item given by DATA (in hex). DATA must not contain\n\
spaces. If DATA is 20 bytes, newserv assumes it contains an unused item ID\n\
@@ -276,7 +279,8 @@ enum class Behavior {
EXTRACT_BML,
FORMAT_RARE_ITEM_SET,
CONVERT_ITEMRT_REL_TO_JSON,
SHOW_EP3_DATA,
SHOW_EP3_MAPS,
SHOW_EP3_CARDS,
DESCRIBE_ITEM,
ENCODE_ITEM,
PARSE_OBJECT_GRAPH,
@@ -378,7 +382,6 @@ int main(int argc, char** argv) {
const char* replay_required_access_key = "";
const char* replay_required_password = "";
uint32_t root_object_address = 0;
uint16_t ep3_card_id = 0xFFFF;
uint8_t domain = 1;
uint8_t subdomain = 0xFF;
for (int x = 1; x < argc; x++) {
@@ -538,8 +541,10 @@ int main(int argc, char** argv) {
behavior = Behavior::FORMAT_RARE_ITEM_SET;
} else if (!strcmp(argv[x], "convert-itemrt-rel-to-json")) {
behavior = Behavior::CONVERT_ITEMRT_REL_TO_JSON;
} else if (!strcmp(argv[x], "show-ep3-data")) {
behavior = Behavior::SHOW_EP3_DATA;
} else if (!strcmp(argv[x], "show-ep3-maps")) {
behavior = Behavior::SHOW_EP3_MAPS;
} else if (!strcmp(argv[x], "show-ep3-cards")) {
behavior = Behavior::SHOW_EP3_CARDS;
} else if (!strcmp(argv[x], "describe-item")) {
behavior = Behavior::DESCRIBE_ITEM;
} else if (!strcmp(argv[x], "encode-item")) {
@@ -1438,11 +1443,12 @@ int main(int argc, char** argv) {
break;
}
case Behavior::SHOW_EP3_DATA: {
case Behavior::SHOW_EP3_MAPS:
case Behavior::SHOW_EP3_CARDS: {
config_log.info("Collecting Episode 3 data");
Episode3::DataIndex index("system/ep3", Episode3::BehaviorFlag::LOAD_CARD_TEXT);
if (ep3_card_id == 0xFFFF) {
if (behavior == Behavior::SHOW_EP3_MAPS) {
auto map_ids = index.all_map_ids();
log_info("%zu maps", map_ids.size());
for (uint32_t map_id : map_ids) {
@@ -1451,22 +1457,16 @@ int main(int argc, char** argv) {
fprintf(stdout, "%s\n", s.c_str());
}
} else {
auto card_ids = index.all_card_ids();
log_info("%zu card definitions", card_ids.size());
for (uint32_t card_id : card_ids) {
auto entry = index.definition_for_card_id(card_id);
string s = entry->def.str();
string s = entry->def.str(false);
string tags = entry->debug_tags.empty() ? "(none)" : join(entry->debug_tags, ", ");
string text = entry->text.empty() ? "(No text available)" : entry->text;
fprintf(stdout, "%s\nTags: %s\n%s\n\n", s.c_str(), tags.c_str(), text.c_str());
string text = entry->text.empty() ? "(No text available)" : str_replace_all(entry->text, "\n", "\n ");
fprintf(stdout, "%s\n Tags: %s\n Text:\n %s\n\n", s.c_str(), tags.c_str(), text.c_str());
}
} else {
auto entry = index.definition_for_card_id(ep3_card_id);
string s = entry->def.str();
string tags = entry->debug_tags.empty() ? "(none)" : join(entry->debug_tags, ", ");
string text = entry->text.empty() ? "(No text available)" : entry->text;
fprintf(stdout, "%s\nTags: %s\n%s\n", s.c_str(), tags.c_str(), text.c_str());
}
break;