From 838e53a91e829ea9ae1b69b36ee9a018b515696c Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Tue, 7 Mar 2023 23:16:42 -0800 Subject: [PATCH] use GC logic for BB nonrare item drop generation and shops --- CMakeLists.txt | 4 + src/ChatCommands.cc | 8 +- src/CommandFormats.hh | 4 +- src/CommonItemSet.cc | 140 ++ src/CommonItemSet.hh | 498 ++++-- src/ItemCreator.cc | 1598 ++++++++++++++++++ src/ItemCreator.hh | 193 +++ src/ItemData.cc | 1443 ++++++++++++++++ src/ItemData.hh | 92 + src/ItemParameterTable.cc | 195 +++ src/ItemParameterTable.hh | 239 +++ src/Items.cc | 375 ---- src/Items.hh | 25 - src/Lobby.hh | 4 +- src/Main.cc | 165 +- src/Player.cc | 42 +- src/Player.hh | 20 +- src/ProxyCommands.cc | 4 +- src/RareItemSet.cc | 68 +- src/RareItemSet.hh | 52 +- src/ReceiveCommands.cc | 22 +- src/ReceiveSubcommands.cc | 128 +- src/ServerShell.cc | 4 +- src/ServerState.hh | 8 +- src/StaticGameData.cc | 1293 +------------- src/StaticGameData.hh | 10 +- system/blueburst/ArmorRandom_GC.rel | Bin 0 -> 768 bytes system/blueburst/ItemPT_GC.gsl | Bin 0 -> 667648 bytes system/blueburst/ToolRandom_GC.rel | Bin 0 -> 672 bytes system/blueburst/WeaponRandomHard_GC.rel | Bin 0 -> 2880 bytes system/blueburst/WeaponRandomNormal_GC.rel | Bin 0 -> 2496 bytes system/blueburst/WeaponRandomUltimate_GC.rel | Bin 0 -> 3296 bytes system/blueburst/WeaponRandomVeryHard_GC.rel | Bin 0 -> 2720 bytes system/blueburst/droptype.ini | 154 -- system/blueburst/enemies.nsi | Bin 49848 -> 0 bytes system/config.example.json | 59 - 36 files changed, 4577 insertions(+), 2270 deletions(-) create mode 100644 src/CommonItemSet.cc create mode 100644 src/ItemCreator.cc create mode 100644 src/ItemCreator.hh create mode 100644 src/ItemData.cc create mode 100644 src/ItemData.hh create mode 100644 src/ItemParameterTable.cc create mode 100644 src/ItemParameterTable.hh create mode 100755 system/blueburst/ArmorRandom_GC.rel create mode 100755 system/blueburst/ItemPT_GC.gsl create mode 100755 system/blueburst/ToolRandom_GC.rel create mode 100755 system/blueburst/WeaponRandomHard_GC.rel create mode 100755 system/blueburst/WeaponRandomNormal_GC.rel create mode 100755 system/blueburst/WeaponRandomUltimate_GC.rel create mode 100755 system/blueburst/WeaponRandomVeryHard_GC.rel delete mode 100644 system/blueburst/droptype.ini delete mode 100644 system/blueburst/enemies.nsi 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 0000000000000000000000000000000000000000..c691a079aaa8df6e59bdb40b68ce65f0f301bd93 GIT binary patch literal 768 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 literal 0 HcmV?d00001 diff --git a/system/blueburst/WeaponRandomHard_GC.rel b/system/blueburst/WeaponRandomHard_GC.rel new file mode 100755 index 0000000000000000000000000000000000000000..1e07be5103c75529e6c8c320eafe0bb724c9e5bf GIT binary patch literal 2880 zcmb7EOK#&v5G|=c)ud>XgHQ`ufF!^cATMmk0%Tx-Y-Tg-StK`@MUIfeWRc|@B}d2+ za*VuJ?51f!9!y$jRkP~Vt5?;OWEI-gYSTXS(uaQ4ZdN$a*KhiKdX26&^~1`dt4(;= z;1rPC!`I``Ta+wkxwE>~R{G|qzSYP2Sx*}E&}|idX|>mhzd7i(gTKpu8S%Ppj7R$} zwOJW2n+>G4WIPhQmk6O0Qc?ZRBt&S!syL z5R#QrM-7gknh|9l>5$s0Pg~6Kl;(cn)vx`XA9X}1l@>FdRN=(pJTAb2S%#ZlG<0qW-g}1(eTv9%mVud% z8L|yyhN8xh;rFr&R%6JZDvx(#s6kco99%S2Zi)V_235&1SdCeX$e3A1n)ERj$&*Z!5F@&#NKM?_aQI*gf_4(#rQi4P^b<=Q*H>)Lhl~2TzIR{$7yk)-6i*&vsh5lP5Rd4$s$TmVMG+Y?q(UE&p8nu6oqL!GJmXEild>*AQxx`FE*+I0y=bPm!M;p0)2oyNFSqPAFq$m^HHR$ z2u_gBy#aNo!^3m$CFPj5sM@xxj*V%GrmebGGq|Q}((LbQ+l6BrSKF>QcA6F1+lXs4 zY2qp_v%Q7u+@cJwGEIG3zICUvcY`a+Znp@*RIYKiL2QVu>q@b^#F{hf#5%FNq%8+i zypnGSE3qrFH)pL$+VbqXD@^Cb`l@ccSTC&~d{IY-&7=l1Yzm*WURp1$k6PPVIaoPZ zIh1m+LuA&x+uj~(br|;cR7T#c$q#QU+t_`53X!j)-kfi5i>ee5>!_*i`rqds=qm-11+;&r3)xgN7l z^yI}qcAI!ZpW?TV>QTSvykniAH%q@!zwy5P!QZ8Bdzbo(cY82*KxOy{UBtPo8&}cN430FemYJ_YT-`?wqzS5ZLdB3FZl0GCoI{aG=^yE$D zYGw>N{8`68>(Qad?>RmD@Xz|E{Nbnd>gk{gQvqfe-%R z^uE|nJ=y=IdVJ7QF9BZi$9L+_J|gQ`ryh9f2d8@0iEkQH|48a_pLos#pB(x31x|VV z@cH@#&+j;&kEZcl&>s>H61o1ImwiO$L43%=d^gwI%~_c>lC{wCq8gmb_Ap8Yb; z{qQ03nascW^|)+YLTV&90|GsB?j8?xaQuq|mG6r2L6YBE{;u)M82eMGcmGHQB)=s(rszodl-e*i$2UGD$@ literal 0 HcmV?d00001 diff --git a/system/blueburst/WeaponRandomUltimate_GC.rel b/system/blueburst/WeaponRandomUltimate_GC.rel new file mode 100755 index 0000000000000000000000000000000000000000..8cf17a359ad01af4d809d2388b6c823ea31bd6db GIT binary patch literal 3296 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 literal 0 HcmV?d00001 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 de3da9976f836bb5e5805d86b526512d9d2dadac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 49848 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