diff --git a/dChatServer/ChatPacketHandler.cpp b/dChatServer/ChatPacketHandler.cpp index d37777b6..82cea018 100644 --- a/dChatServer/ChatPacketHandler.cpp +++ b/dChatServer/ChatPacketHandler.cpp @@ -18,6 +18,7 @@ #include "eGameMessageType.h" #include "StringifiedEnum.h" #include "eGameMasterLevel.h" +#include "ChatPackets.h" void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) { //Get from the packet which player we want to do something with: @@ -354,6 +355,67 @@ void ChatPacketHandler::HandleGMLevelUpdate(Packet* packet) { inStream.Read(player.gmLevel); } + +void ChatPacketHandler::HandleWho(Packet* packet) { + CINSTREAM_SKIP_HEADER; + FindPlayerRequest request; + request.Deserialize(inStream); + + const auto& sender = Game::playerContainer.GetPlayerData(request.requestor); + if (!sender) return; + + const auto& player = Game::playerContainer.GetPlayerData(request.playerName.GetAsString()); + bool online = player; + + CBITSTREAM; + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET); + bitStream.Write(request.requestor); + + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::WHO_RESPONSE); + bitStream.Write(online); + bitStream.Write(player.zoneID.GetMapID()); + bitStream.Write(player.zoneID.GetInstanceID()); + bitStream.Write(player.zoneID.GetCloneID()); + bitStream.Write(request.playerName); + + SystemAddress sysAddr = sender.sysAddr; + SEND_PACKET; +} + +void ChatPacketHandler::HandleShowAll(Packet* packet) { + CINSTREAM_SKIP_HEADER; + ShowAllRequest request; + request.Deserialize(inStream); + + const auto& sender = Game::playerContainer.GetPlayerData(request.requestor); + if (!sender) return; + + CBITSTREAM; + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET); + bitStream.Write(request.requestor); + + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::SHOW_ALL_RESPONSE); + bitStream.Write(!request.displayZoneData && !request.displayIndividualPlayers); + bitStream.Write(Game::playerContainer.GetPlayerCount()); + bitStream.Write(Game::playerContainer.GetSimCount()); + bitStream.Write(request.displayIndividualPlayers); + bitStream.Write(request.displayZoneData); + if (request.displayZoneData || request.displayIndividualPlayers){ + for (auto& [playerID, playerData ]: Game::playerContainer.GetAllPlayers()){ + if (!playerData) continue; + bitStream.Write(0); // structure packing + if (request.displayIndividualPlayers) bitStream.Write(LUWString(playerData.playerName)); + if (request.displayZoneData) { + bitStream.Write(playerData.zoneID.GetMapID()); + bitStream.Write(playerData.zoneID.GetInstanceID()); + bitStream.Write(playerData.zoneID.GetCloneID()); + } + } + } + SystemAddress sysAddr = sender.sysAddr; + SEND_PACKET; +} + // 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 void ChatPacketHandler::HandleChatMessage(Packet* packet) { diff --git a/dChatServer/ChatPacketHandler.h b/dChatServer/ChatPacketHandler.h index 847fc899..def9c9b9 100644 --- a/dChatServer/ChatPacketHandler.h +++ b/dChatServer/ChatPacketHandler.h @@ -50,6 +50,8 @@ namespace ChatPacketHandler { void HandleFriendResponse(Packet* packet); void HandleRemoveFriend(Packet* packet); void HandleGMLevelUpdate(Packet* packet); + void HandleWho(Packet* packet); + void HandleShowAll(Packet* packet); void HandleChatMessage(Packet* packet); void HandlePrivateChatMessage(Packet* packet); diff --git a/dChatServer/ChatServer.cpp b/dChatServer/ChatServer.cpp index cc938c3c..81b6ddef 100644 --- a/dChatServer/ChatServer.cpp +++ b/dChatServer/ChatServer.cpp @@ -289,7 +289,11 @@ void HandlePacket(Packet* packet) { Game::playerContainer.RemovePlayer(packet); break; case eChatMessageType::WHO: + ChatPacketHandler::HandleWho(packet); + break; case eChatMessageType::SHOW_ALL: + ChatPacketHandler::HandleShowAll(packet); + break; case eChatMessageType::USER_CHANNEL_CHAT_MESSAGE: case eChatMessageType::WORLD_DISCONNECT_REQUEST: case eChatMessageType::WORLD_PROXIMITY_RESPONSE: diff --git a/dChatServer/PlayerContainer.cpp b/dChatServer/PlayerContainer.cpp index 57b3f233..17e2cd1a 100644 --- a/dChatServer/PlayerContainer.cpp +++ b/dChatServer/PlayerContainer.cpp @@ -49,6 +49,7 @@ void PlayerContainer::InsertPlayer(Packet* packet) { data.sysAddr = packet->systemAddress; m_Names[data.playerID] = GeneralUtils::UTF8ToUTF16(data.playerName); + m_PlayerCount++; LOG("Added user: %s (%llu), zone: %i", data.playerName.c_str(), data.playerID, data.zoneID.GetMapID()); @@ -87,6 +88,7 @@ void PlayerContainer::RemovePlayer(Packet* packet) { } } + m_PlayerCount--; LOG("Removed user: %llu", playerID); m_Players.erase(playerID); diff --git a/dChatServer/PlayerContainer.h b/dChatServer/PlayerContainer.h index 3f2d783a..9a17f927 100644 --- a/dChatServer/PlayerContainer.h +++ b/dChatServer/PlayerContainer.h @@ -71,6 +71,9 @@ public: const PlayerData& GetPlayerData(const std::string& playerName); PlayerData& GetPlayerDataMutable(const LWOOBJID& playerID); PlayerData& GetPlayerDataMutable(const std::string& playerName); + uint32_t GetPlayerCount() { return m_PlayerCount; }; + uint32_t GetSimCount() { return m_SimCount; }; + const std::map& GetAllPlayers() { return m_Players; }; TeamData* CreateLocalTeam(std::vector members); TeamData* CreateTeam(LWOOBJID leader, bool local = false); @@ -93,5 +96,7 @@ private: std::unordered_map m_Names; uint32_t m_MaxNumberOfBestFriends = 5; uint32_t m_MaxNumberOfFriends = 50; + uint32_t m_PlayerCount = 0; + uint32_t m_SimCount = 0; }; diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index bd7d5f28..bf7e9eb4 100644 --- a/dGame/dUtilities/SlashCommandHandler.cpp +++ b/dGame/dUtilities/SlashCommandHandler.cpp @@ -878,8 +878,26 @@ void SlashCommandHandler::Startup() { }; RegisterCommand(TitleCommand); + Command ShowAllCommand{ + .help = "Show all online players across World Servers", + .info = "Usage: /showall (displayZoneData: Default 1) (displayIndividualPlayers: Default 1)", + .aliases = { "showall" }, + .handle = GMGreaterThanZeroCommands::ShowAll, + .requiredLevel = eGameMasterLevel::JUNIOR_MODERATOR + }; + RegisterCommand(ShowAllCommand); + + Command FindPlayerCommand{ + .help = "Find the World Server a player is in if they are online", + .info = "Find the World Server a player is in if they are online", + .aliases = { "findplayer" }, + .handle = GMGreaterThanZeroCommands::FindPlayer, + .requiredLevel = eGameMasterLevel::JUNIOR_MODERATOR + }; + RegisterCommand(FindPlayerCommand); // Register GM Zero Commands + Command HelpCommand{ .help = "Display command info", .info = "If a command is given, display detailed info on that command. Otherwise display a list of commands with short desctiptions.", diff --git a/dGame/dUtilities/SlashCommands/DEVGMCommands.h b/dGame/dUtilities/SlashCommands/DEVGMCommands.h index 149348a1..5e78ed80 100644 --- a/dGame/dUtilities/SlashCommands/DEVGMCommands.h +++ b/dGame/dUtilities/SlashCommands/DEVGMCommands.h @@ -70,4 +70,4 @@ namespace DEVGMCommands { void RollLoot(Entity* entity, const SystemAddress& sysAddr, const std::string args); void CastSkill(Entity* entity, const SystemAddress& sysAddr, const std::string args); void DeleteInven(Entity* entity, const SystemAddress& sysAddr, const std::string args); -} \ No newline at end of file +} diff --git a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp index d0aa66aa..ea33aa03 100644 --- a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp +++ b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp @@ -287,4 +287,39 @@ namespace GMGreaterThanZeroCommands { std::string name = entity->GetCharacter()->GetName() + " - " + args; GameMessages::SendSetName(entity->GetObjectID(), GeneralUtils::UTF8ToUTF16(name), UNASSIGNED_SYSTEM_ADDRESS); } + + void ShowAll(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + bool displayZoneData = true; + bool displayIndividualPlayers = true; + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + + if (!splitArgs.empty() && !splitArgs.at(0).empty()) displayZoneData = splitArgs.at(0) == "1"; + if (splitArgs.size() > 1) displayIndividualPlayers = splitArgs.at(1) == "1"; + + ShowAllRequest request { + .requestor = entity->GetObjectID(), + .displayZoneData = displayZoneData, + .displayIndividualPlayers = displayIndividualPlayers + }; + + CBITSTREAM; + request.Serialize(bitStream); + Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false); + } + + void FindPlayer(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + if (args.empty()) { + GameMessages::SendSlashCommandFeedbackText(entity, u"No player Given"); + return; + } + + FindPlayerRequest request { + .requestor = entity->GetObjectID(), + .playerName = LUWString(args) + }; + + CBITSTREAM; + request.Serialize(bitStream); + Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false); + } } diff --git a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.h b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.h index 4b03441f..e8d60383 100644 --- a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.h +++ b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.h @@ -10,4 +10,6 @@ namespace GMGreaterThanZeroCommands { void GmInvis(Entity* entity, const SystemAddress& sysAddr, const std::string args); void SetName(Entity* entity, const SystemAddress& sysAddr, const std::string args); void Title(Entity* entity, const SystemAddress& sysAddr, const std::string args); -} \ No newline at end of file + void ShowAll(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void FindPlayer(Entity* entity, const SystemAddress& sysAddr, const std::string args); +} diff --git a/dNet/ChatPackets.cpp b/dNet/ChatPackets.cpp index d0354659..6c9b36b7 100644 --- a/dNet/ChatPackets.cpp +++ b/dNet/ChatPackets.cpp @@ -12,6 +12,30 @@ #include "eConnectionType.h" #include "eChatMessageType.h" +void ShowAllRequest::Serialize(RakNet::BitStream& bitStream) { + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::SHOW_ALL); + bitStream.Write(this->requestor); + bitStream.Write(this->displayZoneData); + bitStream.Write(this->displayIndividualPlayers); +} + +void ShowAllRequest::Deserialize(RakNet::BitStream& inStream) { + inStream.Read(this->requestor); + inStream.Read(this->displayZoneData); + inStream.Read(this->displayIndividualPlayers); +} + +void FindPlayerRequest::Serialize(RakNet::BitStream& bitStream) { + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WHO); + bitStream.Write(this->requestor); + bitStream.Write(this->playerName); +} + +void FindPlayerRequest::Deserialize(RakNet::BitStream& inStream) { + inStream.Read(this->requestor); + inStream.Read(this->playerName); +} + void ChatPackets::SendChatMessage(const SystemAddress& sysAddr, char chatChannel, const std::string& senderName, LWOOBJID playerObjectID, bool senderMythran, const std::u16string& message) { CBITSTREAM; BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::GENERAL_CHAT_MESSAGE); diff --git a/dNet/ChatPackets.h b/dNet/ChatPackets.h index 8f6de175..c33d00dd 100644 --- a/dNet/ChatPackets.h +++ b/dNet/ChatPackets.h @@ -11,6 +11,21 @@ struct SystemAddress; #include #include "dCommonVars.h" +struct ShowAllRequest{ + LWOOBJID requestor = LWOOBJID_EMPTY; + bool displayZoneData = true; + bool displayIndividualPlayers = true; + void Serialize(RakNet::BitStream& bitStream); + void Deserialize(RakNet::BitStream& inStream); +}; + +struct FindPlayerRequest{ + LWOOBJID requestor = LWOOBJID_EMPTY; + LUWString playerName; + void Serialize(RakNet::BitStream& bitStream); + void Deserialize(RakNet::BitStream& inStream); +}; + namespace ChatPackets { void SendChatMessage(const SystemAddress& sysAddr, char chatChannel, const std::string& senderName, LWOOBJID playerObjectID, bool senderMythran, const std::u16string& message); void SendSystemMessage(const SystemAddress& sysAddr, const std::u16string& message, bool broadcast = false); diff --git a/dNet/WorldPackets.cpp b/dNet/WorldPackets.cpp index 1c2b8dec..44dd687e 100644 --- a/dNet/WorldPackets.cpp +++ b/dNet/WorldPackets.cpp @@ -12,6 +12,15 @@ #include +void HTTPMonitorInfo::Serialize(RakNet::BitStream &bitStream) const { + bitStream.Write(port); + bitStream.Write(openWeb); + bitStream.Write(supportsSum); + bitStream.Write(supportsDetail); + bitStream.Write(supportsWho); + bitStream.Write(supportsObjects); +} + void WorldPackets::SendLoadStaticZone(const SystemAddress& sysAddr, float x, float y, float z, uint32_t checksum, LWOZONEID zone) { RakNet::BitStream bitStream; BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::LOAD_STATIC_ZONE); @@ -160,3 +169,18 @@ void WorldPackets::SendGMLevelChange(const SystemAddress& sysAddr, bool success, SEND_PACKET; } + +void WorldPackets::SendHTTPMonitorInfo(const SystemAddress& sysAddr, const HTTPMonitorInfo& info) { + CBITSTREAM; + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::HTTP_MONITOR_INFO_RESPONSE); + info.Serialize(bitStream); + SEND_PACKET; +} + +void WorldPackets::SendDebugOuput(const SystemAddress& sysAddr, const std::string& data){ + CBITSTREAM; + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::DEBUG_OUTPUT); + bitStream.Write(data.size()); + bitStream.Write(data); + SEND_PACKET; +} diff --git a/dNet/WorldPackets.h b/dNet/WorldPackets.h index 0d5de079..0081623e 100644 --- a/dNet/WorldPackets.h +++ b/dNet/WorldPackets.h @@ -10,6 +10,19 @@ struct SystemAddress; enum class eGameMasterLevel : uint8_t; enum class eCharacterCreationResponse : uint8_t; enum class eRenameResponse : uint8_t; +namespace RakNet { + class BitStream; +}; + +struct HTTPMonitorInfo { + uint16_t port = 80; + bool openWeb = false; + bool supportsSum = false; + bool supportsDetail = false; + bool supportsWho = false; + bool supportsObjects = false; + void Serialize(RakNet::BitStream &bitstream) const; +}; namespace WorldPackets { void SendLoadStaticZone(const SystemAddress& sysAddr, float x, float y, float z, uint32_t checksum, LWOZONEID zone); @@ -21,6 +34,8 @@ namespace WorldPackets { void SendCreateCharacter(const SystemAddress& sysAddr, int64_t reputation, LWOOBJID player, const std::string& xmlData, const std::u16string& username, eGameMasterLevel gm); void SendChatModerationResponse(const SystemAddress& sysAddr, bool requestAccepted, uint32_t requestID, const std::string& receiver, std::vector> unacceptedItems); void SendGMLevelChange(const SystemAddress& sysAddr, bool success, eGameMasterLevel highestLevel, eGameMasterLevel prevLevel, eGameMasterLevel newLevel); + void SendHTTPMonitorInfo(const SystemAddress& sysAddr, const HTTPMonitorInfo& info); + void SendDebugOuput(const SystemAddress& sysAddr, const std::string& data); } #endif // WORLDPACKETS_H