mirror of
https://github.com/scummvm/scummvm.git
synced 2026-06-20 05:45:29 +00:00
898 lines
27 KiB
C++
898 lines
27 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
*/
|
|
|
|
#include "audio/mixer.h" // for Audio::Mixer::kSFXSoundType
|
|
#include "common/config-manager.h" // for ConfMan
|
|
#include "common/textconsole.h" // for warning, error
|
|
#include "common/util.h" // for ARRAYSIZE
|
|
#include "common/system.h" // for g_system
|
|
#include "engine.h" // for Engine, g_engine
|
|
#include "engines/util.h" // for initGraphics
|
|
#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, ::kLowResX, ::kLo...
|
|
#include "sci/graphics/cursor32.h" // for GfxCursor32
|
|
#include "sci/graphics/frameout.h" // for GfxFrameout
|
|
#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/sci.h" // for SciEngine, g_sci, getSciVersion
|
|
#include "sci/graphics/video32.h"
|
|
#include "sci/video/seq_decoder.h" // for SEQDecoder
|
|
#include "video/avi_decoder.h" // for AVIDecoder
|
|
#include "video/coktel_decoder.h" // for AdvancedVMDDecoder
|
|
namespace Graphics { struct Surface; }
|
|
|
|
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 (_status == kAVINotOpen) {
|
|
return kIOFileNotFound;
|
|
}
|
|
|
|
_pixelDouble = false;
|
|
|
|
if (!width || !height) {
|
|
width = _decoder->getWidth();
|
|
height = _decoder->getHeight();
|
|
} else if (getSciVersion() == SCI_VERSION_2_1_EARLY && g_sci->getGameId() == GID_KQ7) {
|
|
// KQ7 1.51 provides an explicit width and height when it wants scaling,
|
|
// though the width and height it provides are not scaled
|
|
_pixelDouble = true;
|
|
width *= 2;
|
|
height *= 2;
|
|
}
|
|
|
|
// 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.1mid 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) {
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
init();
|
|
|
|
return kIOSuccess;
|
|
}
|
|
|
|
AVIPlayer::IOStatus AVIPlayer::init2x(const int16 x, const int16 y) {
|
|
if (_status == kAVINotOpen) {
|
|
return kIOFileNotFound;
|
|
}
|
|
|
|
_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;
|
|
|
|
bool useScreenDimensions = false;
|
|
if (g_sci->_gfxFrameout->_isHiRes && _decoder->getWidth() > 320) {
|
|
useScreenDimensions = true;
|
|
}
|
|
|
|
// KQ7 1.51 gives video position in screen coordinates, not game
|
|
// coordinates, because in SSCI they are passed to Video for Windows, which
|
|
// renders as an overlay on the game video. Because we put the video into a
|
|
// ScreenItem instead of rendering directly to the hardware surface, the
|
|
// coordinates need to be converted to game script coordinates
|
|
if (g_sci->getGameId() == GID_KQ7 && getSciVersion() == SCI_VERSION_2_1_EARLY) {
|
|
useScreenDimensions = !_pixelDouble;
|
|
// This y-translation is arbitrary, based on what roughly centers the
|
|
// videos in the game window
|
|
_drawRect.translate(-_drawRect.left / 2, -_drawRect.top * 2 / 3);
|
|
}
|
|
|
|
if (useScreenDimensions) {
|
|
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 {
|
|
// Attempting to draw a palettized cursor into a 24bpp surface will
|
|
// cause memory corruption, so hide the cursor in this mode (SCI did not
|
|
// have a 24bpp mode but just directed VFW to display videos instead)
|
|
g_sci->_gfxCursor32->hide();
|
|
|
|
const Buffer ¤tBuffer = 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 (_status == kAVINotOpen) {
|
|
return kIOFileNotFound;
|
|
}
|
|
|
|
if (from >= 0 && to > 0 && from <= to) {
|
|
_decoder->seekToFrame(from);
|
|
_decoder->setEndFrame(to);
|
|
}
|
|
|
|
if (!async) {
|
|
renderVideo();
|
|
} else if (getSciVersion() == SCI_VERSION_2_1_EARLY) {
|
|
playUntilEvent((EventFlags)(kEventFlagEnd | kEventFlagEscapeKey));
|
|
} else {
|
|
_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 ¤tBuffer = g_sci->_gfxFrameout->getCurrentBuffer();
|
|
const Graphics::PixelFormat format = Graphics::PixelFormat::createFormatCLUT8();
|
|
initGraphics(currentBuffer.screenWidth, currentBuffer.screenHeight, isHiRes, &format);
|
|
g_sci->_gfxCursor32->unhide();
|
|
}
|
|
|
|
_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;
|
|
}
|
|
|
|
// KQ7 1.51 encodes videos with palette entry 0 as white, which makes
|
|
// the area around the video turn white too, since it is coded to use
|
|
// palette entry 0. This happens to work in the original game because
|
|
// the video is rendered by VfW, not in the engine itself. To fix this,
|
|
// we just modify the incoming pixel data from the video so if a pixel
|
|
// is using entry 0, we change it to use entry 255, which is guaranteed
|
|
// to always be white
|
|
if (getSciVersion() == SCI_VERSION_2_1_EARLY && g_sci->getGameId() == GID_KQ7) {
|
|
uint8 *target = bitmap.getPixels();
|
|
const uint8 *source = (const uint8 *)surface->getPixels();
|
|
const uint8 *end = (const uint8 *)surface->getPixels() + surface->w * surface->h;
|
|
|
|
while (source != end) {
|
|
uint8 value = *source++;
|
|
*target++ = value == 0 ? 255 : value;
|
|
}
|
|
} else {
|
|
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;
|
|
}
|
|
|
|
// Prevent KQ7 1.51 from setting entry 0 to white
|
|
palette.colors[0].used = false;
|
|
|
|
g_sci->_gfxPalette32->submit(palette);
|
|
}
|
|
|
|
g_sci->_gfxFrameout->updateScreenItem(*_screenItem);
|
|
g_sci->getSciDebugger()->onFrame();
|
|
g_sci->_gfxFrameout->frameOut(true);
|
|
} else {
|
|
assert(surface->format.bytesPerPixel == 4);
|
|
|
|
Common::Rect drawRect(_drawRect);
|
|
|
|
if (_pixelDouble) {
|
|
const uint32 *source = (const uint32 *)surface->getPixels();
|
|
uint32 *target = (uint32 *)_scaleBuffer;
|
|
// target pitch here is in uint32s, not bytes
|
|
const uint16 pitch = surface->pitch / 2;
|
|
for (int y = 0; y < surface->h; ++y) {
|
|
for (int x = 0; x < surface->w; ++x) {
|
|
const uint32 value = *source++;
|
|
|
|
target[0] = value;
|
|
target[1] = value;
|
|
target[pitch] = value;
|
|
target[pitch + 1] = value;
|
|
target += 2;
|
|
}
|
|
target += pitch;
|
|
}
|
|
|
|
g_system->copyRectToScreen(_scaleBuffer, surface->pitch * 2, _drawRect.left, _drawRect.top, _drawRect.width(), _drawRect.height());
|
|
} else {
|
|
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;
|
|
|
|
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) :
|
|
_segMan(segMan),
|
|
_eventMan(eventMan),
|
|
_decoder(new Video::AdvancedVMDDecoder(Audio::Mixer::kSFXSoundType)),
|
|
|
|
_isOpen(false),
|
|
_isInitialized(false),
|
|
_yieldInterval(0),
|
|
_lastYieldedFrameNo(0),
|
|
|
|
_plane(nullptr),
|
|
_screenItem(nullptr),
|
|
_planeIsOwned(true),
|
|
_priority(0),
|
|
_doublePixels(false),
|
|
_stretchVertical(false),
|
|
_blackLines(false),
|
|
_leaveScreenBlack(false),
|
|
_leaveLastFrame(false),
|
|
|
|
_blackoutPlane(nullptr),
|
|
|
|
_startColor(0),
|
|
_endColor(255),
|
|
_blackPalette(false),
|
|
|
|
_boostPercent(100),
|
|
_boostStartColor(0),
|
|
_boostEndColor(255),
|
|
|
|
_showCursor(false) {}
|
|
|
|
VMDPlayer::~VMDPlayer() {
|
|
close();
|
|
delete _decoder;
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark VMDPlayer - Playback
|
|
|
|
VMDPlayer::IOStatus VMDPlayer::open(const Common::String &fileName, const OpenFlags flags) {
|
|
if (_isOpen) {
|
|
error("Attempted to play %s, but another VMD was loaded", fileName.c_str());
|
|
}
|
|
|
|
if (_decoder->loadFile(fileName)) {
|
|
if (flags & kOpenFlagMute) {
|
|
_decoder->setVolume(0);
|
|
}
|
|
_isOpen = true;
|
|
return kIOSuccess;
|
|
} else {
|
|
return kIOError;
|
|
}
|
|
}
|
|
|
|
void VMDPlayer::init(const int16 x, const int16 y, const PlayFlags flags, const int16 boostPercent, const int16 boostStartColor, const int16 boostEndColor) {
|
|
_x = getSciVersion() >= SCI_VERSION_3 ? x : (x & ~1);
|
|
_y = y;
|
|
_doublePixels = flags & kPlayFlagDoublePixels;
|
|
_blackLines = ConfMan.getBool("enable_black_lined_video") && (flags & kPlayFlagBlackLines);
|
|
_boostPercent = 100 + (flags & kPlayFlagBoost ? boostPercent : 0);
|
|
_boostStartColor = CLIP<int16>(boostStartColor, 0, 255);
|
|
_boostEndColor = CLIP<int16>(boostEndColor, 0, 255);
|
|
_leaveScreenBlack = flags & kPlayFlagLeaveScreenBlack;
|
|
_leaveLastFrame = flags & kPlayFlagLeaveLastFrame;
|
|
_blackPalette = flags & kPlayFlagBlackPalette;
|
|
_stretchVertical = flags & kPlayFlagStretchVertical;
|
|
}
|
|
|
|
VMDPlayer::IOStatus VMDPlayer::close() {
|
|
if (!_isOpen) {
|
|
return kIOSuccess;
|
|
}
|
|
|
|
_decoder->close();
|
|
_isOpen = false;
|
|
_isInitialized = false;
|
|
|
|
if (!_planeIsOwned && _screenItem != nullptr) {
|
|
g_sci->_gfxFrameout->deleteScreenItem(*_screenItem);
|
|
_segMan->freeBitmap(_screenItem->_celInfo.bitmap);
|
|
_screenItem = nullptr;
|
|
} else if (_plane != nullptr) {
|
|
g_sci->_gfxFrameout->deletePlane(*_plane);
|
|
_plane = nullptr;
|
|
}
|
|
|
|
if (!_leaveLastFrame && _leaveScreenBlack) {
|
|
// This call *actually* deletes the plane/screen item
|
|
g_sci->_gfxFrameout->frameOut(true);
|
|
}
|
|
|
|
if (_blackoutPlane != nullptr) {
|
|
g_sci->_gfxFrameout->deletePlane(*_blackoutPlane);
|
|
_blackoutPlane = nullptr;
|
|
}
|
|
|
|
if (!_leaveLastFrame && !_leaveScreenBlack) {
|
|
// This call *actually* deletes the blackout plane
|
|
g_sci->_gfxFrameout->frameOut(true);
|
|
}
|
|
|
|
if (!_showCursor) {
|
|
g_sci->_gfxCursor32->unhide();
|
|
}
|
|
|
|
_lastYieldedFrameNo = 0;
|
|
_planeIsOwned = true;
|
|
_priority = 0;
|
|
return kIOSuccess;
|
|
}
|
|
|
|
VMDPlayer::VMDStatus VMDPlayer::getStatus() const {
|
|
if (!_isOpen) {
|
|
return kVMDNotOpen;
|
|
}
|
|
if (_decoder->isPaused()) {
|
|
return kVMDPaused;
|
|
}
|
|
if (_decoder->isPlaying()) {
|
|
return kVMDPlaying;
|
|
}
|
|
if (_decoder->endOfVideo()) {
|
|
return kVMDFinished;
|
|
}
|
|
return kVMDOpen;
|
|
}
|
|
|
|
VMDPlayer::EventFlags VMDPlayer::kernelPlayUntilEvent(const EventFlags flags, const int16 lastFrameNo, const int16 yieldInterval) {
|
|
assert(lastFrameNo >= -1);
|
|
|
|
const int32 maxFrameNo = (int32)(_decoder->getFrameCount() - 1);
|
|
|
|
if ((flags & kEventFlagToFrame) && lastFrameNo > 0) {
|
|
_decoder->setEndFrame(MIN((int32)lastFrameNo, maxFrameNo));
|
|
} else {
|
|
_decoder->setEndFrame(maxFrameNo);
|
|
}
|
|
|
|
if (flags & kEventFlagYieldToVM) {
|
|
_yieldInterval = 3;
|
|
if (yieldInterval == -1 && !(flags & kEventFlagToFrame)) {
|
|
_yieldInterval = lastFrameNo;
|
|
} else if (yieldInterval != -1) {
|
|
_yieldInterval = MIN((int32)yieldInterval, maxFrameNo);
|
|
}
|
|
} else {
|
|
_yieldInterval = maxFrameNo;
|
|
}
|
|
|
|
return playUntilEvent(flags);
|
|
}
|
|
|
|
VMDPlayer::EventFlags VMDPlayer::playUntilEvent(const EventFlags flags) {
|
|
// Flushing all the keyboard and mouse events out of the event manager to
|
|
// avoid letting any events queued from before the video started from
|
|
// accidentally activating an event callback
|
|
for (;;) {
|
|
const SciEvent event = _eventMan->getSciEvent(SCI_EVENT_KEYBOARD | SCI_EVENT_MOUSE_PRESS | SCI_EVENT_MOUSE_RELEASE | SCI_EVENT_QUIT);
|
|
if (event.type == SCI_EVENT_NONE) {
|
|
break;
|
|
} else if (event.type == SCI_EVENT_QUIT) {
|
|
return kEventFlagEnd;
|
|
}
|
|
}
|
|
|
|
_decoder->pauseVideo(false);
|
|
|
|
if (flags & kEventFlagReverse) {
|
|
// NOTE: This flag may not work properly since SSCI does not care
|
|
// if a video has audio, but the VMD decoder does.
|
|
const bool success = _decoder->setReverse(true);
|
|
assert(success);
|
|
_decoder->setVolume(0);
|
|
}
|
|
|
|
if (!_isInitialized) {
|
|
_isInitialized = true;
|
|
|
|
if (!_showCursor) {
|
|
g_sci->_gfxCursor32->hide();
|
|
}
|
|
|
|
Common::Rect vmdRect(_x,
|
|
_y,
|
|
_x + _decoder->getWidth(),
|
|
_y + _decoder->getHeight());
|
|
ScaleInfo vmdScaleInfo;
|
|
|
|
if (!_blackoutRect.isEmpty() && _planeIsOwned) {
|
|
_blackoutPlane = new Plane(_blackoutRect);
|
|
g_sci->_gfxFrameout->addPlane(*_blackoutPlane);
|
|
}
|
|
|
|
if (_doublePixels) {
|
|
vmdScaleInfo.x = 256;
|
|
vmdScaleInfo.y = 256;
|
|
vmdScaleInfo.signal = kScaleSignalDoScaling32;
|
|
vmdRect.right += vmdRect.width();
|
|
vmdRect.bottom += vmdRect.height();
|
|
} else if (_stretchVertical) {
|
|
vmdScaleInfo.y = 256;
|
|
vmdScaleInfo.signal = kScaleSignalDoScaling32;
|
|
vmdRect.bottom += vmdRect.height();
|
|
}
|
|
|
|
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;
|
|
|
|
reg_t bitmapId;
|
|
SciBitmap &vmdBitmap = *_segMan->allocateBitmap(&bitmapId, vmdRect.width(), vmdRect.height(), 255, 0, 0, screenWidth, screenHeight, 0, false, false);
|
|
|
|
if (screenWidth != scriptWidth || screenHeight != scriptHeight) {
|
|
mulru(vmdRect, Ratio(scriptWidth, screenWidth), Ratio(scriptHeight, screenHeight), 1);
|
|
}
|
|
|
|
CelInfo32 vmdCelInfo;
|
|
vmdCelInfo.bitmap = bitmapId;
|
|
_decoder->setSurfaceMemory(vmdBitmap.getPixels(), vmdBitmap.getWidth(), vmdBitmap.getHeight(), 1);
|
|
|
|
if (_planeIsOwned) {
|
|
_x = 0;
|
|
_y = 0;
|
|
_plane = new Plane(vmdRect, kPlanePicColored);
|
|
if (_priority) {
|
|
_plane->_priority = _priority;
|
|
}
|
|
g_sci->_gfxFrameout->addPlane(*_plane);
|
|
_screenItem = new ScreenItem(_plane->_object, vmdCelInfo, Common::Point(), vmdScaleInfo);
|
|
} else {
|
|
_screenItem = new ScreenItem(_plane->_object, vmdCelInfo, Common::Point(_x, _y), vmdScaleInfo);
|
|
if (_priority) {
|
|
_screenItem->_priority = _priority;
|
|
}
|
|
}
|
|
|
|
if (_blackLines) {
|
|
_screenItem->_drawBlackLines = true;
|
|
}
|
|
|
|
// NOTE: There was code for positioning the screen item using insetRect
|
|
// here, but none of the game scripts seem to use this functionality.
|
|
|
|
g_sci->_gfxFrameout->addScreenItem(*_screenItem);
|
|
|
|
_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();
|
|
}
|
|
|
|
const int currentFrameNo = _decoder->getCurFrame();
|
|
|
|
if (_yieldInterval > 0 &&
|
|
currentFrameNo != _lastYieldedFrameNo &&
|
|
(currentFrameNo % _yieldInterval) == 0
|
|
) {
|
|
_lastYieldedFrameNo = currentFrameNo;
|
|
stopFlag = kEventFlagYieldToVM;
|
|
break;
|
|
}
|
|
|
|
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;
|
|
if (getSciVersion() < SCI_VERSION_3) {
|
|
while ((event = _eventMan->getSciEvent(SCI_EVENT_KEYBOARD)),
|
|
event.type != SCI_EVENT_NONE) {
|
|
if (event.character == SCI_KEY_ESC) {
|
|
stop = true;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
stop = (event.character == SCI_KEY_ESC);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
_decoder->pauseVideo(true);
|
|
return stopFlag;
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark VMDPlayer - Rendering
|
|
|
|
void VMDPlayer::renderFrame() const {
|
|
// This writes directly to the CelObjMem we already created,
|
|
// so no need to take its return value
|
|
_decoder->decodeNextFrame();
|
|
|
|
// NOTE: Normally this would write a hunk palette at the end of the
|
|
// video bitmap that CelObjMem would read out and submit, but instead
|
|
// we are just submitting it directly here because the decoder exposes
|
|
// this information a little bit differently than the one in SSCI
|
|
const bool dirtyPalette = _decoder->hasDirtyPalette();
|
|
if (dirtyPalette) {
|
|
Palette palette;
|
|
palette.timestamp = g_sci->getTickCount();
|
|
for (uint16 i = 0; i < _startColor; ++i) {
|
|
palette.colors[i].used = false;
|
|
}
|
|
for (uint16 i = _endColor; i < 256; ++i) {
|
|
palette.colors[i].used = false;
|
|
}
|
|
if (_blackPalette) {
|
|
for (uint16 i = _startColor; i <= _endColor; ++i) {
|
|
palette.colors[i].r = palette.colors[i].g = palette.colors[i].b = 0;
|
|
palette.colors[i].used = true;
|
|
}
|
|
} else {
|
|
fillPalette(palette);
|
|
}
|
|
|
|
g_sci->_gfxPalette32->submit(palette);
|
|
g_sci->_gfxFrameout->updateScreenItem(*_screenItem);
|
|
g_sci->_gfxFrameout->frameOut(true);
|
|
|
|
if (_blackPalette) {
|
|
fillPalette(palette);
|
|
g_sci->_gfxPalette32->submit(palette);
|
|
g_sci->_gfxPalette32->updateForFrame();
|
|
g_sci->_gfxPalette32->updateHardware();
|
|
}
|
|
} else {
|
|
g_sci->_gfxFrameout->updateScreenItem(*_screenItem);
|
|
g_sci->getSciDebugger()->onFrame();
|
|
g_sci->_gfxFrameout->frameOut(true);
|
|
g_sci->_gfxFrameout->throttle();
|
|
}
|
|
}
|
|
|
|
void VMDPlayer::fillPalette(Palette &palette) const {
|
|
const byte *vmdPalette = _decoder->getPalette() + _startColor * 3;
|
|
for (uint16 i = _startColor; i <= _endColor; ++i) {
|
|
int16 r = *vmdPalette++;
|
|
int16 g = *vmdPalette++;
|
|
int16 b = *vmdPalette++;
|
|
|
|
if (_boostPercent != 100 && i >= _boostStartColor && i <= _boostEndColor) {
|
|
r = CLIP<int16>(r * _boostPercent / 100, 0, 255);
|
|
g = CLIP<int16>(g * _boostPercent / 100, 0, 255);
|
|
b = CLIP<int16>(b * _boostPercent / 100, 0, 255);
|
|
}
|
|
|
|
palette.colors[i].r = r;
|
|
palette.colors[i].g = g;
|
|
palette.colors[i].b = b;
|
|
palette.colors[i].used = true;
|
|
}
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark VMDPlayer - Palette
|
|
|
|
void VMDPlayer::restrictPalette(const uint8 startColor, const int16 endColor) {
|
|
_startColor = startColor;
|
|
// At least GK2 sends 256 as the end color, which is wrong,
|
|
// but works in the original engine as the storage size is 4 bytes
|
|
// and used values are clamped to 0-255
|
|
_endColor = MIN((int16)255, endColor);
|
|
}
|
|
|
|
} // End of namespace Sci
|