diff options
author | Paul Gilbert | 2014-02-17 18:32:23 -0500 |
---|---|---|
committer | Paul Gilbert | 2014-02-17 18:32:23 -0500 |
commit | 7d1cc31e1e8158b2fc0989933c088941602c4193 (patch) | |
tree | dc5d78f5cace328427ee832cbf1b1af47da10264 | |
parent | 3333084eaf43c47d30dbb0b08d87e6b57c501d11 (diff) | |
parent | dc8b58a0f9a89b9a59cdf6db300e61c4ac7264e9 (diff) | |
download | scummvm-rg350-7d1cc31e1e8158b2fc0989933c088941602c4193.tar.gz scummvm-rg350-7d1cc31e1e8158b2fc0989933c088941602c4193.tar.bz2 scummvm-rg350-7d1cc31e1e8158b2fc0989933c088941602c4193.zip |
Merge pull request #436 from dreammaster/voyeur
New Engine: Voyeur by Phillips Interactive
-rw-r--r-- | engines/voyeur/animation.cpp | 485 | ||||
-rw-r--r-- | engines/voyeur/animation.h | 196 | ||||
-rw-r--r-- | engines/voyeur/configure.engine | 4 | ||||
-rw-r--r-- | engines/voyeur/data.cpp | 359 | ||||
-rw-r--r-- | engines/voyeur/data.h | 231 | ||||
-rw-r--r-- | engines/voyeur/debugger.cpp | 148 | ||||
-rw-r--r-- | engines/voyeur/debugger.h | 72 | ||||
-rw-r--r-- | engines/voyeur/detection.cpp | 182 | ||||
-rw-r--r-- | engines/voyeur/detection_tables.h | 42 | ||||
-rw-r--r-- | engines/voyeur/events.cpp | 624 | ||||
-rw-r--r-- | engines/voyeur/events.h | 153 | ||||
-rw-r--r-- | engines/voyeur/files.cpp | 1659 | ||||
-rw-r--r-- | engines/voyeur/files.h | 650 | ||||
-rw-r--r-- | engines/voyeur/files_threads.cpp | 1738 | ||||
-rw-r--r-- | engines/voyeur/graphics.cpp | 1065 | ||||
-rw-r--r-- | engines/voyeur/graphics.h | 134 | ||||
-rw-r--r-- | engines/voyeur/module.mk | 23 | ||||
-rw-r--r-- | engines/voyeur/sound.cpp | 85 | ||||
-rw-r--r-- | engines/voyeur/sound.h | 60 | ||||
-rw-r--r-- | engines/voyeur/staticres.cpp | 142 | ||||
-rw-r--r-- | engines/voyeur/staticres.h | 63 | ||||
-rw-r--r-- | engines/voyeur/voyeur.cpp | 919 | ||||
-rw-r--r-- | engines/voyeur/voyeur.h | 321 | ||||
-rw-r--r-- | engines/voyeur/voyeur_game.cpp | 1423 |
24 files changed, 10778 insertions, 0 deletions
diff --git a/engines/voyeur/animation.cpp b/engines/voyeur/animation.cpp new file mode 100644 index 0000000000..f81573f733 --- /dev/null +++ b/engines/voyeur/animation.cpp @@ -0,0 +1,485 @@ +/* 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 "voyeur/animation.h" +#include "voyeur/staticres.h" +#include "voyeur/voyeur.h" +#include "common/endian.h" +#include "common/memstream.h" +#include "common/system.h" +#include "audio/decoders/raw.h" +#include "graphics/surface.h" + +namespace Voyeur { + +// Number of audio frames to keep audio track topped up when playing back video +#define SOUND_FRAMES_READAHEAD 3 + +RL2Decoder::RL2Decoder(Audio::Mixer::SoundType soundType) : _soundType(soundType) { + _paletteStart = 0; + _fileStream = nullptr; + _soundFrameNumber = -1; +} + +RL2Decoder::~RL2Decoder() { + close(); +} + +bool RL2Decoder::loadVideo(int videoId) { + Common::String filename = Common::String::format("%s.rl2", + ::Voyeur::SZ_FILENAMES[videoId * 2]); + return loadFile(filename); +} + +bool RL2Decoder::loadFile(const Common::String &file, bool palFlag) { + bool result = VideoDecoder::loadFile(file); + _paletteStart = palFlag ? 0 : 128; + return result; +} + +bool RL2Decoder::loadStream(Common::SeekableReadStream *stream) { + close(); + + // Load basic file information + _fileStream = stream; + _header.load(stream); + _paletteStart = 0; + + // Check RL2 magic number + if (!_header.isValid()) { + warning("RL2Decoder::loadStream(): attempted to load non-RL2 data (0x%08X)", _header._signature); + return false; + } + + // Add an audio track if sound is present + _audioTrack = nullptr; + if (_header._soundRate) { + _audioTrack = new RL2AudioTrack(_header, stream, _soundType); + addTrack(_audioTrack); + } + + // Create a video track + _videoTrack = new RL2VideoTrack(_header, _audioTrack, stream); + addTrack(_videoTrack); + + // Load the offset/sizes of the video's audio data + _soundFrames.reserve(_header._numFrames); + for (int frameNumber = 0; frameNumber < _header._numFrames; ++frameNumber) { + int offset = _header._frameOffsets[frameNumber]; + int size = _header._frameSoundSizes[frameNumber]; + + _soundFrames.push_back(SoundFrame(offset, size)); + } + + return true; +} + +const Common::List<Common::Rect> *RL2Decoder::getDirtyRects() const { + if (_videoTrack) + return _videoTrack->getDirtyRects(); + + return 0; +} + +void RL2Decoder::clearDirtyRects() { + if (_videoTrack) + _videoTrack->clearDirtyRects(); +} + +void RL2Decoder::copyDirtyRectsToBuffer(uint8 *dst, uint pitch) { + if (_videoTrack) + _videoTrack->copyDirtyRectsToBuffer(dst, pitch); +} + +void RL2Decoder::readNextPacket() { + int frameNumber = getCurFrame(); + RL2AudioTrack *audioTrack = getAudioTrack(); + + // Handle queueing sound data + if (_soundFrameNumber == -1) + _soundFrameNumber = (frameNumber == -1) ? 0 : frameNumber; + + while (audioTrack->numQueuedStreams() < SOUND_FRAMES_READAHEAD && + (_soundFrameNumber < (int)_soundFrames.size())) { + _fileStream->seek(_soundFrames[_soundFrameNumber]._offset); + audioTrack->queueSound(_fileStream, _soundFrames[_soundFrameNumber]._size); + ++_soundFrameNumber; + } +} + +bool RL2Decoder::seekIntern(const Audio::Timestamp &where) { + _soundFrameNumber = -1; + return VideoDecoder::seekIntern(where); +} + +void RL2Decoder::close() { + VideoDecoder::close(); + delete _fileStream; + _fileStream = nullptr; + _soundFrameNumber = -1; +} + +/*------------------------------------------------------------------------*/ + +RL2Decoder::SoundFrame::SoundFrame(int offset, int size) { + _offset = offset; + _size = size; +} + +/*------------------------------------------------------------------------*/ + +RL2Decoder::RL2FileHeader::RL2FileHeader() { + _frameOffsets = nullptr; + _frameSoundSizes = nullptr; +} + +RL2Decoder::RL2FileHeader::~RL2FileHeader() { + delete[] _frameOffsets; + delete[] _frameSoundSizes; +} + +void RL2Decoder::RL2FileHeader::load(Common::SeekableReadStream *stream) { + stream->seek(0); + + _form = stream->readUint32LE(); + _backSize = stream->readUint32LE(); + _signature = stream->readUint32BE(); + + if (!isValid()) + return; + + _dataSize = stream->readUint32LE(); + _numFrames = stream->readUint32LE(); + _method = stream->readUint16LE(); + _soundRate = stream->readUint16LE(); + _rate = stream->readUint16LE(); + _channels = stream->readUint16LE(); + _defSoundSize = stream->readUint16LE(); + _videoBase = stream->readUint16LE(); + _colorCount = stream->readUint32LE(); + assert(_colorCount <= 256); + + stream->read(_palette, 768); + + // Skip over background frame, if any, and the list of overall frame sizes (which we don't use) + stream->skip(_backSize + 4 * _numFrames); + + // Load frame offsets + delete[] _frameOffsets; + _frameOffsets = new uint32[_numFrames]; + for (int i = 0; i < _numFrames; ++i) + _frameOffsets[i] = stream->readUint32LE(); + + // Load the size of the sound portion of each frame + delete[] _frameSoundSizes; + _frameSoundSizes = new int[_numFrames]; + for (int i = 0; i < _numFrames; ++i) + _frameSoundSizes[i] = stream->readUint32LE() & 0xffff; +} + +bool RL2Decoder::RL2FileHeader::isValid() const { + return _signature == MKTAG('R','L','V','2') || _signature == MKTAG('R','L','V','3'); +} + +Common::Rational RL2Decoder::RL2FileHeader::getFrameRate() const { + return (_soundRate > 0) ? Common::Rational(_rate, _defSoundSize) : + Common::Rational(11025, 1103); +} + +/*------------------------------------------------------------------------*/ + +RL2Decoder::RL2VideoTrack::RL2VideoTrack(const RL2FileHeader &header, RL2AudioTrack *audioTrack, + Common::SeekableReadStream *stream): + _header(header), _audioTrack(audioTrack), _fileStream(stream) { + + // Set up surfaces + _surface = new Graphics::Surface(); + _surface->create(320, 200, Graphics::PixelFormat::createFormatCLUT8()); + + _hasBackFrame = header._backSize != 0; + + _backSurface = NULL; + if (_hasBackFrame) + initBackSurface(); + + _videoBase = header._videoBase; + _dirtyPalette = header._colorCount > 0; + + _curFrame = -1; + _initialFrame = true; +} + +RL2Decoder::RL2VideoTrack::~RL2VideoTrack() { + // Free surfaces + _surface->free(); + delete _surface; + if (_backSurface) { + _backSurface->free(); + delete _backSurface; + } +} + +void RL2Decoder::RL2VideoTrack::initBackSurface() { + _backSurface = new Graphics::Surface(); + _backSurface->create(320, 200, Graphics::PixelFormat::createFormatCLUT8()); +} + +bool RL2Decoder::RL2VideoTrack::seek(const Audio::Timestamp &time) { + int frame = getFrameAtTime(time); + + if (frame < 0 || frame >= _header._numFrames) + return false; + + _curFrame = frame; + return true; +} + +uint16 RL2Decoder::RL2VideoTrack::getWidth() const { + return _surface->w; +} + +uint16 RL2Decoder::RL2VideoTrack::getHeight() const { + return _surface->h; +} + +Graphics::PixelFormat RL2Decoder::RL2VideoTrack::getPixelFormat() const { + return _surface->format; +} + +const Graphics::Surface *RL2Decoder::RL2VideoTrack::decodeNextFrame() { + if (_initialFrame && _hasBackFrame) { + // Read in the initial background frame + _fileStream->seek(0x324); + rl2DecodeFrameWithoutTransparency(0); + + Common::copy((byte *)_surface->getPixels(), (byte *)_surface->getPixels() + (320 * 200), + (byte *)_backSurface->getPixels()); + _dirtyRects.push_back(Common::Rect(0, 0, _surface->w, _surface->h)); + _initialFrame = false; + } + + // Move to the next frame data + _fileStream->seek(_header._frameOffsets[++_curFrame]); + + // If there's any sound data, pass it to the audio track + _fileStream->seek(_header._frameSoundSizes[_curFrame], SEEK_CUR); + + // Decode the graphic data using the appropriate method depending on whether the animation + // has a background or just raw frames without any background transparency + if (_backSurface) { + rl2DecodeFrameWithTransparency(_videoBase); + } else { + rl2DecodeFrameWithoutTransparency(_videoBase); + } + + return _surface; +} + +void RL2Decoder::RL2VideoTrack::copyDirtyRectsToBuffer(uint8 *dst, uint pitch) { + for (Common::List<Common::Rect>::const_iterator it = _dirtyRects.begin(); it != _dirtyRects.end(); ++it) { + for (int y = (*it).top; y < (*it).bottom; ++y) { + const int x = (*it).left; + memcpy(dst + y * pitch + x, (byte *)_surface->getPixels() + y * getWidth() + x, (*it).right - x); + } + } + + clearDirtyRects(); +} + +void RL2Decoder::RL2VideoTrack::copyFrame(uint8 *data) { + memcpy((byte *)_surface->getPixels(), data, getWidth() * getHeight()); + + // Redraw + _dirtyRects.clear(); + _dirtyRects.push_back(Common::Rect(0, 0, getWidth(), getHeight())); +} + +void RL2Decoder::RL2VideoTrack::rl2DecodeFrameWithoutTransparency(int screenOffset) { + if (screenOffset == -1) + screenOffset = _videoBase; + int frameSize = _surface->w * _surface->h - screenOffset; + byte *destP = (byte *)_surface->getPixels(); + + // Main frame decode loop + byte nextByte; + for (;;) { + nextByte = _fileStream->readByte(); + + if (nextByte < 0x80) { + // Simple byte to copy to output + assert(frameSize > 0); + *destP++ = nextByte; + --frameSize; + } else if (nextByte > 0x80) { + // Lower 7 bits a run length for the following byte + int runLength = _fileStream->readByte(); + runLength = MIN(runLength, frameSize); + + Common::fill(destP, destP + runLength, nextByte & 0x7f); + destP += runLength; + frameSize -= runLength; + } else { + // Follow byte run length for zeroes. If zero, indicates end of image + int runLength = _fileStream->readByte(); + if (runLength == 0) + break; + + runLength = MIN(runLength, frameSize); + Common::fill(destP, destP + runLength, 0); + destP += runLength; + frameSize -= runLength; + } + } + + // If there's any remaining screen area, zero it out + byte *endP = (byte *)_surface->getPixels() + _surface->w * _surface->h; + if (destP != endP) + Common::fill(destP, endP, 0); +} + +void RL2Decoder::RL2VideoTrack::rl2DecodeFrameWithTransparency(int screenOffset) { + int frameSize = _surface->w * _surface->h - screenOffset; + byte *refP = (byte *)_backSurface->getPixels(); + byte *destP = (byte *)_surface->getPixels(); + + // If there's a screen offset, copy unchanged initial pixels from reference surface + if (screenOffset > 0) + Common::copy(refP, refP + screenOffset, destP); + + // Main decode loop + while (frameSize > 0) { + byte nextByte = _fileStream->readByte(); + + if (nextByte == 0) { + // Move one single byte from reference surface + assert(frameSize > 0); + destP[screenOffset] = refP[screenOffset]; + ++screenOffset; + --frameSize; + } else if (nextByte < 0x80) { + // Single 7-bit pixel to output (128-255) + assert(frameSize > 0); + destP[screenOffset] = nextByte | 0x80; + ++screenOffset; + --frameSize; + } else if (nextByte == 0x80) { + int runLength = _fileStream->readByte(); + if (runLength == 0) + return; + + // Run length of transparency (i.e. pixels to copy from reference frame) + runLength = MIN(runLength, frameSize); + + Common::copy(refP + screenOffset, refP + screenOffset + runLength, destP + screenOffset); + screenOffset += runLength; + frameSize -= runLength; + } else { + // Run length of a single pixel value + int runLength = _fileStream->readByte(); + runLength = MIN(runLength, frameSize); + + Common::fill(destP + screenOffset, destP + screenOffset + runLength, nextByte); + screenOffset += runLength; + frameSize -= runLength; + } + } + + // If there's a remaining section of the screen not covered, copy it from reference surface + if (screenOffset < (_surface->w * _surface->h)) + Common::copy(refP + screenOffset, refP + (_surface->w * _surface->h), destP + screenOffset); +} + +Graphics::Surface *RL2Decoder::RL2VideoTrack::getBackSurface() { + if (!_backSurface) + initBackSurface(); + + return _backSurface; +} + +/*------------------------------------------------------------------------*/ + +RL2Decoder::RL2AudioTrack::RL2AudioTrack(const RL2FileHeader &header, Common::SeekableReadStream *stream, Audio::Mixer::SoundType soundType): + _header(header), _soundType(soundType) { + // Create audio straem for the audio track + _audStream = Audio::makeQueuingAudioStream(_header._rate, _header._channels == 2); +} + +RL2Decoder::RL2AudioTrack::~RL2AudioTrack() { + delete _audStream; +} + +void RL2Decoder::RL2AudioTrack::queueSound(Common::SeekableReadStream *stream, int size) { + // Queue the sound data + byte *data = (byte *)malloc(size); + stream->read(data, size); + Common::MemoryReadStream *memoryStream = new Common::MemoryReadStream(data, size, + DisposeAfterUse::YES); + + _audStream->queueAudioStream(Audio::makeRawStream(memoryStream, _header._rate, + Audio::FLAG_UNSIGNED, DisposeAfterUse::YES), DisposeAfterUse::YES); +} + +Audio::AudioStream *RL2Decoder::RL2AudioTrack::getAudioStream() const { + return _audStream; +} + +void RL2Decoder::play(VoyeurEngine *vm, int resourceOffset, + byte *frames, byte *imgPos) { + vm->flipPageAndWait(); + int paletteStart = getPaletteStart(); + int paletteCount = getPaletteCount(); + + PictureResource videoFrame(getVideoTrack()->getBackSurface()); + int picCtr = 0; + while (!vm->shouldQuit() && !endOfVideo() && !vm->_eventsManager._mouseClicked) { + if (hasDirtyPalette()) { + const byte *palette = getPalette(); + + vm->_graphicsManager.setPalette128(palette, paletteStart, paletteCount); + } + + if (needsUpdate()) { + if (frames) { + // If reached a point where a new background is needed, load it + // and copy over to the video decoder + if (getCurFrame() >= READ_LE_UINT16(frames + picCtr * 4)) { + PictureResource *newPic = vm->_bVoy->boltEntry(0x302 + picCtr)._picResource; + Common::Point pt(READ_LE_UINT16(imgPos + 4 * picCtr) - 32, + READ_LE_UINT16(imgPos + 4 * picCtr + 2) - 20); + + vm->_graphicsManager.sDrawPic(newPic, &videoFrame, pt); + ++picCtr; + } + } + + // Decode the next frame and display + const Graphics::Surface *frame = decodeNextFrame(); + Common::copy((const byte *)frame->getPixels(), (const byte *)frame->getPixels() + 320 * 200, + (byte *)vm->_graphicsManager._screenSurface.getPixels()); + } + + vm->_eventsManager.getMouseInfo(); + g_system->delayMillis(10); + } +} + +} // End of namespace Voyeur diff --git a/engines/voyeur/animation.h b/engines/voyeur/animation.h new file mode 100644 index 0000000000..35199f6b93 --- /dev/null +++ b/engines/voyeur/animation.h @@ -0,0 +1,196 @@ +/* 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. + * + */ + +#ifndef VOYEUR_ANIMATION_H +#define VOYEUR_ANIMATION_H + +#include "video/video_decoder.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" +#include "audio/timestamp.h" +#include "common/array.h" +#include "common/list.h" +#include "common/rect.h" +#include "common/stream.h" +#include "voyeur/files.h" + +namespace Voyeur { + +class VoyeurEngine; + +/** + * Decoder for RL2 videos. + * + * Video decoder used in engines: + * - voyeur + */ +class RL2Decoder : public Video::VideoDecoder { +private: + class RL2FileHeader { + public: + RL2FileHeader(); + ~RL2FileHeader(); + + int _channels; + int _colorCount; + int _numFrames; + int _rate; + int _soundRate; + int _videoBase; + int *_frameSoundSizes; + + uint32 _backSize; + uint32 _signature; + uint32 *_frameOffsets; + + byte _palette[768]; + + void load(Common::SeekableReadStream *stream); + Common::Rational getFrameRate() const; + bool isValid() const; + + private: + uint32 _form; + uint32 _dataSize; + int _method; + int _defSoundSize; + }; + + class SoundFrame { + public: + int _offset; + int _size; + + SoundFrame(int offset, int size); + }; + + class RL2AudioTrack : public AudioTrack { + private: + Audio::Mixer::SoundType _soundType; + const RL2FileHeader &_header; + Audio::QueuingAudioStream *_audStream; + protected: + Audio::AudioStream *getAudioStream() const; + public: + RL2AudioTrack(const RL2FileHeader &header, Common::SeekableReadStream *stream, + Audio::Mixer::SoundType soundType); + ~RL2AudioTrack(); + + Audio::Mixer::SoundType getSoundType() const { return _soundType; } + int numQueuedStreams() const { return _audStream->numQueuedStreams(); } + virtual bool isSeekable() const { return true; } + virtual bool seek(const Audio::Timestamp &time) { return true; } + + void queueSound(Common::SeekableReadStream *stream, int size); + }; + + class RL2VideoTrack : public FixedRateVideoTrack { + public: + RL2VideoTrack(const RL2FileHeader &header, RL2AudioTrack *audioTrack, + Common::SeekableReadStream *stream); + ~RL2VideoTrack(); + + uint16 getWidth() const; + uint16 getHeight() const; + Graphics::Surface *getSurface() { return _surface; } + Graphics::Surface *getBackSurface(); + Graphics::PixelFormat getPixelFormat() const; + int getCurFrame() const { return _curFrame; } + int getFrameCount() const { return _header._numFrames; } + const Graphics::Surface *decodeNextFrame(); + const byte *getPalette() const { _dirtyPalette = false; return _header._palette; } + int getPaletteCount() const { return _header._colorCount; } + bool hasDirtyPalette() const { return _dirtyPalette; } + const Common::List<Common::Rect> *getDirtyRects() const { return &_dirtyRects; } + void clearDirtyRects() { _dirtyRects.clear(); } + void copyDirtyRectsToBuffer(uint8 *dst, uint pitch); + + virtual Common::Rational getFrameRate() const { return _header.getFrameRate(); } + virtual bool isSeekable() const { return true; } + virtual bool seek(const Audio::Timestamp &time); + private: + Common::SeekableReadStream *_fileStream; + const RL2FileHeader &_header; + RL2AudioTrack *_audioTrack; + Graphics::Surface *_surface; + Graphics::Surface *_backSurface; + bool _hasBackFrame; + + mutable bool _dirtyPalette; + + bool _initialFrame; + int _curFrame; + uint32 _videoBase; + uint32 *_frameOffsets; + + Common::List<Common::Rect> _dirtyRects; + + void copyFrame(uint8 *data); + void rl2DecodeFrameWithTransparency(int screenOffset); + void rl2DecodeFrameWithoutTransparency(int screenOffset = -1); + void initBackSurface(); + }; + +private: + RL2AudioTrack *_audioTrack; + RL2VideoTrack *_videoTrack; + Common::SeekableReadStream *_fileStream; + Audio::Mixer::SoundType _soundType; + RL2FileHeader _header; + int _paletteStart; + Common::Array<SoundFrame> _soundFrames; + int _soundFrameNumber; + const Common::List<Common::Rect> *getDirtyRects() const; + + void clearDirtyRects(); + void copyDirtyRectsToBuffer(uint8 *dst, uint pitch); + int getPaletteStart() const { return _paletteStart; } + const RL2FileHeader &getHeader() { return _header; } + virtual void readNextPacket(); + virtual bool seekIntern(const Audio::Timestamp &time); + +public: + RL2Decoder(Audio::Mixer::SoundType soundType = Audio::Mixer::kPlainSoundType); + virtual ~RL2Decoder(); + + virtual void close(); + + bool loadStream(Common::SeekableReadStream *stream); + bool loadFile(const Common::String &file, bool palFlag = false); + bool loadVideo(int videoId); + int getPaletteCount() const { return _header._colorCount; } + + /** + * Play back a given Voyeur RL2 video + * @param vm Engine reference + * @param resourceOffset Starting resource to use for frame pictures + * @param frames Optional frame numbers resource for when to apply image data + * @param imgPos Position to draw image data + */ + void play(VoyeurEngine *vm, int resourceOffset = 0, byte *frames = NULL, byte *imgPos = NULL); + RL2VideoTrack *getVideoTrack() { return _videoTrack; } + RL2AudioTrack *getAudioTrack() { return _audioTrack; } +}; + +} // End of namespace Voyeur + +#endif /* VOYEUR_ANIMATION_H */ diff --git a/engines/voyeur/configure.engine b/engines/voyeur/configure.engine new file mode 100644 index 0000000000..23891fccb7 --- /dev/null +++ b/engines/voyeur/configure.engine @@ -0,0 +1,4 @@ + +# This file is included from the main "configure" script +# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] +add_engine voyeur "Voyeur" no diff --git a/engines/voyeur/data.cpp b/engines/voyeur/data.cpp new file mode 100644 index 0000000000..3a7c75fc2e --- /dev/null +++ b/engines/voyeur/data.cpp @@ -0,0 +1,359 @@ +/* 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 "voyeur/data.h" +#include "voyeur/voyeur.h" + +namespace Voyeur { + +void VoyeurEvent::synchronize(Common::Serializer &s) { + s.syncAsByte(_hour); + s.syncAsByte(_minute); + s.syncAsByte(_isAM); + s.syncAsByte(_type); + s.syncAsSint16LE(_audioVideoId); + s.syncAsSint16LE(_computerOn); + s.syncAsSint16LE(_computerOff); + s.syncAsSint16LE(_dead); +} + +/*------------------------------------------------------------------------*/ + +SVoy::SVoy() { + // Initialize all the data fields + _abortInterface = false; + _fadeICF0 = false; + _isAM = false; + Common::fill(&_phoneCallsReceived[0], &_phoneCallsReceived[5], false); + Common::fill(&_roomHotspotsEnabled[0], &_roomHotspotsEnabled[20], false); + _victimMurdered = false; + + _audioVisualStartTime = 0; + _audioVisualDuration = 0; + _boltGroupId2 = 0; + _computerTextId = 0; + _computerTimeMin = _computerTimeMax = 0; + _curICF0 = 0; + _eventCount = 0; + _fadingStep1 = 0; + _fadingStep2 = 0; + _fadingType = 0; + _incriminatedVictimNumber = 0; + _musicStartTime = 0; + _playStampMode = 0; + _switchBGNum = 0; + _transitionId = 0; + _victimNumber = 0; + _videoEventId = 0; + _vocSecondsOffset = 0; + _RTANum = 0; + _RTVLimit = 0; + _RTVNum = 0; + _viewBounds = nullptr; + Common::fill(&_evPicPtrs[0], &_evPicPtrs[6], (PictureResource *)nullptr); + Common::fill(&_evCmPtrs[0], &_evCmPtrs[6], (CMapResource *)nullptr); + _curICF1 = 0; + _policeEvent = 0; + + _eventFlags = EVTFLAG_TIME_DISABLED; + _fadingAmount1 = _fadingAmount2 = 127; + _murderThreshold = 9999; + _aptLoadMode = -1; + _eventFlags |= EVTFLAG_100; + _totalPhoneCalls = 0; +} + +void SVoy::setVm(VoyeurEngine *vm) { + _vm = vm; +} + +void SVoy::addEvent(int hour, int minute, VoyeurEventType type, int audioVideoId, + int on, int off, int dead) { + VoyeurEvent &e = _events[_eventCount++]; + + e._type = type; + e._hour = hour; + e._minute = minute; + e._isAM = hour < 12; + e._audioVideoId = audioVideoId; + e._computerOn = on; + e._computerOff = off; + e._dead = dead; +} + +void SVoy::synchronize(Common::Serializer &s) { + s.syncAsByte(_isAM); + s.syncAsSint16LE(_RTANum); + s.syncAsSint16LE(_RTVNum); + s.syncAsSint16LE(_switchBGNum); + + _videoHotspotTimes.synchronize(s); + _audioHotspotTimes.synchronize(s); + _evidenceHotspotTimes.synchronize(s); + + for (int idx = 0; idx < 20; ++idx) { + s.syncAsByte(_roomHotspotsEnabled[idx]); + } + + s.syncAsSint16LE(_audioVisualStartTime); + s.syncAsSint16LE(_audioVisualDuration); + s.syncAsSint16LE(_vocSecondsOffset); + s.syncAsSint16LE(_abortInterface); + s.syncAsSint16LE(_playStampMode); + s.syncAsSint16LE(_aptLoadMode); + s.syncAsSint16LE(_transitionId); + s.syncAsSint16LE(_RTVLimit); + s.syncAsSint16LE(_eventFlags); + s.syncAsSint16LE(_boltGroupId2); + + s.syncAsSint16LE(_musicStartTime); + s.syncAsSint16LE(_totalPhoneCalls); + s.syncAsSint16LE(_computerTextId); + s.syncAsSint16LE(_computerTimeMin); + s.syncAsSint16LE(_computerTimeMax); + s.syncAsSint16LE(_victimMurdered); + s.syncAsSint16LE(_murderThreshold); + + // Events + s.syncAsUint16LE(_eventCount); + for (int idx = 0; idx < _eventCount; ++idx) + _events[idx].synchronize(s); + + s.syncAsSint16LE(_fadingAmount1); + s.syncAsSint16LE(_fadingAmount2); + s.syncAsSint16LE(_fadingStep1); + s.syncAsSint16LE(_fadingStep2); + s.syncAsSint16LE(_fadingType); + s.syncAsSint16LE(_victimNumber); + s.syncAsSint16LE(_incriminatedVictimNumber); + s.syncAsSint16LE(_videoEventId); + + if (s.isLoading()) { + // Reset apartment loading mode to initial game value + _aptLoadMode = 140; + _viewBounds = nullptr; + } +} + +void SVoy::addVideoEventStart() { + VoyeurEvent &e = _events[_eventCount]; + e._hour = _vm->_gameHour; + e._minute = _vm->_gameMinute; + e._isAM = _isAM; + e._type = EVTYPE_VIDEO; + e._audioVideoId = _vm->_audioVideoId; + e._computerOn = _vocSecondsOffset; + e._dead = _vm->_eventsManager._videoDead; +} + +void SVoy::addVideoEventEnd() { + VoyeurEvent &e = _events[_eventCount]; + e._computerOff = _RTVNum - _audioVisualStartTime - _vocSecondsOffset; + if (_eventCount < (TOTAL_EVENTS - 1)) + ++_eventCount; +} + +void SVoy::addAudioEventStart() { + VoyeurEvent &e = _events[_eventCount]; + e._hour = _vm->_gameHour; + e._minute = _vm->_gameMinute; + e._isAM = _isAM; + e._type = EVTYPE_AUDIO; + e._audioVideoId = _vm->_audioVideoId; + e._computerOn = _vocSecondsOffset; + e._dead = _vm->_eventsManager._videoDead; +} + +void SVoy::addAudioEventEnd() { + VoyeurEvent &e = _events[_eventCount]; + e._computerOff = _RTVNum - _audioVisualStartTime - _vocSecondsOffset; + if (_eventCount < (TOTAL_EVENTS - 1)) + ++_eventCount; +} + +void SVoy::addEvidEventStart(int v) { + VoyeurEvent &e = _events[_eventCount]; + e._hour = _vm->_gameHour; + e._minute = _vm->_gameMinute; + e._isAM = _isAM; + e._type = EVTYPE_EVID; + e._audioVideoId = _vm->_playStampGroupId; + e._computerOn = _boltGroupId2; + e._computerOff = v; +} + +void SVoy::addEvidEventEnd(int totalPages) { + VoyeurEvent &e = _events[_eventCount]; + e._dead = totalPages; + if (_eventCount < (TOTAL_EVENTS - 1)) + ++_eventCount; +} + +void SVoy::addComputerEventStart() { + VoyeurEvent &e = _events[_eventCount]; + e._hour = _vm->_gameHour; + e._minute = _vm->_gameMinute; + e._isAM = _isAM; + e._type = EVTYPE_COMPUTER; + e._audioVideoId = _vm->_playStampGroupId; + e._computerOn = _computerTextId; +} + +void SVoy::addComputerEventEnd(int v) { + VoyeurEvent &e = _events[_eventCount]; + e._computerOff = v; + if (_eventCount < (TOTAL_EVENTS - 1)) + ++_eventCount; +} + +void SVoy::reviewAnEvidEvent(int eventIndex) { + VoyeurEvent &e = _events[eventIndex]; + _vm->_playStampGroupId = e._audioVideoId; + _boltGroupId2 = e._computerOn; + int frameOff = e._computerOff; + + if (_vm->_bVoy->getBoltGroup(_vm->_playStampGroupId)) { + _vm->_graphicsManager._backColors = _vm->_bVoy->boltEntry(_vm->_playStampGroupId + 1)._cMapResource; + _vm->_graphicsManager._backgroundPage = _vm->_bVoy->boltEntry(_vm->_playStampGroupId)._picResource; + (*_vm->_graphicsManager._vPort)->setupViewPort(_vm->_graphicsManager._backgroundPage); + _vm->_graphicsManager._backColors->startFade(); + + _vm->doEvidDisplay(frameOff, e._dead); + _vm->_bVoy->freeBoltGroup(_vm->_playStampGroupId); + _vm->_playStampGroupId = -1; + + if (_boltGroupId2 != -1) { + _vm->_bVoy->freeBoltGroup(_boltGroupId2); + _boltGroupId2 = -1; + } + } +} + +void SVoy::reviewComputerEvent(int eventIndex) { + VoyeurEvent &e = _events[eventIndex]; + _vm->_playStampGroupId = e._audioVideoId; + _computerTextId = e._computerOn; + + if (_vm->_bVoy->getBoltGroup(_vm->_playStampGroupId)) { + _vm->_graphicsManager._backColors = _vm->_bVoy->boltEntry(_vm->_playStampGroupId + 1)._cMapResource; + _vm->_graphicsManager._backgroundPage = _vm->_bVoy->boltEntry(_vm->_playStampGroupId)._picResource; + (*_vm->_graphicsManager._vPort)->setupViewPort(_vm->_graphicsManager._backgroundPage); + _vm->_graphicsManager._backColors->startFade(); + _vm->flipPageAndWaitForFade(); + + _vm->getComputerBrush(); + _vm->flipPageAndWait(); + _vm->doComputerText(e._computerOff); + + _vm->_bVoy->freeBoltGroup(0x4900); + _vm->_bVoy->freeBoltGroup(_vm->_playStampGroupId); + _vm->_playStampGroupId = -1; + } +} + +bool SVoy::checkForKey() { + _vm->_controlPtr->_state->_victimEvidenceIndex = 0; + if (_vm->_voy._victimMurdered) + return false; + + for (int eventIdx = 0; eventIdx < _eventCount; ++eventIdx) { + VoyeurEvent &e = _events[eventIdx]; + + switch (e._type) { + case EVTYPE_VIDEO: + switch (_vm->_controlPtr->_state->_victimIndex) { + case 1: + if (e._audioVideoId == 33 && e._computerOn < 2 && e._computerOff >= 38) + _vm->_controlPtr->_state->_victimEvidenceIndex = 1; + break; + + case 2: + if (e._audioVideoId == 47 && e._computerOn < 2 && e._computerOff >= 9) + _vm->_controlPtr->_state->_victimEvidenceIndex = 2; + break; + + case 3: + if (e._audioVideoId == 46 && e._computerOn < 2 && e._computerOff > 2) + _vm->_controlPtr->_state->_victimEvidenceIndex = 3; + break; + + case 4: + if (e._audioVideoId == 40 && e._computerOn < 2 && e._computerOff > 6) + _vm->_controlPtr->_state->_victimEvidenceIndex = 4; + break; + + default: + break; + } + break; + + case EVTYPE_AUDIO: + switch (_vm->_controlPtr->_state->_victimIndex) { + case 1: + if (e._audioVideoId == 8 && e._computerOn < 2 && e._computerOff > 26) + _vm->_controlPtr->_state->_victimEvidenceIndex = 1; + break; + + case 3: + if (e._audioVideoId == 20 && e._computerOn < 2 && e._computerOff > 28) + _vm->_controlPtr->_state->_victimEvidenceIndex = 3; + if (e._audioVideoId == 35 && e._computerOn < 2 && e._computerOff > 18) + _vm->_controlPtr->_state->_victimEvidenceIndex = 3; + break; + + default: + break; + } + break; + + case EVTYPE_EVID: + switch (_vm->_controlPtr->_state->_victimIndex) { + case 4: + if (e._audioVideoId == 0x2400 && e._computerOn == 0x4f00 && e._computerOff == 17) + _vm->_controlPtr->_state->_victimEvidenceIndex = 4; + + default: + break; + } + break; + + case EVTYPE_COMPUTER: + switch (_vm->_controlPtr->_state->_victimIndex) { + case 2: + if (e._computerOn == 13 && e._computerOff > 76) + _vm->_controlPtr->_state->_victimEvidenceIndex = 2; + break; + + default: + break; + } + break; + } + + if (_vm->_controlPtr->_state->_victimEvidenceIndex == _vm->_controlPtr->_state->_victimIndex) + return true; + } + + return false; +} + +} // End of namespace Voyeur diff --git a/engines/voyeur/data.h b/engines/voyeur/data.h new file mode 100644 index 0000000000..8eb11ff390 --- /dev/null +++ b/engines/voyeur/data.h @@ -0,0 +1,231 @@ +/* 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. + * + */ + +#ifndef VOYEUR_DATA_H +#define VOYEUR_DATA_H + +#include "common/scummsys.h" +#include "common/serializer.h" +#include "voyeur/files.h" + +namespace Voyeur { + +#define TOTAL_EVENTS 1000 + +enum VoyeurEventType { EVTYPE_VIDEO = 1, EVTYPE_AUDIO = 2, EVTYPE_EVID = 3, + EVTYPE_COMPUTER = 4 }; + +enum EventFlag { EVTFLAG_TIME_DISABLED = 1, EVTFLAG_2 = 2, EVTFLAG_8 = 8, EVTFLAG_RECORDING = 0x10, + EVTFLAG_40 = 0x40, EVTFLAG_VICTIM_PRESET = 0x80, EVTFLAG_100 = 0x100 }; + +struct VoyeurEvent { + int _hour; + int _minute; + bool _isAM; + VoyeurEventType _type; + int _audioVideoId; + int _computerOn; + int _computerOff; + int _dead; + + void synchronize(Common::Serializer &s); +}; + +class VoyeurEngne; + +/** + * Encapsulates a list of the time expired ranges that hotspots in the mansion + * view are enabled for in a given time period. + */ +template<int SLOTS> +class HotspotTimes { +public: + int _min[SLOTS][20]; // Min time expired + int _max[SLOTS][20]; // Max time expired + + HotspotTimes() { + reset(); + } + + /** + * Resets the data to an initial state + */ + void reset() { + for (int hotspotIdx = 0; hotspotIdx < 20; ++hotspotIdx) { + for (int slotIdx = 0; slotIdx < SLOTS; ++slotIdx) { + _min[slotIdx][hotspotIdx] = 9999; + _max[slotIdx][hotspotIdx] = 0; + } + } + } + + /** + * Synchronise the data + */ + void synchronize(Common::Serializer &s) { + for (int slotIndex = 0; slotIndex < SLOTS; ++slotIndex) { + for (int hotspotIndex = 0; hotspotIndex < 20; ++hotspotIndex) { + s.syncAsSint16LE(_min[slotIndex][hotspotIndex]); + s.syncAsSint16LE(_max[slotIndex][hotspotIndex]); + } + } + } + + /** + * Returns true if the given value is in the range specified by the + * min and max at the given hotspot and slot indexes + */ + bool isInRange(int slotIndex, int hotspotIndex, int v) const { + return _min[slotIndex][hotspotIndex] <= v && + v < _max[slotIndex][hotspotIndex]; + } +}; + +class SVoy { +private: + VoyeurEngine *_vm; + +public: + bool _abortInterface; + bool _fadeICF0; // Useless variable? (always the same value) + bool _isAM; + bool _phoneCallsReceived[5]; + bool _roomHotspotsEnabled[20]; + bool _victimMurdered; + + int _aptLoadMode; + int _audioVisualStartTime; + int _audioVisualDuration; + int _boltGroupId2; + int _computerTextId; + int _computerTimeMin; + int _computerTimeMax; + int _curICF0; // Useless variable + int _eventCount; + int _eventFlags; + int _fadingAmount1; + int _fadingAmount2; + int _fadingStep1; + int _fadingStep2; + int _fadingType; + int _incriminatedVictimNumber; + int _murderThreshold; + int _musicStartTime; + int _playStampMode; + int _switchBGNum; + int _totalPhoneCalls; + int _transitionId; + int _victimNumber; + int _videoEventId; + int _vocSecondsOffset; + int _RTANum; + int _RTVLimit; + int _RTVNum; + + HotspotTimes<3> _audioHotspotTimes; + HotspotTimes<3> _evidenceHotspotTimes; + HotspotTimes<8> _videoHotspotTimes; + + Common::Rect _rect4E4; + RectResource *_viewBounds; + PictureResource *_evPicPtrs[6]; + CMapResource *_evCmPtrs[6]; + VoyeurEvent _events[TOTAL_EVENTS]; + + SVoy(); + void setVm(VoyeurEngine *vm); + + /** + * Synchronise the data + */ + void synchronize(Common::Serializer &s); + + /** + * Add an event to the list of game events that have occurred + */ + void addEvent(int hour, int minute, VoyeurEventType type, int audioVideoId, + int on, int off, int dead); + + /** + * Adds the start of a video event happening + */ + void addVideoEventStart(); + + /** + * Adds the finish of a video event happening + */ + void addVideoEventEnd(); + + /** + * Adds the start of an audio event happening + */ + void addAudioEventStart(); + + /** + * Adsd the finish of an audio event happening + */ + void addAudioEventEnd(); + + /** + * Adds the start of an evidence event happening + */ + void addEvidEventStart(int v); + + /** + * Adds the finish of an evidence event happening + */ + void addEvidEventEnd(int totalPages); + + /** + * Adds the start of a computer event happening + */ + void addComputerEventStart(); + + /** + * Adds the finish of a computer event happening + */ + void addComputerEventEnd(int v); + + /** + * Review a previously recorded evidence event + */ + void reviewAnEvidEvent(int eventIndex); + + /** + * Review a previously recorded computer event + */ + void reviewComputerEvent(int eventIndex); + + /** + * Checks for key information in determining what kind of murder + * should take place + */ + bool checkForKey(); + +private: + int _curICF1; // Useless variable + int _policeEvent; +}; + +} // End of namespace Voyeur + +#endif /* VOYEUR_DATA_H */ diff --git a/engines/voyeur/debugger.cpp b/engines/voyeur/debugger.cpp new file mode 100644 index 0000000000..b1bc2633ee --- /dev/null +++ b/engines/voyeur/debugger.cpp @@ -0,0 +1,148 @@ +/* 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 "voyeur/debugger.h" +#include "voyeur/graphics.h" +#include "voyeur/voyeur.h" +#include "voyeur/staticres.h" + +namespace Voyeur { + +Debugger::Debugger() : GUI::Debugger() { + // Register methods + DCmd_Register("continue", WRAP_METHOD(Debugger, Cmd_Exit)); + DCmd_Register("exit", WRAP_METHOD(Debugger, Cmd_Exit)); + DCmd_Register("time", WRAP_METHOD(Debugger, Cmd_Time)); + DCmd_Register("hotspots", WRAP_METHOD(Debugger, Cmd_Hotspots)); + DCmd_Register("mouse", WRAP_METHOD(Debugger, Cmd_Mouse)); + + // Set fields + _isTimeActive = true; + _showMousePosition = false; +} + +static const int TIME_STATES[] = { + 0, 31, 0, 43, 59, 0, 67, 75, 85, 93, 0, 0, 111, 121, 0, 0 +}; + +bool Debugger::Cmd_Time(int argc, const char **argv) { + if (argc < 2) { + // Get the current day and time of day + Common::String dtString = _vm->getDayName(); + Common::String timeString = _vm->getTimeOfDay(); + if (!timeString.empty()) + dtString += " " + timeString; + + DebugPrintf("Time period = %d, date/time is: %s, time is %s\n", + _vm->_voy._transitionId, dtString.c_str(), _isTimeActive ? "on" : "off"); + DebugPrintf("Format: %s [on | off | 1..17 | val <amount>]\n\n", argv[0]); + } else { + if (!strcmp(argv[1], "on")) { + _isTimeActive = true; + DebugPrintf("Time is now on\n\n"); + } else if (!strcmp(argv[1], "off")) { + _isTimeActive = false; + DebugPrintf("Time is now off\n\n"); + } else if (!strcmp(argv[1], "val")) { + if (argc < 3) { + DebugPrintf("Time expired is currently %d.\n", _vm->_voy._RTVNum); + } else { + _vm->_voy._RTVNum = atoi(argv[2]); + DebugPrintf("Time expired is now %d.\n", _vm->_voy._RTVNum); + } + } else { + int timeId = atoi(argv[1]); + if (timeId >= 1 && timeId <= 17) { + int stateId = TIME_STATES[timeId - 1]; + if (!stateId) { + DebugPrintf("Given time period is not used in-game\n"); + } else { + DebugPrintf("Changing to time period: %d\n", timeId); + if (_vm->_mainThread->goToState(-1, stateId)) + _vm->_mainThread->parsePlayCommands(); + + return false; + } + } else { + DebugPrintf("Unknown parameter\n\n"); + } + } + } + + return true; +} + +bool Debugger::Cmd_Hotspots(int argc, const char **argv) { + BoltEntry &boltEntry = _vm->_bVoy->boltEntry(_vm->_playStampGroupId + 1); + if (!boltEntry._rectResource) { + DebugPrintf("No hotspots available\n"); + } else { + Common::Array<RectEntry> &hotspots = boltEntry._rectResource->_entries; + + for (uint hotspotIdx = 0; hotspotIdx < hotspots.size(); ++hotspotIdx) { + Common::String pos = Common::String::format("(%d,%d->%d,%d)", + hotspots[hotspotIdx].left, hotspots[hotspotIdx].top, + hotspots[hotspotIdx].right, hotspots[hotspotIdx].bottom); + + for (int arrIndex = 0; arrIndex < 3; ++arrIndex) { + if (_vm->_voy._audioHotspotTimes._min[arrIndex][hotspotIdx] != 9999) { + DebugPrintf("Hotspot %d %s Audio slot %d, time: %d to %d\n", + hotspotIdx, pos.c_str(), arrIndex, + _vm->_voy._audioHotspotTimes._min[arrIndex][hotspotIdx], + _vm->_voy._audioHotspotTimes._max[arrIndex][hotspotIdx]); + } + + if (_vm->_voy._evidenceHotspotTimes._min[arrIndex][hotspotIdx] != 9999) { + DebugPrintf("Hotspot %d %s Evidence slot %d, time: %d to %d\n", + hotspotIdx, pos.c_str(), arrIndex, + _vm->_voy._evidenceHotspotTimes._min[arrIndex][hotspotIdx], + _vm->_voy._evidenceHotspotTimes._max[arrIndex][hotspotIdx]); + } + } + + for (int arrIndex = 0; arrIndex < 8; ++arrIndex) { + if (_vm->_voy._videoHotspotTimes._min[arrIndex][hotspotIdx] != 9999) { + DebugPrintf("Hotspot %d %s Video slot %d, time: %d to %d\n", + hotspotIdx, pos.c_str(), arrIndex, + _vm->_voy._videoHotspotTimes._min[arrIndex][hotspotIdx], + _vm->_voy._videoHotspotTimes._max[arrIndex][hotspotIdx]); + } + } + } + } + + DebugPrintf("\n"); + return true; +} + +bool Debugger::Cmd_Mouse(int argc, const char **argv) { + if (argc < 2) { + DebugPrintf("mouse [ on | off ]\n"); + } else { + _showMousePosition = !strcmp(argv[1], "on"); + DebugPrintf("Mouse position is now %s\n", _showMousePosition ? "on" : "off"); + } + + return true; +} + +} // End of namespace Voyeur diff --git a/engines/voyeur/debugger.h b/engines/voyeur/debugger.h new file mode 100644 index 0000000000..43eaa5f474 --- /dev/null +++ b/engines/voyeur/debugger.h @@ -0,0 +1,72 @@ +/* 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. + * + */ + +#ifndef VOYEUR_DEBUGGER_H +#define VOYEUR_DEBUGGER_H + +#include "common/scummsys.h" +#include "gui/debugger.h" + +namespace Voyeur { + +class VoyeurEngine; + +class Debugger : public GUI::Debugger { +private: + VoyeurEngine *_vm; +public: + /** + * Specifies whether time should pass, and the video camera's batteries go down + * @default true + */ + bool _isTimeActive; + + /* + * Specifies whether to show the current mouse position on the screen + */ + bool _showMousePosition; +protected: + /** + * Turn time on or off, set the current time period, or the camera delay + * within the current time period. + */ + bool Cmd_Time(int argc, const char **argv); + + /** + * List the active hotspots during the current time period + */ + bool Cmd_Hotspots(int argc, const char **argv); + + /** + * Toggle showing the mouse on the screen + */ + bool Cmd_Mouse(int argc, const char **argv); +public: + Debugger(); + virtual ~Debugger() {} + void setVm(VoyeurEngine *vm) { _vm = vm; } + +}; + +} // End of namespace Voyeur + +#endif diff --git a/engines/voyeur/detection.cpp b/engines/voyeur/detection.cpp new file mode 100644 index 0000000000..ab3932bea1 --- /dev/null +++ b/engines/voyeur/detection.cpp @@ -0,0 +1,182 @@ +/* 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 "voyeur/voyeur.h" + +#include "base/plugins.h" +#include "common/savefile.h" +#include "common/str-array.h" +#include "common/memstream.h" +#include "engines/advancedDetector.h" +#include "common/system.h" +#include "graphics/colormasks.h" +#include "graphics/surface.h" + +#define MAX_SAVES 99 + +namespace Voyeur { + +struct VoyeurGameDescription { + ADGameDescription desc; +}; + +uint32 VoyeurEngine::getFeatures() const { + return _gameDescription->desc.flags; +} + +Common::Language VoyeurEngine::getLanguage() const { + return _gameDescription->desc.language; +} + +Common::Platform VoyeurEngine::getPlatform() const { + return _gameDescription->desc.platform; +} + +bool VoyeurEngine::getIsDemo() const { + return _gameDescription->desc.flags & ADGF_DEMO; +} + +} // End of namespace Voyeur + +static const PlainGameDescriptor voyeurGames[] = { + {"voyeur", "Voyeur"}, + {0, 0} +}; + +#include "voyeur/detection_tables.h" + +class VoyeurMetaEngine : public AdvancedMetaEngine { +public: + VoyeurMetaEngine() : AdvancedMetaEngine(Voyeur::gameDescriptions, sizeof(Voyeur::VoyeurGameDescription), voyeurGames) { + _maxScanDepth = 3; + } + + virtual const char *getName() const { + return "Voyeur Engine"; + } + + virtual const char *getOriginalCopyright() const { + return "Voyeur (c) Philips P.O.V. Entertainment Group"; + } + + virtual bool hasFeature(MetaEngineFeature f) const; + virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const; + virtual SaveStateList listSaves(const char *target) const; + virtual int getMaximumSaveSlot() const; + virtual void removeSaveState(const char *target, int slot) const; + SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const; +}; + +bool VoyeurMetaEngine::hasFeature(MetaEngineFeature f) const { + return + (f == kSupportsListSaves) || + (f == kSupportsLoadingDuringStartup) || + (f == kSupportsDeleteSave) || + (f == kSavesSupportMetaInfo) || + (f == kSavesSupportThumbnail); +} + +bool Voyeur::VoyeurEngine::hasFeature(EngineFeature f) const { + return + (f == kSupportsRTL) || + (f == kSupportsLoadingDuringRuntime) || + (f == kSupportsSavingDuringRuntime); +} + +bool VoyeurMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const { + const Voyeur::VoyeurGameDescription *gd = (const Voyeur::VoyeurGameDescription *)desc; + if (gd) { + *engine = new Voyeur::VoyeurEngine(syst, gd); + } + return gd != 0; +} + +SaveStateList VoyeurMetaEngine::listSaves(const char *target) const { + Common::SaveFileManager *saveFileMan = g_system->getSavefileManager(); + Common::StringArray filenames; + Common::String saveDesc; + Common::String pattern = Common::String::format("%s.0??", target); + + filenames = saveFileMan->listSavefiles(pattern); + sort(filenames.begin(), filenames.end()); // Sort to get the files in numerical order + + SaveStateList saveList; + Voyeur::VoyeurSavegameHeader header; + + for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) { + const char *ext = strrchr(file->c_str(), '.'); + int slot = ext ? atoi(ext + 1) : -1; + + if (slot >= 0 && slot <= MAX_SAVES) { + Common::InSaveFile *in = g_system->getSavefileManager()->openForLoading(*file); + + if (in) { + if (header.read(in)) { + saveList.push_back(SaveStateDescriptor(slot, header._saveName)); + header._thumbnail->free(); + } + delete in; + } + } + } + + return saveList; +} + +int VoyeurMetaEngine::getMaximumSaveSlot() const { + return MAX_SAVES; +} + +void VoyeurMetaEngine::removeSaveState(const char *target, int slot) const { + Common::String filename = Common::String::format("%s.%03d", target, slot); + g_system->getSavefileManager()->removeSavefile(filename); +} + +SaveStateDescriptor VoyeurMetaEngine::querySaveMetaInfos(const char *target, int slot) const { + Common::String filename = Common::String::format("%s.%03d", target, slot); + Common::InSaveFile *f = g_system->getSavefileManager()->openForLoading(filename); + + if (f) { + Voyeur::VoyeurSavegameHeader header; + header.read(f); + delete f; + + // Create the return descriptor + SaveStateDescriptor desc(slot, header._saveName); + desc.setThumbnail(header._thumbnail); + desc.setSaveDate(header._saveYear, header._saveMonth, header._saveDay); + desc.setSaveTime(header._saveHour, header._saveMinutes); + desc.setPlayTime(header._totalFrames * GAME_FRAME_TIME); + + return desc; + } + + return SaveStateDescriptor(); +} + + +#if PLUGIN_ENABLED_DYNAMIC(VOYEUR) + REGISTER_PLUGIN_DYNAMIC(VOYEUR, PLUGIN_TYPE_ENGINE, VoyeurMetaEngine); +#else + REGISTER_PLUGIN_STATIC(VOYEUR, PLUGIN_TYPE_ENGINE, VoyeurMetaEngine); +#endif diff --git a/engines/voyeur/detection_tables.h b/engines/voyeur/detection_tables.h new file mode 100644 index 0000000000..af0faf6acb --- /dev/null +++ b/engines/voyeur/detection_tables.h @@ -0,0 +1,42 @@ +/* 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. + * + */ + +namespace Voyeur { + +static const VoyeurGameDescription gameDescriptions[] = { + { + // Voyeur DOS English + { + "voyeur", + 0, + AD_ENTRY1s("a1100100.rl2", "b44630677618d034970ca0a13c1c1237", 336361), + Common::EN_ANY, + Common::kPlatformDOS, + ADGF_NO_FLAGS, + GUIO1(GUIO_NONE) + }, + }, + + { AD_TABLE_END_MARKER } +}; + +} // End of namespace Voyeur diff --git a/engines/voyeur/events.cpp b/engines/voyeur/events.cpp new file mode 100644 index 0000000000..3b2369c1e1 --- /dev/null +++ b/engines/voyeur/events.cpp @@ -0,0 +1,624 @@ +/* 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 "voyeur/events.h" +#include "voyeur/voyeur.h" +#include "voyeur/staticres.h" +#include "common/events.h" +#include "graphics/cursorman.h" +#include "graphics/font.h" +#include "graphics/fontman.h" +#include "graphics/palette.h" + +namespace Voyeur { + +IntNode::IntNode() { + _intFunc = NULL; + _curTime = 0; + _timeReset = 0; + _flags = 0; +} + +IntNode::IntNode(uint16 curTime, uint16 timeReset, uint16 flags) { + _intFunc = NULL; + _curTime = curTime; + _timeReset = timeReset; + _flags = flags; +} + +/*------------------------------------------------------------------------*/ + +IntData::IntData() { + _flipWait = false; + _hasPalette = false; + _flashTimer = 0; + _flashStep = 0; + field26 = 0; + _skipFading = false; + _palStartIndex = 0; + _palEndIndex = 0; + _palette = NULL; +} + +/*------------------------------------------------------------------------*/ + +EventsManager::EventsManager(): _intPtr(_gameData), + _fadeIntNode(0, 0, 3), _cycleIntNode(0, 0, 3) { + _cycleStatus = 0; + _mouseButton = 0; + _fadeStatus = 0; + _priorFrameTime = g_system->getMillis(); + _gameCounter = 0; + _counterFlag = false; + _recordBlinkCounter = 0; + _cursorBlinked = false; + + Common::fill(&_cycleTime[0], &_cycleTime[4], 0); + Common::fill(&_cycleNext[0], &_cycleNext[4], (byte *)nullptr); + _cyclePtr = NULL; + + _leftClick = _rightClick = false; + _mouseClicked = _mouseUnk = false; + _newLeftClick = _newRightClick = false;; + _newMouseClicked = _newMouseUnk = false; + + _videoDead = 0; +} + +void EventsManager::resetMouse() { + // No implementation +} + +void EventsManager::startMainClockInt() { + _mainIntNode._intFunc = &EventsManager::mainVoyeurIntFunc; + _mainIntNode._flags = 0; + _mainIntNode._curTime = 0; + _mainIntNode._timeReset = _vm->_graphicsManager._palFlag ? 50 : 60; +} + +void EventsManager::mainVoyeurIntFunc() { + if (!(_vm->_voy._eventFlags & EVTFLAG_TIME_DISABLED)) { + ++_vm->_voy._switchBGNum; + + if (_vm->_debugger._isTimeActive) { + // Increase camera discharge + ++_vm->_voy._RTVNum; + + // If the murder threshold has been set, and is passed, then flag the victim + // as murdered, which prevents sending the tape from succeeding + if (_vm->_voy._RTVNum >= _vm->_voy._murderThreshold) + _vm->_voy._victimMurdered = true; + } + } +} + +void EventsManager::sWaitFlip() { + Common::Array<ViewPortResource *> &viewPorts = _vm->_graphicsManager._viewPortListPtr->_entries; + for (uint idx = 0; idx < viewPorts.size(); ++idx) { + ViewPortResource &viewPort = *viewPorts[idx]; + + if (_vm->_graphicsManager._saveBack && (viewPort._flags & DISPFLAG_40)) { + Common::Rect *clipPtr = _vm->_graphicsManager._clipPtr; + _vm->_graphicsManager._clipPtr = &viewPort._clipRect; + + if (viewPort._restoreFn) + (_vm->_graphicsManager.*viewPort._restoreFn)(&viewPort); + + _vm->_graphicsManager._clipPtr = clipPtr; + viewPort._rectListCount[viewPort._pageIndex] = 0; + viewPort._rectListPtr[viewPort._pageIndex]->clear(); + viewPort._flags &= ~DISPFLAG_40; + } + } + + while (_gameData._flipWait && !_vm->shouldQuit()) { + pollEvents(); + g_system->delayMillis(10); + } +} + +void EventsManager::checkForNextFrameCounter() { + // Check for next game frame + uint32 milli = g_system->getMillis(); + if ((milli - _priorFrameTime) >= GAME_FRAME_TIME) { + _counterFlag = !_counterFlag; + if (_counterFlag) + ++_gameCounter; + _priorFrameTime = milli; + + // Run the timer-based updates + voyeurTimer(); + + if ((_gameCounter % GAME_FRAME_RATE) == 0) + mainVoyeurIntFunc(); + + // Give time to the debugger + _vm->_debugger.onFrame(); + + // If mouse position display is on, display the position + if (_vm->_debugger._showMousePosition) + showMousePosition(); + + // Display the frame + g_system->copyRectToScreen((byte *)_vm->_graphicsManager._screenSurface.getPixels(), + SCREEN_WIDTH, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); + g_system->updateScreen(); + + // Signal the ScummVM debugger + _vm->_debugger.onFrame(); + } +} + +void EventsManager::showMousePosition() { + const Graphics::Font &font(*FontMan.getFontByUsage(Graphics::FontManager::kConsoleFont)); + Common::String mousePos = Common::String::format("(%d,%d)", _mousePos.x, _mousePos.y); + if (_vm->_voyeurArea == AREA_INTERFACE) { + Common::Point pt = _mousePos + _vm->_mansionViewPos - Common::Point(40, 27); + if (pt.x < 0) pt.x = 0; + if (pt.y < 0) pt.y = 0; + + mousePos += Common::String::format(" - (%d,%d)", pt.x, pt.y); + } + + _vm->_graphicsManager._screenSurface.fillRect( + Common::Rect(0, 0, 110, font.getFontHeight()), 0); + font.drawString(&_vm->_graphicsManager._screenSurface, mousePos, + 0, 0, 110, 63); +} + +void EventsManager::voyeurTimer() { + _gameData._flashTimer += _gameData._flashStep; + + if (--_gameData.field26 <= 0) { + if (_gameData._flipWait) { + _gameData._flipWait = false; + _gameData._skipFading = false; + } + + _gameData.field26 >>= 8; + } + + videoTimer(); + + // Iterate through the list of registered nodes + Common::List<IntNode *>::iterator i; + for (i = _intNodes.begin(); i != _intNodes.end(); ++i) { + IntNode &node = **i; + + if (node._flags & 1) + continue; + if (!(node._flags & 2)) { + if (--node._curTime != 0) + continue; + + node._curTime = node._timeReset; + } + + (this->*node._intFunc)(); + } + +} + +void EventsManager::videoTimer() { + if (_gameData._hasPalette) { + _gameData._hasPalette = false; + + g_system->getPaletteManager()->setPalette(_gameData._palette + + _gameData._palStartIndex * 3, _gameData._palStartIndex, + _gameData._palEndIndex - _gameData._palStartIndex + 1); + } +} + +void EventsManager::delay(int cycles) { + uint32 totalMilli = cycles * 1000 / GAME_FRAME_RATE; + uint32 delayEnd = g_system->getMillis() + totalMilli; + + while (!_vm->shouldQuit() && g_system->getMillis() < delayEnd) { + g_system->delayMillis(10); + + pollEvents(); + } +} + +void EventsManager::delayClick(int cycles) { + uint32 totalMilli = cycles * 1000 / GAME_FRAME_RATE; + uint32 delayEnd = g_system->getMillis() + totalMilli; + + do { + g_system->delayMillis(10); + getMouseInfo(); + } while (!_vm->shouldQuit() && g_system->getMillis() < delayEnd + && !_vm->_eventsManager._mouseClicked); +} + +void EventsManager::pollEvents() { + checkForNextFrameCounter(); + + Common::Event event; + while (g_system->getEventManager()->pollEvent(event)) { + // Handle keypress + switch (event.type) { + case Common::EVENT_QUIT: + case Common::EVENT_RTL: + case Common::EVENT_KEYUP: + return; + + case Common::EVENT_KEYDOWN: + // Check for debugger + if (event.kbd.keycode == Common::KEYCODE_d && (event.kbd.flags & Common::KBD_CTRL)) { + // Attach to the debugger + _vm->_debugger.attach(); + _vm->_debugger.onFrame(); + } + return; + case Common::EVENT_LBUTTONDOWN: + _mouseButton = 1; + _vm->_eventsManager._newLeftClick = true; + _vm->_eventsManager._newMouseClicked = true; + return; + case Common::EVENT_RBUTTONDOWN: + _mouseButton = 2; + _vm->_eventsManager._newRightClick = true; + _vm->_eventsManager._newMouseClicked = true; + return; + case Common::EVENT_LBUTTONUP: + case Common::EVENT_RBUTTONUP: + _vm->_eventsManager._newMouseClicked = false; + _vm->_eventsManager._newLeftClick = false; + _vm->_eventsManager._newRightClick = false; + _mouseButton = 0; + return; + case Common::EVENT_MOUSEMOVE: + _mousePos = event.mouse; + break; + default: + break; + } + } +} + +void EventsManager::startFade(CMapResource *cMap) { + _fadeIntNode._flags |= 1; + if (_cycleStatus & 1) + _cycleIntNode._flags |= 1; + + _fadeFirstCol = cMap->_start; + _fadeLastCol = cMap->_end; + _fadeCount = cMap->_steps + 1; + + if (cMap->_steps > 0) { + _fadeStatus = cMap->_fadeStatus | 1; + byte *vgaP = &_vm->_graphicsManager._VGAColors[_fadeFirstCol * 3]; + int mapIndex = 0; + + for (int idx = _fadeFirstCol; idx <= _fadeLastCol; ++idx, vgaP += 3) { + ViewPortPalEntry &palEntry = _vm->_graphicsManager._viewPortListPtr->_palette[idx]; + palEntry._rEntry = vgaP[0] << 8; + int rDiff = (cMap->_entries[mapIndex * 3] << 8) - palEntry._rEntry; + palEntry._rChange = rDiff / cMap->_steps; + + palEntry._gEntry = vgaP[1] << 8; + int gDiff = (cMap->_entries[mapIndex * 3 + 1] << 8) - palEntry._gEntry; + palEntry._gChange = gDiff / cMap->_steps; + + palEntry._bEntry = vgaP[2] << 8; + int bDiff = (cMap->_entries[mapIndex * 3 + 2] << 8) - palEntry._bEntry; + palEntry._bChange = bDiff / cMap->_steps; + + palEntry._palIndex = idx; + if (!(cMap->_fadeStatus & 1)) + ++mapIndex; + } + + if (cMap->_fadeStatus & 2) + _intPtr._skipFading = true; + _fadeIntNode._flags &= ~1; + } else { + byte *vgaP = &_vm->_graphicsManager._VGAColors[_fadeFirstCol * 3]; + int mapIndex = 0; + + for (int idx = _fadeFirstCol; idx <= _fadeLastCol; ++idx, vgaP += 3) { + Common::copy(&cMap->_entries[mapIndex], &cMap->_entries[mapIndex + 3], vgaP); + + if (!(cMap->_fadeStatus & 1)) + mapIndex += 3; + } + + if (_intPtr._palStartIndex > _fadeFirstCol) + _intPtr._palStartIndex = _fadeFirstCol; + if (_intPtr._palEndIndex < _fadeLastCol) + _intPtr._palEndIndex = _fadeLastCol; + + _intPtr._hasPalette = true; + } + + if (_cycleStatus & 1) + _cycleIntNode._flags &= ~1; +} + +void EventsManager::addIntNode(IntNode *node) { + _intNodes.push_back(node); +} + +void EventsManager::addFadeInt() { + IntNode &node = _fade2IntNode; + node._intFunc = &EventsManager::fadeIntFunc; + node._flags = 0; + node._curTime = 0; + node._timeReset = 1; + + addIntNode(&node); +} + +void EventsManager::vDoFadeInt() { + if (_intPtr._skipFading) + return; + if (--_fadeCount == 0) { + _fadeIntNode._flags |= 1; + _fadeStatus &= ~1; + return; + } + + + for (int i = _fadeFirstCol; i <= _fadeLastCol; ++i) { + ViewPortPalEntry &palEntry = _vm->_graphicsManager._viewPortListPtr->_palette[i]; + byte *vgaP = &_vm->_graphicsManager._VGAColors[palEntry._palIndex * 3]; + + palEntry._rEntry += palEntry._rChange; + palEntry._gEntry += palEntry._gChange; + palEntry._bEntry += palEntry._bChange; + + vgaP[0] = palEntry._rEntry >> 8; + vgaP[1] = palEntry._gEntry >> 8; + vgaP[2] = palEntry._bEntry >> 8; + } + + if (_intPtr._palStartIndex > _fadeFirstCol) + _intPtr._palStartIndex = _fadeFirstCol; + if (_intPtr._palEndIndex < _fadeLastCol) + _intPtr._palEndIndex = _fadeLastCol; + + _intPtr._hasPalette = true; +} + +void EventsManager::vDoCycleInt() { + for (int idx = 3; idx >= 0; --idx) { + if (_cyclePtr->_type[idx] && --_cycleTime[idx] <= 0) { + byte *pSrc = _cycleNext[idx]; + byte *pPal = _vm->_graphicsManager._VGAColors; + + if (_cyclePtr->_type[idx] != 1) { + // New palette data being specified - loop to set entries + do { + int palIndex = READ_LE_UINT16(pSrc); + pPal[palIndex * 3] = pSrc[3]; + pPal[palIndex * 3 + 1] = pSrc[4]; + pPal[palIndex * 3 + 1] = pSrc[5]; + pSrc += 6; + + if ((int16)READ_LE_UINT16(pSrc) >= 0) { + // Resetting back to start of cycle data + pSrc = _cycleNext[idx]; + break; + } + } while (*(pSrc + 2) == 0); + + _cycleNext[idx] = pSrc; + _cycleTime[idx] = pSrc[2]; + } else { + // Palette rotation to be done + _cycleTime[idx] = pSrc[4]; + + if (pSrc[5] == 1) { + // Move palette entry to end of range + int start = READ_LE_UINT16(pSrc); + int end = READ_LE_UINT16(&pSrc[2]); + assert(start < 0x100 && end < 0x100); + + // Store the RGB of the first entry to be moved + byte r = pPal[start * 3]; + byte g = pPal[start * 3 + 1]; + byte b = pPal[start * 3 + 2]; + + Common::copy(&pPal[start * 3 + 3], &pPal[end * 3 + 3], &pPal[start * 3]); + + // Place the original saved entry at the end of the range + pPal[end * 3] = r; + pPal[end * 3 + 1] = g; + pPal[end * 3 + 2] = b; + + if (_fadeStatus & 1) { + //dx = start, di = end + warning("TODO: Adjustment of ViewPortListResource"); + } + } else { + // Move palette entry to start of range + int start = READ_LE_UINT16(pSrc); + int end = READ_LE_UINT16(&pSrc[2]); + assert(start < 0x100 && end < 0x100); + + // Store the RGB of the entry to be moved + byte r = pPal[end * 3]; + byte g = pPal[end * 3 + 1]; + byte b = pPal[end * 3 + 2]; + + // Move the remainder of the range forwards one entry + Common::copy_backward(&pPal[start * 3], &pPal[end * 3], &pPal[end * 3 + 3]); + + // Place the original saved entry at the end of the range + pPal[start * 3] = r; + pPal[start * 3 + 1] = g; + pPal[start * 3 + 2] = b; + + if (_fadeStatus & 1) { + //dx = start, di = end + warning("TODO: Adjustment of ViewPortListResource"); + } + } + } + + _intPtr._hasPalette = true; + } + } +} + + +void EventsManager::fadeIntFunc() { + switch (_vm->_voy._fadingType) { + case 1: + if (_vm->_voy._fadingAmount1 < 63) + _vm->_voy._fadingAmount1 += _vm->_voy._fadingStep1; + if (_vm->_voy._fadingAmount2 < 63) + _vm->_voy._fadingAmount2 += _vm->_voy._fadingStep2; + if (_vm->_voy._fadingAmount1 > 63) + _vm->_voy._fadingAmount1 = 63; + if (_vm->_voy._fadingAmount2 > 63) + _vm->_voy._fadingAmount2 = 63; + if ((_vm->_voy._fadingAmount1 == 63) && (_vm->_voy._fadingAmount2 == 63)) + _vm->_voy._fadingType = 0; + break; + case 2: + if (_vm->_voy._fadingAmount1 > 0) + _vm->_voy._fadingAmount1 -= _vm->_voy._fadingStep1; + if (_vm->_voy._fadingAmount2 > 0) + _vm->_voy._fadingAmount2 -= _vm->_voy._fadingStep2; + if (_vm->_voy._fadingAmount1 < 0) + _vm->_voy._fadingAmount1 = 0; + if (_vm->_voy._fadingAmount2 < 0) + _vm->_voy._fadingAmount2 = 0; + if ((_vm->_voy._fadingAmount1 == 0) && (_vm->_voy._fadingAmount2 == 0)) + _vm->_voy._fadingType = 0; + break; + default: + break; + } +} + +void EventsManager::deleteIntNode(IntNode *node) { + _intNodes.remove(node); +} + +void EventsManager::vInitColor() { + _fadeIntNode._intFunc = &EventsManager::vDoFadeInt; + _cycleIntNode._intFunc = &EventsManager::vDoCycleInt; + + addIntNode(&_fadeIntNode); + addIntNode(&_cycleIntNode); +} + +void EventsManager::setCursor(PictureResource *pic) { + PictureResource cursor; + cursor._bounds = pic->_bounds; + cursor._flags = DISPFLAG_CURSOR; + + _vm->_graphicsManager.sDrawPic(pic, &cursor, Common::Point()); +} + +void EventsManager::setCursor(byte *cursorData, int width, int height) { + CursorMan.replaceCursor(cursorData, width, height, width / 2, height / 2, 0); +} + +void EventsManager::setCursorColor(int idx, int mode) { + switch (mode) { + case 0: + _vm->_graphicsManager.setColor(idx, 90, 90, 232); + break; + case 1: + _vm->_graphicsManager.setColor(idx, 232, 90, 90); + break; + case 2: + _vm->_graphicsManager.setColor(idx, 90, 232, 90); + break; + case 3: + _vm->_graphicsManager.setColor(idx, 90, 232, 232); + break; + default: + break; + } +} + +void EventsManager::showCursor() { + CursorMan.showMouse(true); +} + +void EventsManager::hideCursor() { + CursorMan.showMouse(false); +} + +void EventsManager::getMouseInfo() { + pollEvents(); + + if (_vm->_voy._eventFlags & EVTFLAG_RECORDING) { + if ((_gameCounter - _recordBlinkCounter) > 8) { + _recordBlinkCounter = _gameCounter; + + if (_cursorBlinked) { + _cursorBlinked = false; + _vm->_graphicsManager.setOneColor(128, 220, 20, 20); + _vm->_graphicsManager.setColor(128, 220, 20, 20); + } else { + _cursorBlinked = true; + _vm->_graphicsManager.setOneColor(128, 220, 220, 220); + _vm->_graphicsManager.setColor(128, 220, 220, 220); + } + } + } + + _vm->_eventsManager._mouseClicked = _vm->_eventsManager._newMouseClicked; + _vm->_eventsManager._leftClick = _vm->_eventsManager._newLeftClick; + _vm->_eventsManager._rightClick = _vm->_eventsManager._newRightClick; + _vm->_eventsManager._mouseUnk = _vm->_eventsManager._newMouseUnk; + + _vm->_eventsManager._newMouseClicked = false; + _vm->_eventsManager._newLeftClick = false; + _vm->_eventsManager._newRightClick = false; + _vm->_eventsManager._mouseUnk = false; +} + +void EventsManager::startCursorBlink() { + if (_vm->_voy._eventFlags & EVTFLAG_RECORDING) { + _vm->_graphicsManager.setOneColor(128, 55, 5, 5); + _vm->_graphicsManager.setColor(128, 220, 20, 20); + _intPtr._hasPalette = true; + + _vm->_graphicsManager.drawDot(); + //copySection(); + } +} + +void EventsManager::incrementTime(int amt) { + for (int i = 0; i < amt; ++i) + mainVoyeurIntFunc(); +} + +void EventsManager::stopEvidDim() { + deleteIntNode(&_evIntNode); +} + +Common::String EventsManager::getEvidString(int eventIndex) { + assert(eventIndex <= _vm->_voy._eventCount); + VoyeurEvent &e = _vm->_voy._events[eventIndex]; + return Common::String::format("%03d %.2d:%.2d %s %s", eventIndex + 1, + e._hour, e._minute, e._isAM ? AM : PM, EVENT_TYPE_STRINGS[e._type - 1]); +} + +} // End of namespace Voyeur diff --git a/engines/voyeur/events.h b/engines/voyeur/events.h new file mode 100644 index 0000000000..eb6cf6d77f --- /dev/null +++ b/engines/voyeur/events.h @@ -0,0 +1,153 @@ +/* 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. + * + */ + +#ifndef VOYEUR_EVENTS_H +#define VOYEUR_EVENTS_H + +#include "common/scummsys.h" +#include "common/list.h" +#include "graphics/surface.h" +#include "voyeur/files.h" + +namespace Voyeur { + +class VoyeurEngine; +class EventsManager; +class CMapResource; + +#define GAME_FRAME_RATE 50 +#define GAME_FRAME_TIME (1000 / GAME_FRAME_RATE) + +typedef void (EventsManager::*EventMethodPtr)(); + +class IntNode { +public: + EventMethodPtr _intFunc; + uint16 _curTime; + uint16 _timeReset; + uint32 _flags; +public: + IntNode(); + IntNode(uint16 curTime, uint16 timeReset, uint16 flags); +}; + +class IntData { +public: + bool _flipWait; + int _flashTimer; + int _flashStep; + int field26; + bool _hasPalette; + bool _skipFading; + int _palStartIndex; + int _palEndIndex; + byte *_palette; +public: + IntData(); +}; + +class EventsManager { +private: + VoyeurEngine *_vm; + uint32 _priorFrameTime; + bool _counterFlag; + uint32 _gameCounter; + uint32 _recordBlinkCounter; // Original field was called _joe :) + int _mouseButton; + Common::List<IntNode *> _intNodes; + Common::Point _mousePos; + bool _cursorBlinked; + + void mainVoyeurIntFunc(); +private: + void checkForNextFrameCounter(); + void voyeurTimer(); + void videoTimer(); + void vDoFadeInt(); + void vDoCycleInt(); + void fadeIntFunc(); + void deleteIntNode(IntNode *node); + + /** + * Debugger support method to show the mouse position + */ + void showMousePosition(); +public: + IntData _gameData; + IntData &_intPtr; + IntNode _fadeIntNode; + IntNode _fade2IntNode; + IntNode _cycleIntNode; + IntNode _evIntNode; + IntNode _mainIntNode; + int _cycleStatus; + int _fadeFirstCol, _fadeLastCol; + int _fadeCount; + int _fadeStatus; + + bool _leftClick, _rightClick; + bool _mouseClicked; + bool _mouseUnk; + bool _newMouseClicked; + bool _newLeftClick, _newRightClick; + bool _newMouseUnk; + + int _videoDead; + int _cycleTime[4]; + byte *_cycleNext[4]; + VInitCycleResource *_cyclePtr; +public: + EventsManager(); + void setVm(VoyeurEngine *vm) { _vm = vm; } + + void resetMouse(); + void setMousePos(const Common::Point &p) { _mousePos = p; } + void startMainClockInt(); + void sWaitFlip(); + void vInitColor(); + + void delay(int cycles); + void delayClick(int cycles); + void pollEvents(); + void startFade(CMapResource *cMap); + void addIntNode(IntNode *node); + void addFadeInt(); + + void setCursor(PictureResource *pic); + void setCursor(byte *cursorData, int width, int height); + void setCursorColor(int idx, int mode); + void showCursor(); + void hideCursor(); + Common::Point getMousePos() { return _mousePos; } + uint32 getGameCounter() const { return _gameCounter; } + void getMouseInfo(); + void startCursorBlink(); + void incrementTime(int amt); + + void stopEvidDim(); + + Common::String getEvidString(int eventIndex); +}; + +} // End of namespace Voyeur + +#endif /* VOYEUR_EVENTS_H */ diff --git a/engines/voyeur/files.cpp b/engines/voyeur/files.cpp new file mode 100644 index 0000000000..3f955fd066 --- /dev/null +++ b/engines/voyeur/files.cpp @@ -0,0 +1,1659 @@ +/* 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 "voyeur/files.h" +#include "voyeur/graphics.h" +#include "voyeur/voyeur.h" +#include "voyeur/staticres.h" + +namespace Voyeur { + +#define BOLT_GROUP_SIZE 16 + +BoltFilesState::BoltFilesState() { + _curLibPtr = NULL; + _curGroupPtr = NULL; + _curMemberPtr = NULL; + _curMemInfoPtr = NULL; + _fromGroupFlag = 0; + _xorMask = 0; + _encrypt = false; + _curFilePosition = 0; + _bufferEnd = 0; + _bufferBegin = 0; + _bytesLeft = 0; + _bufSize = 0; + _bufStart = NULL; + _bufPos = NULL; + _historyIndex = 0; + _runLength = 0; + _decompState = 0; + _runType = 0; + _runValue = 0; + _runOffset = 0; + Common::fill(&_historyBuffer[0], &_historyBuffer[0x200], 0); + _curFd = NULL; + _boltPageFrame = NULL; +} + +#define NEXT_BYTE if (--_bytesLeft < 0) nextBlock() + +byte *BoltFilesState::decompress(byte *buf, int size, int mode) { + if (!buf) { + buf = new byte[size]; + Common::fill(buf, buf + size, 0); + } + byte *bufP = buf; + + if (mode & 8) { + _decompState = 1; + _runType = 0; + _runLength = size; + } + + while (size > 0) { + if (!_decompState) { + NEXT_BYTE; + byte nextByte = *_bufPos++; + + switch (nextByte & 0xC0) { + case 0: + _runType = 0; + _runLength = 30 - (nextByte & 0x1f) + 1; + break; + case 0x40: + _runType = 1; + _runLength = 35 - (nextByte & 0x1f); + NEXT_BYTE; + _runOffset = *_bufPos++ + ((nextByte & 0x20) << 3); + break; + case 0x80: + _runType = 1; + _runLength = (nextByte & 0x20) ? ((32 - (nextByte & 0x1f)) << 2) + 2 : + (32 - (nextByte & 0x1f)) << 2; + NEXT_BYTE; + _runOffset = *_bufPos++ << 1; + break; + default: + _runType = 2; + + if (nextByte & 0x20) { + _runLength = 0; + } else { + NEXT_BYTE; + _runLength = ((32 - (nextByte & 0x1f)) + (*_bufPos++ << 5)) << 2; + NEXT_BYTE; + _bufPos++; + NEXT_BYTE; + _runValue = *_bufPos++; + } + break; + } + + _runOffset = _historyIndex - _runOffset; + } + + int runOffset = _runOffset & 0x1ff; + int len; + if (_runLength <= size) { + len = _runLength; + _decompState = 0; + } else { + _decompState = 1; + len = size; + _runLength -= size; + if (_runType == 1) + _runOffset += len; + } + + // Reduce the remaining size + size -= len; + + // Handle the run lengths + switch (_runType) { + case 0: + while (len-- > 0) { + NEXT_BYTE; + byte v = *_bufPos++; + _historyBuffer[_historyIndex] = v; + *bufP++ = v; + _historyIndex = (_historyIndex + 1) & 0x1ff; + } + break; + case 1: + while (len-- > 0) { + _historyBuffer[_historyIndex] = _historyBuffer[runOffset]; + *bufP++ = _historyBuffer[runOffset]; + _historyIndex = (_historyIndex + 1) & 0x1ff; + runOffset = (runOffset + 1) & 0x1ff; + } + break; + default: + while (len-- > 0) { + _historyBuffer[_historyIndex] = _runValue; + *bufP++ = _runValue; + _historyIndex = (_historyIndex + 1) & 0x1ff; + } + break; + } + } + + return buf; +} + +#undef NEXT_BYTE + +void BoltFilesState::nextBlock() { + if (&_curLibPtr->_file != _curFd || _curFd->pos() != _bufferEnd) + _curLibPtr->_file.seek(_bufferEnd); + + _curFd = &_curLibPtr->_file; + _bufferBegin = _bufferEnd; + int bytesRead = _curFd->read(_bufStart, _bufSize); + + _bufferEnd = _curFilePosition = _curFd->pos(); + _bytesLeft = bytesRead - 1; + _bufPos = _bufStart; +} + +/*------------------------------------------------------------------------*/ + +FilesManager::FilesManager() { + _decompressSize = DECOMPRESS_SIZE; +} + +bool FilesManager::openBoltLib(const Common::String &filename, BoltFile *&boltFile) { + if (boltFile != NULL) { + _boltFilesState._curLibPtr = boltFile; + return true; + } + + // Create the bolt file interface object and load the index + if (filename == "bvoy.blt") + boltFile = _boltFilesState._curLibPtr = new BVoyBoltFile(_boltFilesState); + else if (filename == "stampblt.blt") + boltFile = _boltFilesState._curLibPtr = new StampBoltFile(_boltFilesState); + else + error("Unknown bolt file specified"); + + return true; +} + +byte *FilesManager::fload(const Common::String &filename, int *size) { + Common::File f; + int filesize; + byte *data = NULL; + + if (f.open(filename)) { + // Read in the file + filesize = f.size(); + data = new byte[filesize]; + f.read(data, filesize); + } else { + filesize = 0; + } + + if (size) + *size = filesize; + return data; +} + +/*------------------------------------------------------------------------*/ + +BoltFile::BoltFile(const Common::String &filename, BoltFilesState &state): _state(state) { + if (!_file.open(filename)) + error("Could not open %s", filename.c_str()); + _state._curFilePosition = 0; + + // Read in the file header + byte header[16]; + _file.read(&header[0], 16); + + if (strncmp((const char *)&header[0], "BOLT", 4) != 0) + error("Tried to load non-bolt file"); + + int totalGroups = header[11] ? header[11] : 0x100; + for (int i = 0; i < totalGroups; ++i) + _groups.push_back(BoltGroup(&_file)); +} + +BoltFile::~BoltFile() { + _file.close(); + if (_state._curFd == &_file) + _state._curFd = NULL; + if (_state._curLibPtr == this) + _state._curLibPtr = NULL; +} + +BoltGroup *BoltFile::getBoltGroup(uint16 id, bool process) { + ++_state._fromGroupFlag; + _state._curLibPtr = this; + _state._curGroupPtr = &_groups[(id >> 8) & 0xff]; + + if (!_state._curGroupPtr->_loaded) { + // Load the group index + _state._curGroupPtr->load(id & 0xff00); + } + + if (_state._curGroupPtr->_callInitGro) + initGro(); + + if (process) { + // Pre-process the resources + id &= 0xff00; + for (int idx = 0; idx < _state._curGroupPtr->_count; ++idx, ++id) { + byte *member = getBoltMember(id); + assert(member); + } + } else if (!_state._curGroupPtr->_processed) { + _state._curGroupPtr->_processed = true; + _state._curGroupPtr->load(id & 0xff00); + } + + resolveAll(); + --_state._fromGroupFlag; + + return _state._curGroupPtr; +} + +void BoltFile::freeBoltGroup(uint16 id, bool freeEntries) { + _state._curLibPtr = this; + _state._curGroupPtr = &_groups[(id >> 8) & 0xff]; + + // Unload the group + _state._curGroupPtr->unload(); +} + +void BoltFile::freeBoltMember(uint32 id) { + // No implementation in ScummVM +} + +BoltEntry &BoltFile::getBoltEntryFromLong(uint32 id) { + BoltGroup &group = _groups[id >> 24]; + assert(group._loaded); + + BoltEntry &entry = group._entries[(id >> 16) & 0xff]; + assert(!entry.hasResource() || (id & 0xffff) == 0); + + return entry; +} + +BoltEntry &BoltFile::boltEntry(uint16 id) { + BoltGroup &group = _groups[id >> 8]; + assert(group._loaded); + + BoltEntry &entry = group._entries[id & 0xff]; + assert(entry.hasResource()); + + return entry; +} + +PictureResource *BoltFile::getPictureResource(uint32 id) { + if ((int32)id == -1) + return NULL; + + if (id & 0xffff) + id <<= 16; + return getBoltEntryFromLong(id)._picResource; +} + +CMapResource *BoltFile::getCMapResource(uint32 id) { + if ((int32)id == -1) + return NULL; + + if (id & 0xffff) + id <<= 16; + + return getBoltEntryFromLong(id)._cMapResource; +} + +byte *BoltFile::memberAddr(uint32 id) { + BoltGroup &group = _groups[id >> 8]; + if (!group._loaded) + return NULL; + + // If an entry already has a processed representation, we shouldn't + // still be accessing the raw data + BoltEntry &entry = group._entries[id & 0xff]; + assert(!entry.hasResource()); + + return entry._data; +} + +byte *BoltFile::memberAddrOffset(uint32 id) { + BoltGroup &group = _groups[id >> 24]; + if (!group._loaded) + return NULL; + + // If an entry already has a processed representation, we shouldn't + // still be accessing the raw data + BoltEntry &entry = group._entries[(id >> 16) & 0xff]; + assert(!entry.hasResource()); + + return entry._data + (id & 0xffff); +} + +/** + * Resolves an Id to an offset within a loaded resource + */ +void BoltFile::resolveIt(uint32 id, byte **p) { + if ((int32)id == -1) { + *p = NULL; + } else { + byte *ptr = memberAddrOffset(id); + if (ptr) { + *p = ptr; + } else { + *p = NULL; + assert(_state._resolves.size() < 1000); + _state._resolves.push_back(ResolveEntry(id, p)); + } + } +} + +void BoltFile::resolveFunction(uint32 id, GraphicMethodPtr *fn) { + if ((int32)id == -1) + *fn = NULL; + else + error("Function fnTermGro array not supported"); +} + +/** + * Resolve any data references to within resources that weren't + * previously loaded, but are now + */ +void BoltFile::resolveAll() { + for (uint idx = 0; idx < _state._resolves.size(); ++idx) + *_state._resolves[idx]._p = memberAddrOffset(_state._resolves[idx]._id); + + _state._resolves.clear(); +} + +byte *BoltFile::getBoltMember(uint32 id) { + _state._curLibPtr = this; + + // Get the group, and load it's entry list if not already loaded + _state._curGroupPtr = &_groups[(id >> 8) & 0xff]; + if (!_state._curGroupPtr->_loaded) + _state._curGroupPtr->load(id & 0xff00); + + // Get the entry + _state._curMemberPtr = &_state._curGroupPtr->_entries[id & 0xff]; + if (_state._curMemberPtr->_initMemRequired) + initMem(_state._curMemberPtr->_initMemRequired); + + // Return the data for the entry if it's already been loaded + if (_state._curMemberPtr->_data) + return _state._curMemberPtr->_data; + + _state._xorMask = _state._curMemberPtr->_xorMask; + _state._encrypt = (_state._curMemberPtr->_mode & 0x10) != 0; + + if (_state._curGroupPtr->_processed) { + error("Processed resources are not supported"); + } else { + _state._bufStart = _state._decompressBuf; + _state._bufSize = DECOMPRESS_SIZE; + + if ((_state._curFd != &_file) || (_state._curMemberPtr->_fileOffset < _state._bufferBegin) + || (_state._curMemberPtr->_fileOffset >= _state._bufferEnd)) { + _state._bytesLeft = 0; + _state._bufPos = _state._bufStart; + _state._bufferBegin = -1; + _state._bufferEnd = _state._curMemberPtr->_fileOffset; + } else { + _state._bufPos = _state._curMemberPtr->_fileOffset - _state._bufferBegin + _state._bufStart; + _state._bytesLeft = _state._bufSize - (_state._bufPos - _state._bufStart); + } + } + + _state._decompState = 0; + _state._historyIndex = 0; + + // Initialize the resource + assert(_state._curMemberPtr->_initMethod < 25); + initResource(_state._curMemberPtr->_initMethod); + + return _state._curMemberPtr->_data; +} + +void BoltFile::initDefault() { + _state._curMemberPtr->_data = _state.decompress(0, _state._curMemberPtr->_size, + _state._curMemberPtr->_mode); +} + +/*------------------------------------------------------------------------*/ + +BVoyBoltFile::BVoyBoltFile(BoltFilesState &state): BoltFile("bvoy.blt", state) { +} + +void BVoyBoltFile::initResource(int resType) { + switch (resType) { + case 2: + // Also used for point list, and ending credits credit data + sInitRect(); + break; + case 8: + sInitPic(); + break; + case 10: + vInitCMap(); + break; + case 11: + vInitCycl(); + break; + case 15: + initViewPort(); + break; + case 16: + initViewPortList(); + break; + case 17: + initFont(); + break; + case 18: + initFontInfo(); + break; + case 19: + initSoundMap(); + break; + default: + initDefault(); + break; + } +} + +void BVoyBoltFile::initViewPort() { + initDefault(); + + ViewPortResource *viewPort; + byte *src = _state._curMemberPtr->_data; + _state._curMemberPtr->_viewPortResource = viewPort = new ViewPortResource(_state, src); + + // This is done post-constructor, since viewports can be self referential, so + // we ned the _viewPortResource field to have been set before resolving the pointer + viewPort->_parent = getBoltEntryFromLong(READ_LE_UINT32(src + 2))._viewPortResource; +} + +void BVoyBoltFile::initViewPortList() { + initDefault(); + + ViewPortListResource *res; + _state._curMemberPtr->_viewPortListResource = res = new ViewPortListResource( + _state, _state._curMemberPtr->_data); + + _state._vm->_graphicsManager._viewPortListPtr = res; + _state._vm->_graphicsManager._vPort = &res->_entries[0]; +} + +void BVoyBoltFile::initFontInfo() { + initDefault(); + _state._curMemberPtr->_fontInfoResource = new FontInfoResource( + _state, _state._curMemberPtr->_data); +} + +void BVoyBoltFile::initFont() { + initDefault(); + _state._curMemberPtr->_fontResource = new FontResource(_state, _state._curMemberPtr->_data); +} + +void BVoyBoltFile::initSoundMap() { + initDefault(); +} + +void BVoyBoltFile::sInitRect() { + _state._curMemberPtr->_data = _state.decompress(NULL, _state._curMemberPtr->_size, + _state._curMemberPtr->_mode); + + // Check whether the resouce Id is in the list of extended rects + bool isExtendedRects = false; + for (int i = 0; i < 49 && !isExtendedRects; ++i) + isExtendedRects = RESOLVE_TABLE[i] == (_state._curMemberPtr->_id & 0xff00); + + int rectSize = isExtendedRects ? 12 : 8; + if ((_state._curMemberPtr->_size % rectSize) == 0 || (_state._curMemberPtr->_size % rectSize) == 2) + _state._curMemberPtr->_rectResource = new RectResource(_state._curMemberPtr->_data, + _state._curMemberPtr->_size, isExtendedRects); +} + +void BVoyBoltFile::sInitPic() { + // Read in the header data + _state._curMemberPtr->_data = _state.decompress(NULL, 24, _state._curMemberPtr->_mode); + _state._curMemberPtr->_picResource = new PictureResource(_state, + _state._curMemberPtr->_data); +} + +void BVoyBoltFile::vInitCMap() { + initDefault(); + _state._curMemberPtr->_cMapResource = new CMapResource( + _state, _state._curMemberPtr->_data); +} + +void BVoyBoltFile::vInitCycl() { + initDefault(); + _state._curMemberPtr->_vInitCycleResource = new VInitCycleResource( + _state, _state._curMemberPtr->_data); + _state._curMemberPtr->_vInitCycleResource->vStopCycle(); +} + +/*------------------------------------------------------------------------*/ + +StampBoltFile::StampBoltFile(BoltFilesState &state): BoltFile("stampblt.blt", state) { +} + +void StampBoltFile::initResource(int resType) { + switch (resType) { + case 0: + initThread(); + break; + case 4: + initState(); + break; + case 6: + initPtr(); + break; + case 24: + initControl(); + break; + default: + initDefault(); + break; + } +} + +void StampBoltFile::initThread() { + initDefault(); + + _state._curMemberPtr->_threadResource = new ThreadResource(_state, + _state._curMemberPtr->_data); +} + +void StampBoltFile::initPtr() { + initDefault(); + + _state._curMemberPtr->_ptrResource = new PtrResource(_state, + _state._curMemberPtr->_data); +} + + void initControlData(); + + +void StampBoltFile::initControl() { + initDefault(); + + ControlResource *res; + _state._curMemberPtr->_controlResource = res = new ControlResource(_state, + _state._curMemberPtr->_data); + + _state._vm->_controlGroupPtr = _state._curGroupPtr; + _state._vm->_controlPtr = res; +} + +void StampBoltFile::initState() { + initDefault(); + + assert(_state._curMemberPtr->_size == 16); + _state._curMemberPtr->_stateResource = new StateResource(_state, + _state._curMemberPtr->_data); +} + +/*------------------------------------------------------------------------*/ + +BoltGroup::BoltGroup(Common::SeekableReadStream *f): _file(f) { + byte buffer[BOLT_GROUP_SIZE]; + + _loaded = false; + + _file->read(&buffer[0], BOLT_GROUP_SIZE); + _processed = buffer[0] != 0; + _callInitGro = buffer[1] != 0; + _termGroIndex = buffer[2]; + _count = buffer[3] ? buffer[3] : 256; + _fileOffset = READ_LE_UINT32(&buffer[8]); +} + +BoltGroup::~BoltGroup() { +} + +void BoltGroup::load(uint16 groupId) { + _file->seek(_fileOffset); + + // Read the entries + for (int i = 0; i < _count; ++i) + _entries.push_back(BoltEntry(_file, groupId + i)); + + _loaded = true; +} + +void BoltGroup::unload() { + if (!_loaded) + return; + + _entries.clear(); + _loaded = false; +} + +/*------------------------------------------------------------------------*/ + +BoltEntry::BoltEntry(Common::SeekableReadStream *f, uint16 id): _file(f), _id(id) { + _data = nullptr; + _rectResource = nullptr; + _picResource = nullptr; + _viewPortResource = nullptr; + _viewPortListResource = nullptr; + _fontResource = nullptr; + _fontInfoResource = nullptr; + _cMapResource = nullptr; + _vInitCycleResource = nullptr; + + _ptrResource = nullptr; + _stateResource = nullptr; + _controlResource = nullptr; + _vInitCycleResource = nullptr; + _threadResource = nullptr; + + byte buffer[16]; + _file->read(&buffer[0], 16); + _mode = buffer[0]; + _initMemRequired = buffer[1]; + _initMethod = buffer[3]; + _xorMask = buffer[4] & 0xff; + _size = READ_LE_UINT32(&buffer[4]) & 0xffffff; + _fileOffset = READ_LE_UINT32(&buffer[8]); +} + +BoltEntry::~BoltEntry() { + delete[] _data; + delete _rectResource; + delete _picResource; + delete _viewPortResource; + delete _viewPortListResource; + delete _fontResource; + delete _fontInfoResource; + delete _cMapResource; + + delete _ptrResource; + delete _controlResource; + delete _stateResource; + delete _vInitCycleResource; + delete _threadResource; +} + +void BoltEntry::load() { + // Currently, all entry loading and decompression is done in BoltFile::memberAddr. +} + +/** + * Returns true if the given bolt entry has an attached resource + */ +bool BoltEntry::hasResource() const { + return _rectResource || _picResource || _viewPortResource || _viewPortListResource + || _fontResource || _fontInfoResource || _cMapResource + || _vInitCycleResource + || _ptrResource || _controlResource || _stateResource || _threadResource; +} + +/*------------------------------------------------------------------------*/ + +RectEntry::RectEntry(int x1, int y1, int x2, int y2, int arrIndex, int count): + Common::Rect(x1, y1, x2, y2), _arrIndex(arrIndex), _count(count) { +} + +/*------------------------------------------------------------------------*/ + +RectResource::RectResource(const byte *src, int size, bool isExtendedRects) { + int count; + int rectSize = isExtendedRects ? 12 : 8; + if ((size % rectSize) == 2) { + count = READ_LE_UINT16(src); + src += 2; + } else { + count = size / rectSize; + } + + for (int i = 0; i < count; ++i, src += 8) { + int arrIndex = 0, count = 0; + if (isExtendedRects) { + arrIndex = READ_LE_UINT16(src); + count = READ_LE_UINT16(src + 2); + src += 4; + } + + int x1 = READ_LE_UINT16(src); + int y1 = READ_LE_UINT16(src + 2); + int x2 = READ_LE_UINT16(src + 4); + int y2 = READ_LE_UINT16(src + 6); + + _entries.push_back(RectEntry(x1, y1, x2, y2, arrIndex, count)); + } + + left = _entries[0].left; + top = _entries[0].top; + right = _entries[0].right; + bottom = _entries[0].bottom; +} + +RectResource::RectResource(int x1, int y1, int x2, int y2) { + left = x1; + top = y1; + right = x2; + bottom = y2; +} + +/*------------------------------------------------------------------------*/ + +DisplayResource::DisplayResource() { + _vm = NULL; +} + +DisplayResource::DisplayResource(VoyeurEngine *vm) { + _vm = vm; +} + +void DisplayResource::sFillBox(int width, int height) { + assert(_vm); + bool saveBack = _vm->_graphicsManager._saveBack; + _vm->_graphicsManager._saveBack = false; + + PictureResource pr; + pr._flags = 1; + pr._select = 0xff; + pr._pick = 0; + pr._onOff = _vm->_graphicsManager._drawPtr->_penColor; + pr._bounds = Common::Rect(0, 0, width, height); + + _vm->_graphicsManager.sDrawPic(&pr, this, _vm->_graphicsManager._drawPtr->_pos); + _vm->_graphicsManager._saveBack = saveBack; +} + +bool DisplayResource::clipRect(Common::Rect &rect) { + Common::Rect clipRect; + if (_vm->_graphicsManager._clipPtr) { + clipRect = *_vm->_graphicsManager._clipPtr; + } else if (_flags & DISPFLAG_VIEWPORT) { + clipRect = ((ViewPortResource *)this)->_clipRect; + } else { + clipRect = ((PictureResource *)this)->_bounds; + } + + Common::Rect r = rect; + if (r.left < clipRect.left) { + if (r.right <= clipRect.left) + return false; + r.setWidth(r.right - clipRect.left); + } + if (r.right >= clipRect.right) { + if (r.left >= clipRect.left) + return false; + r.setWidth(clipRect.right - r.left); + } + + if (r.top < clipRect.top) { + if (r.bottom <= clipRect.top) + return false; + r.setHeight(r.bottom - clipRect.top); + } + if (r.bottom >= clipRect.bottom) { + if (r.top >= clipRect.top) + return false; + r.setWidth(clipRect.bottom - r.top); + } + + rect = r; + return true; +} + +int DisplayResource::drawText(const Common::String &msg) { + GraphicsManager &gfxManager = _vm->_graphicsManager; + assert(gfxManager._fontPtr); + assert(gfxManager._fontPtr->_curFont); + FontInfoResource &fontInfo = *gfxManager._fontPtr; + PictureResource &fontChar = *_vm->_graphicsManager._fontChar; + FontResource &fontData = *fontInfo._curFont; + int xShadows[9] = { 0, 1, 1, 1, 0, -1, -1, -1, 0 }; + int yShadows[9] = { 0, 1, 0, -1, -1, -1, 0, 1, 1 }; + + Common::Rect *clipPtr = gfxManager._clipPtr; + if (!(fontInfo._picFlags & DISPFLAG_1)) + gfxManager._clipPtr = NULL; + + int minChar = fontData._minChar; + int padding = fontData._padding; + int fontHeight = fontData._fontHeight; + int totalChars = fontData._maxChar - fontData._minChar; + int msgWidth = 0; + int xp = 0, yp = 0; + + Common::Point pos = Common::Point(fontInfo._pos.x, fontInfo._pos.y + fontData._topPadding); + + fontChar._flags = fontInfo._picFlags | DISPFLAG_2; + fontChar._select = fontInfo._picSelect; + fontChar._bounds.setHeight(fontHeight); + + ViewPortResource *viewPort = !(_flags & DISPFLAG_VIEWPORT) ? NULL : + (ViewPortResource *)this; + + if (gfxManager._drawTextPermFlag || (fontInfo._fontFlags & DISPFLAG_1) || fontInfo._justify || + (gfxManager._saveBack && fontInfo._fontSaveBack && (_flags & DISPFLAG_VIEWPORT))) { + msgWidth = viewPort->textWidth(msg); + yp = pos.y; + xp = pos.x; + + switch (fontInfo._justify) { + case 1: + xp = pos.x + (fontInfo._justifyWidth / 2) - (msgWidth / 2); + break; + case 2: + xp = pos.x + fontInfo._justifyWidth - msgWidth; + break; + default: + break; + } + + if (!(fontInfo._fontFlags & 3)) { + viewPort->_fontRect.left = xp; + viewPort->_fontRect.top = yp; + viewPort->_fontRect.setWidth(msgWidth); + viewPort->_fontRect.setHeight(fontHeight); + } else { + viewPort->_fontRect.left = pos.x; + viewPort->_fontRect.top = pos.y; + viewPort->_fontRect.setWidth(fontInfo._justifyWidth); + viewPort->_fontRect.setHeight(fontInfo._justifyHeight); + } + + pos.x = xp; + pos.y = yp; + + if (fontInfo._fontFlags & 4) { + if (fontInfo._shadow.x <= 0) { + viewPort->_fontRect.left += fontInfo._shadow.x; + viewPort->_fontRect.right -= fontInfo._shadow.x * 2; + } else { + viewPort->_fontRect.right += fontInfo._shadow.x; + } + + if (fontInfo._shadow.y <= 0) { + viewPort->_fontRect.top += fontInfo._shadow.y; + viewPort->_fontRect.bottom -= fontInfo._shadow.y * 2; + } else { + viewPort->_fontRect.bottom += fontInfo._shadow.y; + } + } else if (fontInfo._fontFlags & 8) { + if (fontInfo._shadow.x <= 0) { + viewPort->_fontRect.left += fontInfo._shadow.x; + viewPort->_fontRect.right -= fontInfo._shadow.x * 3; + } else { + viewPort->_fontRect.right += fontInfo._shadow.x * 3; + viewPort->_fontRect.left -= fontInfo._shadow.x; + } + + if (fontInfo._shadow.y <= 0) { + viewPort->_fontRect.top += fontInfo._shadow.y; + viewPort->_fontRect.bottom -= fontInfo._shadow.y * 3; + } else { + viewPort->_fontRect.bottom += fontInfo._shadow.y * 3; + viewPort->_fontRect.top -= fontInfo._shadow.y; + } + } + } + + if (gfxManager._saveBack && fontInfo._fontSaveBack && (_flags & DISPFLAG_VIEWPORT)) { + viewPort->addSaveRect(viewPort->_pageIndex, viewPort->_fontRect); + } + + if (fontInfo._fontFlags & DISPFLAG_1) { + gfxManager._drawPtr->_pos = Common::Point(viewPort->_fontRect.left, viewPort->_fontRect.top); + gfxManager._drawPtr->_penColor = fontInfo._backColor; + sFillBox(viewPort->_fontRect.width(), viewPort->_fontRect.height()); + } + + bool saveBack = gfxManager._saveBack; + gfxManager._saveBack = false; + + int count = 0; + if (fontInfo._fontFlags & DISPFLAG_4) + count = 1; + else if (fontInfo._fontFlags & DISPFLAG_8) + count = 8; + + for (int i = count; i >= 0; --i) { + xp = pos.x; + yp = pos.y; + + switch (xShadows[i]) { + case -1: + xp -= fontInfo._shadow.x; + break; + case 1: + xp += fontInfo._shadow.x; + break; + default: + break; + } + + switch (yShadows[i]) { + case -1: + yp -= fontInfo._shadow.y; + break; + case 1: + yp += fontInfo._shadow.y; + break; + default: + break; + } + + if (i != 0) { + fontChar._pick = 0; + fontChar._onOff = fontInfo._shadowColor; + } else if (fontData._fontDepth == 1 || (fontInfo._fontFlags & 0x10)) { + fontChar._pick = 0; + fontChar._onOff = fontInfo._foreColor; + } else { + fontChar._pick = fontInfo._picPick; + fontChar._onOff = fontInfo._picOnOff; + fontChar._depth = fontData._fontDepth; + } + + // Loop to draw each character in turn + msgWidth = -padding; + const char *msgP = msg.c_str(); + char ch; + + while ((ch = *msgP++) != '\0') { + int charValue = (int)ch - minChar; + if (charValue < 0 || charValue >= totalChars || fontData._charWidth[charValue] == 0) + charValue = fontData._maxChar - minChar; + + int charWidth = fontData._charWidth[charValue]; + fontChar._bounds.setWidth(charWidth); + + uint16 offset = READ_LE_UINT16(fontData._charOffsets + charValue * 2); + fontChar._imgData = fontData._charImages + offset * 2; + + gfxManager.sDrawPic(&fontChar, this, Common::Point(xp, yp)); + + fontChar._imgData = NULL; + xp += charWidth + padding; + msgWidth += charWidth + padding; + } + } + + msgWidth = MAX(msgWidth, 0); + if (fontInfo._justify == ALIGN_LEFT) + fontInfo._pos.x = xp; + + gfxManager._saveBack = saveBack; + gfxManager._clipPtr = clipPtr; + + return msgWidth; +} + +int DisplayResource::textWidth(const Common::String &msg) { + if (msg.size() == 0) + return 0; + + const char *msgP = msg.c_str(); + FontResource &fontData = *_vm->_graphicsManager._fontPtr->_curFont; + int minChar = fontData._minChar; + int maxChar = fontData._maxChar; + int padding = fontData._padding; + int totalWidth = -padding; + char ch; + + // Loop through the characters + while ((ch = *msgP++) != '\0') { + int charValue = (int)ch; + if (charValue < minChar || charValue > maxChar) + charValue = maxChar; + + if (!fontData._charWidth[charValue - minChar]) + charValue = maxChar; + + totalWidth += fontData._charWidth[charValue - minChar] + padding; + } + + if (totalWidth < 0) + totalWidth = 0; + return totalWidth; +} + +/*------------------------------------------------------------------------*/ + +PictureResource::PictureResource(BoltFilesState &state, const byte *src): + DisplayResource(state._vm) { + _flags = READ_LE_UINT16(src); + _select = src[2]; + _pick = src[3]; + _onOff = src[4]; + _depth = src[5]; + + int xs = READ_LE_UINT16(&src[6]); + int ys = READ_LE_UINT16(&src[8]); + _bounds = Common::Rect(xs, ys, xs + READ_LE_UINT16(&src[10]), + ys + READ_LE_UINT16(&src[12])); + _maskData = READ_LE_UINT32(&src[14]); + _planeSize = READ_LE_UINT16(&src[22]); + + _imgData = NULL; + _freeImgData = DisposeAfterUse::YES; + + int nbytes = _bounds.width() * _bounds.height(); + if (_flags & PICFLAG_20) { + if (_flags & (PICFLAG_VFLIP | PICFLAG_HFLIP)) { + // Get the raw data for the picture from another resource + uint32 id = READ_LE_UINT32(&src[18]); + const byte *srcData = state._curLibPtr->boltEntry(id)._data; + _imgData = new byte[nbytes]; + + // Flip the image data either horizontally or vertically + if (_flags & PICFLAG_HFLIP) + flipHorizontal(srcData); + else + flipVertical(srcData); + } else { + uint32 id = READ_LE_UINT32(&src[18]) >> 16; + byte *imgData = state._curLibPtr->boltEntry(id)._picResource->_imgData; + _freeImgData = DisposeAfterUse::NO; + + // TODO: Double check code below. Despite different coding in the + // original, both looked like they do the same formula + if (_flags & PICFLAG_PIC_OFFSET) { + _imgData = imgData + (READ_LE_UINT32(&src[18]) & 0xffff); + } else { + _imgData = imgData + (READ_LE_UINT32(&src[18]) & 0xffff); + } + } + } else if (_flags & PICFLAG_PIC_OFFSET) { + int mode = 0; + if (_bounds.width() == 320) { + mode = 147; + state._sImageShift = 2; + state._SVGAReset = false; + } else { + state._SVGAReset = true; + if (_bounds.width() == 640) { + if (_bounds.height() == 400) { + mode = 220; + state._sImageShift = 3; + } else { + mode = 221; + state._sImageShift = 3; + } + } else if (_bounds.width() == 800) { + mode = 222; + state._sImageShift = 3; + } else if (_bounds.width() == 1024) { + mode = 226; + state._sImageShift = 3; + } + } + + if (mode != state._vm->_graphicsManager._SVGAMode) { + state._vm->_graphicsManager._SVGAMode = mode; + state._vm->_graphicsManager.clearPalette(); + } + + int screenOffset = READ_LE_UINT32(&src[18]) & 0xffff; + assert(screenOffset == 0); + + if (_flags & PICFLAG_CLEAR_SCREEN) { + // Clear screen picture. That's right. This game actually has a picture + // resource flag to clear the screen! Bizarre. + Graphics::Surface &s = state._vm->_graphicsManager._screenSurface; + s.fillRect(Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT), 0); + } else { + // Direct sceren loading picture. In this case, the raw data of the resource + // is directly decompressed into the screen surface. Again, bizarre. + byte *pDest = (byte *)state._vm->_graphicsManager._screenSurface.getPixels(); + state.decompress(pDest, SCREEN_WIDTH * SCREEN_HEIGHT, state._curMemberPtr->_mode); + } + } else { + if (_flags & PICFLAG_CLEAR_SCREEN00) { + if (!(_flags & PICFLAG_CLEAR_SCREEN)) + nbytes = state._curMemberPtr->_size - 24; + + int mask = (nbytes + 0x3FFF) >> 14; + _imgData = NULL; + + if (state._boltPageFrame == 0) + state.EMSGetFrameAddr(&state._boltPageFrame); + if (state._boltPageFrame != 0) { + if (!state.EMSAllocatePages(&_planeSize)) { + _maskData = mask; + + for (int idx = 0; idx < mask; ++idx) + state.EMSMapPageHandle(_planeSize, idx, idx); + + state.decompress(state._boltPageFrame, nbytes, state._curMemberPtr->_mode); + return; + } + } + } + + if (_flags & PICFLAG_CLEAR_SCREEN) { + _imgData = new byte[nbytes]; + Common::fill(_imgData, _imgData + nbytes, 0); + } else { + _imgData = state.decompress(NULL, nbytes, state._curMemberPtr->_mode); + } + } +} + +PictureResource::PictureResource(Graphics::Surface *surface) { + _flags = 0; + _select = 0; + _pick = 0; + _onOff = 0; + _depth = 0; + _maskData = 0; + _planeSize = 0; + + _bounds = Common::Rect(0, 0, surface->w, surface->h); + _imgData = (byte *)surface->getPixels(); + _freeImgData = DisposeAfterUse::NO; +} + +PictureResource::PictureResource() { + _flags = 0; + _select = 0; + _pick = 0; + _onOff = 0; + _depth = 0; + _maskData = 0; + _planeSize = 0; + + _imgData = NULL; + _freeImgData = DisposeAfterUse::NO; +} + +PictureResource::PictureResource(int flags, int select, int pick, int onOff, + int depth, const Common::Rect &bounds, int maskData, byte *imgData, + int planeSize) { + _flags = flags; + _select = select; + _pick = pick; + _onOff = onOff; + _depth = depth; + _bounds = bounds; + _maskData = maskData; + _imgData = imgData; + _planeSize = planeSize; +} + +PictureResource::~PictureResource() { + if (_freeImgData == DisposeAfterUse::YES) + delete[] _imgData; +} + +void PictureResource::flipHorizontal(const byte *data) { + const byte *srcP = data + 18; + byte *destP = _imgData + _bounds.width() - 1; + + for (int y = 0; y < _bounds.height(); ++y) { + for (int x = 0; x < _bounds.width(); ++x, ++srcP, --destP) + *destP = *srcP; + + srcP += _bounds.width(); + destP += _bounds.width(); + } +} + +void PictureResource::flipVertical(const byte *data) { + const byte *srcP = data + 18; + byte *destP = _imgData + _bounds.width() * (_bounds.height() - 1); + + for (int y = 0; y < _bounds.height(); ++y) { + Common::copy(srcP, srcP + _bounds.width(), destP); + srcP += _bounds.width(); + destP -= _bounds.width(); + } +} + +/*------------------------------------------------------------------------*/ + +ViewPortResource::ViewPortResource(BoltFilesState &state, const byte *src): + _state(state), DisplayResource(state._vm) { + _flags = READ_LE_UINT16(src); + _parent = NULL; + _pageCount = READ_LE_UINT16(src + 6); + _pageIndex = READ_LE_UINT16(src + 8); + _lastPage = READ_LE_UINT16(src + 10); + + int xs = READ_LE_UINT16(src + 12); + int ys = READ_LE_UINT16(src + 14); + _bounds = Common::Rect(xs, ys, xs + READ_LE_UINT16(src + 16), + ys + READ_LE_UINT16(src + 18)); + _field18 = READ_LE_UINT16(src + 0x18); + + _currentPic = state._curLibPtr->getPictureResource(READ_LE_UINT32(src + 0x20)); + _activePage = state._curLibPtr->getPictureResource(READ_LE_UINT32(src + 0x24)); + _pages[0] = state._curLibPtr->getPictureResource(READ_LE_UINT32(src + 0x28)); + _pages[1] = state._curLibPtr->getPictureResource(READ_LE_UINT32(src + 0x2C)); + + state._curLibPtr->resolveIt(READ_LE_UINT32(src + 0x30), &_field30); + + // Get the rect list + for (int listIndex = 0; listIndex < 3; ++listIndex) { + _rectListCount[listIndex] = (int16)READ_LE_UINT16(src + 0x40 + 2 * listIndex); + int id = (int)READ_LE_UINT32(src + 0x34 + listIndex * 4); + + if (id == -1) { + _rectListPtr[listIndex] = NULL; + } else { + _rectListPtr[listIndex] = new Common::Array<Common::Rect>(); + + if (_rectListCount[listIndex] > 0) { + int16 *rectList = (int16 *)state._curLibPtr->memberAddrOffset(id); + for (int i = 0; i < _rectListCount[listIndex]; ++i) { + xs = FROM_LE_16(rectList[0]); + ys = FROM_LE_16(rectList[1]); + _rectListPtr[i]->push_back(Common::Rect(xs, ys, xs + FROM_LE_16(rectList[2]), + ys + FROM_LE_16(rectList[3]))); + } + } + } + } + + xs = READ_LE_UINT16(src + 0x46); + ys = READ_LE_UINT16(src + 0x48); + _clipRect = Common::Rect(xs, ys, xs + READ_LE_UINT16(src + 0x4A), + ys + READ_LE_UINT16(src + 0x4C)); + + state._curLibPtr->resolveIt(READ_LE_UINT32(src + 0x7A), &_field7A); + + state._curLibPtr->resolveFunction(READ_LE_UINT32(src + 0x7E), (GraphicMethodPtr *)&_fn1); + state._curLibPtr->resolveFunction(READ_LE_UINT32(src + 0x82), (GraphicMethodPtr *)&_setupFn); + state._curLibPtr->resolveFunction(READ_LE_UINT32(src + 0x86), (GraphicMethodPtr *)&_addFn); + state._curLibPtr->resolveFunction(READ_LE_UINT32(src + 0x8A), (GraphicMethodPtr *)&_restoreFn); + + if (!_restoreFn && _addFn) + _addFn = &GraphicsManager::addRectNoSaveBack; +} + +ViewPortResource::~ViewPortResource() { + for (int i = 0; i < 3; ++i) + delete _rectListPtr[i]; +} + +void ViewPortResource::setupViewPort(PictureResource *page, Common::Rect *clipRect, + ViewPortSetupPtr setupFn, ViewPortAddPtr addFn, ViewPortRestorePtr restoreFn) { + PictureResource *pic = _currentPic; + Common::Rect r = _bounds; + r.translate(pic->_bounds.left, pic->_bounds.top); + int xDiff, yDiff; + + if (page) { + // Clip based on the passed picture resource + xDiff = page->_bounds.left - r.left; + yDiff = page->_bounds.top - r.top; + + if (xDiff > 0) { + int width = r.width(); + r.left = page->_bounds.left; + r.setWidth(xDiff <= width ? width - xDiff : 0); + } + if (yDiff > 0) { + int height = r.height(); + r.top = page->_bounds.top; + r.setHeight(yDiff <= height ? height - yDiff : 0); + } + + xDiff = r.right - page->_bounds.right; + yDiff = r.bottom - page->_bounds.bottom; + + if (xDiff > 0) + r.setWidth(xDiff <= r.width() ? r.width() - xDiff : 0); + if (yDiff > 0) + r.setHeight(yDiff <= r.height() ? r.height() - yDiff : 0); + } + + if (clipRect) { + // Clip based on the passed clip rectangles + xDiff = clipRect->left - r.left; + yDiff = clipRect->top - r.top; + + if (xDiff > 0) { + int width = r.width(); + r.left = clipRect->left; + r.setWidth(xDiff <= width ? width - xDiff : 0); + } + if (yDiff > 0) { + int height = r.height(); + r.top = clipRect->top; + r.setHeight(yDiff <= height ? height - yDiff : 0); + } + + xDiff = r.right - clipRect->right; + yDiff = r.bottom - clipRect->bottom; + + if (xDiff > 0) + r.setWidth(xDiff <= r.width() ? r.width() - xDiff : 0); + if (yDiff > 0) + r.setHeight(yDiff <= r.height() ? r.height() - yDiff : 0); + } + + _activePage = page; + _field18 = 0; + _clipRect = r; + _setupFn = setupFn; + _addFn = addFn; + _restoreFn = restoreFn; + + if (setupFn) + (_state._vm->_graphicsManager.*setupFn)(this); +} + +void ViewPortResource::setupViewPort() { + setupViewPort(_state._vm->_graphicsManager._backgroundPage, NULL, + &GraphicsManager::setupMCGASaveRect, &GraphicsManager::addRectOptSaveRect, + &GraphicsManager::restoreMCGASaveRect); +} + +void ViewPortResource::setupViewPort(PictureResource *pic, Common::Rect *clipRect) { + setupViewPort(pic, clipRect, + &GraphicsManager::setupMCGASaveRect, &GraphicsManager::addRectOptSaveRect, + &GraphicsManager::restoreMCGASaveRect); +} + +void ViewPortResource::addSaveRect(int pageIndex, const Common::Rect &r) { + Common::Rect rect = r; + + if (clipRect(rect)) { + if (_addFn) { + (_state._vm->_graphicsManager.*_addFn)(this, pageIndex, rect); + } else if (_rectListCount[pageIndex] != -1) { + _rectListPtr[pageIndex]->push_back(rect); + } + } +} + +void ViewPortResource::fillPic(byte onOff) { + _state._vm->_graphicsManager.fillPic(this, onOff); +} + +void ViewPortResource::drawIfaceTime() { + // Hour display + _state._vm->_graphicsManager.drawANumber(*_state._vm->_graphicsManager._vPort, + (_state._vm->_gameHour / 10) == 0 ? 10 : _state._vm->_gameHour / 10, + Common::Point(161, 25)); + _state._vm->_graphicsManager.drawANumber(*_state._vm->_graphicsManager._vPort, + _state._vm->_gameHour % 10, Common::Point(172, 25)); + + // Minute display + _state._vm->_graphicsManager.drawANumber(*_state._vm->_graphicsManager._vPort, + _state._vm->_gameMinute / 10, Common::Point(190, 25)); + _state._vm->_graphicsManager.drawANumber(*_state._vm->_graphicsManager._vPort, + _state._vm->_gameMinute % 10, Common::Point(201, 25)); + + // AM/PM indicator + PictureResource *pic = _state._vm->_bVoy->boltEntry(_state._vm->_voy._isAM ? 272 : 273)._picResource; + _state._vm->_graphicsManager.sDrawPic(pic, *_state._vm->_graphicsManager._vPort, + Common::Point(215, 27)); +} + +void ViewPortResource::drawPicPerm(PictureResource *pic, const Common::Point &pt) { + Common::Rect bounds = pic->_bounds; + bounds.translate(pt.x, pt.y); + + bool saveBack = _state._vm->_graphicsManager._saveBack; + _state._vm->_graphicsManager._saveBack = false; + _state._vm->_graphicsManager.sDrawPic(pic, this, pt); + clipRect(bounds); + + for (int pageIndex = 0; pageIndex < _pageCount; ++pageIndex) { + if (_pageIndex != pageIndex) { + addSaveRect(pageIndex, bounds); + } + } + + _state._vm->_graphicsManager._saveBack = saveBack; +} +/*------------------------------------------------------------------------*/ + +ViewPortListResource::ViewPortListResource(BoltFilesState &state, const byte *src) { + uint count = READ_LE_UINT16(src); + _palIndex = READ_LE_UINT16(src + 2); + + // Load palette map + byte *palData = state._curLibPtr->memberAddr(READ_LE_UINT32(src + 4)); + for (uint i = 0; i < 256; ++i, palData += 16) + _palette.push_back(ViewPortPalEntry(palData)); + + // Load view port pointer list + const uint32 *idP = (const uint32 *)&src[8]; + for (uint i = 0; i < count; ++i, ++idP) { + uint32 id = READ_LE_UINT32(idP); + BoltEntry &entry = state._curLibPtr->getBoltEntryFromLong(id); + + assert(entry._viewPortResource); + _entries.push_back(entry._viewPortResource); + } +} + +/*------------------------------------------------------------------------*/ + +ViewPortPalEntry::ViewPortPalEntry(const byte *src) { + const uint16 *v = (const uint16 *)src; + _rEntry = READ_LE_UINT16(v++); + _gEntry = READ_LE_UINT16(v++); + _bEntry = READ_LE_UINT16(v++); + _rChange = READ_LE_UINT16(v++); + _gChange = READ_LE_UINT16(v++); + _bChange = READ_LE_UINT16(v++); + _palIndex = READ_LE_UINT16(v++); +} + + +/*------------------------------------------------------------------------*/ + +FontResource::FontResource(BoltFilesState &state, byte *src) { + _minChar = src[0]; + _maxChar = src[1]; + _fontDepth = src[2]; + _padding = src[3]; + _fontHeight = src[5]; + _topPadding = (int8)src[6]; + + int totalChars = _maxChar - _minChar + 1; + _charWidth = new int[totalChars]; + for (int i = 0; i < totalChars; ++i) + _charWidth[i] = READ_LE_UINT16(src + 8 + 2 * i); + + _charOffsets = src + 8 + totalChars * 2; + _charImages = _charOffsets + totalChars * 2; +} + +FontResource::~FontResource() { + delete[] _charWidth; +} + +/*------------------------------------------------------------------------*/ + +FontInfoResource::FontInfoResource(BoltFilesState &state, const byte *src) { + _curFont = NULL; + _picFlags = src[4]; + _picSelect = src[5]; + _picPick = src[6]; + _picOnOff = src[7]; + _fontFlags = src[8]; + _justify = (FontJustify)src[9]; + _fontSaveBack = READ_LE_UINT16(src + 10); + _pos.x = (int16)READ_LE_UINT16(src + 12); + _pos.y = (int16)READ_LE_UINT16(src + 14); + _justifyWidth = READ_LE_UINT16(src + 16); + _justifyHeight = READ_LE_UINT16(src + 18); + _shadow.x = READ_LE_UINT16(src + 20); + _shadow.y = READ_LE_UINT16(src + 22); + _foreColor = READ_LE_UINT16(src + 24); + _backColor = READ_LE_UINT16(src + 26); + _shadowColor = READ_LE_UINT16(src + 28); +} + +FontInfoResource::FontInfoResource() { + _curFont = NULL; + _picFlags = 3; + _picSelect = 0xff; + _picPick = 0xff; + _picOnOff = 0; + _fontFlags = 0; + _justify = ALIGN_LEFT; + _fontSaveBack = 0; + _justifyWidth = 1; + _justifyHeight = 1; + _shadow = Common::Point(1, 1); + _foreColor = 1; + _backColor = 0; + _shadowColor = 0; +} + +FontInfoResource::FontInfoResource(byte picFlags, byte picSelect, byte picPick, byte picOnOff, + byte fontFlags, FontJustify justify, int fontSaveBack, const Common::Point &pos, + int justifyWidth, int justifyHeight, const Common::Point &shadow, int foreColor, + int backColor, int shadowColor) { + _curFont = NULL; + _picFlags = picFlags; + _picSelect = picSelect; + _picPick = picPick; + _picOnOff = picOnOff; + _fontFlags = fontFlags; + _justify = justify; + _fontSaveBack = fontSaveBack; + _pos = pos; + _justifyWidth = justifyWidth; + _justifyHeight = justifyHeight; + _shadow = shadow; + _foreColor = foreColor; + _backColor = backColor; + _shadowColor = shadowColor; +} + +/*------------------------------------------------------------------------*/ + +CMapResource::CMapResource(BoltFilesState &state, const byte *src): _vm(state._vm) { + _steps = src[0]; + _fadeStatus = src[1]; + _start = READ_LE_UINT16(src + 2); + _end = READ_LE_UINT16(src + 4); + + int count = _end - _start + 1; + _entries = new byte[count * 3]; + Common::copy(src + 6, src + 6 + 3 * count, _entries); + + int palIndex = state._vm->_graphicsManager._viewPortListPtr->_palIndex; + if (_end > palIndex) + _end = palIndex; + if (_start > palIndex) + _start = palIndex; +} + +CMapResource::~CMapResource() { + delete[] _entries; +} + +void CMapResource::startFade() { + _vm->_eventsManager.startFade(this); +} + +/*------------------------------------------------------------------------*/ + +VInitCycleResource::VInitCycleResource(BoltFilesState &state, const byte *src): + _state(state) { + // Set up arrays + for (int i = 0; i < 4; ++i) { + _type[i] = READ_LE_UINT16(src + i * 2); + state._curLibPtr->resolveIt(READ_LE_UINT32(src + 8 + i * 4), &_ptr[i]); + } +} + +void VInitCycleResource::vStartCycle(int flags) { + EventsManager &evt = _state._vm->_eventsManager; + evt._cycleIntNode._flags |= 1; + evt._cyclePtr = this; + + for (int i = 0; i < 4; ++i) { + evt._cycleNext[i] = _ptr[i]; + evt._cycleTime[i] = 0; + } + + evt._cycleStatus = flags | 1; + evt._cycleIntNode._flags &= ~1; +} + +void VInitCycleResource::vStopCycle() { + EventsManager &evt = _state._vm->_eventsManager; + evt._cycleIntNode._flags |= 1; + evt._cycleStatus &= ~1; +} + +/*------------------------------------------------------------------------*/ + +PtrResource::PtrResource(BoltFilesState &state, const byte *src) { + // Load pointer list + const uint32 *idP = (const uint32 *)&src[0]; + int size = state._curMemberPtr->_size; + + for (int i = 0; i < size / 4; ++i, ++idP) { + uint32 id = READ_LE_UINT32(idP); + BoltEntry &entry = state._curLibPtr->getBoltEntryFromLong(id); + + _entries.push_back(&entry); + } +} + +/*------------------------------------------------------------------------*/ + +ControlResource::ControlResource(BoltFilesState &state, const byte *src) { + // Get Id for the state data. Since it refers to a following entry in the same + // group, for simplicity we set the _state back in the main playStamp method + _stateId = READ_LE_UINT32(&src[0x32]); + _state = nullptr; + + for (int i = 0; i < 8; ++i) + _memberIds[i] = READ_LE_UINT16(src + i * 2); + + // Load pointer list + const uint32 *idP = (const uint32 *)&src[0x10]; + int count = READ_LE_UINT16(&src[0x36]); + + Common::fill(&_entries[0], &_entries[8], (byte *)nullptr); + for (int i = 0; i < count; ++i, ++idP) { + uint32 id = READ_LE_UINT32(idP); + state._curLibPtr->resolveIt(id, &_entries[i]); + } +} + +/*------------------------------------------------------------------------*/ + +StateResource::StateResource(BoltFilesState &state, const byte *src): + _victimIndex(_vals[1]), _victimEvidenceIndex(_vals[2]), + _victimMurderIndex(_vals[3]) { + for (int i = 0; i < 4; ++i) + _vals[i] = READ_LE_UINT32(src + i * 4); +} + +void StateResource::synchronize(Common::Serializer &s) { + for (int i = 0; i < 4; ++i) + s.syncAsSint32LE(_vals[i]); +} + +} // End of namespace Voyeur diff --git a/engines/voyeur/files.h b/engines/voyeur/files.h new file mode 100644 index 0000000000..69802d8f68 --- /dev/null +++ b/engines/voyeur/files.h @@ -0,0 +1,650 @@ +/* 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. + * + */ + +#ifndef VOYEUR_FILES_H +#define VOYEUR_FILES_H + +#include "common/scummsys.h" +#include "common/file.h" +#include "common/rect.h" +#include "common/str.h" +#include "voyeur/graphics.h" + +namespace Voyeur { + +class VoyeurEngine; +class BoltFile; +class BoltGroup; +class BoltEntry; +class RectResource; +class PictureResource; +class ViewPortResource; +class ViewPortListResource; +class FontResource; +class CMapResource; +class VInitCycleResource; +class PtrResource; +class ControlResource; +class StateResource; +class ThreadResource; + +#define DECOMPRESS_SIZE 0x7000 + +class ResolveEntry { +public: + uint32 _id; + byte **_p; + + ResolveEntry(uint32 id, byte **p) { _id = id; _p = p; } +}; + +class BoltFilesState { +public: + VoyeurEngine *_vm; + BoltFile *_curLibPtr; + BoltGroup *_curGroupPtr; + BoltEntry *_curMemberPtr; + byte *_curMemInfoPtr; + int _fromGroupFlag; + byte _xorMask; + bool _encrypt; + int _curFilePosition; + int _bufferEnd; + int _bufferBegin; + int _bytesLeft; + int _bufSize; + byte *_bufStart; + byte *_bufPos; + byte _decompressBuf[DECOMPRESS_SIZE]; + int _historyIndex; + byte _historyBuffer[0x200]; + int _runLength; + int _decompState; + int _runType; + int _runValue; + int _runOffset; + Common::File *_curFd; + Common::Array<ResolveEntry> _resolves; + + byte *_boltPageFrame; + int _sImageShift; + bool _SVGAReset; +public: + BoltFilesState(); + + byte *decompress(byte *buf, int size, int mode); + void nextBlock(); + + // Methods in the original stubbed under ScummVM + void EMSGetFrameAddr(byte **pageFrame) {} + bool EMSAllocatePages(uint *planeSize) { return false; } + void EMSMapPageHandle(int planeSize, int idx1, int idx2) {} +}; + +class BoltFile { +private: + Common::Array<BoltGroup> _groups; +protected: + BoltFilesState &_state; + + virtual void initResource(int resType) = 0; + void initDefault(); +private: + void resolveAll(); + byte *getBoltMember(uint32 id); + + // Methods in the original that are stubbed in ScummVM + void termType() {} + void initMem(int id) {} + void termMem() {} + void initGro() {} + void termGro() {} +public: + Common::File _file; +public: + BoltFile(const Common::String &filename, BoltFilesState &state); + virtual ~BoltFile(); + + BoltGroup *getBoltGroup(uint16 id, bool process = true); + void freeBoltGroup(uint16 id, bool freeEntries = true); + void freeBoltMember(uint32 id); + byte *memberAddr(uint32 id); + byte *memberAddrOffset(uint32 id); + void resolveIt(uint32 id, byte **p); + void resolveFunction(uint32 id, GraphicMethodPtr *fn); + + BoltEntry &boltEntry(uint16 id); + BoltEntry &getBoltEntryFromLong(uint32 id); + PictureResource *getPictureResource(uint32 id); + CMapResource *getCMapResource(uint32 id); +}; + +class BVoyBoltFile: public BoltFile { +private: + // initType method table + void sInitRect(); + void sInitPic(); + void vInitCMap(); + void vInitCycl(); + void initViewPort(); + void initViewPortList(); + void initFontInfo(); + void initFont(); + void initSoundMap(); +protected: + virtual void initResource(int resType); +public: + BVoyBoltFile(BoltFilesState &state); +}; + +class StampBoltFile: public BoltFile { +private: + void initThread(); + void initState(); + void initPtr(); + void initControl(); +protected: + virtual void initResource(int resType); +public: + StampBoltFile(BoltFilesState &state); +}; + +class BoltGroup { +private: + Common::SeekableReadStream *_file; +public: + byte _loaded; + bool _processed; + bool _callInitGro; + int _termGroIndex; + int _count; + int _fileOffset; + Common::Array<BoltEntry> _entries; +public: + BoltGroup(Common::SeekableReadStream *f); + virtual ~BoltGroup(); + + void load(uint16 groupId); + void unload(); +}; + + +class BoltEntry { +private: + Common::SeekableReadStream *_file; +public: + uint16 _id; + byte _mode; + byte _initMemRequired; + byte _initMethod; + int _fileOffset; + byte _xorMask; + int _size; + byte *_data; + + // bvoy.blt resource types + RectResource *_rectResource; + PictureResource *_picResource; + ViewPortResource *_viewPortResource; + ViewPortListResource *_viewPortListResource; + FontResource *_fontResource; + FontInfoResource *_fontInfoResource; + CMapResource *_cMapResource; + VInitCycleResource *_vInitCycleResource; + + // stampblt.blt resource types + PtrResource *_ptrResource; + ControlResource *_controlResource; + StateResource *_stateResource; + ThreadResource *_threadResource; +public: + BoltEntry(Common::SeekableReadStream *f, uint16 id); + virtual ~BoltEntry(); + + void load(); + bool hasResource() const; +}; + +class FilesManager { +private: + int _decompressSize; +public: + BoltFilesState _boltFilesState; + BoltFile *_curLibPtr; +public: + FilesManager(); + void setVm(VoyeurEngine *vm) { _boltFilesState._vm = vm; } + + bool openBoltLib(const Common::String &filename, BoltFile *&boltFile); + byte *fload(const Common::String &filename, int *size = NULL); +}; + +class RectEntry: public Common::Rect { +public: + int _arrIndex; + int _count; + + RectEntry(int x1, int y1, int x2, int y2, int arrIndex, int count); +}; + +class RectResource: public Common::Rect { +public: + Common::Array<RectEntry> _entries; +public: + RectResource(const byte *src, int size, bool isExtendedRects); + RectResource(int xp, int yp, int width, int height); + virtual ~RectResource() {} +}; + +enum DisplayFlag { DISPFLAG_1 = 1, DISPFLAG_2 = 2, DISPFLAG_4 = 4, DISPFLAG_8 = 8, + DISPFLAG_10 = 0x10, DISPFLAG_20 = 0x20, DISPFLAG_40 = 0x40, DISPFLAG_80 = 0x80, + DISPFLAG_100 = 0x100, DISPFLAG_200 = 0x200, DISPFLAG_400 = 0x400, + DISPFLAG_800 = 0x800, DISPFLAG_1000 = 0x1000, DISPFLAG_2000 = 0x2000, + DISPFLAG_4000 = 0x4000, DISPFLAG_VIEWPORT = 0x8000, DISPFLAG_CURSOR = 0x10000 }; + +class DisplayResource { +private: + VoyeurEngine *_vm; +public: + uint32 _flags; +public: + DisplayResource(); + DisplayResource(VoyeurEngine *vm); + + /** + * Fill a box of the given size at the current _drawPtr location + */ + void sFillBox(int width, int height); + + /** + * Draw text at the current pen position + */ + int drawText(const Common::String &msg); + + /** + * Return the width of a given text in the current font + */ + int textWidth(const Common::String &msg); + + /** + * Clip the given rectangle by the currently viewable area + */ + bool clipRect(Common::Rect &rect); +}; + +/* bvoy.blt resource types */ + +enum PictureFlag { PICFLAG_2 = 2, PICFLAG_PIC_OFFSET = 8, PICFLAG_CLEAR_SCREEN = 0x10, + PICFLAG_20 = 0x20, PICFLAG_HFLIP = 0x40, PICFLAG_VFLIP = 0x80, PICFLAG_100 = 0x100, + PICFLAG_CLEAR_SCREEN00 = 0x1000 +}; + +class PictureResource: public DisplayResource { +private: + /** + * Flip the image data horizontally + */ + void flipHorizontal(const byte *data); + + /** + * Flip the image data vertically + */ + void flipVertical(const byte *data); +public: + byte _select; + byte _pick; + byte _onOff; + byte _depth; + Common::Rect _bounds; + uint32 _maskData; + uint _planeSize; + + /** + * Image data for the picture + */ + byte *_imgData; + + /** + * Flag to indicate whether to free the image data + */ + DisposeAfterUse::Flag _freeImgData; +public: + PictureResource(BoltFilesState &state, const byte *src); + PictureResource(int flags, int select, int pick, int onOff, int depth, + const Common::Rect &bounds, int maskData, byte *imgData, int planeSize); + PictureResource(Graphics::Surface *surface); + PictureResource(); + virtual ~PictureResource(); +}; + +typedef void (ViewPortResource::*ViewPortMethodPtr)(); + +class ViewPortResource: public DisplayResource { +private: + BoltFilesState &_state; +private: + void setupViewPort(PictureResource *page, Common::Rect *clipRect, ViewPortSetupPtr setupFn, + ViewPortAddPtr addFn, ViewPortRestorePtr restoreFn); +public: + ViewPortResource *_parent; + int _pageCount; + int _pageIndex; + int _lastPage; + Common::Rect _bounds; + int _field18; // Useless variable + PictureResource *_currentPic; + PictureResource *_activePage; + PictureResource *_pages[2]; + byte *_field30; + + // Rect lists and counts. Note that _rectListCount values of '-1' seem to have + // special significance, which is why I'm not making them redundant in favour + // of the arrays' .size() method + Common::Array<Common::Rect> *_rectListPtr[3]; + int _rectListCount[3]; + + Common::Rect _clipRect; + byte *_field7A; + GraphicMethodPtr _fn1; + ViewPortSetupPtr _setupFn; + ViewPortAddPtr _addFn; + ViewPortRestorePtr _restoreFn; + Common::Rect _fontRect; +public: + ViewPortResource(BoltFilesState &state, const byte *src); + virtual ~ViewPortResource(); + + void setupViewPort(); + void setupViewPort(PictureResource *pic, Common::Rect *clipRect = NULL); + void addSaveRect(int pageIndex, const Common::Rect &r); + void fillPic(byte onOff = 0); + void drawIfaceTime(); + void drawPicPerm(PictureResource *pic, const Common::Point &pt); +}; + +class ViewPortPalEntry { +public: + uint16 _rEntry, _gEntry, _bEntry; + uint16 _rChange, _gChange, _bChange; + uint16 _palIndex; +public: + ViewPortPalEntry(const byte *src); +}; + +class ViewPortListResource { +public: + Common::Array<ViewPortPalEntry> _palette; + Common::Array<ViewPortResource *> _entries; + int _palIndex; + + ViewPortListResource(BoltFilesState &state, const byte *src); + virtual ~ViewPortListResource() {} +}; + +class FontResource { +public: + int _minChar, _maxChar; + int _fontDepth; + int _padding; + int _fontHeight; + int _topPadding; + int *_charWidth; + byte *_charOffsets; + byte *_charImages; + + FontResource(BoltFilesState &state, byte *src); + virtual ~FontResource(); +}; + +enum FontJustify { ALIGN_LEFT = 0, ALIGN_CENTRE = 1, ALIGN_RIGHT = 2 }; + +class FontInfoResource { +public: + FontResource *_curFont; + byte _picFlags; + byte _picSelect; + byte _picPick; + byte _picOnOff; + byte _fontFlags; + FontJustify _justify; + int _fontSaveBack; + Common::Point _pos; + int _justifyWidth; + int _justifyHeight; + Common::Point _shadow; + int _foreColor; + int _backColor; + int _shadowColor; +public: + FontInfoResource(BoltFilesState &state, const byte *src); + FontInfoResource(); + FontInfoResource(byte picFlags, byte picSelect, byte picPick, byte picOnOff, byte fontFlags, + FontJustify justify, int fontSaveBack, const Common::Point &pos, int justifyWidth, + int justifyHeight, const Common::Point &shadow, int foreColor, int backColor, + int shadowColor); +}; + +class CMapResource { +private: + VoyeurEngine *_vm; +public: + int _steps; + int _fadeStatus; + int _start; + int _end; + byte *_entries; +public: + CMapResource(BoltFilesState &state, const byte *src); + virtual ~CMapResource(); + + void startFade(); +}; + +class VInitCycleResource { +private: + BoltFilesState &_state; +public: + int _type[4]; + byte *_ptr[4]; +public: + VInitCycleResource(BoltFilesState &state, const byte *src); + virtual ~VInitCycleResource() {} + + void vStartCycle(int flags = 0); + void vStopCycle(); +}; + +/* stampblt.blt resources */ + +class PtrResource { +public: + Common::Array<BoltEntry *> _entries; + + PtrResource(BoltFilesState &state, const byte *src); + virtual ~PtrResource() {} +}; + +class ControlResource { +public: + int _memberIds[8]; + byte *_entries[8]; + int _stateId; + StateResource *_state; + + ControlResource(BoltFilesState &state, const byte *src); + virtual ~ControlResource() {} +}; + +/** + * Stores data about the intended victim + */ +class StateResource { +public: + int _vals[4]; + int &_victimIndex; + int &_victimEvidenceIndex; + int &_victimMurderIndex; + + StateResource(BoltFilesState &state, const byte *src); + virtual ~StateResource() {} + + /** + * Synchronizes the game data + */ + void synchronize(Common::Serializer &s); +}; + +class ThreadResource { +public: + static int _useCount[8]; + static byte *_threadDataPtr; + static CMapResource *_cmd14Pal; + static void initUseCount(); + static void unloadAllStacks(VoyeurEngine *vm); + + static void init(); +private: + VoyeurEngine *_vm; + Common::Point _aptPos; +private: + bool getStateInfo(); + byte *getDataOffset(); + void getButtonsText(); + void getButtonsFlags(); + void getButtonsUnused(); + void performOpenCard(); + const byte *getRecordOffset(const byte *p); + const byte *getNextRecord(const byte *p); + const byte *getSTAMPCard(int cardId); + int getStateFromID(uint32 id); + uint32 getSID(int sid); + void cardAction(const byte *p); + void doSTAMPCardAction(); + bool goToStateID(int stackId, int id); + const byte *cardPerform(const byte *card); + bool cardPerform2(const byte *p, int cardCmdId); + void savePrevious(); + void setButtonFlag(int idx, byte bits); + void clearButtonFlag(int idx, byte bits); + + /** + * Frees the apartment screen data + */ + void freeTheApt(); + + /** + * Does any necessary animation at the start or end of showing the apartment. + */ + void doAptAnim(int mode); + + /** + * Updates the mansion scroll position if ncessary, and returns true if it + * has been changed. + */ + bool checkMansionScroll(); +public: + int _stateId; + int _stackId; + int _savedStateId; + int _savedStackId; + int _newStateId; + int _newStackId; + int _flags; + int _fieldA[8]; // Useless variable + int _field2A[8]; // Useless variable + int _stateFlags; + int _stateCount; + int _parseCount; + uint32 _nextStateId; + byte *_threadInfoPtr; + byte _buttonFlags[64]; + const byte *_field8E[64]; // Useless variable + byte _buttonIds[64]; + const byte *_buttonUnused[48]; + byte *_ctlPtr; + byte *_playCommandsPtr; +public: + ThreadResource(BoltFilesState &state, const byte *src); + virtual ~ThreadResource() {} + + /** + * Initialize the thread + */ + void initThreadStruct(int idx, int id); + + /** + * Loads the specified stack + */ + bool loadAStack(int stackId); + + /** + * Unloads the specified stack + */ + void unloadAStack(int stackId); + + /** + * Go to a new state and/or stack + */ + bool goToState(int stackId, int stateId); + + /** + * Initializes data for the thread based on the current state + */ + bool doState(); + + bool chooseSTAMPButton(int buttonId); + + /** + * Parses the script commands from the currently active stack + */ + void parsePlayCommands(); + + /** + * Do the camera view looking at the mansion + */ + int doInterface(); + + /** + * Do the display of a room that has one or more evidence hotspots + * available for display + */ + void doRoom(); + + /** + * Shows the apartment screen + */ + int doApt(); + + /** + * Loads data needed for displaying the initial apartment screen + */ + void loadTheApt(); + + /** + * Synchronizes the game data + */ + void synchronize(Common::Serializer &s); +}; + +} // End of namespace Voyeur + +#endif /* VOYEUR_FILES_H */ diff --git a/engines/voyeur/files_threads.cpp b/engines/voyeur/files_threads.cpp new file mode 100644 index 0000000000..5989c92439 --- /dev/null +++ b/engines/voyeur/files_threads.cpp @@ -0,0 +1,1738 @@ +/* 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 "voyeur/files.h" +#include "voyeur/graphics.h" +#include "voyeur/voyeur.h" +#include "voyeur/staticres.h" + +namespace Voyeur { + +int ThreadResource::_useCount[8]; +byte *ThreadResource::_threadDataPtr; +CMapResource *ThreadResource::_cmd14Pal; + +void ThreadResource::init() { + Common::fill(&_useCount[0], &_useCount[8], 0); + _threadDataPtr = nullptr; + _cmd14Pal = nullptr; +} + +ThreadResource::ThreadResource(BoltFilesState &state, const byte *src): + _vm(state._vm) { + _stateId = READ_LE_UINT16(&src[0]); + _stackId = READ_LE_UINT16(&src[0]); + _savedStateId = READ_LE_UINT16(&src[0]); + _savedStackId = READ_LE_UINT16(&src[0]); + _flags = src[8]; + _ctlPtr = nullptr; + _aptPos = Common::Point(-1, -1); +} + +void ThreadResource::initThreadStruct(int idx, int id) { + _stackId = -1; + if (loadAStack(idx)) { + _savedStateId = _savedStackId = -1; + _stateId = id; + _newStateId = -1; + _newStackId = -1; + + doState(); + } +} + +bool ThreadResource::loadAStack(int stackId) { + if (_vm->_stampFlags & 1) { + unloadAStack(_stackId); + if (!_useCount[stackId]) { + BoltEntry &boltEntry = _vm->_stampLibPtr->boltEntry(_vm->_controlPtr->_memberIds[stackId]); + if (!boltEntry._data) + return false; + + _vm->_controlPtr->_entries[stackId] = boltEntry._data; + } + + ++_useCount[stackId]; + } + + _ctlPtr = _vm->_controlPtr->_entries[stackId]; + _stackId = stackId; + return true; +} + +void ThreadResource::unloadAStack(int stackId) { + if ((_vm->_stampFlags & 1) && _useCount[stackId]) { + if (--_useCount[stackId] == 0) { + _vm->_stampLibPtr->freeBoltMember(_vm->_controlPtr->_memberIds[stackId]); + } + } +} + +bool ThreadResource::doState() { + _flags |= 1; + + if (!getStateInfo()) + return false; + + getButtonsFlags(); + getButtonsUnused(); + + _vm->_glGoState = -1; + _vm->_glGoStack = -1; + + performOpenCard(); + if (_stateFlags & 1) { + return chooseSTAMPButton(_vm->getRandomNumber(_stateCount - 1)); + } else { + return true; + } +} + +bool ThreadResource::getStateInfo() { + _flags &= 0xff; + int id = READ_LE_UINT16(_ctlPtr); + + if (id <= _stateId) { + _flags |= 0x8000; + return false; + } else { + uint32 fld = READ_LE_UINT32(_ctlPtr + 2); + fld += _stateId << 3; + _nextStateId = READ_LE_UINT32(_ctlPtr + fld + 4); + + fld = READ_LE_UINT32(_ctlPtr + fld); + byte *baseP = _ctlPtr + fld; + _stateCount = READ_LE_UINT16(baseP); + _stateFlags = READ_LE_UINT16(baseP + 2); + _parseCount = READ_LE_UINT16(baseP + 4); + + _playCommandsPtr = getDataOffset(); + _playCommandsPtr += (READ_LE_UINT32(baseP + 6) / 2) << 1; + + _threadInfoPtr = baseP + 10; + + getButtonsText(); + return true; + } +} + +byte *ThreadResource::getDataOffset() { + uint32 offset = READ_LE_UINT32(_ctlPtr + 10); + _threadDataPtr = _ctlPtr + offset; + return _threadDataPtr; +} + +void ThreadResource::getButtonsText() { + int idx = 0; + + for (const byte *p = _threadInfoPtr; *p != 0x49; p = getNextRecord(p)) { + if (*p == 0xC0) { + ++p; + if (*p++ & 0x80) { + assert(idx < 63); + _field8E[idx] = getRecordOffset(p); + p += 4; + } + + ++idx; + _field8E[idx] = NULL; + } + } +} + +void ThreadResource::getButtonsFlags() { + int idx = 0; + + for (const byte *p = _threadInfoPtr; *p != 0x49; p = getNextRecord(p)) { + if (*p == 0xC0) { + if (*++p & 0x20) + _stateFlags |= 2; + + _buttonFlags[idx] = *p++; + _buttonIds[idx] = *p++; + + if (_buttonFlags[idx] & 0x80) + p += 4; + + ++idx; + } + } +} + +void ThreadResource::getButtonsUnused() { + int idx = 0; + + for (const byte *p = _threadInfoPtr; *p++ != 0x4A; p = getNextRecord(p)) { + assert(idx < 47); + _buttonUnused[idx++] = getRecordOffset(p); + _buttonUnused[idx] = nullptr; + p += 4; + } +} + +void ThreadResource::unloadAllStacks(VoyeurEngine *vm) { + if (vm->_stampFlags & 1) { + for (int i = 0; i < 8; ++i) { + if (_useCount[i]) + vm->_stampLibPtr->freeBoltMember(vm->_controlPtr->_memberIds[i]); + } + } +} + +void ThreadResource::performOpenCard() { + for (const byte *p = _threadInfoPtr; *p != 0x49; p = getNextRecord(p)) { + if (*p == 0x47) { + cardAction(p + 1); + return; + } + } +} + +void ThreadResource::initUseCount() { + Common::fill(&_useCount[0], &_useCount[8], 0); +} + +const byte *ThreadResource::getRecordOffset(const byte *p) { + uint32 recSize = READ_LE_UINT32(p) + READ_LE_UINT32(_ctlPtr + 6); + return _ctlPtr + recSize; +} + +const byte *ThreadResource::getNextRecord(const byte *p) { + byte v = *p++; + + switch (v) { + case 2: + case 4: + case 6: + case 8: + case 10: + return p + 8; + case 1: + case 3: + case 5: + case 7: + case 9: + case 11: + case 21: + case 22: + case 25: + case 26: + return p + 5; + case 17: + case 23: + case 24: + case 27: + case 28: + return p + 2; + case 19: + case 41: + return p + 6; + case 18: + case 51: + case 52: + return p + 1; + case 74: + return p + 4; + case 192: + if (*p & 0x80) + p += 4; + return p + 2; + default: + return p; + } +} + +const byte *ThreadResource::getSTAMPCard(int cardId) { + const byte *p; + int count = 0; + + for (p = _threadInfoPtr; count <= cardId && *p != 0x49; p = getNextRecord(p)) { + if (*p == 0xC0) + ++count; + } + + return p; +} + +int ThreadResource::getStateFromID(uint32 id) { + int count = READ_LE_UINT16(_ctlPtr); + + for (int i = 0; i < count; ++i) { + uint32 sid = getSID(i); + if (sid == id) + return i; + } + + return -1; +} + +uint32 ThreadResource::getSID(int sid) { + uint32 offset = READ_LE_UINT32(_ctlPtr + 2) + (sid << 3) + 4; + return READ_LE_UINT32(_ctlPtr + offset); +} + +void ThreadResource::doSTAMPCardAction() { + for (const byte *p = _threadInfoPtr; *p != 0x49; p = getNextRecord(p)) { + if (*p == 0x48) { + cardAction(p + 1); + return; + } + } +} + +void ThreadResource::cardAction(const byte *card) { + _vm->_glGoState = -1; + _vm->_glGoStack = -1; + + // Loop to perform card commands + while (!_vm->shouldQuit() && *card < 70 && _vm->_glGoState == -1) { + card = cardPerform(card); + } +} + +bool ThreadResource::chooseSTAMPButton(int buttonId) { + _flags &= ~1; + + for (int idx = 0; idx < _stateCount; ++idx) { + if (_buttonIds[idx] == buttonId) { + const byte *card = getSTAMPCard(idx); + cardAction(card); + + bool flag = true; + while (!_vm->shouldQuit() && _vm->_glGoStack != -1 && flag) { + doSTAMPCardAction(); + flag = goToStateID(_vm->_glGoStack, _vm->_glGoState); + } + + while (!_vm->shouldQuit() && _vm->_glGoState != -1 && flag) { + doSTAMPCardAction(); + flag = goToState(-1, _vm->_glGoState); + } + + return flag; + } + } + + return false; +} + +void ThreadResource::parsePlayCommands() { + _vm->_voy._playStampMode = -1; + _vm->_voy._audioVisualStartTime = 0; + _vm->_voy._audioVisualDuration = 0; + _vm->_voy._boltGroupId2 = -1; + _vm->_voy._computerTextId = -1; + _vm->_voy._eventFlags &= ~EVTFLAG_8; + _vm->_eventsManager._videoDead = -1; + + // Reset hotspot data + _vm->_voy._videoHotspotTimes.reset(); + _vm->_voy._audioHotspotTimes.reset(); + _vm->_voy._evidenceHotspotTimes.reset(); + Common::fill(&_vm->_voy._roomHotspotsEnabled[0], &_vm->_voy._roomHotspotsEnabled[20], false); + byte *dataP = _playCommandsPtr; + int v2, v3; + PictureResource *pic; + CMapResource *pal; + + for (int parseIndex = 0; parseIndex < _parseCount; ++parseIndex) { + uint16 id = READ_LE_UINT16(dataP); + debugC(DEBUG_BASIC, kDebugScripts, "parsePlayCommands (%d of %d) - cmd #%d", + parseIndex + 1, _parseCount, id); + dataP += 2; + + switch (id) { + case 1: + _vm->_currentVocId = READ_LE_UINT16(dataP); + dataP += 2; + break; + + case 2: + // Play an audio event + v2 = READ_LE_UINT16(dataP); + + if (v2 == 0 || _vm->_controlPtr->_state->_victimIndex == v2) { + _vm->_audioVideoId = READ_LE_UINT16(dataP + 2) - 1; + _vm->_voy._audioVisualStartTime = READ_LE_UINT16(dataP + 4); + _vm->_voy._audioVisualDuration = READ_LE_UINT16(dataP + 6); + + if (_vm->_voy._RTVNum < _vm->_voy._audioVisualStartTime || + (_vm->_voy._audioVisualStartTime + _vm->_voy._audioVisualDuration) < _vm->_voy._RTVNum) { + _vm->_audioVideoId = -1; + } else { + _vm->_voy._vocSecondsOffset = _vm->_voy._RTVNum - _vm->_voy._audioVisualStartTime; + _vm->_voy.addAudioEventStart(); + + // Play the audio + assert(_vm->_audioVideoId < 38); + _vm->playAudio(_vm->_audioVideoId); + + _vm->_voy.addAudioEventEnd(); + _vm->_eventsManager.incrementTime(1); + _vm->_eventsManager.incrementTime(1); + _vm->_audioVideoId = -1; + parseIndex = 999; + } + } + + dataP += 8; + break; + + case 3: + // Play a video event + v2 = READ_LE_UINT16(dataP); + + if (v2 == 0 || _vm->_controlPtr->_state->_victimIndex == v2) { + _vm->_audioVideoId = READ_LE_UINT16(dataP + 2) - 1; + _vm->_voy._audioVisualStartTime = READ_LE_UINT16(dataP + 4); + _vm->_voy._audioVisualDuration = READ_LE_UINT16(dataP + 6); + + if (_vm->_voy._RTVNum < _vm->_voy._audioVisualStartTime || + (_vm->_voy._audioVisualStartTime + _vm->_voy._audioVisualDuration) < _vm->_voy._RTVNum) { + _vm->_audioVideoId = -1; + } else { + _vm->_voy._vocSecondsOffset = _vm->_voy._RTVNum - _vm->_voy._audioVisualStartTime; + _vm->_voy.addVideoEventStart(); + _vm->_voy._eventFlags &= ~EVTFLAG_TIME_DISABLED; + _vm->_voy._eventFlags |= EVTFLAG_RECORDING; + _vm->playAVideo(_vm->_audioVideoId); + + _vm->_voy._eventFlags &= ~EVTFLAG_RECORDING; + _vm->_voy._eventFlags |= EVTFLAG_TIME_DISABLED; + _vm->_voy.addVideoEventEnd(); + _vm->_eventsManager.incrementTime(1); + + _vm->_audioVideoId = -1; + _vm->_playStampGroupId = -1; + + if (_vm->_eventsManager._videoDead != -1) { + _vm->_bVoy->freeBoltGroup(0xE00); + _vm->_eventsManager._videoDead = -1; + _vm->flipPageAndWait(); + } + + _vm->_eventsManager._videoDead = -1; + if (_stateCount == 2 && _vm->_eventsManager._mouseClicked == 0) { + _vm->_voy._playStampMode = 132; + parseIndex = 999; + } else { + _vm->_voy._playStampMode = 129; + } + } + } + + dataP += 8; + break; + + case 4: + case 22: + // Case 22: Endgame news reports + _vm->_audioVideoId = READ_LE_UINT16(dataP) - 1; + dataP += 2; + + if (id == 22) { + int resolveIndex = READ_LE_UINT16(dataP); + dataP += 2; + _vm->_playStampGroupId = _vm->_resolvePtr[resolveIndex]; + } + + _vm->_voy._vocSecondsOffset = 0; + _vm->_voy._audioVisualStartTime = _vm->_voy._RTVNum; + _vm->_voy._eventFlags &= ~(EVTFLAG_TIME_DISABLED | EVTFLAG_RECORDING); + _vm->playAVideo(_vm->_audioVideoId); + _vm->_voy._eventFlags |= EVTFLAG_TIME_DISABLED; + + if (id != 22) { + _vm->_audioVideoId = -1; + parseIndex = 999; + } else { + int count = _vm->_bVoy->getBoltGroup(_vm->_playStampGroupId)->_entries.size() / 2; + _vm->_soundManager.stopVOCPlay(); + _vm->_eventsManager.getMouseInfo(); + + for (int i = 0; i < count; ++i) { + pic = _vm->_bVoy->boltEntry(_vm->_playStampGroupId + i * 2)._picResource; + pal = _vm->_bVoy->boltEntry(_vm->_playStampGroupId + i * 2 + 1)._cMapResource; + + (*_vm->_graphicsManager._vPort)->setupViewPort(pic); + pal->startFade(); + + _vm->flipPageAndWaitForFade(); + + if (i > 0) { + _vm->_bVoy->freeBoltMember(_vm->_playStampGroupId + i * 2); + _vm->_bVoy->freeBoltMember(_vm->_playStampGroupId + i * 2 + 1); + } + + Common::String file = Common::String::format("news%d.voc", i + 1); + _vm->_soundManager.startVOCPlay(file); + + while (!_vm->shouldQuit() && !_vm->_eventsManager._mouseClicked && + _vm->_soundManager.getVOCStatus()) { + _vm->_eventsManager.delayClick(1); + _vm->_eventsManager.getMouseInfo(); + } + + _vm->_soundManager.stopVOCPlay(); + + if (i == (count - 1)) + _vm->_eventsManager.delayClick(480); + + if (_vm->shouldQuit() || _vm->_eventsManager._mouseClicked) + break; + } + + _vm->_bVoy->freeBoltGroup(_vm->_playStampGroupId); + _vm->_playStampGroupId = -1; + _vm->_audioVideoId = -1; + parseIndex = 999; + } + break; + + case 5: + // Check whether transition to a given time period is allowed, and + // if so, load the time information for the new time period + v2 = READ_LE_UINT16(dataP); + if (v2 == 0 || _vm->_controlPtr->_state->_victimIndex == v2) { + _vm->_voy._playStampMode = 5; + int count = READ_LE_UINT16(dataP + 2); + _vm->_voy._RTVLimit = READ_LE_UINT16(dataP + 4); + + if (_vm->_voy._transitionId != count) { + if (_vm->_voy._transitionId > 1) + _vm->_voy._eventFlags &= ~EVTFLAG_100; + + _vm->_voy._transitionId = count; + _vm->_gameMinute = LEVEL_M[count - 1]; + _vm->_gameHour = LEVEL_H[count - 1]; + //_vm->_v2A0A2 = 0; + _vm->_voy._RTVNum = 0; + _vm->_voy._RTANum = 255; + } + + _vm->_voy._isAM = (_vm->_voy._transitionId == 6); + } + + dataP += 6; + break; + + case 6: + _vm->_voy._playStampMode = 6; + v2 = READ_LE_UINT16(dataP); + _vm->_playStampGroupId = _vm->_resolvePtr[v2]; + dataP += 2; + break; + + case 7: + // Load the video event scene hotspot times data + v2 = READ_LE_UINT16(dataP); + v3 = READ_LE_UINT16(dataP + 2) - 1; + + if (v2 == 0 || _vm->_controlPtr->_state->_victimIndex == v2) { + int idx = 0; + while (_vm->_voy._videoHotspotTimes._min[idx][v3] != 9999) + ++idx; + + v2 = READ_LE_UINT16(dataP + 4); + _vm->_voy._videoHotspotTimes._min[idx][v3] = v2; + _vm->_voy._videoHotspotTimes._max[idx][v3] = v2 + READ_LE_UINT16(dataP + 6) - 2; + } + + dataP += 8; + break; + + case 8: + // Load the audio event scene hotspot times data + v2 = READ_LE_UINT16(dataP); + v3 = READ_LE_UINT16(dataP + 2) - 1; + + if (v2 == 0 || _vm->_controlPtr->_state->_victimIndex == v2) { + int idx = 0; + while (_vm->_voy._audioHotspotTimes._min[idx][v3] != 9999) + ++idx; + + v2 = READ_LE_UINT16(dataP + 4); + _vm->_voy._audioHotspotTimes._min[idx][v3] = v2; + _vm->_voy._audioHotspotTimes._max[idx][v3] = v2 + READ_LE_UINT16(dataP + 6) - 2; + } + + dataP += 8; + break; + + case 9: + // Load up evidence event scene hotspot times data + v2 = READ_LE_UINT16(dataP); + v3 = READ_LE_UINT16(dataP + 2) - 1; + + if (v2 == 0 || _vm->_controlPtr->_state->_victimIndex == v2) { + int idx = 0; + while (_vm->_voy._evidenceHotspotTimes._min[idx][v3] != 9999) + ++idx; + + v2 = READ_LE_UINT16(dataP + 4); + _vm->_voy._evidenceHotspotTimes._min[idx][v3] = v2; + _vm->_voy._evidenceHotspotTimes._max[idx][v3] = v2 + READ_LE_UINT16(dataP + 6) - 2; + } + + dataP += 8; + break; + + case 10: + // Pick the person who is to die, during startup + if (_vm->_iForceDeath == -1) { + // No specific person has been preset to be killed, so pick one randomly. + // The loop below was used because the victim was persisted from the previous + // play-through, so it ensured that a different victim is picked. + int randomVal; + do { + randomVal = _vm->getRandomNumber(3) + 1; + } while (randomVal == _vm->_voy._victimNumber); + + _vm->_voy._victimNumber = randomVal; + _vm->_controlPtr->_state->_victimIndex = randomVal; + } else { + // Player has seen something that locks in the character to die + _vm->_voy._victimNumber = _vm->_iForceDeath; + _vm->_controlPtr->_state->_victimIndex = _vm->_iForceDeath; + } + + _vm->saveLastInplay(); + break; + + case 11: + _vm->_voy._eventFlags |= EVTFLAG_2; + break; + + case 12: + v2 = READ_LE_UINT16(dataP); + + if (v2 == 0 || _vm->_controlPtr->_state->_victimIndex == v2) { + _vm->_voy._boltGroupId2 = _vm->_resolvePtr[READ_LE_UINT16(dataP + 2)]; + _vm->_voy._roomHotspotsEnabled[READ_LE_UINT16(dataP + 4) - 1] = true; + } + + dataP += 6; + break; + + case 13: + v2 = READ_LE_UINT16(dataP); + + if (v2 == 0 || _vm->_controlPtr->_state->_victimIndex == v2) { + _vm->_voy._computerTextId = READ_LE_UINT16(dataP + 2); + _vm->_voy._computerTimeMin = READ_LE_UINT16(dataP + 4); + _vm->_voy._computerTimeMax = READ_LE_UINT16(dataP + 6); + + _vm->_voy._rect4E4.left = COMP_BUT_TABLE[_vm->_voy._computerTextId * 4]; + _vm->_voy._rect4E4.top = COMP_BUT_TABLE[_vm->_voy._computerTextId * 4 + 1]; + _vm->_voy._rect4E4.right = COMP_BUT_TABLE[_vm->_voy._computerTextId * 4 + 2]; + _vm->_voy._rect4E4.bottom = COMP_BUT_TABLE[_vm->_voy._computerTextId * 4 + 3]; + } + + dataP += 8; + break; + + case 14: + _vm->_playStampGroupId = 2048; + _vm->_voy._playStampMode = 130; + break; + + case 15: + _vm->showEndingNews(); + break; + + case 16: + _vm->_voy._playStampMode = 16; + break; + + case 17: + _vm->_voy._playStampMode = 17; + break; + + case 18: + // Called during the murder (Sunday 10:30PM) time period, to specify the + // time expired point at which the murder takes place + v2 = READ_LE_UINT16(dataP); + v3 = READ_LE_UINT16(dataP + 2); + + if (v2 == 0 || _vm->_controlPtr->_state->_victimIndex == v2) + _vm->_voy._murderThreshold = v3; + + dataP += 4; + break; + + case 19: + _vm->_voy._aptLoadMode = 140; + loadTheApt(); + _vm->_voy._aptLoadMode = 141; + freeTheApt(); + break; + + case 20: + _vm->_voy._aptLoadMode = -1; + loadTheApt(); + _vm->_voy._aptLoadMode = 141; + freeTheApt(); + break; + + case 21: + _vm->_voy._aptLoadMode = -1; + loadTheApt(); + _vm->_voy._aptLoadMode = 140; + freeTheApt(); + break; + + case 23: + _vm->_voy._transitionId = 17; + _vm->_voy._aptLoadMode = -1; + loadTheApt(); + _vm->_voy._aptLoadMode = 144; + freeTheApt(); + break; + + default: + break; + } + } +} + +const byte *ThreadResource::cardPerform(const byte *card) { + uint16 id = *card++; + int varD = 5; + uint32 v2; + byte bVal; + uint32 idx1, idx2; + debugC(DEBUG_BASIC, kDebugScripts, "cardPerform - %d", id); + + switch (id) { + case 1: + v2 = READ_LE_UINT32(card); + card += 4; + _vm->_controlPtr->_state->_vals[*card++] = v2; + break; + + case 2: + v2 = _vm->_controlPtr->_state->_vals[*card++]; + _vm->_controlPtr->_state->_vals[*card++] = v2; + break; + + case 3: + v2 = READ_LE_UINT32(card); + card += 4; + _vm->_controlPtr->_state->_vals[*card++] = v2; + break; + + case 4: + v2 = _vm->_controlPtr->_state->_vals[*card++]; + _vm->_controlPtr->_state->_vals[*card++] = v2; + break; + + case 5: { + v2 = READ_LE_UINT32(card); + card += 4; + int &v = _vm->_controlPtr->_state->_vals[*card++]; + v -= v2; + break; + } + + case 6: { + idx1 = *card++; + idx2 = *card++; + + v2 = _vm->_controlPtr->_state->_vals[idx1]; + int &v = _vm->_controlPtr->_state->_vals[idx2]; + v -= v2; + break; + } + + case 7: { + int v3 = *card++; + v2 = READ_LE_UINT32(card); + card += 4; + int &v = _vm->_controlPtr->_state->_vals[v3]; + v *= v2; + break; + } + + case 8: { + idx1 = *card++; + idx2 = *card++; + + int &v1 = _vm->_controlPtr->_state->_vals[idx1]; + int &v2 = _vm->_controlPtr->_state->_vals[idx2]; + v1 *= v2; + break; + } + + case 9: { + idx1 = *card++; + v2 = READ_LE_UINT32(card); + card += 4; + + int &v = _vm->_controlPtr->_state->_vals[idx1]; + v /= v2; + break; + } + + case 10: { + idx1 = *card++; + idx2 = *card++; + + int &v1 = _vm->_controlPtr->_state->_vals[idx1]; + int &v2 = _vm->_controlPtr->_state->_vals[idx2]; + v1 /= v2; + break; + } + + case 11: + v2 = READ_LE_UINT32(card); + card += 4; + v2 = _vm->getRandomNumber(v2 - 1) + 1; + _vm->_controlPtr->_state->_vals[*card++] = v2; + break; + + case 17: + _vm->_glGoState = READ_LE_UINT16(card); + card += 2; + _vm->_glGoStack = -1; + break; + + case 18: + v2 = _vm->_controlPtr->_state->_vals[*card++]; + _vm->_glGoState = getStateFromID(v2); + break; + + case 19: + _vm->_glGoState = READ_LE_UINT32(card); + card += 4; + _vm->_glGoStack = READ_LE_UINT16(card); + card += 2; + break; + + case 23: + case 24: + case 27: + case 28: + varD -= 3; + // Deliberate fall-through + + case 21: + case 22: + case 25: + case 26: + bVal = card[varD]; + if (bVal == 61) { + if (cardPerform2(card, id)) { + card += varD; + while (*card != 30 && *card != 29) + card = cardPerform(card); + + if (*card == 29) { + int count = 1; + while (count > 0) { + card = getNextRecord(card); + if (*card == 30) + --count; + if (*card >= 21 && *card <= 28) + ++count; + } + } + } else { + card += varD; + int count = 1; + while (count > 0) { + card = getNextRecord(card); + if (*card == 29 || *card == 30) + --count; + if (*card < 21 || *card > 28) + continue; + + const byte *nextP = getNextRecord(card + 2); + if (*nextP == 61) + ++count; + } + } + + ++card; + } else { + if (cardPerform2(card, id)) { + card += varD; + card = cardPerform(card); + while (*card++ != 61) ; + } else { + card += varD; + while (*card != 61 && *card != 29) + ++card; + } + } + break; + + case 41: + bVal = *card++; + assert(bVal < 8); + _fieldA[bVal] = READ_LE_UINT32(card); + card += 4; + + _field2A[bVal] = READ_LE_UINT16(card); + card += 2; + + case 45: + _newStateId = _nextStateId; + _newStackId = _stackId; + break; + + case 46: + _vm->_glGoState = _newStateId; + _vm->_glGoStack = _newStackId; + _newStateId = -1; + _newStackId = -1; + break; + + case 51: + setButtonFlag(READ_LE_UINT16(card), 64); + break; + + case 52: + clearButtonFlag(READ_LE_UINT16(card), 64); + break; + + default: + break; + } + + return card; +} + +bool ThreadResource::cardPerform2(const byte *pSrc, int cardCmdId) { + int vLong, vLong2; + + switch (cardCmdId) { + case 21: + vLong = (int32)READ_LE_UINT32(pSrc + 1); + return _vm->_controlPtr->_state->_vals[*pSrc] == vLong; + + case 22: + vLong = (int32)READ_LE_UINT32(pSrc + 1); + return _vm->_controlPtr->_state->_vals[*pSrc] != vLong; + + case 23: + vLong = _vm->_controlPtr->_state->_vals[*pSrc]; + vLong2 = _vm->_controlPtr->_state->_vals[*(pSrc + 1)]; + return vLong == vLong2; + + case 24: + vLong = _vm->_controlPtr->_state->_vals[*pSrc]; + vLong2 = _vm->_controlPtr->_state->_vals[*(pSrc + 1)]; + return vLong != vLong2; + + case 25: + vLong = _vm->_controlPtr->_state->_vals[*pSrc]; + vLong2 = (int32)READ_LE_UINT32(pSrc + 1); + return vLong < vLong2; + + case 26: + vLong = _vm->_controlPtr->_state->_vals[*pSrc]; + vLong2 = (int32)READ_LE_UINT32(pSrc + 1); + return vLong > vLong2; + + case 27: + vLong = _vm->_controlPtr->_state->_vals[*pSrc]; + vLong2 = _vm->_controlPtr->_state->_vals[*(pSrc + 1)]; + return vLong < vLong2; + + case 28: + vLong = _vm->_controlPtr->_state->_vals[*pSrc]; + vLong2 = _vm->_controlPtr->_state->_vals[*(pSrc + 1)]; + return vLong > vLong2; + + default: + return false; + } +} + +int ThreadResource::doApt() { + loadTheApt(); + + _vm->_currentVocId = 151; + _vm->_voy._viewBounds = _vm->_bVoy->boltEntry(_vm->_playStampGroupId)._rectResource; + Common::Array<RectEntry> &hotspots = _vm->_bVoy->boltEntry( + _vm->_playStampGroupId + 1)._rectResource->_entries; + _vm->_eventsManager.getMouseInfo(); + + // Very first time apartment is shown, start the phone message + if (_aptPos.x == -1) { + _aptPos.x = hotspots[2].left; + _aptPos.y = hotspots[2].top; + _vm->_currentVocId = 153; + } + + if (_vm->_voy._playStampMode == 16) { + hotspots[0].left = 999; + hotspots[3].left = 999; + _aptPos.x = hotspots[4].left + 28; + _aptPos.y = hotspots[4].top + 28; + } + + _vm->_eventsManager.setMousePos(Common::Point(_aptPos.x, _aptPos.y)); + _vm->_soundManager.startVOCPlay(_vm->_soundManager.getVOCFileName(_vm->_currentVocId)); + _vm->_currentVocId = 151; + + _vm->_graphicsManager.setColor(129, 82, 82, 82); + _vm->_graphicsManager.setColor(130, 112, 112, 112); + _vm->_graphicsManager.setColor(131, 215, 215, 215); + _vm->_graphicsManager.setColor(132, 235, 235, 235); + + _vm->_eventsManager._intPtr._hasPalette = true; + + // Main loop to allow users to move the cursor and select hotspots + int hotspotId; + int prevHotspotId = -1; + Common::Point pt; + PictureResource *pic; + Common::Rect gmmHotspot(75, 125, 130, 140); + + do { + _vm->_voyeurArea = AREA_APARTMENT; + + if (_vm->_loadGameSlot != -1) { + // Load a savegame + _vm->loadGame(_vm->_loadGameSlot); + _vm->_loadGameSlot = -1; + } + + _vm->_eventsManager.getMouseInfo(); + if (!_vm->_soundManager.getVOCStatus()) { + // Previous sound ended, so start up a new one + _vm->_currentVocId = 151 - _vm->getRandomNumber(4); + _vm->_soundManager.startVOCPlay(_vm->_soundManager.getVOCFileName(_vm->_currentVocId)); + } + + // Loop through the hotspot list + hotspotId = -1; + pt = _vm->_eventsManager.getMousePos(); + for (int idx = 0; idx < (int)hotspots.size(); ++idx) { + if (hotspots[idx].contains(pt)) { + // Cursor is within hotspot area + + // Don't allow the camera to be highlighted on Monday morning. + if (idx == 0 && _vm->_voy._transitionId == 17) + continue; + + // Set the highlighted hotspot Id + hotspotId = idx; + + if (hotspotId != prevHotspotId) { + // Check for whether to replace hotspot Id for "Watch TV" for + // "Review the Tape" if player has already watched the TV + if ((_vm->_voy._eventFlags & EVTFLAG_100) && (hotspotId == 2)) + hotspotId = 5; + + // Draw the text description for the highlighted hotspot + pic = _vm->_bVoy->boltEntry(_vm->_playStampGroupId + + hotspotId + 6)._picResource; + _vm->_graphicsManager.sDrawPic(pic, *_vm->_graphicsManager._vPort, + Common::Point(106, 200)); + } + + break; + } + } + + // Check for presence in ScummVM GMM + if (gmmHotspot.contains(pt)) + hotspotId = 42; + + // Draw either standard or highlighted eye cursor + pic = _vm->_bVoy->boltEntry((hotspotId == -1) ? _vm->_playStampGroupId + 2 : + _vm->_playStampGroupId + 3)._picResource; + _vm->_graphicsManager.sDrawPic(pic, *_vm->_graphicsManager._vPort, pt); + + _vm->flipPageAndWait(); + + if (hotspotId == 42 && _vm->_eventsManager._leftClick) { + // Show the ScummVM GMM + _vm->_eventsManager.getMouseInfo(); + _vm->openMainMenuDialog(); + } + + } while (!_vm->shouldQuit() && (!_vm->_eventsManager._leftClick || hotspotId == -1)); + + pt = _vm->_eventsManager.getMousePos(); + _aptPos.x = pt.x; + _aptPos.y = pt.y; + + switch (hotspotId) { + case 0: + _vm->_voy._aptLoadMode = 140; + break; + case 1: + _vm->_voy._aptLoadMode = 143; + break; + case 2: + _vm->_voy._aptLoadMode = 142; + case 5: + _vm->_voy._aptLoadMode = 141; + break; + default: + _vm->_voy._aptLoadMode = -1; + break; + } + + freeTheApt(); + if (_vm->_voy._transitionId == 1 && hotspotId == 0) + _vm->checkTransition(); + + if (!hotspotId) + _vm->makeViewFinder(); + + return hotspotId; +} + +void ThreadResource::doRoom() { + VoyeurEngine &vm = *_vm; + SVoy &voy = vm._voy; + + vm.makeViewFinderP(); + voy._fadingType = 0; + + if (!vm._bVoy->getBoltGroup(vm._playStampGroupId, true)) + return; + + vm._graphicsManager._backColors = vm._bVoy->boltEntry(vm._playStampGroupId + 1)._cMapResource; + vm._graphicsManager._backgroundPage = vm._bVoy->boltEntry(vm._playStampGroupId)._picResource; + (*vm._graphicsManager._vPort)->setupViewPort(vm._graphicsManager._backgroundPage); + vm._graphicsManager._backColors->startFade(); + + voy._fadingStep1 = 2; + voy._fadingStep2 = 0; + voy._fadingType = 1; + + Common::Array<RectEntry> &hotspots = vm._bVoy->boltEntry(vm._playStampGroupId + 4)._rectResource->_entries; + int hotspotId = -1; + + PictureResource *crosshairsCursor = vm._bVoy->boltEntry(vm._playStampGroupId + 2)._picResource; + PictureResource *magnifierCursor = vm._bVoy->boltEntry(vm._playStampGroupId + 3)._picResource; + vm._eventsManager.showCursor(); + + RectResource viewBounds(48, 38, 336, 202); + voy._viewBounds = &viewBounds; + + vm._eventsManager.getMouseInfo(); + vm._eventsManager.setMousePos(Common::Point(192, 120)); + voy._fadingType = 0; + vm._currentVocId = 146; + voy._musicStartTime = voy._RTVNum; + + voy._vocSecondsOffset = 0; + vm._soundManager.startVOCPlay(vm._currentVocId); + voy._eventFlags &= ~EVTFLAG_TIME_DISABLED; + + bool breakFlag = false; + while (!vm.shouldQuit() && !breakFlag) { + _vm->_voyeurArea = AREA_ROOM; + vm._graphicsManager.setColor(128, 0, 255, 0); + vm._eventsManager._intPtr._hasPalette = true; + + do { + if (vm._currentVocId != -1 && !vm._soundManager.getVOCStatus()) { + voy._musicStartTime = voy._RTVNum; + voy._vocSecondsOffset = 0; + vm._soundManager.startVOCPlay(vm._currentVocId); + } + + vm._eventsManager.getMouseInfo(); + Common::Point pt = vm._eventsManager.getMousePos(); + pt += Common::Point(30, 15); + + hotspotId = -1; + if (voy._computerTextId != -1 && voy._rect4E4.contains(pt)) + hotspotId = 999; + + for (uint idx = 0; idx < hotspots.size(); ++idx) { + if (hotspots[idx].contains(pt)) { + int arrIndex = hotspots[idx]._arrIndex; + if (voy._roomHotspotsEnabled[arrIndex - 1]) { + hotspotId = idx; + break; + } + } + } + + if (hotspotId == -1) { + vm._eventsManager.setCursorColor(128, 0); + vm._eventsManager.setCursor(crosshairsCursor); + } else if (hotspotId != 999 || voy._RTVNum < voy._computerTimeMin || + (voy._computerTimeMax - 2) < voy._RTVNum) { + vm._eventsManager.setCursorColor(128, 1); + vm._eventsManager.setCursor(magnifierCursor); + } else { + vm._eventsManager.setCursorColor(128, 2); + vm._eventsManager.setCursor(magnifierCursor); + } + + vm._eventsManager._intPtr._hasPalette = true; + vm._graphicsManager.flipPage(); + vm._eventsManager.sWaitFlip(); + } while (!vm.shouldQuit() && !vm._eventsManager._mouseClicked); + + if (!vm._eventsManager._leftClick || hotspotId == -1) { + if (vm._eventsManager._rightClick) + breakFlag = true; + + Common::Point pt = vm._eventsManager.getMousePos(); + vm._eventsManager.getMouseInfo(); + vm._eventsManager.setMousePos(pt); + } else { + voy._eventFlags |= EVTFLAG_RECORDING; + vm._eventsManager.hideCursor(); + vm._eventsManager.startCursorBlink(); + + if (hotspotId == 999) { + _vm->flipPageAndWait(); + + if (vm._currentVocId != -1) { + voy._vocSecondsOffset = voy._RTVNum - voy._musicStartTime; + vm._soundManager.stopVOCPlay(); + } + + vm.getComputerBrush(); + _vm->flipPageAndWait(); + + vm._voy.addComputerEventStart(); + + vm._eventsManager._mouseClicked = false; + vm._eventsManager.startCursorBlink(); + + int totalChars = vm.doComputerText(9999); + if (totalChars) + vm._voy.addComputerEventEnd(totalChars); + + vm._bVoy->freeBoltGroup(0x4900); + } else { + vm.doEvidDisplay(hotspotId, 999); + } + + voy._eventFlags &= ~EVTFLAG_RECORDING; + if (!vm._eventsManager._mouseClicked) + vm._eventsManager.delayClick(18000); + + // WORKAROUND: Skipped code from the original, that freed the group, + // reloaded it, and reloaded the cursors + + vm._graphicsManager._backColors = vm._bVoy->boltEntry( + vm._playStampGroupId + 1)._cMapResource; + vm._graphicsManager._backgroundPage = vm._bVoy->boltEntry( + vm._playStampGroupId)._picResource; + + (*vm._graphicsManager._vPort)->setupViewPort(); + vm._graphicsManager._backColors->startFade(); + _vm->flipPageAndWait(); + + while (!vm.shouldQuit() && (vm._eventsManager._fadeStatus & 1)) + vm._eventsManager.delay(1); + vm._eventsManager.hideCursor(); + + while (!vm.shouldQuit() && voy._fadingAmount2 > 0) { + if (voy._fadingAmount1 < 63) { + voy._fadingAmount1 += 4; + if (voy._fadingAmount1 > 63) + voy._fadingAmount1 = 63; + } + + if (voy._fadingAmount2 > 0) { + voy._fadingAmount2 -= 8; + if (voy._fadingAmount2 < 0) + voy._fadingAmount2 = 0; + } + + vm._eventsManager.delay(1); + } + + _vm->flipPageAndWait(); + + vm._graphicsManager.fadeUpICF1(0); + voy._eventFlags &= EVTFLAG_RECORDING; + vm._eventsManager.showCursor(); + } + } + + voy._eventFlags = EVTFLAG_TIME_DISABLED; + vm._eventsManager.incrementTime(1); + voy._viewBounds = nullptr; + voy._fadingType = 0; + vm.makeViewFinderP(); + + if (voy._boltGroupId2 != -1) { + vm._bVoy->freeBoltGroup(voy._boltGroupId2, 1); + voy._boltGroupId2 = -1; + } + + if (vm._playStampGroupId != -1) { + vm._bVoy->freeBoltGroup(vm._playStampGroupId); + vm._playStampGroupId = -1; + } + + if (vm._currentVocId != -1) { + vm._soundManager.stopVOCPlay(); + vm._currentVocId = -1; + } + + vm._eventsManager.hideCursor(); + chooseSTAMPButton(0); +} + +int ThreadResource::doInterface() { + PictureResource *pic; + Common::Point pt; + + _vm->_voy._eventFlags |= EVTFLAG_TIME_DISABLED; + if (_vm->_voy._abortInterface) { + _vm->_voy._abortInterface = false; + return -2; + } + + _vm->_voy._eventFlags &= ~EVTFLAG_100; + _vm->_playStampGroupId = -1; + _vm->_eventsManager._intPtr._flashStep = 1; + _vm->_eventsManager._intPtr._flashTimer = 0; + + if (_vm->_voy._RTVNum >= _vm->_voy._RTVLimit || _vm->_voy._RTVNum < 0) + _vm->_voy._RTVNum = _vm->_voy._RTVLimit - 1; + + if (_vm->_voy._transitionId < 15 && _vm->_debugger._isTimeActive && + (_vm->_voy._RTVLimit - 3) < _vm->_voy._RTVNum) { + _vm->_voy._RTVNum = _vm->_voy._RTVLimit; + _vm->makeViewFinder(); + + _vm->initIFace(); + _vm->_voy._RTVNum = _vm->_voy._RTVLimit - 4; + _vm->_voy._eventFlags &= ~EVTFLAG_TIME_DISABLED; + + while (!_vm->shouldQuit() && _vm->_voy._RTVNum < _vm->_voy._RTVLimit) { + _vm->flashTimeBar(); + _vm->_eventsManager.delayClick(1); + } + + _vm->_voy._eventFlags |= EVTFLAG_TIME_DISABLED; + chooseSTAMPButton(20); + parsePlayCommands(); + } + + _vm->checkTransition(); + _vm->makeViewFinder(); + _vm->_eventsManager.getMouseInfo(); + _vm->initIFace(); + + Common::Array<RectEntry> *hotspots = &_vm->_bVoy->boltEntry( + _vm->_playStampGroupId + 1)._rectResource->_entries; + _vm->_currentVocId = 151 - _vm->getRandomNumber(5); + _vm->_voy._vocSecondsOffset = _vm->getRandomNumber(29); + + Common::String fname = _vm->_soundManager.getVOCFileName(_vm->_currentVocId); + _vm->_soundManager.startVOCPlay(fname); + _vm->_eventsManager.getMouseInfo(); + + _vm->_graphicsManager.setColor(240, 220, 220, 220); + _vm->_eventsManager._intPtr._hasPalette = true; + _vm->_voy._eventFlags &= ~EVTFLAG_TIME_DISABLED; + + // Set the cusor + PictureResource *crosshairsCursor = _vm->_bVoy->boltEntry(0x112)._picResource; + PictureResource *eyeCursor = _vm->_bVoy->boltEntry(0x113)._picResource; + PictureResource *listenCursor = _vm->_bVoy->boltEntry(0x114)._picResource; + PictureResource *mangifyCursor = _vm->_bVoy->boltEntry(0x115)._picResource; + + _vm->_eventsManager.setCursor(crosshairsCursor); + + // Main loop + int regionIndex = 0; + Common::Rect mansionViewBounds(MANSION_VIEW_X, MANSION_VIEW_Y, + MANSION_VIEW_X + MANSION_VIEW_WIDTH, MANSION_VIEW_Y + MANSION_VIEW_HEIGHT); + + do { + _vm->_voyeurArea = AREA_INTERFACE; + _vm->doTimeBar(true); + _vm->_eventsManager.getMouseInfo(); + + if (checkMansionScroll()) + _vm->doScroll(_vm->_mansionViewPos); + + _vm->checkPhoneCall(); + if (!_vm->_soundManager.getVOCStatus()) { + _vm->_currentVocId = 151 - _vm->getRandomNumber(5); + _vm->_soundManager.startVOCPlay(_vm->_soundManager.getVOCFileName(_vm->_currentVocId)); + } + + // Calculate the mouse position within the entire mansion + pt = _vm->_eventsManager.getMousePos(); + if (!mansionViewBounds.contains(pt)) + pt = Common::Point(-1, -1); + else + pt = _vm->_mansionViewPos + + Common::Point(pt.x - MANSION_VIEW_X, pt.y - MANSION_VIEW_Y); + regionIndex = -1; + + for (int hotspotIdx = 0; hotspotIdx < (int)hotspots->size(); ++hotspotIdx) { + if ((*hotspots)[hotspotIdx].contains(pt)) { + // Rect check done + for (int arrIndex = 0; arrIndex < 3; ++arrIndex) { + if (_vm->_voy._audioHotspotTimes.isInRange(arrIndex, hotspotIdx, _vm->_voy._RTVNum)) { + // Set the ear cursor for an audio event + _vm->_eventsManager.setCursor(listenCursor); + regionIndex = hotspotIdx; + } + + if (_vm->_voy._evidenceHotspotTimes.isInRange(arrIndex, hotspotIdx, _vm->_voy._RTVNum)) { + // Set the magnifier cursor for an evidence event + _vm->_eventsManager.setCursor(mangifyCursor); + regionIndex = hotspotIdx; + } + } + + for (int arrIndex = 0; arrIndex < 8; ++arrIndex) { + if (_vm->_voy._videoHotspotTimes.isInRange(arrIndex, hotspotIdx, _vm->_voy._RTVNum)) { + // Set the eye cursor for a video event + _vm->_eventsManager.setCursor(eyeCursor); + regionIndex = hotspotIdx; + } + } + } + } + + if (regionIndex == -1) { + // Reset back to the crosshairs cursor + _vm->_eventsManager.setCursor(crosshairsCursor); + } + + // Regularly update the time display + if (_vm->_voy._RTANum & 2) { + _vm->_graphicsManager.drawANumber(*_vm->_graphicsManager._vPort, + _vm->_gameMinute / 10, Common::Point(190, 25)); + _vm->_graphicsManager.drawANumber(*_vm->_graphicsManager._vPort, + _vm->_gameMinute % 10, Common::Point(201, 25)); + + if (_vm->_voy._RTANum & 4) { + int v = _vm->_gameHour / 10; + _vm->_graphicsManager.drawANumber(*_vm->_graphicsManager._vPort, + v == 0 ? 10 : v, Common::Point(161, 25)); + _vm->_graphicsManager.drawANumber(*_vm->_graphicsManager._vPort, + _vm->_gameHour % 10, Common::Point(172, 25)); + + pic = _vm->_bVoy->boltEntry(_vm->_voy._isAM ? 272 : 273)._picResource; + _vm->_graphicsManager.sDrawPic(pic, *_vm->_graphicsManager._vPort, + Common::Point(215, 27)); + } + } + + _vm->_voy._RTANum = 0; + _vm->flipPageAndWait(); + + pt = _vm->_eventsManager.getMousePos(); + if ((_vm->_voy._RTVNum >= _vm->_voy._RTVLimit) || ((_vm->_voy._eventFlags & 0x80) && + _vm->_eventsManager._rightClick && (pt.x == 0))) { + // Time to transition to the next time period + _vm->_eventsManager.getMouseInfo(); + + if (_vm->_voy._transitionId == 15) { + regionIndex = 20; + _vm->_voy._transitionId = 17; + _vm->_soundManager.stopVOCPlay(); + _vm->checkTransition(); + _vm->_eventsManager._leftClick = true; + } else { + _vm->_voy._eventFlags |= EVTFLAG_TIME_DISABLED; + + chooseSTAMPButton(20); + parsePlayCommands(); + _vm->checkTransition(); + _vm->makeViewFinder(); + + _vm->initIFace(); + + hotspots = &_vm->_bVoy->boltEntry(_vm->_playStampGroupId + 1)._rectResource->_entries; + _vm->_eventsManager.getMouseInfo(); + + _vm->_voy._eventFlags &= ~EVTFLAG_TIME_DISABLED; + _vm->_eventsManager._intPtr._flashStep = 1; + _vm->_eventsManager._intPtr._flashTimer = 0; + } + } + } while (!_vm->_eventsManager._rightClick && !_vm->shouldQuit() && + (!_vm->_eventsManager._leftClick || regionIndex == -1)); + + _vm->_eventsManager.hideCursor(); + _vm->_voy._eventFlags |= EVTFLAG_TIME_DISABLED; + _vm->_bVoy->freeBoltGroup(_vm->_playStampGroupId); + if (_vm->_currentVocId != -1) + _vm->_soundManager.stopVOCPlay(); + + return !_vm->_eventsManager._rightClick ? regionIndex : -2; +} + +bool ThreadResource::checkMansionScroll() { + Common::Point pt = _vm->_eventsManager.getMousePos() - + Common::Point(MANSION_VIEW_X, MANSION_VIEW_Y); + Common::Point &viewPos = _vm->_mansionViewPos; + bool result = false; + + // Scroll mansion view if close to any of the mansion edges + if (pt.x >= 0 && pt.x < MANSION_SCROLL_AREA_X && viewPos.x > 0) { + viewPos.x = MAX(viewPos.x - MANSION_SCROLL_INC_X, 0); + result = true; + } + if (pt.x >= (MANSION_VIEW_WIDTH - MANSION_SCROLL_AREA_X) && + pt.x < MANSION_VIEW_WIDTH && viewPos.x < MANSION_MAX_X) { + viewPos.x = MIN(viewPos.x + MANSION_SCROLL_INC_X, MANSION_MAX_X); + result = true; + } + if (pt.y >= 0 && pt.y < MANSION_SCROLL_AREA_Y && viewPos.y > 0) { + viewPos.y = MAX(viewPos.y - MANSION_SCROLL_INC_Y, 0); + result = true; + } + if (pt.y >= (MANSION_VIEW_HEIGHT - MANSION_SCROLL_AREA_Y) && + pt.y < MANSION_VIEW_HEIGHT && viewPos.y < MANSION_MAX_Y) { + viewPos.y = MIN(viewPos.y + MANSION_SCROLL_INC_Y, MANSION_MAX_Y); + result = true; + } + + // Return whether mansion view area has changed + return result; +} + +bool ThreadResource::goToStateID(int stackId, int id) { + debugC(DEBUG_BASIC, kDebugScripts, "goToStateID - %d, %d", stackId, id); + + // Save current stack + savePrevious(); + + if (_stackId == stackId || stackId == -1 || loadAStack(stackId)) { + // Now in the correct state + _stateId = getStateFromID(id); + + if (_stateId != -1) { + return doState(); + } else { + _stateId = _savedStateId; + _stackId = _savedStackId; + } + } + + return false; +} + +bool ThreadResource::goToState(int stackId, int stateId) { + debugC(DEBUG_BASIC, kDebugScripts, "goToState - %d, %d", stackId, stateId); + + savePrevious(); + if (stackId == -1 || loadAStack(stackId)) { + if (stateId != -1) + _stateId = stateId; + + return doState(); + } else { + return false; + } +} + +void ThreadResource::savePrevious() { + if (_savedStateId == _stateId && _stackId == _savedStackId) { + _flags &= ~1; + } else { + _flags |= 1; + _savedStateId = _stateId; + _savedStackId = _stackId; + } +} + +void ThreadResource::setButtonFlag(int idx, byte bits) { + _buttonFlags[idx] |= bits; +} + +void ThreadResource::clearButtonFlag(int idx, byte bits) { + _buttonFlags[idx] &= ~bits; +} + +void ThreadResource::loadTheApt() { + switch (_vm->_voy._transitionId) { + case 1: + case 2: + case 5: + case 6: + case 7: + case 8: + case 9: + case 17: + _vm->_playStampGroupId = 0x5700; + break; + case 3: + _vm->_playStampGroupId = 0x5800; + break; + case 4: + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + _vm->_playStampGroupId = 0x5900; + break; + default: + break; + } + + if (_vm->_voy._aptLoadMode == 143) + _vm->_voy._aptLoadMode = -1; + + if (_vm->_voy._aptLoadMode != -1) { + doAptAnim(1); + _vm->_bVoy->getBoltGroup(_vm->_playStampGroupId); + _vm->_voy._aptLoadMode = -1; + _vm->_graphicsManager._backgroundPage = _vm->_bVoy->boltEntry( + _vm->_playStampGroupId + 5)._picResource; + (*_vm->_graphicsManager._vPort)->setupViewPort( + _vm->_graphicsManager._backgroundPage); + } else { + _vm->_bVoy->getBoltGroup(_vm->_playStampGroupId); + _vm->_graphicsManager._backgroundPage = _vm->_bVoy->boltEntry( + _vm->_playStampGroupId + 5)._picResource; + (*_vm->_graphicsManager._vPort)->setupViewPort( + _vm->_graphicsManager._backgroundPage); + } + + CMapResource *pal = _vm->_bVoy->boltEntry(_vm->_playStampGroupId + 4)._cMapResource; + pal->_steps = 1; + pal->startFade(); + _vm->flipPageAndWaitForFade(); +} + +void ThreadResource::freeTheApt() { + _vm->_graphicsManager.fadeDownICF1(5); + _vm->flipPageAndWaitForFade(); + + _vm->_graphicsManager.fadeUpICF1(0); + + if (_vm->_currentVocId != -1) { + _vm->_soundManager.stopVOCPlay(); + _vm->_currentVocId = -1; + } + + if (_vm->_voy._aptLoadMode == -1) { + _vm->_graphicsManager.fadeDownICF(6); + } else { + doAptAnim(2); + } + + if (_vm->_voy._aptLoadMode == 140) { + _vm->_graphicsManager.screenReset(); + _vm->_graphicsManager.resetPalette(); + } + + (*_vm->_graphicsManager._vPort)->setupViewPort(nullptr); + _vm->_bVoy->freeBoltGroup(_vm->_playStampGroupId); + _vm->_playStampGroupId = -1; + _vm->_voy._viewBounds = nullptr; +} + +void ThreadResource::doAptAnim(int mode) { + _vm->_bVoy->freeBoltGroup(0x100, true); + + // Figure out the resource to use + int id = 0; + switch (_vm->_voy._aptLoadMode) { + case 140: + id = 0x5A00; + break; + case 141: + id = 0x6000; + break; + case 142: + id = 0x6600; + break; + case 143: + id = 0x6C00; + break; + case 144: + id = 0x6F00; + break; + default: + break; + } + + int id2 = (id == 0x6C00 || id == 0x6F00) ? 1 : 2; + switch (_vm->_voy._transitionId) { + case 3: + id += id2 << 8; + break; + case 4: + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + id += id2 << 9; + break; + default: + break; + } + + if (mode == 1) + id += 0x100; + + // Do the display + if (_vm->_bVoy->getBoltGroup(id)) { + CMapResource *pal = _vm->_bVoy->boltEntry(id)._cMapResource; + pal->_steps = 1; + + for (int idx = 0; (idx < 6) && !_vm->shouldQuit(); ++idx) { + PictureResource *pic = _vm->_bVoy->boltEntry(id + idx + 1)._picResource; + (*_vm->_graphicsManager._vPort)->setupViewPort(pic); + pal->startFade(); + + _vm->flipPageAndWait(); + _vm->_eventsManager.delayClick(5); + } + + _vm->_bVoy->freeBoltGroup(id); + } + + _vm->_bVoy->getBoltGroup(0x100); +} + +void ThreadResource::synchronize(Common::Serializer &s) { + s.syncAsSint16LE(_aptPos.x); + s.syncAsSint16LE(_aptPos.y); + + int stateId = _stateId; + int stackId = _stackId; + s.syncAsSint16LE(stateId); + s.syncAsSint16LE(stackId); + + if (s.isLoading() && (stateId != _stateId || stackId != _stackId)) + goToState(stackId, stateId); + + s.syncAsSint16LE(_savedStateId); + s.syncAsSint16LE(_savedStackId); +} + +} // End of namespace Voyeur diff --git a/engines/voyeur/graphics.cpp b/engines/voyeur/graphics.cpp new file mode 100644 index 0000000000..7ed0591871 --- /dev/null +++ b/engines/voyeur/graphics.cpp @@ -0,0 +1,1065 @@ +/* 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 "voyeur/graphics.h" +#include "voyeur/voyeur.h" +#include "voyeur/staticres.h" +#include "engines/util.h" +#include "graphics/palette.h" +#include "graphics/surface.h" + +namespace Voyeur { + +/*------------------------------------------------------------------------*/ + +DrawInfo::DrawInfo(int penColor, const Common::Point &pos, int flags) { + _penColor = penColor; + _pos = pos; + _flags = flags; +} + +/*------------------------------------------------------------------------*/ + +GraphicsManager::GraphicsManager(): _defaultDrawInfo(1, Common::Point(), 0), _drawPtr(&_defaultDrawInfo) { + _SVGAPage = 0; + _SVGAMode = 0; + _SVGAReset = 0; + _screenOffset = 0; + _planeSelect = 0; + _sImageShift = 3; + _palFlag = false; + _MCGAMode = false; + _saveBack = true; + _drawTextPermFlag = false; + _clipPtr = NULL; + _viewPortListPtr = NULL; + _backgroundPage = NULL; + _vPort = NULL; + _fontPtr = NULL; + Common::fill(&_VGAColors[0], &_VGAColors[PALETTE_SIZE], 0); + _fontChar = new PictureResource(0, 0xff, 0xff, 0, 0, Common::Rect(), 0, NULL, 0); +} + +void GraphicsManager::sInitGraphics() { + initGraphics(SCREEN_WIDTH, SCREEN_HEIGHT, false); + _screenSurface.create(SCREEN_WIDTH, SCREEN_HEIGHT, Graphics::PixelFormat::createFormatCLUT8()); + clearPalette(); +} + +GraphicsManager::~GraphicsManager() { + _screenSurface.free(); + delete _fontChar; +} + +void GraphicsManager::setupMCGASaveRect(ViewPortResource *viewPort) { + _MCGAMode = true; + + if (viewPort->_activePage) { + viewPort->_activePage->_flags |= 1; + Common::Rect *clipRect = _clipPtr; + _clipPtr = &viewPort->_clipRect; + + sDrawPic(viewPort->_activePage, viewPort->_currentPic, Common::Point()); + + _clipPtr = clipRect; + } + + viewPort->_rectListCount[1] = -1; +} + +void GraphicsManager::addRectOptSaveRect(ViewPortResource *viewPort, int idx, const Common::Rect &bounds) { + if (viewPort->_rectListCount[idx] == -1) + return; + + // TODO: Lots of code in original, which I suspect may be overlapping rect merging + viewPort->_rectListPtr[idx]->push_back(bounds); + ++viewPort->_rectListCount[idx]; +} + +void GraphicsManager::restoreMCGASaveRect(ViewPortResource *viewPort) { + if (viewPort->_rectListCount[0] != -1) { + for (int i = 0; i < viewPort->_rectListCount[0]; ++i) { + addRectOptSaveRect(viewPort, 1, (*viewPort->_rectListPtr[0])[i]); + } + } else { + viewPort->_rectListCount[1] = -1; + } + + restoreBack(*viewPort->_rectListPtr[1], viewPort->_rectListCount[1], viewPort->_pages[0], + viewPort->_pages[1]); + + int count = viewPort->_rectListCount[0]; + restoreBack(*viewPort->_rectListPtr[0], viewPort->_rectListCount[0], + viewPort->_activePage, viewPort->_currentPic); + + SWAP(viewPort->_rectListPtr[0], viewPort->_rectListPtr[1]); + viewPort->_rectListCount[1] = count; +} + +void GraphicsManager::addRectNoSaveBack(ViewPortResource *viewPort, int idx, const Common::Rect &bounds) { + // Stubbed/dummy method in the original. +} + +void GraphicsManager::sDrawPic(DisplayResource *srcDisplay, DisplayResource *destDisplay, + const Common::Point &initialOffset) { + int var4C = 0; + int width1, width2; + int widthDiff, widthDiff2; + int height1; + int srcOffset; + int screenOffset; + int srcFlags, destFlags; + ViewPortResource *destViewPort = NULL; + Common::Rect newBounds; + Common::Rect backBounds; + int var22 = 0; + int var24 = 0; + bool isClipped = false; + int var26; + byte pixel = 0; + int runLength; + + byte *srcImgData, *destImgData; + byte *srcP, *destP; + byte byteVal, byteVal2; + + // Get the picture parameters, or deference viewport pointers to get their pictures + PictureResource *srcPic = (PictureResource *)srcDisplay; + PictureResource *destPic = (PictureResource *)destDisplay; + + if (srcDisplay->_flags & DISPFLAG_VIEWPORT) { + // A viewport was passed, not a picture + srcPic = ((ViewPortResource *)srcDisplay)->_currentPic; + } + if (destDisplay->_flags & DISPFLAG_VIEWPORT) { + destViewPort = (ViewPortResource *)destDisplay; + destPic = destViewPort->_currentPic; + } + + Common::Point offset = Common::Point(initialOffset.x + srcPic->_bounds.left - destPic->_bounds.left, + initialOffset.y + srcPic->_bounds.top - destPic->_bounds.top); + width1 = width2 = srcPic->_bounds.width(); + height1 = srcPic->_bounds.height(); + srcOffset = 0; + srcFlags = srcPic->_flags; + destFlags = destPic->_flags; + byte *cursorData = NULL; + + if (srcFlags & 1) { + if (_clipPtr) { + int xs = _clipPtr->left - destPic->_bounds.left; + int ys = _clipPtr->top - destPic->_bounds.top; + newBounds = Common::Rect(xs, ys, xs + _clipPtr->width(), ys + _clipPtr->height()); + } else if (destViewPort) { + int xs = destViewPort->_clipRect.left - destPic->_bounds.left; + int ys = destViewPort->_clipRect.top - destPic->_bounds.top; + newBounds = Common::Rect(xs, ys, xs + destViewPort->_clipRect.width(), + ys + destViewPort->_clipRect.height()); + } else { + newBounds = Common::Rect(0, 0, destPic->_bounds.width(), destPic->_bounds.height()); + } + + var24 = offset.y - newBounds.top; + if (var24 < 0) { + srcOffset -= var24 * width2; + height1 += var24; + offset.y = newBounds.top; + + if (height1 <= 0) + return; + + isClipped = true; + } + + int var20 = newBounds.bottom - (offset.y + height1); + if (var20 < 0) { + height1 += var20; + if (height1 <= 0) + return; + } + + var22 = offset.x - newBounds.left; + if (var22 < 0) { + srcOffset -= var22; + width2 += var22; + offset.x = newBounds.left; + + if (width2 <= 0) + return; + + isClipped = true; + } + + var26 = newBounds.right - (offset.x + width2); + if (var26 < 0) { + width2 += var26; + if (width2 <= 0) + return; + + isClipped = true; + } + } + + screenOffset = offset.y * destPic->_bounds.width() + offset.x; + widthDiff = width1 - width2; + widthDiff2 = destPic->_bounds.width() - width2; + + if (destViewPort) { + if (!_saveBack || ((srcPic->_flags & DISPFLAG_800) != 0)) { + backBounds.left = destPic->_bounds.left + offset.x; + backBounds.top = destPic->_bounds.top + offset.y; + backBounds.setWidth(width2); + backBounds.setHeight(height1); + addRectOptSaveRect(destViewPort, 1, backBounds); + + } else if (!destViewPort->_addFn) { + if (destViewPort->_rectListCount[destViewPort->_pageIndex] < -1) { + Common::Rect r; + r.left = destPic->_bounds.left + offset.x; + r.top = destPic->_bounds.top + offset.y; + r.setWidth(width2); + r.setHeight(height1); + + (*destViewPort->_rectListPtr[destViewPort->_pageIndex]).push_back(r); + ++destViewPort->_rectListCount[destViewPort->_pageIndex]; + } + } else { + int xs = offset.x + destPic->_bounds.left; + int ys = offset.y + destPic->_bounds.top; + backBounds = Common::Rect(xs, ys, xs + width2, ys + height1); + + (this->*destViewPort->_addFn)(destViewPort, destViewPort->_bounds.top, backBounds); + } + } + + if (srcFlags & DISPFLAG_1000) { + srcImgData = srcPic->_imgData + (var4C << 14) + _screenOffset; + for (uint idx = 0; idx < srcPic->_maskData; ++idx) { + if (var4C < 4) { + EMSMapPageHandle(srcPic->_planeSize, srcPic->_imgData[idx], var4C); + ++var4C; + } + } + } else { + srcImgData = srcPic->_imgData; + } + if (destFlags & DISPFLAG_1000) { + destImgData = destPic->_imgData + (var4C << 14) + _screenOffset; + for (uint idx = 0; idx < srcPic->_maskData; ++idx) { + if (var4C < 4) { + EMSMapPageHandle(destPic->_planeSize, destPic->_imgData[idx], var4C); + ++var4C; + } + } + } else { + destImgData = destPic->_imgData; + } + + _SVGAPage = _SVGAReset; + if (srcPic->_select != 0xff) + return; + + if (destFlags & DISPFLAG_CURSOR) { + cursorData = new byte[width2 * height1]; + Common::fill(cursorData, cursorData + width2 * height1, 0); + destImgData = cursorData; + } + + if (srcPic->_pick == 0xff) { + if (srcFlags & DISPFLAG_8) { + error("TODO: sDrawPic variation"); + } else { + // loc_258B8 + srcP = srcImgData + srcOffset; + + if (destFlags & DISPFLAG_8) { + // loc_258D8 + destP = destImgData + screenOffset; + + if (srcFlags & DISPFLAG_2) { + // loc_25652 + srcP = srcImgData + srcOffset; + + if (destFlags & DISPFLAG_8) { + // loc_2566F + if (srcFlags & DISPFLAG_2) { + // loc_256FA + srcP = (byte *)_screenSurface.getPixels() + srcOffset; + + for (int yp = 0; yp < height1; ++yp) { + for (int xp = 0; xp < width2; ++width2, ++srcP, ++destP) { + pixel = *srcP; + if (pixel) + *destP = pixel; + } + + srcP += widthDiff; + destP += widthDiff2; + } + } else { + // loc_25706 + for (int yp = 0; yp < height1; ++yp) { + Common::copy(srcP, srcP + width2, destP); + srcP += width2 + widthDiff; + destP += width2 + widthDiff2; + } + } + } else { + // loc_25773 + destP = destImgData + screenOffset; + + if (srcFlags & DISPFLAG_2) { + // loc_25793 + for (int yp = 0; yp < height1; ++yp) { + Common::copy(srcP, srcP + width2, destP); + srcP += width2 + widthDiff; + destP += width2 + widthDiff2; + } + } else { + // loc_25829 + destP = (byte *)_screenSurface.getPixels() + screenOffset; + + for (int yp = 0; yp < height1; ++yp) { + Common::copy(srcP, srcP + width2, destP); + srcP += width2 + widthDiff; + destP += width2 + widthDiff2; + } + } + } + } else { + // loc_25D40 + if (srcFlags & DISPFLAG_100) { + // loc_25D4A + error("TODO: sDrawPic variation"); + } else { + // loc_2606D + destP = (byte *)_screenSurface.getPixels() + screenOffset; + + for (int yp = 0; yp < height1; ++yp) { + Common::copy(srcP, srcP + width2, destP); + destP += width2 + widthDiff2; + srcP += width2 + widthDiff; + } + } + } + } else { + // loc_2615E + destP = destImgData + screenOffset; + + if (srcFlags & DISPFLAG_2) { + // loc_2617e + if (srcFlags & DISPFLAG_100) { + // loc_26188 + srcP = srcImgData; + if (isClipped) { + // loc_26199 + var22 = (var22 < 0) ? -var22 : 0; + var26 = var22 + width2; + var24 = (var24 < 0) ? -var24 : 0; + + width2 = srcPic->_bounds.width(); + height1 = var24 + height1; + + for (int yp = 0; yp < height1; ++yp) { + runLength = 0; + + for (int xp = 0; xp < width2; ++xp, --runLength) { + if (runLength <= 0) { + pixel = *srcP++; + if (pixel & 0x80) { + pixel &= 0x7f; + runLength = *srcP++; + if (!runLength) + runLength = width2; + } + } + + if (yp >= var24 && xp >= var22 && xp < var26) { + if (pixel > 0) + *destP = pixel; + ++destP; + } + } + + if (yp >= var24) + destP += widthDiff2; + } + } else { + // loc_262BE + byteVal = 0; + for (int yp = 0; yp < height1; ++yp) { + for (int xp = 0; xp < width2; ++xp) { + byteVal2 = 0; + if (!byteVal2) { + byteVal = *++srcP; + if (byteVal & 0x80) { + byteVal &= 0x7f; + byteVal2 = *srcP++; + + if (!byteVal2) + byteVal2 = width2; + } + } + + if (byteVal > 0) + *destP = byteVal; + + ++destP; + --byteVal2; + } + + destP += widthDiff2; + } + } + } else { + // loc_2637F + // Copy with transparency + for (int yp = 0; yp < height1; ++yp) { + for (int xp = 0; xp < width2; ++xp, ++srcP, ++destP) { + if (*srcP != 0) + *destP = *srcP; + } + + destP += widthDiff2; + srcP += widthDiff; + } + } + } else { + if (srcFlags & 0x100) { + // Simple run-length encoded image + srcP = srcImgData; + + if (isClipped) { + // loc_26424 + var22 = (var22 < 0) ? -var22 : 0; + var26 = var22 + width2; + var24 = (var24 < 0) ? -var24 : 0; + width2 = srcPic->_bounds.width(); + height1 = var24 + height1; + + for (int yp = 0; yp < height1; ++yp) { + runLength = 0; + for (int xp = 0; xp < width2; ++xp, --runLength) { + if (runLength <= 0) { + pixel = *srcP++; + if (pixel & 0x80) { + pixel &= 0x7F; + runLength = *srcP++; + + if (!runLength) + runLength = width2; + } + } + + if (yp >= var24 && xp >= var22 && xp < var26) { + *destP++ = pixel; + } + } + + if (yp >= var24) + destP += widthDiff2; + } + } else { + // loc_26543 + for (int yp = 0; yp < height1; ++yp) { + int runLength = 0; + for (int xp = 0; xp < width2; ++xp, --runLength) { + if (runLength <= 0) { + // Start of run length, so get pixel and repeat length + pixel = *srcP++; + if (pixel & 0x80) { + pixel &= 0x7f; + runLength = *srcP++; + if (runLength == 0) + runLength = width2; + } + } + + // Copy pixel to output + *destP++ = pixel; + } + + destP += widthDiff2; + } + } + } else { + for (int yp = 0; yp < height1; ++yp) { + Common::copy(srcP, srcP + width2, destP); + destP += width2 + widthDiff2; + srcP += width2 + widthDiff; + } + } + } + } + } + } else { + // loc_26666 + if (srcPic->_pick == 0) { + // loc_2727A + byte onOff = srcPic->_onOff; + + if (srcFlags & DISPFLAG_2) { + if (!(srcFlags & DISPFLAG_8)) { + srcP = srcImgData + srcOffset; + + if (destFlags & DISPFLAG_8) { + // loc_272C3 + error("TODO: sDrawPic variation"); + } else { + destP = destImgData + screenOffset; + for (int yp = 0; yp < height1; ++yp) { + for (int xp = 0; xp < width2; ++xp, ++destP) { + if ((int8)*srcP++ < 0) + *destP = onOff; + } + + destP += widthDiff2; + srcP += widthDiff; + } + } + } + } else { + // loc_27477 + if (destFlags & DISPFLAG_8) { + // loc_27481 + destP = (byte *)_screenSurface.getPixels() + screenOffset; + for (int yp = 0; yp < height1; ++yp) { + Common::fill(destP, destP + width2, onOff); + destP += width2 + widthDiff2; + } + } else { + // loc_2753C + destP = destImgData + screenOffset; + + for (int yp = 0; yp < height1; ++yp) { + Common::fill(destP, destP + width2, onOff); + destP += width2 + widthDiff2; + } + } + } + + } else { + // loc_26673 + int pick = srcPic->_pick; + int onOff = srcPic->_onOff; + + if (!(srcFlags & PICFLAG_PIC_OFFSET)) { + srcP = srcImgData += srcOffset; + int pixel = 0; + + if (destFlags & PICFLAG_PIC_OFFSET) { + destP = destImgData + screenOffset; + if (srcFlags & PICFLAG_2) { + if (srcFlags & PICFLAG_100) { + if (isClipped) { + // loc_266E3 + destP = (byte *)_screenSurface.getPixels() + screenOffset; + var22 = (var22 < 0) ? -var22 : 0; + var26 = var22 + width2; + var24 = (var24 < 0) ? -var24 : 0; + pick = 0x7F; + width2 = srcPic->_bounds.width(); + height1 = var24 + height1; + + for (int yp = 0; yp < height1; ++yp) { + int runLength = 0; + for (int xp = 0; xp < width2; ++xp, --runLength) { + if (runLength <= 0) { + pixel = *srcP++; + if (pixel & 0x80) { + pixel &= 0x7F; + runLength = *srcP++; + if (!runLength) + runLength = width2; + } + } + + if (yp >= var24 && xp >= var22 && xp < var26) { + if (pixel) { + *destP = (pixel & pick) ^ onOff; + } + ++destP; + } + } + if (yp >= var24) + destP += widthDiff2; + } + } else { + // loc_26815 + destP = (byte *)_screenSurface.getPixels() + screenOffset; + + for (int yp = 0; yp < height1; ++yp) { + for (int xp = 0; xp < width2; ++xp, ++destP) { + byteVal2 = 0; + for (int xp = 0; xp < width2; ++xp, ++destP, --byteVal2) { + if (!byteVal2) { + pixel = *srcP++; + if (pixel & 0x80) { + pixel &= 0x7F; + byteVal2 = *srcP++; + if (!byteVal2) { + byteVal2 = width2; + } + } + } + + if (pixel) + *destP = (pixel & pick) ^ onOff; + } + } + + destP += widthDiff2; + } + } + } else { + // Direct screen write + destP = (byte *)_screenSurface.getPixels() + screenOffset; + + for (int yp = 0; yp < height1; ++yp) { + for (int xp = 0; xp < width2; ++xp, ++srcP, ++destP) { + if (*srcP) + *destP = (*srcP & pick) ^ onOff; + } + destP += widthDiff2; + srcP += widthDiff; + } + } + } else if (srcFlags & PICFLAG_100) { + srcP = srcImgData; + if (isClipped) { + // loc_269FD + var22 = (var22 < 0) ? -var22 : 0; + var26 = var22 + width2; + var24 = (var24 < 0) ? -var24 : 0; + width2 = srcPic->_bounds.width(); + height1 = var24 + height1; + + for (int yp = 0; yp < height1; ++yp) { + runLength = 0; + for (int xp = 0; xp < width2; ++xp, --runLength) { + if (runLength <= 0) { + pixel = *srcP++; + if (pixel & 0x80) { + pixel &= 0x7F; + runLength = *srcP++; + + if (!runLength) + runLength = width2; + } + } + + if (yp >= var24 && xp >= var22 && xp < var26) { + *destP++ = (pixel & 0x80) ^ onOff; + } + } + } + } else { + // loc_26BD5 + destP = (byte *)_screenSurface.getPixels() + screenOffset; + + for (int yp = 0; yp < height1; ++yp) { + byteVal2 = 0; + + for (int xp = 0; xp < width2; ++xp, ++destP) { + if (!byteVal2) { + pixel = *srcP++; + if (pixel & 0x80) { + pixel &= 0x7F; + byteVal2 = *srcP++; + if (!byteVal2) + byteVal2 = width2; + } + } + + *destP = (pixel & pick) ^ onOff; + } + + destP += widthDiff2; + } + } + } else { + // loc_26C9A + destP = (byte *)_screenSurface.getPixels() + screenOffset; + + for (int yp = 0; yp < height1; ++yp) { + for (int xp = 0; xp < width2; ++xp, ++srcP, ++destP) { + *destP = (*srcP & pick) ^ onOff; + } + destP += widthDiff2; + srcP += widthDiff; + } + } + } else { + // loc_26D2F + destP = destImgData + screenOffset; + + if (srcFlags & PICFLAG_2) { + // loc_26D4F + if (srcFlags & PICFLAG_100) { + srcP = srcImgData; + + if (isClipped) { + // loc_26D6A + var22 = (var22 < 0) ? -var22 : 0; + var26 = var22 + width2; + var24 = (var24 < 0) ? -var24 : 0; + width2 = srcPic->_bounds.width(); + height1 = var24 + height1; + + for (int yp = 0; yp < height1; ++yp) { + runLength = 0; + + for (int xp = 0; xp < width2; ++xp, --runLength) { + if (runLength <= 0) { + pixel = *srcP++; + if (pixel & 0x80) { + pixel &= 0x7F; + runLength = *srcP++; + if (!runLength) + runLength = width2; + } + } + + if (yp >= var24 && xp >= var22 && xp < var26) { + if (pixel) + *destP = (pixel & pick) ^ onOff; + + ++destP; + } + } + + if (yp >= var24) + destP += widthDiff2; + } + } else { + // loc_26E95 + for (int yp = 0; yp < height1; ++yp) { + byteVal2 = 0; + for (int xp = 0; xp < width2; ++xp, ++destP, --byteVal2) { + if (!byteVal2) { + pixel = *srcP++; + if (pixel & 0x80) { + pixel &= 0x7F; + byteVal2 = *srcP++; + if (!byteVal2) + byteVal2 = width2; + } + } + + if (pixel) + *destP = (pixel & pick) ^ onOff; + } + + destP += widthDiff2; + } + } + } else { + // loc_26F5D + for (int yp = 0; yp < height1; ++yp) { + for (int xp = 0; xp < width2; ++xp, ++srcP, ++destP) { + if (*srcP) + *destP = (*srcP & pick) ^ onOff; + } + destP += widthDiff2; + srcP += widthDiff; + } + } + } else { + // loc_26FEF + if (srcFlags & PICFLAG_100) { + // loc_26FF9 + for (int yp = 0; yp < height1; ++yp) { + for (int xp = 0; xp < width2; ++xp, ++srcP, ++destP) { + *destP = (*srcP & pick) ^ onOff; + } + destP += widthDiff2; + srcP += widthDiff; + } + } else { + // loc_271F0 + srcP = srcImgData; + + if (isClipped) { + // loc_2700A + var22 = (var22 < 0) ? -var22 : 0; + var26 = var22 + width2; + var24 = (var24 < 0) ? -var24 : 0; + width2 = srcPic->_bounds.width(); + height1 = var24 + height1; + + for (int yp = 0; yp < height1; ++yp) { + runLength = 0; + + for (int xp = 0; xp < width2; ++xp, --runLength) { + if (runLength <= 0) { + pixel = *srcP++; + if (pixel & 0x80) { + pixel &= 0x7F; + runLength = *srcP++; + if (!runLength) + runLength = width2; + } + } + + if (yp >= var24 && xp >= var22 && xp < var26) { + *destP++ = (pixel & pick) ^ onOff; + } + } + + if (yp >= var24) + destP += widthDiff2; + } + } else { + // loc_2712F + for (int yp = 0; yp < height1; ++yp) { + byteVal2 = 0; + for (int xp = 0; xp < width2; ++xp, ++destP, --byteVal2) { + if (!byteVal2) { + pixel = *srcP++; + if (pixel & 0x80) { + pixel &= 0x7F; + byteVal2 = *srcP++; + if (!byteVal2) + byteVal2 = width2; + } + } + + *destP = (*srcP & pick) ^ onOff; + } + destP += widthDiff2; + } + } + } + } + } + } + } + } + + if (cursorData) { + _vm->_eventsManager.setCursor(cursorData, width2, height1); + delete[] cursorData; + } +} + +void GraphicsManager::drawANumber(DisplayResource *display, int num, const Common::Point &pt) { + PictureResource *pic = _vm->_bVoy->boltEntry(num + 261)._picResource; + sDrawPic(pic, display, pt); +} + +void GraphicsManager::fillPic(DisplayResource *display, byte onOff) { + PictureResource *pic; + if (display->_flags & DISPFLAG_VIEWPORT) { + pic = ((ViewPortResource *)display)->_currentPic; + } else { + pic = (PictureResource *)display; + } + + PictureResource picResource; + picResource._flags = 0; + picResource._select = 0xff; + picResource._pick = 0; + picResource._onOff = onOff; + picResource._bounds = pic->_bounds; + + sDrawPic(&picResource, display, Common::Point()); +} + +/** + * Queues the given picture for display + */ +void GraphicsManager::sDisplayPic(PictureResource *pic) { + _vm->_eventsManager._intPtr._flipWait = true; +} + +void GraphicsManager::flipPage() { + Common::Array<ViewPortResource *> &viewPorts = _viewPortListPtr->_entries; + bool flipFlag = false; + + for (uint idx = 0; idx < viewPorts.size(); ++idx) { + if (viewPorts[idx]->_flags & DISPFLAG_20) { + if ((viewPorts[idx]->_flags & (DISPFLAG_8 || DISPFLAG_1)) + == (DISPFLAG_8 || DISPFLAG_1)) { + if (_planeSelect == idx) + sDisplayPic(viewPorts[idx]->_currentPic); + flipFlag = true; + } + } + + if (flipFlag) { + ViewPortResource &viewPort = *viewPorts[idx]; + + viewPort._lastPage = viewPort._pageIndex; + ++viewPort._pageIndex; + + if (viewPort._pageIndex >= viewPort._pageCount) + viewPort._pageIndex = 0; + + assert(viewPort._pageIndex < 2); + viewPort._currentPic = viewPort._pages[viewPort._pageIndex]; + viewPort._flags = (viewPort._flags & ~DISPFLAG_8) | DISPFLAG_40; + } + } +} + +void GraphicsManager::restoreBack(Common::Array<Common::Rect> &rectList, int rectListCount, + PictureResource *srcPic, PictureResource *destPic) { + // WORKAROUND: Since _backgroundPage can point to a resource freed at the end of display methods, + // I'm now explicitly resetting it to null in screenReset(), so at this point it can be null + if (!srcPic) + return; + + bool saveBack = _saveBack; + _saveBack = false; + + if (rectListCount == -1) { + sDrawPic(srcPic, destPic, Common::Point()); + } else { + for (int i = rectListCount - 1; i >= 0; --i) { + _clipPtr = &rectList[i]; + sDrawPic(srcPic, destPic, Common::Point()); + } + } + + _saveBack = saveBack; +} + +void GraphicsManager::clearPalette() { + byte palette[768]; + Common::fill(&palette[0], &palette[768], 0); + g_system->getPaletteManager()->setPalette(&palette[0], 0, 256); +} + +void GraphicsManager::setPalette(const byte *palette, int start, int count) { + g_system->getPaletteManager()->setPalette(palette, start, count); + _vm->_eventsManager._gameData._hasPalette = false; +} + +void GraphicsManager::setPalette128(const byte *palette, int start, int count) { + byte rgb[3]; + g_system->getPaletteManager()->grabPalette(&rgb[0], 128, 1); + g_system->getPaletteManager()->setPalette(palette, start, count); + g_system->getPaletteManager()->setPalette(&rgb[0], 128, 1); +} + + +void GraphicsManager::resetPalette() { + for (int i = 0; i < 256; ++i) + setColor(i, 0, 0, 0); + + _vm->_eventsManager._intPtr._hasPalette = true; +} + +void GraphicsManager::setColor(int idx, byte r, byte g, byte b) { + byte *vgaP = &_VGAColors[idx * 3]; + vgaP[0] = r; + vgaP[1] = g; + vgaP[2] = b; + + _vm->_eventsManager._intPtr._palStartIndex = MIN(_vm->_eventsManager._intPtr._palStartIndex, idx); + _vm->_eventsManager._intPtr._palEndIndex = MAX(_vm->_eventsManager._intPtr._palEndIndex, idx); +} + +void GraphicsManager::setOneColor(int idx, byte r, byte g, byte b) { + byte palEntry[3]; + palEntry[0] = r; + palEntry[1] = g; + palEntry[2] = b; + g_system->getPaletteManager()->setPalette(&palEntry[0], idx, 1); +} + +void GraphicsManager::setColors(int start, int count, const byte *pal) { + for (int i = 0; i < count; ++i) { + if ((i + start) != 128) { + const byte *rgb = pal + i * 3; + setColor(i + start, rgb[0], rgb[1], rgb[2]); + } + } + + _vm->_eventsManager._intPtr._hasPalette = true; +} + +void GraphicsManager::screenReset() { + resetPalette(); + + _backgroundPage = NULL; + (*_vPort)->setupViewPort(NULL); + fillPic(*_vPort, 0); + + _vm->flipPageAndWait(); +} + +void GraphicsManager::fadeDownICF1(int steps) { + if (steps > 0) { + int stepAmount = _vm->_voy._fadingAmount2 / steps; + + for (int idx = 0; idx < steps; ++idx) { + _vm->_voy._fadingAmount2 -= stepAmount; + _vm->_eventsManager.delay(1); + } + } + + _vm->_voy._fadingAmount2 = 0; +} + +void GraphicsManager::fadeUpICF1(int steps) { + if (steps > 0) { + int stepAmount = (63 - _vm->_voy._fadingAmount2) / steps; + + for (int idx = 0; idx < steps; ++idx) { + _vm->_voy._fadingAmount2 += stepAmount; + _vm->_eventsManager.delay(1); + } + } + + _vm->_voy._fadingAmount2 = 63; +} + +void GraphicsManager::fadeDownICF(int steps) { + if (steps > 0) { + _vm->_eventsManager.hideCursor(); + int stepAmount1 = _vm->_voy._fadingAmount1 / steps; + int stepAmount2 = _vm->_voy._fadingAmount2 / steps; + + for (int idx = 0; idx < steps; ++idx) { + _vm->_voy._fadingAmount1 -= stepAmount1; + _vm->_voy._fadingAmount2 -= stepAmount2; + _vm->_eventsManager.delay(1); + } + } + + _vm->_voy._fadingAmount1 = 0; + _vm->_voy._fadingAmount2 = 0; +} + +void GraphicsManager::drawDot() { + for (int y = 0; y < 9; ++y) { + byte *pDest = (byte *)_screenSurface.getPixels() + DOT_LINE_START[y] + DOT_LINE_OFFSET[y]; + Common::fill(pDest, pDest + DOT_LINE_LENGTH[y], 0x80); + } +} + +void GraphicsManager::synchronize(Common::Serializer &s) { + s.syncBytes(&_VGAColors[0], PALETTE_SIZE); +} + +} // End of namespace Voyeur diff --git a/engines/voyeur/graphics.h b/engines/voyeur/graphics.h new file mode 100644 index 0000000000..322b90970c --- /dev/null +++ b/engines/voyeur/graphics.h @@ -0,0 +1,134 @@ +/* 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. + * + */ + +#ifndef VOYEUR_GRAPHICS_H +#define VOYEUR_GRAPHICS_H + +#include "common/scummsys.h" +#include "common/array.h" +#include "common/rect.h" +#include "common/serializer.h" +#include "graphics/surface.h" + +namespace Voyeur { + +#define SCREEN_WIDTH 320 +#define SCREEN_HEIGHT 200 +#define PALETTE_COUNT 256 +#define PALETTE_SIZE (256 * 3) + +class VoyeurEngine; +class GraphicsManager; +class DisplayResource; +class PictureResource; +class ViewPortResource; +class ViewPortListResource; +class FontResource; +class FontInfoResource; +class CMapResource; + +class DrawInfo { +public: + int _penColor; + Common::Point _pos; + int _flags; +public: + DrawInfo(int penColor, const Common::Point &pos, int flags); +}; + +typedef void (GraphicsManager::*GraphicMethodPtr)(); +typedef void (GraphicsManager::*ViewPortSetupPtr)(ViewPortResource *); +typedef void (GraphicsManager::*ViewPortAddPtr)(ViewPortResource *, int idx, const Common::Rect &bounds); +typedef void (GraphicsManager::*ViewPortRestorePtr)(ViewPortResource *); + +class GraphicsManager { +public: + VoyeurEngine *_vm; + bool _palFlag; + byte _VGAColors[PALETTE_SIZE]; + Common::Array<byte *> _colorChain; + PictureResource *_backgroundPage; + int _SVGAPage; + int _SVGAMode; + int _SVGAReset; + ViewPortListResource *_viewPortListPtr; + ViewPortResource **_vPort; + bool _MCGAMode; + bool _saveBack; + Common::Rect *_clipPtr; + int _screenOffset; + uint _planeSelect; + int _sImageShift; + Graphics::Surface _screenSurface; + CMapResource *_backColors; + FontInfoResource *_fontPtr; + PictureResource *_fontChar; + DrawInfo *_drawPtr; + DrawInfo _defaultDrawInfo; + bool _drawTextPermFlag; +private: + static void fadeIntFunc(); + static void vDoCycleInt(); + + void restoreBack(Common::Array<Common::Rect> &rectList, int rectListCount, + PictureResource *srcPic, PictureResource *destPic); +public: + GraphicsManager(); + ~GraphicsManager(); + void setVm(VoyeurEngine *vm) { _vm = vm; } + void sInitGraphics(); + + void setupMCGASaveRect(ViewPortResource *viewPort); + void addRectOptSaveRect(ViewPortResource *viewPort, int idx, const Common::Rect &bounds); + void restoreMCGASaveRect(ViewPortResource *viewPort); + void addRectNoSaveBack(ViewPortResource *viewPort, int idx, const Common::Rect &bounds); + + void sDrawPic(DisplayResource *srcDisplay, DisplayResource *destDisplay, const Common::Point &initialOffset); + void fillPic(DisplayResource *display, byte onOff = 0); + void sDisplayPic(PictureResource *pic); + void drawANumber(DisplayResource *display, int num, const Common::Point &pt); + void flipPage(); + void clearPalette(); + void setPalette(const byte *palette, int start, int count); + void setPalette128(const byte *palette, int start, int count); + void resetPalette(); + void setColor(int idx, byte r, byte g, byte b); + void setOneColor(int idx, byte r, byte g, byte b); + void setColors(int start, int count, const byte *pal); + void screenReset(); + void fadeDownICF1(int steps); + void fadeUpICF1(int steps); + void fadeDownICF(int steps); + void drawDot(); + + /** + * Synchronizes the game data + */ + void synchronize(Common::Serializer &s); + + // Methods in the original that are stubbed in ScummVM + void EMSMapPageHandle(int v1, int v2, int v3) {} +}; + +} // End of namespace Voyeur + +#endif /* VOYEUR_GRAPHICS_H */ diff --git a/engines/voyeur/module.mk b/engines/voyeur/module.mk new file mode 100644 index 0000000000..aab254cf36 --- /dev/null +++ b/engines/voyeur/module.mk @@ -0,0 +1,23 @@ +MODULE := engines/voyeur + +MODULE_OBJS := \ + animation.o \ + data.o \ + debugger.o \ + detection.o \ + events.o \ + files.o \ + files_threads.o \ + graphics.o \ + sound.o \ + staticres.o \ + voyeur.o \ + voyeur_game.o + +# This module can be built as a plugin +ifeq ($(ENABLE_VOYEUR), DYNAMIC_PLUGIN) +PLUGIN := 1 +endif + +# Include common rules +include $(srcdir)/rules.mk diff --git a/engines/voyeur/sound.cpp b/engines/voyeur/sound.cpp new file mode 100644 index 0000000000..e81d2891fb --- /dev/null +++ b/engines/voyeur/sound.cpp @@ -0,0 +1,85 @@ +/* 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/audiostream.h" +#include "audio/decoders/raw.h" +#include "common/memstream.h" +#include "voyeur/sound.h" +#include "voyeur/staticres.h" + +namespace Voyeur { + +SoundManager::SoundManager(Audio::Mixer *mixer) { + _mixer = mixer; + _vocOffset = 0; +} + +void SoundManager::playVOCMap(byte *voc, int vocSize) { + Common::MemoryReadStream *dataStream = new Common::MemoryReadStream(voc, vocSize, DisposeAfterUse::NO); + Audio::AudioStream *audioStream = Audio::makeVOCStream(dataStream, Audio::FLAG_UNSIGNED, DisposeAfterUse::YES); + + _mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundHandle, audioStream); +} + +void SoundManager::abortVOCMap() { + _mixer->stopHandle(_soundHandle); +} + +void SoundManager::stopVOCPlay() { + _mixer->stopHandle(_soundHandle); + _vocOffset = 0; +} + +void SoundManager::setVOCOffset(int offset) { + _vocOffset = offset; +} + +Common::String SoundManager::getVOCFileName(int idx) { + return Common::String::format("%s.voc", SZ_FILENAMES[idx]); +} + +void SoundManager::startVOCPlay(const Common::String &filename) { + Common::File f; + if (!f.open(filename)) + error("Could not find voc file - %s", filename.c_str()); + + Audio::SeekableAudioStream *audioStream = Audio::makeVOCStream(f.readStream(f.size()), + Audio::FLAG_UNSIGNED, DisposeAfterUse::YES); + + _mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundHandle, audioStream); + audioStream->seek(Audio::Timestamp(_vocOffset * 1000, 11025)); +} + +void SoundManager::startVOCPlay(int soundId) { + startVOCPlay(getVOCFileName(soundId)); +} + +int SoundManager::getVOCStatus() { + return _mixer->isSoundHandleActive(_soundHandle); +} + +uint32 SoundManager::getVOCFrame() { + Audio::Timestamp timestamp = _mixer->getElapsedTime(_soundHandle); + return timestamp.secs(); +} + +} // End of namespace Voyeur diff --git a/engines/voyeur/sound.h b/engines/voyeur/sound.h new file mode 100644 index 0000000000..ca05b0bfe0 --- /dev/null +++ b/engines/voyeur/sound.h @@ -0,0 +1,60 @@ +/* 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. + * + */ + +#ifndef VOYEUR_SOUND_H +#define VOYEUR_SOUND_H + +#include "common/scummsys.h" +#include "common/str.h" +#include "audio/mixer.h" +#include "audio/decoders/voc.h" +#include "voyeur/files.h" + +namespace Voyeur { + +class SoundManager { +private: + VoyeurEngine *_vm; + Audio::Mixer *_mixer; + Audio::SoundHandle _soundHandle; + int _vocOffset; +public: + SoundManager(Audio::Mixer *mixer); + void setVm(VoyeurEngine *vm) { _vm = vm; } + + void playVOCMap(byte *voc, int vocSize); + void stopVOCPlay(); + void abortVOCMap(); + void setVOCOffset(int offset); + Common::String getVOCFileName(int idx); + void startVOCPlay(const Common::String &filename); + void startVOCPlay(int soundId); + int getVOCStatus(); + uint32 getVOCFrame(); + + // Methods in the original that are stubbed in ScummVM + void continueVocMap() {} +}; + +} // End of namespace Voyeur + +#endif /* VOYEUR_SOUND_H */ diff --git a/engines/voyeur/staticres.cpp b/engines/voyeur/staticres.cpp new file mode 100644 index 0000000000..9025426c57 --- /dev/null +++ b/engines/voyeur/staticres.cpp @@ -0,0 +1,142 @@ +/* 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 "voyeur/staticres.h" + +namespace Voyeur { + +const int COMPUTER_DEFAULTS[] = { + 18, 1, 0, 1, 33, 0, 998, -1, 18, 2, 0, 1, 41, 0, + 998, -1, 18, 3, 0, 1, 47, 0, 998, -1, 18, 4, 0, + 1, 53, 0, 998, -1, 18, 5, 0, 1, 46, 0, 998, -1, + 18, 6, 0, 1, 50, 0, 998, -1, 18, 7, 0, 1, 40, 0, + 998, -1, 18, 8, 0, 1, 43, 0, 998, -1, 19, 1, 0, + 2, 28, 0, 998, -1 +}; + +const int RESOLVE_TABLE[] = { + 0x2A00, 0x4A00, 0x1000, 0x4B00, 0x2C00, 0x4F00, 0x1400, 0x5000, + 0x1700, 0x5100, 0x1800, 0x5200, 0x3300, 0x5400, 0x3700, 0x5500, + 0x1A00, 0x1C00, 0x1E00, 0x1F00, 0x2100, 0x2200, 0x2400, 0x2700, + 0x2B00, 0x1100, 0x4C00, 0x1200, 0x4D00, 0x1300, 0x4E00, 0x2E00, + 0x1900, 0x3200, 0x3400, 0x3800, 0x2800, 0x3E00, 0x4100, 0x2900, + 0x4400, 0x4600, 0x5300, 0x3900, 0x7600, 0x7200, 0x7300, 0x7400, + 0x7500 +}; + +const int LEVEL_H[] = { + 4, 7, 7, 8, 9, 10, 2, 2, 4, 8, 8, 9, 9, 10, 10, 11, 11 +}; + +const int LEVEL_M[] = { + 0, 0, 30, 0, 30, 0, 0, 0, 30, 0, 30, 0, 45, 0, 30, 0, 30 +}; + +const int BLIND_TABLE[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 9, 10, 11, 1, 11, 5, 12, + 13, 16, 15, 16, 17, 18, 5, 6, 18, 17, 13, 13, 14, 14, + 5, 12, 6, 6, 13, 14, 13 +}; + +const int COMP_BUT_TABLE[] = { + 269, 128, 307, 163, + 269, 128, 307, 163, + 68, 79, 98, 102, + 68, 79, 98, 102, + 68, 79, 98, 102, + 68, 79, 98, 102, + 248, 138, 291, 163, + 83, 132, 143, 156, + 248, 138, 291, 163, + 83, 132, 143, 156, + 83, 132, 143, 156, + 248, 138, 291, 163, + 68, 79, 98, 102, + 68, 79, 98, 102 +}; + +const char *const SZ_FILENAMES[] = { + "A2110100", nullptr, "A2300100", nullptr, "B1220100", nullptr, "C1220100", nullptr, + "C1290100", nullptr, "D1220100", nullptr, "D1270100", nullptr, "E1210100", nullptr, + "E1260100", nullptr, "E1280100", nullptr, "E1325100", nullptr, "F1200100", nullptr, + "G1250100", nullptr, "G1260100", nullptr, "H1200100", nullptr, "H1230100", nullptr, + "H1310100", nullptr, "I1300100", nullptr, "J1220100", nullptr, "J1230100", nullptr, + "J1320100", nullptr, "K1260100", nullptr, "K1280100", nullptr, "K1325100", nullptr, + "L1210100", nullptr, "L1280100", nullptr, "L1290100", nullptr, "L1300100", nullptr, + "L1310100", nullptr, "M1260100", nullptr, "M1310100", nullptr, "N1210100", nullptr, + "N1225100", nullptr, "N1275510", nullptr, "N1280510", nullptr, "N1325100", nullptr, + "O1230100", nullptr, "O1260100", nullptr, "O1260520", nullptr, "O1280100", nullptr, + "O1325540", nullptr, "P1276710", nullptr, "P1280540", nullptr, "P1280740", nullptr, + "P1290510", nullptr, "P1325100", nullptr, "P1325300", nullptr, "P1325520", nullptr, + "Q1230100", nullptr, "Q1240530", nullptr, "Q1240730", nullptr, "Q1260100", nullptr, + "Q1260520", nullptr, "Q1260720", nullptr, "Q1325100", nullptr, "R1280540", nullptr, + "Z1110510", nullptr, "Z1110520", nullptr, "Z1110530", nullptr, "Z1110540", nullptr, + "Z1110545", nullptr, "Z2320100", nullptr, "Z2905300", nullptr, "Z3110100", nullptr, + "Z3115510", nullptr, "Z3115520", nullptr, "Z3115530", nullptr, "Z3115540", nullptr, + "Z4915100", nullptr, "Z4915200", nullptr, "Z4915300", + nullptr, nullptr, nullptr, nullptr, nullptr, + "MMARG", "MZACK", "MREED", "MJESSI", "MCHLOE", "MCAMERA", "MENDCRED", + "NEWCALL2", "PHONE1", "PHONE2", "PHONE3", "PHONE6", "PHONE8", + "B1300100", "C1250100", "C1320100", "D1320100", "E1210200", "E1260200", + "E1280200", "E1310100", "G1230100", "G1300100", "I1210100", "I1270100", + "I1280100", "J1250100", "J1280100", "K1260200", "K1270100", "K1325200", + "L1240100", "M1200100", "M1230100", "M1290100", "N1250100", "N1260100", + "N1280100", "O1250510", "O1290510", "O1320510", "O1320710", "P1240100", + "P1240530", "P1260100", "P1270100", "P1280100", "P1280530", "P1320530", + "Q1240100", "E1325100" +}; + +const char *const SATURDAY = "Saturday"; +const char *const SUNDAY = "Sunday"; +const char *const MONDAY = "Monday Morning"; +const char *const AM = "am"; +const char *const PM = "pm"; + +const char *const START_OF_MESSAGE = "*** Start of Message ***"; +const char *const END_OF_MESSAGE = "*** End of Message ***"; + +const char *const EVENT_TYPE_STRINGS[4] = { "Video", "Audio", "Evidence", "Computer" }; + +int DOT_LINE_START[9] = { + 0xE880, 0xE9C0, 0xEB00, 0xEC40, 0xED80, 0xEEC0, 0xF000, 0xF140, 0xF280 +}; +int DOT_LINE_OFFSET[9] = { + 144, 143, 142, 141, 141, 141, 142, 143, 144 +}; +int DOT_LINE_LENGTH[9] = { + 5, 7, 9, 11, 11, 11, 9, 7, 5 +}; + +const char *const PIRACY_MESSAGE[] = { + "It is illegal to make", + "unauthorized copies of", + "this software. Duplication", + "of this software for any", + "reason including sale,", + "loan, rental, or gift is a", + "crime. Penalties include", + "fines of up to $50,000", + "and jail terms up to", + "5 years." +}; + +} // End of namespace Voyeur diff --git a/engines/voyeur/staticres.h b/engines/voyeur/staticres.h new file mode 100644 index 0000000000..20bffe01e3 --- /dev/null +++ b/engines/voyeur/staticres.h @@ -0,0 +1,63 @@ +/* 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. + * + */ + +#ifndef VOYEUR_STATICRES_H +#define VOYEUR_STATICRES_H + +#include "common/scummsys.h" + +namespace Voyeur { + +extern const int COMPUTER_DEFAULTS[]; + +extern const int RESOLVE_TABLE[]; + +extern const int LEVEL_H[]; + +extern const int LEVEL_M[]; + +extern const int BLIND_TABLE[]; + +extern const int COMP_BUT_TABLE[]; + +extern const char *const SZ_FILENAMES[]; + +extern const char *const SATURDAY; +extern const char *const SUNDAY; +extern const char *const MONDAY; +extern const char *const AM; +extern const char *const PM; + +extern const char *const START_OF_MESSAGE; +extern const char *const END_OF_MESSAGE; + +extern const char *const EVENT_TYPE_STRINGS[4]; + +extern int DOT_LINE_START[9]; +extern int DOT_LINE_OFFSET[9]; +extern int DOT_LINE_LENGTH[9]; + +extern const char *const PIRACY_MESSAGE[]; + +} // End of namespace Voyeur + +#endif diff --git a/engines/voyeur/voyeur.cpp b/engines/voyeur/voyeur.cpp new file mode 100644 index 0000000000..d7fa92f1cb --- /dev/null +++ b/engines/voyeur/voyeur.cpp @@ -0,0 +1,919 @@ +/* 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 "voyeur/voyeur.h" +#include "voyeur/animation.h" +#include "voyeur/graphics.h" +#include "voyeur/staticres.h" +#include "common/scummsys.h" +#include "common/config-manager.h" +#include "common/debug-channels.h" +#include "graphics/palette.h" +#include "graphics/scaler.h" +#include "graphics/thumbnail.h" + +namespace Voyeur { + +VoyeurEngine *g_vm; + +VoyeurEngine::VoyeurEngine(OSystem *syst, const VoyeurGameDescription *gameDesc) : Engine(syst), + _gameDescription(gameDesc), _randomSource("Voyeur"), _soundManager(_mixer), + _defaultFontInfo(3, 0xff, 0xff, 0, 0, ALIGN_LEFT, 0, Common::Point(), 1, 1, + Common::Point(1, 1), 1, 0, 0) { + _bVoy = NULL; + _iForceDeath = -1; + _controlPtr = NULL; + _stampFlags = 0; + _playStampGroupId = _currentVocId = 0; + _audioVideoId = -1; + _checkTransitionId = -1; + _gameHour = 0; + _gameMinute = 0; + _flashTimeVal = 0; + _flashTimeFlag = false; + _timeBarVal = -1; + _checkPhoneVal = 0; + _voyeurArea = AREA_NONE; + _loadGameSlot = -1; + + DebugMan.addDebugChannel(kDebugScripts, "scripts", "Game scripts"); + + initializeManagers(); +} + +VoyeurEngine::~VoyeurEngine() { + delete _bVoy; +} + +Common::Error VoyeurEngine::run() { + ESP_Init(); + globalInitBolt(); + + // The original allows the victim to be explicitly specified via the command line. + // We don't currently support this in ScummVM, but I'm leaving the code below + // in case we ever want to make use of it. + if (_iForceDeath >= 1 && _iForceDeath <= 4) + _voy._eventFlags |= EVTFLAG_VICTIM_PRESET; + + _eventsManager.resetMouse(); + if (doHeadTitle()) { + playStamp(); + if (!shouldQuit()) + doTailTitle(); + } + + //doHeadTitle(); + + return Common::kNoError; +} + + +int VoyeurEngine::getRandomNumber(int maxNumber) { + return _randomSource.getRandomNumber(maxNumber); +} + +void VoyeurEngine::initializeManagers() { + _debugger.setVm(this); + _eventsManager.setVm(this); + _filesManager.setVm(this); + _graphicsManager.setVm(this); + _soundManager.setVm(this); + _voy.setVm(this); +} + +void VoyeurEngine::ESP_Init() { + ThreadResource::init(); + + if (ConfMan.hasKey("save_slot")) + _loadGameSlot = ConfMan.getInt("save_slot"); +} + +void VoyeurEngine::globalInitBolt() { + initBolt(); + + _filesManager.openBoltLib("bvoy.blt", _bVoy); + _bVoy->getBoltGroup(0x000); + _bVoy->getBoltGroup(0x100); + + _graphicsManager._fontPtr = &_defaultFontInfo; + _graphicsManager._fontPtr->_curFont = _bVoy->boltEntry(0x101)._fontResource; + assert(_graphicsManager._fontPtr->_curFont); + + // Setup default flags + _voy._fadeICF0 = false; + _voy._viewBounds = nullptr; + _voy._curICF0 = _graphicsManager._palFlag ? 0xFFFFA5E0 : 0x5F90; + + _eventsManager.addFadeInt(); +} + +void VoyeurEngine::initBolt() { + vInitInterrupts(); + _graphicsManager.sInitGraphics(); + _eventsManager.vInitColor(); + initInput(); +} + +void VoyeurEngine::vInitInterrupts() { + _eventsManager._intPtr._palette = &_graphicsManager._VGAColors[0]; +} + +void VoyeurEngine::initInput() { +} + +bool VoyeurEngine::doHeadTitle() { +// char dest[144]; + + _eventsManager.startMainClockInt(); + + if (_loadGameSlot == -1) { + // Show starting screen + if (_bVoy->getBoltGroup(0x500)) { + showConversionScreen(); + _bVoy->freeBoltGroup(0x500); + + if (shouldQuit()) + return false; + } + + if (ConfMan.getBool("copy_protection")) { + // Display lock screen + bool result = doLock(); + if (!result || shouldQuit()) + return false; + } + + // Show the title screen + _eventsManager.getMouseInfo(); + showTitleScreen(); + if (shouldQuit()) + return false; + + // Opening + _eventsManager.getMouseInfo(); + doOpening(); + if (shouldQuit()) + return false; + + _eventsManager.getMouseInfo(); + doTransitionCard("Saturday Afternoon", "Player's Apartment"); + _eventsManager.delayClick(90); + + if (_voy._eventFlags & EVTFLAG_VICTIM_PRESET) { + // Preset victim turned on, so add a default set of incriminating videos + _voy.addEvent(18, 1, EVTYPE_VIDEO, 33, 0, 998, -1); + _voy.addEvent(18, 2, EVTYPE_VIDEO, 41, 0, 998, -1); + _voy.addEvent(18, 3, EVTYPE_VIDEO, 47, 0, 998, -1); + _voy.addEvent(18, 4, EVTYPE_VIDEO, 53, 0, 998, -1); + _voy.addEvent(18, 5, EVTYPE_VIDEO, 46, 0, 998, -1); + _voy.addEvent(18, 6, EVTYPE_VIDEO, 50, 0, 998, -1); + _voy.addEvent(18, 7, EVTYPE_VIDEO, 40, 0, 998, -1); + _voy.addEvent(18, 8, EVTYPE_VIDEO, 43, 0, 998, -1); + _voy.addEvent(19, 1, EVTYPE_AUDIO, 20, 0, 998, -1); + } + } + + _voy._aptLoadMode = 140; + return true; +} + +void VoyeurEngine::showConversionScreen() { + _graphicsManager._backgroundPage = _bVoy->boltEntry(0x502)._picResource; + (*_graphicsManager._vPort)->setupViewPort(); + flipPageAndWait(); + + // Immediate palette load to show the initial screen + CMapResource *cMap = _bVoy->getCMapResource(0x503); + assert(cMap); + cMap->_steps = 0; + cMap->startFade(); + + // Wait briefly + _eventsManager.delayClick(150); + if (shouldQuit()) + return; + + // Fade out the screen + cMap = _bVoy->getCMapResource(0x5040000); + cMap->_steps = 30; + cMap->startFade(); + if (shouldQuit()) + return; + + flipPageAndWaitForFade(); + + _graphicsManager.screenReset(); +} + +bool VoyeurEngine::doLock() { + bool result = true; + int buttonVocSize, wrongVocSize; + byte *buttonVoc = _filesManager.fload("button.voc", &buttonVocSize); + byte *wrongVoc = _filesManager.fload("wrong.voc", &wrongVocSize); + + if (_bVoy->getBoltGroup(0x700)) { + _voy._viewBounds = _bVoy->boltEntry(0x704)._rectResource; + + Common::String password = "3333"; + PictureResource *cursorPic = _bVoy->getPictureResource(0x702); + assert(cursorPic); + + // Get the mappings of keys on the keypad + byte *keyData = _bVoy->memberAddr(0x705); + int keyCount = READ_LE_UINT16(keyData); + + _graphicsManager._backColors = _bVoy->getCMapResource(0x7010000); + _graphicsManager._backgroundPage = _bVoy->getPictureResource(0x700); + (*_graphicsManager._vPort)->setupViewPort(); + + _graphicsManager._backColors->startFade(); + (*_graphicsManager._vPort)->_parent->_flags |= DISPFLAG_8; + _graphicsManager.flipPage(); + _eventsManager.sWaitFlip(); + + while (!shouldQuit() && (_eventsManager._fadeStatus & 1)) + _eventsManager.delay(1); + + _eventsManager.setCursorColor(127, 0); + _graphicsManager.setColor(1, 64, 64, 64); + _graphicsManager.setColor(2, 96, 96, 96); + _graphicsManager.setColor(3, 160, 160, 160); + _graphicsManager.setColor(4, 224, 224, 224); + + // Set up the cursor + _eventsManager.setCursor(cursorPic); + _eventsManager.showCursor(); + + _eventsManager._intPtr._hasPalette = true; + + _graphicsManager._fontPtr->_curFont = _bVoy->boltEntry(0x708)._fontResource; + _graphicsManager._fontPtr->_fontSaveBack = 0; + _graphicsManager._fontPtr->_fontFlags = 0; + + Common::String dateString = "ScummVM"; + Common::String displayString = Common::String::format("Last Play %s", dateString.c_str()); + + bool firstLoop = true; + bool breakFlag = false; + bool flag = false; + while (!breakFlag && !shouldQuit()) { + (*_graphicsManager._vPort)->setupViewPort(); + flipPageAndWait(); + + // Display the last play time + _graphicsManager._fontPtr->_pos = Common::Point(0, 97); + _graphicsManager._fontPtr->_justify = ALIGN_CENTRE; + _graphicsManager._fontPtr->_justifyWidth = 384; + _graphicsManager._fontPtr->_justifyHeight = 97; + + (*_graphicsManager._vPort)->drawText(displayString); + flipPageAndWait(); + + if (firstLoop) { + firstLoop = false; + displayString = ""; + } + + // Loop for getting key presses + int key; + do { + do { + // Scan through the list of key rects to check if a keypad key is highlighted + key = -1; + Common::Point mousePos = _eventsManager.getMousePos() + + Common::Point(30, 20); + + for (int keyIndex = 0; keyIndex < keyCount; ++keyIndex) { + int x1 = READ_LE_UINT16(keyData + (((keyIndex << 2) + 1) << 1)); + int x2 = READ_LE_UINT16(keyData + (((keyIndex << 2) + 3) << 1)); + int y1 = READ_LE_UINT16(keyData + (((keyIndex << 2) + 2) << 1)); + int y2 = READ_LE_UINT16(keyData + (((keyIndex << 2) + 4) << 1)); + + if (mousePos.x >= x1 && mousePos.x <= x2 && mousePos.y >= y1 && mousePos.y <= y2) { + key = keyIndex; + } + } + + _eventsManager.setCursorColor(127, (key == -1) ? 0 : 1); + _eventsManager._intPtr._hasPalette = true; + + _eventsManager.delay(1); + } while (!shouldQuit() && !_eventsManager._mouseClicked); + _eventsManager._mouseClicked = false; + } while (!shouldQuit() && key == -1); + + _soundManager.abortVOCMap(); + _soundManager.playVOCMap(buttonVoc, buttonVocSize); + + while (_soundManager.getVOCStatus()) { + if (shouldQuit()) + break; + + _soundManager.continueVocMap(); + _eventsManager.delay(1); + } + + // Process the key + if (key < 10) { + // Numeric key + if (displayString.size() < 10) { + displayString += '0' + key; + continue; + } + } else if (key == 10) { + // Accept key + if (!flag) { + if ((password.empty() && displayString.empty()) || (password == displayString)) { + breakFlag = true; + result = true; + break; + } + } else { + if (displayString.size() > 0) { + result = true; + breakFlag = true; + break; + } + } + } else if (key == 11) { + // New code + if ((password.empty() && displayString.empty()) || (password != displayString)) { + (*_graphicsManager._vPort)->setupViewPort(); + flag = true; + displayString = ""; + continue; + } + } else if (key == 12) { + // Exit keyword + breakFlag = true; + result = false; + break; + } else { + continue; + } + + _soundManager.playVOCMap(wrongVoc, wrongVocSize); + } + + _graphicsManager.fillPic(*_graphicsManager._vPort); + flipPageAndWait(); + _graphicsManager.resetPalette(); + + _voy._viewBounds = nullptr; + _bVoy->freeBoltGroup(0x700); + } + + _eventsManager.hideCursor(); + + delete[] buttonVoc; + delete[] wrongVoc; + + return result; +} + +void VoyeurEngine::showTitleScreen() { + if (_bVoy->getBoltGroup(0x500)) { + _graphicsManager._backgroundPage = _bVoy->getPictureResource(0x500); + + (*_graphicsManager._vPort)->setupViewPort(); + flipPageAndWait(); + + // Immediate palette load to show the initial screen + CMapResource *cMap = _bVoy->getCMapResource(0x501); + assert(cMap); + cMap->_steps = 60; + cMap->startFade(); + + // Wait briefly + _eventsManager.delayClick(200); + if (shouldQuit()) + return; + + // Fade out the screen + cMap = _bVoy->getCMapResource(0x504); + cMap->_steps = 30; + cMap->startFade(); + + flipPageAndWaitForFade(); + if (shouldQuit()) + return; + + _graphicsManager.screenReset(); + _eventsManager.delayClick(200); + + // Voyeur title + playRL2Video("a1100100.rl2"); + _graphicsManager.screenReset(); + + _bVoy->freeBoltGroup(0x500); + } +} + +void VoyeurEngine::doOpening() { + _graphicsManager.screenReset(); + + if (!_bVoy->getBoltGroup(0x200, true)) + return; + + byte *frameTable = _bVoy->memberAddr(0x215); + byte *xyTable = _bVoy->memberAddr(0x216); +// byte *whTable = _bVoy->memberAddr(0x217); + int frameIndex = 0; + bool creditShow = true; + PictureResource *textPic = nullptr; + Common::Point textPos; + + _voy._vocSecondsOffset = 0; + _voy._RTVNum = 0; + _voy._audioVisualStartTime = _voy._RTVNum; + _voy._eventFlags |= EVTFLAG_RECORDING; + _gameHour = 4; + _gameMinute = 0; + _audioVideoId = 1; + _eventsManager._videoDead = -1; + _voy.addVideoEventStart(); + + _voy._eventFlags &= ~EVTFLAG_TIME_DISABLED; + + for (int i = 0; i < 256; ++i) + _graphicsManager.setColor(i, 8, 8, 8); + + _eventsManager._intPtr._hasPalette = true; + (*_graphicsManager._vPort)->setupViewPort(); + flipPageAndWait(); + + RL2Decoder decoder; + decoder.loadFile("a2300100.rl2"); + decoder.start(); + + while (!shouldQuit() && !decoder.endOfVideo() && !_eventsManager._mouseClicked) { + if (decoder.hasDirtyPalette()) { + const byte *palette = decoder.getPalette(); + _graphicsManager.setPalette(palette, 0, 256); + } + + if (decoder.needsUpdate()) { + const Graphics::Surface *frame = decoder.decodeNextFrame(); + + Common::copy((const byte *)frame->getPixels(), (const byte *)frame->getPixels() + 320 * 200, + (byte *)_graphicsManager._screenSurface.getPixels()); + + if (decoder.getCurFrame() >= (int32)READ_LE_UINT32(frameTable + frameIndex * 4)) { + if (creditShow) { + // Show a credit + textPic = _bVoy->boltEntry(frameIndex / 2 + 0x202)._picResource; + textPos = Common::Point(READ_LE_UINT16(xyTable + frameIndex * 2), + READ_LE_UINT16(xyTable + (frameIndex + 1) * 2)); + + creditShow = false; + } else { + textPic = nullptr; + + creditShow = true; + } + + ++frameIndex; + } + + if (textPic) { + _graphicsManager.sDrawPic(textPic, *_graphicsManager._vPort, textPos); + flipPageAndWait(); + } + } + + _eventsManager.getMouseInfo(); + g_system->delayMillis(10); + } + + if ((_voy._RTVNum - _voy._audioVisualStartTime) < 2) + _eventsManager.delay(60); + + _voy._eventFlags |= EVTFLAG_TIME_DISABLED; + _voy.addVideoEventEnd(); + _voy._eventFlags &= ~EVTFLAG_RECORDING; + + _bVoy->freeBoltGroup(0x200); +} + +void VoyeurEngine::playRL2Video(const Common::String &filename) { + RL2Decoder decoder; + decoder.loadFile(filename); + decoder.start(); + + while (!shouldQuit() && !decoder.endOfVideo() && !_eventsManager._mouseClicked) { + if (decoder.hasDirtyPalette()) { + const byte *palette = decoder.getPalette(); + _graphicsManager.setPalette(palette, 0, 256); + } + + if (decoder.needsUpdate()) { + const Graphics::Surface *frame = decoder.decodeNextFrame(); + + Common::copy((const byte *)frame->getPixels(), (const byte *)frame->getPixels() + 320 * 200, + (byte *)_graphicsManager._screenSurface.getPixels()); + } + + _eventsManager.getMouseInfo(); + g_system->delayMillis(10); + } +} + +void VoyeurEngine::playAVideo(int videoId) { + playAVideoDuration(videoId, 9999); +} + +void VoyeurEngine::playAVideoDuration(int videoId, int duration) { + int totalFrames = duration * 10; + + if (videoId == -1) + return; + + PictureResource *pic = NULL; + if (videoId == 42) { + _eventsManager._videoDead = 0; + pic = _bVoy->boltEntry(0xE00 + _eventsManager._videoDead)._picResource; + } + + RL2Decoder decoder; + decoder.loadVideo(videoId); + + decoder.seek(Audio::Timestamp(_voy._vocSecondsOffset * 1000)); + decoder.start(); + int endFrame = decoder.getCurFrame() + totalFrames; + + _eventsManager.getMouseInfo(); + if (!_voy._fadeICF0) + _eventsManager.startCursorBlink(); + + while (!shouldQuit() && !decoder.endOfVideo() && !_eventsManager._mouseClicked && + (decoder.getCurFrame() < endFrame)) { + if (decoder.needsUpdate()) { + const Graphics::Surface *frame = decoder.decodeNextFrame(); + + Common::copy((const byte *)frame->getPixels(), (const byte *)frame->getPixels() + 320 * 200, + (byte *)_graphicsManager._screenSurface.getPixels()); + if (_voy._eventFlags & EVTFLAG_RECORDING) + _graphicsManager.drawDot(); + } + + if (decoder.hasDirtyPalette()) { + const byte *palette = decoder.getPalette(); + _graphicsManager.setPalette(palette, 0, decoder.getPaletteCount()); + _graphicsManager.setOneColor(128, 220, 20, 20); + } + + _eventsManager.getMouseInfo(); + g_system->delayMillis(10); + } + + // RL2 finished + _graphicsManager.screenReset(); + _voy._eventFlags &= ~EVTFLAG_RECORDING; + + if (_voy._eventFlags & EVTFLAG_8) { + assert(pic); + byte *imgData = (*_graphicsManager._vPort)->_currentPic->_imgData; + (*_graphicsManager._vPort)->_currentPic->_imgData = pic->_imgData; + pic->_imgData = imgData; + _voy._eventFlags &= ~EVTFLAG_8; + } +} + +void VoyeurEngine::playAudio(int audioId) { + _bVoy->getBoltGroup(0x7F00); + _graphicsManager._backgroundPage = _bVoy->boltEntry(0x7F00 + + BLIND_TABLE[audioId] * 2)._picResource; + _graphicsManager._backColors = _bVoy->boltEntry(0x7F01 + + BLIND_TABLE[audioId] * 2)._cMapResource; + + (*_graphicsManager._vPort)->setupViewPort(); + _graphicsManager._backColors->startFade(); + flipPageAndWaitForFade(); + + _voy._eventFlags &= ~EVTFLAG_TIME_DISABLED; + _soundManager.setVOCOffset(_voy._vocSecondsOffset); + Common::String filename = _soundManager.getVOCFileName( + audioId + 159); + _soundManager.startVOCPlay(filename); + _voy._eventFlags |= EVTFLAG_RECORDING; + _eventsManager.startCursorBlink(); + + while (!shouldQuit() && !_eventsManager._mouseClicked && + _soundManager.getVOCStatus()) + _eventsManager.delayClick(1); + + _voy._eventFlags |= EVTFLAG_TIME_DISABLED; + _soundManager.stopVOCPlay(); + + _bVoy->freeBoltGroup(0x7F00); + (*_graphicsManager._vPort)->setupViewPort(NULL); + + _voy._eventFlags &= ~EVTFLAG_RECORDING; + _voy._playStampMode = 129; +} + +void VoyeurEngine::doTransitionCard(const Common::String &time, const Common::String &location) { + _graphicsManager.setColor(128, 16, 16, 16); + _graphicsManager.setColor(224, 220, 220, 220); + _eventsManager._intPtr._hasPalette = true; + + (*_graphicsManager._vPort)->setupViewPort(NULL); + (*_graphicsManager._vPort)->fillPic(128); + _graphicsManager.flipPage(); + _eventsManager.sWaitFlip(); + + flipPageAndWait(); + (*_graphicsManager._vPort)->fillPic(128); + + FontInfoResource &fi = *_graphicsManager._fontPtr; + fi._curFont = _bVoy->boltEntry(257)._fontResource; + fi._foreColor = 224; + fi._fontSaveBack = 0; + fi._pos = Common::Point(0, 116); + fi._justify = ALIGN_CENTRE; + fi._justifyWidth = 384; + fi._justifyHeight = 120; + + (*_graphicsManager._vPort)->drawText(time); + + if (!location.empty()) { + fi._pos = Common::Point(0, 138); + fi._justify = ALIGN_CENTRE; + fi._justifyWidth = 384; + fi._justifyHeight = 140; + + (*_graphicsManager._vPort)->drawText(location); + } + + flipPageAndWait(); +} + +void VoyeurEngine::saveLastInplay() { + // No implementation in ScummVM version +} + +void VoyeurEngine::flipPageAndWait() { + (*_graphicsManager._vPort)->_flags |= DISPFLAG_8; + _graphicsManager.flipPage(); + _eventsManager.sWaitFlip(); +} + +void VoyeurEngine::flipPageAndWaitForFade() { + flipPageAndWait(); + + while (!shouldQuit() && (_eventsManager._fadeStatus & 1)) + _eventsManager.delay(1); +} + +void VoyeurEngine::showEndingNews() { + _playStampGroupId = (_voy._incriminatedVictimNumber - 1) * 256 + 0x7700; + _voy._boltGroupId2 = (_controlPtr->_state->_victimIndex - 1) * 256 + 0x7B00; + + _bVoy->getBoltGroup(_playStampGroupId); + _bVoy->getBoltGroup(_voy._boltGroupId2); + + PictureResource *pic = _bVoy->boltEntry(_playStampGroupId)._picResource; + CMapResource *pal = _bVoy->boltEntry(_playStampGroupId + 1)._cMapResource; + + (*_graphicsManager._vPort)->setupViewPort(pic); + pal->startFade(); + flipPageAndWaitForFade(); + + _eventsManager.getMouseInfo(); + + for (int idx = 1; idx < 4; ++idx) { + if (idx == 3) { + pic = _bVoy->boltEntry(_voy._boltGroupId2)._picResource; + pal = _bVoy->boltEntry(_voy._boltGroupId2 + 1)._cMapResource; + } else { + pic = _bVoy->boltEntry(_playStampGroupId + idx * 2)._picResource; + pal = _bVoy->boltEntry(_playStampGroupId + idx * 2 + 1)._cMapResource; + } + + (*_graphicsManager._vPort)->setupViewPort(pic); + pal->startFade(); + flipPageAndWaitForFade(); + + _bVoy->freeBoltMember(_playStampGroupId + (idx - 1) * 2); + _bVoy->freeBoltMember(_playStampGroupId + (idx - 1) * 2 + 1); + + Common::String fname = Common::String::format("news%d.voc", idx); + _soundManager.startVOCPlay(fname); + + _eventsManager.getMouseInfo(); + while (!shouldQuit() && !_eventsManager._mouseClicked && + _soundManager.getVOCStatus()) { + _eventsManager.delay(1); + _eventsManager.getMouseInfo(); + } + + _soundManager.stopVOCPlay(); + if (idx == 3) + _eventsManager.delay(3); + + if (shouldQuit() || _eventsManager._mouseClicked) + break; + } + + _bVoy->freeBoltGroup(_playStampGroupId); + _bVoy->freeBoltGroup(_voy._boltGroupId2); + _playStampGroupId = -1; + _voy._boltGroupId2 = -1; +} + +/*------------------------------------------------------------------------*/ + +Common::String VoyeurEngine::generateSaveName(int slot) { + return Common::String::format("%s.%03d", _targetName.c_str(), slot); +} + +/** + * Returns true if it is currently okay to restore a game + */ +bool VoyeurEngine::canLoadGameStateCurrently() { + return _voyeurArea == AREA_APARTMENT; +} + +/** + * Returns true if it is currently okay to save the game + */ +bool VoyeurEngine::canSaveGameStateCurrently() { + return _voyeurArea == AREA_APARTMENT; +} + +/** + * Load the savegame at the specified slot index + */ +Common::Error VoyeurEngine::loadGameState(int slot) { + _loadGameSlot = slot; + return Common::kNoError; +} + +void VoyeurEngine::loadGame(int slot) { + // Open up the save file + Common::InSaveFile *saveFile = g_system->getSavefileManager()->openForLoading(generateSaveName(slot)); + if (!saveFile) + return; + + Common::Serializer serializer(saveFile, NULL); + + // Store the current time index before the game is loaded + _checkTransitionId = _voy._transitionId; + + // Stop any playing sound + _soundManager.stopVOCPlay(); + + // Read in the savegame header + VoyeurSavegameHeader header; + if (!header.read(saveFile)) + return; + if (header._thumbnail) + header._thumbnail->free(); + delete header._thumbnail; + + synchronize(serializer); + + delete saveFile; + + // Show a transition card if the time index has changed + checkTransition(); + + // Load the apartment + _mainThread->loadTheApt(); +} + +/** + * Save the game to the given slot index, and with the given name + */ +Common::Error VoyeurEngine::saveGameState(int slot, const Common::String &desc) { + // Open the save file for writing + Common::OutSaveFile *saveFile = g_system->getSavefileManager()->openForSaving(generateSaveName(slot)); + if (!saveFile) + return Common::kCreatingFileFailed; + + // Write out the header + VoyeurSavegameHeader header; + header.write(saveFile, this, desc); + + // Set up a serializer + Common::Serializer serializer(NULL, saveFile); + + // Synchronise the data + synchronize(serializer); + + saveFile->finalize(); + delete saveFile; + + return Common::kNoError; +} + +void VoyeurEngine::synchronize(Common::Serializer &s) { + s.syncAsSint16LE(_glGoState); + s.syncAsSint16LE(_glGoStack); + s.syncAsSint16LE(_stampFlags); + s.syncAsSint16LE(_playStampGroupId); + s.syncAsSint16LE(_currentVocId); + s.syncAsSint16LE(_audioVideoId); + + s.syncAsSint16LE(_iForceDeath); + s.syncAsSint16LE(_gameHour); + s.syncAsSint16LE(_gameMinute); + s.syncAsSint16LE(_flashTimeVal); + s.syncAsSint16LE(_flashTimeFlag); + s.syncAsSint16LE(_timeBarVal); + s.syncAsSint16LE(_checkPhoneVal); + + // Sub-systems + _voy.synchronize(s); + _graphicsManager.synchronize(s); + _mainThread->synchronize(s); + _controlPtr->_state->synchronize(s); +} + +/*------------------------------------------------------------------------*/ + +bool VoyeurSavegameHeader::read(Common::InSaveFile *f) { + char id[4]; + _thumbnail = NULL; + + uint32 signature = f->readUint32BE(); + if (signature != MKTAG('V', 'O', 'Y', 'R')) { + warning("Invalid savegame"); + return false; + } + + _version = f->readByte(); + if (_version > VOYEUR_SAVEGAME_VERSION) + return false; + + char c; + _saveName = ""; + while ((c = f->readByte()) != 0) + _saveName += c; + + // Get the thumbnail + _thumbnail = Graphics::loadThumbnail(*f); + if (!_thumbnail) + return false; + + // Read in the save datet/ime + _saveYear = f->readSint16LE(); + _saveMonth = f->readSint16LE(); + _saveDay = f->readSint16LE(); + _saveHour = f->readSint16LE(); + _saveMinutes = f->readSint16LE(); + _totalFrames = f->readUint32LE(); + + return true; +} + +void VoyeurSavegameHeader::write(Common::OutSaveFile *f, VoyeurEngine *vm, const Common::String &saveName) { + // Write ident string + f->writeUint32BE(MKTAG('V', 'O', 'Y', 'R')); + + // Write out savegame version + f->writeByte(VOYEUR_SAVEGAME_VERSION); + + // Write out savegame name + f->write(saveName.c_str(), saveName.size()); + f->writeByte(0); + + // Create a thumbnail and save it + Graphics::Surface *thumb = new Graphics::Surface(); + ::createThumbnail(thumb, (byte *)vm->_graphicsManager._screenSurface.getPixels(), + SCREEN_WIDTH, SCREEN_HEIGHT, vm->_graphicsManager._VGAColors); + Graphics::saveThumbnail(*f, *thumb); + thumb->free(); + delete thumb; + + // Write the save datet/ime + TimeDate td; + g_system->getTimeAndDate(td); + f->writeSint16LE(td.tm_year + 1900); + f->writeSint16LE(td.tm_mon + 1); + f->writeSint16LE(td.tm_mday); + f->writeSint16LE(td.tm_hour); + f->writeSint16LE(td.tm_min); + f->writeUint32LE(vm->_eventsManager.getGameCounter()); +} + +} // End of namespace Voyeur diff --git a/engines/voyeur/voyeur.h b/engines/voyeur/voyeur.h new file mode 100644 index 0000000000..4daa8f5372 --- /dev/null +++ b/engines/voyeur/voyeur.h @@ -0,0 +1,321 @@ +/* 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. + * + */ + +#ifndef VOYEUR_VOYEUR_H +#define VOYEUR_VOYEUR_H + +#include "voyeur/debugger.h" +#include "voyeur/data.h" +#include "voyeur/events.h" +#include "voyeur/files.h" +#include "voyeur/graphics.h" +#include "voyeur/sound.h" +#include "common/scummsys.h" +#include "common/system.h" +#include "common/error.h" +#include "common/random.h" +#include "common/savefile.h" +#include "common/serializer.h" +#include "common/util.h" +#include "engines/engine.h" +#include "graphics/surface.h" + +/** + * This is the namespace of the Voyeur engine. + * + * Status of this engine: In Development + * + * Games using this engine: + * - Voyeur + */ +namespace Voyeur { + +#define DEBUG_BASIC 1 +#define DEBUG_INTERMEDIATE 2 +#define DEBUG_DETAILED 3 + +// Constants used for doInterface display of the mansion +#define MANSION_MAX_X 784 +#define MANSION_MAX_Y 150 +#define MANSION_VIEW_X 40 +#define MANSION_VIEW_Y 27 +#define MANSION_VIEW_WIDTH 240 +#define MANSION_VIEW_HEIGHT 148 +#define MANSION_SCROLL_AREA_X 20 +#define MANSION_SCROLL_AREA_Y 20 +#define MANSION_SCROLL_INC_X 4 +#define MANSION_SCROLL_INC_Y 4 + +enum VoyeurDebugChannels { + kDebugScripts = 1 << 0 +}; + +enum VoyeurArea { AREA_NONE, AREA_APARTMENT, AREA_INTERFACE, AREA_ROOM, AREA_EVIDENCE }; + +struct VoyeurGameDescription; + +class VoyeurEngine : public Engine { +private: + const VoyeurGameDescription *_gameDescription; + Common::RandomSource _randomSource; + FontInfoResource _defaultFontInfo; + + void ESP_Init(); + void initializeManagers(); + void globalInitBolt(); + void initBolt(); + void vInitInterrupts(); + void initInput(); + + bool doHeadTitle(); + void showConversionScreen(); + bool doLock(); + void showTitleScreen(); + void doOpening(); + + void playStamp(); + void initStamp(); + void closeStamp(); + + /** + * Shows the game ending title animation + */ + void doTailTitle(); + + /** + * Shows the game ending credits + */ + void doClosingCredits(); + + /** + * Shows the final anti-piracy message before exiting the game + */ + void doPiracy(); + + /** + * Review previous tape recordings on the TV + */ + void reviewTape(); + + /** + * Shows the TV gossip animation + */ + void doGossip(); + + /** + * Shows the animation of the VCR tape during the 'Call the Police' sequence + */ + void doTapePlaying(); + + /** + * Does a check as to whether a murder has been witnessed + */ + bool checkForMurder(); + + /** + * Does a check for whether incriminating evidence has been revealed + */ + bool checkForIncriminate(); + + /** + * Plays a video event previously witnessed + */ + void playAVideoEvent(int eventIndex); + + /** + * Shows the user a screen to select one of four characters to send the + * video tape to + */ + int getChooseButton(); + + /** + * Synchronizes the game data + */ + void synchronize(Common::Serializer &s); +protected: + // Engine APIs + virtual Common::Error run(); + virtual bool hasFeature(EngineFeature f) const; +public: + BoltFile *_bVoy; + Debugger _debugger; + EventsManager _eventsManager; + FilesManager _filesManager; + GraphicsManager _graphicsManager; + SoundManager _soundManager; + SVoy _voy; + + BoltFile *_stampLibPtr; + BoltGroup *_controlGroupPtr; + ControlResource *_controlPtr; + byte *_stampData; + BoltGroup *_stackGroupPtr; + int _glGoState; + int _glGoStack; + int _stampFlags; + int _playStampGroupId; + int _currentVocId; + + int _audioVideoId; + const int *_resolvePtr; + int _iForceDeath; + int _checkTransitionId; + int _gameHour; + int _gameMinute; + int _flashTimeVal; + bool _flashTimeFlag; + int _timeBarVal; + int _checkPhoneVal; + Common::Point _mansionViewPos; + ThreadResource *_mainThread; + VoyeurArea _voyeurArea; + int _loadGameSlot; +public: + VoyeurEngine(OSystem *syst, const VoyeurGameDescription *gameDesc); + virtual ~VoyeurEngine(); + void GUIError(const Common::String &msg); + + uint32 getFeatures() const; + Common::Language getLanguage() const; + Common::Platform getPlatform() const; + uint16 getVersion() const; + bool getIsDemo() const; + + int getRandomNumber(int maxNumber); + Common::String generateSaveName(int slotNumber); + virtual bool canLoadGameStateCurrently(); + virtual bool canSaveGameStateCurrently(); + virtual Common::Error loadGameState(int slot); + virtual Common::Error saveGameState(int slot, const Common::String &desc); + void loadGame(int slot); + + void playRL2Video(const Common::String &filename); + void doTransitionCard(const Common::String &time, const Common::String &location); + + /** + * Play a given video + */ + void playAVideo(int videoId); + + /** + * Play a given video for a given amount of time. This is particularly used + * for later tape playback, where it will only play back as much of the video + * as the user originally watched (since they can break out of watching a video). + */ + void playAVideoDuration(int videoId, int duration); + + /** + * Play an audio sequence + */ + void playAudio(int audioId); + + /** + * Saves the last time the game was played + */ + void saveLastInplay(); + void makeViewFinder(); + void makeViewFinderP(); + void initIFace(); + void checkTransition(); + int doComputerText(int maxLen); + void getComputerBrush(); + + /** + * Displays the time/charge remaining on the video camera screen + */ + void doTimeBar(bool force); + + /** + * If necessary, flashes the time remaining bar on the video camera screen + */ + void flashTimeBar(); + + /** + * Handle scrolling of the mansion view in the camera sights + */ + void doScroll(const Common::Point &pt); + + /** + * Check for phone call + */ + void checkPhoneCall(); + + /** + * Display evidence sequence from within a room + * Suspension of disbelief needed to believe that recording from a distance, + * you could still flip through the often pages of evidence for a single hotspot. + */ + void doEvidDisplay(int evidId, int eventId); + + /** + * Flips the active page and waits until it's drawn + */ + void flipPageAndWait(); + + /** + * Flips the active page and waits until it's drawn and faded in + */ + void flipPageAndWaitForFade(); + + /** + * Returns the string for the current in-game day of the week + */ + Common::String getDayName(); + + /** + * Returns the string for the current in-game time of day + */ + Common::String getTimeOfDay(); + + /** + * Show the ending sequence of the arrest + */ + void showEndingNews(); +}; + +#define VOYEUR_SAVEGAME_VERSION 1 + +/** + * Header for Voyeur savegame files + */ +struct VoyeurSavegameHeader { + uint8 _version; + Common::String _saveName; + Graphics::Surface *_thumbnail; + int _saveYear, _saveMonth, _saveDay; + int _saveHour, _saveMinutes; + int _totalFrames; + + /** + * Read in the header from the specified file + */ + bool read(Common::InSaveFile *f); + + /** + * Write out header information to the specified file + */ + void write(Common::OutSaveFile *f, VoyeurEngine *vm, const Common::String &saveName); +}; + +} // End of namespace Voyeur + +#endif /* VOYEUR_VOYEUR_H */ diff --git a/engines/voyeur/voyeur_game.cpp b/engines/voyeur/voyeur_game.cpp new file mode 100644 index 0000000000..b664074b56 --- /dev/null +++ b/engines/voyeur/voyeur_game.cpp @@ -0,0 +1,1423 @@ +/* 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 "voyeur/voyeur.h" +#include "voyeur/staticres.h" +#include "voyeur/animation.h" + +namespace Voyeur { + +void VoyeurEngine::playStamp() { + _stampLibPtr = NULL; + _filesManager.openBoltLib("stampblt.blt", _stampLibPtr); + + _stampLibPtr->getBoltGroup(0); + _controlPtr->_state = _stampLibPtr->boltEntry(_controlPtr->_stateId >> 16)._stateResource; + assert(_controlPtr->_state); + + _resolvePtr = &RESOLVE_TABLE[0]; + initStamp(); + + PtrResource *threadsList = _stampLibPtr->boltEntry(3)._ptrResource; + _mainThread = threadsList->_entries[0]->_threadResource; + _mainThread->initThreadStruct(0, 0); + + _voy._isAM = false; + _gameHour = 9; + _gameMinute = 0; + _voy._abortInterface = true; + + int buttonId; + bool breakFlag = false; + while (!breakFlag && !shouldQuit()) { + _voyeurArea = AREA_NONE; + _eventsManager.getMouseInfo(); + _playStampGroupId = _currentVocId = -1; + _audioVideoId = -1; + + _mainThread->parsePlayCommands(); + + bool flag = breakFlag = (_voy._eventFlags & EVTFLAG_2) != 0; + + switch (_voy._playStampMode) { + case 5: + buttonId = _mainThread->doInterface(); + + if (buttonId == -2) { + switch (_mainThread->doApt()) { + case 0: + _voy._aptLoadMode = 140; + break; + case 1: + _voy._eventFlags &= ~EVTFLAG_TIME_DISABLED; + _voy._abortInterface = true; + _mainThread->chooseSTAMPButton(22); + _voy._aptLoadMode = 143; + break; + case 2: + _voy._eventFlags &= ~EVTFLAG_TIME_DISABLED; + reviewTape(); + _voy._abortInterface = true; + _voy._aptLoadMode = 142; + break; + case 3: + _voy._eventFlags &= ~EVTFLAG_TIME_DISABLED; + _mainThread->chooseSTAMPButton(21); + break; + case 4: + breakFlag = true; + break; + case 5: + doGossip(); + _voy._abortInterface = true; + _voy._aptLoadMode = 141; + _voy._eventFlags &= ~EVTFLAG_100; + break; + default: + break; + } + } else { + _mainThread->chooseSTAMPButton(buttonId); + } + + flag = true; + break; + + case 6: + _mainThread->doRoom(); + flag = true; + break; + + case 16: + _voy._transitionId = 17; + buttonId = _mainThread->doApt(); + + switch (buttonId) { + case 1: + _mainThread->chooseSTAMPButton(22); + flag = true; + break; + case 2: + reviewTape(); + _voy._abortInterface = true; + break; + case 4: + flag = true; + breakFlag = true; + break; + default: + break; + } + break; + + case 17: + // Called the police, showing the tape + doTapePlaying(); + if (!checkForMurder() && _voy._transitionId <= 15) + checkForIncriminate(); + + if (_voy._videoEventId != -1) { + // Show the found video that is of interest to the police + playAVideoEvent(_voy._videoEventId); + _voy._eventFlags &= ~EVTFLAG_RECORDING; + } + + // Handle response + _mainThread->chooseSTAMPButton(0); + flag = true; + break; + + case 130: { + // user selected to send the tape + if (_bVoy->getBoltGroup(_playStampGroupId)) { + _graphicsManager._backgroundPage = _bVoy->boltEntry(_playStampGroupId)._picResource; + _graphicsManager._backColors = _bVoy->boltEntry(_playStampGroupId + 1)._cMapResource; + + buttonId = getChooseButton(); + if (_eventsManager._rightClick) + // Aborted out of selecting a recipient + buttonId = 4; + + _bVoy->freeBoltGroup(_playStampGroupId); + _graphicsManager.screenReset(); + _playStampGroupId = -1; + flag = true; + + if (buttonId != 4) { + _voy._playStampMode = 131; + _voy.checkForKey(); + _mainThread->chooseSTAMPButton(buttonId); + } else { + _mainThread->chooseSTAMPButton(buttonId); + _voy._abortInterface = true; + } + } + break; + } + + default: + break; + } + + do { + if (flag) { + if (_currentVocId != -1) { + _soundManager.stopVOCPlay(); + _currentVocId = -1; + } + + _audioVideoId = -1; + + if (_voy._boltGroupId2 != -1) { + _bVoy->freeBoltGroup(_voy._boltGroupId2); + _voy._boltGroupId2 = -1; + } + + if (_playStampGroupId != -1) { + _bVoy->freeBoltGroup(_playStampGroupId); + _playStampGroupId = -1; + } + + // Break out of loop + flag = false; + + } else if (_mainThread->_stateFlags & 2) { + _eventsManager.getMouseInfo(); + _mainThread->chooseSTAMPButton(0); + flag = true; + } else { + _mainThread->chooseSTAMPButton(0); + flag = true; + } + } while (flag); + } + + _voy._viewBounds = nullptr; + closeStamp(); + _stampLibPtr->freeBoltGroup(0); + delete _stampLibPtr; +} + +void VoyeurEngine::initStamp() { + _stampFlags &= ~1; + _stackGroupPtr = _controlGroupPtr; + + if (!_controlPtr->_entries[0]) + error("No control entries"); + + ThreadResource::initUseCount(); +} + +void VoyeurEngine::closeStamp() { + ThreadResource::unloadAllStacks(this); +} + +void VoyeurEngine::doTailTitle() { + (*_graphicsManager._vPort)->setupViewPort(NULL); + _graphicsManager.screenReset(); + + if (_bVoy->getBoltGroup(0x600)) { + RL2Decoder decoder; + decoder.loadFile("a1100200.rl2"); + decoder.start(); + decoder.play(this); + + if (!shouldQuit() && !_eventsManager._mouseClicked) { + doClosingCredits(); + + if (!shouldQuit() && !_eventsManager._mouseClicked) { + _graphicsManager.screenReset(); + + PictureResource *pic = _bVoy->boltEntry(0x602)._picResource; + CMapResource *pal = _bVoy->boltEntry(0x603)._cMapResource; + + (*_graphicsManager._vPort)->setupViewPort(pic); + pal->startFade(); + flipPageAndWaitForFade(); + _eventsManager.delayClick(300); + + pic = _bVoy->boltEntry(0x604)._picResource; + pal = _bVoy->boltEntry(0x605)._cMapResource; + + (*_graphicsManager._vPort)->setupViewPort(pic); + pal->startFade(); + flipPageAndWaitForFade(); + _eventsManager.delayClick(120); + + _soundManager.stopVOCPlay(); + } + } + + _bVoy->freeBoltGroup(0x600); + } + + if (!shouldQuit()) { + _bVoy->getBoltGroup(0x100); + doPiracy(); + } +} + +void VoyeurEngine::doClosingCredits() { + if (!_bVoy->getBoltGroup(0x400)) + return; + + const char *msg = (const char *)_bVoy->memberAddr(0x404); + const byte *creditList = (const byte *)_bVoy->memberAddr(0x405); + + (*_graphicsManager._vPort)->setupViewPort(NULL); + _graphicsManager.setColor(1, 180, 180, 180); + _graphicsManager.setColor(2, 200, 200, 200); + _eventsManager._intPtr._hasPalette = true; + + _graphicsManager._fontPtr->_curFont = _bVoy->boltEntry(0x402)._fontResource; + _graphicsManager._fontPtr->_foreColor = 2; + _graphicsManager._fontPtr->_backColor = 2; + _graphicsManager._fontPtr->_fontSaveBack = false; + _graphicsManager._fontPtr->_fontFlags = 0; + + _soundManager.startVOCPlay(152); + FontInfoResource &fi = *_graphicsManager._fontPtr; + + for (int idx = 0; idx < 78; ++idx) { + const byte *entry = creditList + idx * 6; + int flags = READ_LE_UINT16(entry + 4); + + if (flags & 0x10) + (*_graphicsManager._vPort)->fillPic(); + + if (flags & 1) { + fi._foreColor = 1; + fi._curFont = _bVoy->boltEntry(0x402)._fontResource; + fi._justify = ALIGN_CENTRE; + fi._justifyWidth = 384; + fi._justifyHeight = 240; + fi._pos = Common::Point(0, READ_LE_UINT16(entry)); + + (*_graphicsManager._vPort)->drawText(msg); + msg += strlen(msg) + 1; + } + + if (flags & 0x40) { + fi._foreColor = 2; + fi._curFont = _bVoy->boltEntry(0x400)._fontResource; + fi._justify = ALIGN_CENTRE; + fi._justifyWidth = 384; + fi._justifyHeight = 240; + fi._pos = Common::Point(0, READ_LE_UINT16(entry)); + + (*_graphicsManager._vPort)->drawText(msg); + msg += strlen(msg) + 1; + } + + if (flags & 2) { + fi._foreColor = 1; + fi._curFont = _bVoy->boltEntry(0x400)._fontResource; + fi._justify = ALIGN_LEFT; + fi._justifyWidth = 384; + fi._justifyHeight = 240; + fi._pos = Common::Point(38, READ_LE_UINT16(entry)); + + (*_graphicsManager._vPort)->drawText(msg); + msg += strlen(msg) + 1; + + fi._foreColor = 2; + fi._justify = ALIGN_LEFT; + fi._justifyWidth = 384; + fi._justifyHeight = 240; + fi._pos = Common::Point(198, READ_LE_UINT16(entry)); + + (*_graphicsManager._vPort)->drawText(msg); + msg += strlen(msg) + 1; + } + + if (flags & 4) { + fi._foreColor = 1; + fi._curFont = _bVoy->boltEntry(0x402)._fontResource; + fi._justify = ALIGN_CENTRE; + fi._justifyWidth = 384; + fi._justifyHeight = 240; + fi._pos = Common::Point(0, READ_LE_UINT16(entry)); + + (*_graphicsManager._vPort)->drawText(msg); + msg += strlen(msg) + 1; + + fi._foreColor = 2; + fi._curFont = _bVoy->boltEntry(0x400)._fontResource; + fi._justify = ALIGN_CENTRE; + fi._justifyWidth = 384; + fi._justifyHeight = 240; + fi._pos = Common::Point(0, READ_LE_UINT16(entry) + 13); + + (*_graphicsManager._vPort)->drawText(msg); + msg += strlen(msg) + 1; + } + + if (flags & 0x20) { + flipPageAndWait(); + _eventsManager.delayClick(READ_LE_UINT16(entry + 2) * 60); + } + + if (shouldQuit() || _eventsManager._mouseClicked) + break; + } + + _soundManager.stopVOCPlay(); + _graphicsManager._fontPtr->_curFont = _bVoy->boltEntry(0x101)._fontResource; + _bVoy->freeBoltGroup(0x400); +} + +void VoyeurEngine::doPiracy() { + _graphicsManager.screenReset(); + _graphicsManager.setColor(1, 0, 0, 0); + _graphicsManager.setColor(2, 255, 255, 255); + _eventsManager._intPtr._hasPalette = true; + (*_graphicsManager._vPort)->setupViewPort(NULL); + (*_graphicsManager._vPort)->fillPic(1); + + FontInfoResource &fi = *_graphicsManager._fontPtr; + fi._curFont = _bVoy->boltEntry(0x101)._fontResource; + fi._foreColor = 2; + fi._backColor = 2; + fi._fontSaveBack = false; + fi._fontFlags = 0; + fi._justify = ALIGN_CENTRE; + fi._justifyWidth = 384; + fi._justifyHeight = 230; + + // Loop through the piracy message array to draw each line + int yp, idx; + for (idx = 0, yp = 33; idx < 10; ++idx) { + fi._pos = Common::Point(0, yp); + (*_graphicsManager._vPort)->drawText(PIRACY_MESSAGE[idx]); + + yp += fi._curFont->_fontHeight + 4; + } + + flipPageAndWait(); + _eventsManager.getMouseInfo(); + _eventsManager.delayClick(720); +} + +void VoyeurEngine::reviewTape() { + int eventStart = 0; + int newX = -1; + int newY = -1; + int eventLine = 7; + Common::Rect tempRect(58, 30, 58 + 223, 30 + 124); + Common::Point pt; + int foundIndex; + int eventNum; + + _bVoy->getBoltGroup(0x900); + PictureResource *cursor = _bVoy->boltEntry(0x903)._picResource; + + if ((_voy._eventCount - 8) != 0) + eventStart = MAX(_voy._eventCount - 8, 0); + + if ((eventStart + _voy._eventCount) <= 7) + eventLine = eventStart + _voy._eventCount - 1; + + bool breakFlag = false; + while (!shouldQuit() && !breakFlag) { + _voy._viewBounds = _bVoy->boltEntry(0x907)._rectResource; + Common::Array<RectEntry> &hotspots = _bVoy->boltEntry(0x906)._rectResource->_entries; + + _graphicsManager._backColors = _bVoy->boltEntry(0x902)._cMapResource; + _graphicsManager._backgroundPage = _bVoy->boltEntry(0x901)._picResource; + (*_graphicsManager._vPort)->setupViewPort(_graphicsManager._backgroundPage); + _graphicsManager._backColors->startFade(); + + flipPageAndWaitForFade(); + + _graphicsManager.setColor(1, 32, 32, 32); + _graphicsManager.setColor(2, 96, 96, 96); + _graphicsManager.setColor(3, 160, 160, 160); + _graphicsManager.setColor(4, 224, 224, 224); + _graphicsManager.setColor(9, 24, 64, 24); + _graphicsManager.setColor(10, 64, 132, 64); + _graphicsManager.setColor(11, 100, 192, 100); + _graphicsManager.setColor(12, 120, 248, 120); + _eventsManager.setCursorColor(128, 1); + + _eventsManager._intPtr._hasPalette = true; + _graphicsManager._fontPtr->_curFont = _bVoy->boltEntry(0x909)._fontResource; + _graphicsManager._fontPtr->_fontSaveBack = false; + _graphicsManager._fontPtr->_fontFlags = 0; + + _eventsManager.getMouseInfo(); + if (newX == -1) { + _eventsManager.setMousePos(Common::Point(hotspots[1].left + 12, hotspots[1].top + 6)); + } else { + _eventsManager.setMousePos(Common::Point(newX, newY)); + } + + _currentVocId = 151; + _voy._vocSecondsOffset = 0; + bool var1E = true; + do { + if (_currentVocId != -1 && !_soundManager.getVOCStatus()) { + _voy._musicStartTime = _voy._RTVNum; + _soundManager.startVOCPlay(_currentVocId); + } + + if (var1E) { + var1E = false; + flipPageAndWait(); + + _graphicsManager._drawPtr->_penColor = 0; + _graphicsManager._drawPtr->_pos = Common::Point(tempRect.left, tempRect.top); + _graphicsManager._backgroundPage->sFillBox(tempRect.width(), tempRect.height()); + + int yp = 45; + int eventNum = eventStart; + for (int lineNum = 0; lineNum < 8 && eventNum < _voy._eventCount; ++lineNum, ++eventNum) { + _graphicsManager._fontPtr->_picFlags = 0; + _graphicsManager._fontPtr->_picSelect = 0xff; + _graphicsManager._fontPtr->_picPick = 7; + _graphicsManager._fontPtr->_picOnOff = (lineNum == eventLine) ? 8 : 0; + _graphicsManager._fontPtr->_pos = Common::Point(68, yp); + _graphicsManager._fontPtr->_justify = ALIGN_LEFT; + _graphicsManager._fontPtr->_justifyWidth = 0; + _graphicsManager._fontPtr->_justifyHeight = 0; + + Common::String msg = _eventsManager.getEvidString(eventNum); + _graphicsManager._backgroundPage->drawText(msg); + + yp += 15; + } + + (*_graphicsManager._vPort)->addSaveRect( + (*_graphicsManager._vPort)->_lastPage, tempRect); + flipPageAndWait(); + + (*_graphicsManager._vPort)->addSaveRect( + (*_graphicsManager._vPort)->_lastPage, tempRect); + } + + _graphicsManager.sDrawPic(cursor, *_graphicsManager._vPort, + _eventsManager.getMousePos()); + flipPageAndWait(); + + _eventsManager.getMouseInfo(); + foundIndex = -1; + + Common::Point tempPos = _eventsManager.getMousePos() + Common::Point(14, 7); + for (uint idx = 0; idx < hotspots.size(); ++idx) { + if (hotspots[idx].contains(tempPos)) { + // Found hotspot area + foundIndex = idx; + break; + } + } + + pt = _eventsManager.getMousePos(); + if (tempPos.x >= 68 && tempPos.x <= 277 && tempPos.y >= 31 && tempPos.y <= 154) { + tempPos.y -= 2; + foundIndex = (tempPos.y - 31) / 15; + if ((tempPos.y - 31) % 15 >= 12 || (eventStart + foundIndex) >= _voy._eventCount) { + _eventsManager.setCursorColor(128, 0); + foundIndex = 999; + } else if (!_eventsManager._leftClick) { + _eventsManager.setCursorColor(128, 2); + foundIndex = -1; + } else { + _eventsManager.setCursorColor(128, 2); + eventLine = foundIndex; + + flipPageAndWait(); + + _graphicsManager._drawPtr->_penColor = 0; + _graphicsManager._drawPtr->_pos = Common::Point(tempRect.left, tempRect.top); + _graphicsManager._backgroundPage->sFillBox(tempRect.width(), tempRect.height()); + + int yp = 45; + eventNum = eventStart; + for (int idx = 0; idx < 8 && eventNum < _voy._eventCount; ++idx, ++eventNum) { + _graphicsManager._fontPtr->_picFlags = 0; + _graphicsManager._fontPtr->_picSelect = 0xff; + _graphicsManager._fontPtr->_picPick = 7; + _graphicsManager._fontPtr->_picOnOff = (idx == eventLine) ? 8 : 0; + _graphicsManager._fontPtr->_pos = Common::Point(68, yp); + _graphicsManager._fontPtr->_justify = ALIGN_LEFT; + _graphicsManager._fontPtr->_justifyWidth = 0; + _graphicsManager._fontPtr->_justifyHeight = 0; + + Common::String msg = _eventsManager.getEvidString(eventNum); + _graphicsManager._backgroundPage->drawText(msg); + + yp += 15; + } + + (*_graphicsManager._vPort)->addSaveRect( + (*_graphicsManager._vPort)->_lastPage, tempRect); + flipPageAndWait(); + + (*_graphicsManager._vPort)->addSaveRect( + (*_graphicsManager._vPort)->_lastPage, tempRect); + flipPageAndWait(); + + _eventsManager.getMouseInfo(); + foundIndex = -1; + } + } else if ((_voy._eventFlags & EVTFLAG_40) && _voy._viewBounds->left == pt.x && + _voy._viewBounds->bottom == pt.y) { + foundIndex = 999; + } else if ((_voy._eventFlags & EVTFLAG_40) && _voy._viewBounds->left == pt.x && + _voy._viewBounds->top == pt.y) { + foundIndex = 998; + } else { + _eventsManager.setCursorColor(128, (foundIndex == -1) ? 0 : 1); + } + + _eventsManager._intPtr._hasPalette = true; + + if (_eventsManager._mouseClicked || _eventsManager._mouseUnk) { + switch (foundIndex) { + case 2: + if (eventStart > 0) { + --eventStart; + var1E = true; + } + foundIndex = -1; + break; + + case 3: + if (eventStart > 0) { + eventStart -= 8; + if (eventStart < 0) + eventStart = 0; + var1E = true; + } + foundIndex = -1; + break; + + case 4: + if ((_voy._eventCount - 8) > eventStart) { + ++eventStart; + var1E = true; + } + foundIndex = -1; + break; + + case 5: + if (_voy._eventCount >= 8 && (_voy._eventCount - 8) != eventStart) { + eventStart += 8; + if ((_voy._eventCount - 8) < eventStart) + eventStart = _voy._eventCount - 8; + var1E = true; + } + foundIndex = -1; + break; + + default: + break; + } + + while (eventLine > 0 && (eventLine + eventStart) >= _voy._eventCount) + --eventLine; + } + + pt = _eventsManager.getMousePos(); + if (_eventsManager._mouseClicked && _voy._viewBounds->left == pt.x && + (_voy._eventFlags & EVTFLAG_40) && _eventsManager._rightClick) { + _controlPtr->_state->_victimIndex = (pt.y / 60) + 1; + foundIndex = -1; + _eventsManager._rightClick = 0; + } + + if (_eventsManager._rightClick) + foundIndex = 0; + + } while (!shouldQuit() && (!_eventsManager._mouseClicked || foundIndex == -1)); + + newY = _eventsManager.getMousePos().y; + _voy._fadingType = 0; + _voy._viewBounds = nullptr; + (*_graphicsManager._vPort)->setupViewPort(NULL); + + if (_currentVocId != -1) { + _voy._vocSecondsOffset = _voy._RTVNum - _voy._musicStartTime; + _soundManager.stopVOCPlay(); + } + + // Break out if the exit button was pressed + if (!foundIndex) + break; + + int eventIndex = eventStart + eventLine; + VoyeurEvent &e = _voy._events[eventIndex]; + switch (e._type) { + case EVTYPE_VIDEO: + playAVideoEvent(eventIndex); + break; + + case EVTYPE_AUDIO: { + _audioVideoId = e._audioVideoId; + _voy._vocSecondsOffset = e._computerOn; + + _bVoy->getBoltGroup(0x7F00); + _graphicsManager._backgroundPage = _bVoy->boltEntry(0x7F00 + + BLIND_TABLE[_audioVideoId])._picResource; + _graphicsManager._backColors = _bVoy->boltEntry(0x7F01 + + BLIND_TABLE[_audioVideoId])._cMapResource; + + (*_graphicsManager._vPort)->setupViewPort(_graphicsManager._backgroundPage); + _graphicsManager._backColors->startFade(); + flipPageAndWaitForFade(); + + _eventsManager._intPtr._flashStep = 1; + _eventsManager._intPtr._flashTimer = 0; + _voy._eventFlags &= ~EVTFLAG_TIME_DISABLED; + + // Play suond for the given duration + _soundManager.setVOCOffset(_voy._vocSecondsOffset); + _soundManager.startVOCPlay(_audioVideoId + 159); + uint32 secondsDuration = e._computerOff; + + _eventsManager.getMouseInfo(); + while (!_eventsManager._mouseClicked && _soundManager.getVOCStatus() && + _soundManager.getVOCFrame() < secondsDuration) { + _eventsManager.getMouseInfo(); + _eventsManager.delay(10); + } + + _voy._eventFlags |= EVTFLAG_TIME_DISABLED; + _soundManager.stopVOCPlay(); + _bVoy->freeBoltGroup(0x7F00); + break; + } + + case EVTYPE_EVID: + _voy.reviewAnEvidEvent(eventIndex); + + _voy._vocSecondsOffset = _voy._RTVNum - _voy._musicStartTime; + _soundManager.stopVOCPlay(); + _bVoy->getBoltGroup(0x900); + break; + + case EVTYPE_COMPUTER: + _voy.reviewComputerEvent(eventIndex); + + _voy._vocSecondsOffset = _voy._RTVNum - _voy._musicStartTime; + _soundManager.stopVOCPlay(); + _bVoy->getBoltGroup(0x900); + break; + + default: + break; + } + } + + _graphicsManager._fontPtr->_curFont = _bVoy->boltEntry(0x101)._fontResource; + + (*_graphicsManager._vPort)->fillPic(0); + flipPageAndWait(); + _bVoy->freeBoltGroup(0x900); +} + +void VoyeurEngine::doGossip() { + _graphicsManager.resetPalette(); + _graphicsManager.screenReset(); + + if (!_bVoy->getBoltGroup(0x300)) + return; + + // Load the gossip animation + RL2Decoder decoder; + decoder.loadFile("a2050100.rl2", false); + decoder.start(); + + // Get the resource data for the first gossip video + PictureResource *bgPic = _bVoy->boltEntry(0x300)._picResource; + CMapResource *pal = _bVoy->boltEntry(0x301)._cMapResource; + pal->startFade(); + + // Transfer initial background to video decoder + PictureResource videoFrame(decoder.getVideoTrack()->getBackSurface()); + bgPic->_bounds.moveTo(0, 0); + _graphicsManager.sDrawPic(bgPic, &videoFrame, Common::Point(0, 0)); + + byte *frameNumsP = _bVoy->memberAddr(0x309); + byte *posP = _bVoy->boltEntry(0x30A)._data; + + // Play the initial gossip video + decoder.play(this, 0x302, frameNumsP, posP); + decoder.close(); + + // Reset the palette and clear the screen + _graphicsManager.resetPalette(); + _graphicsManager.screenReset(); + + // Play interview video + RL2Decoder decoder2; + decoder2.loadFile("a2110100.rl2", true); + decoder2.start(); + + _eventsManager.getMouseInfo(); + decoder2.play(this); + decoder2.close(); + + _bVoy->freeBoltGroup(0x300); + _graphicsManager.screenReset(); +} + +void VoyeurEngine::doTapePlaying() { + if (!_bVoy->getBoltGroup(0xA00)) + return; + + _eventsManager.getMouseInfo(); + _graphicsManager._backColors = _bVoy->boltEntry(0xA01)._cMapResource; + _graphicsManager._backgroundPage = _bVoy->boltEntry(0xA00)._picResource; + PictureResource *pic = _bVoy->boltEntry(0xA02)._picResource; + VInitCycleResource *cycle = _bVoy->boltEntry(0xA05)._vInitCycleResource; + + (*_graphicsManager._vPort)->setupViewPort(_graphicsManager._backgroundPage); + _graphicsManager.sDrawPic(pic, *_graphicsManager._vPort, Common::Point(57, 30)); + _graphicsManager._backColors->startFade(); + flipPageAndWaitForFade(); + + cycle->vStartCycle(); + + _soundManager.startVOCPlay("vcr.voc"); + while (!shouldQuit() && !_eventsManager._mouseClicked && _soundManager.getVOCStatus()) { + _eventsManager.delayClick(2); + } + + _soundManager.stopVOCPlay(); + cycle->vStopCycle(); + _bVoy->freeBoltGroup(0xA00); +} + +bool VoyeurEngine::checkForMurder() { + int v = _controlPtr->_state->_victimMurderIndex; + + for (int idx = 0; idx < _voy._eventCount; ++idx) { + VoyeurEvent &evt = _voy._events[idx]; + + if (evt._type == EVTYPE_VIDEO) { + switch (_controlPtr->_state->_victimIndex) { + case 1: + if (evt._audioVideoId == 41 && evt._computerOn <= 15 && + (evt._computerOff + evt._computerOn) >= 16) { + _controlPtr->_state->_victimMurderIndex = 1; + } + break; + + case 2: + if (evt._audioVideoId == 53 && evt._computerOn <= 19 && + (evt._computerOff + evt._computerOn) >= 21) { + _controlPtr->_state->_victimMurderIndex = 2; + } + break; + + case 3: + if (evt._audioVideoId == 50 && evt._computerOn <= 28 && + (evt._computerOff + evt._computerOn) >= 29) { + _controlPtr->_state->_victimMurderIndex = 3; + } + break; + + case 4: + if (evt._audioVideoId == 43 && evt._computerOn <= 10 && + (evt._computerOff + evt._computerOn) >= 14) { + _controlPtr->_state->_victimMurderIndex = 4; + } + break; + + default: + break; + } + } + + if (_controlPtr->_state->_victimMurderIndex == _controlPtr->_state->_victimIndex) { + _voy._videoEventId = idx; + return true; + } + } + + _controlPtr->_state->_victimMurderIndex = v; + _voy._videoEventId = -1; + return false; +} + +bool VoyeurEngine::checkForIncriminate() { + _voy._incriminatedVictimNumber = 0; + + for (int idx = 0; idx < _voy._eventCount; ++idx) { + VoyeurEvent &evt = _voy._events[idx]; + + if (evt._type == EVTYPE_VIDEO) { + if (evt._audioVideoId == 44 && evt._computerOn <= 40 && + (evt._computerOff + evt._computerOn) >= 70) { + _voy._incriminatedVictimNumber = 1; + } + + if (evt._audioVideoId == 44 && evt._computerOn <= 79 && + (evt._computerOff + evt._computerOn) >= 129) { + _voy._incriminatedVictimNumber = 1; + } + + if (evt._audioVideoId == 20 && evt._computerOn <= 28 && + (evt._computerOff + evt._computerOn) >= 45) { + _voy._incriminatedVictimNumber = 2; + } + + if (evt._audioVideoId == 35 && evt._computerOn <= 17 && + (evt._computerOff + evt._computerOn) >= 36) { + _voy._incriminatedVictimNumber = 3; + } + + if (evt._audioVideoId == 30 && evt._computerOn <= 80 && + (evt._computerOff + evt._computerOn) >= 139) { + _voy._incriminatedVictimNumber = 4; + } + } + + if (_voy._incriminatedVictimNumber) { + _controlPtr->_state->_victimMurderIndex = 88; + _voy._videoEventId = idx; + return true; + } + } + + _voy._videoEventId = -1; + return false; +} + +void VoyeurEngine::playAVideoEvent(int eventIndex) { + VoyeurEvent &evt = _voy._events[eventIndex]; + _audioVideoId = evt._audioVideoId; + _voy._vocSecondsOffset = evt._computerOn; + _eventsManager._videoDead = evt._dead; + _voy._eventFlags &= ~EVTFLAG_TIME_DISABLED; + + playAVideoDuration(_audioVideoId, evt._computerOff); + + _voy._eventFlags |= EVTFLAG_TIME_DISABLED; + if (_eventsManager._videoDead != -1) { + _bVoy->freeBoltGroup(0xE00); + _eventsManager._videoDead = -1; + flipPageAndWait(); + _eventsManager._videoDead = -1; + } + + _audioVideoId = -1; + if (_eventsManager._videoDead != -1) { + _bVoy->freeBoltGroup(0xE00); + _eventsManager._videoDead = -1; + flipPageAndWait(); + } +} + +int VoyeurEngine::getChooseButton() { + int prevIndex = -2; + Common::Array<RectEntry> &hotspots = _bVoy->boltEntry(_playStampGroupId + + 6)._rectResource->_entries; + int selectedIndex = -1; + + (*_graphicsManager._vPort)->setupViewPort(_graphicsManager._backgroundPage); + _graphicsManager._backColors->_steps = 0; + _graphicsManager._backColors->startFade(); + flipPageAndWait(); + + _voy._viewBounds = _bVoy->boltEntry(_playStampGroupId + 7)._rectResource; + PictureResource *cursorPic = _bVoy->boltEntry(_playStampGroupId + 2)._picResource; + + do { + do { + if (_currentVocId != -1 && !_soundManager.getVOCStatus()) + _soundManager.startVOCPlay(_currentVocId); + + _eventsManager.getMouseInfo(); + selectedIndex = -1; + Common::Point pt = _eventsManager.getMousePos(); + + for (uint idx = 0; idx < hotspots.size(); ++idx) { + if (hotspots[idx].contains(pt)) { + if (!_voy._victimMurdered || ((int)idx + 1) != _controlPtr->_state->_victimIndex) { + selectedIndex = idx; + if (selectedIndex != prevIndex) { + PictureResource *btnPic = _bVoy->boltEntry(_playStampGroupId + 8 + idx)._picResource; + _graphicsManager.sDrawPic(btnPic, *_graphicsManager._vPort, + Common::Point(106, 200)); + + cursorPic = _bVoy->boltEntry(_playStampGroupId + 4)._picResource; + } + } + } + } + + if (selectedIndex == -1) { + cursorPic = _bVoy->boltEntry(_playStampGroupId + 2)._picResource; + PictureResource *btnPic = _bVoy->boltEntry(_playStampGroupId + 12)._picResource; + _graphicsManager.sDrawPic(btnPic, *_graphicsManager._vPort, + Common::Point(106, 200)); + } + + _graphicsManager.sDrawPic(cursorPic, *_graphicsManager._vPort, + Common::Point(pt.x + 13, pt.y - 12)); + + flipPageAndWait(); + } while (!shouldQuit() && !_eventsManager._mouseClicked); + } while (!shouldQuit() && selectedIndex == -1 && !_eventsManager._rightClick); + + return selectedIndex; +} + +void VoyeurEngine::makeViewFinder() { + _graphicsManager._backgroundPage = _bVoy->boltEntry(0x103)._picResource; + _graphicsManager.sDrawPic(_graphicsManager._backgroundPage, + *_graphicsManager._vPort, Common::Point(0, 0)); + CMapResource *pal = _bVoy->boltEntry(0x104)._cMapResource; + + int palOffset = 0; + switch (_voy._transitionId) { + case 1: + case 2: + case 5: + case 6: + case 7: + case 8: + case 9: + case 17: + palOffset = 0; + break; + case 3: + palOffset = 1; + break; + case 4: + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + palOffset = 2; + break; + default: + break; + } + + (*_graphicsManager._vPort)->drawIfaceTime(); + doTimeBar(true); + pal->startFade(); + + flipPageAndWaitForFade(); + + _graphicsManager.setColor(241, 105, 105, 105); + _graphicsManager.setColor(242, 105, 105, 105); + _graphicsManager.setColor(243, 105, 105, 105); + _graphicsManager.setColor(palOffset + 241, 219, 235, 235); + + _eventsManager._intPtr._hasPalette = true; +} + +void VoyeurEngine::makeViewFinderP() { + _graphicsManager.screenReset(); +} + +void VoyeurEngine::initIFace() { + int playStamp1 = _playStampGroupId; + switch (_voy._transitionId) { + case 0: + break; + case 1: + case 2: + case 5: + case 6: + case 7: + case 8: + case 9: + _playStampGroupId = 0xB00; + break; + case 3: + _playStampGroupId = 0xC00; + break; + default: + _playStampGroupId = 0xD00; + break; + } + if (playStamp1 != -1) + _bVoy->freeBoltGroup(playStamp1, true); + + _bVoy->getBoltGroup(_playStampGroupId); + CMapResource *pal = _bVoy->boltEntry(_playStampGroupId + 2)._cMapResource; + pal->startFade(); + + // Start the mansion off centered + _mansionViewPos = Common::Point((MANSION_MAX_X - MANSION_VIEW_WIDTH) / 2, + (MANSION_MAX_Y - MANSION_VIEW_HEIGHT) / 2); + doScroll(_mansionViewPos); + + _voy._viewBounds = _bVoy->boltEntry(_playStampGroupId)._rectResource; + + // Show the cursor using ScummVM functionality + _eventsManager.showCursor(); + + // Note: the original did two loops to preload members here, which is + // redundant for ScummVM, since computers are faster these days, and + // getting resources as needed will be fast enough. +} + +void VoyeurEngine::doScroll(const Common::Point &pt) { + Common::Rect clipRect(72, 47, 72 + 240, 47 + 148); + (*_graphicsManager._vPort)->setupViewPort(NULL, &clipRect); + + int base = 0; + switch (_voy._transitionId) { + case 0: + break; + case 1: + case 2: + case 5: + case 6: + case 7: + case 8: + case 9: + base = 0xB00; + break; + case 3: + base = 0xC00; + break; + default: + base = 0xD00; + } + + if (base) { + PictureResource *pic = _bVoy->boltEntry(base + 3)._picResource; + _graphicsManager.sDrawPic(pic, *_graphicsManager._vPort, Common::Point(784 - pt.x - 712, 150 - pt.y - 104)); + pic = _bVoy->boltEntry(base + 4)._picResource; + _graphicsManager.sDrawPic(pic, *_graphicsManager._vPort, Common::Point(784 - pt.x - 712, 150 - pt.y - 44)); + pic = _bVoy->boltEntry(base + 5)._picResource; + _graphicsManager.sDrawPic(pic, *_graphicsManager._vPort, Common::Point(784 - pt.x - 712, 150 - pt.y + 16)); + pic = _bVoy->boltEntry(base + 6)._picResource; + _graphicsManager.sDrawPic(pic, *_graphicsManager._vPort, Common::Point(784 - pt.x - 712, 150 - pt.y + 76)); + pic = _bVoy->boltEntry(base + 7)._picResource; + _graphicsManager.sDrawPic(pic, *_graphicsManager._vPort, Common::Point(784 - pt.x - 712, 150 - pt.y + 136)); + } + + (*_graphicsManager._vPort)->setupViewPort(NULL); +} + +void VoyeurEngine::checkTransition() { + Common::String time, day; + + if (_voy._transitionId != _checkTransitionId) { + // Get the day + day = getDayName(); + + // Only proceed if a valid day string was returned + if (!day.empty()) { + _graphicsManager.fadeDownICF(6); + + // Get the time of day string + time = getTimeOfDay(); + + // Show a transition card with the day and time, and wait + doTransitionCard(day, time); + _eventsManager.delayClick(180); + } + + _checkTransitionId = _voy._transitionId; + } +} + +Common::String VoyeurEngine::getDayName() { + switch (_voy._transitionId) { + case 0: + return ""; + case 1: + case 2: + case 3: + case 4: + return SATURDAY; + case 17: + return MONDAY; + default: + return SUNDAY; + } +} + +Common::String VoyeurEngine::getTimeOfDay() { + if (_voy._transitionId == 17) + return ""; + + return Common::String::format("%d:%02d%s", _gameHour, _gameMinute, _voy._isAM ? AM : PM); +} + +int VoyeurEngine::doComputerText(int maxLen) { + FontInfoResource &font = *_graphicsManager._fontPtr; + int totalChars = 0; + + font._curFont = _bVoy->boltEntry(0x4910)._fontResource; + font._foreColor = 129; + font._fontSaveBack = false; + font._fontFlags = 0; + if (_voy._vocSecondsOffset > 60) + _voy._vocSecondsOffset = 0; + + if (_voy._RTVNum > _voy._computerTimeMax && maxLen == 9999) { + if (_currentVocId != -1) + _soundManager.startVOCPlay(_currentVocId); + font._justify = ALIGN_LEFT; + font._justifyWidth = 384; + font._justifyHeight = 100; + font._pos = Common::Point(128, 100); + (*_graphicsManager._vPort)->drawText(END_OF_MESSAGE); + } else if (_voy._RTVNum < _voy._computerTimeMin && maxLen == 9999) { + if (_currentVocId != -1) + _soundManager.startVOCPlay(_currentVocId); + font._justify = ALIGN_LEFT; + font._justifyWidth = 384; + font._justifyHeight = 100; + font._pos = Common::Point(120, 100); + (*_graphicsManager._vPort)->drawText(START_OF_MESSAGE); + } else { + char *msg = (char *)_bVoy->memberAddr(0x4900 + _voy._computerTextId); + font._pos = Common::Point(96, 60); + + bool showEnd = true; + int yp = 60; + do { + if (_currentVocId != -1 && !_soundManager.getVOCStatus()) { + if (_voy._vocSecondsOffset > 60) + _voy._vocSecondsOffset = 0; + _soundManager.startVOCPlay(_currentVocId); + } + + char c = *msg++; + if (c == '\0') { + if (showEnd) { + _eventsManager.delay(90); + _graphicsManager._drawPtr->_pos = Common::Point(96, 54); + _graphicsManager._drawPtr->_penColor = 254; + (*_graphicsManager._vPort)->sFillBox(196, 124); + _graphicsManager._fontPtr->_justify = ALIGN_LEFT; + _graphicsManager._fontPtr->_justifyWidth = 384; + _graphicsManager._fontPtr->_justifyHeight = 100; + _graphicsManager._fontPtr->_pos = Common::Point(128, 100); + (*_graphicsManager._vPort)->drawText(END_OF_MESSAGE); + } + break; + } + + if (c == '~' || c == '^') { + if (c == '^') { + yp += 10; + } else { + _eventsManager.delay(90); + _graphicsManager._drawPtr->_pos = Common::Point(96, 54); + _graphicsManager._drawPtr->_penColor = 255; + (*_graphicsManager._vPort)->sFillBox(196, 124); + yp = 60; + } + + _graphicsManager._fontPtr->_pos = Common::Point(96, yp); + } else if (c == '_') { + showEnd = false; + } else { + _graphicsManager._fontPtr->_justify = ALIGN_LEFT; + _graphicsManager._fontPtr->_justifyWidth = 0; + _graphicsManager._fontPtr->_justifyHeight = 0; + (*_graphicsManager._vPort)->drawText(Common::String(c)); + _eventsManager.delay(4); + } + + flipPageAndWait(); + _eventsManager.getMouseInfo(); + ++totalChars; + + } while (!shouldQuit() && !_eventsManager._mouseClicked && totalChars < maxLen); + + _voy._computerTimeMax = 0; + } + + flipPageAndWait(); + + _graphicsManager._fontPtr->_curFont = _bVoy->boltEntry(0x101)._fontResource; + return totalChars; +} + +void VoyeurEngine::getComputerBrush() { + if (_bVoy->getBoltGroup(0x4900)) { + PictureResource *pic = _bVoy->boltEntry(0x490E)._picResource; + int xp = (384 - pic->_bounds.width()) / 2; + int yp = (240 - pic->_bounds.height()) / 2 - 4; + + (*_graphicsManager._vPort)->drawPicPerm(pic, Common::Point(xp, yp)); + + CMapResource *pal = _bVoy->boltEntry(0x490F)._cMapResource; + pal->startFade(); + } +} + +void VoyeurEngine::doTimeBar(bool force) { + flashTimeBar(); + + if ((force || _timeBarVal != _voy._RTVNum) && _voy._RTVLimit > 0) { + if (_voy._RTVNum > _voy._RTVLimit || _voy._RTVNum < 0) + _voy._RTVNum = _voy._RTVLimit - 1; + + _timeBarVal = _voy._RTVNum; + int height = ((_voy._RTVLimit - _voy._RTVNum) * 59) / _voy._RTVLimit; + int fullHeight = MAX(151 - height, 93); + + _graphicsManager._drawPtr->_penColor = 134; + _graphicsManager._drawPtr->_pos = Common::Point(39, 92); + + (*_graphicsManager._vPort)->sFillBox(6, fullHeight - 92); + if (height > 0) { + _graphicsManager.setColor(215, 238, 238, 238); + _eventsManager._intPtr._hasPalette = true; + + _graphicsManager._drawPtr->_penColor = 215; + _graphicsManager._drawPtr->_pos = Common::Point(39, fullHeight); + (*_graphicsManager._vPort)->sFillBox(6, height); + } + } +} + +void VoyeurEngine::flashTimeBar() { + if (_voy._RTVNum >= 0 && (_voy._RTVLimit - _voy._RTVNum) < 11 && + (_eventsManager._intPtr._flashTimer >= (_flashTimeVal + 15) || + _eventsManager._intPtr._flashTimer < _flashTimeVal)) { + // Within camera low power range + _flashTimeVal = _eventsManager._intPtr._flashTimer; + + if (_flashTimeFlag) + _graphicsManager.setColor(240, 220, 20, 20); + else + _graphicsManager.setColor(240, 220, 220, 220); + + _eventsManager._intPtr._hasPalette = true; + _flashTimeFlag = !_flashTimeFlag; + } +} + +void VoyeurEngine::checkPhoneCall() { + if ((_voy._RTVLimit - _voy._RTVNum) >= 36 && _voy._totalPhoneCalls < 5 && + _currentVocId <= 151 && _currentVocId > 146) { + if ((_voy._switchBGNum < _checkPhoneVal || _checkPhoneVal > 180) && + !_soundManager.getVOCStatus()) { + int soundIndex; + do { + soundIndex = getRandomNumber(4); + } while (_voy._phoneCallsReceived[soundIndex]); + _currentVocId = 154 + soundIndex; + + _soundManager.stopVOCPlay(); + _soundManager.startVOCPlay(_currentVocId); + _checkPhoneVal = _voy._switchBGNum; + ++_voy._phoneCallsReceived[soundIndex]; + ++_voy._totalPhoneCalls; + } + } +} + +void VoyeurEngine::doEvidDisplay(int evidId, int eventId) { + _eventsManager.getMouseInfo(); + flipPageAndWait(); + + if (_currentVocId != -1) { + _voy._vocSecondsOffset = _voy._RTVNum - _voy._musicStartTime; + _soundManager.stopVOCPlay(); + } + + _bVoy->getBoltGroup(_voy._boltGroupId2); + PictureResource *pic = _bVoy->boltEntry(_voy._boltGroupId2 + evidId * 2)._picResource; + _graphicsManager.sDrawPic(pic, *_graphicsManager._vPort, Common::Point( + (384 - pic->_bounds.width()) / 2, (240 - pic->_bounds.height()) / 2)); + _bVoy->freeBoltMember(_voy._boltGroupId2 + evidId * 2); + + CMapResource *pal = _bVoy->boltEntry(_voy._boltGroupId2 + evidId * 2 + 1)._cMapResource; + pal->startFade(); + + while (!shouldQuit() && (_eventsManager._fadeStatus & 1)) + _eventsManager.delay(1); + _bVoy->freeBoltMember(_voy._boltGroupId2 + evidId * 2 + 1); + + Common::Array<RectEntry> &hotspots = _bVoy->boltEntry(_playStampGroupId + 4)._rectResource->_entries; + int count = hotspots[evidId]._count; + + if (count > 0) { + for (int idx = 1; idx <= count; ++idx) { + _voy._evPicPtrs[idx - 1] = _bVoy->boltEntry(_voy._boltGroupId2 + + (evidId + idx) * 2)._picResource; + _voy._evCmPtrs[idx - 1] = _bVoy->boltEntry(_voy._boltGroupId2 + + (evidId + idx) * 2 + 1)._cMapResource; + } + } + + flipPageAndWait(); + _eventsManager.stopEvidDim(); + + if (eventId == 999) + _voy.addEvidEventStart(evidId); + + _eventsManager.getMouseInfo(); + + int arrIndex = 0; + int evidIdx = evidId; + + while (!shouldQuit() && !_eventsManager._rightClick) { + _voyeurArea = AREA_EVIDENCE; + + if (_currentVocId != -1 && !_soundManager.getVOCStatus()) { + if (_voy._vocSecondsOffset > 60) + _voy._vocSecondsOffset = 0; + + _soundManager.startVOCPlay(_currentVocId); + } + + _eventsManager.delayClick(600); + if (_eventsManager._rightClick) + break; + if (count == 0 || evidIdx >= eventId) + continue; + + PictureResource *pic = _voy._evPicPtrs[arrIndex]; + _graphicsManager.sDrawPic(pic, *_graphicsManager._vPort, + Common::Point((384 - pic->_bounds.width()) / 2, + (240 - pic->_bounds.height()) / 2)); + _voy._evCmPtrs[arrIndex]->startFade(); + while (!shouldQuit() && (_eventsManager._fadeStatus & 1)) + _eventsManager.delay(1); + + flipPageAndWait(); + _eventsManager.delay(6); + + ++evidIdx; + ++arrIndex; + --count; + } + + if (eventId == 999) + _voy.addEvidEventEnd(evidIdx); + + for (int idx = 1; idx <= hotspots[evidId]._count; ++idx) { + _bVoy->freeBoltMember(_voy._boltGroupId2 + (evidId + idx) * 2); + _bVoy->freeBoltMember(_voy._boltGroupId2 + (evidId + idx) * 2 + 1); + } +} + +} // End of namespace Voyeur |