251cced1f8
Various minor things done to project files Updated sample extension project file and updated makefile to the new unified version (more changes likely on the way) Updated regex project file and makefile --HG-- extra : convert_revision : svn%3A39bc706e-5318-0410-9160-8a85361fbb7c/trunk%401971
1308 lines
32 KiB
C++
1308 lines
32 KiB
C++
/**
|
|
* vim: set ts=4 :
|
|
* =============================================================================
|
|
* SourceMod
|
|
* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
|
|
* =============================================================================
|
|
*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
* 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 <http://www.sourcemod.net/license.php>.
|
|
*
|
|
* Version: $Id$
|
|
*/
|
|
|
|
#include "sm_globals.h"
|
|
#include "HalfLife2.h"
|
|
#include "sourcemm_api.h"
|
|
#include "HandleSys.h"
|
|
#include "ConVarManager.h"
|
|
#include "ConCmdManager.h"
|
|
#include "PluginSys.h"
|
|
#include "sm_stringutil.h"
|
|
#include "PlayerManager.h"
|
|
#include "ChatTriggers.h"
|
|
#include "AdminCache.h"
|
|
#include <inetchannel.h>
|
|
#include <bitbuf.h>
|
|
#include <sm_trie_tpl.h>
|
|
|
|
#define NET_SETCONVAR 5
|
|
|
|
enum ConVarBounds
|
|
{
|
|
ConVarBound_Upper = 0,
|
|
ConVarBound_Lower
|
|
};
|
|
|
|
HandleType_t hCmdIterType = 0;
|
|
HandleType_t htConCmdIter = 0;
|
|
|
|
struct GlobCmdIter
|
|
{
|
|
bool started;
|
|
List<ConCmdInfo *>::iterator iter;
|
|
};
|
|
|
|
struct ConCmdIter
|
|
{
|
|
const ConCommandBase *pLast;
|
|
};
|
|
|
|
class ConsoleHelpers :
|
|
public SMGlobalClass,
|
|
public IHandleTypeDispatch
|
|
{
|
|
public:
|
|
virtual void OnSourceModAllInitialized()
|
|
{
|
|
HandleAccess access;
|
|
|
|
g_HandleSys.InitAccessDefaults(NULL, &access);
|
|
|
|
htConCmdIter = g_HandleSys.CreateType("ConCmdIter", this, 0, NULL, &access, g_pCoreIdent, NULL);
|
|
|
|
access.access[HandleAccess_Clone] = HANDLE_RESTRICT_OWNER | HANDLE_RESTRICT_IDENTITY;
|
|
|
|
hCmdIterType = g_HandleSys.CreateType("CmdIter", this, 0, NULL, &access, g_pCoreIdent, NULL);
|
|
}
|
|
virtual void OnHandleDestroy(HandleType_t type, void *object)
|
|
{
|
|
if (type == hCmdIterType)
|
|
{
|
|
GlobCmdIter *iter = (GlobCmdIter *)object;
|
|
delete iter;
|
|
}
|
|
else if (type == htConCmdIter)
|
|
{
|
|
ConCmdIter *iter = (ConCmdIter * )object;
|
|
delete iter;
|
|
}
|
|
}
|
|
virtual bool GetHandleApproxSize(HandleType_t type, void *object, unsigned int *pSize)
|
|
{
|
|
if (type == htConCmdIter)
|
|
{
|
|
*pSize = sizeof(ConCmdIter);
|
|
return true;
|
|
}
|
|
else if (type == hCmdIterType)
|
|
{
|
|
*pSize = sizeof(GlobCmdIter);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
} s_ConsoleHelpers;
|
|
|
|
class CommandFlagsHelper : public IConCommandTracker
|
|
{
|
|
public:
|
|
void OnUnlinkConCommandBase(ConCommandBase *pBase, const char *name, bool is_read_safe)
|
|
{
|
|
m_CmdFlags.remove(name);
|
|
}
|
|
bool GetFlags(const char *name, int *flags)
|
|
{
|
|
ConCommandBase **ppCmd;
|
|
ConCommandBase *pCmd;
|
|
if ((ppCmd=m_CmdFlags.retrieve(name)))
|
|
{
|
|
TrackConCommandBase((*ppCmd), this);
|
|
*flags = (*ppCmd)->GetFlags();
|
|
return true;
|
|
}
|
|
else if ((pCmd=FindConCommandBase(name)))
|
|
{
|
|
m_CmdFlags.insert(name, pCmd);
|
|
TrackConCommandBase(pCmd, this);
|
|
*flags = pCmd->GetFlags();
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
bool SetFlags(const char *name, int flags)
|
|
{
|
|
ConCommandBase **ppCmd;
|
|
ConCommandBase *pCmd;
|
|
if ((ppCmd=m_CmdFlags.retrieve(name)))
|
|
{
|
|
(*ppCmd)->SetFlags(flags);
|
|
TrackConCommandBase((*ppCmd), this);
|
|
return true;
|
|
}
|
|
else if ((pCmd=FindConCommandBase(name)))
|
|
{
|
|
m_CmdFlags.insert(name, pCmd);
|
|
pCmd->SetFlags(flags);
|
|
TrackConCommandBase(pCmd, this);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
private:
|
|
KTrie<ConCommandBase *> m_CmdFlags;
|
|
} s_CommandFlagsHelper;
|
|
|
|
static void ReplicateConVar(ConVar *pConVar)
|
|
{
|
|
int maxClients = g_Players.GetMaxClients();
|
|
|
|
char data[256];
|
|
bf_write buffer(data, sizeof(data));
|
|
|
|
buffer.WriteUBitLong(NET_SETCONVAR, 5);
|
|
buffer.WriteByte(1);
|
|
buffer.WriteString(pConVar->GetName());
|
|
buffer.WriteString(pConVar->GetString());
|
|
|
|
for (int i = 1; i <= maxClients; i++)
|
|
{
|
|
CPlayer *pPlayer = g_Players.GetPlayerByIndex(i);
|
|
|
|
if (pPlayer && pPlayer->IsInGame() && !pPlayer->IsFakeClient())
|
|
{
|
|
INetChannel *netchan = static_cast<INetChannel *>(engine->GetPlayerNetInfo(i));
|
|
netchan->SendData(buffer);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void NotifyConVar(ConVar *pConVar)
|
|
{
|
|
IGameEvent *pEvent = gameevents->CreateEvent("server_cvar");
|
|
pEvent->SetString("cvarname", pConVar->GetName());
|
|
if (IsFlagSet(pConVar, FCVAR_PROTECTED))
|
|
{
|
|
pEvent->SetString("cvarvalue", "***PROTECTED***");
|
|
}
|
|
else
|
|
{
|
|
pEvent->SetString("cvarvalue", pConVar->GetString());
|
|
}
|
|
|
|
gameevents->FireEvent(pEvent);
|
|
}
|
|
|
|
static cell_t sm_CreateConVar(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
char *name, *defaultVal, *helpText;
|
|
|
|
pContext->LocalToString(params[1], &name);
|
|
|
|
// While the engine seems to accept a blank convar name, it causes a crash upon server quit
|
|
if (name == NULL || strcmp(name, "") == 0)
|
|
{
|
|
return pContext->ThrowNativeError("Convar with blank name is not permitted");
|
|
}
|
|
|
|
pContext->LocalToString(params[2], &defaultVal);
|
|
pContext->LocalToString(params[3], &helpText);
|
|
|
|
bool hasMin = params[5] ? true : false;
|
|
bool hasMax = params[7] ? true : false;
|
|
float min = sp_ctof(params[6]);
|
|
float max = sp_ctof(params[8]);
|
|
|
|
Handle_t hndl = g_ConVarManager.CreateConVar(pContext, name, defaultVal, helpText, params[4], hasMin, min, hasMax, max);
|
|
|
|
if (hndl == BAD_HANDLE)
|
|
{
|
|
return pContext->ThrowNativeError("Convar \"%s\" was not created. A console command with the same might already exist.", name);
|
|
}
|
|
|
|
return hndl;
|
|
}
|
|
|
|
static cell_t sm_FindConVar(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
char *name;
|
|
|
|
pContext->LocalToString(params[1], &name);
|
|
|
|
return g_ConVarManager.FindConVar(name);
|
|
}
|
|
|
|
static cell_t sm_HookConVarChange(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
Handle_t hndl = static_cast<Handle_t>(params[1]);
|
|
HandleError err;
|
|
ConVar *pConVar;
|
|
|
|
if ((err=g_ConVarManager.ReadConVarHandle(hndl, &pConVar))
|
|
!= HandleError_None)
|
|
{
|
|
return pContext->ThrowNativeError("Invalid convar handle %x (error %d)", hndl, err);
|
|
}
|
|
|
|
IPluginFunction *pFunction = pContext->GetFunctionById(params[2]);
|
|
|
|
if (!pFunction)
|
|
{
|
|
return pContext->ThrowNativeError("Invalid function id (%X)", params[2]);
|
|
}
|
|
|
|
g_ConVarManager.HookConVarChange(pConVar, pFunction);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static cell_t sm_UnhookConVarChange(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
Handle_t hndl = static_cast<Handle_t>(params[1]);
|
|
HandleError err;
|
|
ConVar *pConVar;
|
|
|
|
if ((err=g_ConVarManager.ReadConVarHandle(hndl, &pConVar))
|
|
!= HandleError_None)
|
|
{
|
|
return pContext->ThrowNativeError("Invalid convar handle %x (error %d)", hndl, err);
|
|
}
|
|
|
|
IPluginFunction *pFunction = pContext->GetFunctionById(params[2]);
|
|
|
|
if (!pFunction)
|
|
{
|
|
return pContext->ThrowNativeError("Invalid function id (%X)", params[2]);
|
|
}
|
|
|
|
g_ConVarManager.UnhookConVarChange(pConVar, pFunction);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static cell_t sm_GetConVarBool(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
Handle_t hndl = static_cast<Handle_t>(params[1]);
|
|
HandleError err;
|
|
ConVar *pConVar;
|
|
|
|
if ((err=g_ConVarManager.ReadConVarHandle(hndl, &pConVar))
|
|
!= HandleError_None)
|
|
{
|
|
return pContext->ThrowNativeError("Invalid convar handle %x (error %d)", hndl, err);
|
|
}
|
|
|
|
return pConVar->GetBool();
|
|
}
|
|
|
|
static cell_t sm_GetConVarInt(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
Handle_t hndl = static_cast<Handle_t>(params[1]);
|
|
HandleError err;
|
|
ConVar *pConVar;
|
|
|
|
if ((err=g_ConVarManager.ReadConVarHandle(hndl, &pConVar))
|
|
!= HandleError_None)
|
|
{
|
|
return pContext->ThrowNativeError("Invalid convar handle %x (error %d)", hndl, err);
|
|
}
|
|
|
|
return pConVar->GetInt();
|
|
}
|
|
|
|
/* This handles both SetConVarBool() and SetConVarInt() */
|
|
static cell_t sm_SetConVarNum(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
Handle_t hndl = static_cast<Handle_t>(params[1]);
|
|
HandleError err;
|
|
ConVar *pConVar;
|
|
|
|
if ((err=g_ConVarManager.ReadConVarHandle(hndl, &pConVar))
|
|
!= HandleError_None)
|
|
{
|
|
return pContext->ThrowNativeError("Invalid convar handle %x (error %d)", hndl, err);
|
|
}
|
|
|
|
pConVar->SetValue(params[2]);
|
|
|
|
/* Should we replicate it? */
|
|
if (params[3] && IsFlagSet(pConVar, FCVAR_REPLICATED))
|
|
{
|
|
ReplicateConVar(pConVar);
|
|
}
|
|
|
|
/* Should we notify clients? */
|
|
if (params[4] && IsFlagSet(pConVar, FCVAR_NOTIFY))
|
|
{
|
|
NotifyConVar(pConVar);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static cell_t sm_GetConVarFloat(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
Handle_t hndl = static_cast<Handle_t>(params[1]);
|
|
HandleError err;
|
|
ConVar *pConVar;
|
|
|
|
if ((err=g_ConVarManager.ReadConVarHandle(hndl, &pConVar))
|
|
!= HandleError_None)
|
|
{
|
|
return pContext->ThrowNativeError("Invalid convar handle %x (error %d)", hndl, err);
|
|
}
|
|
|
|
float value = pConVar->GetFloat();
|
|
|
|
return sp_ftoc(value);
|
|
}
|
|
|
|
static cell_t sm_SetConVarFloat(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
Handle_t hndl = static_cast<Handle_t>(params[1]);
|
|
HandleError err;
|
|
ConVar *pConVar;
|
|
|
|
if ((err=g_ConVarManager.ReadConVarHandle(hndl, &pConVar))
|
|
!= HandleError_None)
|
|
{
|
|
return pContext->ThrowNativeError("Invalid convar handle %x (error %d)", hndl, err);
|
|
}
|
|
|
|
float value = sp_ctof(params[2]);
|
|
pConVar->SetValue(value);
|
|
|
|
/* Should we replicate it? */
|
|
if (params[3] && IsFlagSet(pConVar, FCVAR_REPLICATED))
|
|
{
|
|
ReplicateConVar(pConVar);
|
|
}
|
|
|
|
/* Should we notify clients? */
|
|
if (params[4] && IsFlagSet(pConVar, FCVAR_NOTIFY))
|
|
{
|
|
NotifyConVar(pConVar);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static cell_t sm_GetConVarString(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
Handle_t hndl = static_cast<Handle_t>(params[1]);
|
|
HandleError err;
|
|
ConVar *pConVar;
|
|
|
|
if ((err=g_ConVarManager.ReadConVarHandle(hndl, &pConVar))
|
|
!= HandleError_None)
|
|
{
|
|
return pContext->ThrowNativeError("Invalid convar handle %x (error %d)", hndl, err);
|
|
}
|
|
|
|
pContext->StringToLocalUTF8(params[2], params[3], pConVar->GetString(), NULL);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static cell_t sm_SetConVarString(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
Handle_t hndl = static_cast<Handle_t>(params[1]);
|
|
HandleError err;
|
|
ConVar *pConVar;
|
|
|
|
if ((err=g_ConVarManager.ReadConVarHandle(hndl, &pConVar))
|
|
!= HandleError_None)
|
|
{
|
|
return pContext->ThrowNativeError("Invalid convar handle %x (error %d)", hndl, err);
|
|
}
|
|
|
|
char *value;
|
|
pContext->LocalToString(params[2], &value);
|
|
|
|
pConVar->SetValue(value);
|
|
|
|
/* Should we replicate it? */
|
|
if (params[3] && IsFlagSet(pConVar, FCVAR_REPLICATED))
|
|
{
|
|
ReplicateConVar(pConVar);
|
|
}
|
|
|
|
/* Should we notify clients? */
|
|
if (params[4] && IsFlagSet(pConVar, FCVAR_NOTIFY))
|
|
{
|
|
NotifyConVar(pConVar);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static cell_t sm_ResetConVar(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
Handle_t hndl = static_cast<Handle_t>(params[1]);
|
|
HandleError err;
|
|
ConVar *pConVar;
|
|
|
|
if ((err=g_ConVarManager.ReadConVarHandle(hndl, &pConVar))
|
|
!= HandleError_None)
|
|
{
|
|
return pContext->ThrowNativeError("Invalid convar handle %x (error %d)", hndl, err);
|
|
}
|
|
|
|
pConVar->Revert();
|
|
|
|
/* Should we replicate it? */
|
|
if (params[2] && IsFlagSet(pConVar, FCVAR_REPLICATED))
|
|
{
|
|
ReplicateConVar(pConVar);
|
|
}
|
|
|
|
/* Should we notify clients? */
|
|
if (params[3] && IsFlagSet(pConVar, FCVAR_NOTIFY))
|
|
{
|
|
NotifyConVar(pConVar);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static cell_t sm_GetConVarFlags(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
Handle_t hndl = static_cast<Handle_t>(params[1]);
|
|
HandleError err;
|
|
ConVar *pConVar;
|
|
|
|
if ((err=g_ConVarManager.ReadConVarHandle(hndl, &pConVar))
|
|
!= HandleError_None)
|
|
{
|
|
return pContext->ThrowNativeError("Invalid convar handle %x (error %d)", hndl, err);
|
|
}
|
|
|
|
return pConVar->GetFlags();
|
|
}
|
|
|
|
static cell_t sm_SetConVarFlags(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
Handle_t hndl = static_cast<Handle_t>(params[1]);
|
|
HandleError err;
|
|
ConVar *pConVar;
|
|
|
|
if ((err=g_ConVarManager.ReadConVarHandle(hndl, &pConVar))
|
|
!= HandleError_None)
|
|
{
|
|
return pContext->ThrowNativeError("Invalid convar handle %x (error %d)", hndl, err);
|
|
}
|
|
|
|
pConVar->SetFlags(params[2]);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static cell_t sm_GetConVarBounds(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
Handle_t hndl = static_cast<Handle_t>(params[1]);
|
|
HandleError err;
|
|
ConVar *pConVar;
|
|
|
|
if ((err=g_ConVarManager.ReadConVarHandle(hndl, &pConVar))
|
|
!= HandleError_None)
|
|
{
|
|
return pContext->ThrowNativeError("Invalid convar handle %x (error %d)", hndl, err);
|
|
}
|
|
|
|
cell_t *addr;
|
|
bool hasBound;
|
|
float bound;
|
|
|
|
switch (params[2])
|
|
{
|
|
case ConVarBound_Upper:
|
|
hasBound = pConVar->GetMax(bound);
|
|
break;
|
|
case ConVarBound_Lower:
|
|
hasBound = pConVar->GetMin(bound);
|
|
break;
|
|
default:
|
|
return pContext->ThrowNativeError("Invalid ConVarBounds value %d");
|
|
}
|
|
|
|
pContext->LocalToPhysAddr(params[3], &addr);
|
|
*addr = sp_ftoc(bound);
|
|
|
|
return hasBound;
|
|
}
|
|
|
|
static cell_t sm_SetConVarBounds(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
Handle_t hndl = static_cast<Handle_t>(params[1]);
|
|
HandleError err;
|
|
ConVar *pConVar;
|
|
|
|
if ((err=g_ConVarManager.ReadConVarHandle(hndl, &pConVar))
|
|
!= HandleError_None)
|
|
{
|
|
return pContext->ThrowNativeError("Invalid convar handle %x (error %d)", hndl, err);
|
|
}
|
|
|
|
switch (params[2])
|
|
{
|
|
case ConVarBound_Upper:
|
|
pConVar->SetMax(params[3] ? true : false, sp_ctof(params[4]));
|
|
break;
|
|
case ConVarBound_Lower:
|
|
pConVar->SetMin(params[3] ? true : false, sp_ctof(params[4]));
|
|
break;
|
|
default:
|
|
return pContext->ThrowNativeError("Invalid ConVarBounds value %d");
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static cell_t sm_GetConVarName(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
Handle_t hndl = static_cast<Handle_t>(params[1]);
|
|
HandleError err;
|
|
ConVar *pConVar;
|
|
|
|
if ((err=g_ConVarManager.ReadConVarHandle(hndl, &pConVar))
|
|
!= HandleError_None)
|
|
{
|
|
return pContext->ThrowNativeError("Invalid convar handle %x (error %d)", hndl, err);
|
|
}
|
|
|
|
pContext->StringToLocalUTF8(params[2], params[3], pConVar->GetName(), NULL);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static bool s_QueryAlreadyWarned = false;
|
|
|
|
static cell_t sm_QueryClientConVar(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
CPlayer *pPlayer;
|
|
char *name;
|
|
IPluginFunction *pCallback;
|
|
|
|
if (!g_ConVarManager.IsQueryingSupported())
|
|
{
|
|
if (!s_QueryAlreadyWarned)
|
|
{
|
|
s_QueryAlreadyWarned = true;
|
|
return pContext->ThrowNativeError("Game does not support client convar querying (one time warning)");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
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]);
|
|
}
|
|
|
|
/* Trying a query on a bot results in callback not be fired, so don't bother */
|
|
if (pPlayer->IsFakeClient())
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
pContext->LocalToString(params[2], &name);
|
|
pCallback = pContext->GetFunctionById(params[3]);
|
|
|
|
if (!pCallback)
|
|
{
|
|
return pContext->ThrowNativeError("Invalid function id (%X)", params[3]);
|
|
}
|
|
|
|
return g_ConVarManager.QueryClientConVar(pPlayer->GetEdict(), name, pCallback, params[4]);
|
|
}
|
|
|
|
static cell_t sm_RegServerCmd(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
char *name,*help;
|
|
IPluginFunction *pFunction;
|
|
|
|
pContext->LocalToString(params[1], &name);
|
|
pContext->LocalToString(params[3], &help);
|
|
pFunction = pContext->GetFunctionById(params[2]);
|
|
|
|
if (!pFunction)
|
|
{
|
|
return pContext->ThrowNativeError("Invalid function id (%X)", params[2]);
|
|
}
|
|
|
|
if (!g_ConCmds.AddServerCommand(pFunction, name, help, params[4]))
|
|
{
|
|
return pContext->ThrowNativeError("Command \"%s\" could not be created. A convar with the same name already exists.", name);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static cell_t sm_RegConsoleCmd(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
char *name,*help;
|
|
IPluginFunction *pFunction;
|
|
|
|
pContext->LocalToString(params[1], &name);
|
|
pContext->LocalToString(params[3], &help);
|
|
pFunction = pContext->GetFunctionById(params[2]);
|
|
|
|
if (!pFunction)
|
|
{
|
|
return pContext->ThrowNativeError("Invalid function id (%X)", params[2]);
|
|
}
|
|
|
|
if (!g_ConCmds.AddConsoleCommand(pFunction, name, help, params[4]))
|
|
{
|
|
return pContext->ThrowNativeError("Command \"%s\" could not be created. A convar with the same name already exists.", name);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static cell_t sm_RegAdminCmd(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
char *name,*help;
|
|
const char *group;
|
|
IPluginFunction *pFunction;
|
|
FlagBits flags = params[3];
|
|
int cmdflags = params[6];
|
|
|
|
pContext->LocalToString(params[1], &name);
|
|
pContext->LocalToString(params[4], &help);
|
|
pContext->LocalToString(params[5], (char **)&group);
|
|
pFunction = pContext->GetFunctionById(params[2]);
|
|
|
|
if (group[0] == '\0')
|
|
{
|
|
CPlugin *pPlugin = g_PluginSys.GetPluginByCtx(pContext->GetContext());
|
|
group = pPlugin->GetFilename();
|
|
}
|
|
|
|
if (!pFunction)
|
|
{
|
|
return pContext->ThrowNativeError("Invalid function id (%X)", params[2]);
|
|
}
|
|
|
|
if (!g_ConCmds.AddAdminCommand(pFunction, name, group, flags, help, cmdflags))
|
|
{
|
|
return pContext->ThrowNativeError("Command \"%s\" could not be created. A convar with the same name already exists.", name);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static cell_t sm_GetCmdArgs(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
const CCommand *pCmd = g_HL2.PeekCommandStack();
|
|
|
|
if (!pCmd)
|
|
{
|
|
return pContext->ThrowNativeError("No command callback available");
|
|
}
|
|
|
|
return pCmd->ArgC() - 1;
|
|
}
|
|
|
|
static cell_t sm_GetCmdArg(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
const CCommand *pCmd = g_HL2.PeekCommandStack();
|
|
|
|
if (!pCmd)
|
|
{
|
|
return pContext->ThrowNativeError("No command callback available");
|
|
}
|
|
|
|
size_t length;
|
|
|
|
const char *arg = pCmd->Arg(params[1]);
|
|
|
|
pContext->StringToLocalUTF8(params[2], params[3], arg ? arg : "", &length);
|
|
|
|
return (cell_t)length;
|
|
}
|
|
|
|
static cell_t sm_GetCmdArgString(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
const CCommand *pCmd = g_HL2.PeekCommandStack();
|
|
|
|
if (!pCmd)
|
|
{
|
|
return pContext->ThrowNativeError("No command callback available");
|
|
}
|
|
|
|
const char *args = pCmd->ArgS();
|
|
size_t length;
|
|
|
|
if (!args)
|
|
{
|
|
args = "";
|
|
}
|
|
|
|
pContext->StringToLocalUTF8(params[1], params[2], args, &length);
|
|
|
|
return (cell_t)length;
|
|
}
|
|
|
|
static cell_t sm_PrintToServer(IPluginContext *pCtx, const cell_t *params)
|
|
{
|
|
char buffer[1024];
|
|
char *fmt;
|
|
int arg = 2;
|
|
|
|
pCtx->LocalToString(params[1], &fmt);
|
|
size_t res = atcprintf(buffer, sizeof(buffer)-2, fmt, pCtx, params, &arg);
|
|
|
|
buffer[res++] = '\n';
|
|
buffer[res] = '\0';
|
|
|
|
META_CONPRINT(buffer);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static cell_t sm_PrintToConsole(IPluginContext *pCtx, const cell_t *params)
|
|
{
|
|
int index = params[1];
|
|
if ((index < 0) || (index > g_Players.GetMaxClients()))
|
|
{
|
|
return pCtx->ThrowNativeError("Client index %d is invalid", index);
|
|
}
|
|
|
|
CPlayer *pPlayer = NULL;
|
|
if (index != 0)
|
|
{
|
|
pPlayer = g_Players.GetPlayerByIndex(index);
|
|
if (!pPlayer->IsInGame())
|
|
{
|
|
return pCtx->ThrowNativeError("Client %d is not in game", index);
|
|
}
|
|
|
|
/* Silent fail on bots, engine will crash */
|
|
if (pPlayer->IsFakeClient())
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
char buffer[1024];
|
|
char *fmt;
|
|
int arg = 3;
|
|
|
|
pCtx->LocalToString(params[2], &fmt);
|
|
size_t res = atcprintf(buffer, sizeof(buffer)-2, fmt, pCtx, params, &arg);
|
|
|
|
buffer[res++] = '\n';
|
|
buffer[res] = '\0';
|
|
|
|
if (index != 0)
|
|
{
|
|
engine->ClientPrintf(pPlayer->GetEdict(), buffer);
|
|
} else {
|
|
META_CONPRINT(buffer);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static cell_t sm_ServerCommand(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
g_SourceMod.SetGlobalTarget(LANG_SERVER);
|
|
|
|
char buffer[1024];
|
|
size_t len = g_SourceMod.FormatString(buffer, sizeof(buffer)-2, pContext, params, 1);
|
|
|
|
if (pContext->GetContext()->n_err != SP_ERROR_NONE)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/* One byte for null terminator, one for newline */
|
|
buffer[len++] = '\n';
|
|
buffer[len] = '\0';
|
|
|
|
engine->ServerCommand(buffer);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static cell_t sm_InsertServerCommand(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
g_SourceMod.SetGlobalTarget(LANG_SERVER);
|
|
|
|
char buffer[1024];
|
|
size_t len = g_SourceMod.FormatString(buffer, sizeof(buffer)-2, pContext, params, 1);
|
|
|
|
if (pContext->GetContext()->n_err != SP_ERROR_NONE)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/* One byte for null terminator, one for newline */
|
|
buffer[len++] = '\n';
|
|
buffer[len] = '\0';
|
|
|
|
InsertServerCommand(buffer);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static cell_t sm_ServerExecute(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
engine->ServerExecute();
|
|
|
|
return 1;
|
|
}
|
|
|
|
static cell_t sm_ClientCommand(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
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]);
|
|
}
|
|
|
|
g_SourceMod.SetGlobalTarget(params[1]);
|
|
|
|
char buffer[256];
|
|
g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, 2);
|
|
|
|
if (pContext->GetContext()->n_err != SP_ERROR_NONE)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
engine->ClientCommand(pPlayer->GetEdict(), "%s", buffer);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static cell_t FakeClientCommand(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
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]);
|
|
}
|
|
|
|
char buffer[256];
|
|
g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, 2);
|
|
|
|
if (pContext->GetContext()->n_err != SP_ERROR_NONE)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
serverpluginhelpers->ClientCommand(pPlayer->GetEdict(), buffer);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static cell_t FakeClientCommandEx(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
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]);
|
|
}
|
|
|
|
char buffer[256];
|
|
g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, 2);
|
|
|
|
if (pContext->GetContext()->n_err != SP_ERROR_NONE)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
g_HL2.AddToFakeCliCmdQueue(params[1], engine->GetPlayerUserId(pPlayer->GetEdict()), buffer);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static cell_t ReplyToCommand(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
g_SourceMod.SetGlobalTarget(params[1]);
|
|
|
|
/* Build the format string */
|
|
char buffer[1024];
|
|
size_t len = g_SourceMod.FormatString(buffer, sizeof(buffer)-2, pContext, params, 2);
|
|
|
|
if (pContext->GetContext()->n_err != SP_ERROR_NONE)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/* 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]);
|
|
}
|
|
|
|
g_SourceMod.SetGlobalTarget(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;
|
|
}
|
|
|
|
static cell_t GetCmdReplyTarget(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
return g_ChatTriggers.GetReplyTo();
|
|
}
|
|
|
|
static cell_t SetCmdReplyTarget(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
return g_ChatTriggers.SetReplyTo(params[1]);
|
|
}
|
|
|
|
static cell_t GetCommandIterator(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
GlobCmdIter *iter = new GlobCmdIter;
|
|
iter->started = false;
|
|
|
|
Handle_t hndl = g_HandleSys.CreateHandle(hCmdIterType, iter, pContext->GetIdentity(), g_pCoreIdent, NULL);
|
|
if (hndl == BAD_HANDLE)
|
|
{
|
|
delete iter;
|
|
}
|
|
|
|
return hndl;
|
|
}
|
|
|
|
static cell_t ReadCommandIterator(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
GlobCmdIter *iter;
|
|
HandleError err;
|
|
HandleSecurity sec(pContext->GetIdentity(), g_pCoreIdent);
|
|
|
|
if ((err = g_HandleSys.ReadHandle(params[1], hCmdIterType, &sec, (void **)&iter))
|
|
!= HandleError_None)
|
|
{
|
|
return pContext->ThrowNativeError("Invalid GlobCmdIter Handle %x", params[1]);
|
|
}
|
|
|
|
const List<ConCmdInfo *> &cmds = g_ConCmds.GetCommandList();
|
|
|
|
if (!iter->started)
|
|
{
|
|
iter->iter = cmds.begin();
|
|
iter->started = true;
|
|
}
|
|
|
|
while (iter->iter != cmds.end()
|
|
&& !(*(iter->iter))->sourceMod)
|
|
{
|
|
iter->iter++;
|
|
}
|
|
|
|
if (iter->iter == cmds.end())
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
ConCmdInfo *pInfo = (*(iter->iter));
|
|
|
|
pContext->StringToLocalUTF8(params[2], params[3], pInfo->pCmd->GetName(), NULL);
|
|
pContext->StringToLocalUTF8(params[5], params[6], pInfo->pCmd->GetHelpText(), NULL);
|
|
|
|
cell_t *addr;
|
|
pContext->LocalToPhysAddr(params[4], &addr);
|
|
*addr = pInfo->admin.eflags;
|
|
|
|
iter->iter++;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static cell_t CheckCommandAccess(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
if (params[1] == 0)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
char *cmd;
|
|
pContext->LocalToString(params[2], &cmd);
|
|
|
|
/* Auto-detect a command if we can */
|
|
FlagBits bits = params[3];
|
|
bool found_command = false;
|
|
if (params[0] < 4 || !params[4])
|
|
{
|
|
found_command = g_ConCmds.LookForCommandAdminFlags(cmd, &bits);
|
|
}
|
|
|
|
if (!found_command)
|
|
{
|
|
g_Admins.GetCommandOverride(cmd, Override_Command, &bits);
|
|
}
|
|
|
|
return g_ConCmds.CheckCommandAccess(params[1], cmd, bits) ? 1 : 0;
|
|
}
|
|
|
|
static cell_t IsChatTrigger(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
return g_ChatTriggers.IsChatTrigger() ? 1 : 0;
|
|
}
|
|
|
|
static cell_t SetCommandFlags(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
char *name;
|
|
pContext->LocalToString(params[1], &name);
|
|
|
|
return (s_CommandFlagsHelper.SetFlags(name, params[2])) ? 1 : 0;
|
|
}
|
|
|
|
static cell_t GetCommandFlags(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
char *name;
|
|
int flags;
|
|
pContext->LocalToString(params[1], &name);
|
|
|
|
return (s_CommandFlagsHelper.GetFlags(name, &flags)) ? flags : -1;
|
|
}
|
|
|
|
static cell_t FindFirstConCommand(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
Handle_t hndl;
|
|
ConCmdIter *pIter;
|
|
cell_t *pIsCmd, *pFlags;
|
|
const ConCommandBase *pConCmd;
|
|
const char *desc;
|
|
|
|
pContext->LocalToPhysAddr(params[3], &pIsCmd);
|
|
pContext->LocalToPhysAddr(params[4], &pFlags);
|
|
|
|
pConCmd = icvar->GetCommands();
|
|
|
|
if (pConCmd == NULL)
|
|
{
|
|
return BAD_HANDLE;
|
|
}
|
|
|
|
pContext->StringToLocalUTF8(params[1], params[2], pConCmd->GetName(), NULL);
|
|
*pIsCmd = pConCmd->IsCommand() ? 1 : 0;
|
|
*pFlags = pConCmd->GetFlags();
|
|
|
|
if (params[6])
|
|
{
|
|
desc = pConCmd->GetHelpText();
|
|
pContext->StringToLocalUTF8(params[5], params[6], (desc && desc[0]) ? desc : "", NULL);
|
|
}
|
|
|
|
pIter = new ConCmdIter;
|
|
pIter->pLast = pConCmd;
|
|
|
|
if ((hndl = g_HandleSys.CreateHandle(htConCmdIter, pIter, pContext->GetIdentity(), g_pCoreIdent, NULL))
|
|
== BAD_HANDLE)
|
|
{
|
|
delete pIter;
|
|
return BAD_HANDLE;
|
|
}
|
|
|
|
return hndl;
|
|
}
|
|
|
|
static cell_t FindNextConCommand(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
HandleError err;
|
|
ConCmdIter *pIter;
|
|
cell_t *pIsCmd, *pFlags;
|
|
const char *desc;
|
|
HandleSecurity sec(pContext->GetIdentity(), g_pCoreIdent);
|
|
|
|
if ((err = g_HandleSys.ReadHandle(params[1], htConCmdIter, &sec, (void **)&pIter)) != HandleError_None)
|
|
{
|
|
return pContext->ThrowNativeError("Invalid Handle %x (error %d)", params[1], err);
|
|
}
|
|
|
|
if (pIter->pLast == NULL)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if ((pIter->pLast = pIter->pLast->GetNext()) == NULL)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
pContext->LocalToPhysAddr(params[4], &pIsCmd);
|
|
pContext->LocalToPhysAddr(params[5], &pFlags);
|
|
|
|
pContext->StringToLocalUTF8(params[2], params[3], pIter->pLast->GetName(), NULL);
|
|
*pIsCmd = pIter->pLast->IsCommand() ? 1 : 0;
|
|
*pFlags = pIter->pLast->GetFlags();
|
|
|
|
if (params[7])
|
|
{
|
|
desc = pIter->pLast->GetHelpText();
|
|
pContext->StringToLocalUTF8(params[6], params[7], (desc && desc[0]) ? desc : "", NULL);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static cell_t SendConVarValue(IPluginContext *pContext, const cell_t *params)
|
|
{
|
|
Handle_t hndl = static_cast<Handle_t>(params[2]);
|
|
HandleError err;
|
|
ConVar *pConVar;
|
|
|
|
char *value;
|
|
pContext->LocalToString(params[3], &value);
|
|
|
|
if ((err=g_ConVarManager.ReadConVarHandle(hndl, &pConVar))
|
|
!= HandleError_None)
|
|
{
|
|
return pContext->ThrowNativeError("Invalid convar handle %x (error %d)", hndl, err);
|
|
}
|
|
|
|
char data[256];
|
|
bf_write buffer(data, sizeof(data));
|
|
|
|
buffer.WriteUBitLong(NET_SETCONVAR, 5);
|
|
buffer.WriteByte(1);
|
|
buffer.WriteString(pConVar->GetName());
|
|
buffer.WriteString(value);
|
|
|
|
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]);
|
|
}
|
|
|
|
if (pPlayer->IsFakeClient())
|
|
{
|
|
return pContext->ThrowNativeError("Client %d is fake and cannot be targeted", params[1]);
|
|
}
|
|
|
|
INetChannel *netchan = static_cast<INetChannel *>(engine->GetPlayerNetInfo(params[1]));
|
|
netchan->SendData(buffer);
|
|
|
|
return 1;
|
|
}
|
|
|
|
REGISTER_NATIVES(consoleNatives)
|
|
{
|
|
{"CreateConVar", sm_CreateConVar},
|
|
{"FindConVar", sm_FindConVar},
|
|
{"HookConVarChange", sm_HookConVarChange},
|
|
{"UnhookConVarChange", sm_UnhookConVarChange},
|
|
{"GetConVarBool", sm_GetConVarBool},
|
|
{"SetConVarBool", sm_SetConVarNum},
|
|
{"GetConVarInt", sm_GetConVarInt},
|
|
{"SetConVarInt", sm_SetConVarNum},
|
|
{"GetConVarFloat", sm_GetConVarFloat},
|
|
{"SetConVarFloat", sm_SetConVarFloat},
|
|
{"GetConVarString", sm_GetConVarString},
|
|
{"SetConVarString", sm_SetConVarString},
|
|
{"GetConVarFlags", sm_GetConVarFlags},
|
|
{"SetConVarFlags", sm_SetConVarFlags},
|
|
{"ResetConVar", sm_ResetConVar},
|
|
{"GetConVarName", sm_GetConVarName},
|
|
{"GetConVarBounds", sm_GetConVarBounds},
|
|
{"SetConVarBounds", sm_SetConVarBounds},
|
|
{"QueryClientConVar", sm_QueryClientConVar},
|
|
{"RegServerCmd", sm_RegServerCmd},
|
|
{"RegConsoleCmd", sm_RegConsoleCmd},
|
|
{"GetCmdArgString", sm_GetCmdArgString},
|
|
{"GetCmdArgs", sm_GetCmdArgs},
|
|
{"GetCmdArg", sm_GetCmdArg},
|
|
{"PrintToServer", sm_PrintToServer},
|
|
{"PrintToConsole", sm_PrintToConsole},
|
|
{"RegAdminCmd", sm_RegAdminCmd},
|
|
{"ServerCommand", sm_ServerCommand},
|
|
{"InsertServerCommand", sm_InsertServerCommand},
|
|
{"ServerExecute", sm_ServerExecute},
|
|
{"ClientCommand", sm_ClientCommand},
|
|
{"FakeClientCommand", FakeClientCommand},
|
|
{"ReplyToCommand", ReplyToCommand},
|
|
{"GetCmdReplySource", GetCmdReplyTarget},
|
|
{"SetCmdReplySource", SetCmdReplyTarget},
|
|
{"GetCommandIterator", GetCommandIterator},
|
|
{"ReadCommandIterator", ReadCommandIterator},
|
|
{"CheckCommandAccess", CheckCommandAccess},
|
|
{"FakeClientCommandEx", FakeClientCommandEx},
|
|
{"IsChatTrigger", IsChatTrigger},
|
|
{"SetCommandFlags", SetCommandFlags},
|
|
{"GetCommandFlags", GetCommandFlags},
|
|
{"FindFirstConCommand", FindFirstConCommand},
|
|
{"FindNextConCommand", FindNextConCommand},
|
|
{"SendConVarValue", SendConVarValue},
|
|
{NULL, NULL}
|
|
};
|