From e05f900315deeaa107b5f9481ebae2d4f61e4aaf Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Mon, 26 Apr 2021 01:45:16 +0200 Subject: [PATCH] - added the data structures to hold the cutscene data. --- source/build/src/defs.cpp | 7 ++- source/common/engine/sc_man.cpp | 36 ++++++++++++ source/common/engine/sc_man.h | 25 +++++++++ source/core/gamecontrol.cpp | 9 +-- source/core/mapinfo.cpp | 29 ++++------ source/core/mapinfo.h | 46 ++++++++++++++-- source/core/menu/razemenu.cpp | 13 +++-- source/core/parsefuncs.h | 88 +++++++++++++++++++++++++++++- source/core/statusbar2.cpp | 2 +- source/games/blood/src/credits.cpp | 4 +- source/games/blood/src/levels.cpp | 10 ++-- source/games/blood/src/levels.h | 4 +- source/games/duke/src/funct.h | 1 + source/games/duke/src/game.cpp | 1 + source/games/duke/src/gamedef.cpp | 59 +++++++++++--------- source/games/sw/src/scrip2.cpp | 4 +- 16 files changed, 267 insertions(+), 71 deletions(-) diff --git a/source/build/src/defs.cpp b/source/build/src/defs.cpp index d141f2219..b629626be 100644 --- a/source/build/src/defs.cpp +++ b/source/build/src/defs.cpp @@ -207,7 +207,7 @@ enum scripttoken_t T_RFFDEFINEID, T_EXTRA, T_ROTATE, - T_SURFACE, T_VIEW, + T_SURFACE, T_VIEW, T_MAP, T_DEFINECUTSCENE, }; static int32_t lastmodelid = -1, lastvoxid = -1, modelskin = -1, lastmodelskin = -1, seenframe = 0; @@ -360,6 +360,8 @@ static int32_t defsparser(scriptfile *script) { "shadefactor", T_SHADEFACTOR }, { "newgamechoices", T_NEWGAMECHOICES }, { "rffdefineid", T_RFFDEFINEID }, // dummy + { "map", T_MAP }, + { "definecutscene", T_DEFINECUTSCENE }, }; script->SetNoOctals(true); @@ -376,6 +378,9 @@ static int32_t defsparser(scriptfile *script) auto pos = scriptfile_getposition(script); switch (tokn) { + case T_DEFINECUTSCENE: + parseDefineCutscene(*script, pos); + break; case T_ERROR: pos.Message(MSG_ERROR, "Unknown error"); break; diff --git a/source/common/engine/sc_man.cpp b/source/common/engine/sc_man.cpp index fa9a2f6c4..f24f25d92 100644 --- a/source/common/engine/sc_man.cpp +++ b/source/common/engine/sc_man.cpp @@ -1287,6 +1287,42 @@ void FScanner::AddSymbol(const char* name, double value) symbols.Insert(name, sym); } +//========================================================================== +// +// +// +//========================================================================== + +int FScanner::StartBraces(FScanner::SavedPos* braceend) +{ + if (CheckString("{")) + { + auto here = SavePos(); + SkipToEndOfBlock(); + *braceend = SavePos(); + RestorePos(here); + return 0; + } + else + { + ScriptError("'{' expected"); + return -1; + } +} + +//========================================================================== +// +// +// +//========================================================================== + +bool FScanner::FoundEndBrace(FScanner::SavedPos& braceend) +{ + auto here = SavePos(); + return here.SavedScriptPtr >= braceend.SavedScriptPtr; +} + + //========================================================================== // // a class that remembers a parser position diff --git a/source/common/engine/sc_man.h b/source/common/engine/sc_man.h index 3a894fcb3..56a4a8d82 100644 --- a/source/common/engine/sc_man.h +++ b/source/common/engine/sc_man.h @@ -94,6 +94,8 @@ public: inline void AddSymbol(const char* name, uint32_t value) { return AddSymbol(name, uint64_t(value)); } void AddSymbol(const char* name, double value); void SkipToEndOfBlock(); + int StartBraces(FScanner::SavedPos* braceend); + bool FoundEndBrace(FScanner::SavedPos& braceend); static FString TokenName(int token, const char *string=NULL); @@ -120,7 +122,30 @@ public: return true; } + bool GetNumber(int64_t& var, bool evaluate = false) + { + if (!GetNumber(evaluate)) return false; + var = BigNumber; + return true; + } + + bool GetString(FString& var) + { + if (!GetString()) return false; + var = String; + return true; + } + bool GetFloat(bool evaluate = false); + + bool GetFloat(double& var, bool evaluate = false) + { + if (!GetFloat(evaluate)) return false; + var = Float; + return true; + } + + void MustGetFloat(bool evaluate = false); bool CheckFloat(bool evaluate = false); diff --git a/source/core/gamecontrol.cpp b/source/core/gamecontrol.cpp index 2b475f59e..3f1778cea 100644 --- a/source/core/gamecontrol.cpp +++ b/source/core/gamecontrol.cpp @@ -599,15 +599,16 @@ void SetDefaultStrings() if ((g_gameType & GAMEFLAG_DUKE) && fileSystem.FindFile("E4L1.MAP") < 0) { // Pre-Atomic releases do not define this. - gVolumeNames[0] = "$L.A. Meltdown"; - gVolumeNames[1] = "$Lunar Apocalypse"; - gVolumeNames[2] = "$Shrapnel City"; - if (g_gameType & GAMEFLAG_SHAREWARE) gVolumeNames[3] = "$The Birth"; + volumeList[0].name = "$L.A. Meltdown"; + volumeList[1].name = "$Lunar Apocalypse"; + volumeList[2].name = "$Shrapnel City"; + if (g_gameType & GAMEFLAG_SHAREWARE) volumeList[3].name = "$The Birth"; gSkillNames[0] = "$Piece of Cake"; gSkillNames[1] = "$Let's Rock"; 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()) { diff --git a/source/core/mapinfo.cpp b/source/core/mapinfo.cpp index 609081f7f..3f23bca6c 100644 --- a/source/core/mapinfo.cpp +++ b/source/core/mapinfo.cpp @@ -41,30 +41,28 @@ #include "raze_sound.h" FString gSkillNames[MAXSKILLS]; -FString gVolumeNames[MAXVOLUMES]; -FString gVolumeSubtitles[MAXVOLUMES]; -int32_t gVolumeFlags[MAXVOLUMES]; int gDefaultVolume = 0, gDefaultSkill = 1; -MapRecord mapList[512]; // Due to how this gets used it needs to be static. EDuke defines 7 episode plus one spare episode with 64 potential levels each and relies on the static array which is freely accessible by scripts. -MapRecord *currentLevel; // level that is currently played. (The real level, not what script hacks modfifying the current level index can pretend.) +GlobalCutscenes globalCutscenes; +VolumeRecord volumeList[MAXVOLUMES]; +TArray mapList; +MapRecord *currentLevel; // level that is currently played. MapRecord* lastLevel; // Same here, for the last level. -unsigned int numUsedSlots; CCMD(listmaps) { - for (unsigned int i = 0; i < numUsedSlots; i++) + for (auto& map : mapList) { - int lump = fileSystem.FindFile(mapList[i].fileName); + int lump = fileSystem.FindFile(map.fileName); if (lump >= 0) { int rfnum = fileSystem.GetFileContainer(lump); - Printf("%s - %s (%s)\n", mapList[i].fileName.GetChars(), mapList[i].DisplayName(), fileSystem.GetResourceFileName(rfnum)); + Printf("%s - %s (%s)\n", map.fileName.GetChars(), map.DisplayName(), fileSystem.GetResourceFileName(rfnum)); } else { - Printf("%s - %s (defined but does not exist)\n", mapList[i].fileName.GetChars(), mapList[i].DisplayName()); + Printf("%s - %s (defined but does not exist)\n", map.fileName.GetChars(), map.DisplayName()); } } } @@ -72,9 +70,8 @@ CCMD(listmaps) MapRecord *FindMapByName(const char *nm) { - for (unsigned i = 0; i < numUsedSlots; i++) + for (auto& map : mapList) { - auto &map = mapList[i]; if (map.labelName.CompareNoCase(nm) == 0) { return ↦ @@ -86,9 +83,8 @@ MapRecord *FindMapByName(const char *nm) MapRecord *FindMapByLevelNum(int num) { - for (unsigned i = 0; i < numUsedSlots; i++) + for (auto& map : mapList) { - auto &map = mapList[i]; if (map.levelNumber == num) { return ↦ @@ -142,15 +138,14 @@ bool SetMusicForMap(const char* mapname, const char* music, bool namehack) MapRecord *AllocateMap() { - return &mapList[numUsedSlots++]; + return &mapList[mapList.Reserve(1)]; } MapRecord* SetupUserMap(const char* boardfilename, const char *defaultmusic) { - for (unsigned i = 0; i < numUsedSlots; i++) + for (auto& map : mapList) { - auto &map = mapList[i]; if (map.fileName.CompareNoCase(boardfilename) == 0) { return ↦ diff --git a/source/core/mapinfo.h b/source/core/mapinfo.h index 9a1e0050d..8b09db4ce 100644 --- a/source/core/mapinfo.h +++ b/source/core/mapinfo.h @@ -18,13 +18,11 @@ enum EMax enum EVolFlags { EF_HIDEFROMSP = 1, + EF_GOTONEXTVOLUME = 2, // for RR which continues the game in the next volume }; // These get filled in by the map definition parsers of the front ends. extern FString gSkillNames[MAXSKILLS]; -extern FString gVolumeNames[MAXVOLUMES]; -extern FString gVolumeSubtitles[MAXVOLUMES]; -extern int32_t gVolumeFlags[MAXVOLUMES]; extern int gDefaultVolume, gDefaultSkill; @@ -46,6 +44,42 @@ enum { MAX_MESSAGES = 32 }; +// Cutscene rules for maps are as follows: +// * when an episode is started, the episode intro will play, if none is defined, the map's intro will play. +// * when an episde is ended, the episode outro will play after the summary screen. +// * when a map ends, its own outro scene will play before the summary screen, if none is defined, use the default map outro handler. +// * when a new map starts after the summary screen, its own intro scene will play, if none is defined, use the default map intro handler. +// * setting any of these fields to 'none' will override any default and play nothing, even if a default is set. +class DObject; + +struct CutsceneDef +{ + FString video; + FString function; + int sound = 0; + int framespersec = 0; // only relevant for ANM. + + void Create(DObject* runner); +}; + +struct GlobalCutscenes +{ + CutsceneDef Intro; + CutsceneDef DefaultMapIntro; + CutsceneDef DefaultMapOutro; + FString MPSummaryScreen; + FString SummaryScreen; +}; + +struct VolumeRecord +{ + FString name; + FString subtitle; + CutsceneDef intro; + CutsceneDef outro; + int32_t flags = 0; +}; + struct MapRecord { int parTime = 0; @@ -54,6 +88,8 @@ struct MapRecord FString labelName; FString name; FString music; + CutsceneDef intro; + CutsceneDef outro; int cdSongId = -1; int flags = 0; int levelNumber = -1; @@ -101,8 +137,8 @@ struct MapRecord }; - -extern MapRecord mapList[512]; +extern GlobalCutscenes globalCutscenes; +extern VolumeRecord volumeList[MAXVOLUMES]; extern MapRecord *currentLevel; bool SetMusicForMap(const char* mapname, const char* music, bool namehack = false); diff --git a/source/core/menu/razemenu.cpp b/source/core/menu/razemenu.cpp index ddbf0be9e..89f8857cc 100644 --- a/source/core/menu/razemenu.cpp +++ b/source/core/menu/razemenu.cpp @@ -121,7 +121,7 @@ bool M_SetSpecialMenu(FName& menu, int param) if (gi->StartGame(NewGameStartupInfo)) { M_ClearMenus(); - STAT_StartNewGame(gVolumeNames[NewGameStartupInfo.Episode], NewGameStartupInfo.Skill); + STAT_StartNewGame(volumeList[NewGameStartupInfo.Episode].name, NewGameStartupInfo.Skill); inputState.ClearAllInput(); } return false; @@ -387,20 +387,21 @@ static void BuildEpisodeMenu() for (int i = 0; i < MAXVOLUMES; i++) { - if (gVolumeNames[i].IsNotEmpty() && !(gVolumeFlags[i] & EF_HIDEFROMSP)) + auto& vol = volumeList[i]; + if (vol.name.IsNotEmpty() && !(vol.flags & EF_HIDEFROMSP)) { int isShareware = ((g_gameType & GAMEFLAG_DUKE) && (g_gameType & GAMEFLAG_SHAREWARE) && i > 0); - auto it = CreateCustomListMenuItemText(ld->mXpos, y, ld->mLinespacing, gVolumeNames[i][0], - gVolumeNames[i], ld->mFont, CR_UNTRANSLATED, isShareware, NAME_Skillmenu, i); // font colors are not used, so hijack one for the shareware flag. + auto it = CreateCustomListMenuItemText(ld->mXpos, y, ld->mLinespacing, vol.name[0], + vol.name, ld->mFont, CR_UNTRANSLATED, isShareware, NAME_Skillmenu, i); // font colors are not used, so hijack one for the shareware flag. y += ld->mLinespacing; ld->mItems.Push(it); addedVolumes++; - if (gVolumeSubtitles[i].IsNotEmpty()) + if (vol.subtitle.IsNotEmpty()) { auto it = CreateCustomListMenuItemText(ld->mXpos, y, ld->mLinespacing * 6 / 10, 1, - gVolumeSubtitles[i], SmallFont, CR_GRAY, false, NAME_None, i); + vol.subtitle, SmallFont, CR_GRAY, false, NAME_None, i); y += ld->mLinespacing * 6 / 10; ld->mItems.Push(it); textadded = true; diff --git a/source/core/parsefuncs.h b/source/core/parsefuncs.h index 05b3aa87b..5c23b6e02 100644 --- a/source/core/parsefuncs.h +++ b/source/core/parsefuncs.h @@ -35,7 +35,6 @@ ** */ - void parseAnimTileRange(FScanner& sc, FScriptPosition& pos) { SetAnim set; @@ -46,3 +45,90 @@ void parseAnimTileRange(FScanner& sc, FScriptPosition& pos) processSetAnim("animtilerange", pos, set); } +//=========================================================================== +// +// +// +//=========================================================================== + +static void parseCutscene(FScanner& sc, CutsceneDef& cdef) +{ + FScanner::SavedPos eblockend; + + if (sc.StartBraces(&eblockend)) return; + FString sound; + while (!sc.FoundEndBrace(eblockend)) + { + sc.MustGetString(); + if (sc.Compare("video")) { sc.GetString(cdef.video); cdef.function = ""; } + else if (sc.Compare("function")) { sc.GetString(cdef.function); cdef.video = ""; } + else if (sc.Compare("sound")) sc.GetString(sound); + else if (sc.Compare("clear")) { cdef.function = "none"; cdef.video = ""; } // this means 'play nothing', not 'not defined'. + } + if (sound.IsNotEmpty()) + { + + } +} + +void parseDefineCutscene(FScanner& sc, FScriptPosition& pos) +{ + int scenenum = -1; + + if (!sc.GetString()) return; + + if (sc.Compare("intro")) + { + parseCutscene(sc, globalCutscenes.Intro); + } + if (sc.Compare("mapintro")) // sets the global default for a map entry handler. + { + parseCutscene(sc, globalCutscenes.DefaultMapIntro); + } + if (sc.Compare("mapoutro")) // sets the global default for a map exit handler. + { + parseCutscene(sc, globalCutscenes.DefaultMapOutro); + } + else if (sc.Compare("episode")) + { + FScanner::SavedPos eblockend; + sc.MustGetNumber(); + if (sc.Number < 1 || sc.Number > MAXVOLUMES) + { + sc.ScriptError("episode number %d out of range. Must be positive", sc.Number); + return; + } + int vol = sc.Number - 1; + + if (sc.StartBraces(&eblockend)) return; + while (!sc.FoundEndBrace(eblockend)) + { + sc.MustGetString(); + if (sc.Compare("intro")) parseCutscene(sc, volumeList[vol].intro); + else if (sc.Compare("outro")) parseCutscene(sc, volumeList[vol].outro); + else if (sc.Compare("flags")) sc.GetNumber(volumeList[vol].flags); + } + } + else if (sc.Compare("map")) + { + FScanner::SavedPos eblockend; + sc.MustGetString(); + auto maprec = FindMapByName(sc.String); + if (!maprec) + { + sc.ScriptError("%s: map not found", sc.String); + return; + } + if (sc.StartBraces(&eblockend)) return; + while (!sc.FoundEndBrace(eblockend)) + { + sc.MustGetString(); + if (sc.Compare("intro")) parseCutscene(sc, maprec->intro); + else if (sc.Compare("outro")) parseCutscene(sc, maprec->outro); + } + } + else if (sc.Compare("summary")) sc.GetString(globalCutscenes.SummaryScreen); + else if (sc.Compare("mpsummary")) sc.GetString(globalCutscenes.MPSummaryScreen); + +} + diff --git a/source/core/statusbar2.cpp b/source/core/statusbar2.cpp index 7ba06d80a..a4b9f68e8 100644 --- a/source/core/statusbar2.cpp +++ b/source/core/statusbar2.cpp @@ -240,7 +240,7 @@ void DBaseStatusBar::PrintAutomapInfo(FLevelStats& stats, bool forcetextfont) { y = 200 - stats.screenbottomspace - spacing; } - const auto &volname = gVolumeNames[volfromlevelnum(lev->levelNumber)]; + const auto &volname = volumeList[volfromlevelnum(lev->levelNumber)].name; if (volname.IsEmpty() && am_nameontop) y = 1; DrawText(twod, stats.font, stats.standardColor, 2 * hud_statscale, y, mapname, DTA_FullscreenScale, FSMode_ScaleToHeight, DTA_VirtualWidth, 320, DTA_VirtualHeight, 200, diff --git a/source/games/blood/src/credits.cpp b/source/games/blood/src/credits.cpp index 43f30c6d4..2fa3fa12c 100644 --- a/source/games/blood/src/credits.cpp +++ b/source/games/blood/src/credits.cpp @@ -144,7 +144,7 @@ void levelPlayIntroScene(int nEpisode, CompletionFunc completion) ambKillAll(); seqKillAll(); EPISODEINFO *pEpisode = &gEpisodeInfo[nEpisode]; - playSmk(pEpisode->cutsceneAName, pEpisode->cutsceneASound, pEpisode->at9028, completion); + playSmk(pEpisode->cutsceneAName, pEpisode->cutsceneASound, pEpisode->cutsceneAWave, completion); } void levelPlayEndScene(int nEpisode, CompletionFunc completion) @@ -156,7 +156,7 @@ void levelPlayEndScene(int nEpisode, CompletionFunc completion) ambKillAll(); seqKillAll(); EPISODEINFO *pEpisode = &gEpisodeInfo[nEpisode]; - playSmk(pEpisode->cutsceneBName, pEpisode->cutsceneBSound, pEpisode->at902c, completion); + playSmk(pEpisode->cutsceneBName, pEpisode->cutsceneBSound, pEpisode->cutsceneBWave, completion); } diff --git a/source/games/blood/src/levels.cpp b/source/games/blood/src/levels.cpp index 34f1c6d40..21286c08a 100644 --- a/source/games/blood/src/levels.cpp +++ b/source/games/blood/src/levels.cpp @@ -157,16 +157,16 @@ void levelLoadDefaults(void) break; EPISODEINFO *pEpisodeInfo = &gEpisodeInfo[i]; auto ep_str = BloodINI->GetKeyString(buffer, "Title", buffer); - gVolumeNames[i] = ep_str; // only keep one table for the names. Todo: Consolidate this across games. + volumeList[i].name = ep_str; strncpy(pEpisodeInfo->cutsceneAName, BloodINI->GetKeyString(buffer, "CutSceneA", ""), BMAX_PATH); - pEpisodeInfo->at9028 = BloodINI->GetKeyInt(buffer, "CutWavA", -1); - if (pEpisodeInfo->at9028 == 0) + pEpisodeInfo->cutsceneAWave = BloodINI->GetKeyInt(buffer, "CutWavA", -1); + if (pEpisodeInfo->cutsceneAWave == 0) strncpy(pEpisodeInfo->cutsceneASound, BloodINI->GetKeyString(buffer, "CutWavA", ""), BMAX_PATH); else pEpisodeInfo->cutsceneASound[0] = 0; strncpy(pEpisodeInfo->cutsceneBName, BloodINI->GetKeyString(buffer, "CutSceneB", ""), BMAX_PATH); - pEpisodeInfo->at902c = BloodINI->GetKeyInt(buffer, "CutWavB", -1); - if (pEpisodeInfo->at902c == 0) + pEpisodeInfo->cutsceneBWave = BloodINI->GetKeyInt(buffer, "CutWavB", -1); + if (pEpisodeInfo->cutsceneBWave == 0) strncpy(pEpisodeInfo->cutsceneBSound, BloodINI->GetKeyString(buffer, "CutWavB", ""), BMAX_PATH); else pEpisodeInfo->cutsceneBSound[0] = 0; diff --git a/source/games/blood/src/levels.h b/source/games/blood/src/levels.h index 27d65964d..eb707c396 100644 --- a/source/games/blood/src/levels.h +++ b/source/games/blood/src/levels.h @@ -79,8 +79,8 @@ struct EPISODEINFO unsigned int cutALevel : 4; char cutsceneAName[BMAX_PATH]; char cutsceneBName[BMAX_PATH]; - int at9028; - int at902c; + int cutsceneAWave; + int cutsceneBWave; char cutsceneASound[BMAX_PATH]; char cutsceneBSound[BMAX_PATH]; }; diff --git a/source/games/duke/src/funct.h b/source/games/duke/src/funct.h index 1785fabce..9beaf03eb 100644 --- a/source/games/duke/src/funct.h +++ b/source/games/duke/src/funct.h @@ -233,6 +233,7 @@ void PlayerColorChanged(void); bool movementBlocked(player_struct *p); void loadcons(); void recordoldspritepos(); +void FixMapinfo(); int* animateptr(int i); diff --git a/source/games/duke/src/game.cpp b/source/games/duke/src/game.cpp index da5c038cf..8e580c9f0 100644 --- a/source/games/duke/src/game.cpp +++ b/source/games/duke/src/game.cpp @@ -325,6 +325,7 @@ void GameInterface::app_init() screenpeek = myconnectindex; LoadDefinitions(); + FixMapinfo(); // Add some corrections. Can only be done after .def has been parsed. fi.InitFonts(); SetTileNames(); TileFiles.SetBackup(); diff --git a/source/games/duke/src/gamedef.cpp b/source/games/duke/src/gamedef.cpp index 830e83496..687d3dca6 100644 --- a/source/games/duke/src/gamedef.cpp +++ b/source/games/duke/src/gamedef.cpp @@ -1658,7 +1658,7 @@ int ConCompiler::parsecommand() textptr++, i++; } parsebuffer.Push(0); - gVolumeNames[j] = FStringTable::MakeMacro(parsebuffer.Data(), i); + volumeList[j].name = FStringTable::MakeMacro(parsebuffer.Data(), i); return 0; case concmd_defineskillname: popscriptvalue(); @@ -3206,8 +3206,16 @@ void loadcons() InitGameVarPointers(); ResetSystemDefaults(); S_WorldTourMappingsForOldSounds(); // create a sound mapping for World Tour. + S_CacheAllSounds(); + comp.setmusic(); +} + +void FixMapinfo() +{ + // todo: export this to proper map definition features. if (isWorldTour()) { + // fix broken secret exit in WT's super secret map. int num = fileSystem.CheckNumForName("e1l7.map"); int file = fileSystem.GetFileContainer(num); if (file <= fileSystem.GetMaxIwadNum()) @@ -3216,35 +3224,36 @@ void loadcons() if (maprec) maprec->nextLevel = levelnum(0, 4); } } - else if (isRRRA()) - { - // RRRA goes directly to the second episode after E1L7 to continue the game. - int num = fileSystem.CheckNumForName("e1l7.map"); - int file = fileSystem.GetFileContainer(num); - if (file <= fileSystem.GetMaxIwadNum()) - { - auto maprec = FindMapByName("e1l7"); - if (maprec) maprec->nextLevel = levelnum(1, 0); - } - } else if (isRR()) { - // 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)); - auto maprec2 = FindMapByLevelNum(levelnum(1, 7)); - auto maprec3 = FindMapByName("endgame"); - int num3 = fileSystem.FindFile("endgame.map"); - if (maprec1 && !maprec2 && !maprec3 && num3 >= 0) + if (volumeList[0].flags & EF_GOTONEXTVOLUME) { - auto maprec = AllocateMap(); - maprec->designerTime = 0; - maprec->parTime = 0; - maprec->SetFileName("endgame.map"); - maprec->SetName("$TXT_CLOSEENCOUNTERS"); - maprec->levelNumber = levelnum(1, 7); + // 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 + if (maprec1 && !maprec2) + { + maprec1->nextLevel = levelnum(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 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) + { + auto maprec = AllocateMap(); + maprec->designerTime = 0; + maprec->parTime = 0; + maprec->SetFileName("endgame.map"); + maprec->SetName("$TXT_CLOSEENCOUNTERS"); + maprec->levelNumber = levelnum(1, 7); + } } } - comp.setmusic(); } END_DUKE_NS diff --git a/source/games/sw/src/scrip2.cpp b/source/games/sw/src/scrip2.cpp index 30774a115..c6be81a83 100644 --- a/source/games/sw/src/scrip2.cpp +++ b/source/games/sw/src/scrip2.cpp @@ -523,13 +523,13 @@ void LoadCustomInfoFromScript(const char *filename) case CM_TITLE: { sc.MustGetString(); - if (curep != -1) gVolumeNames[curep] = sc.String; + if (curep != -1) volumeList[curep].name = sc.String; break; } case CM_SUBTITLE: { sc.MustGetString(); - if (curep != -1) gVolumeSubtitles[curep] = sc.String; + if (curep != -1) volumeList[curep].subtitle = sc.String; break; } default: