diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index ba72d32f..e2dc8ddb 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -4676,7 +4676,9 @@ struct G_ActivateMagEffect_6x61 { // 6x62: Unknown // This command has a handler, but it does nothing even on DC NTE. -// 6x63: Destroy floor item (used when too many items have been dropped) +// 6x63: Destroy floor item +// This is sent by the leader to destroy a floor item when there are 50 or more +// items already on the ground on the current floor. struct G_DestroyFloorItem_6x5C_6x63 { G_UnusedHeader header; diff --git a/src/Lobby.hh b/src/Lobby.hh index 2d3c0e76..0c313df7 100644 --- a/src/Lobby.hh +++ b/src/Lobby.hh @@ -91,8 +91,7 @@ struct Lobby : public std::enable_shared_from_this { SERVER_DUPLICATE = 4, }; - std::weak_ptr - server_state; + std::weak_ptr server_state; PrefixedLogger log; uint32_t lobby_id; diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 98eac6af..8d8da439 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -3243,6 +3243,12 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, stri c->update_channel_name(); + // If the player is BB and has just left a game, sync their save file to the + // client to make sure it's up to date + if ((c->version() == Version::BB_V4) && (command == 0x98)) { + send_complete_player_bb(c); + } + if (command == 0x61) { if (c->pending_character_export) { unique_ptr pending_export = std::move(c->pending_character_export); diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 24e0da90..de5d1e8b 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -3724,29 +3724,52 @@ static void on_destroy_floor_item(shared_ptr c, uint8_t command, uint8_t } auto s = c->require_server_state(); - auto fi = l->remove_item(cmd.floor, cmd.item_id, 0xFF); - auto name = s->describe_item(c->version(), fi->data, false); - l->log.info("Player %hhu destroyed floor item %08" PRIX32 " (%s)", c->lobby_client_id, cmd.item_id.load(), name.c_str()); + shared_ptr fi; + try { + fi = l->remove_item(cmd.floor, cmd.item_id, 0xFF); + } catch (const out_of_range&) { + } - // Only forward to players for whom the item was visible - for (size_t z = 0; z < l->clients.size(); z++) { - auto lc = l->clients[z]; - if (lc && fi->visible_to_client(z)) { - if (lc->version() != c->version()) { - G_DestroyFloorItem_6x5C_6x63 out_cmd = cmd; - switch (lc->version()) { - case Version::DC_NTE: - out_cmd.header.subcommand = is_6x5C ? 0x4E : 0x55; - break; - case Version::DC_V1_11_2000_PROTOTYPE: - out_cmd.header.subcommand = is_6x5C ? 0x55 : 0x5C; - break; - default: - out_cmd.header.subcommand = is_6x5C ? 0x5C : 0x63; + if (!fi) { + // There are generally two data races that could occur here. Either the + // player attempted to evict the item at the same time the server did (that + // is, the client's and server's 6x63 commands crossed paths on the + // network), or the player attempted to evict an item that was already + // picked up. The former case is easy to handle; we can just ignore the + // command. The latter case is more difficult - we have to know which + // player picked up the item and send a 6x2B command to the sender, to sync + // their item state with the server's again. We can't just look through the + // players' inventories to find the item ID, since item IDs can be + // destroyed when stackable items or Meseta are picked up. + // TODO: We don't actually handle the evict/pickup conflict case. This case + // is probably quite rare, but we should eventually handle it. + l->log.info("Player %hhu attempted to destroy floor item %08" PRIX32 ", but it is missing", + c->lobby_client_id, cmd.item_id.load()); + + } else { + auto name = s->describe_item(c->version(), fi->data, false); + l->log.info("Player %hhu destroyed floor item %08" PRIX32 " (%s)", c->lobby_client_id, cmd.item_id.load(), name.c_str()); + + // Only forward to players for whom the item was visible + for (size_t z = 0; z < l->clients.size(); z++) { + auto lc = l->clients[z]; + if (lc && fi->visible_to_client(z)) { + if (lc->version() != c->version()) { + G_DestroyFloorItem_6x5C_6x63 out_cmd = cmd; + switch (lc->version()) { + case Version::DC_NTE: + out_cmd.header.subcommand = is_6x5C ? 0x4E : 0x55; + break; + case Version::DC_V1_11_2000_PROTOTYPE: + out_cmd.header.subcommand = is_6x5C ? 0x55 : 0x5C; + break; + default: + out_cmd.header.subcommand = is_6x5C ? 0x5C : 0x63; + } + send_command_t(lc, command, flag, out_cmd); + } else { + send_command_t(lc, command, flag, cmd); } - send_command_t(lc, command, flag, out_cmd); - } else { - send_command_t(lc, command, flag, cmd); } } }