From 0319e519602e3587a19babf9dcc2c6413c652f8b Mon Sep 17 00:00:00 2001 From: zhupengfei Date: Fri, 9 Nov 2018 21:55:57 +0800 Subject: [PATCH] multiplayer: Add status message for user joining/leaving The room server is now able to send a new type of packet: IdStatusMessage which is parsed and displayed by the client. --- src/citra_qt/multiplayer/chat_room.cpp | 32 ++++++++++++++++++-- src/citra_qt/multiplayer/chat_room.h | 3 ++ src/network/room.cpp | 42 +++++++++++++++++++++++--- src/network/room.h | 7 +++++ src/network/room_member.cpp | 39 ++++++++++++++++++++++++ src/network/room_member.h | 18 +++++++++++ 6 files changed, 134 insertions(+), 7 deletions(-) diff --git a/src/citra_qt/multiplayer/chat_room.cpp b/src/citra_qt/multiplayer/chat_room.cpp index 357fdca39..ff12b3f1a 100644 --- a/src/citra_qt/multiplayer/chat_room.cpp +++ b/src/citra_qt/multiplayer/chat_room.cpp @@ -70,12 +70,11 @@ public: } QString GetSystemChatMessage() const { - return QString("[%1] %3") - .arg(timestamp, system_color, message); + return QString("[%1] * %3").arg(timestamp, system_color, message); } private: - static constexpr const char system_color[] = "#888888"; + static constexpr const char system_color[] = "#FF8C00"; QString timestamp; QString message; }; @@ -133,6 +132,7 @@ ChatRoom::ChatRoom(QWidget* parent) : QWidget(parent), ui(std::make_unique(); + qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); @@ -140,7 +140,12 @@ ChatRoom::ChatRoom(QWidget* parent) : QWidget(parent), ui(std::make_uniqueBindOnChatMessageRecieved( [this](const Network::ChatEntry& chat) { emit ChatReceived(chat); }); + member->BindOnStatusMessageReceived( + [this](const Network::StatusMessageEntry& status_message) { + emit StatusMessageReceived(status_message); + }); connect(this, &ChatRoom::ChatReceived, this, &ChatRoom::OnChatReceive); + connect(this, &ChatRoom::StatusMessageReceived, this, &ChatRoom::OnStatusMessageReceive); } else { // TODO (jroweboy) network was not initialized? } @@ -220,6 +225,27 @@ void ChatRoom::OnChatReceive(const Network::ChatEntry& chat) { } } +void ChatRoom::OnStatusMessageReceive(const Network::StatusMessageEntry& status_message) { + QString name; + if (status_message.username.empty() || status_message.username == status_message.nickname) { + name = QString::fromStdString(status_message.nickname); + } else { + name = QString("%1 (%2)").arg(QString::fromStdString(status_message.nickname), + QString::fromStdString(status_message.username)); + } + QString message; + switch (status_message.type) { + case Network::IdMemberJoin: + message = tr("%1 has joined").arg(name); + break; + case Network::IdMemberLeave: + message = tr("%1 has left").arg(name); + break; + } + if (!message.isEmpty()) + AppendStatusMessage(message); +} + void ChatRoom::OnSendChat() { if (auto room = Network::GetRoomMember().lock()) { if (room->GetState() != Network::RoomMember::State::Joined) { diff --git a/src/citra_qt/multiplayer/chat_room.h b/src/citra_qt/multiplayer/chat_room.h index d76f995b8..7a7c84b48 100644 --- a/src/citra_qt/multiplayer/chat_room.h +++ b/src/citra_qt/multiplayer/chat_room.h @@ -39,6 +39,7 @@ public: public slots: void OnRoomUpdate(const Network::RoomInformation& info); void OnChatReceive(const Network::ChatEntry&); + void OnStatusMessageReceive(const Network::StatusMessageEntry&); void OnSendChat(); void OnChatTextChanged(); void PopupContextMenu(const QPoint& menu_location); @@ -47,6 +48,7 @@ public slots: signals: void ChatReceived(const Network::ChatEntry&); + void StatusMessageReceived(const Network::StatusMessageEntry&); private: static constexpr u32 max_chat_lines = 1000; @@ -61,5 +63,6 @@ private: }; Q_DECLARE_METATYPE(Network::ChatEntry); +Q_DECLARE_METATYPE(Network::StatusMessageEntry); Q_DECLARE_METATYPE(Network::RoomInformation); Q_DECLARE_METATYPE(Network::RoomMember::State); diff --git a/src/network/room.cpp b/src/network/room.cpp index cae4a7258..ee62df220 100644 --- a/src/network/room.cpp +++ b/src/network/room.cpp @@ -127,6 +127,12 @@ public: */ void SendCloseMessage(); + /** + * Sends a system message to all the connected clients. + */ + void SendStatusMessage(StatusMessageTypes type, const std::string& nickname, + const std::string& username); + /** * Sends the information about the room, along with the list of members * to every connected client in the room. @@ -290,6 +296,9 @@ void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) { } member.user_data = verify_backend->LoadUserData(uid, token); + // Notify everyone that the user has joined. + SendStatusMessage(IdMemberJoin, member.nickname, member.user_data.username); + { std::lock_guard lock(member_mutex); members.push_back(std::move(member)); @@ -415,6 +424,24 @@ void Room::RoomImpl::SendCloseMessage() { } } +void Room::RoomImpl::SendStatusMessage(StatusMessageTypes type, const std::string& nickname, + const std::string& username) { + Packet packet; + packet << static_cast(IdStatusMessage); + packet << static_cast(type); + packet << nickname; + packet << username; + std::lock_guard lock(member_mutex); + if (!members.empty()) { + ENetPacket* enet_packet = + enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE); + for (auto& member : members) { + enet_peer_send(member.peer, 0, enet_packet); + } + } + enet_host_flush(server); +} + void Room::RoomImpl::BroadcastRoomInformation() { Packet packet; packet << static_cast(IdRoomInformation); @@ -571,16 +598,23 @@ void Room::RoomImpl::HandleGameNamePacket(const ENetEvent* event) { void Room::RoomImpl::HandleClientDisconnection(ENetPeer* client) { // Remove the client from the members list. + std::string nickname, username; { std::lock_guard lock(member_mutex); - members.erase( - std::remove_if(members.begin(), members.end(), - [client](const Member& member) { return member.peer == client; }), - members.end()); + auto member = std::find_if(members.begin(), members.end(), [client](const Member& member) { + return member.peer == client; + }); + if (member != members.end()) { + nickname = member->nickname; + username = member->user_data.username; + members.erase(member); + } } // Announce the change to all clients. enet_peer_disconnect(client, 0); + if (!nickname.empty()) + SendStatusMessage(IdMemberLeave, nickname, username); BroadcastRoomInformation(); } diff --git a/src/network/room.h b/src/network/room.h index d1a26e62d..a3d93eea9 100644 --- a/src/network/room.h +++ b/src/network/room.h @@ -61,6 +61,13 @@ enum RoomMessageTypes : u8 { IdCloseRoom, IdRoomIsFull, IdConsoleIdCollision, + IdStatusMessage, +}; + +/// Types of system status messages +enum StatusMessageTypes : u8 { + IdMemberJoin = 1, ///< Member joining + IdMemberLeave, ///< Member leaving }; /// This is what a server [person creating a server] would use. diff --git a/src/network/room_member.cpp b/src/network/room_member.cpp index 8fe67c086..79ba71da8 100644 --- a/src/network/room_member.cpp +++ b/src/network/room_member.cpp @@ -58,6 +58,7 @@ public: private: CallbackSet callback_set_wifi_packet; CallbackSet callback_set_chat_messages; + CallbackSet callback_set_status_messages; CallbackSet callback_set_room_information; CallbackSet callback_set_state; }; @@ -109,6 +110,13 @@ public: */ void HandleChatPacket(const ENetEvent* event); + /** + * Extracts a system message entry from a received ENet packet and adds it to the system message + * queue. + * @param event The ENet event that was received. + */ + void HandleStatusMessagePacket(const ENetEvent* event); + /** * Disconnects the RoomMember from the Room */ @@ -148,6 +156,9 @@ void RoomMember::RoomMemberImpl::MemberLoop() { case IdChatMessage: HandleChatPacket(&event); break; + case IdStatusMessage: + HandleStatusMessagePacket(&event); + break; case IdRoomInformation: HandleRoomInformationPacket(&event); break; @@ -317,6 +328,22 @@ void RoomMember::RoomMemberImpl::HandleChatPacket(const ENetEvent* event) { Invoke(chat_entry); } +void RoomMember::RoomMemberImpl::HandleStatusMessagePacket(const ENetEvent* event) { + Packet packet; + packet.Append(event->packet->data, event->packet->dataLength); + + // Ignore the first byte, which is the message id. + packet.IgnoreBytes(sizeof(u8)); + + StatusMessageEntry status_message_entry{}; + u8 type{}; + packet >> type; + status_message_entry.type = static_cast(type); + packet >> status_message_entry.nickname; + packet >> status_message_entry.username; + Invoke(status_message_entry); +} + void RoomMember::RoomMemberImpl::Disconnect() { member_information.clear(); room_information.member_slots = 0; @@ -367,6 +394,12 @@ RoomMember::RoomMemberImpl::CallbackSet& RoomMember::RoomMemberImpl:: return callback_set_chat_messages; } +template <> +RoomMember::RoomMemberImpl::CallbackSet& +RoomMember::RoomMemberImpl::Callbacks::Get() { + return callback_set_status_messages; +} + template void RoomMember::RoomMemberImpl::Invoke(const T& data) { std::lock_guard lock(callback_mutex); @@ -519,6 +552,11 @@ RoomMember::CallbackHandle RoomMember::BindOnChatMessageRecieved( return room_member_impl->Bind(callback); } +RoomMember::CallbackHandle RoomMember::BindOnStatusMessageReceived( + std::function callback) { + return room_member_impl->Bind(callback); +} + template void RoomMember::Unbind(CallbackHandle handle) { std::lock_guard lock(room_member_impl->callback_mutex); @@ -538,5 +576,6 @@ template void RoomMember::Unbind(CallbackHandle); template void RoomMember::Unbind(CallbackHandle); template void RoomMember::Unbind(CallbackHandle); template void RoomMember::Unbind(CallbackHandle); +template void RoomMember::Unbind(CallbackHandle); } // namespace Network diff --git a/src/network/room_member.h b/src/network/room_member.h index 4329263aa..5062b225e 100644 --- a/src/network/room_member.h +++ b/src/network/room_member.h @@ -40,6 +40,14 @@ struct ChatEntry { std::string message; ///< Body of the message. }; +/// Represents a system status message. +struct StatusMessageEntry { + StatusMessageTypes type; ///< Type of the message + /// Subject of the message. i.e. the user who is joining/leaving/being banned, etc. + std::string nickname; + std::string username; +}; + /** * This is what a client [person joining a server] would use. * It also has to be used if you host a game yourself (You'd create both, a Room and a @@ -192,6 +200,16 @@ public: CallbackHandle BindOnChatMessageRecieved( std::function callback); + /** + * Binds a function to an event that will be triggered every time a StatusMessage is + * received. The function will be called every time the event is triggered. The callback + * function must not bind or unbind a function. Doing so will cause a deadlock + * @param callback The function to call + * @return A handle used for removing the function from the registered list + */ + CallbackHandle BindOnStatusMessageReceived( + std::function callback); + /** * Leaves the current room. */