- added the data structures to hold the cutscene data.

This commit is contained in:
Christoph Oelckers 2021-04-26 01:45:16 +02:00
parent 57853bf8fe
commit e05f900315
16 changed files with 267 additions and 71 deletions

View file

@ -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;

View file

@ -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

View file

@ -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);

View file

@ -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())
{

View file

@ -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<MapRecord> 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 &map;
@ -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 &map;
@ -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 &map;

View file

@ -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);

View file

@ -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;

View file

@ -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);
}

View file

@ -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,

View file

@ -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);
}

View file

@ -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;

View file

@ -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];
};

View file

@ -233,6 +233,7 @@ void PlayerColorChanged(void);
bool movementBlocked(player_struct *p);
void loadcons();
void recordoldspritepos();
void FixMapinfo();
int* animateptr(int i);

View file

@ -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();

View file

@ -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

View file

@ -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: