Skip to content

Instantly share code, notes, and snippets.

@Liareth
Created November 24, 2017 00:30
Show Gist options
  • Save Liareth/3fd7d008fff3676d32153a3abd3fab3b to your computer and use it in GitHub Desktop.
Save Liareth/3fd7d008fff3676d32153a3abd3fab3b to your computer and use it in GitHub Desktop.
#include "Chat.hpp"
#include "API/CAppManager.hpp"
#include "API/CExoString.hpp"
#include "API/CNWSClient.hpp"
#include "API/CNWSDungeonMaster.hpp"
#include "API/CNWSMessage.hpp"
#include "API/CNWSPlayer.hpp"
#include "API/Constants.hpp"
#include "API/CServerExoApp.hpp"
#include "API/CVirtualMachine.hpp"
#include "API/Functions.hpp"
#include "API/Globals.hpp"
#include "Services/Config/Config.hpp"
#include "Services/Hooks/Hooks.hpp"
#include "Services/Log/Log.hpp"
#include "ViewPtr.hpp"
using namespace NWNXLib;
static ViewPtr<Chat::Chat> g_plugin;
NWNX_PLUGIN_ENTRY Plugin::Info* PluginInfo()
{
return new Plugin::Info
{
"Chat",
"Allows chat events to be captured, skipped, and manual chat messages to be dispatched.",
"Scholar Midnight",
"liarethnwn@gmail.com",
1,
true
};
}
NWNX_PLUGIN_ENTRY Plugin* PluginLoad(Plugin::CreateParams params)
{
g_plugin = new Chat::Chat(params);
return g_plugin;
}
using namespace NWNXLib::API;
using namespace NWNXLib::Services;
namespace Chat {
Chat::Chat(const Plugin::CreateParams& params)
: Plugin(params), m_skipMessage(false), m_depth(0)
{
m_chatScript = GetServices()->m_config->Get<std::string>("CHAT_SCRIPT", "");
GetServices()->m_events->RegisterEvent("SEND_MESSAGE", std::bind(&Chat::OnSendMessage, this, std::placeholders::_1));
GetServices()->m_events->RegisterEvent("REGISTER_CHAT_SCRIPT", std::bind(&Chat::OnRegisterChatScript, this, std::placeholders::_1));
GetServices()->m_events->RegisterEvent("SKIP_MESSAGE", std::bind(&Chat::OnSkipMessage, this, std::placeholders::_1));
GetServices()->m_events->RegisterEvent("GET_CHANNEL", std::bind(&Chat::OnGetChannel, this, std::placeholders::_1));
GetServices()->m_events->RegisterEvent("GET_MESSAGE", std::bind(&Chat::OnGetMessage, this, std::placeholders::_1));
GetServices()->m_events->RegisterEvent("GET_SENDER", std::bind(&Chat::OnGetSender, this, std::placeholders::_1));
GetServices()->m_events->RegisterEvent("GET_TARGET", std::bind(&Chat::OnGetTarget, this, std::placeholders::_1));
GetServices()->m_hooks->RequestExclusiveHook<Functions::CNWSMessage__SendServerToPlayerChatMessage>(&Chat::SendServerToPlayerChatMessage);
m_hook = GetServices()->m_hooks->FindHookByAddress(Functions::CNWSMessage__SendServerToPlayerChatMessage);
}
Chat::~Chat()
{
}
void Chat::SendServerToPlayerChatMessage(CNWSMessage* thisPtr, ChatChannel channel, Types::ObjectID sender,
CExoString message, Types::PlayerID target, CExoString* tellName)
{
Chat& plugin = *g_plugin;
if (plugin.m_depth == 0 && !plugin.m_chatScript.empty())
{
plugin.m_activeChannel = channel;
plugin.m_activeMessage = message.m_sString ? std::string(message.m_sString) : "";
plugin.m_activeSenderObjectId = sender;
CNWSClient* client = (*Globals::g_pAppManager)->m_pServerExoApp->GetClientObjectByPlayerId(target, 0);
plugin.m_activeTargetObjectId = client ? static_cast<CNWSPlayer*>(client)->m_oidPCObject : Constants::OBJECT_INVALID;
CExoString script = plugin.m_chatScript.c_str();
++plugin.m_depth;
(*Globals::g_pVirtualMachine)->RunScript(&script, sender, 1);
--plugin.m_depth;
}
plugin.GetServices()->m_log->Debug("%s chat message. Channel: '%i', Message: '%s', Sender (ObjID): '0x%08x', Target (PlayerID): '0x%08x'",
plugin.m_skipMessage ? "Skipped" : "Sent", channel, message.m_sString, sender, target);
if (!plugin.m_skipMessage)
{
plugin.m_hook->CallOriginal<void>(thisPtr, channel, sender, message, target, tellName);
}
if (plugin.m_depth == 0)
{
plugin.m_skipMessage = false;
}
}
Events::ArgumentStack Chat::OnSendMessage(Events::ArgumentStack&& args)
{
const auto channel = static_cast<ChatChannel>(Events::ExtractArgument<int32_t>(args));
const auto message = Events::ExtractArgument<std::string>(args);
const auto speaker = Events::ExtractArgument<Types::ObjectID>(args);
const auto target = Events::ExtractArgument<Types::ObjectID>(args);
const bool hasManualPlayerId = target != Constants::OBJECT_INVALID;
const Types::PlayerID playerId = hasManualPlayerId ?
(*Globals::g_pAppManager)->m_pServerExoApp->GetPlayerIDByGameObjectID(target) :
Constants::PLAYERID_ALL_CLIENTS;
Events::ArgumentStack stack;
if (playerId != Constants::PLAYERID_INVALIDID)
{
bool sentMessage = false;
CNWSMessage* messageDispatch = static_cast<CNWSMessage*>((*Globals::g_pAppManager)->m_pServerExoApp->GetNWSMessage());
if (hasManualPlayerId && (channel != ChatChannel::PLAYER_TELL || channel != ChatChannel::DM_TELL))
{
// We have a custom target but we're not in the tell channel. We have to call the functions directly.
switch (channel)
{
case ChatChannel::PLAYER_TALK:
messageDispatch->SendServerToPlayerChat_Talk(playerId, speaker, message.c_str());
sentMessage = true;
break;
case ChatChannel::PLAYER_SHOUT:
messageDispatch->SendServerToPlayerChat_Shout(playerId, speaker, message.c_str());
sentMessage = true;
break;
case ChatChannel::PLAYER_WHISPER:
messageDispatch->SendServerToPlayerChat_Whisper(playerId, speaker, message.c_str());
sentMessage = true;
break;
default:
break;
}
}
if (!sentMessage)
{
messageDispatch->SendServerToPlayerChatMessage(static_cast<uint8_t>(channel), speaker, message.c_str(), playerId, nullptr);
}
Events::InsertArgument(stack, 1);
}
else
{
Events::InsertArgument(stack, 0);
}
return stack;
}
Events::ArgumentStack Chat::OnRegisterChatScript(Events::ArgumentStack&& args)
{
m_chatScript = Events::ExtractArgument<std::string>(args);
return Events::ArgumentStack();
}
Events::ArgumentStack Chat::OnSkipMessage(Events::ArgumentStack&&)
{
m_skipMessage = true;
return Events::ArgumentStack();
}
Events::ArgumentStack Chat::OnGetChannel(Events::ArgumentStack&&)
{
Events::ArgumentStack stack;
Events::InsertArgument(stack, static_cast<int32_t>(m_activeChannel));
return stack;
}
Events::ArgumentStack Chat::OnGetMessage(Events::ArgumentStack&&)
{
Events::ArgumentStack stack;
Events::InsertArgument(stack, m_activeMessage);
return stack;
}
Events::ArgumentStack Chat::OnGetSender(Events::ArgumentStack&&)
{
Events::ArgumentStack stack;
Events::InsertArgument(stack, m_activeSenderObjectId);
return stack;
}
Events::ArgumentStack Chat::OnGetTarget(Events::ArgumentStack&&)
{
Events::ArgumentStack stack;
Events::InsertArgument(stack, m_activeTargetObjectId);
return stack;
}
}
#pragma once
#include "API/Types.hpp"
#include "Common.hpp"
#include "Plugin.hpp"
#include "Services/Events/Events.hpp"
namespace NWNXLib { namespace Hooking { class FunctionHook; } }
namespace Chat {
enum class ChatChannel
{
PLAYER_TALK = 1,
PLAYER_SHOUT = 2,
PLAYER_WHISPER = 3,
PLAYER_TELL = 4,
SERVER_MSG = 5,
PLAYER_PARTY = 6,
PLAYER_DM = 14,
DM_TALK = 17,
DM_SHOUT = 18,
DM_WHISPER = 19,
DM_TELL = 20,
DM_PARTY = 22,
DM_DM = 30
};
class Chat : public NWNXLib::Plugin
{
public:
Chat(const Plugin::CreateParams& params);
virtual ~Chat();
private:
NWNXLib::Hooking::FunctionHook* m_hook;
ChatChannel m_activeChannel;
std::string m_activeMessage;
NWNXLib::API::Types::ObjectID m_activeSenderObjectId;
NWNXLib::API::Types::ObjectID m_activeTargetObjectId;
std::string m_chatScript;
bool m_skipMessage;
uint32_t m_depth;
static void SendServerToPlayerChatMessage(NWNXLib::API::CNWSMessage* thisPtr, ChatChannel channel, NWNXLib::API::Types::ObjectID sender,
NWNXLib::API::CExoString message, NWNXLib::API::Types::ObjectID target, NWNXLib::API::CExoString* tellName);
NWNXLib::Services::Events::ArgumentStack OnSendMessage(NWNXLib::Services::Events::ArgumentStack&& args);
NWNXLib::Services::Events::ArgumentStack OnRegisterChatScript(NWNXLib::Services::Events::ArgumentStack&& args);
NWNXLib::Services::Events::ArgumentStack OnSkipMessage(NWNXLib::Services::Events::ArgumentStack&& args);
NWNXLib::Services::Events::ArgumentStack OnGetChannel(NWNXLib::Services::Events::ArgumentStack&& args);
NWNXLib::Services::Events::ArgumentStack OnGetMessage(NWNXLib::Services::Events::ArgumentStack&& args);
NWNXLib::Services::Events::ArgumentStack OnGetSender(NWNXLib::Services::Events::ArgumentStack&& args);
NWNXLib::Services::Events::ArgumentStack OnGetTarget(NWNXLib::Services::Events::ArgumentStack&& args);
};
}
// These following functions should be called by NWNX plugin developers, who should expose
// their own, more friendly headers.
//
// For example, this following function would wrap a call which passes three parameters,
// receives three back, and constructs a vector frm the result.
//
// vector GetVectorFromCoords(float x, float y, float z)
// {
// string pluginName = "NWNX_TestPlugin";
// string funcName = "GiveMeBackTheSameValues";
//
// // Note the inverse argument push order.
// // C++-side, arguments will be consumed from right to left.
// NWNX_PushArgumentFloat(pluginName, funcName, z);
// NWNX_PushArgumentFloat(pluginName, funcName, y);
// NWNX_PushArgumentFloat(pluginName, funcName, x);
//
// // This calls the function, which will prepare the return values.
// NWNX_CallFunction(pluginName, funcName);
//
// // C++-side pushes the return values in reverse order so we can consume them naturally here.
// float _x = NWNX_GetReturnValueFloat(pluginName, funcName);
// float _y = NWNX_GetReturnValueFloat(pluginName, funcName);
// float _z = NWNX_GetReturnValueFloat(pluginName, funcName);
//
// return vector(_x, _y, _z);
// }
void NWNX_CallFunction(string pluginName, string functionName);
void NWNX_PushArgumentInt(string pluginName, string functionName, int value);
void NWNX_PushArgumentFloat(string pluginName, string functionName, float value);
void NWNX_PushArgumentObject(string pluginName, string functionName, object value);
void NWNX_PushArgumentString(string pluginName, string functionName, string value);
int NWNX_GetReturnValueInt(string pluginName, string functionName);
float NWNX_GetReturnValueFloat(string pluginName, string functionName);
object NWNX_GetReturnValueObject(string pluginName, string functionName);
string NWNX_GetReturnValueString(string pluginName, string functionName);
void NWNX_INTERNAL_CallFunction(string pluginName, string functionName);
void NWNX_INTERNAL_PushArgument(string pluginName, string functionName, string value);
string NWNX_INTERNAL_GetReturnValueString(string pluginName, string functionName, string type);
object NWNX_INTERNAL_GetReturnValueObject(string pluginName, string functionName, string type);
void NWNX_CallFunction(string pluginName, string functionName)
{
NWNX_INTERNAL_CallFunction(pluginName, functionName);
}
void NWNX_PushArgumentInt(string pluginName, string functionName, int value)
{
NWNX_INTERNAL_PushArgument(pluginName, functionName, "0 " + IntToString(value));
}
void NWNX_PushArgumentFloat(string pluginName, string functionName, float value)
{
NWNX_INTERNAL_PushArgument(pluginName, functionName, "1 " + FloatToString(value));
}
void NWNX_PushArgumentObject(string pluginName, string functionName, object value)
{
NWNX_INTERNAL_PushArgument(pluginName, functionName, "2 " + ObjectToString(value));
}
void NWNX_PushArgumentString(string pluginName, string functionName, string value)
{
NWNX_INTERNAL_PushArgument(pluginName, functionName, "3 " + value);
}
int NWNX_GetReturnValueInt(string pluginName, string functionName)
{
return StringToInt(NWNX_INTERNAL_GetReturnValueString(pluginName, functionName, "0 "));
}
float NWNX_GetReturnValueFloat(string pluginName, string functionName)
{
return StringToFloat(NWNX_INTERNAL_GetReturnValueString(pluginName, functionName, "1 "));
}
object NWNX_GetReturnValueObject(string pluginName, string functionName)
{
return NWNX_INTERNAL_GetReturnValueObject(pluginName, functionName, "2 ");
}
string NWNX_GetReturnValueString(string pluginName, string functionName)
{
return NWNX_INTERNAL_GetReturnValueString(pluginName, functionName, "3 ");
}
void NWNX_INTERNAL_CallFunction(string pluginName, string functionName)
{
SetLocalString(GetModule(), "NWNX!CALL_FUNCTION!" + pluginName + "!" + functionName, "1");
}
void NWNX_INTERNAL_PushArgument(string pluginName, string functionName, string value)
{
SetLocalString(GetModule(), "NWNX!PUSH_ARGUMENT!" + pluginName + "!" + functionName, value);
}
string NWNX_INTERNAL_GetReturnValueString(string pluginName, string functionName, string type)
{
return GetLocalString(GetModule(), "NWNX!GET_RETURN_VALUE!" + pluginName + "!" + functionName + "!" + type);
}
object NWNX_INTERNAL_GetReturnValueObject(string pluginName, string functionName, string type)
{
return GetLocalObject(GetModule(), "NWNX!GET_RETURN_VALUE!" + pluginName + "!" + functionName + "!" + type);
}
#include "nwnx"
const int NWNX_CHAT_CHANNEL_PLAYER_TALK = 1;
const int NWNX_CHAT_CHANNEL_PLAYER_SHOUT = 2;
const int NWNX_CHAT_CHANNEL_PLAYER_WHISPER = 3;
const int NWNX_CHAT_CHANNEL_PLAYER_TELL = 4;
const int NWNX_CHAT_CHANNEL_SERVER_MSG = 5;
const int NWNX_CHAT_CHANNEL_PLAYER_PARTY = 6;
const int NWNX_CHAT_CHANNEL_PLAYER_DM = 14;
const int NWNX_CHAT_CHANNEL_DM_TALK = 17;
const int NWNX_CHAT_CHANNEL_DM_SHOUT = 18;
const int NWNX_CHAT_CHANNEL_DM_WHISPER = 19;
const int NWNX_CHAT_CHANNEL_DM_TELL = 20;
const int NWNX_CHAT_CHANNEL_DM_PARTY = 22;
const int NWNX_CHAT_CHANNEL_DM_DM = 30;
// Sends a chat message. Channel is a NWNX_* constant.
// If no target is provided, then it broadcasts to all eligible targets.
// Returns TRUE if successful, FALSE otherwise.
int NWNX_Chat_SendMessage(int channel, string message, object sender = OBJECT_SELF, object target = OBJECT_INVALID);
// Registers the script which receives all chat messages.
// If a script was previously registered, this one will take over.
void NWNX_Chat_RegisterChatScript(string script);
// Skips the message.
// Must be called from an chat or system script handler.
void NWNX_Chat_SkipMessage();
// Gets the channel. Channel is a NWNX_* constant.
// Must be called from an chat or system script handler.
int NWNX_Chat_GetChannel();
// Gets the message.
// Must be called from an chat or system script handler.
string NWNX_Chat_GetMessage();
// Gets the sender.
// Must be called from an chat or system script handler.
object NWNX_Chat_GetSender();
// Gets the target. May be OBJECT_INVALID if no target.
// Must be called from an chat or system script handler.
object NWNX_Chat_GetTarget();
int NWNX_Chat_SendMessage(int channel, string message, object sender = OBJECT_SELF, object target = OBJECT_INVALID)
{
NWNX_PushArgumentObject("NWNX_Chat", "SEND_MESSAGE", target);
NWNX_PushArgumentObject("NWNX_Chat", "SEND_MESSAGE", sender);
NWNX_PushArgumentString("NWNX_Chat", "SEND_MESSAGE", message);
NWNX_PushArgumentInt("NWNX_Chat", "SEND_MESSAGE", channel);
NWNX_CallFunction("NWNX_Chat", "SEND_MESSAGE");
return NWNX_GetReturnValueInt("NWNX_Chat", "SEND_MESSAGE");
}
void NWNX_Chat_RegisterChatScript(string script)
{
NWNX_PushArgumentString("NWNX_Chat", "REGISTER_CHAT_SCRIPT", script);
NWNX_CallFunction("NWNX_Chat", "REGISTER_CHAT_SCRIPT");
}
void NWNX_Chat_SkipMessage()
{
NWNX_CallFunction("NWNX_Chat", "SKIP_MESSAGE");
}
int NWNX_Chat_GetChannel()
{
NWNX_CallFunction("NWNX_Chat", "GET_CHANNEL");
return NWNX_GetReturnValueInt("NWNX_Chat", "GET_CHANNEL");
}
string NWNX_Chat_GetMessage()
{
NWNX_CallFunction("NWNX_Chat", "GET_MESSAGE");
return NWNX_GetReturnValueString("NWNX_Chat", "GET_MESSAGE");
}
object NWNX_Chat_GetSender()
{
NWNX_CallFunction("NWNX_Chat", "GET_SENDER");
return NWNX_GetReturnValueObject("NWNX_Chat", "GET_SENDER");
}
object NWNX_Chat_GetTarget()
{
NWNX_CallFunction("NWNX_Chat", "GET_TARGET");
return NWNX_GetReturnValueObject("NWNX_Chat", "GET_TARGET");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment