This commit is contained in:
Aaron Kimbre 2025-01-31 00:30:05 -06:00
parent aedc8a09fe
commit 6978b56016
6 changed files with 127 additions and 102 deletions

View File

@ -20,7 +20,7 @@
#include "eGameMasterLevel.h" #include "eGameMasterLevel.h"
#include "ChatPackets.h" #include "ChatPackets.h"
#include "json.hpp" #include "json.hpp"
#include "Web.h" #include "ChatWeb.h"
void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) { void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
//Get from the packet which player we want to do something with: //Get from the packet which player we want to do something with:
@ -428,50 +428,41 @@ void ChatPacketHandler::HandleShowAll(Packet* packet) {
// that are sent to the server. Because of this, there are large gaps of unused data in chat messages // that are sent to the server. Because of this, there are large gaps of unused data in chat messages
void ChatPacketHandler::HandleChatMessage(Packet* packet) { void ChatPacketHandler::HandleChatMessage(Packet* packet) {
CINSTREAM_SKIP_HEADER; CINSTREAM_SKIP_HEADER;
LWOOBJID playerID; ChatMessage data;
inStream.Read(playerID); LWOOBJID sender;
LOG("Got a message from player %llu", playerID); inStream.Read(sender);
LOG("Got a message from player %llu", sender);
const auto& sender = Game::playerContainer.GetPlayerData(playerID); data.sender = Game::playerContainer.GetPlayerData(sender);
if (!sender || sender.GetIsMuted()) return; if (!data.sender || data.sender.GetIsMuted()) return;
eChatChannel channel; eChatChannel channel;
uint32_t size; uint32_t size;
inStream.IgnoreBytes(4); inStream.IgnoreBytes(4);
inStream.Read(channel); inStream.Read(data.channel);
inStream.Read(size); inStream.Read(size);
inStream.IgnoreBytes(77); inStream.IgnoreBytes(77);
LOG("message size: %u", size); LOG("message size: %u", size);
LUWString message(size); data.message = LUWString(size);
inStream.Read(message); inStream.Read(data.message);
LOG("Got message %s from (%s) via [%s]: %s", message.GetAsString().c_str(), sender.playerName.c_str(), StringifiedEnum::ToString(channel).data(), message.GetAsString().c_str()); LOG("Got message from (%s) via [%s]: %s", data.sender.playerName.c_str(), StringifiedEnum::ToString(data.channel).data(), data.message.GetAsString().c_str());
// build chat json data
nlohmann::json data;
data["playerName"] = sender.playerName;
data["message"] = message.GetAsString();
auto& zoneID = data["zone_id"];
zoneID["map_id"] = sender.zoneID.GetMapID();
zoneID["instance_id"] = sender.zoneID.GetInstanceID();
zoneID["clone_id"] = sender.zoneID.GetCloneID();
switch (channel) { switch (channel) {
case eChatChannel::LOCAL: { case eChatChannel::LOCAL: {
Game::web.SendWSMessage("chat_local", data);
break; break;
} }
case eChatChannel::TEAM: { case eChatChannel::TEAM: {
auto* team = Game::playerContainer.GetTeam(playerID); auto* team = Game::playerContainer.GetTeam(data.sender.playerID);
if (team == nullptr) return; if (team == nullptr) return;
data.teamID = team->teamID;
for (const auto memberId : team->memberIDs) { for (const auto memberId : team->memberIDs) {
const auto& otherMember = Game::playerContainer.GetPlayerData(memberId); const auto& otherMember = Game::playerContainer.GetPlayerData(memberId);
if (!otherMember) return; if (!otherMember) return;
SendPrivateChatMessage(sender, otherMember, otherMember, message, eChatChannel::TEAM, eChatMessageResponseCode::SENT); SendPrivateChatMessage(data.sender, otherMember, otherMember, data.message, eChatChannel::TEAM, eChatMessageResponseCode::SENT);
data["teamID"] = team->teamID;
Game::web.SendWSMessage("chat_team", data);
} }
break; break;
} }
@ -479,70 +470,68 @@ void ChatPacketHandler::HandleChatMessage(Packet* packet) {
LOG("Unhandled Chat channel [%s]", StringifiedEnum::ToString(channel).data()); LOG("Unhandled Chat channel [%s]", StringifiedEnum::ToString(channel).data());
break; break;
} }
ChatWeb::SendWSChatMessage(data);
} }
// the structure the client uses to send this packet is shared in many chat messages // the structure the client uses to send this packet is shared in many chat messages
// that are sent to the server. Because of this, there are large gaps of unused data in chat messages // that are sent to the server. Because of this, there are large gaps of unused data in chat messages
void ChatPacketHandler::HandlePrivateChatMessage(Packet* packet) { void ChatPacketHandler::HandlePrivateChatMessage(Packet* packet) {
ChatMessage data;
data.channel = eChatChannel::GENERAL;
CINSTREAM_SKIP_HEADER; CINSTREAM_SKIP_HEADER;
LWOOBJID playerID; LWOOBJID playerID;
inStream.Read(playerID); inStream.Read(playerID);
const auto& sender = Game::playerContainer.GetPlayerData(playerID); data.sender = Game::playerContainer.GetPlayerData(playerID);
if (!sender || sender.GetIsMuted()) return; if (!data.sender || data.sender.GetIsMuted()) return;
eChatChannel channel;
uint32_t size; uint32_t size;
LUWString LUReceiverName;
inStream.IgnoreBytes(4); inStream.IgnoreBytes(4);
inStream.Read(channel); inStream.Read(data.channel);
if (channel != eChatChannel::PRIVATE_CHAT) LOG("WARNING: Received Private chat with the wrong channel!"); if (data.channel != eChatChannel::PRIVATE_CHAT) LOG("WARNING: Received Private chat with the wrong channel!");
inStream.Read(size); inStream.Read(size);
inStream.IgnoreBytes(77); inStream.IgnoreBytes(77);
LUWString LUReceiverName;
inStream.Read(LUReceiverName); inStream.Read(LUReceiverName);
auto receiverName = LUReceiverName.GetAsString(); auto receiverName = LUReceiverName.GetAsString();
inStream.IgnoreBytes(2); inStream.IgnoreBytes(2);
LUWString message(size); data.message = LUWString(size);
inStream.Read(message); inStream.Read(data.message);
LOG("Got a message from (%s) via [%s]: %s to %s", sender.playerName.c_str(), StringifiedEnum::ToString(channel).data(), message.GetAsString().c_str(), receiverName.c_str()); LOG("Got a message from (%s) via [%s]: %s to %s", data.sender.playerName.c_str(), StringifiedEnum::ToString(data.channel).data(), data.message.GetAsString().c_str(), receiverName.c_str());
const auto& receiver = Game::playerContainer.GetPlayerData(receiverName); data.receiver = Game::playerContainer.GetPlayerData(receiverName);
if (!receiver) { if (!data.receiver) {
PlayerData otherPlayer; PlayerData otherPlayer;
otherPlayer.playerName = receiverName; otherPlayer.playerName = receiverName;
auto responseType = Database::Get()->GetCharacterInfo(receiverName) auto responseType = Database::Get()->GetCharacterInfo(receiverName)
? eChatMessageResponseCode::NOTONLINE ? eChatMessageResponseCode::NOTONLINE
: eChatMessageResponseCode::GENERALERROR; : eChatMessageResponseCode::GENERALERROR;
SendPrivateChatMessage(sender, otherPlayer, sender, message, eChatChannel::GENERAL, responseType); SendPrivateChatMessage(data.sender, otherPlayer, data.sender, data.message, data.channel, responseType);
return; return;
} }
// Check to see if they are friends // Check to see if they are friends
// only freinds can whispr each other // only freinds can whispr each other
for (const auto& fr : receiver.friends) { for (const auto& fr : data.receiver.friends) {
if (fr.friendID == sender.playerID) { if (fr.friendID == data.sender.playerID) {
nlohmann::json data; data.channel = eChatChannel::PRIVATE_CHAT;
data["sender"] = sender.playerName; // To the sender:
data["receiver"] = receiverName; SendPrivateChatMessage(data.sender, data.receiver, data.sender, data.message, data.channel, eChatMessageResponseCode::SENT);
data["message"] = message.GetAsString(); // To the receiver:
data["zone_id"]["map_id"] = sender.zoneID.GetMapID(); SendPrivateChatMessage(data.sender, data.receiver, data.receiver, data.message, data.channel, eChatMessageResponseCode::RECEIVEDNEWWHISPER);
data["zone_id"]["instance_id"] = sender.zoneID.GetInstanceID(); // To the WebSocket
data["zone_id"]["clone_id"] = sender.zoneID.GetCloneID(); ChatWeb::SendWSChatMessage(data);
Game::web.SendWSMessage("chat_private", data);
//To the sender:
SendPrivateChatMessage(sender, receiver, sender, message, eChatChannel::PRIVATE_CHAT, eChatMessageResponseCode::SENT);
//To the receiver:
SendPrivateChatMessage(sender, receiver, receiver, message, eChatChannel::PRIVATE_CHAT, eChatMessageResponseCode::RECEIVEDNEWWHISPER);
return; return;
} }
} }
SendPrivateChatMessage(sender, receiver, sender, message, eChatChannel::GENERAL, eChatMessageResponseCode::NOTFRIENDS); SendPrivateChatMessage(data.sender, data.receiver, data.sender, data.message, data.channel, eChatMessageResponseCode::NOTFRIENDS);
} }
void ChatPacketHandler::SendPrivateChatMessage(const PlayerData& sender, const PlayerData& receiver, const PlayerData& routeTo, const LUWString& message, const eChatChannel channel, const eChatMessageResponseCode responseCode) { void ChatPacketHandler::SendPrivateChatMessage(const PlayerData& sender, const PlayerData& receiver, const PlayerData& routeTo, const LUWString& message, const eChatChannel channel, const eChatMessageResponseCode responseCode) {

View File

@ -44,6 +44,14 @@ enum class eChatMessageResponseCode : uint8_t {
RECEIVERFREETRIAL, RECEIVERFREETRIAL,
}; };
struct ChatMessage {
LUWString message;
PlayerData sender;
PlayerData receiver;
eChatChannel channel;
LWOOBJID teamID;
};
namespace ChatPacketHandler { namespace ChatPacketHandler {
void HandleFriendlistRequest(Packet* packet); void HandleFriendlistRequest(Packet* packet);
void HandleFriendRequest(Packet* packet); void HandleFriendRequest(Packet* packet);

View File

@ -56,7 +56,6 @@ void HandleHTTPAnnounceRequest(HTTPReply& reply, std::string body) {
} }
} }
void HandleWSChat(mg_connection* connection, json data) { void HandleWSChat(mg_connection* connection, json data) {
auto check = JSONUtils::CheckRequiredData(data, { "user", "message" }); auto check = JSONUtils::CheckRequiredData(data, { "user", "message" });
if (!check.empty()) { if (!check.empty()) {
@ -65,41 +64,80 @@ void HandleWSChat(mg_connection* connection, json data) {
const auto user = data["user"].get<std::string>(); const auto user = data["user"].get<std::string>();
const auto message = data["message"].get<std::string>(); const auto message = data["message"].get<std::string>();
LOG_DEBUG("EXTERNAL Chat message from %s: %s", user.c_str(), message.c_str()); LOG_DEBUG("EXTERNAL Chat message from %s: %s", user.c_str(), message.c_str());
//TODO: Send chat message to corret world server to broadcast to players // TODO: use chat filter and respond if the message isn't allowed
// TODO: Send chat message to corret world server to broadcast to players
} }
} }
void ChatWeb::RegisterRoutes() { namespace ChatWeb {
// REST API v1 routes void RegisterRoutes() {
std::string v1_route = "/api/v1/"; // REST API v1 routes
Game::web.RegisterHTTPRoute({ std::string v1_route = "/api/v1/";
.path = v1_route + "players", Game::web.RegisterHTTPRoute({
.method = eHTTPMethod::GET, .path = v1_route + "players",
.handle = HandleHTTPPlayersRequest .method = eHTTPMethod::GET,
}); .handle = HandleHTTPPlayersRequest
});
Game::web.RegisterHTTPRoute({ Game::web.RegisterHTTPRoute({
.path = v1_route + "teams", .path = v1_route + "teams",
.method = eHTTPMethod::GET, .method = eHTTPMethod::GET,
.handle = HandleHTTPTeamsRequest .handle = HandleHTTPTeamsRequest
}); });
Game::web.RegisterHTTPRoute({ Game::web.RegisterHTTPRoute({
.path = v1_route + "announce", .path = v1_route + "announce",
.method = eHTTPMethod::POST, .method = eHTTPMethod::POST,
.handle = HandleHTTPAnnounceRequest .handle = HandleHTTPAnnounceRequest
}); });
// WebSocket Events // WebSocket Events
Game::web.RegisterWSEvent({ Game::web.RegisterWSEvent({
.name = "chat", .name = "chat",
.handle = HandleWSChat .handle = HandleWSChat
}); });
// WebSocket subscriptions // WebSocket subscriptions
Game::web.RegisterWSSubscription("chat_local"); Game::web.RegisterWSSubscription("chat_local");
Game::web.RegisterWSSubscription("chat_team"); Game::web.RegisterWSSubscription("chat_team");
Game::web.RegisterWSSubscription("chat_private"); Game::web.RegisterWSSubscription("chat_private");
Game::web.RegisterWSSubscription("player"); Game::web.RegisterWSSubscription("chat");
Game::web.RegisterWSSubscription("team"); Game::web.RegisterWSSubscription("player");
Game::web.RegisterWSSubscription("team");
}
void SendWSPlayerUpdate(const PlayerData& player, eActivityType activityType) {
json data;
data["player_data"] = player;
data["update_type"] = magic_enum::enum_name(activityType);
Game::web.SendWSMessage("player_update", data);
}
void SendWSChatMessage(const ChatMessage& chatMessage) {
json data;
data["message"] = chatMessage.message.GetAsString();
data["sender"] = chatMessage.sender;
data["channel"] = magic_enum::enum_name(chatMessage.channel);
std::string event = "chat"; // generic catch all
switch (chatMessage.channel) {
case eChatChannel::LOCAL:
event = "chat_local";
break;
case eChatChannel::TEAM:
event = "chat_team";
data["teamID"] = chatMessage.teamID;
break;
case eChatChannel::PRIVATE_CHAT:
data["receiver"] = chatMessage.receiver;
event = "chat_private";
break;
default:
LOG_DEBUG("Unhandled Chat channel [%s] in websocket send", StringifiedEnum::ToString(chatMessage.channel).data());
break;
}
Game::web.SendWSMessage(event, data);
}
} }

View File

@ -5,9 +5,14 @@
#include <functional> #include <functional>
#include "Web.h" #include "Web.h"
#include "PlayerContainer.h"
#include "IActivityLog.h"
#include "ChatPacketHandler.h"
namespace ChatWeb { namespace ChatWeb {
void RegisterRoutes(); void RegisterRoutes();
void SendWSPlayerUpdate(const PlayerData& player, eActivityType activityType);
void SendWSChatMessage(const ChatMessage& chatMessage);
}; };

View File

@ -12,8 +12,7 @@
#include "ChatPackets.h" #include "ChatPackets.h"
#include "dConfig.h" #include "dConfig.h"
#include "MessageType/Chat.h" #include "MessageType/Chat.h"
#include "json.hpp" #include "ChatWeb.h"
#include "Web.h"
void PlayerContainer::Initialize() { void PlayerContainer::Initialize() {
m_MaxNumberOfBestFriends = m_MaxNumberOfBestFriends =
@ -60,18 +59,8 @@ void PlayerContainer::InsertPlayer(Packet* packet) {
m_PlayerCount++; m_PlayerCount++;
LOG("Added user: %s (%llu), zone: %i", data.playerName.c_str(), data.playerID, data.zoneID.GetMapID()); LOG("Added user: %s (%llu), zone: %i", data.playerName.c_str(), data.playerID, data.zoneID.GetMapID());
// Send to connected websockets ChatWeb::SendWSPlayerUpdate(data, isLogin ? eActivityType::PlayerLoggedIn : eActivityType::PlayerChangedZone);
nlohmann::json wsdata; Database::Get()->UpdateActivityLog(data.playerID, isLogin ? eActivityType::PlayerLoggedIn : eActivityType::PlayerChangedZone, data.zoneID.GetMapID());
wsdata["action"] = "character_update";
wsdata["type"] = "add";
wsdata["playerName"] = data.playerName;
wsdata["playerID"] = data.playerID;
auto& zoneID = wsdata["zone_id"];
zoneID["map_id"] = data.zoneID.GetMapID();
zoneID["instance_id"] = data.zoneID.GetInstanceID();
zoneID["clone_id"] = data.zoneID.GetCloneID();
Game::web.SendWSMessage("player", wsdata);
Database::Get()->UpdateActivityLog(data.playerID, eActivityType::PlayerLoggedIn, data.zoneID.GetMapID());
m_PlayersToRemove.erase(playerId); m_PlayersToRemove.erase(playerId);
} }
@ -125,12 +114,7 @@ void PlayerContainer::RemovePlayer(const LWOOBJID playerID) {
} }
} }
nlohmann::json wsdata; ChatWeb::SendWSPlayerUpdate(player, eActivityType::PlayerLoggedOut);
wsdata["action"] = "character_update";
wsdata["type"] = "remove";
wsdata["playerName"] = player.playerName;
wsdata["playerID"] = player.playerID;
Game::web.SendWSMessage("player", wsdata);
m_PlayerCount--; m_PlayerCount--;
LOG("Removed user: %llu", playerID); LOG("Removed user: %llu", playerID);

View File

@ -8,6 +8,7 @@
enum class eActivityType : uint32_t { enum class eActivityType : uint32_t {
PlayerLoggedIn, PlayerLoggedIn,
PlayerLoggedOut, PlayerLoggedOut,
PlayerChangedZone,
}; };
class IActivityLog { class IActivityLog {