[Scummvm-git-logs] scummvm master -> e2720d39f327bd152a472cf41b1272fb0e866d16
fracturehill
noreply at scummvm.org
Sat Oct 7 12:18:21 UTC 2023
This automated email contains information about 13 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .
Summary:
8b7c7f9b17 NANCY: Implement SetVolume action record
7f16ac517d NANCY: Define kNoScene constant
a3f1695f2d NANCY: Implement terse Overlay variants
85b1332ab7 NANCY: Do not retain satisfied status for dependencies
6475f9bf6a NANCY: Animated Overlay loop improvement
6ed79ba757 NANCY: Add support for Bink video
f126ac3d5f NANCY: Fix intro sound in nancy3
67d36e46b7 NANCY: Add support for nancy7 special effects
3a72322d55 NANCY: Ensure objects' last position is cleared
4aa0727f95 NANCY: Reset textbox scrollbar when adding new line
151561e6ee NANCY: Implement KeypadTersePuzzle
90beb8887e AGS: Fix clipping issue in blitting code
e2720d39f3 AGS: Fix blitting-related crash
Commit: 8b7c7f9b17f9c196254fed11c5dab9f3d866bc57
https://github.com/scummvm/scummvm/commit/8b7c7f9b17f9c196254fed11c5dab9f3d866bc57
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-10-07T14:45:21+03:00
Commit Message:
NANCY: Implement SetVolume action record
Changed paths:
engines/nancy/action/arfactory.cpp
engines/nancy/action/soundrecords.cpp
engines/nancy/action/soundrecords.h
engines/nancy/sound.cpp
diff --git a/engines/nancy/action/arfactory.cpp b/engines/nancy/action/arfactory.cpp
index 9e2773464c4..9fd92dc3a43 100644
--- a/engines/nancy/action/arfactory.cpp
+++ b/engines/nancy/action/arfactory.cpp
@@ -234,6 +234,8 @@ ActionRecord *ActionManager::createActionRecord(uint16 type, Common::SeekableRea
return new EnableDisableInventory();
case 125:
return new PopInvViewPriorScene();
+ case 140:
+ return new SetVolume();
case 150:
return new PlaySound();
case 151:
diff --git a/engines/nancy/action/soundrecords.cpp b/engines/nancy/action/soundrecords.cpp
index 138013e7988..059f9a258b4 100644
--- a/engines/nancy/action/soundrecords.cpp
+++ b/engines/nancy/action/soundrecords.cpp
@@ -33,6 +33,16 @@
namespace Nancy {
namespace Action {
+void SetVolume::readData(Common::SeekableReadStream &stream) {
+ channel = stream.readUint16LE();
+ volume = stream.readUint16LE();
+}
+
+void SetVolume::execute() {
+ g_nancy->_sound->setVolume(channel, volume);
+ _isDone = true;
+}
+
void PlaySound::readData(Common::SeekableReadStream &stream) {
_sound.readDIGI(stream);
diff --git a/engines/nancy/action/soundrecords.h b/engines/nancy/action/soundrecords.h
index cdddf1b1862..beb37e9ce50 100644
--- a/engines/nancy/action/soundrecords.h
+++ b/engines/nancy/action/soundrecords.h
@@ -27,6 +27,19 @@
namespace Nancy {
namespace Action {
+// Sets the volume for a particular channel.
+class SetVolume : public ActionRecord {
+public:
+ void readData(Common::SeekableReadStream &stream) override;
+ void execute() override;
+
+ uint16 channel = 0;
+ uint16 volume = 0;
+
+protected:
+ Common::String getRecordTypeName() const override { return "SetVolume"; }
+};
+
// Used for sound effects. From nancy3 up it includes 3D sound data, which lets
// the sound move in 3D space as the player rotates/changes scenes. Also supports
// changing the scene and/or setting a flag
diff --git a/engines/nancy/sound.cpp b/engines/nancy/sound.cpp
index a8c4adda1ad..b68553264e9 100644
--- a/engines/nancy/sound.cpp
+++ b/engines/nancy/sound.cpp
@@ -498,10 +498,10 @@ byte SoundManager::getVolume(const Common::String &chunkName) {
}
void SoundManager::setVolume(uint16 channelID, uint16 volume) {
- if (channelID >= _channels.size())
+ if (channelID >= _channels.size() || !isSoundPlaying(channelID))
return;
- _mixer->setChannelVolume(_channels[channelID].handle, volume);
+ _mixer->setChannelVolume(_channels[channelID].handle, volume * 255 / 100);
}
void SoundManager::setVolume(const SoundDescription &description, uint16 volume) {
Commit: 7f16ac517dd8196b800b159f900dcaf11c8059c5
https://github.com/scummvm/scummvm/commit/7f16ac517dd8196b800b159f900dcaf11c8059c5
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-10-07T14:45:21+03:00
Commit Message:
NANCY: Define kNoScene constant
Changed paths:
engines/nancy/action/actionmanager.cpp
engines/nancy/action/soundrecords.cpp
engines/nancy/commontypes.h
engines/nancy/enginedata.h
engines/nancy/state/scene.cpp
diff --git a/engines/nancy/action/actionmanager.cpp b/engines/nancy/action/actionmanager.cpp
index f1f7c4b2807..39faa67d328 100644
--- a/engines/nancy/action/actionmanager.cpp
+++ b/engines/nancy/action/actionmanager.cpp
@@ -604,7 +604,7 @@ void ActionManager::synchronizeMovieWithSound() {
// A movie and a sound both got activated this frame, check if their scene changes match
if ( movie->_videoSceneChange == PlaySecondaryMovie::kMovieSceneChange &&
movie->_sceneChange.sceneID == sound->_sceneChange.sceneID &&
- movie->_sceneChange.sceneID != 9999) {
+ movie->_sceneChange.sceneID != kNoScene) {
// They match, check how long the sound is...
Audio::Timestamp length = g_nancy->_sound->getLength(sound->_sound);
diff --git a/engines/nancy/action/soundrecords.cpp b/engines/nancy/action/soundrecords.cpp
index 059f9a258b4..3e07e9770b7 100644
--- a/engines/nancy/action/soundrecords.cpp
+++ b/engines/nancy/action/soundrecords.cpp
@@ -184,7 +184,7 @@ void PlaySoundMultiHS::readData(Common::SeekableReadStream &stream) {
stream.skip(2);
} else {
_flag.label = kEvNoEvent;
- _sceneChange.sceneID = 9999;
+ _sceneChange.sceneID = kNoScene;
}
uint16 numHotspots = stream.readUint16LE();
diff --git a/engines/nancy/commontypes.h b/engines/nancy/commontypes.h
index d92a3ac6ec9..f0e56b3171a 100644
--- a/engines/nancy/commontypes.h
+++ b/engines/nancy/commontypes.h
@@ -46,6 +46,7 @@ class NancyEngine;
static const int8 kFlagNoLabel = -1;
static const int8 kEvNoEvent = -1;
static const int8 kFrNoFrame = -1;
+static const uint16 kNoScene = 9999;
// Inventory items use types
static const byte kInvItemUseThenLose = 0;
@@ -147,7 +148,7 @@ enum NancyState {
// Describes a scene transition
struct SceneChangeDescription {
- uint16 sceneID = 0;
+ uint16 sceneID = kNoScene;
uint16 frameID = 0;
uint16 verticalOffset = 0;
uint16 continueSceneSound = kLoadSceneSound;
diff --git a/engines/nancy/enginedata.h b/engines/nancy/enginedata.h
index b2caec2d536..c292d11d547 100644
--- a/engines/nancy/enginedata.h
+++ b/engines/nancy/enginedata.h
@@ -106,7 +106,7 @@ struct INV : public EngineData {
struct ItemDescription {
Common::String name;
byte keepItem = kInvItemKeepAlways;
- uint16 sceneID = 9999;
+ uint16 sceneID = kNoScene;
uint16 sceneSoundFlag = kContinueSceneSound;
Common::Rect sourceRect;
Common::Rect highlightedSourceRect;
diff --git a/engines/nancy/state/scene.cpp b/engines/nancy/state/scene.cpp
index 25417f367f3..56468f93051 100644
--- a/engines/nancy/state/scene.cpp
+++ b/engines/nancy/state/scene.cpp
@@ -228,7 +228,7 @@ bool Scene::onStateExit(const NancyState::NancyState nextState) {
}
void Scene::changeScene(const SceneChangeDescription &sceneDescription) {
- if (sceneDescription.sceneID == 9999 || _state == kLoad) {
+ if (sceneDescription.sceneID == kNoScene || _state == kLoad) {
return;
}
Commit: a3f1695f2d7426f35a41df5ff2be271390f38a8a
https://github.com/scummvm/scummvm/commit/a3f1695f2d7426f35a41df5ff2be271390f38a8a
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-10-07T14:45:21+03:00
Commit Message:
NANCY: Implement terse Overlay variants
Added the short variants of Overlay, OverlayStaticTerse,
and OverlayAnimTerse, which were introduced in nancy7.
Changed paths:
engines/nancy/action/arfactory.cpp
engines/nancy/action/overlay.cpp
engines/nancy/action/overlay.h
diff --git a/engines/nancy/action/arfactory.cpp b/engines/nancy/action/arfactory.cpp
index 9fd92dc3a43..7f6e68540e3 100644
--- a/engines/nancy/action/arfactory.cpp
+++ b/engines/nancy/action/arfactory.cpp
@@ -130,12 +130,23 @@ ActionRecord *ActionManager::createActionRecord(uint16 type, Common::SeekableRea
case 54:
if (g_nancy->getGameType() <= kGameTypeNancy1) {
return new Overlay(false); // PlayStaticBitmapAnimation
+ } else {
+ return new Overlay(true);
}
- // fall through
case 55:
- return new Overlay(true); // PlayIntStaticBitmapAnimation
+ if (g_nancy->getGameType() <= kGameTypeNancy1) {
+ return new Overlay(true); // PlayIntStaticBitmapAnimation
+ } else if (g_nancy->getGameType() >= kGameTypeNancy7) {
+ return new OverlayStaticTerse();
+ }
+ return nullptr;
case 56:
- return new ConversationVideo();
+ if (g_nancy->getGameType() <= kGameTypeNancy6) {
+ return new ConversationVideo();
+ } else if (g_nancy->getGameType() >= kGameTypeNancy7) {
+ return new OverlayAnimTerse();
+ }
+ return nullptr;
case 57:
return new ConversationCel();
case 58:
diff --git a/engines/nancy/action/overlay.cpp b/engines/nancy/action/overlay.cpp
index 4bc1019a4c1..22043b1a2df 100644
--- a/engines/nancy/action/overlay.cpp
+++ b/engines/nancy/action/overlay.cpp
@@ -365,6 +365,50 @@ void Overlay::setFrame(uint frame) {
_needsRedraw = true;
}
+void OverlayStaticTerse::readData(Common::SeekableReadStream &stream) {
+ readFilename(stream, _imageName);
+ _transparency = stream.readUint16LE();
+ _z = stream.readUint16LE();
+
+ Common::Rect dest, src;
+ readRect(stream, dest);
+ readRect(stream, src);
+
+ _srcRects.push_back(src);
+ _blitDescriptions.resize(1);
+ _blitDescriptions[0].src = Common::Rect(src.width(), src.height());
+ _blitDescriptions[0].dest = dest;
+
+ _overlayType = kPlayOverlayStatic;
+}
+
+void OverlayAnimTerse::readData(Common::SeekableReadStream &stream) {
+ readFilename(stream, _imageName);
+ stream.skip(2); // VIDEO_STOP_RENDERING, VIDEO_CONTINUE_RENDERING
+ _transparency = stream.readUint16LE();
+ _hasSceneChange = stream.readUint16LE();
+ _z = stream.readUint16LE();
+ _playDirection = stream.readUint16LE();
+ _loop = stream.readUint16LE();
+
+ _sceneChange.sceneID = stream.readUint16LE();
+ _sceneChange.continueSceneSound = kContinueSceneSound;
+ _sceneChange.listenerFrontVector.set(0, 0, 1);
+ _flagsOnTrigger.descs[0].label = stream.readSint16LE();
+ _flagsOnTrigger.descs[0].flag = stream.readUint16LE();
+
+ _firstFrame = _loopFirstFrame = stream.readUint16LE();
+ _loopLastFrame = stream.readUint16LE();
+
+ _blitDescriptions.resize(1);
+ readRect(stream, _blitDescriptions[0].dest);
+
+ readRectArray(stream, _srcRects, _loopLastFrame - _loopFirstFrame + 1);
+
+ _overlayType = kPlayOverlayAnimated;
+ _frameTime = Common::Rational(1000, 15).toInt(); // Always set to 15 fps
+}
+
void TableIndexOverlay::readData(Common::SeekableReadStream &stream) {
_tableIndex = stream.readUint16LE();
Overlay::readData(stream);
diff --git a/engines/nancy/action/overlay.h b/engines/nancy/action/overlay.h
index 00a1c0be13c..5e336149be1 100644
--- a/engines/nancy/action/overlay.h
+++ b/engines/nancy/action/overlay.h
@@ -91,6 +91,30 @@ protected:
Graphics::ManagedSurface _fullSurface;
};
+// Short version of a static overlay; assumes scene background doesn't move
+class OverlayStaticTerse : public Overlay {
+public:
+ OverlayStaticTerse() : Overlay(true) {}
+ virtual ~OverlayStaticTerse() {}
+
+ void readData(Common::SeekableReadStream &stream) override;
+
+protected:
+ Common::String getRecordTypeName() const override { return "OverlayStaticTerse"; }
+};
+
+// Short version of an animated overlay; assumes scene background doesn't move
+class OverlayAnimTerse : public Overlay {
+public:
+ OverlayAnimTerse() : Overlay(true) {}
+ virtual ~OverlayAnimTerse() {}
+
+ void readData(Common::SeekableReadStream &stream) override;
+
+protected:
+ Common::String getRecordTypeName() const override { return "OverlayAnimTerse"; }
+};
+
class TableIndexOverlay : public Overlay {
public:
TableIndexOverlay() : Overlay(true) {}
Commit: 85b1332ab7de211fd00d15de1860e7623474fbcd
https://github.com/scummvm/scummvm/commit/85b1332ab7de211fd00d15de1860e7623474fbcd
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-10-07T14:45:21+03:00
Commit Message:
NANCY: Do not retain satisfied status for dependencies
Fixed all instances of dependencies that, once satisfied, stay
that way forever. This fixes more complex event flag
dependency like the one in nancy7 scene 2805.
Changed paths:
engines/nancy/action/actionmanager.cpp
diff --git a/engines/nancy/action/actionmanager.cpp b/engines/nancy/action/actionmanager.cpp
index 39faa67d328..b795a7d3028 100644
--- a/engines/nancy/action/actionmanager.cpp
+++ b/engines/nancy/action/actionmanager.cpp
@@ -326,11 +326,15 @@ void ActionManager::processDependency(DependencyRecord &dep, ActionRecord &recor
if (NancySceneState._flags.items[dep.label] == g_nancy->_false &&
dep.label != NancySceneState._flags.heldItem) {
dep.satisfied = true;
+ } else {
+ dep.satisfied = false;
}
} else {
if (NancySceneState._flags.items[dep.label] == g_nancy->_true ||
dep.label == NancySceneState._flags.heldItem) {
dep.satisfied = true;
+ } else {
+ dep.satisfied = false;
}
}
@@ -340,6 +344,8 @@ void ActionManager::processDependency(DependencyRecord &dep, ActionRecord &recor
// nancy1 has code for some timer array that never gets used
// and is discarded from nancy2 onward
dep.satisfied = true;
+ } else {
+ dep.satisfied = false;
}
break;
@@ -353,7 +359,11 @@ void ActionManager::processDependency(DependencyRecord &dep, ActionRecord &recor
if (elapsed >= dep.timeData) {
dep.satisfied = true;
+ } else {
+ dep.satisfied = false;
}
+ } else {
+ dep.satisfied = false;
}
} else {
dep.satisfied = NancySceneState.getLogicCondition(dep.label, dep.condition);
@@ -363,12 +373,16 @@ void ActionManager::processDependency(DependencyRecord &dep, ActionRecord &recor
case DependencyType::kElapsedGameTime:
if (NancySceneState._timers.lastTotalTime >= dep.timeData) {
dep.satisfied = true;
+ } else {
+ dep.satisfied = false;
}
break;
case DependencyType::kElapsedSceneTime:
if (NancySceneState._timers.sceneTime >= dep.timeData) {
dep.satisfied = true;
+ } else {
+ dep.satisfied = false;
}
break;
@@ -400,6 +414,8 @@ void ActionManager::processDependency(DependencyRecord &dep, ActionRecord &recor
if ( (dep.minutes < count && g_nancy->getGameType() <= kGameTypeNancy6) ||
(dep.minutes > count && g_nancy->getGameType() >= kGameTypeNancy7)) {
dep.satisfied = true;
+ } else {
+ dep.satisfied = false;
}
break;
@@ -407,12 +423,16 @@ void ActionManager::processDependency(DependencyRecord &dep, ActionRecord &recor
if ( (dep.minutes > count && g_nancy->getGameType() <= kGameTypeNancy6) ||
(dep.minutes < count && g_nancy->getGameType() >= kGameTypeNancy7)) {
dep.satisfied = true;
+ } else {
+ dep.satisfied = false;
}
break;
case 3:
if (dep.minutes == count) {
dep.satisfied = true;
+ } else {
+ dep.satisfied = false;
}
break;
@@ -480,24 +500,32 @@ void ActionManager::processDependency(DependencyRecord &dep, ActionRecord &recor
case DependencyType::kPlayerTOD:
if (dep.label == NancySceneState.getPlayerTOD()) {
dep.satisfied = true;
+ } else {
+ dep.satisfied = false;
}
break;
case DependencyType::kTimerLessThanDependencyTime:
if (NancySceneState._timers.timerTime <= dep.timeData) {
dep.satisfied = true;
+ } else {
+ dep.satisfied = false;
}
break;
case DependencyType::kTimerGreaterThanDependencyTime:
if (NancySceneState._timers.timerTime > dep.timeData) {
dep.satisfied = true;
+ } else {
+ dep.satisfied = false;
}
break;
case DependencyType::kDifficultyLevel:
if (dep.condition == NancySceneState._difficulty) {
dep.satisfied = true;
+ } else {
+ dep.satisfied = false;
}
break;
@@ -505,10 +533,14 @@ void ActionManager::processDependency(DependencyRecord &dep, ActionRecord &recor
if (ConfMan.getBool("subtitles")) {
if (dep.condition == 2) {
dep.satisfied = true;
+ } else {
+ dep.satisfied = false;
}
} else {
if (dep.condition == 1) {
dep.satisfied = true;
+ } else {
+ dep.satisfied = false;
}
}
@@ -527,6 +559,8 @@ void ActionManager::processDependency(DependencyRecord &dep, ActionRecord &recor
if (!dep.stopEvaluating) {
if ((int)g_nancy->_randomSource->getRandomNumber(99) < dep.condition) {
dep.satisfied = true;
+ } else {
+ dep.satisfied = false;
}
dep.stopEvaluating = true;
Commit: 6475f9bf6afe7d37156df707666708d0db741f75
https://github.com/scummvm/scummvm/commit/6475f9bf6afe7d37156df707666708d0db741f75
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-10-07T14:45:21+03:00
Commit Message:
NANCY: Animated Overlay loop improvement
Looping Overlays no longer speed up to "catch up" to the
current frame when a slowdown happens (e.g. when the
console is opened)
Changed paths:
engines/nancy/action/overlay.cpp
engines/nancy/action/overlay.h
diff --git a/engines/nancy/action/overlay.cpp b/engines/nancy/action/overlay.cpp
index 22043b1a2df..5cfb87cd474 100644
--- a/engines/nancy/action/overlay.cpp
+++ b/engines/nancy/action/overlay.cpp
@@ -192,30 +192,33 @@ void Overlay::execute() {
}
}
+ uint16 frameDiff = 1;
+ uint16 nextFrame = _currentFrame;
+
if (_nextFrameTime == 0) {
_nextFrameTime = _currentFrameTime + _frameTime;
} else {
- _nextFrameTime += _frameTime;
+ uint32 timeDiff = _currentFrameTime - _nextFrameTime;
+ frameDiff = timeDiff / _frameTime;
+ _nextFrameTime += _frameTime * frameDiff;
}
- uint16 nextFrame = _currentFrame;
-
if (_playDirection == kPlayOverlayReverse) {
- if (nextFrame - 1 < _loopFirstFrame) {
+ if (nextFrame - frameDiff < _loopFirstFrame) {
// We keep looping if sound is present (nancy1 only)
if (_loop == kPlayOverlayLoop || (_sound.name != "NO SOUND" && g_nancy->getGameType() == kGameTypeNancy1)) {
- nextFrame = _loopLastFrame;
+ nextFrame = _loopLastFrame - (frameDiff % (_loopLastFrame - _loopFirstFrame + 1));
}
} else {
- --nextFrame;
+ nextFrame -= frameDiff;
}
} else {
- if (nextFrame + 1 > _loopLastFrame) {
+ if (nextFrame + frameDiff > _loopLastFrame) {
if (_loop == kPlayOverlayLoop || (_sound.name != "NO SOUND" && g_nancy->getGameType() == kGameTypeNancy1)) {
- nextFrame = _loopFirstFrame;
+ nextFrame = _loopFirstFrame + (frameDiff % (_loopLastFrame - _loopFirstFrame + 1));
}
} else {
- ++nextFrame;
+ nextFrame += frameDiff;
}
}
diff --git a/engines/nancy/action/overlay.h b/engines/nancy/action/overlay.h
index 5e336149be1..c7ade17c38c 100644
--- a/engines/nancy/action/overlay.h
+++ b/engines/nancy/action/overlay.h
@@ -63,7 +63,7 @@ public:
uint16 _firstFrame = 0;
uint16 _loopFirstFrame = 0;
uint16 _loopLastFrame = 0;
- Time _frameTime;
+ uint32 _frameTime = 0;
FlagDescription _interruptCondition;
SceneChangeDescription _sceneChange;
MultiEventFlagDescription _flagsOnTrigger;
@@ -78,7 +78,7 @@ public:
int16 _currentFrame = -1;
int16 _currentViewportFrame = -1;
- Time _nextFrameTime;
+ uint32 _nextFrameTime = 0;
bool _isInterruptible;
protected:
Commit: 6ed79ba757dea06c5b25c53d4e048f0123563b92
https://github.com/scummvm/scummvm/commit/6ed79ba757dea06c5b25c53d4e048f0123563b92
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-10-07T14:45:21+03:00
Commit Message:
NANCY: Add support for Bink video
nancy7 started using Bink videos for some SecondaryMovies
Changed paths:
engines/nancy/action/actionmanager.cpp
engines/nancy/action/secondarymovie.cpp
engines/nancy/action/secondarymovie.h
engines/nancy/commontypes.h
engines/nancy/configure.engine
diff --git a/engines/nancy/action/actionmanager.cpp b/engines/nancy/action/actionmanager.cpp
index b795a7d3028..cd07dba50e5 100644
--- a/engines/nancy/action/actionmanager.cpp
+++ b/engines/nancy/action/actionmanager.cpp
@@ -644,7 +644,7 @@ void ActionManager::synchronizeMovieWithSound() {
if (length.msecs() != 0) {
// ..and set the movie's playback speed to match
- movie->_decoder.setRate(Common::Rational(movie->_decoder.getDuration().msecs(), length.msecs()));
+ movie->_decoder->setRate(Common::Rational(movie->_decoder->getDuration().msecs(), length.msecs()));
}
}
}
diff --git a/engines/nancy/action/secondarymovie.cpp b/engines/nancy/action/secondarymovie.cpp
index 0103e948816..b99c091c440 100644
--- a/engines/nancy/action/secondarymovie.cpp
+++ b/engines/nancy/action/secondarymovie.cpp
@@ -23,18 +23,21 @@
#include "engines/nancy/nancy.h"
#include "engines/nancy/sound.h"
#include "engines/nancy/util.h"
+#include "engines/nancy/video.h"
#include "engines/nancy/action/secondarymovie.h"
-
#include "engines/nancy/state/scene.h"
#include "common/serializer.h"
+#include "video/bink_decoder.h"
+
namespace Nancy {
namespace Action {
PlaySecondaryMovie::~PlaySecondaryMovie() {
- _decoder.close();
+ _decoder->close();
+ delete _decoder;
if (_playerCursorAllowed == kNoPlayerCursorAllowed) {
g_nancy->setMouseEnabled(true);
@@ -49,6 +52,7 @@ void PlaySecondaryMovie::readData(Common::SeekableReadStream &stream) {
readFilename(ser, _paletteName, kGameTypeVampire, kGameTypeVampire);
readFilename(ser, _bitmapOverlayName);
+ ser.syncAsUint16LE(_videoType);
ser.skip(2); // videoPlaySource
ser.syncAsUint16LE(_videoFormat);
ser.skip(4, kGameTypeVampire, kGameTypeVampire); // paletteStart, paletteSize
@@ -88,8 +92,16 @@ void PlaySecondaryMovie::readData(Common::SeekableReadStream &stream) {
}
void PlaySecondaryMovie::init() {
- if (!_decoder.isVideoLoaded()) {
- if (!_decoder.loadFile(_videoName + ".avf")) {
+ if (!_decoder) {
+ if (_videoType == kVideoPlaytypeAVF) {
+ _decoder = new AVFDecoder();
+ } else if (_videoType == kVideoPlaytypeBink) {
+ _decoder = new Video::BinkDecoder();
+ }
+ }
+
+ if (!_decoder->isVideoLoaded()) {
+ if (!_decoder->loadFile(_videoName + (_videoType == kVideoPlaytypeAVF ? ".avf" : ".bik"))) {
error("Couldn't load video file %s", _videoName.c_str());
}
@@ -114,7 +126,7 @@ void PlaySecondaryMovie::init() {
}
void PlaySecondaryMovie::onPause(bool pause) {
- _decoder.pauseVideo(pause);
+ _decoder->pauseVideo(pause);
RenderActionRecord::onPause(pause);
}
@@ -158,18 +170,18 @@ void PlaySecondaryMovie::execute() {
// another action record, but doesn't do so, because updateGraphics() gets called after all
// action record execution. Instead, the movie's own scene change (which is inexplicably enabled)
// gets triggered, and teleports the player to the wrong place instead of making them lose the game
- if (!_decoder.isPlaying() && _isVisible && !_isFinished) {
- _decoder.start();
+ if (!_decoder->isPlaying() && _isVisible && !_isFinished) {
+ _decoder->start();
if (_playDirection == kPlayMovieReverse) {
- _decoder.setRate(-_decoder.getRate());
- _decoder.seekToFrame(_lastFrame);
+ _decoder->setRate(-_decoder->getRate());
+ _decoder->seekToFrame(_lastFrame);
} else {
- _decoder.seekToFrame(_firstFrame);
+ _decoder->seekToFrame(_firstFrame);
}
}
- if (_decoder.needsUpdate()) {
+ if (_decoder->needsUpdate()) {
uint descID = 0;
for (uint i = 0; i < _videoDescs.size(); ++i) {
@@ -178,26 +190,26 @@ void PlaySecondaryMovie::execute() {
}
}
- GraphicsManager::copyToManaged(*_decoder.decodeNextFrame(), _fullFrame, g_nancy->getGameType() == kGameTypeVampire, _videoFormat == kSmallVideoFormat);
+ GraphicsManager::copyToManaged(*_decoder->decodeNextFrame(), _fullFrame, g_nancy->getGameType() == kGameTypeVampire, _videoFormat == kSmallVideoFormat);
_drawSurface.create(_fullFrame, _videoDescs[descID].srcRect);
moveTo(_videoDescs[descID].destRect);
_needsRedraw = true;
for (auto &f : _frameFlags) {
- if (_decoder.getCurFrame() == f.frameID) {
+ if (_decoder->getCurFrame() == f.frameID) {
NancySceneState.setEventFlag(f.flagDesc);
}
}
}
- if ((_decoder.getCurFrame() == _lastFrame && _playDirection == kPlayMovieForward) ||
- (_decoder.getCurFrame() == _firstFrame && _playDirection == kPlayMovieReverse) ||
- _decoder.atEnd()) {
+ if ((_decoder->getCurFrame() == _lastFrame && _playDirection == kPlayMovieForward) ||
+ (_decoder->getCurFrame() == _firstFrame && _playDirection == kPlayMovieReverse) ||
+ _decoder->endOfVideo()) {
// Stop the video and block it from starting again, but also wait for
// sound to end before changing state
- _decoder.pauseVideo(true);
+ _decoder->pauseVideo(true);
_isFinished = true;
if (!g_nancy->_sound->isSoundPlaying(_sound)) {
@@ -224,8 +236,8 @@ void PlaySecondaryMovie::execute() {
// Allow looping
if (!_isDone) {
_isFinished = false;
- _decoder.seek(0);
- _decoder.pauseVideo(false);
+ _decoder->seek(0);
+ _decoder->pauseVideo(false);
}
break;
diff --git a/engines/nancy/action/secondarymovie.h b/engines/nancy/action/secondarymovie.h
index 0e7d079fd38..b33c0bbbe1a 100644
--- a/engines/nancy/action/secondarymovie.h
+++ b/engines/nancy/action/secondarymovie.h
@@ -22,13 +22,16 @@
#ifndef NANCY_ACTION_SECONDARYMOVIE_H
#define NANCY_ACTION_SECONDARYMOVIE_H
-#include "engines/nancy/video.h"
#include "engines/nancy/action/actionrecord.h"
+namespace Video {
+class VideoDecoder;
+}
+
namespace Nancy {
namespace Action {
-// Plays an AVF video. Optionally supports:
+// Plays an AVF or Bink video. Optionally supports:
// - playing a sound;
// - reverse playback;
// - moving with the scene's background frame;
@@ -65,6 +68,7 @@ public:
Common::String _paletteName;
Common::String _bitmapOverlayName;
+ uint16 _videoType = kVideoPlaytypeAVF;
uint16 _videoFormat = kLargeVideoFormat;
uint16 _videoSceneChange = kMovieNoSceneChange;
byte _playerCursorAllowed = kPlayerCursorAllowed;
@@ -79,7 +83,7 @@ public:
SceneChangeDescription _sceneChange;
Common::Array<SecondaryVideoDescription> _videoDescs;
- AVFDecoder _decoder;
+ Video::VideoDecoder *_decoder = nullptr;
protected:
Common::String getRecordTypeName() const override { return "PlaySecondaryMovie"; }
diff --git a/engines/nancy/commontypes.h b/engines/nancy/commontypes.h
index f0e56b3171a..a29b96904d8 100644
--- a/engines/nancy/commontypes.h
+++ b/engines/nancy/commontypes.h
@@ -91,6 +91,9 @@ static const byte kPlayerDuskDawn = 2;
static const byte kSmallVideoFormat = 1;
static const byte kLargeVideoFormat = 2;
+static const byte kVideoPlaytypeAVF = 0;
+static const byte kVideoPlaytypeBink = 1;
+
// Overlay
static const byte kPlayOverlayPlain = 1;
static const byte kPlayOverlayTransparent = 2;
diff --git a/engines/nancy/configure.engine b/engines/nancy/configure.engine
index 6c977d9fc51..b49e32ba802 100644
--- a/engines/nancy/configure.engine
+++ b/engines/nancy/configure.engine
@@ -1,3 +1,3 @@
# This file is included from the main "configure" script
# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps]
-add_engine nancy "Nancy Drew" yes "" "" "16bit highres vorbis"
+add_engine nancy "Nancy Drew" yes "" "" "16bit highres vorbis bink"
Commit: f126ac3d5faf4b82367864f53ff27b613a4a8d5c
https://github.com/scummvm/scummvm/commit/f126ac3d5faf4b82367864f53ff27b613a4a8d5c
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-10-07T14:45:22+03:00
Commit Message:
NANCY: Fix intro sound in nancy3
It seems a scene change only interrupts the execution of
the rest of the ActionRecords in a scene starting from nancy4.
Adding the version check fixes the intro of nancy3, where
the narration wouldn't start playing.
Changed paths:
engines/nancy/action/actionmanager.cpp
diff --git a/engines/nancy/action/actionmanager.cpp b/engines/nancy/action/actionmanager.cpp
index cd07dba50e5..3424df17b6f 100644
--- a/engines/nancy/action/actionmanager.cpp
+++ b/engines/nancy/action/actionmanager.cpp
@@ -257,8 +257,9 @@ void ActionManager::processActionRecords() {
record->execute();
}
- if (NancySceneState._state == State::Scene::kLoad) {
- // changeScene() must have been called, abort any further processing
+ if (g_nancy->getGameType() >= kGameTypeNancy4 && NancySceneState._state == State::Scene::kLoad) {
+ // changeScene() must have been called, abort any further processing.
+ // Both old and new behavior is needed (nancy3 intro narration, nancy4 garden gate)
return;
}
}
Commit: 67d36e46b7b3adcbcec7047acf4d5f7b36454e00
https://github.com/scummvm/scummvm/commit/67d36e46b7b3adcbcec7047acf4d5f7b36454e00
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-10-07T14:45:22+03:00
Commit Message:
NANCY: Add support for nancy7 special effects
The SpecialEffect action record got some significant changes
in nancy7, including a new type of fade, and the removal
of the predefined number of frames to fade. Testing with the
original is tough, since it runs way too fast on modern PCs,
and the new fade type is never actually used properly, so
the current implementation is only an approximation. Also,
I've discovered that prior to these changes the entire screen
got crossfaded, so the screenshotViewport() function is now
entirely removed.
Changed paths:
engines/nancy/action/miscrecords.cpp
engines/nancy/action/miscrecords.h
engines/nancy/graphics.cpp
engines/nancy/graphics.h
engines/nancy/misc/specialeffect.cpp
engines/nancy/misc/specialeffect.h
engines/nancy/state/scene.cpp
engines/nancy/state/scene.h
diff --git a/engines/nancy/action/miscrecords.cpp b/engines/nancy/action/miscrecords.cpp
index 920bfe07d86..fcf720384d8 100644
--- a/engines/nancy/action/miscrecords.cpp
+++ b/engines/nancy/action/miscrecords.cpp
@@ -74,13 +74,25 @@ void LightningOn::readData(Common::SeekableReadStream &stream) {
}
void SpecialEffect::readData(Common::SeekableReadStream &stream) {
- _type = stream.readByte();
- _fadeToBlackTime = stream.readUint16LE();
- _frameTime = stream.readUint16LE();
+ if (g_nancy->getGameType() <= kGameTypeNancy6) {
+ _type = stream.readByte();
+ _fadeToBlackTime = stream.readUint16LE();
+ _frameTime = stream.readUint16LE();
+ } else {
+ _type = stream.readByte();
+ _totalTime = stream.readUint16LE();
+ _fadeToBlackTime = stream.readUint16LE();
+ readRect(stream, _rect);
+ }
}
void SpecialEffect::execute() {
- NancySceneState.specialEffect(_type, _fadeToBlackTime, _frameTime);
+ if (g_nancy->getGameType() <= kGameTypeNancy6) {
+ NancySceneState.specialEffect(_type, _fadeToBlackTime, _frameTime);
+ } else {
+ NancySceneState.specialEffect(_type, _totalTime, _fadeToBlackTime, _rect);
+ }
+
_isDone = true;
}
diff --git a/engines/nancy/action/miscrecords.h b/engines/nancy/action/miscrecords.h
index 9aa531c7165..393caf91ec3 100644
--- a/engines/nancy/action/miscrecords.h
+++ b/engines/nancy/action/miscrecords.h
@@ -80,6 +80,8 @@ public:
byte _type = 1;
uint16 _fadeToBlackTime = 0;
uint16 _frameTime = 0;
+ uint16 _totalTime = 0;
+ Common::Rect _rect;
protected:
Common::String getRecordTypeName() const override { return "SpecialEffect"; }
diff --git a/engines/nancy/graphics.cpp b/engines/nancy/graphics.cpp
index 49d17b1fde2..7d6a44c0d00 100644
--- a/engines/nancy/graphics.cpp
+++ b/engines/nancy/graphics.cpp
@@ -350,10 +350,10 @@ void GraphicsManager::rotateBlit(const Graphics::ManagedSurface &src, Graphics::
}
}
-void GraphicsManager::crossDissolve(const Graphics::ManagedSurface &from, const Graphics::ManagedSurface &to, byte alpha, Graphics::ManagedSurface &inResult) {
- assert(from.getBounds() == to.getBounds() && to.getBounds() == inResult.getBounds());
- inResult.blitFrom(from, Common::Point());
- inResult.transBlitFrom(to, (uint32)-1, false, 0, alpha);
+void GraphicsManager::crossDissolve(const Graphics::ManagedSurface &from, const Graphics::ManagedSurface &to, byte alpha, const Common::Rect rect, Graphics::ManagedSurface &inResult) {
+ assert(from.getBounds() == to.getBounds());
+ inResult.blitFrom(from, rect, Common::Point());
+ inResult.transBlitFrom(to, rect, Common::Point(), (uint32)-1, false, 0, alpha);
}
void GraphicsManager::debugDrawToScreen(const Graphics::ManagedSurface &surf) {
@@ -385,16 +385,6 @@ void GraphicsManager::grabViewportObjects(Common::Array<RenderObject *> &inArray
}
}
-void GraphicsManager::screenshotViewport(Graphics::ManagedSurface &inSurf) {
- const VIEW *viewportData = (const VIEW *)g_nancy->getEngineData("VIEW");
- assert(viewportData);
-
- draw(false);
- inSurf.free();
- inSurf.create(viewportData->bounds.width(), viewportData->bounds.height(), _screenPixelFormat);
- inSurf.blitFrom(_screen, viewportData->screenPosition, viewportData->bounds);
-}
-
void GraphicsManager::screenshotScreen(Graphics::ManagedSurface &inSurf) {
draw(false);
inSurf.free();
diff --git a/engines/nancy/graphics.h b/engines/nancy/graphics.h
index 660c4b08658..55fc27beb8e 100644
--- a/engines/nancy/graphics.h
+++ b/engines/nancy/graphics.h
@@ -59,7 +59,6 @@ public:
Graphics::ManagedSurface &getAutotextSurface(uint16 id) { return _autotextSurfaces.getOrCreateVal(id); }
void grabViewportObjects(Common::Array<RenderObject *> &inArray);
- void screenshotViewport(Graphics::ManagedSurface &inSurf);
void screenshotScreen(Graphics::ManagedSurface &inSurf);
static void loadSurfacePalette(Graphics::ManagedSurface &inSurf, const Common::String paletteFilename, uint paletteStart = 0, uint paletteSize = 256);
@@ -67,7 +66,7 @@ public:
static void copyToManaged(void *src, Graphics::ManagedSurface &dst, uint srcW, uint srcH, const Graphics::PixelFormat &format, bool verticalFlip = false, bool doubleSize = false);
static void rotateBlit(const Graphics::ManagedSurface &src, Graphics::ManagedSurface &dest, byte rotation);
- static void crossDissolve(const Graphics::ManagedSurface &from, const Graphics::ManagedSurface &to, byte alpha, Graphics::ManagedSurface &inResult);
+ static void crossDissolve(const Graphics::ManagedSurface &from, const Graphics::ManagedSurface &to, byte alpha, const Common::Rect rect, Graphics::ManagedSurface &inResult);
// Debug
void debugDrawToScreen(const Graphics::ManagedSurface &surf);
diff --git a/engines/nancy/misc/specialeffect.cpp b/engines/nancy/misc/specialeffect.cpp
index 96a846d5083..307b3c25f91 100644
--- a/engines/nancy/misc/specialeffect.cpp
+++ b/engines/nancy/misc/specialeffect.cpp
@@ -28,35 +28,88 @@ namespace Nancy {
namespace Misc {
void SpecialEffect::init() {
- _specialEffectData = (const SPEC *)g_nancy->getEngineData("SPEC");
- assert(_specialEffectData);
+ if (g_nancy->getGameType() <= kGameTypeNancy6) {
+ // nancy2-6 have a fixed number of frames for the effect, which is defined in the SPEC chunk
+ const SPEC *specialEffectData = (const SPEC *)g_nancy->getEngineData("SPEC");
+ assert(specialEffectData);
- const VIEW *viewportData = (const VIEW *)g_nancy->getEngineData("VIEW");
- assert(viewportData);
+ _numFrames = _type == kBlackout ? specialEffectData->fadeToBlackNumFrames : specialEffectData->crossDissolveNumFrames;
+ _frameTime = _type == kBlackout ? specialEffectData->fadeToBlackFrameTime : _frameTime;
- _numFrames = _type == kSceneChangeFadeOutToBlack ? _specialEffectData->fadeToBlackNumFrames : _specialEffectData->crossDissolveNumFrames;
- _frameTime = _type == kSceneChangeFadeOutToBlack ? _specialEffectData->fadeToBlackFrameTime : _frameTime;
+ // We use the type definitions in nancy7, which are 1-indexed
+ ++_type;
+ }
+
+ // nancy7 got rid of the SPEC chunk, and the data now contains the total amount of time
+ // that the effect should run for instead.
+ if (_rect.isEmpty()) {
+ if (g_nancy->getGameType() <= kGameTypeNancy6 && _type == kCrossDissolve) {
+ // Earlier games did the whole screen (most easily testable in the nancy3 intro if one moves the scrollbar)
+ _rect = Common::Rect(640, 480);
+ } else {
+ const VIEW *viewportData = (const VIEW *)g_nancy->getEngineData("VIEW");
+ assert(viewportData);
+
+ _rect = viewportData->screenPosition;
+ }
+ }
- _drawSurface.create(viewportData->bounds.width(), viewportData->bounds.height(), g_nancy->_graphicsManager->getScreenPixelFormat());
- moveTo(viewportData->screenPosition);
+ _drawSurface.create(_rect.width(), _rect.height(), g_nancy->_graphicsManager->getScreenPixelFormat());
+ moveTo(_rect);
setTransparent(false);
RenderObject::init();
}
void SpecialEffect::updateGraphics() {
- if (g_nancy->getTotalPlayTime() > _nextFrameTime && _currentFrame < (int)_numFrames && isInitialized()) {
- ++_currentFrame;
- _nextFrameTime += _frameTime;
-
- GraphicsManager::crossDissolve(_fadeFrom, _fadeTo, 255 * _currentFrame / _numFrames, _drawSurface);
- setVisible(true);
+ if (_numFrames) {
+ // Early version with constant number of frames, linear interpolation
+ if (g_nancy->getTotalPlayTime() > _nextFrameTime && _currentFrame < (int)_numFrames && isInitialized()) {
+ ++_currentFrame;
+ _nextFrameTime += _frameTime;
+
+ GraphicsManager::crossDissolve(_fadeFrom, _fadeTo, 255 * _currentFrame / _numFrames, _rect, _drawSurface);
+ setVisible(true);
+ }
+ } else {
+ // nancy7+ version, draws as many frames as possible, ease in/out interpolation
+ if (_startTime == 0) {
+ _startTime = g_nancy->getTotalPlayTime();
+ }
+
+ if (g_nancy->getTotalPlayTime() > _startTime + _totalTime) {
+ if (_currentFrame == 0) {
+ // Ensure at least one dissolve frame is shown
+ ++_currentFrame;
+ GraphicsManager::crossDissolve(_fadeFrom, _fadeTo, 128, _rect, _drawSurface);
+ setVisible(true);
+ }
+ } else {
+ // Use a Bezier curve for all fades. Not entirely accurate to the original engine,
+ // since that pre-calculated the number of frames and did some exponent magic on them
+ float alpha = (float)(g_nancy->getTotalPlayTime() - _startTime) / (float)_totalTime;
+ alpha = alpha * alpha * (3.0 - 2.0 * alpha);
+ alpha *= 255;
+ GraphicsManager::crossDissolve(_fadeFrom, _fadeTo, alpha, _rect, _drawSurface);
+ setVisible(true);
+ ++_currentFrame;
+
+ if (alpha > 255 && _type == kThroughBlack) {
+ _throughBlackStarted2nd = true;
+ void *temp = _fadeFrom.getPixels();
+ _fadeFrom.setPixels(_fadeTo.getPixels());
+ _fadeTo.setPixels(temp);
+ g_nancy->_graphicsManager->screenshotScreen(_fadeTo);
+ _startTime = g_nancy->getTotalPlayTime();
+ _currentFrame = 0;
+ }
+ }
}
}
void SpecialEffect::onSceneChange() {
- g_nancy->_graphicsManager->screenshotViewport(_fadeFrom);
- _drawSurface.rawBlitFrom(_fadeFrom, _fadeFrom.getBounds(), Common::Point());
+ g_nancy->_graphicsManager->screenshotScreen(_fadeFrom);
+ _drawSurface.rawBlitFrom(_fadeFrom, _rect, Common::Point());
}
void SpecialEffect::afterSceneChange() {
@@ -64,10 +117,10 @@ void SpecialEffect::afterSceneChange() {
return;
}
- if (_type == kSceneChangeFadeCrossDissolve) {
- g_nancy->_graphicsManager->screenshotViewport(_fadeTo);
+ if (_type == kCrossDissolve) {
+ g_nancy->_graphicsManager->screenshotScreen(_fadeTo);
} else {
- _fadeTo.create(_drawSurface.w, _drawSurface.h, _drawSurface.format);
+ _fadeTo.create(640, 480, _drawSurface.format);
_fadeTo.clear();
}
@@ -80,15 +133,16 @@ void SpecialEffect::afterSceneChange() {
registerGraphics();
_nextFrameTime = g_nancy->getTotalPlayTime() + _frameTime;
- _fadeToBlackEndTime = g_nancy->getTotalPlayTime() + _fadeToBlackTime;
+ _fadeToBlackEndTime = g_nancy->getTotalPlayTime() + _totalTime + _fadeToBlackTime;
_initialized = true;
}
bool SpecialEffect::isDone() const {
- if (_type == kSceneChangeFadeCrossDissolve) {
- return _currentFrame >= (int)_numFrames;
- } else {
+ if (_type != kCrossDissolve) {
return g_nancy->getTotalPlayTime() > _fadeToBlackEndTime;
+ } else {
+ bool canFinish = _type == kThroughBlack ? _throughBlackStarted2nd : true;
+ return _totalTime ? ((g_nancy->getTotalPlayTime() > _startTime + _totalTime) && (_currentFrame != 0) && canFinish) : (_currentFrame >= (int)_numFrames);
}
}
diff --git a/engines/nancy/misc/specialeffect.h b/engines/nancy/misc/specialeffect.h
index f842325d5f8..75ba696ee60 100644
--- a/engines/nancy/misc/specialeffect.h
+++ b/engines/nancy/misc/specialeffect.h
@@ -33,14 +33,22 @@ namespace Misc {
class SpecialEffect : public RenderObject {
public:
- static const byte kSceneChangeFadeOutToBlack = 1;
- static const byte kSceneChangeFadeCrossDissolve = 1;
+ static const byte kBlackout = 1;
+ static const byte kCrossDissolve = 2;
+ static const byte kThroughBlack = 3;
SpecialEffect(byte type, uint16 fadeToBlackTime, uint16 frameTime) :
RenderObject(16),
_type(type),
_fadeToBlackTime(fadeToBlackTime),
_frameTime(frameTime) {}
+
+ SpecialEffect(byte type, uint32 totalTime, uint16 fadeToBlackTime, Common::Rect rect) :
+ RenderObject(16),
+ _type(type),
+ _totalTime(totalTime),
+ _fadeToBlackTime(fadeToBlackTime),
+ _rect(rect) {}
virtual ~SpecialEffect() {}
void init() override;
@@ -55,20 +63,22 @@ public:
protected:
bool _initialized = false;
- Time _nextFrameTime;
- Time _fadeToBlackEndTime;
+ uint32 _nextFrameTime = 0;
+ uint32 _fadeToBlackEndTime = 0;
Graphics::ManagedSurface _fadeFrom;
Graphics::ManagedSurface _fadeTo;
byte _type = 1;
uint16 _fadeToBlackTime = 0;
- uint16 _frameTime = 0;
+ uint32 _frameTime = 0;
+ uint32 _totalTime = 0;
+ Common::Rect _rect;
int _currentFrame = 0;
uint _numFrames = 0;
-
- const SPEC *_specialEffectData = nullptr;
+ uint32 _startTime = 0;
+ bool _throughBlackStarted2nd = false;
};
} // End of namespace Misc
diff --git a/engines/nancy/state/scene.cpp b/engines/nancy/state/scene.cpp
index 56468f93051..fcbcbb6226d 100644
--- a/engines/nancy/state/scene.cpp
+++ b/engines/nancy/state/scene.cpp
@@ -808,6 +808,11 @@ void Scene::specialEffect(byte type, uint16 fadeToBlackTime, uint16 frameTime) {
_specialEffects.back().init();
}
+void Scene::specialEffect(byte type, uint16 totalTime, uint16 fadeToBlackTime, Common::Rect rect) {
+ _specialEffects.push(Misc::SpecialEffect(type, totalTime, fadeToBlackTime, rect));
+ _specialEffects.back().init();
+}
+
PuzzleData *Scene::getPuzzleData(const uint32 tag) {
// Lazy initialization ensures both init() and synchronize() will not need
// to care about which puzzles a specific game has
@@ -938,18 +943,6 @@ void Scene::run() {
}
Time currentPlayTime = g_nancy->getTotalPlayTime();
-
- if (_specialEffects.size()) {
- if (_specialEffects.front().isInitialized()) {
- if (_specialEffects.front().isDone()) {
- _specialEffects.pop();
- g_nancy->_graphicsManager->redrawAll();
- }
- } else {
- _specialEffects.front().afterSceneChange();
- }
- }
-
Time deltaTime = currentPlayTime - _timers.lastTotalTime;
_timers.lastTotalTime = currentPlayTime;
@@ -981,6 +974,18 @@ void Scene::run() {
_lightning->run();
}
+ // Do this after the first records are processed to fix the text in nancy3 intro
+ if (_specialEffects.size()) {
+ if (_specialEffects.front().isInitialized()) {
+ if (_specialEffects.front().isDone()) {
+ _specialEffects.pop();
+ g_nancy->_graphicsManager->redrawAll();
+ }
+ } else {
+ _specialEffects.front().afterSceneChange();
+ }
+ }
+
g_nancy->_sound->soundEffectMaintenance();
if (_state == kLoad) {
diff --git a/engines/nancy/state/scene.h b/engines/nancy/state/scene.h
index 2f7f1773420..09def5277eb 100644
--- a/engines/nancy/state/scene.h
+++ b/engines/nancy/state/scene.h
@@ -196,6 +196,7 @@ public:
// Used from nancy2 onwards
void specialEffect(byte type, uint16 fadeToBlackTime, uint16 frameTime);
+ void specialEffect(byte type, uint16 totalTime, uint16 fadeToBlackTime, Common::Rect rect);
// Get the persistent data for a given puzzle type
PuzzleData *getPuzzleData(const uint32 tag);
Commit: 3a72322d55023f595da5bef2efcc742d9e9974af
https://github.com/scummvm/scummvm/commit/3a72322d55023f595da5bef2efcc742d9e9974af
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-10-07T14:45:22+03:00
Commit Message:
NANCY: Ensure objects' last position is cleared
Added a safeguard against an issue that could pop up
when moveTo() is called multiple times between draws.
Changed paths:
engines/nancy/action/puzzle/tangrampuzzle.cpp
engines/nancy/graphics.cpp
engines/nancy/renderobject.cpp
engines/nancy/renderobject.h
diff --git a/engines/nancy/action/puzzle/tangrampuzzle.cpp b/engines/nancy/action/puzzle/tangrampuzzle.cpp
index 9e89a2ddcf9..8418930f28d 100644
--- a/engines/nancy/action/puzzle/tangrampuzzle.cpp
+++ b/engines/nancy/action/puzzle/tangrampuzzle.cpp
@@ -371,10 +371,7 @@ void TangramPuzzle::rotateTile(uint id) {
Common::Rect newPos = tileToRotate._drawSurface.getBounds();
newPos.moveTo(oldPos.left + oldPos.width() / 2 - newPos.width() / 2, oldPos.top + oldPos.height() / 2 - newPos.height() / 2);
-
- // Do NOT use moveTo()!
- // If moved and rotated in the same frame, we need to make sure the last position isn't overwritten
- tileToRotate._screenPosition = newPos;
+ tileToRotate.moveTo(newPos);
_needsRedraw = true;
diff --git a/engines/nancy/graphics.cpp b/engines/nancy/graphics.cpp
index 7d6a44c0d00..33299a847c9 100644
--- a/engines/nancy/graphics.cpp
+++ b/engines/nancy/graphics.cpp
@@ -94,6 +94,7 @@ void GraphicsManager::draw(bool updateScreen) {
}
current._needsRedraw = false;
+ current._hasMoved = false;
current._previousScreenPosition = current._screenPosition;
}
diff --git a/engines/nancy/renderobject.cpp b/engines/nancy/renderobject.cpp
index bb9b2ce48f4..d960c6a6787 100644
--- a/engines/nancy/renderobject.cpp
+++ b/engines/nancy/renderobject.cpp
@@ -30,7 +30,8 @@ namespace Nancy {
RenderObject::RenderObject(uint16 zOrder) :
_needsRedraw(true),
_isVisible(true),
- _z(zOrder) {}
+ _z(zOrder),
+ _hasMoved(false) {}
RenderObject::RenderObject(uint16 zOrder, Graphics::ManagedSurface &surface, const Common::Rect &srcBounds, const Common::Rect &destBounds) :
RenderObject(zOrder) {
@@ -54,15 +55,25 @@ RenderObject::~RenderObject() {
}
void RenderObject::moveTo(const Common::Point &position) {
- _previousScreenPosition = _screenPosition;
+ // Make sure we don't overwrite the _actual_ last position
+ if (!_hasMoved) {
+ _previousScreenPosition = _screenPosition;
+ }
+
_screenPosition.moveTo(position);
_needsRedraw = true;
+ _hasMoved = true;
}
void RenderObject::moveTo(const Common::Rect &bounds) {
- _previousScreenPosition = _screenPosition;
+ // Make sure we don't overwrite the _actual_ last position
+ if (!_hasMoved) {
+ _previousScreenPosition = _screenPosition;
+ }
+
_screenPosition = bounds;
_needsRedraw = true;
+ _hasMoved = true;
}
void RenderObject::setVisible(bool visible) {
diff --git a/engines/nancy/renderobject.h b/engines/nancy/renderobject.h
index 80caea45269..f03a24b8d92 100644
--- a/engines/nancy/renderobject.h
+++ b/engines/nancy/renderobject.h
@@ -78,6 +78,7 @@ protected:
bool _needsRedraw;
bool _isVisible;
+ bool _hasMoved;
uint16 _z;
Common::Rect _previousScreenPosition;
Common::Rect _screenPosition;
Commit: 4aa0727f953775e82d788fc800c35ba059db5184
https://github.com/scummvm/scummvm/commit/4aa0727f953775e82d788fc800c35ba059db5184
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-10-07T14:45:22+03:00
Commit Message:
NANCY: Reset textbox scrollbar when adding new line
Changed paths:
engines/nancy/ui/textbox.cpp
diff --git a/engines/nancy/ui/textbox.cpp b/engines/nancy/ui/textbox.cpp
index 13b3f93948e..e60f2232e35 100644
--- a/engines/nancy/ui/textbox.cpp
+++ b/engines/nancy/ui/textbox.cpp
@@ -173,6 +173,9 @@ void Textbox::addTextLine(const Common::String &text, uint32 autoClearTime) {
// Currently only used by inventory closed captions
_autoClearTime = g_nancy->getTotalPlayTime() + autoClearTime;
}
+
+ _scrollbar->resetPosition();
+ onScrollbarMove();
}
void Textbox::setOverrideFont(const uint fontID) {
@@ -206,6 +209,9 @@ void Textbox::onScrollbarMove() {
}
uint16 Textbox::getInnerHeight() const {
+ // As early as nancy3 this behavior stopped being relevant, as the original
+ // engine always scrolls down to the bottom of the entire inner surface.
+ // However, that makes the scrollbar almost unusable, so I'm not changing this.
const TBOX *tbox = (const TBOX *)g_nancy->getEngineData("TBOX");
assert(tbox);
Commit: 151561e6eed0928cf7381e5bb68a150297cff727
https://github.com/scummvm/scummvm/commit/151561e6eed0928cf7381e5bb68a150297cff727
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-10-07T14:45:22+03:00
Commit Message:
NANCY: Implement KeypadTersePuzzle
Implemented the short variant of KeypadPuzzle introduced
in nancy7. Also added the new flag that marks whether
we need to check the order the buttons were pressed in.
Changed paths:
engines/nancy/action/arfactory.cpp
engines/nancy/action/puzzle/orderingpuzzle.cpp
engines/nancy/action/puzzle/orderingpuzzle.h
diff --git a/engines/nancy/action/arfactory.cpp b/engines/nancy/action/arfactory.cpp
index 7f6e68540e3..a56b3d2358c 100644
--- a/engines/nancy/action/arfactory.cpp
+++ b/engines/nancy/action/arfactory.cpp
@@ -311,6 +311,8 @@ ActionRecord *ActionManager::createActionRecord(uint16 type, Common::SeekableRea
return new MouseLightPuzzle();
case 220:
return new TwoDialPuzzle();
+ case 224:
+ return new OrderingPuzzle(OrderingPuzzle::kKeypadTerse);
default:
return nullptr;
}
diff --git a/engines/nancy/action/puzzle/orderingpuzzle.cpp b/engines/nancy/action/puzzle/orderingpuzzle.cpp
index cdf4a35360f..19570ce6103 100644
--- a/engines/nancy/action/puzzle/orderingpuzzle.cpp
+++ b/engines/nancy/action/puzzle/orderingpuzzle.cpp
@@ -71,6 +71,7 @@ void OrderingPuzzle::init() {
void OrderingPuzzle::readData(Common::SeekableReadStream &stream) {
bool isPiano = _puzzleType == kPiano;
bool isOrderItems = _puzzleType == kOrderItems;
+ bool isKeypad = _puzzleType == kKeypad || _puzzleType == kKeypadTerse;
readFilename(stream, _imageName);
Common::Serializer ser(&stream, nullptr);
ser.setVersion(g_nancy->getGameType());
@@ -92,6 +93,8 @@ void OrderingPuzzle::readData(Common::SeekableReadStream &stream) {
case kPiano :
_itemsStayDown = false;
break;
+ case kKeypadTerse:
+ // fall through
case kKeypad :
ser.syncAsByte(_itemsStayDown);
ser.syncAsByte(_needButtonToCheckSuccess);
@@ -103,43 +106,56 @@ void OrderingPuzzle::readData(Common::SeekableReadStream &stream) {
break;
}
- readRectArray(ser, _down1Rects, numElements, maxNumElements);
-
- if (isOrderItems) {
- readRectArray(stream, _up2Rects, numElements, maxNumElements);
- readRectArray(stream, _down2Rects, numElements, maxNumElements);
- }
+ // nancy7 moved the keypad rects at the end
+ if (g_nancy->getGameType() <= kGameTypeNancy6 || !isKeypad) {
+ readRectArray(ser, _down1Rects, numElements, maxNumElements);
+
+ if (isOrderItems) {
+ readRectArray(stream, _up2Rects, numElements, maxNumElements);
+ readRectArray(stream, _down2Rects, numElements, maxNumElements);
+ }
- readRectArray(ser, _destRects, numElements, maxNumElements);
+ readRectArray(ser, _destRects, numElements, maxNumElements);
- if (isPiano) {
- readRectArray(stream, _hotspots, numElements, maxNumElements);
- } else {
- _hotspots = _destRects;
+ if (isPiano) {
+ readRectArray(stream, _hotspots, numElements, maxNumElements);
+ } else {
+ _hotspots = _destRects;
+ }
}
uint sequenceLength = 5;
ser.syncAsUint16LE(sequenceLength, kGameTypeNancy1);
+ if (isKeypad) {
+ ser.syncAsByte(_checkOrder, kGameTypeNancy7);
+ }
+
_correctSequence.resize(sequenceLength);
+ uint sizeElem;
for (uint i = 0; i < sequenceLength; ++i) {
switch (_puzzleType) {
+ case kKeypadTerse:
+ // fall through
case kKeypad :
// fall through
case kOrdering:
ser.syncAsByte(_correctSequence[i]);
+ sizeElem = 1;
break;
case kPiano:
ser.syncAsUint16LE(_correctSequence[i]);
+ sizeElem = 2;
break;
case kOrderItems:
// For some reason, OrderItems labels starting from 1
ser.syncAsUint16LE(_correctSequence[i]);
--_correctSequence[i];
+ sizeElem = 2;
break;
}
}
- ser.skip((maxNumElements - sequenceLength) * ((_puzzleType == kOrdering || _puzzleType == kKeypad) ? 1 : 2), kGameTypeNancy1);
+ ser.skip((maxNumElements - sequenceLength) * sizeElem, kGameTypeNancy1);
if (isOrderItems) {
uint numOverlays = 0;
@@ -165,6 +181,53 @@ void OrderingPuzzle::readData(Common::SeekableReadStream &stream) {
_exitScene.readData(stream, ser.getVersion() == kGameTypeVampire);
readRect(stream, _exitHotspot);
+ if (isKeypad && g_nancy->getGameType() >= kGameTypeNancy7) {
+ if (_puzzleType == kKeypad) {
+ readRectArray(ser, _down1Rects, numElements, maxNumElements);
+ readRectArray(ser, _destRects, numElements, maxNumElements);
+ } else if (_puzzleType == kKeypadTerse) {
+ _down1Rects.resize(numElements);
+ _destRects.resize(numElements);
+
+ // Terse elements are the same size & placed on a grid (in the source image AND on screen)
+ uint16 columns = stream.readUint16LE();
+ stream.skip(2); // rows
+
+ uint16 width = stream.readUint16LE();
+ uint16 height = stream.readUint16LE();
+
+ Common::Point srcStartPos, srcDist, destStartPos, destDist;
+
+ srcStartPos.x = stream.readUint16LE();
+ srcStartPos.y = stream.readUint16LE();
+ srcDist.x = stream.readUint16LE();
+ srcDist.y = stream.readUint16LE();
+
+ destStartPos.x = stream.readUint16LE();
+ destStartPos.y = stream.readUint16LE();
+ destDist.x = stream.readUint16LE();
+ destDist.y = stream.readUint16LE();
+
+ for (uint i = 0; i < numElements; ++i) {
+ uint x = i % columns;
+ uint y = i / columns;
+ Common::Rect &src = _down1Rects[i];
+ src.left = srcStartPos.x + (x * srcDist.x) + (width * x);
+ src.top = srcStartPos.y + (y * srcDist.y) + (height * y);
+ src.setWidth(width + 1);
+ src.setHeight(height + 1);
+
+ Common::Rect &dest = _destRects[i];
+ dest.left = destStartPos.x + (x * destDist.x) + (width * x);
+ dest.top = destStartPos.y + (y * destDist.y) + (height * y);
+ dest.setWidth(width + 1);
+ dest.setHeight(height + 1);
+ }
+ }
+
+ _hotspots = _destRects;
+ }
+
_downItems.resize(numElements, false);
_secondStateItems.resize(numElements, false);
}
@@ -206,13 +269,34 @@ void OrderingPuzzle::execute() {
if (_puzzleType != kPiano) {
if (_clickedSequence.size() >= _correctSequence.size()) {
+ bool equal = true;
+ if (_checkOrder) {
+ equal = (_clickedSequence == _correctSequence);
+ } else {
+ for (uint i = 0; i < _correctSequence.size(); ++i) {
+ bool found = false;
+ for (uint j = 0; j < _clickedSequence.size(); ++j) {
+ if (_correctSequence[i] == _clickedSequence[j]) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ // Couldn't find one of the items in the correct sequence
+ equal = false;
+ break;
+ }
+ }
+ }
+
// Check the pressed sequence. If its length is above a certain number,
// clear it and start anew
- if (_clickedSequence != _correctSequence) {
+ if (!equal) {
if (_puzzleType != kOrderItems) {
uint maxNumPressed = 4;
if (g_nancy->getGameType() > kGameTypeVampire) {
- if (_puzzleType == kKeypad) {
+ if (_puzzleType == kKeypad || _puzzleType == kKeypadTerse) {
maxNumPressed = _correctSequence.size();
} else {
maxNumPressed = _correctSequence.size() + 1;
@@ -401,7 +485,7 @@ void OrderingPuzzle::handleInput(NancyInput &input) {
g_nancy->_sound->loadSound(_pushDownSound);
}
- if (_puzzleType == kOrdering || _puzzleType == kKeypad) {
+ if (_puzzleType == kOrdering || _puzzleType == kKeypad || _puzzleType == kKeypadTerse) {
// OrderingPuzzle and KeypadPuzzle allow for depressing buttons after they're pressed.
// If the button is the last one the player pressed, it is removed from the order.
// If not, the sequence is kept wrong and will be reset after enough buttons are pressed
@@ -434,6 +518,8 @@ Common::String OrderingPuzzle::getRecordTypeName() const {
return "OrderItemsPuzzle";
case kKeypad:
return "KeypadPuzzle";
+ case kKeypadTerse:
+ return "KeypadTersePuzzle";
default:
return "OrderingPuzzle";
}
diff --git a/engines/nancy/action/puzzle/orderingpuzzle.h b/engines/nancy/action/puzzle/orderingpuzzle.h
index ec125673b80..41eb58b1d2a 100644
--- a/engines/nancy/action/puzzle/orderingpuzzle.h
+++ b/engines/nancy/action/puzzle/orderingpuzzle.h
@@ -34,11 +34,12 @@ namespace Action {
// - OrderItemsPuzzle: Buttons may depress or stay down, but player can't depress manually.
// Has second button state that is activated when player is holding a specific item. (see fingerprint keypad puzzle in nancy4)
// - KeypadPuzzle: Buttons may auto-depress, stay down, and can be depressed manually by player.
-// Adds an optional button for manually checking for correct solution, and doubles the number of possible buttons.
+// Adds an optional button for manually checking for correct solution, number of possible buttons is 30.
+// - KeypadPuzzleTerse: Same as above, but data format is shorter, and supports up to 100 buttons
class OrderingPuzzle : public RenderActionRecord {
public:
enum SolveState { kNotSolved, kPlaySound, kWaitForSound };
- enum PuzzleType { kOrdering, kPiano, kOrderItems, kKeypad };
+ enum PuzzleType { kOrdering, kPiano, kOrderItems, kKeypad, kKeypadTerse };
OrderingPuzzle(PuzzleType type) : RenderActionRecord(7), _puzzleType(type) {}
virtual ~OrderingPuzzle() {}
@@ -61,6 +62,7 @@ protected:
bool _hasSecondState = false;
bool _itemsStayDown = true;
bool _needButtonToCheckSuccess = false;
+ bool _checkOrder = true;
Common::Rect _checkButtonSrc;
Common::Rect _checkButtonDest;
Common::Array<Common::Rect> _down1Rects;
Commit: 90beb8887ec9f593e1a9ef6a67461d95d4b9ae4b
https://github.com/scummvm/scummvm/commit/90beb8887ec9f593e1a9ef6a67461d95d4b9ae4b
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-10-07T14:45:22+03:00
Commit Message:
AGS: Fix clipping issue in blitting code
Fixed a clipping issue that would crash ScummVM when
attempting to blit to a surface with a negative dest x
(affecting both the optimized and non-optimized paths).
Changed paths:
engines/ags/lib/allegro/surface_avx2.cpp
engines/ags/lib/allegro/surface_generic.cpp
engines/ags/lib/allegro/surface_neon.cpp
engines/ags/lib/allegro/surface_sse2.cpp
diff --git a/engines/ags/lib/allegro/surface_avx2.cpp b/engines/ags/lib/allegro/surface_avx2.cpp
index a3b78394a38..6b99149fd97 100644
--- a/engines/ags/lib/allegro/surface_avx2.cpp
+++ b/engines/ags/lib/allegro/surface_avx2.cpp
@@ -479,14 +479,14 @@ static void drawInner4BppWithConv(BITMAP::DrawInnerArgs &args) {
// Clip the bounds ahead of time (so we don't waste time checking if we are in bounds when
// we are in the inner loop)
int xCtrStart = 0, xCtrBppStart = 0, xCtrWidth = args.dstRect.width();
- if (args.xStart + xCtrWidth > args.destArea.w) {
- xCtrWidth = args.destArea.w - args.xStart;
- }
if (args.xStart < 0) {
xCtrStart = -args.xStart;
xCtrBppStart = xCtrStart * SrcBytesPerPixel;
args.xStart = 0;
}
+ if (args.xStart + xCtrWidth > args.destArea.w) {
+ xCtrWidth = args.destArea.w - args.xStart;
+ }
int destY = args.yStart, srcYCtr = 0, yCtr = 0, scaleYCtr = 0, yCtrHeight = (xCtrWidth % 4 == 0) ? args.dstRect.height() : (args.dstRect.height() - 1);
if (Scale) yCtrHeight = args.dstRect.height();
if (args.yStart < 0) {
diff --git a/engines/ags/lib/allegro/surface_generic.cpp b/engines/ags/lib/allegro/surface_generic.cpp
index e032dcd3109..1dc753cfed9 100644
--- a/engines/ags/lib/allegro/surface_generic.cpp
+++ b/engines/ags/lib/allegro/surface_generic.cpp
@@ -34,14 +34,14 @@ void BITMAP::drawInnerGeneric(DrawInnerArgs &args) {
// Instead of skipping pixels outside our boundary here, we just clip
// our area instead.
int xCtrStart = 0, xCtrBppStart = 0, xCtrWidth = args.dstRect.width();
- if (args.xStart + xCtrWidth > args.destArea.w) { // Clip the right
- xCtrWidth = args.destArea.w - args.xStart;
- }
if (args.xStart < 0) { // Clip the left
xCtrStart = -args.xStart;
xCtrBppStart = xCtrStart * SrcBytesPerPixel;
args.xStart = 0;
}
+ if (args.xStart + xCtrWidth > args.destArea.w) { // Clip the right
+ xCtrWidth = args.destArea.w - args.xStart;
+ }
int destY = args.yStart, yCtr = 0, srcYCtr = 0, scaleYCtr = 0, yCtrHeight = args.dstRect.height();
if (args.yStart < 0) { // Clip the top
yCtr = -args.yStart;
diff --git a/engines/ags/lib/allegro/surface_neon.cpp b/engines/ags/lib/allegro/surface_neon.cpp
index 5fa0f273fad..9d70695c63d 100644
--- a/engines/ags/lib/allegro/surface_neon.cpp
+++ b/engines/ags/lib/allegro/surface_neon.cpp
@@ -476,14 +476,14 @@ static void drawInner4BppWithConv(BITMAP::DrawInnerArgs &args) {
// Clip the bounds ahead of time (so we don't waste time checking if we are in bounds when
// we are in the inner loop)
int xCtrStart = 0, xCtrBppStart = 0, xCtrWidth = args.dstRect.width();
- if (args.xStart + xCtrWidth > args.destArea.w) {
- xCtrWidth = args.destArea.w - args.xStart;
- }
if (args.xStart < 0) {
xCtrStart = -args.xStart;
xCtrBppStart = xCtrStart * SrcBytesPerPixel;
args.xStart = 0;
}
+ if (args.xStart + xCtrWidth > args.destArea.w) {
+ xCtrWidth = args.destArea.w - args.xStart;
+ }
int destY = args.yStart, srcYCtr = 0, yCtr = 0, scaleYCtr = 0, yCtrHeight = (xCtrWidth % 4 == 0) ? args.dstRect.height() : (args.dstRect.height() - 1);
if (Scale) yCtrHeight = args.dstRect.height();
if (args.yStart < 0) {
diff --git a/engines/ags/lib/allegro/surface_sse2.cpp b/engines/ags/lib/allegro/surface_sse2.cpp
index 80b161ddaa3..ce7f4275604 100644
--- a/engines/ags/lib/allegro/surface_sse2.cpp
+++ b/engines/ags/lib/allegro/surface_sse2.cpp
@@ -496,14 +496,14 @@ static void drawInner4BppWithConv(BITMAP::DrawInnerArgs &args) {
// Clip the bounds ahead of time (so we don't waste time checking if we are in bounds when
// we are in the inner loop)
int xCtrStart = 0, xCtrBppStart = 0, xCtrWidth = args.dstRect.width();
- if (args.xStart + xCtrWidth > args.destArea.w) {
- xCtrWidth = args.destArea.w - args.xStart;
- }
if (args.xStart < 0) {
xCtrStart = -args.xStart;
xCtrBppStart = xCtrStart * SrcBytesPerPixel;
args.xStart = 0;
}
+ if (args.xStart + xCtrWidth > args.destArea.w) {
+ xCtrWidth = args.destArea.w - args.xStart;
+ }
int destY = args.yStart, srcYCtr = 0, yCtr = 0, scaleYCtr = 0, yCtrHeight = (xCtrWidth % 4 == 0) ? args.dstRect.height() : (args.dstRect.height() - 1);
if (Scale) yCtrHeight = args.dstRect.height();
if (args.yStart < 0) {
Commit: e2720d39f327bd152a472cf41b1272fb0e866d16
https://github.com/scummvm/scummvm/commit/e2720d39f327bd152a472cf41b1272fb0e866d16
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-10-07T14:45:31+03:00
Commit Message:
AGS: Fix blitting-related crash
Fixed an off-by-one error in the non-scaling blit code, which
would result in an attempt to draw to one row beyond the
bounds of the dest surface (affects only the optimized paths).
Changed paths:
engines/ags/lib/allegro/surface_avx2.cpp
engines/ags/lib/allegro/surface_neon.cpp
engines/ags/lib/allegro/surface_sse2.cpp
diff --git a/engines/ags/lib/allegro/surface_avx2.cpp b/engines/ags/lib/allegro/surface_avx2.cpp
index 6b99149fd97..e0d031bda0c 100644
--- a/engines/ags/lib/allegro/surface_avx2.cpp
+++ b/engines/ags/lib/allegro/surface_avx2.cpp
@@ -505,7 +505,9 @@ static void drawInner4BppWithConv(BITMAP::DrawInnerArgs &args) {
const byte *srcP = (const byte *)args.src.getBasePtr(
args.horizFlip ? args.srcArea.right - 8 : args.srcArea.left,
args.vertFlip ? args.srcArea.bottom - 1 - yCtr : args.srcArea.top + yCtr);
- for (; yCtr < yCtrHeight; ++destY, ++yCtr, scaleYCtr += args.scaleY) {
+
+ // When not scaling, last row is handled separately
+ for (; Scale ? (yCtr < yCtrHeight) : (yCtr < yCtrHeight - 1); ++destY, ++yCtr, scaleYCtr += args.scaleY) {
__m256i xCtrWidthSIMD = _mm256_set1_epi32(xCtrWidth); // This is the width of the row
if (!Scale) {
diff --git a/engines/ags/lib/allegro/surface_neon.cpp b/engines/ags/lib/allegro/surface_neon.cpp
index 9d70695c63d..21e4712d7a4 100644
--- a/engines/ags/lib/allegro/surface_neon.cpp
+++ b/engines/ags/lib/allegro/surface_neon.cpp
@@ -502,7 +502,9 @@ static void drawInner4BppWithConv(BITMAP::DrawInnerArgs &args) {
const byte *srcP = (const byte *)args.src.getBasePtr(
args.horizFlip ? args.srcArea.right - 4 : args.srcArea.left,
args.vertFlip ? args.srcArea.bottom - 1 - yCtr : args.srcArea.top + yCtr);
- for (; yCtr < yCtrHeight; ++destY, ++yCtr, scaleYCtr += args.scaleY) {
+
+ // When not scaling, last row is handled separately
+ for (; Scale ? (yCtr < yCtrHeight) : (yCtr < yCtrHeight - 1); ++destY, ++yCtr, scaleYCtr += args.scaleY) {
uint32x4_t xCtrWidthSIMD = vdupq_n_u32(xCtrWidth); // This is the width of the row
if (!Scale) {
diff --git a/engines/ags/lib/allegro/surface_sse2.cpp b/engines/ags/lib/allegro/surface_sse2.cpp
index ce7f4275604..a2b9ceafb1b 100644
--- a/engines/ags/lib/allegro/surface_sse2.cpp
+++ b/engines/ags/lib/allegro/surface_sse2.cpp
@@ -522,7 +522,9 @@ static void drawInner4BppWithConv(BITMAP::DrawInnerArgs &args) {
const byte *srcP = (const byte *)args.src.getBasePtr(
args.horizFlip ? args.srcArea.right - 4 : args.srcArea.left,
args.vertFlip ? args.srcArea.bottom - 1 - yCtr : args.srcArea.top + yCtr);
- for (; yCtr < yCtrHeight; ++destY, ++yCtr, scaleYCtr += args.scaleY) {
+
+ // When not scaling, last row is handled separately
+ for (; Scale ? (yCtr < yCtrHeight) : (yCtr < yCtrHeight - 1); ++destY, ++yCtr, scaleYCtr += args.scaleY) {
__m128i xCtrWidthSIMD = _mm_set1_epi32(xCtrWidth); // This is the width of the row
if (!Scale) {
More information about the Scummvm-git-logs
mailing list