diff --git a/Items.cc b/Items.cc index 10fd2434..07c70a2e 100644 --- a/Items.cc +++ b/Items.cc @@ -281,7 +281,7 @@ int32_t CommonItemCreator::decide_item_type(bool is_box) const { return -1; } -ItemData CommonItemCreator::create_item(bool is_box, uint8_t episode, +ItemData CommonItemCreator::create_drop_item(bool is_box, uint8_t episode, uint8_t difficulty, uint8_t area, uint8_t section_id) const { // change the area if it's invalid (data for the bosses are actually in other areas) if (area > 10) { @@ -465,3 +465,124 @@ ItemData CommonItemCreator::create_item(bool is_box, uint8_t episode, return item; } + + +ItemData CommonItemCreator::create_shop_item(uint8_t difficulty, + uint8_t item_type) const { + static const uint8_t max_percentages[4] = {20, 35, 45, 50}; + static const uint8_t max_quantity[4] = { 1, 1, 2, 2}; + static const uint8_t max_tech_level[4] = { 8, 15, 23, 30}; + static const uint8_t max_anti_level[4] = { 2, 4, 6, 7}; + + ItemData item; + memset(&item, 0, sizeof(item)); + + item.item_data1[0] = item_type; + while (item.item_data1[0] == 2) { + item.item_data1[0] = rand() % 3; + } + switch (item.item_data1[0]) { + case 0: { // weapon + item.item_data1[1] = (rand() % 12) + 1; + if (item.item_data1[1] > 9) { + item.item_data1[2] = difficulty; + } else { + item.item_data1[2] = (rand() & 1) + difficulty; + } + + item.item_data1[3] = rand() % 11; + item.item_data1[4] = rand() % 11; + + size_t num_percentages = 0; + for (size_t x = 0; (x < 5) && (num_percentages < 3); x++) { + if ((rand() % 4) == 1) { + item.item_data1[(num_percentages * 2) + 6] = x; + item.item_data1[(num_percentages * 2) + 7] = rand() % (max_percentages[difficulty] + 1); + num_percentages++; + } + } + break; + } + + case 1: // armor + item.item_data1[1] = 0; + while (item.item_data1[1] == 0) { + item.item_data1[1] = rand() & 3; + } + switch (item.item_data1[1]) { + case 1: + item.item_data1[2] = (rand() % 6) + (difficulty * 6); + item.item_data1[5] = rand() % 5; + break; + case 2: + item.item_data2[2] = (rand() % 6) + (difficulty * 5); + *reinterpret_cast(&item.item_data1[6]) = (rand() % 9) - 4; + *reinterpret_cast(&item.item_data1[9]) = (rand() % 9) - 4; + break; + case 3: + item.item_data2[2] = rand() % 0x3B; + *reinterpret_cast(&item.item_data1[7]) = (rand() % 5) - 4; + break; + } + break; + + case 3: // tool + item.item_data1[1] = rand() % 12; + switch (item.item_data1[1]) { + case 0: + case 1: + if (difficulty == 0) { + item.item_data1[2] = 0; + } else if (difficulty == 1) { + item.item_data1[2] = rand() % 2; + } else if (difficulty == 2) { + item.item_data1[2] = (rand() % 2) + 1; + } else if (difficulty == 3) { + item.item_data1[2] = 2; + } + break; + + case 6: + item.item_data1[2] = rand() % 2; + break; + + case 10: + item.item_data1[2] = rand() % 3; + break; + + case 11: + item.item_data1[2] = rand() % 7; + break; + } + + switch (item.item_data1[1]) { + case 2: + item.item_data1[4] = rand() % 19; + switch (item.item_data1[4]) { + case 14: + case 17: + item.item_data1[2] = 0; // reverser & ryuker always level 1 + break; + case 16: + item.item_data1[2] = rand() % max_anti_level[difficulty]; + break; + default: + item.item_data1[2] = rand() % max_tech_level[difficulty]; + } + break; + case 0: + case 1: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + case 16: + item.item_data1[5] = rand() % (max_quantity[difficulty] + 1); + break; + } + } + + return item; +} diff --git a/Items.hh b/Items.hh index 357fdee6..356cdc64 100644 --- a/Items.hh +++ b/Items.hh @@ -20,6 +20,7 @@ struct CommonItemCreator { const std::vector>& unit_types); int32_t decide_item_type(bool is_box) const; - ItemData create_item(bool is_box, uint8_t episode, uint8_t difficulty, + ItemData create_drop_item(bool is_box, uint8_t episode, uint8_t difficulty, uint8_t area, uint8_t section_id) const; + ItemData create_shop_item(uint8_t difficulty, uint8_t shop_type) const; }; diff --git a/Lobby.cc b/Lobby.cc index 34489e11..f8fc55cb 100644 --- a/Lobby.cc +++ b/Lobby.cc @@ -176,7 +176,7 @@ void Lobby::remove_item(uint32_t item_id, PlayerInventoryItem* item) { this->item_id_to_floor_item.erase(item_it); } -uint32_t Lobby::generate_item_id(uint32_t client_id) { +uint32_t Lobby::generate_item_id(uint8_t client_id) { if (client_id < this->max_clients) { return this->next_item_id[client_id]++; } diff --git a/Lobby.hh b/Lobby.hh index 554ce5e5..51737350 100644 --- a/Lobby.hh +++ b/Lobby.hh @@ -80,7 +80,7 @@ struct Lobby { void add_item(const PlayerInventoryItem& item); void remove_item(uint32_t item_id, PlayerInventoryItem* item); size_t find_item(uint32_t item_id); - uint32_t generate_item_id(uint32_t client_id); + uint32_t generate_item_id(uint8_t client_id); void assign_item_ids_for_player(uint32_t client_id, PlayerInventory& inv); diff --git a/Player.hh b/Player.hh index 78bf020f..73bdb8d4 100644 --- a/Player.hh +++ b/Player.hh @@ -4,6 +4,7 @@ #include #include +#include #include "Version.hh" @@ -329,9 +330,9 @@ struct PlayerBB { uint8_t quest_data1[0x0208]; // 04F0 // player PlayerBank bank; // 06F8 // player uint32_t serial_number; // 19C0 // player - char16_t name[0x18]; // 19C4 // player - char16_t team_name[0x10]; // 19C4 // player - char16_t guild_card_desc[0x58]; // 1A14 // player + char16_t name[0x18]; // 19C4 // player + char16_t team_name[0x10]; // 19C4 // player + char16_t guild_card_desc[0x58]; // 1A14 // player uint8_t reserved1; // 1AC4 // player uint8_t reserved2; // 1AC5 // player uint8_t section_id; // 1AC6 // player @@ -339,8 +340,8 @@ struct PlayerBB { uint32_t unknown3; // 1AC8 // uint8_t symbol_chats[0x04E0]; // 1ACC // account uint8_t shortcuts[0x0A40]; // 1FAC // account - char16_t auto_reply[0x00AC]; // 29EC // player - char16_t info_board[0x00AC]; // 2B44 // player + char16_t auto_reply[0x00AC]; // 29EC // player + char16_t info_board[0x00AC]; // 2B44 // player uint8_t unknown5[0x001C]; // 2C9C // uint8_t challenge_data[0x0140]; // 2CB8 // player uint8_t tech_menu_config[0x0028]; // 2DF8 // player @@ -399,6 +400,7 @@ struct Player { uint8_t quest_data1[0x0208]; // player uint8_t quest_data2[0x0058]; // player uint32_t serial_number; + std::vector current_shop_contents; uint8_t shortcuts[0x0A40]; // account uint8_t symbol_chats[0x04E0]; // account char16_t team_name[0x0010]; // account diff --git a/ReceiveSubcommands.cc b/ReceiveSubcommands.cc index 1e285a14..36bd592b 100644 --- a/ReceiveSubcommands.cc +++ b/ReceiveSubcommands.cc @@ -357,6 +357,35 @@ static void process_subcommand_use_item(shared_ptr s, forward_subcommand(l, c, command, flag, p, count); } +static void process_subcommand_open_shop(shared_ptr s, + shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, + const PSOSubcommand* p, size_t count) { + check_size(count, 2); + uint32_t shop_type = p[1].dword; + + if ((l->version == GameVersion::BB) && l->is_game()) { + size_t num_items = (rand() % 4) + 9; + c->player.current_shop_contents.clear(); + while (c->player.current_shop_contents.size() < num_items) { + ItemData item_data; + if (shop_type == 0) { // tool shop + item_data = s->common_item_creator->create_shop_item(l->difficulty, 3); + } else if (shop_type == 1) { // weapon shop + item_data = s->common_item_creator->create_shop_item(l->difficulty, 0); + } else if (shop_type == 2) { // guards shop + item_data = s->common_item_creator->create_shop_item(l->difficulty, 1); + } else { // unknown shop... just leave it blank I guess + break; + } + + item_data.item_id = l->generate_item_id(c->lobby_client_id); + c->player.current_shop_contents.emplace_back(item_data); + } + + send_shop(c, shop_type); + } +} + static void process_subcommand_open_bank(shared_ptr s, shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, const PSOSubcommand* p, size_t count) { @@ -418,7 +447,7 @@ static void process_subcommand_bank_action(shared_ptr s, PlayerBankItem bank_item; c->player.bank.remove_item(cmd->item_id, cmd->item_amount, &bank_item); PlayerInventoryItem item = bank_item.to_inventory_item(); - item.data.item_id = l->generate_item_id(0xFFFFFFFF); + item.data.item_id = l->generate_item_id(0xFF); c->player.add_item(item); send_create_inventory_item(l, c, item.data); } @@ -514,7 +543,7 @@ static void process_subcommand_enemy_drop_item(shared_ptr s, } } else { try { - item.data = s->common_item_creator->create_item(false, l->episode, + item.data = s->common_item_creator->create_drop_item(false, l->episode, l->difficulty, cmd->area, l->section_id); } catch (const out_of_range&) { // create_common_item throws this when it doesn't want to make an item @@ -522,7 +551,7 @@ static void process_subcommand_enemy_drop_item(shared_ptr s, } } } - item.data.item_id = l->generate_item_id(0xFFFFFFFF); + 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, @@ -586,7 +615,7 @@ static void process_subcommand_box_drop_item(shared_ptr s, } } else { try { - item.data = s->common_item_creator->create_item(true, l->episode, + item.data = s->common_item_creator->create_drop_item(true, l->episode, l->difficulty, cmd->area, l->section_id); } catch (const out_of_range&) { // create_common_item throws this when it doesn't want to make an item @@ -594,7 +623,7 @@ static void process_subcommand_box_drop_item(shared_ptr s, } } } - item.data.item_id = l->generate_item_id(0xFFFFFFFF); + 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, @@ -1067,7 +1096,7 @@ subcommand_handler_t subcommand_handlers[0x100] = { process_subcommand_unimplemented, process_subcommand_unimplemented, process_subcommand_unimplemented, - process_subcommand_unimplemented, + process_subcommand_open_shop, process_subcommand_unimplemented, process_subcommand_unimplemented, process_subcommand_identify_item, diff --git a/SendCommands.cc b/SendCommands.cc index 43301ef8..bf7049dc 100644 --- a/SendCommands.cc +++ b/SendCommands.cc @@ -1936,6 +1936,37 @@ void send_bank(shared_ptr c) { send_command(c, 0x6C, 0x00, cmd, items); } +// sends the player a shop's contents +void send_shop(shared_ptr c, uint8_t shop_type) { + struct { + uint8_t subcommand; // B6 + uint8_t size; // 2C regardless of the number of items?? + uint16_t params; // 037F + uint8_t shop_type; + uint8_t num_items; + uint16_t unused; + ItemData entries[20]; + } cmd = { + 0xB6, + 0x2C, + 0x037F, + shop_type, + static_cast(c->player.current_shop_contents.size()), + 0, + }; + + size_t count = c->player.current_shop_contents.size(); + if (count > sizeof(cmd.entries) / sizeof(cmd.entries[0])) { + throw logic_error("too many items in shop"); + } + + for (size_t x = 0; x < count; x++) { + cmd.entries[x] = c->player.current_shop_contents[x]; + } + + send_command(c, 0x6C, 0x00, &cmd, sizeof(cmd) - sizeof(cmd.entries[0]) * (20 - count)); +} + // notifies players about a level up void send_level_up(shared_ptr l, shared_ptr c) { PlayerStats stats = c->player.disp.stats; diff --git a/SendCommands.hh b/SendCommands.hh index 05a04cc1..6f8c8c53 100644 --- a/SendCommands.hh +++ b/SendCommands.hh @@ -166,6 +166,7 @@ void send_create_inventory_item(std::shared_ptr l, std::shared_ptr l, std::shared_ptr c, uint32_t item_id, uint32_t amount); void send_bank(std::shared_ptr c); +void send_shop(std::shared_ptr c, uint8_t shop_type); void send_level_up(std::shared_ptr l, std::shared_ptr c); void send_give_experience(std::shared_ptr l, std::shared_ptr c, uint32_t amount);