From 02e8977ea003413553cbe20ba4cd6498aa3912a6 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Sun, 21 Oct 2007 20:06:30 +0000 Subject: [PATCH] - added ShowActivity2() which has a more convenient display algorithm - added commandfilters.inc and ProcessTargetString(), an extensible API for processing a command target and getting back formatted replies + player lists. This makes SearchForClients() look paltry, and base plugins will slowly be moved to support the new functionality - removed IsPlayerAlive() from SDKTools, added it to Core - fixed a small bug in re-entrant translations - core.games.txt is now automatically added to all plugins --HG-- extra : convert_revision : svn%3A39bc706e-5318-0410-9160-8a85361fbb7c/trunk%401627 --- core/PlayerManager.cpp | 320 +++++++++++++++++++++++++ core/PlayerManager.h | 10 + core/Translator.cpp | 6 +- core/Translator.h | 1 + core/sm_stringutil.cpp | 18 +- core/smn_halflife.cpp | 29 +++ core/smn_player.cpp | 115 ++++++++- core/systems/PluginSys.cpp | 4 + plugins/include/clients.inc | 11 + plugins/include/commandfilters.inc | 130 ++++++++++ plugins/include/console.inc | 20 +- plugins/include/sdktools_functions.inc | 9 - plugins/include/sourcemod.inc | 1 + translations/core.phrases.txt | 32 +++ 14 files changed, 676 insertions(+), 30 deletions(-) create mode 100644 plugins/include/commandfilters.inc diff --git a/core/PlayerManager.cpp b/core/PlayerManager.cpp index ae749461..5d701c5c 100644 --- a/core/PlayerManager.cpp +++ b/core/PlayerManager.cpp @@ -45,12 +45,15 @@ #include "HalfLife2.h" #include #include +#include "GameConfigs.h" PlayerManager g_Players; bool g_OnMapStarted = false; IForward *PreAdminCheck = NULL; IForward *PostAdminCheck = NULL; const unsigned int *g_NumPlayersToAuth = NULL; +int lifestate_offset = -1; +List target_processors; SH_DECL_HOOK5(IServerGameClients, ClientConnect, SH_NOATTRIB, 0, bool, edict_t *, const char *, const char *, char *, int); SH_DECL_HOOK2_void(IServerGameClients, ClientPutInServer, SH_NOATTRIB, 0, edict_t *, const char *); @@ -814,6 +817,281 @@ unsigned int PlayerManager::SetReplyTo(unsigned int reply) return g_ChatTriggers.SetReplyTo(reply); } +int PlayerManager::FilterCommandTarget(IGamePlayer *pAdmin, IGamePlayer *pTarget, int flags) +{ + return InternalFilterCommandTarget((CPlayer *)pAdmin, (CPlayer *)pTarget, flags); +} + +void PlayerManager::RegisterCommandTargetProcessor(ICommandTargetProcessor *pHandler) +{ + target_processors.push_back(pHandler); +} + +void PlayerManager::UnregisterCommandTargetProcessor(ICommandTargetProcessor *pHandler) +{ + target_processors.remove(pHandler); +} + +int PlayerManager::InternalFilterCommandTarget(CPlayer *pAdmin, CPlayer *pTarget, int flags) +{ + if ((flags & COMMAND_FILTER_CONNECTED) == COMMAND_FILTER_CONNECTED + && !pTarget->IsConnected()) + { + return COMMAND_TARGET_NONE; + } + else if ((flags & COMMAND_FILTER_CONNECTED) != COMMAND_FILTER_CONNECTED && + !pTarget->IsInGame()) + { + return COMMAND_TARGET_NOT_IN_GAME; + } + + if ((flags & COMMAND_FILTER_NO_BOTS) == COMMAND_FILTER_NO_BOTS + && pTarget->IsFakeClient()) + { + return COMMAND_TARGET_NOT_HUMAN; + } + + if (pAdmin != NULL) + { + if ((flags & COMMAND_FILTER_NO_IMMUNITY) != COMMAND_FILTER_NO_IMMUNITY + && !g_Admins.CanAdminTarget(pAdmin->GetAdminId(), pTarget->GetAdminId())) + { + return COMMAND_TARGET_IMMUNE; + } + } + + if ((flags & COMMAND_FILTER_ALIVE) == COMMAND_FILTER_ALIVE + && pTarget->GetLifeState() != PLAYER_LIFE_ALIVE) + { + return COMMAND_TARGET_NOT_ALIVE; + } + + if ((flags & COMMAND_FILTER_DEAD) == COMMAND_FILTER_DEAD + && pTarget->GetLifeState() != PLAYER_LIFE_DEAD) + { + return COMMAND_TARGET_NOT_DEAD; + } + + return COMMAND_TARGET_VALID; +} + +void PlayerManager::ProcessCommandTarget(cmd_target_info_t *info) +{ + CPlayer *pTarget, *pAdmin; + int max_clients, total = 0; + + max_clients = GetMaxClients(); + + if (info->max_targets < 1) + { + info->reason = COMMAND_TARGET_NONE; + info->num_targets = 0; + } + + if (info->admin == 0) + { + pAdmin = NULL; + } + else + { + pAdmin = GetPlayerByIndex(info->admin); + } + + if (info->pattern[0] == '#') + { + int userid = atoi(&info->pattern[1]); + int client = GetClientOfUserId(userid); + + /* See if a valid userid matched */ + if (client > 0) + { + IGamePlayer *pTarget = GetPlayerByIndex(client); + if (pTarget != NULL) + { + if ((info->reason = FilterCommandTarget(pAdmin, pTarget, info->flags)) == COMMAND_TARGET_VALID) + { + info->targets[0] = client; + info->num_targets = 1; + strncopy(info->target_name, pTarget->GetName(), info->target_name_maxlength); + info->target_name_style = COMMAND_TARGETNAME_RAW; + } + else + { + info->num_targets = 0; + } + return; + } + } + + /* See if an exact name matches */ + for (int i = 1; i <= max_clients; i++) + { + if ((pTarget = GetPlayerByIndex(i)) == NULL) + { + continue; + } + if (!pTarget->IsConnected()) + { + continue; + } + if (strcmp(pTarget->GetName(), &info->pattern[1]) == 0) + { + if ((info->reason = FilterCommandTarget(pAdmin, pTarget, info->flags)) + == COMMAND_TARGET_VALID) + { + info->targets[0] = i; + info->num_targets = 1; + strncopy(info->target_name, pTarget->GetName(), info->target_name_maxlength); + info->target_name_style = COMMAND_TARGETNAME_RAW; + } + else + { + info->num_targets = 0; + } + return; + } + } + } + + if ((info->flags & COMMAND_FILTER_NO_MULTI) != COMMAND_FILTER_NO_MULTI) + { + bool is_multi = false; + bool bots_only = false; + + if (strcmp(info->pattern, "@all") == 0) + { + is_multi = true; + strncopy(info->target_name, "all players", info->target_name_maxlength); + info->target_name_style = COMMAND_TARGETNAME_ML; + } + else if (strcmp(info->pattern, "@dead") == 0) + { + is_multi = true; + if ((info->flags & COMMAND_FILTER_ALIVE) == COMMAND_FILTER_ALIVE) + { + info->num_targets = 0; + info->reason = COMMAND_TARGET_NOT_ALIVE; + return; + } + info->flags |= COMMAND_FILTER_DEAD; + strncopy(info->target_name, "all dead players", info->target_name_maxlength); + info->target_name_style = COMMAND_TARGETNAME_ML; + } + else if (strcmp(info->pattern, "@alive") == 0) + { + is_multi = true; + if ((info->flags & COMMAND_FILTER_DEAD) == COMMAND_FILTER_DEAD) + { + info->num_targets = 0; + info->reason = COMMAND_TARGET_NOT_DEAD; + return; + } + strncopy(info->target_name, "all alive players", info->target_name_maxlength); + info->target_name_style = COMMAND_TARGETNAME_ML; + info->flags |= COMMAND_FILTER_ALIVE; + } + else if (strcmp(info->pattern, "@bots") == 0) + { + is_multi = true; + if ((info->flags & COMMAND_FILTER_NO_BOTS) == COMMAND_FILTER_NO_BOTS) + { + info->num_targets = 0; + info->reason = COMMAND_FILTER_NO_BOTS; + return; + } + strncopy(info->target_name, "all bots", info->target_name_maxlength); + info->target_name_style = COMMAND_TARGETNAME_ML; + bots_only = true; + } + else if (strcmp(info->pattern, "@humans") == 0) + { + is_multi = true; + strncopy(info->target_name, "all humans", info->target_name_maxlength); + info->target_name_style = COMMAND_TARGETNAME_ML; + info->flags |= COMMAND_FILTER_NO_BOTS; + } + + if (is_multi) + { + for (int i = 1; i <= max_clients && total < info->max_targets; i++) + { + if ((pTarget = GetPlayerByIndex(i)) == NULL) + { + continue; + } + if (FilterCommandTarget(pAdmin, pTarget, info->flags) > 0) + { + if (!bots_only || pTarget->IsFakeClient()) + { + info->targets[total++] = i; + } + } + } + + info->num_targets = total; + info->reason = (info->num_targets) ? COMMAND_TARGET_VALID : COMMAND_TARGET_EMPTY_FILTER; + return; + } + } + + List::iterator iter; + for (iter = target_processors.begin(); iter != target_processors.end(); iter++) + { + ICommandTargetProcessor *pProcessor = (*iter); + if (pProcessor->ProcessCommandTarget(info)) + { + return; + } + } + + /* Check partial names */ + int found_client = 0; + CPlayer *pFoundClient = NULL; + for (int i = 1; i <= max_clients; i++) + { + if ((pTarget = GetPlayerByIndex(i)) == NULL) + { + continue; + } + + if (stristr(pTarget->GetName(), info->pattern) != NULL) + { + if (found_client) + { + info->num_targets = 0; + info->reason = COMMAND_TARGET_AMBIGUOUS; + return; + } + else + { + found_client = i; + pFoundClient = pTarget; + } + } + } + + if (found_client) + { + if ((info->reason = FilterCommandTarget(pAdmin, pFoundClient, info->flags)) + == COMMAND_TARGET_VALID) + { + info->targets[0] = found_client; + info->num_targets = 1; + strncopy(info->target_name, pFoundClient->GetName(), info->target_name_maxlength); + info->target_name_style = COMMAND_TARGETNAME_RAW; + } + else + { + info->num_targets = 0; + } + } + else + { + info->num_targets = 0; + info->reason = COMMAND_TARGET_NONE; + } +} + /******************* *** PLAYER CODE *** *******************/ @@ -1118,3 +1396,45 @@ void CPlayer::MarkAsBeingKicked() { m_bIsInKickQueue = true; } + +int CPlayer::GetLifeState() +{ + if (lifestate_offset == -1) + { + if (!g_pGameConf->GetOffset("m_lifeState", &lifestate_offset)) + { + lifestate_offset = -2; + } + } + + if (lifestate_offset < 0) + { + IPlayerInfo *info = GetPlayerInfo(); + if (info == NULL) + { + return PLAYER_LIFE_UNKNOWN; + } + return info->IsDead() ? PLAYER_LIFE_DEAD : PLAYER_LIFE_ALIVE; + } + + if (m_pEdict == NULL) + { + return PLAYER_LIFE_UNKNOWN; + } + + CBaseEntity *pEntity; + IServerUnknown *pUnknown = m_pEdict->GetUnknown(); + if (pUnknown == NULL || (pEntity = pUnknown->GetBaseEntity()) == NULL) + { + return PLAYER_LIFE_UNKNOWN; + } + + if (*((uint8_t *)pEntity + lifestate_offset) == LIFE_ALIVE) + { + return PLAYER_LIFE_ALIVE; + } + else + { + return PLAYER_LIFE_DEAD; + } +} diff --git a/core/PlayerManager.h b/core/PlayerManager.h index d44b5c9c..4e1a65ed 100644 --- a/core/PlayerManager.h +++ b/core/PlayerManager.h @@ -44,6 +44,10 @@ using namespace SourceHook; +#define PLAYER_LIFE_UNKNOWN 0 +#define PLAYER_LIFE_ALIVE 1 +#define PLAYER_LIFE_DEAD 2 + class CPlayer : public IGamePlayer { friend class PlayerManager; @@ -70,6 +74,7 @@ public: void DoBasicAdminChecks(); bool IsInKickQueue(); void MarkAsBeingKicked(); + int GetLifeState(); private: void Initialize(const char *name, const char *ip, edict_t *pEntity); void Connect(); @@ -138,6 +143,11 @@ public: //IPlayerManager int GetNumPlayers(); int GetClientOfUserId(int userid); bool IsServerActivated(); + int FilterCommandTarget(IGamePlayer *pAdmin, IGamePlayer *pTarget, int flags); + int InternalFilterCommandTarget(CPlayer *pAdmin, CPlayer *pTarget, int flags); + void RegisterCommandTargetProcessor(ICommandTargetProcessor *pHandler); + void UnregisterCommandTargetProcessor(ICommandTargetProcessor *pHandler); + void ProcessCommandTarget(cmd_target_info_t *info); public: inline int MaxClients() { diff --git a/core/Translator.cpp b/core/Translator.cpp index b2ea9659..e50345c5 100644 --- a/core/Translator.cpp +++ b/core/Translator.cpp @@ -41,6 +41,7 @@ Translator g_Translator; CPhraseFile *g_pCorePhrases = NULL; +unsigned int g_pCorePhraseID = 0; struct trans_t { @@ -712,10 +713,13 @@ void Translator::OnSourceModAllInitialized() if (g_LibSys.PathExists(path)) { id = FindOrAddPhraseFile("core.cfg"); - } else { + } + else + { id = FindOrAddPhraseFile("core.phrases.txt"); } + g_pCorePhraseID = id; g_pCorePhrases = GetFileByIndex(id); } diff --git a/core/Translator.h b/core/Translator.h index f04ba473..755cbb36 100644 --- a/core/Translator.h +++ b/core/Translator.h @@ -164,6 +164,7 @@ private: }; extern CPhraseFile *g_pCorePhrases; +extern unsigned int g_pCorePhraseID; extern Translator g_Translator; #endif //_INCLUDE_SOURCEMOD_TRANSLATOR_H_ diff --git a/core/sm_stringutil.cpp b/core/sm_stringutil.cpp index cdfbae92..35e36b0b 100644 --- a/core/sm_stringutil.cpp +++ b/core/sm_stringutil.cpp @@ -154,6 +154,8 @@ try_serverlang: if (max_params) { + cell_t new_params[MAX_TRANSLATE_PARAMS]; + /* Check if we're going to over the limit */ if ((*arg) + (max_params - 1) > (size_t)params[0]) { @@ -164,12 +166,18 @@ try_serverlang: goto error_out; } - /* Re-order the parameters that this translation requires */ - ReorderTranslationParams(&pTrans, const_cast(¶ms[*arg])); - } + /* If we need to re-order the parameters, do so with a temporary array. + * Otherwise, we could run into trouble with continual formats, a la ShowActivity(). + */ + memcpy(new_params, params, sizeof(cell_t) * (params[0] + 1)); + ReorderTranslationParams(&pTrans, &new_params[*arg]); - /* Now, call back into atcprintf() which is re-entrant */ - return atcprintf(buffer, maxlen, pTrans.szPhrase, pCtx, params, arg); + return atcprintf(buffer, maxlen, pTrans.szPhrase, pCtx, new_params, arg); + } + else + { + return atcprintf(buffer, maxlen, pTrans.szPhrase, pCtx, params, arg); + } error_out: *error = true; diff --git a/core/smn_halflife.cpp b/core/smn_halflife.cpp index 92f5f8fc..73091109 100644 --- a/core/smn_halflife.cpp +++ b/core/smn_halflife.cpp @@ -408,6 +408,34 @@ static cell_t ShowVGUIPanel(IPluginContext *pContext, const cell_t *params) return 1; } +static cell_t smn_IsPlayerAlive(IPluginContext *pContext, const cell_t *params) +{ + CPlayer *player = g_Players.GetPlayerByIndex(params[1]); + if (player == NULL) + { + return pContext->ThrowNativeError("Invalid client index %d", params[1]); + } + else if (!player->IsInGame()) + { + return pContext->ThrowNativeError("Client %d is not in game", params[1]); + } + + unsigned int state = player->GetLifeState(); + + if (state == PLAYER_LIFE_UNKNOWN) + { + return pContext->ThrowNativeError("\"IsPlayerAlive\" not supported by this mod"); + } + else if (state == PLAYER_LIFE_ALIVE) + { + return 1; + } + else + { + return 0; + } +} + REGISTER_NATIVES(halflifeNatives) { {"CreateFakeClient", CreateFakeClient}, @@ -436,5 +464,6 @@ REGISTER_NATIVES(halflifeNatives) {"PrintCenterText", PrintCenterText}, {"PrintHintText", PrintHintText}, {"ShowVGUIPanel", ShowVGUIPanel}, + {"IsPlayerAlive", smn_IsPlayerAlive}, {NULL, NULL}, }; diff --git a/core/smn_player.cpp b/core/smn_player.cpp index 3e9d1425..38861968 100644 --- a/core/smn_player.cpp +++ b/core/smn_player.cpp @@ -881,7 +881,11 @@ static cell_t GetClientOfUserId(IPluginContext *pContext, const cell_t *params) return g_Players.GetClientOfUserId(params[1]); } -static cell_t _ShowActivity(IPluginContext *pContext, const cell_t *params, const char *tag, cell_t fmt_param) +static cell_t _ShowActivity(IPluginContext *pContext, + const cell_t *params, + const char *tag, + cell_t fmt_param, + unsigned int mode) { char message[255]; char buffer[255]; @@ -912,13 +916,31 @@ static cell_t _ShowActivity(IPluginContext *pContext, const cell_t *params, cons { g_SourceMod.SetGlobalTarget(client); g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, fmt_param); + + if (pContext->GetContext()->n_err != SP_ERROR_NONE) + { + return 0; + } + UTIL_Format(message, sizeof(message), "%s%s\n", tag, buffer); engine->ClientPrintf(pPlayer->GetEdict(), message); display_in_chat = true; } - } else { + else if (mode == 2) + { + display_in_chat = true; + } + } + else + { g_SourceMod.SetGlobalTarget(LANG_SERVER); g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, fmt_param); + + if (pContext->GetContext()->n_err != SP_ERROR_NONE) + { + return 0; + } + UTIL_Format(message, sizeof(message), "%s%s\n", tag, buffer); META_CONPRINT(message); } @@ -947,15 +969,23 @@ static cell_t _ShowActivity(IPluginContext *pContext, const cell_t *params, cons if ((value & 1) || (value & 2)) { const char *newsign = sign; - if (value & 2) + if ((value & 2) || (i == client)) { newsign = name; } g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, fmt_param); + + if (pContext->GetContext()->n_err != SP_ERROR_NONE) + { + return 0; + } + UTIL_Format(message, sizeof(message), "%s%s: %s", tag, newsign, buffer); g_HL2.TextMsg(i, HUD_PRINTTALK, message); } - } else { + } + else + { /* Treat this as an admin user */ bool is_root = g_Admins.GetAdminFlag(id, Admin_Root, Access_Effective); if ((value & 4) @@ -963,11 +993,17 @@ static cell_t _ShowActivity(IPluginContext *pContext, const cell_t *params, cons || ((value & 16) && is_root)) { const char *newsign = sign; - if ((value & 8) || ((value & 16) && is_root)) + if ((value & 8) || ((value & 16) && is_root) || (i == client)) { newsign = name; } g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, fmt_param); + + if (pContext->GetContext()->n_err != SP_ERROR_NONE) + { + return 0; + } + UTIL_Format(message, sizeof(message), "%s%s: %s", tag, newsign, buffer); g_HL2.TextMsg(i, HUD_PRINTTALK, message); } @@ -979,7 +1015,7 @@ static cell_t _ShowActivity(IPluginContext *pContext, const cell_t *params, cons static cell_t ShowActivity(IPluginContext *pContext, const cell_t *params) { - return _ShowActivity(pContext, params, "[SM] ", 2); + return _ShowActivity(pContext, params, "[SM] ", 2, 1); } static cell_t ShowActivityEx(IPluginContext *pContext, const cell_t *params) @@ -987,7 +1023,15 @@ static cell_t ShowActivityEx(IPluginContext *pContext, const cell_t *params) char *str; pContext->LocalToString(params[2], &str); - return _ShowActivity(pContext, params, str, 3); + return _ShowActivity(pContext, params, str, 3, 1); +} + +static cell_t ShowActivity2(IPluginContext *pContext, const cell_t *params) +{ + char *str; + pContext->LocalToString(params[2], &str); + + return _ShowActivity(pContext, params, str, 3, 2); } static cell_t KickClient(IPluginContext *pContext, const cell_t *params) @@ -1036,7 +1080,9 @@ static cell_t ChangeClientTeam(IPluginContext *pContext, const cell_t *params) if (!pPlayer) { return pContext->ThrowNativeError("Client index %d is invalid", client); - } else if (!pPlayer->IsInGame()) { + } + else if (!pPlayer->IsInGame()) + { return pContext->ThrowNativeError("Client %d is not in game", client); } @@ -1059,9 +1105,13 @@ static cell_t RunAdminCacheChecks(IPluginContext *pContext, const cell_t *params if (!pPlayer) { return pContext->ThrowNativeError("Client index %d is invalid", client); - } else if (!pPlayer->IsInGame()) { + } + else if (!pPlayer->IsInGame()) + { return pContext->ThrowNativeError("Client %d is not in game", client); - } else if (!pPlayer->IsAuthorized()) { + } + else if (!pPlayer->IsAuthorized()) + { return pContext->ThrowNativeError("Client %d is not authorized", client); } @@ -1079,9 +1129,12 @@ static cell_t NotifyPostAdminCheck(IPluginContext *pContext, const cell_t *param if (!pPlayer) { return pContext->ThrowNativeError("Client index %d is invalid", client); - } else if (!pPlayer->IsInGame()) { + } + else if (!pPlayer->IsInGame()) { return pContext->ThrowNativeError("Client %d is not in game", client); - } else if (!pPlayer->IsAuthorized()) { + } + else if (!pPlayer->IsAuthorized()) + { return pContext->ThrowNativeError("Client %d is not authorized", client); } @@ -1108,6 +1161,42 @@ static cell_t IsClientInKickQueue(IPluginContext *pContext, const cell_t *params return pPlayer->IsInKickQueue() ? 1 : 0; } +static cell_t ProcessTargetString(IPluginContext *pContext, const cell_t *params) +{ + cmd_target_info_t info; + + pContext->LocalToString(params[1], (char **)&info.pattern); + info.admin = params[2]; + pContext->LocalToPhysAddr(params[3], &info.targets); + info.max_targets = params[4]; + info.flags = params[5]; + pContext->LocalToString(params[6], &info.target_name); + info.target_name_maxlength = params[7]; + + cell_t *tn_is_ml; + pContext->LocalToPhysAddr(params[8], &tn_is_ml); + + g_Players.ProcessCommandTarget(&info); + + if (info.target_name_style == COMMAND_TARGETNAME_ML) + { + *tn_is_ml = 1; + } + else + { + *tn_is_ml = 0; + } + + if (info.num_targets == 0) + { + return info.reason; + } + else + { + return info.num_targets; + } +} + REGISTER_NATIVES(playernatives) { {"AddUserFlags", AddUserFlags}, @@ -1154,10 +1243,12 @@ REGISTER_NATIVES(playernatives) {"GetClientOfUserId", GetClientOfUserId}, {"ShowActivity", ShowActivity}, {"ShowActivityEx", ShowActivityEx}, + {"ShowActivity2", ShowActivity2}, {"KickClient", KickClient}, {"RunAdminCacheChecks", RunAdminCacheChecks}, {"NotifyPostAdminCheck", NotifyPostAdminCheck}, {"IsClientInKickQueue", IsClientInKickQueue}, + {"ProcessTargetString", ProcessTargetString}, {NULL, NULL} }; diff --git a/core/systems/PluginSys.cpp b/core/systems/PluginSys.cpp index e50147df..53f7b7e0 100644 --- a/core/systems/PluginSys.cpp +++ b/core/systems/PluginSys.cpp @@ -45,6 +45,7 @@ #include "ConCmdManager.h" #include "PlayerManager.h" #include "CoreConfig.h" +#include "Translator.h" CPluginManager g_PluginSys; HandleType_t g_PluginType = 0; @@ -1379,6 +1380,9 @@ bool CPluginManager::RunSecondPass(CPlugin *pPlugin, char *error, size_t maxleng OnLibraryAction((*s_iter).c_str(), true, false); } + /* Finally, add the core language file */ + pPlugin->AddLangFile(g_pCorePhraseID); + return true; } diff --git a/plugins/include/clients.inc b/plugins/include/clients.inc index 9a8ba68a..219a25dd 100644 --- a/plugins/include/clients.inc +++ b/plugins/include/clients.inc @@ -255,6 +255,17 @@ native bool:IsFakeClient(client); */ native bool:IsClientObserver(client); +/** + * Returns if the client is alive or dead. + * + * Note: This function was originally in SDKTools and was moved to core. + * + * @param client Player's index. + * @return True if the client is alive, false otherwise. + * @error Invalid client index, client not in game, or no mod support. + */ +native bool:IsPlayerAlive(client); + /** * Retrieves values from client replicated keys. * diff --git a/plugins/include/commandfilters.inc b/plugins/include/commandfilters.inc new file mode 100644 index 00000000..fa8829a9 --- /dev/null +++ b/plugins/include/commandfilters.inc @@ -0,0 +1,130 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#if defined _commandfilters_included + #endinput +#endif +#define _commandfilters_included + +#define MAX_TARGET_LENGTH 64 + +#define COMMAND_FILTER_ALIVE (1<<0) /* Only allow alive players */ +#define COMMAND_FILTER_DEAD (1<<1) /* Only filter dead players */ +#define COMMAND_FILTER_CONNECTED (1<<2) /* Allow players not fully in-game */ +#define COMMAND_FILTER_NO_IMMUNITY (1<<3) /* Ignore immunity rules */ +#define COMMAND_FILTER_NO_MULTI (1<<4) /* Do not allow multiple target patterns */ +#define COMMAND_FILTER_NO_BOTS (1<<5) /* Do not allow bots to be targetted */ + +#define COMMAND_TARGET_NONE 0 /* No target was found */ +#define COMMAND_TARGET_NOT_ALIVE -1 /* Single client is not alive */ +#define COMMAND_TARGET_NOT_DEAD -2 /* Single client is not dead */ +#define COMMAND_TARGET_NOT_IN_GAME -3 /* Single client is not in game */ +#define COMMAND_TARGET_IMMUNE -4 /* Single client is immune */ +#define COMMAND_TARGET_EMPTY_FILTER -5 /* A multi-filter (such as @all) had no targets */ +#define COMMAND_TARGET_NOT_HUMAN -6 /* Target was not human */ +#define COMMAND_TARGET_AMBIGUOUS -7 /* Partial name had too many targets */ + +/** + * Processes a generic command target string, and resolves it to a list + * of clients or one client, based on filtering rules and a pattern. + * + * @param pattern Pattern to find clients against. + * @param admin Admin performing the action, or 0 if the server. + * @param targets Array to hold targets. + * @param max_targets Maximum size of the targets array. + * @param filter_flags Filter flags. + * @param target_name Buffer to store the target name. + * @param tn_maxlength Maximum length of the target name buffer. + * @param tn_is_ml Set to true if the target name buffer is an ML phrase, + * false if it is a normal string. + * @return If a multi-target pattern was used, the number of clients found + * is returned. If a single-target pattern was used, 1 is returned + * if one valid client is found. Otherwise, a CLIENT_TARGET reason + * for failure is returned. + */ +native ProcessTargetString(const String:pattern[], + admin, + targets[], + max_targets, + filter_flags, + String:target_name[], + tn_maxlength, + &bool:tn_is_ml); + +/** + * Replies to a client with a given message describing a targetting + * failure reason. + * + * Note: The translation phrases are found in common.phrases.txt. + * + * @param client Client index, or 0 for server. + * @param reason COMMAND_TARGET reason. + * @noreturn + */ +stock ReplyToTargetError(client, reason) +{ + switch (reason) + { + case COMMAND_TARGET_NONE: + { + ReplyToCommand(client, "[SM] %t", "No matching client"); + } + case COMMAND_TARGET_NOT_ALIVE: + { + ReplyToCommand(client, "[SM] %t", "Target must be alive"); + } + case COMMAND_TARGET_NOT_DEAD: + { + ReplyToCommand(client, "[SM] %t", "Target must be dead"); + } + case COMMAND_TARGET_NOT_IN_GAME: + { + ReplyToCommand(client, "[SM] %t", "Target is not in game"); + } + case COMMAND_TARGET_IMMUNE: + { + ReplyToCommand(client, "[SM] %t", "Unable to target"); + } + case COMMAND_TARGET_EMPTY_FILTER: + { + ReplyToCommand(client, "[SM] %t", "No matching clients"); + } + case COMMAND_TARGET_NOT_HUMAN: + { + ReplyToCommand(client, "[SM] %t", "Cannot target bot"); + } + case COMMAND_TARGET_AMBIGUOUS: + { + ReplyToCommand(client, "[SM] %t", "More than one client matched"); + } + } +} diff --git a/plugins/include/console.inc b/plugins/include/console.inc index 95096c24..eb2aa00c 100644 --- a/plugins/include/console.inc +++ b/plugins/include/console.inc @@ -241,9 +241,10 @@ native bool:IsChatTrigger(); /** * Displays usage of an admin command to users depending on the - * setting of the sm_show_activity cvar. Additionally, the client - * who typed the command will receive the same text without any - * client name prefix (as a confirmation). + * setting of the sm_show_activity cvar. + * + * If the original client did not use a console command, they will + * not see the public display in their chat. * * @param client Client index doing the action, or 0 for server. * @param format Formatting rules. @@ -266,6 +267,19 @@ native ShowActivity(client, const String:format[], any:...); */ native ShowActivityEx(client, const String:tag[], const String:format[], any:...); +/** + * Same as ShowActivityEx(), except the text will always be mirrored to the + * client's chat (and console, if the reply source specifies as such). + * + * @param client Client index doing the action, or 0 for server. + * @param tag Tag to display with. + * @param format Formatting rules. + * @param ... Variable number of format parameters. + * @noreturn + * @error + */ +native ShowActivity2(client, const String:tag[], const String:format[], any:...); + /** * Called when a server-only command is invoked. * diff --git a/plugins/include/sdktools_functions.inc b/plugins/include/sdktools_functions.inc index 2565b6b8..2b7f27c9 100644 --- a/plugins/include/sdktools_functions.inc +++ b/plugins/include/sdktools_functions.inc @@ -142,15 +142,6 @@ native FindEntityByClassname(startEnt, const String:classname[]); */ native bool:GetClientEyeAngles(client, Float:ang[3]); -/** - * Returns if the client is alive or dead. - * - * @param client Player's index. - * @return True if the client is alive, false otherwise. - * @error Invalid client index, client not in game, or no mod support. - */ -native bool:IsPlayerAlive(client); - /** * Creates an entity by string name, but does not spawn it (see DispatchSpawn). * If ForceEdictIndex is not -1, then it will use the edict by that index. If the index is diff --git a/plugins/include/sourcemod.inc b/plugins/include/sourcemod.inc index bab00c80..c95f1d3e 100644 --- a/plugins/include/sourcemod.inc +++ b/plugins/include/sourcemod.inc @@ -70,6 +70,7 @@ struct Plugin #include #include #include +#include /** * Declare this as a struct in your plugin to expose its information. diff --git a/translations/core.phrases.txt b/translations/core.phrases.txt index 2ee6f2c8..6c4900b9 100644 --- a/translations/core.phrases.txt +++ b/translations/core.phrases.txt @@ -24,4 +24,36 @@ { "en" "Previous" } + + "all players" + { + "en" "all players" + } + + "all humans" + { + "en" "all humans" + } + + "all bots" + { + "en" "all bots" + } + + "all dead players" + { + "en" "all dead players" + } + + "all alive players" + { + "en" "all alive players" + } + + /* This is a special "pass-thru" translation */ + "_S" + { + "#format" "{1:s}" + "en" "{1}" + } }