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