3 * ===============================================================
4 * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved.
5 * ===============================================================
7 * This file is not open source and may not be copied without explicit
8 * written permission of AlliedModders LLC. This file may not be redistributed
9 * in whole or significant part.
10 * For information, see LICENSE.txt or http://www.sourcemod.net/license.php
15 #include "sm_globals.h"
16 #include "HalfLife2.h"
17 #include "sourcemm_api.h"
18 #include "HandleSys.h"
19 #include "ConVarManager.h"
20 #include "ConCmdManager.h"
21 #include "PluginSys.h"
22 #include "sm_stringutil.h"
23 #include "PlayerManager.h"
24 #include "ChatTriggers.h"
25 #include <inetchannel.h>
28 #define NET_SETCONVAR 5
32 ConVarBound_Upper = 0,
36 static void ReplicateConVar(ConVar *pConVar)
38 int maxClients = g_Players.GetMaxClients();
41 bf_write buffer(data, sizeof(data));
43 buffer.WriteUBitLong(NET_SETCONVAR, 5);
45 buffer.WriteString(pConVar->GetName());
46 buffer.WriteString(pConVar->GetString());
48 for (int i = 1; i <= maxClients; i++)
50 CPlayer *pPlayer = g_Players.GetPlayerByIndex(i);
52 if (pPlayer && pPlayer->IsInGame() && !pPlayer->IsFakeClient())
54 INetChannel *netchan = static_cast<INetChannel *>(engine->GetPlayerNetInfo(i));
55 netchan->SendData(buffer);
60 static void NotifyConVar(ConVar *pConVar)
62 IGameEvent *pEvent = gameevents->CreateEvent("server_cvar");
63 pEvent->SetString("cvarname", pConVar->GetName());
64 if (pConVar->IsBitSet(FCVAR_PROTECTED))
66 pEvent->SetString("cvarvalue", "***PROTECTED***");
68 pEvent->SetString("cvarvalue", pConVar->GetString());
71 gameevents->FireEvent(pEvent);
74 static cell_t sm_CreateConVar(IPluginContext *pContext, const cell_t *params)
76 char *name, *defaultVal, *helpText;
78 pContext->LocalToString(params[1], &name);
80 // While the engine seems to accept a blank convar name, it causes a crash upon server quit
81 if (name == NULL || strcmp(name, "") == 0)
83 return pContext->ThrowNativeError("Convar with blank name is not permitted");
86 pContext->LocalToString(params[2], &defaultVal);
87 pContext->LocalToString(params[3], &helpText);
89 bool hasMin = params[5] ? true : false;
90 bool hasMax = params[7] ? true : false;
91 float min = sp_ctof(params[6]);
92 float max = sp_ctof(params[8]);
94 Handle_t hndl = g_ConVarManager.CreateConVar(pContext, name, defaultVal, helpText, params[4], hasMin, min, hasMax, max);
96 if (hndl == BAD_HANDLE)
98 return pContext->ThrowNativeError("Convar \"%s\" was not created. A console command with the same name already exists.", name);
104 static cell_t sm_FindConVar(IPluginContext *pContext, const cell_t *params)
108 pContext->LocalToString(params[1], &name);
110 return g_ConVarManager.FindConVar(name);
113 static cell_t sm_HookConVarChange(IPluginContext *pContext, const cell_t *params)
115 Handle_t hndl = static_cast<Handle_t>(params[1]);
119 if ((err=g_HandleSys.ReadHandle(hndl, g_ConVarManager.GetHandleType(), NULL, (void **)&pConVar))
122 return pContext->ThrowNativeError("Invalid convar handle %x (error %d)", hndl, err);
125 IPluginFunction *pFunction = pContext->GetFunctionById(params[2]);
129 return pContext->ThrowNativeError("Invalid function id (%X)", params[2]);
132 g_ConVarManager.HookConVarChange(pConVar, pFunction);
137 static cell_t sm_UnhookConVarChange(IPluginContext *pContext, const cell_t *params)
139 Handle_t hndl = static_cast<Handle_t>(params[1]);
143 if ((err=g_HandleSys.ReadHandle(hndl, g_ConVarManager.GetHandleType(), NULL, (void **)&pConVar))
146 return pContext->ThrowNativeError("Invalid convar handle %x (error %d)", hndl, err);
149 IPluginFunction *pFunction = pContext->GetFunctionById(params[2]);
153 return pContext->ThrowNativeError("Invalid function id (%X)", params[2]);
156 g_ConVarManager.UnhookConVarChange(pConVar, pFunction);
161 static cell_t sm_GetConVarBool(IPluginContext *pContext, const cell_t *params)
163 Handle_t hndl = static_cast<Handle_t>(params[1]);
167 if ((err=g_HandleSys.ReadHandle(hndl, g_ConVarManager.GetHandleType(), NULL, (void **)&pConVar))
170 return pContext->ThrowNativeError("Invalid convar handle %x (error %d)", hndl, err);
173 return pConVar->GetBool();
176 static cell_t sm_GetConVarInt(IPluginContext *pContext, const cell_t *params)
178 Handle_t hndl = static_cast<Handle_t>(params[1]);
182 if ((err=g_HandleSys.ReadHandle(hndl, g_ConVarManager.GetHandleType(), NULL, (void **)&pConVar))
185 return pContext->ThrowNativeError("Invalid convar handle %x (error %d)", hndl, err);
188 return pConVar->GetInt();
191 /* This handles both SetConVarBool() and SetConVarInt() */
192 static cell_t sm_SetConVarNum(IPluginContext *pContext, const cell_t *params)
194 Handle_t hndl = static_cast<Handle_t>(params[1]);
198 if ((err=g_HandleSys.ReadHandle(hndl, g_ConVarManager.GetHandleType(), NULL, (void **)&pConVar))
201 return pContext->ThrowNativeError("Invalid convar handle %x (error %d)", hndl, err);
204 pConVar->SetValue(params[2]);
206 /* Should we replicate it? */
207 if (params[3] && pConVar->IsBitSet(FCVAR_REPLICATED))
209 ReplicateConVar(pConVar);
212 /* Should we notify clients? */
213 if (params[4] && pConVar->IsBitSet(FCVAR_NOTIFY))
215 NotifyConVar(pConVar);
221 static cell_t sm_GetConVarFloat(IPluginContext *pContext, const cell_t *params)
223 Handle_t hndl = static_cast<Handle_t>(params[1]);
227 if ((err=g_HandleSys.ReadHandle(hndl, g_ConVarManager.GetHandleType(), NULL, (void **)&pConVar))
230 return pContext->ThrowNativeError("Invalid convar handle %x (error %d)", hndl, err);
233 float value = pConVar->GetFloat();
235 return sp_ftoc(value);
238 static cell_t sm_SetConVarFloat(IPluginContext *pContext, const cell_t *params)
240 Handle_t hndl = static_cast<Handle_t>(params[1]);
244 if ((err=g_HandleSys.ReadHandle(hndl, g_ConVarManager.GetHandleType(), NULL, (void **)&pConVar))
247 return pContext->ThrowNativeError("Invalid convar handle %x (error %d)", hndl, err);
250 float value = sp_ctof(params[2]);
251 pConVar->SetValue(value);
253 /* Should we replicate it? */
254 if (params[3] && pConVar->IsBitSet(FCVAR_REPLICATED))
256 ReplicateConVar(pConVar);
259 /* Should we notify clients? */
260 if (params[4] && pConVar->IsBitSet(FCVAR_NOTIFY))
262 NotifyConVar(pConVar);
268 static cell_t sm_GetConVarString(IPluginContext *pContext, const cell_t *params)
270 Handle_t hndl = static_cast<Handle_t>(params[1]);
274 if ((err=g_HandleSys.ReadHandle(hndl, g_ConVarManager.GetHandleType(), NULL, (void **)&pConVar))
277 return pContext->ThrowNativeError("Invalid convar handle %x (error %d)", hndl, err);
280 pContext->StringToLocalUTF8(params[2], params[3], pConVar->GetString(), NULL);
285 static cell_t sm_SetConVarString(IPluginContext *pContext, const cell_t *params)
287 Handle_t hndl = static_cast<Handle_t>(params[1]);
291 if ((err=g_HandleSys.ReadHandle(hndl, g_ConVarManager.GetHandleType(), NULL, (void **)&pConVar))
294 return pContext->ThrowNativeError("Invalid convar handle %x (error %d)", hndl, err);
298 pContext->LocalToString(params[2], &value);
300 pConVar->SetValue(value);
302 /* Should we replicate it? */
303 if (params[3] && pConVar->IsBitSet(FCVAR_REPLICATED))
305 ReplicateConVar(pConVar);
308 /* Should we notify clients? */
309 if (params[4] && pConVar->IsBitSet(FCVAR_NOTIFY))
311 NotifyConVar(pConVar);
317 static cell_t sm_GetConVarFlags(IPluginContext *pContext, const cell_t *params)
319 Handle_t hndl = static_cast<Handle_t>(params[1]);
323 if ((err=g_HandleSys.ReadHandle(hndl, g_ConVarManager.GetHandleType(), NULL, (void **)&pConVar))
326 return pContext->ThrowNativeError("Invalid convar handle %x (error %d)", hndl, err);
329 return pConVar->GetFlags();
332 static cell_t sm_SetConVarFlags(IPluginContext *pContext, const cell_t *params)
334 Handle_t hndl = static_cast<Handle_t>(params[1]);
338 if ((err=g_HandleSys.ReadHandle(hndl, g_ConVarManager.GetHandleType(), NULL, (void **)&pConVar))
341 return pContext->ThrowNativeError("Invalid convar handle %x (error %d)", hndl, err);
344 pConVar->SetFlags(params[2]);
349 static cell_t sm_GetConVarBounds(IPluginContext *pContext, const cell_t *params)
351 Handle_t hndl = static_cast<Handle_t>(params[1]);
355 if ((err=g_HandleSys.ReadHandle(hndl, g_ConVarManager.GetHandleType(), NULL, (void **)&pConVar))
358 return pContext->ThrowNativeError("Invalid convar handle %x (error %d)", hndl, err);
367 case ConVarBound_Upper:
368 hasBound = pConVar->GetMax(bound);
370 case ConVarBound_Lower:
371 hasBound = pConVar->GetMin(bound);
374 return pContext->ThrowNativeError("Invalid ConVarBounds value %d");
377 pContext->LocalToPhysAddr(params[3], &addr);
378 *addr = sp_ftoc(bound);
383 static cell_t sm_SetConVarBounds(IPluginContext *pContext, const cell_t *params)
385 Handle_t hndl = static_cast<Handle_t>(params[1]);
389 if ((err=g_HandleSys.ReadHandle(hndl, g_ConVarManager.GetHandleType(), NULL, (void **)&pConVar))
392 return pContext->ThrowNativeError("Invalid convar handle %x (error %d)", hndl, err);
397 case ConVarBound_Upper:
398 pConVar->SetMax(params[3] ? true : false, sp_ctof(params[4]));
400 case ConVarBound_Lower:
401 pConVar->SetMin(params[3] ? true : false, sp_ctof(params[4]));
404 return pContext->ThrowNativeError("Invalid ConVarBounds value %d");
410 static cell_t sm_GetConVarName(IPluginContext *pContext, const cell_t *params)
412 Handle_t hndl = static_cast<Handle_t>(params[1]);
416 if ((err=g_HandleSys.ReadHandle(hndl, g_ConVarManager.GetHandleType(), NULL, (void **)&pConVar))
419 return pContext->ThrowNativeError("Invalid convar handle %x (error %d)", hndl, err);
422 pContext->StringToLocalUTF8(params[2], params[3], pConVar->GetName(), NULL);
427 static cell_t sm_ResetConVar(IPluginContext *pContext, const cell_t *params)
429 Handle_t hndl = static_cast<Handle_t>(params[1]);
433 if ((err=g_HandleSys.ReadHandle(hndl, g_ConVarManager.GetHandleType(), NULL, (void **)&pConVar))
436 return pContext->ThrowNativeError("Invalid convar handle %x (error %d)", hndl, err);
444 static bool s_QueryAlreadyWarned = false;
446 static cell_t sm_QueryClientConVar(IPluginContext *pContext, const cell_t *params)
450 IPluginFunction *pCallback;
452 if (g_IsOriginalEngine)
454 if (!s_QueryAlreadyWarned)
456 s_QueryAlreadyWarned = true;
457 return pContext->ThrowNativeError("Game does not support client convar querying (one time warning)");
463 pPlayer = g_Players.GetPlayerByIndex(params[1]);
467 return pContext->ThrowNativeError("Client index %d is invalid", params[1]);
470 if (!pPlayer->IsConnected())
472 return pContext->ThrowNativeError("Client %d is not connected", params[1]);
475 /* Trying a query on a bot results in callback not be fired, so don't bother */
476 if (pPlayer->IsFakeClient())
481 pContext->LocalToString(params[2], &name);
482 pCallback = pContext->GetFunctionById(params[3]);
486 return pContext->ThrowNativeError("Invalid function id (%X)", params[3]);
489 return g_ConVarManager.QueryClientConVar(pPlayer->GetEdict(), name, pCallback, params[4]);
492 static cell_t sm_RegServerCmd(IPluginContext *pContext, const cell_t *params)
495 IPluginFunction *pFunction;
497 pContext->LocalToString(params[1], &name);
498 pContext->LocalToString(params[3], &help);
499 pFunction = pContext->GetFunctionById(params[2]);
503 return pContext->ThrowNativeError("Invalid function id (%X)", params[2]);
506 if (!g_ConCmds.AddServerCommand(pFunction, name, help, params[4]))
508 return pContext->ThrowNativeError("Command \"%s\" could not be created. A convar with the same name already exists.", name);
514 static cell_t sm_RegConsoleCmd(IPluginContext *pContext, const cell_t *params)
517 IPluginFunction *pFunction;
519 pContext->LocalToString(params[1], &name);
520 pContext->LocalToString(params[3], &help);
521 pFunction = pContext->GetFunctionById(params[2]);
525 return pContext->ThrowNativeError("Invalid function id (%X)", params[2]);
528 if (!g_ConCmds.AddConsoleCommand(pFunction, name, help, params[4]))
530 return pContext->ThrowNativeError("Command \"%s\" could not be created. A convar with the same name already exists.", name);
536 static cell_t sm_RegAdminCmd(IPluginContext *pContext, const cell_t *params)
540 IPluginFunction *pFunction;
541 FlagBits flags = params[3];
542 int cmdflags = params[6];
544 pContext->LocalToString(params[1], &name);
545 pContext->LocalToString(params[4], &help);
546 pContext->LocalToString(params[5], (char **)&group);
547 pFunction = pContext->GetFunctionById(params[2]);
549 if (group[0] == '\0')
551 CPlugin *pPlugin = g_PluginSys.GetPluginByCtx(pContext->GetContext());
552 group = pPlugin->GetFilename();
557 return pContext->ThrowNativeError("Invalid function id (%X)", params[2]);
560 if (!g_ConCmds.AddAdminCommand(pFunction, name, group, flags, help, cmdflags))
562 return pContext->ThrowNativeError("Command \"%s\" could not be created. A convar with the same name already exists.", name);
568 static cell_t sm_GetCmdArgs(IPluginContext *pContext, const cell_t *params)
570 return engine->Cmd_Argc() - 1;
573 static cell_t sm_GetCmdArg(IPluginContext *pContext, const cell_t *params)
575 const char *arg = engine->Cmd_Argv(params[1]);
577 pContext->StringToLocalUTF8(params[2], params[3], arg, NULL);
582 static cell_t sm_GetCmdArgString(IPluginContext *pContext, const cell_t *params)
584 const char *args = engine->Cmd_Args();
591 pContext->StringToLocalUTF8(params[1], params[2], args, NULL);
596 static cell_t sm_PrintToServer(IPluginContext *pCtx, const cell_t *params)
602 pCtx->LocalToString(params[1], &fmt);
603 size_t res = atcprintf(buffer, sizeof(buffer)-2, fmt, pCtx, params, &arg);
605 buffer[res++] = '\n';
608 META_CONPRINT(buffer);
613 static cell_t sm_PrintToConsole(IPluginContext *pCtx, const cell_t *params)
615 int index = params[1];
616 if ((index < 0) || (index > g_Players.GetMaxClients()))
618 return pCtx->ThrowNativeError("Client index %d is invalid", index);
621 CPlayer *pPlayer = NULL;
624 pPlayer = g_Players.GetPlayerByIndex(index);
625 if (!pPlayer->IsInGame())
627 return pCtx->ThrowNativeError("Client %d is not in game", index);
630 /* Silent fail on bots, engine will crash */
631 if (pPlayer->IsFakeClient())
641 pCtx->LocalToString(params[2], &fmt);
642 size_t res = atcprintf(buffer, sizeof(buffer)-2, fmt, pCtx, params, &arg);
644 buffer[res++] = '\n';
649 engine->ClientPrintf(pPlayer->GetEdict(), buffer);
651 META_CONPRINT(buffer);
657 static cell_t sm_ServerCommand(IPluginContext *pContext, const cell_t *params)
660 size_t len = g_SourceMod.FormatString(buffer, sizeof(buffer)-2, pContext, params, 1);
662 g_SourceMod.SetGlobalTarget(LANG_SERVER);
664 /* One byte for null terminator, one for newline */
665 buffer[len++] = '\n';
668 engine->ServerCommand(buffer);
673 static cell_t sm_InsertServerCommand(IPluginContext *pContext, const cell_t *params)
676 size_t len = g_SourceMod.FormatString(buffer, sizeof(buffer)-2, pContext, params, 1);
678 g_SourceMod.SetGlobalTarget(LANG_SERVER);
680 /* One byte for null terminator, one for newline */
681 buffer[len++] = '\n';
684 engine->InsertServerCommand(buffer);
689 static cell_t sm_ServerExecute(IPluginContext *pContext, const cell_t *params)
691 engine->ServerExecute();
696 static cell_t sm_ClientCommand(IPluginContext *pContext, const cell_t *params)
698 CPlayer *pPlayer = g_Players.GetPlayerByIndex(params[1]);
702 return pContext->ThrowNativeError("Client index %d is invalid", params[1]);
705 if (!pPlayer->IsConnected())
707 return pContext->ThrowNativeError("Client %d is not connected", params[1]);
710 g_SourceMod.SetGlobalTarget(params[1]);
713 g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, 2);
715 engine->ClientCommand(pPlayer->GetEdict(), "%s", buffer);
721 static cell_t FakeClientCommand(IPluginContext *pContext, const cell_t *params)
723 CPlayer *pPlayer = g_Players.GetPlayerByIndex(params[1]);
727 return pContext->ThrowNativeError("Client index %d is invalid", params[1]);
730 if (!pPlayer->IsConnected())
732 return pContext->ThrowNativeError("Client %d is not connected", params[1]);
736 g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, 2);
738 unsigned int old = g_ChatTriggers.SetReplyTo(SM_REPLY_CONSOLE);
739 serverpluginhelpers->ClientCommand(pPlayer->GetEdict(), buffer);
740 g_ChatTriggers.SetReplyTo(old);
745 static cell_t ReplyToCommand(IPluginContext *pContext, const cell_t *params)
747 g_SourceMod.SetGlobalTarget(params[1]);
749 /* Build the format string */
751 size_t len = g_SourceMod.FormatString(buffer, sizeof(buffer)-2, pContext, params, 2);
753 /* If we're printing to the server, shortcut out */
757 buffer[len++] = '\n';
759 META_CONPRINT(buffer);
763 CPlayer *pPlayer = g_Players.GetPlayerByIndex(params[1]);
767 return pContext->ThrowNativeError("Client index %d is invalid", params[1]);
770 if (!pPlayer->IsConnected())
772 return pContext->ThrowNativeError("Client %d is not connected", params[1]);
775 g_SourceMod.SetGlobalTarget(params[1]);
777 unsigned int replyto = g_ChatTriggers.GetReplyTo();
778 if (replyto == SM_REPLY_CONSOLE)
780 buffer[len++] = '\n';
782 engine->ClientPrintf(pPlayer->GetEdict(), buffer);
783 } else if (replyto == SM_REPLY_CHAT) {
789 g_HL2.TextMsg(params[1], HUD_PRINTTALK, buffer);
795 static cell_t GetCmdReplyTarget(IPluginContext *pContext, const cell_t *params)
797 return g_ChatTriggers.GetReplyTo();
800 static cell_t SetCmdReplyTarget(IPluginContext *pContext, const cell_t *params)
802 return g_ChatTriggers.SetReplyTo(params[1]);
805 REGISTER_NATIVES(consoleNatives)
807 {"CreateConVar", sm_CreateConVar},
808 {"FindConVar", sm_FindConVar},
809 {"HookConVarChange", sm_HookConVarChange},
810 {"UnhookConVarChange", sm_UnhookConVarChange},
811 {"GetConVarBool", sm_GetConVarBool},
812 {"SetConVarBool", sm_SetConVarNum},
813 {"GetConVarInt", sm_GetConVarInt},
814 {"SetConVarInt", sm_SetConVarNum},
815 {"GetConVarFloat", sm_GetConVarFloat},
816 {"SetConVarFloat", sm_SetConVarFloat},
817 {"GetConVarString", sm_GetConVarString},
818 {"SetConVarString", sm_SetConVarString},
819 {"GetConVarFlags", sm_GetConVarFlags},
820 {"SetConVarFlags", sm_SetConVarFlags},
821 {"GetConVarName", sm_GetConVarName},
822 {"GetConVarBounds", sm_GetConVarBounds},
823 {"SetConVarBounds", sm_SetConVarBounds},
824 {"ResetConVar", sm_ResetConVar},
825 {"QueryClientConVar", sm_QueryClientConVar},
826 {"RegServerCmd", sm_RegServerCmd},
827 {"RegConsoleCmd", sm_RegConsoleCmd},
828 {"GetCmdArgString", sm_GetCmdArgString},
829 {"GetCmdArgs", sm_GetCmdArgs},
830 {"GetCmdArg", sm_GetCmdArg},
831 {"PrintToServer", sm_PrintToServer},
832 {"PrintToConsole", sm_PrintToConsole},
833 {"RegAdminCmd", sm_RegAdminCmd},
834 {"ServerCommand", sm_ServerCommand},
835 {"InsertServerCommand", sm_InsertServerCommand},
836 {"ServerExecute", sm_ServerExecute},
837 {"ClientCommand", sm_ClientCommand},
838 {"FakeClientCommand", FakeClientCommand},
839 {"ReplyToCommand", ReplyToCommand},
840 {"GetCmdReplySource", GetCmdReplyTarget},
841 {"SetCmdReplySource", SetCmdReplyTarget},