From f732d4ec64ac7d5b314ac8e929381ce92a947810 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sat, 1 May 2021 22:52:28 +0200 Subject: [PATCH] - added MAPINFO parser, based on GZDoom's. This isn't hooked up yet, but all necessary structures and fields have been added so that selected pieces can be tested. --- source/CMakeLists.txt | 1 + source/core/cheats.cpp | 2 +- source/core/g_mapinfo.cpp | 1042 +++++++++++++++++++++++++++ source/core/g_mapinfo.h | 108 +++ source/core/gamecontrol.cpp | 3 +- source/core/mapinfo.cpp | 60 +- source/core/mapinfo.h | 95 ++- source/core/menu/razemenu.cpp | 2 +- source/games/blood/src/blood.cpp | 2 +- source/games/blood/src/d_menu.cpp | 2 +- source/games/blood/src/levels.cpp | 4 +- source/games/blood/src/messages.cpp | 2 +- source/games/duke/src/cheats.cpp | 2 +- source/games/duke/src/d_menu.cpp | 2 +- source/games/duke/src/gamedef.cpp | 20 +- source/games/duke/src/gameexec.cpp | 4 +- source/games/duke/src/player_r.cpp | 30 +- source/games/duke/src/premap.cpp | 6 +- source/games/duke/src/premap_r.cpp | 6 +- 19 files changed, 1337 insertions(+), 56 deletions(-) create mode 100644 source/core/g_mapinfo.cpp create mode 100644 source/core/g_mapinfo.h diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 745e513a56a..593fb50b2a3 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -1048,6 +1048,7 @@ set (PCH_SOURCES core/gamehud.cpp core/gamefuncs.cpp core/gameinput.cpp + core/g_mapinfo.cpp core/interpolate.cpp core/inputstate.cpp core/maphack.cpp diff --git a/source/core/cheats.cpp b/source/core/cheats.cpp index b802a7d42b3..72812e62333 100644 --- a/source/core/cheats.cpp +++ b/source/core/cheats.cpp @@ -294,7 +294,7 @@ static MapRecord* levelwarp_common(FCommandLine& argv, const char *cmdname, cons Printf(PRINT_BOLD, "Invalid level! Numbers must be > 0\n"); return nullptr; } - auto map = FindMapByLevelNum(numparm == 1 ? m : levelnum(e - 1, m - 1)); + auto map = FindMapByLevelNum(numparm == 1 ? m : makelevelnum(e - 1, m - 1)); if (!map) { if (numparm == 2) Printf(PRINT_BOLD, "Level E%s L%s not found!\n", argv[1], argv[2]); diff --git a/source/core/g_mapinfo.cpp b/source/core/g_mapinfo.cpp new file mode 100644 index 00000000000..ef185b236e6 --- /dev/null +++ b/source/core/g_mapinfo.cpp @@ -0,0 +1,1042 @@ +/* +** g_level.cpp +** Parses MAPINFO +** +**--------------------------------------------------------------------------- +** Copyright 1998-2016 Randy Heit +** Copyright 2009-2021 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 +#include "mapinfo.h" +#include "g_mapinfo.h" +#include "templates.h" +#include "filesystem.h" +#include "cmdlib.h" +#include "v_video.h" +#include "gi.h" +#include "gstrings.h" +#include "autosegs.h" +#include "i_system.h" +#include "gamecontrol.h" +#include "autosegs.h" + +extern TArray clusters; +extern TArray volumes; +extern TArray> mapList; // must be allocated as pointers because it can whack the currentlLevel pointer if this was a flat array. + +static MapRecord TheDefaultLevelInfo; +static ClusterDef TheDefaultClusterInfo; + +TArray ParsedLumps(8); + +//========================================================================== +// +// +//========================================================================== + +void FMapInfoParser::ParseOpenBrace() +{ + sc.MustGetStringName("{"); + sc.SetCMode(true); +} + +//========================================================================== +// +// +//========================================================================== + +bool FMapInfoParser::ParseCloseBrace() +{ + return sc.Compare("}"); +} + +//========================================================================== +// +// +//========================================================================== + +bool FMapInfoParser::CheckAssign() +{ + return sc.CheckString("="); +} + +//========================================================================== +// +// +//========================================================================== + +void FMapInfoParser::ParseAssign() +{ + sc.MustGetStringName("="); +} + +//========================================================================== +// +// +//========================================================================== + +void FMapInfoParser::MustParseAssign() +{ + sc.MustGetStringName("="); +} + +//========================================================================== +// +// +//========================================================================== + +void FMapInfoParser::ParseComma() +{ + sc.MustGetStringName(","); +} + +//========================================================================== +// +// +//========================================================================== + +bool FMapInfoParser::CheckNumber() +{ + if (sc.CheckString(",")) + { + sc.MustGetNumber(); + return true; + } + return false; +} + +//========================================================================== +// +// +//========================================================================== + +bool FMapInfoParser::CheckFloat() +{ + if (sc.CheckString(",")) + { + sc.MustGetFloat(); + return true; + } + return false; +} + +//========================================================================== +// +// 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 (sc.End) + { + sc.ScriptError("Unexpected end of file in %s definition", block); + } +} + +//========================================================================== +// +// ParseLookupname +// +//========================================================================== + +bool FMapInfoParser::ParseLookupName(FString &dest) +{ + sc.MustGetString(); + dest = sc.String; + return true; +} + +//========================================================================== +// +// +//========================================================================== + +void FMapInfoParser::ParseLumpOrTextureName(FString &name) +{ + sc.MustGetString(); + name = sc.String; +} + + +//========================================================================== +// +// +//========================================================================== + +void FMapInfoParser::ParseMusic(FString &name, int &order) +{ + sc.MustGetString(); + name = sc.String; + if (CheckNumber()) + { + order = sc.Number; + } +} + +//========================================================================== +// +// +// +//========================================================================== + +void FMapInfoParser::ParseCutscene(CutsceneDef& cdef) +{ + FString sound; + sc.MustGetStringName("{"); + while (!sc.CheckString("}")) + { + sc.MustGetString(); + if (sc.Compare("video")) { ParseAssign(); sc.MustGetString(); cdef.video = sc.String; cdef.function = ""; } + else if (sc.Compare("function")) { ParseAssign(); sc.MustGetString(); cdef.function = sc.String; cdef.video = ""; } + else if (sc.Compare("sound")) { ParseAssign(); sc.MustGetString(); cdef.soundName = sc.String; } + else if (sc.Compare("soundid")) { ParseAssign(); sc.MustGetNumber(); cdef.soundID = sc.Number; } + else if (sc.Compare("fps")) { ParseAssign(); sc.MustGetNumber(); cdef.framespersec = sc.Number; } + else if (sc.Compare("transitiononly")) cdef.transitiononly = true; + else if (sc.Compare("delete")) { cdef.function = "none"; cdef.video = ""; } // this means 'play nothing', not 'not defined'. + else if (sc.Compare("clear")) cdef = {}; + } +} + +//========================================================================== +// +// +// +//========================================================================== + +void FMapInfoParser::ParseCluster() +{ + sc.MustGetNumber (); + auto clusterinfo = MustFindCluster(sc.Number); + + ParseOpenBrace(); + while (sc.GetString()) + { + if (sc.Compare("clear")) + { + *clusterinfo = {}; + } + else if (sc.Compare("name")) + { + ParseAssign(); + ParseLookupName(clusterinfo->name); + } + else if (sc.Compare("intro")) + { + ParseAssign(); + ParseCutscene(clusterinfo->intro); + } + else if (sc.Compare("outro")) + { + ParseAssign(); + ParseCutscene(clusterinfo->outro); + } + else if (sc.Compare("gameover")) + { + ParseAssign(); + ParseCutscene(clusterinfo->gameover); + } + else if (sc.Compare("interbackground")) + { + ParseAssign(); + ParseLookupName(clusterinfo->InterBackground); + } + else if (!ParseCloseBrace()) + { + // Unknown + sc.ScriptMessage("Unknown property '%s' found in cluster definition\n", sc.String); + SkipToNext(); + } + else + { + break; + } + } + CheckEndOfFile("cluster"); +} + +//========================================================================== +// +// allow modification of maps defined through legacy means. +// +//========================================================================== + +bool FMapInfoParser::CheckLegacyMapDefinition(FString& mapname) +{ + if (Internal && (g_gameType & GAMEFLAG_BLOOD | GAMEFLAG_DUKECOMPAT | GAMEFLAG_SW) && sc.CheckString("{")) + { + sc.MustGetNumber(); + int vol = sc.Number; + if (!(g_gameType & GAMEFLAG_SW)) + { + // Blood and Duke use volume/level pairs + sc.MustGetStringName(","); + sc.MustGetNumber(); + int indx = sc.Number; + auto map = FindMapByIndexOnly(vol, indx); + if (!map) I_Error("Map {%d, %d} does not exist", vol, indx); + mapname = map->fileName; // we need the actual file name for further processing. + } + else + { + // SW only uses the level number + auto map = FindMapByLevelNum(vol); + if (!map) I_Error("Map {%d} does not exist", vol); + mapname = map->fileName; // we need the actual file name for further processing. + + } + sc.MustGetStringName("}"); + return true; + } + return false; +} + +//========================================================================== +// +// ParseNextMap +// Parses a next map field +// +//========================================================================== + +void FMapInfoParser::ParseMapName(FString &mapname) +{ + if (!CheckLegacyMapDefinition(mapname)) + { + sc.MustGetString(); + mapname = ExtractFileBase(sc.String); + } +} + +//========================================================================== +// +// Map options +// +//========================================================================== + +DEFINE_MAP_OPTION(clear, true) +{ + // Save the names, reset and restore the names + FString fn = info->fileName; + FString dn = info->name; + FString ln = info->labelName; + *info = *parse.defaultinfoptr; + info->fileName = fn; + info->name = dn; + info->labelName = ln; +} + + +DEFINE_MAP_OPTION(makelevelnum, true) +{ + parse.ParseAssign(); + parse.sc.MustGetNumber(); + info->levelNumber = parse.sc.Number; +} + +DEFINE_MAP_OPTION(next, true) +{ + parse.ParseAssign(); + parse.ParseMapName(info->NextMap); +} + +DEFINE_MAP_OPTION(author, true) +{ + parse.ParseAssign(); + parse.sc.MustGetString(); + info->Author = parse.sc.String; +} + +DEFINE_MAP_OPTION(secretnext, true) +{ + parse.ParseAssign(); + parse.ParseMapName(info->NextSecret); +} + +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. + MustFindCluster(info->cluster); +} + +DEFINE_MAP_OPTION(fade, true) +{ + parse.ParseAssign(); + parse.sc.MustGetString(); + info->fadeto = V_GetColor(nullptr, parse.sc); +} + +DEFINE_MAP_OPTION(partime, true) +{ + parse.ParseAssign(); + parse.sc.MustGetNumber(); + info->parTime = parse.sc.Number; +} + +DEFINE_MAP_OPTION(designertime, true) +{ + parse.ParseAssign(); + parse.sc.MustGetNumber(); + info->designerTime = parse.sc.Number; +} + +DEFINE_MAP_OPTION(music, true) +{ + parse.ParseAssign(); + parse.ParseMusic(info->music, info->musicorder); +} + +DEFINE_MAP_OPTION(cdtrack, true) +{ + parse.ParseAssign(); + parse.sc.MustGetNumber(); + info->cdSongId = parse.sc.Number; +} + +DEFINE_MAP_OPTION(intro, true) +{ + parse.ParseAssign(); + parse.ParseCutscene(info->intro); +} + +DEFINE_MAP_OPTION(outro, true) +{ + parse.ParseAssign(); + parse.ParseCutscene(info->outro); +} + +DEFINE_MAP_OPTION(interbackground, true) +{ + parse.ParseAssign(); + parse.sc.MustGetString(); + info->InterBackground = parse.sc.String; +} + + +/* currently all sounds are precached. This requires significant work on sound management and info collection. +DEFINE_MAP_OPTION(PrecacheSounds, true) +{ + parse.ParseAssign(); + + do + { + parse.sc.MustGetString(); + FSoundID snd = parse.sc.String; + if (snd == 0) + { + parse.sc.ScriptMessage("Unknown sound \"%s\"", parse.sc.String); + } + else + { + info->PrecacheSounds.Push(snd); + } + } while (parse.sc.CheckString(",")); +} +*/ + +DEFINE_MAP_OPTION(PrecacheTextures, true) +{ + parse.ParseAssign(); + do + { + parse.sc.MustGetString(); + //the texture manager is not initialized here so all we can do is store the texture's name. + info->PrecacheTextures.Push(parse.sc.String); + } while (parse.sc.CheckString(",")); +} + +DEFINE_MAP_OPTION(bordertexture, true) +{ + parse.ParseAssign(); + parse.ParseLumpOrTextureName(info->BorderTexture); +} + +DEFINE_MAP_OPTION(fogdensity, false) +{ + parse.ParseAssign(); + parse.sc.MustGetNumber(); + info->fogdensity = clamp(parse.sc.Number, 0, 512) >> 1; +} + +DEFINE_MAP_OPTION(skyfog, false) +{ + parse.ParseAssign(); + parse.sc.MustGetNumber(); + info->skyfog = parse.sc.Number; +} + +/* stuff for later when the new renderer is done. +DEFINE_MAP_OPTION(lightmode, false) +{ + parse.ParseAssign(); + parse.sc.MustGetNumber(); + + if ((parse.sc.Number >= 0 && parse.sc.Number <= 4) || parse.sc.Number == 8 || parse.sc.Number == 16) + { + info->lightmode = ELightMode(parse.sc.Number); + } + else + { + parse.sc.ScriptMessage("Invalid light mode %d", parse.sc.Number); + } +} +*/ + +DEFINE_MAP_OPTION(skyrotate, false) +{ + parse.ParseAssign(); + parse.sc.MustGetFloat(); + info->skyrotatevector.X = (float)parse.sc.Float; + parse.sc.MustGetStringName(","); + parse.sc.MustGetFloat(); + info->skyrotatevector.Y = (float)parse.sc.Float; + parse.sc.MustGetStringName(","); + parse.sc.MustGetFloat(); + info->skyrotatevector.Z = (float)parse.sc.Float; + info->skyrotatevector.W = 0; + info->skyrotatevector.MakeUnit(); + parse.sc.MustGetStringName(","); + parse.sc.MustGetFloat(); + info->skyrotatevector.W = (float)parse.sc.Float; // W is the rotation speed. This must not be normalized +} + +DEFINE_MAP_OPTION(rr_startsound, false) +{ + parse.ParseAssign(); + parse.sc.MustGetNumber(); + info->rr_startsound = parse.sc.Number; +} + + +//========================================================================== +// +// All flag based map options +// +//========================================================================== + +enum EMIType +{ + MITYPE_IGNORE, + MITYPE_EATNEXT, + MITYPE_SETFLAG, + MITYPE_CLRFLAG, + MITYPE_SCFLAGS, + MITYPE_SETFLAGG, + MITYPE_CLRFLAGG, + MITYPE_SCFLAGSG, + MITYPE_COMPATFLAG, +}; + +struct MapInfoFlagHandler +{ + const char *name; + EMIType type; + uint32_t data1, data2; + int gameflagmask; +} +MapFlagHandlers[] = +{ + { "nointermission", MITYPE_SETFLAG, LEVEL_NOINTERMISSION, 0, -1 }, + { "secretexitoverride", MITYPE_SETFLAG, LEVEL_SECRETEXITOVERRIDE, 0, -1 }, + { "clearinventory", MITYPE_SETFLAG, LEVEL_CLEARINVENTORY, 0, -1 }, + { "clearweapons", MITYPE_SETFLAG, LEVEL_CLEARWEAPONS, 0, -1 }, + { "rrra_hulkspawn", MITYPE_SETFLAGG,LEVEL_RR_HULKSPAWN, 0, GAMEFLAG_RRRA }, + { "rr_clearmoonshine", MITYPE_SETFLAGG,LEVEL_RR_CLEARMOONSHINE, 0, GAMEFLAG_RR }, + + { NULL, MITYPE_IGNORE, 0, 0} +}; + +//========================================================================== +// +// ParseMapDefinition +// Parses the body of a map definition, including defaultmap etc. +// +//========================================================================== + +void FMapInfoParser::ParseMapDefinition(MapRecord &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: + if (!CheckAssign()) + { + info.flags |= handler->data1; + } + else + { + sc.MustGetNumber(); + if (sc.Number) info.flags |= handler->data1; + else 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_SETFLAGG: + if (!CheckAssign()) + { + info.gameflags |= handler->data1; + } + else + { + sc.MustGetNumber(); + if (sc.Number) info.gameflags |= handler->data1; + else info.gameflags &= ~handler->data1; + } + info.gameflags |= handler->data2; + break; + + case MITYPE_CLRFLAGG: + info.gameflags &= ~handler->data1; + info.gameflags |= handler->data2; + break; + case MITYPE_SCFLAGSG: + info.gameflags = (info.gameflags & handler->data2) | handler->data1; + break; + + default: + // should never happen + assert(false); + break; + } + } + else + { + bool success = false; + + AutoSegs::MapInfoOptions.ForEach([this, &success, &info](FMapOptInfo* option) + { + if (sc.Compare(option->name)) + { + option->handler(*this, &info); + success = true; + return false; // break + } + + return true; // continue + }); + + 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) || !strnicmp(mapname, "LEV", 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[2] == 'L') && + mapname[3] >= '0' && mapname[3] <= '9') + { + int epinum = mapname[1] - '1'; + int mapnum = mapname[3] - '0'; + return makelevelnum(epinum, mapnum); + } + return 0; +} + +//========================================================================== +// +// ParseMapHeader +// Parses the header of a map definition ('map mapxx mapname') +// +//========================================================================== + +MapRecord *FMapInfoParser::ParseMapHeader(MapRecord &defaultinfo) +{ + FString mapname; + + if (!CheckLegacyMapDefinition(mapname)) + { + ParseLookupName(mapname); + } + + auto map = FindMapByName(mapname); + if (!map) + { + map = AllocateMap(); + *map = defaultinfo; + DefaultExtension(mapname, ".map"); + map->SetFileName(mapname); + } + + if (!sc.CheckString("{")) + { + sc.MustGetString(); + map->name = sc.String; + } + else + { + if (map->name.IsEmpty()) I_Error("Missing level name"); + sc.UnGet(); + } + map->levelNumber = GetDefaultLevelNum(map->labelName); + return map; +} + + +//========================================================================== +// +// Episode definitions start with the header "episode " +// and then can be followed by any of the following: +// +// name "Episode name as text" +// picname "Picture to display the episode name" +// key "Shortcut key for the menu" +// noskillmenu +// remove +// +//========================================================================== + +void FMapInfoParser::ParseEpisodeInfo () +{ + unsigned int i; + FString map; + FString pic; + FString name; + bool remove = false; + char key = 0; + int flags = 0; + + // Get map name + sc.MustGetString (); + map = sc.String; + + ParseOpenBrace(); + + while (sc.GetString()) + { + if (sc.Compare ("optional")) + { + flags |= VF_OPTIONAL; + } + else if (sc.Compare("sharewarelock")) + { + flags |= VF_SHAREWARELOCK; + } + else if (sc.Compare ("name")) + { + ParseAssign(); + sc.MustGetString (); + name = sc.String; + } + else if (sc.Compare ("remove")) + { + remove = true; + } + else if (sc.Compare ("key")) + { + ParseAssign(); + sc.MustGetString (); + key = sc.String[0]; + } + else if (sc.Compare("noskillmenu")) + { + flags |= VF_NOSKILL; + } + else if (!ParseCloseBrace()) + { + // Unknown + sc.ScriptMessage("Unknown property '%s' found in episode definition\n", sc.String); + SkipToNext(); + } + else + { + break; + } + } + CheckEndOfFile("episode"); + + + for (i = 0; i < volumes.Size(); i++) + { + if (volumes[i].startmap.CompareNoCase(map) == 0) + { + break; + } + } + + if (remove) + { + // If the remove property is given for an episode, remove it. + volumes.Delete(i); + } + else + { + // Only allocate a new entry if this doesn't replace an existing episode. + if (i >= volumes.Size()) + { + i = volumes.Reserve(1); + } + + auto epi = &volumes[i]; + + epi->startmap = map; + epi->name = name; + epi->shortcut = tolower(key); + epi->flags = flags; + epi->index = i; + } +} + + +//========================================================================== +// +// SetLevelNum +// Avoid duplicate levelnums. The level being set always has precedence. +// +//========================================================================== + +void SetLevelNum (MapRecord *info, int num) +{ + for (auto& map : mapList) + { + + if (map->levelNumber == num) + map->levelNumber = 0; + } + info->levelNumber = num; +} + +//========================================================================== +// +// G_DoParseMapInfo +// Parses a single MAPINFO lump +// data for wadlevelinfos and wadclusterinfos. +// +//========================================================================== + +void FMapInfoParser::ParseMapInfo (int lump, MapRecord &gamedefaults, MapRecord &defaultinfo) +{ + sc.OpenLumpNum(lump); + Internal = (fileSystem.GetFileContainer(lump) == 0); + + defaultinfo = gamedefaults; + defaultinfoptr = &defaultinfo; + + if (ParsedLumps.Find(lump) != ParsedLumps.Size()) + { + sc.ScriptMessage("MAPINFO file is processed more than once\n"); + } + else + { + ParsedLumps.Push(lump); + } + + while (sc.GetString ()) + { + if (sc.Compare("include")) + { + sc.MustGetString(); + int inclump = fileSystem.CheckNumForFullName(sc.String, true); + if (inclump < 0) + { + sc.ScriptError("include file '%s' not found", sc.String); + } + if (fileSystem.GetFileContainer(sc.LumpNum) != fileSystem.GetFileContainer(inclump)) + { + // Do not allow overriding includes from the default MAPINFO + if (fileSystem.GetFileContainer(sc.LumpNum) == 0) + { + I_FatalError("File %s is overriding core lump %s.", + fileSystem.GetResourceFileFullName(fileSystem.GetFileContainer(inclump)), sc.String); + } + } + FScanner saved_sc = sc; + ParseMapInfo(inclump, gamedefaults, defaultinfo); + sc = saved_sc; + } + else if (sc.Compare("gamedefaults")) + { + gamedefaults = {}; + 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")) + { + auto levelinfo = ParseMapHeader(defaultinfo); + + ParseMapDefinition(*levelinfo); + SetLevelNum (levelinfo, levelinfo->levelNumber); // 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("cluster")) + { + ParseCluster(); + } + else if (sc.Compare("episode")) + { + ParseEpisodeInfo(); + } + else if (sc.Compare("clearepisodes")) + { + volumes.Clear(); + } + else if (sc.Compare("clearall")) + { + // Wipe out all legacy content to start a fresh definition. + volumes.Clear(); + mapList.Clear(); + clusters.Clear(); + } + else if (sc.Compare("gameinfo")) + { + // todo: global game propeties. + } + 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 () +{ + int lump, lastlump = 0; + MapRecord gamedefaults; + + // first parse the internal one which sets up the needed basics and patches the legacy definitions of each game. + FMapInfoParser parse; + MapRecord defaultinfo; + int baselump = fileSystem.GetNumForFullName("engine/rmapinfo.txt"); + if (fileSystem.GetFileContainer(baselump) > 0) + { + I_FatalError("File %s is overriding core lump %s.", + fileSystem.GetResourceFileName(fileSystem.GetFileContainer(baselump)), "engine/rmapinfo.txt"); + } + parse.ParseMapInfo(baselump, gamedefaults, defaultinfo); + + // Parse any extra MAPINFOs. + while ((lump = fileSystem.FindLump ("RMAPINFO", &lastlump, false)) != -1) + { + FMapInfoParser parse; + MapRecord defaultinfo; + parse.ParseMapInfo(lump, gamedefaults, defaultinfo); + } + + if (volumes.Size() == 0) + { + I_FatalError ("No volumes defined."); + } +} diff --git a/source/core/g_mapinfo.h b/source/core/g_mapinfo.h new file mode 100644 index 00000000000..375091bbc77 --- /dev/null +++ b/source/core/g_mapinfo.h @@ -0,0 +1,108 @@ +/* +** g_level.h +** +**--------------------------------------------------------------------------- +** 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. +**--------------------------------------------------------------------------- +** +*/ + +#ifndef __G_LEVEL_H__ +#define __G_LEVEL_H__ + +#include "autosegs.h" +#include "vectors.h" +#include "sc_man.h" +#include "file_zip.h" + +struct FMapInfoParser +{ + FScanner sc; + bool Internal; + MapRecord* defaultinfoptr; + + FMapInfoParser(bool internal = false) + { + Internal = internal; + } + + bool CheckLegacyMapDefinition(FString& mapname); + bool ParseLookupName(FString &dest); + void ParseMusic(FString &name, int &order); + //void ParseLumpOrTextureName(char *name); + void ParseLumpOrTextureName(FString &name); + + void ParseCutscene(CutsceneDef& cdef); + void ParseCluster(); + void ParseMapName(FString &mapname); + MapRecord *ParseMapHeader(MapRecord &defaultinfo); + void ParseMapDefinition(MapRecord &leveldef); + void ParseEpisodeInfo (); + void ParseSkill (); + void ParseMapInfo (int lump, MapRecord &gamedefaults, MapRecord &defaultinfo); + + void ParseOpenBrace(); + bool ParseCloseBrace(); + bool CheckAssign(); + void ParseAssign(); + void MustParseAssign(); + void ParseComma(); + bool CheckNumber(); + bool CheckFloat(); + void SkipToNext(); + void CheckEndOfFile(const char *block); +}; + +#if defined(_MSC_VER) +#pragma section(SECTION_YREG,read) +#define MSVC_YSEG __declspec(allocate(SECTION_YREG)) +#define GCC_YSEG +#else +#define MSVC_YSEG +#define GCC_YSEG __attribute__((section(SECTION_YREG))) __attribute__((used)) +#endif + +#define DEFINE_MAP_OPTION(name, old) \ + static void MapOptHandler_##name(FMapInfoParser &parse, MapRecord *info); \ + static FMapOptInfo MapOpt_##name = \ + { #name, MapOptHandler_##name, old }; \ + MSVC_YSEG FMapOptInfo *mapopt_##name GCC_YSEG = &MapOpt_##name; \ + static void MapOptHandler_##name(FMapInfoParser &parse, MapRecord *info) + + +struct FMapOptInfo +{ + const char *name; + void (*handler) (FMapInfoParser &parse, MapRecord *levelinfo); + bool old; +}; + + +void G_ParseMapInfo(); + + +#endif //__G_LEVEL_H__ diff --git a/source/core/gamecontrol.cpp b/source/core/gamecontrol.cpp index b08f263837f..cae95b7e512 100644 --- a/source/core/gamecontrol.cpp +++ b/source/core/gamecontrol.cpp @@ -608,7 +608,6 @@ void SetDefaultStrings() gSkillNames[2] = "$Come get Some"; gSkillNames[3] = "$Damn I'm Good"; } - if (g_gameType & GAMEFLAG_RR) volumeList[0].flags |= EF_GOTONEXTVOLUME; // Blood hard codes its skill names, so we have to define them manually. if (isBlood()) { @@ -1518,7 +1517,7 @@ DEFINE_FIELD_X(MapRecord, MapRecord, cluster) DEFINE_FIELD_X(MapRecord, MapRecord, nextLevel) DEFINE_FIELD_X(MapRecord, MapRecord, nextSecret) //native readonly String messages[MAX_MESSAGES]; -DEFINE_FIELD_X(MapRecord, MapRecord, author) +DEFINE_FIELD_X(MapRecord, MapRecord, Author) DEFINE_FIELD_X(SummaryInfo, SummaryInfo, kills) DEFINE_FIELD_X(SummaryInfo, SummaryInfo, maxkills) diff --git a/source/core/mapinfo.cpp b/source/core/mapinfo.cpp index b280de69b0c..cc6f78dafdf 100644 --- a/source/core/mapinfo.cpp +++ b/source/core/mapinfo.cpp @@ -44,8 +44,10 @@ FString gSkillNames[MAXSKILLS]; int gDefaultVolume = 0, gDefaultSkill = 1; GlobalCutscenes globalCutscenes; +TArray clusters; +TArray volumes; +TArray> mapList; // must be allocated as pointers because it can whack the currentlLevel pointer if this was a flat array. VolumeRecord volumeList[MAXVOLUMES]; -static TArray> mapList; MapRecord *currentLevel; // level that is currently played. MapRecord* lastLevel; // Same here, for the last level. @@ -58,7 +60,7 @@ CCMD(listmaps) if (lump >= 0) { int rfnum = fileSystem.GetFileContainer(lump); - Printf("%s - %s (%s)\n", map->fileName.GetChars(), map->DisplayName(), fileSystem.GetResourceFileName(rfnum)); + Printf("%s - %s (%s)\n", map->LabelName(), map->DisplayName(), fileSystem.GetResourceFileName(rfnum)); } else { @@ -67,6 +69,58 @@ CCMD(listmaps) } } +CCMD(mapinfo) +{ + const char* mapname = nullptr; + if (argv.argc() > 1) mapname = argv[1]; + + if (!mapname) + { + for (auto& vol : volumes) + { + Printf("Volume %d\n\tName = '%s'\n\tstartmap = '%s'\n}\n", vol.index, vol.name.GetChars(), vol.startmap.GetChars()); + } + for (auto& clus : clusters) + { + if (clus.intro.isdefined() || clus.outro.isdefined()) + { + Printf("Cluster %d\n\tName = '%s'\n", clus.index, clus.name.GetChars()); + if (clus.intro.function.IsNotEmpty()) Printf("\tIntro function = %d\n", clus.intro.function.GetChars()); + if (clus.intro.video.IsNotEmpty()) Printf("\tIntro video = %d\n", clus.intro.video.GetChars()); + if (clus.outro.function.IsNotEmpty()) Printf("\tOutro function = %d\n", clus.outro.function.GetChars()); + if (clus.outro.video.IsNotEmpty()) Printf("\tOutro video = %d\n", clus.outro.video.GetChars()); + Printf("}\n"); + } + } + } + for (auto& map : mapList) + { + if (mapname && map->labelName.CompareNoCase(mapname)) continue; + int lump = fileSystem.FindFile(map->fileName); + if (lump >= 0) + { + int rfnum = fileSystem.GetFileContainer(lump); + Printf("%s - %s (%s)\n{\n", map->fileName.GetChars(), map->DisplayName(), fileSystem.GetResourceFileName(rfnum)); + Printf("\tlevel number = %d\n\tCluster = %d\n\tIndex = %d\n", map->levelNumber, map->cluster, map->mapindex); + if (map->Author.IsNotEmpty()) Printf("\tAuthor = '%s'\n", map->Author.GetChars()); + if (map->NextMap.IsNotEmpty()) Printf("\tNext map = '%s'\n", map->NextMap.GetChars()); + if (map->NextSecret.IsNotEmpty()) Printf("\tNext secret map = '%s'\n", map->NextSecret.GetChars()); + if (map->music.IsNotEmpty()) Printf("\tMusic = '%s:%d'", map->music.GetChars(), map->musicorder); + if (map->cdSongId > 0) Printf("\tCD track = %d\n", map->cdSongId); + if (map->parTime) Printf("\tPar Time = %d\n", map->parTime); + if (map->designerTime) Printf("\tPar Time = %d\n", map->designerTime); + if (map->intro.function.IsNotEmpty()) Printf("\tIntro function = %d\n", map->intro.function.GetChars()); + if (map->intro.video.IsNotEmpty()) Printf("\tIntro video = %d\n", map->intro.video.GetChars()); + if (map->outro.function.IsNotEmpty()) Printf("\tOutro function = %d\n", map->outro.function.GetChars()); + if (map->outro.video.IsNotEmpty()) Printf("\tOutro video = %d\n", map->outro.video.GetChars()); + Printf("}\n"); + } + else + { + Printf("%s - %s (defined but does not exist)\n", map->fileName.GetChars(), map->DisplayName()); + } + } +} MapRecord *FindMapByName(const char *nm) { @@ -147,7 +201,7 @@ bool SetMusicForMap(const char* mapname, const char* music, bool namehack) if (numMatches != 4 || toupper(b1) != 'E' || toupper(b2) != 'L') return false; - index = FindMapByLevelNum(levelnum(ep - 1, lev - 1)); + index = FindMapByLevelNum(makelevelnum(ep - 1, lev - 1)); } if (index != nullptr) diff --git a/source/core/mapinfo.h b/source/core/mapinfo.h index 0263b58e3d8..2d26a290e87 100644 --- a/source/core/mapinfo.h +++ b/source/core/mapinfo.h @@ -3,6 +3,8 @@ #include "gstrings.h" #include "cmdlib.h" #include "quotemgr.h" +#include "palentry.h" +#include "vectors.h" #ifdef GetMessage #undef GetMessage // Windows strikes... #endif @@ -17,8 +19,24 @@ enum EMax enum EVolFlags { - EF_HIDEFROMSP = 1, - EF_GOTONEXTVOLUME = 2, // for RR which continues the game in the next volume + VF_HIDEFROMSP = 1, + VF_OPTIONAL = 2, + VF_SHAREWARELOCK = 4, // show in shareware but lock access. + VF_NOSKILL = 8, +}; + +enum EMapFlags +{ + LEVEL_NOINTERMISSION = 1, + LEVEL_SECRETEXITOVERRIDE = 2, // when given an explicit level number, override with secret exit in the map, mainly for compiling episodes out of single levels. + LEVEL_CLEARINVENTORY = 4, + LEVEL_CLEARWEAPONS = 8, +}; + +enum EMapGameFlags +{ + LEVEL_RR_HULKSPAWN = 1, + LEVEL_RR_CLEARMOONSHINE = 2, }; // These get filled in by the map definition parsers of the front ends. @@ -51,12 +69,16 @@ struct CutsceneDef { FString video; FString function; + FString soundName; + int soundID; // ResID not SoundID! int sound = 0; int framespersec = 0; // only relevant for ANM. bool transitiononly = false; // only play when transitioning between maps, but not when starting on a map or ending a game. void Create(DObject* runner); bool Create(DObject* runner, MapRecord* map, bool transition); + bool isdefined() { return video.IsNotEmpty() || function.IsNotEmpty(); } + int GetSound(); }; struct GlobalCutscenes @@ -70,13 +92,28 @@ struct GlobalCutscenes FString SummaryScreen; }; -struct VolumeRecord +struct ClusterDef { + FString name; // What gets displayed for this cluster. In Duke this is normally the corresponding volume name but does not have to be. + CutsceneDef intro; // plays when entering this cluster + CutsceneDef outro; // plays when leaving this cluster + CutsceneDef gameover; // when defined, plays when the player dies in this cluster + FString InterBackground; + int index = -1; + int flags = 0; // engine and common flags + int gameflags = 0; // game specific flags. +}; + +struct VolumeRecord // episodes +{ + FString startmap; FString name; FString subtitle; + int index = -1; CutsceneDef intro; CutsceneDef outro; - int32_t flags = 0; + int flags = 0; + int shortcut = 0; }; struct MapRecord @@ -87,19 +124,36 @@ struct MapRecord FString labelName; FString name; FString music; + FString Author; + FString NextMap; + FString NextSecret; + int cdSongId = -1; + int musicorder = -1; + CutsceneDef intro; CutsceneDef outro; - int cdSongId = -1; int flags = 0; + int gameflags = 0; int levelNumber = -1; + int mapindex = -1; // index in the episode. This only for finding the next map in the progression when nothing explicit is defined. int cluster = -1; + PalEntry fadeto = 0; + int fogdensity = 0; + int skyfog = 0; + FString BorderTexture; + FString InterBackground; + TArray PrecacheTextures; + FVector4 skyrotatevector; + // The rest is only used by Blood int nextLevel = -1; int nextSecret = -1; FString messages[MAX_MESSAGES]; - FString author; int8_t fog = -1, weather = -1; // Blood defines these but they aren't used. + + // game specific stuff + int rr_startsound = 0; const char* LabelName() const { @@ -133,8 +187,6 @@ struct MapRecord { messages[num] = msg; } - - }; struct SummaryInfo @@ -159,12 +211,37 @@ bool SetMusicForMap(const char* mapname, const char* music, bool namehack = fals MapRecord *FindMapByName(const char *nm); MapRecord *FindMapByLevelNum(int num); MapRecord* FindMapByClusterAndLevelNum(int clst, int num); +inline MapRecord* FindMapByIndexOnly(int clst, int num) { return FindMapByClusterAndLevelNum(clst, num); } MapRecord *FindNextMap(MapRecord *thismap); MapRecord* SetupUserMap(const char* boardfilename, const char *defaultmusic = nullptr); MapRecord* AllocateMap(); +inline VolumeRecord* FindVolume(int index) { return nullptr; } +inline ClusterDef* FindCluster(int index) { return nullptr; } +inline ClusterDef* AllocateCluster() { return nullptr; } +inline VolumeRecord* AllocateVolume() { return nullptr; } +void SetLevelNum(MapRecord* info, int num); + +inline VolumeRecord* MustFindVolume(int index) +{ + auto r = FindVolume(index); + if (r) return r; + r = AllocateVolume(); + r->index = index; + return r; +} +inline ClusterDef* MustFindCluster(int index) +{ + auto r = FindCluster(index); + if (r) return r; + r = AllocateCluster(); + r->index = index; + return r; +} + + // These should be the only places converting between level numbers and volume/map pairs -constexpr inline int levelnum(int vol, int map) +constexpr inline int makelevelnum(int vol, int map) { return vol * 1000 + map; } diff --git a/source/core/menu/razemenu.cpp b/source/core/menu/razemenu.cpp index 89f8857cc61..5df1055cc20 100644 --- a/source/core/menu/razemenu.cpp +++ b/source/core/menu/razemenu.cpp @@ -388,7 +388,7 @@ static void BuildEpisodeMenu() for (int i = 0; i < MAXVOLUMES; i++) { auto& vol = volumeList[i]; - if (vol.name.IsNotEmpty() && !(vol.flags & EF_HIDEFROMSP)) + if (vol.name.IsNotEmpty() && !(vol.flags & VF_HIDEFROMSP)) { int isShareware = ((g_gameType & GAMEFLAG_DUKE) && (g_gameType & GAMEFLAG_SHAREWARE) && i > 0); diff --git a/source/games/blood/src/blood.cpp b/source/games/blood/src/blood.cpp index 97d78fb1a25..9c7ddf997c2 100644 --- a/source/games/blood/src/blood.cpp +++ b/source/games/blood/src/blood.cpp @@ -329,7 +329,7 @@ void GameInterface::Ticker() STAT_Update(false); // Fixme: Link maps, not episode/level pairs. int ep = volfromlevelnum(currentLevel->levelNumber); - auto map = FindMapByLevelNum(levelnum(ep, gNextLevel)); + auto map = FindMapByLevelNum(makelevelnum(ep, gNextLevel)); CompleteLevel(map); } } diff --git a/source/games/blood/src/d_menu.cpp b/source/games/blood/src/d_menu.cpp index 8db9fc49417..4e0e35a5917 100644 --- a/source/games/blood/src/d_menu.cpp +++ b/source/games/blood/src/d_menu.cpp @@ -171,7 +171,7 @@ bool GameInterface::StartGame(FNewGameStartup& gs) } sfxKillAllSounds(); - auto map = FindMapByLevelNum(levelnum(gs.Episode, gs.Level)); + auto map = FindMapByLevelNum(makelevelnum(gs.Episode, gs.Level)); DeferedStartGame(map, gs.Skill); return true; } diff --git a/source/games/blood/src/levels.cpp b/source/games/blood/src/levels.cpp index b977578ce4e..d987a8c4434 100644 --- a/source/games/blood/src/levels.cpp +++ b/source/games/blood/src/levels.cpp @@ -95,7 +95,7 @@ void levelLoadMapInfo(IniFile *pIni, MapRecord *pLevelInfo, const char *pzSectio { char buffer[16]; pLevelInfo->SetName(pIni->GetKeyString(pzSection, "Title", pLevelInfo->labelName)); - pLevelInfo->author = pIni->GetKeyString(pzSection, "Author", ""); + pLevelInfo->Author = pIni->GetKeyString(pzSection, "Author", ""); pLevelInfo->music = pIni->GetKeyString(pzSection, "Song", ""); DefaultExtension(pLevelInfo->music, ".mid"); pLevelInfo->cdSongId = pIni->GetKeyInt(pzSection, "Track", -1); pLevelInfo->nextLevel = pIni->GetKeyInt(pzSection, "EndingA", -1); @@ -187,7 +187,7 @@ void levelLoadDefaults(void) auto pLevelInfo = AllocateMap(); const char *pMap = BloodINI->GetKeyString(buffer, buffer2, NULL); CheckSectionAbend(pMap); - pLevelInfo->levelNumber = levelnum(i, j); + pLevelInfo->levelNumber = makelevelnum(i, j); pLevelInfo->cluster = i + 1; pLevelInfo->labelName = pMap; pLevelInfo->fileName.Format("%s.map", pMap); diff --git a/source/games/blood/src/messages.cpp b/source/games/blood/src/messages.cpp index aaf74168745..42b92b34482 100644 --- a/source/games/blood/src/messages.cpp +++ b/source/games/blood/src/messages.cpp @@ -421,7 +421,7 @@ static bool cheatMario(cheatseq_t* c) int nEpisode, nLevel; if (parseArgs((char*)c->Args, &nEpisode, &nLevel) == 2) { - auto map = FindMapByLevelNum(levelnum(nEpisode, nLevel)); + auto map = FindMapByLevelNum(makelevelnum(nEpisode, nLevel)); if (map) DeferedStartGame(map, -1); } return true; diff --git a/source/games/duke/src/cheats.cpp b/source/games/duke/src/cheats.cpp index 6c7f67bc3a7..7dbce007785 100644 --- a/source/games/duke/src/cheats.cpp +++ b/source/games/duke/src/cheats.cpp @@ -296,7 +296,7 @@ static bool cheatLevel(cheatseq_t *s) levnume = (s->Args[1] - '0')*10+(s->Args[2]-'0') - 1; // Instead of hard coded range checks on volume and level, let's just check if the level is defined. - auto map = FindMapByLevelNum(levelnum(volnume, levnume)); + auto map = FindMapByLevelNum(makelevelnum(volnume, levnume)); if (map) { ChangeLevel(map, -1); diff --git a/source/games/duke/src/d_menu.cpp b/source/games/duke/src/d_menu.cpp index 879597e58bf..4f08d61fac4 100644 --- a/source/games/duke/src/d_menu.cpp +++ b/source/games/duke/src/d_menu.cpp @@ -127,7 +127,7 @@ bool GameInterface::StartGame(FNewGameStartup& gs) Net_ClearFifo(); } - auto map = FindMapByLevelNum(levelnum(gs.Episode, gs.Level)); + auto map = FindMapByLevelNum(makelevelnum(gs.Episode, gs.Level)); if (map) { DeferedStartGame(map, gs.Skill); diff --git a/source/games/duke/src/gamedef.cpp b/source/games/duke/src/gamedef.cpp index 5720d3f8226..e7943e64031 100644 --- a/source/games/duke/src/gamedef.cpp +++ b/source/games/duke/src/gamedef.cpp @@ -1016,7 +1016,7 @@ int ConCompiler::parsecommand() if (k >= 0) { tempMusic.Reserve(1); - tempMusic.Last().levnum = levelnum(k, i); + tempMusic.Last().levnum = makelevelnum(k, i); tempMusic.Last().music = parsebuffer.Data(); } else @@ -1695,7 +1695,7 @@ int ConCompiler::parsecommand() textptr++, i++; } parsebuffer.Push(0); - auto levnum = levelnum(j, k); + auto levnum = makelevelnum(j, k); auto map = FindMapByLevelNum(levnum); if (!map) map = AllocateMap(); map->SetFileName(parsebuffer.Data()); @@ -3222,26 +3222,26 @@ void FixMapinfo() if (file <= fileSystem.GetMaxIwadNum()) { auto maprec = FindMapByName("e1l7"); - if (maprec) maprec->nextLevel = levelnum(0, 4); + if (maprec) maprec->nextLevel = makelevelnum(0, 4); } } else if (isRR()) { - if (volumeList[0].flags & EF_GOTONEXTVOLUME) + if (true) { // RR goes directly to the second episode after E1L7 to continue the game. - auto maprec1 = FindMapByLevelNum(levelnum(0, 6)); // E1L7 must exist - auto maprec2 = FindMapByLevelNum(levelnum(0, 7)); // E1L8 must not exist + auto maprec1 = FindMapByLevelNum(makelevelnum(0, 6)); // E1L7 must exist + auto maprec2 = FindMapByLevelNum(makelevelnum(0, 7)); // E1L8 must not exist if (maprec1 && !maprec2) { - maprec1->nextLevel = levelnum(1, 0); + maprec1->nextLevel = makelevelnum(1, 0); } } if (!isRRRA()) { // RR does not define its final level and crudely hacked it into the progression. This puts it into the E2L8 slot so that the game can naturally progress there. - auto maprec1 = FindMapByLevelNum(levelnum(1, 6)); // E2L7 must exist - auto maprec2 = FindMapByLevelNum(levelnum(1, 7)); // E2L8 must not exist + auto maprec1 = FindMapByLevelNum(makelevelnum(1, 6)); // E2L7 must exist + auto maprec2 = FindMapByLevelNum(makelevelnum(1, 7)); // E2L8 must not exist auto maprec3 = FindMapByName("endgame"); // endgame must not have a map record already int num3 = fileSystem.FindFile("endgame.map"); // endgame.map must exist. if (maprec1 && !maprec2 && !maprec3 && num3 >= 0) @@ -3251,7 +3251,7 @@ void FixMapinfo() maprec->parTime = 0; maprec->SetFileName("endgame.map"); maprec->SetName("$TXT_CLOSEENCOUNTERS"); - maprec->levelNumber = levelnum(1, 7); + maprec->levelNumber = makelevelnum(1, 7); maprec->cluster = 2; } } diff --git a/source/games/duke/src/gameexec.cpp b/source/games/duke/src/gameexec.cpp index f730e631266..7c4d72aee53 100644 --- a/source/games/duke/src/gameexec.cpp +++ b/source/games/duke/src/gameexec.cpp @@ -3455,7 +3455,7 @@ int ParseState::parse(void) insptr++; // skip command volnume = GetGameVarID(*insptr++, g_ac, g_p); levnume = GetGameVarID(*insptr++, g_ac, g_p); - auto level = FindMapByLevelNum(levelnum(volnume - 1, levnume - 1)); + auto level = FindMapByLevelNum(makelevelnum(volnume - 1, levnume - 1)); if (level != nullptr) ChangeLevel(level, -1); break; @@ -3572,7 +3572,7 @@ int ParseState::parse(void) { insptr++; int music_select = *insptr++; - auto level = FindMapByLevelNum(levelnum(currentLevel->cluster, music_select)); + auto level = FindMapByLevelNum(makelevelnum(currentLevel->cluster, music_select)); if (level) S_PlayLevelMusic(level); break; } diff --git a/source/games/duke/src/player_r.cpp b/source/games/duke/src/player_r.cpp index 7726b35254b..8403e5922e3 100644 --- a/source/games/duke/src/player_r.cpp +++ b/source/games/duke/src/player_r.cpp @@ -1469,20 +1469,20 @@ int doincrements_r(struct player_struct* p) switch (currentLevel->levelNumber) { default: snd = 391; break; - case levelnum(0, 0): snd = isRRRA() ? 63 : 391; break; - case levelnum(0, 1): snd = 64; break; - case levelnum(0, 2): snd = 77; break; - case levelnum(0, 3): snd = 80; break; - case levelnum(0, 4): snd = 102; break; - case levelnum(0, 5): snd = 103; break; - case levelnum(0, 6): snd = 104; break; - case levelnum(1, 0): snd = 105; break; - case levelnum(1, 1): snd = 176; break; - case levelnum(1, 2): snd = 177; break; - case levelnum(1, 3): snd = 198; break; - case levelnum(1, 4): snd = 230; break; - case levelnum(1, 5): snd = 255; break; - case levelnum(1, 6): snd = 283; break; + case makelevelnum(0, 0): snd = isRRRA() ? 63 : 391; break; + case makelevelnum(0, 1): snd = 64; break; + case makelevelnum(0, 2): snd = 77; break; + case makelevelnum(0, 3): snd = 80; break; + case makelevelnum(0, 4): snd = 102; break; + case makelevelnum(0, 5): snd = 103; break; + case makelevelnum(0, 6): snd = 104; break; + case makelevelnum(1, 0): snd = 105; break; + case makelevelnum(1, 1): snd = 176; break; + case makelevelnum(1, 2): snd = 177; break; + case makelevelnum(1, 3): snd = 198; break; + case makelevelnum(1, 4): snd = 230; break; + case makelevelnum(1, 5): snd = 255; break; + case makelevelnum(1, 6): snd = 283; break; } S_PlayActorSound(snd, pact); } @@ -3401,7 +3401,7 @@ void processinput_r(int snum) } } else if (psectlotag == 7777) - if (currentLevel->levelNumber == levelnum(1, 6)) + if (currentLevel->levelNumber == makelevelnum(1, 6)) lastlevel = 1; if (psectlotag == 848 && sector[psect].floorpicnum == WATERTILE2) diff --git a/source/games/duke/src/premap.cpp b/source/games/duke/src/premap.cpp index ca78eeab5e9..b6589daa62a 100644 --- a/source/games/duke/src/premap.cpp +++ b/source/games/duke/src/premap.cpp @@ -832,7 +832,7 @@ static int LoadTheMap(MapRecord *mi, struct player_struct *p, int gamemode) SECRET_SetMapName(mi->DisplayName(), mi->name); STAT_NewLevel(mi->fileName); - if (isRR() && !isRRRA() && mi->levelNumber == levelnum(1, 1)) + if (isRR() && !isRRRA() && mi->levelNumber == makelevelnum(1, 1)) { for (int i = PISTOL_WEAPON; i < MAX_WEAPONS; i++) ps[0].ammo_amount[i] = 0; @@ -845,7 +845,7 @@ static int LoadTheMap(MapRecord *mi, struct player_struct *p, int gamemode) if (isRR()) prelevel_r(gamemode); else prelevel_d(gamemode); - if (isRRRA() && mi->levelNumber == levelnum(0, 2)) + if (isRRRA() && mi->levelNumber == makelevelnum(0, 2)) { for (int i = PISTOL_WEAPON; i < MAX_WEAPONS; i++) ps[0].ammo_amount[i] = 0; @@ -1001,7 +1001,7 @@ bool setnextmap(bool checksecretexit) { if (ud.secretlevel > 0) { - int newlevnum = levelnum(volfromlevelnum(currentLevel->levelNumber), ud.secretlevel-1); + int newlevnum = makelevelnum(volfromlevelnum(currentLevel->levelNumber), ud.secretlevel-1); map = FindMapByLevelNum(newlevnum); if (map) { diff --git a/source/games/duke/src/premap_r.cpp b/source/games/duke/src/premap_r.cpp index abf5e54e5ce..7b0af18db7a 100644 --- a/source/games/duke/src/premap_r.cpp +++ b/source/games/duke/src/premap_r.cpp @@ -366,11 +366,11 @@ static void cachegoodsprites(void) for (i = SMALLSMOKE; i < (SMALLSMOKE + 4); i++) tloadtile(i); - if (isRRRA() && currentLevel->levelNumber == levelnum(0, 4)) + if (isRRRA() && currentLevel->levelNumber == makelevelnum(0, 4)) { tloadtile(RRTILE2577); } - if (!isRRRA() && currentLevel->levelNumber == levelnum(1, 2)) + if (!isRRRA() && currentLevel->levelNumber == makelevelnum(1, 2)) { tloadtile(RRTILE3190); tloadtile(RRTILE3191); @@ -465,7 +465,7 @@ void prelevel_r(int g) if (isRRRA()) { - if (currentLevel->levelNumber == levelnum(1, 4)) + if (currentLevel->levelNumber == makelevelnum(1, 4)) ps[myconnectindex].steroids_amount = 0; for (j = 0; j < MAXSPRITES; j++)