core/HalfLife2.cpp
author Nicholas Hastings <skamonkey@gmail.com>
Thu Aug 15 10:18:27 2013 -0400 (2013-08-15)
changeset 4015 5f1fcac17b60
parent 3997 59bcfc51eb79
child 4069 336e32fe098f
permissions -rw-r--r--
Fixed chat trigger crash regression on ep1 (bug 5394, r=asherkin).
     1 /**
     2  * vim: set ts=4 :
     3  * =============================================================================
     4  * SourceMod
     5  * Copyright (C) 2004-2008 AlliedModders LLC.  All rights reserved.
     6  * =============================================================================
     7  *
     8  * This program is free software; you can redistribute it and/or modify it under
     9  * the terms of the GNU General Public License, version 3.0, as published by the
    10  * Free Software Foundation.
    11  * 
    12  * This program is distributed in the hope that it will be useful, but WITHOUT
    13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    14  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
    15  * details.
    16  *
    17  * You should have received a copy of the GNU General Public License along with
    18  * this program.  If not, see <http://www.gnu.org/licenses/>.
    19  *
    20  * As a special exception, AlliedModders LLC gives you permission to link the
    21  * code of this program (as well as its derivative works) to "Half-Life 2," the
    22  * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
    23  * by the Valve Corporation.  You must obey the GNU General Public License in
    24  * all respects for all other code used.  Additionally, AlliedModders LLC grants
    25  * this exception to all derivative works.  AlliedModders LLC defines further
    26  * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
    27  * or <http://www.sourcemod.net/license.php>.
    28  *
    29  * Version: $Id$
    30  */
    31 
    32 #include "HalfLife2.h"
    33 #include "sourcemod.h"
    34 #include "sourcemm_api.h"
    35 #include "UserMessages.h"
    36 #include "PlayerManager.h"
    37 #include "sm_stringutil.h"
    38 #include <IGameConfigs.h>
    39 #include <compat_wrappers.h>
    40 #include <Logger.h>
    41 #include "LibrarySys.h"
    42 #include "logic_bridge.h"
    43 #include <tier0/mem.h>
    44 
    45 #if SOURCE_ENGINE == SE_DOTA
    46 #include <game/shared/protobuf/usermessages.pb.h>
    47 #elif SOURCE_ENGINE == SE_CSGO
    48 #include <cstrike15_usermessages.pb.h>
    49 #endif
    50 
    51 
    52 typedef ICommandLine *(*FakeGetCommandLine)();
    53 
    54 #if defined _WIN32
    55 #define TIER0_NAME			"tier0.dll"
    56 #define VSTDLIB_NAME		"vstdlib.dll"
    57 #elif defined __APPLE__
    58 #define TIER0_NAME			"libtier0.dylib"
    59 #define VSTDLIB_NAME		"libvstdlib.dylib"
    60 #elif defined __linux__
    61 #if SOURCE_ENGINE == SE_HL2DM || SOURCE_ENGINE == SE_DODS || SOURCE_ENGINE == SE_CSS || SOURCE_ENGINE == SE_TF2 || SOURCE_ENGINE == SE_LEFT4DEAD2
    62 #define TIER0_NAME			"libtier0_srv.so"
    63 #define VSTDLIB_NAME		"libvstdlib_srv.so"
    64 #elif SOURCE_ENGINE >= SE_LEFT4DEAD
    65 #define TIER0_NAME			"libtier0.so"
    66 #define VSTDLIB_NAME		"libvstdlib.so"
    67 #else
    68 #define TIER0_NAME			"tier0_i486.so"
    69 #define VSTDLIB_NAME		"vstdlib_i486.so"
    70 #endif
    71 #endif
    72 
    73 CHalfLife2 g_HL2;
    74 ConVar *sv_lan = NULL;
    75 
    76 static void *g_EntList = NULL;
    77 static int entInfoOffset = -1;
    78 
    79 namespace SourceHook
    80 {
    81 	template<>
    82 	int HashFunction<datamap_t *>(datamap_t * const &k)
    83 	{
    84 		return reinterpret_cast<int>(k);
    85 	}
    86 
    87 	template<>
    88 	int Compare<datamap_t *>(datamap_t * const &k1, datamap_t * const &k2)
    89 	{
    90 		return (k1-k2);
    91 	}
    92 }
    93 
    94 CHalfLife2::CHalfLife2()
    95 {
    96 	m_pClasses = sm_trie_create();
    97 }
    98 
    99 CHalfLife2::~CHalfLife2()
   100 {
   101 	sm_trie_destroy(m_pClasses);
   102 
   103 	List<DataTableInfo *>::iterator iter;
   104 	DataTableInfo *pInfo;
   105 	for (iter=m_Tables.begin(); iter!=m_Tables.end(); iter++)
   106 	{
   107 		pInfo = (*iter);
   108 		delete pInfo;
   109 	}
   110 
   111 	m_Tables.clear();
   112 
   113 	THash<datamap_t *, DataMapTrie>::iterator h_iter;
   114 	for (h_iter=m_Maps.begin(); h_iter!=m_Maps.end(); h_iter++)
   115 	{
   116 		if (h_iter->val.trie)
   117 		{
   118 			delete h_iter->val.trie;
   119 			h_iter->val.trie = NULL;
   120 		}
   121 	}
   122 
   123 	m_Maps.clear();
   124 }
   125 
   126 #if SOURCE_ENGINE != SE_DARKMESSIAH
   127 CSharedEdictChangeInfo *g_pSharedChangeInfo = NULL;
   128 #endif
   129 
   130 #if !defined METAMOD_PLAPI_VERSION || PLAPI_VERSION < 11
   131 bool is_original_engine = false;
   132 #endif
   133 
   134 void CHalfLife2::OnSourceModStartup(bool late)
   135 {
   136 #if SOURCE_ENGINE != SE_DARKMESSIAH
   137 
   138 	/* The Ship currently is the only known game to use an older version of the engine */
   139 #if defined METAMOD_PLAPI_VERSION || PLAPI_VERSION >= 11
   140 	if (g_SMAPI->GetSourceEngineBuild() == SOURCE_ENGINE_ORIGINAL)
   141 #else
   142 	if (strcasecmp(g_SourceMod.GetGameFolderName(), "ship") == 0)
   143 #endif
   144 	{
   145 		is_original_engine = true;
   146 	}
   147 	else if (g_pSharedChangeInfo == NULL)
   148 	{
   149 		g_pSharedChangeInfo = engine->GetSharedEdictChangeInfo();
   150 	}
   151 #endif
   152 }
   153 
   154 void CHalfLife2::OnSourceModAllInitialized()
   155 {
   156 	m_MsgTextMsg = g_UserMsgs.GetMessageIndex("TextMsg");
   157 	m_HinTextMsg = g_UserMsgs.GetMessageIndex("HintText");
   158 	m_SayTextMsg = g_UserMsgs.GetMessageIndex("SayText");
   159 	m_VGUIMenu = g_UserMsgs.GetMessageIndex("VGUIMenu");
   160 	sharesys->AddInterface(NULL, this);
   161 }
   162 
   163 void CHalfLife2::OnSourceModAllInitialized_Post()
   164 {
   165 	InitLogicalEntData();
   166 	InitCommandLine();
   167 }
   168 
   169 void CHalfLife2::InitLogicalEntData()
   170 {
   171 	char *addr = NULL;
   172 
   173 	/*
   174 	 * gEntList and/or g_pEntityList
   175 	 *
   176 	 * First try to lookup pointer directly for platforms with symbols.
   177 	 * If symbols aren't present (Windows or stripped Linux/Mac), 
   178 	 * attempt find via LevelShutdown + offset
   179 	 */
   180 	if (g_pGameConf->GetMemSig("gEntList", (void **)&addr))
   181 	{
   182 #if !defined PLATFORM_WINDOWS
   183 		if (!addr)
   184 		{
   185 			// Key exists so notify if lookup fails, but try other method.
   186 			g_Logger.LogError("Failed lookup of gEntList directly - Reverting to lookup via LevelShutdown");
   187 		}
   188 		else
   189 		{
   190 #endif
   191 			g_EntList = reinterpret_cast<void *>(addr);
   192 #if !defined PLATFORM_WINDOWS
   193 		}
   194 #endif
   195 	}
   196 
   197 
   198 	if (!g_EntList)
   199 	{
   200 		if (!g_pGameConf->GetMemSig("LevelShutdown", (void **)&addr))
   201 		{
   202 			g_Logger.LogError("Logical Entities not supported by this mod (LevelShutdown) - Reverting to networkable entities only");
   203 			return;
   204 		}
   205 
   206 		if (!addr)
   207 		{
   208 			g_Logger.LogError("Failed lookup of LevelShutdown - Reverting to networkable entities only");
   209 			return;
   210 		}
   211 
   212 		int offset;
   213 		if (!g_pGameConf->GetOffset("gEntList", &offset))
   214 		{
   215 			g_Logger.LogError("Logical Entities not supported by this mod (gEntList) - Reverting to networkable entities only");
   216 			return;
   217 		}
   218 
   219 		g_EntList = *reinterpret_cast<void **>(addr + offset);
   220 	}
   221 	
   222 	if (!g_EntList)
   223 	{
   224 		g_Logger.LogError("Failed lookup of gEntList - Reverting to networkable entities only");
   225 		return;
   226 	}
   227 
   228 	if (!g_pGameConf->GetOffset("EntInfo", &entInfoOffset))
   229 	{
   230 		g_Logger.LogError("Logical Entities not supported by this mod (EntInfo) - Reverting to networkable entities only");
   231 		return;
   232 	}
   233 }
   234 
   235 void CHalfLife2::InitCommandLine()
   236 {
   237 	char path[PLATFORM_MAX_PATH];
   238 	char error[256];
   239 	
   240 	g_SourceMod.BuildPath(Path_Game, path, sizeof(path), "../bin/" TIER0_NAME);
   241 
   242 	if (!g_LibSys.IsPathFile(path))
   243 	{
   244 		g_Logger.LogError("Could not find path for: " TIER0_NAME);
   245 		return;
   246 	}
   247 
   248 	ILibrary *lib = g_LibSys.OpenLibrary(path, error, sizeof(error));
   249 	m_pGetCommandLine = lib->GetSymbolAddress("CommandLine_Tier0");
   250 
   251 	/* '_Tier0' dropped on Alien Swarm version */
   252 	if (m_pGetCommandLine == NULL)
   253 	{
   254 		m_pGetCommandLine = lib->GetSymbolAddress("CommandLine");
   255 	}
   256 
   257 	if (m_pGetCommandLine == NULL)
   258 	{
   259 		/* We probably have a Ship engine. */
   260 		lib->CloseLibrary();
   261 		g_SourceMod.BuildPath(Path_Game, path, sizeof(path), "../bin/" VSTDLIB_NAME);
   262 		if (!g_LibSys.IsPathFile(path))
   263 		{
   264 			g_Logger.LogError("Could not find path for: " VSTDLIB_NAME);
   265 			return;
   266 		}
   267 
   268 		if ((lib = g_LibSys.OpenLibrary(path, error, sizeof(error))) == NULL)
   269 		{
   270 			g_Logger.LogError("Could not load %s: %s", path, error);
   271 			return;
   272 		}
   273 
   274 		m_pGetCommandLine = lib->GetSymbolAddress("CommandLine");
   275 
   276 		if (m_pGetCommandLine == NULL)
   277 		{
   278 			g_Logger.LogError("Could not locate any command line functionality");
   279 		}
   280 
   281 		lib->CloseLibrary();
   282 	}
   283 }
   284 
   285 ICommandLine *CHalfLife2::GetValveCommandLine()
   286 {
   287 	if (!m_pGetCommandLine)
   288 		return NULL;
   289 
   290 	return ((FakeGetCommandLine)((FakeGetCommandLine *)m_pGetCommandLine))();
   291 }
   292 
   293 #if !defined METAMOD_PLAPI_VERSION || PLAPI_VERSION < 11
   294 bool CHalfLife2::IsOriginalEngine()
   295 {
   296 	return is_original_engine;
   297 }
   298 #endif
   299 
   300 #if SOURCE_ENGINE != SE_DARKMESSIAH
   301 IChangeInfoAccessor *CBaseEdict::GetChangeAccessor()
   302 {
   303 #if SOURCE_ENGINE == SE_DOTA
   304 	return engine->GetChangeAccessor( IndexOfEdict((const edict_t *)this) );
   305 #else
   306 	return engine->GetChangeAccessor( (const edict_t *)this );
   307 #endif
   308 }
   309 #endif
   310 
   311 bool UTIL_FindInSendTable(SendTable *pTable, 
   312 						  const char *name,
   313 						  sm_sendprop_info_t *info,
   314 						  unsigned int offset)
   315 {
   316 	const char *pname;
   317 	int props = pTable->GetNumProps();
   318 	SendProp *prop;
   319 
   320 	for (int i=0; i<props; i++)
   321 	{
   322 		prop = pTable->GetProp(i);
   323 		pname = prop->GetName();
   324 		if (pname && strcmp(name, pname) == 0)
   325 		{
   326 			info->prop = prop;
   327 			info->actual_offset = offset + info->prop->GetOffset();
   328 			return true;
   329 		}
   330 		if (prop->GetDataTable())
   331 		{
   332 			if (UTIL_FindInSendTable(prop->GetDataTable(), 
   333 				name,
   334 				info,
   335 				offset + prop->GetOffset())
   336 				)
   337 			{
   338 				return true;
   339 			}
   340 		}
   341 	}
   342 
   343 	return false;
   344 }
   345 
   346 bool UTIL_FindDataMapInfo(datamap_t *pMap, const char *name, sm_datatable_info_t *pDataTable)
   347 {
   348 	while (pMap)
   349 	{
   350 		for (int i = 0; i < pMap->dataNumFields; ++i)
   351 		{
   352 			if (pMap->dataDesc[i].fieldName == NULL)
   353 			{
   354 				continue;
   355 			}
   356 			if (strcmp(name, pMap->dataDesc[i].fieldName) == 0)
   357 			{
   358 				pDataTable->prop = &(pMap->dataDesc[i]);
   359 				pDataTable->actual_offset = GetTypeDescOffs(pDataTable->prop);
   360 				return true;
   361 			}
   362 			if (pMap->dataDesc[i].td == NULL || !UTIL_FindDataMapInfo(pMap->dataDesc[i].td, name, pDataTable))
   363 			{
   364 				continue;
   365 			}
   366 			
   367 			pDataTable->actual_offset += GetTypeDescOffs(&(pMap->dataDesc[i]));
   368 			return true;
   369 		}
   370 		
   371 		pMap = pMap->baseMap;
   372 	}
   373 
   374 	return false; 
   375 }
   376 
   377 ServerClass *CHalfLife2::FindServerClass(const char *classname)
   378 {
   379 	DataTableInfo *pInfo = _FindServerClass(classname);
   380 
   381 	if (!pInfo)
   382 	{
   383 		return NULL;
   384 	}
   385 
   386 	return pInfo->sc;
   387 }
   388 
   389 DataTableInfo *CHalfLife2::_FindServerClass(const char *classname)
   390 {
   391 	DataTableInfo *pInfo = NULL;
   392 
   393 	if (!sm_trie_retrieve(m_pClasses, classname, (void **)&pInfo))
   394 	{
   395 		ServerClass *sc = gamedll->GetAllServerClasses();
   396 		while (sc)
   397 		{
   398 			if (strcmp(classname, sc->GetName()) == 0)
   399 			{
   400 				pInfo = new DataTableInfo;
   401 				pInfo->sc = sc;
   402 				sm_trie_insert(m_pClasses, classname, pInfo);
   403 				m_Tables.push_back(pInfo);
   404 				break;
   405 			}
   406 			sc = sc->m_pNext;
   407 		}
   408 		if (!pInfo)
   409 		{
   410 			return NULL;
   411 		}
   412 	}
   413 
   414 	return pInfo;
   415 }
   416 
   417 bool CHalfLife2::FindSendPropInfo(const char *classname, const char *offset, sm_sendprop_info_t *info)
   418 {
   419 	DataTableInfo *pInfo;
   420 	sm_sendprop_info_t *prop;
   421 
   422 	if ((pInfo = _FindServerClass(classname)) == NULL)
   423 	{
   424 		return false;
   425 	}
   426 
   427 	if ((prop = pInfo->lookup.retrieve(offset)) == NULL)
   428 	{
   429 		sm_sendprop_info_t temp_info;
   430 
   431 		if (!UTIL_FindInSendTable(pInfo->sc->m_pTable, offset, &temp_info, 0))
   432 		{
   433 			return false;
   434 		}
   435 
   436 		pInfo->lookup.insert(offset, temp_info);
   437 		*info = temp_info;
   438 	}
   439 	else
   440 	{
   441 		*info = *prop;
   442 	}
   443 	
   444 	return true;
   445 }
   446 
   447 SendProp *CHalfLife2::FindInSendTable(const char *classname, const char *offset)
   448 {
   449 	sm_sendprop_info_t info;
   450 
   451 	if (!FindSendPropInfo(classname, offset, &info))
   452 	{
   453 		return NULL;
   454 	}
   455 
   456 	return info.prop;
   457 }
   458 
   459 typedescription_t *CHalfLife2::FindInDataMap(datamap_t *pMap, const char *offset)
   460 {
   461 	sm_datatable_info_t dt_info;
   462 	
   463 	if (!(this->FindDataMapInfo(pMap, offset, &dt_info)))
   464 	{
   465 		return NULL;
   466 	}
   467 	
   468 	return dt_info.prop;
   469 }
   470 
   471 bool CHalfLife2::FindDataMapInfo(datamap_t *pMap, const char *offset, sm_datatable_info_t *pDataTable)
   472 {
   473 	DataMapTrie &val = m_Maps[pMap];
   474 
   475 	if (!val.trie)
   476 	{
   477 		val.trie = new KTrie<sm_datatable_info_t>;
   478 	}
   479 	
   480 	sm_datatable_info_t * pNewTable = val.trie->retrieve(offset);
   481 	
   482 	if (!pNewTable)
   483 	{
   484 		if (UTIL_FindDataMapInfo(pMap, offset, pDataTable))
   485 		{
   486 			val.trie->insert(offset, *pDataTable);
   487 		}
   488 		else
   489 		{
   490 			pDataTable->prop = NULL;
   491 		}
   492 	}
   493 	else
   494 	{
   495 		*pDataTable = *pNewTable;
   496 	}
   497 
   498 	return (pDataTable->prop != NULL);
   499 }
   500 
   501 void CHalfLife2::SetEdictStateChanged(edict_t *pEdict, unsigned short offset)
   502 {
   503 #if SOURCE_ENGINE != SE_DARKMESSIAH
   504 	if (g_pSharedChangeInfo != NULL)
   505 	{
   506 		if (offset)
   507 		{
   508 			pEdict->StateChanged(offset);
   509 		}
   510 		else
   511 		{
   512 			pEdict->StateChanged();
   513 		}
   514 	}
   515 	else
   516 #endif
   517 	{
   518 		pEdict->m_fStateFlags |= FL_EDICT_CHANGED;
   519 	}
   520 }
   521 
   522 bool CHalfLife2::TextMsg(int client, int dest, const char *msg)
   523 {
   524 #ifndef USE_PROTOBUF_USERMESSAGES
   525 	bf_write *pBitBuf = NULL;
   526 #endif
   527 	cell_t players[] = {client};
   528 
   529 	if (dest == HUD_PRINTTALK)
   530 	{
   531 		const char *chat_saytext = g_pGameConf->GetKeyValue("ChatSayText");
   532 
   533 		/* Use SayText user message instead */
   534 		if (chat_saytext != NULL && strcmp(chat_saytext, "yes") == 0)
   535 		{
   536 			char buffer[192];
   537 			UTIL_Format(buffer, sizeof(buffer), "%s\1\n", msg);
   538 
   539 #if SOURCE_ENGINE == SE_DOTA
   540 			CUserMsg_SayText *pMsg;
   541 			if ((pMsg = (CUserMsg_SayText *)g_UserMsgs.StartProtobufMessage(m_SayTextMsg, players, 1, USERMSG_RELIABLE)) == NULL)
   542 			{
   543 				return false;
   544 			}
   545 
   546 			pMsg->set_client(0);
   547 			pMsg->set_text(buffer);
   548 			pMsg->set_chat(false);
   549 #elif SOURCE_ENGINE == SE_CSGO
   550 			CCSUsrMsg_SayText *pMsg;
   551 			if ((pMsg = (CCSUsrMsg_SayText *)g_UserMsgs.StartProtobufMessage(m_SayTextMsg, players, 1, USERMSG_RELIABLE)) == NULL)
   552 			{
   553 				return false;
   554 			}
   555 
   556 			pMsg->set_ent_idx(0);
   557 			pMsg->set_text(buffer);
   558 			pMsg->set_chat(false);
   559 #else
   560 			if ((pBitBuf = g_UserMsgs.StartBitBufMessage(m_SayTextMsg, players, 1, USERMSG_RELIABLE)) == NULL)
   561 			{
   562 				return false;
   563 			}
   564 
   565 			pBitBuf->WriteByte(0);
   566 			pBitBuf->WriteString(buffer);
   567 			pBitBuf->WriteByte(1);
   568 #endif
   569 
   570 			g_UserMsgs.EndMessage();
   571 
   572 			return true;
   573 		}
   574 	}
   575 
   576 #if SOURCE_ENGINE == SE_DOTA
   577 	CUserMsg_TextMsg *pMsg;
   578 	if ((pMsg = (CUserMsg_TextMsg *)g_UserMsgs.StartProtobufMessage(m_MsgTextMsg, players, 1, USERMSG_RELIABLE)) == NULL)
   579 	{
   580 		return false;
   581 	}
   582 
   583 	pMsg->set_dest(dest);
   584 	pMsg->add_param(msg);
   585 	pMsg->add_param("");
   586 	pMsg->add_param("");
   587 	pMsg->add_param("");
   588 	pMsg->add_param("");
   589 #elif SOURCE_ENGINE == SE_CSGO
   590 	CCSUsrMsg_TextMsg *pMsg;
   591 	if ((pMsg = (CCSUsrMsg_TextMsg *)g_UserMsgs.StartProtobufMessage(m_MsgTextMsg, players, 1, USERMSG_RELIABLE)) == NULL)
   592 	{
   593 		return false;
   594 	}
   595 
   596 	// Client tries to read all 5 'params' and will crash if less
   597 	pMsg->set_msg_dst(dest);
   598 	pMsg->add_params(msg);
   599 	pMsg->add_params("");
   600 	pMsg->add_params("");
   601 	pMsg->add_params("");
   602 	pMsg->add_params("");
   603 #else
   604 	if ((pBitBuf = g_UserMsgs.StartBitBufMessage(m_MsgTextMsg, players, 1, USERMSG_RELIABLE)) == NULL)
   605 	{
   606 		return false;
   607 	}
   608 
   609 	pBitBuf->WriteByte(dest);
   610 	pBitBuf->WriteString(msg);
   611 #endif
   612 
   613 	g_UserMsgs.EndMessage();
   614 
   615 	return true;
   616 }
   617 
   618 bool CHalfLife2::HintTextMsg(int client, const char *msg)
   619 {
   620 	cell_t players[] = {client};
   621 
   622 #if SOURCE_ENGINE == SE_DOTA
   623 	CUserMsg_HintText *pMsg;
   624 	if ((pMsg = (CUserMsg_HintText *)g_UserMsgs.StartProtobufMessage(m_HinTextMsg, players, 1, USERMSG_RELIABLE)) == NULL)
   625 	{
   626 		return false;
   627 	}
   628 
   629 	pMsg->set_message(msg);
   630 #elif SOURCE_ENGINE == SE_CSGO
   631 	CCSUsrMsg_HintText *pMsg;
   632 	if ((pMsg = (CCSUsrMsg_HintText *)g_UserMsgs.StartProtobufMessage(m_HinTextMsg, players, 1, USERMSG_RELIABLE)) == NULL)
   633 	{
   634 		return false;
   635 	}
   636 
   637 	pMsg->set_text(msg);
   638 #else
   639 	bf_write *pBitBuf = NULL;
   640 
   641 	if ((pBitBuf = g_UserMsgs.StartBitBufMessage(m_HinTextMsg, players, 1, USERMSG_RELIABLE)) == NULL)
   642 	{
   643 		return false;
   644 	}
   645 
   646 	const char *pre_byte = g_pGameConf->GetKeyValue("HintTextPreByte");
   647 	if (pre_byte != NULL && strcmp(pre_byte, "yes") == 0)
   648 	{
   649 		pBitBuf->WriteByte(1);
   650 	}
   651 	pBitBuf->WriteString(msg);
   652 #endif
   653 	g_UserMsgs.EndMessage();
   654 
   655 	return true;
   656 }
   657 
   658 bool CHalfLife2::HintTextMsg(cell_t *players, int count, const char *msg)
   659 {
   660 #if SOURCE_ENGINE == SE_DOTA
   661 	CUserMsg_HintText *pMsg;
   662 	if ((pMsg = (CUserMsg_HintText *)g_UserMsgs.StartProtobufMessage(m_HinTextMsg, players, count, USERMSG_RELIABLE)) == NULL)
   663 	{
   664 		return false;
   665 	}
   666 
   667 	pMsg->set_message(msg);
   668 #elif SOURCE_ENGINE == SE_CSGO
   669 	CCSUsrMsg_HintText *pMsg;
   670 	if ((pMsg = (CCSUsrMsg_HintText *)g_UserMsgs.StartProtobufMessage(m_HinTextMsg, players, count, USERMSG_RELIABLE)) == NULL)
   671 	{
   672 		return false;
   673 	}
   674 
   675 	pMsg->set_text(msg);
   676 #else
   677 	bf_write *pBitBuf = NULL;
   678 
   679 	if ((pBitBuf = g_UserMsgs.StartBitBufMessage(m_HinTextMsg, players, count, USERMSG_RELIABLE)) == NULL)
   680 	{
   681 		return false;
   682 	}
   683 
   684 	const char *pre_byte = g_pGameConf->GetKeyValue("HintTextPreByte");
   685 	if (pre_byte != NULL && strcmp(pre_byte, "yes") == 0)
   686 	{
   687 		pBitBuf->WriteByte(1);
   688 	}
   689 	pBitBuf->WriteString(msg);
   690 #endif
   691 
   692 	g_UserMsgs.EndMessage();
   693 
   694 	return true;
   695 }
   696 
   697 bool CHalfLife2::ShowVGUIMenu(int client, const char *name, KeyValues *data, bool show)
   698 {
   699 	KeyValues *SubKey = NULL;
   700 	int count = 0;
   701 	cell_t players[] = {client};
   702 
   703 #if SOURCE_ENGINE == SE_DOTA
   704 	CUserMsg_VGUIMenu *pMsg;
   705 	if ((pMsg = (CUserMsg_VGUIMenu *)g_UserMsgs.StartProtobufMessage(m_VGUIMenu, players, 1, USERMSG_RELIABLE)) == NULL)
   706 	{
   707 		return false;
   708 	}
   709 #elif SOURCE_ENGINE == SE_CSGO
   710 	CCSUsrMsg_VGUIMenu *pMsg;
   711 	if ((pMsg = (CCSUsrMsg_VGUIMenu *)g_UserMsgs.StartProtobufMessage(m_VGUIMenu, players, 1, USERMSG_RELIABLE)) == NULL)
   712 	{
   713 		return false;
   714 	}
   715 #else
   716 	bf_write *pBitBuf = NULL;
   717 	if ((pBitBuf = g_UserMsgs.StartBitBufMessage(m_VGUIMenu, players, 1, USERMSG_RELIABLE)) == NULL)
   718 	{
   719 		return false;
   720 	}
   721 #endif
   722 
   723 	if (data)
   724 	{
   725 		SubKey = data->GetFirstSubKey();
   726 		while (SubKey)
   727 		{
   728 			count++;
   729 			SubKey = SubKey->GetNextKey();
   730 		}
   731 		SubKey = data->GetFirstSubKey();
   732 	}
   733 
   734 #if SOURCE_ENGINE == SE_DOTA
   735 	pMsg->set_name(name);
   736 	pMsg->set_show(show);
   737 
   738 	while (SubKey)
   739 	{
   740 		CUserMsg_VGUIMenu_Keys *key = pMsg->add_keys();
   741 		key->set_name(SubKey->GetName());
   742 		key->set_value(SubKey->GetString());
   743 		SubKey = SubKey->GetNextKey();
   744 	}
   745 #elif SOURCE_ENGINE == SE_CSGO
   746 	pMsg->set_name(name);
   747 	pMsg->set_show(show);
   748 
   749 	while (SubKey)
   750 	{
   751 		CCSUsrMsg_VGUIMenu_Subkey *key = pMsg->add_subkeys();
   752 		key->set_name(SubKey->GetName());
   753 		key->set_str(SubKey->GetString());
   754 		SubKey = SubKey->GetNextKey();
   755 	}
   756 #else
   757 	pBitBuf->WriteString(name);
   758 	pBitBuf->WriteByte((show) ? 1 : 0);
   759 	pBitBuf->WriteByte(count);
   760 	while (SubKey)
   761 	{
   762 		pBitBuf->WriteString(SubKey->GetName());
   763 		pBitBuf->WriteString(SubKey->GetString());
   764 		SubKey = SubKey->GetNextKey();
   765 	}
   766 #endif
   767 
   768 	g_UserMsgs.EndMessage();
   769 
   770 	return true;
   771 }
   772 
   773 void CHalfLife2::AddToFakeCliCmdQueue(int client, int userid, const char *cmd)
   774 {
   775 	DelayedFakeCliCmd *pFake;
   776 
   777 	if (m_FreeCmds.empty())
   778 	{
   779 		pFake = new DelayedFakeCliCmd;
   780 	} else {
   781 		pFake = m_FreeCmds.front();
   782 		m_FreeCmds.pop();
   783 	}
   784 
   785 	pFake->client = client;
   786 	pFake->userid = userid;
   787 	pFake->cmd.assign(cmd);
   788 
   789 	m_CmdQueue.push(pFake);
   790 }
   791 
   792 void CHalfLife2::ProcessFakeCliCmdQueue()
   793 {
   794 	while (!m_CmdQueue.empty())
   795 	{
   796 		DelayedFakeCliCmd *pFake = m_CmdQueue.first();
   797 
   798 		if (g_Players.GetClientOfUserId(pFake->userid) == pFake->client)
   799 		{
   800 			CPlayer *pPlayer = g_Players.GetPlayerByIndex(pFake->client);
   801 #if SOURCE_ENGINE == SE_DOTA
   802 			engine->ClientCommand(pPlayer->GetIndex(), "%s", pFake->cmd.c_str());
   803 #else
   804 			serverpluginhelpers->ClientCommand(pPlayer->GetEdict(), pFake->cmd.c_str());
   805 #endif
   806 		}
   807 
   808 		m_CmdQueue.pop();
   809 	}
   810 }
   811 
   812 bool CHalfLife2::IsLANServer()
   813 {
   814 	sv_lan = icvar->FindVar("sv_lan");
   815 
   816 	if (!sv_lan)
   817 	{
   818 		return false;
   819 	}
   820 
   821 	return (sv_lan->GetInt() != 0);
   822 }
   823 
   824 bool CHalfLife2::KVLoadFromFile(KeyValues *kv, IBaseFileSystem *filesystem, const char *resourceName, const char *pathID)
   825 {
   826 #if defined METAMOD_PLAPI_VERSION || PLAPI_VERSION >= 11
   827 	if (g_SMAPI->GetSourceEngineBuild() == SOURCE_ENGINE_ORIGINAL)
   828 #else
   829 	if (strcasecmp(g_SourceMod.GetGameFolderName(), "ship") == 0)
   830 #endif
   831 	{
   832 		Assert(filesystem);
   833 #ifdef _MSC_VER
   834 		Assert(_heapchk() == _HEAPOK);
   835 #endif
   836 
   837 		FileHandle_t f = filesystem->Open(resourceName, "rb", pathID);
   838 		if (!f)
   839 			return false;
   840 
   841 		// load file into a null-terminated buffer
   842 		int fileSize = filesystem->Size(f);
   843 		char *buffer = (char *)MemAllocScratch(fileSize + 1);
   844 
   845 		Assert(buffer);
   846 
   847 		filesystem->Read(buffer, fileSize, f); // read into local buffer
   848 
   849 		buffer[fileSize] = 0; // null terminate file as EOF
   850 
   851 		filesystem->Close( f );	// close file after reading
   852 
   853 		bool retOK = kv->LoadFromBuffer( resourceName, buffer, filesystem );
   854 
   855 		MemFreeScratch();
   856 
   857 		return retOK;
   858 	}
   859 	else
   860 	{
   861 		return kv->LoadFromFile(filesystem, resourceName, pathID);
   862 	}
   863 }
   864 
   865 void CHalfLife2::PushCommandStack(const CCommand *cmd)
   866 {
   867 	CachedCommandInfo info;
   868 
   869 	info.args = cmd;
   870 #if SOURCE_ENGINE <= SE_DARKMESSIAH
   871 	strncopy(info.cmd, cmd->Arg(0), sizeof(info.cmd));
   872 #endif
   873 
   874 	m_CommandStack.push(info);
   875 }
   876 
   877 const CCommand *CHalfLife2::PeekCommandStack()
   878 {
   879 	if (m_CommandStack.empty())
   880 	{
   881 		return NULL;
   882 	}
   883 
   884 	return m_CommandStack.front().args;
   885 }
   886 
   887 void CHalfLife2::PopCommandStack()
   888 {
   889 	m_CommandStack.pop();
   890 }
   891 
   892 const char *CHalfLife2::CurrentCommandName()
   893 {
   894 #if SOURCE_ENGINE >= SE_ORANGEBOX
   895 	return m_CommandStack.front().args->Arg(0);
   896 #else
   897 	return m_CommandStack.front().cmd;
   898 #endif
   899 }
   900 
   901 void CHalfLife2::AddDelayedKick(int client, int userid, const char *msg)
   902 {
   903 	CPlayer *pPlayer = g_Players.GetPlayerByIndex(client);
   904 	if (!pPlayer || !pPlayer->IsConnected() || pPlayer->IsInKickQueue())
   905 	{
   906 		return;
   907 	}
   908 	
   909 	pPlayer->MarkAsBeingKicked();
   910 
   911 	DelayedKickInfo kick;
   912 
   913 	kick.client = client;
   914 	kick.userid = userid;
   915 	UTIL_Format(kick.buffer, sizeof(kick.buffer), "%s", msg);
   916 
   917 	m_DelayedKicks.push(kick);
   918 }
   919 
   920 void CHalfLife2::ProcessDelayedKicks()
   921 {
   922 	while (!m_DelayedKicks.empty())
   923 	{
   924 		DelayedKickInfo info = m_DelayedKicks.first();
   925 		m_DelayedKicks.pop();
   926 
   927 		CPlayer *player = g_Players.GetPlayerByIndex(info.client);
   928 		if (player == NULL || player->GetUserId() != info.userid)
   929 		{
   930 			continue;
   931 		}
   932 
   933 		player->Kick(info.buffer);
   934 	}
   935 }
   936 
   937 edict_t *CHalfLife2::EdictOfIndex(int index)
   938 {
   939 	return ::PEntityOfEntIndex(index);
   940 }
   941 
   942 int CHalfLife2::IndexOfEdict(edict_t *pEnt)
   943 {
   944 	return ::IndexOfEdict(pEnt);
   945 }
   946 
   947 edict_t *CHalfLife2::GetHandleEntity(CBaseHandle &hndl)
   948 {
   949 	if (!hndl.IsValid())
   950 	{
   951 		return NULL;
   952 	}
   953 
   954 	int index = hndl.GetEntryIndex();
   955 
   956 	edict_t *pStoredEdict;
   957 	CBaseEntity *pStoredEntity;
   958 
   959 	if (!IndexToAThings(index, &pStoredEntity, &pStoredEdict))
   960 	{
   961 		return NULL;
   962 	}
   963 
   964 	if (pStoredEdict == NULL || pStoredEntity == NULL)
   965 	{
   966 		return NULL;
   967 	}
   968 
   969 	IServerEntity *pSE = pStoredEdict->GetIServerEntity();
   970 
   971 	if (pSE == NULL)
   972 	{
   973 		return NULL;
   974 	}
   975 
   976 	if (pSE->GetRefEHandle() != hndl)
   977 	{
   978 		return NULL;
   979 	}
   980 
   981 	return pStoredEdict;
   982 }
   983 
   984 void CHalfLife2::SetHandleEntity(CBaseHandle &hndl, edict_t *pEnt)
   985 {
   986 	IServerEntity *pEntOther = pEnt->GetIServerEntity();
   987 
   988 	if (pEntOther == NULL)
   989 	{
   990 		return;
   991 	}
   992 
   993 	hndl.Set(pEntOther);
   994 }
   995 
   996 const char *CHalfLife2::GetCurrentMap()
   997 {
   998 	return STRING(gpGlobals->mapname);
   999 }
  1000 
  1001 void CHalfLife2::ServerCommand(const char *buffer)
  1002 {
  1003 	engine->ServerCommand(buffer);
  1004 }
  1005 
  1006 cell_t CHalfLife2::EntityToReference(CBaseEntity *pEntity)
  1007 {
  1008 	IServerUnknown *pUnknown = (IServerUnknown *)pEntity;
  1009 	CBaseHandle hndl = pUnknown->GetRefEHandle();
  1010 	return (hndl.ToInt() | (1<<31));
  1011 }
  1012 
  1013 CBaseEntity *CHalfLife2::ReferenceToEntity(cell_t entRef)
  1014 {
  1015 	if ((unsigned)entRef == INVALID_EHANDLE_INDEX)
  1016 	{
  1017 		return NULL;
  1018 	}
  1019 
  1020 	CEntInfo *pInfo = NULL;
  1021 
  1022 	if (entRef & (1<<31))
  1023 	{
  1024 		/* Proper ent reference */
  1025 		int hndlValue = entRef & ~(1<<31);
  1026 		CBaseHandle hndl(hndlValue);
  1027 
  1028 		pInfo = LookupEntity(hndl.GetEntryIndex());
  1029 		if (!pInfo || pInfo->m_SerialNumber != hndl.GetSerialNumber())
  1030 		{
  1031 			return NULL;
  1032 		}
  1033 	}
  1034 	else
  1035 	{
  1036 		/* Old style index only */
  1037 		pInfo = LookupEntity(entRef);
  1038 	}
  1039 
  1040 	if (!pInfo)
  1041 	{
  1042 		return NULL;
  1043 	}
  1044 
  1045 	IServerUnknown *pUnk = static_cast<IServerUnknown *>(pInfo->m_pEntity);
  1046 	if (pUnk)
  1047 	{
  1048 		return pUnk->GetBaseEntity();
  1049 	}
  1050 
  1051 	return NULL;
  1052 }
  1053 
  1054 /**
  1055  * Retrieves the CEntInfo pointer from g_EntList for a given entity index
  1056  */
  1057 CEntInfo *CHalfLife2::LookupEntity(int entIndex)
  1058 {
  1059 	// Make sure that our index is within the bounds of the global ent array
  1060 	if (entIndex < 0 || entIndex >= NUM_ENT_ENTRIES)
  1061 	{
  1062 		return NULL;
  1063 	}
  1064 
  1065 	if (!g_EntList || entInfoOffset == -1)
  1066 	{
  1067 		/* Attempt to use engine interface instead */
  1068 		static CEntInfo tempInfo;
  1069 		tempInfo.m_pNext = NULL;
  1070 		tempInfo.m_pPrev = NULL;
  1071 
  1072 		edict_t *pEdict = PEntityOfEntIndex(entIndex);
  1073 
  1074 		if (!pEdict)
  1075 		{
  1076 			return NULL;
  1077 		}
  1078 
  1079 		IServerUnknown *pUnk = pEdict->GetUnknown();
  1080 
  1081 		if (!pUnk)
  1082 		{
  1083 			return NULL;
  1084 		}
  1085 
  1086 		tempInfo.m_pEntity = pUnk;
  1087 		tempInfo.m_SerialNumber = pUnk->GetRefEHandle().GetSerialNumber();
  1088 
  1089 		return &tempInfo;
  1090 	}
  1091 
  1092 	CEntInfo *pArray = (CEntInfo *)(((unsigned char *)g_EntList) + entInfoOffset);
  1093 	return &pArray[entIndex];
  1094 }
  1095 
  1096 /**
  1097 	SERIAL_MASK = 0x7fff (15 bits)
  1098 
  1099 	#define	MAX_EDICT_BITS				11
  1100 	#define NUM_ENT_ENTRY_BITS		(MAX_EDICT_BITS + 1)
  1101 	m_Index = iEntry | (iSerialNumber << NUM_ENT_ENTRY_BITS); 
  1102 	
  1103 	Top 5 bits of a handle are unused.
  1104 	Bit 31 - Our 'reference' flag indicator
  1105 */
  1106 
  1107 cell_t CHalfLife2::IndexToReference(int entIndex)
  1108 {
  1109 	CBaseEntity *pEnt = ReferenceToEntity(entIndex);
  1110 	if (!pEnt)
  1111 	{
  1112 		return INVALID_EHANDLE_INDEX;
  1113 	}
  1114 
  1115 	return EntityToReference(pEnt);
  1116 }
  1117 
  1118 int CHalfLife2::ReferenceToIndex(cell_t entRef)
  1119 {
  1120 	if ((unsigned)entRef == INVALID_EHANDLE_INDEX)
  1121 	{
  1122 		return INVALID_EHANDLE_INDEX;
  1123 	}
  1124 
  1125 	if (entRef & (1<<31))
  1126 	{
  1127 		/* Proper ent reference */
  1128 		int hndlValue = entRef & ~(1<<31);
  1129 		CBaseHandle hndl(hndlValue);
  1130 
  1131 		CEntInfo *pInfo = LookupEntity(hndl.GetEntryIndex());
  1132 
  1133 		if (pInfo->m_SerialNumber != hndl.GetSerialNumber())
  1134 		{
  1135 			return INVALID_EHANDLE_INDEX;
  1136 		}
  1137 
  1138 		return hndl.GetEntryIndex();
  1139 	}
  1140 
  1141 	return entRef;
  1142 }
  1143 
  1144 cell_t CHalfLife2::EntityToBCompatRef(CBaseEntity *pEntity)
  1145 {
  1146 	if (pEntity == NULL)
  1147 	{
  1148 		return INVALID_EHANDLE_INDEX;
  1149 	}
  1150 
  1151 	IServerUnknown *pUnknown = (IServerUnknown *)pEntity;
  1152 	CBaseHandle hndl = pUnknown->GetRefEHandle();
  1153 
  1154 	if (hndl == INVALID_EHANDLE_INDEX)
  1155 	{
  1156 		return INVALID_EHANDLE_INDEX;
  1157 	}
  1158 	
  1159 	if (hndl.GetEntryIndex() >= MAX_EDICTS)
  1160 	{
  1161 		return (hndl.ToInt() | (1<<31));
  1162 	}
  1163 	else
  1164 	{
  1165 		return hndl.GetEntryIndex();
  1166 	}
  1167 }
  1168 
  1169 cell_t CHalfLife2::ReferenceToBCompatRef(cell_t entRef)
  1170 {
  1171 	if ((unsigned)entRef == INVALID_EHANDLE_INDEX)
  1172 	{
  1173 		return INVALID_EHANDLE_INDEX;
  1174 	}
  1175 
  1176 	int hndlValue = entRef & ~(1<<31);
  1177 	CBaseHandle hndl(hndlValue);
  1178 
  1179 	if (hndl.GetEntryIndex() < MAX_EDICTS)
  1180 	{
  1181 		return hndl.GetEntryIndex();
  1182 	}
  1183 
  1184 	return entRef;
  1185 }
  1186 
  1187 void *CHalfLife2::GetGlobalEntityList()
  1188 {
  1189 	return g_EntList;
  1190 }
  1191 
  1192 int CHalfLife2::GetSendPropOffset(SendProp *prop)
  1193 {
  1194 	return prop->GetOffset();
  1195 }
  1196 
  1197 const char *CHalfLife2::GetEntityClassname(edict_t * pEdict)
  1198 {
  1199 	if (pEdict == NULL || pEdict->IsFree())
  1200 	{
  1201 		return NULL;
  1202 	}
  1203 	
  1204 	IServerUnknown *pUnk = pEdict->GetUnknown();
  1205 	if (pUnk == NULL)
  1206 	{
  1207 		return NULL;
  1208 	}
  1209 	
  1210 	CBaseEntity * pEntity = pUnk->GetBaseEntity();
  1211 	
  1212 	if (pEntity == NULL)
  1213 	{
  1214 		return NULL;
  1215 	}
  1216 	
  1217 	return GetEntityClassname(pEntity);
  1218 }
  1219 
  1220 const char *CHalfLife2::GetEntityClassname(CBaseEntity *pEntity)
  1221 {
  1222 	static int offset = -1;
  1223 	if (offset == -1)
  1224 	{
  1225 		CBaseEntity *pGetterEnt = ReferenceToEntity(0);
  1226 		datamap_t *pMap = GetDataMap(pGetterEnt);
  1227 		
  1228 		sm_datatable_info_t info;
  1229 		if (!FindDataMapInfo(pMap, "m_iClassname", &info))
  1230 		{
  1231 			return NULL;
  1232 		}
  1233 		
  1234 		offset = info.actual_offset;
  1235 	}
  1236 
  1237 	return *(const char **)(((unsigned char *)pEntity) + offset);
  1238 }
  1239 
  1240 #if SOURCE_ENGINE >= SE_LEFT4DEAD
  1241 static bool ResolveFuzzyMapName(const char *fuzzyName, char *outFullname, int size)
  1242 {
  1243 	static ConCommand *pHelperCmd = g_pCVar->FindCommand("changelevel");
  1244 	if (!pHelperCmd || !pHelperCmd->CanAutoComplete())
  1245 		return false;
  1246 
  1247 	static size_t helperCmdLen = strlen(pHelperCmd->GetName());
  1248 
  1249 	CUtlVector<CUtlString> results;
  1250 	pHelperCmd->AutoCompleteSuggest(fuzzyName, results);
  1251 	if (results.Count() == 0)
  1252 		return false;
  1253 
  1254 	// Results come back as you'd see in autocomplete. (ie. "changelevel fullmapnamehere"),
  1255 	// so skip ahead to start of map path/name
  1256 
  1257 	// Like the engine, we're only going to deal with the first match.
  1258 
  1259 	strncopy(outFullname, &results[0][helperCmdLen + 1], size);
  1260 
  1261 	return true;
  1262 }
  1263 #endif
  1264 
  1265 bool CHalfLife2::IsMapValid(const char *map)
  1266 {
  1267 	if (!map || !map[0])
  1268 		return false;
  1269 
  1270 	bool ret = engine->IsMapValid(map);
  1271 #if SOURCE_ENGINE >= SE_LEFT4DEAD
  1272 	if (!ret)
  1273 	{
  1274 		static char szFuzzyName[PLATFORM_MAX_PATH];
  1275 		if (ResolveFuzzyMapName(map, szFuzzyName, sizeof(szFuzzyName)))
  1276 		{
  1277 			ret = engine->IsMapValid(szFuzzyName);
  1278 		}
  1279 	}
  1280 #endif
  1281 	return ret;
  1282 }