diff --git a/CMakeLists.txt b/CMakeLists.txt index 6954aa05..a7a84947 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,6 +44,7 @@ add_executable(newserv src/Channel.cc src/ChatCommands.cc src/Client.cc + src/CommonItemSet.cc src/Compression.cc src/DNSServer.cc src/Episode3/AssistServer.cc @@ -63,6 +64,9 @@ add_executable(newserv src/GSLArchive.cc src/IPFrameInfo.cc src/IPStackSimulator.cc + src/ItemCreator.cc + src/ItemData.cc + src/ItemParameterTable.cc src/Items.cc src/LevelTable.cc src/License.cc diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index 456d2f4a..7a97966f 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -989,7 +989,7 @@ static void server_command_what(shared_ptr, shared_ptr l, send_text_message(c, u"$C4No items are near you"); } else { const auto& item = l->item_id_to_floor_item.at(nearest_item_id); - string name = name_for_item(item.inv_item.data, true); + string name = item.inv_item.data.name(true); send_text_message(c, decode_sjis(name)); } } @@ -1091,7 +1091,7 @@ static void server_command_item(shared_ptr, shared_ptr l, l->add_item(item, c->area, c->x, c->z); send_drop_stacked_item(l, item.data, c->area, c->x, c->z); - string name = name_for_item(item.data, true); + string name = item.data.name(true); send_text_message(c, u"$C7Item created:\n" + decode_sjis(name)); } @@ -1137,14 +1137,14 @@ static void proxy_command_item(shared_ptr, if (set_drop) { session.next_drop_item = item; - string name = name_for_item(session.next_drop_item.data, true); + string name = session.next_drop_item.data.name(true); send_text_message(session.client_channel, u"$C7Next drop:\n" + decode_sjis(name)); } else { send_drop_stacked_item(session.client_channel, item.data, session.area, session.x, session.z); send_drop_stacked_item(session.server_channel, item.data, session.area, session.x, session.z); - string name = name_for_item(item.data, true); + string name = item.data.name(true); send_text_message(session.client_channel, u"$C7Item created:\n" + decode_sjis(name)); } } diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index 63fcd36d..dc18de6a 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -3925,8 +3925,8 @@ struct G_DropItem_PC_V3_BB_6x5F { struct G_EnemyDropItemRequest_DC_6x60 { G_UnusedHeader header; uint8_t area; - uint8_t enemy_id; - le_uint16_t request_id; + uint8_t rt_index; + le_uint16_t enemy_id; le_float x; le_float z; le_uint16_t unknown_a1; diff --git a/src/CommonItemSet.cc b/src/CommonItemSet.cc new file mode 100644 index 00000000..74c9f2e5 --- /dev/null +++ b/src/CommonItemSet.cc @@ -0,0 +1,140 @@ +#include "CommonItemSet.hh" + +#include "StaticGameData.hh" + + + +CommonItemSet::CommonItemSet(shared_ptr data) + : gsl(data, true) { } + +const CommonItemSet::BETable& CommonItemSet::get_table( + Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) const { + // TODO: What should we do for Ep4? + string filename = string_printf("ItemPT%s%s%c%1d.rel", + ((mode == GameMode::CHALLENGE) ? "c" : ""), + ((episode == Episode::EP2) ? "l" : ""), + tolower(abbreviation_for_difficulty(difficulty)), + secid); + auto data = this->gsl.get(filename); + if (data.second < sizeof(BETable)) { + throw runtime_error(string_printf( + "ItemPT entry %s is too small (received %zX bytes, expected %zX bytes)", + filename.c_str(), data.second, sizeof(BETable))); + } + return *reinterpret_cast(data.first); +} + + + +RELFileSet::RELFileSet(std::shared_ptr data) + : data(data), r(*this->data) { } + + + +ArmorRandomSet::ArmorRandomSet(std::shared_ptr data) + : RELFileSet(data) { + // For some reason the footer tables are doubly indirect in this file + uint32_t specs_offset_offset = r.pget_u32b(data->size() - 0x10); + uint32_t specs_offset = r.pget_u32b(specs_offset_offset); + this->tables = &r.pget>(specs_offset); +} + +std::pair +ArmorRandomSet::get_armor_table(size_t index) const { + return this->get_table(this->tables->at(0), index); +} + +std::pair +ArmorRandomSet::get_shield_table(size_t index) const { + return this->get_table(this->tables->at(1), index); +} + +std::pair +ArmorRandomSet::get_unit_table(size_t index) const { + return this->get_table(this->tables->at(2), index); +} + + + +ToolRandomSet::ToolRandomSet(std::shared_ptr data) + : RELFileSet(data) { + uint32_t specs_offset = r.pget_u32b(data->size() - 0x10); + this->common_recovery_table_spec = &r.pget(r.pget_u32b( + specs_offset)); + this->rare_recovery_table_spec = &r.pget(r.pget_u32b( + specs_offset + sizeof(uint32_t)), 2 * sizeof(TableSpec)); + this->tech_disk_table_spec = this->rare_recovery_table_spec + 1; + this->tech_disk_level_table_spec = &r.pget(r.pget_u32b( + specs_offset + 2 * sizeof(uint32_t))); +} + +pair ToolRandomSet::get_common_recovery_table( + size_t index) const { + return this->get_table(*this->common_recovery_table_spec, index); +} + +pair +ToolRandomSet::get_rare_recovery_table(size_t index) const { + return this->get_table(*this->rare_recovery_table_spec, index); +} + +pair +ToolRandomSet::get_tech_disk_table(size_t index) const { + return this->get_table(*this->tech_disk_table_spec, index); +} + +pair +ToolRandomSet::get_tech_disk_level_table(size_t index) const { + return this->get_table(*this->tech_disk_level_table_spec, index); +} + + + +WeaponRandomSet::WeaponRandomSet(std::shared_ptr data) + : RELFileSet(data) { + uint32_t offsets_offset = this->r.pget_u32b(data->size() - 0x10); + this->offsets = &this->r.pget(offsets_offset); +} + +std::pair +WeaponRandomSet::get_weapon_type_table(size_t index) const { + const auto& spec = this->r.pget( + this->offsets->weapon_type_table + index * sizeof(TableSpec)); + const auto* data = &this->r.pget( + spec.offset, spec.entries_per_table * sizeof(WeightTableEntry8)); + return make_pair(data, spec.entries_per_table); +} + +const parray* +WeaponRandomSet::get_bonus_type_table(size_t which, size_t index) const { + uint32_t base_offset = which + ? this->offsets->bonus_type_table2 : this->offsets->bonus_type_table1; + return &this->r.pget>( + base_offset + sizeof(parray) * index); +} + +const WeaponRandomSet::RangeTableEntry* +WeaponRandomSet::get_bonus_range(size_t which, size_t index) const { + uint32_t base_offset = which + ? this->offsets->bonus_range_table2 : this->offsets->bonus_range_table1; + return &this->r.pget( + base_offset + sizeof(RangeTableEntry) * index); +} + +const parray* +WeaponRandomSet::get_special_mode_table(size_t index) const { + return &this->r.pget>( + this->offsets->special_mode_table + sizeof(parray) * index); +} + +const WeaponRandomSet::RangeTableEntry* +WeaponRandomSet::get_standard_grind_range(size_t index) const { + return &this->r.pget( + this->offsets->standard_grind_range_table + sizeof(RangeTableEntry) * index); +} + +const WeaponRandomSet::RangeTableEntry* +WeaponRandomSet::get_favored_grind_range(size_t index) const { + return &this->r.pget( + this->offsets->favored_grind_range_table + sizeof(RangeTableEntry) * index); +} diff --git a/src/CommonItemSet.hh b/src/CommonItemSet.hh index 8f5eddcc..b8880b85 100644 --- a/src/CommonItemSet.hh +++ b/src/CommonItemSet.hh @@ -2,204 +2,358 @@ #include +#include "GSLArchive.hh" +#include "StaticGameData.hh" #include "Text.hh" +class CommonItemSet { +public: + template + struct Range { + IntT min; + IntT max; + } __attribute__((packed)); -// This file describes the structure of PSO GC ItemPT.gsl entries. -// TODO: This is not (yet) used anywhere in newserv, but we can use it as a base -// for PSOBB common item generation. Implement this. + // The Table structure below describes the format of a single ItemPT entry + // (which corresponds to a single difficulty, episode, section ID, and + // challenge flag). ItemPT entries (within ItemPT.gsl) are named like this: + // string_printf("ItemPT%s%s%c%1d.rel", + // (is_challenge ? "c" : ""), + // (is_ep2 ? "l" : ""), + // char_for_difficulty(difficulty), // One of "nhvu" + // section_id); + // For GC, use be_uint16_t/be_uint32_t; for other platforms use le variants + template + struct Table { + // This data structure uses index probability tables in multiple places. An + // index probability table is a table where each entry holds the probability + // that that entry's index is used. For example, if the armor slot count + // probability table contains [77, 17, 5, 1, 0], this means there is a 77% + // chance of no slots, 17% chance of 1 slot, 5% chance of 2 slots, 1% chance + // of 3 slots, and no chance of 4 slots. The values in index probability + // tables do not have to add up to 100; the game sums all of them and + // chooses a random number less than that maximum. -// The ItemPT structure below describes the format of a single ItemPT entry -// (which corresponds to a single difficulty, episode, section ID, and challenge -// flag). ItemPT entries (within ItemPT.gsl) are named like this: -// string_printf("ItemPT_%s%s%c%1d.rel", -// (is_challenge ? "c" : ""), -// (is_ep2 ? "l" : ""), -// char_for_difficulty(difficulty), // One of "nhvu" -// section_id); + // The area (floor) number is used in many places as well. Unlike the normal + // area numbers, which start with Pioneer 2, the area numbers in this + // structure start with Forest 1, and boss areas are treated as the first + // area of the next section (so De Rol Le has Mines 1 drops, for example). + // Final boss areas are treated as the last non-boss area (so Dark Falz + // boxes are like Ruins 3 boxes). We refer to these adjusted area numbers as + // (area - 1). -template -struct Range { - IntT low; - IntT high; -} __attribute__((packed)); + // This index probability table determines the types of non-rare weapons. + // The indexes in this table correspond to the non-rare weapon types 01 + // through 0C (Saber through Wand). + /* 0000 */ parray base_weapon_type_prob_table; -// For GC, use be_uint16_t/be_uint32_t; for other platforms use the le variants -template -struct ItemPT { - // This data structure uses index probability tables in multiple places. An - // index probability table is a table where each entry holds the probability - // that that entry's index is used. For example, if the armor slot count - // probability table contains [77, 17, 5, 1, 0], this means there is a 77% - // chance of no slots, 17% chance of 1 slot, 5% chance of 2 slots, 1% chance - // of 3 slots, and no chance of 4 slots. The values in index probability - // tables do not have to add up to 100; the game sums all of them and chooses - // a random number less than that maximum. + // This table specifies the base subtype for each weapon type. Negative + // values here mean that the weapon cannot be found in the first N areas (so + // -2, for example, means that the weapon never appears in Forest 1 or 2 at + // all). Nonnegative values here mean the subtype can be found in all areas, + // and specify the base subtype (usually in the range [0, 4]). The subtype + // of weapon that actually appears depends on this value and a value from + // the following table. + /* 000C */ parray subtype_base_table; - // The area (floor) number is used in many places as well. Unlike the normal - // area numbers, which start with Pioneer 2, the area numbers in this - // structure start with Forest 1, and boss areas are treated as the first area - // of the next section (so De Rol Le has Mines 1 drops, for example). Final - // boss areas are treated as the last non-boss area (so Dark Falz boxes are - // like Ruins 3 boxes). We refer to these adjusted area numbers as (area - 1). + // This table specifies how many areas each weapon subtype appears in. For + // example, if Sword (subtype 02, which is index 1 in this table and the + // table above) has a subtype base of -2 and a subtype area lneght of 4, + // then Sword items can be found when area - 1 is 2, 3, 4, or 5 (Cave 1 + // through Mine 1), and Gigush (the next sword subtype) can be found in Mine + // 1 through Ruins 3. + /* 0018 */ parray subtype_area_length_table; - // This index probability table determines the types of non-rare weapons. The - // indexes in this table correspond to the non-rare weapon types 01 through 0C - // (Saber through Wand). - /* 0000 */ parray base_weapon_type_prob_table; + // This index probability table specifies how likely each possible grind + // value is. The table is indexed as [grind][subtype_area_index], where the + // subtype area index is how many areas the player is beyond the first area + // in which the subtype can first be found (clamped to [0, 3]). To continue + // the example above, in Cave 3, subtype_area_index would be 2, since Swords + // can first be found two areas earlier in Cave 1. + // For example, this table could look like this: + // [64 1E 19 14] // Chance of getting a grind +0 + // [00 1E 17 0F] // Chance of getting a grind +1 + // [00 14 14 0E] // Chance of getting a grind +2 + // ... + // C1 C2 C3 M1 // (Episode 1 area values from the example for reference) + /* 0024 */ parray, 9> grind_prob_tables; - // This table specifies the base subtype for each weapon type. Negative values - // here mean that the weapon cannot be found in the first N areas (so -2, for - // example, means that the weapon never appears in Forest 1 or 2 at all). - // Nonnegative values here mean the subtype can be found in all areas, and - // specify the base subtype (usually in the range [0, 4]). The subtype of - // weapon that actually appears depends on this value and a value from the - // following table. - /* 000C */ parray subtype_base_table; + // This array specifies the chance that a rare weapon will have each + // possible bonus value. This is indexed as [(bonus_value - 10 / 5)][spec], + // so the first row refers the probability of getting a -10% bonus, the next + // row is the chance of getting -5%, etc., all the way up to +100%. For + // non-rare items, spec is determined randomly based on the following field; + // for rare items, spec is always 5. + /* 0048 */ parray, 0x17> bonus_value_prob_tables; - // This table specifies how many areas each weapon subtype appears in. For - // example, if Sword (subtype 02, which is index 1 in this table and the table - // above) has a subtype base of -2 and a subtype area lneght of 4, then Sword - // items can be found when area - 1 is 2, 3, 4, or 5 (Cave 1 through Mine 1), - // and Gigush (the next sword subtype) can be found in Mine 1 through Ruins 3. - /* 0018 */ parray subtype_area_length_table; + // This array specifies the value of spec to be used in the above lookup for + // non-rare items. This is NOT an index probability table; this is a direct + // lookup with indexes [bonus_index][area - 1]. A value of 0xFF in any byte + // of this array prevents any weapon from having a bonus in that slot. + // For example, the array might look like this: + // [00 00 00 01 01 01 01 02 02 02] + // [FF FF FF 00 00 00 01 01 01 01] + // [FF FF FF FF FF FF FF FF FF 00] + // F1 F2 C1 C2 C3 M1 M2 R1 R2 R3 // (Episode 1 areas, for reference) + // In this example, spec is 0, 1, or 2 in all cases where a weapon can have + // a bonus. In Forest 1 and 2 and Cave 1, weapons may have at most one + // bonus; in all other areas except Ruins 3, they can have at most two + // bonuses, and in Ruins 3, they can have up to three bonuses. + /* 015C */ parray, 3> nonrare_bonus_prob_spec; - // This index probability table specifies how likely each possible grind value - // is. The table is indexed as [grind][subtype_area_index], where the subtype - // area index is how many areas the player is beyond the first area in which - // the subtype can first be found (clamped to [0, 3]). To continue the example - // above, in Cave 3, subtype_area_index would be 2, since Swords can first be - // found two areas earlier in Cave 1. - // For example, this table could look like this: - // [64 1E 19 14] // Chance of getting a grind +0 - // [00 1E 17 0F] // Chance of getting a grind +1 - // [00 14 14 0E] // Chance of getting a grind +2 - // ... - // C1 C2 C3 M1 // (Episode 1 area values from the example, for reference) - /* 0024 */ parray, 9> grind_prob_tables; + // This array specifies the chance that a weapon will have each bonus type. + // The table is indexed as [bonus_type][area - 1] for non-rare items; for + // rare items, a random value in the range [0, 9] is used instead of + // (area - 1). + // For example, the table might look like this: + // [46 46 3F 3E 3E 3D 3C 3C 3A 3A] // Chance of getting no bonus + // [14 14 0A 0A 09 02 02 04 05 05] // Chance of getting Native bonus + // [0A 0A 12 11 11 09 09 08 08 08] // Chance of getting A.Beast bonus + // [00 00 09 0A 0B 13 12 08 09 09] // Chance of getting Machine bonus + // [00 00 00 01 01 08 0A 13 13 13] // Chance of getting Dark bonus + // [00 00 00 00 00 01 01 01 01 01] // Chance of getting Hit bonus + // F1 F2 C1 C2 C3 M1 M2 R1 R2 R3 // (Episode 1 areas, for reference) + /* 017A */ parray, 6> bonus_type_prob_tables; - // This array specifies the chance that a rare weapon will have each possible - // bonus value. The table is indexed as [(bonus_value - 10 / 5)][spec], so the - // first row refers the probability of getting a -10% bonus, the next row is - // the chance of getting -5%, etc., all the way up to +100%. For non-rare - // items, spec is determined randomly based on the following field; for rare - // items, spec is always 5. - /* 0048 */ parray, 0x17> bonus_value_prob_tables; + // This array (indexed by area - 1) specifies a multiplier of used in + // special ability determination. It seems this uses the star values from + // ItemPMT, but not yet clear exactly in what way. + // TODO: Figure out exactly what this does. Anchor: 80106FEC + /* 01B6 */ parray special_mult; - // This array specifies the value of spec to be used in the above lookup for - // non-rare items. This is NOT an index probability table; this is a direct - // lookup with indexes [bonus_index][area - 1]. A value of 0xFF in any byte of - // this array prevents any weapon from having a bonus in that slot. - // For example, the array might look like this: - // [00 00 00 01 01 01 01 02 02 02] - // [FF FF FF 00 00 00 01 01 01 01] - // [FF FF FF FF FF FF FF FF FF 00] - // F1 F2 C1 C2 C3 M1 M2 R1 R2 R3 // (Episode 1 areas, for reference) - // In this example, spec is 0, 1, or 2 in all cases where a weapon can have a - // bonus. In Forest 1 and 2 and Cave 1, weapons may have at most one bonus; in - // all other areas except Ruins 3, they can have at most two bonuses, and in - // Ruins 3, they can have up to three bonuses. - /* 015C */ parray, 3> nonrare_bonus_prob_spec; + // This array (indexed by area - 1) specifies the probability that any + // non-rare weapon will have a special ability. + /* 01C0 */ parray special_percent; - // This array specifies the chance that a weapon will have each bonus type. - // The table is indexed as [bonus_type][area - 1] for non-rare items; for rare - // items, a random value in the range [0, 9] is used instead of (area - 1). - // For example, the table might look like this: - // [46 46 3F 3E 3E 3D 3C 3C 3A 3A] // Chance of getting no bonus - // [14 14 0A 0A 09 02 02 04 05 05] // Chance of getting Native bonus - // [0A 0A 12 11 11 09 09 08 08 08] // Chance of getting A.Beast bonus - // [00 00 09 0A 0B 13 12 08 09 09] // Chance of getting Machine bonus - // [00 00 00 01 01 08 0A 13 13 13] // Chance of getting Dark bonus - // [00 00 00 00 00 01 01 01 01 01] // Chance of getting Hit bonus - // F1 F2 C1 C2 C3 M1 M2 R1 R2 R3 // (Episode 1 areas, for reference) - /* 017A */ parray, 6> bonus_type_prob_tables; + // TODO: Figure out exactly how this table is used. Anchor: 80106D34 + /* 01CA */ parray armor_shield_type_index_prob_table; - // This array (indexed by area - 1) specifies a multiplier of used in special - // ability determination. It seems this uses the star values from ItemPMT, but - // not yet clear exactly in what way. - // TODO: Figure out exactly what this does. Anchor: 80106FEC - /* 01B6 */ parray special_mult; + // This index probability table specifies how common each possible slot + // count is for armor drops. + /* 01CF */ parray armor_slot_count_prob_table; - // This array (indexed by area - 1) specifies the probability that any - // non-rare weapon will have a special ability. - /* 01C0 */ parray special_percent; + // These values specify maximum indexes into another array which is + // generated at runtime. The values here are multiplied by a random float in + // the range [0, n] to look up the value in the secondary array, which is + // what ends up determining the unit type. + // TODO: Figure out and document the exact logic here. Anchor: 80106364 + /* 01D4 */ parray unit_maxes; - // TODO: Figure out exactly how this table is used. Anchor: 80106D34 - /* 01CA */ parray armor_shield_type_index_prob_table; + // This index probability table is indexed by [tool_class][area - 1]. The + // tool class refers to an entry in ItemPMT, which links it to the actual + // item code. + /* 01DE */ parray, 0x1C> tool_class_prob_table; - // This index probability table specifies how common each possible slot count - // is for armor drops. - /* 01CF */ parray armor_slot_count_prob_table; + // This index probability table determines how likely each technique is to + // appear. The table is indexed as [technique_num][area - 1]. + /* 040E */ parray, 0x13> technique_index_prob_table; - // These values specify maximum indexes into another array which is generated - // at runtime. The values here are multiplied by a random float in the range - // [0, n] to look up the value in the secondary array, which is what ends up - // determining the unit type. - // TODO: Figure out and document the exact logic here. Anchor: 80106364 - /* 01D4 */ parray unit_maxes; + // This table specifies the ranges for technique disk levels. The table is + // indexed as [technique_num][area - 1]. If either min or max in the range + // is 0xFF, or if max < min, technique disks are not dropped for that + // technique and area pair. + /* 04CC */ parray, 0x0A>, 0x13> technique_level_ranges; - // This index probability table is indexed by [tool_class][area - 1]. The tool - // class refers to an entry in ItemPMT, which links it to the actual item - // code. - /* 01DE */ parray, 0x1C> tool_class_prob_table; + // Each byte in this table (indexed by enemy_type) represents the percent + // chance that the enemy drops anything at all. (This check is done after + // the rare drop check, so it only applies to non-rare items.) + /* 0648 */ parray enemy_type_drop_probs; - // This index probability table determines how likely each technique is to - // appear. The table is indexed as [technique_num][area - 1]. - /* 040E */ parray 0x13> technique_index_prob_table; + // This array (indexed by enemy_id) specifies the range of meseta values + // that each enemy can drop. + /* 06AC */ parray, 0x64> enemy_meseta_ranges; - // This table specifies the ranges for technique disk levels. The table is - // indexed as [technique_num][area - 1]. If either min or max in the range is - // 0xFF, or if max < min, technique disks are not dropped for that technique - // and area pair. - /* 04CC */ parray, 0x0A>, 0x13> technique_level_ranges; + // Each byte in this table (indexed by enemy_type) represents the class of + // item that the enemy can drop. The values are: + // 00 = weapon + // 01 = armor + // 02 = shield + // 03 = unit + // 04 = tool + // 05 = meseta + // Anything else = no item + /* 083C */ parray enemy_item_classes; - // Each byte in this table (indexed by enemy_type) represents the percent - // chance that the enemy drops anything at all. (This check is done after the - // rare drop check, so it only applies to non-rare items.) - /* 0648 */ parray enemy_type_drop_probs; + // This table (indexed by area - 1) specifies the ranges of meseta values + // that can drop from boxes. + /* 08A0 */ parray, 0x0A> box_meseta_ranges; - // This array (indexed by enemy_id) specifies the range of meseta values that - // each enemy can drop. - /* 06AC */ parray, 0x64> enemy_meseta_ranges; + // This index probability table determines which type of items drop from + // boxes. The table is indexed as [item_class][area - 1], with item_class as + // the result value (that is, in the example below, the game looks at a + // single column and sums the values going down, then the chosen item class + // is one of the row indexes based on the weight values in the column.) The + // resulting item_class value has the same meaning as in enemy_item_classes + // above. + // For example, this array might look like the following: + // [07 07 08 08 06 07 08 09 09 0A] // Chances per area of a weapon drop + // [02 02 02 02 03 02 02 02 03 03] // Chances per area of an armor drop + // [02 02 02 02 03 02 02 02 03 03] // Chances per area of a shield drop + // [00 00 03 03 03 04 03 04 05 05] // Chances per area of a unit drop + // [11 11 12 12 12 12 12 12 12 12] // Chances per area of a tool drop + // [32 32 32 32 32 32 32 32 32 32] // Chances per area of a meseta drop + // [16 16 11 11 11 11 11 0F 0C 0B] // Chances per area of an empty box + // F1 F2 C1 C2 C3 M1 M2 R1 R2 R3 // (Episode 1 areas, for reference) + /* 08C8 */ parray, 7> box_item_class_prob_tables; - // Each byte in this table (indexed by enemy_type) represents the class of - // item that the enemy can drop. The values are: - // 00 = weapon - // 01 = armor - // 02 = shield - // 03 = unit - // 04 = tool - // 05 = meseta - // Anything else = no item - /* 083C */ parray enemy_item_classes; + /* 090E */ parray unused1; - // This table (indexed by area - 1) specifies the ranges of meseta values that - // can drop from boxes. - /* 08A0 */ parray, 0x0A> box_meseta_ranges; + /* 0910 */ U32T base_weapon_type_prob_table_offset; + /* 0914 */ U32T subtype_base_table_offset; + /* 0918 */ U32T subtype_area_length_table_offset; + /* 091C */ U32T grind_prob_tables_offset; + /* 0920 */ U32T armor_shield_type_index_prob_table_offset; + /* 0924 */ U32T armor_slot_count_prob_table_offset; + /* 0928 */ U32T enemy_meseta_ranges_offset; + /* 092C */ U32T enemy_type_drop_probs_offset; + /* 0930 */ U32T enemy_item_classes_offset; + /* 0934 */ U32T box_meseta_ranges_offset; + /* 0938 */ U32T bonus_value_prob_tables_offset; + /* 093C */ U32T nonrare_bonus_prob_spec_offset; + /* 0940 */ U32T bonus_type_prob_tables_offset; + /* 0944 */ U32T special_mult_offset; + /* 0948 */ U32T special_percent_offset; + /* 094C */ U32T tool_class_prob_table_offset; + /* 0950 */ U32T technique_index_prob_table_offset; + /* 0954 */ U32T technique_level_ranges_offset; + /* 0958 */ uint8_t armor_or_shield_type_bias; + /* 0959 */ parray unused2; + /* 095C */ U32T unit_maxes_offset; + /* 0960 */ U32T box_item_class_prob_tables_offset; + /* 0964 */ U32T unused_offset2; + /* 0968 */ U32T unused_offset3; + /* 096C */ U32T unused_offset4; + /* 0970 */ U32T unused_offset5; + /* 0974 */ U32T unused_offset6; + /* 0978 */ U32T unused_offset7; + /* 097C */ U32T unused_offset8; + /* 0980 */ U16T unknown_f1[0x20]; + /* 09C0 */ U32T unknown_f1_offset; + /* 09C4 */ U32T unknown_f2[3]; + /* 09D0 */ U32T offset_table_offset; + /* 09D4 */ U32T unknown_f3[3]; + /* 09E0 (end of structure) */ - // This index probability table determines which type of items drop from - // boxes. The table is indexed as [item_class][area - 1], with item_class as - // the result value (that is, in the example below, the game looks at a single - // column and sums the values going down, then the chosen item class is one of - // the row indexes based on the weight values in the column.) The resulting - // item_class value has the same meaning as in enemy_item_classes above. - // For example, this array might look like the following: - // [07 07 08 08 06 07 08 09 09 0A] // Chances per area of a weapon drop - // [02 02 02 02 03 02 02 02 03 03] // Chances per area of an armor drop - // [02 02 02 02 03 02 02 02 03 03] // Chances per area of a shield drop - // [00 00 03 03 03 04 03 04 05 05] // Chances per area of a unit drop - // [11 11 12 12 12 12 12 12 12 12] // Chances per area of a tool drop - // [32 32 32 32 32 32 32 32 32 32] // Chances per area of a meseta drop - // [16 16 11 11 11 11 11 0F 0C 0B] // Chances per area of an empty box - // F1 F2 C1 C2 C3 M1 M2 R1 R2 R3 // (Episode 1 areas, for reference) - /* 08C8 */ parray, 7> box_item_class_prob_tables; + void print(FILE* stream) const; + } __attribute__((packed)); - /* 0910 */ U32T offset_table[0x1C]; - /* 0980 */ U16T unknown_f1[0x20]; - /* 09C0 */ U32T unknown_f1_offset; - /* 09C4 */ U32T unknown_f2[3]; - /* 09D0 */ U32T offset_table_offset; - /* 09D4 */ U32T unknown_f3[3]; - /* 09E0 (end of structure) */ -} __attribute__((packed)); + CommonItemSet(std::shared_ptr data); + + using BETable = Table; + using LETable = Table; + + const BETable& get_table( + Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) const; + +private: + GSLArchive gsl; +}; + + + +class RELFileSet { +public: + template + struct WeightTableEntry { + ValueT value; + WeightT weight; + } __attribute__((packed)); + + using WeightTableEntry8 = WeightTableEntry; + using WeightTableEntry32 = WeightTableEntry; + +protected: + std::shared_ptr data; + StringReader r; + + struct TableSpec { + be_uint32_t offset; + uint8_t entries_per_table; + parray unused; + } __attribute__((packed)); + + RELFileSet(std::shared_ptr data); + + template + std::pair get_table( + const TableSpec& spec, size_t index) const { + const T* entries = &r.pget( + spec.offset + index * spec.entries_per_table * sizeof(T), + spec.entries_per_table * sizeof(T)); + return make_pair(entries, spec.entries_per_table); + } +}; + +class ArmorRandomSet : public RELFileSet { +public: + // This class parses and accesses data from ArmorRandom.rel + ArmorRandomSet(std::shared_ptr data); + + std::pair get_armor_table(size_t index) const; + std::pair get_shield_table(size_t index) const; + std::pair get_unit_table(size_t index) const; + +private: + const parray* tables; +}; + +class ToolRandomSet : public RELFileSet { +public: + // This class parses and accesses data from ToolRandom.rel + ToolRandomSet(std::shared_ptr data); + + struct TechDiskLevelEntry { + enum class Mode : uint8_t { + LEVEL_1 = 0, + PLAYER_LEVEL_DIVISOR = 1, + RANDOM_IN_RANGE = 2, + }; + Mode mode; + uint8_t player_level_divisor_or_min_level; + uint8_t max_level; + } __attribute__((packed)); + + std::pair get_common_recovery_table(size_t index) const; + std::pair get_rare_recovery_table(size_t index) const; + std::pair get_tech_disk_table(size_t index) const; + std::pair get_tech_disk_level_table(size_t index) const; + +private: + const TableSpec* common_recovery_table_spec; + const TableSpec* rare_recovery_table_spec; + const TableSpec* tech_disk_table_spec; + const TableSpec* tech_disk_level_table_spec; +}; + +class WeaponRandomSet : public RELFileSet { +public: + // This class parses and accesses data from WeaponRandom*.rel + WeaponRandomSet(std::shared_ptr data); + + struct RangeTableEntry { + be_uint32_t min; + be_uint32_t max; + } __attribute__((packed)); + + std::pair get_weapon_type_table(size_t index) const; + const parray* get_bonus_type_table(size_t which, size_t index) const; + const RangeTableEntry* get_bonus_range(size_t which, size_t index) const; + const parray* get_special_mode_table(size_t index) const; + const RangeTableEntry* get_standard_grind_range(size_t index) const; + const RangeTableEntry* get_favored_grind_range(size_t index) const; + +private: + struct Offsets { + be_uint32_t weapon_type_table; // [{c, o -> (table)}](10) + be_uint32_t bonus_type_table1; // [[{u32 value, u32 weight}](6)](9) + be_uint32_t bonus_type_table2; // [[{u32 value, u32 weight}](6)](9) + be_uint32_t bonus_range_table1; // [{u32 min_index, u32 max_index}](9) + be_uint32_t bonus_range_table2; // [{u32 min_index, u32 max_index}](9) + be_uint32_t special_mode_table; // [[{u32 value, u32 weight}](3)](8) + be_uint32_t standard_grind_range_table; // [{u32 min, u32 max}](6) + be_uint32_t favored_grind_range_table; // [{u32 min, u32 max}](6) + } __attribute__((packed)); + + const Offsets* offsets; +}; diff --git a/src/ItemCreator.cc b/src/ItemCreator.cc new file mode 100644 index 00000000..5c5c561c --- /dev/null +++ b/src/ItemCreator.cc @@ -0,0 +1,1598 @@ +#include "ItemCreator.hh" + +#include +#include + +using namespace std; + + + +ItemCreator::ItemCreator( + shared_ptr common_item_set, + shared_ptr rare_item_set, + shared_ptr armor_random_set, + shared_ptr tool_random_set, + shared_ptr weapon_random_set, + shared_ptr item_parameter_table, + Episode episode, + GameMode mode, + uint8_t difficulty, + uint8_t section_id, + uint32_t random_seed, + shared_ptr restrictions) + : log("[ItemCreator] "), + episode(episode), + mode(mode), + difficulty(difficulty), + section_id(section_id), + common_item_set(common_item_set), + rare_item_set(rare_item_set), + armor_random_set(armor_random_set), + tool_random_set(tool_random_set), + weapon_random_set(weapon_random_set), + item_parameter_table(item_parameter_table), + pt(&this->common_item_set->get_table( + this->episode, this->mode, this->difficulty, this->section_id)), + rt(&this->rare_item_set->get_table( + this->episode, this->mode, this->difficulty, this->section_id)), + restrictions(restrictions), + random_crypt(random_seed) { + print_data(stderr, this->pt, sizeof(*this->pt)); +} + + + +bool ItemCreator::are_rare_drops_allowed() const { + // Note: The client has an additional check here, which appears to be a subtle + // anti-cheating measure. There is a flag on the client, initially zero, which + // is set to 1 when certain unexpected item-related things happen (for + // example, a player possessing a mag with a level above 200). When the flag + // is set, this function returns false, which prevents all rare item drops. + // newserv intentionally does not implement this flag. + return (this->mode != GameMode::CHALLENGE); +} + +uint8_t ItemCreator::normalize_area_number(uint8_t area) const { + if (!this->item_drop_sub || (area < 0x10) || (area > 0x11)) { + switch (this->episode) { + case Episode::EP1: + if (area >= 15) { + throw runtime_error("invalid Episode 1 area number"); + } + switch (area) { + case 11: + return 3; // Dragon -> Cave 1 + case 12: + return 6; // De Rol Le -> Mine 1 + case 13: + return 8; // Vol Opt -> Ruins 1 + case 14: + return 10; // Dark Falz -> Ruins 3 + default: + return area; + } + throw logic_error("this should be impossible"); + case Episode::EP2: { + static const vector area_subs = { + 0x01, // 13 (VR Temple Alpha) + 0x02, // 14 (VR Temple Beta) + 0x03, // 15 (VR Spaceship Alpha) + 0x04, // 16 (VR Spaceship Beta) + 0x08, // 17 (Central Control Area) + 0x05, // 18 (Jungle North) + 0x06, // 19 (Jungle South) + 0x07, // 1A (Mountain) + 0x08, // 1B (Seaside) + 0x09, // 1C (Seabed Upper) + 0x0A, // 1D (Seabed Lower) + 0x09, // 1E (Gal Gryphon) + 0x0A, // 1F (Olga Flow) + 0x03, // 20 (Barba Ray) + 0x05, // 21 (Gol Dragon) + 0x08, // 22 (Seaside Night) + 0x0A, // 23 (Tower) + }; + if ((area >= 0x13) && (area < 0x24)) { + return area_subs.at(area - 0x13); + } + return area; + } + case Episode::EP4: + // TODO: Figure out remaps for Episode 4 (if there are any) + return area; + default: + throw logic_error("invalid episode number"); + } + + } else { + return this->item_drop_sub->override_area; + } +} + + + +ItemData ItemCreator::on_box_item_drop(uint8_t area) { + return this->on_box_item_drop_with_norm_area(normalize_area_number(area) - 1); +} + +ItemData ItemCreator::on_monster_item_drop(uint32_t enemy_type, uint8_t area) { + return this->on_monster_item_drop_with_norm_area( + enemy_type, normalize_area_number(area) - 1); +} + +ItemData ItemCreator::on_box_item_drop_with_norm_area(uint8_t area_norm) { + ItemData item = this->check_rare_specs_and_create_rare_box_item(area_norm); + if (item.empty()) { + uint8_t item_class = this->get_rand_from_weighted_tables_2d_vertical( + this->pt->box_item_class_prob_tables, area_norm); + switch (item_class) { + case 0: // Weapon + item.data1[0] = 0; + break; + case 1: // Armor + item.data1[0] = 1; + item.data1[1] = 1; + break; + case 2: // Shield + item.data1[0] = 1; + item.data1[1] = 2; + break; + case 3: // Unit + item.data1[0] = 1; + item.data1[1] = 3; + break; + case 4: // Tool + item.data1[0] = 3; + break; + case 5: // Meseta + item.data1[0] = 4; + break; + case 6: // Nothing + break; + default: + throw logic_error("this should be impossible"); + } + this->generate_common_item_variances(area_norm, item); + } + return item; +} + +ItemData ItemCreator::on_monster_item_drop_with_norm_area( + uint32_t enemy_type, uint8_t norm_area) { + if (enemy_type > 0x58) { + this->log.info("Invalid enemy type: %" PRIX32, enemy_type); + return ItemData(); + } + this->log.info("Enemy type: %" PRIX32, enemy_type); + + uint8_t type_drop_prob = this->pt->enemy_type_drop_probs[enemy_type]; + uint8_t drop_sample = this->rand_int(100); + if (drop_sample >= type_drop_prob) { + this->log.info("Drop not chosen (%hhu vs. %hhu)", drop_sample, type_drop_prob); + return ItemData(); + } + + ItemData item = this->check_rare_spec_and_create_rare_enemy_item(enemy_type); + if (item.empty()) { + uint32_t item_class_determinant = this->should_allow_meseta_drops() + ? this->rand_int(3) : (this->rand_int(2) + 1); + + uint32_t item_class; + switch (item_class_determinant) { + case 0: + item_class = 5; + break; + case 1: + item_class = 4; + break; + case 2: + item_class = this->pt->enemy_item_classes[enemy_type]; + break; + default: + throw logic_error("invalid item class determinant"); + } + + this->log.info("Rare drop not chosen; item class determinant is %" PRIu32 "; item class is %" PRIu32, + item_class_determinant, item_class); + + switch (item_class) { + case 0: // Weapon + item.data1[0] = 0x00; + break; + case 1: // Armor + item.data1w[0] = 0x0101; + break; + case 2: // Shield + item.data1w[0] = 0x0201; + break; + case 3: // Unit + item.data1w[0] = 0x0301; + break; + case 4: // Tool + item.data1[0] = 0x03; + break; + case 5: // Meseta + item.data1[0] = 0x04; + item.data2d = this->choose_meseta_amount( + this->pt->enemy_meseta_ranges, enemy_type) & 0xFFFF; + break; + default: + return item; + } + + if (item.data1[0] != 0x04) { + this->generate_common_item_variances(norm_area, item); + } + } + + return item; +} + +ItemData ItemCreator::check_rare_specs_and_create_rare_box_item( + uint8_t area_norm) { + ItemData item; + if (!this->are_rare_drops_allowed()) { + return item; + } + + for (size_t z = 0; z < this->rt->box_count; z++) { + if (area_norm + 1 == this->rt->box_areas[z]) { + item = this->check_rate_and_create_rare_item(this->rt->box_rares[z]); + if (!item.empty()) { + break; + } + } + } + return item; +} + + + +uint32_t ItemCreator::rand_int(uint64_t max) { + return this->random_crypt.next() % max; +} + +float ItemCreator::rand_float_0_1_from_crypt(size_t which) { + (void)which; + // This lacks some precision, but matches the original implementation. + return (static_cast(this->random_crypt.next() >> 16) / 65536.0); +} + +template +uint32_t ItemCreator::choose_meseta_amount( + const parray, NumRanges> ranges, + size_t table_index) { + uint16_t min = ranges[table_index].min; + uint16_t max = ranges[table_index].max; + + // Note: The original code seems like it has a bug here: it compares to 0xFF + // instead of 0xFFFF (and returns 0xFF if either limit matches 0xFF). + if (((min == 0xFFFF) || (max == 0xFFFF)) || (max < min)) { + return 0xFFFF; + } else if (min != max) { + return this->rand_int((max - min) + 1) + min; + } + return min; +} + +bool ItemCreator::should_allow_meseta_drops() const { + return (this->mode != GameMode::CHALLENGE); +} + +ItemData ItemCreator::check_rare_spec_and_create_rare_enemy_item( + uint32_t enemy_type) { + if (this->are_rare_drops_allowed() && + (enemy_type > 0) && (enemy_type < 0x58)) { + return this->check_rate_and_create_rare_item( + this->rt->monster_rares[enemy_type]); + } else { + return ItemData(); + } +} + +ItemData ItemCreator::check_rate_and_create_rare_item( + const RareItemSet::Table::Drop& drop) { + if (drop.probability == 0) { + return ItemData(); + } + + // Note: The original code uses 0xFFFFFFFF as the maximum here. We use + // 0x100000000 instead, which makes all rare items SLIGHTLY more rare. + if (this->rand_int(0x100000000) >= this->rare_item_set->expand_rate(drop.probability)) { + return ItemData(); + } + + ItemData item; + item.data1[0] = drop.item_code[0]; + item.data1[1] = drop.item_code[1]; + item.data1[2] = drop.item_code[2]; + switch (item.data1[0]) { + case 0: + this->generate_rare_weapon_bonuses(item, this->rand_int(10)); + this->set_item_unidentified_flag_if_challenge(item); + break; + case 1: + this->generate_common_armor_slots_and_bonuses(item); + break; + case 2: + this->generate_common_mag_variances(item); + break; + case 3: + this->clear_tool_item_if_invalid(item); + this->set_tool_item_amount_to_1(item); + break; + case 4: + break; + default: + throw logic_error("invalid item class"); + } + + this->clear_item_if_restricted(item); + this->set_item_unidentified_or_present_flags_if_special(item); + return item; +} + +void ItemCreator::generate_rare_weapon_bonuses( + ItemData& item, uint32_t random_sample) { + if (item.data1[0] != 0) { + return; + } + + for (size_t z = 0; z < 6; z += 2) { + uint8_t bonus_type = this->get_rand_from_weighted_tables_2d_vertical( + this->pt->bonus_type_prob_tables, random_sample); + int16_t bonus_value = this->get_rand_from_weighted_tables_2d_vertical( + this->pt->bonus_value_prob_tables, 5); + item.data1[z + 6] = bonus_type; + item.data1[z + 7] = bonus_value * 5 - 10; + // Note: The original code has a special case here, which divides + // item.data1[z + 7] by 5 and multiplies it by 5 again if bonus_type is 5 + // (Hit). Why this is done is unclear, because item.data1[z + 7] must + // already be a multiple of 5. + } + + this->deduplicate_weapon_bonuses(item); +} + +void ItemCreator::generate_common_weapon_bonuses( + ItemData& item, uint8_t area_norm) { + if (item.data1[0] == 0) { + for (size_t row = 0; row < 3; row++) { + uint8_t spec = this->pt->nonrare_bonus_prob_spec[row][area_norm]; + if (spec != 0xFF) { + item.data1[(row * 2) + 6] = this->get_rand_from_weighted_tables_2d_vertical( + this->pt->bonus_type_prob_tables, area_norm); + int16_t amount = this->get_rand_from_weighted_tables_2d_vertical( + this->pt->bonus_value_prob_tables, spec); + item.data1[(row * 2) + 7] = amount * 5 - 10; + } + // Note: The original code has a special case here, which divides + // item.data1[z + 7] by 5 and multiplies it by 5 again if bonus_type is 5 + // (Hit). Why this is done is unclear, because item.data1[z + 7] must + // already be a multiple of 5. + } + this->deduplicate_weapon_bonuses(item); + } +} + +void ItemCreator::deduplicate_weapon_bonuses(ItemData& item) const { + for (size_t x = 0; x < 6; x += 2) { + for (size_t y = 0; y < x; y += 2) { + if (item.data1[y + 6] == 0x00) { + item.data1[x + 6] = 0x00; + } else if (item.data1[x + 6] == item.data1[y + 6]) { + item.data1[x + 6] = 0x00; + } + } + if (item.data1[x + 6] == 0x00) { + item.data1[x + 7] = 0x00; + } + } +} + +void ItemCreator::set_item_unidentified_or_present_flags_if_special(ItemData& item) const { + if (this->item_parameter_table->is_unsealable_item(item)) { + item.set_unidentified_or_present_flag(0); + } +} + +void ItemCreator::set_item_unidentified_flag_if_challenge(ItemData& item) const { + if ((this->mode == GameMode::CHALLENGE) && + (item.data1[0] == 0x00) && + (this->item_parameter_table->is_item_rare(item) || (item.data1[4] != 0))) { + item.data1[4] |= 0x80; + } +} + + + +void ItemCreator::set_tool_item_amount_to_1(ItemData& item) const { + if (item.data1[0] == 0x03) { + item.set_tool_item_amount(1); + } +} + +void ItemCreator::clear_tool_item_if_invalid(ItemData& item) { + if ((item.data1[1] == 2) && + ((item.data1[2] > 0x1D) || (item.data1[4] > 0x12))) { + item.clear(); + } +} + + + +void ItemCreator::clear_item_if_restricted(ItemData& item) const { + if (this->item_parameter_table->is_item_rare(item) && !this->are_rare_drops_allowed()) { + item.clear(); + this->log.info("Restricted: item is rare, but rares not allowed"); + return; + } + + if (this->mode == GameMode::CHALLENGE) { + // Forbid HP/TP-restoring units and meseta in challenge mode + if (item.data1[0] == 1) { + if ((item.data1[1] == 3) && (item.data1[2] >= 0x33) && (item.data1[2] <= 0x38)) { + this->log.info("Restricted: restore items not allowed in Challenge mode"); + item.clear(); + } + } else if (item.data1[0] == 4) { + this->log.info("Restricted: meseta not allowed in Challenge mode"); + item.clear(); + } + } + + if (this->restrictions) { + switch (item.data1[0]) { + case 0: + case 1: + switch (this->restrictions->weapon_and_armor_mode) { + case Restrictions::WeaponAndArmorMode::NORMAL: + case Restrictions::WeaponAndArmorMode::NORMAL2: + break; + case Restrictions::WeaponAndArmorMode::FORBID_RARES: + if (!this->item_parameter_table->is_item_rare(item)) { + this->log.info("Restricted: rare items not allowed"); + break; + } + [[fallthrough]]; + case Restrictions::WeaponAndArmorMode::FORBID_ALL: + this->log.info("Restricted: weapons and armors not allowed"); + item.clear(); + break; + default: + throw logic_error("invalid weapon and armor mode"); + } + break; + case 2: + if (this->restrictions->forbid_mags) { + this->log.info("Restricted: mags not allowed"); + item.clear(); + } + break; + case 3: + if (this->restrictions->tool_mode == Restrictions::ToolMode::FORBID_ALL) { + this->log.info("Restricted: tools not allowed"); + item.clear(); + } else if (item.data1[1] == 2) { + switch (this->restrictions->tech_disk_mode) { + case Restrictions::TechDiskMode::NORMAL: + break; + case Restrictions::TechDiskMode::FORBID_ALL: + this->log.info("Restricted: tech disks not allowed"); + item.clear(); + break; + case Restrictions::TechDiskMode::LIMIT_LEVEL: + this->log.info("Restricted: tech disk level limited to %hhu", + static_cast(this->restrictions->max_tech_disk_level + 1)); + if (this->restrictions->max_tech_disk_level == 0) { + item.data1[2] = 0; + } else { + item.data1[2] %= this->restrictions->max_tech_disk_level; + } + break; + default: + throw logic_error("invalid tech disk mode"); + } + } else if ((item.data1[1] == 9) && this->restrictions->forbid_scape_dolls) { + this->log.info("Restricted: scape dolls not allowed"); + item.clear(); + } + break; + case 4: + if (this->restrictions->meseta_drop_mode == Restrictions::MesetaDropMode::FORBID_ALL) { + this->log.info("Restricted: meseta not allowed"); + item.clear(); + } + break; + default: + throw logic_error("invalid item"); + } + } +} + + + + + +void ItemCreator::generate_common_item_variances( + uint32_t norm_area, ItemData& item) { + switch (item.data1[0]) { + case 0: + this->generate_common_weapon_variances(norm_area, item); + break; + case 1: + if (item.data1[1] == 3) { + float f1 = 1.0 + this->pt->unit_maxes[norm_area]; + float f2 = this->rand_float_0_1_from_crypt(14); + this->generate_common_unit_variances( + static_cast(f1 * f2) & 0xFF, item); + if (item.data1[2] == 0xFF) { + item.clear(); + } + } else { + this->generate_common_armor_or_shield_type_and_variances( + norm_area, item); + } + break; + case 2: + this->generate_common_mag_variances(item); + break; + case 3: + this->generate_common_tool_variances(norm_area, item); + break; + case 4: + item.data2d = this->choose_meseta_amount( + this->pt->box_meseta_ranges, norm_area) & 0xFFFF; + break; + default: + // Note: The original code does the following here: + // item.clear(); + // item.data1[0] = 0x05; + throw logic_error("invalid item class"); + } + + this->clear_item_if_restricted(item); + this->set_item_unidentified_or_present_flags_if_special(item); +} + +void ItemCreator::generate_common_armor_or_shield_type_and_variances( + char area_norm, ItemData& item) { + this->generate_common_armor_slots_and_bonuses(item); + + uint8_t type = this->get_rand_from_weighted_tables_1d( + this->pt->armor_shield_type_index_prob_table); + item.data1[2] = area_norm + type + this->pt->armor_or_shield_type_bias; + if (item.data1[2] < 3) { + item.data1[2] = 0; + } else { + item.data1[2] -= 3; + } +} + +void ItemCreator::generate_common_armor_slots_and_bonuses(ItemData& item) { + if ((item.data1[0] != 0x01) || (item.data1[1] < 1) || (item.data1[1] > 2)) { + return; + } + + if (item.data1[1] == 1) { + this->generate_common_armor_slot_count(item); + } + + const auto& def = this->item_parameter_table->get_armor_or_shield( + item.data1[1], item.data1[2]); + item.set_armor_or_shield_defense_bonus(def.dfp_range * this->rand_float_0_1_from_crypt(12)); + item.set_common_armor_evasion_bonus(def.evp_range * this->rand_float_0_1_from_crypt(13)); +} + +void ItemCreator::generate_common_armor_slot_count(ItemData& item) { + item.data1[5] = this->get_rand_from_weighted_tables_1d( + this->pt->armor_slot_count_prob_table); +} + + + +void ItemCreator::generate_common_tool_variances( + uint32_t area_norm, ItemData& item) { + item.clear(); + + uint8_t tool_class = this->get_rand_from_weighted_tables_2d_vertical( + this->pt->tool_class_prob_table, area_norm); + if (tool_class == 0x1A) { + tool_class = 0x73; + } + + this->generate_common_tool_type(tool_class, item); + if (item.data1[1] == 0x02) { // Tech disk + item.data1[4] = this->get_rand_from_weighted_tables_2d_vertical( + this->pt->technique_index_prob_table, area_norm); + item.data1[2] = this->generate_tech_disk_level(item.data1[4], area_norm); + this->clear_tool_item_if_invalid(item); + } + this->set_tool_item_amount_to_1(item); +} + +uint8_t ItemCreator::generate_tech_disk_level( + uint32_t tech_num, uint32_t area_norm) { + uint8_t min = this->pt->technique_level_ranges[tech_num][area_norm].min; + uint8_t max = this->pt->technique_level_ranges[tech_num][area_norm].max; + + if (((min == 0xFF) || (max == 0xFF)) || (max < min)) { + return 0xFF; + } else if (min != max) { + return this->rand_int((max - min) + 1) + min; + } + return min; +} + +void ItemCreator::generate_common_tool_type( + uint8_t tool_class, ItemData& item) const { + auto data = this->item_parameter_table->find_tool_by_class(tool_class); + item.data1[0] = 0x03; + item.data1[1] = data.first; + item.data1[2] = data.second; +} + + + +void ItemCreator::generate_common_mag_variances(ItemData& item) const { + if (item.data1[0] == 0x02) { + item.data1[1] = 0x00; + item.assign_mag_stats(ItemMagStats()); + } +} + + + +void ItemCreator::generate_common_weapon_variances( + uint8_t area_norm, ItemData& item) { + item.clear(); + item.data1[0] = 0x00; + + parray weapon_type_prob_table; + weapon_type_prob_table[0] = 0; + memmove( + weapon_type_prob_table.data() + 1, + this->pt->base_weapon_type_prob_table.data(), + 0x0C); + + for (size_t z = 1; z < 13; z++) { + // Technically this should be `if (... < 0)`, but whatever + if ((area_norm + this->pt->subtype_base_table[z - 1]) & 0x80) { + weapon_type_prob_table[z] = 0; + } + } + + item.data1[1] = this->get_rand_from_weighted_tables_1d(weapon_type_prob_table); + if (item.data1[1] == 0) { + item.clear(); + } else { + int8_t subtype_base = this->pt->subtype_base_table[item.data1[1] - 1]; + uint8_t area_length = this->pt->subtype_area_length_table[item.data1[1] - 1]; + if (subtype_base < 0) { + item.data1[2] = (area_norm + subtype_base) / area_length; + this->generate_common_weapon_grind( + item, (area_norm + subtype_base) - (item.data1[2] * area_length)); + } else { + item.data1[2] = subtype_base + (area_norm / area_length); + this->generate_common_weapon_grind( + item, area_norm - (area_norm / area_length) * area_length); + } + this->generate_common_weapon_bonuses(item, area_norm); + this->generate_common_weapon_special(item, area_norm); + this->set_item_unidentified_flag_if_challenge(item); + } + return; +} + +void ItemCreator::generate_common_weapon_grind( + ItemData& item, uint8_t offset_within_subtype_range) { + if (item.data1[0] == 0) { + uint8_t offset = clamp(offset_within_subtype_range, 0, 3); + item.data1[3] = this->get_rand_from_weighted_tables_2d_vertical( + this->pt->grind_prob_tables, offset); + } +} + +void ItemCreator::generate_common_weapon_special( + ItemData& item, uint8_t area_norm) { + if (item.data1[0] != 0) { + return; + } + if (this->item_parameter_table->is_item_rare(item)) { + return; + } + uint8_t special_mult = this->pt->special_mult[area_norm]; + if (special_mult == 0) { + return; + } + if (this->rand_int(100) >= this->pt->special_percent[area_norm]) { + return; + } + item.data1[4] = this->unknown_8011CA54( + special_mult * this->rand_float_0_1_from_crypt(10)); +} + +uint8_t ItemCreator::unknown_8011CA54(uint8_t det) { + if (det < 4) { + static const uint8_t maxes[4] = {8, 10, 11, 11}; + uint8_t det2 = this->rand_int(maxes[det]); + size_t index = 0; + for (size_t z = 1; z < 0x29; z++) { + if (det + 1 == this->unknown_8011C63C(z)) { + if (index == det2) { + return z; + } else { + index++; + } + } + } + } + return 0; +} + +uint8_t ItemCreator::unknown_8011C63C(uint8_t det) const { + if (!(det & 0x3F) || (det & 0x80)) { + return 0; + } + // Note: PSO GC uses 0x1CB here. 0x256 was chosen to point to the same data in + // PSO BB's ItemPMT file. + return this->item_parameter_table->get_item_stars(det + 0x0256); +} + + + +void ItemCreator::generate_unit_weights_tables() { + // Note: This part of the function was originally in a different function, + // since it had another callsite. Unlike the original code, we generate these + // tables only once at construction time, so we've inlined the function here. + size_t z; + for (z = 0; z < 0x10; z++) { + uint8_t v = this->item_parameter_table->get_item_stars(z + 0x37D); + this->unit_weights_table1[(z * 5) + 0] = v - 1; + this->unit_weights_table1[(z * 5) + 1] = v - 1; + this->unit_weights_table1[(z * 5) + 2] = v; + this->unit_weights_table1[(z * 5) + 3] = v + 1; + this->unit_weights_table1[(z * 5) + 4] = v + 1; + } + for (; z < 0x48; z++) { + this->unit_weights_table1[z + 0x50] = + this->item_parameter_table->get_item_stars(z + 0x37D); + } + // Note: Inlining ends here + + this->unit_weights_table2.clear(0); + for (size_t z = 0; z < 0x88; z++) { + uint8_t index = this->unit_weights_table1[z]; + if (index < this->unit_weights_table2.size()) { + this->unit_weights_table2[index]++; + } + z = z + 1; + } +} + +void ItemCreator::generate_common_unit_variances(uint8_t det, ItemData& item) { + if (det >= 0x0D) { + return; + } + item.clear(); + item.data1[0] = 0x01; + item.data1[1] = 0x03; + + // Note: The original code calls generate_unit_weights_table1 here (which we + // have inlined into generate_unit_weights_tables above). This call seems + // unnecessary because the contents of the tables don't depend on anything + // except what appears in ItemPMT, which is essentially constant, so we + // don't bother regenerating the table here. + + if (this->unit_weights_table2[det] == 0) { + return; + } + + size_t which = this->rand_int(this->unit_weights_table2[det]); + size_t current_index = 0; + for (size_t z = 0; z < this->unit_weights_table1.size(); z++) { + if (det != this->unit_weights_table1[z]) { + continue; + } + if (current_index != which) { + current_index++; + } else { + if (z > 0x4F) { + if (det <= 0x87) { + item.data1[2] = z + 0xC0; + } + } else { + item.data1[2] = z / 5; + const auto& def = this->item_parameter_table->get_unit(item.data1[2]); + switch (z % 5) { + case 0: + item.set_item_unit_bonus(-(def.modifier_amount * 2)); + break; + case 1: + item.set_item_unit_bonus(-def.modifier_amount); + break; + case 2: + break; + case 3: + item.set_item_unit_bonus(def.modifier_amount); + break; + case 4: + item.set_item_unit_bonus(def.modifier_amount * 2); + break; + } + } + break; + } + } +} + + + +// Returns a weighted random result, indicating the chosen position in the +// weighted table. +// +// For example, an input table of 40 40 40 40 would be equally likely to return +// 0, 1, 2, or 3. An input table of 40 40 80 would return 2 50% of the time, and +// 0 or 1 each 25% of the time. +template +IntT ItemCreator::get_rand_from_weighted_tables( + const IntT* tables, size_t offset, size_t num_values, size_t stride) { + uint64_t rand_max = 0; + for (size_t x = 0; x != num_values; x++) { + rand_max += tables[x * stride + offset]; + } + if (rand_max == 0) { + throw runtime_error("weighted table is empty"); + } + + uint32_t x = this->rand_int(rand_max); + for (size_t z = 0; z < num_values; z++) { + IntT table_value = tables[z * stride + offset]; + if (x < table_value) { + return z; + } + x -= table_value; + } + throw logic_error("selector was not less than rand_max"); +} + +template +IntT ItemCreator::get_rand_from_weighted_tables_1d( + const parray& tables) { + return ItemCreator::get_rand_from_weighted_tables(tables.data(), 0, X, 1); +} + +template +IntT ItemCreator::get_rand_from_weighted_tables_2d_vertical( + const parray, Y>& tables, size_t offset) { + return ItemCreator::get_rand_from_weighted_tables(tables[0].data(), + offset, Y, X); +} + + + +// Note: There are clearly better ways of doing this, but this implementation +// closely follows what the original code in the client does. +template +struct ProbabilityTable { + ItemT items[MaxCount]; + size_t count; + + ProbabilityTable() : count(0) { } + + void push(ItemT item) { + if (this->count == MaxCount) { + throw runtime_error("push to full probability table"); + } + this->items[this->count++] = item; + } + + ItemT pop() { + if (this->count == 0) { + throw runtime_error("pop from empty probability table"); + } + return this->items[--this->count]; + } + + void shuffle(PSOLFGEncryption& random_crypt) { + for (size_t z = 1; z < this->count; z++) { + size_t other_z = random_crypt.next() % (z + 1); + ItemT t = this->items[z]; + this->items[z] = this->items[other_z]; + this->items[other_z] = t; + } + } +}; + +vector ItemCreator::generate_armor_shop_contents(size_t player_level) { + vector shop; + this->generate_armor_shop_armors(shop, player_level); + this->generate_armor_shop_shields(shop, player_level); + this->generate_armor_shop_units(shop, player_level); + return shop; +} + +size_t ItemCreator::get_table_index_for_armor_shop( + size_t player_level) { + if (player_level < 11) { + return 0; + } else if (player_level < 26) { + return 1; + } else if (player_level < 43) { + return 2; + } else if (player_level < 61) { + return 3; + } else { + return 4; + } +} + +bool ItemCreator::shop_does_not_contain_duplicate_armor( + const vector& shop, const ItemData& item) { + for (const auto& shop_item : shop) { + if ((shop_item.data1[0] == item.data1[0]) && + (shop_item.data1[1] == item.data1[1]) && + (shop_item.data1[2] == item.data1[2]) && + (shop_item.data1[5] == item.data1[5])) { + return false; + } + } + return true; +} + +bool ItemCreator::shop_does_not_contain_duplicate_tech_disk( + const vector& shop, const ItemData& item) { + for (const auto& shop_item : shop) { + if ((shop_item.data1[0] == item.data1[0]) && + (shop_item.data1[1] == item.data1[1]) && + (shop_item.data1[2] == item.data1[2]) && + (shop_item.data1[4] == item.data1[4])) { + return false; + } + } + return true; +} + +bool ItemCreator::shop_does_not_contain_duplicate_or_too_many_similar_weapons( + const vector& shop, const ItemData& item) { + size_t similar_items = 0; + for (const auto& shop_item : shop) { + // Disallow exact matches + if (shop_item == item) { + return false; + } + + if ((shop_item.data1[0] == item.data1[0]) && + (shop_item.data1[1] == item.data1[1])) { + similar_items++; + if (similar_items >= 2) { + return false; + } + } + } + return true; +} + +bool ItemCreator::shop_does_not_contain_duplicate_item_by_primary_identifier( + const vector& shop, const ItemData& item) { + for (const auto& shop_item : shop) { + if ((shop_item.data1[0] == item.data1[0]) && + (shop_item.data1[1] == item.data1[1]) && + (shop_item.data1[2] == item.data1[2])) { + return false; + } + } + return true; +} + +void ItemCreator::generate_armor_shop_armors( + vector& shop, size_t player_level) { + size_t num_items; + if (player_level < 11) { + num_items = 4; + } else if (player_level < 26) { + num_items = 6; + } else { + // Note: The original code has another case here that can result in 8 items, + // but that overflows BB's shop item list command, so we omit it here. + num_items = 7; + } + size_t table_index = this->get_table_index_for_armor_shop(player_level); + + ProbabilityTable pt; + auto src_table = this->armor_random_set->get_armor_table(table_index); + for (size_t z = 0; z < src_table.second; z++) { + for (size_t y = 0; y < src_table.first[z].weight; y++) { + pt.push(src_table.first[z].value); + } + } + pt.shuffle(this->random_crypt); + + for (size_t items_generated = 0; items_generated < num_items; ) { + ItemData item; + item.data1[0] = 1; + item.data1[1] = 1; + item.data1[2] = pt.pop(); + + if ((this->difficulty == 3) && (player_level > 99)) { + if (player_level > 150) { + item.data1[2] += 3; + } else if (player_level >= 100) { + item.data1[2] += 2; + } + } + + this->generate_common_armor_slot_count(item); + if (this->shop_does_not_contain_duplicate_armor(shop, item)) { + shop.emplace_back(move(item)); + items_generated++; + } + } +} + +void ItemCreator::generate_armor_shop_shields( + vector& shop, size_t player_level) { + size_t num_items; + if (player_level < 11) { + num_items = 4; + } else if (player_level < 26) { + num_items = 5; + } else if (player_level < 42) { + num_items = 6; + } else { + num_items = 7; + } + size_t table_index = this->get_table_index_for_armor_shop(player_level); + + ProbabilityTable pt; + auto src_table = this->armor_random_set->get_shield_table(table_index); + for (size_t z = 0; z < src_table.second; z++) { + for (size_t y = 0; y < src_table.first[z].weight; y++) { + pt.push(src_table.first[z].value); + } + } + pt.shuffle(this->random_crypt); + + for (size_t items_generated = 0; items_generated < num_items; ) { + ItemData item; + item.data1[0] = 1; + item.data1[1] = 2; + item.data1[2] = pt.pop(); + + if ((this->difficulty == 3) && (player_level > 99)) { + if (player_level > 150) { + item.data1[2] += 3; + } else if (player_level >= 100) { + item.data1[2] += 2; + } + } + + if (this->shop_does_not_contain_duplicate_item_by_primary_identifier(shop, item)) { + shop.emplace_back(move(item)); + items_generated++; + } + } +} + +void ItemCreator::generate_armor_shop_units( + vector& shop, size_t player_level) { + size_t num_items; + if (player_level < 11) { + return; // num_items = 0 + } else if (player_level < 26) { + num_items = 3; + } else if (player_level < 43) { + num_items = 5; + } else { + num_items = 6; + } + size_t table_index = this->get_table_index_for_armor_shop(player_level); + + ProbabilityTable pt; + auto src_table = this->armor_random_set->get_unit_table(table_index); + for (size_t z = 0; z < src_table.second; z++) { + for (size_t y = 0; y < src_table.first[z].weight; y++) { + pt.push(src_table.first[z].value); + } + } + pt.shuffle(this->random_crypt); + + for (size_t items_generated = 0; items_generated < num_items; ) { + ItemData item; + item.data1[0] = 1; + item.data1[1] = 3; + item.data1[2] = pt.pop(); + if (this->shop_does_not_contain_duplicate_item_by_primary_identifier(shop, item)) { + shop.emplace_back(move(item)); + items_generated++; + } + } +} + + + +vector ItemCreator::generate_tool_shop_contents(size_t player_level) { + vector shop; + this->generate_common_tool_shop_recovery_items(shop, player_level); + this->generate_rare_tool_shop_recovery_items(shop, player_level); + this->generate_tool_shop_tech_disks(shop, player_level); + sort(shop.begin(), shop.end(), ItemData::compare_for_sort); + return shop; +} + +size_t ItemCreator::get_table_index_for_tool_shop(size_t player_level) { + if (player_level < 11) { + return 0; + } else if (player_level < 26) { + return 1; + } else if (player_level < 43) { + return 2; + } else if (player_level < 61) { + return 3; + } else { + return 4; + } +} + +static const vector> tool_item_defs({ + {0x00, 0x00}, + {0x00, 0x01}, + {0x00, 0x02}, + {0x01, 0x00}, + {0x01, 0x01}, + {0x01, 0x02}, + {0x06, 0x00}, + {0x06, 0x01}, + {0x03, 0x00}, + {0x04, 0x00}, + {0x05, 0x00}, + {0x07, 0x00}, + {0x08, 0x00}, + {0x09, 0x00}, + {0x0A, 0x00}, + {0xFF, 0xFF}, +}); + +void ItemCreator::generate_common_tool_shop_recovery_items( + vector& shop, size_t player_level) { + size_t table_index; + if (player_level < 11) { + table_index = 0; + } else if (player_level < 26) { + table_index = 1; + } else if (player_level < 45) { + table_index = 2; + } else if (player_level < 61) { + table_index = 3; + } else if (player_level < 100) { + table_index = 4; + } else { + table_index = 5; + } + + auto table = this->tool_random_set->get_common_recovery_table(table_index); + for (size_t z = 0; z < table.second; z++) { + uint8_t type = table.first[z]; + if (type == 0x0F) { + continue; + } + + auto& item = shop.emplace_back(); + item.data1[0] = 3; + item.data1[1] = tool_item_defs[type].first; + item.data1[2] = tool_item_defs[type].second; + } +} + +void ItemCreator::generate_rare_tool_shop_recovery_items( + vector& shop, size_t player_level) { + if (player_level < 11) { + return; + } + static constexpr size_t num_items = 2; + + ProbabilityTable pt; + size_t table_index = this->get_table_index_for_tool_shop(player_level); + auto table = this->tool_random_set->get_rare_recovery_table(table_index); + for (size_t z = 0; z < table.second; z++) { + const auto& e = table.first[z]; + for (size_t y = 0; y < e.weight; y++) { + pt.push(e.value); + } + } + pt.shuffle(this->random_crypt); + + size_t effective_num_items = num_items; + size_t items_generated = 0; + while (items_generated < effective_num_items) { + uint8_t type = pt.pop(); + if (type == 0x0F) { + if (effective_num_items == num_items) { + effective_num_items--; + } + } else { + ItemData item; + item.data1[0] = 3; + item.data1[1] = tool_item_defs[type].first; + item.data1[2] = tool_item_defs[type].second; + if (this->shop_does_not_contain_duplicate_item_by_primary_identifier(shop, item)) { + shop.emplace_back(move(item)); + items_generated++; + } + } + } +} + +void ItemCreator::generate_tool_shop_tech_disks( + vector& shop, size_t player_level) { + size_t num_items; + if (player_level < 11) { + num_items = 4; + } else if (player_level < 43) { + num_items = 5; + } else { + num_items = 7; + } + + size_t table_index = this->get_table_index_for_tool_shop(player_level); + auto table = this->tool_random_set->get_tech_disk_table(table_index); + + ProbabilityTable pt; + for (size_t z = 0; z < table.second; z++) { + const auto& e = table.first[z]; + for (size_t y = 0; y < e.weight; y++) { + pt.push(e.value); + } + } + pt.shuffle(this->random_crypt); + + static const array tech_num_map = { + 0x00, 0x03, 0x06, 0x0F, 0x10, 0x0D, 0x0A, 0x0B, 0x0C, 0x01, 0x04, 0x07, + 0x0E, 0x11, 0x02, 0x05, 0x08, 0x09, 0x12}; + + size_t items_generated = 0; + while (items_generated < num_items) { + uint8_t tech_num_index = pt.pop(); + ItemData item; + item.data1[0] = 3; + item.data1[1] = 2; + item.data1[4] = tech_num_map.at(tech_num_index); + this->choose_tech_disk_level_for_tool_shop( + item, player_level, tech_num_index); + if (this->shop_does_not_contain_duplicate_tech_disk(shop, item)) { + shop.emplace_back(move(item)); + items_generated++; + } + } +} + +void ItemCreator::choose_tech_disk_level_for_tool_shop( + ItemData& item, size_t player_level, uint8_t tech_num_index) { + size_t table_index = this->get_table_index_for_tool_shop(player_level); + auto table = this->tool_random_set->get_tech_disk_level_table(table_index); + if (tech_num_index >= table.second) { + throw runtime_error("technique number out of range"); + } + const auto& e = table.first[tech_num_index]; + + switch (e.mode) { + case ToolRandomSet::TechDiskLevelEntry::Mode::LEVEL_1: + item.data1[2] = 0; + break; + case ToolRandomSet::TechDiskLevelEntry::Mode::PLAYER_LEVEL_DIVISOR: + item.data1[2] = clamp( + (min(player_level, 99) / e.player_level_divisor_or_min_level) - 1, 0, 14); + break; + case ToolRandomSet::TechDiskLevelEntry::Mode::RANDOM_IN_RANGE: { + // Note: This logic does not give a uniform distribution - if the minimum + // level is not zero (level 1), then the minimum level is more likely than + // all the other levels. This behavior matches the client's logic, though + // it's unclear if this nonuniformity was intentional. + int16_t min_level = max(e.player_level_divisor_or_min_level - 1, 0); + item.data1[2] = clamp(this->rand_int(e.max_level), min_level, 14); + break; + } + default: + throw logic_error("invalid tech disk level mode"); + } +} + + + +vector ItemCreator::generate_weapon_shop_contents(size_t player_level) { + size_t num_items; + if (player_level < 11) { + num_items = 10; + } else if (player_level < 43) { + num_items = 12; + } else { + num_items = 16; + } + + size_t table_index; + if (this->difficulty == 3) { + if (player_level < 11) { + table_index = 0; + } else if (player_level < 26) { + table_index = 1; + } else if (player_level < 43) { + table_index = 2; + } else if (player_level < 61) { + table_index = 3; + } else if (player_level < 100) { + table_index = 4; + } else if (player_level < 151) { + table_index = 5; + } else { + table_index = 6; + } + } else { + if (player_level < 11) { + table_index = 0; + } else if (player_level < 26) { + table_index = 1; + } else if (player_level < 43) { + table_index = 2; + } else if (player_level < 61) { + table_index = 3; + } else if (player_level < 101) { + table_index = 4; + } else { + table_index = 4; + } + } + + ProbabilityTable pt; + auto table = this->weapon_random_set->get_weapon_type_table(table_index); + for (size_t z = 0; z < table.second; z++) { + const auto& e = table.first[z]; + for (size_t y = 0; y < e.weight; y++) { + pt.push(e.value); + } + } + pt.shuffle(this->random_crypt); + + vector shop; + while (shop.size() < num_items) { + ItemData item; + + uint8_t which = pt.pop(); + if (which == 0x39) { + static const vector> defs({ + {0x28, 0x00}, {0x2A, 0x00}, {0x2B, 0x00}, {0x35, 0x00}, {0x52, 0x00}, + {0x48, 0x00}, {0x64, 0x00}, {0x59, 0x00}, {0x8A, 0x00}, {0x99, 0x00}, + }); + const auto& def = defs.at(this->section_id); + item.data1[0] = 0; + item.data1[1] = def.first; + item.data1[2] = def.second; + + } else if (which == 0x3A) { + static const vector> defs({ + {0x99, 0x00}, {0x64, 0x00}, {0x8A, 0x00}, {0x28, 0x00}, {0x59, 0x00}, + {0x2B, 0x00}, {0x52, 0x00}, {0x2A, 0x00}, {0x48, 0x00}, {0x35, 0x00}, + }); + const auto& def = defs.at(this->section_id); + item.data1[0] = 0; + item.data1[1] = def.first; + item.data1[2] = def.second; + + } else { + static const vector> defs({ + {0x01, 0x00}, {0x01, 0x01}, {0x01, 0x02}, {0x01, 0x03}, {0x01, 0x04}, + {0x03, 0x00}, {0x03, 0x01}, {0x03, 0x02}, {0x03, 0x03}, {0x03, 0x04}, + {0x02, 0x00}, {0x02, 0x01}, {0x02, 0x02}, {0x02, 0x03}, {0x02, 0x04}, + {0x05, 0x00}, {0x05, 0x01}, {0x05, 0x02}, {0x05, 0x03}, {0x05, 0x04}, + {0x04, 0x00}, {0x04, 0x01}, {0x04, 0x02}, {0x04, 0x03}, {0x04, 0x04}, + {0x06, 0x00}, {0x06, 0x01}, {0x06, 0x02}, {0x06, 0x03}, {0x06, 0x04}, + {0x07, 0x00}, {0x07, 0x01}, {0x07, 0x02}, {0x07, 0x03}, {0x07, 0x04}, + {0x08, 0x00}, {0x08, 0x01}, {0x08, 0x02}, {0x08, 0x03}, {0x08, 0x04}, + {0x09, 0x00}, {0x09, 0x01}, {0x09, 0x02}, {0x09, 0x03}, {0x09, 0x04}, + {0x0A, 0x00}, {0x0A, 0x01}, {0x0A, 0x02}, {0x0A, 0x03}, {0x0B, 0x00}, + {0x0B, 0x01}, {0x0B, 0x02}, {0x0B, 0x03}, {0x0C, 0x00}, {0x0C, 0x01}, + {0x0C, 0x02}, {0x0C, 0x03}, {0xFF, 0xFF}, {0xFF, 0xFF}, {0x01, 0x05}, + {0x02, 0x05}, {0x06, 0x05}, {0x08, 0x05}, {0x0A, 0x04}, {0x0C, 0x04}, + {0x0B, 0x04}, {0x01, 0x06}, {0x03, 0x05}, {0x07, 0x05}, {0x0A, 0x05}, + {0x0C, 0x05}, {0x0B, 0x05}, + }); + const auto& def = defs.at(which); + item.data1[0] = 0; + item.data1[1] = def.first; + item.data1[2] = def.second; + } + + this->generate_weapon_shop_item_grind(item, player_level); + this->generate_weapon_shop_item_special(item, player_level); + this->generate_weapon_shop_item_bonus1(item, player_level); + this->generate_weapon_shop_item_bonus2(item, player_level); + item.data1[10] = 0; + item.data1[11] = 0; + + if (this->shop_does_not_contain_duplicate_or_too_many_similar_weapons(shop, item)) { + shop.emplace_back(move(item)); + } + } + + sort(shop.begin(), shop.end(), ItemData::compare_for_sort); + return shop; +} + +void ItemCreator::generate_weapon_shop_item_grind( + ItemData& item, size_t player_level) { + size_t table_index; + if (player_level < 4) { + table_index = 0; + } else if (player_level < 11) { + table_index = 1; + } else if (player_level < 26) { + table_index = 2; + } else if (player_level < 41) { + table_index = 3; + } else if (player_level < 56) { + table_index = 4; + } else { + table_index = 5; + } + + static const array favored_weapon_by_section_id = { + 0x09, 0x07, 0x02, 0x04, 0x08, 0x0A, 0xFF, 0x03, 0xFF, 0x05}; + + uint8_t favored_weapon = favored_weapon_by_section_id.at(this->section_id); + bool is_favored = (favored_weapon != 0xFF) && (item.data1[1] == favored_weapon); + const auto* range = is_favored + ? this->weapon_random_set->get_favored_grind_range(table_index) + : this->weapon_random_set->get_standard_grind_range(table_index); + + const auto& weapon_def = this->item_parameter_table->get_weapon( + item.data1[1], item.data1[2]); + item.data1[3] = clamp( + this->rand_int(range->max + 1), range->min, weapon_def.max_grind); +} + +void ItemCreator::generate_weapon_shop_item_special( + ItemData& item, size_t player_level) { + ProbabilityTable pt; + + size_t table_index; + if (player_level < 11) { + table_index = 0; + } else if (player_level < 18) { + table_index = 1; + } else if (player_level < 26) { + table_index = 2; + } else if (player_level < 36) { + table_index = 3; + } else if (player_level < 46) { + table_index = 4; + } else if (player_level < 61) { + table_index = 5; + } else if (player_level < 76) { + table_index = 6; + } else { + table_index = 7; + } + + const auto* table = this->weapon_random_set->get_special_mode_table(table_index); + for (size_t z = 0; z < table->size(); z++) { + const auto& e = table->at(z); + for (size_t y = 0; y < e.weight; y++) { + pt.push(e.value); + } + } + pt.shuffle(this->random_crypt); + + // TODO: mode should be an enum class, but I'm too lazy to make the types work + // out in the intermediate structures (WeightTableEntry and ProbabilityTable) + uint8_t mode = pt.pop(); + switch (mode) { + case 0: + item.data1[4] = 0; + break; + case 1: + item.data1[4] = this->unknown_8011CA54(0); + break; + case 2: + item.data1[4] = this->unknown_8011CA54(1); + break; + default: + throw runtime_error("invalid special mode"); + } +} + +static const array bonus_values = { + -50, -45, -40, -35, -30, -25, -20, -15, -10, -5, + 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, +}; + +void ItemCreator::generate_weapon_shop_item_bonus1( + ItemData& item, size_t player_level) { + size_t table_index; + if (player_level < 4) { + table_index = 0; + } else if (player_level < 11) { + table_index = 1; + } else if (player_level < 18) { + table_index = 2; + } else if (player_level < 26) { + table_index = 3; + } else if (player_level < 36) { + table_index = 4; + } else if (player_level < 46) { + table_index = 5; + } else if (player_level < 61) { + table_index = 6; + } else if (player_level < 76) { + table_index = 7; + } else { + table_index = 8; + } + + const auto* type_table = this->weapon_random_set->get_bonus_type_table( + 0, table_index); + ProbabilityTable pt; + for (size_t z = 0; z < type_table->size(); z++) { + const auto& e = type_table->at(z); + for (size_t y = 0; y < e.weight; y++) { + pt.push(e.value); + } + } + pt.shuffle(this->random_crypt); + + item.data1[6] = pt.pop(); + if (item.data1[6] == 0) { + item.data1[7] = 0; + + } else { + const auto* range = this->weapon_random_set->get_bonus_range(0, table_index); + item.data1[7] = bonus_values.at(max( + this->rand_int(range->max + 1), range->min)); + } +} + +void ItemCreator::generate_weapon_shop_item_bonus2( + ItemData& item, size_t player_level) { + size_t table_index; + if (player_level < 6) { + table_index = 0; + } else if (player_level < 11) { + table_index = 1; + } else if (player_level < 18) { + table_index = 2; + } else if (player_level < 26) { + table_index = 3; + } else if (player_level < 36) { + table_index = 4; + } else if (player_level < 46) { + table_index = 5; + } else if (player_level < 61) { + table_index = 6; + } else if (player_level < 76) { + table_index = 7; + } else { + table_index = 8; + } + + const auto* type_table = this->weapon_random_set->get_bonus_type_table( + 1, table_index); + ProbabilityTable pt; + for (size_t z = 0; z < type_table->size(); z++) { + const auto& e = type_table->at(z); + for (size_t y = 0; y < e.weight; y++) { + pt.push(e.value); + } + } + pt.shuffle(this->random_crypt); + + do { + item.data1[8] = pt.pop(); + } while ((item.data1[8] != 0) && (item.data1[8] == item.data1[6])); + + if (item.data1[8] == 0) { + item.data1[9] = 0; + + } else { + const auto* range = this->weapon_random_set->get_bonus_range(1, table_index); + item.data1[9] = bonus_values.at(max( + this->rand_int(range->max + 1), range->min)); + } +} diff --git a/src/ItemCreator.hh b/src/ItemCreator.hh new file mode 100644 index 00000000..1cf41f2b --- /dev/null +++ b/src/ItemCreator.hh @@ -0,0 +1,193 @@ +#pragma once + +#include + +#include "CommonItemSet.hh" +#include "RareItemSet.hh" +#include "ItemParameterTable.hh" +#include "StaticGameData.hh" +#include "PSOEncryption.hh" + + + +struct ItemDropSub { + uint8_t override_area; +}; + +class ItemCreator { +public: + struct Restrictions { + // Note: The original code has a uint8_t enable_item_restrictions here; we + // omit it because the caller can just pass a null pointer for this struct. + // Note: The original code has many more fields in this structure; we only + // include those which are used by the item creator. This structure is + // probably actually the battle rules structure in the original game, but + // many rules apply only client-side and newserv doesn't have to care about + // their effects, so we omit those fields. The NORMAL and NORMAL2 modes + // behave identically from the item creator's perspective, but may actually + // mean "keep equipment" vs. "reset equipment" on the client side. + enum class TechDiskMode { + NORMAL = 0, + FORBID_ALL = 1, + LIMIT_LEVEL = 2, + }; + enum class WeaponAndArmorMode { + NORMAL = 0, + NORMAL2 = 1, + FORBID_ALL = 2, + FORBID_RARES = 3, + }; + enum class ToolMode { + NORMAL = 0, + NORMAL2 = 1, + FORBID_ALL = 2, + }; + enum class MesetaDropMode { + NORMAL = 0, + FORBID_ALL = 1, + UNKNOWN = 2, + }; + TechDiskMode tech_disk_mode; + WeaponAndArmorMode weapon_and_armor_mode; + bool forbid_mags; + ToolMode tool_mode; + MesetaDropMode meseta_drop_mode; + bool forbid_scape_dolls; + uint8_t max_tech_disk_level; // 0xFF = no maximum + }; + + ItemCreator( + std::shared_ptr common_item_set, + std::shared_ptr rare_item_set, + std::shared_ptr armor_random_set, + std::shared_ptr tool_random_set, + std::shared_ptr weapon_random_set, + std::shared_ptr item_parameter_table, + Episode episode, + GameMode mode, + uint8_t difficulty, + uint8_t section_id, + uint32_t random_seed, + std::shared_ptr restrictions = nullptr); + ~ItemCreator() = default; + + ItemData on_monster_item_drop(uint32_t enemy_type, uint8_t area); + ItemData on_box_item_drop(uint8_t area); + + std::vector generate_armor_shop_contents(size_t player_level); + std::vector generate_tool_shop_contents(size_t player_level); + std::vector generate_weapon_shop_contents(size_t player_level); + +private: + PrefixedLogger log; + Episode episode; + GameMode mode; + uint8_t difficulty; + uint8_t section_id; + std::shared_ptr common_item_set; + std::shared_ptr rare_item_set; + std::shared_ptr armor_random_set; + std::shared_ptr tool_random_set; + std::shared_ptr weapon_random_set; + std::shared_ptr item_parameter_table; + const CommonItemSet::Table* pt; + const RareItemSet::Table* rt; + std::shared_ptr restrictions; + + std::shared_ptr item_drop_sub; + parray unit_weights_table1; + parray unit_weights_table2; + + // Note: The original implementation uses 17 different random states for some + // reason. We forego that and use only one for simplicity. + PSOV2Encryption random_crypt; + + bool are_rare_drops_allowed() const; + uint8_t normalize_area_number(uint8_t area) const; + + ItemData on_monster_item_drop_with_norm_area( + uint32_t enemy_type, uint8_t norm_area); + ItemData on_box_item_drop_with_norm_area(uint8_t area_norm); + + uint32_t rand_int(uint64_t max); + float rand_float_0_1_from_crypt(size_t which); + + template + uint32_t choose_meseta_amount( + const parray, NumRanges> ranges, + size_t table_index); + + bool should_allow_meseta_drops() const; + + ItemData check_rare_spec_and_create_rare_enemy_item(uint32_t enemy_type); + ItemData check_rare_specs_and_create_rare_box_item(uint8_t area_norm); + ItemData check_rate_and_create_rare_item( + const RareItemSet::Table::Drop& drop); + + void generate_rare_weapon_bonuses(ItemData& item, uint32_t random_sample); + void deduplicate_weapon_bonuses(ItemData& item) const; + void set_item_unidentified_or_present_flags_if_special(ItemData& item) const; + void set_item_unidentified_flag_if_challenge(ItemData& item) const; + void set_tool_item_amount_to_1(ItemData& item) const; + + void generate_common_item_variances(uint32_t norm_area, ItemData& item); + void generate_common_armor_slots_and_bonuses(ItemData& item); + void generate_common_armor_slot_count(ItemData& item); + void generate_common_armor_or_shield_type_and_variances( + char area_norm, ItemData& item); + void generate_common_tool_variances(uint32_t area_norm, ItemData& item); + uint8_t generate_tech_disk_level(uint32_t tech_num, uint32_t area_norm); + void generate_common_tool_type(uint8_t tool_class, ItemData& item) const; + void generate_common_mag_variances(ItemData& item) const; + void generate_common_weapon_variances(uint8_t area_norm, ItemData& item); + void generate_common_weapon_grind(ItemData& item, + uint8_t offset_within_subtype_range); + void generate_common_weapon_bonuses(ItemData& item, uint8_t area_norm); + void generate_common_weapon_special(ItemData& item, uint8_t area_norm); + uint8_t unknown_8011CA54(uint8_t det); + uint8_t unknown_8011C63C(uint8_t det) const; + void generate_unit_weights_tables(); + void generate_common_unit_variances(uint8_t det, ItemData& item); + void choose_tech_disk_level_for_tool_shop( + ItemData& item, size_t player_level, uint8_t tech_num_index); + static void clear_tool_item_if_invalid(ItemData& item); + void clear_item_if_restricted(ItemData& item) const; + + static size_t get_table_index_for_armor_shop(size_t player_level); + static bool shop_does_not_contain_duplicate_armor( + const std::vector& shop, const ItemData& item); + static bool shop_does_not_contain_duplicate_tech_disk( + const std::vector& shop, const ItemData& item); + static bool shop_does_not_contain_duplicate_or_too_many_similar_weapons( + const std::vector& shop, const ItemData& item); + static bool shop_does_not_contain_duplicate_item_by_primary_identifier( + const std::vector& shop, const ItemData& item); + void generate_armor_shop_armors( + std::vector& shop, size_t player_level); + void generate_armor_shop_shields( + std::vector& shop, size_t player_level); + void generate_armor_shop_units( + std::vector& shop, size_t player_level); + + static size_t get_table_index_for_tool_shop(size_t player_level); + void generate_common_tool_shop_recovery_items( + std::vector& shop, size_t player_level); + void generate_rare_tool_shop_recovery_items( + std::vector& shop, size_t player_level); + void generate_tool_shop_tech_disks( + std::vector& shop, size_t player_level); + + void generate_weapon_shop_item_grind(ItemData& item, size_t player_level); + void generate_weapon_shop_item_special(ItemData& item, size_t player_level); + void generate_weapon_shop_item_bonus1(ItemData& item, size_t player_level); + void generate_weapon_shop_item_bonus2(ItemData& item, size_t player_level); + + template + IntT get_rand_from_weighted_tables( + const IntT* tables, size_t offset, size_t num_values, size_t stride); + template + IntT get_rand_from_weighted_tables_1d(const parray& tables); + template + IntT get_rand_from_weighted_tables_2d_vertical( + const parray, Y>& tables, size_t offset); +}; diff --git a/src/ItemData.cc b/src/ItemData.cc new file mode 100644 index 00000000..682b274e --- /dev/null +++ b/src/ItemData.cc @@ -0,0 +1,1443 @@ +#include "ItemData.hh" + +#include "StaticGameData.hh" + +using namespace std; + + + +ItemData::ItemData() { + this->clear(); +} + +ItemData::ItemData(const ItemData& other) { + this->data1d = other.data1d; + this->id = other.id; + this->data2d = other.data2d; +} + +ItemData& ItemData::operator=(const ItemData& other) { + this->data1d = other.data1d; + this->id = other.id; + this->data2d = other.data2d; + return *this; +} + +bool ItemData::operator==(const ItemData& other) const { + return ((this->data1d[0] == other.data1d[0]) && + (this->data1d[1] == other.data1d[1]) && + (this->data1d[2] == other.data1d[2]) && + (this->id == other.id) && + (this->data2d == other.data2d)); +} + +bool ItemData::operator!=(const ItemData& other) const { + return !this->operator==(other); +} + +void ItemData::clear() { + this->data1d.clear(0); + this->id = 0xFFFFFFFF; + this->data2d = 0; +} + +bool ItemData::empty() const { + return (this->data1d[0] == 0) && + (this->data1d[1] == 0) && + (this->data1d[2] == 0) && + (this->data2d == 0); +} + +uint32_t ItemData::primary_identifier() const { + // The game treats any item starting with 04 as Meseta, and ignores the rest + // of data1 (the value is in data2) + if (this->data1[0] == 0x04) { + return 0x00040000; + } + if (this->data1[0] == 0x03 && this->data1[1] == 0x02) { + return 0x00030200; // Tech disk (data1[2] is level, so omit it) + } else if (this->data1[0] == 0x02) { + return 0x00020000 | (this->data1[1] << 8); // Mag + } else { + return (this->data1[0] << 16) | (this->data1[1] << 8) | this->data1[2]; + } +} + +bool ItemData::is_stackable() const { + return this->max_stack_size() > 1; +} + +size_t ItemData::stack_size() const { + if (max_stack_size_for_item(this->data1[0], this->data1[1]) > 1) { + return this->data1[5]; + } + return 1; +} + +size_t ItemData::max_stack_size() const { + return max_stack_size_for_item(this->data1[0], this->data1[1]); +} + + + +void ItemData::assign_mag_stats(const ItemMagStats& mag) { + // this->data1[0] and [1] unchanged + this->data1[2] = mag.level(); + this->data1[3] = mag.photon_blasts; + this->data1w[2] = mag.def & 0x7FFE; + this->data1w[3] = mag.pow & 0x7FFE; + this->data1w[4] = mag.dex & 0x7FFE; + this->data1w[5] = mag.mind & 0x7FFE; + // this->id unchanged + this->data2[0] = mag.synchro; + this->data2[1] = mag.iq; + this->data2[2] = mag.flags; + this->data2[3] = mag.color; +} + +void ItemData::clear_mag_stats() { + if (this->data1[0] == 2) { + this->data1[1] = '\0'; + this->assign_mag_stats(ItemMagStats()); + } +} + + + +void ItemData::set_unidentified_or_present_flag(uint16_t v) { + this->data1[10] = (v >> 8) | 0x80; + this->data1[11] = v; +} + +void ItemData::set_tool_item_amount(uint8_t amount) { + if (this->is_stackable()) { + this->data1[5] = amount; + } else if (this->data1[0] == 0x03) { + this->data1[5] = 0x00; + } +} + + + +void ItemData::set_armor_or_shield_defense_bonus(int16_t bonus) { + this->data1w[3] = bonus; +} + +void ItemData::set_common_armor_evasion_bonus(int16_t bonus) { + this->data1w[4] = bonus; +} + + + +void ItemData::set_item_unit_bonus(int16_t bonus) { + this->data1w[3] = bonus; +} + + + +bool ItemData::compare_for_sort(const ItemData& a, const ItemData& b) { + for (size_t z = 0; z < 12; z++) { + if (a.data1[z] < b.data1[z]) { + return true; + } else if (a.data1[z] > b.data1[z]) { + return false; + } + } + for (size_t z = 0; z < 4; z++) { + if (a.data2[z] < b.data2[z]) { + return true; + } else if (a.data2[z] > b.data2[z]) { + return false; + } + } + return false; +} + + + +const unordered_map name_for_weapon_special({ + {0x00, nullptr}, + {0x01, "Draw"}, + {0x02, "Drain"}, + {0x03, "Fill"}, + {0x04, "Gush"}, + {0x05, "Heart"}, + {0x06, "Mind"}, + {0x07, "Soul"}, + {0x08, "Geist"}, + {0x09, "Master\'s"}, + {0x0A, "Lord\'s"}, + {0x0B, "King\'s"}, + {0x0C, "Charge"}, + {0x0D, "Spirit"}, + {0x0E, "Berserk"}, + {0x0F, "Ice"}, + {0x10, "Frost"}, + {0x11, "Freeze"}, + {0x12, "Blizzard"}, + {0x13, "Bind"}, + {0x14, "Hold"}, + {0x15, "Seize"}, + {0x16, "Arrest"}, + {0x17, "Heat"}, + {0x18, "Fire"}, + {0x19, "Flame"}, + {0x1A, "Burning"}, + {0x1B, "Shock"}, + {0x1C, "Thunder"}, + {0x1D, "Storm"}, + {0x1E, "Tempest"}, + {0x1F, "Dim"}, + {0x20, "Shadow"}, + {0x21, "Dark"}, + {0x22, "Hell"}, + {0x23, "Panic"}, + {0x24, "Riot"}, + {0x25, "Havoc"}, + {0x26, "Chaos"}, + {0x27, "Devil\'s"}, + {0x28, "Demon\'s"}, +}); + +const unordered_map name_for_s_rank_special({ + {0x01, "Jellen"}, + {0x02, "Zalure"}, + {0x05, "Burning"}, + {0x06, "Tempest"}, + {0x07, "Blizzard"}, + {0x08, "Arrest"}, + {0x09, "Chaos"}, + {0x0A, "Hell"}, + {0x0B, "Spirit"}, + {0x0C, "Berserk"}, + {0x0D, "Demon\'s"}, + {0x0E, "Gush"}, + {0x0F, "Geist"}, + {0x10, "King\'s"}, +}); + +struct ItemNameInfo { + const char* name; + bool is_rare; + bool is_s_rank; + + ItemNameInfo(const char* name, bool is_rare = true, bool is_s_rank = false) + : name(name), is_rare(is_rare), is_s_rank(is_s_rank) { } +}; + +const unordered_map name_info_for_primary_identifier({ + // Weapons (00xxxx) + {0x000100, {"Saber", false}}, + {0x000101, {"Brand", false}}, + {0x000102, {"Buster", false}}, + {0x000103, {"Pallasch", false}}, + {0x000104, {"Gladius", false}}, + {0x000105, "DB\'s SABER"}, + {0x000106, "KALADBOLG"}, + {0x000107, "DURANDAL"}, + {0x000108, "GALATINE"}, + {0x000200, {"Sword", false}}, + {0x000201, {"Gigush", false}}, + {0x000202, {"Breaker", false}}, + {0x000203, {"Claymore", false}}, + {0x000204, {"Calibur", false}}, + {0x000205, "FLOWEN\'S SWORD"}, + {0x000206, "LAST SURVIVOR"}, + {0x000207, "DRAGON SLAYER"}, + {0x000300, {"Dagger", false}}, + {0x000301, {"Knife", false}}, + {0x000302, {"Blade", false}}, + {0x000303, {"Edge", false}}, + {0x000304, {"Ripper", false}}, + {0x000305, "BLADE DANCE"}, + {0x000306, "BLOODY ART"}, + {0x000307, "CROSS SCAR"}, + {0x000308, "ZERO DIVIDE"}, + {0x000309, "TWIN KAMUI"}, + {0x000400, {"Partisan", false}}, + {0x000401, {"Halbert", false}}, + {0x000402, {"Glaive", false}}, + {0x000403, {"Berdys", false}}, + {0x000404, {"Gungnir", false}}, + {0x000405, "BRIONAC"}, + {0x000406, "VJAYA"}, + {0x000407, "GAE BOLG"}, + {0x000408, "ASTERON BELT"}, + {0x000500, {"Slicer", false}}, + {0x000501, {"Spinner", false}}, + {0x000502, {"Cutter", false}}, + {0x000503, {"Sawcer", false}}, + {0x000504, {"Diska", false}}, + {0x000505, "SLICER OF ASSASSIN"}, + {0x000506, "DISKA OF LIBERATOR"}, + {0x000507, "DISKA OF BRAVEMAN"}, + {0x000508, "IZMAELA"}, + {0x000600, {"Handgun", false}}, + {0x000601, {"Autogun", false}}, + {0x000602, {"Lockgun", false}}, + {0x000603, {"Railgun", false}}, + {0x000604, {"Raygun", false}}, + {0x000605, "VARISTA"}, + {0x000606, "CUSTOM RAY ver.00"}, + {0x000607, "BRAVACE"}, + {0x000608, "TENSION BLASTER"}, + {0x000700, {"Rifle", false}}, + {0x000701, {"Sniper", false}}, + {0x000702, {"Blaster", false}}, + {0x000703, {"Beam", false}}, + {0x000704, {"Laser", false}}, + {0x000705, "VISK-235W"}, + {0x000706, "WALS-MK2"}, + {0x000707, "JUSTY-23ST"}, + {0x000708, "RIANOV 303SNR"}, + {0x000709, "RIANOV 303SNR-1"}, + {0x00070A, "RIANOV 303SNR-2"}, + {0x00070B, "RIANOV 303SNR-3"}, + {0x00070C, "RIANOV 303SNR-4"}, + {0x00070D, "RIANOV 303SNR-5"}, + {0x000800, {"Mechgun", false}}, + {0x000801, {"Assault", false}}, + {0x000802, {"Repeater", false}}, + {0x000803, {"Gatling", false}}, + {0x000804, {"Vulcan", false}}, + {0x000805, "M&A60 VISE"}, + {0x000806, "H&S25 JUSTICE"}, + {0x000807, "L&K14 COMBAT"}, + {0x000900, {"Shot", false}}, + {0x000901, {"Spread", false}}, + {0x000902, {"Cannon", false}}, + {0x000903, {"Launcher", false}}, + {0x000904, {"Arms", false}}, + {0x000905, "CRUSH BULLET"}, + {0x000906, "METEOR SMASH"}, + {0x000907, "FINAL IMPACT"}, + {0x000A00, {"Cane", false}}, + {0x000A01, {"Stick", false}}, + {0x000A02, {"Mace", false}}, + {0x000A03, {"Club", false}}, + {0x000A04, "CLUB OF LACONIUM"}, + {0x000A05, "MACE OF ADAMAN"}, + {0x000A06, "CLUB OF ZUMIURAN"}, + {0x000A07, "LOLLIPOP"}, + {0x000B00, {"Rod", false}}, + {0x000B01, {"Pole", false}}, + {0x000B02, {"Pillar", false}}, + {0x000B03, {"Striker", false}}, + {0x000B04, "BATTLE VERGE"}, + {0x000B05, "BRAVE HAMMER"}, + {0x000B06, "ALIVE AQHU"}, + {0x000B07, "VALKYRIE"}, + {0x000C00, {"Wand", false}}, + {0x000C01, {"Staff", false}}, + {0x000C02, {"Baton", false}}, + {0x000C03, {"Scepter", false}}, + {0x000C04, "FIRE SCEPTER:AGNI"}, + {0x000C05, "ICE STAFF:DAGON"}, + {0x000C06, "STORM WAND:INDRA"}, + {0x000C07, "EARTH WAND BROWNIE"}, + {0x000D00, "PHOTON CLAW"}, + {0x000D01, "SILENCE CLAW"}, + {0x000D02, "NEI\'S CLAW (REPLICA)"}, + {0x000D03, "PHOENIX CLAW"}, + {0x000E00, "DOUBLE SABER"}, + {0x000E01, "STAG CUTLERY"}, + {0x000E02, "TWIN BRAND"}, + {0x000F00, "BRAVE KNUCKLE"}, + {0x000F01, "ANGRY FIST"}, + {0x000F02, "GOD HAND"}, + {0x000F03, "SONIC KNUCKLE"}, + {0x001000, "OROTIAGITO"}, + {0x001001, "AGITO (AUW 1975)"}, + {0x001002, "AGITO (AUW 1983)"}, + {0x001003, "AGITO (AUW 2001)"}, + {0x001004, "AGITO (AUW 1991)"}, + {0x001005, "AGITO (AUW 1977)"}, + {0x001006, "AGITO (AUW 1980)"}, + {0x001007, "RAIKIRI"}, + {0x001100, "SOUL EATER"}, + {0x001101, "SOUL BANISH"}, + {0x001200, "SPREAD NEEDLE"}, + {0x001300, "HOLY RAY"}, + {0x001400, "INFERNO BAZOOKA"}, + {0x001401, "RAMBLING MAY"}, + {0x001402, "L&K38 COMBAT"}, + {0x001500, "FLAME VISIT"}, + {0x001501, "BURNING VISIT"}, + {0x001600, "AKIKO\'S FRYING PAN"}, + {0x001700, "SORCERER\'S CANE"}, + {0x001800, "S-BEAT\'S BLADE"}, + {0x001900, "P-ARMS\'S BLADE"}, + {0x001A00, "DELSABER\'S BUSTER"}, + {0x001B00, "BRINGER\'S RIFLE"}, + {0x001C00, "EGG BLASTER"}, + {0x001D00, "PSYCHO WAND"}, + {0x001E00, "HEAVEN PUNISHER"}, + {0x001F00, "LAVIS CANNON"}, + {0x002000, "VICTOR AXE"}, + {0x002001, "LACONIUM AXE"}, + {0x002100, "CHAIN SAWD"}, + {0x002200, "CADUCEUS"}, + {0x002201, "MERCURIUS ROD"}, + {0x002300, "STING TIP"}, + {0x002400, "MAGICAL PIECE"}, + {0x002500, "TECHNICAL CROZIER"}, + {0x002600, "SUPPRESSED GUN"}, + {0x002700, "ANCIENT SABER"}, + {0x002800, "HARISEN BATTLE FAN"}, + {0x002900, "YAMIGARASU"}, + {0x002A00, "AKIKO\'S WOK"}, + {0x002B00, "TOY HAMMER"}, + {0x002C00, "ELYSION"}, + {0x002D00, "RED SABER"}, + {0x002E00, "METEOR CUDGEL"}, + {0x002F00, "MONKEY KING BAR"}, + {0x002F01, "BLACK KING BAR"}, + {0x003000, "DOUBLE CANNON"}, + {0x003001, "GIRASOLE"}, + {0x003100, "HUGE BATTLE FAN"}, + {0x003200, "TSUMIKIRI J-SWORD"}, + {0x003300, "SEALED J-SWORD"}, + {0x003400, "RED SWORD"}, + {0x003500, "CRAZY TUNE"}, + {0x003600, "TWIN CHAKRAM"}, + {0x003700, "WOK OF AKIKO\'S SHOP"}, + {0x003800, "LAVIS BLADE"}, + {0x003900, "RED DAGGER"}, + {0x003A00, "MADAM\'S PARASOL"}, + {0x003B00, "MADAM\'S UMBRELLA"}, + {0x003C00, "IMPERIAL PICK"}, + {0x003D00, "BERDYSH"}, + {0x003E00, "RED PARTISAN"}, + {0x003F00, "FLIGHT CUTTER"}, + {0x004000, "FLIGHT FAN"}, + {0x004100, "RED SLICER"}, + {0x004200, "HANDGUN:GULD"}, + {0x004201, "MASTER RAVEN"}, + {0x004300, "HANDGUN:MILLA"}, + {0x004301, "LAST SWAN"}, + {0x004400, "RED HANDGUN"}, + {0x004500, "FROZEN SHOOTER"}, + {0x004501, "SNOW QUEEN"}, + {0x004600, "ANTI ANDROID RIFLE"}, + {0x004700, "ROCKET PUNCH"}, + {0x004800, "SAMBA MARACAS"}, + {0x004900, "TWIN PSYCHOGUN"}, + {0x004A00, "DRILL LAUNCHER"}, + {0x004B00, "GULD MILLA"}, + {0x004B01, "DUAL BIRD"}, + {0x004C00, "RED MECHGUN"}, + {0x004D00, "BELRA CANNON"}, + {0x004E00, "PANZER FAUST"}, + {0x004E01, "IRON FAUST"}, + {0x004F00, "SUMMIT MOON"}, + {0x005000, "WINDMILL"}, + {0x005100, "EVIL CURST"}, + {0x005200, "FLOWER CANE"}, + {0x005300, "HILDEBEAR\'S CANE"}, + {0x005400, "HILDEBLUE\'S CANE"}, + {0x005500, "RABBIT WAND"}, + {0x005600, "PLANTAIN LEAF"}, + {0x005601, "FATSIA"}, + {0x005700, "DEMONIC FORK"}, + {0x005800, "STRIKER OF CHAO"}, + {0x005900, "BROOM"}, + {0x005A00, "PROPHETS OF MOTAV"}, + {0x005B00, "THE SIGH OF A GOD"}, + {0x005C00, "TWINKLE STAR"}, + {0x005D00, "PLANTAIN FAN"}, + {0x005E00, "TWIN BLAZE"}, + {0x005F00, "MARINA\'S BAG"}, + {0x006000, "DRAGON\'S CLAW"}, + {0x006100, "PANTHER\'S CLAW"}, + {0x006200, "S-RED\'S BLADE"}, + {0x006300, "PLANTAIN HUGE FAN"}, + {0x006400, "CHAMELEON SCYTHE"}, + {0x006500, "YASMINKOV 3000R"}, + {0x006600, "ANO RIFLE"}, + {0x006700, "BARANZ LAUNCHER"}, + {0x006800, "BRANCH OF PAKUPAKU"}, + {0x006900, "HEART OF POUMN"}, + {0x006A00, "YASMINKOV 2000H"}, + {0x006B00, "YASMINKOV 7000V"}, + {0x006C00, "YASMINKOV 9000M"}, + {0x006D00, "MASER BEAM"}, + {0x006D01, "POWER MASER"}, + {0x006E00, "GAME MAGAZINE"}, + {0x006F00, "FLOWER BOUQUET"}, + {0x007000, {"S-RANK SABER", true, true}}, + {0x007100, {"S-RANK SWORD", true, true}}, + {0x007200, {"S-RANK BLADE", true, true}}, + {0x007300, {"S-RANK PARTISAN", true, true}}, + {0x007400, {"S-RANK SLICER", true, true}}, + {0x007500, {"S-RANK GUN", true, true}}, + {0x007600, {"S-RANK RIFLE", true, true}}, + {0x007700, {"S-RANK MECHGUN", true, true}}, + {0x007800, {"S-RANK SHOT", true, true}}, + {0x007900, {"S-RANK CANE", true, true}}, + {0x007A00, {"S-RANK ROD", true, true}}, + {0x007B00, {"S-RANK WAND", true, true}}, + {0x007C00, {"S-RANK TWIN", true, true}}, + {0x007D00, {"S-RANK CLAW", true, true}}, + {0x007E00, {"S-RANK BAZOOKA", true, true}}, + {0x007F00, {"S-RANK NEEDLE", true, true}}, + {0x008000, {"S-RANK SCYTHE", true, true}}, + {0x008100, {"S-RANK HAMMER", true, true}}, + {0x008200, {"S-RANK MOON", true, true}}, + {0x008300, {"S-RANK PSYCHOGUN", true, true}}, + {0x008400, {"S-RANK PUNCH", true, true}}, + {0x008500, {"S-RANK WINDMILL", true, true}}, + {0x008600, {"S-RANK HARISEN", true, true}}, + {0x008700, {"S-RANK KATANA", true, true}}, + {0x008800, {"S-RANK J-CUTTER", true, true}}, + {0x008900, "MUSASHI"}, + {0x008901, "YAMATO"}, + {0x008902, "ASUKA"}, + {0x008903, "SANGE & YASHA"}, + {0x008A00, "SANGE"}, + {0x008A01, "YASHA"}, + {0x008A02, "KAMUI"}, + {0x008B00, "PHOTON LAUNCHER"}, + {0x008B01, "GUILTY LIGHT"}, + {0x008B02, "RED SCORPIO"}, + {0x008B03, "PHONON MASER"}, + {0x008C00, "TALIS"}, + {0x008C01, "MAHU"}, + {0x008C02, "HITOGATA"}, + {0x008C03, "DANCING HITOGATA"}, + {0x008C04, "KUNAI"}, + {0x008D00, "NUG-2000 BAZOOKA"}, + {0x008E00, "S-BERILL\'S HANDS #0"}, + {0x008E01, "S-BERILL\'S HANDS #1"}, + {0x008F00, "FLOWEN\'S SWORD (AUW 3060; GREENILL)"}, + {0x008F01, "FLOWEN\'S SWORD (AUW 3064; SKYLY)"}, + {0x008F02, "FLOWEN\'S SWORD (AUW 3067; BLUEFULL)"}, + {0x008F03, "FLOWEN\'S SWORD (AUW 3073; PURPLENUM)"}, + {0x008F04, "FLOWEN\'S SWORD (AUW 3077; PINKAL)"}, + {0x008F05, "FLOWEN\'S SWORD (AUW 3082; REDRIA)"}, + {0x008F06, "FLOWEN\'S SWORD (AUW 3083; ORAN)"}, + {0x008F07, "FLOWEN\'S SWORD (AUW 3084; YELLOWBOZE)"}, + {0x008F08, "FLOWEN\'S SWORD (AUW 3079; WHITILL)"}, + {0x009000, "DB\'S SWORD (AUW 3062; GREENILL)"}, + {0x009001, "DB\'S SWORD (AUW 3067; SKYLY)"}, + {0x009002, "DB\'S SWORD (AUW 3069; BLUEFULL)"}, + {0x009003, "DB\'S SWORD (AUW 3064; PURPLENUM)"}, + {0x009004, "DB\'S SWORD (AUW 3069; PINKAL)"}, + {0x009005, "DB\'S SWORD (AUW 3073; REDRIA)"}, + {0x009006, "DB\'S SWORD (AUW 3070; ORAN)"}, + {0x009007, "DB\'S SWORD (AUW 3075; YELLOWBOZE)"}, + {0x009008, "DB\'S SWORD (AUW 3077; WHITILL)"}, + {0x009100, "GI GUE BAZOOKA"}, + {0x009200, "GUARDIANNA"}, + {0x009300, "VIRIDIA CARD"}, + {0x009301, "GREENILL CARD"}, + {0x009302, "SKYLY CARD"}, + {0x009303, "BLUEFULL CARD"}, + {0x009304, "PURPLENUM CARD"}, + {0x009305, "PINKAL CARD"}, + {0x009306, "REDRIA CARD"}, + {0x009307, "ORAN CARD"}, + {0x009308, "YELLOWBOZE CARD"}, + {0x009309, "WHITILL CARD"}, + {0x009400, "MORNING GLORY"}, + {0x009500, "PARTISAN OF LIGHTING"}, + {0x009600, "GAL WIND"}, + {0x009700, "ZANBA"}, + {0x009800, "RIKA\'S CLAW"}, + {0x009900, "ANGEL HARP"}, + {0x009A00, "DEMOLITION COMET"}, + {0x009B00, "NEI\'S CLAW"}, + {0x009C00, "RAINBOW BATON"}, + {0x009D00, "DARK FLOW"}, + {0x009E00, "DARK METEOR"}, + {0x009F00, "DARK BRIDGE"}, + {0x00A000, "G-ASSASSIN\'S SABERS"}, + {0x00A100, "RAPPY\'S FAN"}, + {0x00A200, "BOOMA\'S CLAW"}, + {0x00A201, "GOBOOMA\'S CLAW"}, + {0x00A202, "GIGOBOOMA\'S CLAW"}, + {0x00A300, "RUBY BULLET"}, + {0x00A400, "AMORE ROSE"}, + {0x00A500, {"S-RANK SWORDS", true, true}}, + {0x00A600, {"S-RANK LAUNCHER", true, true}}, + {0x00A700, {"S-RANK CARD", true, true}}, + {0x00A800, {"S-RANK KNUCKLE", true, true}}, + {0x00A900, {"S-RANK AXE", true, true}}, + {0x00AA00, "SLICER OF FANATIC"}, + {0x00AB00, "LAME D\'ARGENT"}, + {0x00AC00, "EXCALIBUR"}, + {0x00AD03, "RAGE DE FEU"}, + {0x00AE00, "DAISY CHAIN"}, + {0x00AF00, "OPHELIE SEIZE"}, + {0x00B000, "MILLE MARTEAUX"}, + {0x00B100, "LE COGNEUR"}, + {0x00B200, "COMMANDER BLADE"}, + {0x00B300, "VIVIENNE"}, + {0x00B400, "KUSANAGI"}, + {0x00B500, "SACRED DUSTER"}, + {0x00B600, "GUREN"}, + {0x00B700, "SHOUREN"}, + {0x00B800, "JIZAI"}, + {0x00B900, "FLAMBERGE"}, + {0x00BA00, "YUNCHANG"}, + {0x00BB00, "SNAKE SPIRE"}, + {0x00BC00, "FLAPJACK FLAPPER"}, + {0x00BD00, "GETSUGASAN"}, + {0x00BE00, "MAGUWA"}, + {0x00BF00, "HEAVEN STRIKER"}, + {0x00C000, "CANNON ROUGE"}, + {0x00C100, "METEOR ROUGE"}, + {0x00C200, "SOLFERINO"}, + {0x00C300, "CLIO"}, + {0x00C400, "SIREN GLASS HAMMER"}, + {0x00C500, "GLIDE DIVINE"}, + {0x00C600, "SHICHISHITO"}, + {0x00C700, "MURASAME"}, + {0x00C800, "DAYLIGHT SCAR"}, + {0x00C900, "DECALOG"}, + {0x00CA00, "5TH ANNIV. BLADE"}, + {0x00CB00, "PRINCIPAL\'S GIFT PARASOL"}, + {0x00CC00, "AKIKO\'S CLEAVER"}, + {0x00CD00, "TANEGASHIMA"}, + {0x00CE00, "TREE CLIPPERS"}, + {0x00CF00, "NICE SHOT"}, + {0x00D200, "ANO BAZOOKA"}, + {0x00D300, "SYNTHESIZER"}, + {0x00D400, "BAMBOO SPEAR"}, + {0x00D500, "KAN\'EI TSUHO"}, + {0x00D600, "JITTE"}, + {0x00D700, "BUTTERFLY NET"}, + {0x00D800, "SYRINGE"}, + {0x00D900, "BATTLEDORE"}, + {0x00DA00, "RACKET"}, + {0x00DB00, "HAMMER"}, + {0x00DC00, "GREAT BOUQUET"}, + {0x00DD00, "TypeSA/Saber"}, + {0x00DE00, "TypeSL/Saber"}, + {0x00DE01, "TypeSL/Slicer"}, + {0x00DE02, "TypeSL/Claw"}, + {0x00DE03, "TypeSL/Katana"}, + {0x00DF00, "TypeJS/Saber"}, + {0x00DF01, "TypeJS/Slicer"}, + {0x00DF02, "TypeJS/J-Sword"}, + {0x00E000, "TypeSW/Sword"}, + {0x00E001, "TypeSW/Slicer"}, + {0x00E002, "TypeSW/J-Sword"}, + {0x00E100, "TypeRO/Sword"}, + {0x00E101, "TypeRO/Halbert"}, + {0x00E102, "TypeRO/Rod"}, + {0x00E200, "TypeBL/BLADE"}, + {0x00E300, "TypeKN/Blade"}, + {0x00E301, "TypeKN/Claw"}, + {0x00E400, "TypeHA/Halbert"}, + {0x00E401, "TypeHA/Rod"}, + {0x00E500, "TypeDS/D.Saber"}, + {0x00E501, "TypeDS/Rod"}, + {0x00E502, "TypeDS"}, + {0x00E600, "TypeCL/Claw"}, + {0x00E700, "TypeSS/SW"}, + {0x00E800, "TypeGU/Handgun"}, + {0x00E801, "TypeGU/Mechgun"}, + {0x00E900, "TypeRI/Rifle"}, + {0x00EA00, "TypeME/Mechgun"}, + {0x00EB00, "TypeSH/Shot"}, + {0x00EC00, "TypeWA/Wand"}, + + // Armors (0101xx) + {0x010100, {"Frame", false}}, + {0x010101, {"Armor", false}}, + {0x010102, {"Psy Armor", false}}, + {0x010103, {"Giga Frame", false}}, + {0x010104, {"Soul Frame", false}}, + {0x010105, {"Cross Armor", false}}, + {0x010106, {"Solid Frame", false}}, + {0x010107, {"Brave Armor", false}}, + {0x010108, {"Hyper Frame", false}}, + {0x010109, {"Grand Armor", false}}, + {0x01010A, {"Shock Frame", false}}, + {0x01010B, {"King\'s Frame", false}}, + {0x01010C, {"Dragon Frame", false}}, + {0x01010D, {"Absorb Armor", false}}, + {0x01010E, {"Protect Frame", false}}, + {0x01010F, {"General Armor", false}}, + {0x010110, {"Perfect Frame", false}}, + {0x010111, {"Valiant Frame", false}}, + {0x010112, {"Imperial Armor", false}}, + {0x010113, {"Holiness Armor", false}}, + {0x010114, {"Guardian Armor", false}}, + {0x010115, {"Divinity Armor", false}}, + {0x010116, {"Ultimate Frame", false}}, + {0x010117, {"Celestial Armor", false}}, + {0x010118, "HUNTER FIELD"}, + {0x010119, "RANGER FIELD"}, + {0x01011A, "FORCE FIELD"}, + {0x01011B, "REVIVAL GARMENT"}, + {0x01011C, "SPIRIT GARMENT"}, + {0x01011D, "STINK FRAME"}, + {0x01011E, "D-PARTS Ver1.01"}, + {0x01011F, "D-PARTS Ver2.10"}, + {0x010120, "PARASITE WEAR:De Rol"}, + {0x010121, "PARASITE WEAR:Nelgal"}, + {0x010122, "PARASITE WEAR:Vajulla"}, + {0x010123, "SENSE PLATE"}, + {0x010124, "GRAVITON PLATE"}, + {0x010125, "ATTRIBUTE PLATE"}, + {0x010126, "FLOWEN\'S FRAME"}, + {0x010127, "CUSTOM FRAME Ver.00"}, + {0x010128, "DB\'s ARMOR"}, + {0x010129, "GUARD WAVE"}, + {0x01012A, "DF FIELD"}, + {0x01012B, "LUMINOUS FIELD"}, + {0x01012C, "CHU CHU FEVER"}, + {0x01012D, "LOVE HEART"}, + {0x01012E, "FLAME GARMENT"}, + {0x01012F, "VIRUS ARMOR:Lafuteria"}, + {0x010130, "BRIGHTNESS CIRCLE"}, + {0x010131, "AURA FIELD"}, + {0x010132, "ELECTRO FRAME"}, + {0x010133, "SACRED CLOTH"}, + {0x010134, "SMOKING PLATE"}, + {0x010135, "STAR CUIRASS"}, + {0x010136, "BLACK HOUND CUIRASS"}, + {0x010137, "MORNING PRAYER"}, + {0x010138, "BLACK ODOSHI DOMARU"}, + {0x010139, "RED ODOSHI DOMARU"}, + {0x01013A, "BLACK ODOSHI RED NIMAIDOU"}, + {0x01013B, "BLUE ODOSHI VIOLET NIMAIDOU"}, + {0x01013C, "DIRTY LIFE JACKET"}, + {0x01013E, "WEDDING DRESS"}, + {0x010140, "RED COAT"}, + {0x010141, "THIRTEEN"}, + {0x010142, "MOTHER GARB"}, + {0x010143, "MOTHER GARB+"}, + {0x010144, "DRESS PLATE"}, + {0x010145, "SWEETHEART"}, + {0x010146, "IGNITION CLOAK"}, + {0x010147, "CONGEAL CLOAK"}, + {0x010148, "TEMPEST CLOAK"}, + {0x010149, "CURSED CLOAK"}, + {0x01014A, "SELECT CLOAK"}, + {0x01014B, "SPIRIT CUIRASS"}, + {0x01014C, "REVIVAL CUIRASS"}, + {0x01014D, "ALLIANCE UNIFORM"}, + {0x01014E, "OFFICER UNIFORM"}, + {0x01014F, "COMMANDER UNIFORM"}, + {0x010150, "CRIMSON COAT"}, + {0x010151, "INFANTRY GEAR"}, + {0x010152, "LIEUTENANT GEAR"}, + {0x010153, "INFANTRY MANTLE"}, + {0x010154, "LIEUTENANT MANTLE"}, + {0x010155, "UNION FIELD"}, + {0x010156, "SAMURAI ARMOR"}, + {0x010157, "STEALTH SUIT"}, + + // Shields (0102xx) + {0x010200, {"Barrier", false}}, + {0x010201, {"Shield", false}}, + {0x010202, {"Core Shield", false}}, + {0x010203, {"Giga Shield", false}}, + {0x010204, {"Soul Barrier", false}}, + {0x010205, {"Hard Shield", false}}, + {0x010206, {"Brave Barrier", false}}, + {0x010207, {"Solid Shield", false}}, + {0x010208, {"Flame Barrier", false}}, + {0x010209, {"Plasma Barrier", false}}, + {0x01020A, {"Freeze Barrier", false}}, + {0x01020B, {"Psychic Barrier", false}}, + {0x01020C, {"General Shield", false}}, + {0x01020D, {"Protect Barrier", false}}, + {0x01020E, {"Glorious Shield", false}}, + {0x01020F, {"Imperial Barrier", false}}, + {0x010210, {"Guardian Shield", false}}, + {0x010211, {"Divinity Barrier", false}}, + {0x010212, {"Ultimate Shield", false}}, + {0x010213, {"Spiritual Shield", false}}, + {0x010214, {"Celestial Shield", false}}, + {0x010215, "INVISIBLE GUARD"}, + {0x010216, "SACRED GUARD"}, + {0x010217, "S-PARTS Ver1.16"}, + {0x010218, "S-PARTS Ver2.01"}, + {0x010219, "LIGHT RELIEF"}, + {0x01021A, "SHIELD OF DELSABER"}, + {0x01021B, "FORCE WALL"}, + {0x01021C, "RANGER WALL"}, + {0x01021D, "HUNTER WALL"}, + {0x01021E, "ATTRIBUTE WALL"}, + {0x01021F, "SECRET GEAR"}, + {0x010220, "COMBAT GEAR"}, + {0x010221, "PROTO REGENE GEAR"}, + {0x010222, "REGENERATE GEAR"}, + {0x010223, "REGENE GEAR ADV."}, + {0x010224, "FLOWEN\'S SHIELD"}, + {0x010225, "CUSTOM BARRIER Ver.00"}, + {0x010226, "DB\'S SHIELD"}, + {0x010227, "RED RING"}, + {0x010228, "TRIPOLIC SHIELD"}, + {0x010229, "STANDSTILL SHIELD"}, + {0x01022A, "SAFETY HEART"}, + {0x01022B, "KASAMI BRACER"}, + {0x01022C, "GODS SHIELD SUZAKU"}, + {0x01022D, "GODS SHIELD GENBU"}, + {0x01022E, "GODS SHIELD BYAKKO"}, + {0x01022F, "GODS SHIELD SEIRYU"}, + {0x010230, "HUNTER\'S SHELL"}, + {0x010231, "RICO\'S GLASSES"}, + {0x010232, "RICO\'S EARRING"}, + {0x010235, {"SECURE FEET", false}}, + {0x01023A, {"RESTA MERGE", false}}, + {0x01023B, {"ANTI MERGE", false}}, + {0x01023C, {"SHIFTA MERGE", false}}, + {0x01023D, {"DEBAND MERGE", false}}, + {0x01023E, {"FOIE MERGE", false}}, + {0x01023F, {"GIFOIE MERGE", false}}, + {0x010240, {"RAFOIE MERGE", false}}, + {0x010241, {"RED MERGE", false}}, + {0x010242, {"BARTA MERGE", false}}, + {0x010243, {"GIBARTA MERGE", false}}, + {0x010244, {"RABARTA MERGE", false}}, + {0x010245, {"BLUE MERGE", false}}, + {0x010246, {"ZONDE MERGE", false}}, + {0x010247, {"GIZONDE MERGE", false}}, + {0x010248, {"RAZONDE MERGE", false}}, + {0x010249, {"YELLOW MERGE", false}}, + {0x01024A, {"RECOVERY BARRIER", false}}, + {0x01024B, {"ASSIST BARRIER", false}}, + {0x01024C, {"RED BARRIER", false}}, + {0x01024D, {"BLUE BARRIER", false}}, + {0x01024E, {"YELLOW BARRIER", false}}, + {0x01024F, "WEAPONS GOLD SHIELD"}, + {0x010250, "BLACK GEAR"}, + {0x010251, "WORKS GUARD"}, + {0x010252, "RAGOL RING"}, + {0x010253, "BLUE RING (7 Colors)"}, + {0x010259, "BLUE RING"}, + {0x01025F, "GREEN RING"}, + {0x010266, "YELLOW RING"}, + {0x01026C, "PURPLE RING"}, + {0x010275, "WHITE RING"}, + {0x010280, "BLACK RING"}, + {0x010283, "WEAPONS SILVER SHIELD"}, + {0x010284, "WEAPONS COPPER SHIELD"}, + {0x010285, "GRATIA"}, + {0x010286, "TRIPOLIC REFLECTOR"}, + {0x010287, "STRIKER PLUS"}, + {0x010288, "REGENERATE GEAR B.P."}, + {0x010289, "RUPIKA"}, + {0x01028A, "YATA MIRROR"}, + {0x01028B, "BUNNY EARS"}, + {0x01028C, "CAT EARS"}, + {0x01028D, "THREE SEALS"}, + {0x01028F, "DF SHIELD"}, + {0x010290, "FROM THE DEPTHS"}, + {0x010291, "DE ROL LE SHIELD"}, + {0x010292, "HONEYCOMB REFLECTOR"}, + {0x010293, "EPSIGUARD"}, + {0x010294, "ANGEL RING"}, + {0x010295, "UNION GUARD"}, + {0x010297, "UNION"}, + {0x010298, "BLACK SHIELD UNION GUARD"}, + {0x010299, "STINK SHIELD"}, + {0x01029A, "BLACK"}, + {0x01029B, "GENPEI Heightened"}, + {0x01029C, "GENPEI Greenill"}, + {0x01029D, "GENPEI Skyly"}, + {0x01029E, "GENPEI Bluefull"}, + {0x01029F, "GENPEI Purplenum"}, + {0x0102A0, "GENPEI Pinkal"}, + {0x0102A1, "GENPEI Redria"}, + {0x0102A2, "GENPEI Oran"}, + {0x0102A3, "GENPEI Yellowboze"}, + {0x0102A4, "GENPEI Whitill"}, + + // Units (0103xx) + {0x010300, {"Knight/Power", false}}, + {0x010301, {"General/Power", false}}, + {0x010302, {"Ogre/Power", false}}, + {0x010303, "God/Power"}, + {0x010304, {"Priest/Mind", false}}, + {0x010305, {"General/Mind", false}}, + {0x010306, {"Angel/Mind", false}}, + {0x010307, "God/Mind"}, + {0x010308, {"Marksman/Arm", false}}, + {0x010309, {"General/Arm", false}}, + {0x01030A, {"Elf/Arm", false}}, + {0x01030B, "God/Arm"}, + {0x01030C, {"Thief/Legs", false}}, + {0x01030D, {"General/Legs", false}}, + {0x01030E, {"Elf/Legs", false}}, + {0x01030F, "God/Legs"}, + {0x010310, {"Digger/HP", false}}, + {0x010311, {"General/HP", false}}, + {0x010312, {"Dragon/HP", false}}, + {0x010313, "God/HP"}, + {0x010314, {"Magician/TP", false}}, + {0x010315, {"General/TP", false}}, + {0x010316, {"Angel/TP", false}}, + {0x010317, "God/TP"}, + {0x010318, {"Warrior/Body", false}}, + {0x010319, {"General/Body", false}}, + {0x01031A, {"Metal/Body", false}}, + {0x01031B, "God/Body"}, + {0x01031C, {"Angel/Luck", false}}, + {0x01031D, "God/Luck"}, + {0x01031E, {"Master/Ability", false}}, + {0x01031F, {"Hero/Ability", false}}, + {0x010320, "God/Ability"}, + {0x010321, {"Resist/Fire", false}}, + {0x010322, {"Resist/Flame", false}}, + {0x010323, {"Resist/Burning", false}}, + {0x010324, {"Resist/Cold", false}}, + {0x010325, {"Resist/Freeze", false}}, + {0x010326, {"Resist/Blizzard", false}}, + {0x010327, {"Resist/Shock", false}}, + {0x010328, {"Resist/Thunder", false}}, + {0x010329, {"Resist/Storm", false}}, + {0x01032A, {"Resist/Light", false}}, + {0x01032B, {"Resist/Saint", false}}, + {0x01032C, {"Resist/Holy", false}}, + {0x01032D, {"Resist/Dark", false}}, + {0x01032E, {"Resist/Evil", false}}, + {0x01032F, {"Resist/Devil", false}}, + {0x010330, {"All/Resist", false}}, + {0x010331, {"Super/Resist", false}}, + {0x010332, "Perfect/Resist"}, + {0x010333, {"HP/Restorate", false}}, + {0x010334, {"HP/Generate", false}}, + {0x010335, {"HP/Revival", false}}, + {0x010336, {"TP/Restorate", false}}, + {0x010337, {"TP/Generate", false}}, + {0x010338, {"TP/Revival", false}}, + {0x010339, {"PB/Amplifier", false}}, + {0x01033A, {"PB/Generate", false}}, + {0x01033B, {"PB/Create", false}}, + {0x01033C, {"Wizard/Technique", false}}, + {0x01033D, {"Devil/Technique", false}}, + {0x01033E, "God/Technique"}, + {0x01033F, {"General/Battle", false}}, + {0x010340, {"Devil/Battle", false}}, + {0x010341, "God/Battle"}, + {0x010342, "Cure/Poison"}, + {0x010343, "Cure/Paralysis"}, + {0x010344, "Cure/Slow"}, + {0x010345, "Cure/Confuse"}, + {0x010346, "Cure/Freeze"}, + {0x010347, "Cure/Shock"}, + {0x010348, "Yasakani Magatama"}, + {0x010349, "V101"}, + {0x01034A, "V501"}, + {0x01034B, "V502"}, + {0x01034C, "V801"}, + {0x01034D, "LIMITER"}, + {0x01034E, "ADEPT"}, + {0x01034F, "SWORDSMAN LORE"}, + {0x010350, "PROOF OF SWORD-SAINT"}, + {0x010351, "SMARTLINK"}, + {0x010352, "DIVINE PROTECTION"}, + {0x010353, "Heavenly/Battle"}, + {0x010354, "Heavenly/Power"}, + {0x010355, "Heavenly/Mind"}, + {0x010356, "Heavenly/Arms"}, + {0x010357, "Heavenly/Legs"}, + {0x010358, "Heavenly/Body"}, + {0x010359, "Heavenly/Luck"}, + {0x01035A, "Heavenly/Ability"}, + {0x01035B, "Centurion/Ability"}, + {0x01035C, "Friend Ring"}, + {0x01035D, "Heavenly/HP"}, + {0x01035E, "Heavenly/TP"}, + {0x01035F, "Heavenly/Resist"}, + {0x010360, "Heavenly/Technique"}, + {0x010361, "HP/Resurrection"}, + {0x010362, "TP/Resurrection"}, + {0x010363, "PB/Increase"}, + + // Mags (02xxxx) + {0x020000, {"Mag", false}}, + {0x020100, {"Varuna", false}}, + {0x020200, {"Mitra", false}}, + {0x020300, {"Surya", false}}, + {0x020400, {"Vayu", false}}, + {0x020500, {"Varaha", false}}, + {0x020600, {"Kama", false}}, + {0x020700, {"Ushasu", false}}, + {0x020800, {"Apsaras", false}}, + {0x020900, {"Kumara", false}}, + {0x020A00, {"Kaitabha", false}}, + {0x020B00, {"Tapas", false}}, + {0x020C00, {"Bhirava", false}}, + {0x020D00, {"Kalki", false}}, + {0x020E00, {"Rudra", false}}, + {0x020F00, {"Marutah", false}}, + {0x021000, {"Yaksa", false}}, + {0x021100, {"Sita", false}}, + {0x021200, {"Garuda", false}}, + {0x021300, {"Nandin", false}}, + {0x021400, {"Ashvinau", false}}, + {0x021500, {"Ribhava", false}}, + {0x021600, {"Soma", false}}, + {0x021700, {"Ila", false}}, + {0x021800, {"Durga", false}}, + {0x021900, {"Vritra", false}}, + {0x021A00, {"Namuci", false}}, + {0x021B00, {"Sumba", false}}, + {0x021C00, {"Naga", false}}, + {0x021D00, {"Pitri", false}}, + {0x021E00, {"Kabanda", false}}, + {0x021F00, {"Ravana", false}}, + {0x022000, {"Marica", false}}, + {0x022100, {"Soniti", false}}, + {0x022200, {"Preta", false}}, + {0x022300, {"Andhaka", false}}, + {0x022400, {"Bana", false}}, + {0x022500, {"Naraka", false}}, + {0x022600, {"Madhu", false}}, + {0x022700, {"Churel", false}}, + {0x022800, "ROBOCHAO"}, + {0x022900, "OPA-OPA"}, + {0x022A00, "PIAN"}, + {0x022B00, "CHAO"}, + {0x022C00, "CHU CHU"}, + {0x022D00, "KAPU KAPU"}, + {0x022E00, "ANGEL\'S WING"}, + {0x022F00, "DEVIL\'S WING"}, + {0x023000, "ELENOR"}, + {0x023100, "MARK3"}, + {0x023200, "MASTER SYSTEM"}, + {0x023300, "GENESIS"}, + {0x023400, "SEGA SATURN"}, + {0x023500, "DREAMCAST"}, + {0x023600, "HAMBURGER"}, + {0x023700, "PANZER\'S TAIL"}, + {0x023800, "DAVIL\'S TAIL"}, + {0x023900, "Deva"}, + {0x023A00, "Rati"}, + {0x023B00, "Savitri"}, + {0x023C00, "Rukmin"}, + {0x023D00, "Pushan"}, + {0x023E00, "Diwari"}, + {0x023F00, "Sato"}, + {0x024000, "Bhima"}, + {0x024100, "Nidra"}, + + // Tools (03xxxx) + {0x030000, {"Monomate", false}}, + {0x030001, {"Dimate", false}}, + {0x030002, {"Trimate", false}}, + {0x030100, {"Monofluid", false}}, + {0x030101, {"Difluid", false}}, + {0x030102, {"Trifluid", false}}, + {0x030200, {"", false}}, // Special-cased in name_for_item + {0x030300, {"Sol Atomizer", false}}, + {0x030400, {"Moon Atomizer", false}}, + {0x030500, {"Star Atomizer", false}}, + {0x030600, {"Antidote", false}}, + {0x030601, {"Antiparalysis", false}}, + {0x030700, {"Telepipe", false}}, + {0x030800, {"Trap Vision", false}}, + {0x030900, {"Scape Doll", false}}, + {0x030A00, {"Monogrinder", false}}, + {0x030A01, {"Digrinder", false}}, + {0x030A02, {"Trigrinder", false}}, + {0x030B00, {"Power Material", false}}, + {0x030B01, {"Mind Material", false}}, + {0x030B02, {"Evade Material", false}}, + {0x030B03, {"HP Material", false}}, + {0x030B04, {"TP Material", false}}, + {0x030B05, {"Def Material", false}}, + {0x030B06, {"Luck Material", false}}, + {0x030C00, "Cell Of MAG 502"}, + {0x030C01, "Cell Of MAG 213"}, + {0x030C02, "Parts Of RoboChao"}, + {0x030C03, "Heart Of Opa Opa"}, + {0x030C04, "Heart Of Pian"}, + {0x030C05, "Heart Of Chao"}, + {0x030D00, "Sorcerer\'s Right Arm"}, + {0x030D01, "S-beat\'s Arms"}, + {0x030D02, "P-arm\'s Arms"}, + {0x030D03, "Delsaber\'s Right Arm"}, + {0x030D04, "C-bringer\'s Right Arm"}, + {0x030D05, "Delsaber\'s Left Arm"}, + {0x030D06, "S-red\'s Arms"}, + {0x030D07, "Dragon\'s Claw"}, + {0x030D08, "Hildebear\'s Head"}, + {0x030D09, "Hildeblue\'s Head"}, + {0x030D0A, "Parts of Baranz"}, + {0x030D0B, "Belra\'s Right Arm"}, + {0x030D0C, "Gi Gue\'s Body"}, + {0x030D0D, "Sinow Berill\'s Arms"}, + {0x030D0E, "G-Assassin\'s Arms"}, + {0x030D0F, "Booma\'s Right Arm"}, + {0x030D10, "Gobooma\'s Right Arm"}, + {0x030D11, "Gigobooma\'s Right Arm"}, + {0x030D12, "Gal Gryphon's Wing"}, + {0x030D13, "Rappy\'s Wing"}, + {0x030D14, "Cladding of Epsilon"}, + {0x030D15, "De Rol Le Shell"}, + {0x030E00, "Berill Photon"}, + {0x030E01, "Parasitic gene \"Flow\""}, + {0x030E02, "Magic stone \"Iritista\""}, + {0x030E03, "Blue-black stone"}, + {0x030E04, "Syncesta"}, + {0x030E05, "Magic Water"}, + {0x030E06, "Parasitic cell Type-D"}, + {0x030E07, "magic rock \"Heart Key\""}, + {0x030E08, "magic rock \"Moola\""}, + {0x030E09, "Star Amplifier"}, + {0x030E0A, "Book of HITOGATA"}, + {0x030E0B, "Heart of Chu Chu"}, + {0x030E0C, "Parts of EGG BLASTER"}, + {0x030E0D, "Heart of Angel"}, + {0x030E0E, "Heart of Devil"}, + {0x030E0F, "Kit of Hamburger"}, + {0x030E10, "Panther\'s Spirit"}, + {0x030E11, "Kit of MARK3"}, + {0x030E12, "Kit of MASTER SYSTEM"}, + {0x030E13, "Kit of GENESIS"}, + {0x030E14, "Kit of SEGA SATURN"}, + {0x030E15, "Kit of DREAMCAST"}, + {0x030E16, {"Amplifier of Resta", false}}, + {0x030E17, {"Amplifier of Anti", false}}, + {0x030E18, {"Amplifier of Shifta", false}}, + {0x030E19, {"Amplifier of Deband", false}}, + {0x030E1A, {"Amplifier of Foie", false}}, + {0x030E1B, {"Amplifier of Gifoie", false}}, + {0x030E1C, {"Amplifier of Rafoie", false}}, + {0x030E1D, {"Amplifier of Barta", false}}, + {0x030E1E, {"Amplifier of Gibarta", false}}, + {0x030E1F, {"Amplifier of Rabarta", false}}, + {0x030E20, {"Amplifier of Zonde", false}}, + {0x030E21, {"Amplifier of Gizonde", false}}, + {0x030E22, {"Amplifier of Razonde", false}}, + {0x030E23, {"Amplifier of Red", false}}, + {0x030E24, {"Amplifier of Blue", false}}, + {0x030E25, {"Amplifier of Yellow", false}}, + {0x030E26, "Heart of KAPU KAPU"}, + {0x030E27, "Photon Booster"}, + {0x030F00, "AddSlot"}, + {0x031000, "Photon Drop"}, + {0x031001, "Photon Sphere"}, + {0x031002, "Photon Crystal"}, + {0x031003, "Secret Lottery Ticket"}, + {0x031100, "Book of KATANA1"}, + {0x031101, "Book of KATANA2"}, + {0x031102, "Book of KATANA3"}, + {0x031200, "Weapons Bronze Badge"}, + {0x031201, "Weapons Silver Badge"}, + {0x031202, "Weapons Gold Badge"}, + {0x031203, "Weapons Crystal Badge"}, + {0x031204, "Weapons Steel Badge"}, + {0x031205, "Weapons Aluminum Badge"}, + {0x031206, "Weapons Leather Badge"}, + {0x031207, "Weapons Bone Badge"}, + {0x031208, "Letter of appreciation"}, + {0x031209, "Autograph Album"}, + {0x03120A, "Valentine\'s Chocolate"}, + {0x03120B, "New Year\'s Card"}, + {0x03120C, "Christmas Card"}, + {0x03120D, "Birthday Card"}, + {0x03120E, "Proof of Sonic Team"}, + {0x03120F, "Special Event Ticket"}, + {0x031210, "Flower Bouquet"}, + {0x031211, "Cake"}, + {0x031212, "Accessories"}, + {0x031213, "Mr.Naka\'s Business Card"}, + {0x031300, "Present"}, + {0x031400, "Chocolate"}, + {0x031401, "Candy"}, + {0x031402, "Cake"}, + {0x031403, "Silver Badge"}, + {0x031404, "Gold Badge"}, + {0x031405, "Crystal Badge"}, + {0x031406, "Iron Badge"}, + {0x031407, "Aluminum Badge"}, + {0x031408, "Leather Badge"}, + {0x031409, "Bone Badge"}, + {0x03140A, "Bouquet"}, + {0x03140B, "Decoction"}, + {0x031500, "Christmas Present"}, + {0x031501, "Easter Egg"}, + {0x031502, "Jack-O\'-Lantern"}, + {0x031600, "DISK Vol.1"}, + {0x031601, "DISK Vol.2"}, + {0x031602, "DISK Vol.3"}, + {0x031603, "DISK Vol.4"}, + {0x031604, "DISK Vol.5"}, + {0x031605, "DISK Vol.6"}, + {0x031606, "DISK Vol.7"}, + {0x031607, "DISK Vol.8"}, + {0x031608, "DISK Vol.9"}, + {0x031609, "DISK Vol.10"}, + {0x03160A, "DISK Vol.11"}, + {0x03160B, "DISK Vol.12"}, + {0x031700, "Hunters Report"}, + {0x031701, "Hunters Report (Rank A)"}, + {0x031702, "Hunters Report (Rank B)"}, + {0x031703, "Hunters Report (Rank C)"}, + {0x031704, "Hunters Report (Rank F)"}, + {0x031800, "Tablet"}, + {0x031802, "Dragon Scale"}, + {0x031803, "Heaven Striker Coat"}, + {0x031804, "Pioneer Parts"}, + {0x031805, "Amitie\'s Memo"}, + {0x031806, "Heart of Morolian"}, + {0x031807, "Rappy\'s Beak"}, + {0x031809, "D-Photon Core"}, + {0x03180A, "Liberta Kit"}, + {0x03180B, "Cell of MAG 0503"}, + {0x03180C, "Cell of MAG 0504"}, + {0x03180D, "Cell of MAG 0505"}, + {0x03180F, "Cell of MAG 0507"}, + {0x031900, "Team Points 500"}, + {0x031901, "Team Points 1000"}, + {0x031902, "Team Points 5000"}, + {0x031903, "Team Points 10000"}, +}); + +string ItemData::name(bool include_color_codes) const { + if (this->data1[0] == 0x04) { + return string_printf("%s%" PRIu32 " Meseta", + include_color_codes ? "$C7" : "", this->data2d.load()); + } + + vector ret_tokens; + + // For weapons, specials appear before the weapon name + if ((this->data1[0] == 0x00) && (this->data1[4] != 0x00)) { + // 0x80 is the unidentified flag, but we always return the identified name + // of the item here, so we ignore it + bool is_present = this->data1[4] & 0x40; + uint8_t special_id = this->data1[4] & 0x3F; + if (is_present) { + ret_tokens.emplace_back("Wrapped"); + } + if (special_id) { + try { + ret_tokens.emplace_back(name_for_weapon_special.at(special_id)); + } catch (const out_of_range&) { + ret_tokens.emplace_back(string_printf("!SP:%02hhX", special_id)); + } + } + } + // Mags can be wrapped as well + if ((this->data1[0] == 0x02) && (this->data2[1] & 0x40)) { + ret_tokens.emplace_back("Wrapped"); + } + + // Add the item name. Technique disks are special because the level is part of + // the primary identifier, so we manually generate the name instead of looking + // it up. + ItemNameInfo name_info(nullptr, false, false); + uint32_t primary_identifier = this->primary_identifier(); + if ((primary_identifier & 0xFFFFFF00) == 0x00030200) { + string technique_name; + try { + technique_name = tech_id_to_name.at(this->data1[4]); + technique_name[0] = toupper(technique_name[0]); + } catch (const out_of_range&) { + technique_name = string_printf("!TECH:%02hhX", this->data1[4]); + } + ret_tokens.emplace_back(string_printf( + "Disk:%s Lv.%d", technique_name.c_str(), this->data1[2] + 1)); + } else { + try { + name_info = name_info_for_primary_identifier.at(primary_identifier); + ret_tokens.emplace_back(name_info.name); + } catch (const out_of_range&) { + ret_tokens.emplace_back(string_printf("!ID:%06" PRIX32, primary_identifier)); + } + } + + // For weapons, add the grind and percentages, or S-rank name if applicable + if (this->data1[0] == 0x00) { + if (this->data1[3] > 0) { + ret_tokens.emplace_back(string_printf("+%hhu", this->data1[3])); + } + + if (name_info.is_s_rank && (this->data1[6] & 0x18)) { + // S-rank (has name instead of percent bonuses) + uint8_t char_indexes[8] = { + static_cast((this->data1w[3] >> 5) & 0x1F), + static_cast(this->data1w[3] & 0x1F), + static_cast((this->data1w[4] >> 10) & 0x1F), + static_cast((this->data1w[4] >> 5) & 0x1F), + static_cast(this->data1w[4] & 0x1F), + static_cast((this->data1w[5] >> 10) & 0x1F), + static_cast((this->data1w[5] >> 5) & 0x1F), + static_cast(this->data1w[5] & 0x1F), + }; + const char* translation_table = "\0ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"; + + string name; + for (size_t x = 0; x < 8; x++) { + char ch = translation_table[char_indexes[x]]; + if (ch == 0) { + break; + } + name += ch; + } + if (!name.empty()) { + ret_tokens.emplace_back("(" + name + ")"); + } + + } else { // Not S-rank (extended name bits not set) + uint8_t percentages[5] = {0, 0, 0, 0, 0}; + for (size_t x = 0; x < 3; x++) { + uint8_t which = this->data1[6 + 2 * x]; + uint8_t value = this->data1[7 + 2 * x]; + if (which == 0) { + continue; + } + if (which > 5) { + ret_tokens.emplace_back(string_printf("!PC:%02hhX%02hhX", which, value)); + } else { + percentages[which - 1] = value; + } + } + ret_tokens.emplace_back(string_printf("%hhu/%hhu/%hhu/%hhu/%hhu", + percentages[0], percentages[1], percentages[2], percentages[3], percentages[4])); + } + + // For armors, add the slots, unit modifiers, and/or DEF/EVP bonuses + } else if (this->data1[0] == 0x01) { + if (this->data1[1] == 0x03) { // Units + uint16_t modifier = (this->data1[8] << 8) | this->data1[7]; + if (modifier == 0x0001 || modifier == 0x0002) { + ret_tokens.back().append("+"); + } else if (modifier == 0x0003 || modifier == 0x0004) { + ret_tokens.back().append("++"); + } else if (modifier == 0xFFFF || modifier == 0xFFFE) { + ret_tokens.back().append("-"); + } else if (modifier == 0xFFFD || modifier == 0xFFFC) { + ret_tokens.back().append("--"); + } else if (modifier != 0x0000) { + ret_tokens.emplace_back(string_printf("!MD:%04hX", modifier)); + } + + } else { // Armor/shields + if (this->data1[5] > 0) { + if (this->data1[5] == 1) { + ret_tokens.emplace_back("(1 slot)"); + } else { + ret_tokens.emplace_back(string_printf("(%hhu slots)", this->data1[5])); + } + } + if (this->data1w[3] != 0) { + ret_tokens.emplace_back(string_printf("+%hdDEF", + static_cast(this->data1w[3].load()))); + } + if (this->data1w[4] != 0) { + ret_tokens.emplace_back(string_printf("+%hdEVP", + static_cast(this->data1w[4].load()))); + } + } + + // For mags, add tons of info + } else if (this->data1[0] == 0x02) { + ret_tokens.emplace_back(string_printf("LV%hhu", this->data1[2])); + + ret_tokens.emplace_back(string_printf("%d/%d/%d/%d", + this->data1w[2] / 100, this->data1w[3] / 100, + this->data1w[4] / 100, this->data1w[5] / 100)); + ret_tokens.emplace_back(string_printf("%hhu%%", this->data2[3])); + ret_tokens.emplace_back(string_printf("%hhuIQ", this->data2[2])); + + uint8_t flags = this->data2[1]; + if (flags & 7) { + static const vector pb_shortnames = { + "F", "E", "G", "P", "L", "M&Y", "MG", "GR"}; + + const char* pb_names[3] = {nullptr, nullptr, nullptr}; + uint8_t center_pb = (flags & 2) ? (this->data1[3] & 7) : 0xFF; + uint8_t right_pb = (flags & 1) ? ((this->data1[3] >> 3) & 7) : 0xFF; + uint8_t left_pb = (flags & 4) ? ((this->data1[3] >> 6) & 3) : 0xFF; + if (center_pb != 0xFF) { + pb_names[1] = pb_shortnames[center_pb]; + } + if (right_pb != 0xFF) { + pb_names[2] = pb_shortnames[right_pb]; + } + if (left_pb != 0xFF) { + // There are only two bits for the left PB (as opposed to 3 for the + // center and right PBs). This works because PBs can't be duplicated; + // there are 6 valid PBs for each slot, but the center and right slots + // are used first, leaving 4 valid options for the left slot. To encode + // this in two bits, the game takes the list of all PBs, removes the + // center and right PBs from the list, and the left PB is then used as + // an index into this modified list to determine the actual left PB. + // Here, we don't construct a temporary list and instead just skip the + // center and right PB values with a loop. + uint8_t actual_left_pb = 0; + for (;;) { + if ((actual_left_pb == center_pb) || (actual_left_pb == right_pb)) { + actual_left_pb++; + continue; + } + if (left_pb > 0) { + actual_left_pb++; + left_pb--; + continue; + } + break; + } + pb_names[0] = pb_shortnames[actual_left_pb]; + } + + string token = "PB:"; + for (size_t x = 0; x < 3; x++) { + if (pb_names[x] == nullptr) { + continue; + } + if (token.size() > 3) { + token += ','; + } + token += pb_names[x]; + } + ret_tokens.emplace_back(move(token)); + + static const vector mag_colors({ + /* 00 */ "red", + /* 01 */ "blue", + /* 02 */ "yellow", + /* 03 */ "green", + /* 04 */ "purple", + /* 05 */ "black", + /* 06 */ "white", + /* 07 */ "cyan", + /* 08 */ "brown", + /* 09 */ "orange", + /* 0A */ "light blue", + /* 0B */ "olive", + /* 0C */ "light cyan", + /* 0D */ "dark purple", + /* 0E */ "grey", + /* 0F */ "light grey", + /* 10 */ "pink", + /* 11 */ "dark cyan", + /* 12 */ "costume color", + }); + try { + ret_tokens.emplace_back(string_printf("(%s)", mag_colors.at(this->data2[0]))); + } catch (const out_of_range&) { + ret_tokens.emplace_back(string_printf("(!CL:%02hhX)", this->data2[0])); + } + } + + // For tools, add the amount (if applicable) + } else if (this->data1[0] == 0x03) { + if (this->max_stack_size() > 1) { + ret_tokens.emplace_back(string_printf("x%hhu", this->data1[5])); + } + } + + string ret = join(ret_tokens, " "); + if (include_color_codes) { + if (name_info.is_s_rank) { + return "$C4" + ret; + } else if (name_info.is_rare) { + return "$C6" + ret; + } else { + return "$C7" + ret; + } + } else { + return ret; + } +} diff --git a/src/ItemData.hh b/src/ItemData.hh new file mode 100644 index 00000000..45c30731 --- /dev/null +++ b/src/ItemData.hh @@ -0,0 +1,92 @@ +#pragma once + +#include +#include + +#include "Text.hh" + + + +constexpr uint32_t MESETA_IDENTIFIER = 0x00040000; + +struct ItemMagStats { + uint16_t iq; + uint16_t synchro; + uint16_t def; + uint16_t pow; + uint16_t dex; + uint16_t mind; + uint8_t flags; + uint8_t photon_blasts; + uint8_t color; + + ItemMagStats() + : iq(0), + synchro(40), + def(500), + pow(0), + dex(0), + mind(0), + flags(0), + photon_blasts(0), + color(14) { } + + inline uint16_t def_level() const { + return this->def / 100; + } + inline uint16_t pow_level() const { + return this->pow / 100; + } + inline uint16_t dex_level() const { + return this->dex / 100; + } + inline uint16_t mind_level() const { + return this->mind / 100; + } + inline uint16_t level() const { + return this->def_level() + this->pow_level() + this->dex_level() + this->mind_level(); + } +}; + +struct ItemData { // 0x14 bytes + union { + parray data1; + parray data1w; + parray data1d; + } __attribute__((packed)); + le_uint32_t id; + union { + parray data2; + parray data2w; + le_uint32_t data2d; + } __attribute__((packed)); + + ItemData(); + ItemData(const ItemData& other); + ItemData& operator=(const ItemData& other); + + bool operator==(const ItemData& other) const; + bool operator!=(const ItemData& other) const; + + void clear(); + + std::string name(bool include_color_codes) const; + uint32_t primary_identifier() const; + + bool is_stackable() const; + size_t stack_size() const; + size_t max_stack_size() const; + + void assign_mag_stats(const ItemMagStats& mag); + void clear_mag_stats(); + + void set_unidentified_or_present_flag(uint16_t v); + void set_tool_item_amount(uint8_t amount); + void set_armor_or_shield_defense_bonus(int16_t bonus); + void set_common_armor_evasion_bonus(int16_t bonus); + void set_item_unit_bonus(int16_t bonus); + + bool empty() const; + + static bool compare_for_sort(const ItemData& a, const ItemData& b); +} __attribute__((packed)); diff --git a/src/ItemParameterTable.cc b/src/ItemParameterTable.cc new file mode 100644 index 00000000..d20b4f4e --- /dev/null +++ b/src/ItemParameterTable.cc @@ -0,0 +1,195 @@ +#include "ItemParameterTable.hh" + +using namespace std; + + + +ItemParameterTable::ItemParameterTable(shared_ptr data) + : data(data), r(*data) { + size_t offset_table_offset = this->r.pget_u32l(this->data->size() - 0x10); + this->offsets = &r.pget(offset_table_offset); +} + +const ItemParameterTable::Weapon& ItemParameterTable::get_weapon( + uint8_t data1_1, uint8_t data1_2) const { + if (data1_1 >= 0xED) { + throw runtime_error("weapon ID out of range"); + } + const auto& co = this->r.pget( + this->offsets->weapon_table + sizeof(CountAndOffset) * data1_1); + if (data1_2 >= co.count) { + throw runtime_error("weapon ID out of range"); + } + return this->r.pget(co.offset + sizeof(Weapon) * data1_2); +} + +const ItemParameterTable::ArmorOrShield& ItemParameterTable::get_armor_or_shield( + uint8_t data1_1, uint8_t data1_2) const { + if ((data1_1 < 1) || (data1_1 > 2)) { + throw runtime_error("armor/shield ID out of range"); + } + const auto& co = this->r.pget( + this->offsets->armor_table + sizeof(CountAndOffset) * (data1_1 - 1)); + if (data1_2 >= co.count) { + throw runtime_error("armor/shield ID out of range"); + } + return this->r.pget(co.offset + sizeof(ArmorOrShield) * data1_2); +} + +const ItemParameterTable::Unit& ItemParameterTable::get_unit( + uint8_t data1_2) const { + const auto& co = this->r.pget(this->offsets->unit_table); + if (data1_2 >= co.count) { + throw runtime_error("unit ID out of range"); + } + return this->r.pget(co.offset + sizeof(Unit) * data1_2); +} + +const ItemParameterTable::Tool& ItemParameterTable::get_tool( + uint8_t data1_1, uint8_t data1_2) const { + if (data1_1 > 0x1A) { + throw runtime_error("tool ID out of range"); + } + const auto& co = this->r.pget( + this->offsets->tool_table + sizeof(CountAndOffset) * data1_1); + if (data1_2 >= co.count) { + throw runtime_error("tool ID out of range"); + } + return this->r.pget(co.offset + sizeof(Tool) * data1_2); +} + +pair ItemParameterTable::find_tool_by_class( + uint8_t tool_class) const { + const auto& cos = this->r.pget>( + this->offsets->tool_table); + for (size_t z = 0; z < cos.size(); z++) { + const auto& co = cos[z]; + const auto* defs = &this->r.pget(co.offset, sizeof(Tool) * co.count); + for (size_t y = 0; y < co.count; y++) { + if (defs[y].base.id == tool_class) { + return make_pair(z, y); + } + } + } + throw out_of_range("invalid tool class"); +} + +const ItemParameterTable::Mag& ItemParameterTable::get_mag( + uint8_t data1_1) const { + const auto& co = this->r.pget(this->offsets->mag_table); + if (data1_1 >= co.count) { + throw runtime_error("unit ID out of range"); + } + return this->r.pget(co.offset + sizeof(Mag) * data1_1); +} + +float ItemParameterTable::get_sale_divisor(uint8_t data1_0, uint8_t data1_1) const { + if (data1_0 == 0) { // Weapon + if (data1_1 < 0xED) { + return this->r.pget_f32l( + this->offsets->weapon_sale_divisor_table + data1_1 * sizeof(float)); + } + return 0.0f; + } + + const auto& divisors = this->r.pget( + this->offsets->sale_divisor_table); + if (data1_0 == 1) { + switch (data1_1) { + case 1: + return divisors.armor_divisor; + case 2: + return divisors.shield_divisor; + case 3: + return divisors.unit_divisor; + } + return 0.0f; + } + + if (data1_0 == 2) { + return divisors.mag_divisor; + } + + return 0.0f; +} + +uint8_t ItemParameterTable::get_item_stars(uint16_t slot) const { + if ((slot >= 0xB1) && (slot < 0x437)) { + return this->r.pget_u8(this->offsets->star_value_table + slot - 0xB1); + } + return 0; +} + +uint8_t ItemParameterTable::get_max_tech_level(uint8_t char_class, uint8_t tech_num) const { + if (char_class >= 12) { + throw logic_error("invalid character class"); + } + if (tech_num >= 19) { + throw logic_error("invalid technique number"); + } + return r.pget_u8(this->offsets->max_tech_level_table + tech_num * 12 + char_class); +} + + + +const ItemParameterTable::ItemBase& ItemParameterTable::get_item_definition( + const ItemData& item) const { + switch (item.data1[0]) { + case 0: + return this->get_weapon(item.data1[1], item.data1[2]).base; + case 1: + if (item.data1[1] == 3) { + return this->get_unit(item.data1[2]).base; + } else if ((item.data1[1] == 1) || (item.data1[1] == 2)) { + return this->get_armor_or_shield(item.data1[1], item.data1[2]).base; + } + throw logic_error("invalid item"); + case 2: + return this->get_mag(item.data1[1]).base; + case 3: + if (item.data1[1] == 2) { + return this->get_tool(2, item.data1[4]).base; + } else { + return this->get_tool(item.data1[1], item.data1[2]).base; + } + throw logic_error("this should be impossible"); + case 4: + throw logic_error("item is meseta and therefore has no definition"); + default: + throw logic_error("invalid item"); + } +} + +uint8_t ItemParameterTable::get_item_stars(const ItemData& item) const { + if (item.data1[0] == 2) { + return (item.data1[1] > 0x27) ? 12 : 0; + } else if (item.data1[0] < 2) { + return this->get_item_stars(this->get_item_definition(item).id); + } else if (item.data1[0] == 3) { + const auto& def = (item.data1[1] == 2) + ? this->get_tool(2, item.data1[4]) + : this->get_tool(item.data1[1], item.data1[2]); + return (def.item_flag & 0x80) ? 12 : 0; + } else { + return 0; + } +} + +bool ItemParameterTable::is_item_rare(const ItemData& item) const { + return (this->get_item_stars(item) >= 9); +} + +bool ItemParameterTable::is_unsealable_item(const ItemData& item) const { + const auto& co = this->r.pget(this->offsets->unsealable_table); + const auto* defs = &this->r.pget( + co.offset, co.count * sizeof(UnsealableItem)); + for (size_t z = 0; z < co.count; z++) { + if ((defs[z].item[0] == item.data1[0]) && + (defs[z].item[1] == item.data1[1]) && + (defs[z].item[2] == item.data1[2])) { + return true; + } + } + return false; +} + diff --git a/src/ItemParameterTable.hh b/src/ItemParameterTable.hh new file mode 100644 index 00000000..b88da083 --- /dev/null +++ b/src/ItemParameterTable.hh @@ -0,0 +1,239 @@ +#pragma once + +#include + +#include +#include +#include + +#include "Text.hh" +#include "ItemData.hh" + + + +class ItemParameterTable { +public: + struct ItemBase { + le_uint32_t id; + le_uint16_t type; + le_uint16_t skin; + le_uint32_t team_points; + } __attribute__((packed)); + + struct ArmorOrShield { + ItemBase base; + le_uint16_t dfp; + le_uint16_t evp; + uint8_t block_particle; + uint8_t block_effect; + uint8_t item_class; + uint8_t unknown_a1; + uint8_t required_level; + uint8_t efr; + uint8_t eth; + uint8_t eic; + uint8_t edk; + uint8_t elt; + uint8_t dfp_range; + uint8_t evp_range; + uint8_t stat_boost; + uint8_t tech_boost; + le_uint16_t unknown_a2; + } __attribute__((packed)); + + struct Unit { + ItemBase base; + le_uint16_t stat; + le_uint16_t stat_amount; + le_int16_t modifier_amount; + parray unused; + } __attribute__((packed)); + + struct Mag { + ItemBase base; + uint8_t feed_table; + uint8_t unknown_a1; + uint8_t photon_blast; + uint8_t activation; + uint8_t on_pb_full; + uint8_t on_low_hp; + uint8_t on_death; + uint8_t on_boss; + uint8_t on_pb_full_flag; + uint8_t on_low_hp_flag; + uint8_t on_death_flag; + uint8_t on_boss_flag; + uint8_t item_class; + parray unused; + } __attribute__((packed)); + + struct Tool { + ItemBase base; + le_uint16_t amount; + le_uint16_t tech; + le_int32_t cost; + uint8_t item_flag; + parray unused; + } __attribute__((packed)); + + struct Weapon { + ItemBase base; + uint8_t item_class; + uint8_t unknown_a0; + le_uint16_t atp_min; + le_uint16_t atp_max; + le_uint16_t atp_required; + le_uint16_t mst_required; + le_uint16_t ata_required; + le_uint16_t mst; + uint8_t max_grind; + uint8_t photon; + uint8_t special; + uint8_t ata; + uint8_t stat_boost; + uint8_t projectile; + int8_t trail1_x; + int8_t trail1_y; + int8_t trail2_x; + int8_t trail2_y; + int8_t color; + uint8_t unknown_a1; + uint8_t unknown_a2; + uint8_t unknown_a3; + uint8_t unknown_a4; + uint8_t unknown_a5; + uint8_t tech_boost; + uint8_t combo_type; + } __attribute__((packed)); + + struct MagFeedResult { + int8_t defense; + int8_t power; + int8_t dexterity; + int8_t mind; + int8_t iq; + int8_t sync; + parray unused; + } __attribute__((packed)); + + struct MagFeedResultsList { + parray results; + } __attribute__((packed)); + + struct MagFeedResultsTable { + parray table; + } __attribute__((packed)); + + struct ItemStarValue { + uint8_t num_stars; + } __attribute__((packed)); + + struct Special { + le_uint16_t type; + le_uint16_t amount; + } __attribute__((packed)); + + struct StatBoost { + uint8_t stat1; + uint8_t stat2; + le_uint16_t amount1; + le_uint16_t amount2; + } __attribute__((packed)); + + struct MaxTechniqueLevels { + // Indexed as [tech_num][char_class] + parray, 19> max_level; + } __attribute__((packed)); + + struct ItemCombination { + parray used_item; + parray equipped_item; + parray result_item; + uint8_t maglevel; + uint8_t grind; + uint8_t level; + uint8_t char_class; + parray unused; + } __attribute__((packed)); + + struct TechniqueBoost { + le_uint32_t tech1; + le_float boost1; + le_uint32_t tech2; + le_float boost2; + le_uint32_t tech3; + le_float boost3; + } __attribute__((packed)); + + struct EventItem { + parray item; + uint8_t probability; + } __attribute__((packed)); + + struct UnsealableItem { + parray item; + uint8_t unused; + } __attribute__((packed)); + + struct NonWeaponSaleDivisors { + le_float armor_divisor; + le_float shield_divisor; + le_float unit_divisor; + le_float mag_divisor; + } __attribute__((packed)); + + struct TableOffsets { + /* 00 / 14884 */ le_uint32_t weapon_table; // -> [{count, offset -> [Weapon]}](0xED) + /* 04 / 1478C */ le_uint32_t armor_table; // -> [{count, offset -> [ArmorOrShield]}](2; armors and shields) + /* 08 / 1479C */ le_uint32_t unit_table; // -> {count, offset -> [Unit]} (last if out of range) + /* 0C / 147AC */ le_uint32_t tool_table; // -> [{count, offset -> [Tool]}](0x1A) (last if out of range) + /* 10 / 147A4 */ le_uint32_t mag_table; // -> {count, offset -> [Mag]} + /* 14 / 0F4B8 */ le_uint32_t attack_animation_table; // -> [uint8_t](0xED) + /* 18 / 0DE7C */ le_uint32_t photon_color_table; // -> [0x24-byte structs](0x20) + /* 1C / 0E194 */ le_uint32_t weapon_range_table; // -> ??? + /* 20 / 0F5A8 */ le_uint32_t weapon_sale_divisor_table; // -> [float](0xED) + /* 24 / 0F83C */ le_uint32_t sale_divisor_table; // -> NonWeaponSaleDivisors + /* 28 / 1502C */ le_uint32_t mag_feed_table; // -> [offset -> MagFeedResultsList](8) + /* 2C / 0FB0C */ le_uint32_t star_value_table; // -> [uint8_t] (indexed by .id from weapon, armor, etc.) + /* 30 / 0FE3C */ le_uint32_t special_data_table; // -> [Special] + /* 34 / 0FEE0 */ le_uint32_t weapon_effect_table; // -> [16-byte structs] + /* 38 / 1275C */ le_uint32_t stat_boost_table; // -> [StatBoost] + /* 3C / 11C80 */ le_uint32_t shield_effect_table; // -> [8-byte structs] + /* 40 / 12894 */ le_uint32_t max_tech_level_table; // -> MaxTechniqueLevels + /* 44 / 14FF4 */ le_uint32_t combination_table; // -> [{count, offset -> [ItemCombination]}] + /* 48 / 12754 */ le_uint32_t unknown_a1; + /* 4C / 14278 */ le_uint32_t tech_boost_table; // -> [TechniqueBoost] (always 0x2C of them? from counts struct?) + /* 50 / 15014 */ le_uint32_t unwrap_table; // -> {count, offset -> [{count, offset -> [4-byte structs]}]} + /* 54 / 1501C */ le_uint32_t unsealable_table; // -> {count, offset -> [UnsealableItem]} + /* 58 / 15024 */ le_uint32_t ranged_special_table; // -> {count, offset -> [4-byte structs]} + } __attribute__((packed)); + + struct CountAndOffset { + le_uint32_t count; + le_uint32_t offset; + } __attribute__((packed)); + + ItemParameterTable(std::shared_ptr data); + ~ItemParameterTable() = default; + + const Weapon& get_weapon(uint8_t data1_1, uint8_t data1_2) const; + const ArmorOrShield& get_armor_or_shield(uint8_t data1_1, uint8_t data1_2) const; + const Unit& get_unit(uint8_t data1_2) const; + const Tool& get_tool(uint8_t data1_1, uint8_t data1_2) const; + std::pair find_tool_by_class(uint8_t tool_class) const; + const Mag& get_mag(uint8_t data1_1) const; + float get_sale_divisor(uint8_t data1_0, uint8_t data1_1) const; + const MagFeedResult& get_mag_feed_result(uint8_t table_index, uint8_t which) const; + uint8_t get_item_stars(uint16_t slot) const; + uint8_t get_max_tech_level(uint8_t char_class, uint8_t tech_num) const; + + const ItemBase& get_item_definition(const ItemData& item) const; + uint8_t get_item_stars(const ItemData& item) const; + bool is_item_rare(const ItemData& item) const; + bool is_unsealable_item(const ItemData& param_1) const; + +private: + std::shared_ptr data; + StringReader r; + const TableOffsets* offsets; +}; diff --git a/src/Items.cc b/src/Items.cc index 9ab7549d..79824cbd 100644 --- a/src/Items.cc +++ b/src/Items.cc @@ -8,15 +8,6 @@ using namespace std; -uint32_t random_int(shared_ptr rand, uint32_t min, uint32_t max) { - uint32_t range = max - min + 1; - return min + ((*rand)() % range); -} - - - -//////////////////////////////////////////////////////////////////////////////// - /* these items all need some kind of special handling that hasn't been implemented yet. 030B04 = TP Material (?) @@ -236,369 +227,3 @@ void player_use_item(shared_ptr c, size_t item_index) { c->game_data.player()->remove_item(item.data.id, 1, c->version() != GameVersion::BB); } } - -//////////////////////////////////////////////////////////////////////////////// - -CommonItemData::CommonItemData( - vector&& enemy_item_categories, - vector&& box_item_categories, - vector>&& unit_types) : - enemy_item_categories(move(enemy_item_categories)), - box_item_categories(move(box_item_categories)), - unit_types(move(unit_types)) { - - // sanity check the values - if (this->enemy_item_categories.size() != 8) { - throw invalid_argument("enemy item categories is incorrect length"); - } - if (this->box_item_categories.size() != 8) { - throw invalid_argument("box item categories is incorrect length"); - } - if (this->unit_types.size() != 4) { - throw invalid_argument("unit types is incorrect length"); - } - - { - uint64_t sum = 0; - for (uint32_t v : this->enemy_item_categories) { - sum += v; - } - if (sum > 0xFFFFFFFF) { - throw invalid_argument("enemy item category sum is too large"); - } - } - - { - uint64_t sum = 0; - for (uint32_t v : this->box_item_categories) { - sum += v; - } - if (sum > 0xFFFFFFFF) { - throw invalid_argument("box item category sum is too large"); - } - } -} - -CommonItemCreator::CommonItemCreator( - std::shared_ptr data, - std::shared_ptr random) - : data(data), random(random) { } - -int32_t CommonItemCreator::decide_item_type(bool is_box) const { - uint32_t det = (*this->random)(); - - const auto& v = is_box ? this->data->box_item_categories : this->data->enemy_item_categories; - for (size_t x = 0; x < v.size(); x++) { - uint32_t probability = v.at(x); - if (probability > det) { - return x; - } - det -= probability; - } - return -1; -} - -ItemData CommonItemCreator::create_drop_item(bool is_box, Episode episode, - uint8_t difficulty, uint8_t area, uint8_t) const { - // TODO: use the section ID (last argument) to vary drop frequencies appropriately - // change the area if it's invalid (data for the bosses are actually in other areas) - if (area > 10) { - if (episode == Episode::EP1) { - if (area == 11) { - area = 3; // dragon - } else if (area == 12) { - area = 6; // de rol le - } else if (area == 13) { - area = 8; // vol opt - } else if (area == 14) { - area = 10; // dark falz - } else { - area = 1; // unknown area -> forest 1 - } - } else if (episode == Episode::EP2) { - if (area == 12) { - area = 9; // gal gryphon - } else if (area == 13) { - area = 10; // olga flow - } else if (area == 14) { - area = 3; // barba ray - } else if (area == 15) { - area = 6; // gol dragon - } else { - area = 10; // tower - } - } else if (episode == Episode::EP4) { - area = 1; - } - } - - ItemData item; - - // picks a random non-rare item type, then gives it appropriate random stats - // modify some of the constants in this section to change the system's - // parameters - int32_t type = this->decide_item_type(is_box); - switch (type) { - case 0x00: // material - item.data1[0] = 0x03; - item.data1[1] = 0x0B; - item.data1[2] = random_int(this->random, 0, 6); - break; - - case 0x01: // equipment - switch (random_int(this->random, 0, 3)) { - case 0x00: // weapon - item.data1[1] = random_int(this->random, 1, 12); // random normal class - item.data1[2] = difficulty + random_int(this->random, 0, 2); // special type - if ((item.data1[1] > 0x09) && (item.data1[2] > 0x04)) { - item.data1[2] = 0x04; // no special classes above 4 - } - item.data1[4] = 0x80; // untekked - if (item.data1[2] < 0x04) { - item.data1[4] |= random_int(this->random, 0, 40); // give a special - } - for (size_t x = 0, y = 0; (x < 5) && (y < 3); x++) { // percentages - if (random_int(this->random, 0, 10) == 1) { // 1/11 chance of getting each type of percentage - item.data1[6 + (y * 2)] = x + 1; - item.data1[7 + (y * 2)] = random_int(this->random, 0, 10) * 5; - y++; - } - } - break; - - case 0x01: // armor - item.data1[0] = 0x01; - item.data1[1] = 0x01; - item.data1[2] = (6 * difficulty) + random_int(this->random, 0, ((area / 2) + 2) - 1); // standard type based on difficulty and area - if (item.data1[2] > 0x17) { - item.data1[2] = 0x17; // no standard types above 0x17 - } - if (random_int(this->random, 0, 10) == 0) { // +/- - item.data1[4] = random_int(this->random, 0, 5); - item.data1[6] = random_int(this->random, 0, 2); - } - item.data1[5] = random_int(this->random, 0, 4); // slots - break; - - case 0x02: // shield - item.data1[0] = 0x01; - item.data1[1] = 0x02; - item.data1[2] = (5 * difficulty) + random_int(this->random, 0, ((area / 2) + 2) - 1); // standard type based on difficulty and area - if (item.data1[2] > 0x14) { - item.data1[2] = 0x14; // no standard types above 0x14 - } - if (random_int(this->random, 0, 10) == 0) { // +/- - item.data1[4] = random_int(this->random, 0, 5); - item.data1[6] = random_int(this->random, 0, 5); - } - break; - - case 0x03: { // unit - const auto& type_table = this->data->unit_types.at(difficulty); - uint8_t type = type_table[random_int(this->random, 0, type_table.size() - 1)]; - if (type == 0xFF) { - throw out_of_range("no item dropped"); // 0xFF -> no item drops - } - item.data1[0] = 0x01; - item.data1[1] = 0x03; - item.data1[2] = type; - break; - } - } - break; - - case 0x02: // technique - item.data1[0] = 0x03; - item.data1[1] = 0x02; - item.data1[4] = random_int(this->random, 0, 18); // tech type - if ((item.data1[4] != 14) && (item.data1[4] != 17)) { // if not ryuker or reverser, give it a level - if (item.data1[4] == 16) { // if not anti, give it a level between 1 and 30 - if (area > 3) { - item.data1[2] = difficulty + random_int(this->random, 0, ((area - 1) / 2) - 1); - } else { - item.data1[2] = difficulty; - } - if (item.data1[2] > 6) { - item.data1[2] = 6; - } - } else { - item.data1[2] = (5 * difficulty) + random_int(this->random, 0, ((area * 3) / 2) - 1); // else between 1 and 7 - } - } - break; - - case 0x03: // scape doll - item.data1[0] = 0x03; - item.data1[1] = 0x09; - item.data1[2] = 0x00; - break; - - case 0x04: // grinder - item.data1[0] = 0x03; - item.data1[1] = 0x0A; - item.data1[2] = random_int(this->random, 0, 2); // mono, di, tri - break; - - case 0x05: // consumable - item.data1[0] = 0x03; - item.data1[5] = 0x01; - switch (random_int(this->random, 0, 2)) { - case 0: // antidote / antiparalysis - item.data1[1] = 6; - item.data1[2] = random_int(this->random, 0, 1); - break; - - case 1: // telepipe / trap vision - item.data1[1] = 7 + random_int(this->random, 0, 1); - break; - - case 2: // sol / moon / star atomizer - item.data1[1] = 3 + random_int(this->random, 0, 2); - break; - } - break; - - case 0x06: // consumable - item.data1[0] = 0x03; - item.data1[5] = 0x01; - item.data1[1] = random_int(this->random, 0, 1); // mate or fluid - if (difficulty == 0) { - item.data1[2] = random_int(this->random, 0, 1); // only mono and di on normal - } else if (difficulty == 3) { - item.data1[2] = random_int(this->random, 1, 2); // only di and tri on ultimate - } else { - item.data1[2] = random_int(this->random, 0, 2); // else, any of the three - } - break; - - case 0x07: // meseta - item.data1[0] = 0x04; - item.data2d = (90 * difficulty) + (random_int(this->random, 1, 20) * (area * 2)); // meseta amount - break; - - default: - throw out_of_range("no item created"); - } - - return item; -} - - -ItemData CommonItemCreator::create_shop_item(uint8_t difficulty, - uint8_t item_type) const { - static const uint8_t max_percentages[4] = {20, 35, 45, 50}; - static const uint8_t max_quantity[4] = { 1, 1, 2, 2}; - static const uint8_t max_tech_level[4] = { 8, 15, 23, 30}; - static const uint8_t max_anti_level[4] = { 2, 4, 6, 7}; - - ItemData item; - - item.data1[0] = item_type; - while (item.data1[0] == 2) { - item.data1[0] = rand() % 3; - } - switch (item.data1[0]) { - case 0: { // weapon - item.data1[1] = (rand() % 12) + 1; - if (item.data1[1] > 9) { - item.data1[2] = difficulty; - } else { - item.data1[2] = (rand() & 1) + difficulty; - } - - item.data1[3] = rand() % 11; - item.data1[4] = rand() % 11; - - size_t num_percentages = 0; - for (size_t x = 0; (x < 5) && (num_percentages < 3); x++) { - if ((rand() % 4) == 1) { - item.data1[(num_percentages * 2) + 6] = x; - item.data1[(num_percentages * 2) + 7] = rand() % (max_percentages[difficulty] + 1); - num_percentages++; - } - } - break; - } - - case 1: // armor - item.data1[1] = 0; - while (item.data1[1] == 0) { - item.data1[1] = rand() & 3; - } - switch (item.data1[1]) { - case 1: - item.data1[2] = (rand() % 6) + (difficulty * 6); - item.data1[5] = rand() % 5; - break; - case 2: - item.data2[2] = (rand() % 6) + (difficulty * 5); - *reinterpret_cast(&item.data1[6]) = (rand() % 9) - 4; - *reinterpret_cast(&item.data1[9]) = (rand() % 9) - 4; - break; - case 3: - item.data2[2] = rand() % 0x3B; - *reinterpret_cast(&item.data1[7]) = (rand() % 5) - 4; - break; - } - break; - - case 3: // tool - item.data1[1] = rand() % 12; - switch (item.data1[1]) { - case 0: - case 1: - if (difficulty == 0) { - item.data1[2] = 0; - } else if (difficulty == 1) { - item.data1[2] = rand() % 2; - } else if (difficulty == 2) { - item.data1[2] = (rand() % 2) + 1; - } else if (difficulty == 3) { - item.data1[2] = 2; - } - break; - - case 6: - item.data1[2] = rand() % 2; - break; - - case 10: - item.data1[2] = rand() % 3; - break; - - case 11: - item.data1[2] = rand() % 7; - break; - } - - switch (item.data1[1]) { - case 2: - item.data1[4] = rand() % 19; - switch (item.data1[4]) { - case 14: - case 17: - item.data1[2] = 0; // reverser & ryuker always level 1 - break; - case 16: - item.data1[2] = rand() % max_anti_level[difficulty]; - break; - default: - item.data1[2] = rand() % max_tech_level[difficulty]; - } - break; - case 0: - case 1: - case 3: - case 4: - case 5: - case 6: - case 7: - case 8: - case 16: - item.data1[5] = rand() % (max_quantity[difficulty] + 1); - break; - } - } - - return item; -} diff --git a/src/Items.hh b/src/Items.hh index 19418fe9..57b7e9d6 100644 --- a/src/Items.hh +++ b/src/Items.hh @@ -11,28 +11,3 @@ void player_use_item(std::shared_ptr c, size_t item_index); - -struct CommonItemData { - std::vector enemy_item_categories; - std::vector box_item_categories; - std::vector> unit_types; - - CommonItemData( - std::vector&& enemy_item_categories, - std::vector&& box_item_categories, - std::vector>&& unit_types); -}; - -struct CommonItemCreator { - std::shared_ptr data; - std::shared_ptr random; - - CommonItemCreator( - std::shared_ptr data, - std::shared_ptr random); - - int32_t decide_item_type(bool is_box) const; - ItemData create_drop_item(bool is_box, Episode episode, uint8_t difficulty, - uint8_t area, uint8_t section_id) const; - ItemData create_shop_item(uint8_t difficulty, uint8_t shop_type) const; -}; diff --git a/src/Lobby.hh b/src/Lobby.hh index d5e6c365..f4fc54bb 100644 --- a/src/Lobby.hh +++ b/src/Lobby.hh @@ -13,7 +13,7 @@ #include "Client.hh" #include "Episode3/BattleRecord.hh" #include "Episode3/Server.hh" -#include "Items.hh" +#include "ItemCreator.hh" #include "Map.hh" #include "Player.hh" #include "Quest.hh" @@ -75,7 +75,7 @@ struct Lobby : public std::enable_shared_from_this { // This seed is also sent to the client for rare enemy generation uint32_t random_seed; std::shared_ptr random; - std::shared_ptr common_item_creator; + std::shared_ptr item_creator; // Ep3 stuff // There are three kinds of Episode 3 games. All of these types have the flag diff --git a/src/Main.cc b/src/Main.cc index 589a1820..b85a90e7 100644 --- a/src/Main.cc +++ b/src/Main.cc @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -80,17 +81,6 @@ void populate_state_from_config(shared_ptr s, s->set_port_configuration(parse_port_configuration(d.at("PortConfiguration"))); - { - auto enemy_categories = parse_int_vector(d.at("CommonItemDropRates-Enemy")); - auto box_categories = parse_int_vector(d.at("CommonItemDropRates-Box")); - vector> unit_types; - for (const auto& item : d.at("CommonUnitTypes")->as_list()) { - unit_types.emplace_back(parse_int_vector(item)); - } - s->common_item_data.reset(new CommonItemData( - move(enemy_categories), move(box_categories), move(unit_types))); - } - auto local_address_str = d.at("LocalAddress")->as_string(); try { s->local_address = s->all_addresses.at(local_address_str); @@ -388,6 +378,8 @@ enum class Behavior { DECODE_QUEST_FILE, DECODE_SJIS, EXTRACT_GSL, + FORMAT_ITEMRT_ENTRY, + FORMAT_ITEMRT_REL, SHOW_EP3_DATA, PARSE_OBJECT_GRAPH, REPLAY_LOG, @@ -406,6 +398,8 @@ static bool behavior_takes_input_filename(Behavior b) { (b == Behavior::DECRYPT_TRIVIAL_DATA) || (b == Behavior::DECODE_QUEST_FILE) || (b == Behavior::DECODE_SJIS) || + (b == Behavior::FORMAT_ITEMRT_ENTRY) || + (b == Behavior::FORMAT_ITEMRT_REL) || (b == Behavior::EXTRACT_GSL) || (b == Behavior::PARSE_OBJECT_GRAPH) || (b == Behavior::REPLAY_LOG); @@ -533,6 +527,10 @@ int main(int argc, char** argv) { quest_file_type = QuestFileFormat::QST; } else if (!strcmp(argv[x], "cat-client")) { behavior = Behavior::CAT_CLIENT; + } else if (!strcmp(argv[x], "format-itemrt-entry")) { + behavior = Behavior::FORMAT_ITEMRT_ENTRY; + } else if (!strcmp(argv[x], "format-itemrt-rel")) { + behavior = Behavior::FORMAT_ITEMRT_REL; } else if (!strcmp(argv[x], "show-ep3-data")) { behavior = Behavior::SHOW_EP3_DATA; } else if (!strcmp(argv[x], "parse-object-graph")) { @@ -873,6 +871,111 @@ int main(int argc, char** argv) { break; } + case Behavior::FORMAT_ITEMRT_ENTRY: { + string data = read_input_data(); + if (data.size() < sizeof(RareItemSet::Table)) { + throw runtime_error("input data too small"); + } + const auto& table = *reinterpret_cast(data.data()); + + auto format_drop = +[](const RareItemSet::Table::Drop& r) -> string { + ItemData item; + item.data1[0] = r.item_code[0]; + item.data1[1] = r.item_code[1]; + item.data1[2] = r.item_code[2]; + string name = item.name(false); + + uint32_t expanded_probability = RareItemSet::expand_rate(r.probability); + auto frac = reduce_fraction(expanded_probability, 0x100000000); + return string_printf("(%02hhX => %08" PRIX32 " => %" PRIu64 "/%" PRIu64 ") %02hhX%02hhX%02hhX (%s)", + r.probability, expanded_probability, frac.first, frac.second, r.item_code[0], r.item_code[1], r.item_code[2], name.c_str()); + }; + + fprintf(stdout, "Monster rares:\n"); + for (size_t z = 0; z < 0x65; z++) { + const auto& r = table.monster_rares[z]; + if (r.item_code[0] == 0 && r.item_code[1] == 0 && r.item_code[2] == 0) { + continue; + } + string s = format_drop(r); + fprintf(stdout, " %02zX: %s\n", z, s.c_str()); + } + + fprintf(stdout, "Box rares:\n"); + for (size_t z = 0; z < 0x1E; z++) { + const auto& r = table.box_rares[z]; + if (r.item_code[0] == 0 && r.item_code[1] == 0 && r.item_code[2] == 0) { + continue; + } + string s = format_drop(r); + fprintf(stdout, " %02zX: area %02hhX %s\n", z, table.box_areas[z], s.c_str()); + } + break; + } + + case Behavior::FORMAT_ITEMRT_REL: { + shared_ptr data(new string(read_input_data())); + RELRareItemSet rs(data); + + auto format_drop = +[](const RareItemSet::Table::Drop& r) -> string { + ItemData item; + item.data1[0] = r.item_code[0]; + item.data1[1] = r.item_code[1]; + item.data1[2] = r.item_code[2]; + string name = item.name(false); + + uint32_t expanded_probability = RareItemSet::expand_rate(r.probability); + auto frac = reduce_fraction(expanded_probability, 0x100000000); + return string_printf("(%02hhX => %08" PRIX32 " => %" PRIu64 "/%" PRIu64 ") %02hhX%02hhX%02hhX (%s)", + r.probability, expanded_probability, frac.first, frac.second, r.item_code[0], r.item_code[1], r.item_code[2], name.c_str()); + }; + + auto print_collection = [&](GameMode mode, Episode episode, uint8_t difficulty, uint8_t section_id) -> void { + const auto& table = rs.get_table(episode, mode, difficulty, section_id); + + string secid_name = name_for_section_id(section_id); + fprintf(stdout, "%s %s %s %s\n", + name_for_mode(mode), + name_for_episode(episode), + name_for_difficulty(difficulty), + secid_name.c_str()); + + fprintf(stdout, " Monster rares:\n"); + for (size_t z = 0; z < 0x65; z++) { + const auto& r = table.monster_rares[z]; + if (r.item_code[0] == 0 && r.item_code[1] == 0 && r.item_code[2] == 0) { + continue; + } + string s = format_drop(r); + fprintf(stdout, " %02zX: %s\n", z, s.c_str()); + } + + fprintf(stdout, " Box rares:\n"); + for (size_t z = 0; z < 0x1E; z++) { + const auto& r = table.box_rares[z]; + if (r.item_code[0] == 0 && r.item_code[1] == 0 && r.item_code[2] == 0) { + continue; + } + string s = format_drop(r); + fprintf(stdout, " %02zX: area %02hhX %s\n", z, table.box_areas[z], s.c_str()); + } + }; + + static const vector episodes = { + Episode::EP1, + Episode::EP2, + Episode::EP4, + }; + for (Episode episode : episodes) { + for (uint8_t difficulty = 0; difficulty < 4; difficulty++) { + for (uint8_t section_id = 0; section_id < 10; section_id++) { + print_collection(GameMode::NORMAL, episode, difficulty, section_id); + } + } + } + break; + } + case Behavior::SHOW_EP3_DATA: { config_log.info("Collecting Episode 3 data"); Episode3::DataIndex index("system/ep3", Episode3::BehaviorFlag::LOAD_CARD_TEXT); @@ -976,10 +1079,46 @@ int main(int argc, char** argv) { state->level_table.reset(new LevelTable( state->load_bb_file("PlyLevelTbl.prs"), true)); - config_log.info("Loading rare table"); - state->rare_item_set.reset(new RareItemSet( + config_log.info("Loading rare item table"); + state->rare_item_set.reset(new RELRareItemSet( state->load_bb_file("ItemRT.rel"))); + // Note: These files don't exist in BB, so we use the GC versions of them + // instead. This doesn't include Episode 4 of course, so we use Episode 1 + // parameters for Episode 4 implicitly. + { + config_log.info("Loading common item tables"); + shared_ptr pt_data(new string(load_file( + "system/blueburst/ItemPT_GC.gsl"))); + state->common_item_set.reset(new CommonItemSet(pt_data)); + + shared_ptr armor_data(new string(load_file( + "system/blueburst/ArmorRandom_GC.rel"))); + state->armor_random_set.reset(new ArmorRandomSet(armor_data)); + + shared_ptr tool_data(new string(load_file( + "system/blueburst/ToolRandom_GC.rel"))); + state->tool_random_set.reset(new ToolRandomSet(tool_data)); + + const char* filenames[4] = { + "system/blueburst/WeaponRandomNormal_GC.rel", + "system/blueburst/WeaponRandomHard_GC.rel", + "system/blueburst/WeaponRandomVeryHard_GC.rel", + "system/blueburst/WeaponRandomUltimate_GC.rel", + }; + for (size_t z = 0; z < 4; z++) { + shared_ptr weapon_data(new string(load_file(filenames[z]))); + state->weapon_random_sets[z].reset(new WeaponRandomSet(weapon_data)); + } + } + + config_log.info("Loading item definition table"); + { + shared_ptr data(new string(prs_decompress(load_file( + "system/blueburst/ItemPMT.prs")))); + state->item_parameter_table.reset(new ItemParameterTable(data)); + } + config_log.info("Collecting Episode 3 data"); state->ep3_data_index.reset(new Episode3::DataIndex( "system/ep3", state->ep3_behavior_flags)); diff --git a/src/Player.cc b/src/Player.cc index 6fc3df5d..c98573b4 100644 --- a/src/Player.cc +++ b/src/Player.cc @@ -8,6 +8,7 @@ #include #include +#include "ItemData.hh" #include "FileContentsCache.hh" #include "Loggers.hh" #include "StaticGameData.hh" @@ -640,35 +641,6 @@ void PlayerLobbyDataBB::clear() { //////////////////////////////////////////////////////////////////////////////// -constexpr uint32_t MESETA_IDENTIFIER = 0x00040000; - -ItemData::ItemData() { - this->clear(); -} - -void ItemData::clear() { - this->data1d[0] = 0; - this->data1d[1] = 0; - this->data1d[2] = 0; - this->id = 0xFFFFFFFF; - this->data2d = 0; -} - -uint32_t ItemData::primary_identifier() const { - // The game treats any item starting with 04 as Meseta, and ignores the rest - // of data1 (the value is in data2) - if (this->data1[0] == 0x04) { - return 0x00040000; - } - if (this->data1[0] == 0x03 && this->data1[1] == 0x02) { - return 0x00030200; // Tech disk (data1[2] is level, so omit it) - } else if (this->data1[0] == 0x02) { - return 0x00020000 | (this->data1[1] << 8); // Mag - } else { - return (this->data1[0] << 16) | (this->data1[1] << 8) | this->data1[2]; - } -} - PlayerInventoryItem::PlayerInventoryItem() { this->clear(); } @@ -688,7 +660,7 @@ PlayerBankItem::PlayerBankItem() { PlayerBankItem::PlayerBankItem(const PlayerInventoryItem& src) : data(src.data), - amount(stack_size_for_item(this->data)), + amount(this->data.stack_size()), show_flags(1) { } void PlayerBankItem::clear() { @@ -723,7 +695,7 @@ void SavedPlayerDataBB::add_item(const PlayerInventoryItem& item) { } // Handle combinable items - size_t combine_max = stack_size_for_item(item.data); + size_t combine_max = item.data.max_stack_size(); if (combine_max > 1) { // Get the item index if there's already a stack of the same item in the // player's inventory @@ -764,7 +736,7 @@ void PlayerBank::add_item(const PlayerBankItem& item) { return; } - size_t combine_max = stack_size_for_item(item.data); + size_t combine_max = item.data.max_stack_size(); if (combine_max > 1) { size_t y; for (y = 0; y < this->num_items; y++) { @@ -818,7 +790,7 @@ PlayerInventoryItem SavedPlayerDataBB::remove_item( // then create a new item and reduce the amount of the existing stack. Note // that passing amount == 0 means to remove the entire stack, so this only // applies if amount is nonzero. - if (amount && (stack_size_for_item(inventory_item.data) > 1) && + if (amount && (inventory_item.data.stack_size() > 1) && (amount < inventory_item.data.data1[5])) { ret = inventory_item; ret.data.data1[5] = amount; @@ -855,7 +827,7 @@ PlayerBankItem PlayerBank::remove_item(uint32_t item_id, uint32_t amount) { size_t index = this->find_item(item_id); auto& bank_item = this->items[index]; - if (amount && (stack_size_for_item(bank_item.data) > 1) && + if (amount && (bank_item.data.stack_size() > 1) && (amount < bank_item.data.data1[5])) { ret = bank_item; ret.data.data1[5] = amount; @@ -897,7 +869,7 @@ void SavedPlayerDataBB::print_inventory(FILE* stream) const { fprintf(stream, "[PlayerInventory] %hhu items\n", this->inventory.num_items); for (size_t x = 0; x < this->inventory.num_items; x++) { const auto& item = this->inventory.items[x]; - auto name = name_for_item(item.data, false); + auto name = item.data.name(false); fprintf(stream, "[PlayerInventory] %zu (%08" PRIX32 "): %06" PRIX32 " (%s)\n", x, item.data.id.load(), item.data.primary_identifier(), name.c_str()); } diff --git a/src/Player.hh b/src/Player.hh index 20f8bb89..6dd093b4 100644 --- a/src/Player.hh +++ b/src/Player.hh @@ -9,31 +9,13 @@ #include #include "LevelTable.hh" +#include "ItemData.hh" #include "Version.hh" #include "Text.hh" #include "Episode3/DataIndex.hh" -struct ItemData { // 0x14 bytes - union { - uint8_t data1[12]; - le_uint16_t data1w[6]; - le_uint32_t data1d[3]; - } __attribute__((packed)); - le_uint32_t id; - union { - uint8_t data2[4]; - le_uint16_t data2w[2]; - le_uint32_t data2d; - } __attribute__((packed)); - - ItemData(); - void clear(); - - uint32_t primary_identifier() const; -} __attribute__((packed)); - struct PlayerBankItem; struct PlayerInventoryItem { // 0x1C bytes diff --git a/src/ProxyCommands.cc b/src/ProxyCommands.cc index 920a75fe..49276331 100644 --- a/src/ProxyCommands.cc +++ b/src/ProxyCommands.cc @@ -956,9 +956,9 @@ static HandlerResult S_6x(shared_ptr, sizeof(G_EnemyDropItemRequest_PC_V3_BB_6x60)); session.next_drop_item.data.id = session.next_item_id++; send_drop_item(session.server_channel, session.next_drop_item.data, - true, cmd.area, cmd.x, cmd.z, cmd.request_id); + true, cmd.area, cmd.x, cmd.z, cmd.enemy_id); send_drop_item(session.client_channel, session.next_drop_item.data, - true, cmd.area, cmd.x, cmd.z, cmd.request_id); + true, cmd.area, cmd.x, cmd.z, cmd.enemy_id); session.next_drop_item.clear(); return HandlerResult::Type::SUPPRESS; diff --git a/src/RareItemSet.cc b/src/RareItemSet.cc index 74f9628c..05a34aa4 100644 --- a/src/RareItemSet.cc +++ b/src/RareItemSet.cc @@ -3,20 +3,66 @@ #include #include +#include "StaticGameData.hh" + using namespace std; -RareItemSet::RareItemSet(shared_ptr data) : data(data) { - // TODO: Actually parse the GSL here instead of treating it as a blob +uint32_t RareItemSet::expand_rate(uint8_t pc) { + int8_t shift = ((pc >> 3) & 0x1F) - 4; + if (shift < 0) { + shift = 0; + } + return ((2 << shift) * ((pc & 7) + 7)); +} + +bool RareItemSet::sample(mt19937& random, uint8_t pc) { + return (random() < RareItemSet::expand_rate(pc)); +} + + + +GSLRareItemSet::GSLRareItemSet(shared_ptr data, bool is_big_endian) + : gsl(data, is_big_endian) { } + +const GSLRareItemSet::Table& GSLRareItemSet::get_table( + Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) const { + if (difficulty > 3) { + throw logic_error("incorrect difficulty"); + } + if (secid > 10) { + throw logic_error("incorrect section id"); + } + + if ((episode != Episode::EP1) && (episode != Episode::EP2)) { + throw runtime_error("invalid episode"); + } + + string filename = string_printf("ItemRT%s%s%c%1d.rel", + ((mode == GameMode::CHALLENGE) ? "c" : ""), + ((episode == Episode::EP2) ? "l" : ""), + tolower(abbreviation_for_difficulty(difficulty)), // One of "nhvu" + secid); + auto entry = this->gsl.get(filename); + if (entry.second < sizeof(Table)) { + throw runtime_error(string_printf("table %s is too small", filename.c_str())); + } + return *reinterpret_cast(entry.first); +} + + + +RELRareItemSet::RELRareItemSet(shared_ptr data) : data(data) { if (this->data->size() != sizeof(Table) * 10 * 4 * 3) { throw runtime_error("data file size is incorrect"); } - this->tables = reinterpret_cast(this->data->data()); } -const RareItemSet::Table& RareItemSet::get_table( - Episode episode, uint8_t difficulty, uint8_t secid) const { +const RELRareItemSet::Table& RELRareItemSet::get_table( + Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) const { + (void)mode; // TODO: Shouldn't we check for challenge mode somewhere? + if (difficulty > 3) { throw logic_error("incorrect difficulty"); } @@ -39,14 +85,6 @@ const RareItemSet::Table& RareItemSet::get_table( throw invalid_argument("incorrect episode"); } - return this->tables[(ep_index * 10 * 4) + (difficulty * 10) + secid]; -} - -bool RareItemSet::sample(mt19937& random, uint8_t pc) { - int8_t shift = ((pc >> 3) & 0x1F) - 4; - if (shift < 0) { - shift = 0; - } - uint32_t rate = ((2 << shift) * ((pc & 7) + 7)); - return (random() < rate); + const auto* tables = reinterpret_cast(this->data->data()); + return tables[(ep_index * 10 * 4) + (difficulty * 10) + secid]; } diff --git a/src/RareItemSet.hh b/src/RareItemSet.hh index 3342b8fb..d7acf1c3 100644 --- a/src/RareItemSet.hh +++ b/src/RareItemSet.hh @@ -7,34 +7,70 @@ #include #include "StaticGameData.hh" +#include "GSLArchive.hh" class RareItemSet { public: struct Table { + // 0x280 in size; describes one difficulty, section ID, and episode // TODO: It looks like this structure can actually vary. We see the offsets // 0194 and 01B2 in the unused section, along with the value 1E (number of // box rares). In PSOGC, these all appear to be the same size/format, but // that's probably not strictly required to be the case. - // 0x280 in size; describes one difficulty, section ID, and episode struct Drop { uint8_t probability; uint8_t item_code[3]; } __attribute__((packed)); - Drop monster_rares[0x65]; // 0000 - 0194 in file - uint8_t box_areas[0x1E]; // 0194 - 01B2 in file - Drop box_rares[0x1E]; // 01B2 - 022A in file - uint8_t unused[0x56]; + /* 0000 */ parray monster_rares; + /* 0194 */ parray box_areas; + /* 01B2 */ parray box_rares; + /* 022A */ parray unknown_a1; + /* 022C */ be_uint32_t monster_rares_offset; // == 0x0000 + /* 0230 */ be_uint32_t box_count; // == 0x1E + /* 0234 */ be_uint32_t box_areas_offset; // == 0x0194 + /* 0238 */ be_uint32_t box_rares_offset; // == 0x01B2 + /* 023C */ be_uint32_t unused_offset1; + /* 0240 */ parray unknown_a2; + /* 0260 */ be_uint32_t unknown_a2_offset; + /* 0264 */ be_uint32_t unknown_a2_count; + /* 0268 */ be_uint32_t unknown_a3; + /* 026C */ be_uint32_t unknown_a4; + /* 0270 */ be_uint32_t offset_table_offset; // == 0x022C + /* 0274 */ parray unknown_a5; + /* 0280 */ } __attribute__((packed)); - RareItemSet(std::shared_ptr data); + virtual ~RareItemSet() = default; - const Table& get_table(Episode episode, uint8_t difficulty, uint8_t secid) const; + virtual const Table& get_table(Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) const = 0; static bool sample(std::mt19937& rand, uint8_t probability); + static uint32_t expand_rate(uint8_t pc); + +protected: + RareItemSet() = default; +}; + + + +class GSLRareItemSet : public RareItemSet { +public: + GSLRareItemSet(std::shared_ptr data, bool is_big_endian); + virtual ~GSLRareItemSet() = default; + virtual const Table& get_table(Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) const; + +private: + GSLArchive gsl; +}; + +class RELRareItemSet : public RareItemSet { +public: + RELRareItemSet(std::shared_ptr data); + virtual ~RELRareItemSet() = default; + virtual const Table& get_table(Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) const; private: std::shared_ptr data; - const Table* tables; }; diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index f66ff141..4bb91bad 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -11,16 +11,17 @@ #include #include -#include "Loggers.hh" #include "ChatCommands.hh" +#include "Episode3/Tournament.hh" #include "FileContentsCache.hh" +#include "ItemCreator.hh" +#include "Loggers.hh" #include "ProxyServer.hh" #include "PSOProtocol.hh" #include "ReceiveSubcommands.hh" #include "SendCommands.hh" #include "StaticGameData.hh" #include "Text.hh" -#include "Episode3/Tournament.hh" using namespace std; @@ -3189,8 +3190,21 @@ shared_ptr create_game_generic( game->battle_player = battle_player; battle_player->set_lobby(game); } - game->common_item_creator.reset(new CommonItemCreator( - s->common_item_data, game->random)); + if (game->version == GameVersion::BB) { + // TODO: Use appropriate restrictions here if in battle mode + game->item_creator.reset(new ItemCreator( + s->common_item_set, + s->rare_item_set, + s->armor_random_set, + s->tool_random_set, + s->weapon_random_sets.at(game->difficulty), + s->item_parameter_table, + game->episode, + game->mode, + game->difficulty, + game->section_id, + game->random_seed)); + } game->event = Lobby::game_event_for_lobby_event(current_lobby->event); game->block = 0xFF; game->max_clients = (game->flags & Lobby::Flag::IS_SPECTATOR_TEAM) ? 12 : 4; diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index a6b727bd..e0b5a4ce 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -486,12 +486,12 @@ static void on_player_drop_item(shared_ptr, auto item = c->game_data.player()->remove_item(cmd.item_id, 0, c->version() != GameVersion::BB); l->add_item(item, cmd.area, cmd.x, cmd.z); - auto name = name_for_item(item.data, false); + auto name = item.data.name(false); l->log.info("Player %hu dropped item %08" PRIX32 " (%s) at %hu:(%g, %g)", cmd.header.client_id.load(), cmd.item_id.load(), name.c_str(), cmd.area.load(), cmd.x.load(), cmd.z.load()); if (c->options.debug) { - string name = name_for_item(item.data, true); + string name = item.data.name(true); send_text_message_printf(c, "$C5Items: drop %08" PRIX32 "\n%s", cmd.item_id.load(), name.c_str()); } @@ -523,11 +523,11 @@ static void on_create_inventory_item(shared_ptr, item.data = cmd.item; c->game_data.player()->add_item(item); - auto name = name_for_item(item.data, false); + auto name = item.data.name(false); l->log.info("Player %hu created inventory item %08" PRIX32 " (%s)", cmd.header.client_id.load(), cmd.item.id.load(), name.c_str()); if (c->options.debug) { - string name = name_for_item(item.data, true); + string name = item.data.name(true); send_text_message_printf(c, "$C5Items: create %08" PRIX32 "\n%s", cmd.item.id.load(), name.c_str()); } @@ -560,12 +560,12 @@ static void on_drop_partial_stack(shared_ptr, item.data = cmd.data; l->add_item(item, cmd.area, cmd.x, cmd.z); - auto name = name_for_item(item.data, false); + auto name = item.data.name(false); l->log.info("Player %hu split stack to create ground item %08" PRIX32 " (%s) at %hu:(%g, %g)", cmd.header.client_id.load(), item.data.id.load(), name.c_str(), cmd.area.load(), cmd.x.load(), cmd.z.load()); if (c->options.debug) { - string name = name_for_item(item.data, true); + string name = item.data.name(true); send_text_message_printf(c, "$C5Items: split %08" PRIX32 "\n%s", item.data.id.load(), name.c_str()); } @@ -605,12 +605,12 @@ static void on_drop_partial_stack_bb(shared_ptr, l->add_item(item, cmd.area, cmd.x, cmd.z); - auto name = name_for_item(item.data, false); + auto name = item.data.name(false); l->log.info("Player %hu split stack %08" PRIX32 " (removed: %s) at %hu:(%g, %g)", cmd.header.client_id.load(), cmd.item_id.load(), name.c_str(), cmd.area.load(), cmd.x.load(), cmd.z.load()); if (c->options.debug) { - string name = name_for_item(item.data, true); + string name = item.data.name(true); send_text_message_printf(c, "$C5Items: split/BB %08" PRIX32 "\n%s", cmd.item_id.load(), name.c_str()); } @@ -642,11 +642,11 @@ static void on_buy_shop_item(shared_ptr, item.data = cmd.item; c->game_data.player()->add_item(item); - auto name = name_for_item(item.data, false); + auto name = item.data.name(false); l->log.info("Player %hu bought item %08" PRIX32 " (%s) from shop", cmd.header.client_id.load(), item.data.id.load(), name.c_str()); if (c->options.debug) { - string name = name_for_item(item.data, true); + string name = item.data.name(true); send_text_message_printf(c, "$C5Items: buy %08" PRIX32 "\n%s", item.data.id.load(), name.c_str()); } @@ -676,11 +676,11 @@ static void on_box_or_enemy_item_drop(shared_ptr, item.data = cmd.data; l->add_item(item, cmd.area, cmd.x, cmd.z); - auto name = name_for_item(item.data, false); + auto name = item.data.name(false); l->log.info("Leader created ground item %08" PRIX32 " (%s) at %hhu:(%g, %g)", item.data.id.load(), name.c_str(), cmd.area, cmd.x.load(), cmd.z.load()); if (c->options.debug) { - string name = name_for_item(item.data, true); + string name = item.data.name(true); send_text_message_printf(c, "$C5Items: drop %08" PRIX32 "\n%s", item.data.id.load(), name.c_str()); } @@ -712,11 +712,11 @@ static void on_pick_up_item(shared_ptr, auto item = l->remove_item(cmd.item_id); effective_c->game_data.player()->add_item(item); - auto name = name_for_item(item.data, false); + auto name = item.data.name(false); l->log.info("Player %hu picked up %08" PRIX32 " (%s)", cmd.header.client_id.load(), cmd.item_id.load(), name.c_str()); if (c->options.debug) { - string name = name_for_item(item.data, true); + string name = item.data.name(true); send_text_message_printf(c, "$C5Items: pick %08" PRIX32 "\n%s", cmd.item_id.load(), name.c_str()); } @@ -744,11 +744,11 @@ static void on_pick_up_item_request(shared_ptr, auto item = l->remove_item(cmd.item_id); c->game_data.player()->add_item(item); - auto name = name_for_item(item.data, false); + auto name = item.data.name(false); l->log.info("Player %hu picked up %08" PRIX32 " (%s)", cmd.header.client_id.load(), cmd.item_id.load(), name.c_str()); if (c->options.debug) { - string name = name_for_item(item.data, true); + string name = item.data.name(true); send_text_message_printf(c, "$C5Items: pick/BB %08" PRIX32 "\n%s", cmd.item_id.load(), name.c_str()); } @@ -802,8 +802,8 @@ static void on_use_item(shared_ptr, // Note: We do this weird scoping thing because player_use_item will // likely delete the item, which will break the reference here. const auto& item = c->game_data.player()->inventory.items[index].data; - name = name_for_item(item, false); - colored_name = name_for_item(item, true); + name = item.name(false); + colored_name = item.name(true); } player_use_item(c, index); @@ -825,28 +825,32 @@ static void on_open_shop_bb_or_ep3_battle_subs(shared_ptr s, if (l->is_ep3()) { on_ep3_battle_subs(s, l, c, command, flag, data); - } else if (!l->common_item_creator.get()) { + } else if (!l->item_creator.get()) { throw runtime_error("received shop subcommand without item creator present"); } else { const auto& cmd = check_size_sc(data, 0x08); if ((l->version == GameVersion::BB) && l->is_game()) { - size_t num_items = 9 + (rand() % 4); - c->game_data.shop_contents.clear(); - while (c->game_data.shop_contents.size() < num_items) { - ItemData item_data; - if (cmd.shop_type == 0) { // tool shop - item_data = l->common_item_creator->create_shop_item(l->difficulty, 3); - } else if (cmd.shop_type == 1) { // weapon shop - item_data = l->common_item_creator->create_shop_item(l->difficulty, 0); - } else if (cmd.shop_type == 2) { // guards shop - item_data = l->common_item_creator->create_shop_item(l->difficulty, 1); - } else { // unknown shop... just leave it blank I guess - break; - } + if (!l->item_creator) { + throw logic_error("item creator missing from BB game"); + } - item_data.id = l->generate_item_id(c->lobby_client_id); - c->game_data.shop_contents.emplace_back(item_data); + size_t level = c->game_data.player()->disp.level + 1; + switch (cmd.shop_type) { + case 0: + c->game_data.shop_contents = l->item_creator->generate_tool_shop_contents(level); + break; + case 1: + c->game_data.shop_contents = l->item_creator->generate_weapon_shop_contents(level); + break; + case 2: + c->game_data.shop_contents = l->item_creator->generate_armor_shop_contents(level); + break; + default: + throw runtime_error("invalid shop type"); + } + for (auto& item : c->game_data.shop_contents) { + item.id = l->generate_item_id(c->lobby_client_id); } send_shop(c, cmd.shop_type); @@ -946,7 +950,6 @@ static void on_sort_inventory_bb(shared_ptr, // EXP/Drop Item commands static bool drop_item( - std::shared_ptr s, std::shared_ptr l, int64_t enemy_id, uint8_t area, @@ -958,48 +961,15 @@ static bool drop_item( // If the game is BB, run the rare + common drop logic if (l->version == GameVersion::BB) { - if (!l->common_item_creator.get()) { + if (!l->item_creator.get()) { throw runtime_error("received box drop subcommand without item creator present"); } - const RareItemSet::Table::Drop* drop = nullptr; - if (s->rare_item_set) { - const auto& table = s->rare_item_set->get_table( - l->episode, l->difficulty, l->section_id); - if (enemy_id < 0) { - for (size_t z = 0; z < 30; z++) { - if (table.box_areas[z] != area) { - continue; - } - if (RareItemSet::sample(*l->random, table.box_rares[z].probability)) { - drop = &table.box_rares[z]; - break; - } - } - } else { - if ((enemy_id <= 0x65) && - RareItemSet::sample(*l->random, table.monster_rares[enemy_id].probability)) { - drop = &table.monster_rares[enemy_id]; - } - } - } - - if (drop) { - item.data.data1[0] = drop->item_code[0]; - item.data.data1[1] = drop->item_code[1]; - item.data.data1[2] = drop->item_code[2]; - // TODO: Add random percentages / modifiers - if (item.data.data1d[0] == 0) { - item.data.data1[4] |= 0x80; // Make it unidentified if it's a weapon - } + if (enemy_id >= 0) { + item.data = l->item_creator->on_monster_item_drop( + l->enemies.at(enemy_id).rt_index, area); } else { - try { - item.data = l->common_item_creator->create_drop_item( - false, l->episode, l->difficulty, area, l->section_id); - } catch (const out_of_range&) { - // create_common_item throws this when it doesn't want to make an item - return true; - } + item.data = l->item_creator->on_box_item_drop(area); } // If the game is not BB, forward the request to the leader instead of @@ -1017,7 +987,7 @@ static bool drop_item( return true; } -static void on_enemy_drop_item_request(shared_ptr s, +static void on_enemy_drop_item_request(shared_ptr, shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, const string& data) { if (!l->is_game()) { @@ -1027,12 +997,12 @@ static void on_enemy_drop_item_request(shared_ptr s, const auto& cmd = check_size_sc(data, sizeof(G_EnemyDropItemRequest_DC_6x60), sizeof(G_EnemyDropItemRequest_PC_V3_BB_6x60)); - if (!drop_item(s, l, cmd.enemy_id, cmd.area, cmd.x, cmd.z, cmd.request_id)) { + if (!drop_item(l, cmd.enemy_id, cmd.area, cmd.x, cmd.z, cmd.enemy_id)) { forward_subcommand(l, c, command, flag, data); } } -static void on_box_drop_item_request(shared_ptr s, +static void on_box_drop_item_request(shared_ptr, shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, const string& data) { if (!l->is_game()) { @@ -1040,7 +1010,7 @@ static void on_box_drop_item_request(shared_ptr s, } const auto& cmd = check_size_sc(data); - if (!drop_item(s, l, -1, cmd.area, cmd.x, cmd.z, cmd.request_id)) { + if (!drop_item(l, -1, cmd.area, cmd.x, cmd.z, cmd.request_id)) { forward_subcommand(l, c, command, flag, data); } } @@ -1215,7 +1185,7 @@ static void on_destroy_inventory_item(shared_ptr, if (l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED) { auto item = c->game_data.player()->remove_item( cmd.item_id, cmd.amount, c->version() != GameVersion::BB); - auto name = name_for_item(item.data, false); + auto name = item.data.name(false); l->log.info("Inventory item %hu:%08" PRIX32 " destroyed (%s)", cmd.header.client_id.load(), cmd.item_id.load(), name.c_str()); c->game_data.player()->print_inventory(stderr); @@ -1232,7 +1202,7 @@ static void on_destroy_ground_item(shared_ptr, } if (l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED) { auto item = l->remove_item(cmd.item_id); - auto name = name_for_item(item.data, false); + auto name = item.data.name(false); l->log.info("Ground item %08" PRIX32 " destroyed (%s)", cmd.item_id.load(), name.c_str()); forward_subcommand(l, c, command, flag, data); diff --git a/src/ServerShell.cc b/src/ServerShell.cc index f7524a36..0f014aac 100644 --- a/src/ServerShell.cc +++ b/src/ServerShell.cc @@ -765,14 +765,14 @@ session with ID 17205AE4, run the command `on 17205AE4 sc 1D 00 04 00`.\n\ if (command_name == "set-next-item") { session->next_drop_item = item; - string name = name_for_item(session->next_drop_item.data, true); + string name = session->next_drop_item.data.name(true); send_text_message(session->client_channel, u"$C7Next drop:\n" + decode_sjis(name)); } else { send_drop_stacked_item(session->client_channel, item.data, session->area, session->x, session->z); send_drop_stacked_item(session->server_channel, item.data, session->area, session->x, session->z); - string name = name_for_item(item.data, true); + string name = item.data.name(true); send_text_message(session->client_channel, u"$C7Item created:\n" + decode_sjis(name)); } diff --git a/src/ServerState.hh b/src/ServerState.hh index 3b308572..b24f6a17 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -20,6 +20,8 @@ #include "Lobby.hh" #include "Menu.hh" #include "Quest.hh" +#include "CommonItemSet.hh" +#include "ItemParameterTable.hh" @@ -64,9 +66,13 @@ struct ServerState { std::shared_ptr quest_index; std::shared_ptr level_table; std::shared_ptr battle_params; - std::shared_ptr common_item_data; std::shared_ptr bb_data_gsl; std::shared_ptr rare_item_set; + std::shared_ptr common_item_set; + std::shared_ptr armor_random_set; + std::shared_ptr tool_random_set; + std::array, 4> weapon_random_sets; + std::shared_ptr item_parameter_table; std::shared_ptr ep3_tournament_index; diff --git a/src/StaticGameData.cc b/src/StaticGameData.cc index c18879c0..5a9fe16e 100644 --- a/src/StaticGameData.cc +++ b/src/StaticGameData.cc @@ -432,7 +432,7 @@ char char_for_language_code(uint8_t language) { -size_t stack_size_for_item(uint8_t data0, uint8_t data1) { +size_t max_stack_size_for_item(uint8_t data0, uint8_t data1) { if (data0 == 4) { return 999999; } @@ -446,1049 +446,8 @@ size_t stack_size_for_item(uint8_t data0, uint8_t data1) { return 1; } -size_t stack_size_for_item(const ItemData& item) { - return stack_size_for_item(item.data1[0], item.data1[1]); -} - -const unordered_map name_for_weapon_special({ - {0x00, nullptr}, - {0x01, "Draw"}, - {0x02, "Drain"}, - {0x03, "Fill"}, - {0x04, "Gush"}, - {0x05, "Heart"}, - {0x06, "Mind"}, - {0x07, "Soul"}, - {0x08, "Geist"}, - {0x09, "Master\'s"}, - {0x0A, "Lord\'s"}, - {0x0B, "King\'s"}, - {0x0C, "Charge"}, - {0x0D, "Spirit"}, - {0x0E, "Berserk"}, - {0x0F, "Ice"}, - {0x10, "Frost"}, - {0x11, "Freeze"}, - {0x12, "Blizzard"}, - {0x13, "Bind"}, - {0x14, "Hold"}, - {0x15, "Seize"}, - {0x16, "Arrest"}, - {0x17, "Heat"}, - {0x18, "Fire"}, - {0x19, "Flame"}, - {0x1A, "Burning"}, - {0x1B, "Shock"}, - {0x1C, "Thunder"}, - {0x1D, "Storm"}, - {0x1E, "Tempest"}, - {0x1F, "Dim"}, - {0x20, "Shadow"}, - {0x21, "Dark"}, - {0x22, "Hell"}, - {0x23, "Panic"}, - {0x24, "Riot"}, - {0x25, "Havoc"}, - {0x26, "Chaos"}, - {0x27, "Devil\'s"}, - {0x28, "Demon\'s"}, -}); - -const unordered_map name_for_s_rank_special({ - {0x01, "Jellen"}, - {0x02, "Zalure"}, - {0x05, "Burning"}, - {0x06, "Tempest"}, - {0x07, "Blizzard"}, - {0x08, "Arrest"}, - {0x09, "Chaos"}, - {0x0A, "Hell"}, - {0x0B, "Spirit"}, - {0x0C, "Berserk"}, - {0x0D, "Demon\'s"}, - {0x0E, "Gush"}, - {0x0F, "Geist"}, - {0x10, "King\'s"}, -}); - -struct ItemNameInfo { - const char* name; - bool is_rare; - bool is_s_rank; - - ItemNameInfo(const char* name, bool is_rare = true, bool is_s_rank = false) - : name(name), is_rare(is_rare), is_s_rank(is_s_rank) { } -}; - -const unordered_map name_info_for_primary_identifier({ - // Weapons (00xxxx) - {0x000100, {"Saber", false}}, - {0x000101, {"Brand", false}}, - {0x000102, {"Buster", false}}, - {0x000103, {"Pallasch", false}}, - {0x000104, {"Gladius", false}}, - {0x000105, "DB\'s SABER"}, - {0x000106, "KALADBOLG"}, - {0x000107, "DURANDAL"}, - {0x000108, "GALATINE"}, - {0x000200, {"Sword", false}}, - {0x000201, {"Gigush", false}}, - {0x000202, {"Breaker", false}}, - {0x000203, {"Claymore", false}}, - {0x000204, {"Calibur", false}}, - {0x000205, "FLOWEN\'S SWORD"}, - {0x000206, "LAST SURVIVOR"}, - {0x000207, "DRAGON SLAYER"}, - {0x000300, {"Dagger", false}}, - {0x000301, {"Knife", false}}, - {0x000302, {"Blade", false}}, - {0x000303, {"Edge", false}}, - {0x000304, {"Ripper", false}}, - {0x000305, "BLADE DANCE"}, - {0x000306, "BLOODY ART"}, - {0x000307, "CROSS SCAR"}, - {0x000308, "ZERO DIVIDE"}, - {0x000309, "TWIN KAMUI"}, - {0x000400, {"Partisan", false}}, - {0x000401, {"Halbert", false}}, - {0x000402, {"Glaive", false}}, - {0x000403, {"Berdys", false}}, - {0x000404, {"Gungnir", false}}, - {0x000405, "BRIONAC"}, - {0x000406, "VJAYA"}, - {0x000407, "GAE BOLG"}, - {0x000408, "ASTERON BELT"}, - {0x000500, {"Slicer", false}}, - {0x000501, {"Spinner", false}}, - {0x000502, {"Cutter", false}}, - {0x000503, {"Sawcer", false}}, - {0x000504, {"Diska", false}}, - {0x000505, "SLICER OF ASSASSIN"}, - {0x000506, "DISKA OF LIBERATOR"}, - {0x000507, "DISKA OF BRAVEMAN"}, - {0x000508, "IZMAELA"}, - {0x000600, {"Handgun", false}}, - {0x000601, {"Autogun", false}}, - {0x000602, {"Lockgun", false}}, - {0x000603, {"Railgun", false}}, - {0x000604, {"Raygun", false}}, - {0x000605, "VARISTA"}, - {0x000606, "CUSTOM RAY ver.00"}, - {0x000607, "BRAVACE"}, - {0x000608, "TENSION BLASTER"}, - {0x000700, {"Rifle", false}}, - {0x000701, {"Sniper", false}}, - {0x000702, {"Blaster", false}}, - {0x000703, {"Beam", false}}, - {0x000704, {"Laser", false}}, - {0x000705, "VISK-235W"}, - {0x000706, "WALS-MK2"}, - {0x000707, "JUSTY-23ST"}, - {0x000708, "RIANOV 303SNR"}, - {0x000709, "RIANOV 303SNR-1"}, - {0x00070A, "RIANOV 303SNR-2"}, - {0x00070B, "RIANOV 303SNR-3"}, - {0x00070C, "RIANOV 303SNR-4"}, - {0x00070D, "RIANOV 303SNR-5"}, - {0x000800, {"Mechgun", false}}, - {0x000801, {"Assault", false}}, - {0x000802, {"Repeater", false}}, - {0x000803, {"Gatling", false}}, - {0x000804, {"Vulcan", false}}, - {0x000805, "M&A60 VISE"}, - {0x000806, "H&S25 JUSTICE"}, - {0x000807, "L&K14 COMBAT"}, - {0x000900, {"Shot", false}}, - {0x000901, {"Spread", false}}, - {0x000902, {"Cannon", false}}, - {0x000903, {"Launcher", false}}, - {0x000904, {"Arms", false}}, - {0x000905, "CRUSH BULLET"}, - {0x000906, "METEOR SMASH"}, - {0x000907, "FINAL IMPACT"}, - {0x000A00, {"Cane", false}}, - {0x000A01, {"Stick", false}}, - {0x000A02, {"Mace", false}}, - {0x000A03, {"Club", false}}, - {0x000A04, "CLUB OF LACONIUM"}, - {0x000A05, "MACE OF ADAMAN"}, - {0x000A06, "CLUB OF ZUMIURAN"}, - {0x000A07, "LOLLIPOP"}, - {0x000B00, {"Rod", false}}, - {0x000B01, {"Pole", false}}, - {0x000B02, {"Pillar", false}}, - {0x000B03, {"Striker", false}}, - {0x000B04, "BATTLE VERGE"}, - {0x000B05, "BRAVE HAMMER"}, - {0x000B06, "ALIVE AQHU"}, - {0x000B07, "VALKYRIE"}, - {0x000C00, {"Wand", false}}, - {0x000C01, {"Staff", false}}, - {0x000C02, {"Baton", false}}, - {0x000C03, {"Scepter", false}}, - {0x000C04, "FIRE SCEPTER:AGNI"}, - {0x000C05, "ICE STAFF:DAGON"}, - {0x000C06, "STORM WAND:INDRA"}, - {0x000C07, "EARTH WAND BROWNIE"}, - {0x000D00, "PHOTON CLAW"}, - {0x000D01, "SILENCE CLAW"}, - {0x000D02, "NEI\'S CLAW (REPLICA)"}, - {0x000D03, "PHOENIX CLAW"}, - {0x000E00, "DOUBLE SABER"}, - {0x000E01, "STAG CUTLERY"}, - {0x000E02, "TWIN BRAND"}, - {0x000F00, "BRAVE KNUCKLE"}, - {0x000F01, "ANGRY FIST"}, - {0x000F02, "GOD HAND"}, - {0x000F03, "SONIC KNUCKLE"}, - {0x001000, "OROTIAGITO"}, - {0x001001, "AGITO (AUW 1975)"}, - {0x001002, "AGITO (AUW 1983)"}, - {0x001003, "AGITO (AUW 2001)"}, - {0x001004, "AGITO (AUW 1991)"}, - {0x001005, "AGITO (AUW 1977)"}, - {0x001006, "AGITO (AUW 1980)"}, - {0x001007, "RAIKIRI"}, - {0x001100, "SOUL EATER"}, - {0x001101, "SOUL BANISH"}, - {0x001200, "SPREAD NEEDLE"}, - {0x001300, "HOLY RAY"}, - {0x001400, "INFERNO BAZOOKA"}, - {0x001401, "RAMBLING MAY"}, - {0x001402, "L&K38 COMBAT"}, - {0x001500, "FLAME VISIT"}, - {0x001501, "BURNING VISIT"}, - {0x001600, "AKIKO\'S FRYING PAN"}, - {0x001700, "SORCERER\'S CANE"}, - {0x001800, "S-BEAT\'S BLADE"}, - {0x001900, "P-ARMS\'S BLADE"}, - {0x001A00, "DELSABER\'S BUSTER"}, - {0x001B00, "BRINGER\'S RIFLE"}, - {0x001C00, "EGG BLASTER"}, - {0x001D00, "PSYCHO WAND"}, - {0x001E00, "HEAVEN PUNISHER"}, - {0x001F00, "LAVIS CANNON"}, - {0x002000, "VICTOR AXE"}, - {0x002001, "LACONIUM AXE"}, - {0x002100, "CHAIN SAWD"}, - {0x002200, "CADUCEUS"}, - {0x002201, "MERCURIUS ROD"}, - {0x002300, "STING TIP"}, - {0x002400, "MAGICAL PIECE"}, - {0x002500, "TECHNICAL CROZIER"}, - {0x002600, "SUPPRESSED GUN"}, - {0x002700, "ANCIENT SABER"}, - {0x002800, "HARISEN BATTLE FAN"}, - {0x002900, "YAMIGARASU"}, - {0x002A00, "AKIKO\'S WOK"}, - {0x002B00, "TOY HAMMER"}, - {0x002C00, "ELYSION"}, - {0x002D00, "RED SABER"}, - {0x002E00, "METEOR CUDGEL"}, - {0x002F00, "MONKEY KING BAR"}, - {0x002F01, "BLACK KING BAR"}, - {0x003000, "DOUBLE CANNON"}, - {0x003001, "GIRASOLE"}, - {0x003100, "HUGE BATTLE FAN"}, - {0x003200, "TSUMIKIRI J-SWORD"}, - {0x003300, "SEALED J-SWORD"}, - {0x003400, "RED SWORD"}, - {0x003500, "CRAZY TUNE"}, - {0x003600, "TWIN CHAKRAM"}, - {0x003700, "WOK OF AKIKO\'S SHOP"}, - {0x003800, "LAVIS BLADE"}, - {0x003900, "RED DAGGER"}, - {0x003A00, "MADAM\'S PARASOL"}, - {0x003B00, "MADAM\'S UMBRELLA"}, - {0x003C00, "IMPERIAL PICK"}, - {0x003D00, "BERDYSH"}, - {0x003E00, "RED PARTISAN"}, - {0x003F00, "FLIGHT CUTTER"}, - {0x004000, "FLIGHT FAN"}, - {0x004100, "RED SLICER"}, - {0x004200, "HANDGUN:GULD"}, - {0x004201, "MASTER RAVEN"}, - {0x004300, "HANDGUN:MILLA"}, - {0x004301, "LAST SWAN"}, - {0x004400, "RED HANDGUN"}, - {0x004500, "FROZEN SHOOTER"}, - {0x004501, "SNOW QUEEN"}, - {0x004600, "ANTI ANDROID RIFLE"}, - {0x004700, "ROCKET PUNCH"}, - {0x004800, "SAMBA MARACAS"}, - {0x004900, "TWIN PSYCHOGUN"}, - {0x004A00, "DRILL LAUNCHER"}, - {0x004B00, "GULD MILLA"}, - {0x004B01, "DUAL BIRD"}, - {0x004C00, "RED MECHGUN"}, - {0x004D00, "BELRA CANNON"}, - {0x004E00, "PANZER FAUST"}, - {0x004E01, "IRON FAUST"}, - {0x004F00, "SUMMIT MOON"}, - {0x005000, "WINDMILL"}, - {0x005100, "EVIL CURST"}, - {0x005200, "FLOWER CANE"}, - {0x005300, "HILDEBEAR\'S CANE"}, - {0x005400, "HILDEBLUE\'S CANE"}, - {0x005500, "RABBIT WAND"}, - {0x005600, "PLANTAIN LEAF"}, - {0x005601, "FATSIA"}, - {0x005700, "DEMONIC FORK"}, - {0x005800, "STRIKER OF CHAO"}, - {0x005900, "BROOM"}, - {0x005A00, "PROPHETS OF MOTAV"}, - {0x005B00, "THE SIGH OF A GOD"}, - {0x005C00, "TWINKLE STAR"}, - {0x005D00, "PLANTAIN FAN"}, - {0x005E00, "TWIN BLAZE"}, - {0x005F00, "MARINA\'S BAG"}, - {0x006000, "DRAGON\'S CLAW"}, - {0x006100, "PANTHER\'S CLAW"}, - {0x006200, "S-RED\'S BLADE"}, - {0x006300, "PLANTAIN HUGE FAN"}, - {0x006400, "CHAMELEON SCYTHE"}, - {0x006500, "YASMINKOV 3000R"}, - {0x006600, "ANO RIFLE"}, - {0x006700, "BARANZ LAUNCHER"}, - {0x006800, "BRANCH OF PAKUPAKU"}, - {0x006900, "HEART OF POUMN"}, - {0x006A00, "YASMINKOV 2000H"}, - {0x006B00, "YASMINKOV 7000V"}, - {0x006C00, "YASMINKOV 9000M"}, - {0x006D00, "MASER BEAM"}, - {0x006D01, "POWER MASER"}, - {0x006E00, "GAME MAGAZINE"}, - {0x006F00, "FLOWER BOUQUET"}, - {0x007000, {"S-RANK SABER", true, true}}, - {0x007100, {"S-RANK SWORD", true, true}}, - {0x007200, {"S-RANK BLADE", true, true}}, - {0x007300, {"S-RANK PARTISAN", true, true}}, - {0x007400, {"S-RANK SLICER", true, true}}, - {0x007500, {"S-RANK GUN", true, true}}, - {0x007600, {"S-RANK RIFLE", true, true}}, - {0x007700, {"S-RANK MECHGUN", true, true}}, - {0x007800, {"S-RANK SHOT", true, true}}, - {0x007900, {"S-RANK CANE", true, true}}, - {0x007A00, {"S-RANK ROD", true, true}}, - {0x007B00, {"S-RANK WAND", true, true}}, - {0x007C00, {"S-RANK TWIN", true, true}}, - {0x007D00, {"S-RANK CLAW", true, true}}, - {0x007E00, {"S-RANK BAZOOKA", true, true}}, - {0x007F00, {"S-RANK NEEDLE", true, true}}, - {0x008000, {"S-RANK SCYTHE", true, true}}, - {0x008100, {"S-RANK HAMMER", true, true}}, - {0x008200, {"S-RANK MOON", true, true}}, - {0x008300, {"S-RANK PSYCHOGUN", true, true}}, - {0x008400, {"S-RANK PUNCH", true, true}}, - {0x008500, {"S-RANK WINDMILL", true, true}}, - {0x008600, {"S-RANK HARISEN", true, true}}, - {0x008700, {"S-RANK KATANA", true, true}}, - {0x008800, {"S-RANK J-CUTTER", true, true}}, - {0x008900, "MUSASHI"}, - {0x008901, "YAMATO"}, - {0x008902, "ASUKA"}, - {0x008903, "SANGE & YASHA"}, - {0x008A00, "SANGE"}, - {0x008A01, "YASHA"}, - {0x008A02, "KAMUI"}, - {0x008B00, "PHOTON LAUNCHER"}, - {0x008B01, "GUILTY LIGHT"}, - {0x008B02, "RED SCORPIO"}, - {0x008B03, "PHONON MASER"}, - {0x008C00, "TALIS"}, - {0x008C01, "MAHU"}, - {0x008C02, "HITOGATA"}, - {0x008C03, "DANCING HITOGATA"}, - {0x008C04, "KUNAI"}, - {0x008D00, "NUG-2000 BAZOOKA"}, - {0x008E00, "S-BERILL\'S HANDS #0"}, - {0x008E01, "S-BERILL\'S HANDS #1"}, - {0x008F00, "FLOWEN\'S SWORD (AUW 3060; GREENILL)"}, - {0x008F01, "FLOWEN\'S SWORD (AUW 3064; SKYLY)"}, - {0x008F02, "FLOWEN\'S SWORD (AUW 3067; BLUEFULL)"}, - {0x008F03, "FLOWEN\'S SWORD (AUW 3073; PURPLENUM)"}, - {0x008F04, "FLOWEN\'S SWORD (AUW 3077; PINKAL)"}, - {0x008F05, "FLOWEN\'S SWORD (AUW 3082; REDRIA)"}, - {0x008F06, "FLOWEN\'S SWORD (AUW 3083; ORAN)"}, - {0x008F07, "FLOWEN\'S SWORD (AUW 3084; YELLOWBOZE)"}, - {0x008F08, "FLOWEN\'S SWORD (AUW 3079; WHITILL)"}, - {0x009000, "DB\'S SWORD (AUW 3062; GREENILL)"}, - {0x009001, "DB\'S SWORD (AUW 3067; SKYLY)"}, - {0x009002, "DB\'S SWORD (AUW 3069; BLUEFULL)"}, - {0x009003, "DB\'S SWORD (AUW 3064; PURPLENUM)"}, - {0x009004, "DB\'S SWORD (AUW 3069; PINKAL)"}, - {0x009005, "DB\'S SWORD (AUW 3073; REDRIA)"}, - {0x009006, "DB\'S SWORD (AUW 3070; ORAN)"}, - {0x009007, "DB\'S SWORD (AUW 3075; YELLOWBOZE)"}, - {0x009008, "DB\'S SWORD (AUW 3077; WHITILL)"}, - {0x009100, "GI GUE BAZOOKA"}, - {0x009200, "GUARDIANNA"}, - {0x009300, "VIRIDIA CARD"}, - {0x009301, "GREENILL CARD"}, - {0x009302, "SKYLY CARD"}, - {0x009303, "BLUEFULL CARD"}, - {0x009304, "PURPLENUM CARD"}, - {0x009305, "PINKAL CARD"}, - {0x009306, "REDRIA CARD"}, - {0x009307, "ORAN CARD"}, - {0x009308, "YELLOWBOZE CARD"}, - {0x009309, "WHITILL CARD"}, - {0x009400, "MORNING GLORY"}, - {0x009500, "PARTISAN OF LIGHTING"}, - {0x009600, "GAL WIND"}, - {0x009700, "ZANBA"}, - {0x009800, "RIKA\'S CLAW"}, - {0x009900, "ANGEL HARP"}, - {0x009A00, "DEMOLITION COMET"}, - {0x009B00, "NEI\'S CLAW"}, - {0x009C00, "RAINBOW BATON"}, - {0x009D00, "DARK FLOW"}, - {0x009E00, "DARK METEOR"}, - {0x009F00, "DARK BRIDGE"}, - {0x00A000, "G-ASSASSIN\'S SABERS"}, - {0x00A100, "RAPPY\'S FAN"}, - {0x00A200, "BOOMA\'S CLAW"}, - {0x00A201, "GOBOOMA\'S CLAW"}, - {0x00A202, "GIGOBOOMA\'S CLAW"}, - {0x00A300, "RUBY BULLET"}, - {0x00A400, "AMORE ROSE"}, - {0x00A500, {"S-RANK SWORDS", true, true}}, - {0x00A600, {"S-RANK LAUNCHER", true, true}}, - {0x00A700, {"S-RANK CARD", true, true}}, - {0x00A800, {"S-RANK KNUCKLE", true, true}}, - {0x00A900, {"S-RANK AXE", true, true}}, - {0x00AA00, "SLICER OF FANATIC"}, - {0x00AB00, "LAME D\'ARGENT"}, - {0x00AC00, "EXCALIBUR"}, - {0x00AD03, "RAGE DE FEU"}, - {0x00AE00, "DAISY CHAIN"}, - {0x00AF00, "OPHELIE SEIZE"}, - {0x00B000, "MILLE MARTEAUX"}, - {0x00B100, "LE COGNEUR"}, - {0x00B200, "COMMANDER BLADE"}, - {0x00B300, "VIVIENNE"}, - {0x00B400, "KUSANAGI"}, - {0x00B500, "SACRED DUSTER"}, - {0x00B600, "GUREN"}, - {0x00B700, "SHOUREN"}, - {0x00B800, "JIZAI"}, - {0x00B900, "FLAMBERGE"}, - {0x00BA00, "YUNCHANG"}, - {0x00BB00, "SNAKE SPIRE"}, - {0x00BC00, "FLAPJACK FLAPPER"}, - {0x00BD00, "GETSUGASAN"}, - {0x00BE00, "MAGUWA"}, - {0x00BF00, "HEAVEN STRIKER"}, - {0x00C000, "CANNON ROUGE"}, - {0x00C100, "METEOR ROUGE"}, - {0x00C200, "SOLFERINO"}, - {0x00C300, "CLIO"}, - {0x00C400, "SIREN GLASS HAMMER"}, - {0x00C500, "GLIDE DIVINE"}, - {0x00C600, "SHICHISHITO"}, - {0x00C700, "MURASAME"}, - {0x00C800, "DAYLIGHT SCAR"}, - {0x00C900, "DECALOG"}, - {0x00CA00, "5TH ANNIV. BLADE"}, - {0x00CB00, "PRINCIPAL\'S GIFT PARASOL"}, - {0x00CC00, "AKIKO\'S CLEAVER"}, - {0x00CD00, "TANEGASHIMA"}, - {0x00CE00, "TREE CLIPPERS"}, - {0x00CF00, "NICE SHOT"}, - {0x00D200, "ANO BAZOOKA"}, - {0x00D300, "SYNTHESIZER"}, - {0x00D400, "BAMBOO SPEAR"}, - {0x00D500, "KAN\'EI TSUHO"}, - {0x00D600, "JITTE"}, - {0x00D700, "BUTTERFLY NET"}, - {0x00D800, "SYRINGE"}, - {0x00D900, "BATTLEDORE"}, - {0x00DA00, "RACKET"}, - {0x00DB00, "HAMMER"}, - {0x00DC00, "GREAT BOUQUET"}, - {0x00DD00, "TypeSA/Saber"}, - {0x00DE00, "TypeSL/Saber"}, - {0x00DE01, "TypeSL/Slicer"}, - {0x00DE02, "TypeSL/Claw"}, - {0x00DE03, "TypeSL/Katana"}, - {0x00DF00, "TypeJS/Saber"}, - {0x00DF01, "TypeJS/Slicer"}, - {0x00DF02, "TypeJS/J-Sword"}, - {0x00E000, "TypeSW/Sword"}, - {0x00E001, "TypeSW/Slicer"}, - {0x00E002, "TypeSW/J-Sword"}, - {0x00E100, "TypeRO/Sword"}, - {0x00E101, "TypeRO/Halbert"}, - {0x00E102, "TypeRO/Rod"}, - {0x00E200, "TypeBL/BLADE"}, - {0x00E300, "TypeKN/Blade"}, - {0x00E301, "TypeKN/Claw"}, - {0x00E400, "TypeHA/Halbert"}, - {0x00E401, "TypeHA/Rod"}, - {0x00E500, "TypeDS/D.Saber"}, - {0x00E501, "TypeDS/Rod"}, - {0x00E502, "TypeDS"}, - {0x00E600, "TypeCL/Claw"}, - {0x00E700, "TypeSS/SW"}, - {0x00E800, "TypeGU/Handgun"}, - {0x00E801, "TypeGU/Mechgun"}, - {0x00E900, "TypeRI/Rifle"}, - {0x00EA00, "TypeME/Mechgun"}, - {0x00EB00, "TypeSH/Shot"}, - {0x00EC00, "TypeWA/Wand"}, - - // Armors (0101xx) - {0x010100, {"Frame", false}}, - {0x010101, {"Armor", false}}, - {0x010102, {"Psy Armor", false}}, - {0x010103, {"Giga Frame", false}}, - {0x010104, {"Soul Frame", false}}, - {0x010105, {"Cross Armor", false}}, - {0x010106, {"Solid Frame", false}}, - {0x010107, {"Brave Armor", false}}, - {0x010108, {"Hyper Frame", false}}, - {0x010109, {"Grand Armor", false}}, - {0x01010A, {"Shock Frame", false}}, - {0x01010B, {"King\'s Frame", false}}, - {0x01010C, {"Dragon Frame", false}}, - {0x01010D, {"Absorb Armor", false}}, - {0x01010E, {"Protect Frame", false}}, - {0x01010F, {"General Armor", false}}, - {0x010110, {"Perfect Frame", false}}, - {0x010111, {"Valiant Frame", false}}, - {0x010112, {"Imperial Armor", false}}, - {0x010113, {"Holiness Armor", false}}, - {0x010114, {"Guardian Armor", false}}, - {0x010115, {"Divinity Armor", false}}, - {0x010116, {"Ultimate Frame", false}}, - {0x010117, {"Celestial Armor", false}}, - {0x010118, "HUNTER FIELD"}, - {0x010119, "RANGER FIELD"}, - {0x01011A, "FORCE FIELD"}, - {0x01011B, "REVIVAL GARMENT"}, - {0x01011C, "SPIRIT GARMENT"}, - {0x01011D, "STINK FRAME"}, - {0x01011E, "D-PARTS Ver1.01"}, - {0x01011F, "D-PARTS Ver2.10"}, - {0x010120, "PARASITE WEAR:De Rol"}, - {0x010121, "PARASITE WEAR:Nelgal"}, - {0x010122, "PARASITE WEAR:Vajulla"}, - {0x010123, "SENSE PLATE"}, - {0x010124, "GRAVITON PLATE"}, - {0x010125, "ATTRIBUTE PLATE"}, - {0x010126, "FLOWEN\'S FRAME"}, - {0x010127, "CUSTOM FRAME Ver.00"}, - {0x010128, "DB\'s ARMOR"}, - {0x010129, "GUARD WAVE"}, - {0x01012A, "DF FIELD"}, - {0x01012B, "LUMINOUS FIELD"}, - {0x01012C, "CHU CHU FEVER"}, - {0x01012D, "LOVE HEART"}, - {0x01012E, "FLAME GARMENT"}, - {0x01012F, "VIRUS ARMOR:Lafuteria"}, - {0x010130, "BRIGHTNESS CIRCLE"}, - {0x010131, "AURA FIELD"}, - {0x010132, "ELECTRO FRAME"}, - {0x010133, "SACRED CLOTH"}, - {0x010134, "SMOKING PLATE"}, - {0x010135, "STAR CUIRASS"}, - {0x010136, "BLACK HOUND CUIRASS"}, - {0x010137, "MORNING PRAYER"}, - {0x010138, "BLACK ODOSHI DOMARU"}, - {0x010139, "RED ODOSHI DOMARU"}, - {0x01013A, "BLACK ODOSHI RED NIMAIDOU"}, - {0x01013B, "BLUE ODOSHI VIOLET NIMAIDOU"}, - {0x01013C, "DIRTY LIFE JACKET"}, - {0x01013E, "WEDDING DRESS"}, - {0x010140, "RED COAT"}, - {0x010141, "THIRTEEN"}, - {0x010142, "MOTHER GARB"}, - {0x010143, "MOTHER GARB+"}, - {0x010144, "DRESS PLATE"}, - {0x010145, "SWEETHEART"}, - {0x010146, "IGNITION CLOAK"}, - {0x010147, "CONGEAL CLOAK"}, - {0x010148, "TEMPEST CLOAK"}, - {0x010149, "CURSED CLOAK"}, - {0x01014A, "SELECT CLOAK"}, - {0x01014B, "SPIRIT CUIRASS"}, - {0x01014C, "REVIVAL CUIRASS"}, - {0x01014D, "ALLIANCE UNIFORM"}, - {0x01014E, "OFFICER UNIFORM"}, - {0x01014F, "COMMANDER UNIFORM"}, - {0x010150, "CRIMSON COAT"}, - {0x010151, "INFANTRY GEAR"}, - {0x010152, "LIEUTENANT GEAR"}, - {0x010153, "INFANTRY MANTLE"}, - {0x010154, "LIEUTENANT MANTLE"}, - {0x010155, "UNION FIELD"}, - {0x010156, "SAMURAI ARMOR"}, - {0x010157, "STEALTH SUIT"}, - - // Shields (0102xx) - {0x010200, {"Barrier", false}}, - {0x010201, {"Shield", false}}, - {0x010202, {"Core Shield", false}}, - {0x010203, {"Giga Shield", false}}, - {0x010204, {"Soul Barrier", false}}, - {0x010205, {"Hard Shield", false}}, - {0x010206, {"Brave Barrier", false}}, - {0x010207, {"Solid Shield", false}}, - {0x010208, {"Flame Barrier", false}}, - {0x010209, {"Plasma Barrier", false}}, - {0x01020A, {"Freeze Barrier", false}}, - {0x01020B, {"Psychic Barrier", false}}, - {0x01020C, {"General Shield", false}}, - {0x01020D, {"Protect Barrier", false}}, - {0x01020E, {"Glorious Shield", false}}, - {0x01020F, {"Imperial Barrier", false}}, - {0x010210, {"Guardian Shield", false}}, - {0x010211, {"Divinity Barrier", false}}, - {0x010212, {"Ultimate Shield", false}}, - {0x010213, {"Spiritual Shield", false}}, - {0x010214, {"Celestial Shield", false}}, - {0x010215, "INVISIBLE GUARD"}, - {0x010216, "SACRED GUARD"}, - {0x010217, "S-PARTS Ver1.16"}, - {0x010218, "S-PARTS Ver2.01"}, - {0x010219, "LIGHT RELIEF"}, - {0x01021A, "SHIELD OF DELSABER"}, - {0x01021B, "FORCE WALL"}, - {0x01021C, "RANGER WALL"}, - {0x01021D, "HUNTER WALL"}, - {0x01021E, "ATTRIBUTE WALL"}, - {0x01021F, "SECRET GEAR"}, - {0x010220, "COMBAT GEAR"}, - {0x010221, "PROTO REGENE GEAR"}, - {0x010222, "REGENERATE GEAR"}, - {0x010223, "REGENE GEAR ADV."}, - {0x010224, "FLOWEN\'S SHIELD"}, - {0x010225, "CUSTOM BARRIER Ver.00"}, - {0x010226, "DB\'S SHIELD"}, - {0x010227, "RED RING"}, - {0x010228, "TRIPOLIC SHIELD"}, - {0x010229, "STANDSTILL SHIELD"}, - {0x01022A, "SAFETY HEART"}, - {0x01022B, "KASAMI BRACER"}, - {0x01022C, "GODS SHIELD SUZAKU"}, - {0x01022D, "GODS SHIELD GENBU"}, - {0x01022E, "GODS SHIELD BYAKKO"}, - {0x01022F, "GODS SHIELD SEIRYU"}, - {0x010230, "HUNTER\'S SHELL"}, - {0x010231, "RICO\'S GLASSES"}, - {0x010232, "RICO\'S EARRING"}, - {0x010235, {"SECURE FEET", false}}, - {0x01023A, {"RESTA MERGE", false}}, - {0x01023B, {"ANTI MERGE", false}}, - {0x01023C, {"SHIFTA MERGE", false}}, - {0x01023D, {"DEBAND MERGE", false}}, - {0x01023E, {"FOIE MERGE", false}}, - {0x01023F, {"GIFOIE MERGE", false}}, - {0x010240, {"RAFOIE MERGE", false}}, - {0x010241, {"RED MERGE", false}}, - {0x010242, {"BARTA MERGE", false}}, - {0x010243, {"GIBARTA MERGE", false}}, - {0x010244, {"RABARTA MERGE", false}}, - {0x010245, {"BLUE MERGE", false}}, - {0x010246, {"ZONDE MERGE", false}}, - {0x010247, {"GIZONDE MERGE", false}}, - {0x010248, {"RAZONDE MERGE", false}}, - {0x010249, {"YELLOW MERGE", false}}, - {0x01024A, {"RECOVERY BARRIER", false}}, - {0x01024B, {"ASSIST BARRIER", false}}, - {0x01024C, {"RED BARRIER", false}}, - {0x01024D, {"BLUE BARRIER", false}}, - {0x01024E, {"YELLOW BARRIER", false}}, - {0x01024F, "WEAPONS GOLD SHIELD"}, - {0x010250, "BLACK GEAR"}, - {0x010251, "WORKS GUARD"}, - {0x010252, "RAGOL RING"}, - {0x010253, "BLUE RING (7 Colors)"}, - {0x010259, "BLUE RING"}, - {0x01025F, "GREEN RING"}, - {0x010266, "YELLOW RING"}, - {0x01026C, "PURPLE RING"}, - {0x010275, "WHITE RING"}, - {0x010280, "BLACK RING"}, - {0x010283, "WEAPONS SILVER SHIELD"}, - {0x010284, "WEAPONS COPPER SHIELD"}, - {0x010285, "GRATIA"}, - {0x010286, "TRIPOLIC REFLECTOR"}, - {0x010287, "STRIKER PLUS"}, - {0x010288, "REGENERATE GEAR B.P."}, - {0x010289, "RUPIKA"}, - {0x01028A, "YATA MIRROR"}, - {0x01028B, "BUNNY EARS"}, - {0x01028C, "CAT EARS"}, - {0x01028D, "THREE SEALS"}, - {0x01028F, "DF SHIELD"}, - {0x010290, "FROM THE DEPTHS"}, - {0x010291, "DE ROL LE SHIELD"}, - {0x010292, "HONEYCOMB REFLECTOR"}, - {0x010293, "EPSIGUARD"}, - {0x010294, "ANGEL RING"}, - {0x010295, "UNION GUARD"}, - {0x010297, "UNION"}, - {0x010298, "BLACK SHIELD UNION GUARD"}, - {0x010299, "STINK SHIELD"}, - {0x01029A, "BLACK"}, - {0x01029B, "GENPEI Heightened"}, - {0x01029C, "GENPEI Greenill"}, - {0x01029D, "GENPEI Skyly"}, - {0x01029E, "GENPEI Bluefull"}, - {0x01029F, "GENPEI Purplenum"}, - {0x0102A0, "GENPEI Pinkal"}, - {0x0102A1, "GENPEI Redria"}, - {0x0102A2, "GENPEI Oran"}, - {0x0102A3, "GENPEI Yellowboze"}, - {0x0102A4, "GENPEI Whitill"}, - - // Units (0103xx) - {0x010300, {"Knight/Power", false}}, - {0x010301, {"General/Power", false}}, - {0x010302, {"Ogre/Power", false}}, - {0x010303, "God/Power"}, - {0x010304, {"Priest/Mind", false}}, - {0x010305, {"General/Mind", false}}, - {0x010306, {"Angel/Mind", false}}, - {0x010307, "God/Mind"}, - {0x010308, {"Marksman/Arm", false}}, - {0x010309, {"General/Arm", false}}, - {0x01030A, {"Elf/Arm", false}}, - {0x01030B, "God/Arm"}, - {0x01030C, {"Thief/Legs", false}}, - {0x01030D, {"General/Legs", false}}, - {0x01030E, {"Elf/Legs", false}}, - {0x01030F, "God/Legs"}, - {0x010310, {"Digger/HP", false}}, - {0x010311, {"General/HP", false}}, - {0x010312, {"Dragon/HP", false}}, - {0x010313, "God/HP"}, - {0x010314, {"Magician/TP", false}}, - {0x010315, {"General/TP", false}}, - {0x010316, {"Angel/TP", false}}, - {0x010317, "God/TP"}, - {0x010318, {"Warrior/Body", false}}, - {0x010319, {"General/Body", false}}, - {0x01031A, {"Metal/Body", false}}, - {0x01031B, "God/Body"}, - {0x01031C, {"Angel/Luck", false}}, - {0x01031D, "God/Luck"}, - {0x01031E, {"Master/Ability", false}}, - {0x01031F, {"Hero/Ability", false}}, - {0x010320, "God/Ability"}, - {0x010321, {"Resist/Fire", false}}, - {0x010322, {"Resist/Flame", false}}, - {0x010323, {"Resist/Burning", false}}, - {0x010324, {"Resist/Cold", false}}, - {0x010325, {"Resist/Freeze", false}}, - {0x010326, {"Resist/Blizzard", false}}, - {0x010327, {"Resist/Shock", false}}, - {0x010328, {"Resist/Thunder", false}}, - {0x010329, {"Resist/Storm", false}}, - {0x01032A, {"Resist/Light", false}}, - {0x01032B, {"Resist/Saint", false}}, - {0x01032C, {"Resist/Holy", false}}, - {0x01032D, {"Resist/Dark", false}}, - {0x01032E, {"Resist/Evil", false}}, - {0x01032F, {"Resist/Devil", false}}, - {0x010330, {"All/Resist", false}}, - {0x010331, {"Super/Resist", false}}, - {0x010332, "Perfect/Resist"}, - {0x010333, {"HP/Restorate", false}}, - {0x010334, {"HP/Generate", false}}, - {0x010335, {"HP/Revival", false}}, - {0x010336, {"TP/Restorate", false}}, - {0x010337, {"TP/Generate", false}}, - {0x010338, {"TP/Revival", false}}, - {0x010339, {"PB/Amplifier", false}}, - {0x01033A, {"PB/Generate", false}}, - {0x01033B, {"PB/Create", false}}, - {0x01033C, {"Wizard/Technique", false}}, - {0x01033D, {"Devil/Technique", false}}, - {0x01033E, "God/Technique"}, - {0x01033F, {"General/Battle", false}}, - {0x010340, {"Devil/Battle", false}}, - {0x010341, "God/Battle"}, - {0x010342, "Cure/Poison"}, - {0x010343, "Cure/Paralysis"}, - {0x010344, "Cure/Slow"}, - {0x010345, "Cure/Confuse"}, - {0x010346, "Cure/Freeze"}, - {0x010347, "Cure/Shock"}, - {0x010348, "Yasakani Magatama"}, - {0x010349, "V101"}, - {0x01034A, "V501"}, - {0x01034B, "V502"}, - {0x01034C, "V801"}, - {0x01034D, "LIMITER"}, - {0x01034E, "ADEPT"}, - {0x01034F, "SWORDSMAN LORE"}, - {0x010350, "PROOF OF SWORD-SAINT"}, - {0x010351, "SMARTLINK"}, - {0x010352, "DIVINE PROTECTION"}, - {0x010353, "Heavenly/Battle"}, - {0x010354, "Heavenly/Power"}, - {0x010355, "Heavenly/Mind"}, - {0x010356, "Heavenly/Arms"}, - {0x010357, "Heavenly/Legs"}, - {0x010358, "Heavenly/Body"}, - {0x010359, "Heavenly/Luck"}, - {0x01035A, "Heavenly/Ability"}, - {0x01035B, "Centurion/Ability"}, - {0x01035C, "Friend Ring"}, - {0x01035D, "Heavenly/HP"}, - {0x01035E, "Heavenly/TP"}, - {0x01035F, "Heavenly/Resist"}, - {0x010360, "Heavenly/Technique"}, - {0x010361, "HP/Resurrection"}, - {0x010362, "TP/Resurrection"}, - {0x010363, "PB/Increase"}, - - // Mags (02xxxx) - {0x020000, {"Mag", false}}, - {0x020100, {"Varuna", false}}, - {0x020200, {"Mitra", false}}, - {0x020300, {"Surya", false}}, - {0x020400, {"Vayu", false}}, - {0x020500, {"Varaha", false}}, - {0x020600, {"Kama", false}}, - {0x020700, {"Ushasu", false}}, - {0x020800, {"Apsaras", false}}, - {0x020900, {"Kumara", false}}, - {0x020A00, {"Kaitabha", false}}, - {0x020B00, {"Tapas", false}}, - {0x020C00, {"Bhirava", false}}, - {0x020D00, {"Kalki", false}}, - {0x020E00, {"Rudra", false}}, - {0x020F00, {"Marutah", false}}, - {0x021000, {"Yaksa", false}}, - {0x021100, {"Sita", false}}, - {0x021200, {"Garuda", false}}, - {0x021300, {"Nandin", false}}, - {0x021400, {"Ashvinau", false}}, - {0x021500, {"Ribhava", false}}, - {0x021600, {"Soma", false}}, - {0x021700, {"Ila", false}}, - {0x021800, {"Durga", false}}, - {0x021900, {"Vritra", false}}, - {0x021A00, {"Namuci", false}}, - {0x021B00, {"Sumba", false}}, - {0x021C00, {"Naga", false}}, - {0x021D00, {"Pitri", false}}, - {0x021E00, {"Kabanda", false}}, - {0x021F00, {"Ravana", false}}, - {0x022000, {"Marica", false}}, - {0x022100, {"Soniti", false}}, - {0x022200, {"Preta", false}}, - {0x022300, {"Andhaka", false}}, - {0x022400, {"Bana", false}}, - {0x022500, {"Naraka", false}}, - {0x022600, {"Madhu", false}}, - {0x022700, {"Churel", false}}, - {0x022800, "ROBOCHAO"}, - {0x022900, "OPA-OPA"}, - {0x022A00, "PIAN"}, - {0x022B00, "CHAO"}, - {0x022C00, "CHU CHU"}, - {0x022D00, "KAPU KAPU"}, - {0x022E00, "ANGEL\'S WING"}, - {0x022F00, "DEVIL\'S WING"}, - {0x023000, "ELENOR"}, - {0x023100, "MARK3"}, - {0x023200, "MASTER SYSTEM"}, - {0x023300, "GENESIS"}, - {0x023400, "SEGA SATURN"}, - {0x023500, "DREAMCAST"}, - {0x023600, "HAMBURGER"}, - {0x023700, "PANZER\'S TAIL"}, - {0x023800, "DAVIL\'S TAIL"}, - {0x023900, "Deva"}, - {0x023A00, "Rati"}, - {0x023B00, "Savitri"}, - {0x023C00, "Rukmin"}, - {0x023D00, "Pushan"}, - {0x023E00, "Diwari"}, - {0x023F00, "Sato"}, - {0x024000, "Bhima"}, - {0x024100, "Nidra"}, - - // Tools (03xxxx) - {0x030000, {"Monomate", false}}, - {0x030001, {"Dimate", false}}, - {0x030002, {"Trimate", false}}, - {0x030100, {"Monofluid", false}}, - {0x030101, {"Difluid", false}}, - {0x030102, {"Trifluid", false}}, - {0x030200, {"", false}}, // Special-cased in name_for_item - {0x030300, {"Sol Atomizer", false}}, - {0x030400, {"Moon Atomizer", false}}, - {0x030500, {"Star Atomizer", false}}, - {0x030600, {"Antidote", false}}, - {0x030601, {"Antiparalysis", false}}, - {0x030700, {"Telepipe", false}}, - {0x030800, {"Trap Vision", false}}, - {0x030900, {"Scape Doll", false}}, - {0x030A00, {"Monogrinder", false}}, - {0x030A01, {"Digrinder", false}}, - {0x030A02, {"Trigrinder", false}}, - {0x030B00, {"Power Material", false}}, - {0x030B01, {"Mind Material", false}}, - {0x030B02, {"Evade Material", false}}, - {0x030B03, {"HP Material", false}}, - {0x030B04, {"TP Material", false}}, - {0x030B05, {"Def Material", false}}, - {0x030B06, {"Luck Material", false}}, - {0x030C00, "Cell Of MAG 502"}, - {0x030C01, "Cell Of MAG 213"}, - {0x030C02, "Parts Of RoboChao"}, - {0x030C03, "Heart Of Opa Opa"}, - {0x030C04, "Heart Of Pian"}, - {0x030C05, "Heart Of Chao"}, - {0x030D00, "Sorcerer\'s Right Arm"}, - {0x030D01, "S-beat\'s Arms"}, - {0x030D02, "P-arm\'s Arms"}, - {0x030D03, "Delsaber\'s Right Arm"}, - {0x030D04, "C-bringer\'s Right Arm"}, - {0x030D05, "Delsaber\'s Left Arm"}, - {0x030D06, "S-red\'s Arms"}, - {0x030D07, "Dragon\'s Claw"}, - {0x030D08, "Hildebear\'s Head"}, - {0x030D09, "Hildeblue\'s Head"}, - {0x030D0A, "Parts of Baranz"}, - {0x030D0B, "Belra\'s Right Arm"}, - {0x030D0C, "Gi Gue\'s Body"}, - {0x030D0D, "Sinow Berill\'s Arms"}, - {0x030D0E, "G-Assassin\'s Arms"}, - {0x030D0F, "Booma\'s Right Arm"}, - {0x030D10, "Gobooma\'s Right Arm"}, - {0x030D11, "Gigobooma\'s Right Arm"}, - {0x030D12, "Gal Gryphon's Wing"}, - {0x030D13, "Rappy\'s Wing"}, - {0x030D14, "Cladding of Epsilon"}, - {0x030D15, "De Rol Le Shell"}, - {0x030E00, "Berill Photon"}, - {0x030E01, "Parasitic gene \"Flow\""}, - {0x030E02, "Magic stone \"Iritista\""}, - {0x030E03, "Blue-black stone"}, - {0x030E04, "Syncesta"}, - {0x030E05, "Magic Water"}, - {0x030E06, "Parasitic cell Type-D"}, - {0x030E07, "magic rock \"Heart Key\""}, - {0x030E08, "magic rock \"Moola\""}, - {0x030E09, "Star Amplifier"}, - {0x030E0A, "Book of HITOGATA"}, - {0x030E0B, "Heart of Chu Chu"}, - {0x030E0C, "Parts of EGG BLASTER"}, - {0x030E0D, "Heart of Angel"}, - {0x030E0E, "Heart of Devil"}, - {0x030E0F, "Kit of Hamburger"}, - {0x030E10, "Panther\'s Spirit"}, - {0x030E11, "Kit of MARK3"}, - {0x030E12, "Kit of MASTER SYSTEM"}, - {0x030E13, "Kit of GENESIS"}, - {0x030E14, "Kit of SEGA SATURN"}, - {0x030E15, "Kit of DREAMCAST"}, - {0x030E16, {"Amplifier of Resta", false}}, - {0x030E17, {"Amplifier of Anti", false}}, - {0x030E18, {"Amplifier of Shifta", false}}, - {0x030E19, {"Amplifier of Deband", false}}, - {0x030E1A, {"Amplifier of Foie", false}}, - {0x030E1B, {"Amplifier of Gifoie", false}}, - {0x030E1C, {"Amplifier of Rafoie", false}}, - {0x030E1D, {"Amplifier of Barta", false}}, - {0x030E1E, {"Amplifier of Gibarta", false}}, - {0x030E1F, {"Amplifier of Rabarta", false}}, - {0x030E20, {"Amplifier of Zonde", false}}, - {0x030E21, {"Amplifier of Gizonde", false}}, - {0x030E22, {"Amplifier of Razonde", false}}, - {0x030E23, {"Amplifier of Red", false}}, - {0x030E24, {"Amplifier of Blue", false}}, - {0x030E25, {"Amplifier of Yellow", false}}, - {0x030E26, "Heart of KAPU KAPU"}, - {0x030E27, "Photon Booster"}, - {0x030F00, "AddSlot"}, - {0x031000, "Photon Drop"}, - {0x031001, "Photon Sphere"}, - {0x031002, "Photon Crystal"}, - {0x031003, "Secret Lottery Ticket"}, - {0x031100, "Book of KATANA1"}, - {0x031101, "Book of KATANA2"}, - {0x031102, "Book of KATANA3"}, - {0x031200, "Weapons Bronze Badge"}, - {0x031201, "Weapons Silver Badge"}, - {0x031202, "Weapons Gold Badge"}, - {0x031203, "Weapons Crystal Badge"}, - {0x031204, "Weapons Steel Badge"}, - {0x031205, "Weapons Aluminum Badge"}, - {0x031206, "Weapons Leather Badge"}, - {0x031207, "Weapons Bone Badge"}, - {0x031208, "Letter of appreciation"}, - {0x031209, "Autograph Album"}, - {0x03120A, "Valentine\'s Chocolate"}, - {0x03120B, "New Year\'s Card"}, - {0x03120C, "Christmas Card"}, - {0x03120D, "Birthday Card"}, - {0x03120E, "Proof of Sonic Team"}, - {0x03120F, "Special Event Ticket"}, - {0x031210, "Flower Bouquet"}, - {0x031211, "Cake"}, - {0x031212, "Accessories"}, - {0x031213, "Mr.Naka\'s Business Card"}, - {0x031300, "Present"}, - {0x031400, "Chocolate"}, - {0x031401, "Candy"}, - {0x031402, "Cake"}, - {0x031403, "Silver Badge"}, - {0x031404, "Gold Badge"}, - {0x031405, "Crystal Badge"}, - {0x031406, "Iron Badge"}, - {0x031407, "Aluminum Badge"}, - {0x031408, "Leather Badge"}, - {0x031409, "Bone Badge"}, - {0x03140A, "Bouquet"}, - {0x03140B, "Decoction"}, - {0x031500, "Christmas Present"}, - {0x031501, "Easter Egg"}, - {0x031502, "Jack-O\'-Lantern"}, - {0x031600, "DISK Vol.1"}, - {0x031601, "DISK Vol.2"}, - {0x031602, "DISK Vol.3"}, - {0x031603, "DISK Vol.4"}, - {0x031604, "DISK Vol.5"}, - {0x031605, "DISK Vol.6"}, - {0x031606, "DISK Vol.7"}, - {0x031607, "DISK Vol.8"}, - {0x031608, "DISK Vol.9"}, - {0x031609, "DISK Vol.10"}, - {0x03160A, "DISK Vol.11"}, - {0x03160B, "DISK Vol.12"}, - {0x031700, "Hunters Report"}, - {0x031701, "Hunters Report (Rank A)"}, - {0x031702, "Hunters Report (Rank B)"}, - {0x031703, "Hunters Report (Rank C)"}, - {0x031704, "Hunters Report (Rank F)"}, - {0x031800, "Tablet"}, - {0x031802, "Dragon Scale"}, - {0x031803, "Heaven Striker Coat"}, - {0x031804, "Pioneer Parts"}, - {0x031805, "Amitie\'s Memo"}, - {0x031806, "Heart of Morolian"}, - {0x031807, "Rappy\'s Beak"}, - {0x031809, "D-Photon Core"}, - {0x03180A, "Liberta Kit"}, - {0x03180B, "Cell of MAG 0503"}, - {0x03180C, "Cell of MAG 0504"}, - {0x03180D, "Cell of MAG 0505"}, - {0x03180F, "Cell of MAG 0507"}, - {0x031900, "Team Points 500"}, - {0x031901, "Team Points 1000"}, - {0x031902, "Team Points 5000"}, - {0x031903, "Team Points 10000"}, -}); - const vector tech_id_to_name({ "foie", "gifoie", "rafoie", "barta", "gibarta", "rabarta", @@ -1548,253 +507,3 @@ uint8_t technique_for_name(const string& name) { uint8_t technique_for_name(const u16string& name) { return technique_for_name(encode_sjis(name)); } - -string name_for_item(const ItemData& item, bool include_color_codes) { - if (item.data1[0] == 0x04) { - return string_printf("%s%" PRIu32 " Meseta", - include_color_codes ? "$C7" : "", item.data2d.load()); - } - - vector ret_tokens; - - // For weapons, specials appear before the weapon name - if ((item.data1[0] == 0x00) && (item.data1[4] != 0x00)) { - // 0x80 is the unidentified flag, but we always return the identified name - // of the item here, so we ignore it - bool is_present = item.data1[4] & 0x40; - uint8_t special_id = item.data1[4] & 0x3F; - if (is_present) { - ret_tokens.emplace_back("Wrapped"); - } - if (special_id) { - try { - ret_tokens.emplace_back(name_for_weapon_special.at(special_id)); - } catch (const out_of_range&) { - ret_tokens.emplace_back(string_printf("!SP:%02hhX", special_id)); - } - } - } - // Mags can be wrapped as well - if ((item.data1[0] == 0x02) && (item.data2[1] & 0x40)) { - ret_tokens.emplace_back("Wrapped"); - } - - // Add the item name. Technique disks are special because the level is part of - // the primary identifier, so we manually generate the name instead of looking - // it up. - ItemNameInfo name_info(nullptr, false, false); - uint32_t primary_identifier = item.primary_identifier(); - if ((primary_identifier & 0xFFFFFF00) == 0x00030200) { - string technique_name; - try { - technique_name = tech_id_to_name.at(item.data1[4]); - technique_name[0] = toupper(technique_name[0]); - } catch (const out_of_range&) { - technique_name = string_printf("!TECH:%02hhX", item.data1[4]); - } - ret_tokens.emplace_back(string_printf( - "Disk:%s Lv.%d", technique_name.c_str(), item.data1[2] + 1)); - } else { - try { - name_info = name_info_for_primary_identifier.at(primary_identifier); - ret_tokens.emplace_back(name_info.name); - } catch (const out_of_range&) { - ret_tokens.emplace_back(string_printf("!ID:%06" PRIX32, primary_identifier)); - } - } - - // For weapons, add the grind and percentages, or S-rank name if applicable - if (item.data1[0] == 0x00) { - if (item.data1[3] > 0) { - ret_tokens.emplace_back(string_printf("+%hhu", item.data1[3])); - } - - if (name_info.is_s_rank && (item.data1[6] & 0x18)) { - // S-rank (has name instead of percent bonuses) - uint8_t char_indexes[8] = { - static_cast((item.data1w[3] >> 5) & 0x1F), - static_cast(item.data1w[3] & 0x1F), - static_cast((item.data1w[4] >> 10) & 0x1F), - static_cast((item.data1w[4] >> 5) & 0x1F), - static_cast(item.data1w[4] & 0x1F), - static_cast((item.data1w[5] >> 10) & 0x1F), - static_cast((item.data1w[5] >> 5) & 0x1F), - static_cast(item.data1w[5] & 0x1F), - }; - const char* translation_table = "\0ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"; - - string name; - for (size_t x = 0; x < 8; x++) { - char ch = translation_table[char_indexes[x]]; - if (ch == 0) { - break; - } - name += ch; - } - if (!name.empty()) { - ret_tokens.emplace_back("(" + name + ")"); - } - - } else { // Not S-rank (extended name bits not set) - uint8_t percentages[5] = {0, 0, 0, 0, 0}; - for (size_t x = 0; x < 3; x++) { - uint8_t which = item.data1[6 + 2 * x]; - uint8_t value = item.data1[7 + 2 * x]; - if (which == 0) { - continue; - } - if (which > 5) { - ret_tokens.emplace_back(string_printf("!PC:%02hhX%02hhX", which, value)); - } else { - percentages[which - 1] = value; - } - } - ret_tokens.emplace_back(string_printf("%hhu/%hhu/%hhu/%hhu/%hhu", - percentages[0], percentages[1], percentages[2], percentages[3], percentages[4])); - } - - // For armors, add the slots, unit modifiers, and/or DEF/EVP bonuses - } else if (item.data1[0] == 0x01) { - if (item.data1[1] == 0x03) { // Units - uint16_t modifier = (item.data1[8] << 8) | item.data1[7]; - if (modifier == 0x0001 || modifier == 0x0002) { - ret_tokens.back().append("+"); - } else if (modifier == 0x0003 || modifier == 0x0004) { - ret_tokens.back().append("++"); - } else if (modifier == 0xFFFF || modifier == 0xFFFE) { - ret_tokens.back().append("-"); - } else if (modifier == 0xFFFD || modifier == 0xFFFC) { - ret_tokens.back().append("--"); - } else if (modifier != 0x0000) { - ret_tokens.emplace_back(string_printf("!MD:%04hX", modifier)); - } - - } else { // Armor/shields - if (item.data1[5] > 0) { - if (item.data1[5] == 1) { - ret_tokens.emplace_back("(1 slot)"); - } else { - ret_tokens.emplace_back(string_printf("(%hhu slots)", item.data1[5])); - } - } - if (item.data1w[3] != 0) { - ret_tokens.emplace_back(string_printf("+%hdDEF", - static_cast(item.data1w[3].load()))); - } - if (item.data1w[4] != 0) { - ret_tokens.emplace_back(string_printf("+%hdEVP", - static_cast(item.data1w[4].load()))); - } - } - - // For mags, add tons of info - } else if (item.data1[0] == 0x02) { - ret_tokens.emplace_back(string_printf("LV%hhu", item.data1[2])); - - ret_tokens.emplace_back(string_printf("%d/%d/%d/%d", - item.data1w[2] / 100, item.data1w[3] / 100, - item.data1w[4] / 100, item.data1w[5] / 100)); - ret_tokens.emplace_back(string_printf("%hu%%", item.data2[3])); - ret_tokens.emplace_back(string_printf("%huIQ", item.data2[2])); - - uint8_t flags = item.data2[1]; - if (flags & 7) { - static const vector pb_shortnames = { - "F", "E", "G", "P", "L", "M&Y", "MG", "GR"}; - - const char* pb_names[3] = {nullptr, nullptr, nullptr}; - uint8_t center_pb = (flags & 2) ? (item.data1[3] & 7) : 0xFF; - uint8_t right_pb = (flags & 1) ? ((item.data1[3] >> 3) & 7) : 0xFF; - uint8_t left_pb = (flags & 4) ? ((item.data1[3] >> 6) & 3) : 0xFF; - if (center_pb != 0xFF) { - pb_names[1] = pb_shortnames[center_pb]; - } - if (right_pb != 0xFF) { - pb_names[2] = pb_shortnames[right_pb]; - } - if (left_pb != 0xFF) { - // There are only two bits for the left PB (as opposed to 3 for the - // center and right PBs). This works because PBs can't be duplicated; - // there are 6 valid PBs for each slot, but the center and right slots - // are used first, leaving 4 valid options for the left slot. To encode - // this in two bits, the game takes the list of all PBs, removes the - // center and right PBs from the list, and the left PB is then used as - // an index into this modified list to determine the actual left PB. - // Here, we don't construct a temporary list and instead just skip the - // center and right PB values with a loop. - uint8_t actual_left_pb = 0; - for (;;) { - if ((actual_left_pb == center_pb) || (actual_left_pb == right_pb)) { - actual_left_pb++; - continue; - } - if (left_pb > 0) { - actual_left_pb++; - left_pb--; - continue; - } - break; - } - pb_names[0] = pb_shortnames[actual_left_pb]; - } - - string token = "PB:"; - for (size_t x = 0; x < 3; x++) { - if (pb_names[x] == nullptr) { - continue; - } - if (token.size() > 3) { - token += ','; - } - token += pb_names[x]; - } - ret_tokens.emplace_back(move(token)); - - static const vector mag_colors({ - /* 00 */ "red", - /* 01 */ "blue", - /* 02 */ "yellow", - /* 03 */ "green", - /* 04 */ "purple", - /* 05 */ "black", - /* 06 */ "white", - /* 07 */ "cyan", - /* 08 */ "brown", - /* 09 */ "orange", - /* 0A */ "light blue", - /* 0B */ "olive", - /* 0C */ "light cyan", - /* 0D */ "dark purple", - /* 0E */ "grey", - /* 0F */ "light grey", - /* 10 */ "pink", - /* 11 */ "dark cyan", - /* 12 */ "costume color", - }); - try { - ret_tokens.emplace_back(string_printf("(%s)", mag_colors.at(item.data2[0]))); - } catch (const out_of_range&) { - ret_tokens.emplace_back(string_printf("(!CL:%02hhX)", item.data2[0])); - } - } - - // For tools, add the amount (if applicable) - } else if (item.data1[0] == 0x03) { - if (stack_size_for_item(item) > 1) { - ret_tokens.emplace_back(string_printf("x%hhu", item.data1[5])); - } - } - - string ret = join(ret_tokens, " "); - if (include_color_codes) { - if (name_info.is_s_rank) { - return "$C4" + ret; - } else if (name_info.is_rare) { - return "$C6" + ret; - } else { - return "$C7" + ret; - } - } else { - return ret; - } -} diff --git a/src/StaticGameData.hh b/src/StaticGameData.hh index 661420a8..d183531e 100644 --- a/src/StaticGameData.hh +++ b/src/StaticGameData.hh @@ -34,12 +34,10 @@ const char* abbreviation_for_mode(GameMode mode); -size_t stack_size_for_item(uint8_t data0, uint8_t data1); -size_t stack_size_for_item(const ItemData& item); +size_t max_stack_size_for_item(uint8_t data0, uint8_t data1); -extern const std::unordered_map name_for_weapon_special; -extern const std::unordered_map name_for_s_rank_special; -extern const std::unordered_map name_for_primary_identifier; +extern const vector tech_id_to_name; +extern const unordered_map name_to_tech_id; const std::string& name_for_technique(uint8_t tech); std::u16string u16name_for_technique(uint8_t tech); @@ -73,5 +71,3 @@ const char* name_for_difficulty(uint8_t difficulty); char abbreviation_for_difficulty(uint8_t difficulty); char char_for_language_code(uint8_t language); - -std::string name_for_item(const ItemData& item, bool include_color_codes); diff --git a/system/blueburst/ArmorRandom_GC.rel b/system/blueburst/ArmorRandom_GC.rel new file mode 100755 index 00000000..c691a079 Binary files /dev/null and b/system/blueburst/ArmorRandom_GC.rel differ diff --git a/system/blueburst/ItemPT_GC.gsl b/system/blueburst/ItemPT_GC.gsl new file mode 100755 index 00000000..a0ffa9b6 Binary files /dev/null and b/system/blueburst/ItemPT_GC.gsl differ diff --git a/system/blueburst/ToolRandom_GC.rel b/system/blueburst/ToolRandom_GC.rel new file mode 100755 index 00000000..c91920f2 Binary files /dev/null and b/system/blueburst/ToolRandom_GC.rel differ diff --git a/system/blueburst/WeaponRandomHard_GC.rel b/system/blueburst/WeaponRandomHard_GC.rel new file mode 100755 index 00000000..1e07be51 Binary files /dev/null and b/system/blueburst/WeaponRandomHard_GC.rel differ diff --git a/system/blueburst/WeaponRandomNormal_GC.rel b/system/blueburst/WeaponRandomNormal_GC.rel new file mode 100755 index 00000000..fc7c34f0 Binary files /dev/null and b/system/blueburst/WeaponRandomNormal_GC.rel differ diff --git a/system/blueburst/WeaponRandomUltimate_GC.rel b/system/blueburst/WeaponRandomUltimate_GC.rel new file mode 100755 index 00000000..8cf17a35 Binary files /dev/null and b/system/blueburst/WeaponRandomUltimate_GC.rel differ diff --git a/system/blueburst/WeaponRandomVeryHard_GC.rel b/system/blueburst/WeaponRandomVeryHard_GC.rel new file mode 100755 index 00000000..73246dc5 Binary files /dev/null and b/system/blueburst/WeaponRandomVeryHard_GC.rel differ diff --git a/system/blueburst/droptype.ini b/system/blueburst/droptype.ini deleted file mode 100644 index aae6cfb8..00000000 --- a/system/blueburst/droptype.ini +++ /dev/null @@ -1,154 +0,0 @@ -# Types of nonrare items that enemies drop. - -# NORMAL MODE - -# shield = 01 -# armor = 02 -# unit = 03 -# random = 04 -# unknown = 05 -# weapon = 06 - -Drop_Item_00_00 06 # booma -Drop_Item_00_01 06 # gobooma -Drop_Item_00_02 01 # gigobooma -Drop_Item_00_03 02 # savage wolf -Drop_Item_00_04 06 # barbarous wolf -Drop_Item_00_05 02 # rag rappy -Drop_Item_00_06 01 # al rappy -Drop_Item_00_07 06 # mothmant -Drop_Item_00_7A 05 # monest -Drop_Item_00_08 06 # hildebear -Drop_Item_00_09 06 # hildeblue -Drop_Item_00_0A 04 # dragon -Drop_Item_00_0B 06 # evil shark -Drop_Item_00_0C 06 # pal shark -Drop_Item_00_0D 03 # guil shark -Drop_Item_00_0E 02 # poison lily -Drop_Item_00_0F 03 # nar lily -Drop_Item_00_10 06 # grass assassin -Drop_Item_00_11 01 # nano dragon -Drop_Item_00_12 02 # pofuilly slime -Drop_Item_00_13 03 # pouilly slime -Drop_Item_00_14 01 # pan arms -Drop_Item_00_15 03 # migium -Drop_Item_00_16 02 # hidoom -Drop_Item_00_17 04 # de rol le -Drop_Item_00_18 06 # gillchic -Drop_Item_00_19 06 # dubchic -Drop_Item_00_1A 01 # canadine -Drop_Item_00_1B 02 # canane -Drop_Item_00_1C 06 # sinow beat -Drop_Item_00_1D 03 # sinow gold -Drop_Item_00_1E 01 # garanz -Drop_Item_00_1F 04 # vol opt -Drop_Item_00_20 06 # dimenian -Drop_Item_00_21 06 # la dimenian -Drop_Item_00_22 02 # so dimenian -Drop_Item_00_23 06 # claw -Drop_Item_00_24 03 # bulclaw -Drop_Item_00_25 06 # bulk -Drop_Item_00_26 02 # delsaber -Drop_Item_00_27 02 # dark belra -Drop_Item_00_28 01 # dark gunner -Drop_Item_00_29 01 # death dunner -Drop_Item_00_2A 03 # chaos sorceror -Drop_Item_00_2B 03 # chaos bringer -Drop_Item_00_2C 04 # dark falz - -Drop_Item_00_2E 06 # dimenian -Drop_Item_00_2F 06 # la dimenian -Drop_Item_00_30 01 # so dimenian -Drop_Item_00_31 02 # rag rappy -Drop_Item_00_32 01 # love rappy -Drop_Item_00_33 04 # hallo rappy -Drop_Item_00_34 04 # saint rappy -Drop_Item_00_35 04 # egg rappy -Drop_Item_00_36 06 # grass assassin -Drop_Item_00_37 02 # poison lily -Drop_Item_00_38 03 # nar lily -Drop_Item_00_39 06 # mothmant -Drop_Item_00_3A 06 # hildebear -Drop_Item_00_3B 06 # hildeblue -Drop_Item_00_3C 02 # dark belra -Drop_Item_00_3D 04 # barba ray - -Drop_Item_00_3E 02 # savage wolf -Drop_Item_00_3F 06 # barbarous wolf -Drop_Item_00_40 06 # gillchic -Drop_Item_00_41 06 # dubchic -Drop_Item_00_42 02 # delsaber -Drop_Item_00_43 01 # pan arms -Drop_Item_00_44 03 # migium -Drop_Item_00_45 02 # hidoom -Drop_Item_00_46 01 # garanz -Drop_Item_00_47 03 # chaos sorceror -Drop_Item_00_48 04 # gol dragon - -Drop_Item_00_49 06 # merillia -Drop_Item_00_4A 06 # meriltas -Drop_Item_00_4B 06 # ul gibbon -Drop_Item_00_4C 01 # zol gibbon -Drop_Item_00_4D 06 # gee -Drop_Item_00_4E 06 # sinow berill -Drop_Item_00_4F 02 # sinow spigell -Drop_Item_00_50 05 # mericarol -Drop_Item_00_51 02 # merikle -Drop_Item_00_52 01 # mericus -Drop_Item_00_53 03 # gibbles -Drop_Item_00_54 01 # gi gue -Drop_Item_00_55 06 # del lily -Drop_Item_00_56 02 # ill gill -Drop_Item_00_57 03 # epsilon -Drop_Item_00_58 04 # gal gryphon - -Drop_Item_00_59 06 # dolmolm -Drop_Item_00_5A 06 # dolmdarl -Drop_Item_00_5B 03 # recobox -Drop_Item_00_5C 06 # recon -Drop_Item_00_5D 01 # deldepth -Drop_Item_00_5E 06 # delbiter -Drop_Item_00_5F 06 # morfos -Drop_Item_00_60 06 # sinow zoa -Drop_Item_00_61 02 # sinow zele -Drop_Item_00_62 04 # olga flow - -Drop_Item_00_64 05 # boota -Drop_Item_00_65 05 # ze boota -Drop_Item_00_66 05 # ba boota -Drop_Item_00_67 05 # sand rappy -Drop_Item_00_68 05 # del rappy -Drop_Item_00_69 05 # satellite lizard -Drop_Item_00_6A 05 # yowie -Drop_Item_00_6B 05 # astark -Drop_Item_00_6C 05 # zu -Drop_Item_00_6D 05 # pazuzu -Drop_Item_00_6E 05 # dorphon -Drop_Item_00_6F 05 # dorphon eclair -Drop_Item_00_70 05 # goran -Drop_Item_00_71 05 # pyro goran -Drop_Item_00_72 05 # goran detonator -Drop_Item_00_73 05 # merissa a -Drop_Item_00_74 05 # merissa aa -Drop_Item_00_75 05 # girtablulu -Drop_Item_00_76 05 # saint-million -Drop_Item_00_77 05 # shambertin -Drop_Item_00_78 05 # kondrieu - -# HARD MODE - -Drop_Item_01_30 02 # so dimenian -Drop_Item_01_56 01 # ill gill - -# VERY HARD MODE - -Drop_Item_02_02 03 # gigobooma -Drop_Item_02_04 01 # barbarous wolf 08 -Drop_Item_02_05 01 # rag rappy 05 -Drop_Item_02_31 01 # rag rappy -Drop_Item_02_3F 01 # barbarous wolf -Drop_Item_02_56 02 # ill gill - -# ULTIMATE MODE - -# no changes \ No newline at end of file diff --git a/system/blueburst/enemies.nsi b/system/blueburst/enemies.nsi deleted file mode 100644 index de3da997..00000000 Binary files a/system/blueburst/enemies.nsi and /dev/null differ diff --git a/system/config.example.json b/system/config.example.json index 8615044d..3f886c3e 100644 --- a/system/config.example.json +++ b/system/config.example.json @@ -316,63 +316,4 @@ // be turned off here. This option has no effect on Blue Burst games - item // tracking is always enabled for them. "EnableItemTracking": true, - - // Item drop rates for non-rare items in BB games. For each type (boxes or - // enemies), all the categories must add up to a number less than 0x100000000. - // Each number is a probability (out of 0x100000000) that the given item type - // will appear. - "CommonItemDropRates-Enemy": [ - 0x03000000, // material - 0x20000000, // equipment - 0x06000000, // technique disk - 0x01800000, // scape doll - 0x06000000, // grinder - 0x10000000, // atomizers, etc. - 0x20000000, // mates/fluids - 0x40000000, // meseta - ], - "CommonItemDropRates-Box": [ - 0x00800000, // material - 0x20000000, // equipment - 0x01000000, // technique disk - 0x02000000, // scape doll - 0x08000000, // grinder - 0x10000000, // atomizers, etc. - 0x20000000, // mates/fluids - 0x80000000, // meseta - ], - - // Unit drop rates for non-rare items in BB games. Each entry is an array of - // unit types, one array per difficulty. Each entry in the array has an equal - // probability of dropping. If a unit type is 0xFF, then no item will drop. - "CommonUnitTypes": [ - // normal - [0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x08, 0x08, 0x08, 0x0C, 0x0C, 0x0C, - 0x10, 0x10, 0x10, 0x14, 0x14, 0x14, 0x18, 0x18, 0x18, 0x21, 0x21, 0x21, - 0x24, 0x24, 0x24, 0x27, 0x27, 0x27, 0x2A, 0x2A, 0x2A, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF], - // hard - [0x01, 0x01, 0x05, 0x05, 0x09, 0x09, 0x0D, 0x0D, 0x11, 0x11, 0x15, 0x15, - 0x19, 0x19, 0x21, 0x21, 0x24, 0x24, 0x27, 0x27, 0x2A, 0x2A, 0x30, 0x30, - 0x33, 0x33, 0x36, 0x36, 0x39, 0x39, 0x3C, 0x3C, 0x3F, 0x3F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF], - // very hard - [0x02, 0x02, 0x06, 0x06, 0x0A, 0x0A, 0x0E, 0x0E, 0x12, 0x12, 0x16, 0x16, - 0x1A, 0x1A, 0x22, 0x22, 0x25, 0x25, 0x28, 0x28, 0x2B, 0x2B, 0x31, 0x31, - 0x34, 0x34, 0x37, 0x37, 0x3A, 0x3A, 0x3D, 0x3D, 0x3F, 0x3F, 0x41, 0x41, - 0x42, 0x42, 0x43, 0x43, 0x44, 0x44, 0x45, 0x45, 0x46, 0x46, 0x47, 0x47, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF], - // ultimate - [0x02, 0x02, 0x06, 0x06, 0x0A, 0x0A, 0x0E, 0x0E, 0x12, 0x12, 0x16, 0x16, - 0x1A, 0x1A, 0x23, 0x23, 0x26, 0x26, 0x29, 0x29, 0x2C, 0x2C, 0x31, 0x31, - 0x34, 0x34, 0x37, 0x37, 0x3A, 0x3A, 0x3D, 0x3D, 0x3F, 0x3F, 0x41, 0x41, - 0x42, 0x42, 0x43, 0x43, 0x44, 0x44, 0x45, 0x45, 0x46, 0x46, 0x47, 0x47, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF], - ], } \ No newline at end of file