From acb9c656c5aa1a624050ae02362ba8c32db4deab Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Wed, 29 Nov 2023 16:42:24 -0800 Subject: [PATCH] implement 6xCB subcommand --- src/Client.hh | 1 + src/CommandFormats.hh | 17 +++++----- src/PlayerSubordinates.cc | 11 +++--- src/ReceiveSubcommands.cc | 71 ++++++++++++++++++++++++++++++++++++++- 4 files changed, 86 insertions(+), 14 deletions(-) diff --git a/src/Client.hh b/src/Client.hh index 15940553..5bc06ca1 100644 --- a/src/Client.hh +++ b/src/Client.hh @@ -54,6 +54,7 @@ struct Client : public std::enable_shared_from_this { HAS_EP3_MEDIA_UPDATES = 0x0000000010000000, USE_OVERRIDE_RANDOM_SEED = 0x0000000020000000, HAS_GUILD_CARD_NUMBER = 0x0000000040000000, + AT_BANK_COUNTER = 0x0000000080000000, // Cheat mode flags SWITCH_ASSIST_ENABLED = 0x0000000100000000, diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index 4e400e03..ebaf5b4c 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -3356,8 +3356,9 @@ struct S_TeamInfoForPlayer_BB_13EA_15EA_Entry { // header.flag specifies the number of entries. The entry format appears to be // the same as for the 13EA command. -// 16EA (S->C): Unknown -// No arguments except header.flag. +// 16EA (S->C): Transfer item via Simple Mail result +// No arguments except header.flag, which is 0 if the transfer failed and +// nonzero if it succeeded. // 18EA: Intra-team ranking information // No arguments (C->S) @@ -5486,7 +5487,7 @@ struct G_BankAction_BB_6xBD { G_UnusedHeader header; le_uint32_t item_id = 0; // 0xFFFFFFFF = meseta; anything else = item le_uint32_t meseta_amount = 0; - uint8_t action = 0; // 0 = deposit, 1 = take + uint8_t action = 0; // 0 = deposit, 1 = take, 3 = done (close bank window) uint8_t item_amount = 0; le_uint16_t unused2 = 0; } __packed__; @@ -5608,13 +5609,13 @@ struct G_ItemRewardRequest_BB_6xCA { ItemData item_data; } __packed__; -// 6xCB: Request to transfer item (BB) +// 6xCB: Transfer item via mail message (BB) -struct G_ItemTransferRequest_BB_6xCB { +struct G_TransferItemViaMailMessage_BB_6xCB { G_ClientIDHeader header; - le_uint32_t unknown_a1 = 0; - le_uint32_t unknown_a2 = 0; - le_uint32_t unknown_a3 = 0; + le_uint32_t item_id = 0; + le_uint32_t amount = 0; + le_uint32_t target_guild_card_number = 0; } __packed__; // 6xCC: Exchange item for team points (BB) diff --git a/src/PlayerSubordinates.cc b/src/PlayerSubordinates.cc index af60dcf3..8b0263bf 100644 --- a/src/PlayerSubordinates.cc +++ b/src/PlayerSubordinates.cc @@ -466,17 +466,18 @@ void PlayerBank::add_item(const ItemData& item) { } if (y < this->num_items) { - this->items[y].data.data1[5] += item.data1[5]; - if (this->items[y].data.data1[5] > combine_max) { - this->items[y].data.data1[5] = combine_max; + uint8_t new_count = this->items[y].data.data1[5] + item.data1[5]; + if (new_count > combine_max) { + throw runtime_error("stack size would exceed limit"); } - this->items[y].amount = this->items[y].data.data1[5]; + this->items[y].data.data1[5] = new_count; + this->items[y].amount = new_count; return; } } if (this->num_items >= 200) { - throw runtime_error("bank is full"); + throw runtime_error("no free space in bank"); } auto& last_item = this->items[this->num_items]; last_item.data = item; diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index c4df7ba2..dfe4630e 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -1428,6 +1428,7 @@ static void on_open_shop_bb_or_ep3_battle_subs(shared_ptr c, uint8_t com static void on_open_bank_bb_or_card_trade_counter_ep3(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { auto l = c->require_lobby(); if ((l->base_version == Version::BB_V4) && l->is_game()) { + c->config.set_flag(Client::Flag::AT_BANK_COUNTER); send_bank(c); } else if (l->is_ep3()) { forward_subcommand(c, command, flag, data, size); @@ -1501,6 +1502,9 @@ static void on_ep3_private_word_select_bb_bank_action(shared_ptr c, uint c->lobby_client_id, cmd.item_id.load(), cmd.item_amount, name.c_str()); c->game_data.character()->print_inventory(stderr, c->version(), s->item_name_index); } + + } else if (cmd.action == 3) { // Leave bank counter + c->config.clear_flag(Client::Flag::AT_BANK_COUNTER); } } else if (is_ep3(c->version())) { @@ -2110,6 +2114,71 @@ void on_item_reward_request_bb(shared_ptr c, uint8_t, uint8_t, const voi send_create_inventory_item(c, item); } +void on_transfer_item_via_mail_message_bb(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { + const auto& cmd = check_size_t(data, size); + + auto team = c->team(); + if (!team) { + throw runtime_error("player is not in a team"); + } + auto l = c->require_lobby(); + if (!l->is_game()) { + return; + } + if (cmd.header.client_id != c->lobby_client_id) { + return; + } + + if (l->base_version != Version::BB_V4) { + throw runtime_error("item tracking not enabled in BB game"); + } + if (!l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { + throw runtime_error("item tracking not enabled in BB game"); + } + + forward_subcommand(c, command, flag, data, size); + + auto s = c->require_server_state(); + auto p = c->game_data.character(); + auto item = p->remove_item(cmd.item_id, cmd.amount, c->version() != Version::BB_V4); + + auto name = s->describe_item(c->version(), item, false); + l->log.info("Player %hhu sent inventory item %hu:%08" PRIX32 " (%s) x%" PRIu32 " to player %08" PRIX32, + c->lobby_client_id, cmd.header.client_id.load(), cmd.item_id.load(), name.c_str(), cmd.amount.load(), cmd.target_guild_card_number.load()); + if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { + string name = s->describe_item(c->version(), item, true); + send_text_message_printf(c, "$C5SEND/MAIL %08" PRIX32 " x%" PRIu32 "\n%s", cmd.item_id.load(), cmd.amount.load(), name.c_str()); + } + p->print_inventory(stderr, c->version(), s->item_name_index); + + // To receive an item, the player must be online, using BB, have a character + // loaded (that is, be in a lobby or game), not be at the bank counter at the + // moment, and there must be room in their bank to receive the item. + bool item_sent = false; + auto target_c = s->find_client(nullptr, cmd.target_guild_card_number); + if (target_c && + (target_c->version() == Version::BB_V4) && + (target_c->game_data.character(false) != nullptr) && + !target_c->config.check_flag(Client::Flag::AT_BANK_COUNTER)) { + auto target_p = target_c->game_data.character(false); + try { + target_p->bank.add_item(item); + item_sent = true; + } catch (const runtime_error&) { + } + } + + if (item_sent) { + send_command(c, 0x16EA, 0x00000001); + } else { + send_command(c, 0x16EA, 0x00000000); + // If the item failed to send, add it back to the sender's inventory + item.id = l->generate_item_id(0xFF); + p->add_item(item); + send_create_inventory_item(c, item); + } +} + void on_exchange_item_for_team_points_bb(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { const auto& cmd = check_size_t(data, size); @@ -2926,7 +2995,7 @@ SubcommandDefinition subcommand_definitions[0x100] = { /* 6xC8 */ {0x00, 0x00, 0xC8, on_enemy_exp_request_bb}, /* 6xC9 */ {0x00, 0x00, 0xC9, on_meseta_reward_request_bb}, /* 6xCA */ {0x00, 0x00, 0xCA, on_item_reward_request_bb}, - /* 6xCB */ {0x00, 0x00, 0xCB, nullptr}, + /* 6xCB */ {0x00, 0x00, 0xCB, on_transfer_item_via_mail_message_bb}, /* 6xCC */ {0x00, 0x00, 0xCC, on_exchange_item_for_team_points_bb}, /* 6xCD */ {0x00, 0x00, 0xCD, on_forward_check_size}, /* 6xCE */ {0x00, 0x00, 0xCE, on_forward_check_size},