diff --git a/src/Episode3/DataIndexes.cc b/src/Episode3/DataIndexes.cc index 9c809e0f..257ebd86 100644 --- a/src/Episode3/DataIndexes.cc +++ b/src/Episode3/DataIndexes.cc @@ -729,7 +729,7 @@ string CardDefinition::Effect::str_for_arg(const string& arg) { string CardDefinition::Effect::str() const { uint8_t type = static_cast(this->type); - string cmd_str = string_printf("(%hhu) %02hhX", this->effect_num, type); + string cmd_str = string_printf("%02hhX", type); try { const char* name = description_for_condition_type.at(type).name; if (name) { @@ -747,8 +747,8 @@ string CardDefinition::Effect::str() const { string arg1str = this->str_for_arg(this->arg1); 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(), + return string_printf("((%hhu) cmd=%s%s, when=%02hhX, arg1=%s, arg2=%s, arg3=%s, cond=%02hhX, a2=%02hhX)", + this->effect_num, cmd_str.c_str(), expr_str.c_str(), this->when, arg1str.data(), arg2str.data(), arg3str.data(), static_cast(this->apply_criterion), this->unknown_a2); } @@ -962,6 +962,36 @@ string string_for_drop_rate(uint16_t drop_rate) { return string_printf("[%hu: %s]", drop_rate, description.c_str()); } +static const char* short_name_for_assist_ai_param_target(uint8_t target) { + switch (target) { + case 0: + return "ANY"; + case 1: + return "SELF"; + case 2: + return "SELF_OR_ALLY"; + case 3: + return "ENEMY"; + default: + return "__UNKNOWN__"; + } +} + +static const char* name_for_assist_ai_param_target(uint8_t target) { + switch (target) { + case 0: + return "any player"; + case 1: + return "self"; + case 2: + return "self or ally"; + case 3: + return "enemy player"; + default: + return "__UNKNOWN__"; + } +} + string CardDefinition::str(bool single_line) const { string type_str; try { @@ -1010,21 +1040,28 @@ string CardDefinition::str(bool single_line) const { string drop0_str = string_for_drop_rate(this->drop_rates[0]); string drop1_str = string_for_drop_rate(this->drop_rates[1]); + string cost_str = string_printf("%hhX", this->self_cost); + if (this->ally_cost) { + if (single_line) { + cost_str += string_printf("+%hhX", this->ally_cost); + } else { + cost_str += string_printf(" (self) + %hhX (ally)", this->ally_cost); + } + } + 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 " + "cost=%s 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=[%s, %s] effects=[%s]]", + "top=%s class=%s assist_ai_params=[target=%s priority=%hhu effect=%hhu] drop_rates=[%s, %s] 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, + cost_str.c_str(), target_mode_str.c_str(), range_str.c_str(), assist_turns_str.c_str(), @@ -1038,10 +1075,10 @@ string CardDefinition::str(bool single_line) const { 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(), + short_name_for_assist_ai_param_target((this->assist_ai_params / 1000) % 10), + static_cast((this->assist_ai_params / 100) % 10), + static_cast(this->assist_ai_params % 100), drop0_str.c_str(), drop1_str.c_str(), effects_str.c_str()); @@ -1069,15 +1106,14 @@ Card: %04" PRIX32 " \"%s\"\n\ Type: %s, class: %s\n\ Usability condition: %s\n\ Rarity: %s\n\ - Cost: %hhX (self) + %hhX (ally)\n\ + Cost: %s\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\ + Assist AI parameters: [target %s, priority %hu, effect %hu]\n\ Drop rates: [%s, %s] (%s drop)\n\ Effects:%s", this->card_id.load(), @@ -1086,8 +1122,7 @@ Card: %04" PRIX32 " \"%s\"\n\ card_class_str.c_str(), criterion_str.c_str(), rarity_str.c_str(), - this->self_cost, - this->ally_cost, + cost_str.c_str(), target_mode_str.c_str(), range_str.c_str(), assist_turns_str.c_str(), @@ -1100,9 +1135,9 @@ Card: %04" PRIX32 " \"%s\"\n\ 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(), + name_for_assist_ai_param_target((this->assist_ai_params / 1000) % 10), + static_cast((this->assist_ai_params / 100) % 10), + static_cast(this->assist_ai_params % 100), drop0_str.c_str(), drop1_str.c_str(), this->cannot_drop ? "cannot" : "can", diff --git a/src/Episode3/DataIndexes.hh b/src/Episode3/DataIndexes.hh index 1c6731d5..9e438d25 100644 --- a/src/Episode3/DataIndexes.hh +++ b/src/Episode3/DataIndexes.hh @@ -518,24 +518,27 @@ struct CardDefinition { /* 0091 */ uint8_t cannot_drop; /* 0092 */ CriterionCode usable_criterion; /* 0093 */ CardRarity rarity; - /* 0094 */ be_uint16_t unknown_a2; + /* 0094 */ be_uint16_t unused4; // 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. /* 0096 */ 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 +). - /* 0098 */ parray assist_effect; + // If this card is an assist card, this field controls how COM players handle + // playing it. (This field is ignored for other card types.) This integer + // encodes the following fields: + // - assist_ai_params % 100 (that is, the two lowest decimal places) appears + // to specify the effect, though a few unrelated cards share values in this + // field. It's not yet known how exactly this is used by the COM logic. + // - (assist_ai_params / 100) % 10 specifies the priority. It appears the COM + // logic always chooses the assist card with the highest value in this field + // if there are multiple cards to consider. + // - (assist_ai_params / 1000) % 10 specifies on who the assist card may be + // played (0 = any player, 1 = self, 2 = self or ally, 3 = enemy only). + /* 0098 */ be_uint16_t assist_ai_params; + /* 009A */ be_uint16_t unused5; - // Drop rates are decimal-encoded with the following fields: + // Drop rates are integers which encode the following data: // - rate % 10 (that is, the lowest decimal place) specifies the required game // mode. 0 means any mode, 1 means offline story mode, 2 means 1P free // battle, 3 means 2P+ free battle (specifically, PvP - two humans vs. two @@ -568,40 +571,40 @@ struct CardDefinition { // Finally, cards are chosen from the buckets with a weighted distribution // according to these tables (row is player's level class, column is card's // rarity class): - // Offline - // 1 2 3 4 5 6 7 8 9 10 - // 1 => 8000 2000 50 - // 2 => 6000 3500 500 50 - // 3 => 4500 3500 1500 400 100 - // 4 => 3000 3000 2500 1000 450 50 - // 5 => 2000 2600 2750 2000 500 100 50 - // 6 => 1900 2200 2500 2100 830 350 100 20 - // 7 => 1900 2000 2000 2000 1000 500 500 100 - // 8 => 160000 160000 190000 190000 130000 100000 50000 19999 1 - // 9 => 120000 120000 150000 160000 150000 150000 100000 49989 10 1 - // 10 => 120000 120000 130000 150000 160000 150000 100000 69965 30 5 + // Offline: + // LC | RC = 0 1 2 3 4 5 6 7 8 9 + // 1 | 8000 2000 50 + // 2 | 6000 3500 500 50 + // 3 | 4500 3500 1500 400 100 + // 4 | 3000 3000 2500 1000 450 50 + // 5 | 2000 2600 2750 2000 500 100 50 + // 6 | 1900 2200 2500 2100 830 350 100 20 + // 7 | 1900 2000 2000 2000 1000 500 500 100 + // 8 | 160000 160000 190000 190000 130000 100000 50000 19999 1 + // 9 | 120000 120000 150000 160000 150000 150000 100000 49989 10 1 + // 10 | 120000 120000 130000 150000 160000 150000 100000 69965 30 5 // Online - // 1 2 3 4 5 6 7 8 9 10 - // 1 => 8000 2000 50 - // 2 => 6000 3500 500 20 - // 3 => 4500 4000 1500 200 - // 4 => 3500 3500 2300 700 20 - // 5 => 2700 2800 2500 1500 500 10 - // 6 => 2300 2300 2300 1900 900 300 1 - // 7 => 1995 2100 2100 2100 1000 700 5 - // 8 => 1789 2100 2100 2100 1100 800 10 1 - // 9 => 14620 20000 21000 22000 13000 9000 300 80 - // 10 => 133997 190000 200000 200000 150000 120000 5000 1000 2 1 + // LC | RC = 0 1 2 3 4 5 6 7 8 9 + // 1 | 8000 2000 50 + // 2 | 6000 3500 500 20 + // 3 | 4500 4000 1500 200 + // 4 | 3500 3500 2300 700 20 + // 5 | 2700 2800 2500 1500 500 10 + // 6 | 2300 2300 2300 1900 900 300 1 + // 7 | 1995 2100 2100 2100 1000 700 5 + // 8 | 1789 2100 2100 2100 1100 800 10 1 + // 9 | 14620 20000 21000 22000 13000 9000 300 80 + // 10 | 133997 190000 200000 200000 150000 120000 5000 1000 2 1 // These values are all relative to other values in the same row. For example, - // if your character is in level class 1, you'll get cards of rarity class 1 - // about 80% of the time, cards of rarity class 2 about 20% of the time, and - // cards of rarity class 3 about 0.5% of the time. (The actual probabilities + // if your character is in level class 1, you'll get cards of rarity class 0 + // about 80% of the time, cards of rarity class 1 about 20% of the time, and + // cards of rarity class 2 about 0.5% of the time. (The actual probabilities // are 8000/10050, 2000/10050, and 50/10050.) - // The drop rates are completely ignored if any of the following are true - // (which means the card can never be found in a normal post-battle draw): + // The drop rates for a card are completely ignored if any of the following + // are true (which means it can never be found in a normal post-battle draw): // - type is SC_HUNTERS or SC_ARKZ // - card_class is BOSS_ATTACK_ACTION (0x23) or BOSS_TECH (0x24) - // - rarity is E, D1, D2, or INVIS + // - rarity is E, D1, or D2 // - cannot_drop is 1 (specifically 1; other nonzero values here don't // prevent the card from appearing in post-battle draws) /* 009C */ parray drop_rates; @@ -610,7 +613,7 @@ struct CardDefinition { /* 00B4 */ ptext jp_short_name; /* 00BF */ ptext en_short_name; /* 00C7 */ parray effects; - /* 0127 */ uint8_t unused4; + /* 0127 */ uint8_t unused6; /* 0128 */ bool is_sc() const; diff --git a/src/Main.cc b/src/Main.cc index b6ab590c..8cab0785 100644 --- a/src/Main.cc +++ b/src/Main.cc @@ -382,6 +382,7 @@ int main(int argc, char** argv) { bool compress_optimal = false; bool json = false; bool download = false; + bool one_line = false; const char* find_decryption_seed_ciphertext = nullptr; vector find_decryption_seed_plaintexts; const char* input_filename = nullptr; @@ -398,6 +399,8 @@ int main(int argc, char** argv) { return 0; } else if (!strncmp(argv[x], "--threads=", 10)) { num_threads = strtoull(&argv[x][10], nullptr, 0); + } else if (!strcmp(argv[x], "--one-line")) { + one_line = true; } else if (!strcmp(argv[x], "--download")) { download = true; } else if (!strcmp(argv[x], "--patch")) { @@ -1512,10 +1515,14 @@ int main(int argc, char** argv) { log_info("%zu card definitions", card_ids.size()); for (uint32_t card_id : card_ids) { auto entry = card_index.definition_for_id(card_id); - 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)" : 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()); + string s = entry->def.str(one_line); + if (one_line) { + fprintf(stdout, "%s\n", s.c_str()); + } else { + string tags = entry->debug_tags.empty() ? "(none)" : join(entry->debug_tags, ", "); + 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()); + } } break; }