mirror of
https://github.com/ZDoom/gzdoom.git
synced 2024-11-28 06:42:09 +00:00
9f35788164
- extended am_drawmapback so that map backgrounds can only be drawn if used with custom colorsets and Raven's, assuming that these sets were specifically made for the accompanying backgrounds.
1943 lines
50 KiB
C++
1943 lines
50 KiB
C++
/*
|
|
** 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 (!strnicmp (name, wadlevelinfos[i].mapname, 8))
|
|
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())
|
|
{
|
|
uppercopy(TheDefaultLevelInfo.skypic1, "SKY1");
|
|
uppercopy(TheDefaultLevelInfo.skypic2, "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[0] = 0;
|
|
mapbg[0] = 0;
|
|
levelnum = 0;
|
|
pname[0] = 0;
|
|
nextmap[0] = 0;
|
|
secretmap[0] = 0;
|
|
strcpy (skypic1, "-NOFLAT-");
|
|
strcpy (skypic2, "-NOFLAT-");
|
|
cluster = 0;
|
|
partime = 0;
|
|
sucktime = 0;
|
|
flags = 0;
|
|
if (gameinfo.gametype == GAME_Hexen)
|
|
flags2 = 0;
|
|
else
|
|
flags2 = LEVEL2_LAXMONSTERACTIVATION;
|
|
Music = "";
|
|
LevelName = "";
|
|
strcpy (fadetable, "COLORMAP");
|
|
WallHorizLight = -8;
|
|
WallVertLight = +8;
|
|
f1[0] = 0;
|
|
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;
|
|
WarpTrans = 0;
|
|
airsupply = 20;
|
|
compatflags = compatflags2 = 0;
|
|
compatmask = compatmask2 = 0;
|
|
Translator = "";
|
|
RedirectType = 0;
|
|
RedirectMap[0] = 0;
|
|
EnterPic = "";
|
|
ExitPic = "";
|
|
InterMusic = "";
|
|
intermusicorder = 0;
|
|
SoundInfo = "";
|
|
SndSeq = "";
|
|
bordertexture[0] = 0;
|
|
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[0] == 'E' && mapname[2] == 'M')
|
|
{
|
|
mysnprintf (checkstring, countof(checkstring), "%s: ", mapname);
|
|
}
|
|
else if (mapname[0] == 'M' && mapname[1] == 'A' && mapname[2] == 'P')
|
|
{
|
|
mysnprintf (checkstring, countof(checkstring), "%d: ", atoi(mapname + 3));
|
|
}
|
|
else if (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(RedirectMap))
|
|
{
|
|
return FindLevelInfo(RedirectMap);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
//
|
|
//==========================================================================
|
|
|
|
bool level_info_t::isValid()
|
|
{
|
|
return mapname[0] != 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(char *mapname)
|
|
{
|
|
if (sc.CheckNumber())
|
|
{
|
|
if (HexenHack)
|
|
{
|
|
mysnprintf (mapname, 9, "&wt@%02d", sc.Number);
|
|
}
|
|
else
|
|
{
|
|
mysnprintf (mapname, 9, "MAP%02d", sc.Number);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*mapname = 0;
|
|
sc.MustGetString();
|
|
strncpy (mapname, sc.String, 8);
|
|
mapname[8] = 0;
|
|
FName seq = CheckEndSequence();
|
|
if (seq != NAME_None)
|
|
{
|
|
mysnprintf(mapname, 11, "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->secretmap);
|
|
}
|
|
|
|
DEFINE_MAP_OPTION(secret, true) // Just an alias for secretnext, for Vavoom compatibility
|
|
{
|
|
parse.ParseAssign();
|
|
parse.ParseNextMap(info->secretmap);
|
|
}
|
|
|
|
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(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.ParseLumpOrTextureName(info->RedirectMap);
|
|
}
|
|
|
|
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->f1);
|
|
}
|
|
|
|
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->mapbg);
|
|
}
|
|
|
|
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_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 },
|
|
{ "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 },
|
|
{ "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_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;
|
|
|
|
}
|
|
|
|
uppercopy (levelinfo->mapname, mapname);
|
|
levelinfo->mapname[8] = 0;
|
|
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;
|
|
char map[9];
|
|
FString pic;
|
|
FString name;
|
|
bool remove = false;
|
|
char key = 0;
|
|
bool noskill = false;
|
|
bool optional = false;
|
|
bool extended = false;
|
|
|
|
// Get map name
|
|
sc.MustGetString ();
|
|
uppercopy (map, sc.String);
|
|
map[8] = 0;
|
|
|
|
if (sc.CheckString ("teaser"))
|
|
{
|
|
sc.MustGetString ();
|
|
if (gameinfo.flags & GI_SHAREWARE)
|
|
{
|
|
uppercopy (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 (strcmp (levelinfo->skypic2, "-NOFLAT-") == 0)
|
|
{
|
|
strcpy (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 (const char *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 != NULL)
|
|
{
|
|
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);
|
|
}
|
|
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.");
|
|
}
|
|
}
|
|
|