core/logic/smn_maplists.cpp
author Kyle Sanderson <kyle.leet@gmail.com>
Mon Aug 12 00:42:56 2013 +0100 (2013-08-12)
changeset 3994 4bc73bac0f4c
parent 3973 5c5ed57cca88
child 4066 c055944bdfd6
permissions -rw-r--r--
Added helper stock to check if a command exists. (bug 5510, r=asherkin)
dvander@2726
     1
/**
dvander@2726
     2
 * vim: set ts=4 :
dvander@2726
     3
 * =============================================================================
dvander@2726
     4
 * SourceMod
dvander@2726
     5
 * Copyright (C) 2004-2008 AlliedModders LLC.  All rights reserved.
dvander@2726
     6
 * =============================================================================
dvander@2726
     7
 *
dvander@2726
     8
 * This program is free software; you can redistribute it and/or modify it under
dvander@2726
     9
 * the terms of the GNU General Public License, version 3.0, as published by the
dvander@2726
    10
 * Free Software Foundation.
dvander@2726
    11
 * 
dvander@2726
    12
 * This program is distributed in the hope that it will be useful, but WITHOUT
dvander@2726
    13
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
dvander@2726
    14
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
dvander@2726
    15
 * details.
dvander@2726
    16
 *
dvander@2726
    17
 * You should have received a copy of the GNU General Public License along with
dvander@2726
    18
 * this program.  If not, see <http://www.gnu.org/licenses/>.
dvander@2726
    19
 *
dvander@2726
    20
 * As a special exception, AlliedModders LLC gives you permission to link the
dvander@2726
    21
 * code of this program (as well as its derivative works) to "Half-Life 2," the
dvander@2726
    22
 * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
dvander@2726
    23
 * by the Valve Corporation.  You must obey the GNU General Public License in
dvander@2726
    24
 * all respects for all other code used.  Additionally, AlliedModders LLC grants
dvander@2726
    25
 * this exception to all derivative works.  AlliedModders LLC defines further
dvander@2726
    26
 * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
dvander@2726
    27
 * or <http://www.sourcemod.net/license.php>.
dvander@2726
    28
 *
dvander@2726
    29
 * Version: $Id$
dvander@2726
    30
 */
dvander@2726
    31
dvander@2726
    32
#include <sh_list.h>
dvander@2726
    33
#include <sm_trie_tpl.h>
dvander@2726
    34
#include "common_logic.h"
dvander@2726
    35
#include "CellArray.h"
skamonkey@3789
    36
#include <IGameHelpers.h>
dvander@2726
    37
#include <ILibrarySys.h>
dvander@2726
    38
#include <ITextParsers.h>
dvander@2726
    39
#include <ISourceMod.h>
voidedweasel@3929
    40
#include "stringutil.h"
dvander@2726
    41
dvander@2726
    42
using namespace SourceHook;
dvander@2726
    43
dvander@2726
    44
struct maplist_info_t
dvander@2726
    45
{
dvander@2726
    46
	bool bIsCompat;
dvander@2726
    47
	bool bIsPath;
dvander@2726
    48
	char name[PLATFORM_MAX_PATH];
dvander@2726
    49
	char path[PLATFORM_MAX_PATH];
dvander@2726
    50
	time_t last_modified_time;
dvander@2726
    51
	CellArray *pArray;
dvander@2726
    52
	int serial;
dvander@2726
    53
};
dvander@2726
    54
dvander@2726
    55
#define MAPLIST_FLAG_MAPSFOLDER		(1<<0)		/**< On failure, use all maps in the maps folder. */
dvander@2726
    56
#define MAPLIST_FLAG_CLEARARRAY		(1<<1)		/**< If an input array is specified, clear it before adding. */
dvander@2726
    57
#define MAPLIST_FLAG_NO_DEFAULT		(1<<2)		/**< Do not read "default" or "mapcyclefile" on failure. */
dvander@2726
    58
dvander@2726
    59
class MapLists : public SMGlobalClass, public ITextListener_SMC
dvander@2726
    60
{
dvander@2726
    61
public:
dvander@2726
    62
	enum MapListState
dvander@2726
    63
	{
dvander@2726
    64
		MPS_NONE,
dvander@2726
    65
		MPS_GLOBAL,
dvander@2726
    66
		MPS_MAPLIST,
dvander@2726
    67
	};
dvander@2726
    68
public:
dvander@2726
    69
	MapLists()
dvander@2726
    70
	{
dvander@2726
    71
		m_pMapCycleFile = NULL;
dvander@2726
    72
		m_ConfigLastChanged = 0;
dvander@2726
    73
		m_nSerialChange = 0;
dvander@2726
    74
	}
dvander@2726
    75
	void OnSourceModAllInitialized()
dvander@2726
    76
	{
dvander@2726
    77
		g_pSM->BuildPath(Path_SM, m_ConfigFile, sizeof(m_ConfigFile), "configs/maplists.cfg");
dvander@2726
    78
	}
dvander@2726
    79
	void OnSourceModShutdown()
dvander@2726
    80
	{
dvander@2726
    81
		DumpCache(NULL);
dvander@2726
    82
	}
skamonkey@3973
    83
	void GetMapCycleFilePath(char *pBuffer, int maxlen)
skamonkey@3973
    84
	{
skamonkey@3973
    85
		const char *pEngineName = smcore.GetSourceEngineName();
skamonkey@3973
    86
		const char *pMapCycleFileName = m_pMapCycleFile ? smcore.GetCvarString(m_pMapCycleFile) : "mapcycle.txt";
skamonkey@3973
    87
skamonkey@3973
    88
		if (strcmp(pEngineName, "tf2") == 0 || strcmp(pEngineName, "css") == 0
skamonkey@3973
    89
			|| strcmp(pEngineName, "dods") == 0 || strcmp(pEngineName, "hl2dm") == 0)
skamonkey@3973
    90
		{
skamonkey@3973
    91
			// These four games and Source SDK 2013 do a lookup in this order; so shall we.
skamonkey@3973
    92
			g_pSM->BuildPath(Path_Game,
skamonkey@3973
    93
				pBuffer,
skamonkey@3973
    94
				maxlen,
skamonkey@3973
    95
				"cfg/%s",
skamonkey@3973
    96
				pMapCycleFileName);
skamonkey@3973
    97
skamonkey@3973
    98
			if (!libsys->PathExists(pBuffer))
skamonkey@3973
    99
			{
skamonkey@3973
   100
				g_pSM->BuildPath(Path_Game,
skamonkey@3973
   101
					pBuffer,
skamonkey@3973
   102
					maxlen,
skamonkey@3973
   103
					"%s",
skamonkey@3973
   104
					pMapCycleFileName);
skamonkey@3973
   105
skamonkey@3973
   106
				if (!libsys->PathExists(pBuffer))
skamonkey@3973
   107
				{
skamonkey@3973
   108
					g_pSM->BuildPath(Path_Game,
skamonkey@3973
   109
						pBuffer,
skamonkey@3973
   110
						maxlen,
skamonkey@3973
   111
						"cfg/mapcycle_default.txt");
skamonkey@3973
   112
				}
skamonkey@3973
   113
			}
skamonkey@3973
   114
		}
skamonkey@3973
   115
		else
skamonkey@3973
   116
		{
skamonkey@3973
   117
			g_pSM->BuildPath(Path_Game,
skamonkey@3973
   118
				pBuffer,
skamonkey@3973
   119
				maxlen,
skamonkey@3973
   120
				"%s",
skamonkey@3973
   121
				pMapCycleFileName);
skamonkey@3973
   122
		}
skamonkey@3973
   123
	}
dvander@2726
   124
	void AddOrUpdateDefault(const char *name, const char *file)
dvander@2726
   125
	{
dvander@2726
   126
		char path[PLATFORM_MAX_PATH];
dvander@2726
   127
		maplist_info_t *pMapList, **ppMapList;
dvander@2726
   128
dvander@2726
   129
		if ((ppMapList = m_ListLookup.retrieve(name)) == NULL)
dvander@2726
   130
		{
dvander@2726
   131
			pMapList = new maplist_info_t;
dvander@2726
   132
			pMapList->bIsCompat = true;
dvander@2726
   133
			pMapList->bIsPath = true;
dvander@2726
   134
			pMapList->last_modified_time = 0;
dvander@2726
   135
			smcore.strncopy(pMapList->name, name, sizeof(pMapList->name));
dvander@2726
   136
			pMapList->pArray = NULL;
dvander@2726
   137
			g_pSM->BuildPath(Path_Game, pMapList->path, sizeof(pMapList->path), "%s", file);
dvander@2726
   138
			pMapList->serial = 0;
dvander@2726
   139
			m_ListLookup.insert(name, pMapList);
dvander@2726
   140
			m_MapLists.push_back(pMapList);
dvander@2726
   141
			return;
dvander@2726
   142
		}
dvander@2726
   143
dvander@2726
   144
		pMapList = *ppMapList;
dvander@2726
   145
dvander@2726
   146
		/* Don't modify if it's from the config file */
dvander@2726
   147
		if (!pMapList->bIsCompat)
dvander@2726
   148
		{
dvander@2726
   149
			return;
dvander@2726
   150
		}
dvander@2726
   151
dvander@2726
   152
		g_pSM->BuildPath(Path_Game, path, sizeof(path), "%s", file);
dvander@2726
   153
dvander@2726
   154
		/* If the path matches, don't reset the serial/time */
dvander@2726
   155
		if (strcmp(path, pMapList->path) == 0)
dvander@2726
   156
		{
dvander@2726
   157
			return;
dvander@2726
   158
		}
dvander@2726
   159
dvander@2726
   160
		smcore.strncopy(pMapList->path, path, sizeof(pMapList->path));
dvander@2726
   161
		pMapList->bIsPath = true;
dvander@2726
   162
		pMapList->last_modified_time = 0;
dvander@2726
   163
		pMapList->serial = 0;
dvander@2726
   164
	}
dvander@2726
   165
	void UpdateCache()
dvander@2726
   166
	{
dvander@2726
   167
		bool fileFound;
dvander@2726
   168
		SMCError error;
dvander@2726
   169
		time_t fileTime;
dvander@2726
   170
		SMCStates states = {0, 0};
dvander@2726
   171
		
dvander@2726
   172
		fileFound = libsys->FileTime(m_ConfigFile, FileTime_LastChange, &fileTime);
dvander@2726
   173
dvander@2726
   174
		/* If the file is found and hasn't changed, bail out now. */
dvander@2726
   175
		if (fileFound && fileTime == m_ConfigLastChanged)
dvander@2726
   176
		{
dvander@2726
   177
			return;
dvander@2726
   178
		}
dvander@2726
   179
dvander@2726
   180
		/* If the file wasn't found, and we already have entries, we bail out too. 
dvander@2726
   181
		 * This case lets us optimize when a user deletes the config file, so we 
dvander@2726
   182
		 * don't reparse every single time the function is called.
dvander@2726
   183
		 */
dvander@2726
   184
		if (!fileFound && m_MapLists.size() > 0)
dvander@2726
   185
		{
dvander@2726
   186
			return;
dvander@2726
   187
		}
dvander@2726
   188
dvander@2726
   189
		m_pMapCycleFile = smcore.FindConVar("mapcyclefile");
dvander@2726
   190
dvander@2726
   191
		/* Dump everything we know about. */
dvander@2726
   192
		List<maplist_info_t *> compat;
dvander@2726
   193
		DumpCache(&compat);
dvander@2726
   194
dvander@2726
   195
		/* All this is to add the default entry back in. */
dvander@2726
   196
		maplist_info_t *pDefList = new maplist_info_t;
dvander@2726
   197
dvander@2726
   198
		pDefList->bIsPath = true;
dvander@2726
   199
		smcore.strncopy(pDefList->name, "mapcyclefile", sizeof(pDefList->name));
skamonkey@3966
   200
skamonkey@3973
   201
		GetMapCycleFilePath(pDefList->path, sizeof(pDefList->path));
skamonkey@3966
   202
		
dvander@2726
   203
		pDefList->last_modified_time = 0;
dvander@2726
   204
		pDefList->pArray = NULL;
dvander@2726
   205
		pDefList->serial = 0;
dvander@2726
   206
dvander@2726
   207
		m_ListLookup.insert("mapcyclefile", pDefList);
dvander@2726
   208
		m_MapLists.push_back(pDefList);
dvander@2726
   209
dvander@2726
   210
		/* Now parse the config file even if we don't know about it.
dvander@2726
   211
		 * This will give us a nice error message.
dvander@2726
   212
		 */
dvander@2726
   213
		if ((error = textparser->ParseFile_SMC(m_ConfigFile, this, &states))
dvander@2726
   214
			!= SMCError_Okay)
dvander@2726
   215
		{
dvander@2726
   216
			const char *errmsg = textparser->GetSMCErrorString(error);
dvander@2726
   217
			if (errmsg == NULL)
dvander@2726
   218
			{
dvander@2726
   219
				errmsg = "Unknown error";
dvander@2726
   220
			}
dvander@2726
   221
			smcore.LogError("[SM] Could not parse file \"%s\"", m_ConfigFile);
dvander@2726
   222
			smcore.LogError("[SM] Error on line %d (col %d): %s", states.line, states.col, errmsg);
dvander@2726
   223
		}
dvander@2726
   224
		else
dvander@2726
   225
		{
dvander@2726
   226
			m_ConfigLastChanged = fileTime;
dvander@2726
   227
		}
dvander@2726
   228
dvander@2726
   229
		/* Now, re-add compat stuff back in if we can. */
dvander@2726
   230
		List<maplist_info_t *>::iterator iter = compat.begin();
dvander@2726
   231
		while (iter != compat.end())
dvander@2726
   232
		{
dvander@2726
   233
			if (m_ListLookup.retrieve((*iter)->name) != NULL)
dvander@2726
   234
			{
dvander@2726
   235
				/* The compatibility shim is no longer needed. */
dvander@2726
   236
				delete (*iter)->pArray;
dvander@2726
   237
				delete (*iter);
dvander@2726
   238
			}
dvander@2726
   239
			else
dvander@2726
   240
			{
dvander@2726
   241
				m_ListLookup.insert((*iter)->name, (*iter));
dvander@2726
   242
				m_MapLists.push_back((*iter));
dvander@2726
   243
			}
dvander@2726
   244
			iter = compat.erase(iter);
dvander@2726
   245
		}
dvander@2726
   246
	}
dvander@2726
   247
	void ReadSMC_ParseStart()
dvander@2726
   248
	{
dvander@2726
   249
		m_CurState = MPS_NONE;
dvander@2726
   250
		m_IgnoreLevel = 0;
dvander@2726
   251
		m_pCurMapList = NULL;
dvander@2726
   252
	}
dvander@2726
   253
	SMCResult ReadSMC_NewSection(const SMCStates *states, const char *name)
dvander@2726
   254
	{
dvander@2726
   255
		if (m_IgnoreLevel)
dvander@2726
   256
		{
dvander@2726
   257
			m_IgnoreLevel++;
dvander@2726
   258
			return SMCResult_Continue;
dvander@2726
   259
		}
dvander@2726
   260
dvander@2726
   261
		if (m_CurState == MPS_NONE)
dvander@2726
   262
		{
dvander@2726
   263
			if (strcmp(name, "MapLists") == 0)
dvander@2726
   264
			{
dvander@2726
   265
				m_CurState = MPS_GLOBAL;
dvander@2726
   266
			}
dvander@2726
   267
			else
dvander@2726
   268
			{
dvander@2726
   269
				m_IgnoreLevel = 1;
dvander@2726
   270
			}
dvander@2726
   271
		}
dvander@2726
   272
		else if (m_CurState == MPS_GLOBAL)
dvander@2726
   273
		{
dvander@2726
   274
			m_pCurMapList = new maplist_info_t;
dvander@2726
   275
dvander@2726
   276
			memset(m_pCurMapList, 0, sizeof(maplist_info_t));
dvander@2726
   277
			smcore.strncopy(m_pCurMapList->name, name, sizeof(m_pCurMapList->name));
dvander@2726
   278
dvander@2726
   279
			m_CurState = MPS_MAPLIST;
dvander@2726
   280
		}
dvander@2726
   281
		else if (m_CurState == MPS_MAPLIST)
dvander@2726
   282
		{
dvander@2726
   283
			m_IgnoreLevel++;
dvander@2726
   284
		}
dvander@2726
   285
dvander@2726
   286
		return SMCResult_Continue;
dvander@2726
   287
	}
dvander@2726
   288
	SMCResult ReadSMC_KeyValue(const SMCStates *states, const char *key, const char *value)
dvander@2726
   289
	{
dvander@2726
   290
		if (m_IgnoreLevel || m_pCurMapList == NULL)
dvander@2726
   291
		{
dvander@2726
   292
			return SMCResult_Continue;
dvander@2726
   293
		}
dvander@2726
   294
dvander@2726
   295
		if (strcmp(key, "file") == 0)
dvander@2726
   296
		{
dvander@2726
   297
			g_pSM->BuildPath(Path_Game, 
dvander@2726
   298
				m_pCurMapList->path,
dvander@2726
   299
				sizeof(m_pCurMapList->path),
dvander@2726
   300
				"%s",
dvander@2726
   301
				value);
dvander@2726
   302
			m_pCurMapList->bIsPath = true;
dvander@2726
   303
		}
dvander@2726
   304
		else if (strcmp(key, "target") == 0)
dvander@2726
   305
		{
dvander@2726
   306
			smcore.strncopy(m_pCurMapList->path, value, sizeof(m_pCurMapList->path));
dvander@2726
   307
			m_pCurMapList->bIsPath = false;
dvander@2726
   308
		}
dvander@2726
   309
dvander@2726
   310
		return SMCResult_Continue;
dvander@2726
   311
	}
dvander@2726
   312
	SMCResult ReadSMC_LeavingSection(const SMCStates *states)
dvander@2726
   313
	{
dvander@2726
   314
		if (m_IgnoreLevel)
dvander@2726
   315
		{
dvander@2726
   316
			m_IgnoreLevel--;
dvander@2726
   317
			return SMCResult_Continue;
dvander@2726
   318
		}
dvander@2726
   319
dvander@2726
   320
		if (m_CurState == MPS_MAPLIST)
dvander@2726
   321
		{
dvander@2726
   322
			if (m_pCurMapList != NULL
dvander@2726
   323
				&& m_pCurMapList->path[0] != '\0'
dvander@2726
   324
				&& m_ListLookup.retrieve(m_pCurMapList->name) == NULL)
dvander@2726
   325
			{
dvander@2726
   326
				m_ListLookup.insert(m_pCurMapList->name, m_pCurMapList);
dvander@2726
   327
				m_MapLists.push_back(m_pCurMapList);
dvander@2726
   328
				m_pCurMapList = NULL;
dvander@2726
   329
			}
dvander@2726
   330
			else
dvander@2726
   331
			{
dvander@2726
   332
				delete m_pCurMapList;
dvander@2726
   333
				m_pCurMapList = NULL;
dvander@2726
   334
			}
dvander@2726
   335
			m_CurState = MPS_GLOBAL;
dvander@2726
   336
		}
dvander@2726
   337
		else if (m_CurState == MPS_GLOBAL)
dvander@2726
   338
		{
dvander@2726
   339
			m_CurState = MPS_NONE;
dvander@2726
   340
		}
dvander@2726
   341
dvander@2726
   342
		return SMCResult_Continue;
dvander@2726
   343
	}
dvander@2726
   344
	void ReadSMC_ParseEnd(bool halted, bool failed)
dvander@2726
   345
	{
dvander@2726
   346
		delete m_pCurMapList;
dvander@2726
   347
		m_pCurMapList = NULL;
dvander@2726
   348
	}
dvander@2726
   349
	static int sort_maps_in_adt_array(const void *str1, const void *str2)
dvander@2726
   350
	{
dvander@2726
   351
		return strcmp((char *)str1, (char *)str2);
dvander@2726
   352
	}
dvander@2726
   353
	CellArray *UpdateMapList(CellArray *pUseArray, const char *name, int *pSerial, unsigned int flags)
dvander@2726
   354
	{
dvander@2726
   355
		int change_serial;
dvander@2726
   356
		CellArray *pNewArray = NULL;
dvander@2726
   357
		bool success, free_new_array;
dvander@2726
   358
dvander@2726
   359
		free_new_array = false;
dvander@2726
   360
		
dvander@2726
   361
		if ((success = GetMapList(&pNewArray, name, &change_serial)) == false)
dvander@2726
   362
		{
dvander@2726
   363
			if ((flags & MAPLIST_FLAG_NO_DEFAULT) != MAPLIST_FLAG_NO_DEFAULT)
dvander@2726
   364
			{
dvander@2726
   365
				/* If this list failed, and it's not the default, try the default. 
dvander@2726
   366
				 */
dvander@2726
   367
				if (strcmp(name, "default") != 0)
dvander@2726
   368
				{
dvander@2726
   369
					success = GetMapList(&pNewArray, name, &change_serial);
dvander@2726
   370
				}
dvander@2726
   371
				/* If either of the last two conditions failed, try again if we can. */
dvander@2726
   372
				if (!success && strcmp(name, "mapcyclefile") != 0)
dvander@2726
   373
				{
dvander@2726
   374
					success = GetMapList(&pNewArray, "mapcyclefile", &change_serial);
dvander@2726
   375
				}
dvander@2726
   376
			}
dvander@2726
   377
		}
dvander@2726
   378
dvander@2726
   379
		/* If there was a success, and the serial has not changed, bail out. */
dvander@2726
   380
		if (success && *pSerial == change_serial)
dvander@2726
   381
		{
dvander@2726
   382
			return NULL;
dvander@2726
   383
		}
dvander@2726
   384
dvander@2726
   385
		/**
dvander@2726
   386
		 * If there was a success but no map list, we need to look in the maps folder.
dvander@2726
   387
		 * If there was a failure and the flag is specified, we need to look in the maps folder.
dvander@2726
   388
		 */
dvander@2726
   389
		if ((success && pNewArray == NULL)
dvander@2726
   390
			|| (!success && ((flags & MAPLIST_FLAG_MAPSFOLDER) == MAPLIST_FLAG_MAPSFOLDER)))
dvander@2726
   391
		{
dvander@2726
   392
			pNewArray = new CellArray(64);
dvander@2726
   393
			free_new_array = true;
voidedweasel@3929
   394
voidedweasel@3929
   395
			cell_t *blk;
dvander@2726
   396
voidedweasel@3929
   397
			FileFindHandle_t findHandle;
voidedweasel@3929
   398
			const char *fileName = smcore.filesystem->FindFirstEx("maps/*.bsp", "GAME", &findHandle);
voidedweasel@3929
   399
voidedweasel@3929
   400
			while (fileName)
dvander@2726
   401
			{
dvander@2726
   402
				char buffer[PLATFORM_MAX_PATH];
dvander@2726
   403
voidedweasel@3929
   404
				UTIL_StripExtension(fileName, buffer, sizeof(buffer));
voidedweasel@3929
   405
voidedweasel@3929
   406
				if (!engine->IsMapValid(buffer))
voidedweasel@3929
   407
				{
voidedweasel@3929
   408
					fileName = smcore.filesystem->FindNext(findHandle);
voidedweasel@3929
   409
					continue;
voidedweasel@3929
   410
				}
voidedweasel@3929
   411
voidedweasel@3929
   412
				if ((blk = pNewArray->push()) == NULL)
dvander@2726
   413
				{
voidedweasel@3929
   414
					fileName = smcore.filesystem->FindNext(findHandle);
voidedweasel@3929
   415
					continue;
dvander@2726
   416
				}
voidedweasel@3929
   417
voidedweasel@3929
   418
				smcore.strncopy((char *)blk, buffer, 255);
voidedweasel@3929
   419
voidedweasel@3929
   420
				fileName = smcore.filesystem->FindNext(findHandle);
dvander@2726
   421
			}
dvander@2726
   422
voidedweasel@3929
   423
			smcore.filesystem->FindClose(findHandle);
voidedweasel@3929
   424
dvander@2726
   425
			/* Remove the array if there were no items. */
dvander@2726
   426
			if (pNewArray->size() == 0)
dvander@2726
   427
			{
dvander@2726
   428
				delete pNewArray;
dvander@2726
   429
				pNewArray = NULL;
dvander@2726
   430
			}
dvander@2726
   431
			else
dvander@2726
   432
			{
dvander@2726
   433
				qsort(pNewArray->base(), 
dvander@2726
   434
					pNewArray->size(), 
dvander@2726
   435
					pNewArray->blocksize() * sizeof(cell_t), 
dvander@2726
   436
					sort_maps_in_adt_array);
dvander@2726
   437
			}
dvander@2726
   438
dvander@2726
   439
			change_serial = -1;
dvander@2726
   440
		}
dvander@2726
   441
dvander@2726
   442
		/* If there is still no array by this point, bail out. */
dvander@2726
   443
		if (pNewArray == NULL)
dvander@2726
   444
		{
dvander@2726
   445
			*pSerial = -1;
dvander@2726
   446
			return NULL;
dvander@2726
   447
		}
dvander@2726
   448
dvander@2726
   449
		*pSerial = change_serial;
dvander@2726
   450
dvander@2726
   451
		/* If there is no input array, return something temporary. */
dvander@2726
   452
		if (pUseArray == NULL)
dvander@2726
   453
		{
dvander@2726
   454
			if (free_new_array)
dvander@2726
   455
			{
dvander@2726
   456
				return pNewArray;
dvander@2726
   457
			}
dvander@2726
   458
			else
dvander@2726
   459
			{
dvander@2726
   460
				return pNewArray->clone();
dvander@2726
   461
			}
dvander@2726
   462
		}
dvander@2726
   463
dvander@2726
   464
		/* Clear the input array if necessary. */
dvander@2726
   465
		if ((flags & MAPLIST_FLAG_CLEARARRAY) == MAPLIST_FLAG_CLEARARRAY)
dvander@2726
   466
		{
dvander@2726
   467
			pUseArray->clear();
dvander@2726
   468
		}
dvander@2726
   469
dvander@2726
   470
		/* Copy. */
dvander@2726
   471
		cell_t *blk_dst;
dvander@2726
   472
		cell_t *blk_src;
dvander@2726
   473
		for (size_t i = 0; i < pNewArray->size(); i++)
dvander@2726
   474
		{
dvander@2726
   475
			blk_dst = pUseArray->push();
dvander@2726
   476
			blk_src = pNewArray->at(i);
dvander@2726
   477
			smcore.strncopy((char *)blk_dst, (char *)blk_src, pUseArray->blocksize() * sizeof(cell_t));
dvander@2726
   478
		}
dvander@2726
   479
dvander@2726
   480
		/* Free resources if necessary. */
dvander@2726
   481
		if (free_new_array)
dvander@2726
   482
		{
dvander@2726
   483
			delete pNewArray;
dvander@2726
   484
		}
dvander@2726
   485
dvander@2726
   486
		/* Return the array we were given. */
dvander@2726
   487
		return pUseArray;
dvander@2726
   488
	}
dvander@2726
   489
private:
dvander@2726
   490
	bool GetMapList(CellArray **ppArray, const char *name, int *pSerial)
dvander@2726
   491
	{
dvander@2726
   492
		time_t last_time;
dvander@2726
   493
		maplist_info_t *pMapList, **ppMapList;
dvander@2726
   494
dvander@2726
   495
		if ((ppMapList = m_ListLookup.retrieve(name)) == NULL)
dvander@2726
   496
		{
dvander@2726
   497
			return false;
dvander@2726
   498
		}
dvander@2726
   499
dvander@2726
   500
		pMapList = *ppMapList;
dvander@2726
   501
dvander@2726
   502
		if (!pMapList->bIsPath)
dvander@2726
   503
		{
dvander@2726
   504
			return GetMapList(ppArray, pMapList->path, pSerial);
dvander@2726
   505
		}
dvander@2726
   506
dvander@2726
   507
		/* If it is a path, and the path is "*", assume all files must be used. */
dvander@2726
   508
		if (strcmp(pMapList->path, "*") == 0)
dvander@2726
   509
		{
dvander@2726
   510
			*ppArray = NULL;
dvander@2726
   511
			return true;
dvander@2726
   512
		}
dvander@2726
   513
dvander@2726
   514
		if (m_pMapCycleFile != NULL && strcmp(name, "mapcyclefile") == 0)
dvander@2726
   515
		{
dvander@2726
   516
			char path[PLATFORM_MAX_PATH];
skamonkey@3973
   517
			GetMapCycleFilePath(path, sizeof(path));
dvander@2726
   518
dvander@2726
   519
			if (strcmp(path, pMapList->path) != 0)
dvander@2726
   520
			{
dvander@2726
   521
				smcore.strncopy(pMapList->path, path, sizeof(pMapList->path));
dvander@2726
   522
				pMapList->last_modified_time = 0;
dvander@2726
   523
			}
dvander@2726
   524
		}
dvander@2726
   525
dvander@2726
   526
		if (!libsys->FileTime(pMapList->path, FileTime_LastChange, &last_time)
dvander@2726
   527
			|| last_time > pMapList->last_modified_time)
dvander@2726
   528
		{
dvander@2726
   529
			/* Reparse */
dvander@2726
   530
			FILE *fp;
dvander@2726
   531
			cell_t *blk;
dvander@2726
   532
			char buffer[255];
dvander@2726
   533
dvander@2726
   534
			if ((fp = fopen(pMapList->path, "rt")) == NULL)
dvander@2726
   535
			{
dvander@2726
   536
				return false;
dvander@2726
   537
			}
dvander@2726
   538
dvander@2726
   539
			delete pMapList->pArray;
dvander@2726
   540
			pMapList->pArray = new CellArray(64);
dvander@2726
   541
dvander@2726
   542
			while (!feof(fp) && fgets(buffer, sizeof(buffer), fp) != NULL)
dvander@2726
   543
			{
dvander@2726
   544
				size_t len = strlen(buffer);
dvander@2726
   545
				char *ptr = smcore.TrimWhitespace(buffer, len);
dvander@2726
   546
				if (*ptr == '\0'
dvander@2726
   547
					|| *ptr == ';'
dvander@2726
   548
					|| strncmp(ptr, "//", 2) == 0)
dvander@2726
   549
				{
dvander@2726
   550
					continue;
dvander@2726
   551
				}
skamonkey@3789
   552
				if (!gamehelpers->IsMapValid(ptr))
dvander@2726
   553
				{
dvander@2726
   554
					continue;
dvander@2726
   555
				}
dvander@2726
   556
				if ((blk = pMapList->pArray->push()) != NULL)
dvander@2726
   557
				{
dvander@2726
   558
					smcore.strncopy((char *)blk, ptr, 255);
dvander@2726
   559
				}
dvander@2726
   560
			}
dvander@2726
   561
dvander@2726
   562
			fclose(fp);
dvander@2726
   563
dvander@2726
   564
			pMapList->last_modified_time = last_time;
dvander@2726
   565
			pMapList->serial = ++m_nSerialChange;
dvander@2726
   566
		}
dvander@2726
   567
dvander@2726
   568
		if (pMapList->pArray == NULL || pMapList->pArray->size() == 0)
dvander@2726
   569
		{
dvander@2726
   570
			return false;
dvander@2726
   571
		}
dvander@2726
   572
dvander@2726
   573
		*pSerial = pMapList->serial;
dvander@2726
   574
		*ppArray = pMapList->pArray;
dvander@2726
   575
dvander@2726
   576
		return true;
dvander@2726
   577
	}
dvander@2726
   578
	void DumpCache(List<maplist_info_t *> *compat_list)
dvander@2726
   579
	{
dvander@2726
   580
		m_ListLookup.clear();
dvander@2726
   581
dvander@2726
   582
		List<maplist_info_t *>::iterator iter = m_MapLists.begin();
dvander@2726
   583
		while (iter != m_MapLists.end())
dvander@2726
   584
		{
dvander@2726
   585
			if (compat_list != NULL && (*iter)->bIsCompat)
dvander@2726
   586
			{
dvander@2726
   587
				compat_list->push_back((*iter));
dvander@2726
   588
			}
dvander@2726
   589
			else
dvander@2726
   590
			{
dvander@2726
   591
				delete (*iter)->pArray;
dvander@2726
   592
				delete (*iter);
dvander@2726
   593
			}
dvander@2726
   594
			iter = m_MapLists.erase(iter);
dvander@2726
   595
		}
dvander@2726
   596
	}
dvander@2726
   597
private:
dvander@2726
   598
	char m_ConfigFile[PLATFORM_MAX_PATH];
dvander@2726
   599
	time_t m_ConfigLastChanged;
dvander@2726
   600
	ConVar *m_pMapCycleFile;
dvander@2726
   601
	KTrie<maplist_info_t *> m_ListLookup;
dvander@2726
   602
	List<maplist_info_t *> m_MapLists;
dvander@2726
   603
	MapListState m_CurState;
dvander@2726
   604
	unsigned int m_IgnoreLevel;
dvander@2726
   605
	maplist_info_t *m_pCurMapList;
dvander@2726
   606
	int m_nSerialChange;
dvander@2726
   607
} s_MapLists;
dvander@2726
   608
dvander@2726
   609
static cell_t LoadMapList(IPluginContext *pContext, const cell_t *params)
dvander@2726
   610
{
dvander@2726
   611
	char *str;
dvander@2726
   612
	Handle_t hndl;
dvander@2726
   613
	cell_t *addr, flags;
dvander@2726
   614
	CellArray *pArray, *pNewArray;
dvander@2726
   615
dvander@2726
   616
	hndl = params[1];
dvander@2726
   617
	pContext->LocalToPhysAddr(params[2], &addr);
dvander@2726
   618
	pContext->LocalToString(params[3], &str);
dvander@2726
   619
	flags = params[4];
dvander@2726
   620
dvander@2726
   621
	/* Make sure the input Handle is valid */
dvander@2726
   622
	pArray = NULL;
dvander@2726
   623
	if (hndl != BAD_HANDLE)
dvander@2726
   624
	{
dvander@2726
   625
		HandleError err;
dvander@2726
   626
		HandleSecurity sec(pContext->GetIdentity(), g_pCoreIdent);
dvander@2726
   627
dvander@2726
   628
		if ((err = handlesys->ReadHandle(hndl, htCellArray, &sec, (void **)&pArray))
dvander@2726
   629
			!= HandleError_None)
dvander@2726
   630
		{
dvander@2726
   631
			return pContext->ThrowNativeError("Invalid Handle %x (error %d)", hndl, err);
dvander@2726
   632
		}
dvander@2726
   633
	}
dvander@2726
   634
dvander@2726
   635
	/* Make sure the map list cache is up to date at the root */
dvander@2726
   636
	s_MapLists.UpdateCache();
dvander@2726
   637
dvander@2726
   638
	/* Try to get the map list. */
dvander@2726
   639
	if ((pNewArray = s_MapLists.UpdateMapList(pArray, str, addr, flags)) == NULL)
dvander@2726
   640
	{
dvander@2726
   641
		return BAD_HANDLE;
dvander@2726
   642
	}
dvander@2726
   643
dvander@2726
   644
	/* If the user wanted a new array, create it now. */
dvander@2726
   645
	if (hndl == BAD_HANDLE)
dvander@2726
   646
	{
dvander@2726
   647
		if ((hndl = handlesys->CreateHandle(htCellArray, pNewArray, pContext->GetIdentity(), g_pCoreIdent, NULL))
dvander@2726
   648
			== BAD_HANDLE)
dvander@2726
   649
		{
dvander@2726
   650
			*addr = -1;
dvander@2726
   651
			delete pNewArray;
dvander@2726
   652
			return BAD_HANDLE;
dvander@2726
   653
		}
dvander@2726
   654
	}
dvander@2726
   655
dvander@2726
   656
	return hndl;
dvander@2726
   657
}
dvander@2726
   658
dvander@2726
   659
static cell_t SetMapListCompatBind(IPluginContext *pContext, const cell_t *params)
dvander@2726
   660
{
dvander@2726
   661
	char *name, *file;
dvander@2726
   662
dvander@2726
   663
	pContext->LocalToString(params[1], &name);
dvander@2726
   664
	pContext->LocalToString(params[2], &file);
dvander@2726
   665
dvander@2726
   666
	s_MapLists.UpdateCache();
dvander@2726
   667
	s_MapLists.AddOrUpdateDefault(name, file);
dvander@2726
   668
dvander@2726
   669
	return 1;
dvander@2726
   670
}
dvander@2726
   671
dvander@2726
   672
REGISTER_NATIVES(mapListNatives)
dvander@2726
   673
{
dvander@2726
   674
	{"ReadMapList",				LoadMapList},
dvander@2726
   675
	{"SetMapListCompatBind",	SetMapListCompatBind},
dvander@2726
   676
	{NULL,						NULL},
dvander@2726
   677
};