/* 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. * * $URL$ * $Id$ * */ #include "gob/videoplayer.h" #include "gob/helper.h" #include "gob/global.h" #include "gob/dataio.h" #include "gob/video.h" #include "gob/draw.h" #include "gob/game.h" #include "gob/palanim.h" #include "gob/inter.h" #include "gob/map.h" #include "gob/sound/sound.h" namespace Gob { const char *VideoPlayer::_extensions[] = { "IMD", "IMD", "VMD", "RMD", "SMD" }; VideoPlayer::Video::Video(GobEngine *vm) : _vm(vm), _stream(0), _video(0) { } VideoPlayer::Video::~Video() { close(); } bool VideoPlayer::Video::open(const char *fileName, Type which, int16 width, int16 height) { close(); int16 handle = _vm->_dataIO->openData(fileName); if (handle < 0) { warning("Couldn't open video \"%s\": No such file", fileName); return false; } _stream = _vm->_dataIO->openAsStream(handle, true); if (which == kVideoTypeIMD) { _video = new Graphics::Imd(); } else if (which == kVideoTypePreIMD) { _video = new Graphics::PreImd(width, height); } else if (which == kVideoTypeVMD) { _video = new Graphics::Vmd(_vm->_video->_palLUT); } else if (which == kVideoTypeRMD) { _video = new Graphics::Vmd(_vm->_video->_palLUT); } else { warning("Couldn't open video \"%s\": Invalid video Type", fileName); close(); return false; } if (!_video->load(*_stream)) { warning("While loading video \"%s\"", fileName); close(); return false; } _fileName = fileName; _defaultX = _video->getX(); _defaultY = _video->getY(); return true; } void VideoPlayer::Video::close() { delete _video; delete _stream; _video = 0; _stream = 0; _fileName.clear(); memset(&_state, 0, sizeof(Graphics::CoktelVideo::State)); _defaultX = _defaultY = 0; } bool VideoPlayer::Video::isOpen() const { return (_video != 0); } const char *VideoPlayer::Video::getFileName() const { return _fileName.c_str(); } Graphics::CoktelVideo *VideoPlayer::Video::getVideo() { return _video; } const Graphics::CoktelVideo *VideoPlayer::Video::getVideo() const { return _video; } uint32 VideoPlayer::Video::getFeatures() const { return _video->getFeatures(); } Graphics::CoktelVideo::State VideoPlayer::Video::getState() const { return _state; } int16 VideoPlayer::Video::getDefaultX() const { return _defaultX; } int16 VideoPlayer::Video::getDefaultY() const { return _defaultY; } bool VideoPlayer::Video::hasExtraData(const char *fileName) const { if (!_video) return false; return _video->hasExtraData(fileName); } Common::MemoryReadStream *VideoPlayer::Video::getExtraData(const char *fileName) { if (!_video) return 0; return _video->getExtraData(fileName); } Graphics::CoktelVideo::State VideoPlayer::Video::nextFrame() { if (_video) _state = _video->nextFrame(); return _state; } VideoPlayer::VideoPlayer(GobEngine *vm) : _vm(vm) { _primaryVideo = new Video(vm); _ownSurf = false; _backSurf = false; _needBlit = false; _noCursorSwitch = false; _woodruffCohCottWorkaround = false; } VideoPlayer::~VideoPlayer() { delete _primaryVideo; for (uint i = 0; i < _videoSlots.size(); i++) delete _videoSlots[i]; } bool VideoPlayer::findFile(char *fileName, Type &which) { char *extStart = strrchr(fileName, '.'); // There's no empty extension, Or the filename with its current extension is not found if ((extStart) && ((extStart == (fileName + strlen(fileName) - 1)) || (!_vm->_dataIO->existData(fileName)))) { *extStart = 0; extStart = 0; } if (extStart) { // The requested file already has an extension. Verifying. int i; for (i = 0; i < ARRAYSIZE(_extensions); i++) { if (!scumm_stricmp(extStart + 1, _extensions[i])) { if ((which != kVideoTypeTry) && (which == ((Type) i))) { warning("Attempted to open video \"%s\", " "but requested a different type", fileName); return false; } which = (Type) i; break; } } if (i >= ARRAYSIZE(_extensions)) extStart = 0; } if (!extStart) { // No or unrecognized extension. Probing. int len = strlen(fileName); int i; for (i = 0; i < ARRAYSIZE(_extensions); i++) { if ((which == kVideoTypeTry) || (which == ((Type) i))) { fileName[len] = '.'; fileName[len + 1] = 0; strcat(fileName, _extensions[i]); if (_vm->_dataIO->existData(fileName)) { which = (Type) i; break; } } } if ((i >= ARRAYSIZE(_extensions)) || (which == kVideoTypeTry)) { fileName[len] = 0; warning("Couldn't open video \"%s\"", fileName); return false; } } return true; } bool VideoPlayer::primaryOpen(const char *videoFile, int16 x, int16 y, int32 flags, Type which, int16 width, int16 height) { char fileName[256]; strncpy0(fileName, videoFile, 250); if (!findFile(fileName, which)) return false; if (scumm_strnicmp(_primaryVideo->getFileName(), fileName, strlen(fileName))) { if (!_primaryVideo->open(fileName, which, width, height)) return false; // WORKAROUND: In some rare cases, the cursor should still be // displayed while a video is playing. _noCursorSwitch = false; if (_vm->getGameType() == kGameTypeLostInTime) { if (!scumm_stricmp(fileName, "PORTA03.IMD") || !scumm_stricmp(fileName, "PORTA03A.IMD") || !scumm_stricmp(fileName, "CALE1.IMD") || !scumm_stricmp(fileName, "AMIL2.IMD") || !scumm_stricmp(fileName, "AMIL3B.IMD") || !scumm_stricmp(fileName, "DELB.IMD")) _noCursorSwitch = true; } // WORKAROUND: In Woodruff, Coh Cott vanished in one video on her party. // This is a bug in video, so we work around it. _woodruffCohCottWorkaround = false; if (_vm->getGameType() == kGameTypeWoodruff) { if (!scumm_stricmp(fileName, "SQ32-03.VMD")) _woodruffCohCottWorkaround = true; } _ownSurf = false; if (!(flags & kFlagNoVideo)) { SurfaceDescPtr surf; if (flags & kFlagOtherSurface) { _ownSurf = true; _backSurf = false; surf = _vm->_video->initSurfDesc(_vm->_global->_videoMode, _primaryVideo->getVideo()->getWidth(), _primaryVideo->getVideo()->getHeight(), 0); _vm->_draw->_spritesArray[x] = surf; x = 0; } else if (flags & kFlagScreenSurface) { _ownSurf = true; _backSurf = false; surf = _vm->_video->initSurfDesc(_vm->_global->_videoMode, _vm->_width, _vm->_height, 0); _vm->_draw->_spritesArray[0] = surf; } else { _backSurf = ((flags & kFlagFrontSurface) == 0); surf = _vm->_draw->_spritesArray[_backSurf ? 21 : 20]; } _primaryVideo->getVideo()->setVideoMemory(surf->getVidMem(), surf->getWidth(), surf->getHeight()); } else _primaryVideo->getVideo()->setVideoMemory(); _needBlit = ((flags & kFlagUseBackSurfaceContent) != 0) && ((flags & kFlagFrontSurface) != 0); _primaryVideo->getVideo()->enableSound(*_vm->_mixer); } if (!_primaryVideo->isOpen()) return false; _primaryVideo->getVideo()->setFrameRate(_vm->_util->getFrameRate()); _primaryVideo->getVideo()->setXY(x, y); WRITE_VAR(7, _primaryVideo->getVideo()->getFramesCount()); return true; } bool VideoPlayer::primaryPlay(int16 startFrame, int16 lastFrame, int16 breakKey, uint16 palCmd, int16 palStart, int16 palEnd, int16 palFrame, int16 endFrame, bool fade, int16 reverseTo, bool forceSeek) { if (!_primaryVideo->isOpen()) return false; Graphics::CoktelVideo &video = *(_primaryVideo->getVideo()); breakKey = 27; if (startFrame < 0) startFrame = video.getCurrentFrame(); if (lastFrame < 0) lastFrame = video.getFramesCount() - 1; if (palFrame < 0) palFrame = startFrame; if (endFrame < 0) endFrame = lastFrame; palCmd &= 0x3F; int16 realStartFrame = startFrame; if (video.getCurrentFrame() != startFrame) { if (!forceSeek && (video.getFeatures() & Graphics::CoktelVideo::kFeaturesSound)) startFrame = video.getCurrentFrame(); else video.seekFrame(startFrame); } _vm->_draw->_showCursor = _noCursorSwitch ? 3 : 0; if (fade) _vm->_palAnim->fade(0, -2, 0); bool canceled = false; while (startFrame <= lastFrame) { if (doPlay(startFrame, breakKey, palCmd, palStart, palEnd, palFrame, endFrame, startFrame < realStartFrame)) { canceled = true; break; } evalBgShading(video); if (fade) { _vm->_palAnim->fade(_vm->_global->_pPaletteDesc, -2, 0); fade = false; } if (!_noCursorSwitch) video.waitEndFrame(); startFrame++; } evalBgShading(video); if (reverseTo >= 0) { int16 toFrame = video.getFramesCount() - reverseTo; for (int i = video.getCurrentFrame(); i >= toFrame; i--) { video.seekFrame(i, SEEK_SET, true); bool b = doPlay(i, breakKey, 0, 0, 0, 0, 0); evalBgShading(video); if (b) { _vm->_palAnim->fade(0, -2, 0); memset((char *)_vm->_draw->_vgaPalette, 0, 768); } if (!_noCursorSwitch) video.waitEndFrame(); } } evalBgShading(video); return canceled; } void VideoPlayer::primaryClose() { _primaryVideo->close(); } int VideoPlayer::slotOpen(const char *videoFile, Type which, int16 width, int16 height) { Video *video = new Video(_vm); char fileName[256]; strncpy0(fileName, videoFile, 250); if (!findFile(fileName, which)) { delete video; return -1; } if (!video->open(fileName, which, width, height)) { delete video; return -1; } video->getVideo()->setVideoMemory(); video->getVideo()->enableSound(*_vm->_mixer); int slot = getNextFreeSlot(); _videoSlots[slot] = video; WRITE_VAR(7, video->getVideo()->getFramesCount()); return slot; } int VideoPlayer::getNextFreeSlot() { uint slot; for (slot = 0; slot < _videoSlots.size(); slot++) if (!_videoSlots[slot]) break; if (slot == _videoSlots.size()) _videoSlots.push_back(0); return slot; } void VideoPlayer::slotPlay(int slot, int16 frame) { if ((slot < 0) || (((uint) slot) >= _videoSlots.size()) || !_videoSlots[slot]) return; Graphics::CoktelVideo &video = *(_videoSlots[slot]->getVideo()); if (frame < 0) frame = video.getCurrentFrame(); if (frame >= video.getFramesCount()) return; if (video.getCurrentFrame() != frame) video.seekFrame(frame); _videoSlots[slot]->nextFrame(); WRITE_VAR(11, frame); evalBgShading(video); } void VideoPlayer::slotClose(int slot) { if ((slot < 0) || (((uint) slot) >= _videoSlots.size()) || !_videoSlots[slot]) return; delete _videoSlots[slot]; _videoSlots[slot] = 0; } void VideoPlayer::slotCopyFrame(int slot, byte *dest, uint16 left, uint16 top, uint16 width, uint16 height, uint16 x, uint16 y, uint16 pitch, int16 transp) { if ((slot < 0) || (((uint) slot) >= _videoSlots.size()) || !_videoSlots[slot]) return; _videoSlots[slot]->getVideo()->copyCurrentFrame(dest, left, top, width, height, x, y, pitch, transp); } void VideoPlayer::slotCopyPalette(int slot, int16 palStart, int16 palEnd) { if ((slot < 0) || (((uint) slot) >= _videoSlots.size()) || !_videoSlots[slot]) return; copyPalette(*(_videoSlots[slot]->getVideo()), palStart, palEnd); } void VideoPlayer::slotWaitEndFrame(int slot, bool onlySound) { Video *video = getVideoBySlot(slot); if (video) { Graphics::CoktelVideo &cVideo = *video->getVideo(); if (!onlySound || (cVideo.getFeatures() & Graphics::CoktelVideo::kFeaturesSound)) cVideo.waitEndFrame(); } } bool VideoPlayer::slotIsOpen(int slot) const { if ((slot >= 0) && (((uint) slot) < _videoSlots.size()) && _videoSlots[slot]) return true; return false; } void VideoPlayer::slotSetDoubleMode(int slot, bool doubleMode) { Video *video = getVideoBySlot(slot); if (video) video->getVideo()->setDoubleMode(doubleMode); } const VideoPlayer::Video *VideoPlayer::getVideoBySlot(int slot) const { if (slot < 0) { if (_primaryVideo->isOpen()) return _primaryVideo; } else if (((uint) slot) < _videoSlots.size() && _videoSlots[slot]) return _videoSlots[slot]; return 0; } VideoPlayer::Video *VideoPlayer::getVideoBySlot(int slot) { if (slot < 0) { if (_primaryVideo->isOpen()) return _primaryVideo; } else if (((uint) slot) < _videoSlots.size() && _videoSlots[slot]) return _videoSlots[slot]; return 0; } const char *VideoPlayer::getFileName(int slot) const { const Video *video = getVideoBySlot(slot); if (video) return video->getFileName(); return ""; } uint16 VideoPlayer::getFlags(int slot) const { const Video *video = getVideoBySlot(slot); if (video) return video->getVideo()->getFlags(); return 0; } int16 VideoPlayer::getFramesCount(int slot) const { const Video *video = getVideoBySlot(slot); if (video) return video->getVideo()->getFramesCount(); return 0; } int16 VideoPlayer::getCurrentFrame(int slot) const { const Video *video = getVideoBySlot(slot); if (video) return video->getVideo()->getCurrentFrame(); return 0; } int16 VideoPlayer::getWidth(int slot) const { const Video *video = getVideoBySlot(slot); if (video) return video->getVideo()->getWidth(); return 0; } int16 VideoPlayer::getHeight(int slot) const { const Video *video = getVideoBySlot(slot); if (video) return video->getVideo()->getHeight(); return 0; } int16 VideoPlayer::getDefaultX(int slot) const { const Video *video = getVideoBySlot(slot); if (video) return video->getDefaultX(); return 0; } int16 VideoPlayer::getDefaultY(int slot) const { const Video *video = getVideoBySlot(slot); if (video) return video->getDefaultY(); return 0; } uint32 VideoPlayer::getFeatures(int slot) const { const Video *video = getVideoBySlot(slot); if (video) return video->getFeatures(); return 0; } Graphics::CoktelVideo::State VideoPlayer::getState(int slot) const { const Video *video = getVideoBySlot(slot); Graphics::CoktelVideo::State state; if (video) state = video->getState(); return state; } bool VideoPlayer::hasExtraData(const char *fileName, int slot) const { const Video *video = getVideoBySlot(slot); if (video) return video->hasExtraData(fileName); return false; } Common::MemoryReadStream *VideoPlayer::getExtraData(const char *fileName, int slot) { Video *video = getVideoBySlot(slot); if (video) return video->getExtraData(fileName); return 0; } void VideoPlayer::playFrame(int16 frame, int16 breakKey, uint16 palCmd, int16 palStart, int16 palEnd, int16 palFrame, int16 endFrame, bool noRetrace) { if (!_primaryVideo) return; Video &video = *_primaryVideo; Graphics::CoktelVideo &cVideo = *video.getVideo(); if (cVideo.getCurrentFrame() != frame) cVideo.seekFrame(frame); if (palFrame < 0) palFrame = 0; if (endFrame < 0) endFrame = cVideo.getFramesCount() - 1; bool modifiedPal = false; if ((frame == palFrame) || ((frame == endFrame) && (palCmd == 8))) { modifiedPal = true; _vm->_draw->_applyPal = true; if (palCmd >= 4) copyPalette(cVideo, palStart, palEnd); } if (modifiedPal && (palCmd == 8) && !_backSurf) _vm->_video->setFullPalette(_vm->_global->_pPaletteDesc); if (_needBlit) _vm->_draw->forceBlit(); Graphics::CoktelVideo::State state = video.nextFrame(); WRITE_VAR(11, frame); if (_woodruffCohCottWorkaround && (frame == 32)) { // WORKAROUND: This frame mistakenly masks Coh Cott, making her vanish // To prevent that, we'll never draw that part state.left += 50; } if (_needBlit) _vm->_draw->forceBlit(true); if (modifiedPal && (palCmd == 16)) { if (_backSurf) _vm->_draw->forceBlit(); _vm->_palAnim->fade(_vm->_global->_pPaletteDesc, -2, 0); _vm->_draw->_noInvalidated = true; _vm->_video->dirtyRectsAll(); } if ((state.flags & Graphics::CoktelVideo::kStatePalette) && (palCmd > 1)) { copyPalette(cVideo, palStart, palEnd); if (!_backSurf) _vm->_video->setFullPalette(_vm->_global->_pPaletteDesc); else _vm->_draw->_applyPal = true; } if (modifiedPal && (palCmd == 8) && _backSurf) _vm->_video->setFullPalette(_vm->_global->_pPaletteDesc); if (!_ownSurf) { if (_backSurf) { _vm->_draw->invalidateRect(state.left, state.top, state.right, state.bottom); _vm->_draw->blitInvalidated(); } else _vm->_video->dirtyRectsAdd(state.left, state.top, state.right, state.bottom); if (!noRetrace) _vm->_video->retrace(); } // Subtitle if (state.flags & Graphics::CoktelVideo::kStateSpeech) _vm->_draw->printTotText(state.speechId); if (modifiedPal && ((palCmd == 2) || (palCmd == 4))) _vm->_palAnim->fade(_vm->_global->_pPaletteDesc, -2, 0); } bool VideoPlayer::doPlay(int16 frame, int16 breakKey, uint16 palCmd, int16 palStart, int16 palEnd, int16 palFrame, int16 endFrame, bool noRetrace) { playFrame(frame, breakKey, palCmd, palStart, palEnd, palFrame, endFrame, noRetrace); _vm->_util->processInput(); if (_vm->shouldQuit()) { _primaryVideo->getVideo()->disableSound(); return true; } if (breakKey != 0) { _vm->_util->getMouseState(&_vm->_global->_inter_mouseX, &_vm->_global->_inter_mouseY, &_vm->_game->_mouseButtons); _vm->_inter->storeKey(_vm->_util->checkKey()); if (VAR(0) == (unsigned) breakKey) { _primaryVideo->getVideo()->disableSound(); // Seek to the last frame. Some scripts depend on that. _primaryVideo->getVideo()->seekFrame(endFrame, SEEK_SET, true); return true; } } return false; } void VideoPlayer::copyPalette(Graphics::CoktelVideo &video, int16 palStart, int16 palEnd) { if (!(video.getFeatures() & Graphics::CoktelVideo::kFeaturesPalette)) return; if (palStart < 0) palStart = 0; if (palEnd < 0) palEnd = 255; memcpy(((char *)(_vm->_global->_pPaletteDesc->vgaPal)) + palStart * 3, video.getPalette() + palStart * 3, (palEnd - palStart + 1) * 3); } void VideoPlayer::writeVideoInfo(const char *videoFile, int16 varX, int16 varY, int16 varFrames, int16 varWidth, int16 varHeight) { if (primaryOpen(videoFile)) { int16 x, y, width, height; x = _primaryVideo->getVideo()->getX(); y = _primaryVideo->getVideo()->getY(); width = _primaryVideo->getVideo()->getWidth(); height = _primaryVideo->getVideo()->getHeight(); if (VAR_OFFSET(varX) == 0xFFFFFFFF) _primaryVideo->getVideo()->getFrameCoords(1, x, y, width, height); WRITE_VAR_OFFSET(varX, x); WRITE_VAR_OFFSET(varY, y); WRITE_VAR_OFFSET(varFrames, _primaryVideo->getVideo()->getFramesCount()); WRITE_VAR_OFFSET(varWidth, width); WRITE_VAR_OFFSET(varHeight, height); primaryClose(); } else { WRITE_VAR_OFFSET(varX, (uint32) -1); WRITE_VAR_OFFSET(varY, (uint32) -1); WRITE_VAR_OFFSET(varFrames, (uint32) -1); WRITE_VAR_OFFSET(varWidth, (uint32) -1); WRITE_VAR_OFFSET(varHeight, (uint32) -1); } } void VideoPlayer::evalBgShading(Graphics::CoktelVideo &video) { if (video.isSoundPlaying()) _vm->_sound->bgShade(); else _vm->_sound->bgUnshade(); } } // End of namespace Gob