add ep3 cards and rare tables to HTTP server

This commit is contained in:
Martin Michelsen
2024-02-24 19:13:18 -08:00
parent c3b3cf5140
commit eaa02b2b78
7 changed files with 280 additions and 4 deletions
+214
View File
@@ -80,6 +80,16 @@ const char* name_for_link_color(uint8_t color) {
}
}
JSON json_for_link_colors(const parray<uint8_t, 8>& colors) {
JSON ret = JSON::list();
for (size_t z = 0; z < colors.size(); z++) {
if (colors[z]) {
ret.emplace_back(name_for_link_color(colors[z]));
}
}
return ret;
}
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)
@@ -1016,6 +1026,111 @@ Card: %04" PRIX32 " \"%s\"\n\
}
}
JSON CardDefinition::Stat::json() const {
const char* type_str = "unknown";
switch (this->type) {
case Type::BLANK:
type_str = "BLANK";
break;
case Type::STAT:
type_str = "DEFAULT";
break;
case Type::PLUS_STAT:
type_str = "PLUS";
break;
case Type::MINUS_STAT:
type_str = "MINUS";
break;
case Type::EQUALS_STAT:
type_str = "EQUALS";
break;
case Type::UNKNOWN:
type_str = "UNKNOWN";
break;
case Type::PLUS_UNKNOWN:
type_str = "PLUS_UNKNOWN";
break;
case Type::MINUS_UNKNOWN:
type_str = "MINUS_UNKNOWN";
break;
case Type::EQUALS_UNKNOWN:
type_str = "EQUALS_UNKNOWN";
break;
}
return JSON::dict({
{"type", type_str},
{"value", this->stat},
});
}
JSON CardDefinition::Effect::json() const {
return JSON::dict({
{"EffectNum", this->effect_num},
{"ConditionType", name_for_enum(this->type)},
{"Expression", this->expr.decode()},
{"When", this->when},
{"Arg1", this->arg1.decode()},
{"Arg2", this->arg2.decode()},
{"Arg3", this->arg3.decode()},
{"ApplyCriterion", name_for_enum(this->apply_criterion)},
{"NameIndex", this->name_index},
});
}
JSON CardDefinition::json() const {
JSON range_json;
if (this->range[0] == 0x000FFFFF) {
range_json = "ENTIRE_FIELD";
} else {
range_json = JSON::list();
for (size_t y = 0; y < 6; y++) {
uint32_t row = this->range[y];
auto& row_json = range_json.emplace_back(JSON::list());
for (size_t x = 0; x < 5; x++) {
row_json.emplace_back((row & 0x00010000) ? true : false);
row <<= 4;
}
}
}
JSON effects_json = JSON::list();
for (size_t z = 0; z < this->effects.size(); z++) {
if (!this->effects[z].is_empty()) {
effects_json.emplace_back(this->effects[z].json());
}
}
return JSON::dict({
{"CardID", this->card_id.load()},
{"JPName", this->jp_name.decode()},
{"CardType", name_for_enum(this->type)},
{"SelfCost", this->self_cost},
{"AllyCost", this->ally_cost},
{"HP", this->hp.json()},
{"AP", this->ap.json()},
{"TP", this->tp.json()},
{"MV", this->mv.json()},
{"LeftColors", json_for_link_colors(this->left_colors)},
{"RightColors", json_for_link_colors(this->right_colors)},
{"TopColors", json_for_link_colors(this->top_colors)},
{"Range", std::move(range_json)},
{"TargetMode", name_for_target_mode(this->target_mode)},
{"AssistTurns", this->assist_turns},
{"CannotMove", this->cannot_move ? true : false},
{"CannotAttack", this->cannot_attack ? true : false},
{"CannotDrop", this->cannot_drop ? true : false},
{"UsableCriterion", name_for_enum(this->usable_criterion)},
{"Rank", name_for_rank(this->rank)},
{"CardClass", name_for_enum(this->card_class())},
{"AssistAIParams", this->assist_ai_params.load()},
{"DropRates", JSON::list({this->drop_rates[0].load(), this->drop_rates[1].load()})},
{"ENName", this->en_name.decode()},
{"JPShortName", this->jp_short_name.decode()},
{"ENShortName", this->en_short_name.decode()},
{"Effects", std::move(effects_json)},
});
}
void PlayerConfig::decrypt() {
if (!this->is_encrypted) {
return;
@@ -1498,6 +1613,97 @@ void MapDefinition::assert_semantically_equivalent(const MapDefinition& other) c
}
}
JSON MapDefinition::CameraSpec::json() const {
return JSON::dict({
{"Camera", JSON::list({this->camera_x.load(), this->camera_y.load(), this->camera_z.load()})},
{"Focus", JSON::list({this->focus_x.load(), this->focus_y.load(), this->focus_z.load()})},
});
}
JSON MapDefinition::NPCDeck::json() const {
JSON card_ids_json = JSON::list();
for (size_t z = 0; z < this->card_ids.size(); z++) {
if (this->card_ids[z] != 0xFFFF) {
card_ids_json.emplace_back(this->card_ids[z].load());
}
}
return JSON::dict({
{"Name", this->name.decode()},
{"CardIDs", std::move(card_ids_json)},
});
}
JSON MapDefinition::AIParams::json() const {
JSON params_json = JSON::list();
for (size_t z = 0; z < this->params.size(); z++) {
params_json.emplace_back(this->params[z].load());
}
return JSON::dict({
{"IsArkz", this->is_arkz ? true : false},
{"Name", this->name.decode()},
{"CardIDs", std::move(params_json)},
});
}
JSON MapDefinition::DialogueSet::json() const {
JSON strings_json = JSON::list();
for (size_t z = 0; z < this->strings.size(); z++) {
strings_json.emplace_back(this->strings[z].decode());
}
return JSON::dict({
{"When", this->when.load()},
{"PercentChance", this->percent_chance.load()},
{"CardIDs", std::move(strings_json)},
});
}
JSON MapDefinition::EntryState::json() const {
JSON player_type_json;
switch (this->player_type) {
case 0x00:
player_type_json = "Player";
break;
case 0x01:
player_type_json = "Player/COM";
break;
case 0x02:
player_type_json = "COM";
break;
case 0x03:
player_type_json = "NPC";
break;
case 0x04:
player_type_json = "NONE";
break;
case 0xFF:
player_type_json = "FREE";
break;
default:
player_type_json = this->player_type;
}
JSON deck_type_json;
switch (this->deck_type) {
case 0x00:
deck_type_json = "HERO ONLY";
break;
case 0x01:
deck_type_json = "DARK ONLY";
break;
case 0xFF:
deck_type_json = "ANY";
break;
default:
deck_type_json = this->deck_type;
}
return JSON::dict({
{"PlayerType", std::move(player_type_json)},
{"DeckType", std::move(deck_type_json)},
});
}
// TODO:
// JSON MapDefinition::json() const { ... }
string MapDefinition::CameraSpec::str() const {
return string_printf(
"CameraSpec[a1=(%g %g %g %g %g %g %g %g %g) camera=(%g %g %g) focus=(%g %g %g) a2=(%g %g %g)]",
@@ -2259,6 +2465,14 @@ uint64_t CardIndex::definitions_mtime() const {
return this->mtime_for_card_definitions;
}
JSON CardIndex::definitions_json() const {
JSON ret = JSON::dict();
for (const auto& it : this->card_definitions_by_name) {
ret.emplace(it.first, it.second->def.json());
}
return ret;
}
string CardIndex::normalize_card_name(const string& name) {
string ret;
for (char ch : name) {
+10
View File
@@ -468,6 +468,7 @@ struct CardDefinition {
void decode_code();
std::string str() const;
JSON json() const;
} __attribute__((packed));
struct Effect {
@@ -504,6 +505,7 @@ struct CardDefinition {
bool is_empty() const;
static std::string str_for_arg(const std::string& arg);
std::string str(const char* separator = ", ", const TextSet* text_archive = nullptr) const;
JSON json() const;
} __attribute__((packed));
/* 0000 */ be_uint32_t card_id;
@@ -770,6 +772,7 @@ struct CardDefinition {
void decode_range();
std::string str(bool single_line = true, const TextSet* text_archive = nullptr) const;
JSON json() const;
} __attribute__((packed)); // 0x128 bytes in total
struct CardDefinitionsFooter {
@@ -1150,6 +1153,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
/* 48 */
std::string str() const;
JSON json() const;
} __attribute__((packed));
// This array specifies the camera zone maps. A camera zone map is a subset of
@@ -1207,6 +1211,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
/* 00 */ pstring<TextEncoding::SJIS, 0x18> name;
/* 18 */ parray<be_uint16_t, 0x20> card_ids; // Last one appears to always be FFFF
/* 58 */
JSON json() const;
} __attribute__((packed));
/* 1FE8 */ parray<NPCDeck, 3> npc_decks; // Unused if name[0] == 0
@@ -1222,6 +1227,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
// TODO: Figure out exactly how these are used and document here.
/* 0018 */ parray<be_uint16_t, 0x7E> params;
/* 0114 */
JSON json() const;
} __attribute__((packed));
/* 20F0 */ parray<AIParams, 3> npc_ai_params; // Unused if name[0] == 0
@@ -1274,6 +1280,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
// strings, excluding any that are empty or begin with the character '^'.
/* 0004 */ parray<pstring<TextEncoding::SJIS, 0x40>, 4> strings;
/* 0104 */
JSON json() const;
} __attribute__((packed));
// There are up to 0x10 of these per valid NPC, but only the first 13 of them
@@ -1356,6 +1363,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
bool operator==(const EntryState& other) const = default;
bool operator!=(const EntryState& other) const = default;
JSON json() const;
} __attribute__((packed));
/* 5A10 */ parray<EntryState, 4> entry_states;
/* 5A18 */
@@ -1371,6 +1379,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
void assert_semantically_equivalent(const MapDefinition& other) const;
std::string str(const CardIndex* card_index, uint8_t language) const;
JSON json() const;
} __attribute__((packed));
struct MapDefinitionTrial {
@@ -1453,6 +1462,7 @@ public:
std::shared_ptr<const CardEntry> definition_for_name_normalized(const std::string& name) const;
std::set<uint32_t> all_ids() const;
uint64_t definitions_mtime() const;
JSON definitions_json() const;
private:
static std::string normalize_card_name(const std::string& name);
+47
View File
@@ -815,6 +815,38 @@ JSON HTTPServer::generate_all_json() const {
});
}
JSON HTTPServer::generate_ep3_cards_json(bool trial) const {
const auto& index = trial ? this->state->ep3_card_index_trial : this->state->ep3_card_index;
return index->definitions_json();
}
JSON HTTPServer::generate_rare_tables_json() const {
JSON ret = JSON::list();
for (const auto& it : this->state->rare_item_sets) {
ret.emplace_back(it.first);
}
return ret;
}
JSON HTTPServer::generate_rare_table_json(const std::string& table_name) const {
try {
const auto& table = this->state->rare_item_sets.at(table_name);
shared_ptr<const ItemNameIndex> name_index;
if (ends_with(table_name, "-v1")) {
name_index = this->state->item_name_index(Version::DC_V1);
} else if (ends_with(table_name, "-v2")) {
name_index = this->state->item_name_index(Version::PC_V2);
} else if (ends_with(table_name, "-v3")) {
name_index = this->state->item_name_index(Version::GC_V3);
} else if (ends_with(table_name, "-v4")) {
name_index = this->state->item_name_index(Version::BB_V4);
}
return table->json(name_index);
} catch (const out_of_range&) {
throw http_error(404, "table does not exist");
}
}
void HTTPServer::handle_request(struct evhttp_request* req) {
JSON ret;
uint32_t serialize_options = 0;
@@ -835,14 +867,29 @@ void HTTPServer::handle_request(struct evhttp_request* req) {
if (uri == "/") {
auto endpoints_json = JSON::list({
"/y/data/ep3-cards",
"/y/data/ep3-cards-trial",
"/y/data/rare-tables",
"/y/data/config",
"/y/clients",
"/y/proxy-clients",
"/y/lobbies",
"/y/server",
"/y/summary",
"/y/all",
});
ret = JSON::dict({{"endpoints", std::move(endpoints_json)}});
} else if (uri == "/y/data/ep3-cards") {
ret = this->generate_ep3_cards_json(false);
} else if (uri == "/y/data/ep3-cards-trial") {
ret = this->generate_ep3_cards_json(true);
} else if (uri == "/y/data/rare-tables") {
ret = this->generate_rare_tables_json();
} else if (!strncmp(uri.c_str(), "/y/data/rare-tables/", 20)) {
ret = this->generate_rare_table_json(uri.substr(20));
} else if (uri == "/y/data/config") {
ret = this->state->config_json;
} else if (uri == "/y/clients") {
ret = this->generate_game_server_clients_json();
} else if (uri == "/y/proxy-clients") {
+4
View File
@@ -61,4 +61,8 @@ protected:
JSON generate_lobbies_json() const;
JSON generate_summary_json() const;
JSON generate_all_json() const;
JSON generate_ep3_cards_json(bool trial) const;
JSON generate_rare_tables_json() const;
JSON generate_rare_table_json(const std::string& table_name) const;
};
+2 -1
View File
@@ -1649,7 +1649,8 @@ Action a_convert_rare_item_set(
if (output_filename.empty() || (output_filename == "-")) {
rs->print_all_collections(stdout, s->item_name_index(version));
} else if (ends_with(output_filename, ".json")) {
string data = rs->serialize_json(s->item_name_index(version));
auto json = rs->json(s->item_name_index(version));
string data = json.serialize(JSON::SerializeOption::FORMAT | JSON::SerializeOption::HEX_INTEGERS | JSON::SerializeOption::SORT_DICT_KEYS);
write_output_data(args, data.data(), data.size(), nullptr);
} else if (ends_with(output_filename, ".gsl")) {
string data = rs->serialize_gsl(args.get<bool>("big-endian"));
+2 -2
View File
@@ -427,7 +427,7 @@ std::string RareItemSet::serialize_gsl(bool big_endian) const {
return GSLArchive::generate(files, big_endian);
}
std::string RareItemSet::serialize_json(shared_ptr<const ItemNameIndex> name_index) const {
JSON RareItemSet::json(shared_ptr<const ItemNameIndex> name_index) const {
auto modes_dict = JSON::dict();
static const array<GameMode, 4> modes = {GameMode::NORMAL, GameMode::BATTLE, GameMode::CHALLENGE, GameMode::SOLO};
for (const auto& mode : modes) {
@@ -507,7 +507,7 @@ std::string RareItemSet::serialize_json(shared_ptr<const ItemNameIndex> name_ind
modes_dict.emplace(name_for_mode(mode), std::move(episodes_dict));
}
return modes_dict.serialize(JSON::SerializeOption::FORMAT | JSON::SerializeOption::HEX_INTEGERS | JSON::SerializeOption::SORT_DICT_KEYS);
return modes_dict;
}
void RareItemSet::print_collection(
+1 -1
View File
@@ -37,7 +37,7 @@ public:
std::string serialize_afs(bool is_v1) const;
std::string serialize_gsl(bool big_endian) const;
std::string serialize_json(std::shared_ptr<const ItemNameIndex> name_index = nullptr) const;
JSON json(std::shared_ptr<const ItemNameIndex> name_index = nullptr) const;
void print_collection(
FILE* stream,