diff --git a/.gitignore b/.gitignore index e071ea2e8a..80c2238ae1 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,5 @@ /src/r_drawersasm.obj /src/r_drawersasm.o .vs +/src/gl/unused +/mapfiles_release/*.map diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7ede5128bb..96910731c2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -958,6 +958,7 @@ set (PCH_SOURCES stats.cpp stringtable.cpp teaminfo.cpp + umapinfo.cpp v_blend.cpp v_collection.cpp v_draw.cpp diff --git a/src/g_level.h b/src/g_level.h index 2df5af9466..8b27dc68d2 100644 --- a/src/g_level.h +++ b/src/g_level.h @@ -253,7 +253,7 @@ struct FSpecialAction { FName Type; // this is initialized before the actors... uint8_t Action; - int Args[5]; // must allow 16 bit tags for 666 & 667! + int Args[5]; }; class DScroller; @@ -307,6 +307,11 @@ struct FExitText FString mText; FString mMusic; FString mBackdrop; + + FExitText(int def = 0, int order = -1, const FString &text = "", const FString &backdrop = "", const FString &music = "") + : mDefined(int16_t(def)), mOrder(int16_t(order)), mText(text), mMusic(music), mBackdrop(backdrop) + { + } }; struct level_info_t @@ -619,5 +624,8 @@ struct FEpisode extern TArray AllEpisodes; +int ParseUMapInfo(int lumpnum); +void CommitUMapinfo(level_info_t *defaultinfo); + #endif //__G_LEVEL_H__ diff --git a/src/g_mapinfo.cpp b/src/g_mapinfo.cpp index 5f22b25cf3..cb750f4a11 100644 --- a/src/g_mapinfo.cpp +++ b/src/g_mapinfo.cpp @@ -2206,7 +2206,7 @@ void G_ParseMapInfo (FString basemapinfo) parse.ParseMapInfo(baselump, gamedefaults, defaultinfo); } - static const char *mapinfonames[] = { "MAPINFO", "ZMAPINFO", NULL }; + static const char *mapinfonames[] = { "MAPINFO", "ZMAPINFO", "UMAPINFO", NULL }; int nindex; // Parse any extra MAPINFOs. @@ -2222,9 +2222,26 @@ void G_ParseMapInfo (FString basemapinfo) if (altlump >= 0) continue; } - FMapInfoParser parse(nindex == 1? FMapInfoParser::FMT_New : FMapInfoParser::FMT_Unknown); - level_info_t defaultinfo; - parse.ParseMapInfo(lump, gamedefaults, defaultinfo); + else if (nindex == 2) + { + // MAPINFO and ZMAPINFO will override UMAPINFO if in the same WAD. + int wad = Wads.GetLumpFile(lump); + int altlump = Wads.CheckNumForName("ZMAPINFO", ns_global, wad, true); + if (altlump >= 0) continue; + altlump = Wads.CheckNumForName("MAPINFO", ns_global, wad, true); + if (altlump >= 0) continue; + } + if (nindex != 2) + { + CommitUMapinfo(&gamedefaults); // UMPAINFOs are collected until a regular MAPINFO is found so that they properly use the base settings. + FMapInfoParser parse(nindex == 1 ? FMapInfoParser::FMT_New : FMapInfoParser::FMT_Unknown); + level_info_t defaultinfo; + parse.ParseMapInfo(lump, gamedefaults, defaultinfo); + } + else + { + ParseUMapInfo(lump); + } } if (AllEpisodes.Size() == 0) diff --git a/src/intermission/intermission_parse.cpp b/src/intermission/intermission_parse.cpp index 2c3114afe9..2841b70f71 100644 --- a/src/intermission/intermission_parse.cpp +++ b/src/intermission/intermission_parse.cpp @@ -724,6 +724,18 @@ FName FMapInfoParser::ParseEndGame() // //========================================================================== +FName MakeEndPic(const char *string) +{ + FString seqname; + seqname << "@EndPic_" << string; + FIntermissionDescriptor *desc = new FIntermissionDescriptor; + FIntermissionAction *action = new FIntermissionAction; + action->mBackground = string; + desc->mActions.Push(action); + ReplaceIntermission(seqname, desc); + return FName(seqname); +} + FName FMapInfoParser::CheckEndSequence() { const char *seqname = NULL; @@ -756,14 +768,7 @@ FName FMapInfoParser::CheckEndSequence() { ParseComma(); sc.MustGetString (); - FString seqname; - seqname << "@EndPic_" << sc.String; - FIntermissionDescriptor *desc = new FIntermissionDescriptor; - FIntermissionAction *action = new FIntermissionAction; - action->mBackground = sc.String; - desc->mActions.Push(action); - ReplaceIntermission(seqname, desc); - return FName(seqname); + return MakeEndPic(sc.String); } else if (sc.Compare("endbunny")) { diff --git a/src/sc_man.cpp b/src/sc_man.cpp index 6482fbc103..2a515d91bd 100644 --- a/src/sc_man.cpp +++ b/src/sc_man.cpp @@ -949,6 +949,76 @@ bool FScanner::Compare (const char *text) return (stricmp (text, String) == 0); } + +//========================================================================== +// +// Convenience helpers that parse an entire number including a leading minus or plus sign +// +//========================================================================== + +bool FScanner::ScanValue(bool allowfloat) +{ + bool neg = false; + if (!GetToken()) + { + return false; + } + if (TokenType == '-' || TokenType == '+') + { + neg = TokenType == '-'; + if (!GetToken()) + { + return false; + } + } + if (TokenType != TK_IntConst && (TokenType != TK_FloatConst || !allowfloat)) + { + return false; + } + if (neg) + { + Number = -Number; + Float = -Float; + } + return true; +} + +bool FScanner::CheckValue(bool allowfloat) +{ + auto savedstate = SavePos(); + bool res = ScanValue(allowfloat); + if (!res) RestorePos(savedstate); + return res; +} + +void FScanner::MustGetValue(bool allowfloat) +{ + if (!ScanValue(allowfloat)) ScriptError(allowfloat ? "Numeric constant expected" : "Integer constant expected"); +} + +bool FScanner::CheckBoolToken() +{ + if (CheckToken(TK_True)) + { + Number = 1; + Float = 1; + return true; + } + if (CheckToken(TK_False)) + { + Number = 0; + Float = 0; + return true; + } + return false; +} + +void FScanner::MustGetBoolToken() +{ + if (!CheckBoolToken()) + ScriptError("Expected true or false"); +} + //========================================================================== // // FScanner :: TokenName diff --git a/src/sc_man.h b/src/sc_man.h index 860935b8d5..44dd9370a5 100644 --- a/src/sc_man.h +++ b/src/sc_man.h @@ -57,6 +57,12 @@ public: bool GetFloat(); void MustGetFloat(); bool CheckFloat(); + + // Token based variant + bool CheckValue(bool allowfloat); + void MustGetValue(bool allowfloat); + bool CheckBoolToken(); + void MustGetBoolToken(); void UnGet(); @@ -107,6 +113,9 @@ protected: bool StateOptions; bool Escape; VersionInfo ParseVersion = { 0, 0, 0 }; // no ZScript extensions by default + + + bool ScanValue(bool allowfloat); }; enum diff --git a/src/umapinfo.cpp b/src/umapinfo.cpp new file mode 100644 index 0000000000..c8d4167403 --- /dev/null +++ b/src/umapinfo.cpp @@ -0,0 +1,431 @@ +//----------------------------------------------------------------------------- +// +// Copyright 2017 Christoph Oelckers +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/ +// +//----------------------------------------------------------------------------- + +#include +#include +#include +#include +#include "w_wad.h" +#include "g_level.h" +#include "sc_man.h" +#include "r_defs.h" +#include "p_setup.h" +#include "gi.h" + +FName MakeEndPic(const char *string); + +struct BossAction +{ + int type; + int special; + int tag; +}; + +struct UMapEntry +{ + FString MapName; + FString LevelName; + FString InterText; + FString InterTextSecret; + TArray BossActions; + + char levelpic[9]; + char nextmap[9]; + char nextsecret[9]; + char music[9]; + char skytexture[9]; + char endpic[9]; + char exitpic[9]; + char enterpic[9]; + char interbackdrop[9] = "FLOOR4_8"; + char intermusic[9]; + int partime; + int nointermission; +}; + +static TArray Maps; + + +// ----------------------------------------------- +// +// Parses a set of string and concatenates them +// +// ----------------------------------------------- + +static FString ParseMultiString(FScanner &scanner, int error) +{ + FString build; + + if (scanner.CheckToken(TK_Identifier)) + { + if (!stricmp(scanner.String, "clear")) + { + return "-"; + } + else + { + scanner.ScriptError("Either 'clear' or string constant expected"); + } + } + + do + { + scanner.MustGetToken(TK_StringConst); + if (build.Len() > 0) build += "\n"; + build += scanner.String; + } + while (scanner.CheckToken(',')); + return build; +} + +// ----------------------------------------------- +// +// Parses a lump name. The buffer must be at least 9 characters. +// +// ----------------------------------------------- + +static int ParseLumpName(FScanner &scanner, char *buffer) +{ + scanner.MustGetToken(TK_StringConst); + if (strlen(scanner.String) > 8) + { + scanner.ScriptError("String too long. Maximum size is 8 characters."); + return 0; + } + uppercopy(buffer, scanner.String); + return 1; +} + +// ----------------------------------------------- +// +// Parses a standard property that is already known +// These do not get stored in the property list +// but in dedicated struct member variables. +// +// ----------------------------------------------- + +static int ParseStandardProperty(FScanner &scanner, UMapEntry *mape) +{ + // find the next line with content. + // this line is no property. + + scanner.MustGetToken(TK_Identifier); + FString pname = scanner.String; + scanner.MustGetToken('='); + + if (!pname.CompareNoCase("levelname")) + { + scanner.MustGetToken(TK_StringConst); + mape->LevelName, scanner.String; + } + else if (!pname.CompareNoCase("next")) + { + ParseLumpName(scanner, mape->nextmap); + } + else if (!pname.CompareNoCase("nextsecret")) + { + ParseLumpName(scanner, mape->nextsecret); + } + else if (!pname.CompareNoCase("levelpic")) + { + ParseLumpName(scanner, mape->levelpic); + } + else if (!pname.CompareNoCase("skytexture")) + { + ParseLumpName(scanner, mape->skytexture); + } + else if (!pname.CompareNoCase("music")) + { + ParseLumpName(scanner, mape->music); + } + else if (!pname.CompareNoCase("endpic")) + { + ParseLumpName(scanner, mape->endpic); + } + else if (!pname.CompareNoCase("endcast")) + { + scanner.MustGetBoolToken(); + if (scanner.Number) strcpy(mape->endpic, "$CAST"); + else strcpy(mape->endpic, "-"); + } + else if (!pname.CompareNoCase("endbunny")) + { + scanner.MustGetBoolToken(); + if (scanner.Number) strcpy(mape->endpic, "$BUNNY"); + else strcpy(mape->endpic, "-"); + } + else if (!pname.CompareNoCase("endgame")) + { + scanner.MustGetBoolToken(); + if (scanner.Number) strcpy(mape->endpic, "!"); + else strcpy(mape->endpic, "-"); + } + else if (!pname.CompareNoCase("exitpic")) + { + ParseLumpName(scanner, mape->exitpic); + } + else if (!pname.CompareNoCase("enterpic")) + { + ParseLumpName(scanner, mape->enterpic); + } + else if (!pname.CompareNoCase("nointermission")) + { + scanner.MustGetBoolToken(); + mape->nointermission = scanner.Number; + } + else if (!pname.CompareNoCase("partime")) + { + scanner.MustGetValue(false); + mape->partime = TICRATE * scanner.Number; + } + else if (!pname.CompareNoCase("intertext")) + { + mape->InterText = ParseMultiString(scanner, 1); + if (mape->InterText.IsEmpty()) return 0; + } + else if (!pname.CompareNoCase("intertextsecret")) + { + mape->InterTextSecret = ParseMultiString(scanner, 1); + if (mape->InterTextSecret.IsEmpty()) return 0; + } + else if (!pname.CompareNoCase("interbackdrop")) + { + ParseLumpName(scanner, mape->interbackdrop); + } + else if (!pname.CompareNoCase("intermusic")) + { + ParseLumpName(scanner, mape->intermusic); + } + else if (!pname.CompareNoCase("episode")) + { + FString Episode = ParseMultiString(scanner, 1); + if (Episode.IsEmpty()) return 0; + //M_AddEpisode(mape->mapname, lname); + } + else if (!pname.CompareNoCase("bossaction")) + { + scanner.MustGetToken(TK_Identifier); + int classnum, special, tag; + if (!stricmp(scanner.String, "clear")) + { + // mark level free of boss actions + classnum = special = tag = -1; + mape->BossActions.Clear(); + } + else + { + FName type = scanner.String; + scanner.MustGetToken(','); + scanner.MustGetValue(false); + int special = scanner.Number; + scanner.MustGetToken(','); + scanner.MustGetValue(false); + int tag = scanner.Number; + // allow no 0-tag specials here, unless a level exit. + if (tag != 0 || special == 11 || special == 51 || special == 52 || special == 124) + { + FSpecialAction & bossact = mape->BossActions[mape->BossActions.Reserve(1)]; + line_t line; + maplinedef_t mld; + mld.special = special; + mld.tag = tag; + P_TranslateLineDef(&line, &mld); + bossact = { type, (uint8_t)line.special, {line.args[0], line.args[1],line.args[2],line.args[3],line.args[4]} }; + } + } + } + else + { + // Skip over all unknown properties. + do + { + if (!scanner.CheckFloat()) + { + scanner.MustGetAnyToken(); + if (scanner.TokenType != TK_Identifier && scanner.TokenType != TK_StringConst && scanner.TokenType != TK_True && scanner.TokenType != TK_False) + { + scanner.ScriptError("Identifier or value expecte3d"); + } + } + + } while (scanner.CheckToken(',')); + } + return 1; +} + +// ----------------------------------------------- +// +// Parses a complete map entry +// +// ----------------------------------------------- + +static int ParseMapEntry(FScanner &scanner, UMapEntry *val) +{ + scanner.MustGetToken(TK_Identifier); + + val->MapName, scanner.String; + scanner.MustGetToken('{'); + while(!scanner.CheckToken('}')) + { + ParseStandardProperty(scanner, val); + } + return 1; +} + +// ----------------------------------------------- +// +// Parses a complete UMAPINFO lump +// +// ----------------------------------------------- + +int ParseUMapInfo(int lumpnum) +{ + FScanner scanner(lumpnum); + unsigned int i; + + while (scanner.CheckToken(TK_Identifier)) + { + if (!scanner.Compare("map")) + { + scanner.ScriptError("'MAP' expected"); + } + UMapEntry parsed; + ParseMapEntry(scanner, &parsed); + + // Endpic overrides level exits. + if (parsed.endpic[0]) + { + parsed.nextmap[0] = parsed.nextsecret[0] = 0; + if (parsed.endpic[0] == '!') parsed.endpic[0] = 0; + } + /* + else if (!parsed.nextmap[0] && !parsed.endpic[0]) + { + if (!parsed.MapName.CompareNoCase("MAP30")) uppercopy(parsed.endpic, "$CAST"); + else if (!parsed.MapName.CompareNoCase("E1M8")) uppercopy(parsed.endpic, gameinfo.creditPages.Last()); + else if (!parsed.MapName.CompareNoCase("E2M8")) uppercopy(parsed.endpic, "VICTORY"); + else if (!parsed.MapName.CompareNoCase("E3M8")) uppercopy(parsed.endpic, "$BUNNY"); + else if (!parsed.MapName.CompareNoCase("E4M8")) uppercopy(parsed.endpic, "ENDPIC"); + else if (gameinfo.gametype == GAME_Chex && !parsed.MapName.CompareNoCase("E1M5")) uppercopy(parsed.endpic, "CREDIT"); + else + { + parsed.nextmap[0] = 0; // keep previous setting + } + } + */ + + // Does this property already exist? If yes, replace it. + for(i = 0; i < Maps.Size(); i++) + { + if (!parsed.MapName.Compare(Maps[i].MapName)) + { + Maps[i] = parsed; + return 1; + } + } + // Not found so create a new one. + Maps.Push(parsed); + + } + return 1; +} + + +// This will get called if after an UMAPINFO lump a regular (Z)MAPINFO is found or when MAPINFO parsing is complete. +void CommitUMapinfo(level_info_t *defaultinfo) +{ + for (auto &map : Maps) + { + auto levelinfo = FindLevelInfo(map.MapName); + if (levelinfo == nullptr) + { + *levelinfo = *defaultinfo; + if (map.MapName.IsNotEmpty()) levelinfo->MapName = map.MapName; + if (map.MapName.IsNotEmpty()) levelinfo->LevelName = map.LevelName; + if (map.levelpic[0]) levelinfo->PName = map.levelpic; + if (map.nextmap[0]) levelinfo->NextMap = map.nextmap; + else if (map.endpic[0]) + { + FName name; + + if (!stricmp(map.endpic, "$CAST")) + { + name = "INTER_CAST"; + } + else if (!stricmp(map.endpic, "$BUNNY")) + { + name = "INTER_BUNNY"; + } + else + { + name = MakeEndPic(map.endpic); + } + if (name != NAME_None) + { + levelinfo->NextMap.Format("enDSeQ%04x", int(name)); + } + } + + if (map.nextsecret[0]) levelinfo->NextSecretMap = map.nextsecret; + if (map.music[0]) + { + levelinfo->Music = map.music; + levelinfo->musicorder = 0; + } + if (map.skytexture[0]) + { + levelinfo->SkyPic1 = map.skytexture; + levelinfo->skyspeed1 = 0; + levelinfo->SkyPic2 = ""; + levelinfo->skyspeed2 = 0; + } + if (map.partime > 0) levelinfo->partime = map.partime; + if (map.enterpic[0]) levelinfo->EnterPic = map.enterpic; + if (map.exitpic[0]) levelinfo->ExitPic = map.exitpic; + if (map.intermusic[0]) + { + levelinfo->InterMusic = map.intermusic; + levelinfo->intermusicorder = 0; + } + if (map.BossActions.Size() > 0) levelinfo->specialactions = std::move(map.BossActions); + + const int exflags = FExitText::DEF_TEXT | FExitText::DEF_BACKDROP | FExitText::DEF_MUSIC; + if (map.InterText.IsNotEmpty()) + { + if (map.InterText.Compare("-") != 0) + levelinfo->ExitMapTexts[NAME_Normal] = { exflags, 0, map.InterText, map.interbackdrop, map.intermusic[0]? map.intermusic : gameinfo.intermissionMusic }; + else + levelinfo->ExitMapTexts[NAME_Normal] = { 0, 0 }; + } + if (map.InterTextSecret.IsNotEmpty()) + { + if (map.InterTextSecret.Compare("-") != 0) + levelinfo->ExitMapTexts[NAME_Secret] = { exflags, 0, map.InterTextSecret, map.interbackdrop, map.intermusic[0] ? map.intermusic : gameinfo.intermissionMusic }; + else + levelinfo->ExitMapTexts[NAME_Secret] = { 0, 0 }; + } + if (map.nointermission) levelinfo->flags |= LEVEL_NOINTERMISSION; + } + } + + + // All done. If we get here again, start fresh. + Maps.Clear(); + Maps.ShrinkToFit(); +} \ No newline at end of file