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:
zhupengfei 2018-11-09 21:55:57 +08:00
parent 386bf5c861
commit 0319e51960
6 changed files with 134 additions and 7 deletions

View file

@ -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) {

View file

@ -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);

View file

@ -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();
} }

View file

@ -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.

View file

@ -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

View file

@ -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.
*/ */