qzdoom/src/g_mapinfo.cpp

2015 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 "m_menu.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"
int FindEndSequence (int type, const char *picname);
TArray<cluster_info_t> wadclusterinfos;
TArray<level_info_t> wadlevelinfos;
level_info_t TheDefaultLevelInfo;
static cluster_info_t TheDefaultClusterInfo;
//==========================================================================
//
//
//==========================================================================
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)
{
int i;
if ((i = FindWadLevelInfo (mapname)) > -1)
{
return &wadlevelinfos[i];
}
else
{
if (TheDefaultLevelInfo.LevelName.IsEmpty())
{
uppercopy(TheDefaultLevelInfo.skypic1, "SKY1");
uppercopy(TheDefaultLevelInfo.skypic2, "SKY1");
TheDefaultLevelInfo.LevelName = "Unnamed";
}
return &TheDefaultLevelInfo;
}
}
//==========================================================================
//
//
//==========================================================================
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();
}
}
//==========================================================================
//
// 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;
flags2 = gameinfo.gametype == GAME_Hexen? 0 : 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 = 0;
compatmask = 0;
Translator = "";
RedirectType = 0;
RedirectMap[0] = 0;
EnterPic = "";
ExitPic = "";
InterMusic = "";
intermusicorder = 0;
SoundInfo = "";
SndSeq = "";
bordertexture[0] = 0;
teamdamage = 0.f;
specialactions.Clear();
DefaultEnvironment = 0;
}
//==========================================================================
//
//
//==========================================================================
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));
}
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)
{
EndSequence newSeq;
bool useseq = false;
if (sc.CheckNumber())
{
if (HexenHack)
{
mysnprintf (mapname, 9, "&wt@%02d", sc.Number);
}
else
{
mysnprintf (mapname, 9, "MAP%02d", sc.Number);
}
}
else
{
sc.MustGetString();
if (sc.Compare("endgame"))
{
if (!sc.CheckString("{"))
{
// Make Demon Eclipse work again
sc.UnGet();
goto standard_endgame;
}
newSeq.Advanced = true;
newSeq.EndType = END_Pic1;
newSeq.PlayTheEnd = false;
newSeq.MusicLooping = true;
while (!sc.CheckString("}"))
{
sc.MustGetString();
if (sc.Compare("pic"))
{
ParseAssign();
sc.MustGetString();
newSeq.EndType = END_Pic;
newSeq.PicName = sc.String;
}
else if (sc.Compare("hscroll"))
{
ParseAssign();
newSeq.EndType = END_Bunny;
sc.MustGetString();
newSeq.PicName = sc.String;
ParseComma();
sc.MustGetString();
newSeq.PicName2 = sc.String;
if (CheckNumber())
newSeq.PlayTheEnd = !!sc.Number;
}
else if (sc.Compare("vscroll"))
{
ParseAssign();
newSeq.EndType = END_Demon;
sc.MustGetString();
newSeq.PicName = sc.String;
ParseComma();
sc.MustGetString();
newSeq.PicName2 = sc.String;
}
else if (sc.Compare("cast"))
{
newSeq.EndType = END_Cast;
}
else if (sc.Compare("music"))
{
ParseAssign();
sc.MustGetString();
newSeq.Music = sc.String;
if (CheckNumber())
{
newSeq.MusicLooping = !!sc.Number;
}
}
else
{
if (format_type == FMT_New)
{
// Unknown
sc.ScriptMessage("Unknown property '%s' found in endgame definition\n", sc.String);
SkipToNext();
}
else
{
sc.ScriptError("Unknown property '%s' found in endgame definition\n", sc.String);
}
}
}
useseq = true;
}
else if (strnicmp (sc.String, "EndGame", 7) == 0)
{
int type;
switch (sc.String[7])
{
case '1': type = END_Pic1; break;
case '2': type = END_Pic2; break;
case '3': type = END_Bunny; break;
case 'C': type = END_Cast; break;
case 'W': type = END_Underwater; break;
case 'S': type = END_Strife; break;
standard_endgame:
default: type = END_Pic3; break;
}
newSeq.EndType = type;
useseq = true;
}
else if (sc.Compare("endpic"))
{
ParseComma();
sc.MustGetString ();
newSeq.EndType = END_Pic;
newSeq.PicName = sc.String;
useseq = true;
}
else if (sc.Compare("endbunny"))
{
newSeq.EndType = END_Bunny;
useseq = true;
}
else if (sc.Compare("endcast"))
{
newSeq.EndType = END_Cast;
useseq = true;
}
else if (sc.Compare("enddemon"))
{
newSeq.EndType = END_Demon;
useseq = true;
}
else if (sc.Compare("endchess"))
{
newSeq.EndType = END_Chess;
useseq = true;
}
else if (sc.Compare("endunderwater"))
{
newSeq.EndType = END_Underwater;
useseq = true;
}
else if (sc.Compare("endbuystrife"))
{
newSeq.EndType = END_BuyStrife;
useseq = true;
}
else if (sc.Compare("endtitle"))
{
newSeq.EndType = END_TitleScreen;
useseq = true;
}
else
{
strncpy (mapname, sc.String, 8);
mapname[8] = 0;
}
if (useseq)
{
int seqnum = -1;
if (!newSeq.Advanced)
{
seqnum = FindEndSequence (newSeq.EndType, newSeq.PicName);
}
if (seqnum == -1)
{
seqnum = (int)EndSequences.Push (newSeq);
}
// mapname can point to nextmap and secretmap which are both 12 characters long
mysnprintf(mapname, 11, "enDSeQ%04x", (WORD)seqnum);
}
}
}
//==========================================================================
//
// 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);
// Flag the level so that the $MAP command doesn't override this.
info->flags2 |= LEVEL2_MUSICDEFINED;
}
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'");
}
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(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(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 },
{ "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_SETFLAG2, LEVEL2_DEATHSLIDESHOW, 0 },
{ "strictmonsteractivation", MITYPE_CLRFLAG2, LEVEL2_LAXMONSTERACTIVATION, LEVEL2_LAXACTIVATIONMAPINFO },
{ "laxmonsteractivation", MITYPE_SETFLAG2, LEVEL2_LAXMONSTERACTIVATION, LEVEL2_LAXACTIVATIONMAPINFO },
{ "additive_scrollers", MITYPE_COMPATFLAG, COMPATF_BOOMSCROLL},
{ "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 },
{ "unfreezesingleplayerconversations",MITYPE_SETFLAG2, LEVEL2_CONV_SINGLE_UNFREEZE, 0 },
{ "nobotnodes", MITYPE_IGNORE, 0, 0 }, // Skulltag option: nobotnodes
{ "compat_shorttex", MITYPE_COMPATFLAG, COMPATF_SHORTTEX},
{ "compat_stairs", MITYPE_COMPATFLAG, COMPATF_STAIRINDEX},
{ "compat_limitpain", MITYPE_COMPATFLAG, COMPATF_LIMITPAIN},
{ "compat_nopassover", MITYPE_COMPATFLAG, COMPATF_NO_PASSMOBJ},
{ "compat_notossdrops", MITYPE_COMPATFLAG, COMPATF_NOTOSSDROPS},
{ "compat_useblocking", MITYPE_COMPATFLAG, COMPATF_USEBLOCKING},
{ "compat_nodoorlight", MITYPE_COMPATFLAG, COMPATF_NODOORLIGHT},
{ "compat_ravenscroll", MITYPE_COMPATFLAG, COMPATF_RAVENSCROLL},
{ "compat_soundtarget", MITYPE_COMPATFLAG, COMPATF_SOUNDTARGET},
{ "compat_dehhealth", MITYPE_COMPATFLAG, COMPATF_DEHHEALTH},
{ "compat_trace", MITYPE_COMPATFLAG, COMPATF_TRACE},
{ "compat_dropoff", MITYPE_COMPATFLAG, COMPATF_DROPOFF},
{ "compat_boomscroll", MITYPE_COMPATFLAG, COMPATF_BOOMSCROLL},
{ "compat_invisibility", MITYPE_COMPATFLAG, COMPATF_INVISIBILITY},
{ "compat_silent_instant_floors", MITYPE_COMPATFLAG, COMPATF_SILENT_INSTANT_FLOORS},
{ "compat_sectorsounds", MITYPE_COMPATFLAG, COMPATF_SECTORSOUNDS},
{ "compat_missileclip", MITYPE_COMPATFLAG, COMPATF_MISSILECLIP},
{ "compat_crossdropoff", MITYPE_COMPATFLAG, COMPATF_CROSSDROPOFF},
{ "compat_anybossdeath", MITYPE_COMPATFLAG, COMPATF_ANYBOSSDEATH},
{ "compat_minotaur", MITYPE_COMPATFLAG, COMPATF_MINOTAUR},
{ "compat_mushroom", MITYPE_COMPATFLAG, COMPATF_MUSHROOM},
{ "compat_mbfmonstermove", MITYPE_COMPATFLAG, COMPATF_MBFMONSTERMOVE},
{ "compat_corpsegibs", MITYPE_COMPATFLAG, COMPATF_CORPSEGIBS},
{ "compat_noblockfriends", MITYPE_COMPATFLAG, COMPATF_NOBLOCKFRIENDS},
{ "compat_spritesort", MITYPE_COMPATFLAG, COMPATF_SPRITESORT},
{ "compat_light", MITYPE_COMPATFLAG, COMPATF_LIGHT},
{ "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}
};
//==========================================================================
//
// 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;
else info.compatflags &= ~handler->data1;
info.compatmask |= handler->data1;
}
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);
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 ()
{
int i;
char map[9];
char *pic = NULL;
bool picisgfx = false; // Shut up, GCC!!!!
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 ();
ReplaceString (&pic, sc.String);
picisgfx = false;
}
else if (sc.Compare ("picname"))
{
ParseAssign();
sc.MustGetString ();
ReplaceString (&pic, sc.String);
picisgfx = true;
}
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 < EpiDef.numitems; ++i)
{
if (strncmp (EpisodeMaps[i], map, 8) == 0)
{
break;
}
}
if (remove)
{
// If the remove property is given for an episode, remove it.
if (i < EpiDef.numitems)
{
if (i+1 < EpiDef.numitems)
{
memmove (&EpisodeMaps[i], &EpisodeMaps[i+1],
sizeof(EpisodeMaps[0])*(EpiDef.numitems - i - 1));
memmove (&EpisodeMenu[i], &EpisodeMenu[i+1],
sizeof(EpisodeMenu[0])*(EpiDef.numitems - i - 1));
memmove (&EpisodeNoSkill[i], &EpisodeNoSkill[i+1],
sizeof(EpisodeNoSkill[0])*(EpiDef.numitems - i - 1));
}
EpiDef.numitems--;
}
}
else
{
if (pic == NULL)
{
pic = copystring (map);
picisgfx = false;
}
if (i == EpiDef.numitems)
{
if (EpiDef.numitems == MAX_EPISODES)
{
i = EpiDef.numitems - 1;
}
else
{
i = EpiDef.numitems++;
}
}
else
{
delete[] const_cast<char *>(EpisodeMenu[i].name);
}
EpisodeMenu[i].name = pic;
EpisodeMenu[i].alphaKey = tolower(key);
EpisodeMenu[i].fulltext = !picisgfx;
EpisodeNoSkill[i] = noskill;
strncpy (EpisodeMaps[i], map, 8);
EpisodeMaps[i][8] = 0;
}
}
//==========================================================================
//
// Clears episode definitions
//
//==========================================================================
void ClearEpisodes()
{
for (int i = 0; i < EpiDef.numitems; ++i)
{
delete[] const_cast<char *>(EpisodeMenu[i].name);
EpisodeMenu[i].name = NULL;
}
EpiDef.numitems = 0;
}
//==========================================================================
//
// 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
{
sc.ScriptError("%s: Unknown top level keyword", sc.String);
}
}
}
//==========================================================================
//
// 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;
atterm(ClearEpisodes);
// 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);
}
EndSequences.ShrinkToFit ();
if (EpiDef.numitems == 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.");
}
}