dvander@893: #include "ChatTriggers.h" dvander@893: #include "sm_stringutil.h" dvander@893: #include "TextParsers.h" dvander@893: #include "ConCmdManager.h" dvander@893: dvander@893: /* :HACKHACK: We can't SH_DECL here because ConCmdManager.cpp does */ dvander@893: extern bool __SourceHook_FHRemoveConCommandDispatch(void *, bool, class fastdelegate::FastDelegate0); dvander@904: ds@908: #if SH_IMPL_VERSION >= 4 dvander@893: extern int __SourceHook_FHAddConCommandDispatch(void *, bool, class fastdelegate::FastDelegate0); dvander@904: #else dvander@904: extern bool __SourceHook_FHAddConCommandDispatch(void *, bool, class fastdelegate::FastDelegate0); dvander@904: #endif dvander@893: dvander@893: ChatTriggers g_ChatTriggers; dvander@893: dvander@893: ChatTriggers::ChatTriggers() : m_pSayCmd(NULL), m_bWillProcessInPost(false), dvander@893: m_ReplyTo(SM_REPLY_CONSOLE) dvander@893: { dvander@893: m_PubTrigger = sm_strdup("!"); dvander@893: m_PrivTrigger = sm_strdup("/"); dvander@893: m_PubTriggerSize = 1; dvander@893: m_PrivTriggerSize = 1; dvander@893: } dvander@893: dvander@897: ChatTriggers::~ChatTriggers() dvander@897: { dvander@897: delete [] m_PubTrigger; dvander@897: m_PubTrigger = NULL; dvander@897: delete [] m_PrivTrigger; dvander@897: m_PrivTrigger = NULL; dvander@897: } dvander@896: dvander@896: ConfigResult ChatTriggers::OnSourceModConfigChanged(const char *key, dvander@896: const char *value, dvander@896: ConfigSource source, dvander@896: char *error, dvander@896: size_t maxlength) dvander@896: { dvander@896: if (strcmp(key, "PublicChatTrigger") == 0) dvander@896: { dvander@896: delete [] m_PubTrigger; dvander@896: m_PubTrigger = sm_strdup(value); dvander@896: m_PubTriggerSize = strlen(m_PubTrigger); dvander@896: return ConfigResult_Accept; dvander@896: } else if (strcmp(key, "SilentChatTrigger") == 0) { dvander@896: delete [] m_PrivTrigger; dvander@896: m_PrivTrigger = sm_strdup(value); dvander@896: m_PrivTriggerSize = strlen(m_PrivTrigger); dvander@896: return ConfigResult_Accept; dvander@896: } dvander@896: dvander@896: return ConfigResult_Ignore; dvander@896: } dvander@896: dvander@893: void ChatTriggers::OnSourceModGameInitialized() dvander@893: { dvander@897: unsigned int total = 2; dvander@893: ConCommandBase *pCmd = icvar->GetCommands(); dvander@893: const char *name; dvander@893: while (pCmd) dvander@893: { dvander@893: if (pCmd->IsCommand()) dvander@893: { dvander@893: name = pCmd->GetName(); dvander@897: if (!m_pSayCmd && strcmp(name, "say") == 0) dvander@893: { dvander@893: m_pSayCmd = (ConCommand *)pCmd; dvander@897: if (--total == 0) dvander@897: { dvander@897: break; dvander@897: } dvander@897: } else if (!m_pSayTeamCmd && strcmp(name, "say_team") == 0) { dvander@897: m_pSayTeamCmd = (ConCommand *)pCmd; dvander@897: if (--total == 0) dvander@897: { dvander@897: break; dvander@897: } dvander@893: } dvander@893: } dvander@893: pCmd = const_cast(pCmd->GetNext()); dvander@893: } dvander@893: dvander@893: if (m_pSayCmd) dvander@893: { dvander@893: SH_ADD_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayCmd, this, &ChatTriggers::OnSayCommand_Pre, false); dvander@893: SH_ADD_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayCmd, this, &ChatTriggers::OnSayCommand_Post, true); dvander@893: } dvander@897: if (m_pSayTeamCmd) dvander@897: { dvander@897: SH_ADD_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayTeamCmd, this, &ChatTriggers::OnSayCommand_Pre, false); dvander@897: SH_ADD_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayTeamCmd, this, &ChatTriggers::OnSayCommand_Post, true); dvander@897: } dvander@893: } dvander@893: dvander@893: void ChatTriggers::OnSourceModShutdown() dvander@893: { dvander@897: if (m_pSayTeamCmd) dvander@897: { dvander@897: SH_REMOVE_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayTeamCmd, this, &ChatTriggers::OnSayCommand_Post, true); dvander@897: SH_REMOVE_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayTeamCmd, this, &ChatTriggers::OnSayCommand_Pre, false); dvander@897: } dvander@893: if (m_pSayCmd) dvander@893: { dvander@893: SH_REMOVE_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayCmd, this, &ChatTriggers::OnSayCommand_Post, true); dvander@893: SH_REMOVE_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayCmd, this, &ChatTriggers::OnSayCommand_Pre, false); dvander@893: } dvander@893: } dvander@893: dvander@893: void ChatTriggers::OnSayCommand_Pre() dvander@893: { dvander@893: int client = g_ConCmds.GetCommandClient(); dvander@893: dvander@893: /* The server console cannot do this */ dvander@893: if (client == 0) dvander@893: { dvander@893: RETURN_META(MRES_IGNORED); dvander@893: } dvander@893: dvander@893: const char *args = engine->Cmd_Args(); dvander@893: dvander@893: /* Handle quoted string sets */ dvander@893: bool is_quoted = false; dvander@893: if (args[0] == '"') dvander@893: { dvander@893: args++; dvander@893: is_quoted = true; dvander@893: } dvander@893: dvander@893: bool is_trigger = false; dvander@893: bool is_silent = false; dvander@893: dvander@893: /* Check for either trigger */ dvander@893: if (m_PubTriggerSize && strncmp(args, m_PubTrigger, m_PubTriggerSize) == 0) dvander@893: { dvander@893: is_trigger = true; dvander@893: } else if (m_PrivTriggerSize && strncmp(args, m_PrivTrigger, m_PrivTriggerSize) == 0) { dvander@893: is_trigger = true; dvander@893: is_silent = true; dvander@893: } dvander@893: dvander@893: if (!is_trigger) dvander@893: { dvander@893: RETURN_META(MRES_IGNORED); dvander@893: } dvander@893: dvander@893: /* If we're a public command, process later */ dvander@893: if (!is_silent) dvander@893: { dvander@893: /* We have to process this in _post_ instead. Darn. */ dvander@893: m_bWillProcessInPost = true; dvander@893: RETURN_META(MRES_IGNORED); dvander@893: } dvander@893: dvander@893: /* Otherwise, process now */ dvander@893: if (ProcessTrigger(engine->PEntityOfEntIndex(client), &args[m_PrivTriggerSize], is_quoted)) dvander@893: { dvander@893: /* If we succeed, block the original say! */ dvander@893: RETURN_META(MRES_SUPERCEDE); dvander@893: } dvander@893: dvander@893: RETURN_META(MRES_IGNORED); dvander@893: } dvander@893: dvander@893: void ChatTriggers::OnSayCommand_Post() dvander@893: { dvander@893: if (m_bWillProcessInPost) dvander@893: { dvander@893: /* Reset this for re-entrancy */ dvander@893: m_bWillProcessInPost = false; dvander@893: dvander@893: /* Get our arguments */ dvander@893: const char *args = engine->Cmd_Args(); dvander@893: dvander@893: /* Handle quotations */ dvander@893: bool is_quoted = false; dvander@893: if (args[0] == '"') dvander@893: { dvander@893: args++; dvander@893: is_quoted = true; dvander@893: } dvander@893: dvander@893: int client = g_ConCmds.GetCommandClient(); dvander@893: dvander@893: ProcessTrigger(engine->PEntityOfEntIndex(client), &args[m_PubTriggerSize], is_quoted); dvander@893: } dvander@893: } dvander@893: dvander@893: bool ChatTriggers::ProcessTrigger(edict_t *pEdict, const char *args, bool is_quoted) dvander@893: { dvander@893: /* Eat up whitespace */ dvander@893: while (*args != '\0' && IsWhitespace(args)) dvander@893: { dvander@893: args++; dvander@893: } dvander@893: dvander@893: /* Check if we're still valid */ dvander@893: if (*args == '\0') dvander@893: { dvander@893: return false; dvander@893: } dvander@893: dvander@893: /* Extract a command. This is kind of sloppy. */ dvander@893: char cmd_buf[64]; dvander@893: size_t cmd_len = 0; dvander@893: const char *inptr = args; dvander@893: while (*inptr != '\0' dvander@893: && !IsWhitespace(inptr) dvander@935: && *inptr != '"' dvander@893: && cmd_len < sizeof(cmd_buf) - 1) dvander@893: { dvander@893: cmd_buf[cmd_len++] = *inptr++; dvander@893: } dvander@893: cmd_buf[cmd_len] = '\0'; dvander@893: dvander@893: /* See if we have this registered */ dvander@893: bool prepended = false; dvander@893: if (!g_ConCmds.LookForSourceModCommand(cmd_buf)) dvander@893: { dvander@893: /* Check if we had an "sm_" prefix */ dvander@893: if (strncmp(cmd_buf, "sm_", 3) == 0) dvander@893: { dvander@893: return false; dvander@893: } dvander@893: dvander@893: /* Now, prepend. Don't worry about the buffers. This will dvander@893: * work because the sizes are limited from earlier. dvander@893: */ dvander@893: char new_buf[80]; dvander@893: strcpy(new_buf, "sm_"); dvander@893: strncopy(&new_buf[3], cmd_buf, sizeof(new_buf)-3); dvander@893: dvander@893: /* Recheck */ dvander@893: if (!g_ConCmds.LookForSourceModCommand(new_buf)) dvander@893: { dvander@893: return false; dvander@893: } dvander@893: dvander@893: prepended = true; dvander@893: } dvander@893: dvander@893: /* See if we need to do extra string manipulation */ dvander@893: if (is_quoted || prepended) dvander@893: { dvander@893: static char buffer[300]; dvander@893: size_t len; dvander@893: dvander@893: /* Check if we need to prepend sm_ */ dvander@893: if (prepended) dvander@893: { dvander@893: len = UTIL_Format(buffer, sizeof(buffer), "sm_%s", args); dvander@893: } else { dvander@893: len = strncopy(buffer, args, sizeof(buffer)); dvander@893: } dvander@893: dvander@893: /* Check if we need to strip a quote */ dvander@893: if (is_quoted) dvander@893: { dvander@893: if (buffer[len-1] == '"') dvander@893: { dvander@893: buffer[--len] = '\0'; dvander@893: } dvander@893: } dvander@893: dvander@893: args = buffer; dvander@893: } dvander@893: dvander@893: /* Finally, execute! */ dvander@893: unsigned int old = SetReplyTo(SM_REPLY_CHAT); dvander@893: serverpluginhelpers->ClientCommand(pEdict, args); dvander@893: SetReplyTo(old); dvander@893: dvander@893: return true; dvander@893: } dvander@893: dvander@893: unsigned int ChatTriggers::SetReplyTo(unsigned int reply) dvander@893: { dvander@893: unsigned int old = m_ReplyTo; dvander@893: dvander@893: m_ReplyTo = reply; dvander@893: dvander@893: return old; dvander@893: } dvander@893: dvander@893: unsigned int ChatTriggers::GetReplyTo() dvander@893: { dvander@893: return m_ReplyTo; dvander@893: }