- Cutscene support in Blood.

Intro tested, rest to do.
This commit is contained in:
Christoph Oelckers 2021-04-28 20:16:13 +02:00
parent 869dbd70e5
commit eaf5e1fba5
20 changed files with 483 additions and 489 deletions

View file

@ -220,6 +220,7 @@ static void GameTicker()
case ga_mainmenu:
FX_StopAllSounds();
if (isBlood()) Mus_Stop();
case ga_mainmenunostopsound:
gi->FreeLevelData();
gamestate = GS_MENUSCREEN;

View file

@ -246,9 +246,10 @@ public:
public:
bool isvalid() { return !failed; }
VpxPlayer(FileReader& fr_, TArray<int>& animSnd_, int flags, int origframedelay, FString& error) : animSnd(std::move(animSnd_))
VpxPlayer(FileReader& fr_, TArray<int>& animSnd_, int flags_, int origframedelay, FString& error) : animSnd(std::move(animSnd_))
{
fr = std::move(fr_);
flags = flags_;
if (!ReadIVFHeader(origframedelay))
{
@ -552,13 +553,14 @@ public:
}
SmkPlayer(const char *fn, TArray<int>& ans, int flags) : animSnd(std::move(ans))
SmkPlayer(const char *fn, TArray<int>& ans, int flags_) : animSnd(std::move(ans))
{
hSMK = Smacker_Open(fn);
if (!hSMK.isValid)
{
return;
}
flags = flags_;
Smacker_GetFrameSize(hSMK, nWidth, nHeight);
pFrame.Resize(nWidth * nHeight + std::max(nWidth, nHeight));
nFrameRate = Smacker_GetFrameRate(hSMK);

View file

@ -357,7 +357,7 @@ bool ScreenJobValidate()
bool StartCutscene(CutsceneDef& cs, int flags, const CompletionFunc& completion_)
{
if (cs.function.IsNotEmpty() && cs.video.IsNotEmpty() && cs.function.CompareNoCase("none") != 0)
if ((cs.function.IsNotEmpty() || cs.video.IsNotEmpty()) && cs.function.CompareNoCase("none") != 0)
{
completion = completion_;
runner = CreateRunner();
@ -407,7 +407,9 @@ void PlayLogos(gameaction_t complete_ga, gameaction_t def_ga, bool stopmusic)
}
else
{
if (!StartCutscene(globalCutscenes.Intro, SJ_BLOCKUI, [=](bool) { gameaction = complete_ga; })) gameaction = def_ga;
if (!StartCutscene(globalCutscenes.Intro, SJ_BLOCKUI, [=](bool) {
gameaction = complete_ga;
})) gameaction = def_ga;
}
}

View file

@ -31,7 +31,6 @@
#include "src/callback.cpp"
#include "src/choke.cpp"
#include "src/controls.cpp"
#include "src/credits.cpp"
#include "src/db.cpp"
#include "src/dude.cpp"
#include "src/endgame.cpp"

View file

@ -49,6 +49,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "v_draw.h"
#include "texturemanager.h"
#include "statusbar.h"
#include "vm.h"
BEGIN_BLD_NS
@ -225,28 +226,10 @@ void StartLevel(MapRecord* level)
void NewLevel(MapRecord *sng, int skill)
{
auto completion = [=](bool = false)
{
if (skill != -1) gGameOptions.nDifficulty = skill;
gSkill = gGameOptions.nDifficulty;
StartLevel(sng);
gameaction = ga_level;
};
bool startedCutscene = false;
if (!(sng->flags & MI_USERMAP))
{
int episode = volfromlevelnum(sng->levelNumber);
int level = mapfromlevelnum(sng->levelNumber);
if (gEpisodeInfo[episode].cutALevel == level && gEpisodeInfo[episode].cutsceneAName[0])
{
levelPlayIntroScene(episode, completion);
startedCutscene = true;
}
}
if (!startedCutscene) completion(false);
}
void GameInterface::NewGame(MapRecord *sng, int skill, bool)
@ -330,35 +313,20 @@ void GameInterface::Ticker()
team_ticker[i] = 0;
}
if ((gGameOptions.uGameFlags & GF_AdvanceLevel) != 0)
int gf = gGameOptions.uGameFlags;
if (gf & GF_AdvanceLevel)
{
seqKillAll();
if (gGameOptions.uGameFlags & GF_EndGame)
if (gf & GF_EndGame)
{
STAT_Update(true);
if (gGameOptions.nGameType == 0)
{
auto completion = [](bool) {
gGameOptions.uGameFlags &= ~(GF_AdvanceLevel|GF_EndGame);
gameaction = ga_creditsmenu;
};
if (gGameOptions.uGameFlags & GF_PlayCutscene)
{
levelPlayEndScene(volfromlevelnum(currentLevel->levelNumber), completion);
}
else completion(false);
}
else
{
gGameOptions.uGameFlags &= ~(GF_AdvanceLevel|GF_EndGame);
}
CompleteLevel(nullptr);
}
else
{
STAT_Update(false);
EndLevel();
Mus_Stop();
// Fixme: Link maps, not episode/level pairs.
int ep = volfromlevelnum(currentLevel->levelNumber);
auto map = FindMapByLevelNum(levelnum(ep, gNextLevel));
@ -474,13 +442,24 @@ void GameInterface::app_init()
if (!tileInit(0, NULL))
I_FatalError("TILES###.ART files not found");
//----------
// There's a small problem here. We have a nasty circular dependency thanks to .def's all-inclusive mentality.
// snd may depend on rffdefineid in .def
// .def depends on level definitions.
// level definitions depend on sound to resolve cutscene sounds.
// Conclusion: All map related definitions need to be taken out of .def - meaning 'definecutscene' cannot be done in there
// Unless that is done no sound related data can be done with rffdefineid.
Printf(PRINT_NONOTIFY, "Initializing sound system\n");
sndInit();
levelLoadDefaults();
LoadDefinitions();
//---------
SetTileNames();
C_InitConback(TexMan.CheckForTexture("BACKTILE", ETextureType::Any), true, 0.25);
TileFiles.SetBackup();
powerupInit();
Printf(PRINT_NONOTIFY, "Loading cosine table\n");
trigInit();
Printf(PRINT_NONOTIFY, "Initializing view subsystem\n");
@ -496,9 +475,6 @@ void GameInterface::app_init()
gGameOptions.nGameType = 0;
UpdateNetworkMenus();
Printf(PRINT_NONOTIFY, "Initializing sound system\n");
sndInit();
gChoke.init(518, chokeCallback);
UpdateDacs(0, true);
@ -573,4 +549,48 @@ ReservedSpace GameInterface::GetReservedScreenSpace(int viewsize)
return new GameInterface;
}
enum
{
kLoadScreenCRC = -2051908571,
kLoadScreenWideBackWidth = 256,
kLoadScreenWideSideWidth = 128,
};
DEFINE_ACTION_FUNCTION(_Blood, OriginalLoadScreen)
{
static int bLoadScreenCrcMatch = -1;
if (bLoadScreenCrcMatch == -1) bLoadScreenCrcMatch = tileGetCRC32(kLoadScreen) == kLoadScreenCRC;
ACTION_RETURN_INT(bLoadScreenCrcMatch);
}
DEFINE_ACTION_FUNCTION(_Blood, PlayIntroMusic)
{
Mus_Play(nullptr, "PESTIS.MID", false);
return 0;
}
DEFINE_ACTION_FUNCTION(_Blood, sndStartSample)
{
PARAM_PROLOGUE;
PARAM_INT(id);
PARAM_INT(vol);
PARAM_INT(chan);
PARAM_BOOL(looped);
PARAM_INT(chanflags);
sndStartSample(id, vol, chan, looped, EChanFlags::FromInt(chanflags));
return 0;
}
DEFINE_ACTION_FUNCTION(_Blood, sndStartSampleNamed)
{
PARAM_PROLOGUE;
PARAM_STRING(id);
PARAM_INT(vol);
PARAM_INT(chan);
sndStartSample(id, vol, chan);
return 0;
}
END_BLD_NS

View file

@ -1,164 +0,0 @@
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 Nuke.YKT
This file is part of NBlood.
NBlood is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.
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, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
//-------------------------------------------------------------------------
#include "ns.h" // Must come before everything else!
#include "build.h"
#include "compat.h"
#include "SmackerDecoder.h"
#include "blood.h"
#include "animtexture.h"
#include "raze_sound.h"
#include "v_2ddrawer.h"
#include "screenjob.h"
#include "gamestate.h"
#include "razemenu.h"
BEGIN_BLD_NS
#if 0
class DBloodIntroImage : public DImageScreen
{
bool mus;
public:
DBloodIntroImage(int tilenum, int flags = 0, bool withmusic = false) : DImageScreen(tilenum, flags), mus(withmusic)
{}
void Start() override
{
sndStartSample("THUNDER2", 128, -1);
if (mus) sndPlaySpecialMusicOrNothing(MUS_INTRO);
}
};
#endif
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void playlogos()
{
#if 0
TArray<DScreenJob*> jobs;
static AnimSound logosound[] =
{
{ 1, -1 },
{ -1, -1 },
{ 1, -1 },
{ -1,-1 }
};
if (logosound[0].soundnum == -1)
{
logosound[0].soundnum = S_FindSound("logo.wav");
logosound[2].soundnum = S_FindSound("gt.wav");
}
if (!userConfig.nologo)
{
if (fileSystem.FindFile("logo.smk") != -1)
{
jobs.Push(PlayVideo("logo.smk", &logosound[0], 0));
}
else
{
jobs.Push(Create<DBloodIntroImage>(2050));
}
if (fileSystem.FindFile("gti.smk") != -1)
{
jobs.Push(PlayVideo("gti.smk", &logosound[2], 0));
}
else
{
jobs.Push(Create<DBloodIntroImage>(2052));
}
}
jobs.Push(Create<DBlackScreen>(1));
jobs.Push(Create<DBloodIntroImage>(2518, DScreenJob::fadein, true));
RunScreenJob(jobs, [](bool) {
Mus_Stop();
gameaction = ga_mainmenu;
}, SJ_BLOCKUI);
#endif
}
void playSmk(const char *smk, const char *wav, int wavid, CompletionFunc func)
{
#if 0
TArray<DScreenJob*> jobs;
static AnimSound smksound[] =
{
{ 1, -1 },
{ -1, -1 },
};
int id = S_FindSoundByResID(wavid);
if (id <= 0)
{
FString wavv = wav;
FixPathSeperator(wavv);
id = S_FindSound(wavv);
// Strip the drive letter and retry.
if (id <= 0 && wavv.Len() > 3 && wavv[1] == ':' && isalpha(wavv[0]) && wavv[2] == '/')
{
id = S_FindSound(wavv.GetChars() + 3);
}
}
FString smkk = smk;
FixPathSeperator(smkk);
smksound[0].soundnum = id;
jobs.Push(PlayVideo(smkk, smksound, nullptr));
RunScreenJob(jobs, func);
#endif
}
void levelPlayIntroScene(int nEpisode, CompletionFunc completion)
{
Mus_Stop();
sndKillAllSounds();
sfxKillAllSounds();
ambKillAll();
seqKillAll();
EPISODEINFO *pEpisode = &gEpisodeInfo[nEpisode];
playSmk(pEpisode->cutsceneAName, pEpisode->cutsceneASound, pEpisode->cutsceneAWave, completion);
}
void levelPlayEndScene(int nEpisode, CompletionFunc completion)
{
gGameOptions.uGameFlags &= ~GF_PlayCutscene;
Mus_Stop();
sndKillAllSounds();
sfxKillAllSounds();
ambKillAll();
seqKillAll();
EPISODEINFO *pEpisode = &gEpisodeInfo[nEpisode];
playSmk(pEpisode->cutsceneBName, pEpisode->cutsceneBSound, pEpisode->cutsceneBWave, completion);
}
END_BLD_NS

View file

@ -36,158 +36,27 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
BEGIN_BLD_NS
enum
{
kLoadScreenCRC = -2051908571,
kLoadScreenWideBackWidth = 256,
kLoadScreenWideSideWidth = 128,
};
static int bLoadScreenCrcMatch = -1;
static void drawTextScreenBackground(void)
{
if (bLoadScreenCrcMatch == -1) bLoadScreenCrcMatch = tileGetCRC32(kLoadScreen) == kLoadScreenCRC;
if (bLoadScreenCrcMatch)
{
if (ActiveRatio(twod->GetWidth(), twod->GetHeight()) < 1.34f)
{
DrawTexture(twod, tileGetTexture(kLoadScreen), 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit43, TAG_DONE);
}
else
{
int width = scale(twod->GetWidth(), 240, twod->GetHeight());
int nCount = (width + kLoadScreenWideBackWidth - 1) / kLoadScreenWideBackWidth;
for (int i = 0; i < nCount; i++)
{
DrawTexture(twod, tileGetTexture(kLoadScreenWideBack), (i * kLoadScreenWideBackWidth), 0,
DTA_VirtualWidth, width, DTA_VirtualHeight, 200, DTA_KeepRatio, true, TAG_DONE);
}
DrawTexture(twod, tileGetTexture(kLoadScreenWideLeft), 0, 0, DTA_VirtualWidth, width, DTA_VirtualHeight, 200, DTA_KeepRatio, true, DTA_TopLeft, true, TAG_DONE);
DrawTexture(twod, tileGetTexture(kLoadScreenWideRight), width - tileWidth(kLoadScreenWideRight), 0, DTA_TopLeft, true,
DTA_VirtualWidth, width, DTA_VirtualHeight, 200, DTA_KeepRatio, true, TAG_DONE);
DrawTexture(twod, tileGetTexture(kLoadScreenWideMiddle), (width - tileWidth(kLoadScreenWideMiddle)) / 2, 0, DTA_TopLeft, true,
DTA_VirtualWidth, width, DTA_VirtualHeight, 200, DTA_KeepRatio, true, TAG_DONE);
}
}
else
{
DrawTexture(twod, tileGetTexture(kLoadScreen), 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit43, TAG_DONE);
}
}
// One these screens get scriptified this should use the version in BloodMenuDelegate.
static void DrawCaption(const char* text)
{
double scalex = 1.; // Expand the box if the text is longer
int width = BigFont->StringWidth(text);
int boxwidth = tileWidth(2038);
if (boxwidth - 10 < width) scalex = double(width) / (boxwidth - 10);
DrawTexture(twod, tileGetTexture(2038, true), 160, 20, DTA_FullscreenScale, FSMode_Fit320x200Top, DTA_CenterOffsetRel, true, DTA_ScaleX, scalex, TAG_DONE);
DrawText(twod, BigFont, CR_UNDEFINED, 160 - width / 2, 20 - tileHeight(4193) / 2, text, DTA_FullscreenScale, FSMode_Fit320x200Top, TAG_DONE);
}
#if 0 // public DScreenJob
class DBloodSummaryScreen : public DSkippableScreenJob
{
void DrawKills(void)
{
char pBuffer[40];
if (gGameOptions.nGameType == 0)
{
viewDrawText(1, FStringf("%s:", GStrings("KILLS")), 75, 50, -128, 0, 0, 1);
mysnprintf(pBuffer, 40,"%2d", gKillMgr.Kills);
viewDrawText(1, pBuffer, 160, 50, -128, 0, 0, 1);
viewDrawText(1, GStrings("OF"), 190, 50, -128, 0, 0, 1);
mysnprintf(pBuffer, 40, "%2d", gKillMgr.TotalKills);
viewDrawText(1, pBuffer, 220, 50, -128, 0, 0, 1);
}
else
{
viewDrawText(3, "#", 85, 35, -128, 0, 0, 1);
viewDrawText(3, GStrings("NAME"), 100, 35, -128, 0, 0, 1);
viewDrawText(3, GStrings("FRAGS"), 210, 35, -128, 0, 0, 1);
int nStart = 0;
int nEnd = kMaxPlayers;
for (int i = nStart; i < nEnd; i++) if (playeringame[i])
{
mysnprintf(pBuffer, 40, "%-2d", i);
viewDrawText(3, pBuffer, 85, 50 + 8 * i, -128, 0, 0, 1);
mysnprintf(pBuffer, 40, "%s", PlayerName(i));
viewDrawText(3, pBuffer, 100, 50 + 8 * i, -128, 0, 0, 1);
mysnprintf(pBuffer, 40, "%d", gPlayer[i].fragCount);
viewDrawText(3, pBuffer, 210, 50 + 8 * i, -128, 0, 0, 1);
}
}
}
void DrawSecrets(void)
{
char pBuffer[40];
viewDrawText(1, FStringf("%s:", GStrings("TXT_SECRETS")), 75, 70, -128, 0, 0, 1);
mysnprintf(pBuffer, 40, "%2d", gSecretMgr.Founds);
viewDrawText(1, pBuffer, 160, 70, -128, 0, 0, 1);
viewDrawText(1, GStrings("OF"), 190, 70, -128, 0, 0, 1);
mysnprintf(pBuffer, 40, "%2d", gSecretMgr.Total);
viewDrawText(1, pBuffer, 220, 70, -128, 0, 0, 1);
if (gSecretMgr.Super > 0)
viewDrawText(1, GStrings("TXT_SUPERSECRET"), 160, 100, -128, 2, 1, 1);
}
void Draw(double) override
{
drawTextScreenBackground();
if (gGameOptions.nGameType == 0)
{
DrawCaption(GStrings("TXTB_LEVELSTATS"));
if (bPlayerCheated)
{
auto text = GStrings("TXTB_CHEATED");
int font = 3;
if (!SmallFont2->CanPrint(text)) font = 0;
viewDrawText(font, text, 160, 32, -128, 0, 1, font == 3);
}
DrawKills();
DrawSecrets();
}
else
{
DrawCaption(GStrings("TXTB_FRAGSTATS"));
DrawKills();
}
int myclock = ticks * 120 / GameTicRate;
if ((myclock & 32))
{
auto text = GStrings("PRESSKEY");
int font = 3;
if (!SmallFont2->CanPrint(text)) font = 0;
viewDrawText(font, text, 160, 134, -128, 0, 1, font == 3);
}
}
};
#endif
void GameInterface::LevelCompleted(MapRecord *map, int skill)
{
#if 0
TArray<DScreenJob*> job(1, true);
job[0] = Create<DBloodSummaryScreen>();
sndStartSample(268, 128, -1, false, CHANF_UI);
EndLevel();
Mus_Stop();
RunScreenJob(job, [=](bool)
SummaryInfo info{};
info.kills = gKillMgr.Kills;
info.maxkills = gKillMgr.TotalKills;
info.secrets = gSecretMgr.Founds;
info.maxsecrets = gSecretMgr.Total;
info.time = gSecretMgr.Super;
info.endofgame = map == nullptr;
ShowIntermission(currentLevel, map, &info, [=](bool)
{
soundEngine->StopAllChannels();
gameaction = ga_nextlevel;
gameaction = map? ga_nextlevel : ga_creditsmenu;
if (!map) gGameOptions.uGameFlags &= ~(GF_AdvanceLevel | GF_EndGame);
});
#else
gameaction = ga_nextlevel;
#endif
}
@ -278,40 +147,4 @@ void SerializeGameStats(FSerializer& arc)
CSecretMgr gSecretMgr;
CKillMgr gKillMgr;
#if 0
class DBloodLoadScreen : public DScreenJob
{
const char* pzLoadingScreenText1;
MapRecord* rec;
public:
DBloodLoadScreen(const char* caption, MapRecord* maprec) : DScreenJob(), rec(maprec)
{
if (gGameOptions.nGameType == 0) pzLoadingScreenText1 = GStrings("TXTB_LLEVEL");
else pzLoadingScreenText1 = GStrings(FStringf("TXTB_NETGT%d", gGameOptions.nGameType));
}
void Draw(double) override
{
twod->ClearScreen();
drawTextScreenBackground();
DrawCaption(pzLoadingScreenText1);
viewDrawText(1, rec->DisplayName(), 160, 50, -128, 0, 1, 1);
auto text = GStrings("TXTB_PLSWAIT");
int font = 3;
if (!SmallFont2->CanPrint(text)) font = 0;
viewDrawText(font, GStrings("TXTB_PLSWAIT"), 160, 134, -128, 0, 1, font == 3);
}
};
void loadscreen(const char *caption, MapRecord* rec, CompletionFunc func)
{
TArray<DScreenJob*> job(1, true);
job[0] = Create<DBloodLoadScreen>(caption, rec);
RunScreenJob(job, func);
}
#endif
END_BLD_NS

View file

@ -38,10 +38,7 @@ GAMEOPTIONS gSingleGameOptions = {
0, 2, 0, 0, 0, 0, 0, 0, 2, 3600, 1800, 1800, 7200
};
EPISODEINFO gEpisodeInfo[kMaxEpisodes+1];
int gSkill = 2;
int gEpisodeCount;
int gNextLevel; // fixme: let this contain a full level number.
char BloodIniFile[BMAX_PATH] = "BLOOD.INI";
@ -142,39 +139,45 @@ static const char* DefFile(void)
return userConfig.DefaultCon.IsNotEmpty() ? userConfig.DefaultCon.GetChars() : "blood.ini";
}
static FString cleanPath(const char* pth)
{
FString path = pth;
FixPathSeperator(path);
if (FileExists(path)) return path;
if (path.Len() > 3 && path[1] == ':' && isalpha(path[0]) && path[2] == '/')
{
return path.Mid(3);
}
return path;
}
void levelLoadDefaults(void)
{
char buffer[64];
char buffer2[16];
int cutALevel = 0;
levelInitINI(DefFile());
memset(gEpisodeInfo, 0, sizeof(gEpisodeInfo));
quoteMgr.InitializeQuote(MUS_INTRO, "PESTIS.MID");
int i;
for (i = 0; i < kMaxEpisodes; i++)
{
sprintf(buffer, "Episode%d", i+1);
if (!BloodINI->SectionExists(buffer))
break;
EPISODEINFO *pEpisodeInfo = &gEpisodeInfo[i];
CutsceneDef &csB = volumeList[i].outro;
auto ep_str = BloodINI->GetKeyString(buffer, "Title", buffer);
volumeList[i].name = ep_str;
strncpy(pEpisodeInfo->cutsceneAName, BloodINI->GetKeyString(buffer, "CutSceneA", ""), BMAX_PATH);
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->cutsceneBWave = BloodINI->GetKeyInt(buffer, "CutWavB", -1);
if (pEpisodeInfo->cutsceneBWave == 0)
strncpy(pEpisodeInfo->cutsceneBSound, BloodINI->GetKeyString(buffer, "CutWavB", ""), BMAX_PATH);
else
pEpisodeInfo->cutsceneBSound[0] = 0;
pEpisodeInfo->bloodbath = BloodINI->GetKeyInt(buffer, "BloodBathOnly", 0);
pEpisodeInfo->cutALevel = BloodINI->GetKeyInt(buffer, "CutSceneALevel", 0);
if (pEpisodeInfo->cutALevel > 0)
pEpisodeInfo->cutALevel--;
csB.video = cleanPath(BloodINI->GetKeyString(buffer, "CutSceneB", ""));
csB.sound = soundEngine->FindSoundByResID(BloodINI->GetKeyInt(buffer, "CutWavB", -1) + 0x40000000);
if (csB.sound == 0)
csB.sound = soundEngine->FindSound(cleanPath(BloodINI->GetKeyString(buffer, "CutWavB", "")));
//pEpisodeInfo->bloodbath = BloodINI->GetKeyInt(buffer, "BloodBathOnly", 0);
cutALevel = BloodINI->GetKeyInt(buffer, "CutSceneALevel", 0) - 1;
if (cutALevel < 0) cutALevel = 0;
int j;
for (j = 0; j < kMaxLevels; j++)
{
@ -189,10 +192,16 @@ void levelLoadDefaults(void)
pLevelInfo->labelName = pMap;
pLevelInfo->fileName.Format("%s.map", pMap);
levelLoadMapInfo(BloodINI, pLevelInfo, pMap, i, j);
if (j == cutALevel)
{
CutsceneDef& csA = pLevelInfo->intro;
csA.video = cleanPath(BloodINI->GetKeyString(buffer, "CutSceneA", ""));
csA.sound = soundEngine->FindSoundByResID(BloodINI->GetKeyInt(buffer, "CutWavA", -1) + 0x40000000);
if (csA.sound == 0)
csA.sound = soundEngine->FindSound(cleanPath(BloodINI->GetKeyString(buffer, "CutWavA", "")));
}
}
pEpisodeInfo->nLevels = j;
}
gEpisodeCount = i;
}
void levelGetNextLevels(int *pnEndingA, int *pnEndingB)
@ -208,20 +217,17 @@ void levelGetNextLevels(int *pnEndingA, int *pnEndingB)
*pnEndingB = nEndingB;
}
void levelEndLevel(int arg)
void levelEndLevel(int secret)
{
int nEndingA, nEndingB;
auto episode = volfromlevelnum(currentLevel->levelNumber);
EPISODEINFO *pEpisodeInfo = &gEpisodeInfo[episode];
gGameOptions.uGameFlags |= GF_AdvanceLevel;
levelGetNextLevels(&nEndingA, &nEndingB);
switch (arg)
switch (secret)
{
case 0:
if (nEndingA == -1)
{
if (pEpisodeInfo->cutsceneBName[0])
gGameOptions.uGameFlags |= GF_PlayCutscene;
gGameOptions.uGameFlags |= GF_EndGame;
}
else
@ -230,17 +236,8 @@ void levelEndLevel(int arg)
case 1:
if (nEndingB == -1)
{
if (episode + 1 < gEpisodeCount)
{
if (pEpisodeInfo->cutsceneBName[0])
gGameOptions.uGameFlags |= GF_PlayCutscene;
gGameOptions.uGameFlags |= GF_EndGame;
}
else
{
gGameOptions.uGameFlags |= GF_AdvanceLevel;
}
}
else
gNextLevel = nEndingB;
break;

View file

@ -67,38 +67,16 @@ struct GAMEOPTIONS {
#pragma pack(pop)
enum {
MUS_INTRO = 0,
MUS_LOADING = 1,
};
struct EPISODEINFO
{
int nLevels;
unsigned int bloodbath : 1;
unsigned int cutALevel : 4;
char cutsceneAName[BMAX_PATH];
char cutsceneBName[BMAX_PATH];
int cutsceneAWave;
int cutsceneBWave;
char cutsceneASound[BMAX_PATH];
char cutsceneBSound[BMAX_PATH];
};
extern EPISODEINFO gEpisodeInfo[];
extern GAMEOPTIONS gSingleGameOptions;
extern GAMEOPTIONS gGameOptions;
extern int gSkill;
extern char BloodIniFile[];
extern bool bINIOverride;
extern int gEpisodeCount;
extern int gNextLevel;
extern bool gGameStarted;
void levelInitINI(const char *pzIni);
void levelOverrideINI(const char *pzIni);
void levelPlayIntroScene(int nEpisode, CompletionFunc completion);
void levelPlayEndScene(int nEpisode, CompletionFunc completion);
void levelSetupSecret(int nCount);
void levelTriggerSecret(int nSecret);
void CheckSectionAbend(const char *pzSection);

View file

@ -267,10 +267,8 @@ static int parseArgs(char *pzArgs, int *nArg1, int *nArg2)
{
if (!nArg1 || !nArg2 || strlen(pzArgs) < 3)
return -1;
*nArg1 = pzArgs[0] - '0' - 1;
*nArg1 = pzArgs[0] - '0';
*nArg2 = (pzArgs[1] - '0')*10+(pzArgs[2]-'0') - 1;
*nArg1 = ClipRange(*nArg1, 0, gEpisodeCount-1);
*nArg2 = ClipRange(*nArg2, 0, gEpisodeInfo[*nArg1].nLevels-1);
return 2;
}

View file

@ -3,6 +3,7 @@ x(MENUBAR, 2038)
x(BackTile, 253)
x(Monolithscreen, 2050)
x(GTIScreen, 2052)
x(TitleScreen, 2518)
x(CrosshairTile, 2319)
x(LoadScreen, 2049)

View file

@ -400,10 +400,6 @@ void powerupClear(PLAYER *pPlayer)
}
}
void powerupInit(void)
{
}
int packItemToPowerup(int nPack)
{
int nPowerUp = -1;

View file

@ -241,7 +241,6 @@ void powerupDeactivate(PLAYER *pPlayer, int nPowerUp);
void powerupSetState(PLAYER *pPlayer, int nPowerUp, char bState);
void powerupProcess(PLAYER *pPlayer);
void powerupClear(PLAYER *pPlayer);
void powerupInit(void);
int packItemToPowerup(int nPack);
int powerupToPackItem(int nPowerUp);
char packAddItem(PLAYER *pPlayer, unsigned int nPack);

View file

@ -63,7 +63,6 @@ inline bool S_IsSoundValid(int num)
void S_PlayRRMusic(int newTrack = -1);
void S_PlayBonusMusic();
void S_PlayLevelMusic(MapRecord* mi);
void S_PlaySpecialMusic(unsigned int);
void S_ContinueLevelMusic(void);
// Placeholders.

View file

@ -59,3 +59,18 @@ tilefromtexture 9277 { file "tiles/9277.png" }
tilefromtexture 9278 { file "tiles/9278.png" }
tilefromtexture 9279 { file "tiles/9279.png" }
tilefromtexture 9280 { file "tiles/9280.png" }
// Cutscene definitions for Blood
definecutscene intro
{
function BloodCutscenes.BuildIntro
}
definecutscene loading
{
function BloodCutscenes.BuildLoading
}
definecutscene summary BloodCutscenes.BuildSPSummary
definecutscene mpsummary BloodCutscenes.BuildMPSummary

View file

@ -5,8 +5,7 @@ definecutscene intro
function RRCutscenes.BuildIntro
}
// The 'E1End' cutscene is tied to the level as it is not an end-of-game event.
definecutscene map e1l7
definecutscene episode 1
{
outro
{

View file

@ -33,6 +33,8 @@ version "4.3"
#include "zscript/games/duke/ui/screens.zs"
#include "zscript/games/duke/ui/cutscenes.zs"
#include "zscript/games/duke/ui/menu.zs"
#include "zscript/games/blood/bloodgame.zs"
#include "zscript/games/blood/ui/menu.zs"
#include "zscript/games/blood/ui/screens.zs"
#include "zscript/games/sw/ui/menu.zs"
#include "zscript/games/exhumed/ui/menu.zs"

View file

@ -0,0 +1,8 @@
// contains all global Blood definitions
struct Blood native
{
native static void PlayIntroMusic();
native static bool OriginalLoadScreen(); // doing it generically would necessitate exporting the tile manage which we do not want.
native static void sndStartSample(int resid, int volume, int channel, bool loop = false, int chanflags = 0);
native static void sndStartSampleNamed(String sname, int volume, int channel);
}

View file

@ -9,25 +9,7 @@ class BloodMenuDelegate : RazeMenuDelegate
{
override int DrawCaption(String title, Font fnt, int y, bool drawit)
{
let font = generic_ui? NewConsoleFont : BigFont; // this ignores the passed font intentionally.
let texid = TexMan.CheckForTexture("MENUBAR");
let texsize = TexMan.GetScaledSize(texid);
let fonth = font.GetGlyphHeight("A");
if (drawit)
{
int width = font.StringWidth(title);
if (texid.isValid())
{
double scalex = 1.; // Expand the box if the text is longer
if (texsize.X - 10 < width) scalex = width / (texsize.X - 10);
screen.DrawTexture(texid, false, 160, 20, DTA_FullscreenScale, FSMode_Fit320x200Top, DTA_CenterOffsetRel, true, DTA_ScaleX, scalex);
}
screen.DrawText(font, Font.CR_UNDEFINED, 160 - width / 2, 20 - fonth / 2, title, DTA_FullscreenScale, FSMode_Fit320x200Top);
}
double fx, fy, fw, fh;
[fx, fy, fw, fh] = Screen.GetFullscreenRect(320, 200, FSMode_ScaleToFit43Top);
int h = texid.isValid() && texsize.Y < 40? texsize.Y : fonth;
return int((y+h) * fh / 200); // This must be the covered height of the header in true pixels.
return BloodScreen.DrawCaption(title, y, drawit); // this ignores the passed font intentionally.
}
override bool DrawSelector(ListMenuDescriptor desc)

View file

@ -0,0 +1,327 @@
class BloodIntroImage : ImageScreen
{
bool mus;
ScreenJob Init(String texture, int flags = 0, bool withmusic = false)
{
Super.InitNamed(texture, flags);
mus = withmusic;
return self;
}
override void Start()
{
Blood.sndStartSampleNamed("THUNDER2", 128, -1);
if (mus) Blood.PlayIntroMusic(); // this is script defined.
}
}
struct BloodScreen
{
enum EConstants
{
kLoadScreenWideBackWidth = 256,
kLoadScreenWideSideWidth = 128,
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
static void DrawBackground()
{
if (Blood.OriginalLoadScreen() && Screen.GetAspectRatio() >= 1.34)
{
int width = screen.GetWidth() * 240 / screen.GetHeight();
int nCount = (width + kLoadScreenWideBackWidth - 1) / kLoadScreenWideBackWidth;
for (int i = 0; i < nCount; i++)
{
Screen.DrawTexture(TexMan.CheckForTexture("LoadScreenWideBack"), false, (i * kLoadScreenWideBackWidth), 0, DTA_VirtualWidth, width, DTA_VirtualHeight, 200, DTA_KeepRatio, true);
}
Screen.DrawTexture(TexMan.CheckForTexture("LoadScreenWideLeft"), false, 0, 0, DTA_VirtualWidth, width, DTA_VirtualHeight, 200, DTA_KeepRatio, true, DTA_TopLeft, true);
let texid = TexMan.CheckForTexture("LoadScreenWideRight");
let size = TexMan.GetScaledSize(texid);
Screen.DrawTexture(texid, false, width - size.x, 0, DTA_TopLeft, true, DTA_VirtualWidth, width, DTA_VirtualHeight, 200, DTA_KeepRatio, true);
texid = TexMan.CheckForTexture("LoadScreenWideMiddle");
size = TexMan.GetScaledSize(texid);
Screen.DrawTexture(texid, false, (width - size.x) / 2, 0, DTA_TopLeft, true, DTA_VirtualWidth, width, DTA_VirtualHeight, 200, DTA_KeepRatio, true);
}
else
{
Screen.DrawTexture(TexMan.CheckForTexture("LoadScreen"), false, 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit43);
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
static int DrawCaption(String title, int y, bool drawit)
{
let font = generic_ui? NewConsoleFont : BigFont;
let texid = TexMan.CheckForTexture("MENUBAR");
let texsize = TexMan.GetScaledSize(texid);
let fonth = font.GetGlyphHeight("A");
if (drawit)
{
int width = font.StringWidth(title);
if (texid.isValid())
{
double scalex = 1.; // Expand the box if the text is longer
if (texsize.X - 10 < width) scalex = width / (texsize.X - 10);
screen.DrawTexture(texid, false, 160, 20, DTA_FullscreenScale, FSMode_Fit320x200Top, DTA_CenterOffsetRel, true, DTA_ScaleX, scalex);
}
screen.DrawText(font, Font.CR_UNDEFINED, 160 - width / 2, 20 - fonth / 2, title, DTA_FullscreenScale, FSMode_Fit320x200Top);
}
double fx, fy, fw, fh;
[fx, fy, fw, fh] = Screen.GetFullscreenRect(320, 200, FSMode_ScaleToFit43Top);
int h = texid.isValid() && texsize.Y < 40? texsize.Y : fonth;
return int((y+h) * fh / 200); // This must be the covered height of the header in true pixels.
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
static void DrawText(Font pFont, String pString, int x, int y, int position = 0, int nShade = 0, int nPalette = 0, bool shadow = true, float alpha = 1.)
{
if (position > 0) x -= pFont.StringWidth(pString) * position / 2;
if (shadow) Screen.DrawText(pFont, Font.CR_UNDEFINED, x+1, y+1, pString, DTA_FullscreenScale, FSMode_Fit320x200, DTA_Color, 0xff000000, DTA_Alpha, 0.5);
Screen.DrawText(pFont, Font.CR_UNDEFINED, x, y, pString, DTA_FullscreenScale, FSMode_Fit320x200, DTA_TranslationIndex, Translation.MakeID(Translation_Remap, nPalette),
DTA_Color, Raze.shadeToLight(nShade), DTA_Alpha, alpha);
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
class BloodSummaryScreen : SummaryScreenBase
{
ScreenJob Init(MapRecord map, SummaryInfo info)
{
Super.Init(fadein|fadeout);
SetParameters(map, info);
return self;
}
override void Start()
{
Blood.sndStartSample(268, 128, -1, false, CHANF_UI);
}
void DrawKills()
{
String pBuffer;
BloodScreen.DrawText(BigFont, Stringtable.Localize("$KILLS") .. ":", 75, 50);
pBuffer = String.Format("%2d", stats.Kills);
BloodScreen.DrawText(BigFont, pBuffer, 160, 50);
BloodScreen.DrawText(BigFont, "$OF", 190, 50);
pBuffer = String.Format( "%2d", stats.MaxKills);
BloodScreen.DrawText(BigFont, pBuffer, 220, 50);
}
void DrawSecrets()
{
String pBuffer;
BloodScreen.DrawText(BigFont, StringTable.Localize("$TXT_SECRETS") .. ":", 75, 70);
pBuffer = String.Format( "%2d", stats.secrets);
BloodScreen.DrawText(BigFont, pBuffer, 160, 70);
BloodScreen.DrawText(BigFont, "$OF", 190, 70);
pBuffer = String.Format( "%2d", stats.maxsecrets);
BloodScreen.DrawText(BigFont, pBuffer, 220, 70);
if (stats.SuperSecrets > 0) BloodScreen.DrawText(BigFont, "$TXT_SUPERSECRET", 160, 100, 1, 0, 2);
}
override void Draw(double sm)
{
BloodScreen.DrawBackground();
BloodScreen.DrawCaption("$TXTB_LEVELSTATS", 0, true);
if (stats.cheated)
{
let text = StringTable.Localize("$TXTB_CHEATED");
let font = SmallFont2.CanPrint(text)? SmallFont2 : SmallFont;
BloodScreen.DrawText(font, text, 160, 32, 1, shadow:font == SmallFont2);
}
DrawKills();
DrawSecrets();
int myclock = ticks * 120 / GameTicRate;
if ((myclock & 32))
{
let text = StringTable.Localize("$PRESSKEY");
let font = SmallFont2.CanPrint(text)? SmallFont2 : SmallFont;
BloodScreen.DrawText(font, text, 160, 134, 1, shadow:font == SmallFont2);
}
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
class BloodMPSummaryScreen : SkippableScreenJob
{
int numplayers;
ScreenJob Init(int numplayer)
{
Super.Init(fadein|fadeout);
numplayers = numplayer;
return self;
}
override void Start()
{
Blood.sndStartSample(268, 128, -1, false, CHANF_UI);
}
void DrawKills()
{
String pBuffer;
BloodScreen.DrawText(SmallFont2, "#", 85, 35);
BloodScreen.DrawText(SmallFont2, "$NAME", 100, 35);
BloodScreen.DrawText(SmallFont2, "$FRAGS", 210, 35);
for (int i = 0; i < numplayers; i++)
{
pBuffer = String.Format( "%-2d", i);
BloodScreen.DrawText(SmallFont2, pBuffer, 85, 50 + 8 * i);
pBuffer = String.Format( "%s", Raze.PlayerName(i));
BloodScreen.DrawText(SmallFont2, pBuffer, 100, 50 + 8 * i);
pBuffer = String.Format( "%d", Raze.playerFrags(i, -1));
BloodScreen.DrawText(SmallFont2, pBuffer, 210, 50 + 8 * i);
}
}
override void Draw(double sr)
{
BloodScreen.DrawBackground();
BloodScreen.DrawCaption("$TXTB_FRAGSTATS", 0, true);
DrawKills();
int myclock = ticks * 120 / GameTicRate;
if ((myclock & 32))
{
let text = StringTable.Localize("$PRESSKEY");
let font = SmallFont2.CanPrint(text)? SmallFont2 : SmallFont;
BloodScreen.DrawText(font, text, 160, 134, 1, shadow:font == SmallFont2);
}
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
class BloodLoadScreen : ScreenJob
{
String loadtext;
MapRecord rec;
ScreenJob Init(MapRecord maprec)
{
Super.Init(fadein);
/*if (gGameOptions.nGameType == 0)*/ loadtext = "$TXTB_LLEVEL";
//else loadText = String.Format("$TXTB_NETGT%d", gGameOptions.nGameType));
rec = maprec;
return self;
}
override void Draw(double sr)
{
BloodScreen.DrawBackground();
BloodScreen.DrawCaption(loadtext, 0, true);
BloodScreen.DrawText(BigFont, rec.DisplayName(), 160, 50, 1);
let text = StringTable.Localize("$TXTB_PLSWAIT");
let font = SmallFont2.CanPrint(text)? SmallFont2 : SmallFont;
BloodScreen.DrawText(font, text, 160, 134, 1, shadow:font == SmallFont2);
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
class BloodCutscenes
{
static void BuildIntro(ScreenJobRunner runner)
{
if (!userConfig.nologo)
{
Array<int> soundinfo;
if (Wads.CheckNumForFullName("logo.smk") != -1)
{
String s = "logo.wav"; // sound name must be converted at run-time, not compile time!
runner.Append(MoviePlayerJob.CreateWithSound("logo.smk", s, MoviePlayer.FIXEDVIEWPORT));
}
else
{
runner.Append(new("BloodIntroImage").Init("MonolithScreen", ScreenJob.fadein|ScreenJob.fadeout));
}
if (Wads.CheckNumForFullName("gti.smk") != -1)
{
String s = "gt.wav";
runner.Append(MoviePlayerJob.CreateWithSound("gti.smk", s, MoviePlayer.FIXEDVIEWPORT));
}
else
{
runner.Append(new("BloodIntroImage").Init("GTIScreen", ScreenJob.fadein|ScreenJob.fadeout));
}
}
runner.Append(new("BloodIntroImage").Init("Titlescreen", ScreenJob.fadein, true));
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
static void BuildMPSummary(ScreenJobRunner runner, MapRecord map, SummaryInfo stats)
{
runner.Append(new("BloodMPSummaryScreen").Init(stats.playercount));
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
static void BuildSPSummary(ScreenJobRunner runner, MapRecord map, SummaryInfo stats)
{
runner.Append(new("BloodSummaryScreen").Init(map, stats));
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
static void BuildLoading(ScreenJobRunner runner, MapRecord map)
{
runner.Append(new("BloodLoadScreen").Init(map));
}
}