New client event dispatchers to commumicate to WoW Client using formatted event structure

This commit is contained in:
2025-01-18 11:27:30 -05:00
parent f42cba3099
commit 2aeafa689a
6 changed files with 283 additions and 17 deletions

View File

@@ -0,0 +1,45 @@
#include "MpClientDispatcher.h"
#include "MpEventProcessor.h"
#include "Player.h"
#include "MpLogger.h"
#include "Chat.h"
#include "WorldPacket.h"
#include <string_view>
#include <string>
/**
* 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<std::string>& 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;
}

View File

@@ -0,0 +1,35 @@
#ifndef MYTHIC_PLUS_CLIENT_DISPATCHER_H
#define MYTHIC_PLUS_CLIENT_DISPATCHER_H
#include "MpEvent.h"
#include "Player.h"
#include <string>
#include <string_view>
#include <vector>
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<std::string>& args);
private:
MpClientDispatcher() {};
~MpClientDispatcher() {};
};
#define sMpClientDispatcher MpClientDispatcher::instance()
#endif

View File

@@ -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<std::string_view, MpEvent> MpEventMap = {{
inline std::unordered_map<std::string_view, MpEvent> MpEventMap = {{
{"UpgradeAdvancement", MpEvent::UpgradeAdvancement},
{"ResetAdvancement", MpEvent::ResetAdvancement},
{"ResetAllAdvancements", MpEvent::ResetAllAdvancements}
{"ResetAllAdvancements", MpEvent::ResetAllAdvancements},
{"GetAdvancementRank", MpEvent::ResetAllAdvancements}
}};
inline std::unordered_map<MpClientEvent, std::string_view> MpClientEventNames = {{
{MpClientEvent::Error, "Error"},
{MpClientEvent::UpgradeAdvancement, "UpgradeAdvancement"},
{MpClientEvent::ResetAdvancement, "ResetAdvancement"},
{MpClientEvent::ResetAllAdvancements, "ResetAllAdvancements"},
{MpClientEvent::GetAdvancementRank, "GetAdvancementRank"}
}};

View File

@@ -0,0 +1,139 @@
#include "MpEvent.h"
#include "MpLogger.h"
#include "AdvancementMgr.h"
#include "MpEventProcessor.h"
#include "MpClientDispatcher.h"
#include "Player.h"
#include <string>
/**
* 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<std::string>& args)
{
// Store the event data to send back to the client for parsing
std::vector<std::string> 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<MpAdvancements>(advancementId), diceLevel, itemEntry1, itemEntry2, itemEntry3)) {
return SendEventError(player, EventName(),MP_EVENT_CODE::INVALID_ARGUMENT, "Failed to upgrade advancement for player " + player->GetName());
}
std::vector<std::string> 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<std::string>& args)
{
// Store the event data to send back to the client for parsing
std::vector<std::string> 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<MpAdvancements>(advancementId));
if(!rank) {
return SendEventError(player, EventName(),MP_EVENT_CODE::INVALID_ARGUMENT, "Failed to get advancement rank for player " + player->GetName());
}
std::vector<std::string> 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<std::string> clientError = { std::to_string(static_cast<int>(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<UpdateAdvancements>();
sMpEventProcessor->RegisterHandler(MpEvent::UpgradeAdvancement, updateAdvancements);
}

View File

@@ -3,23 +3,40 @@
#include "MythicPlus.h"
#include "MpLogger.h"
#include "Player.h"
#include <boost/algorithm/string/replace.hpp>
#include <string_view>
#include <string>
#include <vector>
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<std::string> 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::vector<std::
return false;
}
return _eventHandlers[event]->Execute(args);
return _eventHandlers[event]->Execute(player, args);
}
// Find our eventId using the string name

View File

@@ -2,27 +2,39 @@
#define MYTHIC_PLUS_EVENT_PROCESSOR_H
#include "MpEvent.h"
#include "Player.h"
#include <string>
#include <string_view>
#include <vector>
/**
* 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<std::string>&) = 0;
[[nodiscard]] virtual bool Execute(Player* player, std::vector<std::string>& args) = 0;
[[nodiscard]] virtual const std::string& EventName() const = 0;
};
using EventParseRslt = std::tuple<MpEvent, uint32_t, std::vector<std::string>>;
/**
* 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<std::string> _splitString(const std::string& s, char delimiter = '|');
};