text: /*
  ** g_level.cpp
  ** Parses MAPINFO
  **
  **---------------------------------------------------------------------------
  ** Copyright 1998-2006 Randy Heit
  ** Copyright 2009 Christoph Oelckers
  ** All rights reserved.
  **
  ** Redistribution and use in source and binary forms, with or without
  ** modification, are permitted provided that the following conditions
  ** are met:
  **
  ** 1. Redistributions of source code must retain the above copyright
  **    notice, this list of conditions and the following disclaimer.
  ** 2. Redistributions in binary form must reproduce the above copyright
  **    notice, this list of conditions and the following disclaimer in the
  **    documentation and/or other materials provided with the distribution.
  ** 3. The name of the author may not be used to endorse or promote products
  **    derived from this software without specific prior written permission.
  **
  ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
  ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
  ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  **---------------------------------------------------------------------------
  **
  */
  
  #include <assert.h>
  #include "templates.h"
  #include "g_level.h"
  #include "sc_man.h"
  #include "w_wad.h"
  #include "cmdlib.h"
  #include "v_video.h"
  #include "p_lnspec.h"
  #include "p_setup.h"
  #include "i_system.h"
  #include "gi.h"
  #include "gstrings.h"
  #include "farchive.h"
  #include "p_acs.h"
  #include "doomstat.h"
  #include "d_player.h"
  #include "autosegs.h"
  #include "version.h"
  #include "v_text.h"
  
  TArray<cluster_info_t> wadclusterinfos;
  TArray<level_info_t> wadlevelinfos;
  
  level_info_t TheDefaultLevelInfo;
  static cluster_info_t TheDefaultClusterInfo;
  
  TArray<FEpisode> AllEpisodes;
  
  extern TMap<int, FString> HexenMusic;
  
  //==========================================================================
  //
  //
  //==========================================================================
  
  static int FindWadLevelInfo (const char *name)
  {
  	for (unsigned int i = 0; i < wadlevelinfos.Size(); i++)
  	{
  		if (!wadlevelinfos[i].MapName.CompareNoCase(name))
  			return i;
  	}
  	return -1;
  }
  
  //==========================================================================
  //
  //
  //==========================================================================
  
  level_info_t *FindLevelInfo (const char *mapname, bool allowdefault)
  {
  	int i;
  
  	if ((i = FindWadLevelInfo (mapname)) > -1)
  	{
  		return &wadlevelinfos[i];
  	}
  	else if (allowdefault)
  	{
  		if (TheDefaultLevelInfo.LevelName.IsEmpty())
  		{
  			TheDefaultLevelInfo.SkyPic2 = TheDefaultLevelInfo.SkyPic1 = "SKY1";
  			TheDefaultLevelInfo.LevelName = "Unnamed";
  		}
  		return &TheDefaultLevelInfo;
  	}
  	return NULL;
  }
  
  //==========================================================================
  //
  //
  //==========================================================================
  
  level_info_t *FindLevelByNum (int num)
  {
  	for (unsigned int i = 0; i < wadlevelinfos.Size(); i++)
  		if (wadlevelinfos[i].levelnum == num)
  			return &wadlevelinfos[i];
  
  	return NULL;
  }
  
  
  //==========================================================================
  //
  //
  //==========================================================================
  
  static level_info_t *FindLevelByWarpTrans (int num)
  {
  	for (unsigned i = wadlevelinfos.Size(); i-- != 0; )
  		if (wadlevelinfos[i].WarpTrans == num)
  			return &wadlevelinfos[i];
  
  	return NULL;
  }
  
  //==========================================================================
  //
  //
  //==========================================================================
  
  bool CheckWarpTransMap (FString &mapname, bool substitute)
  {
  	if (mapname[0] == '&' && (mapname[1] & 0xDF) == 'W' &&
  		(mapname[2] & 0xDF) == 'T' && mapname[3] == '@')
  	{
  		level_info_t *lev = FindLevelByWarpTrans (atoi (&mapname[4]));
  		if (lev != NULL)
  		{
  			mapname = lev->MapName;
  			return true;
  		}
  		else if (substitute)
  		{
  			char a = mapname[4], b = mapname[5];
  			mapname = "MAP";
  			mapname << a << b;
  		}
  	}
  	return false;
  }
  
  //==========================================================================
  //
  //
  //==========================================================================
  
  static int FindWadClusterInfo (int cluster)
  {
  	for (unsigned int i = 0; i < wadclusterinfos.Size(); i++)
  		if (wadclusterinfos[i].cluster == cluster)
  			return i;
  		
  	return -1;
  }
  
  //==========================================================================
  //
  //
  //==========================================================================
  
  cluster_info_t *FindClusterInfo (int cluster)
  {
  	int i;
  
  	if ((i = FindWadClusterInfo (cluster)) > -1)
  		return &wadclusterinfos[i];
  	else
  		return &TheDefaultClusterInfo;
  }
  
  //==========================================================================
  //
  //
  //==========================================================================
  
  void G_ClearSnapshots (void)
  {
  	for (unsigned int i = 0; i < wadlevelinfos.Size(); i++)
  	{
  		wadlevelinfos[i].ClearSnapshot();
  	}
  	// Since strings are only locked when snapshotting a level, unlock them
  	// all now, since we got rid of all the snapshots that cared about them.
  	GlobalACSStrings.UnlockAll();
  }
  
  //==========================================================================
  //
  // Remove any existing defereds
  //
  //==========================================================================
  
  void P_RemoveDefereds (void)
  {
  	for (unsigned int i = 0; i < wadlevelinfos.Size(); i++)
  	{
  		wadlevelinfos[i].ClearDefered();
  	}
  }
  
  
  //==========================================================================
  //
  //
  //==========================================================================
  
  void level_info_t::Reset()
  {
  	MapName = "";
  	MapBackground = "";
  	levelnum = 0;
  	PName = "";
  	NextMap = "";
  	NextSecretMap = "";
  	SkyPic1 = SkyPic2 = "-NOFLAT-";
  	cluster = 0;
  	partime = 0;
  	sucktime = 0;
  	flags = 0;
  	if (gameinfo.gametype == GAME_Hexen)
  		flags2 = 0;
  	else
  		flags2 = LEVEL2_LAXMONSTERACTIVATION;
  	Music = "";
  	LevelName = "";
  	FadeTable = "COLORMAP";
  	WallHorizLight = -8;
  	WallVertLight = +8;
  	F1Pic = "";
  	musicorder = 0;
  	snapshot = NULL;
  	snapshotVer = 0;
  	defered = 0;
  	skyspeed1 = skyspeed2 = 0.f;
  	fadeto = 0;
  	outsidefog = 0xff000000;
  	cdtrack = 0;
  	cdid = 0;
  	gravity = 0.f;
  	aircontrol = 0.f;
  	jumpdelay = 0;
  	WarpTrans = 0;
  	airsupply = 20;
  	compatflags = compatflags2 = 0;
  	compatmask = compatmask2 = 0;
  	Translator = "";
  	RedirectType = 0;
  	RedirectMapName = "";
  	EnterPic = "";
  	ExitPic = "";
  	InterMusic = "";
  	intermusicorder = 0;
  	SoundInfo = "";
  	SndSeq = "";
  	BorderTexture = "";
  	teamdamage = 0.f;
  	specialactions.Clear();
  	DefaultEnvironment = 0;
  	PrecacheSounds.Clear();
  }
  
  
  //==========================================================================
  //
  //
  //==========================================================================
  
  FString level_info_t::LookupLevelName()
  {
  	if (flags & LEVEL_LOOKUPLEVELNAME)
  	{
  		const char *thename;
  		const char *lookedup;
  
  		lookedup = GStrings[LevelName];
  		if (lookedup == NULL)
  		{
  			thename = LevelName;
  		}
  		else
  		{
  			char checkstring[32];
  
  			// Strip out the header from the localized string
  			if (MapName.Len() > 3 && MapName[0] == 'E' && MapName[2] == 'M')
  			{
  				mysnprintf (checkstring, countof(checkstring), "%s: ", MapName.GetChars());
  			}
  			else if (MapName.Len() > 3 && MapName[0] == 'M' && MapName[1] == 'A' && MapName[2] == 'P')
  			{
  				mysnprintf (checkstring, countof(checkstring), "%d: ", atoi(&MapName[3]));
  			}
  			else if (MapName.Len() > 5 && MapName[0] == 'L' && MapName[1] == 'E' && MapName[2] == 'V' && MapName[3] == 'E' && MapName[4] == 'L')
  			{
  				mysnprintf (checkstring, countof(checkstring), "%d: ", atoi(&MapName[5]));
  			}
  			thename = strstr (lookedup, checkstring);
  			if (thename == NULL)
  			{
  				thename = lookedup;
  			}
  			else
  			{
  				thename += strlen (checkstring);
  			}
  		}
  		return thename;
  	}
  	else return LevelName;
  }
  
  
  //==========================================================================
  //
  //
  //==========================================================================
  
  void level_info_t::ClearSnapshot()
  {
  	if (snapshot != NULL) delete snapshot;
  	snapshot = NULL;
  }
  
  //==========================================================================
  //
  //
  //==========================================================================
  
  void level_info_t::ClearDefered()
  {
  	acsdefered_t *def = defered;
  	while (def)
  	{
  		acsdefered_t *next = def->next;
  		delete def;
  		def = next;
  	}
  	defered = NULL;
  }
  
  //==========================================================================
  //
  //
  //==========================================================================
  
  level_info_t *level_info_t::CheckLevelRedirect ()
  {
  	if (RedirectType != NAME_None)
  	{
  		const PClass *type = PClass::FindClass(RedirectType);
  		if (type != NULL)
  		{
  			for (int i = 0; i < MAXPLAYERS; ++i)
  			{
  				if (playeringame[i] && players[i].mo->FindInventory (type))
  				{
  					// check for actual presence of the map.
  					if (P_CheckMapData(RedirectMapName))
  					{
  						return FindLevelInfo(RedirectMapName);
  					}
  					break;
  				}
  			}
  		}
  	}
  	return NULL;
  }
  
  //==========================================================================
  //
  //
  //==========================================================================
  
  bool level_info_t::isValid()
  {
  	return MapName.Len() != 0 || this == &TheDefaultLevelInfo;
  }
  
  //==========================================================================
  //
  //
  //==========================================================================
  
  void cluster_info_t::Reset()
  {
  	cluster = 0;
  	FinaleFlat = "";
  	ExitText = "";
  	EnterText = "";
  	MessageMusic = "";
  	musicorder = 0;
  	flags = 0;
  	cdtrack = 0;
  	ClusterName = "";
  	cdid = 0;
  }
  
  
  
  //==========================================================================
  //
  //
  //==========================================================================
  
  void FMapInfoParser::ParseOpenBrace()
  {
  	switch(format_type)
  	{
  	default:
  		format_type = sc.CheckString("{") ? FMT_New : FMT_Old;
  		if (format_type == FMT_New) 
  			sc.SetCMode(true);
  		break;
  
  	case FMT_Old:
  		break;
  
  	case FMT_New:
  		sc.MustGetStringName("{");
  		sc.SetCMode(true);
  		break;
  	}
  }
  
  //==========================================================================
  //
  //
  //==========================================================================
  
  bool FMapInfoParser::ParseCloseBrace()
  {
  	if (format_type == FMT_New)
  	{
  		return sc.Compare("}");
  	}
  	else
  	{
  		// We have to assume that the next keyword
  		// starts a new top level block
  		sc.UnGet();
  		return true;
  	}
  }
  
  //==========================================================================
  //
  //
  //==========================================================================
  
  bool FMapInfoParser::CheckAssign()
  {
  	if (format_type == FMT_New) return sc.CheckString("=");
  	else return false;	// force explicit handling
  }
  
  //==========================================================================
  //
  //
  //==========================================================================
  
  void FMapInfoParser::ParseAssign()
  {
  	if (format_type == FMT_New) sc.MustGetStringName("=");
  }
  
  //==========================================================================
  //
  //
  //==========================================================================
  
  void FMapInfoParser::MustParseAssign()
  {
  	if (format_type == FMT_New) sc.MustGetStringName("=");
  	else sc.ScriptError(NULL);
  }
  
  //==========================================================================
  //
  //
  //==========================================================================
  
  void FMapInfoParser::ParseComma()
  {
  	if (format_type == FMT_New) sc.MustGetStringName(",");
  }
  
  //==========================================================================
  //
  //
  //==========================================================================
  
  bool FMapInfoParser::CheckNumber()
  {
  	if (format_type == FMT_New) 
  	{
  		if (sc.CheckString(","))
  		{
  			sc.MustGetNumber();
  			return true;
  		}
  		return false;
  	}
  	else return sc.CheckNumber();
  }
  
  //==========================================================================
  //
  //
  //==========================================================================
  
  bool FMapInfoParser::CheckFloat()
  {
  	if (format_type == FMT_New) 
  	{
  		if (sc.CheckString(","))
  		{
  			sc.MustGetFloat();
  			return true;
  		}
  		return false;
  	}
  	else return sc.CheckFloat();
  }
  
  //==========================================================================
  //
  // skips an entire parameter list that's separated by commas
  //
  //==========================================================================
  
  void FMapInfoParser::SkipToNext()
  {
  	if (sc.CheckString("="))
  	{
  		do
  		{
  			sc.MustGetString();
  		}
  		while (sc.CheckString(","));
  	}
  }
  
  
  //==========================================================================
  //
  // checks if the current block was properly terminated
  //
  //==========================================================================
  
  void FMapInfoParser::CheckEndOfFile(const char *block)
  {
  	if (format_type == FMT_New && !sc.Compare("}"))
  	{
  		sc.ScriptError("Unexpected end of file in %s definition", block);
  	}
  }
  
  //==========================================================================
  //
  // ParseLookupname
  // Parses a string that may reference the string table
  //
  //==========================================================================
  
  bool FMapInfoParser::ParseLookupName(FString &dest)
  {
  	sc.MustGetString();
  	if (sc.Compare("lookup"))
  	{
  		ParseComma();
  		sc.MustGetString();
  		dest = sc.String;
  		return true;
  	}
  	else if (sc.String[0] == '$')
  	{
  		dest = sc.String+1;
  		return true;
  	}
  	else if (format_type == FMT_Old)
  	{
  		dest = sc.String;
  		return false;
  	}
  	else
  	{
  		sc.UnGet();
  		dest = "";
  		do
  		{
  			sc.MustGetString();
  			dest << sc.String << '\n';
  		}
  		while (sc.CheckString(","));
  		// strip off the last newline
  		dest.Truncate(long(dest.Len()-1));
  		return false;
  	}
  }
  
  //==========================================================================
  //
  //
  //==========================================================================
  
  /*
  void FMapInfoParser::ParseLumpOrTextureName(char *name)
  {
  	sc.MustGetString();
  	uppercopy(name, sc.String);
  	name[8]=0;
  }
  */
  
  void FMapInfoParser::ParseLumpOrTextureName(FString &name)
  {
  	sc.MustGetString();
  	name = sc.String;
  }
  
  
  //==========================================================================
  //
  //
  //==========================================================================
  
  void FMapInfoParser::ParseMusic(FString &name, int &order)
  {
  	sc.MustGetString();
  
  	order = 0;
  	char *colon = strchr (sc.String, ':');
  	if (colon)
  	{
  		order = atoi(colon+1);
  		*colon = 0;
  	}
  	name = sc.String;
  	if (!colon && CheckNumber())
  	{
  		order = sc.Number;
  	}
  }
  
  //==========================================================================
  //
  // ParseCluster
  // Parses a cluster definition
  //
  //==========================================================================
  
  void FMapInfoParser::ParseCluster()
  {
  	sc.MustGetNumber ();
  	int clusterindex = FindWadClusterInfo (sc.Number);
  	if (clusterindex == -1)
  	{
  		clusterindex = wadclusterinfos.Reserve(1);
  	}
  
  	cluster_info_t *clusterinfo = &wadclusterinfos[clusterindex];
  	clusterinfo->Reset();
  	clusterinfo->cluster = sc.Number;
  
  	ParseOpenBrace();
  
  	while (sc.GetString())
  	{
  		if (sc.Compare("name"))
  		{
  			ParseAssign();
  			if (ParseLookupName(clusterinfo->ClusterName))
  				clusterinfo->flags |= CLUSTER_LOOKUPCLUSTERNAME;
  		}
  		else if (sc.Compare("entertext"))
  		{
  			ParseAssign();
  			if (ParseLookupName(clusterinfo->EnterText))
  				clusterinfo->flags |= CLUSTER_LOOKUPENTERTEXT;
  		}
  		else if (sc.Compare("exittext"))
  		{
  			ParseAssign();
  			if (ParseLookupName(clusterinfo->ExitText))
  				clusterinfo->flags |= CLUSTER_LOOKUPEXITTEXT;
  		}
  		else if (sc.Compare("music"))
  		{
  			int order = 0;
  
  			ParseAssign();
  			ParseMusic(clusterinfo->MessageMusic, clusterinfo->musicorder);
  		}
  		else if (sc.Compare("flat"))
  		{
  			ParseAssign();
  			ParseLumpOrTextureName(clusterinfo->FinaleFlat);
  		}
  		else if (sc.Compare("pic"))
  		{
  			ParseAssign();
  			ParseLumpOrTextureName(clusterinfo->FinaleFlat);
  			clusterinfo->flags |= CLUSTER_FINALEPIC;
  		}
  		else if (sc.Compare("hub"))
  		{
  			clusterinfo->flags |= CLUSTER_HUB;
  		}
  		else if (sc.Compare("cdtrack"))
  		{
  			ParseAssign();
  			sc.MustGetNumber();
  			clusterinfo->cdtrack = sc.Number;
  		}
  		else if (sc.Compare("cdid"))
  		{
  			ParseAssign();
  			sc.MustGetString();
  			clusterinfo->cdid = strtoul (sc.String, NULL, 16);
  		}
  		else if (sc.Compare("entertextislump"))
  		{
  			clusterinfo->flags |= CLUSTER_ENTERTEXTINLUMP;
  		}
  		else if (sc.Compare("exittextislump"))
  		{
  			clusterinfo->flags |= CLUSTER_EXITTEXTINLUMP;
  		}
  		else if (!ParseCloseBrace())
  		{
  			// Unknown
  			sc.ScriptMessage("Unknown property '%s' found in map definition\n", sc.String);
  			SkipToNext();
  		}
  		else
  		{
  			break;
  		}
  	}
  	CheckEndOfFile("cluster");
  }
  
  
  //==========================================================================
  //
  // ParseNextMap
  // Parses a next map field
  //
  //==========================================================================
  
  void FMapInfoParser::ParseNextMap(FString &mapname)
  {
  	if (sc.CheckNumber())
  	{
  		if (HexenHack)
  		{
  			mapname.Format("&wt@%02d", sc.Number);
  		}
  		else
  		{
  			mapname.Format("MAP%02d", sc.Number);
  		}
  	}
  	else
  	{
  		sc.MustGetString();
  		mapname = sc.String;
  		FName seq = CheckEndSequence();
  		if (seq != NAME_None)
  		{
  			mapname.Format("enDSeQ%04x", int(seq));
  		}
  	}
  }
  
  //==========================================================================
  //
  // Map options
  //
  //==========================================================================
  
  DEFINE_MAP_OPTION(levelnum, true)
  {
  	parse.ParseAssign();
  	parse.sc.MustGetNumber();
  	info->levelnum = parse.sc.Number;
  }
  
  DEFINE_MAP_OPTION(next, true)
  {
  	parse.ParseAssign();
  	parse.ParseNextMap(info->NextMap);
  }
  
  DEFINE_MAP_OPTION(secretnext, true)
  {
  	parse.ParseAssign();
  	parse.ParseNextMap(info->NextSecretMap);
  }
  
  DEFINE_MAP_OPTION(secret, true) // Just an alias for secretnext, for Vavoom compatibility
  {
  	parse.ParseAssign();
  	parse.ParseNextMap(info->NextSecretMap);
  }
  
  DEFINE_MAP_OPTION(cluster, true)
  {
  	parse.ParseAssign();
  	parse.sc.MustGetNumber();
  	info->cluster = parse.sc.Number;
  
  	// If this cluster hasn't been defined yet, add it. This is especially needed
  	// for Hexen, because it doesn't have clusterdefs. If we don't do this, every
  	// level on Hexen will sometimes be considered as being on the same hub,
  	// depending on the check done.
  	if (FindWadClusterInfo (parse.sc.Number) == -1)
  	{
  		unsigned int clusterindex = wadclusterinfos.Reserve(1);
  		cluster_info_t *clusterinfo = &wadclusterinfos[clusterindex];
  		clusterinfo->Reset();
  		clusterinfo->cluster = parse.sc.Number;
  		if (parse.HexenHack)
  		{
  			clusterinfo->flags |= CLUSTER_HUB;
  		}
  	}
  }
  
  DEFINE_MAP_OPTION(sky1, true)
  {
  	parse.ParseAssign();
  	parse.ParseLumpOrTextureName(info->SkyPic1);
  	if (parse.CheckFloat())
  	{
  		if (parse.HexenHack)
  		{
  			parse.sc.Float /= 256;
  		}
  		info->skyspeed1 = float(parse.sc.Float * (35. / 1000.));
  	}
  }
  
  DEFINE_MAP_OPTION(sky2, true)
  {
  	parse.ParseAssign();
  	parse.ParseLumpOrTextureName(info->SkyPic2);
  	if (parse.CheckFloat())
  	{
  		if (parse.HexenHack)
  		{
  			parse.sc.Float /= 256;
  		}
  		info->skyspeed2 = float(parse.sc.Float * (35. / 1000.));
  	}
  }
  
  // Vavoom compatibility
  DEFINE_MAP_OPTION(skybox, true)
  {
  	parse.ParseAssign();
  	parse.ParseLumpOrTextureName(info->SkyPic1);
  	info->skyspeed1 = 0;
  }
  
  DEFINE_MAP_OPTION(fade, true)
  {
  	parse.ParseAssign();
  	parse.sc.MustGetString();
  	info->fadeto = V_GetColor(NULL, parse.sc.String);
  }
  
  DEFINE_MAP_OPTION(outsidefog, true)
  {
  	parse.ParseAssign();
  	parse.sc.MustGetString();
  	info->outsidefog = V_GetColor(NULL, parse.sc.String);
  }
  
  DEFINE_MAP_OPTION(titlepatch, true)
  {
  	parse.ParseAssign();
  	parse.ParseLumpOrTextureName(info->PName);
  }
  
  DEFINE_MAP_OPTION(partime, true)
  {
  	parse.ParseAssign();
  	parse.sc.MustGetNumber();
  	info->partime = parse.sc.Number;
  }
  
  DEFINE_MAP_OPTION(par, true)
  {
  	parse.ParseAssign();
  	parse.sc.MustGetNumber();
  	info->partime = parse.sc.Number;
  }
  
  DEFINE_MAP_OPTION(sucktime, true)
  {
  	parse.ParseAssign();
  	parse.sc.MustGetNumber();
  	info->sucktime = parse.sc.Number;
  }
  
  DEFINE_MAP_OPTION(music, true)
  {
  	parse.ParseAssign();
  	parse.ParseMusic(info->Music, info->musicorder);
  }
  
  DEFINE_MAP_OPTION(intermusic, true)
  {
  	parse.ParseAssign();
  	parse.ParseMusic(info->InterMusic, info->intermusicorder);
  }
  
  DEFINE_MAP_OPTION(fadetable, true)
  {
  	parse.ParseAssign();
  	parse.ParseLumpOrTextureName(info->FadeTable);
  }
  
  DEFINE_MAP_OPTION(evenlighting, true)
  {
  	info->WallVertLight = info->WallHorizLight = 0;
  }
  
  DEFINE_MAP_OPTION(cdtrack, true)
  {
  	parse.ParseAssign();
  	parse.sc.MustGetNumber();
  	info->cdtrack = parse.sc.Number;
  }
  
  DEFINE_MAP_OPTION(cdid, true)
  {
  	parse.ParseAssign();
  	parse.sc.MustGetString();
  	info->cdid = strtoul (parse.sc.String, NULL, 16);
  }
  
  DEFINE_MAP_OPTION(warptrans, true)
  {
  	parse.ParseAssign();
  	parse.sc.MustGetNumber();
  	info->WarpTrans = parse.sc.Number;
  }
  
  DEFINE_MAP_OPTION(vertwallshade, true)
  {
  	parse.ParseAssign();
  	parse.sc.MustGetNumber();
  	info->WallVertLight = (SBYTE)clamp (parse.sc.Number / 2, -128, 127);
  }
  
  DEFINE_MAP_OPTION(horizwallshade, true)
  {
  	parse.ParseAssign();
  	parse.sc.MustGetNumber();
  	info->WallHorizLight = (SBYTE)clamp (parse.sc.Number / 2, -128, 127);
  }
  
  DEFINE_MAP_OPTION(gravity, true)
  {
  	parse.ParseAssign();
  	parse.sc.MustGetFloat();
  	info->gravity = float(parse.sc.Float);
  }
  
  DEFINE_MAP_OPTION(aircontrol, true)
  {
  	parse.ParseAssign();
  	parse.sc.MustGetFloat();
  	info->aircontrol = float(parse.sc.Float);
  }
  
  DEFINE_MAP_OPTION(jumpdelay, false)
  {
  	parse.ParseAssign();
  	parse.sc.MustGetNumber();
  	info->jumpdelay = parse.sc.Number;
  }
  
  DEFINE_MAP_OPTION(airsupply, true)
  {
  	parse.ParseAssign();
  	parse.sc.MustGetNumber();
  	info->airsupply = parse.sc.Number;
  }
  
  DEFINE_MAP_OPTION(interpic, true)
  {
  	parse.ParseAssign();
  	parse.sc.MustGetString();
  	info->ExitPic = parse.sc.String;
  }
  
  DEFINE_MAP_OPTION(exitpic, true)
  {
  	parse.ParseAssign();
  	parse.sc.MustGetString();
  	info->ExitPic = parse.sc.String;
  }
  
  DEFINE_MAP_OPTION(enterpic, true)
  {
  	parse.ParseAssign();
  	parse.sc.MustGetString();
  	info->EnterPic = parse.sc.String;
  }
  
  DEFINE_MAP_OPTION(specialaction, true)
  {
  	parse.ParseAssign();
  
  	FSpecialAction *sa = &info->specialactions[info->specialactions.Reserve(1)];
  	int min_arg, max_arg;
  	if (parse.format_type == parse.FMT_Old) parse.sc.SetCMode(true);
  	parse.sc.MustGetString();
  	sa->Type = FName(parse.sc.String);
  	parse.sc.CheckString(",");
  	parse.sc.MustGetString();
  	sa->Action = P_FindLineSpecial(parse.sc.String, &min_arg, &max_arg);
  	if (sa->Action == 0 || min_arg < 0)
  	{
  		parse.sc.ScriptError("Unknown specialaction '%s'", parse.sc.String);
  	}
  	int j = 0;
  	while (j < 5 && parse.sc.CheckString(","))
  	{
  		parse.sc.MustGetNumber();
  		sa->Args[j++] = parse.sc.Number;
  	}
  	if (parse.format_type == parse.FMT_Old) parse.sc.SetCMode(false);
  }
  
  DEFINE_MAP_OPTION(PrecacheSounds, true)
  {
  	parse.ParseAssign();
  
  	do
  	{
  		parse.sc.MustGetString();
  		FSoundID snd = parse.sc.String;
  		if (snd == 0)
  		{
  			parse.sc.ScriptMessage("Unknown sound \"%s\"", parse.sc.String);
  		}
  		else
  		{
  			info->PrecacheSounds.Push(snd);
  		}
  	} while (parse.sc.CheckString(","));
  }
  
  DEFINE_MAP_OPTION(redirect, true)
  {
  	parse.ParseAssign();
  	parse.sc.MustGetString();
  	info->RedirectType = parse.sc.String;
  	parse.ParseComma();
  	parse.ParseNextMap(info->RedirectMapName);
  }
  
  DEFINE_MAP_OPTION(sndseq, true)
  {
  	parse.ParseAssign();
  	parse.sc.MustGetString();
  	info->SndSeq = parse.sc.String;
  }
  
  DEFINE_MAP_OPTION(sndinfo, true)
  {
  	parse.ParseAssign();
  	parse.sc.MustGetString();
  	info->SoundInfo = parse.sc.String;
  }
  
  DEFINE_MAP_OPTION(soundinfo, true)
  {
  	parse.ParseAssign();
  	parse.sc.MustGetString();
  	info->SoundInfo = parse.sc.String;
  }
  
  DEFINE_MAP_OPTION(translator, true)
  {
  	parse.ParseAssign();
  	parse.sc.MustGetString();
  	info->Translator = parse.sc.String;
  }
  
  DEFINE_MAP_OPTION(deathsequence, false)
  {
  	parse.ParseAssign();
  	parse.sc.MustGetString();
  	info->deathsequence = parse.sc.String;
  }
  
  DEFINE_MAP_OPTION(slideshow, false)
  {
  	parse.ParseAssign();
  	parse.sc.MustGetString();
  	info->slideshow = parse.sc.String;
  }
  
  DEFINE_MAP_OPTION(bordertexture, true)
  {
  	parse.ParseAssign();
  	parse.ParseLumpOrTextureName(info->BorderTexture);
  }
  
  DEFINE_MAP_OPTION(f1, true)
  {
  	parse.ParseAssign();
  	parse.ParseLumpOrTextureName(info->F1Pic);
  }
  
  DEFINE_MAP_OPTION(teamdamage, true)
  {
  	parse.ParseAssign();
  	parse.sc.MustGetFloat();
  	info->teamdamage = float(parse.sc.Float);
  }
  
  DEFINE_MAP_OPTION(mapbackground, true)
  {
  	parse.ParseAssign();
  	parse.ParseLumpOrTextureName(info->MapBackground);
  }
  
  DEFINE_MAP_OPTION(defaultenvironment, false)
  {
  	int id;
  
  	parse.ParseAssign();
  	if (parse.sc.CheckNumber())
  	{ // Numeric ID XXX [, YYY]
  		id = parse.sc.Number << 8;
  		if (parse.CheckNumber())
  		{
  			id |= parse.sc.Number;
  		}
  	}
  	else
  	{ // Named environment
  		parse.sc.MustGetString();
  		ReverbContainer *reverb = S_FindEnvironment(parse.sc.String);
  		if (reverb == NULL)
  		{
  			parse.sc.ScriptMessage("Unknown sound environment '%s'\n", parse.sc.String);
  			id = 0;
  		}
  		else
  		{
  			id = reverb->ID;
  		}
  	}
  	info->DefaultEnvironment = id;
  }
  
  
  //==========================================================================
  //
  // All flag based map options 
  //
  //==========================================================================
  
  enum EMIType
  {
  	MITYPE_IGNORE,
  	MITYPE_EATNEXT,
  	MITYPE_SETFLAG,
  	MITYPE_CLRFLAG,
  	MITYPE_SCFLAGS,
  	MITYPE_SETFLAG2,
  	MITYPE_CLRFLAG2,
  	MITYPE_SCFLAGS2,
  	MITYPE_SETFLAG3,
  	MITYPE_CLRFLAG3,
  	MITYPE_SCFLAGS3,
  	MITYPE_COMPATFLAG,
  };
  
  struct MapInfoFlagHandler
  {
  	const char *name;
  	EMIType type;
  	DWORD data1, data2;
  }
  MapFlagHandlers[] =
  {
  	{ "nointermission",					MITYPE_SETFLAG,	LEVEL_NOINTERMISSION, 0 },
  	{ "intermission",					MITYPE_CLRFLAG,	LEVEL_NOINTERMISSION, 0 },
  	{ "doublesky",						MITYPE_SETFLAG,	LEVEL_DOUBLESKY, 0 },
  	{ "nosoundclipping",				MITYPE_IGNORE,	0, 0 },	// was nosoundclipping
  	{ "allowmonstertelefrags",			MITYPE_SETFLAG,	LEVEL_MONSTERSTELEFRAG, 0 },
  	{ "map07special",					MITYPE_SETFLAG,	LEVEL_MAP07SPECIAL, 0 },
  	{ "baronspecial",					MITYPE_SETFLAG,	LEVEL_BRUISERSPECIAL, 0 },
  	{ "cyberdemonspecial",				MITYPE_SETFLAG,	LEVEL_CYBORGSPECIAL, 0 },
  	{ "spidermastermindspecial",		MITYPE_SETFLAG,	LEVEL_SPIDERSPECIAL, 0 },
  	{ "minotaurspecial",				MITYPE_SETFLAG,	LEVEL_MINOTAURSPECIAL, 0 },
  	{ "dsparilspecial",					MITYPE_SETFLAG,	LEVEL_SORCERER2SPECIAL, 0 },
  	{ "ironlichspecial",				MITYPE_SETFLAG,	LEVEL_HEADSPECIAL, 0 },
  	{ "specialaction_exitlevel",		MITYPE_SCFLAGS,	0, ~LEVEL_SPECACTIONSMASK },
  	{ "specialaction_opendoor",			MITYPE_SCFLAGS,	LEVEL_SPECOPENDOOR, ~LEVEL_SPECACTIONSMASK },
  	{ "specialaction_lowerfloor",		MITYPE_SCFLAGS,	LEVEL_SPECLOWERFLOOR, ~LEVEL_SPECACTIONSMASK },
  	{ "specialaction_lowerfloortohighest",MITYPE_SCFLAGS,LEVEL_SPECLOWERFLOORTOHIGHEST, ~LEVEL_SPECACTIONSMASK },
  	{ "specialaction_killmonsters",		MITYPE_SETFLAG,	LEVEL_SPECKILLMONSTERS, 0 },
  	{ "lightning",						MITYPE_SETFLAG,	LEVEL_STARTLIGHTNING, 0 },
  	{ "smoothlighting",					MITYPE_SETFLAG2,	LEVEL2_SMOOTHLIGHTING, 0 },
  	{ "noautosequences",				MITYPE_SETFLAG,	LEVEL_SNDSEQTOTALCTRL, 0 },
  	{ "autosequences",					MITYPE_CLRFLAG,	LEVEL_SNDSEQTOTALCTRL, 0 },
  	{ "forcenoskystretch",				MITYPE_SETFLAG,	LEVEL_FORCENOSKYSTRETCH, 0 },
  	{ "skystretch",						MITYPE_CLRFLAG,	LEVEL_FORCENOSKYSTRETCH, 0 },
  	{ "allowfreelook",					MITYPE_SCFLAGS,	LEVEL_FREELOOK_YES, ~LEVEL_FREELOOK_NO },
  	{ "nofreelook",						MITYPE_SCFLAGS,	LEVEL_FREELOOK_NO, ~LEVEL_FREELOOK_YES },
  	{ "allowjump",						MITYPE_CLRFLAG,	LEVEL_JUMP_NO, 0 },
  	{ "nojump",							MITYPE_SETFLAG,	LEVEL_JUMP_NO, 0 },
  	{ "fallingdamage",					MITYPE_SCFLAGS,	LEVEL_FALLDMG_HX, ~LEVEL_FALLDMG_ZD },
  	{ "oldfallingdamage",				MITYPE_SCFLAGS,	LEVEL_FALLDMG_ZD, ~LEVEL_FALLDMG_HX },
  	{ "forcefallingdamage",				MITYPE_SCFLAGS,	LEVEL_FALLDMG_ZD, ~LEVEL_FALLDMG_HX },
  	{ "strifefallingdamage",			MITYPE_SETFLAG,	LEVEL_FALLDMG_ZD|LEVEL_FALLDMG_HX, 0 },
  	{ "nofallingdamage",				MITYPE_SCFLAGS,	0, ~(LEVEL_FALLDMG_ZD|LEVEL_FALLDMG_HX) },
  	{ "noallies",						MITYPE_SETFLAG,	LEVEL_NOALLIES, 0 },
  	{ "filterstarts",					MITYPE_SETFLAG,	LEVEL_FILTERSTARTS, 0 },
  	{ "useplayerstartz",				MITYPE_SETFLAG, LEVEL_USEPLAYERSTARTZ, 0 },
  	{ "randomplayerstarts",				MITYPE_SETFLAG2, LEVEL2_RANDOMPLAYERSTARTS, 0 },
  	{ "activateowndeathspecials",		MITYPE_SETFLAG,	LEVEL_ACTOWNSPECIAL, 0 },
  	{ "killeractivatesdeathspecials",	MITYPE_CLRFLAG,	LEVEL_ACTOWNSPECIAL, 0 },
  	{ "missilesactivateimpactlines",	MITYPE_SETFLAG2,	LEVEL2_MISSILESACTIVATEIMPACT, 0 },
  	{ "missileshootersactivetimpactlines",MITYPE_CLRFLAG2,	LEVEL2_MISSILESACTIVATEIMPACT, 0 },
  	{ "noinventorybar",					MITYPE_SETFLAG,	LEVEL_NOINVENTORYBAR, 0 },
  	{ "deathslideshow",					MITYPE_IGNORE,		0, 0 },
  	{ "strictmonsteractivation",		MITYPE_CLRFLAG2,	LEVEL2_LAXMONSTERACTIVATION, LEVEL2_LAXACTIVATIONMAPINFO },
  	{ "laxmonsteractivation",			MITYPE_SETFLAG2,	LEVEL2_LAXMONSTERACTIVATION, LEVEL2_LAXACTIVATIONMAPINFO },
  	{ "additive_scrollers",				MITYPE_COMPATFLAG, COMPATF_BOOMSCROLL, 0 },
  	{ "keepfullinventory",				MITYPE_SETFLAG2,	LEVEL2_KEEPFULLINVENTORY, 0 },
  	{ "monsterfallingdamage",			MITYPE_SETFLAG2,	LEVEL2_MONSTERFALLINGDAMAGE, 0 },
  	{ "nomonsterfallingdamage",			MITYPE_CLRFLAG2,	LEVEL2_MONSTERFALLINGDAMAGE, 0 },
  	{ "clipmidtextures",				MITYPE_SETFLAG2,	LEVEL2_CLIPMIDTEX, 0 },
  	{ "wrapmidtextures",				MITYPE_SETFLAG2,	LEVEL2_WRAPMIDTEX, 0 },
  	{ "allowcrouch",					MITYPE_CLRFLAG,	LEVEL_CROUCH_NO, 0 },
  	{ "nocrouch",						MITYPE_SETFLAG,	LEVEL_CROUCH_NO, 0 },
  	{ "pausemusicinmenus",				MITYPE_SCFLAGS2,	LEVEL2_PAUSE_MUSIC_IN_MENUS, 0 },
  	{ "noinfighting",					MITYPE_SCFLAGS2,	LEVEL2_NOINFIGHTING, ~LEVEL2_TOTALINFIGHTING },
  	{ "normalinfighting",				MITYPE_SCFLAGS2,	0, ~(LEVEL2_NOINFIGHTING|LEVEL2_TOTALINFIGHTING)},
  	{ "totalinfighting",				MITYPE_SCFLAGS2,	LEVEL2_TOTALINFIGHTING, ~LEVEL2_NOINFIGHTING },
  	{ "infiniteflightpowerup",			MITYPE_SETFLAG2,	LEVEL2_INFINITE_FLIGHT, 0 },
  	{ "noinfiniteflightpowerup",		MITYPE_CLRFLAG2,	LEVEL2_INFINITE_FLIGHT, 0 },
  	{ "allowrespawn",					MITYPE_SETFLAG2,	LEVEL2_ALLOWRESPAWN, 0 },
  	{ "teamplayon",						MITYPE_SCFLAGS2,	LEVEL2_FORCETEAMPLAYON, ~LEVEL2_FORCETEAMPLAYOFF },
  	{ "teamplayoff",					MITYPE_SCFLAGS2,	LEVEL2_FORCETEAMPLAYOFF, ~LEVEL2_FORCETEAMPLAYON },
  	{ "checkswitchrange",				MITYPE_SETFLAG2,	LEVEL2_CHECKSWITCHRANGE, 0 },
  	{ "nocheckswitchrange",				MITYPE_CLRFLAG2,	LEVEL2_CHECKSWITCHRANGE, 0 },
  	{ "grinding_polyobj",				MITYPE_SETFLAG2,	LEVEL2_POLYGRIND, 0 },
  	{ "no_grinding_polyobj",			MITYPE_CLRFLAG2,	LEVEL2_POLYGRIND, 0 },
  	{ "resetinventory",					MITYPE_SETFLAG2,	LEVEL2_RESETINVENTORY, 0 },
  	{ "resethealth",					MITYPE_SETFLAG2,	LEVEL2_RESETHEALTH, 0 },
  	{ "endofgame",						MITYPE_SETFLAG2,	LEVEL2_ENDGAME, 0 },
  	{ "nostatistics",					MITYPE_SETFLAG2,	LEVEL2_NOSTATISTICS, 0 },
  	{ "noautosavehint",					MITYPE_SETFLAG2,	LEVEL2_NOAUTOSAVEHINT, 0 },
  	{ "forgetstate",					MITYPE_SETFLAG2,	LEVEL2_FORGETSTATE, 0 },
  	{ "rememberstate",					MITYPE_CLRFLAG2,	LEVEL2_FORGETSTATE, 0 },
  	{ "unfreezesingleplayerconversations",MITYPE_SETFLAG2,	LEVEL2_CONV_SINGLE_UNFREEZE, 0 },
  	{ "spawnwithweaponraised",			MITYPE_SETFLAG2,	LEVEL2_PRERAISEWEAPON, 0 },
  	{ "forcefakecontrast",				MITYPE_SETFLAG3,	LEVEL3_FORCEFAKECONTRAST, 0 },
  	{ "nobotnodes",						MITYPE_IGNORE,	0, 0 },		// Skulltag option: nobotnodes
  	{ "compat_shorttex",				MITYPE_COMPATFLAG, COMPATF_SHORTTEX, 0 },
  	{ "compat_stairs",					MITYPE_COMPATFLAG, COMPATF_STAIRINDEX, 0 },
  	{ "compat_limitpain",				MITYPE_COMPATFLAG, COMPATF_LIMITPAIN, 0 },
  	{ "compat_nopassover",				MITYPE_COMPATFLAG, COMPATF_NO_PASSMOBJ, 0 },
  	{ "compat_notossdrops",				MITYPE_COMPATFLAG, COMPATF_NOTOSSDROPS, 0 },
  	{ "compat_useblocking", 			MITYPE_COMPATFLAG, COMPATF_USEBLOCKING, 0 },
  	{ "compat_nodoorlight",				MITYPE_COMPATFLAG, COMPATF_NODOORLIGHT, 0 },
  	{ "compat_ravenscroll",				MITYPE_COMPATFLAG, COMPATF_RAVENSCROLL, 0 },
  	{ "compat_soundtarget",				MITYPE_COMPATFLAG, COMPATF_SOUNDTARGET, 0 },
  	{ "compat_dehhealth",				MITYPE_COMPATFLAG, COMPATF_DEHHEALTH, 0 },
  	{ "compat_trace",					MITYPE_COMPATFLAG, COMPATF_TRACE, 0 },
  	{ "compat_dropoff",					MITYPE_COMPATFLAG, COMPATF_DROPOFF, 0 },
  	{ "compat_boomscroll",				MITYPE_COMPATFLAG, COMPATF_BOOMSCROLL, 0 },
  	{ "compat_invisibility",			MITYPE_COMPATFLAG, COMPATF_INVISIBILITY, 0 },
  	{ "compat_silent_instant_floors",	MITYPE_COMPATFLAG, COMPATF_SILENT_INSTANT_FLOORS, 0 },
  	{ "compat_sectorsounds",			MITYPE_COMPATFLAG, COMPATF_SECTORSOUNDS, 0 },
  	{ "compat_missileclip",				MITYPE_COMPATFLAG, COMPATF_MISSILECLIP, 0 },
  	{ "compat_crossdropoff",			MITYPE_COMPATFLAG, COMPATF_CROSSDROPOFF, 0 },
  	{ "compat_anybossdeath",			MITYPE_COMPATFLAG, COMPATF_ANYBOSSDEATH, 0 },
  	{ "compat_minotaur",				MITYPE_COMPATFLAG, COMPATF_MINOTAUR, 0 },
  	{ "compat_mushroom",				MITYPE_COMPATFLAG, COMPATF_MUSHROOM, 0 },
  	{ "compat_mbfmonstermove",			MITYPE_COMPATFLAG, COMPATF_MBFMONSTERMOVE, 0 },
  	{ "compat_corpsegibs",				MITYPE_COMPATFLAG, COMPATF_CORPSEGIBS, 0 },
  	{ "compat_noblockfriends",			MITYPE_COMPATFLAG, COMPATF_NOBLOCKFRIENDS, 0 },
  	{ "compat_spritesort",				MITYPE_COMPATFLAG, COMPATF_SPRITESORT, 0 },
  	{ "compat_light",					MITYPE_COMPATFLAG, COMPATF_LIGHT, 0 },
  	{ "compat_polyobj",					MITYPE_COMPATFLAG, COMPATF_POLYOBJ, 0 },
  	{ "compat_maskedmidtex",			MITYPE_COMPATFLAG, COMPATF_MASKEDMIDTEX, 0 },
  	{ "compat_badangles",				MITYPE_COMPATFLAG, 0, COMPATF2_BADANGLES },
  	{ "compat_floormove",				MITYPE_COMPATFLAG, 0, COMPATF2_FLOORMOVE },
  	{ "compat_soundcutoff",				MITYPE_COMPATFLAG, 0, COMPATF2_SOUNDCUTOFF },
  	{ "cd_start_track",					MITYPE_EATNEXT,	0, 0 },
  	{ "cd_end1_track",					MITYPE_EATNEXT,	0, 0 },
  	{ "cd_end2_track",					MITYPE_EATNEXT,	0, 0 },
  	{ "cd_end3_track",					MITYPE_EATNEXT,	0, 0 },
  	{ "cd_intermission_track",			MITYPE_EATNEXT,	0, 0 },
  	{ "cd_title_track",					MITYPE_EATNEXT,	0, 0 },
  	{ NULL, MITYPE_IGNORE, 0, 0}
  };
  
  //==========================================================================
  //
  // ParseMapDefinition
  // Parses the body of a map definition, including defaultmap etc.
  //
  //==========================================================================
  
  void FMapInfoParser::ParseMapDefinition(level_info_t &info)
  {
  	int index;
  
  	ParseOpenBrace();
  
  	while (sc.GetString())
  	{
  		if ((index = sc.MatchString(&MapFlagHandlers->name, sizeof(*MapFlagHandlers))) >= 0)
  		{
  			MapInfoFlagHandler *handler = &MapFlagHandlers[index];
  			switch (handler->type)
  			{
  			case MITYPE_EATNEXT:
  				ParseAssign();
  				sc.MustGetString();
  				break;
  
  			case MITYPE_IGNORE:
  				break;
  
  			case MITYPE_SETFLAG:
  				info.flags |= handler->data1;
  				info.flags |= handler->data2;
  				break;
  
  			case MITYPE_CLRFLAG:
  				info.flags &= ~handler->data1;
  				info.flags |= handler->data2;
  				break;
  
  			case MITYPE_SCFLAGS:
  				info.flags = (info.flags & handler->data2) | handler->data1;
  				break;
  
  			case MITYPE_SETFLAG2:
  				info.flags2 |= handler->data1;
  				info.flags2 |= handler->data2;
  				break;
  
  			case MITYPE_CLRFLAG2:
  				info.flags2 &= ~handler->data1;
  				info.flags2 |= handler->data2;
  				break;
  
  			case MITYPE_SCFLAGS2:
  				info.flags2 = (info.flags2 & handler->data2) | handler->data1;
  				break;
  
  			case MITYPE_SETFLAG3:
  				info.flags3 |= handler->data1;
  				info.flags3 |= handler->data2;
  				break;
  
  			case MITYPE_CLRFLAG3:
  				info.flags3 &= ~handler->data1;
  				info.flags3 |= handler->data2;
  				break;
  
  			case MITYPE_SCFLAGS3:
  				info.flags3 = (info.flags3 & handler->data2) | handler->data1;
  				break;
  
  			case MITYPE_COMPATFLAG:
  			{
  				int set = 1;
  				if (format_type == FMT_New)
  				{
  					if (CheckAssign())
  					{
  						sc.MustGetNumber();
  						set = sc.Number;
  					}
  				}
  				else
  				{
  					if (sc.CheckNumber()) set = sc.Number;
  				}
  
  				if (set)
  				{
  					info.compatflags |= handler->data1;
  					info.compatflags2 |= handler->data2;
  				}
  				else
  				{
  					info.compatflags &= ~handler->data1;
  					info.compatflags2 &= ~handler->data2;
  				}
  				info.compatmask |= handler->data1;
  				info.compatmask2 |= handler->data2;
  			}
  			break;
  
  			default:
  				// should never happen
  				assert(false);
  				break;
  			}
  		}
  		else
  		{
  			FAutoSegIterator probe(YRegHead, YRegTail);
  			bool success = false;
  
  			while (*++probe != NULL)
  			{
  				if (sc.Compare(((FMapOptInfo *)(*probe))->name))
  				{
  					if (!((FMapOptInfo *)(*probe))->old && format_type != FMT_New)
  					{
  						sc.ScriptError("MAPINFO option '%s' requires the new MAPINFO format", sc.String);
  					}
  					((FMapOptInfo *)(*probe))->handler(*this, &info);
  					success = true;
  					break;
  				}
  			}
  
  			if (!success)
  			{
  				if (!ParseCloseBrace())
  				{
  					// Unknown
  					sc.ScriptMessage("Unknown property '%s' found in map definition\n", sc.String);
  					SkipToNext();
  				}
  				else
  				{
  					break;
  				}
  			}
  		}
  	}
  	CheckEndOfFile("map");
  }
  
  
  //==========================================================================
  //
  // GetDefaultLevelNum
  // Gets a default level num from a map name.
  //
  //==========================================================================
  
  static int GetDefaultLevelNum(const char *mapname)
  {
  	if (!strnicmp (mapname, "MAP", 3) && strlen(mapname) <= 5)
  	{
  		int mapnum = atoi (mapname + 3);
  
  		if (mapnum >= 1 && mapnum <= 99)
  			return mapnum;
  	}
  	else if (mapname[0] == 'E' &&
  			mapname[1] >= '0' && mapname[1] <= '9' &&
  			mapname[2] == 'M' &&
  			mapname[3] >= '0' && mapname[3] <= '9')
  	{
  		int epinum = mapname[1] - '1';
  		int mapnum = mapname[3] - '0';
  		return epinum*10 + mapnum;
  	}
  	return 0;
  }
  
  //==========================================================================
  //
  // ParseMapHeader
  // Parses the header of a map definition ('map mapxx mapname')
  //
  //==========================================================================
  
  level_info_t *FMapInfoParser::ParseMapHeader(level_info_t &defaultinfo)
  {
  	FName mapname;
  
  	if (sc.CheckNumber())
  	{	// MAPNAME is a number; assume a Hexen wad
  		char maptemp[8];
  		mysnprintf (maptemp, countof(maptemp), "MAP%02d", sc.Number);
  		mapname = maptemp;
  		HexenHack = true;
  	}
  	else 
  	{
  		sc.MustGetString();
  		mapname = sc.String;
  	}
  	int levelindex = FindWadLevelInfo (mapname);
  	if (levelindex == -1)
  	{
  		levelindex = wadlevelinfos.Reserve(1);
  	}
  	level_info_t *levelinfo = &wadlevelinfos[levelindex];
  	*levelinfo = defaultinfo;
  	if (HexenHack)
  	{
  		levelinfo->WallHorizLight = levelinfo->WallVertLight = 0;
  
  		// Hexen levels are automatically nointermission,
  		// no auto sound sequences, falling damage,
  		// monsters activate their own specials, and missiles
  		// are always the activators of impact lines.
  		levelinfo->flags |= LEVEL_NOINTERMISSION
  						 | LEVEL_SNDSEQTOTALCTRL
  						 | LEVEL_FALLDMG_HX
  						 | LEVEL_ACTOWNSPECIAL;
  		levelinfo->flags2|= LEVEL2_HEXENHACK
  						 | LEVEL2_INFINITE_FLIGHT
  						 | LEVEL2_MISSILESACTIVATEIMPACT
  						 | LEVEL2_MONSTERFALLINGDAMAGE;
  
  	}
  
  	levelinfo->MapName = mapname;
  	levelinfo->MapName.ToUpper();
  	sc.MustGetString ();
  	if (sc.String[0] == '$')
  	{
  		// For consistency with other definitions allow $Stringtablename here, too.
  		levelinfo->flags |= LEVEL_LOOKUPLEVELNAME;
  		levelinfo->LevelName = sc.String + 1;
  	}
  	else
  	{
  		if (sc.Compare ("lookup"))
  		{
  			sc.MustGetString ();
  			levelinfo->flags |= LEVEL_LOOKUPLEVELNAME;
  		}
  		levelinfo->LevelName = sc.String;
  	}
  
  	// Set up levelnum now so that you can use Teleport_NewMap specials
  	// to teleport to maps with standard names without needing a levelnum.
  	levelinfo->levelnum = GetDefaultLevelNum(levelinfo->MapName);
  
  	// Does this map have a song defined via SNDINFO's $map command?
  	// Set that as this map's default music if it does.
  	FString *song;
  	if ((song = HexenMusic.CheckKey(levelinfo->levelnum)) != NULL)
  	{
  		levelinfo->Music = *song;
  	}
  
  	return levelinfo;
  }
  
  
  //==========================================================================
  //
  // Episode definitions start with the header "episode <start-map>"
  // and then can be followed by any of the following:
  //
  // name "Episode name as text"
  // picname "Picture to display the episode name"
  // key "Shortcut key for the menu"
  // noskillmenu
  // remove
  //
  //==========================================================================
  
  void FMapInfoParser::ParseEpisodeInfo ()
  {
  	unsigned int i;
  	FString map;
  	FString pic;
  	FString name;
  	bool remove = false;
  	char key = 0;
  	bool noskill = false;
  	bool optional = false;
  	bool extended = false;
  
  	// Get map name
  	sc.MustGetString ();
  	map = sc.String;
  
  	if (sc.CheckString ("teaser"))
  	{
  		sc.MustGetString ();
  		if (gameinfo.flags & GI_SHAREWARE)
  		{
  			map = sc.String;
  		}
  	}
  
  	ParseOpenBrace();
  
  	while (sc.GetString())
  	{
  		if (sc.Compare ("optional"))
  		{
  			// For M4 in Doom
  			optional = true;
  		}
  		else if (sc.Compare ("extended"))
  		{
  			// For M4 and M5 in Heretic
  			extended = true;
  		}
  		else if (sc.Compare ("name"))
  		{
  			ParseAssign();
  			sc.MustGetString ();
  			name = sc.String;
  		}
  		else if (sc.Compare ("picname"))
  		{
  			ParseAssign();
  			sc.MustGetString ();
  			pic = sc.String;
  		}
  		else if (sc.Compare ("remove"))
  		{
  			remove = true;
  		}
  		else if (sc.Compare ("key"))
  		{
  			ParseAssign();
  			sc.MustGetString ();
  			key = sc.String[0];
  		}
  		else if (sc.Compare("noskillmenu"))
  		{
  			noskill = true;
  		}
  		else if (!ParseCloseBrace())
  		{
  			// Unknown
  			sc.ScriptMessage("Unknown property '%s' found in episode definition\n", sc.String);
  			SkipToNext();
  		}
  		else
  		{
  			break;
  		}
  	}
  	CheckEndOfFile("episode");
  
  	if (extended && !(gameinfo.flags & GI_MENUHACK_EXTENDED))
  	{ // If the episode is for the extended Heretic, but this is
  	  // not the extended Heretic, ignore it.
  		return;
  	}
  
  	if (optional && !remove)
  	{
  		if (!P_CheckMapData(map))
  		{
  			// If the episode is optional and the map does not exist
  			// just ignore this episode definition.
  			return;
  		}
  	}
  
  
  	for (i = 0; i < AllEpisodes.Size(); i++)
  	{
  		if (AllEpisodes[i].mEpisodeMap.CompareNoCase(map) == 0)
  		{
  			break;
  		}
  	}
  
  	if (remove)
  	{
  		// If the remove property is given for an episode, remove it.
  		AllEpisodes.Delete(i);
  	}
  	else
  	{
  		// Only allocate a new entry if this doesn't replace an existing episode.
  		if (i >= AllEpisodes.Size())
  		{
  			i = AllEpisodes.Reserve(1);
  		}
  
  		FEpisode *epi = &AllEpisodes[i];
  
  		epi->mEpisodeMap = map;
  		epi->mEpisodeName = name;
  		epi->mPicName = pic;
  		epi->mShortcut = tolower(key);
  		epi->mNoSkill = noskill;
  	}
  }
  
  
  //==========================================================================
  //
  // Clears episode definitions
  //
  //==========================================================================
  
  void ClearEpisodes()
  {
  	AllEpisodes.Clear();
  }
  
  //==========================================================================
  //
  // SetLevelNum
  // Avoid duplicate levelnums. The level being set always has precedence.
  //
  //==========================================================================
  
  static void SetLevelNum (level_info_t *info, int num)
  {
  	for (unsigned int i = 0; i < wadlevelinfos.Size(); ++i)
  	{
  		if (wadlevelinfos[i].levelnum == num)
  			wadlevelinfos[i].levelnum = 0;
  	}
  	info->levelnum = num;
  }
  
  //==========================================================================
  //
  // G_DoParseMapInfo
  // Parses a single MAPINFO lump
  // data for wadlevelinfos and wadclusterinfos.
  //
  //==========================================================================
  
  void FMapInfoParser::ParseMapInfo (int lump, level_info_t &gamedefaults, level_info_t &defaultinfo)
  {
  	sc.OpenLumpNum(lump);
  
  	defaultinfo = gamedefaults;
  	HexenHack = false;
  
  	while (sc.GetString ())
  	{
  		if (sc.Compare("include"))
  		{
  			sc.MustGetString();
  			int inclump = Wads.CheckNumForFullName(sc.String, true);
  			if (inclump < 0)
  			{
  				sc.ScriptError("include file '%s' not found", sc.String);
  			}
  			if (Wads.GetLumpFile(sc.LumpNum) != Wads.GetLumpFile(inclump))
  			{
  				// Do not allow overriding includes from the default MAPINFO
  				if (Wads.GetLumpFile(sc.LumpNum) == 0)
  				{
  					I_FatalError("File %s is overriding core lump %s.",
  						Wads.GetWadFullName(Wads.GetLumpFile(inclump)), sc.String);
  				}
  			}
  			FScanner saved_sc = sc;
  			ParseMapInfo(inclump, gamedefaults, defaultinfo);
  			sc = saved_sc;
  		}
  		else if (sc.Compare("gamedefaults"))
  		{
  			gamedefaults.Reset();
  			ParseMapDefinition(gamedefaults);
  			defaultinfo = gamedefaults;
  		}
  		else if (sc.Compare("defaultmap"))
  		{
  			defaultinfo = gamedefaults;
  			ParseMapDefinition(defaultinfo);
  		}
  		else if (sc.Compare("adddefaultmap"))
  		{
  			// Same as above but adds to the existing definitions instead of replacing them completely
  			ParseMapDefinition(defaultinfo);
  		}
  		else if (sc.Compare("map"))
  		{
  			level_info_t *levelinfo = ParseMapHeader(defaultinfo);
  
  			ParseMapDefinition(*levelinfo);
  
  			// When the second sky is -NOFLAT-, make it a copy of the first sky
  			if (!levelinfo->SkyPic2.CompareNoCase("-NOFLAT-"))
  			{
  				levelinfo->SkyPic2 = levelinfo->SkyPic1;
  			}
  			SetLevelNum (levelinfo, levelinfo->levelnum);	// Wipe out matching levelnums from other maps.
  		}
  		// clusterdef is the old keyword but the new format has enough 
  		// structuring that 'cluster' can be handled, too. The old format does not.
  		else if (sc.Compare("clusterdef") || (format_type != FMT_Old && sc.Compare("cluster")))
  		{
  			ParseCluster();
  		}
  		else if (sc.Compare("episode"))
  		{
  			ParseEpisodeInfo();
  		}
  		else if (sc.Compare("clearepisodes"))
  		{
  			ClearEpisodes();
  		}
  		else if (sc.Compare("skill"))
  		{
  			ParseSkill();
  		}
  		else if (sc.Compare("clearskills"))
  		{
  			AllSkills.Clear();
  			DefaultSkill = -1;
  		}
  		else if (sc.Compare("gameinfo"))
  		{
  			if (format_type != FMT_Old)
  			{
  				format_type = FMT_New;
  				ParseGameInfo();
  			}
  			else
  			{
  				sc.ScriptError("gameinfo definitions not supported with old MAPINFO syntax");
  			}
  		}
  		else if (sc.Compare("intermission"))
  		{
  			if (format_type != FMT_Old)
  			{
  				format_type = FMT_New;
  				ParseIntermission();
  			}
  			else
  			{
  				sc.ScriptError("intermission definitions not supported with old MAPINFO syntax");
  			}
  		}
  		else if (sc.Compare("automap") || sc.Compare("automap_overlay"))
  		{
  			if (format_type != FMT_Old)
  			{
  				format_type = FMT_New;
  				ParseAMColors(sc.Compare("automap_overlay"));
  			}
  			else
  			{
  				sc.ScriptError("automap colorset definitions not supported with old MAPINFO syntax");
  			}
  		}
  		else
  		{
  			sc.ScriptError("%s: Unknown top level keyword", sc.String);
  		}
  	}
  }
  
  //==========================================================================
  //
  //
  //
  //==========================================================================
  
  void DeinitIntermissions();
  
  static void ClearMapinfo()
  {
  	wadclusterinfos.Clear();
  	wadlevelinfos.Clear();
  	ClearEpisodes();
  	AllSkills.Clear();
  	DefaultSkill = -1;
  	DeinitIntermissions();
  	level.info = NULL;
  }
  
  //==========================================================================
  //
  // G_ParseMapInfo
  // Parses the MAPINFO lumps of all loaded WADs and generates
  // data for wadlevelinfos and wadclusterinfos.
  //
  //==========================================================================
  
  void G_ParseMapInfo (FString basemapinfo)
  {
  	int lump, lastlump = 0;
  	level_info_t gamedefaults;
  
  	ClearMapinfo();
  	atterm(ClearMapinfo);
  
  	// Parse the default MAPINFO for the current game. This lump *MUST* come from zdoom.pk3.
  	if (basemapinfo.IsNotEmpty())
  	{
  		FMapInfoParser parse;
  		level_info_t defaultinfo;
  		int baselump = Wads.GetNumForFullName(basemapinfo);
  		if (Wads.GetLumpFile(baselump) > 0)
  		{
  			I_FatalError("File %s is overriding core lump %s.", 
  				Wads.GetWadFullName(Wads.GetLumpFile(baselump)), basemapinfo.GetChars());
  		}
  		parse.ParseMapInfo(baselump, gamedefaults, defaultinfo);
  	}
  
  	static const char *mapinfonames[] = { "MAPINFO", "ZMAPINFO", NULL };
  	int nindex;
  
  	// Parse any extra MAPINFOs.
  	while ((lump = Wads.FindLumpMulti (mapinfonames, &lastlump, false, &nindex)) != -1)
  	{
  		if (nindex == 0)
  		{
  			// If this lump is named MAPINFO we need to check if the same WAD contains a ZMAPINFO lump.
  			// If that exists we need to skip this one.
  
  			int wad = Wads.GetLumpFile(lump);
  			int altlump = Wads.CheckNumForName("ZMAPINFO", ns_global, wad, true);
  
  			if (altlump >= 0) continue;
  		}
  		FMapInfoParser parse(nindex == 1? FMapInfoParser::FMT_New : FMapInfoParser::FMT_Unknown);
  		level_info_t defaultinfo;
  		parse.ParseMapInfo(lump, gamedefaults, defaultinfo);
  	}
  
  	if (AllEpisodes.Size() == 0)
  	{
  		I_FatalError ("You cannot use clearepisodes in a MAPINFO if you do not define any new episodes after it.");
  	}
  	if (AllSkills.Size() == 0)
  	{
  		I_FatalError ("You cannot use clearskills in a MAPINFO if you do not define any new skills after it.");
  	}
  }
  
  