core/ConVarManager.cpp
author Scott Ehlert <ds@alliedmods.net>
Sat Apr 14 04:27:47 2007 +0000 (2007-04-14)
changeset 696 d5cc8d0fa899
parent 653 ef60870067ff
child 729 110c14c99545
permissions -rw-r--r--
Added client convar querying :o
Also added ISourceMod::GetModFolderName() for returning the name of the mod directory by itself
     1 /**
     2  * vim: set ts=4 :
     3  * ===============================================================
     4  * SourceMod (C)2004-2007 AlliedModders LLC.  All rights reserved.
     5  * ===============================================================
     6  *
     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
    11  *
    12  * Version: $Id$
    13  */
    14 
    15 #include "ConVarManager.h"
    16 #include "HalfLife2.h"
    17 #include "PluginSys.h"
    18 #include "ForwardSys.h"
    19 #include "HandleSys.h"
    20 #include "sm_srvcmds.h"
    21 #include "sm_stringutil.h"
    22 #include <sh_vector.h>
    23 
    24 ConVarManager g_ConVarManager;
    25 
    26 SH_DECL_HOOK5_void(IServerGameDLL, OnQueryCvarValueFinished, SH_NOATTRIB, 0, QueryCvarCookie_t, edict_t *, EQueryCvarValueStatus, const char *, const char *);
    27 SH_DECL_HOOK5_void(IServerPluginCallbacks, OnQueryCvarValueFinished, SH_NOATTRIB, 0, QueryCvarCookie_t, edict_t *, EQueryCvarValueStatus, const char *, const char *);
    28 
    29 const ParamType CONVARCHANGE_PARAMS[] = {Param_Cell, Param_String, Param_String};
    30 typedef List<const ConVar *> ConVarList;
    31 
    32 ConVarManager::ConVarManager() : m_ConVarType(0), m_VSPIface(NULL), m_CanQueryConVars(false)
    33 {
    34 	/* Create a convar lookup trie */
    35 	m_ConVarCache = sm_trie_create();
    36 }
    37 
    38 ConVarManager::~ConVarManager()
    39 {
    40 	List<ConVarInfo *>::iterator iter;
    41 
    42 	/* Destroy our convar lookup trie */
    43 	sm_trie_destroy(m_ConVarCache);
    44 
    45 	/* Destroy all the ConVarInfo structures */
    46 	for (iter = m_ConVars.begin(); iter != m_ConVars.end(); iter++)
    47 	{
    48 		delete (*iter);
    49 	}
    50 
    51 	m_ConVars.clear();
    52 }
    53 
    54 void ConVarManager::OnSourceModAllInitialized()
    55 {
    56 	/* Only valid with ServerGameDLL006 or greater */
    57 	if (g_SMAPI->GetGameDLLVersion() >= 6)
    58 	{
    59 		SH_ADD_HOOK_MEMFUNC(IServerGameDLL, OnQueryCvarValueFinished, gamedll, this, &ConVarManager::OnQueryCvarValueFinished, false);
    60 		m_CanQueryConVars = true;
    61 	}
    62 
    63 	HandleAccess sec;
    64 
    65 	/* Set up access rights for the 'ConVar' handle type */
    66 	sec.access[HandleAccess_Read] = 0;
    67 	sec.access[HandleAccess_Delete] = HANDLE_RESTRICT_IDENTITY | HANDLE_RESTRICT_OWNER;
    68 	sec.access[HandleAccess_Clone] = HANDLE_RESTRICT_IDENTITY | HANDLE_RESTRICT_OWNER;
    69 
    70 	/* Create the 'ConVar' handle type */
    71 	m_ConVarType = g_HandleSys.CreateType("ConVar", this, 0, NULL, &sec, g_pCoreIdent, NULL);
    72 
    73 	/* Add the 'convars' option to the 'sm' console command */
    74 	g_RootMenu.AddRootConsoleCommand("convars", "View convars created by a plugin", this);
    75 }
    76 
    77 void ConVarManager::OnSourceModShutdown()
    78 {
    79 	if (m_CanQueryConVars)
    80 	{
    81 		SH_REMOVE_HOOK_MEMFUNC(IServerGameDLL, OnQueryCvarValueFinished, gamedll, this, &ConVarManager::OnQueryCvarValueFinished, false);
    82 		SH_REMOVE_HOOK_MEMFUNC(IServerPluginCallbacks, OnQueryCvarValueFinished, m_VSPIface, this, &ConVarManager::OnQueryCvarValueFinished, false);
    83 	}
    84 
    85 	IChangeableForward *fwd;
    86 	List<ConVarInfo *>::iterator i;
    87 
    88 	/* Iterate list of ConVarInfo structures */
    89 	for (i = m_ConVars.begin(); i != m_ConVars.end(); i++)
    90 	{
    91 		fwd = (*i)->pChangeForward;
    92 
    93 		/* Free any convar-change forwards that still exist */
    94 		if (fwd)
    95 		{
    96 			g_Forwards.ReleaseForward(fwd);
    97 		}
    98 	}
    99 
   100 	/* Remove the 'convars' option from the 'sm' console command */
   101 	g_RootMenu.RemoveRootConsoleCommand("convars", this);
   102 
   103 	/* Remove the 'ConVar' handle type */
   104 	g_HandleSys.RemoveType(m_ConVarType, g_pCoreIdent);
   105 }
   106 
   107 void ConVarManager::OnSourceModVSPReceived(IServerPluginCallbacks *iface)
   108 {
   109 	/* This will be called after OnSourceModAllInitialized(), so...
   110 	 *
   111 	 * If we haven't been able to hook the IServerGameDLL version at this point,
   112 	 * then use hook IServerPluginCallbacks version from the engine.
   113 	 */
   114 
   115 	/* The Ship currently only supports ServerPluginCallbacks001, but we need 002 */
   116 	if (g_IsOriginalEngine)
   117 	{
   118 		return;
   119 	}
   120 
   121 	if (!m_CanQueryConVars)
   122 	{
   123 		m_VSPIface = iface;
   124 
   125 		SH_ADD_HOOK_MEMFUNC(IServerPluginCallbacks, OnQueryCvarValueFinished, iface, this, &ConVarManager::OnQueryCvarValueFinished, false);
   126 		m_CanQueryConVars = true;
   127 	}
   128 }
   129 
   130 void ConVarManager::OnPluginUnloaded(IPlugin *plugin)
   131 {
   132 	ConVarList *pConVarList;
   133 
   134 	/* If plugin has a convar list, free its memory */
   135 	if (plugin->GetProperty("ConVarList", (void **)&pConVarList, true))
   136 	{
   137 		delete pConVarList;
   138 	}
   139 }
   140 
   141 void ConVarManager::OnHandleDestroy(HandleType_t type, void *object)
   142 {
   143 	ConVarInfo *info;
   144 	ConVar *pConVar = static_cast<ConVar *>(object);
   145 
   146 	/* Find convar in lookup trie */
   147 	sm_trie_retrieve(m_ConVarCache, pConVar->GetName(), reinterpret_cast<void **>(&info));
   148 
   149 	/* If convar was created by SourceMod plugin... */
   150 	if (info->sourceMod)
   151 	{
   152 		/* Delete string allocations */
   153 		delete [] pConVar->GetName(); 
   154 		delete [] pConVar->GetDefault();
   155 		delete [] pConVar->GetHelpText();
   156 
   157 		/* Then unlink it from SourceMM */
   158 		g_SMAPI->UnregisterConCmdBase(g_PLAPI, pConVar);
   159 	}
   160 }
   161 
   162 void ConVarManager::OnRootConsoleCommand(const char *command, unsigned int argcount)
   163 {
   164 	if (argcount >= 3)
   165 	{
   166 		/* Get plugin index that was passed */
   167 		int id = atoi(g_RootMenu.GetArgument(2));
   168 		
   169 		/* Get plugin object */
   170 		CPlugin *plugin = g_PluginSys.GetPluginByOrder(id);
   171 
   172 		if (!plugin)
   173 		{
   174 			g_RootMenu.ConsolePrint("[SM] Plugin index %d not found.", id);
   175 			return;
   176 		}
   177 
   178 		/* Get plugin name */
   179 		const sm_plugininfo_t *plinfo = plugin->GetPublicInfo();
   180 		const char *plname = IS_STR_FILLED(plinfo->name) ? plinfo->name : plugin->GetFilename();
   181 
   182 		ConVarList *pConVarList;
   183 		ConVarList::iterator iter;
   184 
   185 		/* If no convar list... */
   186 		if (!plugin->GetProperty("ConVarList", (void **)&pConVarList))
   187 		{
   188 			g_RootMenu.ConsolePrint("[SM] No convars found for: %s", plname);
   189 			return;
   190 		}
   191 
   192 		g_RootMenu.ConsolePrint("[SM] Listing %d convars for: %s", pConVarList->size(), plname);
   193 		g_RootMenu.ConsolePrint("  %-32.31s %s", "[Name]", "[Value]");
   194 
   195 		/* Iterate convar list and display each one */
   196 		for (iter = pConVarList->begin(); iter != pConVarList->end(); iter++)
   197 		{
   198 			const ConVar *pConVar = (*iter);
   199 			g_RootMenu.ConsolePrint("  %-32.31s %s", pConVar->GetName(), pConVar->GetString()); 
   200 		}
   201 
   202 		return;
   203 	}
   204 
   205 	/* Display usage of subcommand */
   206 	g_RootMenu.ConsolePrint("[SM] Usage: sm convars <plugin #>");
   207 }
   208 
   209 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)
   210 {
   211 	ConVar *pConVar = NULL;
   212 	ConVarInfo *pInfo = NULL;
   213 	Handle_t hndl = 0;
   214 
   215 	/* Find out if the convar exists already */
   216 	pConVar = icvar->FindVar(name);
   217 
   218 	/* If the convar already exists... */
   219 	if (pConVar)
   220 	{
   221 		/* First find out if we already have a handle to it */
   222 		if (sm_trie_retrieve(m_ConVarCache, name, (void **)&pInfo))
   223 		{
   224 			return pInfo->handle;
   225 		} else {
   226 			/* If we don't, then create a new handle from the convar */
   227 			hndl = g_HandleSys.CreateHandle(m_ConVarType, pConVar, NULL, g_pCoreIdent, NULL);
   228 
   229 			/* Create and initialize ConVarInfo structure */
   230 			pInfo = new ConVarInfo();
   231 			pInfo->handle = hndl;
   232 			pInfo->sourceMod = false;
   233 			pInfo->pChangeForward = NULL;
   234 			pInfo->origCallback = pConVar->GetCallback();
   235 
   236 			/* Insert struct into caches */
   237 			m_ConVars.push_back(pInfo);
   238 			sm_trie_insert(m_ConVarCache, name, pInfo);
   239 
   240 			return hndl;
   241 		}
   242 	}
   243 
   244 	/* To prevent creating a convar that has the same name as a console command... ugh */
   245 	ConCommandBase *pBase = icvar->GetCommands();
   246 
   247 	while (pBase)
   248 	{
   249 		if (pBase->IsCommand() && strcmp(pBase->GetName(), name) == 0)
   250 		{
   251 			return BAD_HANDLE;
   252 		}
   253 
   254 		pBase = const_cast<ConCommandBase *>(pBase->GetNext());
   255 	}
   256 
   257 	/* Since an existing convar (or concmd with the same name) was not found , now we can finally create it */
   258 	pConVar = new ConVar(sm_strdup(name), sm_strdup(defaultVal), flags, sm_strdup(description), hasMin, min, hasMax, max);
   259 
   260 	/* Add convar to plugin's list */
   261 	AddConVarToPluginList(pContext, pConVar);
   262 
   263 	/* Create a handle from the new convar */
   264 	hndl = g_HandleSys.CreateHandle(m_ConVarType, pConVar, NULL, g_pCoreIdent, NULL);
   265 
   266 	/* Create and initialize ConVarInfo structure */
   267 	pInfo = new ConVarInfo();
   268 	pInfo->handle = hndl;
   269 	pInfo->sourceMod = true;
   270 	pInfo->pChangeForward = NULL;
   271 	pInfo->origCallback = NULL;
   272 
   273 	/* Insert struct into caches */
   274 	m_ConVars.push_back(pInfo);
   275 	sm_trie_insert(m_ConVarCache, name, pInfo);
   276 
   277 	return hndl;
   278 }
   279 
   280 Handle_t ConVarManager::FindConVar(const char *name)
   281 {
   282 	ConVar *pConVar = NULL;
   283 	ConVarInfo *pInfo;
   284 	Handle_t hndl;
   285 
   286 	/* Search for convar */
   287 	pConVar = icvar->FindVar(name);
   288 
   289 	/* If it doesn't exist, then return an invalid handle */
   290 	if (!pConVar)
   291 	{
   292 		return BAD_HANDLE;
   293 	}
   294 
   295 	/* At this point, the convar exists. So, find out if we already have a handle */
   296 	if (sm_trie_retrieve(m_ConVarCache, name, (void **)&pInfo))
   297 	{
   298 		return pInfo->handle;
   299 	}
   300 
   301 	/* If we don't have a handle, then create a new one */
   302 	hndl = g_HandleSys.CreateHandle(m_ConVarType, pConVar, NULL, g_pCoreIdent, NULL);
   303 
   304 	/* Create and initilize ConVarInfo structure */
   305 	pInfo = new ConVarInfo();
   306 	pInfo->handle = hndl;
   307 	pInfo->sourceMod = false;
   308 	pInfo->pChangeForward = NULL;
   309 	pInfo->origCallback = pConVar->GetCallback();
   310 
   311 	/* Insert struct into our caches */
   312 	m_ConVars.push_back(pInfo);
   313 	sm_trie_insert(m_ConVarCache, name, pInfo);
   314 
   315 	return hndl;
   316 }
   317 
   318 void ConVarManager::HookConVarChange(ConVar *pConVar, IPluginFunction *pFunction)
   319 {
   320 	ConVarInfo *pInfo;
   321 	IChangeableForward *pForward;
   322 
   323 	/* Find the convar in the lookup trie */
   324 	if (sm_trie_retrieve(m_ConVarCache, pConVar->GetName(), (void **)&pInfo))
   325 	{
   326 		/* Get the forward */
   327 		pForward = pInfo->pChangeForward;
   328 
   329 		/* If forward does not exist, create it */
   330 		if (!pForward)
   331 		{
   332 			pForward = g_Forwards.CreateForwardEx(NULL, ET_Ignore, 3, CONVARCHANGE_PARAMS);
   333 			pInfo->pChangeForward = pForward;
   334 
   335 			/* Install our own callback */
   336 			pConVar->InstallChangeCallback(OnConVarChanged);
   337 		}
   338 
   339 		/* Add function to forward's list */
   340 		pForward->AddFunction(pFunction);
   341 	}
   342 }
   343 
   344 void ConVarManager::UnhookConVarChange(ConVar *pConVar, IPluginFunction *pFunction)
   345 {
   346 	ConVarInfo *pInfo;
   347 	IChangeableForward *pForward;
   348 	IPluginContext *pContext = pFunction->GetParentContext();
   349 
   350 	/* Find the convar in the lookup trie */
   351 	if (sm_trie_retrieve(m_ConVarCache, pConVar->GetName(), (void **)&pInfo))
   352 	{
   353 		/* Get the forward */
   354 		pForward = pInfo->pChangeForward;
   355 
   356 		/* If the forward doesn't exist, we can't unhook anything */
   357 		if (!pForward)
   358 		{
   359 			pContext->ThrowNativeError("Convar \"%s\" has no active hook", pConVar->GetName());
   360 			return;
   361 		}
   362 
   363 		/* Remove the function from the forward's list */
   364 		if (!pForward->RemoveFunction(pFunction))
   365 		{
   366 			pContext->ThrowNativeError("Invalid hook callback specified for convar \"%s\"", pConVar->GetName());
   367 			return;
   368 		}
   369 
   370 		/* If the forward now has 0 functions in it... */
   371 		if (pForward->GetFunctionCount() == 0)
   372 		{
   373 			/* Free this forward */
   374 			g_Forwards.ReleaseForward(pForward);
   375 			pInfo->pChangeForward = NULL;
   376 
   377 			/* Put back the original convar callback */
   378 			pConVar->InstallChangeCallback(pInfo->origCallback);
   379 		}
   380 	}
   381 }
   382 
   383 QueryCvarCookie_t ConVarManager::QueryClientConVar(edict_t *pPlayer, const char *name, IPluginFunction *pCallback, Handle_t hndl)
   384 {
   385 	QueryCvarCookie_t cookie;
   386 
   387 	/* Call StartQueryCvarValue() in either the IVEngineServer or IServerPluginHelpers depending on situation */
   388 	if (!m_VSPIface)
   389 	{
   390 		cookie = engine->StartQueryCvarValue(pPlayer, name);	
   391 	} else {
   392 		cookie = serverpluginhelpers->StartQueryCvarValue(pPlayer, name);
   393 	}
   394 
   395 	ConVarQuery query = {cookie, pCallback, hndl};
   396 	m_ConVarQueries.push_back(query);
   397 
   398 	return cookie;
   399 }
   400 
   401 void ConVarManager::AddConVarToPluginList(IPluginContext *pContext, const ConVar *pConVar)
   402 {
   403 	ConVarList *pConVarList;
   404 	ConVarList::iterator iter;
   405 	bool inserted = false;
   406 	const char *orig = pConVar->GetName();
   407 
   408 	IPlugin *plugin = g_PluginSys.FindPluginByContext(pContext->GetContext());
   409 
   410 	/* Check plugin for an existing convar list */
   411 	if (!plugin->GetProperty("ConVarList", (void **)&pConVarList))
   412 	{
   413 		pConVarList = new ConVarList();
   414 		plugin->SetProperty("ConVarList", pConVarList);
   415 	}
   416 
   417 	/* Insert convar into list which is sorted alphabetically */
   418 	for (iter = pConVarList->begin(); iter != pConVarList->end(); iter++)
   419 	{
   420 		if (strcmp(orig, (*iter)->GetName()) < 0)
   421 		{
   422 			pConVarList->insert(iter, pConVar);
   423 			inserted = true;
   424 			break;
   425 		}
   426 	}
   427 
   428 	if (!inserted)
   429 	{
   430 		pConVarList->push_back(pConVar);
   431 	}
   432 }
   433 
   434 void ConVarManager::OnConVarChanged(ConVar *pConVar, const char *oldValue)
   435 {
   436 	/* If the values are the same, exit early in order to not trigger callbacks */
   437 	if (strcmp(pConVar->GetString(), oldValue) == 0)
   438 	{
   439 		return;
   440 	}
   441 
   442 	Trie *pCache = g_ConVarManager.GetConVarCache();
   443 	ConVarInfo *pInfo;
   444 
   445 	/* Find the convar in the lookup trie */
   446 	sm_trie_retrieve(pCache, pConVar->GetName(), (void **)&pInfo);
   447 
   448 	FnChangeCallback origCallback = pInfo->origCallback;
   449 	IChangeableForward *pForward = pInfo->pChangeForward;
   450 
   451 	/* If there was a change callback installed previously, call it */
   452 	if (origCallback)
   453 	{
   454 		origCallback(pConVar, oldValue);
   455 	}
   456 
   457 	/* Now call forwards in plugins that have hooked this */
   458 	pForward->PushCell(pInfo->handle);
   459 	pForward->PushString(oldValue);
   460 	pForward->PushString(pConVar->GetString());
   461 	pForward->Execute(NULL);
   462 }
   463 
   464 void ConVarManager::OnQueryCvarValueFinished(QueryCvarCookie_t cookie, edict_t *pPlayer, EQueryCvarValueStatus result, const char *cvarName, const char *cvarValue)
   465 {
   466 	IPluginFunction *pCallback = NULL;
   467 	cell_t value = 0;
   468 	List<ConVarQuery>::iterator iter;
   469 
   470 	for (iter = m_ConVarQueries.begin(); iter != m_ConVarQueries.end(); iter++)
   471 	{
   472 		ConVarQuery &query = (*iter);
   473 		
   474 		if (query.cookie == cookie)
   475 		{
   476 			pCallback = query.pCallback;
   477 			value = query.value;
   478 
   479 			break;
   480 		}
   481 	}
   482 
   483 	if (pCallback)
   484 	{
   485 		cell_t ret;
   486 
   487 		pCallback->PushCell(cookie);
   488 		pCallback->PushCell(engine->IndexOfEdict(pPlayer));
   489 		pCallback->PushCell(result);
   490 		pCallback->PushString(cvarName);
   491 
   492 		if (result == eQueryCvarValueStatus_ValueIntact)
   493 		{
   494 			pCallback->PushString(cvarValue);
   495 		} else {
   496 			pCallback->PushString("\0");
   497 		}
   498 
   499 		pCallback->PushCell(value);
   500 		pCallback->Execute(&ret);
   501 
   502 		m_ConVarQueries.erase(iter);
   503 	}
   504 }
   505 
   506 
   507 static int s_YamagramState = 0;
   508 
   509 void _YamagramPrinterTwoPointOhOh(int yamagram)
   510 {
   511 	switch (yamagram)
   512 	{
   513 	case 0:
   514 		g_RootMenu.ConsolePrint("Answer the following questions correctly and Gaben may not eat you after all.");
   515 		g_RootMenu.ConsolePrint("You will be given one hint in the form of my patented yamagrams.");
   516 		g_RootMenu.ConsolePrint("Type sm_nana to see the last question.");
   517 		g_RootMenu.ConsolePrint("Type sm_nana <answer> to attempt an answer of the question.");
   518 		g_RootMenu.ConsolePrint("-------------------------------");
   519 		_YamagramPrinterTwoPointOhOh(1);
   520 		return;
   521 	case 1:
   522 		g_RootMenu.ConsolePrint("Question Ichi (1)");
   523 		g_RootMenu.ConsolePrint("One can turn into a cow by doing what action?");
   524 		g_RootMenu.ConsolePrint("Hint: AGE SANS GRIT");
   525 		break;
   526 	case 2:
   527 		g_RootMenu.ConsolePrint("Question Ni (2)");
   528 		g_RootMenu.ConsolePrint("What kind of hat should you wear when using the Internet?");
   529 		g_RootMenu.ConsolePrint("Hint: BRR MOOSE");
   530 		break;
   531 	case 3:
   532 		g_RootMenu.ConsolePrint("Question San (3)");
   533 		g_RootMenu.ConsolePrint("Who is the lead developer of SourceMod?");
   534 		g_RootMenu.ConsolePrint("Hint: VEAL BANDANA DID RIP SOON");
   535 		break;
   536 	case 4:
   537 		g_RootMenu.ConsolePrint("Question Yon (4)");
   538 		g_RootMenu.ConsolePrint("A terrible translation of 'SVN Revision' to Japanese romaji might be ...");
   539 		g_RootMenu.ConsolePrint("Hint: I TAKE IN AN AIR OK");
   540 		break;
   541 	case 5:
   542 		g_RootMenu.ConsolePrint("Question Go (5)");
   543 		g_RootMenu.ConsolePrint("What is a fundamental concept in the game of Go?");
   544 		g_RootMenu.ConsolePrint("Hint: AD LADEN THIEF");
   545 		break;
   546 	case 6:
   547 		g_RootMenu.ConsolePrint("Question Roku (6)");
   548 		g_RootMenu.ConsolePrint("Why am I asking all these strange questions?");
   549 		g_RootMenu.ConsolePrint("Hint: CHUBBY TITAN EATS EWE WAGE DATA");
   550 		break;
   551 	case 7:
   552 		g_RootMenu.ConsolePrint("Question Nana (7)");
   553 		g_RootMenu.ConsolePrint("What is my name?");
   554 		g_RootMenu.ConsolePrint("Hint: AD MODE LAG US");
   555 		break;
   556 	default:
   557 		break;
   558 	}
   559 
   560 	s_YamagramState = yamagram;
   561 }
   562 
   563 void _IntExt_CallYamagrams()
   564 {
   565 	bool correct = false;
   566 	const char *arg = engine->Cmd_Args();
   567 
   568 	if (!arg || arg[0] == '\0')
   569 	{
   570 		_YamagramPrinterTwoPointOhOh(s_YamagramState);
   571 		return;
   572 	}
   573 
   574 	switch (s_YamagramState)
   575 	{
   576 	case 1:
   577 		correct = !strcasecmp(arg, "eating grass");
   578 		break;
   579 	case 2:
   580 		correct = !strcasecmp(arg, "sombrero");
   581 		break;
   582 	case 3:
   583 		correct = !strcasecmp(arg, "david bailopan anderson");
   584 		break;
   585 	case 4:
   586 		correct = !strcasecmp(arg, "kaitei no kairan");
   587 		break;
   588 	case 5:
   589 		correct = !strcasecmp(arg, "life and death");
   590 		break;
   591 	case 6:
   592 		correct = !strcasecmp(arg, "because gabe wanted it that way");
   593 		if (correct)
   594 		{
   595 			g_RootMenu.ConsolePrint("Congratulations, you have answered 6 of my questions.");
   596 			g_RootMenu.ConsolePrint("However, I have one final question for you. It wouldn't be nana without it.");
   597 			g_RootMenu.ConsolePrint("-------------------------------");
   598 			_YamagramPrinterTwoPointOhOh(7);
   599 			return;
   600 		}
   601 		break;
   602 	case 7:
   603 		correct = !strcasecmp(arg, "damaged soul");
   604 		if (correct)
   605 		{
   606 			g_RootMenu.ConsolePrint("You don't know how lucky you are to still be alive!");
   607 			g_RootMenu.ConsolePrint("Congratulations. You have answered all 7 questions correctly.");
   608 			g_RootMenu.ConsolePrint("The SourceMod Dev Team will be at your door with anti-Gaben grenades");
   609 			g_RootMenu.ConsolePrint("within seconds. You will also be provided with a rocket launcher,");
   610 			g_RootMenu.ConsolePrint("just in case Alfred decides to strike you with a blitzkrieg in retaliation.");
   611 
   612 			s_YamagramState = 0;
   613 			return;
   614 		}
   615 		break;
   616 	default:
   617 		break;
   618 	}
   619 
   620 	if (s_YamagramState > 0)
   621 	{
   622 		if (correct)
   623 		{
   624 			g_RootMenu.ConsolePrint("Correct! You are one step closer to avoiding the deadly jaws of Gaben.");
   625 			g_RootMenu.ConsolePrint("-------------------------------");
   626 			s_YamagramState++;
   627 		} else {
   628 			g_RootMenu.ConsolePrint("Wrong! You better be more careful. Gaben may be at your door at any minute.");
   629 			return;
   630 		}
   631 	}
   632 
   633 	_YamagramPrinterTwoPointOhOh(s_YamagramState);
   634 }
   635 
   636 void _IntExt_EnableYamagrams()
   637 {
   638 	static ConCommand *pCmd = NULL;
   639 	if (!pCmd)
   640 	{
   641 		pCmd = new ConCommand("sm_nana", _IntExt_CallYamagrams, "Try these yamagrams!", FCVAR_GAMEDLL);
   642 		g_RootMenu.ConsolePrint("[SM] Warning: Gaben has been alerted of your actions. You may be eaten.");
   643 	} else {
   644 		g_RootMenu.ConsolePrint("[SM] Gaben has already been alerted of your actions...");
   645 	}
   646 }
   647 
   648 void _IntExt_OnHostnameChanged(ConVar *pConVar, char const *oldValue)
   649 {
   650 	if (strcmp(oldValue, "Good morning, DS-san.") == 0
   651 		&& strcmp(pConVar->GetString(), "Good night, talking desk lamp.") == 0)
   652 	{
   653 		_IntExt_EnableYamagrams();
   654 	}
   655 }