core/HalfLife2.cpp
author Scott Ehlert <ds@alliedmods.net>
Wed Feb 18 02:19:22 2009 -0600 (2009-02-18)
changeset 2583 d3b2571c378b
parent 2400 76b7b4575e21
child 2596 4fb11ed1e6bc
permissions -rw-r--r--
Added support for Dark Messiah engine and game. (no bug, r=me).

The development of this feature would not be possible without the support of the following people from the game's community:
Dylan Riggs, Carl Pettengill, Ed Moreland, and Christian.
     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 "GameConfigs.h"
    39 #include <compat_wrappers.h>
    40 
    41 CHalfLife2 g_HL2;
    42 ConVar *sv_lan = NULL;
    43 
    44 namespace SourceHook
    45 {
    46 	template<>
    47 	int HashFunction<datamap_t *>(datamap_t * const &k)
    48 	{
    49 		return reinterpret_cast<int>(k);
    50 	}
    51 
    52 	template<>
    53 	int Compare<datamap_t *>(datamap_t * const &k1, datamap_t * const &k2)
    54 	{
    55 		return (k1-k2);
    56 	}
    57 }
    58 
    59 CHalfLife2::CHalfLife2()
    60 {
    61 	m_pClasses = sm_trie_create();
    62 }
    63 
    64 CHalfLife2::~CHalfLife2()
    65 {
    66 	sm_trie_destroy(m_pClasses);
    67 
    68 	List<DataTableInfo *>::iterator iter;
    69 	DataTableInfo *pInfo;
    70 	for (iter=m_Tables.begin(); iter!=m_Tables.end(); iter++)
    71 	{
    72 		pInfo = (*iter);
    73 		delete pInfo;
    74 	}
    75 
    76 	m_Tables.clear();
    77 
    78 	THash<datamap_t *, DataMapTrie>::iterator h_iter;
    79 	for (h_iter=m_Maps.begin(); h_iter!=m_Maps.end(); h_iter++)
    80 	{
    81 		if (h_iter->val.trie)
    82 		{
    83 			sm_trie_destroy(h_iter->val.trie);
    84 			h_iter->val.trie = NULL;
    85 		}
    86 	}
    87 
    88 	m_Maps.clear();
    89 }
    90 
    91 #if SOURCE_ENGINE != SE_DARKMESSIAH
    92 CSharedEdictChangeInfo *g_pSharedChangeInfo = NULL;
    93 #endif
    94 
    95 bool is_original_engine = false;
    96 
    97 void CHalfLife2::OnSourceModStartup(bool late)
    98 {
    99 #if SOURCE_ENGINE != SE_DARKMESSIAH
   100 
   101 	/* The Ship currently is the only known game to use an older version of the engine */
   102 #if defined METAMOD_PLAPI_VERSION
   103 	if (g_SMAPI->GetSourceEngineBuild() == SOURCE_ENGINE_ORIGINAL)
   104 #else
   105 	if (strcasecmp(g_SourceMod.GetGameFolderName(), "ship") == 0)
   106 #endif
   107 	{
   108 		is_original_engine = true;
   109 	}
   110 	else if (g_pSharedChangeInfo == NULL)
   111 	{
   112 		g_pSharedChangeInfo = engine->GetSharedEdictChangeInfo();
   113 	}
   114 #endif
   115 }
   116 
   117 void CHalfLife2::OnSourceModAllInitialized()
   118 {
   119 	m_MsgTextMsg = g_UserMsgs.GetMessageIndex("TextMsg");
   120 	m_HinTextMsg = g_UserMsgs.GetMessageIndex("HintText");
   121 	m_SayTextMsg = g_UserMsgs.GetMessageIndex("SayText");
   122 	m_VGUIMenu = g_UserMsgs.GetMessageIndex("VGUIMenu");
   123 	g_ShareSys.AddInterface(NULL, this);
   124 }
   125 
   126 #if !defined METAMOD_PLAPI_VERSION
   127 bool CHalfLife2::IsOriginalEngine()
   128 {
   129 	return is_original_engine;
   130 }
   131 #endif
   132 
   133 #if SOURCE_ENGINE != SE_DARKMESSIAH
   134 IChangeInfoAccessor *CBaseEdict::GetChangeAccessor()
   135 {
   136 	return engine->GetChangeAccessor( (const edict_t *)this );
   137 }
   138 #endif
   139 
   140 bool UTIL_FindInSendTable(SendTable *pTable, 
   141 						  const char *name,
   142 						  sm_sendprop_info_t *info,
   143 						  unsigned int offset)
   144 {
   145 	const char *pname;
   146 	int props = pTable->GetNumProps();
   147 	SendProp *prop;
   148 
   149 	for (int i=0; i<props; i++)
   150 	{
   151 		prop = pTable->GetProp(i);
   152 		pname = prop->GetName();
   153 		if (pname && strcmp(name, pname) == 0)
   154 		{
   155 			info->prop = prop;
   156 			info->actual_offset = offset + info->prop->GetOffset();
   157 			return true;
   158 		}
   159 		if (prop->GetDataTable())
   160 		{
   161 			if (UTIL_FindInSendTable(prop->GetDataTable(), 
   162 				name,
   163 				info,
   164 				offset + prop->GetOffset())
   165 				)
   166 			{
   167 				return true;
   168 			}
   169 		}
   170 	}
   171 
   172 	return false;
   173 }
   174 
   175 typedescription_t *UTIL_FindInDataMap(datamap_t *pMap, const char *name)
   176 {
   177 	while (pMap)
   178 	{
   179 		for (int i=0; i<pMap->dataNumFields; i++)
   180 		{
   181 			if (pMap->dataDesc[i].fieldName == NULL)
   182 			{
   183 				continue;
   184 			}
   185 			if (strcmp(name, pMap->dataDesc[i].fieldName) == 0)
   186 			{
   187 				return &(pMap->dataDesc[i]);
   188 			}
   189 			if (pMap->dataDesc[i].td)
   190 			{
   191 				typedescription_t *_td;
   192 				if ((_td=UTIL_FindInDataMap(pMap->dataDesc[i].td, name)) != NULL)
   193 				{
   194 					return _td;
   195 				}
   196 			}
   197 		}
   198 		pMap = pMap->baseMap;
   199 	}
   200 
   201 	return NULL; 
   202 }
   203 
   204 ServerClass *CHalfLife2::FindServerClass(const char *classname)
   205 {
   206 	DataTableInfo *pInfo = _FindServerClass(classname);
   207 
   208 	if (!pInfo)
   209 	{
   210 		return NULL;
   211 	}
   212 
   213 	return pInfo->sc;
   214 }
   215 
   216 DataTableInfo *CHalfLife2::_FindServerClass(const char *classname)
   217 {
   218 	DataTableInfo *pInfo = NULL;
   219 
   220 	if (!sm_trie_retrieve(m_pClasses, classname, (void **)&pInfo))
   221 	{
   222 		ServerClass *sc = gamedll->GetAllServerClasses();
   223 		while (sc)
   224 		{
   225 			if (strcmp(classname, sc->GetName()) == 0)
   226 			{
   227 				pInfo = new DataTableInfo;
   228 				pInfo->sc = sc;
   229 				sm_trie_insert(m_pClasses, classname, pInfo);
   230 				m_Tables.push_back(pInfo);
   231 				break;
   232 			}
   233 			sc = sc->m_pNext;
   234 		}
   235 		if (!pInfo)
   236 		{
   237 			return NULL;
   238 		}
   239 	}
   240 
   241 	return pInfo;
   242 }
   243 
   244 bool CHalfLife2::FindSendPropInfo(const char *classname, const char *offset, sm_sendprop_info_t *info)
   245 {
   246 	DataTableInfo *pInfo;
   247 	sm_sendprop_info_t *prop;
   248 
   249 	if ((pInfo = _FindServerClass(classname)) == NULL)
   250 	{
   251 		return false;
   252 	}
   253 
   254 	if ((prop = pInfo->lookup.retrieve(offset)) == NULL)
   255 	{
   256 		sm_sendprop_info_t temp_info;
   257 
   258 		if (!UTIL_FindInSendTable(pInfo->sc->m_pTable, offset, &temp_info, 0))
   259 		{
   260 			return false;
   261 		}
   262 
   263 		pInfo->lookup.insert(offset, temp_info);
   264 		*info = temp_info;
   265 	}
   266 	else
   267 	{
   268 		*info = *prop;
   269 	}
   270 	
   271 	return true;
   272 }
   273 
   274 SendProp *CHalfLife2::FindInSendTable(const char *classname, const char *offset)
   275 {
   276 	sm_sendprop_info_t info;
   277 
   278 	if (!FindSendPropInfo(classname, offset, &info))
   279 	{
   280 		return NULL;
   281 	}
   282 
   283 	return info.prop;
   284 }
   285 
   286 typedescription_t *CHalfLife2::FindInDataMap(datamap_t *pMap, const char *offset)
   287 {
   288 	typedescription_t *td = NULL;
   289 	DataMapTrie &val = m_Maps[pMap];
   290 
   291 	if (!val.trie)
   292 	{
   293 		val.trie = sm_trie_create();
   294 	}
   295 	if (!sm_trie_retrieve(val.trie, offset, (void **)&td))
   296 	{
   297 		if ((td = UTIL_FindInDataMap(pMap, offset)) != NULL)
   298 		{
   299 			sm_trie_insert(val.trie, offset, td);
   300 		}
   301 	}
   302 
   303 	return td;
   304 }
   305 
   306 void CHalfLife2::SetEdictStateChanged(edict_t *pEdict, unsigned short offset)
   307 {
   308 #if SOURCE_ENGINE != SE_DARKMESSIAH
   309 	if (g_pSharedChangeInfo != NULL)
   310 	{
   311 		if (offset)
   312 		{
   313 			pEdict->StateChanged(offset);
   314 		}
   315 		else
   316 		{
   317 			pEdict->StateChanged();
   318 		}
   319 	}
   320 	else
   321 #endif
   322 	{
   323 		pEdict->m_fStateFlags |= FL_EDICT_CHANGED;
   324 	}
   325 }
   326 
   327 bool CHalfLife2::TextMsg(int client, int dest, const char *msg)
   328 {
   329 	bf_write *pBitBuf = NULL;
   330 	cell_t players[] = {client};
   331 
   332 	if (dest == HUD_PRINTTALK)
   333 	{
   334 		const char *chat_saytext = g_pGameConf->GetKeyValue("ChatSayText");
   335 
   336 		/* Use SayText user message instead */
   337 		if (chat_saytext != NULL && strcmp(chat_saytext, "yes") == 0)
   338 		{
   339 			char buffer[192];
   340 			UTIL_Format(buffer, sizeof(buffer), "%s\1\n", msg);
   341 
   342 			if ((pBitBuf = g_UserMsgs.StartMessage(m_SayTextMsg, players, 1, USERMSG_RELIABLE)) == NULL)
   343 			{
   344 				return false;
   345 			}
   346 
   347 			pBitBuf->WriteByte(0);
   348 			pBitBuf->WriteString(buffer);
   349 			pBitBuf->WriteByte(1);
   350 
   351 			g_UserMsgs.EndMessage();
   352 
   353 			return true;
   354 		}
   355 	}
   356 
   357 	if ((pBitBuf = g_UserMsgs.StartMessage(m_MsgTextMsg, players, 1, USERMSG_RELIABLE)) == NULL)
   358 	{
   359 		return false;
   360 	}
   361 
   362 	pBitBuf->WriteByte(dest);
   363 	pBitBuf->WriteString(msg);
   364 
   365 	g_UserMsgs.EndMessage();
   366 
   367 	return true;
   368 }
   369 
   370 bool CHalfLife2::HintTextMsg(int client, const char *msg)
   371 {
   372 	bf_write *pBitBuf = NULL;
   373 	cell_t players[] = {client};
   374 
   375 	if ((pBitBuf = g_UserMsgs.StartMessage(m_HinTextMsg, players, 1, USERMSG_RELIABLE)) == NULL)
   376 	{
   377 		return false;
   378 	}
   379 
   380 	const char *pre_byte = g_pGameConf->GetKeyValue("HintTextPreByte");
   381 	if (pre_byte != NULL && strcmp(pre_byte, "yes") == 0)
   382 	{
   383 		pBitBuf->WriteByte(1);
   384 	}
   385 	pBitBuf->WriteString(msg);
   386 	g_UserMsgs.EndMessage();
   387 
   388 	return true;
   389 }
   390 
   391 bool CHalfLife2::ShowVGUIMenu(int client, const char *name, KeyValues *data, bool show)
   392 {
   393 	bf_write *pBitBuf = NULL;
   394 	KeyValues *SubKey = NULL;
   395 	int count = 0;
   396 	cell_t players[] = {client};
   397 
   398 	if ((pBitBuf = g_UserMsgs.StartMessage(m_VGUIMenu, players, 1, USERMSG_RELIABLE)) == NULL)
   399 	{
   400 		return false;
   401 	}
   402 
   403 	if (data)
   404 	{
   405 		SubKey = data->GetFirstSubKey();
   406 		while (SubKey)
   407 		{
   408 			count++;
   409 			SubKey = SubKey->GetNextKey();
   410 		}
   411 		SubKey = data->GetFirstSubKey();
   412 	}
   413 
   414 	pBitBuf->WriteString(name);
   415 	pBitBuf->WriteByte((show) ? 1 : 0);
   416 	pBitBuf->WriteByte(count);
   417 	while (SubKey)
   418 	{
   419 		pBitBuf->WriteString(SubKey->GetName());
   420 		pBitBuf->WriteString(SubKey->GetString());
   421 		SubKey = SubKey->GetNextKey();
   422 	}
   423 	g_UserMsgs.EndMessage();
   424 
   425 	return true;
   426 }
   427 
   428 void CHalfLife2::AddToFakeCliCmdQueue(int client, int userid, const char *cmd)
   429 {
   430 	DelayedFakeCliCmd *pFake;
   431 
   432 	if (m_FreeCmds.empty())
   433 	{
   434 		pFake = new DelayedFakeCliCmd;
   435 	} else {
   436 		pFake = m_FreeCmds.front();
   437 		m_FreeCmds.pop();
   438 	}
   439 
   440 	pFake->client = client;
   441 	pFake->userid = userid;
   442 	pFake->cmd.assign(cmd);
   443 
   444 	m_CmdQueue.push(pFake);
   445 }
   446 
   447 void CHalfLife2::ProcessFakeCliCmdQueue()
   448 {
   449 	while (!m_CmdQueue.empty())
   450 	{
   451 		DelayedFakeCliCmd *pFake = m_CmdQueue.first();
   452 
   453 		if (g_Players.GetClientOfUserId(pFake->userid) == pFake->client)
   454 		{
   455 			CPlayer *pPlayer = g_Players.GetPlayerByIndex(pFake->client);
   456 			serverpluginhelpers->ClientCommand(pPlayer->GetEdict(), pFake->cmd.c_str());
   457 		}
   458 
   459 		m_CmdQueue.pop();
   460 	}
   461 }
   462 
   463 bool CHalfLife2::IsLANServer()
   464 {
   465 	sv_lan = icvar->FindVar("sv_lan");
   466 
   467 	if (!sv_lan)
   468 	{
   469 		return false;
   470 	}
   471 
   472 	return (sv_lan->GetInt() != 0);
   473 }
   474 
   475 bool CHalfLife2::KVLoadFromFile(KeyValues *kv, IBaseFileSystem *filesystem, const char *resourceName, const char *pathID)
   476 {
   477 #if defined METAMOD_PLAPI_VERSION
   478 	if (g_SMAPI->GetSourceEngineBuild() == SOURCE_ENGINE_ORIGINAL)
   479 #else
   480 	if (strcasecmp(g_SourceMod.GetGameFolderName(), "ship") == 0)
   481 #endif
   482 	{
   483 		Assert(filesystem);
   484 #ifdef _MSC_VER
   485 		Assert(_heapchk() == _HEAPOK);
   486 #endif
   487 
   488 		FileHandle_t f = filesystem->Open(resourceName, "rb", pathID);
   489 		if (!f)
   490 			return false;
   491 
   492 		// load file into a null-terminated buffer
   493 		int fileSize = filesystem->Size(f);
   494 		char *buffer = (char *)MemAllocScratch(fileSize + 1);
   495 
   496 		Assert(buffer);
   497 
   498 		filesystem->Read(buffer, fileSize, f); // read into local buffer
   499 
   500 		buffer[fileSize] = 0; // null terminate file as EOF
   501 
   502 		filesystem->Close( f );	// close file after reading
   503 
   504 		bool retOK = kv->LoadFromBuffer( resourceName, buffer, filesystem );
   505 
   506 		MemFreeScratch();
   507 
   508 		return retOK;
   509 	}
   510 	else
   511 	{
   512 		return kv->LoadFromFile(filesystem, resourceName, pathID);
   513 	}
   514 }
   515 
   516 void CHalfLife2::PushCommandStack(const CCommand *cmd)
   517 {
   518 	CachedCommandInfo info;
   519 
   520 	info.args = cmd;
   521 #if SOURCE_ENGINE <= SE_DARKMESSIAH
   522 	strncopy(info.cmd, cmd->Arg(0), sizeof(info.cmd));
   523 #endif
   524 
   525 	m_CommandStack.push(info);
   526 }
   527 
   528 const CCommand *CHalfLife2::PeekCommandStack()
   529 {
   530 	if (m_CommandStack.empty())
   531 	{
   532 		return NULL;
   533 	}
   534 
   535 	return m_CommandStack.front().args;
   536 }
   537 
   538 void CHalfLife2::PopCommandStack()
   539 {
   540 	m_CommandStack.pop();
   541 }
   542 
   543 const char *CHalfLife2::CurrentCommandName()
   544 {
   545 #if SOURCE_ENGINE >= SE_ORANGEBOX
   546 	return m_CommandStack.front().args->Arg(0);
   547 #else
   548 	return m_CommandStack.front().cmd;
   549 #endif
   550 }
   551 
   552 void CHalfLife2::AddDelayedKick(int client, int userid, const char *msg)
   553 {
   554 	DelayedKickInfo kick;
   555 
   556 	kick.client = client;
   557 	kick.userid = userid;
   558 	UTIL_Format(kick.buffer, sizeof(kick.buffer), "%s", msg);
   559 
   560 	m_DelayedKicks.push(kick);
   561 }
   562 
   563 void CHalfLife2::ProcessDelayedKicks()
   564 {
   565 	while (!m_DelayedKicks.empty())
   566 	{
   567 		DelayedKickInfo info = m_DelayedKicks.first();
   568 		m_DelayedKicks.pop();
   569 
   570 		CPlayer *player = g_Players.GetPlayerByIndex(info.client);
   571 		if (player == NULL || player->GetUserId() != info.userid)
   572 		{
   573 			continue;
   574 		}
   575 
   576 		player->Kick(info.buffer);
   577 	}
   578 }