From 82c651a3ad1b3feb58aa89335f9c932c0827d9d6 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Wed, 15 Nov 2023 22:19:44 -0800 Subject: [PATCH] implement BB trade window --- TODO.md | 1 - src/CommandFormats.hh | 6 +++++- src/ReceiveCommands.cc | 41 +++++++++++++++++++++++++++++++++++++---- 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/TODO.md b/TODO.md index 74d6106b..84a9877f 100644 --- a/TODO.md +++ b/TODO.md @@ -34,7 +34,6 @@ - Find any remaining mismatches in enemy indexes / experience - Support EXP multipliers in config.json -- Implement trade window - Fix some edge cases on the BB proxy server (e.g. Change Ship) - Implement less-common subcommands - 6xAC: Sort inventory diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index 862d883f..15aa177d 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -2588,7 +2588,11 @@ struct SC_TradeItems_D0_D3 { // D0 when sent by client, D3 when sent by server // See D0 description for usage information. // D3 (S->C): Execute trade (V3/BB) -// Same format as D0. See D0 description for usage information. +// On V3, this command has the same format as D0. See the D0 description for +// usage information. +// On BB, this command has no arguments (and the server generates the +// appropriate delete and create inventory item commands), but the D3 command +// must still must be sent before the D4 command to advance the trade state. // D4 (C->S): Trade failed (V3/BB) // No arguments diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 9f16aadf..795fee16 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -4116,12 +4116,45 @@ static void on_D2_V3_BB(shared_ptr c, uint16_t, uint32_t, string& data) throw runtime_error("player executed a trade with no other side pending"); } + auto complete_trade_for_side = [&](shared_ptr to_c, shared_ptr from_c) { + if (c->version() == GameVersion::BB) { + // On BB, the server is expected to generate the delete item and create + // item commands + auto to_p = to_c->game_data.character(); + auto from_p = from_c->game_data.character(); + for (const auto& trade_item : from_c->game_data.pending_item_trade->items) { + size_t amount = trade_item.stack_size(); + + auto item = from_p->remove_item(trade_item.id, amount, false); + // This is a special case: when the trade is executed, the client + // deletes the traded items from its own inventory automatically, so we + // should NOT send the 6x29 to that client; we should only send it to + // the other clients in the game. + G_DeleteInventoryItem_6x29 cmd = {{0x29, 0x03, from_c->lobby_client_id}, item.id, amount}; + for (auto lc : l->clients) { + if (lc && (lc != from_c)) { + send_command_t(l, 0x60, 0x00, cmd); + } + } + + to_p->add_item(trade_item); + send_create_inventory_item(to_c, item); + } + send_command(to_c, 0xD3, 0x00); + + } else { + // On V3, the clients will handle it; we just send their final trade lists + // to each other + send_execute_item_trade(to_c, target_c->game_data.pending_item_trade->items); + } + + send_command(to_c, 0xD4, 0x01); + }; + c->game_data.pending_item_trade->confirmed = true; if (target_c->game_data.pending_item_trade->confirmed) { - send_execute_item_trade(c, target_c->game_data.pending_item_trade->items); - send_execute_item_trade(target_c, c->game_data.pending_item_trade->items); - send_command(c, 0xD4, 0x01); - send_command(target_c, 0xD4, 0x01); + complete_trade_for_side(c, target_c); + complete_trade_for_side(target_c, c); c->game_data.pending_item_trade.reset(); target_c->game_data.pending_item_trade.reset(); }