- added experimental (so far) support for SourceMod chat triggers
- added ReplyToCommand() for replying to triggers nicely --HG-- extra : convert_revision : svn%3A39bc706e-5318-0410-9160-8a85361fbb7c/trunk%40899
This commit is contained in:
parent
bd15f93d7d
commit
c843825e71
232
core/ChatTriggers.cpp
Normal file
232
core/ChatTriggers.cpp
Normal file
@ -0,0 +1,232 @@
|
||||
#include "ChatTriggers.h"
|
||||
#include "sm_stringutil.h"
|
||||
#include "TextParsers.h"
|
||||
#include "ConCmdManager.h"
|
||||
|
||||
/* :HACKHACK: We can't SH_DECL here because ConCmdManager.cpp does */
|
||||
extern bool __SourceHook_FHRemoveConCommandDispatch(void *, bool, class fastdelegate::FastDelegate0<void>);
|
||||
extern int __SourceHook_FHAddConCommandDispatch(void *, bool, class fastdelegate::FastDelegate0<void>);
|
||||
|
||||
ChatTriggers g_ChatTriggers;
|
||||
|
||||
ChatTriggers::ChatTriggers() : m_pSayCmd(NULL), m_bWillProcessInPost(false),
|
||||
m_ReplyTo(SM_REPLY_CONSOLE)
|
||||
{
|
||||
m_PubTrigger = sm_strdup("!");
|
||||
m_PrivTrigger = sm_strdup("/");
|
||||
m_PubTriggerSize = 1;
|
||||
m_PrivTriggerSize = 1;
|
||||
}
|
||||
|
||||
void ChatTriggers::OnSourceModGameInitialized()
|
||||
{
|
||||
ConCommandBase *pCmd = icvar->GetCommands();
|
||||
const char *name;
|
||||
while (pCmd)
|
||||
{
|
||||
if (pCmd->IsCommand())
|
||||
{
|
||||
name = pCmd->GetName();
|
||||
if (strcmp(name, "say") == 0)
|
||||
{
|
||||
m_pSayCmd = (ConCommand *)pCmd;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pCmd = const_cast<ConCommandBase *>(pCmd->GetNext());
|
||||
}
|
||||
|
||||
if (m_pSayCmd)
|
||||
{
|
||||
SH_ADD_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayCmd, this, &ChatTriggers::OnSayCommand_Pre, false);
|
||||
SH_ADD_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayCmd, this, &ChatTriggers::OnSayCommand_Post, true);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatTriggers::OnSourceModShutdown()
|
||||
{
|
||||
if (m_pSayCmd)
|
||||
{
|
||||
SH_REMOVE_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayCmd, this, &ChatTriggers::OnSayCommand_Post, true);
|
||||
SH_REMOVE_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayCmd, this, &ChatTriggers::OnSayCommand_Pre, false);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatTriggers::OnSayCommand_Pre()
|
||||
{
|
||||
int client = g_ConCmds.GetCommandClient();
|
||||
|
||||
/* The server console cannot do this */
|
||||
if (client == 0)
|
||||
{
|
||||
RETURN_META(MRES_IGNORED);
|
||||
}
|
||||
|
||||
const char *args = engine->Cmd_Args();
|
||||
|
||||
/* Handle quoted string sets */
|
||||
bool is_quoted = false;
|
||||
if (args[0] == '"')
|
||||
{
|
||||
args++;
|
||||
is_quoted = true;
|
||||
}
|
||||
|
||||
bool is_trigger = false;
|
||||
bool is_silent = false;
|
||||
|
||||
/* Check for either trigger */
|
||||
if (m_PubTriggerSize && strncmp(args, m_PubTrigger, m_PubTriggerSize) == 0)
|
||||
{
|
||||
is_trigger = true;
|
||||
} else if (m_PrivTriggerSize && strncmp(args, m_PrivTrigger, m_PrivTriggerSize) == 0) {
|
||||
is_trigger = true;
|
||||
is_silent = true;
|
||||
}
|
||||
|
||||
if (!is_trigger)
|
||||
{
|
||||
RETURN_META(MRES_IGNORED);
|
||||
}
|
||||
|
||||
/* If we're a public command, process later */
|
||||
if (!is_silent)
|
||||
{
|
||||
/* We have to process this in _post_ instead. Darn. */
|
||||
m_bWillProcessInPost = true;
|
||||
RETURN_META(MRES_IGNORED);
|
||||
}
|
||||
|
||||
/* Otherwise, process now */
|
||||
if (ProcessTrigger(engine->PEntityOfEntIndex(client), &args[m_PrivTriggerSize], is_quoted))
|
||||
{
|
||||
/* If we succeed, block the original say! */
|
||||
RETURN_META(MRES_SUPERCEDE);
|
||||
}
|
||||
|
||||
RETURN_META(MRES_IGNORED);
|
||||
}
|
||||
|
||||
void ChatTriggers::OnSayCommand_Post()
|
||||
{
|
||||
if (m_bWillProcessInPost)
|
||||
{
|
||||
/* Reset this for re-entrancy */
|
||||
m_bWillProcessInPost = false;
|
||||
|
||||
/* Get our arguments */
|
||||
const char *args = engine->Cmd_Args();
|
||||
|
||||
/* Handle quotations */
|
||||
bool is_quoted = false;
|
||||
if (args[0] == '"')
|
||||
{
|
||||
args++;
|
||||
is_quoted = true;
|
||||
}
|
||||
|
||||
int client = g_ConCmds.GetCommandClient();
|
||||
|
||||
ProcessTrigger(engine->PEntityOfEntIndex(client), &args[m_PubTriggerSize], is_quoted);
|
||||
}
|
||||
}
|
||||
|
||||
bool ChatTriggers::ProcessTrigger(edict_t *pEdict, const char *args, bool is_quoted)
|
||||
{
|
||||
/* Eat up whitespace */
|
||||
while (*args != '\0' && IsWhitespace(args))
|
||||
{
|
||||
args++;
|
||||
}
|
||||
|
||||
/* Check if we're still valid */
|
||||
if (*args == '\0')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Extract a command. This is kind of sloppy. */
|
||||
char cmd_buf[64];
|
||||
size_t cmd_len = 0;
|
||||
const char *inptr = args;
|
||||
while (*inptr != '\0'
|
||||
&& !IsWhitespace(inptr)
|
||||
&& cmd_len < sizeof(cmd_buf) - 1)
|
||||
{
|
||||
cmd_buf[cmd_len++] = *inptr++;
|
||||
}
|
||||
cmd_buf[cmd_len] = '\0';
|
||||
|
||||
/* See if we have this registered */
|
||||
bool prepended = false;
|
||||
if (!g_ConCmds.LookForSourceModCommand(cmd_buf))
|
||||
{
|
||||
/* Check if we had an "sm_" prefix */
|
||||
if (strncmp(cmd_buf, "sm_", 3) == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Now, prepend. Don't worry about the buffers. This will
|
||||
* work because the sizes are limited from earlier.
|
||||
*/
|
||||
char new_buf[80];
|
||||
strcpy(new_buf, "sm_");
|
||||
strncopy(&new_buf[3], cmd_buf, sizeof(new_buf)-3);
|
||||
|
||||
/* Recheck */
|
||||
if (!g_ConCmds.LookForSourceModCommand(new_buf))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
prepended = true;
|
||||
}
|
||||
|
||||
/* See if we need to do extra string manipulation */
|
||||
if (is_quoted || prepended)
|
||||
{
|
||||
static char buffer[300];
|
||||
size_t len;
|
||||
|
||||
/* Check if we need to prepend sm_ */
|
||||
if (prepended)
|
||||
{
|
||||
len = UTIL_Format(buffer, sizeof(buffer), "sm_%s", args);
|
||||
} else {
|
||||
len = strncopy(buffer, args, sizeof(buffer));
|
||||
}
|
||||
|
||||
/* Check if we need to strip a quote */
|
||||
if (is_quoted)
|
||||
{
|
||||
if (buffer[len-1] == '"')
|
||||
{
|
||||
buffer[--len] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
args = buffer;
|
||||
}
|
||||
|
||||
/* Finally, execute! */
|
||||
unsigned int old = SetReplyTo(SM_REPLY_CHAT);
|
||||
serverpluginhelpers->ClientCommand(pEdict, args);
|
||||
SetReplyTo(old);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned int ChatTriggers::SetReplyTo(unsigned int reply)
|
||||
{
|
||||
unsigned int old = m_ReplyTo;
|
||||
|
||||
m_ReplyTo = reply;
|
||||
|
||||
return old;
|
||||
}
|
||||
|
||||
unsigned int ChatTriggers::GetReplyTo()
|
||||
{
|
||||
return m_ReplyTo;
|
||||
}
|
37
core/ChatTriggers.h
Normal file
37
core/ChatTriggers.h
Normal file
@ -0,0 +1,37 @@
|
||||
#ifndef _INCLUDE_SOURCEMOD_CHAT_TRIGGERS_H_
|
||||
#define _INCLUDE_SOURCEMOD_CHAT_TRIGGERS_H_
|
||||
|
||||
#include "sm_globals.h"
|
||||
#include "sourcemm_api.h"
|
||||
|
||||
#define SM_REPLY_CONSOLE 0
|
||||
#define SM_REPLY_CHAT 1
|
||||
|
||||
class ChatTriggers : public SMGlobalClass
|
||||
{
|
||||
public:
|
||||
ChatTriggers();
|
||||
public: //SMGlobalClass
|
||||
void OnSourceModGameInitialized();
|
||||
void OnSourceModShutdown();
|
||||
private: //ConCommand
|
||||
void OnSayCommand_Pre();
|
||||
void OnSayCommand_Post();
|
||||
public:
|
||||
unsigned int GetReplyTo();
|
||||
unsigned int SetReplyTo(unsigned int reply);
|
||||
private:
|
||||
bool ProcessTrigger(edict_t *pEdict, const char *args, bool is_quoted);
|
||||
private:
|
||||
ConCommand *m_pSayCmd;
|
||||
char *m_PubTrigger;
|
||||
size_t m_PubTriggerSize;
|
||||
char *m_PrivTrigger;
|
||||
size_t m_PrivTriggerSize;
|
||||
bool m_bWillProcessInPost;
|
||||
unsigned int m_ReplyTo;
|
||||
};
|
||||
|
||||
extern ChatTriggers g_ChatTriggers;
|
||||
|
||||
#endif //_INCLUDE_SOURCEMOD_CHAT_TRIGGERS_H_
|
@ -58,13 +58,17 @@ void ConCmdManager::OnSourceModAllInitialized()
|
||||
SH_ADD_HOOK_MEMFUNC(IServerGameClients, SetCommandClient, serverClients, this, &ConCmdManager::SetCommandClient, false);
|
||||
|
||||
ConCommandBase *pCmd = icvar->GetCommands();
|
||||
const char *name;
|
||||
while (pCmd)
|
||||
{
|
||||
if (pCmd->IsCommand()
|
||||
&& strcmp(pCmd->GetName(), "exec") == 0)
|
||||
if (pCmd->IsCommand())
|
||||
{
|
||||
m_pExecCmd = (ConCommand *)pCmd;
|
||||
break;
|
||||
name = pCmd->GetName();
|
||||
if (strcmp(name, "exec") == 0)
|
||||
{
|
||||
m_pExecCmd = (ConCommand *)pCmd;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pCmd = const_cast<ConCommandBase *>(pCmd->GetNext());
|
||||
}
|
||||
@ -756,6 +760,18 @@ void ConCmdManager::RemoveConCmd(ConCmdInfo *info)
|
||||
delete info;
|
||||
}
|
||||
|
||||
bool ConCmdManager::LookForSourceModCommand(const char *cmd)
|
||||
{
|
||||
ConCmdInfo *pInfo;
|
||||
|
||||
if (!sm_trie_retrieve(m_pCmds, cmd, (void **)&pInfo))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return pInfo->sourceMod && (pInfo->conhooks.size() > 0);
|
||||
}
|
||||
|
||||
ConCmdInfo *ConCmdManager::AddOrFindCommand(const char *name, const char *description, int flags)
|
||||
{
|
||||
ConCmdInfo *pInfo;
|
||||
|
@ -101,6 +101,7 @@ public:
|
||||
ResultType DispatchClientCommand(int client, ResultType type);
|
||||
void UpdateAdminCmdFlags(const char *cmd, OverrideType type, FlagBits bits);
|
||||
void NotifyExecDone(const char *file);
|
||||
bool LookForSourceModCommand(const char *cmd);
|
||||
private:
|
||||
void InternalDispatch();
|
||||
ResultType RunAdminCommand(ConCmdInfo *pInfo, int client, int args);
|
||||
|
@ -189,6 +189,10 @@
|
||||
RelativePath="..\CDataPack.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\ChatTriggers.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\ConCmdManager.cpp"
|
||||
>
|
||||
@ -311,6 +315,10 @@
|
||||
RelativePath="..\CellRecipientFilter.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\ChatTriggers.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\ConCmdManager.h"
|
||||
>
|
||||
|
@ -121,6 +121,13 @@ public:
|
||||
virtual void OnSourceModVSPReceived(IServerPluginCallbacks *iface)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Called once all MM:S plugins are loaded.
|
||||
*/
|
||||
virtual void OnSourceModGameInitialized()
|
||||
{
|
||||
}
|
||||
private:
|
||||
SMGlobalClass *m_pGlobalClassNext;
|
||||
static SMGlobalClass *head;
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "PluginSys.h"
|
||||
#include "sm_stringutil.h"
|
||||
#include "PlayerManager.h"
|
||||
#include "ChatTriggers.h"
|
||||
|
||||
enum ConVarBounds
|
||||
{
|
||||
@ -28,6 +29,8 @@ enum ConVarBounds
|
||||
ConVarBound_Lower
|
||||
};
|
||||
|
||||
#define HUD_PRINTTALK 3
|
||||
|
||||
static cell_t sm_CreateConVar(IPluginContext *pContext, const cell_t *params)
|
||||
{
|
||||
char *name, *defaultVal, *helpText;
|
||||
@ -632,6 +635,77 @@ static cell_t sm_ClientCommand(IPluginContext *pContext, const cell_t *params)
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static cell_t FakeClientCommand(IPluginContext *pContext, const cell_t *params)
|
||||
{
|
||||
CPlayer *pPlayer = g_Players.GetPlayerByIndex(params[1]);
|
||||
|
||||
if (!pPlayer)
|
||||
{
|
||||
return pContext->ThrowNativeError("Player %d is not a valid player", params[1]);
|
||||
}
|
||||
|
||||
if (!pPlayer->IsConnected())
|
||||
{
|
||||
return pContext->ThrowNativeError("Player %d is not connected", params[1]);
|
||||
}
|
||||
|
||||
char buffer[256];
|
||||
g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, 2);
|
||||
|
||||
unsigned int old = g_ChatTriggers.SetReplyTo(SM_REPLY_CONSOLE);
|
||||
serverpluginhelpers->ClientCommand(pPlayer->GetEdict(), buffer);
|
||||
g_ChatTriggers.SetReplyTo(old);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static cell_t ReplyToCommand(IPluginContext *pContext, const cell_t *params)
|
||||
{
|
||||
/* Build the format string */
|
||||
char buffer[1024];
|
||||
size_t len = g_SourceMod.FormatString(buffer, sizeof(buffer)-2, pContext, params, 2);
|
||||
|
||||
/* If we're printing to the server, shortcut out */
|
||||
if (params[1] == 0)
|
||||
{
|
||||
/* Print */
|
||||
buffer[len++] = '\n';
|
||||
buffer[len] = '\0';
|
||||
META_CONPRINT(buffer);
|
||||
return 1;
|
||||
}
|
||||
|
||||
CPlayer *pPlayer = g_Players.GetPlayerByIndex(params[1]);
|
||||
|
||||
if (!pPlayer)
|
||||
{
|
||||
return pContext->ThrowNativeError("Client index %d is invalid", params[1]);
|
||||
}
|
||||
|
||||
if (!pPlayer->IsConnected())
|
||||
{
|
||||
return pContext->ThrowNativeError("Client %d is not connected", params[1]);
|
||||
}
|
||||
|
||||
unsigned int replyto = g_ChatTriggers.GetReplyTo();
|
||||
if (replyto == SM_REPLY_CONSOLE)
|
||||
{
|
||||
buffer[len++] = '\n';
|
||||
buffer[len] = '\0';
|
||||
engine->ClientPrintf(pPlayer->GetEdict(), buffer);
|
||||
} else if (replyto == SM_REPLY_CHAT) {
|
||||
if (len >= 191)
|
||||
{
|
||||
len = 191;
|
||||
}
|
||||
buffer[len] = '\0';
|
||||
g_HL2.TextMsg(params[1], HUD_PRINTTALK, buffer);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
REGISTER_NATIVES(consoleNatives)
|
||||
{
|
||||
{"CreateConVar", sm_CreateConVar},
|
||||
@ -665,5 +739,7 @@ REGISTER_NATIVES(consoleNatives)
|
||||
{"InsertServerCommand", sm_InsertServerCommand},
|
||||
{"ServerExecute", sm_ServerExecute},
|
||||
{"ClientCommand", sm_ClientCommand},
|
||||
{"FakeClientCommand", FakeClientCommand},
|
||||
{"ReplyToCommand", ReplyToCommand},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
@ -233,28 +233,6 @@ static cell_t IsSoundPrecached(IPluginContext *pContext, const cell_t *params)
|
||||
return enginesound->IsSoundPrecached(sample) ? 1 : 0;
|
||||
}
|
||||
|
||||
static cell_t FakeClientCommand(IPluginContext *pContext, const cell_t *params)
|
||||
{
|
||||
CPlayer *pPlayer = g_Players.GetPlayerByIndex(params[1]);
|
||||
|
||||
if (!pPlayer)
|
||||
{
|
||||
return pContext->ThrowNativeError("Player %d is not a valid player", params[1]);
|
||||
}
|
||||
|
||||
if (!pPlayer->IsConnected())
|
||||
{
|
||||
return pContext->ThrowNativeError("Player %d is not connected", params[1]);
|
||||
}
|
||||
|
||||
char buffer[256];
|
||||
g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, 2);
|
||||
|
||||
serverpluginhelpers->ClientCommand(pPlayer->GetEdict(), buffer);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static cell_t smn_CreateDialog(IPluginContext *pContext, const cell_t *params)
|
||||
{
|
||||
KeyValues *pKV;
|
||||
@ -354,7 +332,6 @@ REGISTER_NATIVES(halflifeNatives)
|
||||
{"IsGenericPrecached", IsGenericPrecached},
|
||||
{"PrecacheSound", PrecacheSound},
|
||||
{"IsSoundPrecached", IsSoundPrecached},
|
||||
{"FakeClientCommand", FakeClientCommand},
|
||||
{"CreateDialog", smn_CreateDialog},
|
||||
{"PrintToChat", PrintToChat},
|
||||
{"PrintCenterText", PrintCenterText},
|
||||
|
@ -88,6 +88,7 @@ bool SourceMod_Core::Unpause(char *error, size_t maxlen)
|
||||
|
||||
void SourceMod_Core::AllPluginsLoaded()
|
||||
{
|
||||
g_SourceMod.AllPluginsLoaded();
|
||||
}
|
||||
|
||||
const char *SourceMod_Core::GetAuthor()
|
||||
|
@ -656,6 +656,16 @@ IVirtualMachine *SourceModBase::GetScriptingVM()
|
||||
return g_pVM;
|
||||
}
|
||||
|
||||
void SourceModBase::AllPluginsLoaded()
|
||||
{
|
||||
SMGlobalClass *base = SMGlobalClass::head;
|
||||
while (base)
|
||||
{
|
||||
base->OnSourceModGameInitialized();
|
||||
base = base->m_pGlobalClassNext;
|
||||
}
|
||||
}
|
||||
|
||||
SMGlobalClass *SMGlobalClass::head = NULL;
|
||||
|
||||
SMGlobalClass::SMGlobalClass()
|
||||
|
@ -97,6 +97,7 @@ public: // ISourceMod
|
||||
const char *GetGameFolderName() const;
|
||||
ISourcePawnEngine *GetScriptingEngine();
|
||||
IVirtualMachine *GetScriptingVM();
|
||||
void AllPluginsLoaded();
|
||||
private:
|
||||
/**
|
||||
* @brief Loading plugins
|
||||
|
@ -133,7 +133,7 @@ native PrintToServer(const String:format[], any:...);
|
||||
/**
|
||||
* Sends a message to a client's console.
|
||||
*
|
||||
* @param client Player index.
|
||||
* @param client Client index.
|
||||
* @param format Formatting rules.
|
||||
* @param ... Variable number of format parameters.
|
||||
* @noreturn
|
||||
@ -141,6 +141,21 @@ native PrintToServer(const String:format[], any:...);
|
||||
*/
|
||||
native PrintToConsole(client, const String:format[], any:...);
|
||||
|
||||
/**
|
||||
* Reples to a message in a command.
|
||||
*
|
||||
* A client index of 0 will use PrintToServer().
|
||||
* If the command was from the console, PrintToConsole() is used.
|
||||
* If the command was from chat, PrintToChat() is used.
|
||||
*
|
||||
* @param client Client index, or 0 for server.
|
||||
* @param format Formatting rules.
|
||||
* @param ... Variable number of format parameters.
|
||||
* @noreturn
|
||||
* @error If the client is not connected or invalid.
|
||||
*/
|
||||
native ReplyToCommand(client, const String:fornmat[], any:...);
|
||||
|
||||
/**
|
||||
* Called when a server-only command is invoked.
|
||||
*
|
||||
|
Loading…
Reference in New Issue
Block a user