gzdoom-gles/src/g_level.cpp
Christoph Oelckers ee69e7bf03 - Removed thingdef_specials.h and thingdef_specials.gperf and replaced
line special definition with something that automatically gets updated
  if new specials are added.


SVN r814 (trunk)
2008-03-19 12:48:02 +00:00

3412 lines
82 KiB
C++

/*
** g_level.cpp
** Parses MAPINFO and controls movement between levels
**
**---------------------------------------------------------------------------
** Copyright 1998-2006 Randy Heit
** 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 "d_main.h"
#include "m_alloc.h"
#include "g_level.h"
#include "g_game.h"
#include "s_sound.h"
#include "d_event.h"
#include "m_random.h"
#include "doomstat.h"
#include "wi_stuff.h"
#include "r_data.h"
#include "w_wad.h"
#include "am_map.h"
#include "c_dispatch.h"
#include "i_system.h"
#include "p_setup.h"
#include "p_local.h"
#include "r_sky.h"
#include "c_console.h"
#include "f_finale.h"
#include "gstrings.h"
#include "v_video.h"
#include "st_stuff.h"
#include "hu_stuff.h"
#include "p_saveg.h"
#include "p_acs.h"
#include "d_protocol.h"
#include "v_text.h"
#include "s_sndseq.h"
#include "b_bot.h"
#include "sc_man.h"
#include "sbar.h"
#include "a_lightning.h"
#include "m_png.h"
#include "m_random.h"
#include "version.h"
#include "m_menu.h"
#include "statnums.h"
#include "vectors.h"
#include "sbarinfo.h"
#include "r_translate.h"
#include "p_lnspec.h"
#include "gi.h"
#include "g_hub.h"
EXTERN_CVAR (Float, sv_gravity)
EXTERN_CVAR (Float, sv_aircontrol)
EXTERN_CVAR (Int, disableautosave)
// Hey, GCC, these macros better be safe!
#define lioffset(x) ((size_t)&((level_info_t*)1)->x - 1)
#define cioffset(x) ((size_t)&((cluster_info_t*)1)->x - 1)
#define SNAP_ID MAKE_ID('s','n','A','p')
#define DSNP_ID MAKE_ID('d','s','N','p')
#define VIST_ID MAKE_ID('v','i','S','t')
#define ACSD_ID MAKE_ID('a','c','S','d')
#define RCLS_ID MAKE_ID('r','c','L','s')
#define PCLS_ID MAKE_ID('p','c','L','s')
static int FindEndSequence (int type, const char *picname);
static void SetEndSequence (char *nextmap, int type);
static void InitPlayerClasses ();
static void ParseEpisodeInfo (FScanner &sc);
static void G_DoParseMapInfo (int lump);
static void SetLevelNum (level_info_t *info, int num);
static void ClearEpisodes ();
static void ClearLevelInfoStrings (level_info_t *linfo);
static void ClearClusterInfoStrings (cluster_info_t *cinfo);
static void ParseSkill (FScanner &sc);
static void G_VerifySkill();
static FRandom pr_classchoice ("RandomPlayerClassChoice");
TArray<EndSequence> EndSequences;
extern bool timingdemo;
// Start time for timing demos
int starttime;
// ACS variables with world scope
SDWORD ACS_WorldVars[NUM_WORLDVARS];
FWorldGlobalArray ACS_WorldArrays[NUM_WORLDVARS];
// ACS variables with global scope
SDWORD ACS_GlobalVars[NUM_GLOBALVARS];
FWorldGlobalArray ACS_GlobalArrays[NUM_GLOBALVARS];
extern bool netdemo;
extern FString BackupSaveName;
bool savegamerestore;
extern int mousex, mousey;
extern bool sendpause, sendsave, sendturn180, SendLand;
extern const AInventory *SendItemUse, *SendItemDrop;
void *statcopy; // for statistics driver
level_locals_t level; // info about current level
static TArray<cluster_info_t> wadclusterinfos;
TArray<level_info_t> wadlevelinfos;
TArray<FSkillInfo> AllSkills;
// MAPINFO is parsed slightly differently when the map name is just a number.
static bool HexenHack;
static char unnamed[] = "Unnamed";
static level_info_t TheDefaultLevelInfo =
{
"", // mapname
0, // levelnum
"", // pname,
"", // nextmap
"", // secretmap
"SKY1", // skypic1
0, // cluster
0, // partime
0, // sucktime
0, // flags
NULL, // music
unnamed, // level_name
"COLORMAP", // fadetable
+8, // WallVertLight
-8, // WallHorizLight
"", // [RC] F1
};
static cluster_info_t TheDefaultClusterInfo = { 0 };
static const char *MapInfoTopLevel[] =
{
"map",
"defaultmap",
"clusterdef",
"episode",
"clearepisodes",
"skill",
"clearskills",
NULL
};
enum
{
MITL_MAP,
MITL_DEFAULTMAP,
MITL_CLUSTERDEF,
MITL_EPISODE,
MITL_CLEAREPISODES,
MITL_SKILL,
MITL_CLEARSKILLS,
};
static const char *MapInfoMapLevel[] =
{
"levelnum",
"next",
"secretnext",
"cluster",
"sky1",
"sky2",
"fade",
"outsidefog",
"titlepatch",
"par",
"sucktime",
"music",
"nointermission",
"intermission",
"doublesky",
"nosoundclipping",
"allowmonstertelefrags",
"map07special",
"baronspecial",
"cyberdemonspecial",
"spidermastermindspecial",
"minotaurspecial",
"dsparilspecial",
"ironlichspecial",
"specialaction_exitlevel",
"specialaction_opendoor",
"specialaction_lowerfloor",
"specialaction_killmonsters",
"lightning",
"fadetable",
"evenlighting",
"noautosequences",
"forcenoskystretch",
"allowfreelook",
"nofreelook",
"allowjump",
"nojump",
"fallingdamage", // Hexen falling damage
"oldfallingdamage", // Lesser ZDoom falling damage
"forcefallingdamage", // Skull Tag compatibility name for oldfallingdamage
"strifefallingdamage", // Strife's falling damage is really unforgiving
"nofallingdamage",
"noallies",
"cdtrack",
"cdid",
"cd_start_track",
"cd_end1_track",
"cd_end2_track",
"cd_end3_track",
"cd_intermission_track",
"cd_title_track",
"warptrans",
"vertwallshade",
"horizwallshade",
"gravity",
"aircontrol",
"filterstarts",
"activateowndeathspecials",
"killeractivatesdeathspecials",
"missilesactivateimpactlines",
"missileshootersactivetimpactlines",
"noinventorybar",
"deathslideshow",
"redirect",
"strictmonsteractivation",
"laxmonsteractivation",
"additive_scrollers",
"interpic",
"exitpic",
"enterpic",
"intermusic",
"airsupply",
"specialaction",
"keepfullinventory",
"monsterfallingdamage",
"nomonsterfallingdamage",
"sndseq",
"sndinfo",
"soundinfo",
"clipmidtextures",
"wrapmidtextures",
"allowcrouch",
"nocrouch",
"pausemusicinmenus",
"compat_shorttex",
"compat_stairs",
"compat_limitpain",
"compat_nopassover",
"compat_notossdrops",
"compat_useblocking",
"compat_nodoorlight",
"compat_ravenscroll",
"compat_soundtarget",
"compat_dehhealth",
"compat_trace",
"compat_dropoff",
"compat_boomscroll",
"compat_invisibility",
"bordertexture",
"f1", // [RC] F1 help
"noinfighting",
"normalinfighting",
"totalinfighting",
"infiniteflightpowerup",
"noinfiniteflightpowerup",
"allowrespawn",
"teamdamage",
"fogdensity",
"outsidefogdensity",
"skyfog",
"teamplayon",
"teamplayoff",
"checkswitchrange",
"nocheckswitchrange",
NULL
};
enum EMIType
{
MITYPE_EATNEXT,
MITYPE_IGNORE,
MITYPE_INT,
MITYPE_FLOAT,
MITYPE_HEX,
MITYPE_COLOR,
MITYPE_MAPNAME,
MITYPE_LUMPNAME,
MITYPE_SKY,
MITYPE_SETFLAG,
MITYPE_CLRFLAG,
MITYPE_SCFLAGS,
MITYPE_CLUSTER,
MITYPE_STRING,
MITYPE_MUSIC,
MITYPE_RELLIGHT,
MITYPE_CLRBYTES,
MITYPE_REDIRECT,
MITYPE_SPECIALACTION,
MITYPE_COMPATFLAG,
MITYPE_F1, // [RC] F1 help
};
struct MapInfoHandler
{
EMIType type;
QWORD data1, data2;
}
MapHandlers[] =
{
{ MITYPE_INT, lioffset(levelnum), 0 },
{ MITYPE_MAPNAME, lioffset(nextmap), 0 },
{ MITYPE_MAPNAME, lioffset(secretmap), 0 },
{ MITYPE_CLUSTER, lioffset(cluster), 0 },
{ MITYPE_SKY, lioffset(skypic1), lioffset(skyspeed1) },
{ MITYPE_SKY, lioffset(skypic2), lioffset(skyspeed2) },
{ MITYPE_COLOR, lioffset(fadeto), 0 },
{ MITYPE_COLOR, lioffset(outsidefog), 0 },
{ MITYPE_LUMPNAME, lioffset(pname), 0 },
{ MITYPE_INT, lioffset(partime), 0 },
{ MITYPE_INT, lioffset(sucktime), 0 },
{ MITYPE_MUSIC, lioffset(music), lioffset(musicorder) },
{ MITYPE_SETFLAG, LEVEL_NOINTERMISSION, 0 },
{ MITYPE_CLRFLAG, LEVEL_NOINTERMISSION, 0 },
{ MITYPE_SETFLAG, LEVEL_DOUBLESKY, 0 },
{ MITYPE_IGNORE, 0, 0 }, // was nosoundclipping
{ MITYPE_SETFLAG, LEVEL_MONSTERSTELEFRAG, 0 },
{ MITYPE_SETFLAG, LEVEL_MAP07SPECIAL, 0 },
{ MITYPE_SETFLAG, LEVEL_BRUISERSPECIAL, 0 },
{ MITYPE_SETFLAG, LEVEL_CYBORGSPECIAL, 0 },
{ MITYPE_SETFLAG, LEVEL_SPIDERSPECIAL, 0 },
{ MITYPE_SETFLAG, LEVEL_MINOTAURSPECIAL, 0 },
{ MITYPE_SETFLAG, LEVEL_SORCERER2SPECIAL, 0 },
{ MITYPE_SETFLAG, LEVEL_HEADSPECIAL, 0 },
{ MITYPE_SCFLAGS, 0, ~LEVEL_SPECACTIONSMASK },
{ MITYPE_SCFLAGS, LEVEL_SPECOPENDOOR, ~LEVEL_SPECACTIONSMASK },
{ MITYPE_SCFLAGS, LEVEL_SPECLOWERFLOOR, ~LEVEL_SPECACTIONSMASK },
{ MITYPE_SETFLAG, LEVEL_SPECKILLMONSTERS, 0 },
{ MITYPE_SETFLAG, LEVEL_STARTLIGHTNING, 0 },
{ MITYPE_LUMPNAME, lioffset(fadetable), 0 },
{ MITYPE_CLRBYTES, lioffset(WallVertLight), lioffset(WallHorizLight) },
{ MITYPE_SETFLAG, LEVEL_SNDSEQTOTALCTRL, 0 },
{ MITYPE_SETFLAG, LEVEL_FORCENOSKYSTRETCH, 0 },
{ MITYPE_SCFLAGS, LEVEL_FREELOOK_YES, ~LEVEL_FREELOOK_NO },
{ MITYPE_SCFLAGS, LEVEL_FREELOOK_NO, ~LEVEL_FREELOOK_YES },
{ MITYPE_CLRFLAG, LEVEL_JUMP_NO, 0 },
{ MITYPE_SETFLAG, LEVEL_JUMP_NO, 0 },
{ MITYPE_SCFLAGS, LEVEL_FALLDMG_HX, ~LEVEL_FALLDMG_ZD },
{ MITYPE_SCFLAGS, LEVEL_FALLDMG_ZD, ~LEVEL_FALLDMG_HX },
{ MITYPE_SCFLAGS, LEVEL_FALLDMG_ZD, ~LEVEL_FALLDMG_HX },
{ MITYPE_SETFLAG, LEVEL_FALLDMG_ZD|LEVEL_FALLDMG_HX, 0 },
{ MITYPE_SCFLAGS, 0, ~(LEVEL_FALLDMG_ZD|LEVEL_FALLDMG_HX) },
{ MITYPE_SETFLAG, LEVEL_NOALLIES, 0 },
{ MITYPE_INT, lioffset(cdtrack), 0 },
{ MITYPE_HEX, lioffset(cdid), 0 },
{ MITYPE_EATNEXT, 0, 0 },
{ MITYPE_EATNEXT, 0, 0 },
{ MITYPE_EATNEXT, 0, 0 },
{ MITYPE_EATNEXT, 0, 0 },
{ MITYPE_EATNEXT, 0, 0 },
{ MITYPE_EATNEXT, 0, 0 },
{ MITYPE_INT, lioffset(WarpTrans), 0 },
{ MITYPE_RELLIGHT, lioffset(WallVertLight), 0 },
{ MITYPE_RELLIGHT, lioffset(WallHorizLight), 0 },
{ MITYPE_FLOAT, lioffset(gravity), 0 },
{ MITYPE_FLOAT, lioffset(aircontrol), 0 },
{ MITYPE_SETFLAG, LEVEL_FILTERSTARTS, 0 },
{ MITYPE_SETFLAG, LEVEL_ACTOWNSPECIAL, 0 },
{ MITYPE_CLRFLAG, LEVEL_ACTOWNSPECIAL, 0 },
{ MITYPE_SETFLAG, LEVEL_MISSILESACTIVATEIMPACT, 0 },
{ MITYPE_CLRFLAG, LEVEL_MISSILESACTIVATEIMPACT, 0 },
{ MITYPE_SETFLAG, LEVEL_NOINVENTORYBAR, 0 },
{ MITYPE_SETFLAG, LEVEL_DEATHSLIDESHOW, 0 },
{ MITYPE_REDIRECT, lioffset(RedirectMap), 0 },
{ MITYPE_CLRFLAG, LEVEL_LAXMONSTERACTIVATION, LEVEL_LAXACTIVATIONMAPINFO },
{ MITYPE_SETFLAG, LEVEL_LAXMONSTERACTIVATION, LEVEL_LAXACTIVATIONMAPINFO },
{ MITYPE_COMPATFLAG, COMPATF_BOOMSCROLL},
{ MITYPE_LUMPNAME, lioffset(exitpic), 0 },
{ MITYPE_LUMPNAME, lioffset(exitpic), 0 },
{ MITYPE_LUMPNAME, lioffset(enterpic), 0 },
{ MITYPE_MUSIC, lioffset(intermusic), lioffset(intermusicorder) },
{ MITYPE_INT, lioffset(airsupply), 0 },
{ MITYPE_SPECIALACTION, lioffset(specialactions), 0 },
{ MITYPE_SETFLAG, LEVEL_KEEPFULLINVENTORY, 0 },
{ MITYPE_SETFLAG, LEVEL_MONSTERFALLINGDAMAGE, 0 },
{ MITYPE_CLRFLAG, LEVEL_MONSTERFALLINGDAMAGE, 0 },
{ MITYPE_LUMPNAME, lioffset(sndseq), 0 },
{ MITYPE_LUMPNAME, lioffset(soundinfo), 0 },
{ MITYPE_LUMPNAME, lioffset(soundinfo), 0 },
{ MITYPE_SETFLAG, LEVEL_CLIPMIDTEX, 0 },
{ MITYPE_SETFLAG, LEVEL_WRAPMIDTEX, 0 },
{ MITYPE_CLRFLAG, LEVEL_CROUCH_NO, 0 },
{ MITYPE_SETFLAG, LEVEL_CROUCH_NO, 0 },
{ MITYPE_SCFLAGS, LEVEL_PAUSE_MUSIC_IN_MENUS, 0 },
{ MITYPE_COMPATFLAG, COMPATF_SHORTTEX},
{ MITYPE_COMPATFLAG, COMPATF_STAIRINDEX},
{ MITYPE_COMPATFLAG, COMPATF_LIMITPAIN},
{ MITYPE_COMPATFLAG, COMPATF_NO_PASSMOBJ},
{ MITYPE_COMPATFLAG, COMPATF_NOTOSSDROPS},
{ MITYPE_COMPATFLAG, COMPATF_USEBLOCKING},
{ MITYPE_COMPATFLAG, COMPATF_NODOORLIGHT},
{ MITYPE_COMPATFLAG, COMPATF_RAVENSCROLL},
{ MITYPE_COMPATFLAG, COMPATF_SOUNDTARGET},
{ MITYPE_COMPATFLAG, COMPATF_DEHHEALTH},
{ MITYPE_COMPATFLAG, COMPATF_TRACE},
{ MITYPE_COMPATFLAG, COMPATF_DROPOFF},
{ MITYPE_COMPATFLAG, COMPATF_BOOMSCROLL},
{ MITYPE_COMPATFLAG, COMPATF_INVISIBILITY},
{ MITYPE_LUMPNAME, lioffset(bordertexture), 0 },
{ MITYPE_F1, lioffset(f1), 0, },
{ MITYPE_SCFLAGS, LEVEL_NOINFIGHTING, ~LEVEL_TOTALINFIGHTING },
{ MITYPE_SCFLAGS, 0, ~(LEVEL_NOINFIGHTING|LEVEL_TOTALINFIGHTING)},
{ MITYPE_SCFLAGS, LEVEL_TOTALINFIGHTING, ~LEVEL_NOINFIGHTING },
{ MITYPE_SETFLAG, LEVEL_INFINITE_FLIGHT, 0 },
{ MITYPE_CLRFLAG, LEVEL_INFINITE_FLIGHT, 0 },
{ MITYPE_SETFLAG, LEVEL_ALLOWRESPAWN, 0 },
{ MITYPE_FLOAT, lioffset(teamdamage), 0 },
{ MITYPE_INT, lioffset(fogdensity), 0 },
{ MITYPE_INT, lioffset(outsidefogdensity), 0 },
{ MITYPE_INT, lioffset(skyfog), 0 },
{ MITYPE_SCFLAGS, LEVEL_FORCETEAMPLAYON, ~LEVEL_FORCETEAMPLAYOFF },
{ MITYPE_SCFLAGS, LEVEL_FORCETEAMPLAYOFF, ~LEVEL_FORCETEAMPLAYON },
{ MITYPE_SETFLAG, LEVEL_CHECKSWITCHRANGE, 0 },
{ MITYPE_CLRFLAG, LEVEL_CHECKSWITCHRANGE, 0 },
};
static const char *MapInfoClusterLevel[] =
{
"entertext",
"exittext",
"music",
"flat",
"pic",
"hub",
"cdtrack",
"cdid",
"entertextislump",
"exittextislump",
"name",
NULL
};
MapInfoHandler ClusterHandlers[] =
{
{ MITYPE_STRING, cioffset(entertext), CLUSTER_LOOKUPENTERTEXT },
{ MITYPE_STRING, cioffset(exittext), CLUSTER_LOOKUPEXITTEXT },
{ MITYPE_MUSIC, cioffset(messagemusic), cioffset(musicorder) },
{ MITYPE_LUMPNAME, cioffset(finaleflat), 0 },
{ MITYPE_LUMPNAME, cioffset(finaleflat), CLUSTER_FINALEPIC },
{ MITYPE_SETFLAG, CLUSTER_HUB, 0 },
{ MITYPE_INT, cioffset(cdtrack), 0 },
{ MITYPE_HEX, cioffset(cdid), 0 },
{ MITYPE_SETFLAG, CLUSTER_ENTERTEXTINLUMP, 0 },
{ MITYPE_SETFLAG, CLUSTER_EXITTEXTINLUMP, 0 },
{ MITYPE_STRING, cioffset(clustername), 0 },
};
static void ParseMapInfoLower (FScanner &sc,
MapInfoHandler *handlers,
const char *strings[],
level_info_t *levelinfo,
cluster_info_t *clusterinfo,
QWORD levelflags);
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;
}
static int FindWadClusterInfo (int cluster)
{
for (unsigned int i = 0; i < wadclusterinfos.Size(); i++)
if (wadclusterinfos[i].cluster == cluster)
return i;
return -1;
}
static void SetLevelDefaults (level_info_t *levelinfo)
{
memset (levelinfo, 0, sizeof(*levelinfo));
levelinfo->snapshot = NULL;
levelinfo->outsidefog = 0xff000000;
levelinfo->WallHorizLight = -8;
levelinfo->WallVertLight = +8;
strncpy (levelinfo->fadetable, "COLORMAP", 8);
strcpy (levelinfo->skypic1, "-NOFLAT-");
strcpy (levelinfo->skypic2, "-NOFLAT-");
strcpy (levelinfo->bordertexture, gameinfo.borderFlat);
if (gameinfo.gametype != GAME_Hexen)
{
// For maps without a BEHAVIOR, this will be cleared.
levelinfo->flags |= LEVEL_LAXMONSTERACTIVATION;
}
else
{
levelinfo->flags |= LEVEL_MONSTERFALLINGDAMAGE;
}
levelinfo->airsupply = 10;
}
//
// G_ParseMapInfo
// Parses the MAPINFO lumps of all loaded WADs and generates
// data for wadlevelinfos and wadclusterinfos.
//
void G_ParseMapInfo ()
{
int lump, lastlump = 0;
atterm (G_UnloadMapInfo);
// Parse the default MAPINFO for the current game.
switch (gameinfo.gametype)
{
case GAME_Doom:
G_DoParseMapInfo (Wads.GetNumForFullName ("mapinfo/doomcommon.txt"));
switch (gamemission)
{
case doom:
G_DoParseMapInfo (Wads.GetNumForFullName ("mapinfo/doom1.txt"));
break;
case pack_plut:
G_DoParseMapInfo (Wads.GetNumForFullName ("mapinfo/plutonia.txt"));
break;
case pack_tnt:
G_DoParseMapInfo (Wads.GetNumForFullName ("mapinfo/tnt.txt"));
break;
default:
G_DoParseMapInfo (Wads.GetNumForFullName ("mapinfo/doom2.txt"));
break;
}
break;
case GAME_Heretic:
G_DoParseMapInfo (Wads.GetNumForFullName ("mapinfo/heretic.txt"));
break;
case GAME_Hexen:
G_DoParseMapInfo (Wads.GetNumForFullName ("mapinfo/hexen.txt"));
break;
case GAME_Strife:
G_DoParseMapInfo (Wads.GetNumForFullName ("mapinfo/strife.txt"));
break;
default:
break;
}
// Parse any extra MAPINFOs.
while ((lump = Wads.FindLump ("MAPINFO", &lastlump)) != -1)
{
G_DoParseMapInfo (lump);
}
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.");
}
}
static void G_DoParseMapInfo (int lump)
{
level_info_t defaultinfo;
level_info_t *levelinfo;
int levelindex;
cluster_info_t *clusterinfo;
int clusterindex;
QWORD levelflags;
FScanner sc(lump, Wads.GetLumpFullName(lump));
SetLevelDefaults (&defaultinfo);
HexenHack = false;
while (sc.GetString ())
{
switch (sc.MustMatchString (MapInfoTopLevel))
{
case MITL_DEFAULTMAP:
if (defaultinfo.music != NULL) delete [] defaultinfo.music;
if (defaultinfo.intermusic != NULL) delete [] defaultinfo.intermusic;
SetLevelDefaults (&defaultinfo);
ParseMapInfoLower (sc, MapHandlers, MapInfoMapLevel, &defaultinfo, NULL, defaultinfo.flags);
break;
case MITL_MAP: // map <MAPNAME> <Nice Name>
levelflags = defaultinfo.flags;
sc.MustGetString ();
if (IsNum (sc.String))
{ // MAPNAME is a number; assume a Hexen wad
int map = atoi (sc.String);
sprintf (sc.String, "MAP%02d", map);
HexenHack = true;
// 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.
levelflags |= LEVEL_NOINTERMISSION
| LEVEL_SNDSEQTOTALCTRL
| LEVEL_FALLDMG_HX
| LEVEL_ACTOWNSPECIAL
| LEVEL_MISSILESACTIVATEIMPACT
| LEVEL_INFINITE_FLIGHT;
}
levelindex = FindWadLevelInfo (sc.String);
if (levelindex == -1)
{
levelindex = wadlevelinfos.Reserve(1);
}
else
{
ClearLevelInfoStrings (&wadlevelinfos[levelindex]);
}
levelinfo = &wadlevelinfos[levelindex];
memcpy (levelinfo, &defaultinfo, sizeof(*levelinfo));
if (levelinfo->music != NULL)
{
levelinfo->music = copystring (levelinfo->music);
}
if (levelinfo->intermusic != NULL)
{
levelinfo->intermusic = copystring (levelinfo->intermusic);
}
if (HexenHack)
{
levelinfo->WallHorizLight = levelinfo->WallVertLight = 0;
}
uppercopy (levelinfo->mapname, sc.String);
sc.MustGetString ();
if (sc.Compare ("lookup"))
{
sc.MustGetString ();
ReplaceString (&levelinfo->level_name, sc.String);
levelflags |= LEVEL_LOOKUPLEVELNAME;
}
else
{
ReplaceString (&levelinfo->level_name, 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.
if (!strnicmp (levelinfo->mapname, "MAP", 3) && levelinfo->mapname[5] == 0)
{
int mapnum = atoi (levelinfo->mapname + 3);
if (mapnum >= 1 && mapnum <= 99)
levelinfo->levelnum = mapnum;
}
else if (levelinfo->mapname[0] == 'E' &&
levelinfo->mapname[1] >= '0' && levelinfo->mapname[1] <= '9' &&
levelinfo->mapname[2] == 'M' &&
levelinfo->mapname[3] >= '0' && levelinfo->mapname[3] <= '9')
{
int epinum = levelinfo->mapname[1] - '1';
int mapnum = levelinfo->mapname[3] - '0';
levelinfo->levelnum = epinum*10 + mapnum;
}
ParseMapInfoLower (sc, MapHandlers, MapInfoMapLevel, levelinfo, NULL, levelflags);
// 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);
}
if (levelinfo->f1 != NULL)
{
levelinfo->f1 = copystring (levelinfo->f1);
}
SetLevelNum (levelinfo, levelinfo->levelnum); // Wipe out matching levelnums from other maps.
if (levelinfo->pname[0] != 0)
{
if (TexMan.AddPatch(levelinfo->pname) < 0)
{
levelinfo->pname[0] = 0;
}
}
break;
case MITL_CLUSTERDEF: // clusterdef <clusternum>
sc.MustGetNumber ();
clusterindex = FindWadClusterInfo (sc.Number);
if (clusterindex == -1)
{
clusterindex = wadclusterinfos.Reserve(1);
clusterinfo = &wadclusterinfos[clusterindex];
}
else
{
clusterinfo = &wadclusterinfos[clusterindex];
if (clusterinfo->entertext != NULL)
{
delete[] clusterinfo->entertext;
}
if (clusterinfo->exittext != NULL)
{
delete[] clusterinfo->exittext;
}
if (clusterinfo->messagemusic != NULL)
{
delete[] clusterinfo->messagemusic;
}
if (clusterinfo->clustername != NULL)
{
delete[] clusterinfo->clustername;
}
}
memset (clusterinfo, 0, sizeof(cluster_info_t));
clusterinfo->cluster = sc.Number;
ParseMapInfoLower (sc, ClusterHandlers, MapInfoClusterLevel, NULL, clusterinfo, 0);
break;
case MITL_EPISODE:
ParseEpisodeInfo(sc);
break;
case MITL_CLEAREPISODES:
ClearEpisodes();
break;
case MITL_SKILL:
ParseSkill(sc);
break;
case MITL_CLEARSKILLS:
AllSkills.Clear();
break;
}
}
if (defaultinfo.music != NULL)
{
delete [] defaultinfo.music;
}
if (defaultinfo.intermusic != NULL)
{
delete [] defaultinfo.intermusic;
}
}
static void ClearLevelInfoStrings(level_info_t *linfo)
{
if (linfo->music != NULL)
{
delete[] linfo->music;
linfo->music = NULL;
}
if (linfo->intermusic != NULL)
{
delete[] linfo->intermusic;
linfo->intermusic = NULL;
}
if (linfo->level_name != NULL)
{
delete[] linfo->level_name;
linfo->level_name = NULL;
}
for (FSpecialAction *spac = linfo->specialactions; spac != NULL; )
{
FSpecialAction *next = spac->Next;
delete spac;
spac = next;
}
}
static void ClearClusterInfoStrings(cluster_info_t *cinfo)
{
if (cinfo->exittext != NULL)
{
delete[] cinfo->exittext;
cinfo->exittext = NULL;
}
if (cinfo->entertext != NULL)
{
delete[] cinfo->entertext;
cinfo->entertext = NULL;
}
if (cinfo->messagemusic != NULL)
{
delete[] cinfo->messagemusic;
cinfo->messagemusic = NULL;
}
if (cinfo->clustername != NULL)
{
delete[] cinfo->clustername;
cinfo->clustername = NULL;
}
}
static void ClearEpisodes()
{
for (int i = 0; i < EpiDef.numitems; ++i)
{
delete[] const_cast<char *>(EpisodeMenu[i].name);
EpisodeMenu[i].name = NULL;
}
EpiDef.numitems = 0;
}
static void ParseMapInfoLower (FScanner &sc,
MapInfoHandler *handlers,
const char *strings[],
level_info_t *levelinfo,
cluster_info_t *clusterinfo,
QWORD flags)
{
int entry;
MapInfoHandler *handler;
BYTE *info;
info = levelinfo ? (BYTE *)levelinfo : (BYTE *)clusterinfo;
while (sc.GetString ())
{
if (sc.MatchString (MapInfoTopLevel) != -1)
{
sc.UnGet ();
break;
}
entry = sc.MustMatchString (strings);
handler = handlers + entry;
switch (handler->type)
{
case MITYPE_EATNEXT:
sc.MustGetString ();
break;
case MITYPE_IGNORE:
break;
case MITYPE_INT:
sc.MustGetNumber ();
*((int *)(info + handler->data1)) = sc.Number;
break;
case MITYPE_FLOAT:
sc.MustGetFloat ();
*((float *)(info + handler->data1)) = sc.Float;
break;
case MITYPE_HEX:
sc.MustGetString ();
*((int *)(info + handler->data1)) = strtoul (sc.String, NULL, 16);
break;
case MITYPE_COLOR:
sc.MustGetString ();
*((DWORD *)(info + handler->data1)) = V_GetColor (NULL, sc.String);
break;
case MITYPE_REDIRECT:
sc.MustGetString ();
levelinfo->RedirectType = sc.String;
/*
if (levelinfo->RedirectType == NULL ||
!(levelinfo->RedirectType->IsDescendantOf (RUNTIME_CLASS(AInventory))))
{
SC_ScriptError ("%s is not an inventory item", sc.String);
}
*/
// Intentional fall-through
case MITYPE_MAPNAME: {
EndSequence newSeq;
bool useseq = false;
sc.MustGetString ();
if (IsNum (sc.String))
{
int map = atoi (sc.String);
if (HexenHack)
{
sprintf (sc.String, "&wt@%02d", map);
}
else
{
sprintf (sc.String, "MAP%02d", map);
}
}
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;
default: type = END_Pic3; break;
}
newSeq.EndType = type;
useseq = true;
}
else if (sc.Compare ("endpic"))
{
sc.MustGetString ();
newSeq.EndType = END_Pic;
strncpy (newSeq.PicName, sc.String, 8);
newSeq.PicName[8] = 0;
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
{
strncpy ((char *)(info + handler->data1), sc.String, 8);
}
if (useseq)
{
int seqnum = FindEndSequence (newSeq.EndType, newSeq.PicName);
if (seqnum == -1)
{
seqnum = (int)EndSequences.Push (newSeq);
}
strcpy ((char *)(info + handler->data1), "enDSeQ");
*((WORD *)(info + handler->data1 + 6)) = (WORD)seqnum;
}
break;
}
case MITYPE_LUMPNAME:
sc.MustGetString ();
uppercopy ((char *)(info + handler->data1), sc.String);
flags |= handler->data2;
break;
case MITYPE_SKY:
sc.MustGetString (); // get texture name;
uppercopy ((char *)(info + handler->data1), sc.String);
sc.MustGetFloat (); // get scroll speed
if (HexenHack)
{
sc.Float /= 256;
}
// Sky scroll speed is specified as pixels per tic, but we
// want pixels per millisecond.
*((float *)(info + handler->data2)) = sc.Float * 35 / 1000;
break;
case MITYPE_SETFLAG:
flags |= handler->data1;
flags |= handler->data2;
break;
case MITYPE_CLRFLAG:
flags &= ~handler->data1;
flags |= handler->data2;
break;
case MITYPE_SCFLAGS:
flags = (flags & handler->data2) | handler->data1;
break;
case MITYPE_CLUSTER:
sc.MustGetNumber ();
*((int *)(info + handler->data1)) = 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 (sc.Number) == -1)
{
unsigned int clusterindex = wadclusterinfos.Reserve(1);
clusterinfo = &wadclusterinfos[clusterindex];
memset (clusterinfo, 0, sizeof(cluster_info_t));
clusterinfo->cluster = sc.Number;
if (gameinfo.gametype == GAME_Hexen)
{
clusterinfo->flags |= CLUSTER_HUB;
}
}
break;
case MITYPE_STRING:
sc.MustGetString ();
if (sc.Compare ("lookup"))
{
flags |= handler->data2;
sc.MustGetString ();
}
ReplaceString ((char **)(info + handler->data1), sc.String);
break;
case MITYPE_F1:
sc.MustGetString ();
{
char *colon = strchr (sc.String, ':');
if (colon)
{
*colon = 0;
}
ReplaceString ((char **)(info + handler->data1), sc.String);
}
break;
case MITYPE_MUSIC:
sc.MustGetString ();
{
char *colon = strchr (sc.String, ':');
if (colon)
{
*colon = 0;
}
ReplaceString ((char **)(info + handler->data1), sc.String);
*((int *)(info + handler->data2)) = colon ? atoi (colon + 1) : 0;
if (levelinfo != NULL)
{
// Flag the level so that the $MAP command doesn't override this.
flags|=LEVEL_MUSICDEFINED;
}
}
break;
case MITYPE_RELLIGHT:
sc.MustGetNumber ();
*((SBYTE *)(info + handler->data1)) = (SBYTE)clamp (sc.Number / 2, -128, 127);
break;
case MITYPE_CLRBYTES:
*((BYTE *)(info + handler->data1)) = 0;
*((BYTE *)(info + handler->data2)) = 0;
break;
case MITYPE_SPECIALACTION:
{
FSpecialAction **so = (FSpecialAction**)(info + handler->data1);
FSpecialAction *sa = new FSpecialAction;
int min_arg, max_arg;
sa->Next = *so;
*so = sa;
sc.SetCMode(true);
sc.MustGetString();
sa->Type = FName(sc.String);
sc.CheckString(",");
sc.MustGetString();
sa->Action = P_FindLineSpecial(sc.String, &min_arg, &max_arg);
if (sa->Action == 0 || min_arg < 0)
{
sc.ScriptError("Unknown specialaction '%s'");
}
int j = 0;
while (j < 5 && sc.CheckString(","))
{
sc.MustGetNumber();
sa->Args[j++] = sc.Number;
}
/*
if (j<min || j>max)
{
// Should be an error but can't for compatibility.
}
*/
sc.SetCMode(false);
}
break;
case MITYPE_COMPATFLAG:
if (!sc.CheckNumber()) sc.Number = 1;
if (levelinfo != NULL)
{
if (sc.Number) levelinfo->compatflags |= (DWORD)handler->data1;
else levelinfo->compatflags &= ~ (DWORD)handler->data1;
levelinfo->compatmask |= (DWORD)handler->data1;
}
break;
}
}
if (levelinfo)
{
levelinfo->flags = flags;
}
else
{
clusterinfo->flags = flags;
}
}
// 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
static void ParseEpisodeInfo (FScanner &sc)
{
int i;
char map[9];
char *pic = NULL;
bool picisgfx = false; // Shut up, GCC!!!!
bool remove = false;
char key = 0;
bool noskill = false;
// Get map name
sc.MustGetString ();
uppercopy (map, sc.String);
map[8] = 0;
sc.MustGetString ();
if (sc.Compare ("teaser"))
{
sc.MustGetString ();
if (gameinfo.flags & GI_SHAREWARE)
{
uppercopy (map, sc.String);
}
sc.MustGetString ();
}
do
{
if (sc.Compare ("name"))
{
sc.MustGetString ();
ReplaceString (&pic, sc.String);
picisgfx = false;
}
else if (sc.Compare ("picname"))
{
sc.MustGetString ();
ReplaceString (&pic, sc.String);
picisgfx = true;
}
else if (sc.Compare ("remove"))
{
remove = true;
}
else if (sc.Compare ("key"))
{
sc.MustGetString ();
key = sc.String[0];
}
else if (sc.Compare("noskillmenu"))
{
noskill = true;
}
else
{
sc.UnGet ();
break;
}
}
while (sc.GetString ());
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);
}
}
static int FindEndSequence (int type, const char *picname)
{
unsigned int i, num;
num = EndSequences.Size ();
for (i = 0; i < num; i++)
{
if (EndSequences[i].EndType == type &&
(type != END_Pic || stricmp (EndSequences[i].PicName, picname) == 0))
{
return (int)i;
}
}
return -1;
}
static void SetEndSequence (char *nextmap, int type)
{
int seqnum;
seqnum = FindEndSequence (type, NULL);
if (seqnum == -1)
{
EndSequence newseq;
newseq.EndType = type;
memset (newseq.PicName, 0, sizeof(newseq.PicName));
seqnum = (int)EndSequences.Push (newseq);
}
strcpy (nextmap, "enDSeQ");
*((WORD *)(nextmap + 6)) = (WORD)seqnum;
}
void G_SetForEndGame (char *nextmap)
{
if (!strncmp(nextmap, "enDSeQ",6)) return; // If there is already an end sequence please leave it alone!!!
if (gameinfo.gametype == GAME_Strife)
{
SetEndSequence (nextmap, gameinfo.flags & GI_SHAREWARE ? END_BuyStrife : END_Strife);
}
else if (gameinfo.gametype == GAME_Hexen)
{
SetEndSequence (nextmap, END_Chess);
}
else if (gamemode == commercial)
{
SetEndSequence (nextmap, END_Cast);
}
else
{ // The ExMx games actually have different ends based on the episode,
// but I want to keep this simple.
SetEndSequence (nextmap, END_Pic1);
}
}
void G_UnloadMapInfo ()
{
unsigned int i;
G_ClearSnapshots ();
for (i = 0; i < wadlevelinfos.Size(); ++i)
{
ClearLevelInfoStrings (&wadlevelinfos[i]);
}
wadlevelinfos.Clear();
for (i = 0; i < wadclusterinfos.Size(); ++i)
{
ClearClusterInfoStrings (&wadclusterinfos[i]);
}
wadclusterinfos.Clear();
ClearEpisodes();
}
level_info_t *FindLevelByWarpTrans (int num)
{
for (unsigned i = wadlevelinfos.Size(); i-- != 0; )
if (wadlevelinfos[i].WarpTrans == num)
return &wadlevelinfos[i];
return NULL;
}
static void zapDefereds (acsdefered_t *def)
{
while (def)
{
acsdefered_t *next = def->next;
delete def;
def = next;
}
}
void P_RemoveDefereds (void)
{
// Remove any existing defereds
for (unsigned int i = 0; i < wadlevelinfos.Size(); i++)
{
if (wadlevelinfos[i].defered)
{
zapDefereds (wadlevelinfos[i].defered);
wadlevelinfos[i].defered = NULL;
}
}
}
bool CheckWarpTransMap (char mapname[9], bool substitute)
{
if (mapname[0] == '&' && mapname[1] == 'w' &&
mapname[2] == 't' && mapname[3] == '@')
{
level_info_t *lev = FindLevelByWarpTrans (atoi (mapname + 4));
if (lev != NULL)
{
strncpy (mapname, lev->mapname, 8);
mapname[8] = 0;
return true;
}
else if (substitute)
{
mapname[0] = 'M';
mapname[1] = 'A';
mapname[2] = 'P';
mapname[3] = mapname[4];
mapname[4] = mapname[5];
mapname[5] = 0;
}
}
return false;
}
//
// G_InitNew
// Can be called by the startup code or the menu task,
// consoleplayer, playeringame[] should be set.
//
static char d_mapname[256];
static int d_skill=-1;
void G_DeferedInitNew (const char *mapname, int newskill)
{
strncpy (d_mapname, mapname, 8);
d_skill = newskill;
CheckWarpTransMap (d_mapname, true);
gameaction = ga_newgame2;
}
CCMD (map)
{
if (netgame)
{
Printf ("Use "TEXTCOLOR_BOLD"changemap"TEXTCOLOR_NORMAL" instead. "TEXTCOLOR_BOLD"Map"
TEXTCOLOR_NORMAL" is for single-player only.\n");
return;
}
if (argv.argc() > 1)
{
MapData * map = P_OpenMapData(argv[1]);
if (map == NULL)
Printf ("No map %s\n", argv[1]);
else
{
delete map;
G_DeferedInitNew (argv[1]);
}
}
else
{
Printf ("Usage: map <map name>\n");
}
}
CCMD (open)
{
if (netgame)
{
Printf ("You cannot use open in multiplayer games.\n");
return;
}
if (argv.argc() > 1)
{
sprintf(d_mapname, "file:%s", argv[1]);
MapData * map = P_OpenMapData(d_mapname);
if (map == NULL)
Printf ("No map %s\n", d_mapname);
else
{
delete map;
gameaction = ga_newgame2;
d_skill = -1;
}
}
else
{
Printf ("Usage: open <map file>\n");
}
}
void G_NewInit ()
{
int i;
G_ClearSnapshots ();
SB_state = screen->GetPageCount ();
netgame = false;
netdemo = false;
multiplayer = false;
if (demoplayback)
{
C_RestoreCVars ();
demoplayback = false;
D_SetupUserInfo ();
}
for (i = 0; i < MAXPLAYERS; ++i)
{
player_t *p = &players[i];
userinfo_t saved_ui = players[i].userinfo;
int chasecam = p->cheats & CF_CHASECAM;
p->~player_t();
::new(p) player_t;
players[i].cheats |= chasecam;
players[i].playerstate = PST_DEAD;
playeringame[i] = 0;
players[i].userinfo = saved_ui;
}
BackupSaveName = "";
consoleplayer = 0;
NextSkill = -1;
}
void G_DoNewGame (void)
{
G_NewInit ();
playeringame[consoleplayer] = 1;
if (d_skill != -1) gameskill = d_skill;
G_InitNew (d_mapname, false);
gameaction = ga_nothing;
}
void G_InitNew (const char *mapname, bool bTitleLevel)
{
EGameSpeed oldSpeed;
bool wantFast;
int i;
if (!savegamerestore)
{
G_ClearSnapshots ();
P_RemoveDefereds ();
// [RH] Mark all levels as not visited
for (unsigned int i = 0; i < wadlevelinfos.Size(); i++)
wadlevelinfos[i].flags = wadlevelinfos[i].flags & ~LEVEL_VISITED;
}
UnlatchCVars ();
G_VerifySkill();
UnlatchCVars ();
if (paused)
{
paused = 0;
S_ResumeSound ();
}
if (StatusBar != NULL)
{
StatusBar->Destroy();
StatusBar = NULL;
}
if (bTitleLevel)
{
StatusBar = new DBaseStatusBar (0);
}
else if (SBarInfoScript != NULL)
{
int cstype = SBarInfoScript->GetGameType();
if(cstype == GAME_Doom) //Did the user specify a "base"
{
StatusBar = CreateDoomStatusBar ();
}
else if(cstype == GAME_Heretic)
{
StatusBar = CreateHereticStatusBar();
}
else if(cstype == GAME_Hexen)
{
StatusBar = CreateHexenStatusBar();
}
else if(cstype == GAME_Strife)
{
StatusBar = CreateStrifeStatusBar();
}
else //Use the default, empty or custom.
{
StatusBar = CreateCustomStatusBar();
}
}
if (StatusBar == NULL)
{
if (gameinfo.gametype == GAME_Doom)
{
StatusBar = CreateDoomStatusBar ();
}
else if (gameinfo.gametype == GAME_Heretic)
{
StatusBar = CreateHereticStatusBar ();
}
else if (gameinfo.gametype == GAME_Hexen)
{
StatusBar = CreateHexenStatusBar ();
}
else if (gameinfo.gametype == GAME_Strife)
{
StatusBar = CreateStrifeStatusBar ();
}
else
{
StatusBar = new DBaseStatusBar (0);
}
}
GC::WriteBarrier(StatusBar);
StatusBar->AttachToPlayer (&players[consoleplayer]);
StatusBar->NewGame ();
setsizeneeded = true;
if (gameinfo.gametype == GAME_Strife || (SBarInfoScript != NULL && SBarInfoScript->GetGameType() == GAME_Strife))
{
// Set the initial quest log text for Strife.
for (i = 0; i < MAXPLAYERS; ++i)
{
players[i].SetLogText ("Find help");
}
}
// [RH] If this map doesn't exist, bomb out
MapData * map = P_OpenMapData(mapname);
if (!map)
{
I_Error ("Could not find map %s\n", mapname);
}
delete map;
oldSpeed = GameSpeed;
wantFast = !!G_SkillProperty(SKILLP_FastMonsters);
GameSpeed = wantFast ? SPEED_Fast : SPEED_Normal;
if (oldSpeed != GameSpeed)
{
FActorInfo::StaticSpeedSet ();
}
if (!savegamerestore)
{
if (!netgame)
{ // [RH] Change the random seed for each new single player game
rngseed = rngseed*3/2;
}
FRandom::StaticClearRandom ();
memset (ACS_WorldVars, 0, sizeof(ACS_WorldVars));
memset (ACS_GlobalVars, 0, sizeof(ACS_GlobalVars));
for (i = 0; i < NUM_WORLDVARS; ++i)
{
ACS_WorldArrays[i].Clear ();
}
for (i = 0; i < NUM_GLOBALVARS; ++i)
{
ACS_GlobalArrays[i].Clear ();
}
level.time = 0;
level.maptime = 0;
level.totaltime = 0;
if (!multiplayer || !deathmatch)
{
InitPlayerClasses ();
}
// force players to be initialized upon first level load
for (i = 0; i < MAXPLAYERS; i++)
players[i].playerstate = PST_ENTER; // [BC]
}
usergame = !bTitleLevel; // will be set false if a demo
paused = 0;
demoplayback = false;
automapactive = false;
viewactive = true;
BorderNeedRefresh = screen->GetPageCount ();
//Added by MC: Initialize bots.
if (!deathmatch)
{
bglobal.Init ();
}
if (mapname != level.mapname)
{
strcpy (level.mapname, mapname);
}
if (bTitleLevel)
{
gamestate = GS_TITLELEVEL;
}
else if (gamestate != GS_STARTUP)
{
gamestate = GS_LEVEL;
}
G_DoLoadLevel (0, false);
}
//
// G_DoCompleted
//
static char nextlevel[9];
static int startpos; // [RH] Support for multiple starts per level
extern int NoWipe; // [RH] Don't wipe when travelling in hubs
static bool startkeepfacing; // [RH] Support for keeping your facing angle
static bool resetinventory; // Reset the inventory to the player's default for the next level
static bool unloading;
static bool g_nomonsters;
// [RH] The position parameter to these next three functions should
// match the first parameter of the single player start spots
// that should appear in the next map.
void G_ChangeLevel(const char * levelname, int position, bool keepFacing, int nextSkill,
bool nointermission, bool resetinv, bool nomonsters)
{
if (unloading)
{
Printf (TEXTCOLOR_RED "Unloading scripts cannot exit the level again.\n");
return;
}
strncpy (nextlevel, levelname, 8);
nextlevel[8] = 0;
if (strncmp(nextlevel, "enDSeQ", 6))
{
level_info_t *nextinfo = CheckLevelRedirect (FindLevelInfo (nextlevel));
if (nextinfo)
{
strncpy(nextlevel, nextinfo->mapname, 8);
}
}
if (nextSkill != -1) NextSkill = nextSkill;
g_nomonsters = nomonsters;
if (nointermission) level.flags |= LEVEL_NOINTERMISSION;
cluster_info_t *thiscluster = FindClusterInfo (level.cluster);
cluster_info_t *nextcluster = FindClusterInfo (FindLevelInfo (nextlevel)->cluster);
startpos = position;
startkeepfacing = keepFacing;
gameaction = ga_completed;
resetinventory = resetinv;
bglobal.End(); //Added by MC:
// [RH] Give scripts a chance to do something
unloading = true;
FBehavior::StaticStartTypedScripts (SCRIPT_Unloading, NULL, false, 0, true);
unloading = false;
if (thiscluster && (thiscluster->flags & CLUSTER_HUB))
{
if ((level.flags & LEVEL_NOINTERMISSION) || (nextcluster == thiscluster))
NoWipe = 35;
D_DrawIcon = "TELEICON";
}
for(int i = 0; i < MAXPLAYERS; i++)
{
if (playeringame[i])
{
player_t *player = &players[i];
// Un-crouch all players here.
player->Uncrouch();
// If this is co-op, respawn any dead players now so they can
// keep their inventory on the next map.
if (multiplayer && !deathmatch && player->playerstate == PST_DEAD)
{
// Copied from the end of P_DeathThink [[
player->cls = NULL; // Force a new class if the player is using a random class
player->playerstate = PST_REBORN;
if (player->mo->special1 > 2)
{
player->mo->special1 = 0;
}
// ]]
G_DoReborn(i, false);
}
}
}
}
const char *G_GetExitMap()
{
return level.nextmap;
}
const char *G_GetSecretExitMap()
{
const char *nextmap = level.nextmap;
if (level.secretmap[0] != 0)
{
MapData *map = P_OpenMapData(level.secretmap);
if (map != NULL)
{
delete map;
nextmap = level.secretmap;
}
}
return nextmap;
}
void G_ExitLevel (int position, bool keepFacing)
{
G_ChangeLevel(G_GetExitMap(), position, keepFacing);
}
void G_SecretExitLevel (int position)
{
G_ChangeLevel(G_GetSecretExitMap(), position, false);
}
void G_DoCompleted (void)
{
int i;
gameaction = ga_nothing;
if (gamestate == GS_TITLELEVEL)
{
strncpy (level.mapname, nextlevel, 8);
G_DoLoadLevel (startpos, false);
startpos = 0;
viewactive = true;
return;
}
// [RH] Mark this level as having been visited
if (!(level.flags & LEVEL_CHANGEMAPCHEAT))
FindLevelInfo (level.mapname)->flags |= LEVEL_VISITED;
if (automapactive)
AM_Stop ();
wminfo.finished_ep = level.cluster - 1;
strncpy (wminfo.lname0, level.info->pname, 8);
strncpy (wminfo.current, level.mapname, 8);
if (deathmatch &&
(dmflags & DF_SAME_LEVEL) &&
!(level.flags & LEVEL_CHANGEMAPCHEAT))
{
strncpy (wminfo.next, level.mapname, 8);
strncpy (wminfo.lname1, level.info->pname, 8);
}
else
{
if (strncmp (nextlevel, "enDSeQ", 6) == 0)
{
strncpy (wminfo.next, nextlevel, 8);
wminfo.lname1[0] = 0;
}
else
{
level_info_t *nextinfo = FindLevelInfo (nextlevel);
strncpy (wminfo.next, nextinfo->mapname, 8);
strncpy (wminfo.lname1, nextinfo->pname, 8);
}
}
CheckWarpTransMap (wminfo.next, true);
wminfo.next_ep = FindLevelInfo (nextlevel)->cluster - 1;
wminfo.maxkills = level.total_monsters;
wminfo.maxitems = level.total_items;
wminfo.maxsecret = level.total_secrets;
wminfo.maxfrags = 0;
wminfo.partime = TICRATE * level.partime;
wminfo.sucktime = level.sucktime;
wminfo.pnum = consoleplayer;
wminfo.totaltime = level.totaltime;
for (i=0 ; i<MAXPLAYERS ; i++)
{
wminfo.plyr[i].in = playeringame[i];
wminfo.plyr[i].skills = players[i].killcount;
wminfo.plyr[i].sitems = players[i].itemcount;
wminfo.plyr[i].ssecret = players[i].secretcount;
wminfo.plyr[i].stime = level.time;
memcpy (wminfo.plyr[i].frags, players[i].frags
, sizeof(wminfo.plyr[i].frags));
wminfo.plyr[i].fragcount = players[i].fragcount;
}
// [RH] If we're in a hub and staying within that hub, take a snapshot
// of the level. If we're traveling to a new hub, take stuff from
// the player and clear the world vars. If this is just an
// ordinary cluster (not a hub), take stuff from the player, but
// leave the world vars alone.
cluster_info_t *thiscluster = FindClusterInfo (level.cluster);
cluster_info_t *nextcluster = FindClusterInfo (wminfo.next_ep+1); // next_ep is cluster-1
EFinishLevelType mode;
if (thiscluster != nextcluster || deathmatch ||
!(thiscluster->flags & CLUSTER_HUB))
{
if (nextcluster->flags & CLUSTER_HUB)
{
mode = FINISH_NextHub;
}
else
{
mode = FINISH_NoHub;
}
}
else
{
mode = FINISH_SameHub;
}
// Intermission stats for entire hubs
G_LeavingHub(mode, thiscluster, &wminfo);
for (i = 0; i < MAXPLAYERS; i++)
{
if (playeringame[i])
{ // take away appropriate inventory
G_PlayerFinishLevel (i, mode, resetinventory);
}
}
if (mode == FINISH_SameHub)
{ // Remember the level's state for re-entry.
G_SnapshotLevel ();
}
else
{ // Forget the states of all existing levels.
G_ClearSnapshots ();
if (mode == FINISH_NextHub)
{ // Reset world variables for the new hub.
memset (ACS_WorldVars, 0, sizeof(ACS_WorldVars));
for (i = 0; i < NUM_WORLDVARS; ++i)
{
ACS_WorldArrays[i].Clear ();
}
}
// With hub statistics the time should be per hub.
// Additionally there is a global time counter now so nothing is missed by changing it
//else if (mode == FINISH_NoHub)
{ // Reset time to zero if not entering/staying in a hub.
level.time = 0;
}
level.maptime = 0;
}
if (!deathmatch &&
((level.flags & LEVEL_NOINTERMISSION) ||
((nextcluster == thiscluster) && (thiscluster->flags & CLUSTER_HUB))))
{
G_WorldDone ();
return;
}
gamestate = GS_INTERMISSION;
viewactive = false;
automapactive = false;
// [RH] If you ever get a statistics driver operational, adapt this.
// if (statcopy)
// memcpy (statcopy, &wminfo, sizeof(wminfo));
WI_Start (&wminfo);
}
class DAutosaver : public DThinker
{
DECLARE_CLASS (DAutosaver, DThinker)
public:
void Tick ();
};
IMPLEMENT_CLASS (DAutosaver)
void DAutosaver::Tick ()
{
Net_WriteByte (DEM_CHECKAUTOSAVE);
Destroy ();
}
//
// G_DoLoadLevel
//
extern gamestate_t wipegamestate;
void G_DoLoadLevel (int position, bool autosave)
{
static int lastposition = 0;
gamestate_t oldgs = gamestate;
int i;
if (NextSkill >= 0)
{
UCVarValue val;
val.Int = NextSkill;
gameskill.ForceSet (val, CVAR_Int);
NextSkill = -1;
}
if (position == -1)
position = lastposition;
else
lastposition = position;
G_InitLevelLocals ();
StatusBar->DetachAllMessages ();
// Force 'teamplay' to 'true' if need be.
if (level.flags & LEVEL_FORCETEAMPLAYON)
teamplay = true;
// Force 'teamplay' to 'false' if need be.
if (level.flags & LEVEL_FORCETEAMPLAYOFF)
teamplay = false;
Printf (
"\n\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36"
"\36\36\36\36\36\36\36\36\36\36\36\36\37\n\n"
TEXTCOLOR_BOLD "%s - %s\n\n",
level.mapname, level.level_name);
if (wipegamestate == GS_LEVEL)
wipegamestate = GS_FORCEWIPE;
if (gamestate != GS_TITLELEVEL)
{
gamestate = GS_LEVEL;
}
// Set the sky map.
// First thing, we have a dummy sky texture name,
// a flat. The data is in the WAD only because
// we look for an actual index, instead of simply
// setting one.
skyflatnum = TexMan.GetTexture (gameinfo.SkyFlatName, FTexture::TEX_Flat, FTextureManager::TEXMAN_Overridable);
// DOOM determines the sky texture to be used
// depending on the current episode and the game version.
// [RH] Fetch sky parameters from level_locals_t.
sky1texture = TexMan.GetTexture (level.skypic1, FTexture::TEX_Wall, FTextureManager::TEXMAN_Overridable);
sky2texture = TexMan.GetTexture (level.skypic2, FTexture::TEX_Wall, FTextureManager::TEXMAN_Overridable);
// [RH] Set up details about sky rendering
R_InitSkyMap ();
for (i = 0; i < MAXPLAYERS; i++)
{
if (playeringame[i] && (deathmatch || players[i].playerstate == PST_DEAD))
players[i].playerstate = PST_ENTER; // [BC]
memset (players[i].frags,0,sizeof(players[i].frags));
if (!(dmflags2 & DF2_YES_KEEPFRAGS) && (alwaysapplydmflags || deathmatch))
players[i].fragcount = 0;
}
if (g_nomonsters)
{
level.flags |= LEVEL_NOMONSTERS;
}
else
{
level.flags &= ~LEVEL_NOMONSTERS;
}
P_SetupLevel (level.mapname, position);
AM_LevelInit();
// [RH] Start lightning, if MAPINFO tells us to
if (level.flags & LEVEL_STARTLIGHTNING)
{
P_StartLightning ();
}
gameaction = ga_nothing;
// clear cmd building stuff
ResetButtonStates ();
SendItemUse = NULL;
SendItemDrop = NULL;
mousex = mousey = 0;
sendpause = sendsave = sendturn180 = SendLand = false;
LocalViewAngle = 0;
LocalViewPitch = 0;
paused = 0;
//Added by MC: Initialize bots.
if (deathmatch)
{
bglobal.Init ();
}
if (timingdemo)
{
static bool firstTime = true;
if (firstTime)
{
starttime = I_GetTime (false);
firstTime = false;
}
}
level.starttime = gametic;
level.maptime = 0;
G_UnSnapshotLevel (!savegamerestore); // [RH] Restore the state of the level.
G_FinishTravel ();
if (players[consoleplayer].camera == NULL ||
players[consoleplayer].camera->player != NULL)
{ // If we are viewing through a player, make sure it is us.
players[consoleplayer].camera = players[consoleplayer].mo;
}
StatusBar->AttachToPlayer (&players[consoleplayer]);
P_DoDeferedScripts (); // [RH] Do script actions that were triggered on another map.
if (demoplayback || oldgs == GS_STARTUP || oldgs == GS_TITLELEVEL)
C_HideConsole ();
C_FlushDisplay ();
// [RH] Always save the game when entering a new level.
if (autosave && !savegamerestore && disableautosave < 1)
{
DAutosaver GCCNOWARN *dummy = new DAutosaver;
}
}
//
// G_WorldDone
//
void G_WorldDone (void)
{
cluster_info_t *nextcluster;
cluster_info_t *thiscluster;
gameaction = ga_worlddone;
if (level.flags & LEVEL_CHANGEMAPCHEAT)
return;
thiscluster = FindClusterInfo (level.cluster);
if (strncmp (nextlevel, "enDSeQ", 6) == 0)
{
F_StartFinale (thiscluster->messagemusic, thiscluster->musicorder,
thiscluster->cdtrack, thiscluster->cdid,
thiscluster->finaleflat, thiscluster->exittext,
thiscluster->flags & CLUSTER_EXITTEXTINLUMP,
thiscluster->flags & CLUSTER_FINALEPIC,
thiscluster->flags & CLUSTER_LOOKUPEXITTEXT,
true);
}
else
{
nextcluster = FindClusterInfo (FindLevelInfo (nextlevel)->cluster);
if (nextcluster->cluster != level.cluster && !deathmatch)
{
// Only start the finale if the next level's cluster is different
// than the current one and we're not in deathmatch.
if (nextcluster->entertext)
{
F_StartFinale (nextcluster->messagemusic, nextcluster->musicorder,
nextcluster->cdtrack, nextcluster->cdid,
nextcluster->finaleflat, nextcluster->entertext,
nextcluster->flags & CLUSTER_ENTERTEXTINLUMP,
nextcluster->flags & CLUSTER_FINALEPIC,
nextcluster->flags & CLUSTER_LOOKUPENTERTEXT,
false);
}
else if (thiscluster->exittext)
{
F_StartFinale (thiscluster->messagemusic, thiscluster->musicorder,
thiscluster->cdtrack, nextcluster->cdid,
thiscluster->finaleflat, thiscluster->exittext,
thiscluster->flags & CLUSTER_EXITTEXTINLUMP,
thiscluster->flags & CLUSTER_FINALEPIC,
thiscluster->flags & CLUSTER_LOOKUPEXITTEXT,
false);
}
}
}
}
void G_DoWorldDone (void)
{
gamestate = GS_LEVEL;
if (wminfo.next[0] == 0)
{
// Don't crash if no next map is given. Just repeat the current one.
Printf ("No next map specified.\n");
}
else
{
strncpy (level.mapname, nextlevel, 8);
}
G_StartTravel ();
G_DoLoadLevel (startpos, true);
startpos = 0;
gameaction = ga_nothing;
viewactive = true;
}
//==========================================================================
//
// G_StartTravel
//
// Moves players (and eventually their inventory) to a different statnum,
// so they will not be destroyed when switching levels. This only applies
// to real players, not voodoo dolls.
//
//==========================================================================
void G_StartTravel ()
{
if (deathmatch)
return;
for (int i = 0; i < MAXPLAYERS; ++i)
{
if (playeringame[i])
{
AActor *pawn = players[i].mo;
AInventory *inv;
// Only living players travel. Dead ones get a new body on the new level.
if (players[i].health > 0)
{
pawn->UnlinkFromWorld ();
P_DelSector_List ();
int tid = pawn->tid; // Save TID
pawn->RemoveFromHash ();
pawn->tid = tid; // Restore TID (but no longer linked into the hash chain)
pawn->ChangeStatNum (STAT_TRAVELLING);
for (inv = pawn->Inventory; inv != NULL; inv = inv->Inventory)
{
inv->ChangeStatNum (STAT_TRAVELLING);
inv->UnlinkFromWorld ();
P_DelSector_List ();
}
}
}
}
}
//==========================================================================
//
// G_FinishTravel
//
// Moves any travelling players so that they occupy their newly-spawned
// copies' locations, destroying the new players in the process (because
// they are really fake placeholders to show where the travelling players
// should go).
//
//==========================================================================
void G_FinishTravel ()
{
TThinkerIterator<APlayerPawn> it (STAT_TRAVELLING);
APlayerPawn *pawn, *pawndup, *oldpawn, *next;
AInventory *inv;
next = it.Next ();
while ( (pawn = next) != NULL)
{
next = it.Next ();
pawn->ChangeStatNum (STAT_PLAYER);
pawndup = pawn->player->mo;
assert (pawn != pawndup);
if (pawndup == NULL)
{ // Oh no! there was no start for this player!
pawn->flags |= MF_NOSECTOR|MF_NOBLOCKMAP;
pawn->Destroy ();
}
else
{
oldpawn = pawndup;
// The player being spawned here is a short lived dummy and
// must not start any ENTER script or big problems will happen.
P_SpawnPlayer (&playerstarts[pawn->player - players], true);
pawndup = pawn->player->mo;
if (!startkeepfacing)
{
pawn->angle = pawndup->angle;
pawn->pitch = pawndup->pitch;
}
pawn->x = pawndup->x;
pawn->y = pawndup->y;
pawn->z = pawndup->z;
pawn->momx = pawndup->momx;
pawn->momy = pawndup->momy;
pawn->momz = pawndup->momz;
pawn->Sector = pawndup->Sector;
pawn->floorz = pawndup->floorz;
pawn->ceilingz = pawndup->ceilingz;
pawn->dropoffz = pawndup->dropoffz;
pawn->floorsector = pawndup->floorsector;
pawn->floorpic = pawndup->floorpic;
pawn->ceilingsector = pawndup->ceilingsector;
pawn->ceilingpic = pawndup->ceilingpic;
pawn->floorclip = pawndup->floorclip;
pawn->waterlevel = pawndup->waterlevel;
pawn->target = NULL;
pawn->lastenemy = NULL;
pawn->player->mo = pawn;
DObject::StaticPointerSubstitution (oldpawn, pawn);
oldpawn->Destroy();
pawndup->Destroy ();
pawn->LinkToWorld ();
pawn->AddToHash ();
pawn->SetState(pawn->SpawnState);
for (inv = pawn->Inventory; inv != NULL; inv = inv->Inventory)
{
inv->ChangeStatNum (STAT_INVENTORY);
inv->LinkToWorld ();
inv->Travelled ();
}
if (level.FromSnapshot)
{
FBehavior::StaticStartTypedScripts (SCRIPT_Return, pawn, true);
}
}
}
}
void G_InitLevelLocals ()
{
level_info_t *info;
BaseBlendA = 0.0f; // Remove underwater blend effect, if any
NormalLight.Maps = realcolormaps;
// [BB] Instead of just setting the color, we also have to reset Desaturate and build the lights.
NormalLight.ChangeColor (PalEntry (255, 255, 255), 0);
level.gravity = sv_gravity * 35/TICRATE;
level.aircontrol = (fixed_t)(sv_aircontrol * 65536.f);
level.teamdamage = teamdamage;
level.flags = 0;
info = FindLevelInfo (level.mapname);
level.info = info;
level.skyspeed1 = info->skyspeed1;
level.skyspeed2 = info->skyspeed2;
info = (level_info_t *)info;
strncpy (level.skypic2, info->skypic2, 8);
level.fadeto = info->fadeto;
level.cdtrack = info->cdtrack;
level.cdid = info->cdid;
level.FromSnapshot = false;
if (level.fadeto == 0)
{
R_SetDefaultColormap (info->fadetable);
if (strnicmp (info->fadetable, "COLORMAP", 8) != 0)
{
level.flags |= LEVEL_HASFADETABLE;
}
/*
}
else
{
NormalLight.ChangeFade (level.fadeto);
*/
}
level.airsupply = info->airsupply*TICRATE;
level.outsidefog = info->outsidefog;
level.WallVertLight = info->WallVertLight;
level.WallHorizLight = info->WallHorizLight;
if (info->gravity != 0.f)
{
level.gravity = info->gravity * 35/TICRATE;
}
if (info->aircontrol != 0.f)
{
level.aircontrol = (fixed_t)(info->aircontrol * 65536.f);
}
if (info->teamdamage != 0.f)
{
level.teamdamage = info->teamdamage;
}
G_AirControlChanged ();
if (info->level_name)
{
cluster_info_t *clus = FindClusterInfo (info->cluster);
level.partime = info->partime;
level.sucktime = info->sucktime;
level.cluster = info->cluster;
level.clusterflags = clus ? clus->flags : 0;
level.flags |= info->flags;
level.levelnum = info->levelnum;
level.music = info->music;
level.musicorder = info->musicorder;
level.f1 = info->f1; // [RC] And import the f1 name
strncpy (level.level_name, info->level_name, 63);
G_MaybeLookupLevelName (NULL);
strncpy (level.nextmap, info->nextmap, 8);
level.nextmap[8] = 0;
strncpy (level.secretmap, info->secretmap, 8);
level.secretmap[8] = 0;
strncpy (level.skypic1, info->skypic1, 8);
level.skypic1[8] = 0;
if (!level.skypic2[0])
strncpy (level.skypic2, level.skypic1, 8);
level.skypic2[8] = 0;
}
else
{
level.partime = level.cluster = 0;
level.sucktime = 0;
strcpy (level.level_name, "Unnamed");
level.nextmap[0] =
level.secretmap[0] = 0;
level.music = NULL;
strcpy (level.skypic1, "SKY1");
strcpy (level.skypic2, "SKY1");
level.flags = 0;
level.levelnum = 1;
}
compatflags.Callback();
NormalLight.ChangeFade (level.fadeto);
}
bool level_locals_s::IsJumpingAllowed() const
{
if (dmflags & DF_NO_JUMP)
return false;
if (dmflags & DF_YES_JUMP)
return true;
return !(level.flags & LEVEL_JUMP_NO);
}
bool level_locals_s::IsCrouchingAllowed() const
{
if (dmflags & DF_NO_CROUCH)
return false;
if (dmflags & DF_YES_CROUCH)
return true;
return !(level.flags & LEVEL_CROUCH_NO);
}
bool level_locals_s::IsFreelookAllowed() const
{
if (level.flags & LEVEL_FREELOOK_NO)
return false;
if (level.flags & LEVEL_FREELOOK_YES)
return true;
return !(dmflags & DF_NO_FREELOOK);
}
char *CalcMapName (int episode, int level)
{
static char lumpname[9];
if (gameinfo.flags & GI_MAPxx)
{
sprintf (lumpname, "MAP%02d", level);
}
else
{
lumpname[0] = 'E';
lumpname[1] = '0' + episode;
lumpname[2] = 'M';
lumpname[3] = '0' + level;
lumpname[4] = 0;
}
return lumpname;
}
level_info_t *FindLevelInfo (const char *mapname)
{
int i;
if ((i = FindWadLevelInfo (mapname)) > -1)
return &wadlevelinfos[i];
else
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;
}
level_info_t *CheckLevelRedirect (level_info_t *info)
{
if (info->RedirectType != NAME_None)
{
const PClass *type = PClass::FindClass(info->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.
MapData * map = P_OpenMapData(info->RedirectMap);
if (map != NULL)
{
delete map;
return FindLevelInfo(info->RedirectMap);
}
break;
}
}
}
}
return NULL;
}
static void SetLevelNum (level_info_t *info, int num)
{
// Avoid duplicate levelnums. The level being set always has precedence.
for (unsigned int i = 0; i < wadlevelinfos.Size(); ++i)
{
if (wadlevelinfos[i].levelnum == num)
wadlevelinfos[i].levelnum = 0;
}
info->levelnum = num;
}
cluster_info_t *FindClusterInfo (int cluster)
{
int i;
if ((i = FindWadClusterInfo (cluster)) > -1)
return &wadclusterinfos[i];
else
return &TheDefaultClusterInfo;
}
const char *G_MaybeLookupLevelName (level_info_t *ininfo)
{
level_info_t *info;
if (ininfo == NULL)
{
info = level.info;
}
else
{
info = ininfo;
}
if (info != NULL && info->flags & LEVEL_LOOKUPLEVELNAME)
{
const char *thename;
const char *lookedup;
lookedup = GStrings[info->level_name];
if (lookedup == NULL)
{
thename = info->level_name;
}
else
{
char checkstring[32];
// Strip out the header from the localized string
if (info->mapname[0] == 'E' && info->mapname[2] == 'M')
{
sprintf (checkstring, "%s: ", info->mapname);
}
else if (info->mapname[0] == 'M' && info->mapname[1] == 'A' && info->mapname[2] == 'P')
{
sprintf (checkstring, "%d: ", atoi(info->mapname + 3));
}
thename = strstr (lookedup, checkstring);
if (thename == NULL)
{
thename = lookedup;
}
else
{
thename += strlen (checkstring);
}
}
if (ininfo == NULL)
{
strncpy (level.level_name, thename, 63);
}
return thename;
}
return info != NULL ? info->level_name : NULL;
}
void G_MakeEpisodes ()
{
int i;
// Set the default episodes
if (EpiDef.numitems == 0)
{
static const char eps[5][8] =
{
"E1M1", "E2M1", "E3M1", "E4M1", "E5M1"
};
static const char depinames[4][7] =
{
"M_EPI1", "M_EPI2", "M_EPI3", "M_EPI4"
};
static const char depikeys[4] = { 'k', 't', 'i', 't' };
static const char *hepinames[5] =
{
"$MNU_COTD",
"$MNU_HELLSMAW",
"$MNU_DOME",
"$MNU_OSSUARY",
"$MNU_DEMESNE",
};
static const char hepikeys[5] = { 'c', 'h', 'd', 'o', 's' };
if (gameinfo.flags & GI_MAPxx)
{
if (gameinfo.gametype == GAME_Hexen)
{
// "&wt@01" is a magic name that will become whatever map has
// warptrans 1.
strcpy (EpisodeMaps[0], "&wt@01");
EpisodeMenu[0].name = copystring ("Hexen");
}
else
{
strcpy (EpisodeMaps[0], "MAP01");
EpisodeMenu[0].name = copystring ("Hell on Earth");
}
EpisodeMenu[0].alphaKey = 'h';
EpisodeMenu[0].fulltext = true;
EpiDef.numitems = 1;
}
else if (gameinfo.gametype == GAME_Doom)
{
memcpy (EpisodeMaps, eps, 4*8);
for (i = 0; i < 4; ++i)
{
EpisodeMenu[i].name = copystring (depinames[i]);
EpisodeMenu[i].fulltext = false;
EpisodeMenu[i].alphaKey = depikeys[i];
}
if (gameinfo.flags & GI_MENUHACK_RETAIL)
{
EpiDef.numitems = 4;
}
else
{
EpiDef.numitems = 3;
}
}
else
{
memcpy (EpisodeMaps, eps, 5*8);
for (i = 0; i < 5; ++i)
{
EpisodeMenu[i].name = copystring (hepinames[i]);
EpisodeMenu[i].fulltext = true;
EpisodeMenu[i].alphaKey = hepikeys[i];
}
if (gameinfo.flags & GI_MENUHACK_EXTENDED)
{
EpiDef.numitems = 5;
}
else
{
EpiDef.numitems = 3;
}
}
}
}
void G_AirControlChanged ()
{
if (level.aircontrol <= 256)
{
level.airfriction = FRACUNIT;
}
else
{
// Friction is inversely proportional to the amount of control
float fric = ((float)level.aircontrol/65536.f) * -0.0941f + 1.0004f;
level.airfriction = (fixed_t)(fric * 65536.f);
}
}
void G_SerializeLevel (FArchive &arc, bool hubLoad)
{
int i = level.totaltime;
arc << level.flags
<< level.fadeto
<< level.found_secrets
<< level.found_items
<< level.killed_monsters
<< level.gravity
<< level.aircontrol
<< level.teamdamage
<< level.maptime
<< i;
// Hub transitions must keep the current total time
if (!hubLoad)
level.totaltime=i;
if (arc.IsStoring ())
{
arc.WriteName (level.skypic1);
arc.WriteName (level.skypic2);
}
else
{
strncpy (level.skypic1, arc.ReadName(), 8);
strncpy (level.skypic2, arc.ReadName(), 8);
sky1texture = TexMan.GetTexture (level.skypic1, FTexture::TEX_Wall, FTextureManager::TEXMAN_Overridable);
sky2texture = TexMan.GetTexture (level.skypic2, FTexture::TEX_Wall, FTextureManager::TEXMAN_Overridable);
R_InitSkyMap ();
}
G_AirControlChanged ();
BYTE t;
// Does this level have scrollers?
if (arc.IsStoring ())
{
t = level.Scrolls ? 1 : 0;
arc << t;
}
else
{
arc << t;
if (level.Scrolls)
{
delete[] level.Scrolls;
level.Scrolls = NULL;
}
if (t)
{
level.Scrolls = new FSectorScrollValues[numsectors];
memset (level.Scrolls, 0, sizeof(level.Scrolls)*numsectors);
}
}
FBehavior::StaticSerializeModuleStates (arc);
P_SerializeThinkers (arc, hubLoad);
P_SerializeWorld (arc);
P_SerializePolyobjs (arc);
P_SerializeSounds (arc);
StatusBar->Serialize (arc);
SerializeInterpolations (arc);
arc << level.total_monsters << level.total_items << level.total_secrets;
// Does this level have custom translations?
FRemapTable *trans;
WORD w;
if (arc.IsStoring ())
{
for (unsigned int i = 0; i < translationtables[TRANSLATION_LevelScripted].Size(); ++i)
{
trans = translationtables[TRANSLATION_LevelScripted][i];
if (trans != NULL && !trans->IsIdentity())
{
w = WORD(i);
arc << w;
trans->Serialize(arc);
}
}
w = 0xffff;
arc << w;
}
else
{
while (arc << w, w != 0xffff)
{
trans = translationtables[TRANSLATION_LevelScripted].GetVal(w);
if (trans == NULL)
{
trans = new FRemapTable;
translationtables[TRANSLATION_LevelScripted].SetVal(w, trans);
}
trans->Serialize(arc);
}
}
// This must be saved, too, of course!
FCanvasTextureInfo::Serialize (arc);
AM_SerializeMarkers(arc);
if (!hubLoad)
{
P_SerializePlayers (arc);
}
}
// Archives the current level
void G_SnapshotLevel ()
{
if (level.info->snapshot)
delete level.info->snapshot;
if (level.info->mapname[0] != 0 || level.info == &TheDefaultLevelInfo)
{
level.info->snapshotVer = SAVEVER;
level.info->snapshot = new FCompressedMemFile;
level.info->snapshot->Open ();
FArchive arc (*level.info->snapshot);
SaveVersion = SAVEVER;
G_SerializeLevel (arc, false);
}
}
// Unarchives the current level based on its snapshot
// The level should have already been loaded and setup.
void G_UnSnapshotLevel (bool hubLoad)
{
if (level.info->snapshot == NULL)
return;
if (level.info->mapname[0] != 0 || level.info == &TheDefaultLevelInfo)
{
SaveVersion = level.info->snapshotVer;
level.info->snapshot->Reopen ();
FArchive arc (*level.info->snapshot);
if (hubLoad)
arc.SetHubTravel ();
G_SerializeLevel (arc, hubLoad);
arc.Close ();
level.FromSnapshot = true;
TThinkerIterator<APlayerPawn> it;
APlayerPawn *pawn, *next;
next = it.Next();
while ((pawn = next) != 0)
{
next = it.Next();
if (pawn->player == NULL || pawn->player->mo == NULL || !playeringame[pawn->player - players])
{
int i;
// If this isn't the unmorphed original copy of a player, destroy it, because it's extra.
for (i = 0; i < MAXPLAYERS; ++i)
{
if (playeringame[i] && players[i].morphTics && players[i].mo->tracer == pawn)
{
break;
}
}
if (i == MAXPLAYERS)
{
pawn->Destroy ();
}
}
}
}
// No reason to keep the snapshot around once the level's been entered.
delete level.info->snapshot;
level.info->snapshot = NULL;
}
void G_ClearSnapshots (void)
{
for (unsigned int i = 0; i < wadlevelinfos.Size(); i++)
{
if (wadlevelinfos[i].snapshot)
{
delete wadlevelinfos[i].snapshot;
wadlevelinfos[i].snapshot = NULL;
}
}
}
static void writeMapName (FArchive &arc, const char *name)
{
BYTE size;
if (name[7] != 0)
{
size = 8;
}
else
{
size = (BYTE)strlen (name);
}
arc << size;
arc.Write (name, size);
}
static void writeSnapShot (FArchive &arc, level_info_t *i)
{
arc << i->snapshotVer;
writeMapName (arc, i->mapname);
i->snapshot->Serialize (arc);
}
void G_WriteSnapshots (FILE *file)
{
unsigned int i;
for (i = 0; i < wadlevelinfos.Size(); i++)
{
if (wadlevelinfos[i].snapshot)
{
FPNGChunkArchive arc (file, SNAP_ID);
writeSnapShot (arc, (level_info_t *)&wadlevelinfos[i]);
}
}
if (TheDefaultLevelInfo.snapshot != NULL)
{
FPNGChunkArchive arc (file, DSNP_ID);
writeSnapShot(arc, &TheDefaultLevelInfo);
}
FPNGChunkArchive *arc = NULL;
// Write out which levels have been visited
for (i = 0; i < wadlevelinfos.Size(); ++i)
{
if (wadlevelinfos[i].flags & LEVEL_VISITED)
{
if (arc == NULL)
{
arc = new FPNGChunkArchive (file, VIST_ID);
}
writeMapName (*arc, wadlevelinfos[i].mapname);
}
}
if (arc != NULL)
{
BYTE zero = 0;
*arc << zero;
delete arc;
}
// Store player classes to be used when spawning a random class
if (multiplayer)
{
FPNGChunkArchive arc2 (file, RCLS_ID);
for (i = 0; i < MAXPLAYERS; ++i)
{
SBYTE cnum = SinglePlayerClass[i];
arc2 << cnum;
}
}
// Store player classes that are currently in use
FPNGChunkArchive arc3 (file, PCLS_ID);
for (i = 0; i < MAXPLAYERS; ++i)
{
BYTE pnum;
if (playeringame[i])
{
pnum = i;
arc3 << pnum;
arc3.UserWriteClass (players[i].cls);
}
pnum = 255;
arc3 << pnum;
}
}
void G_ReadSnapshots (PNGHandle *png)
{
DWORD chunkLen;
BYTE namelen;
char mapname[256];
level_info_t *i;
G_ClearSnapshots ();
chunkLen = (DWORD)M_FindPNGChunk (png, SNAP_ID);
while (chunkLen != 0)
{
FPNGChunkArchive arc (png->File->GetFile(), SNAP_ID, chunkLen);
DWORD snapver;
arc << snapver;
arc << namelen;
arc.Read (mapname, namelen);
mapname[namelen] = 0;
i = FindLevelInfo (mapname);
i->snapshotVer = snapver;
i->snapshot = new FCompressedMemFile;
i->snapshot->Serialize (arc);
chunkLen = (DWORD)M_NextPNGChunk (png, SNAP_ID);
}
chunkLen = (DWORD)M_FindPNGChunk (png, DSNP_ID);
if (chunkLen != 0)
{
FPNGChunkArchive arc (png->File->GetFile(), DSNP_ID, chunkLen);
DWORD snapver;
arc << snapver;
arc << namelen;
arc.Read (mapname, namelen);
TheDefaultLevelInfo.snapshotVer = snapver;
TheDefaultLevelInfo.snapshot = new FCompressedMemFile;
TheDefaultLevelInfo.snapshot->Serialize (arc);
}
chunkLen = (DWORD)M_FindPNGChunk (png, VIST_ID);
if (chunkLen != 0)
{
FPNGChunkArchive arc (png->File->GetFile(), VIST_ID, chunkLen);
arc << namelen;
while (namelen != 0)
{
arc.Read (mapname, namelen);
mapname[namelen] = 0;
i = FindLevelInfo (mapname);
i->flags |= LEVEL_VISITED;
arc << namelen;
}
}
chunkLen = (DWORD)M_FindPNGChunk (png, RCLS_ID);
if (chunkLen != 0)
{
FPNGChunkArchive arc (png->File->GetFile(), PCLS_ID, chunkLen);
SBYTE cnum;
for (DWORD j = 0; j < chunkLen; ++j)
{
arc << cnum;
SinglePlayerClass[j] = cnum;
}
}
chunkLen = (DWORD)M_FindPNGChunk (png, PCLS_ID);
if (chunkLen != 0)
{
FPNGChunkArchive arc (png->File->GetFile(), RCLS_ID, chunkLen);
BYTE pnum;
arc << pnum;
while (pnum != 255)
{
arc.UserReadClass (players[pnum].cls);
arc << pnum;
}
}
png->File->ResetFilePtr();
}
static void writeDefereds (FArchive &arc, level_info_t *i)
{
writeMapName (arc, i->mapname);
arc << i->defered;
}
void P_WriteACSDefereds (FILE *file)
{
FPNGChunkArchive *arc = NULL;
for (unsigned int i = 0; i < wadlevelinfos.Size(); i++)
{
if (wadlevelinfos[i].defered)
{
if (arc == NULL)
{
arc = new FPNGChunkArchive (file, ACSD_ID);
}
writeDefereds (*arc, (level_info_t *)&wadlevelinfos[i]);
}
}
if (arc != NULL)
{
// Signal end of defereds
BYTE zero = 0;
*arc << zero;
delete arc;
}
}
void P_ReadACSDefereds (PNGHandle *png)
{
BYTE namelen;
char mapname[256];
size_t chunklen;
P_RemoveDefereds ();
if ((chunklen = M_FindPNGChunk (png, ACSD_ID)) != 0)
{
FPNGChunkArchive arc (png->File->GetFile(), ACSD_ID, chunklen);
arc << namelen;
while (namelen)
{
arc.Read (mapname, namelen);
mapname[namelen] = 0;
level_info_t *i = FindLevelInfo (mapname);
if (i == NULL)
{
I_Error ("Unknown map '%s' in savegame", mapname);
}
arc << i->defered;
arc << namelen;
}
}
png->File->ResetFilePtr();
}
void level_locals_s::Tick ()
{
// Reset carry sectors
if (Scrolls != NULL)
{
memset (Scrolls, 0, sizeof(*Scrolls)*numsectors);
}
}
void level_locals_s::AddScroller (DScroller *scroller, int secnum)
{
if (secnum < 0)
{
return;
}
if (Scrolls == NULL)
{
Scrolls = new FSectorScrollValues[numsectors];
memset (Scrolls, 0, sizeof(*Scrolls)*numsectors);
}
}
// Initializes player classes in case they are random.
// This gets called at the start of a new game, and the classes
// chosen here are used for the remainder of a single-player
// or coop game. These are ignored for deathmatch.
static void InitPlayerClasses ()
{
if (!savegamerestore)
{
for (int i = 0; i < MAXPLAYERS; ++i)
{
SinglePlayerClass[i] = players[i].userinfo.PlayerClass;
if (SinglePlayerClass[i] < 0 || !playeringame[i])
{
SinglePlayerClass[i] = (pr_classchoice()) % PlayerClasses.Size ();
}
players[i].cls = NULL;
players[i].CurrentPlayerClass = SinglePlayerClass[i];
}
}
}
static void ParseSkill (FScanner &sc)
{
FSkillInfo skill;
skill.AmmoFactor = FRACUNIT;
skill.DoubleAmmoFactor = 2*FRACUNIT;
skill.DamageFactor = FRACUNIT;
skill.FastMonsters = false;
skill.DisableCheats = false;
skill.EasyBossBrain = false;
skill.AutoUseHealth = false;
skill.RespawnCounter = 0;
skill.RespawnLimit = 0;
skill.Aggressiveness = FRACUNIT;
skill.SpawnFilter = 0;
skill.ACSReturn = AllSkills.Size();
skill.MenuNameIsLump = false;
skill.MustConfirm = false;
skill.Shortcut = 0;
skill.TextColor = "";
sc.MustGetString();
skill.Name = sc.String;
while (sc.GetString ())
{
if (sc.Compare ("ammofactor"))
{
sc.MustGetFloat ();
skill.AmmoFactor = FLOAT2FIXED(sc.Float);
}
else if (sc.Compare ("doubleammofactor"))
{
sc.MustGetFloat ();
skill.DoubleAmmoFactor = FLOAT2FIXED(sc.Float);
}
else if (sc.Compare ("damagefactor"))
{
sc.MustGetFloat ();
skill.DamageFactor = FLOAT2FIXED(sc.Float);
}
else if (sc.Compare ("fastmonsters"))
{
skill.FastMonsters = true;
}
else if (sc.Compare ("disablecheats"))
{
skill.DisableCheats = true;
}
else if (sc.Compare ("easybossbrain"))
{
skill.EasyBossBrain = true;
}
else if (sc.Compare("autousehealth"))
{
skill.AutoUseHealth = true;
}
else if (sc.Compare("respawntime"))
{
sc.MustGetFloat ();
skill.RespawnCounter = int(sc.Float*TICRATE);
}
else if (sc.Compare("respawnlimit"))
{
sc.MustGetNumber ();
skill.RespawnLimit = sc.Number;
}
else if (sc.Compare("Aggressiveness"))
{
sc.MustGetFloat ();
skill.Aggressiveness = FRACUNIT - FLOAT2FIXED(clamp<float>(sc.Float, 0,1));
}
else if (sc.Compare("SpawnFilter"))
{
sc.MustGetString ();
strlwr(sc.String);
if (strstr(sc.String, "easy")) skill.SpawnFilter|=MTF_EASY;
if (strstr(sc.String, "normal")) skill.SpawnFilter|=MTF_NORMAL;
if (strstr(sc.String, "hard")) skill.SpawnFilter|=MTF_HARD;
}
else if (sc.Compare("ACSReturn"))
{
sc.MustGetNumber ();
skill.ACSReturn = sc.Number;
}
else if (sc.Compare("Name"))
{
sc.MustGetString ();
skill.MenuName = sc.String;
skill.MenuNameIsLump = false;
}
else if (sc.Compare("PlayerClassName"))
{
sc.MustGetString ();
FName pc = sc.String;
sc.MustGetString ();
skill.MenuNamesForPlayerClass[pc]=sc.String;
}
else if (sc.Compare("PicName"))
{
sc.MustGetString ();
skill.MenuName = sc.String;
skill.MenuNameIsLump = true;
}
else if (sc.Compare("MustConfirm"))
{
skill.MustConfirm = true;
if (sc.CheckToken(TK_StringConst))
{
skill.MustConfirmText = sc.String;
}
}
else if (sc.Compare("Key"))
{
sc.MustGetString();
skill.Shortcut = tolower(sc.String[0]);
}
else if (sc.Compare("TextColor"))
{
sc.MustGetString();
skill.TextColor = '[';
skill.TextColor << sc.String << ']';
}
else
{
sc.UnGet ();
break;
}
}
for(unsigned int i = 0; i < AllSkills.Size(); i++)
{
if (AllSkills[i].Name == skill.Name)
{
AllSkills[i] = skill;
return;
}
}
AllSkills.Push(skill);
}
int G_SkillProperty(ESkillProperty prop)
{
if (AllSkills.Size() > 0)
{
switch(prop)
{
case SKILLP_AmmoFactor:
if (dmflags2 & DF2_YES_DOUBLEAMMO)
{
return AllSkills[gameskill].DoubleAmmoFactor;
}
return AllSkills[gameskill].AmmoFactor;
case SKILLP_DamageFactor:
return AllSkills[gameskill].DamageFactor;
case SKILLP_FastMonsters:
return AllSkills[gameskill].FastMonsters || (dmflags & DF_FAST_MONSTERS);
case SKILLP_Respawn:
if (dmflags & DF_MONSTERS_RESPAWN && AllSkills[gameskill].RespawnCounter==0)
return TICRATE * (gameinfo.gametype != GAME_Strife ? 12 : 16);
return AllSkills[gameskill].RespawnCounter;
case SKILLP_RespawnLimit:
return AllSkills[gameskill].RespawnLimit;
case SKILLP_Aggressiveness:
return AllSkills[gameskill].Aggressiveness;
case SKILLP_DisableCheats:
return AllSkills[gameskill].DisableCheats;
case SKILLP_AutoUseHealth:
return AllSkills[gameskill].AutoUseHealth;
case SKILLP_EasyBossBrain:
return AllSkills[gameskill].EasyBossBrain;
case SKILLP_SpawnFilter:
return AllSkills[gameskill].SpawnFilter;
case SKILLP_ACSReturn:
return AllSkills[gameskill].ACSReturn;
}
}
return 0;
}
void G_VerifySkill()
{
if (gameskill >= (int)AllSkills.Size())
gameskill = AllSkills.Size()-1;
else if (gameskill < 0)
gameskill = 0;
}
FSkillInfo &FSkillInfo::operator=(const FSkillInfo &other)
{
Name = other.Name;
AmmoFactor = other.AmmoFactor;
DoubleAmmoFactor = other.DoubleAmmoFactor;
DamageFactor = other.DamageFactor;
FastMonsters = other.FastMonsters;
DisableCheats = other.DisableCheats;
AutoUseHealth = other.AutoUseHealth;
EasyBossBrain = other.EasyBossBrain;
RespawnCounter= other.RespawnCounter;
RespawnLimit= other.RespawnLimit;
Aggressiveness= other.Aggressiveness;
SpawnFilter = other.SpawnFilter;
ACSReturn = other.ACSReturn;
MenuName = other.MenuName;
MenuNamesForPlayerClass = other.MenuNamesForPlayerClass;
MenuNameIsLump = other.MenuNameIsLump;
MustConfirm = other.MustConfirm;
MustConfirmText = other.MustConfirmText;
Shortcut = other.Shortcut;
TextColor = other.TextColor;
return *this;
}
int FSkillInfo::GetTextColor() const
{
if (TextColor.IsEmpty())
{
return CR_UNTRANSLATED;
}
const BYTE *cp = (const BYTE *)TextColor.GetChars();
int color = V_ParseFontColor(cp, 0, 0);
if (color == CR_UNDEFINED)
{
Printf("Undefined color '%s' in definition of skill %s\n", TextColor.GetChars(), Name.GetChars());
color = CR_UNTRANSLATED;
}
return color;
}