From 2aeafa689a9ed990e557422db8f8080e46fca431 Mon Sep 17 00:00:00 2001 From: Ben Carter Date: Sat, 18 Jan 2025 11:27:30 -0500 Subject: [PATCH] New client event dispatchers to commumicate to WoW Client using formatted event structure --- src/Event/MpClientDispatch.cpp | 45 +++++++++++ src/Event/MpClientDispatcher.h | 35 +++++++++ src/Event/MpEvent.h | 32 ++++++-- src/Event/MpEventHandlers.cpp | 139 +++++++++++++++++++++++++++++++++ src/Event/MpEventProcessor.cpp | 21 ++++- src/Event/MpEventProcessor.h | 28 ++++--- 6 files changed, 283 insertions(+), 17 deletions(-) create mode 100644 src/Event/MpClientDispatch.cpp create mode 100644 src/Event/MpClientDispatcher.h diff --git a/src/Event/MpClientDispatch.cpp b/src/Event/MpClientDispatch.cpp new file mode 100644 index 0000000..75c5764 --- /dev/null +++ b/src/Event/MpClientDispatch.cpp @@ -0,0 +1,45 @@ +#include "MpClientDispatcher.h" +#include "MpEventProcessor.h" +#include "Player.h" +#include "MpLogger.h" +#include "Chat.h" +#include "WorldPacket.h" + +#include +#include + +/** + * In order to allow communication from client UIs without modifying mod-eluna directly to support + * This sends a packet directly using the AddOn message channel in the background. + */ +bool MpClientDispatcher::Dispatch(MpClientEvent event, Player* player, std::vector& args) +{ + if(!MpClientEventNames.contains(event)) { + MpLogger::warn("No event registered for event: {}", event); + return false; + } + + // Build the message string in same format to send to the client. + std::string_view eventName = MpClientEventNames.at(event); + std::string message = std::to_string(player->GetGUID().GetCounter()) + "|" + eventName.data(); + for(auto& arg : args) { + message += "|" + arg; + } + + std::string prefix = std::string(MP_DATA_CHAT_CHANNEL); + std::string fullmsg = prefix + "\t" + message; + + WorldPacket data(SMSG_MESSAGECHAT, 100); + data << uint8(CHAT_MSG_ADDON); + data << int32(LANG_ADDON); + data << player->GetGUID().GetCounter(); + data << uint32(0); + data << player->GetGUID().GetCounter(); + data << uint32(fullmsg.length() + 1); + data << fullmsg; + data << uint8(0); + player->GetSession()->SendPacket(&data); + return 0; + + return true; +} diff --git a/src/Event/MpClientDispatcher.h b/src/Event/MpClientDispatcher.h new file mode 100644 index 0000000..e88f73e --- /dev/null +++ b/src/Event/MpClientDispatcher.h @@ -0,0 +1,35 @@ +#ifndef MYTHIC_PLUS_CLIENT_DISPATCHER_H +#define MYTHIC_PLUS_CLIENT_DISPATCHER_H + +#include "MpEvent.h" +#include "Player.h" + +#include +#include +#include + +class MpClientDispatcher +{ + +public: + static MpClientDispatcher* instance() { + static MpClientDispatcher instance; + return &instance; + } + + MpClientDispatcher(const MpClientDispatcher&) = delete; + MpClientDispatcher& operator=(const MpClientDispatcher&) = delete; + + // encode and send a message to the client for an event in the map + bool Dispatch(MpClientEvent event, Player* player, std::vector& args); + +private: + MpClientDispatcher() {}; + ~MpClientDispatcher() {}; + +}; + +#define sMpClientDispatcher MpClientDispatcher::instance() + +#endif + diff --git a/src/Event/MpEvent.h b/src/Event/MpEvent.h index 0df4dbb..b1bdbbc 100644 --- a/src/Event/MpEvent.h +++ b/src/Event/MpEvent.h @@ -6,21 +6,41 @@ #ifndef MP_EVENTS_H #define MP_EVENTS_H -// This defines +// This defines list of incoming events typically from the client enum class MpEvent { - None, // Default catch all for no event - Invalid, // Used to capture identify error events + None, // Default catch all for no event + Invalid, // Used to capture identify error events + UpgradeAdvancement, // Upgrades a player advancement 1 level + ResetAdvancement, // Resets a player Advancement back to 0 + ResetAllAdvancements, // Resets all player advancements + GetAdvancementRank // Get the details about the rank of a specific advancement (cost, bonus, etc) +}; + +// Client events that are communicated to the over an addon message to the player client +enum class MpClientEvent +{ + Error, UpgradeAdvancement, ResetAdvancement, - ResetAllAdvancements + ResetAllAdvancements, + GetAdvancementRank }; // Mapping of Event Strings to EventNames for Event Callbacks from client -std::unordered_map MpEventMap = {{ +inline std::unordered_map MpEventMap = {{ {"UpgradeAdvancement", MpEvent::UpgradeAdvancement}, {"ResetAdvancement", MpEvent::ResetAdvancement}, - {"ResetAllAdvancements", MpEvent::ResetAllAdvancements} + {"ResetAllAdvancements", MpEvent::ResetAllAdvancements}, + {"GetAdvancementRank", MpEvent::ResetAllAdvancements} +}}; + +inline std::unordered_map MpClientEventNames = {{ + {MpClientEvent::Error, "Error"}, + {MpClientEvent::UpgradeAdvancement, "UpgradeAdvancement"}, + {MpClientEvent::ResetAdvancement, "ResetAdvancement"}, + {MpClientEvent::ResetAllAdvancements, "ResetAllAdvancements"}, + {MpClientEvent::GetAdvancementRank, "GetAdvancementRank"} }}; diff --git a/src/Event/MpEventHandlers.cpp b/src/Event/MpEventHandlers.cpp index e69de29..b6ef265 100644 --- a/src/Event/MpEventHandlers.cpp +++ b/src/Event/MpEventHandlers.cpp @@ -0,0 +1,139 @@ +#include "MpEvent.h" +#include "MpLogger.h" +#include "AdvancementMgr.h" +#include "MpEventProcessor.h" +#include "MpClientDispatcher.h" +#include "Player.h" + +#include + +/** + * All handlers for passed between client and server handlers are managed + * here. + */ + +/** + * Handles Updates to a players advancement system + * Event format + * p|playerGuid|UpgradeAdvancement|advancementId|rank|diceSpent|bonus + */ +enum class MP_EVENT_CODE +{ + SUCCESS = 0, + INVALID_EVENT = 100, + INVALID_ARGUMENT_SIZE = 201, + INVALID_ARGUMENT = 202, + INVALID_ARGUMENT_TYPE = 203, + +}; + +class UpdateAdvancements : public MpEventInterface +{ + public: + const std::string& EventName() const override + { + return "UpgradeAdvancement"; + } + + bool Execute(Player* player, std::vector& args) + { + // Store the event data to send back to the client for parsing + std::vector eventData; + + MpLogger::info("(EventProcessor) Executing {}}", EventName()); + for(auto& arg : args) { + MpLogger::info("{} Arg: {}", EventName(), arg); + } + + // Validate the message is int he right format + if(args.size() != 5) { + return SendEventError(player, EventName(),MP_EVENT_CODE::INVALID_ARGUMENT_SIZE, "Invalid number of arguments expected 5, found " + std::to_string(args.size())); + } + + uint32 advancementId = std::stoi(args[0]); + if(advancementId >= MpAdvancements::MP_ADV_MAX) { + return SendEventError(player, EventName(),MP_EVENT_CODE::INVALID_ARGUMENT, "Invalid advancement id " + args[0] + " max is " + std::to_string(MpAdvancements::MP_ADV_MAX)); + } + + uint32 diceLevel = std::stoi(args[1]); + if(diceLevel < 1 || diceLevel > 3) { + return SendEventError(player, EventName(),MP_EVENT_CODE::INVALID_ARGUMENT, "Invalid dice level " + args[1] + " valid values are 1,2,3"); + } + + uint32 itemEntry1 = std::stoi(args[2]); + if(itemEntry1 == 0) { + return SendEventError(player, EventName(),MP_EVENT_CODE::INVALID_ARGUMENT, "Invalid item entry1 can not be empty " + args[2]); + } + + uint32 itemEntry2 = std::stoi(args[3]); + uint32 itemEntry3 = std::stoi(args[4]); + + // Upgrade the advancement for the player! + if(! sAdvancementMgr->UpgradeAdvancement(player, static_cast(advancementId), diceLevel, itemEntry1, itemEntry2, itemEntry3)) { + return SendEventError(player, EventName(),MP_EVENT_CODE::INVALID_ARGUMENT, "Failed to upgrade advancement for player " + player->GetName()); + } + std::vector eventData = {"0", "success"}; + + // Send response back to the client + sMpClientDispatcher->Dispatch(MpClientEvent::UpgradeAdvancement, player, eventData); + + return true; + } +}; + +class GetAdvancementRank : public MpEventInterface +{ + public: + const std::string& EventName() const override + { + return "GetAdvancementRank"; + } + + bool Execute(Player* player, std::vector& args) + { + // Store the event data to send back to the client for parsing + std::vector eventData; + + MpLogger::info("(EventProcessor) Executing {}}", EventName()); + for(auto& arg : args) { + MpLogger::info("{} Arg: {}", EventName(), arg); + } + + // Validate the message is int he right format + if(args.size() != 1) { + return SendEventError(player, EventName(),MP_EVENT_CODE::INVALID_ARGUMENT_SIZE, "Invalid number of arguments expected 1, found " + std::to_string(args.size())); + } + + uint32 advancementId = std::stoi(args[0]); + if(advancementId >= MpAdvancements::MP_ADV_MAX) { + return SendEventError(player, EventName(),MP_EVENT_CODE::INVALID_ARGUMENT, "Invalid advancement id " + args[0] + " max is " + std::to_string(MpAdvancements::MP_ADV_MAX)); + } + + MpAdvancementRank* rank = sAdvancementMgr->GetAdvancementRank(1, static_cast(advancementId)); + if(!rank) { + return SendEventError(player, EventName(),MP_EVENT_CODE::INVALID_ARGUMENT, "Failed to get advancement rank for player " + player->GetName()); + } + + std::vector eventData = {std::to_string(rank->rank), std::to_string(rank->advancementId)}; + + // Send response back to the client + sMpClientDispatcher->Dispatch(MpClientEvent::GetAdvancementRank, player, eventData); + + return true; + } +}; + +// Send an error event to the client +bool SendEventError(Player* player, const std::string& method, MP_EVENT_CODE code, std::string message) +{ + std::vector clientError = { std::to_string(static_cast(code)), message }; + MpLogger::error("Event Processor) Sending client error: {}", clientError); + sMpClientDispatcher->Dispatch(MpClientEvent::Error, player, clientError); + return false; +} + +void MP_Register_EventHandlers() +{ + auto updateAdvancements = std::make_shared(); + sMpEventProcessor->RegisterHandler(MpEvent::UpgradeAdvancement, updateAdvancements); +} diff --git a/src/Event/MpEventProcessor.cpp b/src/Event/MpEventProcessor.cpp index 6146598..213a170 100644 --- a/src/Event/MpEventProcessor.cpp +++ b/src/Event/MpEventProcessor.cpp @@ -3,23 +3,40 @@ #include "MythicPlus.h" #include "MpLogger.h" #include "Player.h" +#include #include #include #include -bool MpEventProcessor::ProcessMessage(Player* player, const std::string& message) { +bool MpEventProcessor::ProcessMessage(Player* player, const std::string& msg) { if(!player) { MpLogger::error("Null player passed to processMessage"); return false; } + // check prefix of message channel is formatted correctly + if(! msg.starts_with(MP_DATA_CHAT_CHANNEL)) { + MpLogger::error("Invalid message format received from player {} message: {}", player->GetName(), msg); + return false; + } + + std::string message = msg; + + // shift the message identifier off the front including first '|' character + message.erase(0, MP_DATA_CHAT_CHANNEL.size()+1); + + // clean up the message before passing it to the parser + boost::replace_all(message, "||", "|"); + EventParseRslt result = _parsePlayerMessage(player, message); MpEvent event = std::get<0>(result); uint32 guid = std::get<1>(result); std::vector args = std::get<2>(result); + MpLogger::info("MpEvent Processor - event: {} guid: {} args: {}", event, guid, args.size()); + // If th message was not able to be parsed it is a failure if(event == MpEvent::Invalid) { MpLogger::warn("Invalid event, could not be parsed for player {} message: {}", player->GetName(), message); @@ -52,7 +69,7 @@ bool MpEventProcessor::Dispatch(MpEvent event, Player* player, std::vectorExecute(args); + return _eventHandlers[event]->Execute(player, args); } // Find our eventId using the string name diff --git a/src/Event/MpEventProcessor.h b/src/Event/MpEventProcessor.h index 7119189..99c2dca 100644 --- a/src/Event/MpEventProcessor.h +++ b/src/Event/MpEventProcessor.h @@ -2,27 +2,39 @@ #define MYTHIC_PLUS_EVENT_PROCESSOR_H #include "MpEvent.h" +#include "Player.h" #include #include #include + +/** + * In order to allow communication from client UIs without modifying mod-eluna directly to support + * this mods functionality the following custom chat channel below is for all MP UI Client interactions. + * + * p|playerGuid|action|input1|input2|input3... + */ +inline const std::string_view MP_DATA_CHAT_CHANNEL = "MPUi"; + /** * Interface for all Mythic+ Event communication between client and server */ class MpEventInterface { public: - MpEventInterface() {}; + MpEventInterface() = default; virtual ~MpEventInterface() = default; - [[nodiscard]] virtual bool Execute(std::vector&) = 0; + [[nodiscard]] virtual bool Execute(Player* player, std::vector& args) = 0; + + [[nodiscard]] virtual const std::string& EventName() const = 0; }; using EventParseRslt = std::tuple>; /** * This class is responsible for processing events that are send from the client. Events - * are processed FIFO and executed immidiately. This is specifically to allow AddOn or AIO + * are processed FIFO and executed. This is specifically to allow AddOn or AIO * communication to this module without requiring additional changes to mod-eluna or the core. * * Message Body Format @@ -39,14 +51,11 @@ public: return &instance; } - MpEventProcessor() = default; - ~MpEventProcessor() = default; - - // /* TODO */ Add generic handler later for other events if needed. - // bool ProcessMessage(std::string_view message); + MpEventProcessor(const MpEventProcessor&) = delete; + MpEventProcessor& operator=(const MpEventProcessor&) = delete; // Process a message from a specific player - bool ProcessMessage(Player* player, const std::string& message); + bool ProcessMessage(Player* player, const std::string& msg); // Registers a handler for a valid MpEvent specified in the MpEvent enum // In this design Event:Handler is 1:1 @@ -68,6 +77,7 @@ private: // Parse a message from the player EventParseRslt _parsePlayerMessage(Player* player, const std::string& msg); + // Helper to break up a string into pieces used in parsing the message std::vector _splitString(const std::string& s, char delimiter = '|'); };