Merge branch 'master' into newrenderer

# Conflicts:
#	source/games/exhumed/src/2d.cpp
This commit is contained in:
Christoph Oelckers 2021-04-17 13:22:30 +02:00
commit 82194bbf6b
48 changed files with 1954 additions and 1533 deletions

View file

@ -1034,6 +1034,7 @@ set (PCH_SOURCES
build/src/polymost.cpp
core/movie/playmve.cpp
core/movie/movieplayer.cpp
core/automap.cpp
core/cheats.cpp
core/cheathandler.cpp

View file

@ -188,6 +188,7 @@ enum
// Raze extensions, using the higher bits to avoid conflitcs with the reserved and undocumented bits above.
CSTAT_SPRITE_MDLROTATE = 1u<<16u, // Only for tsprites: rotate if this is a model or voxel.
CSTAT_SPRITE_NOFIND = 1u<<17u, // Invisible to neartag and hitscan
};
enum

View file

@ -1412,6 +1412,10 @@ int32_t hitscan(const vec3_t *sv, int16_t sectnum, int32_t vx, int32_t vy, int32
{
auto const spr = (uspriteptr_t)&sprite[z];
uint32_t const cstat = spr->cstat;
if (cstat & CSTAT_SPRITE_NOFIND)
continue;
#ifdef USE_OPENGL
if (!hitallsprites)
#endif

View file

@ -1167,6 +1167,8 @@ void neartag(int32_t xs, int32_t ys, int32_t zs, int16_t sectnum, int16_t ange,
{
auto const spr = (uspriteptr_t)&sprite[z];
if (spr->cstat & CSTAT_SPRITE_NOFIND)
continue;
if (blacklist_sprite_func && blacklist_sprite_func(z))
continue;

View file

@ -189,8 +189,9 @@ DEFINE_ACTION_FUNCTION_NATIVE(DShape2D, PushTriangle, Shape2D_PushTriangle)
//
//==========================================================================
int F2DDrawer::AddCommand(const RenderCommand *data)
int F2DDrawer::AddCommand(RenderCommand *data)
{
data->mScreenFade = screenFade;
if (mData.Size() > 0 && data->isCompatible(mData.Last()))
{
// Merge with the last command.

View file

@ -118,6 +118,7 @@ public:
ETexMode mDrawMode;
uint8_t mLightLevel;
uint8_t mFlags;
float mScreenFade;
bool useTransform;
DMatrix3x3 transform;
@ -149,6 +150,7 @@ public:
mLightLevel == other.mLightLevel &&
mColor1.d == other.mColor1.d &&
useTransform == other.useTransform &&
mScreenFade == other.mScreenFade &&
(
!useTransform ||
(
@ -172,7 +174,7 @@ public:
int fullscreenautoaspect = 3;
int cliptop = -1, clipleft = -1, clipwidth = -1, clipheight = -1;
int AddCommand(const RenderCommand *data);
int AddCommand(RenderCommand *data);
void AddIndices(int firstvert, int count, ...);
private:
void AddIndices(int firstvert, TArray<int> &v);

View file

@ -110,13 +110,18 @@ void S_SetMusicCallbacks(MusicCallbacks* cb)
//==========================================================================
static std::unique_ptr<SoundStream> musicStream;
static TArray<SoundStream*> customStreams;
SoundStream *S_CreateCustomStream(size_t size, int samplerate, int numchannels, StreamCallback cb, void *userdata)
{
int flags = 0;
if (numchannels < 2) flags |= SoundStream::Mono;
auto stream = GSnd->CreateStream(cb, int(size), flags, samplerate, userdata);
if (stream) stream->Play(true, 1);
if (stream)
{
stream->Play(true, 1);
customStreams.Push(stream);
}
return stream;
}
@ -125,11 +130,19 @@ void S_StopCustomStream(SoundStream *stream)
if (stream)
{
stream->Stop();
auto f = customStreams.Find(stream);
if (f < customStreams.Size()) customStreams.Delete(f);
delete stream;
}
}
void S_PauseAllCustomStreams(bool on)
{
for (auto s : customStreams)
{
s->SetPaused(on);
}
}
static TArray<int16_t> convert;
static bool FillStream(SoundStream* stream, void* buff, int len, void* userdata)

View file

@ -14,6 +14,7 @@ class SoundStream;
typedef bool(*StreamCallback)(SoundStream* stream, void* buff, int len, void* userdata);
SoundStream *S_CreateCustomStream(size_t size, int samplerate, int numchannels, StreamCallback cb, void *userdata);
void S_StopCustomStream(SoundStream* stream);
void S_PauseAllCustomStreams(bool on);
struct MusicCallbacks
{

View file

@ -76,12 +76,15 @@ void D_ProcessEvents (void)
continue;
if (ev->type == EV_DeviceChange)
UpdateJoystickMenu(I_UpdateDeviceList());
if (gamestate == GS_INTRO)
continue;
if (C_Responder (ev))
continue; // console ate the event
if (M_Responder (ev))
continue; // menu ate the event
if (gamestate != GS_INTRO) // GS_INTRO blocks the UI.
{
if (C_Responder(ev))
continue; // console ate the event
if (M_Responder(ev))
continue; // menu ate the event
}
G_Responder (ev);
}
}

View file

@ -85,7 +85,6 @@ void Draw2D(F2DDrawer *drawer, FRenderState &state)
vb.UploadData(&vertices[0], vertices.Size(), &indices[0], indices.Size());
state.SetVertexBuffer(&vb);
state.EnableFog(false);
state.SetScreenFade(drawer->screenFade);
for(auto &cmd : commands)
{
@ -94,6 +93,7 @@ void Draw2D(F2DDrawer *drawer, FRenderState &state)
state.SetRenderStyle(cmd.mRenderStyle);
state.EnableBrightmap(!(cmd.mRenderStyle.Flags & STYLEF_ColorIsFixed));
state.EnableFog(2); // Special 2D mode 'fog'.
state.SetScreenFade(cmd.mScreenFade);
state.SetTextureMode(cmd.mDrawMode);

View file

@ -44,6 +44,7 @@
#include "mmulti.h"
#include "gstrings.h"
#include "gamecontrol.h"
#include "screenjob.h"
#include "mapinfo.h"
CVAR(Bool, sv_cheats, true, CVAR_ARCHIVE|CVAR_SERVERINFO)
@ -239,6 +240,17 @@ void changeMap(int player, uint8_t** stream, bool skip)
//
//---------------------------------------------------------------------------
void endScreenJob(int player, uint8_t** stream, bool skip)
{
if (!skip) EndScreenJob();
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void ChangeLevel(MapRecord* map, int skill)
{
Net_WriteByte(DEM_CHANGEMAP);

View file

@ -8,3 +8,4 @@ EXTERN_CVAR(Bool, sv_cheats)
void genericCheat(int player, uint8_t** stream, bool skip);
void changeMap(int player, uint8_t** stream, bool skip);
void endScreenJob(int player, uint8_t** stream, bool skip);

View file

@ -43,6 +43,7 @@
#include "gamecontrol.h"
#include "uiinput.h"
#include "automap.h"
#include "screenjob.h"
//==========================================================================
//
@ -53,6 +54,11 @@
bool G_Responder (event_t *ev)
{
if (gamestate == GS_INTRO || gamestate == GS_INTERMISSION)
{
return ScreenJobResponder(ev);
}
if (CT_Responder(ev))
return true; // chat ate the event
if (Cheat_Responder(ev))

View file

@ -1674,6 +1674,7 @@ bool D_CheckNetGame (void)
Net_SetCommandHandler(DEM_GENERICCHEAT, genericCheat);
Net_SetCommandHandler(DEM_CHANGEMAP, changeMap);
Net_SetCommandHandler(DEM_ENDSCREENJOB, endScreenJob);
for (i = 0; i < MAXNETNODES; i++)
{

View file

@ -89,6 +89,7 @@ enum EDemoCommand
DEM_GENERICCHEAT,
DEM_GIVE,
DEM_CHANGEMAP,
DEM_ENDSCREENJOB,
DEM_MAX
};

View file

@ -1008,29 +1008,6 @@ int RunGame()
//
//---------------------------------------------------------------------------
void TickSubsystems()
{
// run these on an independent timer until we got something working for the games.
static const uint64_t tickInterval = 1'000'000'000 / 30;
static uint64_t nexttick = 0;
auto nowtick = I_nsTime();
if (nexttick == 0) nexttick = nowtick;
int cnt = 0;
while (nexttick <= nowtick && cnt < 5)
{
nexttick += tickInterval;
C_Ticker();
M_Ticker();
C_RunDelayedCommands();
cnt++;
}
// If this took too long the engine was most likely suspended so recalibrate the timer.
// Perfect precision is not needed here.
if (cnt == 5) nexttick = nowtick + tickInterval;
}
void updatePauseStatus()
{
// This must go through the network in multiplayer games.
@ -1053,7 +1030,10 @@ void updatePauseStatus()
}
}
paused ? S_PauseSound(!pausedWithKey, !paused) : S_ResumeSound(paused);
if (paused)
S_PauseSound(!pausedWithKey, !paused);
else
S_ResumeSound(paused);
}
//==========================================================================
@ -1143,6 +1123,7 @@ void S_PauseSound (bool notmusic, bool notsfx)
{
soundEngine->SetPaused(true);
GSnd->SetSfxPaused (true, 0);
S_PauseAllCustomStreams(true);
}
}
@ -1161,6 +1142,7 @@ void S_ResumeSound (bool notsfx)
{
soundEngine->SetPaused(false);
GSnd->SetSfxPaused (false, 0);
S_PauseAllCustomStreams(false);
}
}

View file

@ -328,9 +328,10 @@ static void GameTicker()
case GS_MENUSCREEN:
case GS_FULLCONSOLE:
break;
case GS_INTERMISSION:
case GS_INTRO:
// These elements do not tick at game rate.
ScreenJobTick();
break;
}
@ -370,7 +371,7 @@ void Display()
case GS_INTRO:
case GS_INTERMISSION:
// screen jobs are not bound by the game ticker so they need to be ticked in the display loop.
RunScreenJobFrame();
ScreenJobDraw();
break;
case GS_LEVEL:

View file

@ -0,0 +1,814 @@
/*
** movieplayer.cpp
**
**---------------------------------------------------------------------------
** Copyright 2020 Christoph Oelckers
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
#include "types.h"
#include "build.h"
#include "screenjob.h"
#include "i_time.h"
#include "v_2ddrawer.h"
#include "animlib.h"
#include "v_draw.h"
#include "s_soundinternal.h"
#include "animtexture.h"
#include "gamestate.h"
#include "razemenu.h"
#include "raze_sound.h"
#include "SmackerDecoder.h"
#include "movie/playmve.h"
#include "gamecontrol.h"
#include <vpx/vpx_decoder.h>
#include <vpx/vp8dx.h>
#include "raze_music.h"
class MoviePlayer
{
public:
virtual void Start() {}
virtual bool Frame(uint64_t clock) = 0;
virtual void Stop() {}
virtual ~MoviePlayer() = default;
};
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
class AnmPlayer : public MoviePlayer
{
// This doesn't need its own class type
anim_t anim;
TArray<uint8_t> buffer;
int numframes = 0;
int curframe = 1;
int frametime = 0;
int nextframetime = 0;
AnimTextures animtex;
const AnimSound* animSnd;
const int* frameTicks;
bool nostopsound;
public:
bool isvalid() { return numframes > 0; }
AnmPlayer(FileReader& fr, const AnimSound* ans, const int *frameticks, bool nosoundcutoff)
: animSnd(ans), frameTicks(frameticks), nostopsound(nosoundcutoff)
{
buffer = fr.ReadPadded(1);
fr.Close();
if (ANIM_LoadAnim(&anim, buffer.Data(), buffer.Size() - 1) < 0)
{
return;
}
numframes = ANIM_NumFrames(&anim);
animtex.SetSize(AnimTexture::Paletted, 320, 200);
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
bool Frame(uint64_t clock) override
{
int currentclock = int(clock * 120 / 1'000'000'000);
if (currentclock < nextframetime - 1)
{
twod->ClearScreen();
DrawTexture(twod, animtex.GetFrame(), 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit43, DTA_Masked, false, TAG_DONE);
return true;
}
animtex.SetFrame(ANIM_GetPalette(&anim), ANIM_DrawFrame(&anim, curframe));
frametime = currentclock;
twod->ClearScreen();
DrawTexture(twod, animtex.GetFrame(), 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit43, DTA_Masked, false, TAG_DONE);
int delay = 20;
if (frameTicks)
{
if (curframe == 1) delay = frameTicks[0];
else if (curframe < numframes - 2) delay = frameTicks[1];
else delay = frameTicks[2];
}
nextframetime += delay;
if (animSnd) for (int i = 0; animSnd[i].framenum >= 0; i++)
{
if (animSnd[i].framenum == curframe)
{
int sound = animSnd[i].soundnum;
if (sound == -1)
soundEngine->StopAllChannels();
else if (SoundEnabled())
soundEngine->StartSound(SOURCE_None, nullptr, nullptr, CHAN_AUTO, CHANF_NONE, sound, 1.f, ATTN_NONE);
}
}
if (!nostopsound && curframe == numframes && soundEngine->GetSoundPlayingInfo(SOURCE_None, nullptr, -1)) return true;
curframe++;
return curframe < numframes;
}
void Stop() override
{
if (!nostopsound) soundEngine->StopAllChannels();
}
~AnmPlayer()
{
buffer.Reset();
animtex.Clean();
}
};
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
class MvePlayer : public MoviePlayer
{
InterplayDecoder decoder;
bool failed = false;
public:
bool isvalid() { return !failed; }
MvePlayer(FileReader& fr) : decoder(SoundEnabled())
{
failed = !decoder.Open(fr);
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
bool Frame(uint64_t clock) override
{
if (failed) return false;
bool playon = decoder.RunFrame(clock);
twod->ClearScreen();
DrawTexture(twod, decoder.animTex().GetFrame(), 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit43, TAG_DONE);
return playon;
}
~MvePlayer()
{
decoder.Close();
}
};
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
class VpxPlayer : public MoviePlayer
{
bool failed = false;
FileReader fr;
AnimTextures animtex;
const AnimSound* animSnd;
unsigned width, height;
TArray<uint8_t> Pic;
TArray<uint8_t> readBuf;
vpx_codec_ctx_t codec{};
vpx_codec_iter_t iter = nullptr;
uint32_t convnumer;
uint32_t convdenom;
uint64_t nsecsperframe;
uint64_t nextframetime;
int decstate = 0;
int framenum = 0;
int numframes;
int lastsoundframe = -1;
public:
int soundtrack = -1;
public:
bool isvalid() { return !failed; }
VpxPlayer(FileReader& fr_, const AnimSound* animSnd_, int origframedelay, FString& error)
{
fr = std::move(fr_);
animSnd = animSnd_;
if (!ReadIVFHeader(origframedelay))
{
// We should never get here, because any file failing this has been eliminated before this constructor got called.
error.Format("Failed reading IVF header\n");
failed = true;
}
Pic.Resize(width * height * 4);
// Todo: Support VP9 as well?
vpx_codec_dec_cfg_t cfg = { 1, width, height };
if (vpx_codec_dec_init(&codec, &vpx_codec_vp8_dx_algo, &cfg, 0))
{
error.Format("Error initializing VPX codec.\n");
failed = true;
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
bool ReadIVFHeader(int origframedelay)
{
// IVF format: http://wiki.multimedia.cx/index.php?title=IVF
uint32_t magic; fr.Read(&magic, 4); // do not byte swap!
if (magic != MAKE_ID('D', 'K', 'I', 'F')) return false;
uint16_t version = fr.ReadUInt16();
if (version != 0) return false;
uint16_t length = fr.ReadUInt16();
if (length != 32) return false;
fr.Read(&magic, 4);
if (magic != MAKE_ID('V', 'P', '8', '0')) return false;
width = fr.ReadUInt16();
height = fr.ReadUInt16();
uint32_t fpsdenominator = fr.ReadUInt32();
uint32_t fpsnumerator = fr.ReadUInt32();
numframes = fr.ReadUInt32();
if (numframes == 0) return false;
fr.Seek(4, FileReader::SeekCur);
if (fpsdenominator > 1000 || fpsnumerator == 0 || fpsdenominator == 0)
{
// default to 30 fps if the header does not provide useful info.
fpsdenominator = 30;
fpsnumerator = 1;
}
convnumer = 120 * fpsnumerator;
convdenom = fpsdenominator * origframedelay;
nsecsperframe = int64_t(fpsnumerator) * 1'000'000'000 / fpsdenominator;
nextframetime = 0;
return true;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
bool ReadFrame()
{
int corrupted = 0;
int framesize = fr.ReadInt32();
fr.Seek(8, FileReader::SeekCur);
if (framesize == 0) return false;
readBuf.Resize(framesize);
if (fr.Read(readBuf.Data(), framesize) != framesize) return false;
if (vpx_codec_decode(&codec, readBuf.Data(), readBuf.Size(), NULL, 0) != VPX_CODEC_OK) return false;
if (vpx_codec_control(&codec, VP8D_GET_FRAME_CORRUPTED, &corrupted) != VPX_CODEC_OK) return false;
return true;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
vpx_image_t *GetFrameData()
{
vpx_image_t *img;
do
{
if (decstate == 0) // first time / begin
{
if (!ReadFrame()) return nullptr;
decstate = 1;
}
img = vpx_codec_get_frame(&codec, &iter);
if (img == nullptr)
{
decstate = 0;
iter = nullptr;
}
} while (img == nullptr);
return img->d_w == width && img->d_h == height? img : nullptr;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void SetPixel(uint8_t* dest, uint8_t y, uint8_t u, uint8_t v)
{
dest[0] = y;
dest[1] = u;
dest[2] = v;
}
bool CreateNextFrame()
{
auto img = GetFrameData();
if (!img) return false;
uint8_t const* const yplane = img->planes[VPX_PLANE_Y];
uint8_t const* const uplane = img->planes[VPX_PLANE_U];
uint8_t const* const vplane = img->planes[VPX_PLANE_V];
const int ystride = img->stride[VPX_PLANE_Y];
const int ustride = img->stride[VPX_PLANE_U];
const int vstride = img->stride[VPX_PLANE_V];
for (unsigned int y = 0; y < height; y += 2)
{
unsigned int y1 = y + 1;
unsigned int wy = width * y;
unsigned int wy1 = width * y1;
for (unsigned int x = 0; x < width; x += 2)
{
uint8_t u = uplane[ustride * (y >> 1) + (x >> 1)];
uint8_t v = vplane[vstride * (y >> 1) + (x >> 1)];
SetPixel(&Pic[(wy + x) << 2], yplane[ystride * y + x], u, v);
SetPixel(&Pic[(wy + x + 1) << 2], yplane[ystride * y + x + 1], u, v);
SetPixel(&Pic[(wy1 + x) << 2], yplane[ystride * y1 + x], u, v);
SetPixel(&Pic[(wy1 + x + 1) << 2], yplane[ystride * y1 + x + 1], u, v);
}
}
return true;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void Start() override
{
if (soundtrack > 0)
{
Mus_Play(nullptr, fileSystem.GetFileFullName(soundtrack, false), false);
}
animtex.SetSize(AnimTexture::YUV, width, height);
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
bool Frame(uint64_t clock) override
{
bool stop = false;
if (clock > nextframetime)
{
nextframetime += nsecsperframe;
if (!CreateNextFrame())
{
Printf(PRINT_BOLD, "Failed reading next frame\n");
stop = true;
}
else
{
animtex.SetFrame(nullptr, Pic.Data());
}
framenum++;
if (framenum >= numframes) stop = true;
int soundframe = convdenom ? Scale(framenum, convnumer, convdenom) : framenum;
if (soundframe > lastsoundframe)
{
if (animSnd && soundtrack == -1) for (int i = 0; animSnd[i].framenum >= 0; i++)
{
if (animSnd[i].framenum == soundframe)
{
int sound = animSnd[i].soundnum;
if (sound == -1)
soundEngine->StopAllChannels();
else if (SoundEnabled())
soundEngine->StartSound(SOURCE_None, nullptr, nullptr, CHAN_AUTO, CHANF_NONE, sound, 1.f, ATTN_NONE);
}
}
lastsoundframe = soundframe;
}
}
DrawTexture(twod, animtex.GetFrame(), 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit, TAG_DONE);
return !stop;
}
void Stop()
{
Mus_Stop();
}
~VpxPlayer()
{
vpx_codec_destroy(&codec);
animtex.Clean();
}
};
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
struct AudioData
{
int hFx;
SmackerAudioInfo inf;
int16_t samples[6000 * 20]; // must be a multiple of the stream buffer size and larger than the initial chunk of audio
int nWrite;
int nRead;
};
class SmkPlayer : public MoviePlayer
{
SmackerHandle hSMK{};
int numAudioTracks;
AudioData adata;
uint32_t nWidth, nHeight;
uint8_t palette[768];
AnimTextures animtex;
TArray<uint8_t> pFrame;
TArray<uint8_t> audioBuffer;
int nFrameRate;
int nFrames;
bool fullscreenScale;
uint64_t nFrameNs;
int nFrame = 0;
const AnimSound* animSnd;
FString filename;
SoundStream* stream = nullptr;
public:
bool isvalid() { return hSMK.isValid; }
static bool StreamCallbackFunc(SoundStream* stream, void* buff, int len, void* userdata)
{
SmkPlayer* pId = (SmkPlayer*)userdata;
memcpy(buff, &pId->adata.samples[pId->adata.nRead], len);
pId->adata.nRead += len / 2;
if (pId->adata.nRead >= countof(pId->adata.samples)) pId->adata.nRead = 0;
return true;
}
void copy8bitSamples(unsigned count)
{
for (unsigned i = 0; i < count; i++)
{
adata.samples[adata.nWrite] = (audioBuffer[i] - 128) << 8;
if (++adata.nWrite >= countof(adata.samples)) adata.nWrite = 0;
}
}
void copy16bitSamples(unsigned count)
{
auto ptr = (uint16_t*)audioBuffer.Data();
for (unsigned i = 0; i < count/2; i++)
{
adata.samples[adata.nWrite] = *ptr++;
if (++adata.nWrite >= countof(adata.samples)) adata.nWrite = 0;
}
}
SmkPlayer(const char *fn, const AnimSound* ans, bool fixedviewport)
{
hSMK = Smacker_Open(fn);
if (!hSMK.isValid)
{
return;
}
Smacker_GetFrameSize(hSMK, nWidth, nHeight);
pFrame.Resize(nWidth * nHeight + std::max(nWidth, nHeight));
nFrameRate = Smacker_GetFrameRate(hSMK);
nFrameNs = 1'000'000'000 / nFrameRate;
nFrames = Smacker_GetNumFrames(hSMK);
Smacker_GetPalette(hSMK, palette);
fullscreenScale = (!fixedviewport || (nWidth <= 320 && nHeight <= 200) || nWidth >= 640 || nHeight >= 480);
bool hassound = false;
numAudioTracks = Smacker_GetNumAudioTracks(hSMK);
if (numAudioTracks)
{
adata.nWrite = 0;
adata.nRead = 0;
adata.inf = Smacker_GetAudioTrackDetails(hSMK, 0);
if (adata.inf.idealBufferSize > 0)
{
audioBuffer.Resize(adata.inf.idealBufferSize);
auto read = Smacker_GetAudioData(hSMK, 0, (int16_t*)audioBuffer.Data());
if (adata.inf.bitsPerSample == 8) copy8bitSamples(read);
else copy16bitSamples(read);
animSnd = nullptr;
hassound = true;
}
}
if (!hassound)
{
adata.inf = {};
animSnd = ans;
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void Start() override
{
animtex.SetSize(AnimTexture::Paletted, nWidth, nHeight);
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
bool Frame(uint64_t clock) override
{
int frame = clock / nFrameNs;
twod->ClearScreen();
if (frame > nFrame)
{
Smacker_GetPalette(hSMK, palette);
Smacker_GetFrame(hSMK, pFrame.Data());
animtex.SetFrame(palette, pFrame.Data());
if (numAudioTracks)
{
auto read = Smacker_GetAudioData(hSMK, 0, (int16_t*)audioBuffer.Data());
if (adata.inf.bitsPerSample == 8) copy8bitSamples(read);
else copy16bitSamples(read);
if (!stream && read) // the sound may not start in the first frame, but the stream cannot start without any sound data present.
stream = S_CreateCustomStream(6000, adata.inf.sampleRate, adata.inf.nChannels, StreamCallbackFunc, this);
}
}
if (fullscreenScale)
{
DrawTexture(twod, animtex.GetFrame(), 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit43, TAG_DONE);
}
else
{
DrawTexture(twod, animtex.GetFrame(), 320, 240, DTA_VirtualWidth, 640, DTA_VirtualHeight, 480, DTA_CenterOffset, true, TAG_DONE);
}
if (frame > nFrame)
{
nFrame++;
Smacker_GetNextFrame(hSMK);
if (animSnd) for (int i = 0; animSnd[i].framenum >= 0; i++)
{
if (animSnd[i].framenum == nFrame)
{
int sound = animSnd[i].soundnum;
if (sound == -1)
soundEngine->StopAllChannels();
else if (SoundEnabled())
soundEngine->StartSound(SOURCE_None, nullptr, nullptr, CHAN_AUTO, CHANF_NONE, sound, 1.f, ATTN_NONE);
}
}
}
return nFrame < nFrames;
}
~SmkPlayer()
{
Smacker_Close(hSMK);
if (stream) S_StopCustomStream(stream);
soundEngine->StopAllChannels();
animtex.Clean();
}
};
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
class DMoviePlayer : public DSkippableScreenJob
{
MoviePlayer* player;
bool started = false;
public:
DMoviePlayer(MoviePlayer* mp)
{
player = mp;
pausable = false;
}
void Draw(double smoothratio) override
{
if (!player)
{
state = stopped;
return;
}
if (!started)
{
started = true;
player->Start();
}
uint64_t clock = (ticks + smoothratio) * 1'000'000'000. / GameTicRate;
if (state == running && !player->Frame(clock))
{
state = finished;
}
}
void OnDestroy() override
{
if (player)
{
player->Stop();
delete player;
}
player = nullptr;
}
};
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
MoviePlayer* OpenMovie(const char* filename, const AnimSound* ans, const int* frameticks, bool nosoundcutoff, FString& error)
{
FileReader fr;
// first try as .ivf - but only if sounds are provided - the decoder is video only.
if (ans)
{
auto fn = StripExtension(filename);
DefaultExtension(fn, ".ivf");
fr = fileSystem.OpenFileReader(fn);
}
if (!fr.isOpen()) fr = fileSystem.OpenFileReader(filename);
if (!fr.isOpen())
{
int nLen = strlen(filename);
// Strip the drive letter and retry.
if (nLen >= 3 && isalpha(filename[0]) && filename[1] == ':' && filename[2] == '/')
{
filename += 3;
fr = fileSystem.OpenFileReader(filename);
}
if (!fr.isOpen())
{
error.Format("%s: Unable to open video\n", filename);
return nullptr;
}
}
char id[20] = {};
fr.Read(&id, 20);
fr.Seek(-20, FileReader::SeekCur);
if (!memcmp(id, "LPF ", 4))
{
auto anm = new AnmPlayer(fr, ans, frameticks, nosoundcutoff);
if (!anm->isvalid())
{
error.Format("%s: invalid ANM file.\n", filename);
delete anm;
return nullptr;
}
return anm;
}
else if (!memcmp(id, "SMK2", 4))
{
fr.Close();
auto anm = new SmkPlayer(filename, ans, true); // Fixme: Handle Blood's video scaling behavior more intelligently.
if (!anm->isvalid())
{
error.Format("%s: invalid SMK file.\n", filename);
delete anm;
return nullptr;
}
return anm;
}
else if (!memcmp(id, "Interplay MVE File", 18))
{
auto anm = new MvePlayer(fr);
if (!anm->isvalid())
{
delete anm;
return nullptr;
}
return anm;
}
else if (!memcmp(id, "DKIF\0\0 \0VP80", 12))
{
auto anm = new VpxPlayer(fr, ans, frameticks ? frameticks[1] : 0, error);
if (!anm->isvalid())
{
delete anm;
return nullptr;
}
anm->soundtrack = LookupMusic(filename, true);
return anm;
}
// add more formats here.
else
{
error.Format("%s: Unknown video format\n", filename);
return nullptr;
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
DScreenJob* PlayVideo(const char* filename, const AnimSound* ans, const int* frameticks, bool nosoundcutoff)
{
if (!filename)
{
return Create<DBlackScreen>(1);
}
FString error;
auto movie = OpenMovie(filename, ans, frameticks, nosoundcutoff, error);
if (!movie)
{
Printf(TEXTCOLOR_YELLOW, "%s", error.GetChars());
return Create<DBlackScreen>(1);
}
return Create<DMoviePlayer>(movie);
}

View file

@ -57,11 +57,29 @@ IMPLEMENT_CLASS(DScreenJob, true, false)
IMPLEMENT_CLASS(DImageScreen, true, false)
int DBlackScreen::Frame(uint64_t clock, bool skiprequest)
bool DSkippableScreenJob::OnEvent(event_t* evt)
{
int span = int(clock / 1'000'000);
if (evt->type == EV_KeyDown)
{
state = skipped;
Skipped();
}
return true;
}
void DBlackScreen::OnTick()
{
if (cleared)
{
int span = ticks * 1000 / GameTicRate;
if (span > wait) state = finished;
}
}
void DBlackScreen::Draw(double)
{
cleared = true;
twod->ClearScreen();
return span < wait ? 1 : -1;
}
//---------------------------------------------------------------------------
@ -70,688 +88,24 @@ int DBlackScreen::Frame(uint64_t clock, bool skiprequest)
//
//---------------------------------------------------------------------------
int DImageScreen::Frame(uint64_t clock, bool skiprequest)
void DImageScreen::OnTick()
{
if (tilenum > 0)
if (cleared)
{
tex = tileGetTexture(tilenum, true);
int span = ticks * 1000 / GameTicRate;
if (span > waittime) state = finished;
}
if (!tex)
{
twod->ClearScreen();
return 0;
}
int span = int(clock / 1'000'000);
}
void DImageScreen::Draw(double smoothratio)
{
if (tilenum > 0) tex = tileGetTexture(tilenum, true);
twod->ClearScreen();
DrawTexture(twod, tex, 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit43, DTA_LegacyRenderStyle, STYLE_Normal, DTA_TranslationIndex, trans, TAG_DONE);
// Only end after having faded out.
return skiprequest ? -1 : span > waittime? 0 : 1;
if (tex) DrawTexture(twod, tex, 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit43, DTA_LegacyRenderStyle, STYLE_Normal, DTA_TranslationIndex, trans, TAG_DONE);
cleared = true;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
class DAnmPlayer : public DScreenJob
{
// This doesn't need its own class type
anim_t anim;
TArray<uint8_t> buffer;
int numframes = 0;
int curframe = 1;
int frametime = 0;
int nextframetime = 0;
AnimTextures animtex;
const AnimSound* animSnd;
const int* frameTicks;
bool nostopsound;
public:
bool isvalid() { return numframes > 0; }
DAnmPlayer(FileReader& fr, const AnimSound* ans, const int *frameticks, bool nosoundcutoff)
: animSnd(ans), frameTicks(frameticks), nostopsound(nosoundcutoff)
{
buffer = fr.ReadPadded(1);
fr.Close();
if (ANIM_LoadAnim(&anim, buffer.Data(), buffer.Size() - 1) < 0)
{
return;
}
numframes = ANIM_NumFrames(&anim);
animtex.SetSize(AnimTexture::Paletted, 320, 200);
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
int Frame(uint64_t clock, bool skiprequest) override
{
int currentclock = int(clock * 120 / 1'000'000'000);
if (currentclock < nextframetime - 1)
{
twod->ClearScreen();
DrawTexture(twod, animtex.GetFrame(), 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit43, DTA_Masked, false, TAG_DONE);
if (skiprequest && !nostopsound) soundEngine->StopAllChannels();
return skiprequest? -1 : 1;
}
animtex.SetFrame(ANIM_GetPalette(&anim), ANIM_DrawFrame(&anim, curframe));
frametime = currentclock;
twod->ClearScreen();
DrawTexture(twod, animtex.GetFrame(), 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit43, DTA_Masked, false, TAG_DONE);
int delay = 20;
if (frameTicks)
{
if (curframe == 1) delay = frameTicks[0];
else if (curframe < numframes - 2) delay = frameTicks[1];
else delay = frameTicks[2];
}
nextframetime += delay;
if (animSnd) for (int i = 0; animSnd[i].framenum >= 0; i++)
{
if (animSnd[i].framenum == curframe)
{
int sound = animSnd[i].soundnum;
if (sound == -1)
soundEngine->StopAllChannels();
else if (SoundEnabled())
soundEngine->StartSound(SOURCE_None, nullptr, nullptr, CHAN_AUTO, CHANF_UI, sound, 1.f, ATTN_NONE);
}
}
if (!skiprequest && !nostopsound && curframe == numframes && soundEngine->GetSoundPlayingInfo(SOURCE_None, nullptr, -1)) return 1;
curframe++;
if (skiprequest && !nostopsound) soundEngine->StopAllChannels();
return skiprequest ? -1 : curframe < numframes? 1 : 0;
}
void OnDestroy() override
{
buffer.Reset();
animtex.Clean();
}
};
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
class DMvePlayer : public DScreenJob
{
InterplayDecoder decoder;
bool failed = false;
public:
bool isvalid() { return !failed; }
DMvePlayer(FileReader& fr) : decoder(SoundEnabled())
{
failed = !decoder.Open(fr);
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
int Frame(uint64_t clock, bool skiprequest) override
{
if (failed) return -1;
bool playon = decoder.RunFrame(clock);
twod->ClearScreen();
DrawTexture(twod, decoder.animTex().GetFrame(), 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit43, TAG_DONE);
return skiprequest ? -1 : playon ? 1 : 0;
}
void OnDestroy() override
{
decoder.Close();
}
};
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
class DVpxPlayer : public DScreenJob
{
bool failed = false;
FileReader fr;
AnimTextures animtex;
const AnimSound* animSnd;
unsigned width, height;
TArray<uint8_t> Pic;
TArray<uint8_t> readBuf;
vpx_codec_ctx_t codec{};
vpx_codec_iter_t iter = nullptr;
uint32_t convnumer;
uint32_t convdenom;
uint64_t nsecsperframe;
uint64_t nextframetime;
int decstate = 0;
int framenum = 0;
int numframes;
int lastsoundframe = -1;
public:
int soundtrack = -1;
public:
bool isvalid() { return !failed; }
DVpxPlayer(FileReader& fr_, const AnimSound* animSnd_, int origframedelay)
{
fr = std::move(fr_);
animSnd = animSnd_;
if (!ReadIVFHeader(origframedelay))
{
// We should never get here, because any file failing this has been eliminated before this constructor got called.
Printf(PRINT_BOLD, "Failed reading IVF header\n");
failed = true;
}
Pic.Resize(width * height * 4);
// Todo: Support VP9 as well?
vpx_codec_dec_cfg_t cfg = { 1, width, height };
if (vpx_codec_dec_init(&codec, &vpx_codec_vp8_dx_algo, &cfg, 0))
{
Printf(PRINT_BOLD, "Error initializing VPX codec.\n");
failed = true;
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
bool ReadIVFHeader(int origframedelay)
{
// IVF format: http://wiki.multimedia.cx/index.php?title=IVF
uint32_t magic; fr.Read(&magic, 4); // do not byte swap!
if (magic != MAKE_ID('D', 'K', 'I', 'F')) return false;
uint16_t version = fr.ReadUInt16();
if (version != 0) return false;
uint16_t length = fr.ReadUInt16();
if (length != 32) return false;
fr.Read(&magic, 4);
if (magic != MAKE_ID('V', 'P', '8', '0')) return false;
width = fr.ReadUInt16();
height = fr.ReadUInt16();
uint32_t fpsdenominator = fr.ReadUInt32();
uint32_t fpsnumerator = fr.ReadUInt32();
numframes = fr.ReadUInt32();
if (numframes == 0) return false;
fr.Seek(4, FileReader::SeekCur);
if (fpsdenominator > 1000 || fpsnumerator == 0 || fpsdenominator == 0)
{
// default to 30 fps if the header does not provide useful info.
fpsdenominator = 30;
fpsnumerator = 1;
}
convnumer = 120 * fpsnumerator;
convdenom = fpsdenominator * origframedelay;
nsecsperframe = int64_t(fpsnumerator) * 1'000'000'000 / fpsdenominator;
nextframetime = 0;
return true;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
bool ReadFrame()
{
int corrupted = 0;
int framesize = fr.ReadInt32();
fr.Seek(8, FileReader::SeekCur);
if (framesize == 0) return false;
readBuf.Resize(framesize);
if (fr.Read(readBuf.Data(), framesize) != framesize) return false;
if (vpx_codec_decode(&codec, readBuf.Data(), readBuf.Size(), NULL, 0) != VPX_CODEC_OK) return false;
if (vpx_codec_control(&codec, VP8D_GET_FRAME_CORRUPTED, &corrupted) != VPX_CODEC_OK) return false;
return true;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
vpx_image_t *GetFrameData()
{
vpx_image_t *img;
do
{
if (decstate == 0) // first time / begin
{
if (!ReadFrame()) return nullptr;
decstate = 1;
}
img = vpx_codec_get_frame(&codec, &iter);
if (img == nullptr)
{
decstate = 0;
iter = nullptr;
}
} while (img == nullptr);
return img->d_w == width && img->d_h == height? img : nullptr;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void SetPixel(uint8_t* dest, uint8_t y, uint8_t u, uint8_t v)
{
dest[0] = y;
dest[1] = u;
dest[2] = v;
}
bool CreateNextFrame()
{
auto img = GetFrameData();
if (!img) return false;
uint8_t const* const yplane = img->planes[VPX_PLANE_Y];
uint8_t const* const uplane = img->planes[VPX_PLANE_U];
uint8_t const* const vplane = img->planes[VPX_PLANE_V];
const int ystride = img->stride[VPX_PLANE_Y];
const int ustride = img->stride[VPX_PLANE_U];
const int vstride = img->stride[VPX_PLANE_V];
for (unsigned int y = 0; y < height; y += 2)
{
unsigned int y1 = y + 1;
unsigned int wy = width * y;
unsigned int wy1 = width * y1;
for (unsigned int x = 0; x < width; x += 2)
{
uint8_t u = uplane[ustride * (y >> 1) + (x >> 1)];
uint8_t v = vplane[vstride * (y >> 1) + (x >> 1)];
SetPixel(&Pic[(wy + x) << 2], yplane[ystride * y + x], u, v);
SetPixel(&Pic[(wy + x + 1) << 2], yplane[ystride * y + x + 1], u, v);
SetPixel(&Pic[(wy1 + x) << 2], yplane[ystride * y1 + x], u, v);
SetPixel(&Pic[(wy1 + x + 1) << 2], yplane[ystride * y1 + x + 1], u, v);
}
}
return true;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
int Frame(uint64_t clock, bool skiprequest) override
{
if (clock == 0)
{
if (soundtrack > 0)
{
Mus_Play(nullptr, fileSystem.GetFileFullName(soundtrack, false), false);
}
animtex.SetSize(AnimTexture::YUV, width, height);
}
bool stop = false;
if (clock > nextframetime)
{
nextframetime += nsecsperframe;
if (!CreateNextFrame())
{
Printf(PRINT_BOLD, "Failed reading next frame\n");
stop = true;
}
else
{
animtex.SetFrame(nullptr, Pic.Data());
}
framenum++;
if (framenum >= numframes) stop = true;
int soundframe = convdenom ? Scale(framenum, convnumer, convdenom) : framenum;
if (soundframe > lastsoundframe)
{
if (animSnd && soundtrack == -1) for (int i = 0; animSnd[i].framenum >= 0; i++)
{
if (animSnd[i].framenum == soundframe)
{
int sound = animSnd[i].soundnum;
if (sound == -1)
soundEngine->StopAllChannels();
else if (SoundEnabled())
soundEngine->StartSound(SOURCE_None, nullptr, nullptr, CHAN_AUTO, CHANF_UI, sound, 1.f, ATTN_NONE);
}
}
lastsoundframe = soundframe;
}
}
DrawTexture(twod, animtex.GetFrame(), 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit, TAG_DONE);
if (stop || skiprequest) Mus_Stop();
if (stop) return 0;
return skiprequest ? -1 : 1;
}
void OnDestroy() override
{
vpx_codec_destroy(&codec);
}
};
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
struct AudioData
{
int hFx;
SmackerAudioInfo inf;
int16_t samples[6000 * 20]; // must be a multiple of the stream buffer size and larger than the initial chunk of audio
int nWrite;
int nRead;
};
class DSmkPlayer : public DScreenJob
{
SmackerHandle hSMK{};
int numAudioTracks;
AudioData adata;
uint32_t nWidth, nHeight;
uint8_t palette[768];
AnimTextures animtex;
TArray<uint8_t> pFrame;
TArray<uint8_t> audioBuffer;
int nFrameRate;
int nFrames;
bool fullscreenScale;
uint64_t nFrameNs;
int nFrame = 0;
const AnimSound* animSnd;
FString filename;
SoundStream* stream = nullptr;
public:
bool isvalid() { return hSMK.isValid; }
static bool StreamCallbackFunc(SoundStream* stream, void* buff, int len, void* userdata)
{
DSmkPlayer* pId = (DSmkPlayer*)userdata;
memcpy(buff, &pId->adata.samples[pId->adata.nRead], len);
pId->adata.nRead += len / 2;
if (pId->adata.nRead >= countof(pId->adata.samples)) pId->adata.nRead = 0;
return true;
}
void copy8bitSamples(unsigned count)
{
for (unsigned i = 0; i < count; i++)
{
adata.samples[adata.nWrite] = (audioBuffer[i] - 128) << 8;
if (++adata.nWrite >= countof(adata.samples)) adata.nWrite = 0;
}
}
void copy16bitSamples(unsigned count)
{
auto ptr = (uint16_t*)audioBuffer.Data();
for (unsigned i = 0; i < count/2; i++)
{
adata.samples[adata.nWrite] = *ptr++;
if (++adata.nWrite >= countof(adata.samples)) adata.nWrite = 0;
}
}
DSmkPlayer(const char *fn, const AnimSound* ans, bool fixedviewport)
{
hSMK = Smacker_Open(fn);
if (!hSMK.isValid)
{
return;
}
Smacker_GetFrameSize(hSMK, nWidth, nHeight);
pFrame.Resize(nWidth * nHeight + std::max(nWidth, nHeight));
nFrameRate = Smacker_GetFrameRate(hSMK);
nFrameNs = 1'000'000'000 / nFrameRate;
nFrames = Smacker_GetNumFrames(hSMK);
Smacker_GetPalette(hSMK, palette);
fullscreenScale = (!fixedviewport || (nWidth <= 320 && nHeight <= 200) || nWidth >= 640 || nHeight >= 480);
bool hassound = false;
numAudioTracks = Smacker_GetNumAudioTracks(hSMK);
if (numAudioTracks)
{
adata.nWrite = 0;
adata.nRead = 0;
adata.inf = Smacker_GetAudioTrackDetails(hSMK, 0);
if (adata.inf.idealBufferSize > 0)
{
audioBuffer.Resize(adata.inf.idealBufferSize);
auto read = Smacker_GetAudioData(hSMK, 0, (int16_t*)audioBuffer.Data());
if (adata.inf.bitsPerSample == 8) copy8bitSamples(read);
else copy16bitSamples(read);
animSnd = nullptr;
hassound = true;
}
}
if (!hassound)
{
adata.inf = {};
animSnd = ans;
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
int Frame(uint64_t clock, bool skiprequest) override
{
int frame = clock / nFrameNs;
if (clock == 0)
{
animtex.SetSize(AnimTexture::Paletted, nWidth, nHeight);
}
twod->ClearScreen();
if (frame > nFrame)
{
Smacker_GetPalette(hSMK, palette);
Smacker_GetFrame(hSMK, pFrame.Data());
animtex.SetFrame(palette, pFrame.Data());
if (numAudioTracks)
{
auto read = Smacker_GetAudioData(hSMK, 0, (int16_t*)audioBuffer.Data());
if (adata.inf.bitsPerSample == 8) copy8bitSamples(read);
else copy16bitSamples(read);
if (!stream && read) // the sound may not start in the first frame, but the stream cannot start without any sound data present.
stream = S_CreateCustomStream(6000, adata.inf.sampleRate, adata.inf.nChannels, StreamCallbackFunc, this);
}
}
if (fullscreenScale)
{
DrawTexture(twod, animtex.GetFrame(), 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit43, TAG_DONE);
}
else
{
DrawTexture(twod, animtex.GetFrame(), 320, 240, DTA_VirtualWidth, 640, DTA_VirtualHeight, 480, DTA_CenterOffset, true, TAG_DONE);
}
if (frame > nFrame)
{
nFrame++;
Smacker_GetNextFrame(hSMK);
if (animSnd) for (int i = 0; animSnd[i].framenum >= 0; i++)
{
if (animSnd[i].framenum == nFrame)
{
int sound = animSnd[i].soundnum;
if (sound == -1)
soundEngine->StopAllChannels();
else if (SoundEnabled())
soundEngine->StartSound(SOURCE_None, nullptr, nullptr, CHAN_AUTO, CHANF_UI, sound, 1.f, ATTN_NONE);
}
}
}
return skiprequest ? -1 : nFrame < nFrames ? 1 : 0;
}
void OnDestroy() override
{
Smacker_Close(hSMK);
if (stream) S_StopCustomStream(stream);
soundEngine->StopAllChannels();
animtex.Clean();
}
};
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
DScreenJob* PlayVideo(const char* filename, const AnimSound* ans, const int* frameticks, bool nosoundcutoff)
{
auto nothing = []()->DScreenJob* { return Create<DScreenJob>(); };
if (!filename)
{
return nothing();
}
FileReader fr;
// first try as .ivf - but only if sounds are provided - the decoder is video only.
if (ans)
{
auto fn = StripExtension(filename);
DefaultExtension(fn, ".ivf");
fr = fileSystem.OpenFileReader(fn);
}
if (!fr.isOpen()) fr = fileSystem.OpenFileReader(filename);
if (!fr.isOpen())
{
int nLen = strlen(filename);
// Strip the drive letter and retry.
if (nLen >= 3 && isalpha(filename[0]) && filename[1] == ':' && filename[2] == '/')
{
filename += 3;
fr = fileSystem.OpenFileReader(filename);
}
if (!fr.isOpen())
{
Printf(PRINT_BOLD, "%s: Unable to open video\n", filename);
return nothing();
}
}
char id[20] = {};
fr.Read(&id, 20);
fr.Seek(-20, FileReader::SeekCur);
if (!memcmp(id, "LPF ", 4))
{
auto anm = Create<DAnmPlayer>(fr, ans, frameticks, nosoundcutoff);
if (!anm->isvalid())
{
Printf(PRINT_BOLD, "%s: invalid ANM file.\n", filename);
anm->Destroy();
return nothing();
}
return anm;
}
else if (!memcmp(id, "SMK2", 4))
{
fr.Close();
auto anm = Create<DSmkPlayer>(filename, ans, true); // Fixme: Handle Blood's video scaling behavior more intelligently.
if (!anm->isvalid())
{
Printf(PRINT_BOLD, "%s: invalid SMK file.\n", filename);
anm->Destroy();
return nothing();
}
return anm;
}
else if (!memcmp(id, "Interplay MVE File", 18))
{
auto anm = Create<DMvePlayer>(fr);
if (!anm->isvalid())
{
anm->Destroy();
return nothing();
}
return anm;
}
else if (!memcmp(id, "DKIF\0\0 \0VP80", 12))
{
auto anm = Create<DVpxPlayer>(fr, ans, frameticks? frameticks[1] : 0 );
if (!anm->isvalid())
{
anm->Destroy();
return nothing();
}
anm->soundtrack = LookupMusic(filename, true);
return anm;
}
// add more formats here.
else
{
Printf(PRINT_BOLD, "%s: Unknown video format\n", filename);
}
return nothing();
}
//---------------------------------------------------------------------------
//
//
@ -771,11 +125,10 @@ class ScreenJobRunner
int index = -1;
float screenfade;
bool clearbefore;
int64_t startTime = -1;
int64_t lastTime = -1;
int actionState;
int terminateState;
uint64_t clock = 0;
int fadeticks = 0;
int last_paused_tic = -1;
public:
ScreenJobRunner(JobDesc* jobs_, int count, CompletionFunc completion_, bool clearbefore_)
@ -820,75 +173,92 @@ public:
index++;
}
actionState = clearbefore ? State_Clear : State_Run;
if (index < jobs.Size()) screenfade = jobs[index].job->fadestyle & DScreenJob::fadein ? 0.f : 1.f;
lastTime= startTime = -1;
clock = 0;
if (index < jobs.Size())
{
jobs[index].job->fadestate = !paused && jobs[index].job->fadestyle & DScreenJob::fadein? DScreenJob::fadein : DScreenJob::visible;
jobs[index].job->Start();
}
inputState.ClearAllInput();
}
int DisplayFrame()
int DisplayFrame(double smoothratio)
{
auto& job = jobs[index];
auto now = I_GetTimeNS();
bool processed = job.job->ProcessInput();
if (startTime == -1)
{
lastTime = startTime = now;
}
else if (!M_Active())
{
clock += now - lastTime;
if (clock == 0) clock = 1;
}
bool skiprequest = clock > 100'000'000 && inputState.CheckAllInput() && !processed && job.job->fadestate != DScreenJob::fadeout;
lastTime = now;
if (screenfade < 1.f && !M_Active())
if (job.job->fadestate == DScreenJob::fadein)
{
float ms = (clock / 1'000'000) / job.job->fadetime;
screenfade = clamp(ms, 0.f, 1.f);
double ms = (job.job->ticks + smoothratio) * 1000 / GameTicRate / job.job->fadetime;
float screenfade = (float)clamp(ms, 0., 1.);
twod->SetScreenFade(screenfade);
if (job.job->fadestate != DScreenJob::fadeout)
job.job->fadestate = DScreenJob::fadein;
if (screenfade == 1.f) job.job->fadestate = DScreenJob::visible;
}
else
{
job.job->fadestate = DScreenJob::visible;
screenfade = 1.f;
}
job.job->SetClock(clock);
int state = job.job->Frame(clock, skiprequest);
clock = job.job->GetClock();
if (clock == 0) clock = 1;
int state = job.job->DrawFrame(smoothratio);
twod->SetScreenFade(1.f);
return state;
}
int FadeoutFrame()
int FadeoutFrame(double smoothratio)
{
auto now = I_GetTimeNS();
if (startTime == -1)
{
lastTime = startTime = now;
}
else if (!M_Active())
{
clock += now - lastTime;
if (clock == 0) clock = 1;
}
lastTime = now;
float ms = (clock / 1'000'000) / jobs[index].job->fadetime;
float screenfade2 = clamp(screenfade - ms, 0.f, 1.f);
if (!M_Active()) twod->SetScreenFade(screenfade2);
if (screenfade2 <= 0.f)
{
twod->Unlock(); // must unlock before displaying.
return 0;
}
return 1;
auto& job = jobs[index];
double ms = (fadeticks + smoothratio) * 1000 / GameTicRate / job.job->fadetime;
float screenfade = 1.f - (float)clamp(ms, 0., 1.);
twod->SetScreenFade(screenfade);
job.job->DrawFrame(1.);
return (screenfade > 0.f);
}
bool OnEvent(event_t* ev)
{
if (paused || index >= jobs.Size()) return false;
if (ev->type == EV_KeyDown)
{
// We never reach the key binding checks in G_Responder, so for the console we have to check for ourselves here.
auto binding = Bindings.GetBinding(ev->data1);
if (binding.CompareNoCase("toggleconsole") == 0)
{
C_ToggleConsole();
return true;
}
}
if (jobs[index].job->state != DScreenJob::running) return false;
return jobs[index].job->OnEvent(ev);
}
void OnFinished()
{
if (completion) completion(false);
completion = nullptr; // only finish once.
}
void OnTick()
{
if (paused) return;
if (index >= jobs.Size())
{
//DeleteJobs();
//twod->SetScreenFade(1);
//twod->ClearScreen(); // This must not leave the 2d buffer empty.
//if (gamestate == GS_INTRO) OnFinished();
//else Net_WriteByte(DEM_ENDSCREENJOB); // intermissions must be terminated synchronously.
}
else
{
if (jobs[index].job->state == DScreenJob::running)
{
jobs[index].job->ticks++;
jobs[index].job->OnTick();
}
else if (jobs[index].job->state == DScreenJob::stopping)
{
fadeticks++;
}
}
}
bool RunFrame()
{
if (index >= jobs.Size())
@ -899,6 +269,13 @@ public:
if (completion) completion(false);
return false;
}
// ensure that we won't go back in time if the menu is dismissed without advancing our ticker
bool menuon = paused;
if (menuon) last_paused_tic = jobs[index].job->ticks;
else if (last_paused_tic == jobs[index].job->ticks) menuon = true;
double smoothratio = menuon ? 1. : I_GetTimeFrac();
if (actionState == State_Clear)
{
actionState = State_Run;
@ -906,18 +283,16 @@ public:
}
else if (actionState == State_Run)
{
terminateState = DisplayFrame();
terminateState = DisplayFrame(smoothratio);
if (terminateState < 1)
{
// Must lock before displaying.
if (jobs[index].job->fadestyle & DScreenJob::fadeout)
{
twod->Lock();
startTime = -1;
clock = 0;
jobs[index].job->fadestate = DScreenJob::fadeout;
gamestate = GS_INTRO; // block menu and console during fadeout - this can cause timing problems.
jobs[index].job->state = DScreenJob::stopping;
actionState = State_Fadeout;
fadeticks = 0;
}
else
{
@ -927,9 +302,10 @@ public:
}
else if (actionState == State_Fadeout)
{
int ended = FadeoutFrame();
int ended = FadeoutFrame(smoothratio);
if (ended < 1)
{
jobs[index].job->state = DScreenJob::stopped;
AdvanceJob(terminateState < 0);
}
}
@ -964,7 +340,25 @@ void DeleteScreenJob()
twod->SetScreenFade(1);
}
void RunScreenJobFrame()
void EndScreenJob()
{
if (runner) runner->OnFinished();
DeleteScreenJob();
}
bool ScreenJobResponder(event_t* ev)
{
if (runner) return runner->OnEvent(ev);
return false;
}
void ScreenJobTick()
{
if (runner) runner->OnTick();
}
bool ScreenJobDraw()
{
// we cannot recover from this because we have no completion callback to call.
if (!runner)
@ -972,7 +366,7 @@ void RunScreenJobFrame()
// We can get here before a gameaction has been processed. In that case just draw a black screen and wait.
if (gameaction == ga_nothing) I_Error("Trying to run a non-existent screen job");
twod->ClearScreen();
return;
return false;
}
auto res = runner->RunFrame();
if (!res)
@ -980,5 +374,6 @@ void RunScreenJobFrame()
assert((gamestate != GS_INTERMISSION && gamestate != GS_INTRO) || gameaction != ga_nothing);
DeleteScreenJob();
}
return res;
}

View file

@ -2,6 +2,7 @@
#include <functional>
#include "dobject.h"
#include "v_2ddrawer.h"
#include "d_eventbase.h"
using CompletionFunc = std::function<void(bool)>;
struct JobDesc;
@ -10,14 +11,25 @@ class ScreenJobRunner;
class DScreenJob : public DObject
{
DECLARE_CLASS(DScreenJob, DObject)
int64_t now;
const int fadestyle;
const float fadetime; // in milliseconds
int fadestate = fadein;
friend class ScreenJobRunner;
protected:
int ticks = 0;
int state = running;
bool pausable = true;
public:
enum
{
running = 1, // normal operation
skipped = 2, // finished by user skipping
finished = 3, // finished by completing its sequence
stopping = 4, // running ending animations / fadeout, etc. Will not accept more input.
stopped = 5, // we're done here.
};
enum
{
visible = 0,
@ -32,17 +44,20 @@ public:
return false;
}
void SetClock(int64_t nsnow)
virtual void Start() {}
virtual bool OnEvent(event_t* evt) { return false; }
virtual void OnTick() { /*state = finished;*/ }
virtual void Draw(double smoothratio) {}
int DrawFrame(double smoothratio)
{
now = nsnow;
if (state != running) smoothratio = 1; // this is necessary because the ticker won't be incremented anymore to avoid having a negative time span.
Draw(smoothratio);
if (state == skipped) return -1;
if (state == finished) return 0;
return 1;
}
int64_t GetClock() const
{
return now;
}
virtual int Frame(uint64_t clock, bool skiprequest) { return 0; }
int GetFadeState() const { return fadestate; }
};
@ -53,13 +68,31 @@ public:
//
//---------------------------------------------------------------------------
class DSkippableScreenJob : public DScreenJob
{
protected:
DSkippableScreenJob(int fade = 0, float fadet = 250.f) : DScreenJob(fade, fadet)
{}
bool OnEvent(event_t* evt) override;
virtual void Skipped() {}
};
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
class DBlackScreen : public DScreenJob
{
int wait;
bool cleared = false;
public:
DBlackScreen(int w) : wait(w) {}
int Frame(uint64_t clock, bool skiprequest) override;
void OnTick() override;
void Draw(double smooth) override;
};
//---------------------------------------------------------------------------
@ -68,29 +101,31 @@ public:
//
//---------------------------------------------------------------------------
class DImageScreen : public DScreenJob
class DImageScreen : public DSkippableScreenJob
{
DECLARE_CLASS(DImageScreen, DScreenJob)
int tilenum = -1;
int trans;
int waittime; // in ms.
bool cleared = false;
FGameTexture* tex = nullptr;
public:
DImageScreen(FGameTexture* tile, int fade = DScreenJob::fadein | DScreenJob::fadeout, int wait = 3000, int translation = 0) : DScreenJob(fade), waittime(wait)
DImageScreen(FGameTexture* tile, int fade = DScreenJob::fadein | DScreenJob::fadeout, int wait = 3000, int translation = 0) : DSkippableScreenJob(fade), waittime(wait)
{
tex = tile;
trans = translation;
}
DImageScreen(int tile, int fade = DScreenJob::fadein | DScreenJob::fadeout, int wait = 3000, int translation = 0) : DScreenJob(fade), waittime(wait)
DImageScreen(int tile, int fade = DScreenJob::fadein | DScreenJob::fadeout, int wait = 3000, int translation = 0) : DSkippableScreenJob(fade), waittime(wait)
{
tilenum = tile;
trans = translation;
}
int Frame(uint64_t clock, bool skiprequest) override;
void OnTick() override;
void Draw(double smooth) override;
};
@ -105,8 +140,11 @@ struct JobDesc
void RunScreenJob(JobDesc *jobs, int count, CompletionFunc completion, bool clearbefore = true, bool blockingui = false);
void EndScreenJob();
void DeleteScreenJob();
void RunScreenJobFrame();
bool ScreenJobResponder(event_t* ev);
void ScreenJobTick();
bool ScreenJobDraw();
struct AnimSound
{

View file

@ -821,7 +821,7 @@ void tileUpdateAnimations()
{
for (int i = 0; i < MAXTILES; i++)
{
if (picanm[i].sf & PICANM_ANIMTYPE_MASK)
if (TileFiles.tiledata[i].picanm.sf & PICANM_ANIMTYPE_MASK)
{
int j = i + animateoffs(i, 0);

View file

@ -411,25 +411,29 @@ extern PicAnm picanm;
inline int tileWidth(int num)
{
assert(num < MAXTILES);
assert((unsigned)num < MAXTILES);
if ((unsigned)num >= MAXTILES) return 1;
return (int)TileFiles.tiledata[num].texture->GetDisplayWidth();
}
inline int tileHeight(int num)
{
assert(num < MAXTILES);
assert((unsigned)num < MAXTILES);
if ((unsigned)num >= MAXTILES) return 1;
return (int)TileFiles.tiledata[num].texture->GetDisplayHeight();
}
inline int tileLeftOffset(int num)
{
assert(num < MAXTILES);
assert((unsigned)num < MAXTILES);
if ((unsigned)num >= MAXTILES) return 0;
return (int)TileFiles.tiledata[num].texture->GetDisplayLeftOffset();
}
inline int tileTopOffset(int num)
{
assert(num < MAXTILES);
assert((unsigned)num < MAXTILES);
if ((unsigned)num >= MAXTILES) return 0;
return (int)TileFiles.tiledata[num].texture->GetDisplayTopOffset();
}
@ -444,11 +448,11 @@ int32_t animateoffs(int const tilenum, int fakevar);
inline FGameTexture* tileGetTexture(int tile, bool animate = false)
{
assert(tile < MAXTILES);
assert((unsigned)tile < MAXTILES);
if (tile < 0 || tile >= MAXTILES) return nullptr;
if (animate)
{
if (picanm[tile].sf & PICANM_ANIMTYPE_MASK)
if (TileFiles.tiledata[tile].picanm.sf & PICANM_ANIMTYPE_MASK)
tile += animateoffs(tile, 0);
}

File diff suppressed because it is too large Load diff

View file

@ -70,16 +70,16 @@ struct THINGINFO
{
short startHealth;
short mass;
unsigned char clipdist;
uint8_t clipdist;
short flags;
int elastic; // elasticity
int dmgResist; // damage resistance
short cstat;
short picnum;
char shade;
unsigned char pal;
unsigned char xrepeat; // xrepeat
unsigned char yrepeat; // yrepeat
int8_t shade;
uint8_t pal;
uint8_t xrepeat; // xrepeat
uint8_t yrepeat; // yrepeat
int dmgControl[kDamageMax]; // damage
};
@ -87,23 +87,23 @@ struct AMMOITEMDATA
{
short cstat;
short picnum;
char shade;
char pal;
unsigned char xrepeat;
unsigned char yrepeat;
int8_t shade;
uint8_t pal;
uint8_t xrepeat;
uint8_t yrepeat;
short count;
unsigned char type;
unsigned char weaponType;
uint8_t type;
uint8_t weaponType;
};
struct WEAPONITEMDATA
{
short cstat;
short picnum;
char shade;
char pal;
unsigned char xrepeat;
unsigned char yrepeat;
int8_t shade;
uint8_t pal;
uint8_t xrepeat;
uint8_t yrepeat;
short type;
short ammoType;
short count;
@ -113,10 +113,10 @@ struct ITEMDATA
{
short cstat;
short picnum;
char shade;
char pal;
unsigned char xrepeat;
unsigned char yrepeat;
int8_t shade;
uint8_t pal;
uint8_t xrepeat;
uint8_t yrepeat;
short packSlot;
};
@ -125,15 +125,15 @@ struct MissileType
short picnum;
int velocity;
int angleOfs;
unsigned char xrepeat;
unsigned char yrepeat;
char shade;
unsigned char clipDist;
uint8_t xrepeat;
uint8_t yrepeat;
int8_t shade;
uint8_t clipDist;
};
struct EXPLOSION
{
unsigned char repeat;
uint8_t repeat;
char dmg;
char dmgRng;
int radius;

View file

@ -64,7 +64,7 @@ void playlogos()
if (!userConfig.nologo)
{
if (fileSystem.FindFile("logo.smk"))
if (fileSystem.FindFile("logo.smk") != -1)
{
jobs[job++] = { PlayVideo("logo.smk", &logosound[0], 0) };
}
@ -73,7 +73,7 @@ void playlogos()
jobs[job++] = { Create<DBlackScreen>(1), []() { sndStartSample("THUNDER2", 128, -1); } };
jobs[job++] = { Create<DImageScreen>(2050) };
}
if (fileSystem.FindFile("gti.smk"))
if (fileSystem.FindFile("gti.smk") != -1)
{
jobs[job++] = { PlayVideo("gti.smk", &logosound[2], 0) };
}

View file

@ -987,7 +987,7 @@ void dbLoadMap(const char *pPath, int *pX, int *pY, int *pZ, short *pAngle, shor
fr.Seek(0, FileReader::SeekSet);
auto buffer = fr.Read();
unsigned char md4[16];
uint8_t md4[16];
md4once(buffer.Data(), buffer.Size(), md4);
G_LoadMapHack(mapname, md4);

View file

@ -30,7 +30,7 @@ struct DUDEINFO {
short startHealth; // health
unsigned short mass; // mass
int at6; // unused?
unsigned char clipdist; // clipdist
uint8_t clipdist; // clipdist
int eyeHeight;
int aimHeight; // used by just Cerberus
int hearDist; // hear radius

View file

@ -91,7 +91,7 @@ static void DrawCaption(const char* text)
}
class DBloodSummaryScreen : public DScreenJob
class DBloodSummaryScreen : public DSkippableScreenJob
{
void DrawKills(void)
{
@ -139,7 +139,7 @@ class DBloodSummaryScreen : public DScreenJob
}
int Frame(uint64_t clock, bool skiprequest)
void Draw(double) override
{
drawTextScreenBackground();
if (gGameOptions.nGameType == 0)
@ -160,7 +160,7 @@ class DBloodSummaryScreen : public DScreenJob
DrawCaption(GStrings("TXTB_FRAGSTATS"));
DrawKills();
}
int myclock = int(clock * 120 / 1'000'000'000);
int myclock = ticks * 120 / GameTicRate;
if ((myclock & 32))
{
auto text = GStrings("PRESSKEY");
@ -168,7 +168,6 @@ class DBloodSummaryScreen : public DScreenJob
if (!SmallFont2->CanPrint(text)) font = 0;
viewDrawText(font, text, 160, 134, -128, 0, 1, font == 3);
}
return skiprequest ? -1 : 1;
}
};
@ -274,7 +273,7 @@ CSecretMgr gSecretMgr;
CKillMgr gKillMgr;
class DBloodLoadScreen : public DScreenJob
{
{
const char* pzLoadingScreenText1;
MapRecord* rec;
@ -285,7 +284,7 @@ public:
else pzLoadingScreenText1 = GStrings(FStringf("TXTB_NETGT%d", gGameOptions.nGameType));
}
int Frame(uint64_t clock, bool skiprequest)
void Draw(double) override
{
twod->ClearScreen();
drawTextScreenBackground();
@ -297,7 +296,6 @@ public:
if (!SmallFont2->CanPrint(text)) font = 0;
viewDrawText(font, GStrings("TXTB_PLSWAIT"), 160, 134, -128, 0, 1, font == 3);
return 0;
}
};

View file

@ -109,10 +109,10 @@ void FireProcess(void)
void CellularFrame(char *pFrame, int sizeX, int sizeY)
{
int nSquare = sizeX * sizeY;
unsigned char *pPtr1 = (unsigned char*)pFrame;
uint8_t *pPtr1 = (uint8_t*)pFrame;
while (nSquare--)
{
unsigned char *pPtr2 = pPtr1+sizeX;
uint8_t *pPtr2 = pPtr1+sizeX;
int sum = *(pPtr2-1) + *pPtr2 + *(pPtr2+1) + *(pPtr2+sizeX);
if (*(pPtr2+sizeX) > 96)
{

View file

@ -32,18 +32,18 @@ CFX gFX;
struct FXDATA {
CALLBACK_ID funcID; // callback
char at1; // detail
short at2; // seq
short Kills; // flags
int at6; // gravity
int ata; // air drag
uint8_t detail; // detail
short seq; // seq
short flags; // flags
int gravity; // gravity
int drag; // air drag
int ate;
short at12; // picnum
unsigned char at14; // xrepeat
unsigned char at15; // yrepeat
short at16; // cstat
signed char at18; // shade
char at19; // pal
short picnum; // picnum
uint8_t xrepeat; // xrepeat
uint8_t yrepeat; // yrepeat
short cstat; // cstat
int8_t shade; // shade
uint8_t pal; // pal
};
FXDATA gFXData[] = {
@ -166,23 +166,23 @@ spritetype * CFX::fxSpawn(FX_ID nFx, int nSector, int x, int y, int z, unsigned
}
spritetype *pSprite = actSpawnSprite(nSector, x, y, z, 1, 0);
pSprite->type = nFx;
pSprite->picnum = pFX->at12;
pSprite->cstat |= pFX->at16;
pSprite->shade = pFX->at18;
pSprite->pal = pFX->at19;
sprite[pSprite->index].detail = pFX->at1;
if (pFX->at14 > 0)
pSprite->xrepeat = pFX->at14;
if (pFX->at15 > 0)
pSprite->yrepeat = pFX->at15;
if ((pFX->Kills & 1) && Chance(0x8000))
pSprite->picnum = pFX->picnum;
pSprite->cstat |= pFX->cstat;
pSprite->shade = pFX->shade;
pSprite->pal = pFX->pal;
sprite[pSprite->index].detail = pFX->detail;
if (pFX->xrepeat > 0)
pSprite->xrepeat = pFX->xrepeat;
if (pFX->yrepeat > 0)
pSprite->yrepeat = pFX->yrepeat;
if ((pFX->flags & 1) && Chance(0x8000))
pSprite->cstat |= 4;
if ((pFX->Kills & 2) && Chance(0x8000))
if ((pFX->flags & 2) && Chance(0x8000))
pSprite->cstat |= 8;
if (pFX->at2)
if (pFX->seq)
{
int nXSprite = dbInsertXSprite(pSprite->index);
seqSpawn(pFX->at2, 3, nXSprite, -1);
seqSpawn(pFX->seq, 3, nXSprite, -1);
}
if (a6 == 0)
a6 = pFX->ate;
@ -203,7 +203,7 @@ void CFX::fxProcess(void)
assert(nSector >= 0 && nSector < kMaxSectors);
assert(pSprite->type < kFXMax);
FXDATA *pFXData = &gFXData[pSprite->type];
actAirDrag(pSprite, pFXData->ata);
actAirDrag(pSprite, pFXData->drag);
if (xvel[nSprite])
pSprite->x += xvel[nSprite]>>12;
if (yvel[nSprite])
@ -257,7 +257,7 @@ void CFX::fxProcess(void)
continue;
}
}
zvel[nSprite] += pFXData->at6;
zvel[nSprite] += pFXData->gravity;
}
}
@ -349,9 +349,9 @@ void fxPrecache()
{
for (int i = 0; i < kFXMax; i++)
{
tilePrecacheTile(gFXData[i].at12, 0, 0);
if (gFXData[i].at2)
seqPrecacheId(gFXData[i].at2, 0);
tilePrecacheTile(gFXData[i].picnum, 0, 0);
if (gFXData[i].seq)
seqPrecacheId(gFXData[i].seq, 0);
}
}

View file

@ -44,8 +44,8 @@ BEGIN_BLD_NS
static struct {
short nTile;
unsigned char nStat;
unsigned char nPal;
uint8_t nStat;
uint8_t nPal;
int nScale;
short nX, nY;
} burnTable[9] = {

View file

@ -47,8 +47,8 @@ enum EGameFlag
};
struct GAMEOPTIONS {
unsigned char nGameType;
unsigned char nDifficulty;
uint8_t nGameType;
uint8_t nDifficulty;
char nMonsterSettings;
int uGameFlags;
int uNetGameFlags;

View file

@ -49,7 +49,7 @@ void WeaponInit(void);
void WeaponDraw(PLAYER *pPlayer, int a2, double a3, double a4, int a5, int smoothratio);
void WeaponRaise(PLAYER *pPlayer);
void WeaponLower(PLAYER *pPlayer);
char WeaponUpgrade(PLAYER *pPlayer, char newWeapon);
int WeaponUpgrade(PLAYER *pPlayer, int newWeapon);
void WeaponProcess(PLAYER *pPlayer);
void WeaponUpdateState(PLAYER* pPlayer);
void teslaHit(spritetype *pMissile, int a2);
@ -114,7 +114,7 @@ enum SurfaceType {
};
extern char surfType[MAXTILES];
extern signed char tileShade[MAXTILES];
extern int8_t tileShade[MAXTILES];
extern short voxelIndex[MAXTILES];
extern int nPrecacheCount;

View file

@ -182,7 +182,7 @@ struct PLAYER
struct AMMOINFO
{
int max;
signed char vectorType;
int8_t vectorType;
};
struct POWERUPINFO

View file

@ -46,7 +46,7 @@ struct TILE_FRAME
int y;
int z;
int stat;
signed char shade;
int8_t shade;
char palnum;
unsigned short angle;
};
@ -54,9 +54,9 @@ struct TILE_FRAME
struct SOUNDINFO
{
int sound;
unsigned char priority;
unsigned char sndFlags; // (by NoOne) Various sound flags
unsigned char sndRange; // (by NoOne) Random sound range
uint8_t priority;
uint8_t sndFlags; // (by NoOne) Various sound flags
uint8_t sndRange; // (by NoOne) Random sound range
char reserved[1];
};

View file

@ -74,7 +74,7 @@ struct Seq {
struct ACTIVE
{
unsigned char type;
uint8_t type;
unsigned short xindex;
};
@ -86,7 +86,7 @@ struct SEQINST
int nSeqID;
int callback;
short timeCounter;
unsigned char frameIndex;
uint8_t frameIndex;
void Update();
};

View file

@ -41,7 +41,7 @@ int tileEnd[256];
int hTileFile[256];
char surfType[kMaxTiles];
signed char tileShade[kMaxTiles];
int8_t tileShade[kMaxTiles];
short voxelIndex[kMaxTiles];
int tileInit(char a1, const char *a2)

View file

@ -144,7 +144,7 @@ enum
QAV *weaponQAV[kQAVEnd];
char sub_4B1A4(PLAYER *pPlayer)
static bool sub_4B1A4(PLAYER *pPlayer)
{
switch (pPlayer->curWeapon)
{
@ -169,12 +169,12 @@ char sub_4B1A4(PLAYER *pPlayer)
return 0;
}
char BannedUnderwater(int nWeapon)
static bool BannedUnderwater(int nWeapon)
{
return nWeapon == 7 || nWeapon == 6;
}
char CheckWeaponAmmo(PLAYER *pPlayer, int weapon, int ammotype, int count)
static bool CheckWeaponAmmo(PLAYER *pPlayer, int weapon, int ammotype, int count)
{
if (gInfiniteAmmo)
return 1;
@ -187,7 +187,7 @@ char CheckWeaponAmmo(PLAYER *pPlayer, int weapon, int ammotype, int count)
return pPlayer->ammoCount[ammotype] >= count;
}
char CheckAmmo(PLAYER *pPlayer, int ammotype, int count)
static bool CheckAmmo(PLAYER *pPlayer, int ammotype, int count)
{
if (gInfiniteAmmo)
return 1;
@ -200,7 +200,7 @@ char CheckAmmo(PLAYER *pPlayer, int ammotype, int count)
return pPlayer->ammoCount[ammotype] >= count;
}
char checkAmmo2(PLAYER *pPlayer, int ammotype, int amount)
static bool checkAmmo2(PLAYER *pPlayer, int ammotype, int amount)
{
if (gInfiniteAmmo)
return 1;
@ -1735,7 +1735,7 @@ void FireBeast(int nTrigger, PLAYER * pPlayer)
actFireVector(pPlayer->pSprite, 0, pPlayer->zWeapon-pPlayer->pSprite->z, pPlayer->aim.dx+r1, pPlayer->aim.dy+r2, pPlayer->aim.dz+r3, VECTOR_TYPE_9);
}
char gWeaponUpgrade[][13] = {
uint8_t gWeaponUpgrade[][13] = {
{ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
{ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
{ 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0 },
@ -1751,9 +1751,9 @@ char gWeaponUpgrade[][13] = {
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
};
char WeaponUpgrade(PLAYER *pPlayer, char newWeapon)
int WeaponUpgrade(PLAYER *pPlayer, int newWeapon)
{
char weapon = pPlayer->curWeapon;
int weapon = pPlayer->curWeapon;
if (!sub_4B1A4(pPlayer) && (cl_weaponswitch&1) && (gWeaponUpgrade[pPlayer->curWeapon][newWeapon] || (cl_weaponswitch&2)))
weapon = newWeapon;
return weapon;
@ -1762,7 +1762,7 @@ char WeaponUpgrade(PLAYER *pPlayer, char newWeapon)
int OrderNext[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 1 };
int OrderPrev[] = { 12, 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 1 };
char WeaponFindNext(PLAYER *pPlayer, int *a2, char bDir)
static int WeaponFindNext(PLAYER *pPlayer, int *a2, int bDir)
{
int weapon = pPlayer->curWeapon;
do
@ -1795,9 +1795,9 @@ char WeaponFindNext(PLAYER *pPlayer, int *a2, char bDir)
return weapon;
}
char WeaponFindLoaded(PLAYER *pPlayer, int *a2)
static int WeaponFindLoaded(PLAYER *pPlayer, int *a2)
{
char v4 = 1;
int v4 = 1;
int v14 = 0;
if (weaponModes[pPlayer->curWeapon].update > 1)
{
@ -1874,7 +1874,7 @@ int processSprayCan(PLAYER *pPlayer)
return 0;
}
char processTNT(PLAYER *pPlayer)
static bool processTNT(PLAYER *pPlayer)
{
switch (pPlayer->weaponState)
{
@ -1912,7 +1912,7 @@ char processTNT(PLAYER *pPlayer)
return 0;
}
char processProxy(PLAYER *pPlayer)
static bool processProxy(PLAYER *pPlayer)
{
switch (pPlayer->weaponState)
{
@ -1929,7 +1929,7 @@ char processProxy(PLAYER *pPlayer)
return 0;
}
char processRemote(PLAYER *pPlayer)
static bool processRemote(PLAYER *pPlayer)
{
switch (pPlayer->weaponState)
{
@ -1945,7 +1945,7 @@ char processRemote(PLAYER *pPlayer)
return 0;
}
char processLeech(PLAYER *pPlayer)
static bool processLeech(PLAYER *pPlayer)
{
switch (pPlayer->weaponState)
{
@ -1969,7 +1969,7 @@ char processLeech(PLAYER *pPlayer)
return 0;
}
char processTesla(PLAYER *pPlayer)
static bool processTesla(PLAYER *pPlayer)
{
switch (pPlayer->weaponState)
{
@ -2109,7 +2109,7 @@ void WeaponProcess(PLAYER *pPlayer) {
}
pPlayer->nextWeapon = 0;
int t;
char weapon = WeaponFindNext(pPlayer, &t, 1);
int weapon = WeaponFindNext(pPlayer, &t, 1);
pPlayer->weaponMode[weapon] = t;
if (VanillaMode())
{
@ -2131,7 +2131,7 @@ void WeaponProcess(PLAYER *pPlayer) {
}
pPlayer->nextWeapon = 0;
int t;
char weapon = WeaponFindNext(pPlayer, &t, 0);
int weapon = WeaponFindNext(pPlayer, &t, 0);
pPlayer->weaponMode[weapon] = t;
if (VanillaMode())
{
@ -2146,7 +2146,7 @@ void WeaponProcess(PLAYER *pPlayer) {
}
else if (pPlayer->input.getNewWeapon() == WeaponSel_Alt)
{
char weapon;
int weapon;
switch (pPlayer->curWeapon)
{
@ -2189,7 +2189,7 @@ void WeaponProcess(PLAYER *pPlayer) {
{
pPlayer->weaponState = 0;
int t;
char weapon = WeaponFindLoaded(pPlayer, &t);
int weapon = WeaponFindLoaded(pPlayer, &t);
pPlayer->weaponMode[weapon] = t;
if (pPlayer->curWeapon)
{
@ -2263,7 +2263,7 @@ void WeaponProcess(PLAYER *pPlayer) {
{
pPlayer->weaponState = 0;
int t;
char weapon = WeaponFindLoaded(pPlayer, &t);
int weapon = WeaponFindLoaded(pPlayer, &t);
pPlayer->weaponMode[weapon] = t;
if (pPlayer->curWeapon)
{
@ -2597,11 +2597,11 @@ void teslaHit(spritetype *pMissile, int a2)
int nSector = pMissile->sectnum;
int nOwner = pMissile->owner;
GetClosestSpriteSectors(nSector, x, y, nDist, va4);
char v4 = 1;
bool v4 = true;
int v24 = -1;
actHitcodeToData(a2, &gHitInfo, &v24, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
if (a2 == 3 && v24 >= 0 && sprite[v24].statnum == kStatDude)
v4 = 0;
v4 = false;
int nSprite;
StatIterator it(kStatDude);
while ((nSprite = it.NextIndex()) >= 0)

View file

@ -166,20 +166,23 @@ static void MiniText(double x, double y, const char* t, int shade, int align = -
//
//---------------------------------------------------------------------------
class DDRealmsScreen : public DScreenJob
class DDRealmsScreen : public DSkippableScreenJob
{
public:
DDRealmsScreen() : DScreenJob(fadein | fadeout) {}
DDRealmsScreen() : DSkippableScreenJob(fadein | fadeout) {}
int Frame(uint64_t clock, bool skiprequest) override
void OnTick() override
{
if (ticks >= 7 * GameTicRate) state = finished;
}
void Draw(double smoothratio) override
{
const uint64_t duration = 7'000'000'000;
const auto tex = tileGetTexture(DREALMS, true);
int translation = tex->GetTexture()->GetImage()->UseGamePalette() ? TRANSLATION(Translation_BasePalettes, DREALMSPAL) : 0;
twod->ClearScreen();
DrawTexture(twod, tex, 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit43, DTA_TranslationIndex, translation, DTA_LegacyRenderStyle, STYLE_Normal, TAG_DONE);
return skiprequest ? -1 : clock < duration ? 1 : 0;
}
};
@ -189,27 +192,18 @@ public:
//
//---------------------------------------------------------------------------
class DTitleScreen : public DScreenJob
class DTitleScreen : public DSkippableScreenJob
{
int soundanm = 0;
public:
DTitleScreen() : DScreenJob(fadein | fadeout)
DTitleScreen() : DSkippableScreenJob(fadein | fadeout)
{
}
int Frame(uint64_t nsclock, bool skiprequest) override
void OnTick() override
{
twod->ClearScreen();
int clock = nsclock * 120 / 1'000'000'000;
twod->ClearScreen();
// Only translate if the image depends on the global palette.
auto tex = tileGetTexture(BETASCREEN, true);
int translation = tex->GetTexture()->GetImage()->UseGamePalette() ? TRANSLATION(Translation_BasePalettes, TITLEPAL) : 0;
DrawTexture(twod, tex, 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit43, DTA_TranslationIndex, translation, DTA_LegacyRenderStyle, STYLE_Normal, TAG_DONE);
int clock = ticks * 120 / GameTicRate;
if (soundanm == 0 && clock >= 120 && clock < 120 + 60)
{
soundanm = 1;
@ -231,6 +225,25 @@ public:
if (isPlutoPak()) S_PlaySound(PIPEBOMB_EXPLODE, CHAN_AUTO, CHANF_UI);
}
if (clock > (860 + 120))
{
state = finished;
}
}
void Draw(double smoothratio) override
{
twod->ClearScreen();
int clock = (ticks + smoothratio) * 120 / GameTicRate;
twod->ClearScreen();
// Only translate if the image depends on the global palette.
auto tex = tileGetTexture(BETASCREEN, true);
int translation = tex->GetTexture()->GetImage()->UseGamePalette() ? TRANSLATION(Translation_BasePalettes, TITLEPAL) : 0;
DrawTexture(twod, tex, 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit43, DTA_TranslationIndex, translation, DTA_LegacyRenderStyle, STYLE_Normal, TAG_DONE);
double scale = clamp(clock - 120, 0, 60) / 64.;
if (scale > 0.)
{
@ -263,13 +276,6 @@ public:
DTA_CenterOffsetRel, true, DTA_TranslationIndex, translation, DTA_ScaleX, scale, DTA_ScaleY, scale, TAG_DONE);
}
}
if (clock > (860 + 120))
{
return 0;
}
return skiprequest ? -1 : 1;
}
};
@ -297,7 +303,7 @@ void Logo_d(const CompletionFunc &completion)
if (!userConfig.nologo)
{
if (!isShareware()) jobs[job++] = { PlayVideo("logo.anm", logosound, logoframetimes), []() { S_PlaySpecialMusic(MUS_INTRO); } };
else jobs[job++] = { Create<DScreenJob>(), []() { S_PlaySpecialMusic(MUS_INTRO); } };
else jobs[job++] = { Create<DBlackScreen>(1), []() { S_PlaySpecialMusic(MUS_INTRO); } };
if (!isNam()) jobs[job++] = { Create<DDRealmsScreen>(), nullptr };
}
else S_PlaySpecialMusic(MUS_INTRO);
@ -311,70 +317,68 @@ void Logo_d(const CompletionFunc &completion)
//
//---------------------------------------------------------------------------
class DEpisode1End1 : public DScreenJob
class DEpisode1End1 : public DSkippableScreenJob
{
int bonuscnt = 0;
int bossani = -1;
int breatheani = -1;
bool breathebg = false;
static inline const int breathe[] =
{
0, 30,VICTORY1 + 1,176,59,
30, 60,VICTORY1 + 2,176,59,
60, 90,VICTORY1 + 1,176,59,
90, 120,0 ,176,59
};
static inline const int bossmove[] =
{
0, 120,VICTORY1 + 3,86,59,
220, 260,VICTORY1 + 4,86,59,
260, 290,VICTORY1 + 5,86,59,
290, 320,VICTORY1 + 6,86,59,
320, 350,VICTORY1 + 7,86,59,
350, 380,VICTORY1 + 8,86,59,
350, 380,VICTORY1 + 8,86,59,
};
public:
DEpisode1End1() : DScreenJob(fadein | fadeout) {}
DEpisode1End1() : DSkippableScreenJob(fadein | fadeout) {}
int Frame(uint64_t nsclock, bool skiprequest) override
void OnTick()
{
static const int breathe[] =
{
0, 30,VICTORY1 + 1,176,59,
30, 60,VICTORY1 + 2,176,59,
60, 90,VICTORY1 + 1,176,59,
90, 120,0 ,176,59
};
static const int bossmove[] =
{
0, 120,VICTORY1 + 3,86,59,
220, 260,VICTORY1 + 4,86,59,
260, 290,VICTORY1 + 5,86,59,
290, 320,VICTORY1 + 6,86,59,
320, 350,VICTORY1 + 7,86,59,
350, 380,VICTORY1 + 8,86,59,
350, 380,VICTORY1 + 8,86,59,
};
auto translation = TRANSLATION(Translation_BasePalettes, ENDINGPAL);
int currentclock = nsclock * 120 / 1'000'000'000;
uint64_t span = nsclock / 1'000'000;
twod->ClearScreen();
DrawTexture(twod, tileGetTexture(VICTORY1, true), 0, 50, DTA_FullscreenScale, FSMode_Fit320x200,
DTA_TranslationIndex, translation, DTA_LegacyRenderStyle, STYLE_Normal, DTA_TopLeft, true, TAG_DONE);
int currentclock = ticks * 120 / GameTicRate;
bossani = -1;
breathebg = false;
breatheani = -1;
// boss
if (currentclock > 390 && currentclock < 780)
{
for (int t = 0; t < 35; t += 5) if (bossmove[t + 2] && (currentclock % 390) > bossmove[t] && (currentclock % 390) <= bossmove[t + 1])
{
if (t == 10 && bonuscnt == 1)
{
if (t == 10 && bonuscnt == 1)
{
S_PlaySound(SHOTGUN_FIRE, CHAN_AUTO, CHANF_UI);
S_PlaySound(SQUISHED, CHAN_AUTO, CHANF_UI);
bonuscnt++;
bonuscnt++;
}
DrawTexture(twod, tileGetTexture(bossmove[t + 2], true), bossmove[t + 3], bossmove[t + 4], DTA_FullscreenScale, FSMode_Fit320x200,
DTA_TranslationIndex, translation, DTA_TopLeft, true, TAG_DONE);
bossani = t;
}
}
// Breathe
if (currentclock < 450 || currentclock >= 750)
{
if (currentclock >= 750)
{
DrawTexture(twod, tileGetTexture(VICTORY1 + 8, true), 86, 59, DTA_FullscreenScale, FSMode_Fit320x200,
DTA_TranslationIndex, translation, DTA_TopLeft, true, TAG_DONE);
if (currentclock >= 750 && bonuscnt == 2)
{
breathebg = true;
if (currentclock >= 750 && bonuscnt == 2)
{
S_PlaySound(DUKETALKTOBOSS, CHAN_AUTO, CHANF_UI);
bonuscnt++;
bonuscnt++;
}
}
for (int t = 0; t < 20; t += 5)
@ -385,12 +389,37 @@ public:
S_PlaySound(BOSSTALKTODUKE, CHAN_AUTO, CHANF_UI);
bonuscnt++;
}
DrawTexture(twod, tileGetTexture(breathe[t + 2], true), breathe[t + 3], breathe[t + 4], DTA_FullscreenScale, FSMode_Fit320x200,
DTA_TranslationIndex, translation, DTA_TopLeft, true, TAG_DONE);
breatheani = t;
}
}
// Only end after having faded out.
return skiprequest ? -1 : 1;
}
void Draw(double) override
{
auto translation = TRANSLATION(Translation_BasePalettes, ENDINGPAL);
twod->ClearScreen();
DrawTexture(twod, tileGetTexture(VICTORY1, true), 0, 50, DTA_FullscreenScale, FSMode_Fit320x200,
DTA_TranslationIndex, translation, DTA_LegacyRenderStyle, STYLE_Normal, DTA_TopLeft, true, TAG_DONE);
if (bossani != -1)
{
DrawTexture(twod, tileGetTexture(bossmove[bossani + 2], true), bossmove[bossani + 3], bossmove[bossani + 4], DTA_FullscreenScale, FSMode_Fit320x200,
DTA_TranslationIndex, translation, DTA_TopLeft, true, TAG_DONE);
}
if (breathebg)
{
DrawTexture(twod, tileGetTexture(VICTORY1 + 8, true), 86, 59, DTA_FullscreenScale, FSMode_Fit320x200,
DTA_TranslationIndex, translation, DTA_TopLeft, true, TAG_DONE);
}
if (breatheani != -1)
{
DrawTexture(twod, tileGetTexture(breathe[breatheani + 2], true), breathe[breatheani + 3], breathe[breatheani + 4], DTA_FullscreenScale, FSMode_Fit320x200,
DTA_TranslationIndex, translation, DTA_TopLeft, true, TAG_DONE);
}
}
};
@ -409,7 +438,6 @@ public:
FGameTexture* getTexture()
{
// Here we must provide a real texture, even if invalid, so that the sounds play.
auto texid = TexMan.CheckForTexture("radlogo.anm", ETextureType::Any, FTextureManager::TEXMAN_TryAny | FTextureManager::TEXMAN_ForceLookup);
if (texid.isValid()) return TexMan.GetGameTexture(texid);
else return TexMan.GameByIndex(0);
@ -420,7 +448,12 @@ public:
{
}
int Frame(uint64_t clock, bool skiprequest)
void Skipped() override
{
FX_StopAllSounds();
}
void OnTick() override
{
switch (sound)
{
@ -465,23 +498,21 @@ public:
if (!S_CheckSoundPlaying(ENDSEQVOL3SND9))
{
sound++;
waittime = clock + (SoundEnabled()? 1'000'000'000 : 5'000'000'000); // if sound is off this wouldn't wait without a longer delay here.
waittime = ticks + GameTicRate * (SoundEnabled() ? 1 : 5); // if sound is off this wouldn't wait without a longer delay here.
}
break;
case 6:
if (isPlutoPak())
{
if (clock > waittime) skiprequest = true;
if (ticks > waittime) state = finished;
}
break;
default:
break;
}
int ret = DImageScreen::Frame(clock, skiprequest);
if (ret != 1) FX_StopAllSounds();
return ret;
if (state != running) FX_StopAllSounds();
}
};
@ -491,12 +522,12 @@ public:
//
//---------------------------------------------------------------------------
class DEpisode4Text : public DScreenJob
class DEpisode4Text : public DSkippableScreenJob
{
public:
DEpisode4Text() : DScreenJob(fadein | fadeout) {}
DEpisode4Text() : DSkippableScreenJob(fadein | fadeout) {}
int Frame(uint64_t clock, bool skiprequest)
void Draw(double) override
{
twod->ClearScreen();
BigText(160, 60, GStrings("Thanks to all our"));
@ -504,7 +535,6 @@ public:
BigText(160, 60 + 16 + 16, GStrings("us big heads."));
BigText(160, 70 + 16 + 16 + 16, GStrings("Look for a Duke Nukem 3D"));
BigText(160, 70 + 16 + 16 + 16 + 16, GStrings("sequel soon."));
return skiprequest ? -1 : 1;
}
};
@ -523,7 +553,7 @@ public:
{
}
int Frame(uint64_t clock, bool skiprequest)
void OnTick() override
{
switch (sound)
{
@ -539,9 +569,6 @@ public:
default:
break;
}
int ret = DImageScreen::Frame(clock, skiprequest);
if (ret != 1) FX_StopAllSounds();
return ret;
}
};
@ -693,22 +720,25 @@ void doorders(const CompletionFunc& completion)
//
//---------------------------------------------------------------------------
class DDukeMultiplayerBonusScreen : public DScreenJob
class DDukeMultiplayerBonusScreen : public DSkippableScreenJob
{
int playerswhenstarted;
public:
DDukeMultiplayerBonusScreen(int pws) : DScreenJob(fadein|fadeout)
DDukeMultiplayerBonusScreen(int pws) : DSkippableScreenJob(fadein|fadeout)
{
playerswhenstarted = pws;
}
int Frame(uint64_t clock, bool skiprequest)
void Start() override
{
if (clock == 0) S_PlayBonusMusic();
S_PlayBonusMusic();
}
void Draw(double smoothratio) override
{
char tempbuf[32];
int currentclock = int(clock * 120 / 1'000'000'000);
int currentclock = int((ticks + smoothratio) * 120 / GameTicRate);
twod->ClearScreen();
DrawTexture(twod, tileGetTexture(MENUSCREEN), 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit43, DTA_Color, 0xff808080, DTA_LegacyRenderStyle, STYLE_Normal, TAG_DONE);
DrawTexture(twod, tileGetTexture(INGAMEDUKETHREEDEE, true), 160, 34, DTA_FullscreenScale, FSMode_Fit320x200, DTA_CenterOffsetRel, true, TAG_DONE);
@ -783,7 +813,6 @@ public:
}
MiniText(45, 96 + (8 * 7), GStrings("Deaths"), 0, -1, 8);
return skiprequest ? -1 : 1;
}
};
@ -797,13 +826,23 @@ class DDukeLevelSummaryScreen : public DScreenJob
{
const char* lastmapname;
int gfx_offset;
int bonuscnt = 0;
int speech = -1;
int displaystate = 0;
int dukeAnimStart;
void SetTotalClock(int tc)
enum
{
SetClock(tc * (uint64_t)1'000'000'000 / 120);
}
printTimeText = 1,
printTimeVal = 2,
printKillsText = 4,
printKillsVal = 8,
printSecretsText = 16,
printSecretsVal = 32,
printStatsAll = 63,
dukeAnim = 64,
dukeWait = 128,
};
public:
DDukeLevelSummaryScreen() : DScreenJob(fadein | fadeout)
@ -818,7 +857,82 @@ public:
mysnprintf(tempbuf, 32, "%02d:%02d", (time / (26 * 60)) % 60, (time / 26) % 60);
}
void PrintTime(int currentclock)
bool OnEvent(event_t* ev) override
{
if (ev->type == EV_KeyDown)
{
if ((displaystate & printStatsAll) != printStatsAll)
{
S_PlaySound(PIPEBOMB_EXPLODE, CHAN_AUTO, CHANF_UI);
displaystate = printStatsAll;
}
else if (!(displaystate & dukeAnim))
{
displaystate |= dukeAnim;
dukeAnimStart = ticks;
S_PlaySound(SHOTGUN_COCK, CHAN_AUTO, CHANF_UI);
static const uint16_t speeches[] = { BONUS_SPEECH1, BONUS_SPEECH2, BONUS_SPEECH3, BONUS_SPEECH4 };
speech = speeches[(rand() & 3)];
S_PlaySound(speech, CHAN_AUTO, CHANF_UI, 1);
}
return true;
}
return false;
}
void Start() override
{
S_PlayBonusMusic();
}
void OnTick() override
{
if ((displaystate & printStatsAll) != printStatsAll)
{
if (ticks == 15 * 3)
{
displaystate |= printTimeText;
}
else if (ticks == 15 * 4)
{
displaystate |= printTimeVal;
S_PlaySound(PIPEBOMB_EXPLODE, CHAN_AUTO, CHANF_UI);
}
else if (ticks == 15 * 6)
{
displaystate |= printKillsText;
S_PlaySound(FLY_BY, CHAN_AUTO, CHANF_UI);
}
else if (ticks == 15 * 7)
{
displaystate |= printKillsVal;
S_PlaySound(PIPEBOMB_EXPLODE, CHAN_AUTO, CHANF_UI);
}
else if (ticks == 15 * 9)
{
displaystate |= printSecretsText;
}
else if (ticks == 15 * 10)
{
displaystate |= printSecretsVal;
S_PlaySound(PIPEBOMB_EXPLODE, CHAN_AUTO, CHANF_UI);
}
}
if (displaystate & dukeAnim)
{
if (ticks >= dukeAnimStart + 60)
{
displaystate ^= dukeAnim | dukeWait;
}
}
if (displaystate & dukeWait)
{
if (speech <= 0 || !S_CheckSoundPlaying(speech))
state = finished;
}
}
void PrintTime()
{
char tempbuf[32];
GameText(10, 59 + 9, GStrings("TXT_YourTime"), 0);
@ -826,16 +940,8 @@ public:
if (!isNamWW2GI())
GameText(10, 79 + 9, GStrings("TXT_3DRTIME"), 0);
if (bonuscnt == 0)
bonuscnt++;
if (currentclock > (60 * 4))
if (displaystate & printTimeVal)
{
if (bonuscnt == 1)
{
bonuscnt++;
S_PlaySound(PIPEBOMB_EXPLODE, CHAN_AUTO, CHANF_UI);
}
FormatTime(ps[myconnectindex].player_par, tempbuf);
GameText((320 >> 2) + 71, 59 + 9, tempbuf, 0);
@ -850,25 +956,14 @@ public:
}
}
void PrintKills(int currentclock)
void PrintKills()
{
char tempbuf[32];
GameText(10, 94 + 9, GStrings("TXT_EnemiesKilled"), 0);
GameText(10, 104 + 9, GStrings("TXT_EnemiesLeft"), 0);
if (bonuscnt == 2)
if (displaystate & printKillsVal)
{
bonuscnt++;
S_PlaySound(FLY_BY, CHAN_AUTO, CHANF_UI);
}
if (currentclock > (60 * 7))
{
if (bonuscnt == 3)
{
bonuscnt++;
S_PlaySound(PIPEBOMB_EXPLODE, CHAN_AUTO, CHANF_UI);
}
mysnprintf(tempbuf, 32, "%-3d", ps[myconnectindex].actors_killed);
GameText((320 >> 2) + 70, 94 + 9, tempbuf, 0);
@ -887,20 +982,14 @@ public:
}
}
void PrintSecrets(int currentclock)
void PrintSecrets()
{
char tempbuf[32];
GameText(10, 119 + 9, GStrings("TXT_SECFND"), 0);
GameText(10, 129 + 9, GStrings("TXT_SECMISS"), 0);
if (bonuscnt == 4) bonuscnt++;
if (currentclock > (60 * 10))
if (displaystate & printSecretsVal)
{
if (bonuscnt == 5)
{
bonuscnt++;
S_PlaySound(PIPEBOMB_EXPLODE, CHAN_AUTO, CHANF_UI);
}
mysnprintf(tempbuf, 32, "%-3d", ps[myconnectindex].secret_rooms);
GameText((320 >> 2) + 70, 119 + 9, tempbuf, 0);
if (ps[myconnectindex].secret_rooms > 0)
@ -910,41 +999,31 @@ public:
}
}
int Frame(uint64_t clock, bool skiprequest)
void Draw(double) override
{
if (clock == 0) S_PlayBonusMusic();
twod->ClearScreen();
int currentclock = int(clock * 120 / 1'000'000'000);
DrawTexture(twod, tileGetTexture(gfx_offset, true), 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit43, DTA_LegacyRenderStyle, STYLE_Normal, TAG_DONE);
GameText(160, 190, GStrings("PRESSKEY"), 8 - int(sin(currentclock / 10.) * 8), 0);
GameText(160, 190, GStrings("PRESSKEY"), 8 - int(sin(ticks * 12 / GameTicRate) * 8), 0);
if (currentclock > (60 * 3))
if (displaystate & printTimeText)
{
PrintTime(currentclock);
PrintTime();
}
if (currentclock > (60 * 6))
if (displaystate & printKillsText)
{
PrintKills(currentclock);
PrintKills();
}
if (currentclock > (60 * 9))
if (displaystate & printSecretsText)
{
PrintSecrets(currentclock);
PrintSecrets();
}
if (currentclock >= (1000000000L) && currentclock < (1000000320L))
if (displaystate & dukeAnim)
{
switch ((currentclock >> 4) % 15)
switch (((ticks - dukeAnimStart) >> 2) % 15)
{
case 0:
if (bonuscnt == 6)
{
bonuscnt++;
S_PlaySound(SHOTGUN_COCK, CHAN_AUTO, CHANF_UI);
static const uint16_t speeches[] = { BONUS_SPEECH1, BONUS_SPEECH2, BONUS_SPEECH3, BONUS_SPEECH4};
speech = speeches[(rand() & 3)];
S_PlaySound(speech, CHAN_AUTO, CHANF_UI, 1);
}
case 1:
case 4:
case 5:
@ -956,14 +1035,9 @@ public:
break;
}
}
else if (currentclock > (10240 + 120L))
else if (!(displaystate & dukeWait))
{
if (speech > 0 && !skiprequest && soundEngine->GetSoundPlayingInfo(SOURCE_None, nullptr, speech)) return 1;
return 0;
}
else
{
switch((currentclock >> 5) & 3)
switch((ticks >> 3) & 3)
{
case 1:
case 3:
@ -977,26 +1051,6 @@ public:
if (lastmapname) BigText(160, 20 - 6, lastmapname);
BigText(160, 36 - 6, GStrings("Completed"));
if (currentclock > 10240 && currentclock < 10240 + 10240)
SetTotalClock(1024);
if (skiprequest && currentclock > (60 * 2))
{
skiprequest = false;
if (currentclock < (60 * 13))
{
SetTotalClock(60 * 13);
}
else if (currentclock < (1000000000))
{
// force-set bonuscnt here so that it won't desync with the rest of the logic and Duke's voice can be heard.
if (bonuscnt < 6) bonuscnt = 6;
SetTotalClock(1000000000);
}
}
return 1;
}
};
@ -1030,7 +1084,9 @@ void dobonus_d(int bonusonly, const CompletionFunc& completion)
jobs[job++] = { Create<DDukeLevelSummaryScreen>() };
}
if (job)
{
RunScreenJob(jobs, job, completion);
}
else if (completion) completion(false);
}
@ -1092,14 +1148,13 @@ class DDukeLoadScreen : public DScreenJob
public:
DDukeLoadScreen(MapRecord *maprec) : DScreenJob(0), rec(maprec) {}
int Frame(uint64_t clock, bool skiprequest)
void Draw(double) override
{
twod->ClearScreen();
DrawTexture(twod, tileGetTexture(LOADSCREEN), 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit43, DTA_LegacyRenderStyle, STYLE_Normal, TAG_DONE);
BigText(160, 90, (rec->flags & MI_USERMAP)? GStrings("TXT_LOADUM") : GStrings("TXT_LOADING"));
BigText(160, 114, rec->DisplayName());
return 0;
}
};

View file

@ -266,9 +266,14 @@ public:
playerswhenstarted = pws;
}
int Frame(uint64_t clock, bool skiprequest)
void Start() override
{
S_PlayBonusMusic();
}
void Draw(double) override
{
if (clock == 0) S_PlayBonusMusic();
char tempbuf[32];
twod->ClearScreen();
DrawTexture(twod, tileGetTexture(MENUSCREEN), 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit43, DTA_Color, 0xff808080, DTA_LegacyRenderStyle, STYLE_Normal, TAG_DONE);
@ -344,7 +349,6 @@ public:
}
MiniText(45, 96 + (8 * 7), GStrings("Deaths"), 0);
return skiprequest ? -1 : 1;
}
};
@ -358,13 +362,23 @@ class DRRLevelSummaryScreen : public DScreenJob
{
const char* lastmapname;
int gfx_offset;
int bonuscnt = 0;
int displaystate = 0;
int speech = -1;
int exitSoundStart;
void SetTotalClock(int tc)
enum
{
SetClock(tc * (uint64_t)1'000'000'000 / 120);
}
printTimeText = 1,
printTimeVal = 2,
printKillsText = 4,
printKillsVal = 8,
printSecretsText = 16,
printSecretsVal = 32,
printStatsAll = 63,
exitSound = 64,
exitWait = 128,
};
public:
DRRLevelSummaryScreen(bool dofadeout = true) : DScreenJob(dofadeout? (fadein | fadeout) : fadein)
@ -385,53 +399,107 @@ public:
mysnprintf(tempbuf, 32, "%02d:%02d", (time / (26 * 60)) % 60, (time / 26) % 60);
}
void PrintTime(int currentclock)
bool OnEvent(event_t* ev) override
{
if (ev->type == EV_KeyDown)
{
if ((displaystate & printStatsAll) != printStatsAll)
{
S_PlaySound(404, CHAN_AUTO, CHANF_UI);
displaystate = printStatsAll;
}
else if (!(displaystate & exitSound))
{
displaystate |= exitSound;
exitSoundStart = ticks;
S_PlaySound(425, CHAN_AUTO, CHANF_UI);
speech = BONUS_SPEECH1 + (rand() & 3);
S_PlaySound(speech, CHAN_AUTO, CHANF_UI);
}
return true;
}
return false;
}
void Start() override
{
S_PlayBonusMusic();
}
void OnTick() override
{
if ((displaystate & printStatsAll) != printStatsAll)
{
if (ticks == 15 * 3)
{
displaystate |= printTimeText;
}
else if (ticks == 15 * 4)
{
displaystate |= printTimeVal;
S_PlaySound(404, CHAN_AUTO, CHANF_UI);
}
else if (ticks == 15 * 6)
{
displaystate |= printKillsText;
}
else if (ticks == 15 * 7)
{
displaystate |= printKillsVal;
S_PlaySound(404, CHAN_AUTO, CHANF_UI);
}
else if (ticks == 15 * 9)
{
displaystate |= printSecretsText;
}
else if (ticks == 15 * 10)
{
displaystate |= printSecretsVal;
S_PlaySound(404, CHAN_AUTO, CHANF_UI);
}
}
if (displaystate & exitSound)
{
if (ticks >= exitSoundStart + 60)
{
displaystate ^= exitSound | exitWait;
}
}
if (displaystate & exitWait)
{
if (speech <= 0 || !S_CheckSoundPlaying(speech))
state = finished;
}
}
void PrintTime()
{
char tempbuf[32];
BigText(30, 48, GStrings("TXT_YerTime"), -1);
BigText(30, 64, GStrings("TXT_ParTime"), -1);
BigText(30, 80, GStrings("TXT_XTRTIME"), -1);
if (bonuscnt == 0)
bonuscnt++;
if (currentclock > (60 * 4))
if (displaystate & printTimeVal)
{
if (bonuscnt == 1)
{
bonuscnt++;
S_PlaySound(404, CHAN_AUTO, CHANF_UI);
}
FormatTime(ps[myconnectindex].player_par, tempbuf);
BigText(191, 48, tempbuf, -1);
FormatTime(currentLevel->parTime, tempbuf);
BigText(191, 64, tempbuf, -1);
if (!isNamWW2GI())
{
FormatTime(currentLevel->designerTime, tempbuf);
BigText(191, 80, tempbuf, -1);
}
FormatTime(currentLevel->designerTime, tempbuf);
BigText(191, 80, tempbuf, -1);
}
}
void PrintKills(int currentclock)
void PrintKills()
{
char tempbuf[32];
BigText(30, 112, GStrings("TXT_VarmintsKilled"), -1);
BigText(30, 128, GStrings("TXT_VarmintsLeft"), -1);
if (bonuscnt == 2)
bonuscnt++;
if (currentclock > (60 * 7))
if (displaystate & printKillsVal)
{
if (bonuscnt == 3)
{
bonuscnt++;
S_PlaySound(442, CHAN_AUTO, CHANF_UI);
}
mysnprintf(tempbuf, 32, "%-3d", ps[myconnectindex].actors_killed);
BigText(231, 112, tempbuf, -1);
if (ud.player_skill > 3)
@ -449,20 +517,14 @@ public:
}
}
void PrintSecrets(int currentclock)
void PrintSecrets()
{
char tempbuf[32];
BigText(30, 144, GStrings("TXT_SECFND"), -1);
BigText(30, 160, GStrings("TXT_SECMISS"), -1);
if (bonuscnt == 4) bonuscnt++;
if (currentclock > (60 * 10))
if (displaystate & printSecretsVal)
{
if (bonuscnt == 5)
{
bonuscnt++;
S_PlaySound(404, CHAN_AUTO, CHANF_UI);
}
mysnprintf(tempbuf, 32, "%-3d", ps[myconnectindex].secret_rooms);
BigText(231, 144, tempbuf, -1);
if (ps[myconnectindex].secret_rooms > 0)
@ -472,88 +534,56 @@ public:
}
}
int Frame(uint64_t clock, bool skiprequest)
void Draw(double) override
{
if (clock == 0) S_PlayBonusMusic();
twod->ClearScreen();
int currentclock = int(clock * 120 / 1'000'000'000);
DrawTexture(twod, tileGetTexture(gfx_offset, true), 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit43, DTA_LegacyRenderStyle, STYLE_Normal, TAG_DONE);
if (lastmapname) BigText(80, 16, lastmapname, -1);
BigText(15, 192, GStrings("PRESSKEY"), -1);
if (currentclock > (60 * 3))
if (displaystate & printTimeText)
{
PrintTime(currentclock);
PrintTime();
}
if (currentclock > (60 * 6))
if (displaystate & printKillsText)
{
PrintKills(currentclock);
PrintKills();
}
if (currentclock > (60 * 9))
if (displaystate & printSecretsText)
{
PrintSecrets(currentclock);
PrintSecrets();
}
if (currentclock > (1000000000L) && currentclock < (1000000320L))
{
int val = (currentclock >> 4) % 15;
if (val == 0)
{
if (bonuscnt == 6)
{
bonuscnt++;
S_PlaySound(425, CHAN_AUTO, CHANF_UI);
speech = BONUS_SPEECH1 + (rand() & 3);
S_PlaySound(speech, CHAN_AUTO, CHANF_UI);
}
}
}
else if (currentclock > (10240 + 120L))
{
if (speech > 0 && !skiprequest && soundEngine->GetSoundPlayingInfo(SOURCE_None, nullptr, speech)) return 1;
return 0;
}
if (currentclock > 10240 && currentclock < 10240 + 10240)
SetTotalClock(1024);
if (skiprequest && currentclock > (60 * 2))
{
skiprequest = false;
if (currentclock < (60 * 13))
{
SetTotalClock(60 * 13);
}
else if (currentclock < (1000000000))
SetTotalClock(1000000000);
}
return 1;
}
};
class DRRRAEndOfGame : public DScreenJob
class DRRRAEndOfGame : public DSkippableScreenJob
{
public:
DRRRAEndOfGame() : DScreenJob(fadein|fadeout)
DRRRAEndOfGame() : DSkippableScreenJob(fadein|fadeout)
{
}
void Skipped() override
{
S_StopSound(35);
}
void Start() override
{
S_PlaySound(35, CHAN_AUTO, CHANF_UI);
}
int Frame(uint64_t clock, bool skiprequest)
void OnTick() override
{
int currentclock = int(clock * 120 / 1'000'000'000);
auto tex = tileGetTexture(ENDGAME + ((currentclock >> 4) & 1));
if (!S_CheckSoundPlaying(-1, 35) && ticks > 15 * GameTicRate) state = finished; // make sure it stays, even if sound is off.
}
void Draw(double) override
{
auto tex = tileGetTexture(ENDGAME + ((ticks >> 2) & 1));
DrawTexture(twod, tex, 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit43, TAG_DONE);
if (!S_CheckSoundPlaying(-1, 35) && currentclock > 15*120) return 0; // make sure it stays, even if sound is off.
if (skiprequest)
{
S_StopSound(35);
return -1;
}
return 1;
}
};
@ -617,14 +647,13 @@ class DRRLoadScreen : public DScreenJob
public:
DRRLoadScreen(MapRecord* maprec) : DScreenJob(0), rec(maprec) {}
int Frame(uint64_t clock, bool skiprequest)
void Draw(double) override
{
DrawTexture(twod, tileGetTexture(LOADSCREEN), 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit43, DTA_LegacyRenderStyle, STYLE_Normal, TAG_DONE);
int y = isRRRA()? 140 : 90;
BigText(160, y, (rec->flags & MI_USERMAP) ? GStrings("TXT_ENTRUM") : GStrings("TXT_ENTERIN"), 0);
BigText(160, y+24, rec->DisplayName(), 0);
return 0;
}
};

View file

@ -1014,7 +1014,7 @@ void movemasterswitch(DDukeActor *actor, int spectype1, int spectype2)
// This originally depended on undefined behavior as the deleted sprite was still used for the sound
// with no checking if it got reused in the mean time.
spri->picnum = 0; // give it a picnum without any behavior attached, just in case
spri->cstat |= CSTAT_SPRITE_INVISIBLE;
spri->cstat |= CSTAT_SPRITE_INVISIBLE|CSTAT_SPRITE_NOFIND;
changespritestat(actor->GetIndex(), STAT_REMOVED);
}
}

View file

@ -39,6 +39,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "m_random.h"
#include "gstrings.h"
#include "gamefuncs.h"
#include "c_bind.h"
#include <string>
@ -383,13 +384,23 @@ class DLobotomyScreen : public DImageScreen
public:
DLobotomyScreen(FGameTexture *tex, int fade) : DImageScreen(tex, fade)
{}
int Frame(uint64_t clock, bool skiprequest) override
void Skipped() override
{
if (clock == 0) PlayLocalSound(StaticSound[kSoundJonLaugh2], 7000, false, CHANF_UI);
if (skiprequest) StopLocalSound();
return DImageScreen::Frame(clock, skiprequest);
StopLocalSound();
}
void Start() override
{
PlayLocalSound(StaticSound[kSoundJonLaugh2], 7000, false, CHANF_UI);
}
void OnTick() override
{
DImageScreen::OnTick();
if (state == finished) StopLocalSound();
}
};
//---------------------------------------------------------------------------
@ -400,12 +411,12 @@ public:
static const short skullDurations[] = { 6, 25, 43, 50, 68, 78, 101, 111, 134, 158, 173, 230, 600 };
class DMainTitle : public DScreenJob
class DMainTitle : public DSkippableScreenJob
{
const char* a;
const char* b;
int state = 0;
int var_18;
int duration;
int var_4 = 0;
int esi = 130;
int nCount = 0;
@ -413,56 +424,65 @@ class DMainTitle : public DScreenJob
public:
DMainTitle() : DScreenJob(fadein)
DMainTitle() : DSkippableScreenJob(fadein)
{
a = GStrings("TXT_EX_COPYRIGHT1");
b = GStrings("TXT_EX_COPYRIGHT2");
var_18 = skullDurations[0];
duration = skullDurations[0];
}
int Frame(uint64_t clock, bool skiprequest) override
void Start() override
{
int ticker = clock * 120 / 1'000'000'000;
if (clock == 0)
{
PlayLocalSound(StaticSound[59], 0, true, CHANF_UI);
playCDtrack(19, true);
}
if (clock > 1'000'000 && state == 0 && !soundEngine->IsSourcePlayingSomething(SOURCE_None, nullptr,CHAN_AUTO, -1))
void OnTick() override
{
if (time(0) & 0xF)
int ticker = ticks * 120 / GameTicRate;
if (ticks > 1 && state == 0 && !soundEngine->IsSourcePlayingSomething(SOURCE_None, nullptr, CHAN_AUTO, -1))
{
if (time(0) & 0xF) // cheap-ass random...
PlayGameOverSound();
else
else
PlayLocalSound(StaticSound[61], 0, false, CHANF_UI);
state = 1;
start = ticker;
}
if (state == 1)
{
if (ticker > duration)
{
nCount++;
if (nCount > 12)
{
state = finished;
return;
}
duration = start + skullDurations[nCount];
var_4 = var_4 == 0;
}
}
}
void Draw(double) override
{
twod->ClearScreen();
menu_DoPlasma();
DrawRel(kSkullHead, 160, 100);
switch (state)
if (state == 0)
{
case 0:
DrawRel(kSkullJaw, 161, 130);
break;
case 1:
}
else
{
int nStringWidth = SmallFont->StringWidth(a);
DrawText(twod, SmallFont, CR_UNTRANSLATED, 160 - nStringWidth / 2, 200 - 24, a, DTA_FullscreenScale, FSMode_Fit320x200, TAG_DONE);
nStringWidth = SmallFont->StringWidth(b);
DrawText(twod, SmallFont, CR_UNTRANSLATED, 160 - nStringWidth / 2, 200 - 16, b, DTA_FullscreenScale, FSMode_Fit320x200, TAG_DONE);
if (ticker > var_18)
{
nCount++;
if (nCount > 12) return 0;
var_18 = start + skullDurations[nCount];
var_4 = var_4 == 0;
}
short nTile = kSkullJaw;
@ -487,11 +507,8 @@ public:
}
DrawRel(nTile, 161, y);
break;
}
}
return skiprequest? -1 : 1;
}
};
//---------------------------------------------------------------------------
@ -618,10 +635,8 @@ class DMapScreen : public DScreenJob
{
int i;
int x = 0;
int var_2C = 0;
int delta = 0;
int nIdleSeconds = 0;
int startTime = 0;
int runtimer = 0;
int curYPos, destYPos;
int nLevel, nLevelNew, nLevelBest;
@ -633,11 +648,11 @@ public:
destYPos = MapLevelOffsets[nLevelNew] + (200 * (nLevelNew / 2));
if (curYPos < destYPos) {
var_2C = 2;
delta = 2;
}
if (curYPos > destYPos) {
var_2C = -2;
delta = -2;
}
// Trim smoke in widescreen
@ -651,20 +666,13 @@ public:
}
#endif
}
int Frame(uint64_t clock, bool skiprequest) override
void Draw(double smoothratio)
{
int currentclock = int(clock * 120 / 1'000'000'000);
int currentclock = int((ticks + smoothratio) * 120 / GameTicRate);
twod->ClearScreen();
if ((currentclock - startTime) / kTimerTicks)
{
nIdleSeconds++;
startTime = currentclock;
}
int tileY = curYPos;
// Draw the background screens
@ -728,84 +736,82 @@ public:
DrawAbs(nTile, textX, textY, shade);
}
selectedlevelnew = nLevelNew + 1;
}
void OnTick() override
{
if (curYPos != destYPos)
{
// scroll the map every couple of ms
if (currentclock - runtimer >= (kTimerTicks / 32)) {
curYPos += var_2C;
runtimer = currentclock;
}
if (inputState.CheckAllInput())
{
if (var_2C < 8) {
var_2C *= 2;
}
}
if (curYPos > destYPos&& var_2C > 0) {
curYPos += delta;
if (curYPos > destYPos && delta > 0) {
curYPos = destYPos;
}
if (curYPos < destYPos && var_2C < 0) {
if (curYPos < destYPos && delta < 0) {
curYPos = destYPos;
}
nIdleSeconds = 0;
}
selectedlevelnew = nLevelNew + 1;
return skiprequest? -1 : nIdleSeconds < 12? 1 : 0;
else nIdleSeconds++;
if (nIdleSeconds > 300) state = finished;
}
bool ProcessInput() override
bool OnEvent(event_t* ev) override
{
if (buttonMap.ButtonDown(gamefunc_Move_Forward))
int key = ev->data1;
if (ev->type == EV_KeyDown)
{
buttonMap.ClearButton(gamefunc_Move_Forward);
auto binding = Bindings.GetBinding(ev->data1);
if (!binding.CompareNoCase("+move_forward")) key = KEY_UPARROW;
if (!binding.CompareNoCase("+move_backward")) key = KEY_DOWNARROW;
if (key == KEY_UPARROW || key == KEY_PAD_DPAD_UP || key == sc_kpad_8)
{
if (curYPos == destYPos && nLevelNew <= nLevelBest)
{
nLevelNew++;
assert(nLevelNew < 20);
destYPos = MapLevelOffsets[nLevelNew] + (200 * (nLevelNew / 2));
if (curYPos <= destYPos) {
var_2C = 2;
delta = 2;
}
else {
var_2C = -2;
delta = -2;
}
nIdleSeconds = 0;
}
return true;
}
if (buttonMap.ButtonDown(gamefunc_Move_Backward))
if (key == KEY_DOWNARROW || key == KEY_PAD_DPAD_DOWN || key == sc_kpad_2)
{
buttonMap.ClearButton(gamefunc_Move_Backward);
if (curYPos == destYPos && nLevelNew > 0)
{
nLevelNew--;
assert(nLevelNew >= 0);
destYPos = MapLevelOffsets[nLevelNew] + (200 * (nLevelNew / 2));
if (curYPos <= destYPos) {
var_2C = 2;
delta = 2;
}
else {
var_2C = -2;
delta = -2;
}
nIdleSeconds = 0;
}
return true;
}
state = skipped;
return true;
}
return false;
}
};
@ -963,16 +969,17 @@ void uploadCinemaPalettes()
//
//---------------------------------------------------------------------------
class DCinema : public DScreenJob
class DCinema : public DSkippableScreenJob
{
TextOverlay text;
short cinematile;
int currentCinemaPalette;
int edx;
int check;
int cont = 1;
public:
DCinema(int nVal, int checklevel = -1) : DScreenJob(fadein|fadeout)
DCinema(int nVal, int checklevel = -1) : DSkippableScreenJob(fadein|fadeout)
{
if (nVal < 0 || nVal >5) return;
cinematile = cinemas[nVal].tile;
@ -984,12 +991,15 @@ public:
check = checklevel;
}
int Frame(uint64_t clock, bool skiprequest) override
void Start() override
{
if (clock == 0)
if (check > 0 && check != selectedlevelnew)
{
if (check > 0 && check != selectedlevelnew) return 0; // immediately abort if the player selected a different level on the map
state = finished;
return; // immediately abort if the player selected a different level on the map
}
check = -1;
StopAllSounds();
if (edx != -1)
{
@ -997,20 +1007,29 @@ public:
}
}
void OnTick() override
{
if (!cont)
{
state = finished;
// quit the game if we've finished level 4 and displayed the advert text
if (isShareware() && currentCinemaPalette == 3)
{
gameaction = ga_mainmenu;
}
return;
}
}
void Draw(double smoothratio) override
{
twod->ClearScreen();
if (check == 0) return;
DrawTexture(twod, tileGetTexture(cinematile), 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit43, DTA_TranslationIndex, TRANSLATION(Translation_BasePalettes, currentCinemaPalette), TAG_DONE);
text.DisplayText();
auto cont = text.AdvanceCinemaText(clock * (120. / 1'000'000'000));
int ret = skiprequest ? -1 : cont ? 1 : 0;
// quit the game if we've finished level 4 and displayed the advert text
if (isShareware() && currentCinemaPalette == 3 && ret != 1)
{
gameaction = ga_mainmenu;
cont = text.AdvanceCinemaText((ticks + smoothratio) * (120. / GameTicRate));
}
return ret;
}
};
//---------------------------------------------------------------------------
@ -1029,6 +1048,7 @@ class DLastLevelCinema : public DScreenJob
int nextclock = 4;
unsigned int nStringTypeOn, nCharTypeOn;
int screencnt = 0;
bool skiprequest = false;
TArray<FString> screentext;
@ -1123,7 +1143,7 @@ private:
int yy = ebp;
auto p = GStrings["REQUIRED_CHARACTERS"];
if (1)//p && *p)
if (p && *p)
{
yy *= 2;
for (int i = 0; i < nStringTypeOn; i++, yy += 10)
@ -1142,91 +1162,94 @@ private:
}
}
int Frame(uint64_t clock, bool skiprequest) override
bool OnEvent(event_t* ev)
{
if (clock == 0)
if (ev->type == EV_KeyDown) skiprequest = true;
return true;
}
void Start() override
{
PlayLocalSound(StaticSound[kSound75], 0, false, CHANF_UI);
phase = 1;
}
int currentclock = clock * 120 / 1'000'000'000;
twod->ClearScreen();
DrawTexture(twod, tileGetTexture(kTileLoboLaptop), 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit43, TAG_DONE);
void OnTick() override
{
switch (phase)
{
case 1:
if (currentclock >= nextclock)
{
Phase1();
nextclock += 4;
}
if (skiprequest || currentclock >= 240)
if (skiprequest || ticks >= nextclock)
{
InitPhase2();
phase = 2;
skiprequest = 0;
skiprequest = false;
}
break;
case 2:
if (currentclock >= nextclock)
{
if (screentext[nStringTypeOn][nCharTypeOn] != ' ')
PlayLocalSound(StaticSound[kSound71], 0, false, CHANF_UI);
nCharTypeOn++;
nextclock += 4;
if (screentext[nStringTypeOn][nCharTypeOn] == 0)
{
nCharTypeOn = 0;
nStringTypeOn++;
if (nStringTypeOn >= screentext.Size())
{
nextclock = (kTimerTicks * (screentext.Size() + 2)) + currentclock;
nextclock = (GameTicRate * (screentext.Size() + 2)) + ticks;
phase = 3;
}
}
}
DisplayPhase2();
if (skiprequest)
{
nextclock = (kTimerTicks * (screentext.Size() + 2)) + currentclock;
nextclock = (GameTicRate * (screentext.Size() + 2)) + ticks;
phase = 4;
}
break;
case 3:
DisplayPhase2();
if (currentclock >= nextclock || skiprequest)
if (ticks >= nextclock || skiprequest)
{
PlayLocalSound(StaticSound[kSound75], 0, false, CHANF_UI);
phase = 4;
nextclock = currentclock + 240;
skiprequest = 0;
nextclock = ticks + 60;
skiprequest = false;
}
break;
case 4:
if (currentclock >= nextclock)
if (ticks >= nextclock)
{
skiprequest |= !Phase3();
nextclock += 4;
}
if (skiprequest || currentclock >= 240)
if (skiprequest)
{
// Go to the next text page.
if (screencnt != 2)
{
screencnt++;
nextclock = currentclock + 240;
nextclock = ticks + 60;
skiprequest = 0;
phase = 1;
}
else return skiprequest ? -1 : 0;
else state = finished;
}
if (skiprequest)
{
state = finished;
}
return 1;
}
}
void Draw(double) override
{
twod->ClearScreen();
DrawTexture(twod, tileGetTexture(kTileLoboLaptop), 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit43, TAG_DONE);
if (phase == 2 || phase == 3) DisplayPhase2();
}
};
@ -1243,6 +1266,7 @@ class DExCredits : public DScreenJob
TArray<FString> pagelines;
uint64_t page;
uint64_t pagetime;
bool skiprequest = false;
public:
DExCredits()
@ -1253,35 +1277,53 @@ public:
credits = text.Split("\n\n");
}
private:
int Frame(uint64_t clock, bool skiprequest) override
bool OnEvent(event_t* ev)
{
if (clock == 0)
if (ev->type == EV_KeyDown) skiprequest = true;
return true;
}
void Start() override
{
if (credits.Size() == 0) return 0;
if (credits.Size() == 0)
{
state = finished;
return;
}
playCDtrack(19, false);
pagetime = 0;
page = -1;
}
if (clock >= pagetime || skiprequest)
void OnTick() override
{
if (ticks >= pagetime || skiprequest)
{
page++;
if (page < credits.Size())
pagelines = credits[page].Split("\n");
else
{
if (skiprequest || !CDplaying()) return 0;
if (skiprequest || !CDplaying())
{
state = finished;
return;
}
pagelines.Clear();
}
pagetime = clock + 2'000'000'000; //
pagetime = ticks + 60; //
}
}
void Draw(double smoothratio) override
{
twod->ClearScreen();
int y = 100 - ((10 * (pagelines.Size() - 1)) / 2);
for (unsigned i = 0; i < pagelines.Size(); i++)
{
uint64_t ptime = (pagetime-clock) / 1'000'000;
int ptime = clamp((pagetime - ticks - smoothratio) * 1000 / GameTicRate, 0, 2000); // in milliseconds
int light;
if (ptime < 255) light = ptime;
@ -1294,7 +1336,6 @@ private:
DrawText(twod, SmallFont, CR_UNTRANSLATED, 160 - nStringWidth / 2, y, pagelines[i], DTA_FullscreenScale, FSMode_Fit320x200, DTA_Color, color, TAG_DONE);
y += 10;
}
return 1;
}
};

View file

@ -167,7 +167,7 @@ static void Intermission(MapRecord *from_map, MapRecord *to_map)
showmap(from_map ? from_map->levelNumber : -1, to_map->levelNumber, nBestLevel, jobs);
}
else
jobs.Push({ Create<DScreenJob>() }); // we need something in here even in the multiplayer case.
jobs.Push({ Create<DBlackScreen>(1) }); // we need something in here even in the multiplayer case.
}
}
else
@ -193,7 +193,7 @@ static void Intermission(MapRecord *from_map, MapRecord *to_map)
gameaction = ga_nextlevel;
}
}, true, true);
});
}
}
@ -217,15 +217,14 @@ void GameInterface::NewGame(MapRecord *map, int skill, bool frommenu)
// start a new game on the given level
InitNewGame();
if (map->levelNumber == 1) STAT_StartNewGame("Exhumed", 1);
if (frommenu) Intermission(nullptr, map);
else NextLevel(map, skill);
Intermission(nullptr, map);
}
void GameInterface::LevelCompleted(MapRecord *map, int skill)
{
Mus_Stop();
if (currentLevel->levelNumber == 0) gameaction = ga_mainmenu;
else Intermission(currentLevel, map);
Intermission(currentLevel, map);
}
//---------------------------------------------------------------------------

View file

@ -200,7 +200,7 @@ public:
//
//---------------------------------------------------------------------------
class DLmfPlayer : public DScreenJob
class DLmfPlayer : public DSkippableScreenJob
{
LMFPlayer decoder;
double angle = 1536;
@ -216,6 +216,7 @@ public:
lastclock = 0;
nextclock = 0;
fp = std::move(fr);
pausable = false;
}
//---------------------------------------------------------------------------
@ -224,14 +225,16 @@ public:
//
//---------------------------------------------------------------------------
int Frame(uint64_t clock, bool skiprequest) override
void Draw(double smoothratio) override
{
uint64_t clock = (ticks + smoothratio) * 1'000'000'000. / GameTicRate;
if (clock >= nextclock)
{
nextclock += 100'000'000;
if (decoder.ReadFrame(fp) == 0)
{
return 0;
state = finished;
return;
}
}
@ -254,7 +257,6 @@ public:
}
lastclock = clock;
return skiprequest ? -1 : 1;
}
void OnDestroy() override
@ -269,12 +271,11 @@ public:
DScreenJob* PlayMovie(const char* fileName)
{
// clear keys
inputState.ClearAllInput();
auto fp = fileSystem.OpenFileReader(fileName);
if (!fp.isOpen())
{
return Create<DScreenJob>();
return Create<DBlackScreen>(1);
}
char buffer[4];
fp.Read(buffer, 4);

View file

@ -1032,14 +1032,13 @@ void FuncPlayer(int a, int nDamage, int nRun)
StopLocalSound();
InitSpiritHead();
PlayerList[nPlayer].nDestVertPan = q16horiz(0);
if (currentLevel->levelNumber == 11)
{
PlayerList[nPlayer].nDestVertPan = q16horiz(46);
PlayerList[nPlayer].horizon.settarget(46);
}
else
{
PlayerList[nPlayer].nDestVertPan = q16horiz(11);
PlayerList[nPlayer].horizon.settarget(11);
}
}
}
@ -1067,7 +1066,7 @@ void FuncPlayer(int a, int nDamage, int nRun)
zVelB = -zVelB;
}
if (zVelB > 512 && !PlayerList[nPlayer].horizon.horiz.asq16() && !(sPlayerInput[nPlayer].actions & SB_AIMMODE)) {
if (zVelB > 512 && !PlayerList[nPlayer].horizon.horiz.asq16() && cl_slopetilting) {
sPlayerInput[nPlayer].actions |= SB_CENTERVIEW;
}
}
@ -2665,10 +2664,13 @@ loc_1BD2E:
pPlayer->bPlayerPan = false;
}
double nVertPan = (pPlayer->nDestVertPan - pPlayer->horizon.horiz).asq16() * (1. / (FRACUNIT << 2));
if (nVertPan != 0)
if (cl_slopetilting)
{
pPlayer->horizon.addadjustment(abs(nVertPan) >= 4 ? clamp(nVertPan, -4, 4) : nVertPan * 2.);
double nVertPan = (pPlayer->nDestVertPan - pPlayer->horizon.horiz).asq16() * (1. / (FRACUNIT << 2));
if (nVertPan != 0)
{
pPlayer->horizon.addadjustment(abs(nVertPan) >= 4 ? clamp(nVertPan, -4, 4) : nVertPan * 2.);
}
}
}
else // else, player's health is less than 0

View file

@ -45,20 +45,23 @@ BEGIN_SW_NS
//
//---------------------------------------------------------------------------
class DSWDRealmsScreen : public DScreenJob
class DSWDRealmsScreen : public DSkippableScreenJob
{
public:
DSWDRealmsScreen() : DScreenJob(fadein | fadeout) {}
DSWDRealmsScreen() : DSkippableScreenJob(fadein | fadeout) {}
int Frame(uint64_t clock, bool skiprequest) override
void OnTick() override
{
if (ticks > 5 * GameTicRate) state = finished;
}
void Draw(double) override
{
const uint64_t duration = 5'000'000'000;
const auto tex = tileGetTexture(THREED_REALMS_PIC, true);
const int translation = TRANSLATION(Translation_BasePalettes, DREALMSPAL);
twod->ClearScreen();
DrawTexture(twod, tex, 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit43, DTA_TranslationIndex, translation, DTA_LegacyRenderStyle, STYLE_Normal, TAG_DONE);
return skiprequest ? -1 : clock < duration ? 1 : 0;
}
};
@ -166,7 +169,7 @@ DScreenJob* GetFinishAnim(int num)
//
//---------------------------------------------------------------------------
class DSWCreditsScreen : public DScreenJob
class DSWCreditsScreen : public DSkippableScreenJob
{
enum
{
@ -177,21 +180,24 @@ class DSWCreditsScreen : public DScreenJob
int starttime;
int curpic;
int Frame(uint64_t clock, bool skiprequest)
void Skipped() override
{
StopSound();
}
void Start() override
{
// Lo Wang feel like singing!
PlaySound(DIGI_JG95012, v3df_none, CHAN_VOICE, CHANF_UI);
}
void OnTick() override
{
twod->ClearScreen();
int seconds = int(clock / 1'000'000'000);
if (clock == 0)
{
// Lo Wang feel like singing!
PlaySound(DIGI_JG95012, v3df_none, CHAN_VOICE, CHANF_UI);
}
if (state == 0)
{
if (skiprequest || !soundEngine->IsSourcePlayingSomething(SOURCE_None, nullptr, CHAN_VOICE))
if (!soundEngine->IsSourcePlayingSomething(SOURCE_None, nullptr, CHAN_VOICE))
{
skiprequest = false;
starttime = seconds;
starttime = ticks;
state = 1;
StopSound();
curpic = CREDITS1_PIC;
@ -205,15 +211,19 @@ class DSWCreditsScreen : public DScreenJob
}
else
{
if (seconds >= starttime + 8)
if (ticks >= starttime + 8 * GameTicRate)
{
curpic = CREDITS1_PIC + CREDITS2_PIC - curpic;
starttime = seconds;
starttime = ticks;
}
DrawTexture(twod, tileGetTexture(curpic, true), 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit43, DTA_LegacyRenderStyle, STYLE_Normal, TAG_DONE);
}
if (skiprequest) StopSound();
return skiprequest ? -1 : 1;
}
void Draw(double) override
{
twod->ClearScreen();
if (state == 1)
DrawTexture(twod, tileGetTexture(curpic, true), 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit43, DTA_LegacyRenderStyle, STYLE_Normal, TAG_DONE);
}
};
@ -377,31 +387,42 @@ private:
(*(*State)->Animator)(0);
}
int Frame(uint64_t clock, bool skiprequest)
bool OnEvent(event_t* ev) override
{
twod->ClearScreen();
int currentclock = int(clock * 120 / 1'000'000'000);
if (clock == 0)
if (ev->type == EV_KeyDown)
{
PlaySong(nullptr, ThemeSongs[1], ThemeTrack[1]);
}
if (skiprequest && State >= s_BonusRest && State < &s_BonusRest[countof(s_BonusRest)])
{
State = s_BonusAnim[STD_RANDOM_RANGE(countof(s_BonusAnim))];
Tics = 0;
skiprequest = false;
nextclock = currentclock;
}
else
{
while (currentclock > nextclock)
if (State >= s_BonusRest && State < &s_BonusRest[countof(s_BonusRest)])
{
nextclock += synctics;
gStateControl(&State, &Tics);
State = s_BonusAnim[STD_RANDOM_RANGE(countof(s_BonusAnim))];
Tics = 0;
nextclock = ticks;
}
}
return true;
}
void Start() override
{
PlaySong(nullptr, ThemeSongs[1], ThemeTrack[1]);
}
void OnTick() override
{
while (ticks > nextclock)
{
nextclock++;
gStateControl(&State, &Tics);
}
if (State == State->NextState)
{
state = finished;
StopSound();
}
}
void Draw(double) override
{
twod->ClearScreen();
DrawTexture(twod, tileGetTexture(BONUS_SCREEN_PIC, true), 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit43, DTA_LegacyRenderStyle, STYLE_Normal, TAG_DONE);
MNU_DrawString(160, 20, currentLevel->DisplayName(), 1, 19, 0);
@ -437,10 +458,6 @@ private:
MNU_DrawString(60, BONUS_LINE(line), ds, 1, 16);
MNU_DrawString(160, 185, GStrings("PRESSKEY"), 1, 19, 0);
int ret = (State == State->NextState)? 0 : skiprequest ? -1 : 1;
if (ret != 1) StopSound();
return ret;
}
};
@ -466,12 +483,17 @@ enum
};
class DSWMultiSummaryScreen : public DScreenJob
class DSWMultiSummaryScreen : public DSkippableScreenJob
{
short death_total[MAX_SW_PLAYERS_REG]{};
short kills[MAX_SW_PLAYERS_REG]{};
int Frame(uint64_t clock, bool skiprequest)
void Skipped() override
{
StopSound();
}
void Draw(double) override
{
if (clock == 0) PlaySong(nullptr, ThemeSongs[1], ThemeTrack[1]);
@ -571,8 +593,6 @@ class DSWMultiSummaryScreen : public DScreenJob
y += STAT_OFF_Y;
}
if (skiprequest) StopSound();
return skiprequest ? -1 : 1;
}
};
@ -635,7 +655,7 @@ class DSWLoadScreen : public DScreenJob
public:
DSWLoadScreen(MapRecord* maprec) : DScreenJob(0), rec(maprec) {}
int Frame(uint64_t clock, bool skiprequest)
void Draw(double) override
{
const int TITLE_PIC = 2324;
twod->ClearScreen();
@ -643,8 +663,6 @@ public:
MNU_DrawString(160, 170, /*DemoMode ? GStrings("TXT_LBDEMO") :*/ GStrings("TXT_ENTERING"), 1, 16, 0);
MNU_DrawString(160, 180, rec->DisplayName(), 1, 16, 0);
return 0;
}
};

View file

@ -404,12 +404,11 @@ grpinfo
GameID "Blood"
}
/* this doesn't work with the current setup.
grpinfo
{
// This is for identifying older Blood versions. Since I have no information, all I can do is testing for a few known files.
name "BLOOD: Unknown Version"
mustcontain "help1.qav", "cult2d2.seq", "tombstn1.kvx", "normal.plu"
mustcontain "help1.qav", "normal.plu", "inverse.clu", "cosine.dat", "blood.pal"
defname "blood.def"
scriptname "BLOOD.INI"
flags GAMEFLAG_BLOOD
@ -417,7 +416,6 @@ grpinfo
loadgrp "SOUNDS.RFF", "GUI.RFF"
gamefilter "Blood.Blood"
}
*/
grpinfo
{

View file

@ -1033,10 +1033,7 @@ OptionMenu "VideoOptions" protected
StaticText ""
Option "$DSPLYMNU_VOXELS", "r_voxels", "OnOff"
Option "$DSPLYMNU_SHADOWS", "r_shadows", "OnOff"
ifnotgame(Exhumed)
{
Option "$DSPLYMNU_SLOPETILT", "cl_slopetilting", "OnOff"
}
Option "$DSPLYMNU_SLOPETILT", "cl_slopetilting", "OnOff"
ifnotgame(Blood, Exhumed)
{
Option "$DSPLYMNU_VIEWBOB", "cl_viewbob", "OnOff"