aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--engines/sci/engine/kernel.h5
-rw-r--r--engines/sci/engine/kernel_tables.h11
-rw-r--r--engines/sci/engine/kscripts.cpp27
-rw-r--r--engines/sci/engine/kvideo.cpp71
-rw-r--r--engines/sci/graphics/video32.cpp258
-rw-r--r--engines/sci/graphics/video32.h105
-rw-r--r--engines/sci/sound/audio32.h12
7 files changed, 424 insertions, 65 deletions
diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h
index 51f4b5dbcb..ebd7d33655 100644
--- a/engines/sci/engine/kernel.h
+++ b/engines/sci/engine/kernel.h
@@ -657,6 +657,11 @@ reg_t kDoSoundPhantasmagoriaMac(EngineState *s, int argc, reg_t *argv);
// SCI3 Kernel functions
reg_t kPlayDuck(EngineState *s, int argc, reg_t *argv);
+reg_t kPlayDuckPlay(EngineState *s, int argc, reg_t *argv);
+reg_t kPlayDuckSetFrameOut(EngineState *s, int argc, reg_t *argv);
+reg_t kPlayDuckOpen(EngineState *s, int argc, reg_t *argv);
+reg_t kPlayDuckClose(EngineState *s, int argc, reg_t *argv);
+reg_t kPlayDuckSetVolume(EngineState *s, int argc, reg_t *argv);
#endif
reg_t kDoSoundInit(EngineState *s, int argc, reg_t *argv);
diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h
index 211d96bc2f..95f3197896 100644
--- a/engines/sci/engine/kernel_tables.h
+++ b/engines/sci/engine/kernel_tables.h
@@ -495,6 +495,15 @@ static const SciKernelMapSubEntry kRobot_subops[] = {
};
// version, subId, function-mapping, signature, workarounds
+static const SciKernelMapSubEntry kPlayDuck_subops[] = {
+ { SIG_SCI3, 1, MAP_CALL(PlayDuckPlay), "iiiii", NULL },
+ { SIG_SCI3, 2, MAP_CALL(PlayDuckSetFrameOut), "i", NULL },
+ { SIG_SCI3, 5, MAP_CALL(PlayDuckClose), "", NULL },
+ { SIG_SCI3, 6, MAP_CALL(PlayDuckSetVolume), "i", NULL },
+ SCI_SUBOPENTRY_TERMINATOR
+};
+
+// version, subId, function-mapping, signature, workarounds
static const SciKernelMapSubEntry kRemapColors_subops[] = {
{ SIG_SCI32, 0, MAP_CALL(RemapColorsOff), "(i)", NULL },
{ SIG_SCI32, 1, MAP_CALL(RemapColorsByRange), "iiii(i)", NULL },
@@ -1008,7 +1017,7 @@ static SciKernelMapEntry s_kernelMap[] = {
{ MAP_CALL(MorphOn), SIG_EVERYWHERE, "", NULL, NULL },
// SCI3 Kernel Functions
- { MAP_CALL(PlayDuck), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_CALL(PlayDuck), SIG_SCI3, SIGFOR_ALL, "(.*)", kPlayDuck_subops,NULL },
#endif
{ NULL, NULL, SIG_EVERYWHERE, NULL, NULL, NULL }
diff --git a/engines/sci/engine/kscripts.cpp b/engines/sci/engine/kscripts.cpp
index 77ef92b349..7c4c955824 100644
--- a/engines/sci/engine/kscripts.cpp
+++ b/engines/sci/engine/kscripts.cpp
@@ -119,11 +119,28 @@ reg_t kResCheck(EngineState *s, int argc, reg_t *argv) {
}
#ifdef ENABLE_SCI32
- // GK2 stores some VMDs inside of resource volumes, but usually they are
- // streamed from the filesystem
- if (res == nullptr && restype == kResourceTypeVMD) {
- const Common::String fileName = Common::String::format("%u.vmd", argv[1].toUint16());
- return make_reg(0, Common::File::exists(fileName));
+ // GK2 stores some VMDs inside of resource volumes, but usually videos are
+ // streamed from the filesystem.
+ if (res == nullptr) {
+ const char *format = nullptr;
+ switch (restype) {
+ case kResourceTypeRobot:
+ format = "%u.rbt";
+ break;
+ case kResourceTypeDuck:
+ format = "%u.duk";
+ break;
+ case kResourceTypeVMD:
+ format = "%u.vmd";
+ break;
+ default:
+ format = nullptr;
+ }
+
+ if (format) {
+ const Common::String fileName = Common::String::format(format, argv[1].toUint16());
+ return make_reg(0, Common::File::exists(fileName));
+ }
}
#endif
diff --git a/engines/sci/engine/kvideo.cpp b/engines/sci/engine/kvideo.cpp
index 3d689f2b42..e4f2ff7a6d 100644
--- a/engines/sci/engine/kvideo.cpp
+++ b/engines/sci/engine/kvideo.cpp
@@ -472,52 +472,41 @@ reg_t kPlayVMDRestrictPalette(EngineState *s, int argc, reg_t *argv) {
}
reg_t kPlayDuck(EngineState *s, int argc, reg_t *argv) {
- uint16 operation = argv[0].toUint16();
- Video::VideoDecoder *videoDecoder = 0;
- bool reshowCursor = g_sci->_gfxCursor->isVisible();
-
- switch (operation) {
- case 1: // Play
- // 6 params
- s->_videoState.reset();
- s->_videoState.fileName = Common::String::format("%d.duk", argv[1].toUint16());
-
- videoDecoder = new Video::AVIDecoder();
-
- if (!videoDecoder->loadFile(s->_videoState.fileName)) {
- warning("Could not open Duck %s", s->_videoState.fileName.c_str());
- break;
- }
-
- if (reshowCursor)
- g_sci->_gfxCursor->kernelHide();
-
- {
- // Duck videos are 16bpp, so we need to change the active pixel format
- int oldWidth = g_system->getWidth();
- int oldHeight = g_system->getHeight();
- Common::List<Graphics::PixelFormat> formats;
- formats.push_back(videoDecoder->getPixelFormat());
- initGraphics(640, 480, true, formats);
+ if (!s)
+ return make_reg(0, getSciVersion());
+ error("not supposed to call this");
+}
- if (g_system->getScreenFormat().bytesPerPixel != videoDecoder->getPixelFormat().bytesPerPixel)
- error("Could not switch screen format for the duck video");
+reg_t kPlayDuckPlay(EngineState *s, int argc, reg_t *argv) {
+ kPlayDuckOpen(s, argc, argv);
+ g_sci->_video32->getDuckPlayer().play(-1);
+ g_sci->_video32->getDuckPlayer().close();
+ return NULL_REG;
+}
- playVideo(videoDecoder, s->_videoState);
+reg_t kPlayDuckSetFrameOut(EngineState *s, int argc, reg_t *argv) {
+ g_sci->_video32->getDuckPlayer().setDoFrameOut((bool)argv[0].toUint16());
+ return NULL_REG;
+}
- // Switch back to 8bpp
- initGraphics(oldWidth, oldHeight, oldWidth > 320);
- }
+reg_t kPlayDuckOpen(EngineState *s, int argc, reg_t *argv) {
+ const GuiResourceId resourceId = argv[0].toUint16();
+ const int displayMode = argv[1].toSint16();
+ const int16 x = argv[2].toSint16();
+ const int16 y = argv[3].toSint16();
+ // argv[4] is a cache size argument that we do not use
+ g_sci->_video32->getDuckPlayer().open(resourceId, displayMode, x, y);
+ return NULL_REG;
+}
- if (reshowCursor)
- g_sci->_gfxCursor->kernelShow();
- break;
- default:
- kStub(s, argc, argv);
- break;
- }
+reg_t kPlayDuckClose(EngineState *s, int argc, reg_t *argv) {
+ g_sci->_video32->getDuckPlayer().close();
+ return NULL_REG;
+}
- return s->r_acc;
+reg_t kPlayDuckSetVolume(EngineState *s, int argc, reg_t *argv) {
+ g_sci->_video32->getDuckPlayer().setVolume(argv[0].toUint16());
+ return NULL_REG;
}
#endif
diff --git a/engines/sci/graphics/video32.cpp b/engines/sci/graphics/video32.cpp
index f4eb2c2da0..8cd6fc7089 100644
--- a/engines/sci/graphics/video32.cpp
+++ b/engines/sci/graphics/video32.cpp
@@ -51,6 +51,25 @@ namespace Graphics { struct Surface; }
namespace Sci {
+/**
+ * @returns true if the player should quit
+ */
+static bool flushEvents(EventManager *eventMan) {
+ // Flushing all the keyboard and mouse events out of the event manager
+ // keeps events queued from before the start of playback from accidentally
+ // activating a video stop flag
+ for (;;) {
+ const SciEvent event = eventMan->getSciEvent(SCI_EVENT_KEYBOARD | SCI_EVENT_MOUSE_PRESS | SCI_EVENT_MOUSE_RELEASE | SCI_EVENT_HOT_RECTANGLE | SCI_EVENT_QUIT);
+ if (event.type == SCI_EVENT_NONE) {
+ break;
+ } else if (event.type == SCI_EVENT_QUIT) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
#pragma mark SEQPlayer
SEQPlayer::SEQPlayer(SegManager *segMan) :
@@ -672,16 +691,8 @@ VMDPlayer::EventFlags VMDPlayer::kernelPlayUntilEvent(const EventFlags flags, co
}
VMDPlayer::EventFlags VMDPlayer::playUntilEvent(const EventFlags flags) {
- // Flushing all the keyboard and mouse events out of the event manager
- // keeps events queued from before the start of playback from accidentally
- // activating a video stop flag
- for (;;) {
- const SciEvent event = _eventMan->getSciEvent(SCI_EVENT_KEYBOARD | SCI_EVENT_MOUSE_PRESS | SCI_EVENT_MOUSE_RELEASE | SCI_EVENT_HOT_RECTANGLE | SCI_EVENT_QUIT);
- if (event.type == SCI_EVENT_NONE) {
- break;
- } else if (event.type == SCI_EVENT_QUIT) {
- return kEventFlagEnd;
- }
+ if (flushEvents(_eventMan)) {
+ return kEventFlagEnd;
}
if (flags & kEventFlagReverse) {
@@ -914,4 +925,231 @@ void VMDPlayer::restrictPalette(const uint8 startColor, const int16 endColor) {
_endColor = MIN<int16>(255, endColor);
}
+#pragma mark -
+#pragma mark DuckPlayer
+
+DuckPlayer::DuckPlayer(SegManager *segMan, EventManager *eventMan) :
+ _segMan(segMan),
+ _eventMan(eventMan),
+ _decoder(new Video::AVIDecoder(Audio::Mixer::kSFXSoundType)),
+ _plane(nullptr),
+ _status(kDuckClosed),
+ _drawRect(),
+ _volume(Audio::Mixer::kMaxChannelVolume),
+ _doFrameOut(false),
+ _pixelDouble(false),
+ _scaleBuffer(nullptr) {}
+
+DuckPlayer::~DuckPlayer() {
+ close();
+ delete _decoder;
+}
+
+void DuckPlayer::open(const GuiResourceId resourceId, const int displayMode, const int16 x, const int16 y) {
+ if (_status != kDuckClosed) {
+ error("Attempted to play %u.duk, but another video was loaded", resourceId);
+ }
+
+ const Common::String fileName = Common::String::format("%u.duk", resourceId);
+ if (!_decoder->loadFile(fileName)) {
+ error("Can't open %s", fileName.c_str());
+ }
+
+ _decoder->setVolume(_volume);
+ _pixelDouble = displayMode != 0;
+
+ const int16 scale = _pixelDouble ? 2 : 1;
+ _drawRect = Common::Rect(x, y,
+ (x + _decoder->getWidth()) * scale,
+ (y + _decoder->getHeight()) * scale);
+
+ g_sci->_gfxCursor32->hide();
+
+ if (_doFrameOut) {
+ _plane = new Plane(_drawRect, kPlanePicColored);
+ g_sci->_gfxFrameout->addPlane(*_plane);
+ g_sci->_gfxFrameout->frameOut(true);
+ }
+
+ const Buffer &currentBuffer = g_sci->_gfxFrameout->getCurrentBuffer();
+ const Graphics::PixelFormat format = _decoder->getPixelFormat();
+
+ if (_pixelDouble) {
+ assert(_scaleBuffer == nullptr);
+ _scaleBuffer = new byte[_drawRect.width() * _drawRect.height() * format.bytesPerPixel];
+ }
+
+ initGraphics(currentBuffer.screenWidth, currentBuffer.screenHeight, true, &format);
+
+ _status = kDuckOpen;
+}
+
+void DuckPlayer::play(const int lastFrameNo) {
+ flushEvents(_eventMan);
+
+ if (_status != kDuckPlaying) {
+ _status = kDuckPlaying;
+ _decoder->start();
+ }
+
+ while (!g_engine->shouldQuit()) {
+ if (_decoder->endOfVideo() || (lastFrameNo != -1 && _decoder->getCurFrame() >= lastFrameNo)) {
+ break;
+ }
+
+ g_sci->sleep(_decoder->getTimeToNextFrame());
+ while (_decoder->needsUpdate()) {
+ renderFrame();
+ }
+
+ SciEvent event = _eventMan->getSciEvent(SCI_EVENT_MOUSE_PRESS | SCI_EVENT_PEEK);
+ if (event.type == SCI_EVENT_MOUSE_PRESS) {
+ flushEvents(_eventMan);
+ break;
+ }
+
+ event = _eventMan->getSciEvent(SCI_EVENT_KEYBOARD | SCI_EVENT_PEEK);
+ if (event.type == SCI_EVENT_KEYBOARD) {
+ bool stop = false;
+ while ((event = _eventMan->getSciEvent(SCI_EVENT_KEYBOARD)),
+ event.type != SCI_EVENT_NONE) {
+ if (event.character == SCI_KEY_ESC) {
+ stop = true;
+ break;
+ }
+ }
+
+ if (stop) {
+ flushEvents(_eventMan);
+ break;
+ }
+ }
+ }
+}
+
+void DuckPlayer::close() {
+ if (_status == kDuckClosed) {
+ return;
+ }
+
+ _decoder->close();
+
+ const Buffer &currentBuffer = g_sci->_gfxFrameout->getCurrentBuffer();
+ const Graphics::PixelFormat format = Graphics::PixelFormat::createFormatCLUT8();
+ initGraphics(currentBuffer.screenWidth, currentBuffer.screenHeight, true, &format);
+
+ g_sci->_gfxCursor32->unhide();
+
+ if (_doFrameOut) {
+ g_sci->_gfxFrameout->deletePlane(*_plane);
+ g_sci->_gfxFrameout->frameOut(true);
+ _plane = nullptr;
+ }
+
+ _pixelDouble = false;
+ delete[] _scaleBuffer;
+ _scaleBuffer = nullptr;
+
+ _status = kDuckClosed;
+}
+
+static inline uint16 interpolate(const Graphics::PixelFormat &format, const uint16 p1, const uint16 p2) {
+ uint8 r1, g1, b1, r2, g2, b2;
+ format.colorToRGB(p1, r1, g1, b1);
+ format.colorToRGB(p2, r2, g2, b2);
+ return format.RGBToColor((r1 + r2) >> 1, (g1 + g2) >> 1, (b1 + b2) >> 1);
+}
+
+void DuckPlayer::renderFrame() const {
+ const Graphics::Surface *surface = _decoder->decodeNextFrame();
+
+ // Audio-only or non-updated frame
+ if (surface == nullptr) {
+ return;
+ }
+
+ assert(surface->format.bytesPerPixel == 2);
+
+ if (_pixelDouble) {
+ const uint16 *source = (const uint16 *)surface->getPixels();
+ const Graphics::PixelFormat &format = surface->format;
+ uint16 *target = (uint16 *)_scaleBuffer;
+
+#ifndef SCI_DUCK_NO_INTERPOLATION
+ // divide by 2 gets pixel pitch instead of byte pitch for source
+ const uint16 sourcePitch = surface->pitch >> 1;
+#endif
+
+ const uint16 targetPitch = surface->pitch;
+ const bool blackLined = ConfMan.getBool("enable_black_lined_video");
+ for (int y = 0; y < surface->h - 1; ++y) {
+ for (int x = 0; x < surface->w - 1; ++x) {
+#ifndef SCI_DUCK_NO_INTERPOLATION
+ const uint16 a = source[0];
+ const uint16 b = source[1];
+ const uint16 c = source[sourcePitch];
+ const uint16 d = source[sourcePitch + 1];
+
+ target[0] = a;
+ target[1] = interpolate(format, a, b);
+#else
+ const uint16 value = *source;
+ target[0] = value;
+ target[1] = value;
+#endif
+ if (!blackLined) {
+#ifndef SCI_DUCK_NO_INTERPOLATION
+ target[targetPitch] = interpolate(format, a, c);
+ target[targetPitch + 1] = interpolate(format, target[1], interpolate(format, c, d));
+#else
+ target[targetPitch] = value;
+ target[targetPitch + 1] = value;
+#endif
+ }
+
+ target += 2;
+ ++source;
+ }
+
+ const uint16 value = *source++;
+ target[0] = value;
+ target[1] = value;
+ if (!blackLined) {
+ target[targetPitch] = value;
+ target[targetPitch + 1] = value;
+ }
+ target += 2;
+
+ if (blackLined) {
+ memset(target, 0, targetPitch * format.bytesPerPixel);
+ }
+
+ target += targetPitch;
+ }
+
+ for (int x = 0; x < surface->w; ++x) {
+ const uint16 lastValue = *source++;
+ target[0] = lastValue;
+ target[1] = lastValue;
+
+ if (!blackLined) {
+ target[targetPitch] = lastValue;
+ target[targetPitch + 1] = lastValue;
+ target += 2;
+ }
+ }
+
+ if (blackLined) {
+ memset(target, 0, targetPitch);
+ }
+
+ g_system->copyRectToScreen(_scaleBuffer, surface->pitch * 2, _drawRect.left, _drawRect.top, _drawRect.width(), _drawRect.height());
+ } else {
+ g_system->copyRectToScreen(surface->getPixels(), surface->pitch, _drawRect.left, _drawRect.top, surface->w, surface->h);
+ }
+
+ g_system->updateScreen();
+ g_sci->getSciDebugger()->onFrame();
+}
+
} // End of namespace Sci
diff --git a/engines/sci/graphics/video32.h b/engines/sci/graphics/video32.h
index 5ed8fd954a..fae5cafbbe 100644
--- a/engines/sci/graphics/video32.h
+++ b/engines/sci/graphics/video32.h
@@ -28,10 +28,11 @@
#include "common/str.h" // for String
#include "sci/engine/vm_types.h" // for reg_t
#include "sci/video/robot_decoder.h" // for RobotDecoder
+#include "sci/sound/audio32.h" // for Audio32::kMaxVolume
+#include "video/avi_decoder.h" // for AVIDecoder::setVolume
namespace Video {
class AdvancedVMDDecoder;
-class AVIDecoder;
}
namespace Sci {
class EventManager;
@@ -531,6 +532,103 @@ private:
bool _showCursor;
};
+#pragma mark -
+#pragma mark DuckPlayer
+
+class DuckPlayer {
+public:
+ enum DuckStatus {
+ kDuckClosed = 0,
+ kDuckOpen = 1,
+ kDuckPlaying = 2,
+ kDuckPaused = 3
+ };
+
+ DuckPlayer(SegManager *segMan, EventManager *eventMan);
+
+ ~DuckPlayer();
+
+ /**
+ * Opens a stream to a Duck resource.
+ */
+ void open(const GuiResourceId resourceId, const int displayMode, const int16 x, const int16 y);
+
+ /**
+ * Stops playback and closes the currently open Duck stream.
+ */
+ void close();
+
+ /**
+ * Begins playback of the current Duck video.
+ */
+ void play(const int lastFrameNo);
+
+ /**
+ * Sets a flag indicating that an opaque plane should be added
+ * to the graphics manager underneath the video surface during
+ * playback.
+ */
+ void setDoFrameOut(const bool value) { _doFrameOut = value; }
+
+ /**
+ * Sets the volume of the decoder.
+ */
+ void setVolume(const uint8 value) {
+ _volume = (uint)value * Audio::Mixer::kMaxChannelVolume / Audio32::kMaxVolume;
+ _decoder->setVolume(_volume);
+ }
+
+private:
+ SegManager *_segMan;
+ EventManager *_eventMan;
+ Video::AVIDecoder *_decoder;
+
+ /**
+ * An empty plane drawn behind the video when the doFrameOut
+ * flag is true.
+ */
+ Plane *_plane;
+
+ /**
+ * The player status.
+ */
+ DuckStatus _status;
+
+ /**
+ * The screen rect where the video should be drawn.
+ */
+ Common::Rect _drawRect;
+
+ /**
+ * The playback volume for the player.
+ */
+ uint8 _volume;
+
+ /**
+ * If true, frameOut will be called during Duck video playback to update
+ * other parts of the screen.
+ */
+ bool _doFrameOut;
+
+ /**
+ * If true, the video will be pixel doubled during playback.
+ */
+ bool _pixelDouble;
+
+ /**
+ * The buffer used to perform scaling of the video.
+ */
+ byte *_scaleBuffer;
+
+ /**
+ * Renders the current frame to the system video buffer.
+ */
+ void renderFrame() const;
+};
+
+#pragma mark -
+#pragma mark Video32
+
/**
* Video32 provides facilities for playing back
* video in SCI engine.
@@ -541,18 +639,21 @@ public:
_SEQPlayer(segMan),
_AVIPlayer(segMan, eventMan),
_VMDPlayer(segMan, eventMan),
- _robotPlayer(segMan) {}
+ _robotPlayer(segMan),
+ _duckPlayer(segMan, eventMan) {}
SEQPlayer &getSEQPlayer() { return _SEQPlayer; }
AVIPlayer &getAVIPlayer() { return _AVIPlayer; }
VMDPlayer &getVMDPlayer() { return _VMDPlayer; }
RobotDecoder &getRobotPlayer() { return _robotPlayer; }
+ DuckPlayer &getDuckPlayer() { return _duckPlayer; }
private:
SEQPlayer _SEQPlayer;
AVIPlayer _AVIPlayer;
VMDPlayer _VMDPlayer;
RobotDecoder _robotPlayer;
+ DuckPlayer _duckPlayer;
};
} // End of namespace Sci
diff --git a/engines/sci/sound/audio32.h b/engines/sci/sound/audio32.h
index a9905ab6bf..9130cbe687 100644
--- a/engines/sci/sound/audio32.h
+++ b/engines/sci/sound/audio32.h
@@ -160,12 +160,6 @@ public:
Audio32(ResourceManager *resMan);
~Audio32();
-private:
- ResourceManager *_resMan;
- Audio::Mixer *_mixer;
- Audio::SoundHandle _handle;
- Common::Mutex _mutex;
-
enum {
/**
* The maximum channel volume.
@@ -173,6 +167,12 @@ private:
kMaxVolume = 127
};
+private:
+ ResourceManager *_resMan;
+ Audio::Mixer *_mixer;
+ Audio::SoundHandle _handle;
+ Common::Mutex _mutex;
+
#pragma mark -
#pragma mark AudioStream implementation
public: