mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 13:50:03 +00:00 
			
		
		
		
	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.
This commit is contained in:
		
							parent
							
								
									386bf5c861
								
							
						
					
					
						commit
						0319e51960
					
				
					 6 changed files with 134 additions and 7 deletions
				
			
		|  | @ -70,12 +70,11 @@ public: | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     QString GetSystemChatMessage() const { |     QString GetSystemChatMessage() const { | ||||||
|         return QString("[%1] <font color='%2'><i>%3</i></font>") |         return QString("[%1] <font color='%2'>* %3</font>").arg(timestamp, system_color, message); | ||||||
|             .arg(timestamp, system_color, message); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     static constexpr const char system_color[] = "#888888"; |     static constexpr const char system_color[] = "#FF8C00"; | ||||||
|     QString timestamp; |     QString timestamp; | ||||||
|     QString message; |     QString message; | ||||||
| }; | }; | ||||||
|  | @ -133,6 +132,7 @@ ChatRoom::ChatRoom(QWidget* parent) : QWidget(parent), ui(std::make_unique<Ui::C | ||||||
| 
 | 
 | ||||||
|     // register the network structs to use in slots and signals
 |     // register the network structs to use in slots and signals
 | ||||||
|     qRegisterMetaType<Network::ChatEntry>(); |     qRegisterMetaType<Network::ChatEntry>(); | ||||||
|  |     qRegisterMetaType<Network::StatusMessageEntry>(); | ||||||
|     qRegisterMetaType<Network::RoomInformation>(); |     qRegisterMetaType<Network::RoomInformation>(); | ||||||
|     qRegisterMetaType<Network::RoomMember::State>(); |     qRegisterMetaType<Network::RoomMember::State>(); | ||||||
| 
 | 
 | ||||||
|  | @ -140,7 +140,12 @@ ChatRoom::ChatRoom(QWidget* parent) : QWidget(parent), ui(std::make_unique<Ui::C | ||||||
|     if (auto member = Network::GetRoomMember().lock()) { |     if (auto member = Network::GetRoomMember().lock()) { | ||||||
|         member->BindOnChatMessageRecieved( |         member->BindOnChatMessageRecieved( | ||||||
|             [this](const Network::ChatEntry& chat) { emit ChatReceived(chat); }); |             [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::ChatReceived, this, &ChatRoom::OnChatReceive); | ||||||
|  |         connect(this, &ChatRoom::StatusMessageReceived, this, &ChatRoom::OnStatusMessageReceive); | ||||||
|     } else { |     } else { | ||||||
|         // TODO (jroweboy) network was not initialized?
 |         // 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() { | void ChatRoom::OnSendChat() { | ||||||
|     if (auto room = Network::GetRoomMember().lock()) { |     if (auto room = Network::GetRoomMember().lock()) { | ||||||
|         if (room->GetState() != Network::RoomMember::State::Joined) { |         if (room->GetState() != Network::RoomMember::State::Joined) { | ||||||
|  |  | ||||||
|  | @ -39,6 +39,7 @@ public: | ||||||
| public slots: | public slots: | ||||||
|     void OnRoomUpdate(const Network::RoomInformation& info); |     void OnRoomUpdate(const Network::RoomInformation& info); | ||||||
|     void OnChatReceive(const Network::ChatEntry&); |     void OnChatReceive(const Network::ChatEntry&); | ||||||
|  |     void OnStatusMessageReceive(const Network::StatusMessageEntry&); | ||||||
|     void OnSendChat(); |     void OnSendChat(); | ||||||
|     void OnChatTextChanged(); |     void OnChatTextChanged(); | ||||||
|     void PopupContextMenu(const QPoint& menu_location); |     void PopupContextMenu(const QPoint& menu_location); | ||||||
|  | @ -47,6 +48,7 @@ public slots: | ||||||
| 
 | 
 | ||||||
| signals: | signals: | ||||||
|     void ChatReceived(const Network::ChatEntry&); |     void ChatReceived(const Network::ChatEntry&); | ||||||
|  |     void StatusMessageReceived(const Network::StatusMessageEntry&); | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     static constexpr u32 max_chat_lines = 1000; |     static constexpr u32 max_chat_lines = 1000; | ||||||
|  | @ -61,5 +63,6 @@ private: | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| Q_DECLARE_METATYPE(Network::ChatEntry); | Q_DECLARE_METATYPE(Network::ChatEntry); | ||||||
|  | Q_DECLARE_METATYPE(Network::StatusMessageEntry); | ||||||
| Q_DECLARE_METATYPE(Network::RoomInformation); | Q_DECLARE_METATYPE(Network::RoomInformation); | ||||||
| Q_DECLARE_METATYPE(Network::RoomMember::State); | Q_DECLARE_METATYPE(Network::RoomMember::State); | ||||||
|  |  | ||||||
|  | @ -127,6 +127,12 @@ public: | ||||||
|      */ |      */ | ||||||
|     void SendCloseMessage(); |     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 |      * Sends the information about the room, along with the list of members | ||||||
|      * to every connected client in the room. |      * 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); |     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<std::mutex> lock(member_mutex); |         std::lock_guard<std::mutex> lock(member_mutex); | ||||||
|         members.push_back(std::move(member)); |         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<u8>(IdStatusMessage); | ||||||
|  |     packet << static_cast<u8>(type); | ||||||
|  |     packet << nickname; | ||||||
|  |     packet << username; | ||||||
|  |     std::lock_guard<std::mutex> 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() { | void Room::RoomImpl::BroadcastRoomInformation() { | ||||||
|     Packet packet; |     Packet packet; | ||||||
|     packet << static_cast<u8>(IdRoomInformation); |     packet << static_cast<u8>(IdRoomInformation); | ||||||
|  | @ -571,16 +598,23 @@ void Room::RoomImpl::HandleGameNamePacket(const ENetEvent* event) { | ||||||
| 
 | 
 | ||||||
| void Room::RoomImpl::HandleClientDisconnection(ENetPeer* client) { | void Room::RoomImpl::HandleClientDisconnection(ENetPeer* client) { | ||||||
|     // Remove the client from the members list.
 |     // Remove the client from the members list.
 | ||||||
|  |     std::string nickname, username; | ||||||
|     { |     { | ||||||
|         std::lock_guard<std::mutex> lock(member_mutex); |         std::lock_guard<std::mutex> lock(member_mutex); | ||||||
|         members.erase( |         auto member = std::find_if(members.begin(), members.end(), [client](const Member& member) { | ||||||
|             std::remove_if(members.begin(), members.end(), |             return member.peer == client; | ||||||
|                            [client](const Member& member) { return member.peer == client; }), |         }); | ||||||
|             members.end()); |         if (member != members.end()) { | ||||||
|  |             nickname = member->nickname; | ||||||
|  |             username = member->user_data.username; | ||||||
|  |             members.erase(member); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Announce the change to all clients.
 |     // Announce the change to all clients.
 | ||||||
|     enet_peer_disconnect(client, 0); |     enet_peer_disconnect(client, 0); | ||||||
|  |     if (!nickname.empty()) | ||||||
|  |         SendStatusMessage(IdMemberLeave, nickname, username); | ||||||
|     BroadcastRoomInformation(); |     BroadcastRoomInformation(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -61,6 +61,13 @@ enum RoomMessageTypes : u8 { | ||||||
|     IdCloseRoom, |     IdCloseRoom, | ||||||
|     IdRoomIsFull, |     IdRoomIsFull, | ||||||
|     IdConsoleIdCollision, |     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.
 | /// This is what a server [person creating a server] would use.
 | ||||||
|  |  | ||||||
|  | @ -58,6 +58,7 @@ public: | ||||||
|     private: |     private: | ||||||
|         CallbackSet<WifiPacket> callback_set_wifi_packet; |         CallbackSet<WifiPacket> callback_set_wifi_packet; | ||||||
|         CallbackSet<ChatEntry> callback_set_chat_messages; |         CallbackSet<ChatEntry> callback_set_chat_messages; | ||||||
|  |         CallbackSet<StatusMessageEntry> callback_set_status_messages; | ||||||
|         CallbackSet<RoomInformation> callback_set_room_information; |         CallbackSet<RoomInformation> callback_set_room_information; | ||||||
|         CallbackSet<State> callback_set_state; |         CallbackSet<State> callback_set_state; | ||||||
|     }; |     }; | ||||||
|  | @ -109,6 +110,13 @@ public: | ||||||
|      */ |      */ | ||||||
|     void HandleChatPacket(const ENetEvent* event); |     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 |      * Disconnects the RoomMember from the Room | ||||||
|      */ |      */ | ||||||
|  | @ -148,6 +156,9 @@ void RoomMember::RoomMemberImpl::MemberLoop() { | ||||||
|                 case IdChatMessage: |                 case IdChatMessage: | ||||||
|                     HandleChatPacket(&event); |                     HandleChatPacket(&event); | ||||||
|                     break; |                     break; | ||||||
|  |                 case IdStatusMessage: | ||||||
|  |                     HandleStatusMessagePacket(&event); | ||||||
|  |                     break; | ||||||
|                 case IdRoomInformation: |                 case IdRoomInformation: | ||||||
|                     HandleRoomInformationPacket(&event); |                     HandleRoomInformationPacket(&event); | ||||||
|                     break; |                     break; | ||||||
|  | @ -317,6 +328,22 @@ void RoomMember::RoomMemberImpl::HandleChatPacket(const ENetEvent* event) { | ||||||
|     Invoke<ChatEntry>(chat_entry); |     Invoke<ChatEntry>(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<StatusMessageTypes>(type); | ||||||
|  |     packet >> status_message_entry.nickname; | ||||||
|  |     packet >> status_message_entry.username; | ||||||
|  |     Invoke<StatusMessageEntry>(status_message_entry); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void RoomMember::RoomMemberImpl::Disconnect() { | void RoomMember::RoomMemberImpl::Disconnect() { | ||||||
|     member_information.clear(); |     member_information.clear(); | ||||||
|     room_information.member_slots = 0; |     room_information.member_slots = 0; | ||||||
|  | @ -367,6 +394,12 @@ RoomMember::RoomMemberImpl::CallbackSet<ChatEntry>& RoomMember::RoomMemberImpl:: | ||||||
|     return callback_set_chat_messages; |     return callback_set_chat_messages; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | template <> | ||||||
|  | RoomMember::RoomMemberImpl::CallbackSet<StatusMessageEntry>& | ||||||
|  | RoomMember::RoomMemberImpl::Callbacks::Get() { | ||||||
|  |     return callback_set_status_messages; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| template <typename T> | template <typename T> | ||||||
| void RoomMember::RoomMemberImpl::Invoke(const T& data) { | void RoomMember::RoomMemberImpl::Invoke(const T& data) { | ||||||
|     std::lock_guard<std::mutex> lock(callback_mutex); |     std::lock_guard<std::mutex> lock(callback_mutex); | ||||||
|  | @ -519,6 +552,11 @@ RoomMember::CallbackHandle<ChatEntry> RoomMember::BindOnChatMessageRecieved( | ||||||
|     return room_member_impl->Bind(callback); |     return room_member_impl->Bind(callback); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | RoomMember::CallbackHandle<StatusMessageEntry> RoomMember::BindOnStatusMessageReceived( | ||||||
|  |     std::function<void(const StatusMessageEntry&)> callback) { | ||||||
|  |     return room_member_impl->Bind(callback); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| template <typename T> | template <typename T> | ||||||
| void RoomMember::Unbind(CallbackHandle<T> handle) { | void RoomMember::Unbind(CallbackHandle<T> handle) { | ||||||
|     std::lock_guard<std::mutex> lock(room_member_impl->callback_mutex); |     std::lock_guard<std::mutex> lock(room_member_impl->callback_mutex); | ||||||
|  | @ -538,5 +576,6 @@ template void RoomMember::Unbind(CallbackHandle<WifiPacket>); | ||||||
| template void RoomMember::Unbind(CallbackHandle<RoomMember::State>); | template void RoomMember::Unbind(CallbackHandle<RoomMember::State>); | ||||||
| template void RoomMember::Unbind(CallbackHandle<RoomInformation>); | template void RoomMember::Unbind(CallbackHandle<RoomInformation>); | ||||||
| template void RoomMember::Unbind(CallbackHandle<ChatEntry>); | template void RoomMember::Unbind(CallbackHandle<ChatEntry>); | ||||||
|  | template void RoomMember::Unbind(CallbackHandle<StatusMessageEntry>); | ||||||
| 
 | 
 | ||||||
| } // namespace Network
 | } // namespace Network
 | ||||||
|  |  | ||||||
|  | @ -40,6 +40,14 @@ struct ChatEntry { | ||||||
|     std::string message; ///< Body of the message.
 |     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. |  * 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 |  * 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<ChatEntry> BindOnChatMessageRecieved( |     CallbackHandle<ChatEntry> BindOnChatMessageRecieved( | ||||||
|         std::function<void(const ChatEntry&)> callback); |         std::function<void(const ChatEntry&)> 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<StatusMessageEntry> BindOnStatusMessageReceived( | ||||||
|  |         std::function<void(const StatusMessageEntry&)> callback); | ||||||
|  | 
 | ||||||
|     /**
 |     /**
 | ||||||
|      * Leaves the current room. |      * Leaves the current room. | ||||||
|      */ |      */ | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue