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 ConVarManager::ConVarManager() : m_ConVarType(0)
26 // Create a convar lookup trie
27 m_ConVarCache = sm_trie_create();
30 ConVarManager::~ConVarManager()
32 List<ConVarInfo *>::iterator iter;
34 // Destroy our convar lookup trie
35 sm_trie_destroy(m_ConVarCache);
37 // Destroy all the ConVarInfo structures
38 for (iter = m_ConVars.begin(); iter != m_ConVars.end(); iter++)
46 void ConVarManager::OnSourceModAllInitialized()
50 // Set up access rights for the 'ConVar' handle type
51 sec.access[HandleAccess_Read] = 0;
52 sec.access[HandleAccess_Delete] = HANDLE_RESTRICT_IDENTITY | HANDLE_RESTRICT_OWNER;
53 sec.access[HandleAccess_Clone] = HANDLE_RESTRICT_IDENTITY | HANDLE_RESTRICT_OWNER;
55 // Create the 'ConVar' handle type
56 m_ConVarType = g_HandleSys.CreateType("ConVar", this, 0, NULL, &sec, g_pCoreIdent, NULL);
58 // Add the 'cvars' option to the 'sm' console command
59 g_RootMenu.AddRootConsoleCommand("convars", "View convars created by a plugin", this);
62 void ConVarManager::OnSourceModShutdown()
64 IChangeableForward *fwd;
65 List<ConVarInfo *>::iterator i;
67 // Iterate list of ConVarInfo structures
68 for (i = m_ConVars.begin(); i != m_ConVars.end(); i++)
70 fwd = (*i)->changeForward;
72 // Free any convar-change forwards that still exist
75 g_Forwards.ReleaseForward(fwd);
79 // Remove the 'cvars' option from the 'sm' console command
80 g_RootMenu.RemoveRootConsoleCommand("convars", this);
82 // Remove the 'ConVar' handle type
83 g_HandleSys.RemoveType(m_ConVarType, g_pCoreIdent);
86 void ConVarManager::OnPluginDestroyed(IPlugin *plugin)
88 CVector<ConVar *> *cvarList;
90 // If plugin has a convar list, free its memory
91 if (plugin->GetProperty("ConVarList", reinterpret_cast<void **>(&cvarList), true))
97 void ConVarManager::OnHandleDestroy(HandleType_t type, void *object)
100 ConVar *pConVar = static_cast<ConVar *>(object);
102 // Find convar in lookup trie
103 sm_trie_retrieve(m_ConVarCache, pConVar->GetName(), reinterpret_cast<void **>(&info));
105 // If convar was created by SourceMod plugin...
108 // Delete string allocations
109 delete [] pConVar->GetName();
110 delete [] pConVar->GetDefault();
111 delete [] pConVar->GetHelpText();
113 // Then unlink it from SourceMM
114 g_SMAPI->UnregisterConCmdBase(g_PLAPI, pConVar);
118 void ConVarManager::OnRootConsoleCommand(const char *command, unsigned int argcount)
124 // Get plugin index that was passed
125 int num = atoi(g_RootMenu.GetArgument(2));
127 // If invalid plugin index...
128 if (num < 1 || num > (int)g_PluginSys.GetPluginCount())
130 g_RootMenu.ConsolePrint("[SM] Plugin index %d not found.", num);
135 CPlugin *pl = g_PluginSys.GetPluginByOrder(num);
137 CVector<ConVar *> *cvarList = NULL;
139 // Get convar list from 'ConVar' property
140 pl->GetProperty("ConVar", reinterpret_cast<void **>(&cvarList));
142 // If no cvar list...
143 if (cvarList == NULL)
145 g_RootMenu.ConsolePrint("[SM] No convars for \"%s\"", pl->GetPublicInfo()->name);
149 g_RootMenu.ConsolePrint("[SM] Displaying convars for \"%s\"", pl->GetPublicInfo()->name);
151 // Iterate convar list and display each one
152 for (size_t i = 0; i < cvarList->size(); i++, id++)
154 ConVar *pConVar = (*cvarList)[i];
155 g_RootMenu.ConsolePrint(" %02d \"%s\" = \"%s\"", id, pConVar->GetName(), pConVar->GetString());
161 // Display usage of subcommand
162 g_RootMenu.ConsolePrint("[SM] Usage: sm convars <plugin #>");
165 Handle_t ConVarManager::CreateConVar(IPluginContext *pContext, const char *name, const char *defaultVal, const char *helpText, int flags, bool hasMin, float min, bool hasMax, float max)
167 ConVar *pConVar = NULL;
168 ConVarInfo *info = NULL;
169 CVector<ConVar *> *cvarList = NULL;
172 // Find out if the convar exists already
173 pConVar = icvar->FindVar(name);
175 // If the convar already exists...
178 // First check if we already have a handle to it
179 if (sm_trie_retrieve(m_ConVarCache, name, reinterpret_cast<void **>(&info)))
181 // If we do, then return that handle
184 // If we don't, then create a new handle from the convar
185 hndl = g_HandleSys.CreateHandle(m_ConVarType, pConVar, NULL, g_pCoreIdent, NULL);
187 info = new ConVarInfo;
189 info->sourceMod = false;
190 info->changeForward = NULL;
191 info->origCallback = pConVar->GetCallback();
193 m_ConVars.push_back(info);
199 // To prevent creating a convar that has the same name as a console command... ugh
200 ConCommandBase *pBase = icvar->GetCommands();
204 if (strcmp(pBase->GetName(), name) == 0)
206 return pContext->ThrowNativeError("Convar \"%s\" was not created. A console command with the same name already exists.", name);
209 pBase = const_cast<ConCommandBase *>(pBase->GetNext());
212 // Since we didn't find an existing convar (or concmd with the same name), now we can finally create it!
213 pConVar = new ConVar(sm_strdup(name), sm_strdup(defaultVal), flags, sm_strdup(helpText), hasMin, min, hasMax, max);
215 // Find plugin creating convar
216 IPlugin *pl = g_PluginSys.FindPluginByContext(pContext->GetContext());
218 // Get convar list from 'ConVar' property of plugin
219 pl->GetProperty("ConVarList", reinterpret_cast<void **>(&cvarList));
221 // If 'ConVar' property doesn't exist...
222 if (cvarList == NULL)
225 cvarList = new CVector<ConVar *>;
226 pl->SetProperty("ConVarList", cvarList);
229 // Add new convar to plugin's list
230 cvarList->push_back(pConVar);
232 // Create a handle from the new convar
233 hndl = g_HandleSys.CreateHandle(m_ConVarType, pConVar, NULL, g_pCoreIdent, NULL);
235 info = new ConVarInfo;
237 info->sourceMod = true;
238 info->changeForward = NULL;
239 info->origCallback = NULL;
241 m_ConVars.push_back(info);
243 // Insert the handle into our lookup trie
244 sm_trie_insert(m_ConVarCache, name, info);
249 Handle_t ConVarManager::FindConVar(const char *name)
251 ConVar *pConVar = NULL;
252 ConVarInfo *info = NULL;
256 pConVar = icvar->FindVar(name);
258 // If it doesn't exist, then return an invalid handle
264 // At this point, convar exists, so find out if we already have a handle for it
265 if (sm_trie_retrieve(m_ConVarCache, name, reinterpret_cast<void **>(&info)))
267 // If we do, then return that handle
271 // If we don't, then create a new handle from the convar
272 hndl = g_HandleSys.CreateHandle(m_ConVarType, pConVar, NULL, g_pCoreIdent, NULL);
274 info = new ConVarInfo;
276 info->sourceMod = false;
277 info->changeForward = NULL;
278 info->origCallback = pConVar->GetCallback();
280 m_ConVars.push_back(info);
282 // Insert the handle into our cache
283 sm_trie_insert(m_ConVarCache, name, info);
288 void ConVarManager::HookConVarChange(IPluginContext *pContext, ConVar *pConVar, funcid_t funcid)
290 IPluginFunction *func = pContext->GetFunctionById(funcid);
291 IChangeableForward *fwd = NULL;
293 ConVarInfo *info = NULL;
295 // This shouldn't happen...
298 pContext->ThrowNativeError("Invalid function specified");
302 // Create a forward name
303 UTIL_Format(fwdName, sizeof(fwdName), "ConVar.%s", pConVar->GetName());
305 // First find out if the forward already exists
306 g_Forwards.FindForward(fwdName, &fwd);
308 // If the forward doesn't exist...
311 // This is the forward's parameter type list
312 ParamType p[] = {Param_Cell, Param_String, Param_String};
314 // Create the forward
315 fwd = g_Forwards.CreateForwardEx(fwdName, ET_Ignore, 3, p);
317 // Find the convar in the lookup trie
318 if (sm_trie_retrieve(m_ConVarCache, pConVar->GetName(), reinterpret_cast<void **>(&info)))
320 // Set the convar's forward to the newly created one
321 info->changeForward = fwd;
323 // Set the convar's callback to our static one
324 pConVar->InstallChangeCallback(OnConVarChanged);
328 // Add the function to the forward's list
329 fwd->AddFunction(func);
332 void ConVarManager::UnhookConVarChange(IPluginContext *pContext, ConVar *pConVar, funcid_t funcid)
334 IPluginFunction *func = pContext->GetFunctionById(funcid);
335 IChangeableForward *fwd = NULL;
336 ConVarInfo *info = NULL;
338 // This shouldn't happen...
341 pContext->ThrowNativeError("Invalid function specified");
345 // Find the convar in the lookup trie
346 if (sm_trie_retrieve(m_ConVarCache, pConVar->GetName(), reinterpret_cast<void **>(&info)))
349 fwd = info->changeForward;
351 // If the forward doesn't exist, we can't unhook anything
354 pContext->ThrowNativeError("Convar \"%s\" has no active hook", pConVar->GetName());
358 // Remove the function from the forward's list
359 if (!fwd->RemoveFunction(func))
361 pContext->ThrowNativeError("Invalid hook callback specified for convar \"%s\"", pConVar->GetName());
365 // If the forward now has 0 functions in it...
366 if (fwd->GetFunctionCount() == 0)
369 g_Forwards.ReleaseForward(fwd);
370 info->changeForward = NULL;
372 // Put the back the original convar callback
373 pConVar->InstallChangeCallback(info->origCallback);
378 void ConVarManager::OnConVarChanged(ConVar *pConVar, const char *oldValue)
380 // If the values are the same...
381 if (strcmp(pConVar->GetString(), oldValue) == 0)
383 // Exit early in order to not trigger callbacks
387 Trie *cache = g_ConVarManager.GetConVarCache();
390 // Find the convar in the lookup trie
391 sm_trie_retrieve(cache, pConVar->GetName(), reinterpret_cast<void **>(&info));
393 FnChangeCallback origCallback = info->origCallback;
394 IChangeableForward *fwd = info->changeForward;
396 // If there was a change callback installed previously, call it
399 origCallback(pConVar, oldValue);
402 // Now call forwards in plugins that have hooked this
403 fwd->PushCell(info->handle);
404 fwd->PushString(pConVar->GetString());
405 fwd->PushString(oldValue);