BLADERUNNER: Better handling of cut overlay

Also some minor code changes (renamed a few variables and added more explanatory comments)
This commit is contained in:
antoniou79 2023-07-05 14:27:17 +03:00
parent f15f37eb97
commit ea23048d60
6 changed files with 158 additions and 56 deletions

View File

@ -93,15 +93,21 @@ int Overlays::play(const Common::String &name, int loopId, bool loopForever, boo
&& _videos[index].vqaPlayer->getFrameCount() > 0
) {
skipNewVQAPlayerOpen = true;
// INFO The actual enqueuing happens in VQAPlayer -- see: kLoopSetModeEnqueue()
// The enqueuedLoopId is a field that is not stored separately, but will be stored as the value of "loopId"
// (see Overlays::save()) so that, when loading, the engine will know
// that it needs to reach the end frame of the *queued* loop.
// It is not used elsewhere. The actual enqueuing as well as the decision of what loop id should be resumed
// are done in the VQAPlayer class.
_videos[index].enqueuedLoopId = loopId;
}
if (skipNewVQAPlayerOpen || _videos[index].vqaPlayer->open()) {
_videos[index].vqaPlayer->setLoop(
loopId,
loopForever ? -1 : 0,
startNow ? kLoopSetModeImmediate : kLoopSetModeEnqueue,
nullptr, nullptr);
_videos[index].vqaPlayer->setLoop(loopId,
loopForever ? -1 : 0,
startNow ? kLoopSetModeImmediate : kLoopSetModeEnqueue,
nullptr,
nullptr);
} else {
resetSingle(index);
return -1;
@ -128,6 +134,11 @@ void Overlays::resume(bool isLoadingGame) {
_videos[i].vqaPlayer->seekToFrame(_videos[i].frame);
_videos[i].vqaPlayer->update(true);
// Update the enqueued loop id, if it was changed within the vqaPlayer->update() call
// so that if the user saves the game, the correct queued id will be stored
if (_videos[i].enqueuedLoopId != -1 && _videos[i].enqueuedLoopId != _videos[i].vqaPlayer->getLoopIdTarget()) {
_videos[i].enqueuedLoopId = _videos[i].vqaPlayer->getLoopIdTarget();
}
}
}
}
@ -151,6 +162,11 @@ void Overlays::tick() {
for (int i = 0; i < kOverlayVideos; ++i) {
if (_videos[i].loaded) {
_videos[i].frame = _videos[i].vqaPlayer->update(true);
// Update the enqueued loop id, if it was changed within the vqaPlayer->update() call
// so that if the user saves the game, the correct queued id will be stored
if (_videos[i].enqueuedLoopId != -1 && _videos[i].enqueuedLoopId != _videos[i].vqaPlayer->getLoopIdTarget()) {
_videos[i].enqueuedLoopId = _videos[i].vqaPlayer->getLoopIdTarget();
}
if (_videos[i].frame < 0) {
resetSingle(i);
}
@ -202,7 +218,7 @@ void Overlays::save(SaveFileWriteStream &f) {
f.writeStringSz(ov.name, 13);
f.writeSint32LE(ov.hash);
if (ov.enqueuedLoopId != -1) {
// When there is an enqueued video, save that loop Id instead
// When there is an enqueued video, save that loop Id instead
f.writeInt(ov.enqueuedLoopId);
} else {
f.writeInt(ov.loopId);

View File

@ -329,20 +329,30 @@ void SceneScriptUG18::PlayerWalkedIn() {
if (Game_Flag_Query(kFlagUG18GuzzaScene)) {
switch (Global_Variable_Query(kVariableUG18StateOfGuzzaCorpse)) {
case kUG18GuzzaCorpseFloatsDown:
Global_Variable_Set(kVariableUG18StateOfGuzzaCorpse, kUG18GuzzaCorpseStuckInPipes);
// same logic as using the BB06OVER for doll explosion case in BB06
Global_Variable_Set(kVariableUG18StateOfGuzzaCorpse, kUG18GuzzaCorpseDissolves);
// Same logic as using the BB06OVER for doll explosion case in BB06.
// (queuing only works on top of a loop that is repeating)
// Note that in the current engine implementation the last queued loop
// is also supposed to be repeating, which is the case for most queued loops cases.
// If it should not, like here, some special case is required for it in VQAPlayer::update(),
// see use of _specialUG18DoNotRepeatLastLoop.
// Here loop 2 is the last queued loop (queued explicitly in VQAPlayer::update())
// which *should not* be repeated more than once.
Overlay_Play("UG18OVR2", 0, true, true, 0);
Overlay_Play("UG18OVR2", 1, true, false, 0);
break;
case kUG18GuzzaCorpseStuckInPipes:
Global_Variable_Set(kVariableUG18StateOfGuzzaCorpse, kUG18GuzzaCorpseDissolves);
Overlay_Play("UG18OVR2", 1, true, true, 0);
Overlay_Play("UG18OVR2", 2, false, false, 0);
break;
// case kUG18GuzzaCorpseStuckInPipes:
// Global_Variable_Set(kVariableUG18StateOfGuzzaCorpse, kUG18GuzzaCorpseDissolves);
// Overlay_Play("UG18OVR2", 1, true, true, 0);
// Overlay_Play("UG18OVR2", 2, false, false, 0);
// break;
case kUG18GuzzaCorpseDissolves:
Global_Variable_Set(kVariableUG18StateOfGuzzaCorpse, kUG18GuzzaNoCorpse);
Overlay_Remove("UG18OVR2");
break;
default:
break;
}

View File

@ -205,6 +205,15 @@ bool VQADecoder::loadStream(Common::SeekableReadStream *s) {
return true;
}
void VQADecoder::overrideOffsetXY(uint16 offX, uint16 offY) {
_header.offsetX = offX;
_header.offsetY = offY;
if (_videoTrack != nullptr) {
_videoTrack->overrideOffsetXY(offX, offY);
}
};
void VQADecoder::decodeVideoFrame(Graphics::Surface *surface, int frame, bool forceDraw) {
_decodingFrame = frame;
_videoTrack->decodeVideoFrame(surface, forceDraw);
@ -604,18 +613,20 @@ bool VQADecoder::readLNIN(Common::SeekableReadStream *s, uint32 size) {
return true;
}
bool VQADecoder::getLoopBeginAndEndFrame(int loop, int *begin, int *end) {
bool VQADecoder::getLoopBeginAndEndFrame(int loopId, int *begin, int *end) {
assert(begin && end);
if (loop < 0 || loop >= _loopInfo.loopCount)
if (loopId < 0 || loopId >= _loopInfo.loopCount)
return false;
*begin = _loopInfo.loops[loop].begin;
*end = _loopInfo.loops[loop].end;
*begin = _loopInfo.loops[loopId].begin;
*end = _loopInfo.loops[loopId].end;
return true;
}
// Note that some video loops (scene loops) share frames (but will have different start frame (or end frame?))
// Thus this method may not return the "correct" loop here. It will just return the first loop that contains the frame.
int VQADecoder::getLoopIdFromFrame(int frame) {
if (frame >= 0) {
for (int loopId = 0; loopId < _loopInfo.loopCount; ++loopId) {
@ -751,6 +762,11 @@ int VQADecoder::VQAVideoTrack::getFrameCount() const {
return _numFrames;
}
void VQADecoder::VQAVideoTrack::overrideOffsetXY(uint16 offX, uint16 offY) {
_offsetX = offX;
_offsetY = offY;
}
Common::Rational VQADecoder::VQAVideoTrack::getFrameRate() const {
return _frameRate;
}

View File

@ -80,10 +80,12 @@ public:
uint16 offsetX() const { return _header.offsetX; }
uint16 offsetY() const { return _header.offsetY; }
void overrideOffsetXY(uint16 offX, uint16 offY);
bool hasAudio() const { return _header.channels != 0; }
uint16 frequency() const { return _header.freq; }
bool getLoopBeginAndEndFrame(int loop, int *begin, int *end);
bool getLoopBeginAndEndFrame(int loopId, int *begin, int *end);
int getLoopIdFromFrame(int frame);
void allocatePaletteVQPTable(const uint32 numOfPalettes);
@ -198,6 +200,8 @@ public:
int getFrameCount() const;
void overrideOffsetXY(uint16 offX, uint16 offY);
void decodeVideoFrame(Graphics::Surface *surface, bool forceDraw);
void decodeZBuffer(ZBuffer *zbuffer);
void decodeView(View *view);

View File

@ -49,6 +49,7 @@ bool VQAPlayer::open() {
#if !BLADERUNNER_ORIGINAL_BUGS
_specialPS15GlitchFix = false;
_specialUG18DoNotRepeatLastLoop = false;
// TB05 has wrong end of a loop and this will load empty zbuffer from next loop, which will lead to broken pathfinding
if (_name.equals("TB05_2.VQA")) {
_decoder._loopInfo.loops[1].end = 60;
@ -69,6 +70,13 @@ bool VQAPlayer::open() {
} else if (_name.equals("PS15.VQA") || _name.equals("PS15_2.VQA")) {
// Fix should be applied in Act 1-3 versions of this background
_specialPS15GlitchFix = true;
} else if (_name.equals("UG19OVR1.VQA")) {
// Original has x: 244, y: 88
// This still does not look quite right
// TODO What is this overlay supposed to be for?
_decoder.overrideOffsetXY(248, 110);
} else if (_name.equals("UG18OVR2.VQA")) {
_specialUG18DoNotRepeatLastLoop = true;
}
#endif
@ -79,18 +87,18 @@ bool VQAPlayer::open() {
}
_repeatsCount = 0;
_loopNext = -1;
_loopIdTarget = -1;
_frame = -1;
_frameBeginNext = -1;
_frameEnd = getFrameCount() - 1;
_frameEndQueued = -1;
_repeatsCountQueued = -1;
if (_loopInitial >= 0) {
// loopInitial is set to the loop Id value that should play,
if (_loopIdInitial >= 0) {
// loopIdInitial is set to the loop Id value that should play,
// when the SeekableReadStream (_s) is nullptr
// see setLoop()
setLoop(_loopInitial, _repeatsCountInitial, kLoopSetModeImmediate, nullptr, nullptr);
setLoop(_loopIdInitial, _repeatsCountInitial, kLoopSetModeImmediate, nullptr, nullptr);
} else {
_frameNext = 0;
// TODO? Removed as redundant
@ -178,6 +186,8 @@ int VQAPlayer::update(bool forceDraw, bool advanceFrame, bool useTime, Graphics:
if ((_repeatsCount > 0 || _repeatsCount == -1) && (_frameNext > _frameEnd)) {
int loopEndQueued = _frameEndQueued;
bool specialEnqueue = false;
if (_frameEndQueued != -1) {
_frameEnd = _frameEndQueued;
_frameEndQueued = -1;
@ -190,7 +200,7 @@ int VQAPlayer::update(bool forceDraw, bool advanceFrame, bool useTime, Graphics:
// The code is similar to Scene::advanceFrame()
// This will be done once, since this first loop (loopId 1)
// is only executed once before moving on to loopId 2
if (_name.equals("MA05_3.VQA") && _loopNext == 1) {
if (_name.equals("MA05_3.VQA") && _loopIdTarget == 1) {
while (update(false, true, false) != 59) {
updateZBuffer(_vm->_zbuffer);
}
@ -199,34 +209,70 @@ int VQAPlayer::update(bool forceDraw, bool advanceFrame, bool useTime, Graphics:
// Scene::loopEnded()
//
_frameBeginNext = 60;
} else if (_name.equals("UG18OVR2.VQA")) {
// This overlay has three loops (0,1,2) that
// need to be played back to back.
// However, then engine can only queue up to two loops.
// So in this case, we force enqueuing the final loop explicitly here
// loop 0: 0 - 59
// loop 1: 60 - 119
// loop 2: 120 - 135
//
if (_loopIdTarget == 1) {
// we just loaded up the previously enqueued loop 1
_frameNext = _frameBeginNext;
// this also has to be enqueued to be (fake) repeated forever,
// (presumably _repeatsCountQueued is also -1),
// in order for the code to proceed to the newly queued loop 2 after this one (loop 1) ends
_repeatsCount = -1;
if (_callbackLoopEnded != nullptr) {
_callbackLoopEnded(_callbackData, 0, _loopIdTarget);
}
specialEnqueue = true;
_loopIdTarget = 2;
_frameBeginNext = 120;
_frameEndQueued = 135;
_repeatsCountQueued = 0;
}
}
} else if (_specialUG18DoNotRepeatLastLoop && _loopIdTarget == 2) {
// This extra check is needed to stop the last loop (2) of UG18OVR2.VQA from repeating,
// in case we loaded a saved game while the queued loops (1 or 2) were playing.
result = -3;
// _repeatsCount == 0, so return here at the end of the video, to release the resource
return result;
#endif
}
_frameNext = _frameBeginNext;
if (!specialEnqueue) {
_frameNext = _frameBeginNext;
if (loopEndQueued == -1) {
if (_repeatsCount != -1) {
--_repeatsCount;
}
//callback for repeat, it is not used in the blade runner
} else {
_repeatsCount = _repeatsCountQueued;
_repeatsCountQueued = -1;
if (loopEndQueued == -1) {
if (_repeatsCount > 0) {
--_repeatsCount;
}
//callback for repeat, it is not used in the blade runner
} else {
_repeatsCount = _repeatsCountQueued; // ASDF IS THIS STORED IN SAVED GAME?
_repeatsCountQueued = -1;
if (_callbackLoopEnded != nullptr) {
_callbackLoopEnded(_callbackData, 0, _loopNext);
if (_callbackLoopEnded != nullptr) {
_callbackLoopEnded(_callbackData, 0, _loopIdTarget);
}
}
}
result = -1;
} else if (_frameNext > _frameEnd) {
result = -3;
// _repeatsCount == 0, so return here at the end of the video, to release the resource
return result;
} else if (useTime && (now - (_frameNextTime - kVqaFrameTimeDiff) < kVqaFrameTimeDiff)) {
// Not yet time to move to next frame.
// Note, we use unsigned difference to avoid potential time overflow issues
result = -1;
} else if (advanceFrame) {
_frame = _frameNext;
_decoder.readFrame(_frameNext, kVQAReadVideo);
@ -345,19 +391,20 @@ void VQAPlayer::updateLights(Lights *lights) {
_decoder.decodeLights(lights);
}
bool VQAPlayer::setLoop(int loop, int repeatsCount, int loopSetMode, void (*callback)(void *, int, int), void *callbackData) {
bool VQAPlayer::setLoop(int loopId, int repeatsCount, int loopSetMode, void (*callback)(void *, int, int), void *callbackData) {
if (_s == nullptr) {
_loopInitial = loop;
_loopIdInitial = loopId;
_repeatsCountInitial = repeatsCount;
return true;
}
// TODO IF LOOP IS A "TARGET LOOP ID" then begin and end will get the values for that target (final) loop id
int begin, end;
if (!_decoder.getLoopBeginAndEndFrame(loop, &begin, &end)) {
if (!_decoder.getLoopBeginAndEndFrame(loopId, &begin, &end)) {
return false;
}
if (setBeginAndEndFrame(begin, end, repeatsCount, loopSetMode, callback, callbackData)) {
_loopNext = loop;
_loopIdTarget = loopId; // TODO ASDF MAYBE SET THIS AS TARGET LOOP!
return true;
}
return false;
@ -375,7 +422,7 @@ bool VQAPlayer::setBeginAndEndFrame(int begin, int end, int repeatsCount, int lo
}
if (repeatsCount < 0) {
repeatsCount = -1;
repeatsCount = -1; // loop "forever"
}
if (_repeatsCount == 0 && loopSetMode == kLoopSetModeEnqueue) {
@ -384,14 +431,14 @@ bool VQAPlayer::setBeginAndEndFrame(int begin, int end, int repeatsCount, int lo
loopSetMode = kLoopSetModeImmediate;
}
_frameBeginNext = begin;
_frameBeginNext = begin; // TODO ASDF THIS IN THE MULTI QUEUE CASE WILL BE THE BEGIN FRAME OF THE FINAL ITEM IN QUEUE
if (loopSetMode == kLoopSetModeJustStart) {
_repeatsCount = repeatsCount;
_frameEnd = end;
} else if (loopSetMode == kLoopSetModeEnqueue) {
_repeatsCountQueued = repeatsCount;
_frameEndQueued = end;
_repeatsCountQueued = repeatsCount; // TODO applies only to the last of the queued loops
_frameEndQueued = end; // TODO ASDF THIS IN THE MULTI QUEUE CASE WILL BE THE END FRAME OF THE FINAL ITEM IN QUEUE
} else if (loopSetMode == kLoopSetModeImmediate) {
_repeatsCount = repeatsCount;
_frameEnd = end;
@ -411,24 +458,29 @@ bool VQAPlayer::seekToFrame(int frame) {
}
bool VQAPlayer::getCurrentBeginAndEndFrame(int frame, int *begin, int *end) {
int playingLoop = _decoder.getLoopIdFromFrame(frame);
if (playingLoop != -1) {
return _decoder.getLoopBeginAndEndFrame(playingLoop, begin, end);
// updates the values of begin (frame) and end (frame)
// based on the value of the "frame" argument.
// First, the current loopId is detemined from the value of the "frame" argument.
// TODO ASDF THIS MIGHT BE USEFUL!!! _decoder.getLoopIdFromFrame(frame);
// ALSO SEE USE OF THE CURRENT METHOD IN Scene::resume()
int playingLoopId = _decoder.getLoopIdFromFrame(frame);
if (playingLoopId != -1) {
return _decoder.getLoopBeginAndEndFrame(playingLoopId, begin, end);
}
return false;
}
int VQAPlayer::getLoopBeginFrame(int loop) {
int VQAPlayer::getLoopBeginFrame(int loopId) {
int begin, end;
if (!_decoder.getLoopBeginAndEndFrame(loop, &begin, &end)) {
if (!_decoder.getLoopBeginAndEndFrame(loopId, &begin, &end)) {
return -1;
}
return begin;
}
int VQAPlayer::getLoopEndFrame(int loop) {
int VQAPlayer::getLoopEndFrame(int loopId) {
int begin, end;
if (!_decoder.getLoopBeginAndEndFrame(loop, &begin, &end)) {
if (!_decoder.getLoopBeginAndEndFrame(loopId, &begin, &end)) {
return -1;
}
return end;

View File

@ -63,7 +63,7 @@ class VQAPlayer {
int _frameBeginNext; // The frame to begin from, after current playing loop ends.
// Does not necessarily reflect current playing loop's start frame
int _frameEnd; // The frame to end at for current playing loop
int _loopNext; // Does not necessarily reflect current playing loop's id
int _loopIdTarget; // Does not necessarily reflect current playing loop's id (for a queue of loops this will have the id of the last one in the queue)
// Used: - as param for _callbackLoopEnded() (which typically is loopEnded()), but never actually used in there)
// - for the MA05 inshot glitch workaround
// It is set at every setLoop call except for the _loopInitial case (when no video stream is loaded)
@ -78,7 +78,7 @@ class VQAPlayer {
int _lastAudioFrameSuccessfullyQueued;
int _loopInitial;
int _loopIdInitial;
int _repeatsCountInitial;
uint32 _frameNextTime;
@ -87,6 +87,7 @@ class VQAPlayer {
Audio::SoundHandle _soundHandle;
bool _specialPS15GlitchFix;
bool _specialUG18DoNotRepeatLastLoop;
void (*_callbackLoopEnded)(void *, int frame, int loopId);
void *_callbackData;
@ -104,17 +105,18 @@ public:
_frameNext(-1),
_frameBeginNext(-1),
_frameEnd(-1),
_loopNext(-1),
_loopIdTarget(-1),
_repeatsCount(-1),
_repeatsCountQueued(-1),
_frameEndQueued(-1),
_lastAudioFrameSuccessfullyQueued(-1),
_loopInitial(-1),
_loopIdInitial(-1),
_repeatsCountInitial(-1),
_frameNextTime(0),
_hasAudio(false),
_audioStarted(false),
_specialPS15GlitchFix(false),
_specialUG18DoNotRepeatLastLoop(false),
_callbackLoopEnded(nullptr),
_callbackData(nullptr) { }
@ -134,13 +136,15 @@ public:
void updateLights(Lights *lights);
bool setBeginAndEndFrame(int begin, int end, int repeatsCount, int loopSetMode, void(*callback)(void *, int, int), void *callbackData);
bool setLoop(int loop, int repeatsCount, int loopSetMode, void(*callback)(void*, int, int), void *callbackData);
bool setLoop(int loopId, int repeatsCount, int loopSetMode, void(*callback)(void*, int, int), void *callbackData);
bool seekToFrame(int frame);
bool getCurrentBeginAndEndFrame(int frame, int *begin, int *end);
int getLoopBeginFrame(int loop);
int getLoopEndFrame(int loop);
int getLoopBeginFrame(int loopId);
int getLoopEndFrame(int loopId);
int getLoopIdTarget() const { return _loopIdTarget; };
int getFrameCount() const;