From 4f2e333d6c2fcd464178e11e521037125674e61f Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Tue, 2 May 2023 09:21:48 -0700 Subject: [PATCH] update some Ep3 format notes --- src/Episode3/DataIndex.cc | 78 ++++++++++++++++++------------------- src/Episode3/DataIndex.hh | 29 ++++++++------ src/Episode3/PlayerState.cc | 2 +- 3 files changed, 58 insertions(+), 51 deletions(-) diff --git a/src/Episode3/DataIndex.cc b/src/Episode3/DataIndex.cc index d65c1f20..4ad36bab 100644 --- a/src/Episode3/DataIndex.cc +++ b/src/Episode3/DataIndex.cc @@ -45,9 +45,9 @@ Location::Location(uint8_t x, uint8_t y, Direction direction) bool Location::operator==(const Location& other) const { return (this->x == other.x) && - (this->y == other.y) && - (this->direction == other.direction) && - (this->unused == other.unused); + (this->y == other.y) && + (this->direction == other.direction) && + (this->unused == other.unused); } bool Location::operator!=(const Location& other) const { return !this->operator==(other); @@ -55,7 +55,7 @@ bool Location::operator!=(const Location& other) const { std::string Location::str() const { return string_printf("Location[x=%hhu, y=%hhu, dir=%s, u=%hhu]", - this->x, this->y, name_for_direction(this->direction), this->unused); + this->x, this->y, name_for_direction(this->direction), this->unused); } void Location::clear() { @@ -136,8 +136,8 @@ const char* name_for_direction(Direction d) { bool card_class_is_tech_like(CardClass cc) { return (cc == CardClass::TECH) || - (cc == CardClass::PHOTON_BLAST) || - (cc == CardClass::BOSS_TECH); + (cc == CardClass::PHOTON_BLAST) || + (cc == CardClass::BOSS_TECH); } static const vector name_for_card_type({ @@ -503,14 +503,14 @@ string CardDefinition::Stat::str() const { bool CardDefinition::Effect::is_empty() const { return (this->effect_num == 0 && - this->type == ConditionType::NONE && - this->expr.is_filled_with(0) && - this->when == 0 && - this->arg1.is_filled_with(0) && - this->arg2.is_filled_with(0) && - this->arg3.is_filled_with(0) && - this->apply_criterion == CriterionCode::NONE && - this->unknown_a2 == 0); + this->type == ConditionType::NONE && + this->expr.is_filled_with(0) && + this->when == 0 && + this->arg1.is_filled_with(0) && + this->arg2.is_filled_with(0) && + this->arg3.is_filled_with(0) && + this->apply_criterion == CriterionCode::NONE && + this->unknown_a2 == 0); } string CardDefinition::Effect::str_for_arg(const string& arg) { @@ -587,8 +587,8 @@ string CardDefinition::Effect::str() const { string arg2str = this->str_for_arg(this->arg2); string arg3str = this->str_for_arg(this->arg3); return string_printf("(cmd=%s%s, when=%02hhX, arg1=%s, arg2=%s, arg3=%s, cond=%02hhX, a2=%02hhX)", - cmd_str.c_str(), expr_str.c_str(), this->when, arg1str.data(), - arg2str.data(), arg3str.data(), static_cast(this->apply_criterion), this->unknown_a2); + cmd_str.c_str(), expr_str.c_str(), this->when, arg1str.data(), + arg2str.data(), arg3str.data(), static_cast(this->apply_criterion), this->unknown_a2); } bool CardDefinition::is_sc() const { @@ -788,7 +788,7 @@ string CardDefinition::str() const { 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 hidden=%s hp=%s ap=%s tp=%s mv=%s left=%s right=%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(), @@ -803,7 +803,7 @@ string CardDefinition::str() const { assist_turns_str.c_str(), this->cannot_move ? "true" : "false", this->cannot_attack ? "true" : "false", - this->hide_in_deck_edit ? "true" : "false", + this->cannot_drop ? "true" : "false", hp_str.c_str(), ap_str.c_str(), tp_str.c_str(), @@ -901,7 +901,7 @@ string Rules::str() const { break; default: tokens.emplace_back(string_printf("hp_type=(%02hhX)", - static_cast(this->hp_type))); + static_cast(this->hp_type))); break; } @@ -919,7 +919,7 @@ string Rules::str() const { break; default: tokens.emplace_back(string_printf("dice_exchange=(%02hhX)", - static_cast(this->dice_exchange_mode))); + static_cast(this->dice_exchange_mode))); break; } tokens.emplace_back(string_printf("dice_boost=%s", this->disable_dice_boost ? "DISABLED" : "ENABLED")); @@ -942,7 +942,7 @@ string Rules::str() const { break; default: tokens.emplace_back(string_printf("allowed_cards=(%02hhX)", - static_cast(this->allowed_cards))); + static_cast(this->allowed_cards))); break; } tokens.emplace_back(string_printf("assist_cards=%s", this->no_assist_cards ? "DISALLOWED" : "ALLOWED")); @@ -961,17 +961,17 @@ StateFlags::StateFlags() { bool StateFlags::operator==(const StateFlags& other) const { return (this->turn_num == other.turn_num) && - (this->battle_phase == other.battle_phase) && - (this->current_team_turn1 == other.current_team_turn1) && - (this->current_team_turn2 == other.current_team_turn2) && - (this->action_subphase == other.action_subphase) && - (this->setup_phase == other.setup_phase) && - (this->registration_phase == other.registration_phase) && - (this->team_exp == other.team_exp) && - (this->team_dice_boost == other.team_dice_boost) && - (this->first_team_turn == other.first_team_turn) && - (this->tournament_flag == other.tournament_flag) && - (this->client_sc_card_types == other.client_sc_card_types); + (this->battle_phase == other.battle_phase) && + (this->current_team_turn1 == other.current_team_turn1) && + (this->current_team_turn2 == other.current_team_turn2) && + (this->action_subphase == other.action_subphase) && + (this->setup_phase == other.setup_phase) && + (this->registration_phase == other.registration_phase) && + (this->team_exp == other.team_exp) && + (this->team_dice_boost == other.team_dice_boost) && + (this->first_team_turn == other.first_team_turn) && + (this->tournament_flag == other.tournament_flag) && + (this->client_sc_card_types == other.client_sc_card_types); } bool StateFlags::operator!=(const StateFlags& other) const { return !this->operator==(other); @@ -1020,7 +1020,7 @@ string MapDefinition::str(const DataIndex* data_index) const { }; lines.emplace_back(string_printf("Map %08" PRIX32 ": %hhux%hhu", - this->map_number.load(), this->width, this->height)); + this->map_number.load(), this->width, this->height)); lines.emplace_back(string_printf(" a1=%08" PRIX32, this->unknown_a1.load())); lines.emplace_back(string_printf(" environment_number=%02hhX", this->environment_number)); lines.emplace_back(string_printf(" num_alt_maps=%02hhX", this->num_alt_maps)); @@ -1140,7 +1140,7 @@ string MapDefinition::str(const DataIndex* data_index) const { } lines.emplace_back(" a7a=" + format_data_string(this->unknown_a7_a.data(), this->unknown_a7_a.bytes())); lines.emplace_back(string_printf(" a7b=[%08" PRIX32 " %08" PRIX32 " %08" PRIX32 "]", - this->unknown_a7_b[0].load(), this->unknown_a7_b[1].load(), this->unknown_a7_b[2].load())); + this->unknown_a7_b[0].load(), this->unknown_a7_b[1].load(), this->unknown_a7_b[2].load())); if (this->before_message[0]) { lines.emplace_back(" before_message: " + string(this->before_message)); } @@ -1167,7 +1167,7 @@ string MapDefinition::str(const DataIndex* data_index) const { } } lines.emplace_back(string_printf(" a9=[%08" PRIX32 " %08" PRIX32 " %04hX %04hX]", - this->unknown_a9_a.load(), this->unknown_a9_b.load(), this->unknown_a9_c.load(), this->unknown_a9_d.load())); + this->unknown_a9_a.load(), this->unknown_a9_b.load(), this->unknown_a9_c.load(), this->unknown_a9_d.load())); lines.emplace_back(string_printf(" a10=%02hhX", this->unknown_a10)); lines.emplace_back(string_printf(" cyber_block_type=%02hhX", this->cyber_block_type)); lines.emplace_back(string_printf(" a11=%02hhX%02hhX", this->unknown_a11[0], this->unknown_a11[1])); @@ -1522,13 +1522,13 @@ DataIndex::DataIndex(const string& directory, uint32_t behavior_flags) this->maps_by_name.emplace(entry->map.name, entry); string name = entry->map.name; static_game_data_log.info("Indexed Episode 3 %s %s (%08" PRIX32 "; %s)", - is_quest ? "online quest" : "free battle map", - filename.c_str(), entry->map.map_number.load(), name.c_str()); + is_quest ? "online quest" : "free battle map", + filename.c_str(), entry->map.map_number.load(), name.c_str()); } } catch (const exception& e) { static_game_data_log.warning("Failed to index Episode 3 map %s: %s", - filename.c_str(), e.what()); + filename.c_str(), e.what()); } } }; @@ -1664,7 +1664,7 @@ const string& DataIndex::get_compressed_map_list() const { } size_t decompressed_size = sizeof(header) + entries_w.size() + strings_w.size(); static_game_data_log.info("Generated Episode 3 compressed map list (0x%zX -> 0x%zX bytes)", - decompressed_size, this->compressed_map_list.size()); + decompressed_size, this->compressed_map_list.size()); } return this->compressed_map_list; } diff --git a/src/Episode3/DataIndex.hh b/src/Episode3/DataIndex.hh index 6a5d2b95..4966e652 100644 --- a/src/Episode3/DataIndex.hh +++ b/src/Episode3/DataIndex.hh @@ -495,19 +495,26 @@ struct CardDefinition { uint8_t cannot_move; // 0 for SC and creature cards; 1 for everything else uint8_t cannot_attack; // 1 for shields, mags, defense actions, and assist cards uint8_t unused3; - uint8_t hide_in_deck_edit; // 0 = player can use this card (appears in deck edit) + // If cannot_drop is 0, this card can't appear in post-battle rewards. A + // value of 0 here also prevents the card from being used as a God Whim + // random assist. + uint8_t cannot_drop; CriterionCode usable_criterion; CardRarity rarity; be_uint16_t unknown_a2; - be_uint16_t be_card_class; // Used for checking attributes (e.g. item types) - // These two fields seem to always contain the same value, and are always 0 - // for non-assist cards and nonzero for assists. Each assist card has a unique - // value here and no effects, which makes it look like this is how assist - // effects are implemented. There seems to be some 1k-modulation going on here - // too; most cards are in the range 101-174 but a few have e.g. 1150, 2141. A - // few pairs of cards have the same effect, which makes it look like some - // other fields are also involved in determining their effects (see e.g. Skip - // Draw / Skip Move, Dice Fever / Dice Fever +, Reverse Card / Rich +). + // The card class is used for checking attributes (e.g. item types). It's + // stored big-endian here, so there's a helper function (card_class()) that + // returns a usable CardClass enum value. + be_uint16_t be_card_class; + // The two fields of this array seem to always contain the same value, and + // are always 0 for non-assist cards and nonzero for assists. Each assist + // card has a unique value here and no effects, though the server ignores + // these values - assist effects are hardcoded based on the card ID instead. + // There seems to be some 1k-modulation going on here; most cards have values + // here in the range 101-174 but a few have e.g. 1150, 2141. A few pairs of + // cards have the same effect, so this cannot be used by the server anyway to + // determine assist cards' effects (see e.g. Skip Draw / Skip Move, Dice + // Fever / Dice Fever +, Reverse Card / Rich +). parray assist_effect; // Drop rates are decimal-encoded with the following fields: // - rate % 10 (that is, the lowest decimal place) specifies the required game @@ -524,7 +531,7 @@ struct CardDefinition { // - type is SC_HUNTERS or SC_ARKZ // - unknown_a3 is 0x23 or 0x24 // - rarity is E, D1, D2, or INVIS - // - hide_in_deck_edit is 1 (specifically 1; other nonzero values here don't + // - cannot_drop is 1 (specifically 1; other nonzero values here don't // prevent the card from appearing in post-battle draws) parray drop_rates; ptext en_name; diff --git a/src/Episode3/PlayerState.cc b/src/Episode3/PlayerState.cc index 71ad9f6b..54c097cd 100644 --- a/src/Episode3/PlayerState.cc +++ b/src/Episode3/PlayerState.cc @@ -990,7 +990,7 @@ void PlayerState::replace_all_set_assists_with_random_assists() { card_id = ALL_ASSIST_CARD_IDS[index]; if (!this->god_whim_can_use_hidden_cards) { auto ce = this->server()->definition_for_card_id(card_id); - if (!ce || ce->def.hide_in_deck_edit) { + if (!ce || ce->def.cannot_drop) { continue; } }