aboutsummaryrefslogtreecommitdiff
path: root/engines/sci
diff options
context:
space:
mode:
authorColin Snover2016-07-27 21:00:37 -0500
committerColin Snover2016-08-11 20:50:33 -0500
commit13297c19298c5ad73c9e996c5c31ca91de124911 (patch)
tree0e5525f85a47e3343d05770c96a2fcc0ac85926c /engines/sci
parentc28a5733e0911f071f02f3d14aec0dfe53f1aa05 (diff)
downloadscummvm-rg350-13297c19298c5ad73c9e996c5c31ca91de124911.tar.gz
scummvm-rg350-13297c19298c5ad73c9e996c5c31ca91de124911.tar.bz2
scummvm-rg350-13297c19298c5ad73c9e996c5c31ca91de124911.zip
SCI32: Implement kShowMovie
Diffstat (limited to 'engines/sci')
-rw-r--r--engines/sci/engine/kernel.h11
-rw-r--r--engines/sci/engine/kernel_tables.h29
-rw-r--r--engines/sci/engine/kmisc.cpp16
-rw-r--r--engines/sci/engine/kvideo.cpp166
-rw-r--r--engines/sci/engine/vm.cpp15
-rw-r--r--engines/sci/graphics/video32.cpp390
-rw-r--r--engines/sci/graphics/video32.h204
7 files changed, 744 insertions, 87 deletions
diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h
index 5ff4f932be..b02a7e545a 100644
--- a/engines/sci/engine/kernel.h
+++ b/engines/sci/engine/kernel.h
@@ -450,6 +450,17 @@ reg_t kPlayVMDShowCursor(EngineState *s, int argc, reg_t *argv);
reg_t kPlayVMDSetBlackoutArea(EngineState *s, int argc, reg_t *argv);
reg_t kPlayVMDRestrictPalette(EngineState *s, int argc, reg_t *argv);
+reg_t kShowMovie32(EngineState *s, int argc, reg_t *argv);
+reg_t kShowMovieWin(EngineState *s, int argc, reg_t *argv);
+reg_t kShowMovieWinOpen(EngineState *s, int argc, reg_t *argv);
+reg_t kShowMovieWinInit(EngineState *s, int argc, reg_t *argv);
+reg_t kShowMovieWinPlay(EngineState *s, int argc, reg_t *argv);
+reg_t kShowMovieWinClose(EngineState *s, int argc, reg_t *argv);
+reg_t kShowMovieWinCue(EngineState *s, int argc, reg_t *argv);
+reg_t kShowMovieWinGetDuration(EngineState *s, int argc, reg_t *argv);
+reg_t kShowMovieWinPlayUntilEvent(EngineState *s, int argc, reg_t *argv);
+reg_t kShowMovieWinInitDouble(EngineState *s, int argc, reg_t *argv);
+
reg_t kIsHiRes(EngineState *s, int argc, reg_t *argv);
reg_t kArray(EngineState *s, int argc, reg_t *argv);
reg_t kListAt(EngineState *s, int argc, reg_t *argv);
diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h
index e0e4dcc233..76c24b09e3 100644
--- a/engines/sci/engine/kernel_tables.h
+++ b/engines/sci/engine/kernel_tables.h
@@ -423,6 +423,27 @@ static const SciKernelMapSubEntry kList_subops[] = {
SCI_SUBOPENTRY_TERMINATOR
};
+// version, subId, function-mapping, signature, workarounds
+static const SciKernelMapSubEntry kShowMovieWin_subops[] = {
+ { SIG_SCI2, 0, MAP_CALL(ShowMovieWinOpen), "r", NULL },
+ { SIG_SCI2, 1, MAP_CALL(ShowMovieWinInit), "ii(ii)", NULL },
+ { SIG_SCI2, 2, MAP_CALL(ShowMovieWinPlay), "i", NULL },
+ { SIG_SCI2, 6, MAP_CALL(ShowMovieWinClose), "", NULL },
+ { SIG_SINCE_SCI21, 0, MAP_CALL(ShowMovieWinOpen), "ir", NULL },
+ { SIG_SINCE_SCI21, 1, MAP_CALL(ShowMovieWinInit), "iii(ii)", NULL },
+ { SIG_SINCE_SCI21, 2, MAP_CALL(ShowMovieWinPlay), "i(ii)(i)(i)", NULL },
+ { SIG_SINCE_SCI21, 6, MAP_CALL(ShowMovieWinClose), "i", NULL },
+ // Since movies are rendered within the graphics engine in ScummVM,
+ // it is not necessary to copy the palette from SCI to MCI, so this
+ // can be a no-op
+ { SIG_SINCE_SCI21, 7, MAP_EMPTY(ShowMovieWinSetPalette), "i", NULL },
+ { SIG_SINCE_SCI21, 8, MAP_CALL(ShowMovieWinGetDuration), "i", NULL },
+ { SIG_SINCE_SCI21, 11, MAP_CALL(ShowMovieWinCue), "ii", NULL },
+ { SIG_SINCE_SCI21, 14, MAP_CALL(ShowMovieWinPlayUntilEvent), "i(i)", NULL },
+ { SIG_SINCE_SCI21, 15, MAP_CALL(ShowMovieWinInitDouble), "iii", NULL },
+ SCI_SUBOPENTRY_TERMINATOR
+};
+
// There are a lot of subops to PlayVMD, but only a few of them are ever
// actually used by games
// version, subId, function-mapping, signature, workarounds
@@ -688,7 +709,11 @@ static SciKernelMapEntry s_kernelMap[] = {
{ MAP_CALL(SetSynonyms), SIG_EVERYWHERE, "o", NULL, NULL },
{ MAP_CALL(SetVideoMode), SIG_EVERYWHERE, "i", NULL, NULL },
{ MAP_CALL(ShakeScreen), SIG_EVERYWHERE, "(i)(i)", NULL, NULL },
- { MAP_CALL(ShowMovie), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_CALL(ShowMovie), SIG_SCI16, SIGFOR_ALL, "(.*)", NULL, NULL },
+#ifdef ENABLE_SCI32
+ { "ShowMovie", kShowMovie32, SIG_SCI32, SIGFOR_DOS, "ri(i)(i)", NULL, NULL },
+ { "ShowMovie", kShowMovieWin, SIG_SCI32, SIGFOR_WIN, "(.*)", kShowMovieWin_subops, NULL },
+#endif
{ MAP_CALL(Show), SIG_EVERYWHERE, "i", NULL, NULL },
{ MAP_CALL(SinDiv), SIG_EVERYWHERE, "ii", NULL, NULL },
{ MAP_CALL(Sort), SIG_EVERYWHERE, "ooo", NULL, NULL },
@@ -820,7 +845,7 @@ static SciKernelMapEntry s_kernelMap[] = {
{ MAP_CALL(List), SIG_SINCE_SCI21, SIGFOR_ALL, "(.*)", kList_subops, NULL },
{ MAP_CALL(MulDiv), SIG_EVERYWHERE, "iii", NULL, NULL },
{ MAP_CALL(PlayVMD), SIG_EVERYWHERE, "(.*)", kPlayVMD_subops, NULL },
- { MAP_CALL(Robot), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_EMPTY(Robot), SIG_EVERYWHERE, "(.*)", NULL, NULL },
{ MAP_CALL(Save), SIG_EVERYWHERE, "i(.*)", kSave_subops, NULL },
{ MAP_CALL(Text), SIG_SINCE_SCI21MID, SIGFOR_ALL, "i(.*)", kText_subops, NULL },
{ MAP_CALL(AddPicAt), SIG_EVERYWHERE, "oiii(i)(i)", NULL, NULL },
diff --git a/engines/sci/engine/kmisc.cpp b/engines/sci/engine/kmisc.cpp
index 1924848717..c99540967c 100644
--- a/engines/sci/engine/kmisc.cpp
+++ b/engines/sci/engine/kmisc.cpp
@@ -20,6 +20,7 @@
*
*/
+#include "common/config-manager.h"
#include "common/system.h"
#include "sci/sci.h"
@@ -542,7 +543,7 @@ enum kSciPlatforms {
enum kPlatformOps {
kPlatformUnk0 = 0,
kPlatformCDSpeed = 1,
- kPlatformUnk2 = 2,
+ kPlatformColorDepth = 2,
kPlatformCDCheck = 3,
kPlatformGetPlatform = 4,
kPlatformUnk5 = 5,
@@ -563,11 +564,6 @@ reg_t kPlatform(EngineState *s, int argc, reg_t *argv) {
return NULL_REG;
}
- if (g_sci->forceHiresGraphics()) {
- // force Windows platform, so that hires-graphics are enabled
- isWindows = true;
- }
-
uint16 operation = (argc == 0) ? 0 : argv[0].toUint16();
switch (operation) {
@@ -575,9 +571,9 @@ reg_t kPlatform(EngineState *s, int argc, reg_t *argv) {
// TODO: Returns CD Speed?
warning("STUB: kPlatform(CDSpeed)");
break;
- case kPlatformUnk2:
+ case kPlatformColorDepth:
// Always returns 2
- return make_reg(0, 2);
+ return make_reg(0, /* 256-color */ 2);
case kPlatformCDCheck:
// TODO: Some sort of CD check?
warning("STUB: kPlatform(CDCheck)");
@@ -591,9 +587,9 @@ reg_t kPlatform(EngineState *s, int argc, reg_t *argv) {
return make_reg(0, (isWindows) ? kSciPlatformWindows : kSciPlatformDOS);
case kPlatformUnk5:
// This case needs to return the opposite of case 6 to get hires graphics
- return make_reg(0, !isWindows);
+ return make_reg(0, !ConfMan.getBool("enable_high_resolution_graphics"));
case kPlatformIsHiRes:
- return make_reg(0, isWindows);
+ return make_reg(0, ConfMan.getBool("enable_high_resolution_graphics"));
case kPlatformIsItWindows:
return make_reg(0, isWindows);
default:
diff --git a/engines/sci/engine/kvideo.cpp b/engines/sci/engine/kvideo.cpp
index de4d4a282c..86d8a4b817 100644
--- a/engines/sci/engine/kvideo.cpp
+++ b/engines/sci/engine/kvideo.cpp
@@ -61,33 +61,15 @@ void playVideo(Video::VideoDecoder *videoDecoder, VideoState videoState) {
uint16 screenWidth = g_sci->_gfxScreen->getDisplayWidth();
uint16 screenHeight = g_sci->_gfxScreen->getDisplayHeight();
- videoState.fileName.toLowercase();
- bool isVMD = videoState.fileName.hasSuffix(".vmd");
-
- if (screenWidth == 640 && width <= 320 && height <= 240 && ((videoState.flags & kDoubled) || !isVMD)) {
+ if (screenWidth == 640 && width <= 320 && height <= 240) {
width *= 2;
height *= 2;
pitch *= 2;
scaleBuffer = new byte[width * height * bytesPerPixel];
}
- uint16 x, y;
-
- // Sanity check...
- if (videoState.x > 0 && videoState.y > 0 && isVMD) {
- x = videoState.x;
- y = videoState.y;
-
- if (x + width > screenWidth || y + height > screenHeight) {
- // Happens in the Lighthouse demo
- warning("VMD video won't fit on screen, centering it instead");
- x = (screenWidth - width) / 2;
- y = (screenHeight - height) / 2;
- }
- } else {
- x = (screenWidth - width) / 2;
- y = (screenHeight - height) / 2;
- }
+ uint16 x = (screenWidth - width) / 2;
+ uint16 y = (screenHeight - height) / 2;
bool skipVideo = false;
EngineState *s = g_sci->getEngineState();
@@ -181,16 +163,6 @@ reg_t kShowMovie(EngineState *s, int argc, reg_t *argv) {
// TODO: This appears to be some sort of subop. case 0 contains the string
// for the video, so we'll just play it from there for now.
-#ifdef ENABLE_SCI32
- if (getSciVersion() >= SCI_VERSION_2_1_EARLY) {
- // SCI2.1 always has argv[0] as 1, the rest of the arguments seem to
- // follow SCI1.1/2.
- if (argv[0].toUint16() != 1)
- error("SCI2.1 kShowMovie argv[0] not 1");
- argv++;
- argc--;
- }
-#endif
switch (argv[0].toUint16()) {
case 0: {
Common::String filename = s->_segMan->getString(argv[1]);
@@ -243,52 +215,102 @@ reg_t kShowMovie(EngineState *s, int argc, reg_t *argv) {
}
#ifdef ENABLE_SCI32
+reg_t kShowMovie32(EngineState *s, int argc, reg_t *argv) {
+ Common::String fileName = s->_segMan->getString(argv[0]);
+ const int16 numTicks = argv[1].toSint16();
+ const int16 x = argc > 3 ? argv[2].toSint16() : 0;
+ const int16 y = argc > 3 ? argv[3].toSint16() : 0;
-reg_t kRobot(EngineState *s, int argc, reg_t *argv) {
- int16 subop = argv[0].toUint16();
-
- switch (subop) {
- case 0: { // init
- int id = argv[1].toUint16();
- reg_t obj = argv[2];
- int16 flag = argv[3].toSint16();
- int16 x = argv[4].toUint16();
- int16 y = argv[5].toUint16();
- warning("kRobot(init), id %d, obj %04x:%04x, flag %d, x=%d, y=%d", id, PRINT_REG(obj), flag, x, y);
- g_sci->_robotDecoder->load(id);
- g_sci->_robotDecoder->start();
- g_sci->_robotDecoder->setPos(x, y);
- }
- break;
- case 1: // LSL6 hires (startup)
- // TODO
- return NULL_REG; // an integer is expected
- case 4: { // start - we don't really have a use for this one
- //int id = argv[1].toUint16();
- //warning("kRobot(start), id %d", id);
- }
- break;
- case 7: // unknown, called e.g. by Phantasmagoria
- warning("kRobot(%d)", subop);
- break;
- case 8: // sync
- //if (true) { // debug: automatically skip all robot videos
- if (g_sci->_robotDecoder->endOfVideo()) {
- g_sci->_robotDecoder->close();
- // Signal the engine scripts that the video is done
- writeSelector(s->_segMan, argv[1], SELECTOR(signal), SIGNAL_REG);
- } else {
- writeSelector(s->_segMan, argv[1], SELECTOR(signal), NULL_REG);
- }
- break;
- default:
- warning("kRobot(%d)", subop);
- break;
- }
+ g_sci->_video32->getSEQPlayer().play(fileName, numTicks, x, y);
return s->r_acc;
}
+reg_t kShowMovieWin(EngineState *s, int argc, reg_t *argv) {
+ if (!s)
+ return make_reg(0, getSciVersion());
+ error("not supposed to call this");
+}
+
+reg_t kShowMovieWinOpen(EngineState *s, int argc, reg_t *argv) {
+ // SCI2.1 adds a movie ID to the call, but the movie ID is broken,
+ // so just ignore it
+ if (getSciVersion() > SCI_VERSION_2) {
+ ++argv;
+ --argc;
+ }
+
+ const Common::String fileName = s->_segMan->getString(argv[0]);
+ return make_reg(0, g_sci->_video32->getAVIPlayer().open(fileName));
+}
+
+reg_t kShowMovieWinInit(EngineState *s, int argc, reg_t *argv) {
+ // SCI2.1 adds a movie ID to the call, but the movie ID is broken,
+ // so just ignore it
+ if (getSciVersion() > SCI_VERSION_2) {
+ ++argv;
+ --argc;
+ }
+
+ const int16 x = argv[0].toSint16();
+ const int16 y = argv[1].toSint16();
+ const int16 width = argc > 3 ? argv[2].toSint16() : 0;
+ const int16 height = argc > 3 ? argv[3].toSint16() : 0;
+ return make_reg(0, g_sci->_video32->getAVIPlayer().init1x(x, y, width, height));
+}
+
+reg_t kShowMovieWinPlay(EngineState *s, int argc, reg_t *argv) {
+ if (getSciVersion() == SCI_VERSION_2) {
+ AVIPlayer::EventFlags flags = (AVIPlayer::EventFlags)argv[0].toUint16();
+ return make_reg(0, g_sci->_video32->getAVIPlayer().playUntilEvent(flags));
+ } else {
+ // argv[0] is a broken movie ID
+ const int16 from = argc > 2 ? argv[1].toSint16() : 0;
+ const int16 to = argc > 2 ? argv[2].toSint16() : 0;
+ const int16 showStyle = argc > 3 ? argv[3].toSint16() : 0;
+ const bool cue = argc > 4 ? (bool)argv[4].toSint16() : false;
+ return make_reg(0, g_sci->_video32->getAVIPlayer().play(from, to, showStyle, cue));
+ }
+}
+
+reg_t kShowMovieWinClose(EngineState *s, int argc, reg_t *argv) {
+ return make_reg(0, g_sci->_video32->getAVIPlayer().close());
+}
+
+reg_t kShowMovieWinGetDuration(EngineState *s, int argc, reg_t *argv) {
+ return make_reg(0, g_sci->_video32->getAVIPlayer().getDuration());
+}
+
+reg_t kShowMovieWinCue(EngineState *s, int argc, reg_t *argv) {
+ // SCI2.1 adds a movie ID to the call, but the movie ID is broken,
+ // so just ignore it
+ if (getSciVersion() > SCI_VERSION_2) {
+ ++argv;
+ --argc;
+ }
+
+ const uint16 frameNo = argv[0].toUint16();
+ return make_reg(0, g_sci->_video32->getAVIPlayer().cue(frameNo));
+}
+
+reg_t kShowMovieWinPlayUntilEvent(EngineState *s, int argc, reg_t *argv) {
+ const int defaultFlags =
+ AVIPlayer::kEventFlagEnd |
+ AVIPlayer::kEventFlagEscapeKey;
+
+ // argv[0] is the movie number, which is not used by this method
+ const AVIPlayer::EventFlags flags = (AVIPlayer::EventFlags)(argc > 1 ? argv[1].toUint16() : defaultFlags);
+
+ return make_reg(0, g_sci->_video32->getAVIPlayer().playUntilEvent(flags));
+}
+
+reg_t kShowMovieWinInitDouble(EngineState *s, int argc, reg_t *argv) {
+ // argv[0] is a broken movie ID
+ const int16 x = argv[1].toSint16();
+ const int16 y = argv[2].toSint16();
+ return make_reg(0, g_sci->_video32->getAVIPlayer().init2x(x, y));
+}
+
reg_t kPlayVMD(EngineState *s, int argc, reg_t *argv) {
if (!s)
return make_reg(0, getSciVersion());
diff --git a/engines/sci/engine/vm.cpp b/engines/sci/engine/vm.cpp
index 3e12084ed6..548fd477bf 100644
--- a/engines/sci/engine/vm.cpp
+++ b/engines/sci/engine/vm.cpp
@@ -405,6 +405,21 @@ static void callKernelFunc(EngineState *s, int kernelCallNr, int argc) {
error("[VM] k%s[%x]: no subfunction ID parameter given", kernelCall.name, kernelCallNr);
if (argv[0].isPointer())
error("[VM] k%s[%x]: given subfunction ID is actually a pointer", kernelCall.name, kernelCallNr);
+
+#ifdef ENABLE_SCI32
+ // The Windows version of kShowMovie has subops, but the subop number
+ // is put in the second parameter in SCI2.1+, even though every other
+ // kcall with subops puts the subop in the first parameter. To allow use
+ // of the normal subops system, we swap the arguments so the subop
+ // number is in the usual place.
+ if (getSciVersion() > SCI_VERSION_2 &&
+ g_sci->getPlatform() == Common::kPlatformWindows &&
+ strcmp(kernelCall.name, "ShowMovie") == 0) {
+ assert(argc > 1);
+ SWAP(argv[0], argv[1]);
+ }
+#endif
+
const uint16 subId = argv[0].toUint16();
// Skip over subfunction-id
argc--;
diff --git a/engines/sci/graphics/video32.cpp b/engines/sci/graphics/video32.cpp
index dc2641c92a..ac4522c1a0 100644
--- a/engines/sci/graphics/video32.cpp
+++ b/engines/sci/graphics/video32.cpp
@@ -29,11 +29,397 @@
#include "sci/graphics/palette32.h"
#include "sci/graphics/text32.h"
#include "sci/graphics/video32.h"
-#include "sci/sci.h"
+#include "audio/mixer.h" // for Audio::Mixer::kSFXSoundType
+#include "common/config-manager.h" // for ConfMan
+#include "common/textconsole.h" // for error, warning
+#include "common/system.h"
+#include "engine.h" // for Engine, g_engine
+#include "engines/util.h"
+#include "graphics/scaler/scalebit.h"
+#include "sci/console.h" // for Console
+#include "sci/engine/state.h" // for EngineState
+#include "sci/engine/vm_types.h" // for reg_t
+#include "sci/event.h" // for SciEvent, EventManager, SCI_...
+#include "sci/graphics/celobj32.h" // for CelInfo32
+#include "sci/graphics/cursor.h" // for GfxCursor
+#include "sci/graphics/helpers.h" // for Color, Palette
+#include "sci/graphics/palette32.h" // for GfxPalette32
+#include "sci/graphics/plane32.h" // for Plane, PlanePictureCodes::kP...
+#include "sci/graphics/screen_item32.h" // for ScaleInfo, ScreenItem, Scale...
+#include "sci/graphics/text32.h" // for BitmapResource
+#include "sci/video/seq_decoder.h"
+#include "sci/sci.h" // for SciEngine, g_sci, getSciVersion
+#include "video/avi_decoder.h"
#include "video/coktel_decoder.h"
namespace Sci {
+#pragma mark SEQPlayer
+
+SEQPlayer::SEQPlayer(SegManager *segMan) :
+ _segMan(segMan),
+ _decoder(nullptr),
+ _plane(nullptr),
+ _screenItem(nullptr) {}
+
+void SEQPlayer::play(const Common::String &fileName, const int16 numTicks, const int16 x, const int16 y) {
+ delete _decoder;
+ _decoder = new SEQDecoder(numTicks);
+ _decoder->loadFile(fileName);
+
+ // NOTE: In the original engine, video was output directly to the hardware,
+ // bypassing the game's rendering engine. Instead of doing this, we use a
+ // mechanism that is very similar to that used by the VMD player, which
+ // allows the SEQ to be drawn into a bitmap ScreenItem and displayed using
+ // the normal graphics system.
+ _segMan->allocateBitmap(&_bitmap, _decoder->getWidth(), _decoder->getHeight(), kDefaultSkipColor, 0, 0, kLowResX, kLowResY, 0, false, false);
+
+ CelInfo32 celInfo;
+ celInfo.type = kCelTypeMem;
+ celInfo.bitmap = _bitmap;
+
+ _plane = new Plane(Common::Rect(kLowResX, kLowResY), kPlanePicColored);
+ g_sci->_gfxFrameout->addPlane(*_plane);
+
+ // Normally we would use the x, y coordinates passed into the play function
+ // to position the screen item, but because the video frame bitmap is
+ // drawn in low-resolution coordinates, it gets automatically scaled up by
+ // the engine (pixel doubling with aspect ratio correction). As a result,
+ // the animation does not need the extra offsets from the game in order to
+ // be correctly positioned in the middle of the window, so we ignore them.
+ _screenItem = new ScreenItem(_plane->_object, celInfo, Common::Point(0, 0), ScaleInfo());
+ g_sci->_gfxFrameout->addScreenItem(*_screenItem);
+ g_sci->_gfxFrameout->frameOut(true);
+ _decoder->start();
+
+ while (!g_engine->shouldQuit() && !_decoder->endOfVideo()) {
+ renderFrame();
+ g_sci->getEngineState()->speedThrottler(_decoder->getTimeToNextFrame());
+ g_sci->getEngineState()->_throttleTrigger = true;
+ }
+
+ _segMan->freeBitmap(_screenItem->_celInfo.bitmap);
+ g_sci->_gfxFrameout->deletePlane(*_plane);
+ g_sci->_gfxFrameout->frameOut(true);
+ _screenItem = nullptr;
+ _plane = nullptr;
+}
+
+void SEQPlayer::renderFrame() const {
+ const Graphics::Surface *surface = _decoder->decodeNextFrame();
+
+ SciBitmap &bitmap = *_segMan->lookupBitmap(_bitmap);
+ bitmap.getBuffer().copyRectToSurface(*surface, 0, 0, Common::Rect(surface->w, surface->h));
+
+ const bool dirtyPalette = _decoder->hasDirtyPalette();
+ if (dirtyPalette) {
+ Palette palette;
+ const byte *rawPalette = _decoder->getPalette();
+ for (int i = 0; i < ARRAYSIZE(palette.colors); ++i) {
+ palette.colors[i].r = *rawPalette++;
+ palette.colors[i].g = *rawPalette++;
+ palette.colors[i].b = *rawPalette++;
+ palette.colors[i].used = true;
+ }
+
+ g_sci->_gfxPalette32->submit(palette);
+ }
+
+ g_sci->_gfxFrameout->updateScreenItem(*_screenItem);
+ g_sci->getSciDebugger()->onFrame();
+ g_sci->_gfxFrameout->frameOut(true);
+}
+
+#pragma mark -
+#pragma mark AVIPlayer
+
+AVIPlayer::AVIPlayer(SegManager *segMan, EventManager *eventMan) :
+ _segMan(segMan),
+ _eventMan(eventMan),
+ _decoder(new Video::AVIDecoder(Audio::Mixer::kSFXSoundType)),
+ _scaleBuffer(nullptr),
+ _plane(nullptr),
+ _screenItem(nullptr),
+ _status(kAVINotOpen) {}
+
+AVIPlayer::~AVIPlayer() {
+ close();
+ delete _decoder;
+}
+
+AVIPlayer::IOStatus AVIPlayer::open(const Common::String &fileName) {
+ if (_status != kAVINotOpen) {
+ close();
+ }
+
+ if (!_decoder->loadFile(fileName)) {
+ return kIOFileNotFound;
+ }
+
+ _status = kAVIOpen;
+ return kIOSuccess;
+}
+
+AVIPlayer::IOStatus AVIPlayer::init1x(const int16 x, const int16 y, int16 width, int16 height) {
+ if (!width || !height) {
+ width = _decoder->getWidth();
+ height = _decoder->getHeight();
+ }
+
+ // QFG4CD gives non-multiple-of-2 values for width and height,
+ // which would normally be OK except the source video is a pixel bigger
+ // in each dimension
+ width = (width + 1) & ~1;
+ height = (height + 1) & ~1;
+
+ _drawRect.left = x;
+ _drawRect.top = y;
+ _drawRect.right = x + width;
+ _drawRect.bottom = y + height;
+
+ // SCI2.1 uses init2x to draw a pixel-doubled AVI, but SCI2 has only the
+ // one play routine which automatically pixel-doubles in hi-res mode
+ if (getSciVersion() == SCI_VERSION_2) {
+ // NOTE: This is somewhat of a hack; credits.avi from GK1 is not
+ // rendered correctly in SSCI because it is a 640x480 video, but the
+ // game script gives the wrong dimensions. Since this is the only
+ // high-resolution AVI ever used, just set the draw rectangle to draw
+ // the entire screen
+ if (_decoder->getWidth() > 320) {
+ _drawRect.left = 0;
+ _drawRect.top = 0;
+ _drawRect.right = 320;
+ _drawRect.bottom = 200;
+ }
+
+ // In hi-res mode, video will be pixel doubled, so the origin (which
+ // corresponds to the correct position without pixel doubling) needs to
+ // be corrected
+ if (g_sci->_gfxFrameout->_isHiRes && _decoder->getWidth() <= 320) {
+ _drawRect.left /= 2;
+ _drawRect.top /= 2;
+ }
+ }
+
+ _pixelDouble = false;
+ init();
+
+ return kIOSuccess;
+}
+
+AVIPlayer::IOStatus AVIPlayer::init2x(const int16 x, const int16 y) {
+ _drawRect.left = x;
+ _drawRect.top = y;
+ _drawRect.right = x + _decoder->getWidth() * 2;
+ _drawRect.bottom = y + _decoder->getHeight() * 2;
+
+ _pixelDouble = true;
+ init();
+
+ return kIOSuccess;
+}
+
+void AVIPlayer::init() {
+ int16 xRes;
+ int16 yRes;
+
+ if (g_sci->_gfxFrameout->_isHiRes && _decoder->getWidth() > 320) {
+ xRes = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth;
+ yRes = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight;
+ } else {
+ xRes = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ yRes = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+ }
+
+ _plane = new Plane(_drawRect);
+ g_sci->_gfxFrameout->addPlane(*_plane);
+
+ if (_decoder->getPixelFormat().bytesPerPixel == 1) {
+ _segMan->allocateBitmap(&_bitmap, _decoder->getWidth(), _decoder->getHeight(), kDefaultSkipColor, 0, 0, xRes, yRes, 0, false, false);
+
+ CelInfo32 celInfo;
+ celInfo.type = kCelTypeMem;
+ celInfo.bitmap = _bitmap;
+
+ _screenItem = new ScreenItem(_plane->_object, celInfo, Common::Point(_drawRect.left, _drawRect.top), ScaleInfo());
+ g_sci->_gfxFrameout->addScreenItem(*_screenItem);
+ g_sci->_gfxFrameout->frameOut(true);
+ } else {
+ const Buffer &currentBuffer = g_sci->_gfxFrameout->getCurrentBuffer();
+ const Graphics::PixelFormat format = _decoder->getPixelFormat();
+ initGraphics(currentBuffer.screenWidth, currentBuffer.screenHeight, g_sci->_gfxFrameout->_isHiRes, &format);
+
+ if (_pixelDouble) {
+ const int16 width = _drawRect.width();
+ const int16 height = _drawRect.height();
+ _scaleBuffer = calloc(1, width * height * format.bytesPerPixel);
+ }
+ }
+}
+
+AVIPlayer::IOStatus AVIPlayer::play(const int16 from, const int16 to, const int16, const bool async) {
+ if (from >= 0 && to > 0 && from <= to) {
+ _decoder->seekToFrame(from);
+ _decoder->setEndFrame(to);
+ }
+
+ if (!async) {
+ renderVideo();
+ }
+
+ _status = kAVIPlaying;
+ return kIOSuccess;
+}
+
+void AVIPlayer::renderVideo() const {
+ _decoder->start();
+ while (!g_engine->shouldQuit() && !_decoder->endOfVideo()) {
+ g_sci->getEngineState()->speedThrottler(_decoder->getTimeToNextFrame());
+ g_sci->getEngineState()->_throttleTrigger = true;
+ if (_decoder->needsUpdate()) {
+ renderFrame();
+ }
+ }
+}
+
+AVIPlayer::IOStatus AVIPlayer::close() {
+ if (_status == kAVINotOpen) {
+ return kIOSuccess;
+ }
+
+ free(_scaleBuffer);
+ _scaleBuffer = nullptr;
+
+ if (_decoder->getPixelFormat().bytesPerPixel != 1) {
+ const bool isHiRes = g_sci->_gfxFrameout->_isHiRes;
+ const Buffer &currentBuffer = g_sci->_gfxFrameout->getCurrentBuffer();
+ const Graphics::PixelFormat format = Graphics::PixelFormat::createFormatCLUT8();
+ initGraphics(currentBuffer.screenWidth, currentBuffer.screenHeight, isHiRes, &format);
+ }
+
+ _decoder->close();
+ _status = kAVINotOpen;
+ g_sci->_gfxFrameout->deletePlane(*_plane);
+ _plane = nullptr;
+ _screenItem = nullptr;
+ return kIOSuccess;
+}
+
+AVIPlayer::IOStatus AVIPlayer::cue(const uint16 frameNo) {
+ if (!_decoder->seekToFrame(frameNo)) {
+ return kIOSeekFailed;
+ }
+
+ _status = kAVIPaused;
+ return kIOSuccess;
+}
+
+uint16 AVIPlayer::getDuration() const {
+ if (_status == kAVINotOpen) {
+ return 0;
+ }
+
+ return _decoder->getFrameCount();
+}
+
+void AVIPlayer::renderFrame() const {
+ const Graphics::Surface *surface = _decoder->decodeNextFrame();
+
+ if (surface->format.bytesPerPixel == 1) {
+ SciBitmap &bitmap = *_segMan->lookupBitmap(_bitmap);
+ if (surface->w > bitmap.getWidth() || surface->h > bitmap.getHeight()) {
+ warning("Attempted to draw a video frame larger than the destination bitmap");
+ return;
+ }
+
+ bitmap.getBuffer().copyRectToSurface(*surface, 0, 0, Common::Rect(surface->w, surface->h));
+
+ const bool dirtyPalette = _decoder->hasDirtyPalette();
+ if (dirtyPalette) {
+ Palette palette;
+ const byte *rawPalette = _decoder->getPalette();
+ for (int i = 0; i < ARRAYSIZE(palette.colors); ++i) {
+ palette.colors[i].r = *rawPalette++;
+ palette.colors[i].g = *rawPalette++;
+ palette.colors[i].b = *rawPalette++;
+ palette.colors[i].used = true;
+ }
+
+ g_sci->_gfxPalette32->submit(palette);
+ }
+
+ g_sci->_gfxFrameout->updateScreenItem(*_screenItem);
+ g_sci->getSciDebugger()->onFrame();
+ g_sci->_gfxFrameout->frameOut(true);
+ } else {
+ Common::Rect drawRect(_drawRect);
+ const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth;
+ const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight;
+ const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+
+ if (_pixelDouble) {
+ scale(2, _scaleBuffer, surface->pitch * 2, surface->getPixels(), surface->pitch, surface->format.bytesPerPixel, surface->w, surface->h);
+ g_system->copyRectToScreen(_scaleBuffer, surface->pitch * 2, _drawRect.left, _drawRect.top, _drawRect.width(), _drawRect.height());
+ } else {
+ mulinc(drawRect, Ratio(screenWidth, scriptWidth), Ratio(screenHeight, scriptHeight));
+
+ g_system->copyRectToScreen(surface->getPixels(), surface->pitch, drawRect.left, drawRect.top, surface->w, surface->h);
+ }
+ }
+}
+
+AVIPlayer::EventFlags AVIPlayer::playUntilEvent(EventFlags flags) {
+ _decoder->start();
+
+ EventFlags stopFlag = kEventFlagNone;
+ while (!g_engine->shouldQuit()) {
+ if (_decoder->endOfVideo()) {
+ stopFlag = kEventFlagEnd;
+ break;
+ }
+
+ g_sci->getEngineState()->speedThrottler(_decoder->getTimeToNextFrame());
+ g_sci->getEngineState()->_throttleTrigger = true;
+ if (_decoder->needsUpdate()) {
+ renderFrame();
+ }
+
+ SciEvent event = _eventMan->getSciEvent(SCI_EVENT_MOUSE_PRESS | SCI_EVENT_PEEK);
+ if ((flags & kEventFlagMouseDown) && event.type == SCI_EVENT_MOUSE_PRESS) {
+ stopFlag = kEventFlagMouseDown;
+ break;
+ }
+
+ event = _eventMan->getSciEvent(SCI_EVENT_KEYBOARD | SCI_EVENT_PEEK);
+ if ((flags & kEventFlagEscapeKey) && 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) {
+ stopFlag = kEventFlagEscapeKey;
+ break;
+ }
+ }
+
+ // TODO: Hot rectangles
+ if ((flags & kEventFlagHotRectangle) /* && event.type == SCI_EVENT_HOT_RECTANGLE */) {
+ warning("Hot rectangles not implemented in VMD player");
+ stopFlag = kEventFlagHotRectangle;
+ break;
+ }
+ }
+
+ return stopFlag;
+}
+
+#pragma mark -
#pragma mark VMDPlayer
VMDPlayer::VMDPlayer(SegManager *segMan, EventManager *eventMan) :
@@ -117,7 +503,7 @@ VMDPlayer::IOStatus VMDPlayer::close() {
if (!_planeIsOwned && _screenItem != nullptr) {
g_sci->_gfxFrameout->deleteScreenItem(*_screenItem);
- g_sci->getEngineState()->_segMan->freeBitmap(_screenItem->_celInfo.bitmap);
+ _segMan->freeBitmap(_screenItem->_celInfo.bitmap);
_screenItem = nullptr;
} else if (_plane != nullptr) {
g_sci->_gfxFrameout->deletePlane(*_plane);
diff --git a/engines/sci/graphics/video32.h b/engines/sci/graphics/video32.h
index 7033f7c647..b0deba148a 100644
--- a/engines/sci/graphics/video32.h
+++ b/engines/sci/graphics/video32.h
@@ -23,12 +23,208 @@
#ifndef SCI_GRAPHICS_VIDEO32_H
#define SCI_GRAPHICS_VIDEO32_H
-namespace Video { class AdvancedVMDDecoder; }
+#include "common/rect.h" // for Rect
+#include "common/scummsys.h" // for int16, uint8, int32
+#include "common/str.h" // for String
+#include "sci/video/robot_decoder.h" // for RobotDecoder
+
+namespace Video {
+class AdvancedVMDDecoder;
+class AVIDecoder;
+}
namespace Sci {
class Plane;
class ScreenItem;
class SegManager;
+class SEQDecoder;
+struct Palette;
+#pragma mark SEQPlayer
+
+/**
+ * SEQPlayer is used to play SEQ animations.
+ * Used by DOS versions of GK1 and QFG4CD.
+ */
+class SEQPlayer {
+public:
+ SEQPlayer(SegManager *segMan);
+
+ /**
+ * Plays a SEQ animation with the given
+ * file name, with each frame being displayed
+ * for `numTicks` ticks.
+ */
+ void play(const Common::String &fileName, const int16 numTicks, const int16 x, const int16 y);
+
+private:
+ SegManager *_segMan;
+ SEQDecoder *_decoder;
+
+ /**
+ * The plane where the SEQ will be drawn.
+ */
+ Plane *_plane;
+
+ /**
+ * The screen item representing the SEQ surface.
+ */
+ ScreenItem *_screenItem;
+
+ /**
+ * The bitmap used to render video output.
+ */
+ reg_t _bitmap;
+
+ /**
+ * Renders a single frame of video.
+ */
+ void renderFrame() const;
+};
+
+#pragma mark -
+#pragma mark AVIPlayer
+
+/**
+ * AVIPlayer is used to play AVI videos. Used by
+ * Windows versions of GK1CD, KQ7, and QFG4CD.
+ */
+class AVIPlayer {
+public:
+ enum IOStatus {
+ kIOSuccess = 0,
+ kIOFileNotFound = 2,
+ kIOSeekFailed = 12
+ };
+
+ enum AVIStatus {
+ kAVINotOpen = 0,
+ kAVIOpen = 1,
+ kAVIPlaying = 2,
+ kAVIPaused = 3
+ };
+
+ enum EventFlags {
+ kEventFlagNone = 0,
+ kEventFlagEnd = 1,
+ kEventFlagEscapeKey = 2,
+ kEventFlagMouseDown = 4,
+ kEventFlagHotRectangle = 8
+ };
+
+ AVIPlayer(SegManager *segMan, EventManager *eventMan);
+ ~AVIPlayer();
+
+ /**
+ * Opens a stream to an AVI resource.
+ */
+ IOStatus open(const Common::String &fileName);
+
+ /**
+ * Initializes the AVI rendering parameters for the
+ * current AVI. This must be called after `open`.
+ */
+ IOStatus init1x(const int16 x, const int16 y, const int16 width, const int16 height);
+
+ /**
+ * Initializes the AVI rendering parameters for the
+ * current AVI, in pixel-doubling mode. This must
+ * be called after `open`.
+ */
+ IOStatus init2x(const int16 x, const int16 y);
+
+ /**
+ * Begins playback of the current AVI.
+ */
+ IOStatus play(const int16 from, const int16 to, const int16 showStyle, const bool cue);
+
+ /**
+ * Stops playback and closes the currently open AVI stream.
+ */
+ IOStatus close();
+
+ /**
+ * Seeks the currently open AVI stream to the given frame.
+ */
+ IOStatus cue(const uint16 frameNo);
+
+ /**
+ * Returns the duration of the current video.
+ */
+ uint16 getDuration() const;
+
+ /**
+ * Plays the AVI until an event occurs (e.g. user
+ * presses escape, clicks, etc.).
+ */
+ EventFlags playUntilEvent(const EventFlags flags);
+
+private:
+ typedef Common::HashMap<uint16, AVIStatus> StatusMap;
+
+ SegManager *_segMan;
+ EventManager *_eventMan;
+ Video::AVIDecoder *_decoder;
+
+ /**
+ * Playback status of the player.
+ */
+ AVIStatus _status;
+
+ /**
+ * The plane where the AVI will be drawn.
+ */
+ Plane *_plane;
+
+ /**
+ * The screen item representing the AVI surface,
+ * in 8bpp mode. In 24bpp mode, video is drawn
+ * directly to the screen.
+ */
+ ScreenItem *_screenItem;
+
+ /**
+ * The bitmap used to render video output in
+ * 8bpp mode.
+ */
+ reg_t _bitmap;
+
+ /**
+ * The rectangle where the video will be drawn,
+ * in game script coordinates.
+ */
+ Common::Rect _drawRect;
+
+ /**
+ * The scale buffer for pixel-doubled videos
+ * drawn in 24bpp mode.
+ */
+ void *_scaleBuffer;
+
+ /**
+ * In SCI2.1, whether or not the video should
+ * be pixel doubled for playback.
+ */
+ bool _pixelDouble;
+
+ /**
+ * Performs common initialisation for both
+ * scaled and unscaled videos.
+ */
+ void init();
+
+ /**
+ * Renders video without event input until the
+ * video is complete.
+ */
+ void renderVideo() const;
+
+ /**
+ * Renders a single frame of video.
+ */
+ void renderFrame() const;
+};
+
+#pragma mark -
#pragma mark VMDPlayer
/**
@@ -300,11 +496,17 @@ private:
class Video32 {
public:
Video32(SegManager *segMan, EventManager *eventMan) :
+ _SEQPlayer(segMan),
+ _AVIPlayer(segMan, eventMan),
_VMDPlayer(segMan, eventMan) {}
+ SEQPlayer &getSEQPlayer() { return _SEQPlayer; }
+ AVIPlayer &getAVIPlayer() { return _AVIPlayer; }
VMDPlayer &getVMDPlayer() { return _VMDPlayer; }
private:
+ SEQPlayer _SEQPlayer;
+ AVIPlayer _AVIPlayer;
VMDPlayer _VMDPlayer;
};
} // End of namespace Sci