2 * ===============================================================
3 * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved.
4 * ===============================================================
6 * This file is not open source and may not be copied without explicit
7 * written permission of AlliedModders LLC. This file may not be redistributed
8 * in whole or significant part.
9 * For information, see LICENSE.txt or http://www.sourcemod.net/license.php
14 #include "ConVarManager.h"
15 #include "PluginSys.h"
16 #include "ForwardSys.h"
17 #include "HandleSys.h"
18 #include "sm_srvcmds.h"
19 #include "sm_stringutil.h"
20 #include <sh_vector.h>
22 ConVarManager g_ConVarManager;
24 const ParamType CONVARCHANGE_PARAMS[] = {Param_Cell, Param_String, Param_String};
25 typedef List<const ConVar *> ConVarList;
27 ConVarManager::ConVarManager() : m_ConVarType(0)
29 /* Create a convar lookup trie */
30 m_ConVarCache = sm_trie_create();
33 ConVarManager::~ConVarManager()
35 List<ConVarInfo *>::iterator iter;
37 /* Destroy our convar lookup trie */
38 sm_trie_destroy(m_ConVarCache);
40 /* Destroy all the ConVarInfo structures */
41 for (iter = m_ConVars.begin(); iter != m_ConVars.end(); iter++)
49 void ConVarManager::OnSourceModAllInitialized()
53 /* Set up access rights for the 'ConVar' handle type */
54 sec.access[HandleAccess_Read] = 0;
55 sec.access[HandleAccess_Delete] = HANDLE_RESTRICT_IDENTITY | HANDLE_RESTRICT_OWNER;
56 sec.access[HandleAccess_Clone] = HANDLE_RESTRICT_IDENTITY | HANDLE_RESTRICT_OWNER;
58 /* Create the 'ConVar' handle type */
59 m_ConVarType = g_HandleSys.CreateType("ConVar", this, 0, NULL, &sec, g_pCoreIdent, NULL);
61 /* Add the 'convars' option to the 'sm' console command */
62 g_RootMenu.AddRootConsoleCommand("convars", "View convars created by a plugin", this);
65 void ConVarManager::OnSourceModShutdown()
67 IChangeableForward *fwd;
68 List<ConVarInfo *>::iterator i;
70 /* Iterate list of ConVarInfo structures */
71 for (i = m_ConVars.begin(); i != m_ConVars.end(); i++)
73 fwd = (*i)->changeForward;
75 /* Free any convar-change forwards that still exist */
78 g_Forwards.ReleaseForward(fwd);
82 /* Remove the 'convars' option from the 'sm' console command */
83 g_RootMenu.RemoveRootConsoleCommand("convars", this);
85 /* Remove the 'ConVar' handle type */
86 g_HandleSys.RemoveType(m_ConVarType, g_pCoreIdent);
89 void ConVarManager::OnPluginUnloaded(IPlugin *plugin)
91 ConVarList *pConVarList;
93 /* If plugin has a convar list, free its memory */
94 if (plugin->GetProperty("ConVarList", (void **)&pConVarList, true))
100 void ConVarManager::OnHandleDestroy(HandleType_t type, void *object)
103 ConVar *pConVar = static_cast<ConVar *>(object);
105 /* Find convar in lookup trie */
106 sm_trie_retrieve(m_ConVarCache, pConVar->GetName(), reinterpret_cast<void **>(&info));
108 /* If convar was created by SourceMod plugin... */
111 /* Delete string allocations */
112 delete [] pConVar->GetName();
113 delete [] pConVar->GetDefault();
114 delete [] pConVar->GetHelpText();
116 /* Then unlink it from SourceMM */
117 g_SMAPI->UnregisterConCmdBase(g_PLAPI, pConVar);
121 void ConVarManager::OnRootConsoleCommand(const char *command, unsigned int argcount)
125 /* Get plugin index that was passed */
126 int id = atoi(g_RootMenu.GetArgument(2));
128 /* Get plugin object */
129 CPlugin *plugin = g_PluginSys.GetPluginByOrder(id);
133 g_RootMenu.ConsolePrint("[SM] Plugin index %d not found.", id);
137 /* Get plugin name */
138 const sm_plugininfo_t *plinfo = plugin->GetPublicInfo();
139 const char *plname = IS_STR_FILLED(plinfo->name) ? plinfo->name : plugin->GetFilename();
141 ConVarList *pConVarList;
142 ConVarList::iterator iter;
144 /* If no convar list... */
145 if (!plugin->GetProperty("ConVarList", (void **)&pConVarList))
147 g_RootMenu.ConsolePrint("[SM] No convars found for: %s", plname);
151 g_RootMenu.ConsolePrint("[SM] Listing %d convars for: %s", pConVarList->size(), plname);
152 g_RootMenu.ConsolePrint(" %-32.31s %s", "[Name]", "[Value]");
154 /* Iterate convar list and display each one */
155 for (iter = pConVarList->begin(); iter != pConVarList->end(); iter++)
157 const ConVar *pConVar = (*iter);
158 g_RootMenu.ConsolePrint(" %-32.31s %s", pConVar->GetName(), pConVar->GetString());
164 /* Display usage of subcommand */
165 g_RootMenu.ConsolePrint("[SM] Usage: sm convars <plugin #>");
168 Handle_t ConVarManager::CreateConVar(IPluginContext *pContext, const char *name, const char *defaultVal, const char *description, int flags, bool hasMin, float min, bool hasMax, float max)
170 ConVar *pConVar = NULL;
171 ConVarInfo *pInfo = NULL;
174 /* Find out if the convar exists already */
175 pConVar = icvar->FindVar(name);
177 /* If the convar already exists... */
180 /* First find out if we already have a handle to it */
181 if (sm_trie_retrieve(m_ConVarCache, name, (void **)&pInfo))
183 return pInfo->handle;
185 /* If we don't, then create a new handle from the convar */
186 hndl = g_HandleSys.CreateHandle(m_ConVarType, pConVar, NULL, g_pCoreIdent, NULL);
188 /* Create and initialize ConVarInfo structure */
189 pInfo = new ConVarInfo();
190 pInfo->handle = hndl;
191 pInfo->sourceMod = false;
192 pInfo->changeForward = NULL;
193 pInfo->origCallback = pConVar->GetCallback();
195 /* Insert struct into caches */
196 m_ConVars.push_back(pInfo);
197 sm_trie_insert(m_ConVarCache, name, pInfo);
203 // To prevent creating a convar that has the same name as a console command... ugh
204 ConCommandBase *pBase = icvar->GetCommands();
208 if (pBase->IsCommand() && strcmp(pBase->GetName(), name) == 0)
213 pBase = const_cast<ConCommandBase *>(pBase->GetNext());
216 /* Since an existing convar (or concmd with the same name) was not found , now we can finally create it */
217 pConVar = new ConVar(sm_strdup(name), sm_strdup(defaultVal), flags, sm_strdup(description), hasMin, min, hasMax, max);
219 /* Add convar to plugin's list */
220 AddConVarToPluginList(pContext, pConVar);
222 /* Create a handle from the new convar */
223 hndl = g_HandleSys.CreateHandle(m_ConVarType, pConVar, NULL, g_pCoreIdent, NULL);
225 /* Create and initialize ConVarInfo structure */
226 pInfo = new ConVarInfo();
227 pInfo->handle = hndl;
228 pInfo->sourceMod = true;
229 pInfo->changeForward = NULL;
230 pInfo->origCallback = NULL;
232 /* Insert struct into caches */
233 m_ConVars.push_back(pInfo);
234 sm_trie_insert(m_ConVarCache, name, pInfo);
239 Handle_t ConVarManager::FindConVar(const char *name)
241 ConVar *pConVar = NULL;
245 /* Search for convar */
246 pConVar = icvar->FindVar(name);
248 /* If it doesn't exist, then return an invalid handle */
254 /* At this point, the convar exists. So, find out if we already have a handle */
255 if (sm_trie_retrieve(m_ConVarCache, name, (void **)&pInfo))
257 return pInfo->handle;
260 /* If we don't have a handle, then create a new one */
261 hndl = g_HandleSys.CreateHandle(m_ConVarType, pConVar, NULL, g_pCoreIdent, NULL);
263 /* Create and initilize ConVarInfo structure */
264 pInfo = new ConVarInfo();
265 pInfo->handle = hndl;
266 pInfo->sourceMod = false;
267 pInfo->changeForward = NULL;
268 pInfo->origCallback = pConVar->GetCallback();
270 /* Insert struct into our caches */
271 m_ConVars.push_back(pInfo);
272 sm_trie_insert(m_ConVarCache, name, pInfo);
277 void ConVarManager::HookConVarChange(ConVar *pConVar, IPluginFunction *pFunction)
280 IChangeableForward *pForward;
282 /* Find the convar in the lookup trie */
283 if (sm_trie_retrieve(m_ConVarCache, pConVar->GetName(), (void **)&pInfo))
285 /* Get the forward */
286 pForward = pInfo->changeForward;
288 /* If forward does not exist, create it */
291 pForward = g_Forwards.CreateForwardEx(NULL, ET_Ignore, 3, CONVARCHANGE_PARAMS);
292 pInfo->changeForward = pForward;
294 /* Install our own callback */
295 pConVar->InstallChangeCallback(OnConVarChanged);
298 /* Add function to forward's list */
299 pForward->AddFunction(pFunction);
303 void ConVarManager::UnhookConVarChange(ConVar *pConVar, IPluginFunction *pFunction)
306 IChangeableForward *pForward;
307 IPluginContext *pContext = pFunction->GetParentContext();
309 /* Find the convar in the lookup trie */
310 if (sm_trie_retrieve(m_ConVarCache, pConVar->GetName(), (void **)&pInfo))
312 /* Get the forward */
313 pForward = pInfo->changeForward;
315 /* If the forward doesn't exist, we can't unhook anything */
318 pContext->ThrowNativeError("Convar \"%s\" has no active hook", pConVar->GetName());
322 /* Remove the function from the forward's list */
323 if (!pForward->RemoveFunction(pFunction))
325 pContext->ThrowNativeError("Invalid hook callback specified for convar \"%s\"", pConVar->GetName());
329 /* If the forward now has 0 functions in it... */
330 if (pForward->GetFunctionCount() == 0)
332 /* Free this forward */
333 g_Forwards.ReleaseForward(pForward);
334 pInfo->changeForward = NULL;
336 /* Put back the original convar callback */
337 pConVar->InstallChangeCallback(pInfo->origCallback);
342 void ConVarManager::AddConVarToPluginList(IPluginContext *pContext, const ConVar *pConVar)
344 ConVarList *pConVarList;
345 ConVarList::iterator iter;
346 bool inserted = false;
347 const char *orig = pConVar->GetName();
349 IPlugin *plugin = g_PluginSys.FindPluginByContext(pContext->GetContext());
351 /* Check plugin for an existing convar list */
352 if (!plugin->GetProperty("ConVarList", (void **)&pConVarList))
354 pConVarList = new ConVarList();
355 plugin->SetProperty("ConVarList", pConVarList);
358 /* Insert convar into list which is sorted alphabetically */
359 for (iter = pConVarList->begin(); iter != pConVarList->end(); iter++)
361 if (strcmp(orig, (*iter)->GetName()) < 0)
363 pConVarList->insert(iter, pConVar);
371 pConVarList->push_back(pConVar);
375 void ConVarManager::OnConVarChanged(ConVar *pConVar, const char *oldValue)
377 /* If the values are the same, exit early in order to not trigger callbacks */
378 if (strcmp(pConVar->GetString(), oldValue) == 0)
383 Trie *pCache = g_ConVarManager.GetConVarCache();
386 /* Find the convar in the lookup trie */
387 sm_trie_retrieve(pCache, pConVar->GetName(), (void **)&pInfo);
389 FnChangeCallback origCallback = pInfo->origCallback;
390 IChangeableForward *pForward = pInfo->changeForward;
392 /* If there was a change callback installed previously, call it */
395 origCallback(pConVar, oldValue);
398 /* Now call forwards in plugins that have hooked this */
399 pForward->PushCell(pInfo->handle);
400 pForward->PushString(oldValue);
401 pForward->PushString(pConVar->GetString());
402 pForward->Execute(NULL);