diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f6f6d63..b3db3d06 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,6 +59,7 @@ add_executable(newserv src/ServerShell.cc src/ServerState.cc src/Shell.cc + src/StaticGameData.cc src/Text.cc src/Version.cc ) diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index e4c4b793..3a2d2e97 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -13,307 +13,11 @@ #include "Client.hh" #include "SendCommands.hh" #include "Text.hh" +#include "StaticGameData.hh" using namespace std; -//////////////////////////////////////////////////////////////////////////////// - -const vector section_id_to_name({ - "Viridia", "Greennill", "Skyly", "Bluefull", "Purplenum", "Pinkal", "Redria", - "Oran", "Yellowboze", "Whitill"}); - -const unordered_map name_to_section_id({ - {"viridia", 0}, - {"greennill", 1}, - {"skyly", 2}, - {"bluefull", 3}, - {"purplenum", 4}, - {"pinkal", 5}, - {"redria", 6}, - {"oran", 7}, - {"yellowboze", 8}, - {"whitill", 9}}); - -const vector lobby_event_to_name({ - "none", "xmas", "none", "val", "easter", "hallo", "sonic", "newyear", - "summer", "white", "wedding", "fall", "s-spring", "s-summer", "spring"}); - -const unordered_map name_to_lobby_event({ - {"none", 0}, - {"xmas", 1}, - {"val", 3}, - {"easter", 4}, - {"hallo", 5}, - {"sonic", 6}, - {"newyear", 7}, - {"summer", 8}, - {"white", 9}, - {"wedding", 10}, - {"fall", 11}, - {"s-spring", 12}, - {"s-summer", 13}, - {"spring", 14}, -}); - -const unordered_map lobby_type_to_name({ - {0x00, "normal"}, - {0x0F, "inormal"}, - {0x10, "ipc"}, - {0x11, "iball"}, - {0x67, "cave2u"}, - {0xD4, "cave1"}, - {0xE9, "planet"}, - {0xEA, "clouds"}, - {0xED, "cave"}, - {0xEE, "jungle"}, - {0xEF, "forest2-2"}, - {0xF0, "forest2-1"}, - {0xF1, "windpower"}, - {0xF2, "overview"}, - {0xF3, "seaside"}, - {0xF4, "some?"}, - {0xF5, "dmorgue"}, - {0xF6, "caelum"}, - {0xF8, "digital"}, - {0xF9, "boss1"}, - {0xFA, "boss2"}, - {0xFB, "boss3"}, - {0xFC, "dragon"}, - {0xFD, "derolle"}, - {0xFE, "volopt"}, - {0xFF, "darkfalz"}, -}); - -const unordered_map name_to_lobby_type({ - {"normal", 0x00}, - {"inormal", 0x0F}, - {"ipc", 0x10}, - {"iball", 0x11}, - {"cave1", 0xD4}, - {"cave2u", 0x67}, - {"dragon", 0xFC}, - {"derolle", 0xFD}, - {"volopt", 0xFE}, - {"darkfalz", 0xFF}, - {"planet", 0xE9}, - {"clouds", 0xEA}, - {"cave", 0xED}, - {"jungle", 0xEE}, - {"forest2-2", 0xEF}, - {"forest2-1", 0xF0}, - {"windpower", 0xF1}, - {"overview", 0xF2}, - {"seaside", 0xF3}, - {"some?", 0xF4}, - {"dmorgue", 0xF5}, - {"caelum", 0xF6}, - {"digital", 0xF8}, - {"boss1", 0xF9}, - {"boss2", 0xFA}, - {"boss3", 0xFB}, - {"knight", 0xFC}, - {"sky", 0xFE}, - {"morgue", 0xFF}, -}); - -const vector tech_id_to_name({ - "foie", "gifoie", "rafoie", - "barta", "gibarta", "rabarta", - "zonde", "gizonde", "razonde", - "grants", "deband", "jellen", "zalure", "shifta", - "ryuker", "resta", "anti", "reverser", "megid"}); - -const unordered_map name_to_tech_id({ - {"foie", 0}, - {"gifoie", 1}, - {"rafoie", 2}, - {"barta", 3}, - {"gibarta", 4}, - {"rabarta", 5}, - {"zonde", 6}, - {"gizonde", 7}, - {"razonde", 8}, - {"grants", 9}, - {"deband", 10}, - {"jellen", 11}, - {"zalure", 12}, - {"shifta", 13}, - {"ryuker", 14}, - {"resta", 15}, - {"anti", 16}, - {"reverser", 17}, - {"megid", 18}, -}); - -const vector npc_id_to_name({ - "ninja", "rico", "sonic", "knuckles", "tails", "flowen", "elly"}); - -const unordered_map name_to_npc_id({ - {"ninja", 0}, - {"rico", 1}, - {"sonic", 2}, - {"knuckles", 3}, - {"tails", 4}, - {"flowen", 5}, - {"elly", 6}}); - -const string& name_for_section_id(uint8_t section_id) { - if (section_id < section_id_to_name.size()) { - return section_id_to_name[section_id]; - } else { - static const string ret = ""; - return ret; - } -} - -u16string u16name_for_section_id(uint8_t section_id) { - return decode_sjis(name_for_section_id(section_id)); -} - -uint8_t section_id_for_name(const string& name) { - try { - return name_to_section_id.at(name); - } catch (const out_of_range&) { } - try { - uint64_t x = stoul(name); - if (x < section_id_to_name.size()) { - return x; - } - } catch (const invalid_argument&) { - } catch (const out_of_range&) { } - return 0xFF; -} - -uint8_t section_id_for_name(const u16string& name) { - return section_id_for_name(encode_sjis(name)); -} - -const string& name_for_event(uint8_t event) { - if (event < lobby_event_to_name.size()) { - return lobby_event_to_name[event]; - } else { - static const string ret = ""; - return ret; - } -} - -u16string u16name_for_event(uint8_t event) { - return decode_sjis(name_for_event(event)); -} - -uint8_t event_for_name(const string& name) { - try { - return name_to_lobby_event.at(name); - } catch (const out_of_range&) { } - try { - uint64_t x = stoul(name); - if (x < lobby_event_to_name.size()) { - return x; - } - } catch (const invalid_argument&) { - } catch (const out_of_range&) { } - return 0xFF; -} - -uint8_t event_for_name(const u16string& name) { - return event_for_name(encode_sjis(name)); -} - -const string& name_for_lobby_type(uint8_t type) { - try { - return lobby_type_to_name.at(type); - } catch (const out_of_range&) { - static const string ret = ""; - return ret; - } -} - -u16string u16name_for_lobby_type(uint8_t type) { - return decode_sjis(name_for_lobby_type(type)); -} - -uint8_t lobby_type_for_name(const string& name) { - try { - return name_to_lobby_type.at(name); - } catch (const out_of_range&) { } - try { - uint64_t x = stoul(name); - if (x < lobby_type_to_name.size()) { - return x; - } - } catch (const invalid_argument&) { - } catch (const out_of_range&) { } - return 0x80; -} - -uint8_t lobby_type_for_name(const u16string& name) { - return lobby_type_for_name(encode_sjis(name)); -} - -const string& name_for_technique(uint8_t tech) { - try { - return tech_id_to_name.at(tech); - } catch (const out_of_range&) { - static const string ret = ""; - return ret; - } -} - -u16string u16name_for_technique(uint8_t tech) { - return decode_sjis(name_for_technique(tech)); -} - -uint8_t technique_for_name(const string& name) { - try { - return name_to_tech_id.at(name); - } catch (const out_of_range&) { } - try { - uint64_t x = stoul(name); - if (x < tech_id_to_name.size()) { - return x; - } - } catch (const invalid_argument&) { - } catch (const out_of_range&) { } - return 0xFF; -} - -uint8_t technique_for_name(const u16string& name) { - return technique_for_name(encode_sjis(name)); -} - -const string& name_for_npc(uint8_t npc) { - try { - return npc_id_to_name.at(npc); - } catch (const out_of_range&) { - static const string ret = ""; - return ret; - } -} - -u16string u16name_for_npc(uint8_t npc) { - return decode_sjis(name_for_npc(npc)); -} - -uint8_t npc_for_name(const string& name) { - try { - return name_to_npc_id.at(name); - } catch (const out_of_range&) { } - try { - uint64_t x = stoul(name); - if (x < npc_id_to_name.size()) { - return x; - } - } catch (const invalid_argument&) { - } catch (const out_of_range&) { } - return 0xFF; -} - -uint8_t npc_for_name(const u16string& name) { - return npc_for_name(encode_sjis(name)); -} - - //////////////////////////////////////////////////////////////////////////////// // Checks @@ -861,6 +565,38 @@ static void command_next(shared_ptr, shared_ptr l, send_warp(c, new_area); } +static void command_what(shared_ptr, shared_ptr l, + shared_ptr c, const std::u16string&) { + check_is_game(l, true); + if (!l->episode || (l->episode > 3)) { + return; + } + + float min_dist2 = 0.0f; + uint32_t nearest_item_id = 0xFFFFFFFF; + for (const auto& it : l->item_id_to_floor_item) { + if (it.second.area != c->area) { + continue; + } + float dx = it.second.x - c->x; + float dz = it.second.z - c->z; + float dist2 = (dx * dx) + (dz * dz); + if ((nearest_item_id == 0xFFFFFFFF) || (dist2 < min_dist2)) { + nearest_item_id = it.first; + min_dist2 = dist2; + } + } + + if (nearest_item_id == 0xFFFFFFFF) { + send_text_message(c, u"No 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); + send_text_message_printf(c, "$C6%s\n$C7ID: %08" PRIX32, + name.c_str(), item.inv_item.data.item_id.load()); + } +} + static void command_song(shared_ptr, shared_ptr, shared_ptr c, const std::u16string& args) { check_is_ep3(c, true); @@ -961,6 +697,7 @@ static const unordered_map chat_commands({ {u"$swa" , {command_switch_assist , u"Usage:\nswa"}}, {u"$type" , {command_lobby_type , u"Usage:\ntype "}}, {u"$warp" , {command_warp , u"Usage:\nwarp "}}, + {u"$what" , {command_what , u"Usage:\nwhat"}}, }); // This function is called every time any player sends a chat beginning with a diff --git a/src/ChatCommands.hh b/src/ChatCommands.hh index db3c80c5..8187da55 100644 --- a/src/ChatCommands.hh +++ b/src/ChatCommands.hh @@ -9,30 +9,5 @@ #include "Lobby.hh" #include "Client.hh" -const std::string& name_for_section_id(uint8_t section_id); -std::u16string u16name_for_section_id(uint8_t section_id); -uint8_t section_id_for_name(const std::string& name); -uint8_t section_id_for_name(const std::u16string& name); - -const std::string& name_for_event(uint8_t event); -std::u16string u16name_for_event(uint8_t event); -uint8_t event_for_name(const std::string& name); -uint8_t event_for_name(const std::u16string& name); - -const std::string& name_for_lobby_type(uint8_t type); -std::u16string u16name_for_lobby_type(uint8_t type); -uint8_t lobby_type_for_name(const std::string& name); -uint8_t lobby_type_for_name(const std::u16string& name); - -const std::string& name_for_technique(uint8_t tech); -std::u16string u16name_for_technique(uint8_t tech); -uint8_t technique_for_name(const std::string& name); -uint8_t technique_for_name(const std::u16string& name); - -const std::string& name_for_npc(uint8_t npc); -std::u16string u16name_for_npc(uint8_t npc); -uint8_t npc_for_name(const std::string& name); -uint8_t npc_for_name(const std::u16string& name); - void process_chat_command(std::shared_ptr s, std::shared_ptr l, std::shared_ptr c, const std::u16string& text); diff --git a/src/Client.cc b/src/Client.cc index 9b902f42..f0831d0d 100644 --- a/src/Client.cc +++ b/src/Client.cc @@ -33,6 +33,8 @@ Client::Client( play_time_begin(now()), last_recv_time(this->play_time_begin), last_send_time(0), + x(0.0f), + z(0.0f), area(0), lobby_id(0), lobby_client_id(0), diff --git a/src/Client.hh b/src/Client.hh index 51644385..03e6aff8 100644 --- a/src/Client.hh +++ b/src/Client.hh @@ -94,6 +94,8 @@ struct Client { uint64_t last_send_time; // time of last data sent // Lobby/positioning + float x; + float z; uint32_t area; // which area is the client in? uint32_t lobby_id; // which lobby is this person in? uint8_t lobby_client_id; // which client number is this person? diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index 80673251..43dd8a9c 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -453,6 +453,11 @@ struct S_OpenFile_BB_44_A6 { // 61 (C->S): Player data // See PSOPlayerDataPC, PSOPlayerDataGC, PSOPlayerDataBB in Player.hh for this // command's format. +// Note: If the client is in a game, the inventory sent by the client only +// includes items that would not disappear if the client was disconnected! +// Upon joining a game, the client assigns inventory item IDs sequentially as +// (0x00010000 + (0x00200000 * lobby_client_id) + x). So, for example, player +// 3's 8th item's ID would become 0x00610007. // 62: Target command // When a client sends this command, the server should forward it to the player @@ -1434,7 +1439,7 @@ struct G_EnemyHitByPlayer_6x0A { struct G_SetPlayerVisibility_6x22_6x23 { uint8_t subcommand; // 22 = invisible, 23 = visible - uint8_t subsize; + uint8_t size; le_uint16_t client_id; }; @@ -1463,27 +1468,29 @@ struct G_UnequipItem_6x26 { }; // 27: Use item -// Format is G_ItemSubcommand + +struct G_UseItem_6x27 { + uint8_t command; + uint8_t size; + uint8_t client_id; + uint8_t unused; + le_uint32_t item_id; +}; // 28: Feed MAG struct G_FeedMAG_6x28 { uint8_t subcommand; - uint8_t subsize; + uint8_t size; le_uint16_t client_id; le_uint32_t mag_item_id; le_uint32_t fed_item_id; }; // 29: Delete item (via bank deposit / sale / feeding MAG) - -struct G_DestroyItem_6x29 { - uint8_t subcommand; - uint8_t subsize; - le_uint16_t client_id; - le_uint32_t item_id; - le_uint32_t amount; -}; +// This subcommand is also used for reducing the size of stacks - if amount is +// less than the stack count, the item is not deleted; its item ID remains valid +// Format is G_ItemSubcommand // 2A: Drop item @@ -1549,22 +1556,32 @@ struct G_LevelUp_6x30 { struct G_StopAtPosition_6x3E { uint8_t subcommand; - uint8_t subsize; + uint8_t size; le_uint16_t client_id; uint64_t unknown; le_float x; le_float y; le_float z; - uint32_t unused; }; -// 3F: Unknown (supported; lobby & game) +// 3F: Set position + +struct G_SetPosition_6x3F { + uint8_t subcommand; + uint8_t size; + le_uint16_t client_id; + le_uint32_t unknown; + le_uint32_t area; + le_float x; + le_float y; + le_float z; +}; // 40: Walk struct G_WalkToPosition_6x40 { uint8_t subcommand; - uint8_t subsize; + uint8_t size; le_uint16_t client_id; le_float x; le_float z; @@ -1577,7 +1594,7 @@ struct G_WalkToPosition_6x40 { struct G_RunToPosition_6x42 { uint8_t subcommand; - uint8_t subsize; + uint8_t size; le_uint16_t client_id; le_float x; le_float z; @@ -1610,7 +1627,7 @@ struct G_RunToPosition_6x42 { struct G_PickUpItem_6x59 { uint8_t subcommand; - uint8_t subsize; + uint8_t size; le_uint16_t client_id; le_uint16_t client_id2; le_uint16_t area; @@ -1636,21 +1653,22 @@ struct G_PickUpItemRequest_6x5A { struct G_DropStackedItem_6x5D { uint8_t subcommand; - uint8_t subsize; - le_uint16_t unused; + uint8_t size; + uint8_t client_id; // TODO: verify this + uint8_t unused; le_uint16_t area; le_uint16_t unused2; le_float x; - le_float y; - le_uint32_t unused3; + le_float z; ItemData data; + le_uint32_t unused3; }; // 5E: Buy item at shop struct G_BuyShopItem_6x5E { uint8_t subcommand; - uint8_t subsize; + uint8_t size; uint8_t client_id; uint8_t unused; ItemData item; @@ -1660,15 +1678,16 @@ struct G_BuyShopItem_6x5E { struct G_DropItem_6x5F { uint8_t subcommand; - uint8_t subsize; + uint8_t size; le_uint16_t unused; uint8_t area; uint8_t enemy_instance; le_uint16_t request_id; le_float x; - le_float y; + le_float z; le_uint32_t unused2; ItemData data; + le_uint32_t unused3; }; // 60: Request for item drop (handled by the server on BB) @@ -1681,7 +1700,7 @@ struct G_EnemyDropItemRequest_6x60 { uint8_t enemy_id; le_uint16_t request_id; le_float x; - le_float y; + le_float z; le_uint64_t unknown; }; @@ -1761,7 +1780,7 @@ struct G_BoxItemDropRequest_6xA2 { uint8_t unused2; le_uint16_t request_id; le_float x; - le_float y; + le_float z; le_uint32_t unknown[6]; }; @@ -1847,7 +1866,7 @@ struct G_BankAction_BB_6xBD { struct G_CreateInventoryItem_BB_6xBE { uint8_t subcommand; - uint8_t subsize; + uint8_t size; le_uint16_t client_id; ItemData item; le_uint32_t unused; @@ -1877,7 +1896,7 @@ struct G_SplitStackedItem_6xC3 { le_uint16_t area; le_uint16_t unused2; le_float x; - le_float y; + le_float z; le_uint32_t item_id; le_uint32_t amount; }; diff --git a/src/Items.cc b/src/Items.cc index a416e190..a7ad7779 100644 --- a/src/Items.cc +++ b/src/Items.cc @@ -219,7 +219,7 @@ void player_use_item(shared_ptr c, size_t item_index) { } if (should_delete_item) { - c->player.remove_item(item.data.item_id, 1, nullptr); + c->player.remove_item(item.data.item_id, 1); } } diff --git a/src/Lobby.cc b/src/Lobby.cc index a6a3186a..9acab8b8 100644 --- a/src/Lobby.cc +++ b/src/Lobby.cc @@ -96,6 +96,16 @@ void Lobby::add_client(shared_ptr c, bool reverse_indexes) { this->leader_id = c->lobby_client_id; } } + + // If the lobby is a game, assign the inventory's item IDs + if (this->is_game()) { + auto& inv = c->player.inventory; + size_t count = max(inv.num_items, 30); + for (size_t x = 0; x < count; x++) { + inv.items[x].data.item_id = 0x00010000 + 0x00200000 * c->lobby_client_id + x; + } + c->player.print_inventory(stderr); + } } void Lobby::remove_client(shared_ptr c) { @@ -170,17 +180,22 @@ uint8_t Lobby::game_event_for_lobby_event(uint8_t lobby_event) { -void Lobby::add_item(const PlayerInventoryItem& item) { - this->item_id_to_floor_item.emplace(item.data.item_id, item); +void Lobby::add_item(const PlayerInventoryItem& item, uint8_t area, float x, float z) { + auto& fi = this->item_id_to_floor_item[item.data.item_id]; + fi.inv_item = item; + fi.area = area; + fi.x = x; + fi.z = z; } -void Lobby::remove_item(uint32_t item_id, PlayerInventoryItem* item) { +PlayerInventoryItem Lobby::remove_item(uint32_t item_id) { auto item_it = this->item_id_to_floor_item.find(item_id); if (item_it == this->item_id_to_floor_item.end()) { throw out_of_range("item not present"); } - *item = move(item_it->second); + PlayerInventoryItem ret = move(item_it->second.inv_item); this->item_id_to_floor_item.erase(item_it); + return ret; } uint32_t Lobby::generate_item_id(uint8_t client_id) { @@ -189,9 +204,3 @@ uint32_t Lobby::generate_item_id(uint8_t client_id) { } return this->next_game_item_id++; } - -void Lobby::assign_item_ids_for_player(uint32_t client_id, PlayerInventory& inv) { - for (size_t x = 0; x < inv.num_items; x++) { - inv.items[x].data.item_id = this->generate_item_id(client_id); - } -} \ No newline at end of file diff --git a/src/Lobby.hh b/src/Lobby.hh index 3ffa8c63..658ada53 100644 --- a/src/Lobby.hh +++ b/src/Lobby.hh @@ -33,12 +33,18 @@ struct Lobby { uint32_t max_level; // item info + struct FloorItem { + PlayerInventoryItem inv_item; + float x; + float z; + uint8_t area; + }; std::vector enemies; std::shared_ptr rare_item_set; std::array next_item_id; uint32_t next_game_item_id; PlayerInventoryItem next_drop_item; - std::unordered_map item_id_to_floor_item; + std::unordered_map item_id_to_floor_item; parray variations; // game config @@ -83,12 +89,10 @@ struct Lobby { const std::u16string* identifier = nullptr, uint64_t serial_number = 0); - void add_item(const PlayerInventoryItem& item); - void remove_item(uint32_t item_id, PlayerInventoryItem* item); + void add_item(const PlayerInventoryItem& item, uint8_t area, float x, float z); + PlayerInventoryItem remove_item(uint32_t item_id); size_t find_item(uint32_t item_id); uint32_t generate_item_id(uint8_t client_id); - void assign_item_ids_for_player(uint32_t client_id, PlayerInventory& inv); - static uint8_t game_event_for_lobby_event(uint8_t lobby_event); }; diff --git a/src/Player.cc b/src/Player.cc index 2dffbac5..f41470fa 100644 --- a/src/Player.cc +++ b/src/Player.cc @@ -9,6 +9,7 @@ #include "Text.hh" #include "Version.hh" +#include "StaticGameData.hh" using namespace std; @@ -439,29 +440,16 @@ PlayerLobbyDataBB::PlayerLobbyDataBB() noexcept //////////////////////////////////////////////////////////////////////////////// -static const unordered_map combine_item_to_max({ - {0x030000, 10}, - {0x030001, 10}, - {0x030002, 10}, - {0x030100, 10}, - {0x030101, 10}, - {0x030102, 10}, - {0x030300, 10}, - {0x030400, 10}, - {0x030500, 10}, - {0x030600, 10}, - {0x030601, 10}, - {0x030700, 10}, - {0x030800, 10}, - {0x031000, 99}, - {0x031001, 99}, - {0x031002, 99}, -}); - -const uint32_t meseta_identifier = 0x00000004; +const uint32_t meseta_identifier = 0x00040000; uint32_t ItemData::primary_identifier() const { - return (this->item_data1[0] << 16) | (this->item_data1[1] << 8) | this->item_data1[2]; + if (this->item_data1[0] == 0x03 && this->item_data1[1] == 0x02) { + return 0x00030200; // Tech disk (data1[2] is level, so omit it) + } else if (this->item_data1[0] == 0x02) { + return 0x00020000 | (this->item_data1[1] << 8); // Mag + } else { + return (this->item_data1[0] << 16) | (this->item_data1[1] << 8) | this->item_data1[2]; + } } PlayerBankItem PlayerInventoryItem::to_bank_item() const { @@ -577,21 +565,20 @@ void PlayerBank::add_item(const PlayerBankItem& item) { this->num_items++; } -void Player::remove_item(uint32_t item_id, uint32_t amount, - PlayerInventoryItem* item) { +PlayerInventoryItem Player::remove_item(uint32_t item_id, uint32_t amount) { + PlayerInventoryItem ret; // are we removing meseta? then create a meseta item if (item_id == 0xFFFFFFFF) { if (amount > this->disp.meseta) { throw out_of_range("player does not have enough meseta"); } - if (item) { - memset(item, 0, sizeof(*item)); - item->data.item_data1[0] = 0x04; - item->data.item_data2d = amount; - } + + memset(&ret, 0, sizeof(ret)); + ret.data.item_data1[0] = 0x04; + ret.data.item_data2d = amount; this->disp.meseta -= amount; - return; + return ret; } // find this item @@ -602,40 +589,35 @@ void Player::remove_item(uint32_t item_id, uint32_t amount, // (amount == 0 means remove all of it) if (amount && (amount < inventory_item.data.item_data1[5]) && combine_item_to_max.count(inventory_item.data.primary_identifier())) { - if (item) { - *item = inventory_item; - item->data.item_data1[5] = amount; - item->data.item_id = 0xFFFFFFFF; - } + ret = inventory_item; + ret.data.item_data1[5] = amount; + ret.data.item_id = 0xFFFFFFFF; inventory_item.data.item_data1[5] -= amount; - return; + return ret; } // not a combine item, or we're removing the whole stack? then just remove the item - if (item) { - *item = inventory_item; - } + ret = inventory_item; this->inventory.num_items--; memcpy(&this->inventory.items[index], &this->inventory.items[index + 1], sizeof(PlayerInventoryItem) * (this->inventory.num_items - index)); + return ret; } // removes an item from a bank. works just like RemoveItem for inventories; I won't comment it -void PlayerBank::remove_item(uint32_t item_id, uint32_t amount, - PlayerBankItem* item) { +PlayerBankItem PlayerBank::remove_item(uint32_t item_id, uint32_t amount) { + PlayerBankItem ret; // are we removing meseta? then create a meseta item if (item_id == 0xFFFFFFFF) { if (amount > this->meseta) { throw out_of_range("player does not have enough meseta"); } - if (item) { - memset(item, 0, sizeof(*item)); - item->data.item_data1[0] = 0x04; - item->data.item_data2d = amount; - } + memset(&ret, 0, sizeof(ret)); + ret.data.item_data1[0] = 0x04; + ret.data.item_data2d = amount; this->meseta -= amount; - return; + return ret; } // find this item @@ -646,23 +628,20 @@ void PlayerBank::remove_item(uint32_t item_id, uint32_t amount, // (amount == 0 means remove all of it) if (amount && (amount < bank_item.data.item_data1[5]) && combine_item_to_max.count(bank_item.data.primary_identifier())) { - if (item) { - *item = bank_item; - item->data.item_data1[5] = amount; - item->amount = amount; - } + ret = bank_item; + ret.data.item_data1[5] = amount; + ret.amount = amount; bank_item.data.item_data1[5] -= amount; bank_item.amount -= amount; - return; + return ret; } // not a combine item, or we're removing the whole stack? then just remove the item - if (item) { - *item = bank_item; - } + ret = bank_item; this->num_items--; memcpy(&this->items[index], &this->items[index + 1], sizeof(PlayerBankItem) * (this->num_items - index)); + return ret; } size_t PlayerInventory::find_item(uint32_t item_id) { @@ -683,6 +662,17 @@ size_t PlayerBank::find_item(uint32_t item_id) { throw out_of_range("item not present"); } +void Player::print_inventory(FILE* stream) const { + fprintf(stream, "[PlayerInventory] Meseta: %" PRIu32 "\n", this->disp.meseta.load()); + 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]; + string name = name_for_item(item.data); + fprintf(stream, "[PlayerInventory] %zu (%08" PRIX32 "): %08" PRIX32 " (%s)\n", + x, item.data.item_id.load(), item.data.primary_identifier(), name.c_str()); + } +} + string filename_for_player_bb(const string& username, uint8_t player_index) { return string_printf("system/players/player_%s_%hhu.nsc", username.c_str(), static_cast(player_index + 1)); diff --git a/src/Player.hh b/src/Player.hh index 0f85d9b6..bb6dfca4 100644 --- a/src/Player.hh +++ b/src/Player.hh @@ -75,7 +75,7 @@ struct PlayerBank { const std::string& load_filename); void add_item(const PlayerBankItem& item); - void remove_item(uint32_t item_id, uint32_t amount, PlayerBankItem* item); + PlayerBankItem remove_item(uint32_t item_id, uint32_t amount); size_t find_item(uint32_t item_id); } __attribute__((packed)); @@ -430,8 +430,10 @@ struct Player { PlayerBB export_bb_player_data() const; void add_item(const PlayerInventoryItem& item); - void remove_item(uint32_t item_id, uint32_t amount, PlayerInventoryItem* item); + PlayerInventoryItem remove_item(uint32_t item_id, uint32_t amount); size_t find_item(uint32_t item_id); + + void print_inventory(FILE* stream) const; }; diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 047aad42..18b8c414 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -29,17 +29,18 @@ extern FileContentsCache file_cache; enum ClientStateBB { - // initial connection. server will redirect client to another port. + // Initial connection; server will redirect client to another port INITIAL_LOGIN = 0x00, - // second connection. server will send client game data and account data. + // Second connection; server will send client game data and account data DOWNLOAD_DATA = 0x01, - // third connection. choose character menu + // Third connection; client will show the choose character menu CHOOSE_PLAYER = 0x02, - // fourth connection, used for saving characters only. if you do not create a - // character, server sets this state in order to skip it. - SAVE_PLAYER = 0x03, - // last connection. redirects client to login server. - SHIP_SELECT = 0x04, + // Fourth connection; used for saving characters only. If you do not create a + // character, the server sets this state during the third connection so this + // connection is effectively skipped. + SAVE_PLAYER = 0x03, + // Last connection; redirects client to login server + SHIP_SELECT = 0x04, }; @@ -816,9 +817,6 @@ void process_menu_selection(shared_ptr s, shared_ptr c, s->change_client_lobby(c, game); c->flags |= Client::Flag::LOADING; - if (c->version == GameVersion::BB) { - game->assign_item_ids_for_player(c->lobby_client_id, c->player.inventory); - } break; } @@ -1696,8 +1694,6 @@ void process_create_game_bb(shared_ptr s, shared_ptr c, s->add_lobby(game); s->change_client_lobby(c, game); c->flags |= Client::Flag::LOADING; - - game->assign_item_ids_for_player(c->lobby_client_id, c->player.inventory); } void process_lobby_name_request(shared_ptr s, shared_ptr c, @@ -1723,7 +1719,12 @@ void process_client_ready(shared_ptr s, shared_ptr c, send_resume_game(l, c); send_server_time(c); - send_get_player_info(c); + // Only get player info again on BB, since on other versions the returned info + // only includes items that would be saved if the client disconnects + // unexpectedly (that is, only equipped items are included). + if (c->version == GameVersion::BB) { + send_get_player_info(c); + } } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 7c15f717..e1508cb9 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -37,7 +37,7 @@ const CmdT* check_size_sc( max_size = min_size; } const auto* cmd = &check_size_t(data, min_size, max_size); - if (check_size_field && cmd->size) { + if (check_size_field && (cmd->size != data.size() / 4)) { throw runtime_error("invalid subcommand size field"); } return cmd; @@ -227,28 +227,102 @@ static void process_subcommand_switch_state_changed(shared_ptr, } //////////////////////////////////////////////////////////////////////////////// -// BB Item commands -// player drops an item -static void process_subcommand_drop_item(shared_ptr, +template +void process_subcommand_movement(shared_ptr, shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, const string& data) { - if (l->version == GameVersion::BB) { - const auto* cmd = check_size_sc(data); + const auto* cmd = check_size_sc(data); - if ((cmd->client_id != c->lobby_client_id)) { - return; - } - - PlayerInventoryItem item; - c->player.remove_item(cmd->item_id, 0, &item); - l->add_item(item); + if (cmd->client_id != c->lobby_client_id) { + return; } + + c->x = cmd->x; + c->z = cmd->z; + forward_subcommand(l, c, command, flag, data); } -// player splits a stack and drops part of it -static void process_subcommand_drop_stacked_item(shared_ptr, +//////////////////////////////////////////////////////////////////////////////// +// Item commands + +static void process_subcommand_player_drop_item(shared_ptr, + shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, + const string& data) { + const auto* cmd = check_size_sc(data); + + if ((cmd->client_id != c->lobby_client_id)) { + return; + } + + l->add_item(c->player.remove_item(cmd->item_id, 0), cmd->area, cmd->x, cmd->z); + + log(INFO, "[Items/%08" PRIX32 "] Player %hhu dropped item %08" PRIX32 " at %hu:(%g, %g)", + l->lobby_id, cmd->client_id, cmd->item_id.load(), cmd->area.load(), cmd->x.load(), cmd->z.load()); + // c->player.print_inventory(stderr); + + forward_subcommand(l, c, command, flag, data); +} + +static void process_subcommand_create_inventory_item(shared_ptr, + shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, + const string& data) { + const auto* cmd = check_size_sc(data); + + if ((cmd->client_id != c->lobby_client_id)) { + return; + } + if (c->version == GameVersion::BB) { + // BB should never send this command - inventory items should only be + // created by the server in response to shop buy / bank withdraw / etc. reqs + return; + } + + PlayerInventoryItem item; + item.equip_flags = 0; // TODO: Use the right default flags here + item.tech_flag = 0; + item.game_flags = 0; + item.data = cmd->item; + c->player.add_item(item); + + log(INFO, "[Items/%08" PRIX32 "] Player %hhu created inventory item %08" PRIX32, + l->lobby_id, cmd->client_id, cmd->item.item_id.load()); + // c->player.print_inventory(stderr); + + forward_subcommand(l, c, command, flag, data); +} + +static void process_subcommand_drop_partial_stack(shared_ptr, + shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, + const string& data) { + const auto* cmd = check_size_sc(data); + + // TODO: Should we check the client ID here too? + if (!l->is_game()) { + return; + } + if (l->version == GameVersion::BB) { + return; + } + + // TODO: Should we delete anything from the inventory here? Does the client + // send an appropriate 6x29 alongside this? + PlayerInventoryItem item; + item.equip_flags = 0; // TODO: Use the right default flags here + item.tech_flag = 0; + item.game_flags = 0; + item.data = cmd->data; + l->add_item(item, cmd->area, cmd->x, cmd->z); + + log(INFO, "[Items/%08" PRIX32 "] Player %hhu split stack to create ground item %08" PRIX32 " at %hu:(%g, %g)", + l->lobby_id, cmd->client_id, item.data.item_id.load(), cmd->area.load(), cmd->x.load(), cmd->z.load()); + // c->player.print_inventory(stderr); + + forward_subcommand(l, c, command, flag, data); +} + +static void process_subcommand_drop_partial_stack_bb(shared_ptr, shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, const string& data) { if (l->version == GameVersion::BB) { @@ -258,8 +332,7 @@ static void process_subcommand_drop_stacked_item(shared_ptr, return; } - PlayerInventoryItem item; - c->player.remove_item(cmd->item_id, cmd->amount, &item); + auto item = c->player.remove_item(cmd->item_id, cmd->amount); // if a stack was split, the original item still exists, so the dropped item // needs a new ID. remove_item signals this by returning an item with id=-1 @@ -267,19 +340,98 @@ static void process_subcommand_drop_stacked_item(shared_ptr, item.data.item_id = l->generate_item_id(c->lobby_client_id); } - l->add_item(item); + l->add_item(item, cmd->area, cmd->x, cmd->z); - send_drop_stacked_item(l, item.data, cmd->area, cmd->x, cmd->y); + log(INFO, "[Items/%08" PRIX32 "] Player %hhu split stack %08" PRIX32 " (%" PRIu32 " of them) at %hu:(%g, %g)", + l->lobby_id, cmd->client_id, cmd->item_id.load(), cmd->amount.load(), + cmd->area.load(), cmd->x.load(), cmd->z.load()); + // c->player.print_inventory(stderr); + + send_drop_stacked_item(l, item.data, cmd->area, cmd->x, cmd->z); } else { forward_subcommand(l, c, command, flag, data); } } -// player requests to pick up an item +static void process_subcommand_buy_shop_item(shared_ptr, + shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, + const string& data) { + const auto* cmd = check_size_sc(data); + + if (!l->is_game() || (cmd->client_id != c->lobby_client_id)) { + return; + } + if (l->version == GameVersion::BB) { + return; + } + + PlayerInventoryItem item; + item.equip_flags = 0; // TODO: Use the right default flags here + item.tech_flag = 0; + item.game_flags = 0; + item.data = cmd->item; + c->player.add_item(item); + + log(INFO, "[Items/%08" PRIX32 "] Player %hhu bought item %08" PRIX32 " from shop", + l->lobby_id, cmd->client_id, item.data.item_id.load()); + // c->player.print_inventory(stderr); + + forward_subcommand(l, c, command, flag, data); +} + +static void process_subcommand_box_or_enemy_item_drop(shared_ptr, + shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, + const string& data) { + const auto* cmd = check_size_sc(data); + + if (!l->is_game() || (c->lobby_client_id != l->leader_id)) { + return; + } + if (l->version == GameVersion::BB) { + return; + } + + PlayerInventoryItem item; + item.equip_flags = 0; // TODO: Use the right default flags here + item.tech_flag = 0; + item.game_flags = 0; + item.data = cmd->data; + l->add_item(item, cmd->area, cmd->x, cmd->z); + + log(INFO, "[Items/%08" PRIX32 "] Leader created ground item %08" PRIX32 " at %hhu:(%g, %g)", + l->lobby_id, item.data.item_id.load(), cmd->area, cmd->x.load(), cmd->z.load()); + // c->player.print_inventory(stderr); + + forward_subcommand(l, c, command, flag, data); +} + + static void process_subcommand_pick_up_item(shared_ptr, shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, const string& data) { + auto* cmd = check_size_sc(data); + + if (!l->is_game() || (cmd->client_id != c->lobby_client_id)) { + return; + } + if (l->version == GameVersion::BB) { + // BB clients should never send this; only the server should send this + return; + } + c->player.add_item(l->remove_item(cmd->item_id)); + + log(INFO, "[Items/%08" PRIX32 "] Player %hu picked up %08" PRIX32, + l->lobby_id, cmd->client_id.load(), cmd->item_id.load()); + // c->player.print_inventory(stderr); + + forward_subcommand(l, c, command, flag, data); +} + +static void process_subcommand_pick_up_item_request(shared_ptr, + shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, + const string& data) { + // This is handled by the server on BB, and by the leader on other versions if (l->version == GameVersion::BB) { auto* cmd = check_size_sc(data); @@ -287,21 +439,19 @@ static void process_subcommand_pick_up_item(shared_ptr, return; } - PlayerInventoryItem item; - l->remove_item(cmd->item_id, &item); - c->player.add_item(item); + c->player.add_item(l->remove_item(cmd->item_id)); - send_pick_up_item(l, c, item.data.item_id, cmd->area); + send_pick_up_item(l, c, cmd->item_id, cmd->area); } else { forward_subcommand(l, c, command, flag, data); } } -// player equips an item static void process_subcommand_equip_unequip_item(shared_ptr, shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, const string& data) { + // We don't track equip state on non-BB versions if (l->version == GameVersion::BB) { const auto* cmd = check_size_sc(data); @@ -324,27 +474,23 @@ static void process_subcommand_equip_unequip_item(shared_ptr, static void process_subcommand_use_item(shared_ptr, shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, const string& data) { - if (l->version == GameVersion::BB) { - const auto* cmd = check_size_sc(data); + const auto* cmd = check_size_sc(data); - if (cmd->client_id != c->lobby_client_id) { - return; - } - - size_t index = c->player.inventory.find_item(cmd->item_id); - if (cmd->command == 0x25) { - c->player.inventory.items[index].game_flags |= 0x00000008; // equip - } else { - c->player.inventory.items[index].game_flags &= 0xFFFFFFF7; // unequip - } - - player_use_item(c, index); + if (cmd->client_id != c->lobby_client_id) { + return; } + size_t index = c->player.inventory.find_item(cmd->item_id); + player_use_item(c, index); + + log(INFO, "[Items/%08" PRIX32 "] Player used item %hhu:%08" PRIX32, + l->lobby_id, cmd->client_id, cmd->item_id.load()); + // c->player.print_inventory(stderr); + forward_subcommand(l, c, command, flag, data); } -static void process_subcommand_open_shop_or_ep3_unknown(shared_ptr s, +static void process_subcommand_open_shop_bb_or_unknown_ep3(shared_ptr s, shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, const string& data) { if (l->flags & Lobby::Flag::EPISODE_3_ONLY) { @@ -379,14 +525,14 @@ static void process_subcommand_open_shop_or_ep3_unknown(shared_ptr } } -static void process_subcommand_open_bank(shared_ptr, +static void process_subcommand_open_bank_bb(shared_ptr, shared_ptr l, shared_ptr c, uint8_t, uint8_t, const string&) { if ((l->version == GameVersion::BB) && l->is_game()) { send_bank(c); } } -static void process_subcommand_bank_action(shared_ptr, +static void process_subcommand_bank_action_bb(shared_ptr, shared_ptr l, shared_ptr c, uint8_t, uint8_t, const string& data) { if (l->version == GameVersion::BB) { const auto* cmd = check_size_sc(data); @@ -406,8 +552,7 @@ static void process_subcommand_bank_action(shared_ptr, c->player.bank.meseta += cmd->meseta_amount; c->player.disp.meseta -= cmd->meseta_amount; } else { // item - PlayerInventoryItem item; - c->player.remove_item(cmd->item_id, cmd->item_amount, &item); + auto item = c->player.remove_item(cmd->item_id, cmd->item_amount); c->player.bank.add_item(item.to_bank_item()); send_destroy_item(l, c, cmd->item_id, cmd->item_amount); } @@ -422,8 +567,7 @@ static void process_subcommand_bank_action(shared_ptr, c->player.bank.meseta -= cmd->meseta_amount; c->player.disp.meseta += cmd->meseta_amount; } else { // item - PlayerBankItem bank_item; - c->player.bank.remove_item(cmd->item_id, cmd->item_amount, &bank_item); + auto bank_item = c->player.bank.remove_item(cmd->item_id, cmd->item_amount); PlayerInventoryItem item = bank_item.to_inventory_item(); item.data.item_id = l->generate_item_id(0xFF); c->player.add_item(item); @@ -434,7 +578,7 @@ static void process_subcommand_bank_action(shared_ptr, } // player sorts the items in their inventory -static void process_subcommand_sort_inventory(shared_ptr, +static void process_subcommand_sort_inventory_bb(shared_ptr, shared_ptr l, shared_ptr c, uint8_t, uint8_t, const string& data) { if (l->version == GameVersion::BB) { @@ -461,10 +605,9 @@ static void process_subcommand_sort_inventory(shared_ptr, } //////////////////////////////////////////////////////////////////////////////// -// BB EXP/Drop Item commands +// EXP/Drop Item commands -// enemy killed; leader sends drop item request -static void process_subcommand_enemy_drop_item(shared_ptr s, +static void process_subcommand_enemy_drop_item_request(shared_ptr s, shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, const string& data) { if (l->version == GameVersion::BB) { @@ -506,8 +649,8 @@ static void process_subcommand_enemy_drop_item(shared_ptr s, } item.data.item_id = l->generate_item_id(0xFF); - l->add_item(item); - send_drop_item(l, item.data, false, cmd->area, cmd->x, cmd->y, + l->add_item(item, cmd->area, cmd->x, cmd->z); + send_drop_item(l, item.data, false, cmd->area, cmd->x, cmd->z, cmd->request_id); } else { @@ -515,8 +658,7 @@ static void process_subcommand_enemy_drop_item(shared_ptr s, } } -// box broken; leader sends drop item request -static void process_subcommand_box_drop_item(shared_ptr s, +static void process_subcommand_box_drop_item_request(shared_ptr s, shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, const string& data) { if (l->version == GameVersion::BB) { @@ -565,8 +707,8 @@ static void process_subcommand_box_drop_item(shared_ptr s, } item.data.item_id = l->generate_item_id(0xFF); - l->add_item(item); - send_drop_item(l, item.data, false, cmd->area, cmd->x, cmd->y, + l->add_item(item, cmd->area, cmd->x, cmd->z); + send_drop_item(l, item.data, false, cmd->area, cmd->x, cmd->z, cmd->request_id); } else { @@ -704,23 +846,38 @@ static void process_subcommand_enemy_killed(shared_ptr s, } } -// destroy item (sent when there are too many items on the ground) -static void process_subcommand_destroy_item(shared_ptr, +static void process_subcommand_destroy_inventory_item(shared_ptr, shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, const string& data) { - if (l->version == GameVersion::BB) { - const auto* cmd = check_size_sc(data); - if (!l->is_game()) { - return; - } - l->remove_item(cmd->item_id, nullptr); + const auto* cmd = check_size_sc(data); + if (!l->is_game()) { + return; } - + if (cmd->client_id != c->lobby_client_id) { + return; + } + c->player.remove_item(cmd->item_id, cmd->amount); + log(INFO, "[Items/%08" PRIX32 "] Inventory item %hhu:%08" PRIX32 " destroyed (%" PRIX32 " of them)", + l->lobby_id, cmd->client_id, cmd->item_id.load(), cmd->amount.load()); + // c->player.print_inventory(stderr); forward_subcommand(l, c, command, flag, data); } -// player requests to tekk an item -static void process_subcommand_identify_item(shared_ptr, +static void process_subcommand_destroy_ground_item(shared_ptr, + shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, + const string& data) { + const auto* cmd = check_size_sc(data); + if (!l->is_game()) { + return; + } + l->remove_item(cmd->item_id); + log(INFO, "[Items/%08" PRIX32 "] Ground item %08" PRIX32 " destroyed (%" PRIX32 " of them)", + l->lobby_id, cmd->item_id.load(), cmd->amount.load()); + // c->player.print_inventory(stderr); + forward_subcommand(l, c, command, flag, data); +} + +static void process_subcommand_identify_item_bb(shared_ptr, shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, const string& data) { if (l->version == GameVersion::BB) { @@ -905,9 +1062,9 @@ subcommand_handler_t subcommand_handlers[0x100] = { /* 26 */ process_subcommand_equip_unequip_item, // Unequip item /* 27 */ process_subcommand_use_item, /* 28 */ process_subcommand_forward_check_size_game, // Feed MAG - /* 29 */ process_subcommand_forward_check_size_game, // Delete item (via bank deposit / sale / feeding MAG) - /* 2A */ process_subcommand_drop_item, - /* 2B */ process_subcommand_forward_check_size_game, // Create inventory item (e.g. from tekker or bank withdrawal) + /* 29 */ process_subcommand_destroy_inventory_item, // Delete item (via bank deposit / sale / feeding MAG) + /* 2A */ process_subcommand_player_drop_item, + /* 2B */ process_subcommand_create_inventory_item, // Create inventory item (e.g. from tekker or bank withdrawal) /* 2C */ process_subcommand_forward_check_size, // Talk to NPC /* 2D */ process_subcommand_forward_check_size, // Done talking to NPC /* 2E */ process_subcommand_unimplemented, @@ -926,11 +1083,11 @@ subcommand_handler_t subcommand_handlers[0x100] = { /* 3B */ process_subcommand_forward_check_size, /* 3C */ process_subcommand_unimplemented, /* 3D */ process_subcommand_unimplemented, - /* 3E */ process_subcommand_forward_check_size, // Stop moving - /* 3F */ process_subcommand_forward_check_size, - /* 40 */ process_subcommand_forward_check_size, // Walk + /* 3E */ process_subcommand_movement, // Stop moving + /* 3F */ process_subcommand_movement, // Set position (e.g. when materializing after warp) + /* 40 */ process_subcommand_movement, // Walk /* 41 */ process_subcommand_unimplemented, - /* 42 */ process_subcommand_forward_check_size, // Run + /* 42 */ process_subcommand_movement, // Run /* 43 */ process_subcommand_forward_check_size_client, /* 44 */ process_subcommand_forward_check_size_client, /* 45 */ process_subcommand_forward_check_size_client, @@ -953,17 +1110,17 @@ subcommand_handler_t subcommand_handlers[0x100] = { /* 56 */ process_subcommand_forward_check_size_client, /* 57 */ process_subcommand_forward_check_size_client, /* 58 */ process_subcommand_forward_check_size_game, - /* 59 */ process_subcommand_forward_check_size_game, // Item picked up - /* 5A */ process_subcommand_pick_up_item, // Request to pick up item + /* 59 */ process_subcommand_pick_up_item, // Item picked up + /* 5A */ process_subcommand_pick_up_item_request, // Request to pick up item /* 5B */ process_subcommand_unimplemented, /* 5C */ process_subcommand_unimplemented, - /* 5D */ process_subcommand_forward_check_size_game, // Drop meseta or stacked item - /* 5E */ process_subcommand_forward_check_size_game, // Buy item at shop - /* 5F */ process_subcommand_forward_check_size_game, // Drop item from box/enemy - /* 60 */ process_subcommand_enemy_drop_item, // Request for item drop (handled by the server on BB) + /* 5D */ process_subcommand_drop_partial_stack, // Drop meseta or stacked item + /* 5E */ process_subcommand_buy_shop_item, // Buy item at shop + /* 5F */ process_subcommand_box_or_enemy_item_drop, // Drop item from box/enemy + /* 60 */ process_subcommand_enemy_drop_item_request, // Request for item drop (handled by the server on BB) /* 61 */ process_subcommand_forward_check_size_game, // Feed mag /* 62 */ process_subcommand_unimplemented, - /* 63 */ process_subcommand_destroy_item, // Destroy an item on the ground (used when too many items have been dropped) + /* 63 */ process_subcommand_destroy_ground_item, // Destroy an item on the ground (used when too many items have been dropped) /* 64 */ process_subcommand_unimplemented, /* 65 */ process_subcommand_unimplemented, /* 66 */ process_subcommand_forward_check_size_game, // Use star atomizer @@ -1026,7 +1183,7 @@ subcommand_handler_t subcommand_handlers[0x100] = { /* 9F */ process_subcommand_forward_check_size_game, // Episode 2 boss actions /* A0 */ process_subcommand_forward_check_size_game, // Episode 2 boss actions /* A1 */ process_subcommand_unimplemented, - /* A2 */ process_subcommand_box_drop_item, // Request for item drop from box (handled by server on BB) + /* A2 */ process_subcommand_box_drop_item_request, // Request for item drop from box (handled by server on BB) /* A3 */ process_subcommand_forward_check_size_game, // Episode 2 boss actions /* A4 */ process_subcommand_unimplemented, /* A5 */ process_subcommand_forward_check_size_game, // Episode 2 boss actions @@ -1045,22 +1202,22 @@ subcommand_handler_t subcommand_handlers[0x100] = { /* B2 */ process_subcommand_unimplemented, /* B3 */ process_subcommand_unimplemented, /* B4 */ process_subcommand_unimplemented, - /* B5 */ process_subcommand_open_shop_or_ep3_unknown, // BB shop request + /* B5 */ process_subcommand_open_shop_bb_or_unknown_ep3, // BB shop request /* B6 */ process_subcommand_unimplemented, // BB shop contents (server->client only) /* B7 */ process_subcommand_unimplemented, // TODO: BB buy shop item - /* B8 */ process_subcommand_identify_item, // Accept tekker result + /* B8 */ process_subcommand_identify_item_bb, // Accept tekker result /* B9 */ process_subcommand_unimplemented, /* BA */ process_subcommand_unimplemented, - /* BB */ process_subcommand_open_bank, // BB Bank request + /* BB */ process_subcommand_open_bank_bb, // BB Bank request /* BC */ process_subcommand_unimplemented, // BB bank contents (server->client only) - /* BD */ process_subcommand_bank_action, + /* BD */ process_subcommand_bank_action_bb, /* BE */ process_subcommand_unimplemented, // BB create inventory item (server->client only) /* BF */ process_subcommand_forward_check_size_ep3_lobby, // Ep3 change music, also BB give EXP (BB usage is server->client only) /* C0 */ process_subcommand_unimplemented, /* C1 */ process_subcommand_unimplemented, /* C2 */ process_subcommand_unimplemented, - /* C3 */ process_subcommand_drop_stacked_item, // Split stacked item - not sent if entire stack is dropped - /* C4 */ process_subcommand_sort_inventory, + /* C3 */ process_subcommand_drop_partial_stack_bb, // Split stacked item - not sent if entire stack is dropped + /* C4 */ process_subcommand_sort_inventory_bb, /* C5 */ process_subcommand_unimplemented, /* C6 */ process_subcommand_unimplemented, /* C7 */ process_subcommand_unimplemented, diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 4f0bdd11..ceae046a 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -1130,17 +1130,19 @@ void send_revive_player(shared_ptr l, shared_ptr c) { // notifies other players of a dropped item from a box or enemy void send_drop_item(shared_ptr l, const ItemData& item, - bool from_enemy, uint8_t area, float x, float y, uint16_t request_id) { + bool from_enemy, uint8_t area, float x, float z, uint16_t request_id) { G_DropItem_6x5F cmd = { - 0x5F, 0x0A, 0x0000, area, from_enemy, request_id, x, y, 0, item}; + 0x5F, 0x0A, 0x0000, area, from_enemy, request_id, x, z, 0, item, 0}; send_command(l, 0x60, 0x00, cmd); } // notifies other players that a stack was split and part of it dropped (a new item was created) void send_drop_stacked_item(shared_ptr l, const ItemData& item, - uint8_t area, float x, float y) { + uint8_t area, float x, float z) { + // TODO: Is this order correct? The original code sent {item, 0}, but it seems + // GC sends {0, item} (the last two fields in the struct are switched). G_DropStackedItem_6x5D cmd = { - 0x5D, 0x09, 0x0000, area, 0, x, y, 0, item}; + 0x5D, 0x09, 0x00, 0x00, area, 0, x, z, item, 0}; send_command(l, 0x60, 0x00, cmd); } @@ -1163,8 +1165,8 @@ void send_create_inventory_item(shared_ptr l, shared_ptr c, // destroys an item void send_destroy_item(shared_ptr l, shared_ptr c, uint32_t item_id, uint32_t amount) { - G_DestroyItem_6x29 cmd = { - 0x29, 0x03, c->lobby_client_id, item_id, amount}; + G_ItemSubcommand cmd = { + 0x29, 0x03, c->lobby_client_id, 0x00, item_id, amount}; send_command(l, 0x60, 0x00, cmd); } diff --git a/src/SendCommands.hh b/src/SendCommands.hh index 96c5bb2c..e25a807c 100644 --- a/src/SendCommands.hh +++ b/src/SendCommands.hh @@ -186,9 +186,9 @@ void send_set_player_visibility(std::shared_ptr l, void send_revive_player(std::shared_ptr l, std::shared_ptr c); void send_drop_item(std::shared_ptr l, const ItemData& item, - bool from_enemy, uint8_t area, float x, float y, uint16_t request_id); + bool from_enemy, uint8_t area, float x, float z, uint16_t request_id); void send_drop_stacked_item(std::shared_ptr l, const ItemData& item, - uint8_t area, float x, float y); + uint8_t area, float x, float z); void send_pick_up_item(std::shared_ptr l, std::shared_ptr c, uint32_t id, uint8_t area); void send_create_inventory_item(std::shared_ptr l, std::shared_ptr c, diff --git a/src/ServerShell.cc b/src/ServerShell.cc index 62bcb6eb..ebfdf4d7 100644 --- a/src/ServerShell.cc +++ b/src/ServerShell.cc @@ -6,9 +6,9 @@ #include -#include "ChatCommands.hh" #include "ServerState.hh" #include "SendCommands.hh" +#include "StaticGameData.hh" using namespace std; diff --git a/src/StaticGameData.cc b/src/StaticGameData.cc new file mode 100644 index 00000000..1ca5d2ce --- /dev/null +++ b/src/StaticGameData.cc @@ -0,0 +1,1558 @@ +#include "StaticGameData.hh" + +using namespace std; + + + +const vector section_id_to_name({ + "Viridia", "Greennill", "Skyly", "Bluefull", "Purplenum", "Pinkal", "Redria", + "Oran", "Yellowboze", "Whitill"}); + +const unordered_map name_to_section_id({ + {"viridia", 0}, + {"greennill", 1}, + {"skyly", 2}, + {"bluefull", 3}, + {"purplenum", 4}, + {"pinkal", 5}, + {"redria", 6}, + {"oran", 7}, + {"yellowboze", 8}, + {"whitill", 9}}); + +const vector lobby_event_to_name({ + "none", "xmas", "none", "val", "easter", "hallo", "sonic", "newyear", + "summer", "white", "wedding", "fall", "s-spring", "s-summer", "spring"}); + +const unordered_map name_to_lobby_event({ + {"none", 0}, + {"xmas", 1}, + {"val", 3}, + {"easter", 4}, + {"hallo", 5}, + {"sonic", 6}, + {"newyear", 7}, + {"summer", 8}, + {"white", 9}, + {"wedding", 10}, + {"fall", 11}, + {"s-spring", 12}, + {"s-summer", 13}, + {"spring", 14}, +}); + +const unordered_map lobby_type_to_name({ + {0x00, "normal"}, + {0x0F, "inormal"}, + {0x10, "ipc"}, + {0x11, "iball"}, + {0x67, "cave2u"}, + {0xD4, "cave1"}, + {0xE9, "planet"}, + {0xEA, "clouds"}, + {0xED, "cave"}, + {0xEE, "jungle"}, + {0xEF, "forest2-2"}, + {0xF0, "forest2-1"}, + {0xF1, "windpower"}, + {0xF2, "overview"}, + {0xF3, "seaside"}, + {0xF4, "some?"}, + {0xF5, "dmorgue"}, + {0xF6, "caelum"}, + {0xF8, "digital"}, + {0xF9, "boss1"}, + {0xFA, "boss2"}, + {0xFB, "boss3"}, + {0xFC, "dragon"}, + {0xFD, "derolle"}, + {0xFE, "volopt"}, + {0xFF, "darkfalz"}, +}); + +const unordered_map name_to_lobby_type({ + {"normal", 0x00}, + {"inormal", 0x0F}, + {"ipc", 0x10}, + {"iball", 0x11}, + {"cave1", 0xD4}, + {"cave2u", 0x67}, + {"dragon", 0xFC}, + {"derolle", 0xFD}, + {"volopt", 0xFE}, + {"darkfalz", 0xFF}, + {"planet", 0xE9}, + {"clouds", 0xEA}, + {"cave", 0xED}, + {"jungle", 0xEE}, + {"forest2-2", 0xEF}, + {"forest2-1", 0xF0}, + {"windpower", 0xF1}, + {"overview", 0xF2}, + {"seaside", 0xF3}, + {"some?", 0xF4}, + {"dmorgue", 0xF5}, + {"caelum", 0xF6}, + {"digital", 0xF8}, + {"boss1", 0xF9}, + {"boss2", 0xFA}, + {"boss3", 0xFB}, + {"knight", 0xFC}, + {"sky", 0xFE}, + {"morgue", 0xFF}, +}); + +const vector npc_id_to_name({ + "ninja", "rico", "sonic", "knuckles", "tails", "flowen", "elly"}); + +const unordered_map name_to_npc_id({ + {"ninja", 0}, + {"rico", 1}, + {"sonic", 2}, + {"knuckles", 3}, + {"tails", 4}, + {"flowen", 5}, + {"elly", 6}}); + +const string& name_for_section_id(uint8_t section_id) { + if (section_id < section_id_to_name.size()) { + return section_id_to_name[section_id]; + } else { + static const string ret = ""; + return ret; + } +} + +u16string u16name_for_section_id(uint8_t section_id) { + return decode_sjis(name_for_section_id(section_id)); +} + +uint8_t section_id_for_name(const string& name) { + try { + return name_to_section_id.at(name); + } catch (const out_of_range&) { } + try { + uint64_t x = stoul(name); + if (x < section_id_to_name.size()) { + return x; + } + } catch (const invalid_argument&) { + } catch (const out_of_range&) { } + return 0xFF; +} + +uint8_t section_id_for_name(const u16string& name) { + return section_id_for_name(encode_sjis(name)); +} + +const string& name_for_event(uint8_t event) { + if (event < lobby_event_to_name.size()) { + return lobby_event_to_name[event]; + } else { + static const string ret = ""; + return ret; + } +} + +u16string u16name_for_event(uint8_t event) { + return decode_sjis(name_for_event(event)); +} + +uint8_t event_for_name(const string& name) { + try { + return name_to_lobby_event.at(name); + } catch (const out_of_range&) { } + try { + uint64_t x = stoul(name); + if (x < lobby_event_to_name.size()) { + return x; + } + } catch (const invalid_argument&) { + } catch (const out_of_range&) { } + return 0xFF; +} + +uint8_t event_for_name(const u16string& name) { + return event_for_name(encode_sjis(name)); +} + +const string& name_for_lobby_type(uint8_t type) { + try { + return lobby_type_to_name.at(type); + } catch (const out_of_range&) { + static const string ret = ""; + return ret; + } +} + +u16string u16name_for_lobby_type(uint8_t type) { + return decode_sjis(name_for_lobby_type(type)); +} + +uint8_t lobby_type_for_name(const string& name) { + try { + return name_to_lobby_type.at(name); + } catch (const out_of_range&) { } + try { + uint64_t x = stoul(name); + if (x < lobby_type_to_name.size()) { + return x; + } + } catch (const invalid_argument&) { + } catch (const out_of_range&) { } + return 0x80; +} + +uint8_t lobby_type_for_name(const u16string& name) { + return lobby_type_for_name(encode_sjis(name)); +} + +const string& name_for_npc(uint8_t npc) { + try { + return npc_id_to_name.at(npc); + } catch (const out_of_range&) { + static const string ret = ""; + return ret; + } +} + +u16string u16name_for_npc(uint8_t npc) { + return decode_sjis(name_for_npc(npc)); +} + +uint8_t npc_for_name(const string& name) { + try { + return name_to_npc_id.at(name); + } catch (const out_of_range&) { } + try { + uint64_t x = stoul(name); + if (x < npc_id_to_name.size()) { + return x; + } + } catch (const invalid_argument&) { + } catch (const out_of_range&) { } + return 0xFF; +} + +uint8_t npc_for_name(const u16string& name) { + return npc_for_name(encode_sjis(name)); +} + + + +const unordered_map combine_item_to_max({ + {0x030000, 10}, + {0x030001, 10}, + {0x030002, 10}, + {0x030100, 10}, + {0x030101, 10}, + {0x030102, 10}, + {0x030300, 10}, + {0x030400, 10}, + {0x030500, 10}, + {0x030600, 10}, + {0x030601, 10}, + {0x030700, 10}, + {0x030800, 10}, + {0x031000, 99}, + {0x031001, 99}, + {0x031002, 99}, +}); + +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"}, +}); + +const unordered_map name_for_primary_identifier({ + // Weapons (00xxxx) + {0x000100, "Saber"}, + {0x000101, "Brand"}, + {0x000102, "Buster"}, + {0x000103, "Pallasch"}, + {0x000104, "Gladius"}, + {0x000105, "DB\'s SABER"}, + {0x000106, "KALADGOLG"}, + {0x000107, "DURANDAL"}, + {0x000108, "GALATINE"}, + {0x000200, "Sword"}, + {0x000201, "Gigush"}, + {0x000202, "Breaker"}, + {0x000203, "Claymore"}, + {0x000204, "Calibur"}, + {0x000205, "FLOWEN\'S SWORD"}, + {0x000206, "LAST SURVIVOR"}, + {0x000207, "DRAGON SLAYER"}, + {0x000300, "Dagger"}, + {0x000301, "Knife"}, + {0x000302, "Blade"}, + {0x000303, "Edge"}, + {0x000304, "Ripper"}, + {0x000305, "BLADE DANCE"}, + {0x000306, "BLOODY ART"}, + {0x000307, "CROSS SCAR"}, + {0x000308, "ZERO DIVIDE"}, + {0x000309, "TWIN KAMUI"}, + {0x000400, "Partisan"}, + {0x000401, "Halbert"}, + {0x000402, "Glaive"}, + {0x000403, "Berdys"}, + {0x000404, "Gungnir"}, + {0x000405, "BRIONAC"}, + {0x000406, "VJAYA"}, + {0x000407, "GAE BOLG"}, + {0x000408, "ASTERON BELT"}, + {0x000500, "Slicer"}, + {0x000501, "Spinner"}, + {0x000502, "Cutter"}, + {0x000503, "Sawcer"}, + {0x000504, "Diska"}, + {0x000505, "SLICER OF ASSASSIN"}, + {0x000506, "DISKA OF LIBERATOR"}, + {0x000507, "DISKA OF BRAVEMAN"}, + {0x000508, "IZMAELA"}, + {0x000600, "Handgun"}, + {0x000601, "Autogun"}, + {0x000602, "Lockgun"}, + {0x000603, "Railgun"}, + {0x000604, "Raygun"}, + {0x000605, "VARISTA"}, + {0x000606, "CUSTOM RAY ver.00"}, + {0x000607, "BRAVACE"}, + {0x000608, "TENSION BLASTER"}, + {0x000700, "Rifle"}, + {0x000701, "Sniper"}, + {0x000702, "Blaster"}, + {0x000703, "Beam"}, + {0x000704, "Laser"}, + {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"}, + {0x000801, "Assault"}, + {0x000802, "Repeater"}, + {0x000803, "Gatling"}, + {0x000804, "Vulcan"}, + {0x000805, "M&A60 VISE"}, + {0x000806, "H&S25 JUSTICE"}, + {0x000807, "L&K14 COMBAT"}, + {0x000900, "Shot"}, + {0x000901, "Spread"}, + {0x000902, "Cannon"}, + {0x000903, "Launcher"}, + {0x000904, "Arms"}, + {0x000905, "CRUSH BULLET"}, + {0x000906, "METEOR SMASH"}, + {0x000907, "FINAL IMPACT"}, + {0x000A00, "Cane"}, + {0x000A01, "Stick"}, + {0x000A02, "Mace"}, + {0x000A03, "Club"}, + {0x000A04, "CLUB OF LACONIUM"}, + {0x000A05, "MACE OF ADAMAN"}, + {0x000A06, "CLUB OF ZUMIURAN"}, + {0x000A07, "LOLLIPOP"}, + {0x000B00, "Rod"}, + {0x000B01, "Pole"}, + {0x000B02, "Pillar"}, + {0x000B03, "Striker"}, + {0x000B04, "BATTLE VERGE"}, + {0x000B05, "BRAVE HAMMER"}, + {0x000B06, "ALIVE AQHU"}, + {0x000B07, "VALKYRIE"}, + {0x000C00, "Wand"}, + {0x000C01, "Staff"}, + {0x000C02, "Baton"}, + {0x000C03, "Scepter"}, + {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"}, + {0x007100, "(S-Rank) SWORD"}, + {0x007200, "(S-Rank) BLADE"}, + {0x007300, "(S-Rank) PARTISAN"}, + {0x007400, "(S-Rank) SLICER"}, + {0x007500, "(S-Rank) GUN"}, + {0x007600, "(S-Rank) RIFLE"}, + {0x007700, "(S-Rank) MECHGUN"}, + {0x007800, "(S-Rank) SHOT"}, + {0x007900, "(S-Rank) CANE"}, + {0x007A00, "(S-Rank) ROD"}, + {0x007B00, "(S-Rank) WAND"}, + {0x007C00, "(S-Rank) TWIN"}, + {0x007D00, "(S-Rank) CLAW"}, + {0x007E00, "(S-Rank) BAZOOKA"}, + {0x007F00, "(S-Rank) NEEDLE"}, + {0x008000, "(S-Rank) SCYTHE"}, + {0x008100, "(S-Rank) HAMMER"}, + {0x008200, "(S-Rank) MOON"}, + {0x008300, "(S-Rank) PSYCHOGUN"}, + {0x008400, "(S-Rank) PUNCH"}, + {0x008500, "(S-Rank) WINDMILL"}, + {0x008600, "(S-Rank) HARISEN"}, + {0x008700, "(S-Rank) KATANA"}, + {0x008800, "(S-Rank) J-CUTTER"}, + {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"}, + {0x00A600, "(S-Rank) LAUNCHER"}, + {0x00A700, "(S-Rank) CARD"}, + {0x00A800, "(S-Rank) KNUCKLE"}, + {0x00A900, "(S-Rank) AXE"}, + {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"}, + {0x010101, "Armor"}, + {0x010102, "Psy Armor"}, + {0x010103, "Giga Frame"}, + {0x010104, "Soul Frame"}, + {0x010105, "Cross Armor"}, + {0x010106, "Solid Frame"}, + {0x010107, "Brave Armor"}, + {0x010108, "Hyper Frame"}, + {0x010109, "Grand Armor"}, + {0x01010A, "Shock Frame"}, + {0x01010B, "King\'s Frame"}, + {0x01010C, "Dragon Frame"}, + {0x01010D, "Absorb Armor"}, + {0x01010E, "Protect Frame"}, + {0x01010F, "General Armor"}, + {0x010110, "Perfect Frame"}, + {0x010111, "Valiant Frame"}, + {0x010112, "Imperial Armor"}, + {0x010113, "Holiness Armor"}, + {0x010114, "Guardian Armor"}, + {0x010115, "Divinity Armor"}, + {0x010116, "Ultimate Frame"}, + {0x010117, "Celestial Armor"}, + {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"}, + {0x01013D, "Frame"}, + {0x01013E, "WEDDING DRESS"}, + {0x01013F, "Frame"}, + {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"}, + {0x010201, "Shield"}, + {0x010202, "Core Shield"}, + {0x010203, "Giga Shield"}, + {0x010204, "Soul Barrier"}, + {0x010205, "Hard Shield"}, + {0x010206, "Brave Barrier"}, + {0x010207, "Solid Shield"}, + {0x010208, "Flame Barrier"}, + {0x010209, "Plasma Barrier"}, + {0x01020A, "Freeze Barrier"}, + {0x01020B, "Psychic Barrier"}, + {0x01020C, "General Shield"}, + {0x01020D, "Protect Barrier"}, + {0x01020E, "Glorious Shield"}, + {0x01020F, "Imperial Barrier"}, + {0x010210, "Guardian Shield"}, + {0x010211, "Divinity Barrier"}, + {0x010212, "Ultimate Shield"}, + {0x010213, "Spiritual Shield"}, + {0x010214, "Celestial Shield"}, + {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"}, + {0x010238, "Barrier"}, + {0x010239, "Barrier"}, + {0x01023A, "RESTA MERGE"}, + {0x01023B, "ANTI MERGE"}, + {0x01023C, "SHIFTA MERGE"}, + {0x01023D, "DEBAND MERGE"}, + {0x01023E, "FOIE MERGE"}, + {0x01023F, "GIFOIE MERGE"}, + {0x010240, "RAFOIE MERGE"}, + {0x010241, "RED MERGE"}, + {0x010242, "BARTA MERGE"}, + {0x010243, "GIBARTA MERGE"}, + {0x010244, "RABARTA MERGE"}, + {0x010245, "BLUE MERGE"}, + {0x010246, "ZONDE MERGE"}, + {0x010247, "GIZONDE MERGE"}, + {0x010248, "RAZONDE MERGE"}, + {0x010249, "YELLOW MERGE"}, + {0x01024A, "RECOVERY BARRIER"}, + {0x01024B, "ASSIST BARRIER"}, + {0x01024C, "RED BARRIER"}, + {0x01024D, "BLUE BARRIER"}, + {0x01024E, "YELLOW BARRIER"}, + {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"}, + {0x010301, "General/Power"}, + {0x010302, "Ogre/Power"}, + {0x010303, "God/Power"}, + {0x010304, "Priest/Mind"}, + {0x010305, "General/Mind"}, + {0x010306, "Angel/Mind"}, + {0x010307, "God/Mind"}, + {0x010308, "Marksman/Arm"}, + {0x010309, "General/Arm"}, + {0x01030A, "Elf/Arm"}, + {0x01030B, "God/Arm"}, + {0x01030C, "Thief/Legs"}, + {0x01030D, "General/Legs"}, + {0x01030E, "Elf/Legs"}, + {0x01030F, "God/Legs"}, + {0x010310, "Digger/HP"}, + {0x010311, "General/HP"}, + {0x010312, "Dragon/HP"}, + {0x010313, "God/HP"}, + {0x010314, "Magician/TP"}, + {0x010315, "General/TP"}, + {0x010316, "Angel/TP"}, + {0x010317, "God/TP"}, + {0x010318, "Warrior/Body"}, + {0x010319, "General/Body"}, + {0x01031A, "Metal/Body"}, + {0x01031B, "God/Body"}, + {0x01031C, "Angel/Luck"}, + {0x01031D, "God/Luck"}, + {0x01031E, "Master/Ability"}, + {0x01031F, "Hero/Ability"}, + {0x010320, "God/Ability"}, + {0x010321, "Resist/Fire"}, + {0x010322, "Resist/Flame"}, + {0x010323, "Resist/Burning"}, + {0x010324, "Resist/Cold"}, + {0x010325, "Resist/Freeze"}, + {0x010326, "Resist/Blizzard"}, + {0x010327, "Resist/Shock"}, + {0x010328, "Resist/Thunder"}, + {0x010329, "Resist/Storm"}, + {0x01032A, "Resist/Light"}, + {0x01032B, "Resist/Saint"}, + {0x01032C, "Resist/Holy"}, + {0x01032D, "Resist/Dark"}, + {0x01032E, "Resist/Evil"}, + {0x01032F, "Resist/Devil"}, + {0x010330, "All/Resist"}, + {0x010331, "Super/Resist"}, + {0x010332, "Perfect/Resist"}, + {0x010333, "HP/Restorate"}, + {0x010334, "HP/Generate"}, + {0x010335, "HP/Revival"}, + {0x010336, "TP/Restorate"}, + {0x010337, "TP/Generate"}, + {0x010338, "TP/Revival"}, + {0x010339, "PB/Amplifier"}, + {0x01033A, "PB/Generate"}, + {0x01033B, "PB/Create"}, + {0x01033C, "Wizard/Technique"}, + {0x01033D, "Devil/Technique"}, + {0x01033E, "God/Technique"}, + {0x01033F, "General/Battle"}, + {0x010340, "Devil/Battle"}, + {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"}, + {0x020100, "Varuna"}, + {0x020200, "Mitra"}, + {0x020300, "Surya"}, + {0x020400, "Vayu"}, + {0x020500, "Varaha"}, + {0x020600, "Kama"}, + {0x020700, "Ushasu"}, + {0x020800, "Apsaras"}, + {0x020900, "Kumara"}, + {0x020A00, "Kaitabha"}, + {0x020B00, "Tapas"}, + {0x020C00, "Bhirava"}, + {0x020D00, "Kalki"}, + {0x020E00, "Rudra"}, + {0x020F00, "Marutah"}, + {0x021000, "Yaksa"}, + {0x021100, "Sita"}, + {0x021200, "Garuda"}, + {0x021300, "Nandin"}, + {0x021400, "Ashvinau"}, + {0x021500, "Ribhava"}, + {0x021600, "Soma"}, + {0x021700, "Ila"}, + {0x021800, "Durga"}, + {0x021900, "Vritra"}, + {0x021A00, "Namuci"}, + {0x021B00, "Sumba"}, + {0x021C00, "Naga"}, + {0x021D00, "Pitri"}, + {0x021E00, "Kabanda"}, + {0x021F00, "Ravana"}, + {0x022000, "Marica"}, + {0x022100, "Soniti"}, + {0x022200, "Preta"}, + {0x022300, "Andhaka"}, + {0x022400, "Bana"}, + {0x022500, "Naraka"}, + {0x022600, "Madhu"}, + {0x022700, "Churel"}, + {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"}, + {0x030001, "Dimate"}, + {0x030002, "Trimate"}, + {0x030100, "Monofluid"}, + {0x030101, "Difluid"}, + {0x030102, "Trifluid"}, + {0x030200, ""}, // Special-cased in name_for_item + {0x030300, "Sol Atomizer"}, + {0x030400, "Moon Atomizer"}, + {0x030500, "Star Atomizer"}, + {0x030600, "Antidote"}, + {0x030601, "Antiparalysis"}, + {0x030700, "Telepipe"}, + {0x030800, "Trap Vision"}, + {0x030900, "Scape Doll"}, + {0x030A00, "Monogrinder"}, + {0x030A01, "Digrinder"}, + {0x030A02, "Trigrinder"}, + {0x030B00, "Power Material"}, + {0x030B01, "Mind Material"}, + {0x030B02, "Evade Material"}, + {0x030B03, "HP Material"}, + {0x030B04, "TP Material"}, + {0x030B05, "Def Material"}, + {0x030B06, "Luck Material"}, + {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, "S-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, "Patasitic 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"}, + {0x030E17, "Amplifier of Anti"}, + {0x030E18, "Amplifier of Shifta"}, + {0x030E19, "Amplifier of Deband"}, + {0x030E1A, "Amplifier of Foie"}, + {0x030E1B, "Amplifier of Gifoie"}, + {0x030E1C, "Amplifier of Rafoie"}, + {0x030E1D, "Amplifier of Barta"}, + {0x030E1E, "Amplifier of Gibarta"}, + {0x030E1F, "Amplifier of Rabarta"}, + {0x030E20, "Amplifier of Zonde"}, + {0x030E21, "Amplifier of Gizonde"}, + {0x030E22, "Amplifier of Razonde"}, + {0x030E23, "Amplifier of Red"}, + {0x030E24, "Amplifier of Blue"}, + {0x030E25, "Amplifier of Yellow"}, + {0x030E26, "Heart of KAPU KAPU"}, + {0x030E27, "Photon Booster"}, + {0x030F00, "AddSlot"}, + {0x031000, "Photon Drop"}, + {0x031001, "Photon Sphere"}, + {0x031002, "Photo 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"}, + {0x031409, "Bouquet"}, + {0x031409, "Decoction"}, + {0x031500, "Christmas Present"}, + {0x031501, "Easter Egg"}, + {0x031502, "Jack-O-Laturn"}, + {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", + "zonde", "gizonde", "razonde", + "grants", "deband", "jellen", "zalure", "shifta", + "ryuker", "resta", "anti", "reverser", "megid"}); + +const unordered_map name_to_tech_id({ + {"foie", 0}, + {"gifoie", 1}, + {"rafoie", 2}, + {"barta", 3}, + {"gibarta", 4}, + {"rabarta", 5}, + {"zonde", 6}, + {"gizonde", 7}, + {"razonde", 8}, + {"grants", 9}, + {"deband", 10}, + {"jellen", 11}, + {"zalure", 12}, + {"shifta", 13}, + {"ryuker", 14}, + {"resta", 15}, + {"anti", 16}, + {"reverser", 17}, + {"megid", 18}, +}); + +const string& name_for_technique(uint8_t tech) { + try { + return tech_id_to_name.at(tech); + } catch (const out_of_range&) { + static const string ret = ""; + return ret; + } +} + +u16string u16name_for_technique(uint8_t tech) { + return decode_sjis(name_for_technique(tech)); +} + +uint8_t technique_for_name(const string& name) { + try { + return name_to_tech_id.at(name); + } catch (const out_of_range&) { } + try { + uint64_t x = stoul(name); + if (x < tech_id_to_name.size()) { + return x; + } + } catch (const invalid_argument&) { + } catch (const out_of_range&) { } + return 0xFF; +} + +uint8_t technique_for_name(const u16string& name) { + return technique_for_name(encode_sjis(name)); +} + +string name_for_item(const ItemData& item) { + if (item.item_data1[0] == 0x04) { + return string_printf("%" PRIu32 " Meseta", item.item_data2d.load()); + } + + vector ret_tokens; + + // For weapons, specials appear before the weapon name + if ((item.item_data1[0] == 0x00) && (item.item_data1[4] != 0x00)) { + bool is_present = item.item_data1[4] & 0x40; + bool is_unidentified = item.item_data1[4] & 0x80; + uint8_t special_id = item.item_data1[4] & 0x3F; + if (is_unidentified) { + // Note: this looks weird so it won't parse as a trigraph. Thanks, IBM, + // for opposing modernization in C++ >:( + ret_tokens.emplace_back("(?""?""?""?)"); + } + 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.item_data1[0] == 0x02) && (item.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. + uint32_t primary_identifier = item.primary_identifier(); + if ((primary_identifier & 0xFFFFFF00) == 0x00030200) { + string technique_name = name_for_technique(item.item_data1[4]); + technique_name[0] = toupper(technique_name[0]); + ret_tokens.emplace_back(string_printf( + "Disk:%s Lv.%d", technique_name.c_str(), item.item_data1[2] + 1)); + } else { + try { + ret_tokens.emplace_back(name_for_primary_identifier.at(primary_identifier)); + } catch (const out_of_range&) { + ret_tokens.emplace_back("!ID:%06" PRIX32, primary_identifier); + } + } + + // For weapons, add the grind and percentages + if (item.item_data1[0] == 0x00) { + if (item.item_data1[3] > 0) { + ret_tokens.emplace_back(string_printf("+%hhu", item.item_data1[3])); + } + uint8_t percentages[5] = {0, 0, 0, 0, 0}; + for (size_t x = 0; x < 3; x++) { + uint8_t which = item.item_data1[6 + 2 * x]; + uint8_t value = item.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] = 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.item_data1[0] == 0x01) { + if (item.item_data1[1] == 0x03) { // Units + uint16_t modifier = (item.item_data1[8] << 8) | item.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.item_data1[5] > 0) { + if (item.item_data1[5] == 1) { + ret_tokens.emplace_back("(1 slot)"); + } else { + ret_tokens.emplace_back(string_printf("(%hhu slots)", item.item_data1[5])); + } + } + if (item.item_data1w[3] != 0) { + ret_tokens.emplace_back(string_printf("+%hdDEF", + static_cast(item.item_data1w[3].load()))); + } + if (item.item_data1w[4] != 0) { + ret_tokens.emplace_back(string_printf("+%hdEVP", + static_cast(item.item_data1w[4].load()))); + } + } + + // For mags, add tons of info + } else if (item.item_data1[0] == 0x02) { + ret_tokens.emplace_back(string_printf("LV%hhu", item.item_data1[2])); + + ret_tokens.emplace_back(string_printf("%d/%d/%d/%d", + item.item_data1w[2] / 100, item.item_data1w[3] / 100, + item.item_data1w[4] / 100, item.item_data1w[5] / 100)); + ret_tokens.emplace_back(string_printf("%hu%%", item.item_data2[3])); + ret_tokens.emplace_back(string_printf("%huIQ", item.item_data2[2])); + + uint8_t flags = item.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.item_data1[3] & 7) : 0xFF; + uint8_t right_pb = (flags & 1) ? ((item.item_data1[3] >> 3) & 7) : 0xFF; + uint8_t left_pb = (flags & 4) ? ((item.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 instead. + 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({ + "red", + "blue", + "yellow", + "green", + "purple", + "black", + "white", + "cyan", + "brown", + "orange", + "light blue", + "olive", + "light cyan", + "dark purple", + "grey", + "light grey", + "pink", + "dark cyan", + "costume color", + }); + try { + ret_tokens.emplace_back(string_printf("(%s)", mag_colors.at(item.item_data2[0]))); + } catch (const out_of_range&) { + ret_tokens.emplace_back(string_printf("(!CL:%02hhX)", item.item_data2[0])); + } + } + + // For tools, add the amount (if applicable) + } else if (item.item_data1[0] == 0x03) { + if (combine_item_to_max.count(primary_identifier)) { + ret_tokens.emplace_back(string_printf("x%hhu", item.item_data1[5])); + } + } + + return join(ret_tokens, " "); +} diff --git a/src/StaticGameData.hh b/src/StaticGameData.hh new file mode 100644 index 00000000..dcf26372 --- /dev/null +++ b/src/StaticGameData.hh @@ -0,0 +1,41 @@ +#pragma once + +#include + +#include + +#include "Player.hh" + + + +extern const std::unordered_map combine_item_to_max; +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; + +const std::string& name_for_technique(uint8_t tech); +std::u16string u16name_for_technique(uint8_t tech); +uint8_t technique_for_name(const std::string& name); +uint8_t technique_for_name(const std::u16string& name); + +const std::string& name_for_section_id(uint8_t section_id); +std::u16string u16name_for_section_id(uint8_t section_id); +uint8_t section_id_for_name(const std::string& name); +uint8_t section_id_for_name(const std::u16string& name); + +const std::string& name_for_event(uint8_t event); +std::u16string u16name_for_event(uint8_t event); +uint8_t event_for_name(const std::string& name); +uint8_t event_for_name(const std::u16string& name); + +const std::string& name_for_lobby_type(uint8_t type); +std::u16string u16name_for_lobby_type(uint8_t type); +uint8_t lobby_type_for_name(const std::string& name); +uint8_t lobby_type_for_name(const std::u16string& name); + +const std::string& name_for_npc(uint8_t npc); +std::u16string u16name_for_npc(uint8_t npc); +uint8_t npc_for_name(const std::string& name); +uint8_t npc_for_name(const std::u16string& name); + +std::string name_for_item(const ItemData& item);