diff options
280 files changed, 40365 insertions, 572 deletions
@@ -11,6 +11,17 @@ For a more comprehensive changelog of the latest experimental code, see: General: - Updated Munt MT-32 emulation code to version 1.5.0. + AGI: + - It is now possible to disable mouse support (except for Amiga versions + and fanmade games, that require a mouse). + - Fix incorrect volume attenuation in PCjr sound code (bug #6858). + + AGOS: + - Fixed arpeggio effect used in music of Amiga version of Elvira 1. + - Fixed loading and saving progress in the PC version of Waxworks. + - Added Accolade AdLib & MT32 music drivers for the games: + Elvira 1, Elvira 2, Waxworks and Simon the sorcerer 1 demo + Broken Sword 1: - Fix speech endianness detection on big endian systems for the mac version (bug #6720). @@ -20,8 +31,11 @@ For a more comprehensive changelog of the latest experimental code, see: SCI: - Handling of music priority has been greatly improved. - - A lot of fixes for original game script bugs, which are too numerous to - mention here. + - A lot of fixes for original game script bugs that also occurred when + using the original interpreter. + KQ6 (Dual Mode), LSL5, QfG1 (EGA), QfG1 (VGA), QfG2, QfG3, SQ1, SQ4 (CD) + - Restoring from the ScummVM in-game menu should now work all the time. + - Improve support for Japanese PC-9801 games. SCUMM: - It is now possible to play Maniac Mansion from within Day of the @@ -1013,7 +1013,11 @@ site, please see the section on reporting bugs. Inherit the Earth: Quest for the Orb - Amiga versions aren't supported - Simon the Sorcerer 1: + Lure of the Temptress + - No Roland MT-32 support + - Sound support is incomplete and doesn't sound like original + + Simon the Sorcerer 1: - Subtitles aren't available in the English and German CD versions as they are missing the majority of subtitles. @@ -2264,6 +2268,10 @@ Sierra games using the AGI engine add the following non-standard keywords: originalsaveload bool If true, the original save/load screens are used instead of the enhanced ScummVM ones + altamigapalette bool Use an alternative palette, common for all + Amiga games. This was the old behavior + mousesupport bool Enables mouse support. Allows to use mouse + for movement and in game menus Sierra games using the SCI engine add the following non-standard keywords: @@ -2275,6 +2283,12 @@ Sierra games using the SCI engine add the following non-standard keywords: native_fb01 bool If true, the music driver for an IBM Music Feature card or a Yamaha FB-01 FM synth module is used for MIDI output + use_cdaudio bool Use CD audio instead of in-game audio, + when available + windows_cursors bool Use the Windows cursors (smaller and monochrome) + instead of the DOS ones (King's Quest 6) + silver_cursors bool Use the alternate set of silver cursors, + instead of the normal golden ones (Space Quest 4) Broken Sword II adds the following non-standard keywords: diff --git a/audio/decoders/3do.cpp b/audio/decoders/3do.cpp new file mode 100644 index 0000000000..6d558d4c8c --- /dev/null +++ b/audio/decoders/3do.cpp @@ -0,0 +1,343 @@ +/* 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 "common/textconsole.h" +#include "common/stream.h" +#include "common/util.h" + +#include "audio/decoders/3do.h" +#include "audio/decoders/raw.h" +#include "audio/decoders/adpcm_intern.h" + +namespace Audio { + +// Reuses ADPCM table +#define audio_3DO_ADP4_stepSizeTable Ima_ADPCMStream::_imaTable +#define audio_3DO_ADP4_stepSizeIndex ADPCMStream::_stepAdjustTable + +RewindableAudioStream *make3DO_ADP4AudioStream(Common::SeekableReadStream *stream, uint16 sampleRate, bool stereo, uint32 *audioLengthMSecsPtr, DisposeAfterUse::Flag disposeAfterUse, audio_3DO_ADP4_PersistentSpace *persistentSpace) { + if (stereo) { + warning("make3DO_ADP4Stream(): stereo currently not supported"); + return 0; + } + + if (audioLengthMSecsPtr) { + // Caller requires the milliseconds of audio + uint32 audioLengthMSecs = stream->size() * 2 * 1000 / sampleRate; // 1 byte == 2 16-bit sample + if (stereo) { + audioLengthMSecs /= 2; + } + *audioLengthMSecsPtr = audioLengthMSecs; + } + + return new Audio3DO_ADP4_Stream(stream, sampleRate, stereo, disposeAfterUse, persistentSpace); +} + +Audio3DO_ADP4_Stream::Audio3DO_ADP4_Stream(Common::SeekableReadStream *stream, uint16 sampleRate, bool stereo, DisposeAfterUse::Flag disposeAfterUse, audio_3DO_ADP4_PersistentSpace *persistentSpace) + : _sampleRate(sampleRate), _stereo(stereo), + _stream(stream, disposeAfterUse) { + + _callerDecoderData = persistentSpace; + memset(&_initialDecoderData, 0, sizeof(_initialDecoderData)); + _initialRead = true; + + reset(); +} + +void Audio3DO_ADP4_Stream::reset() { + memcpy(&_curDecoderData, &_initialDecoderData, sizeof(_curDecoderData)); + _streamBytesLeft = _stream->size(); + _stream->seek(0); +} + +bool Audio3DO_ADP4_Stream::rewind() { + reset(); + return true; +} + +int16 Audio3DO_ADP4_Stream::decodeSample(byte compressedNibble) { + int16 currentStep = audio_3DO_ADP4_stepSizeTable[_curDecoderData.stepIndex]; + int32 decodedSample = _curDecoderData.lastSample; + int16 delta = currentStep >> 3; + + if (compressedNibble & 1) + delta += currentStep >> 2; + + if (compressedNibble & 2) + delta += currentStep >> 1; + + if (compressedNibble & 4) + delta += currentStep; + + if (compressedNibble & 8) { + decodedSample -= delta; + } else { + decodedSample += delta; + } + + _curDecoderData.lastSample = CLIP<int32>(decodedSample, -32768, 32767); + + _curDecoderData.stepIndex += audio_3DO_ADP4_stepSizeIndex[compressedNibble & 0x07]; + _curDecoderData.stepIndex = CLIP<int16>(_curDecoderData.stepIndex, 0, ARRAYSIZE(audio_3DO_ADP4_stepSizeTable) - 1); + + return _curDecoderData.lastSample; +} + +// Writes the requested amount (or less) of samples into buffer and returns the amount of samples, that got written +int Audio3DO_ADP4_Stream::readBuffer(int16 *buffer, const int numSamples) { + int8 byteCache[AUDIO_3DO_CACHE_SIZE]; + int8 *byteCachePtr = NULL; + int byteCacheSize = 0; + int requestedBytesLeft = 0; + int decodedSamplesCount = 0; + + int8 compressedByte = 0; + + if (endOfData()) + return 0; // no more bytes left + + if (_callerDecoderData) { + // copy caller decoder data over + memcpy(&_curDecoderData, _callerDecoderData, sizeof(_curDecoderData)); + if (_initialRead) { + _initialRead = false; + memcpy(&_initialDecoderData, &_curDecoderData, sizeof(_initialDecoderData)); + } + } + + requestedBytesLeft = numSamples >> 1; // 1 byte for 2 16-bit sample + if (requestedBytesLeft > _streamBytesLeft) + requestedBytesLeft = _streamBytesLeft; // not enough bytes left + + // in case caller requests an uneven amount of samples, we will return an even amount + + // buffering, so that direct decoding of files and such runs way faster + while (requestedBytesLeft) { + if (requestedBytesLeft > AUDIO_3DO_CACHE_SIZE) { + byteCacheSize = AUDIO_3DO_CACHE_SIZE; + } else { + byteCacheSize = requestedBytesLeft; + } + + requestedBytesLeft -= byteCacheSize; + _streamBytesLeft -= byteCacheSize; + + // Fill our byte cache + _stream->read(byteCache, byteCacheSize); + + byteCachePtr = byteCache; + + // Mono + while (byteCacheSize) { + compressedByte = *byteCachePtr++; + byteCacheSize--; + + buffer[decodedSamplesCount] = decodeSample(compressedByte >> 4); + decodedSamplesCount++; + buffer[decodedSamplesCount] = decodeSample(compressedByte & 0x0f); + decodedSamplesCount++; + } + } + + if (_callerDecoderData) { + // copy caller decoder data back + memcpy(_callerDecoderData, &_curDecoderData, sizeof(_curDecoderData)); + } + + return decodedSamplesCount; +} + +// ============================================================================ +static int16 audio_3DO_SDX2_SquareTable[256] = { + -32768,-32258,-31752,-31250,-30752,-30258,-29768,-29282,-28800,-28322, + -27848,-27378,-26912,-26450,-25992,-25538,-25088,-24642,-24200,-23762, + -23328,-22898,-22472,-22050,-21632,-21218,-20808,-20402,-20000,-19602, + -19208,-18818,-18432,-18050,-17672,-17298,-16928,-16562,-16200,-15842, + -15488,-15138,-14792,-14450,-14112,-13778,-13448,-13122,-12800,-12482, + -12168,-11858,-11552,-11250,-10952,-10658,-10368,-10082, -9800, -9522, + -9248, -8978, -8712, -8450, -8192, -7938, -7688, -7442, -7200, -6962, + -6728, -6498, -6272, -6050, -5832, -5618, -5408, -5202, -5000, -4802, + -4608, -4418, -4232, -4050, -3872, -3698, -3528, -3362, -3200, -3042, + -2888, -2738, -2592, -2450, -2312, -2178, -2048, -1922, -1800, -1682, + -1568, -1458, -1352, -1250, -1152, -1058, -968, -882, -800, -722, + -648, -578, -512, -450, -392, -338, -288, -242, -200, -162, + -128, -98, -72, -50, -32, -18, -8, -2, 0, 2, + 8, 18, 32, 50, 72, 98, 128, 162, 200, 242, + 288, 338, 392, 450, 512, 578, 648, 722, 800, 882, + 968, 1058, 1152, 1250, 1352, 1458, 1568, 1682, 1800, 1922, + 2048, 2178, 2312, 2450, 2592, 2738, 2888, 3042, 3200, 3362, + 3528, 3698, 3872, 4050, 4232, 4418, 4608, 4802, 5000, 5202, + 5408, 5618, 5832, 6050, 6272, 6498, 6728, 6962, 7200, 7442, + 7688, 7938, 8192, 8450, 8712, 8978, 9248, 9522, 9800, 10082, + 10368, 10658, 10952, 11250, 11552, 11858, 12168, 12482, 12800, 13122, + 13448, 13778, 14112, 14450, 14792, 15138, 15488, 15842, 16200, 16562, + 16928, 17298, 17672, 18050, 18432, 18818, 19208, 19602, 20000, 20402, + 20808, 21218, 21632, 22050, 22472, 22898, 23328, 23762, 24200, 24642, + 25088, 25538, 25992, 26450, 26912, 27378, 27848, 28322, 28800, 29282, + 29768, 30258, 30752, 31250, 31752, 32258 +}; + +Audio3DO_SDX2_Stream::Audio3DO_SDX2_Stream(Common::SeekableReadStream *stream, uint16 sampleRate, bool stereo, DisposeAfterUse::Flag disposeAfterUse, audio_3DO_SDX2_PersistentSpace *persistentSpace) + : _sampleRate(sampleRate), _stereo(stereo), + _stream(stream, disposeAfterUse) { + + _callerDecoderData = persistentSpace; + memset(&_initialDecoderData, 0, sizeof(_initialDecoderData)); + _initialRead = true; + + reset(); +} + +void Audio3DO_SDX2_Stream::reset() { + memcpy(&_curDecoderData, &_initialDecoderData, sizeof(_curDecoderData)); + _streamBytesLeft = _stream->size(); + _stream->seek(0); +} + +bool Audio3DO_SDX2_Stream::rewind() { + reset(); + return true; +} + +// Writes the requested amount (or less) of samples into buffer and returns the amount of samples, that got written +int Audio3DO_SDX2_Stream::readBuffer(int16 *buffer, const int numSamples) { + int8 byteCache[AUDIO_3DO_CACHE_SIZE]; + int8 *byteCachePtr = NULL; + int byteCacheSize = 0; + int requestedBytesLeft = numSamples; // 1 byte per 16-bit sample + int decodedSamplesCount = 0; + + int8 compressedByte = 0; + uint8 squareTableOffset = 0; + int16 decodedSample = 0; + + if (endOfData()) + return 0; // no more bytes left + + if (_stereo) { + // We expect numSamples to be even in case of Stereo audio + assert((numSamples & 1) == 0); + } + + if (_callerDecoderData) { + // copy caller decoder data over + memcpy(&_curDecoderData, _callerDecoderData, sizeof(_curDecoderData)); + if (_initialRead) { + _initialRead = false; + memcpy(&_initialDecoderData, &_curDecoderData, sizeof(_initialDecoderData)); + } + } + + requestedBytesLeft = numSamples; + if (requestedBytesLeft > _streamBytesLeft) + requestedBytesLeft = _streamBytesLeft; // not enough bytes left + + // buffering, so that direct decoding of files and such runs way faster + while (requestedBytesLeft) { + if (requestedBytesLeft > AUDIO_3DO_CACHE_SIZE) { + byteCacheSize = AUDIO_3DO_CACHE_SIZE; + } else { + byteCacheSize = requestedBytesLeft; + } + + requestedBytesLeft -= byteCacheSize; + _streamBytesLeft -= byteCacheSize; + + // Fill our byte cache + _stream->read(byteCache, byteCacheSize); + + byteCachePtr = byteCache; + + if (!_stereo) { + // Mono + while (byteCacheSize) { + compressedByte = *byteCachePtr++; + byteCacheSize--; + squareTableOffset = compressedByte + 128; + + if (!(compressedByte & 1)) + _curDecoderData.lastSample1 = 0; + + decodedSample = _curDecoderData.lastSample1 + audio_3DO_SDX2_SquareTable[squareTableOffset]; + _curDecoderData.lastSample1 = decodedSample; + + buffer[decodedSamplesCount] = decodedSample; + decodedSamplesCount++; + } + } else { + // Stereo + while (byteCacheSize) { + compressedByte = *byteCachePtr++; + byteCacheSize--; + squareTableOffset = compressedByte + 128; + + if (!(decodedSamplesCount & 1)) { + // First channel + if (!(compressedByte & 1)) + _curDecoderData.lastSample1 = 0; + + decodedSample = _curDecoderData.lastSample1 + audio_3DO_SDX2_SquareTable[squareTableOffset]; + _curDecoderData.lastSample1 = decodedSample; + } else { + // Second channel + if (!(compressedByte & 1)) + _curDecoderData.lastSample2 = 0; + + decodedSample = _curDecoderData.lastSample2 + audio_3DO_SDX2_SquareTable[squareTableOffset]; + _curDecoderData.lastSample2 = decodedSample; + } + + buffer[decodedSamplesCount] = decodedSample; + decodedSamplesCount++; + } + } + } + + if (_callerDecoderData) { + // copy caller decoder data back + memcpy(_callerDecoderData, &_curDecoderData, sizeof(_curDecoderData)); + } + + return decodedSamplesCount; +} + +RewindableAudioStream *make3DO_SDX2AudioStream(Common::SeekableReadStream *stream, uint16 sampleRate, bool stereo, uint32 *audioLengthMSecsPtr, DisposeAfterUse::Flag disposeAfterUse, audio_3DO_SDX2_PersistentSpace *persistentSpace) { + if (stereo) { + if (stream->size() & 1) { + warning("make3DO_SDX2Stream(): stereo data is uneven size"); + return 0; + } + } + + if (audioLengthMSecsPtr) { + // Caller requires the milliseconds of audio + uint32 audioLengthMSecs = stream->size() * 1000 / sampleRate; // 1 byte == 1 16-bit sample + if (stereo) { + audioLengthMSecs /= 2; + } + *audioLengthMSecsPtr = audioLengthMSecs; + } + + return new Audio3DO_SDX2_Stream(stream, sampleRate, stereo, disposeAfterUse, persistentSpace); +} + +} // End of namespace Audio diff --git a/audio/decoders/3do.h b/audio/decoders/3do.h new file mode 100644 index 0000000000..7524358543 --- /dev/null +++ b/audio/decoders/3do.h @@ -0,0 +1,158 @@ +/* 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. + * + */ + +/** + * @file + * Sound decoder used in engines: + * - sherlock (3DO version of Serrated Scalpel) + */ + +#ifndef AUDIO_3DO_SDX2_H +#define AUDIO_3DO_SDX2_H + +#include "common/scummsys.h" +#include "common/types.h" +#include "common/substream.h" + +#include "audio/audiostream.h" +#include "audio/decoders/raw.h" + +namespace Common { +class SeekableReadStream; +} + +namespace Audio { + +class SeekableAudioStream; + +// amount of bytes to be used within the decoder classes as buffers +#define AUDIO_3DO_CACHE_SIZE 1024 + +// persistent spaces +struct audio_3DO_ADP4_PersistentSpace { + int16 lastSample; + int16 stepIndex; +}; + +struct audio_3DO_SDX2_PersistentSpace { + int16 lastSample1; + int16 lastSample2; +}; + +class Audio3DO_ADP4_Stream : public RewindableAudioStream { +public: + Audio3DO_ADP4_Stream(Common::SeekableReadStream *stream, uint16 sampleRate, bool stereo, DisposeAfterUse::Flag disposeAfterUse, audio_3DO_ADP4_PersistentSpace *persistentSpace); + +protected: + const uint16 _sampleRate; + const bool _stereo; + + Common::DisposablePtr<Common::SeekableReadStream> _stream; + int32 _streamBytesLeft; + + void reset(); + bool rewind(); + bool endOfData() const { return (_stream->pos() >= _stream->size()); } + bool isStereo() const { return _stereo; } + int getRate() const { return _sampleRate; } + + int readBuffer(int16 *buffer, const int numSamples); + + bool _initialRead; + audio_3DO_ADP4_PersistentSpace *_callerDecoderData; + audio_3DO_ADP4_PersistentSpace _initialDecoderData; + audio_3DO_ADP4_PersistentSpace _curDecoderData; + +private: + int16 decodeSample(byte compressedNibble); +}; + +class Audio3DO_SDX2_Stream : public RewindableAudioStream { +public: + Audio3DO_SDX2_Stream(Common::SeekableReadStream *stream, uint16 sampleRate, bool stereo, DisposeAfterUse::Flag disposeAfterUse, audio_3DO_SDX2_PersistentSpace *persistentSpacePtr); + +protected: + const uint16 _sampleRate; + const bool _stereo; + + Common::DisposablePtr<Common::SeekableReadStream> _stream; + int32 _streamBytesLeft; + + void reset(); + bool rewind(); + bool endOfData() const { return (_stream->pos() >= _stream->size()); } + bool isStereo() const { return _stereo; } + int getRate() const { return _sampleRate; } + + int readBuffer(int16 *buffer, const int numSamples); + + bool _initialRead; + audio_3DO_SDX2_PersistentSpace *_callerDecoderData; + audio_3DO_SDX2_PersistentSpace _initialDecoderData; + audio_3DO_SDX2_PersistentSpace _curDecoderData; +}; + +/** + * Try to decode 3DO ADP4 data from the given seekable stream and create a SeekableAudioStream + * from that data. + * + * @param stream the SeekableReadStream from which to read the 3DO SDX2 data + * @sampleRate sample rate + * @stereo if it's stereo or mono + * @audioLengthMSecsPtr pointer to a uint32 variable, that is supposed to get the length of the audio in milliseconds + * @disposeAfterUse disposeAfterUse whether to delete the stream after use + * @persistentSpacePtr pointer to the persistent space structure + * @return a new SeekableAudioStream, or NULL, if an error occurred + */ +RewindableAudioStream *make3DO_ADP4AudioStream( + Common::SeekableReadStream *stream, + uint16 sampleRate, + bool stereo, + uint32 *audioLengthMSecsPtr = NULL, // returns the audio length in milliseconds + DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES, + audio_3DO_ADP4_PersistentSpace *persistentSpacePtr = NULL +); + +/** + * Try to decode 3DO SDX2 data from the given seekable stream and create a SeekableAudioStream + * from that data. + * + * @param stream the SeekableReadStream from which to read the 3DO SDX2 data + * @sampleRate sample rate + * @stereo if it's stereo or mono + * @audioLengthMSecsPtr pointer to a uint32 variable, that is supposed to get the length of the audio in milliseconds + * @disposeAfterUse disposeAfterUse whether to delete the stream after use + * @persistentSpacePtr pointer to the persistent space structure + * @return a new SeekableAudioStream, or NULL, if an error occurred + */ +RewindableAudioStream *make3DO_SDX2AudioStream( + Common::SeekableReadStream *stream, + uint16 sampleRate, + bool stereo, + uint32 *audioLengthMSecsPtr = NULL, // returns the audio length in milliseconds + DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES, + audio_3DO_SDX2_PersistentSpace *persistentSpacePtr = NULL +); + +} // End of namespace Audio + +#endif diff --git a/audio/decoders/aiff.cpp b/audio/decoders/aiff.cpp index b714721c02..72baf84582 100644 --- a/audio/decoders/aiff.cpp +++ b/audio/decoders/aiff.cpp @@ -24,16 +24,19 @@ * The code in this file is based on information found at * http://www.borg.com/~jglatt/tech/aiff.htm * - * We currently only implement uncompressed AIFF. If we ever need AIFF-C, SoX - * (http://sox.sourceforge.net) may be a good place to start from. + * Also partially based on libav's aiffdec.c */ +#include "common/debug.h" #include "common/endian.h" #include "common/stream.h" +#include "common/substream.h" #include "common/textconsole.h" +#include "audio/audiostream.h" #include "audio/decoders/aiff.h" #include "audio/decoders/raw.h" +#include "audio/decoders/3do.h" namespace Audio { @@ -62,23 +65,34 @@ uint32 readExtended(Common::SeekableReadStream &stream) { return mantissa; } -bool loadAIFFFromStream(Common::SeekableReadStream &stream, int &size, int &rate, byte &flags) { - byte buf[4]; +// AIFF versions +static const uint32 kVersionAIFF = MKTAG('A', 'I', 'F', 'F'); +static const uint32 kVersionAIFC = MKTAG('A', 'I', 'F', 'C'); - stream.read(buf, 4); - if (memcmp(buf, "FORM", 4) != 0) { - warning("loadAIFFFromStream: No 'FORM' header"); - return false; +// Codecs +static const uint32 kCodecPCM = MKTAG('N', 'O', 'N', 'E'); // very original + +RewindableAudioStream *makeAIFFStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) { + if (stream->readUint32BE() != MKTAG('F', 'O', 'R', 'M')) { + warning("makeAIFFStream: No 'FORM' header"); + + if (disposeAfterUse == DisposeAfterUse::YES) + delete stream; + + return 0; } - stream.readUint32BE(); + stream->readUint32BE(); // file size + + uint32 version = stream->readUint32BE(); - // This could be AIFC, but we don't handle that case. + if (version != kVersionAIFF && version != kVersionAIFC) { + warning("makeAIFFStream: No 'AIFF' or 'AIFC' header"); + + if (disposeAfterUse == DisposeAfterUse::YES) + delete stream; - stream.read(buf, 4); - if (memcmp(buf, "AIFF", 4) != 0) { - warning("loadAIFFFromStream: No 'AIFF' header"); - return false; + return 0; } // From here on, we only care about the COMM and SSND chunks, which are @@ -87,95 +101,131 @@ bool loadAIFFFromStream(Common::SeekableReadStream &stream, int &size, int &rate bool foundCOMM = false; bool foundSSND = false; - uint16 numChannels = 0, bitsPerSample = 0; - uint32 numSampleFrames = 0, offset = 0, blockSize = 0, soundOffset = 0; + uint16 channels = 0, bitsPerSample = 0; + uint32 blockAlign = 0, rate = 0; + uint32 codec = kCodecPCM; // AIFF default + Common::SeekableReadStream *dataStream = 0; - while (!(foundCOMM && foundSSND) && !stream.err() && !stream.eos()) { - uint32 length, pos; + while (!(foundCOMM && foundSSND) && !stream->err() && !stream->eos()) { + uint32 tag = stream->readUint32BE(); + uint32 length = stream->readUint32BE(); + uint32 pos = stream->pos(); - stream.read(buf, 4); - length = stream.readUint32BE(); - pos = stream.pos(); + if (stream->eos() || stream->err()) + break; - if (memcmp(buf, "COMM", 4) == 0) { + switch (tag) { + case MKTAG('C', 'O', 'M', 'M'): foundCOMM = true; - numChannels = stream.readUint16BE(); - numSampleFrames = stream.readUint32BE(); - bitsPerSample = stream.readUint16BE(); - rate = readExtended(stream); - size = numSampleFrames * numChannels * (bitsPerSample / 8); - } else if (memcmp(buf, "SSND", 4) == 0) { + channels = stream->readUint16BE(); + /* frameCount = */ stream->readUint32BE(); + bitsPerSample = stream->readUint16BE(); + rate = readExtended(*stream); + + if (version == kVersionAIFC) + codec = stream->readUint32BE(); + break; + case MKTAG('S', 'S', 'N', 'D'): foundSSND = true; - offset = stream.readUint32BE(); - blockSize = stream.readUint32BE(); - soundOffset = stream.pos(); + /* uint32 offset = */ stream->readUint32BE(); + blockAlign = stream->readUint32BE(); + dataStream = new Common::SeekableSubReadStream(stream, stream->pos(), stream->pos() + length - 8, disposeAfterUse); + break; + case MKTAG('F', 'V', 'E', 'R'): + switch (stream->readUint32BE()) { + case 0: + version = kVersionAIFF; + break; + case 0xA2805140: + version = kVersionAIFC; + break; + default: + warning("Unknown AIFF version chunk version"); + break; + } + break; + case MKTAG('w', 'a', 'v', 'e'): + warning("Found unhandled AIFF-C extra data chunk"); + + if (!dataStream && disposeAfterUse == DisposeAfterUse::YES) + delete stream; + + delete dataStream; + return 0; + default: + debug(1, "Skipping AIFF '%s' chunk", tag2str(tag)); + break; } - stream.seek(pos + length); + stream->seek(pos + length + (length & 1)); // ensure we're also word-aligned } if (!foundCOMM) { - warning("loadAIFFFromStream: Cound not find 'COMM' chunk"); - return false; - } - - if (!foundSSND) { - warning("loadAIFFFromStream: Cound not find 'SSND' chunk"); - return false; - } - - // We only implement a subset of the AIFF standard. - - if (numChannels < 1 || numChannels > 2) { - warning("loadAIFFFromStream: Only 1 or 2 channels are supported, not %d", numChannels); - return false; - } + warning("makeAIFFStream: Cound not find 'COMM' chunk"); - if (bitsPerSample != 8 && bitsPerSample != 16) { - warning("loadAIFFFromStream: Only 8 or 16 bits per sample are supported, not %d", bitsPerSample); - return false; - } + if (!dataStream && disposeAfterUse == DisposeAfterUse::YES) + delete stream; - if (offset != 0 || blockSize != 0) { - warning("loadAIFFFromStream: Block-aligned data is not supported"); - return false; + delete dataStream; + return 0; } - // Samples are always signed, and big endian. - - flags = 0; - if (bitsPerSample == 16) - flags |= Audio::FLAG_16BITS; - if (numChannels == 2) - flags |= Audio::FLAG_STEREO; - - stream.seek(soundOffset); - - // Stream now points at the sample data - - return true; -} - -SeekableAudioStream *makeAIFFStream(Common::SeekableReadStream *stream, - DisposeAfterUse::Flag disposeAfterUse) { - int size, rate; - byte *data, flags; + if (!foundSSND) { + warning("makeAIFFStream: Cound not find 'SSND' chunk"); - if (!loadAIFFFromStream(*stream, size, rate, flags)) { if (disposeAfterUse == DisposeAfterUse::YES) delete stream; + return 0; } - data = (byte *)malloc(size); - assert(data); - stream->read(data, size); + // We only implement a subset of the AIFF standard. - if (disposeAfterUse == DisposeAfterUse::YES) - delete stream; + if (channels < 1 || channels > 2) { + warning("makeAIFFStream: Only 1 or 2 channels are supported, not %d", channels); + delete dataStream; + return 0; + } + + // Seek to the start of dataStream, required for at least FileStream + dataStream->seek(0); + + switch (codec) { + case kCodecPCM: + case MKTAG('t', 'w', 'o', 's'): + case MKTAG('s', 'o', 'w', 't'): { + // PCM samples are always signed. + byte rawFlags = 0; + if (bitsPerSample == 16) + rawFlags |= Audio::FLAG_16BITS; + if (channels == 2) + rawFlags |= Audio::FLAG_STEREO; + if (codec == MKTAG('s', 'o', 'w', 't')) + rawFlags |= Audio::FLAG_LITTLE_ENDIAN; + + return makeRawStream(dataStream, rate, rawFlags); + } + case MKTAG('i', 'm', 'a', '4'): + // TODO: Use QT IMA ADPCM + warning("Unhandled AIFF-C QT IMA ADPCM compression"); + break; + case MKTAG('Q', 'D', 'M', '2'): + // TODO: Need to figure out how to integrate this + // (But hopefully never needed) + warning("Unhandled AIFF-C QDM2 compression"); + break; + case MKTAG('A', 'D', 'P', '4'): + // ADP4 on 3DO + return make3DO_ADP4AudioStream(dataStream, rate, channels == 2); + case MKTAG('S', 'D', 'X', '2'): + // SDX2 on 3DO + return make3DO_SDX2AudioStream(dataStream, rate, channels == 2); + default: + warning("Unhandled AIFF-C compression tag '%s'", tag2str(codec)); + } - // Since we allocated our own buffer for the data, we must specify DisposeAfterUse::YES. - return makeRawStream(data, size, rate, flags); + delete dataStream; + return 0; } } // End of namespace Audio diff --git a/audio/decoders/aiff.h b/audio/decoders/aiff.h index afb0342cfd..3af2efb4c9 100644 --- a/audio/decoders/aiff.h +++ b/audio/decoders/aiff.h @@ -23,6 +23,7 @@ /** * @file * Sound decoder used in engines: + * - bbvs * - pegasus * - saga * - sci @@ -41,28 +42,17 @@ class SeekableReadStream; namespace Audio { -class SeekableAudioStream; - -/** - * Try to load an AIFF from the given seekable stream. Returns true if - * successful. In that case, the stream's seek position will be set to the - * start of the audio data, and size, rate and flags contain information - * necessary for playback. Currently this function only supports uncompressed - * raw PCM. - */ -extern bool loadAIFFFromStream(Common::SeekableReadStream &stream, int &size, int &rate, byte &flags); +class RewindableAudioStream; /** * Try to load an AIFF from the given seekable stream and create an AudioStream * from that data. * - * This function uses loadAIFFFromStream() internally. - * * @param stream the SeekableReadStream from which to read the AIFF data * @param disposeAfterUse whether to delete the stream after use * @return a new SeekableAudioStream, or NULL, if an error occurred */ -SeekableAudioStream *makeAIFFStream( +RewindableAudioStream *makeAIFFStream( Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse); diff --git a/audio/decoders/wave.h b/audio/decoders/wave.h index 1dcaefd845..6bc9f72101 100644 --- a/audio/decoders/wave.h +++ b/audio/decoders/wave.h @@ -23,15 +23,25 @@ /** * @file * Sound decoder used in engines: + * - access * - agos + * - cge + * - cge2 + * - fullpipe * - gob + * - hopkins * - mohawk + * - prince * - saga * - sci * - scumm + * - sherlock * - sword1 * - sword2 + * - tony * - tucker + * - wintermute + * - zvision */ #ifndef AUDIO_WAVE_H diff --git a/audio/fmopl.cpp b/audio/fmopl.cpp index c18e544410..30229ea6bf 100644 --- a/audio/fmopl.cpp +++ b/audio/fmopl.cpp @@ -80,6 +80,12 @@ Config::DriverId Config::detect(OplType type) { } DriverId drv = parse(ConfMan.get("opl_driver")); + if (drv == kAuto) { + // Since the "auto" can be explicitly set for a game, and this + // driver shows up in the GUI as "<default>", check if there is + // a global setting for it before resorting to auto-detection. + drv = parse(ConfMan.get("opl_driver", Common::ConfigManager::kApplicationDomain)); + } // When a valid driver is selected, check whether it supports // the requested OPL chip. diff --git a/audio/mods/protracker.cpp b/audio/mods/protracker.cpp index 82067f67bd..2578e9488a 100644 --- a/audio/mods/protracker.cpp +++ b/audio/mods/protracker.cpp @@ -219,11 +219,10 @@ void ProtrackerStream::updateRow() { case 0x0: if (exy) { _track[track].arpeggio = true; - if (note.period) { - _track[track].arpeggioNotes[0] = note.note; - _track[track].arpeggioNotes[1] = note.note + ex; - _track[track].arpeggioNotes[2] = note.note + ey; - } + byte trackNote = _module.periodToNote(_track[track].period); + _track[track].arpeggioNotes[0] = trackNote; + _track[track].arpeggioNotes[1] = trackNote + ex; + _track[track].arpeggioNotes[2] = trackNote + ey; } break; case 0x1: diff --git a/audio/module.mk b/audio/module.mk index 4e1c031c83..bdb71ab4a3 100644 --- a/audio/module.mk +++ b/audio/module.mk @@ -14,6 +14,7 @@ MODULE_OBJS := \ musicplugin.o \ null.o \ timestamp.o \ + decoders/3do.o \ decoders/aac.o \ decoders/adpcm.o \ decoders/aiff.o \ diff --git a/backends/graphics/openglsdl/openglsdl-graphics.cpp b/backends/graphics/openglsdl/openglsdl-graphics.cpp index c71b9c9219..a2b172f14a 100644 --- a/backends/graphics/openglsdl/openglsdl-graphics.cpp +++ b/backends/graphics/openglsdl/openglsdl-graphics.cpp @@ -57,7 +57,10 @@ OpenGLSdlGraphicsManager::OpenGLSdlGraphicsManager(uint desktopWidth, uint deskt } #else const SDL_Rect *const *availableModes = SDL_ListModes(NULL, SDL_OPENGL | SDL_FULLSCREEN); - if (availableModes != (void *)-1) { + // TODO: NULL means that there are no fullscreen modes supported. We + // should probably use this information and disable any fullscreen support + // in this case. + if (availableModes != NULL && availableModes != (void *)-1) { for (;*availableModes; ++availableModes) { const SDL_Rect *mode = *availableModes; diff --git a/backends/graphics/surfacesdl/surfacesdl-graphics.cpp b/backends/graphics/surfacesdl/surfacesdl-graphics.cpp index d08925349a..9cb14525ee 100644 --- a/backends/graphics/surfacesdl/surfacesdl-graphics.cpp +++ b/backends/graphics/surfacesdl/surfacesdl-graphics.cpp @@ -125,6 +125,8 @@ SurfaceSdlGraphicsManager::SurfaceSdlGraphicsManager(SdlEventSource *sdlEventSou _hwscreen(0), #if SDL_VERSION_ATLEAST(2, 0, 0) _renderer(nullptr), _screenTexture(nullptr), +#else + _originalBitsPerPixel(0), #endif _screen(0), _tmpscreen(0), #ifdef USE_RGB_COLOR @@ -796,7 +798,15 @@ bool SurfaceSdlGraphicsManager::loadGFXMode() { _hwscreen = g_eventRec.getSurface(_videoMode.hardwareWidth, _videoMode.hardwareHeight); } else #endif - { + { + // Save the original bpp to be able to restore the video mode on unload +#if !SDL_VERSION_ATLEAST(2, 0, 0) + if (_originalBitsPerPixel == 0) { + const SDL_VideoInfo *videoInfo = SDL_GetVideoInfo(); + _originalBitsPerPixel = videoInfo->vfmt->BitsPerPixel; + } +#endif + _hwscreen = SDL_SetVideoMode(_videoMode.hardwareWidth, _videoMode.hardwareHeight, 16, _videoMode.fullscreen ? (SDL_FULLSCREEN|SDL_SWSURFACE) : SDL_SWSURFACE ); @@ -929,6 +939,13 @@ void SurfaceSdlGraphicsManager::unloadGFXMode() { } #endif DestroyScalers(); + +#if !SDL_VERSION_ATLEAST(2, 0, 0) + // Reset video mode to original + // This will ensure that any new graphic manager will use the initial BPP when listing available modes + if (_originalBitsPerPixel != 0) + SDL_SetVideoMode(_videoMode.screenWidth, _videoMode.screenHeight, _originalBitsPerPixel, _videoMode.fullscreen ? (SDL_FULLSCREEN | SDL_SWSURFACE) : SDL_SWSURFACE); +#endif } bool SurfaceSdlGraphicsManager::hotswapGFXMode() { diff --git a/backends/graphics/surfacesdl/surfacesdl-graphics.h b/backends/graphics/surfacesdl/surfacesdl-graphics.h index 4ba15a304b..2431ce8664 100644 --- a/backends/graphics/surfacesdl/surfacesdl-graphics.h +++ b/backends/graphics/surfacesdl/surfacesdl-graphics.h @@ -236,6 +236,9 @@ protected: }; VideoState _videoMode, _oldVideoMode; + // Original BPP to restore the video mode on unload + uint8 _originalBitsPerPixel; + /** Force full redraw on next updateScreen */ bool _forceFull; diff --git a/devtools/scumm-md5.txt b/devtools/scumm-md5.txt index 7a1a22b600..305c1724ed 100644 --- a/devtools/scumm-md5.txt +++ b/devtools/scumm-md5.txt @@ -127,6 +127,7 @@ indy3 Indiana Jones and the Last Crusade 66236cd1aec24e1d4aff4c4cc93b7e18 -1 fr DOS EGA EGA ?? v1.3, 25 Aug 89 Andrea Petrucci, Peter Eckerlein 89cfc425566003ff74b7dc7b3e6fd469 -1 fr DOS EGA EGA ?? v1.3, 25 Aug 89 Jorpho 69d70269fafc4445adbb0d223e4f9a3f 5361 en DOS EGA EGA v1.4, 11/07/89 (5.25\") Petr Maruska + 56e8c37a0a08c3a7076f82417461a877 -1 en DOS EGA EGA v1.4, 7 Nov 89 (3.5\") Paulo Vicente 6f6ef668c608c7f534fea6e6d3878dde -1 de DOS EGA EGA v1.4 from 19 Oct 89 dhewg, Peter Eckerlein eb700bb73ca1cc44a1ad5e4b1a4bdeaf 5361 de DOS EGA EGA PC-Spiele a.borque d62d248c3df6ec177405e2cb23d923b2 -1 it DOS EGA EGA v1.4 from 25 Nov 89 Andrea Petrucci, Peter Eckerlein @@ -293,12 +294,14 @@ atlantis Indiana Jones and the Fate of Atlantis 035deab53b47bc43abc763560d0f8d4b -1 en DOS Floppy Demo - 98744fe66ff730e8c2b3b1f58803ab0b -1 en DOS Floppy Demo - Simon Krumrein, sev + 12cdc256eae5a461bcc9a49975999841 -1 en DOS Floppy Demo - Paulo Vicente 99b6f822b0b2612415407865438697d6 -1 en DOS - Demo non-interactive 28d24a33448fab6795850bc9f159a4a2 11170 jp FM-TOWNS FM-TOWNS Demo non-interactive khalek, Fingolfin tentacle Day of the Tentacle acad97ab1c6fc2a5b2d98abf6db4a190 -1 en All? Floppy Floppy Version A ? 2723fea3dae0cb47768c424b145ae0e7 7932 en DOS Floppy Floppy Version B ? Andrej Sinicyn, Andrea Petrucci, Fingolfin + f0ccc12a8704bf57706b42a37f877128 -1 en DOS Floppy Floppy 1.6 Paulo Vicente 92b078d9d6d9d751da9c26b8b3075779 -1 fr DOS Floppy Floppy - Nicolas Sauzède, Andrea Petrucci 94aaedbb8f26d71ed3ad6dd34490e29e -1 fr DOS Floppy Floppy alt? Nicolas Joly 57b0d89af79befe1cabce3bece869e7f -1 de DOS Floppy Floppy - Andrej Sinicyn, Andrea Petrucci @@ -311,7 +314,7 @@ tentacle Day of the Tentacle 4fbbe9f64b8bc547503a379a301183ce -1 it All? - CD - Andrea Petrucci 883af4b0af4f77a92f1dcf1d0a283140 -1 es All? - CD - Andrea Petrucci cc04a076779379524ed4d9c5ee3c6fb1 282467632 en Mac - CD Mac bundle Fingolfin, Joachim Eberhard - ede149fda3edfc1dbd7347e0737cb583 -1 fr Mac - CD Mac bundle ThierryFR + ede149fda3edfc1dbd7347e0737cb583 282830409 fr Mac - CD Mac bundle ThierryFR, Thierry Crozat f73883f13b5a302749a5bad31d909780 -1 de Mac - CD Mac bundle morrissey c83079157ec765a28de445aec9768d60 7477 en All - Demo - Fingolfin @@ -334,7 +337,7 @@ samnmax Sam & Max Hit the Road 4ba7fb331296c283e73d8f5b2096e551 -1 es All? - CD - Andrea Petrucci d43352a805d78b5f4936c6d7779bf575 -1 ru DOS - CD - 166553538ff320c69edafeee29525419 199195304 en Mac - CD Mac bundle Joachim Eberhard - 3a5d13675e9a23aedac0bac7730f0ac1 -1 fr Mac - CD Mac bundle ThierryFR + 3a5d13675e9a23aedac0bac7730f0ac1 228446581 fr Mac - CD Mac bundle ThierryFR, Thierry Crozat c3196c5349e53e387aaff1533d95e53a -1 en DOS Floppy Demo - 0e4c5d54a0ad4b26132e78b5ea76642a 6485 en DOS Floppy Demo WIP Fingolfin @@ -355,6 +358,7 @@ ft Full Throttle e72bb4c2b613db2cf50f89ff6350e70a -1 es All? - - - fe381e45117878b1e942cb876b050fd6 513243679 en Mac - - Mac bundle Fingolfin 04401d747f1a2c1c4b388daff71ed378 535405461 de Mac - - Mac bundle Fingolfin + 403d2ec4d60d3cdae925e6cbf67716d6 489436643 fr Mac - - Mac bundle Thierry Crozat 32a433dea56b86a55b59e4ff7d755711 -1 en DOS Demo Demo - 9d7b67be003fea60be4dcbd193611936 11164 en Mac Demo Demo - Fingolfin @@ -666,6 +670,7 @@ pajama2 Pajama Sam 2: Thunder and Lightning Aren't so Frightening d4e79c3d8645b8266cd78c325bc35154 60557 us All - - - Kirben 6a60d395b78b205c93a956100b1bf5ae -1 de All HE 98.5 - - EdDammer 513f91a9dbe8d5490b39e56a3ac5bbdf -1 nl All HE 98.5 - - daniel9 + 2328be0317008ef047eed7912a4b0850 -1 gb Windows HE 98.5 - - Saleck 55f4e9402bec2bded383843123f37c5c -1 de Windows HE 98.5 - - WindlePoons e5563c8358443c4352fcddf7402a5e0a -1 fr Windows HE 98.5 - - gist974 c6907d44f1166941d982864cd42cdc89 -1 de All HE 99 - - nachbarnebenan diff --git a/engines/agi/agi.cpp b/engines/agi/agi.cpp index 1e663ec29a..2b5d7137bc 100644 --- a/engines/agi/agi.cpp +++ b/engines/agi/agi.cpp @@ -97,50 +97,62 @@ void AgiEngine::processEvents() { } break; case Common::EVENT_LBUTTONDOWN: - key = BUTTON_LEFT; - _mouse.button = kAgiMouseButtonLeft; - keyEnqueue(key); - _mouse.x = event.mouse.x; - _mouse.y = event.mouse.y; + if (_game.mouseEnabled) { + key = BUTTON_LEFT; + _mouse.button = kAgiMouseButtonLeft; + keyEnqueue(key); + _mouse.x = event.mouse.x; + _mouse.y = event.mouse.y; + } break; case Common::EVENT_RBUTTONDOWN: - key = BUTTON_RIGHT; - _mouse.button = kAgiMouseButtonRight; - keyEnqueue(key); - _mouse.x = event.mouse.x; - _mouse.y = event.mouse.y; + if (_game.mouseEnabled) { + key = BUTTON_RIGHT; + _mouse.button = kAgiMouseButtonRight; + keyEnqueue(key); + _mouse.x = event.mouse.x; + _mouse.y = event.mouse.y; + } break; case Common::EVENT_WHEELUP: - key = WHEEL_UP; - keyEnqueue(key); + if (_game.mouseEnabled) { + key = WHEEL_UP; + keyEnqueue(key); + } break; case Common::EVENT_WHEELDOWN: - key = WHEEL_DOWN; - keyEnqueue(key); + if (_game.mouseEnabled) { + key = WHEEL_DOWN; + keyEnqueue(key); + } break; case Common::EVENT_MOUSEMOVE: - _mouse.x = event.mouse.x; - _mouse.y = event.mouse.y; - - if (!_game.mouseFence.isEmpty()) { - if (_mouse.x < _game.mouseFence.left) - _mouse.x = _game.mouseFence.left; - if (_mouse.x > _game.mouseFence.right) - _mouse.x = _game.mouseFence.right; - if (_mouse.y < _game.mouseFence.top) - _mouse.y = _game.mouseFence.top; - if (_mouse.y > _game.mouseFence.bottom) - _mouse.y = _game.mouseFence.bottom; - - g_system->warpMouse(_mouse.x, _mouse.y); + if (_game.mouseEnabled) { + _mouse.x = event.mouse.x; + _mouse.y = event.mouse.y; + + if (!_game.mouseFence.isEmpty()) { + if (_mouse.x < _game.mouseFence.left) + _mouse.x = _game.mouseFence.left; + if (_mouse.x > _game.mouseFence.right) + _mouse.x = _game.mouseFence.right; + if (_mouse.y < _game.mouseFence.top) + _mouse.y = _game.mouseFence.top; + if (_mouse.y > _game.mouseFence.bottom) + _mouse.y = _game.mouseFence.bottom; + + g_system->warpMouse(_mouse.x, _mouse.y); + } } break; case Common::EVENT_LBUTTONUP: case Common::EVENT_RBUTTONUP: - _mouse.button = kAgiMouseButtonUp; - _mouse.x = event.mouse.x; - _mouse.y = event.mouse.y; + if (_game.mouseEnabled) { + _mouse.button = kAgiMouseButtonUp; + _mouse.x = event.mouse.x; + _mouse.y = event.mouse.y; + } break; case Common::EVENT_KEYDOWN: if (event.kbd.hasFlags(Common::KBD_CTRL) && event.kbd.keycode == Common::KEYCODE_d) { @@ -496,12 +508,15 @@ AgiBase::AgiBase(OSystem *syst, const AGIGameDescription *gameDesc) : Engine(sys // Assign default values to the config manager, in case settings are missing ConfMan.registerDefault("originalsaveload", "false"); ConfMan.registerDefault("altamigapalette", "false"); + ConfMan.registerDefault("mousesupport", "true"); _noSaveLoadAllowed = false; _rnd = new Common::RandomSource("agi"); _sound = 0; + _fontData = NULL; + initFeatures(); initVersion(); } @@ -550,6 +565,19 @@ AgiEngine::AgiEngine(OSystem *syst, const AGIGameDescription *gameDesc) : AgiBas memset(&_debug, 0, sizeof(struct AgiDebug)); memset(&_mouse, 0, sizeof(struct Mouse)); + _game.mouseEnabled = true; + if (!ConfMan.getBool("mousesupport")) { + // we effectively disable the mouse for games, that explicitly do not want mouse support to be enabled + _game.mouseEnabled = false; + } + + // We are currently using the custom font for all fanmade games + if (!(getFeatures() & (GF_FANMADE | GF_AGDS))) { + _fontData = fontData_Sierra; // original Sierra font + } else { + _fontData = fontData_FanGames; // our (own?) custom font, that supports umlauts etc. + } + _game._vm = this; _game.clockEnabled = false; @@ -708,7 +736,9 @@ Common::Error AgiBase::init() { } Common::Error AgiEngine::go() { - CursorMan.showMouse(true); + if (_game.mouseEnabled) { + CursorMan.showMouse(true); + } setTotalPlayTime(0); if (_game.state < STATE_LOADED) { diff --git a/engines/agi/agi.h b/engines/agi/agi.h index 6256de05d2..04e02dcf87 100644 --- a/engines/agi/agi.h +++ b/engines/agi/agi.h @@ -643,6 +643,7 @@ struct AgiGame { int simpleSave; /**< select simple savegames */ Common::Rect mouseFence; /**< rectangle set by fence.mouse command */ + bool mouseEnabled; /**< if mouse is supposed to be active */ // IF condition handling int testResult; @@ -780,6 +781,8 @@ protected: void initRenderMode(); + const uint8 *_fontData; + public: GfxMgr *_gfx; @@ -839,6 +842,8 @@ public: bool canLoadGameStateCurrently(); bool canSaveGameStateCurrently(); + + const uint8 *getFontData() { return _fontData; }; }; typedef void (*AgiCommand)(AgiGame *state, uint8 *p); diff --git a/engines/agi/detection.cpp b/engines/agi/detection.cpp index 3230b4e5d3..823ec7be66 100644 --- a/engines/agi/detection.cpp +++ b/engines/agi/detection.cpp @@ -138,21 +138,41 @@ static const PlainGameDescriptor agiGames[] = { {0, 0} }; -static const ExtraGuiOption agiExtraGuiOption = { - _s("Use original save/load screens"), - _s("Use the original save/load screens, instead of the ScummVM ones"), - "originalsaveload", - false -}; +#include "agi/detection_tables.h" -static const ExtraGuiOption agiExtraGuiOptionAmiga = { - _s("Use an alternative palette"), - _s("Use an alternative palette, common for all Amiga games. This was the old behavior"), - "altamigapalette", - false -}; +static const ADExtraGuiOptionsMap optionsList[] = { + { + GAMEOPTION_ORIGINAL_SAVELOAD, + { + _s("Use original save/load screens"), + _s("Use the original save/load screens, instead of the ScummVM ones"), + "originalsaveload", + false + } + }, + + { + GAMEOPTION_AMIGA_ALTERNATIVE_PALETTE, + { + _s("Use an alternative palette"), + _s("Use an alternative palette, common for all Amiga games. This was the old behavior"), + "altamigapalette", + false + } + }, + + { + GAMEOPTION_DISABLE_MOUSE, + { + _s("Mouse support"), + _s("Enables mouse support. Allows to use mouse for movement and in game menus."), + "mousesupport", + true + } + }, -#include "agi/detection_tables.h" + AD_EXTRA_GUI_OPTIONS_TERMINATOR +}; using namespace Agi; @@ -161,7 +181,7 @@ class AgiMetaEngine : public AdvancedMetaEngine { mutable Common::String _extra; public: - AgiMetaEngine() : AdvancedMetaEngine(Agi::gameDescriptions, sizeof(Agi::AGIGameDescription), agiGames) { + AgiMetaEngine() : AdvancedMetaEngine(Agi::gameDescriptions, sizeof(Agi::AGIGameDescription), agiGames, optionsList) { _singleid = "agi"; _guioptions = GUIO1(GUIO_NOSPEECH); } @@ -175,7 +195,6 @@ public: virtual bool hasFeature(MetaEngineFeature f) const; virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const; - virtual const ExtraGuiOptions getExtraGuiOptions(const Common::String &target) const; virtual SaveStateList listSaves(const char *target) const; virtual int getMaximumSaveSlot() const; virtual void removeSaveState(const char *target, int slot) const; @@ -234,14 +253,6 @@ bool AgiMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameD return res; } -const ExtraGuiOptions AgiMetaEngine::getExtraGuiOptions(const Common::String &target) const { - ExtraGuiOptions options; - options.push_back(agiExtraGuiOption); - if (target.contains("-amiga")) - options.push_back(agiExtraGuiOptionAmiga); - return options; -} - SaveStateList AgiMetaEngine::listSaves(const char *target) const { const uint32 AGIflag = MKTAG('A','G','I',':'); Common::SaveFileManager *saveFileMan = g_system->getSavefileManager(); diff --git a/engines/agi/detection_tables.h b/engines/agi/detection_tables.h index 2d7fba3507..0ae822a538 100644 --- a/engines/agi/detection_tables.h +++ b/engines/agi/detection_tables.h @@ -22,7 +22,16 @@ namespace Agi { -#define GAME_LVFPN(id,extra,fname,md5,size,lang,ver,features,gid,platform,interp) { \ +#define GAMEOPTION_ORIGINAL_SAVELOAD GUIO_GAMEOPTIONS1 +#define GAMEOPTION_AMIGA_ALTERNATIVE_PALETTE GUIO_GAMEOPTIONS2 +#define GAMEOPTION_DISABLE_MOUSE GUIO_GAMEOPTIONS3 +// TODO: properly implement GAMEOPTIONs + +#define GAMEOPTIONS_DEFAULT GUIO2(GAMEOPTION_ORIGINAL_SAVELOAD,GAMEOPTION_DISABLE_MOUSE) +#define GAMEOPTIONS_AMIGA GUIO2(GAMEOPTION_ORIGINAL_SAVELOAD,GAMEOPTION_AMIGA_ALTERNATIVE_PALETTE) +#define GAMEOPTIONS_FANMADE_MOUSE GUIO1(GAMEOPTION_ORIGINAL_SAVELOAD) + +#define GAME_LVFPN(id,extra,fname,md5,size,lang,ver,features,gid,platform,interp,guioptions) { \ { \ id, \ extra, \ @@ -30,7 +39,7 @@ namespace Agi { lang, \ platform, \ ADGF_NO_FLAGS, \ - GUIO0() \ + guioptions \ }, \ gid, \ interp, \ @@ -38,7 +47,7 @@ namespace Agi { ver \ } -#define GAME_LVFPNF(id,name,fname,md5,size,lang,ver,features,gid,platform,interp) { \ +#define GAME_LVFPNF(id,name,fname,md5,size,lang,ver,features,gid,platform,interp,guioptions) { \ { \ id, \ name, \ @@ -46,7 +55,7 @@ namespace Agi { lang, \ platform, \ ADGF_USEEXTRAASTITLE, \ - GUIO0() \ + guioptions \ }, \ gid, \ interp, \ @@ -54,43 +63,52 @@ namespace Agi { ver \ } -#define BOOTER2(id,extra,fname,md5,size,ver,gid) GAME_LVFPN(id,extra,fname,md5,size,Common::EN_ANY,ver,0,gid,Common::kPlatformDOS,GType_V2) -#define GAME(id,extra,md5,ver,gid) GAME_LVFPN(id,extra,"logdir",md5,-1,Common::EN_ANY,ver,0,gid,Common::kPlatformDOS,GType_V2) -#define GAME3(id,extra,fname,md5,ver,gid) GAME_LVFPN(id,extra,fname,md5,-1,Common::EN_ANY,ver,0,gid,Common::kPlatformDOS,GType_V3) +#define BOOTER2(id,extra,fname,md5,size,ver,gid) GAME_LVFPN(id,extra,fname,md5,size,Common::EN_ANY,ver,0,gid,Common::kPlatformDOS,GType_V2,GAMEOPTIONS_DEFAULT) +#define GAME(id,extra,md5,ver,gid) GAME_LVFPN(id,extra,"logdir",md5,-1,Common::EN_ANY,ver,0,gid,Common::kPlatformDOS,GType_V2,GAMEOPTIONS_DEFAULT) +#define GAME3(id,extra,fname,md5,ver,gid) GAME_LVFPN(id,extra,fname,md5,-1,Common::EN_ANY,ver,0,gid,Common::kPlatformDOS,GType_V3,GAMEOPTIONS_DEFAULT) -#define GAME_P(id,extra,md5,ver,gid,platform) GAME_LVFPN(id,extra,"logdir",md5,-1,Common::EN_ANY,ver,0,gid,platform,GType_V2) +#define GAME_P(id,extra,md5,ver,gid,platform) GAME_LVFPN(id,extra,"logdir",md5,-1,Common::EN_ANY,ver,0,gid,platform,GType_V2,GAMEOPTIONS_DEFAULT) +#define GAME_PO(id,extra,md5,ver,gid,platform,guioptions) GAME_LVFPN(id,extra,"logdir",md5,-1,Common::EN_ANY,ver,0,gid,platform,GType_V2,guioptions) -#define GAME_FP(id,extra,md5,ver,flags,gid,platform) GAME_LVFPN(id,extra,"logdir",md5,-1,Common::EN_ANY,ver,flags,gid,platform,GType_V2) +#define GAME_FP(id,extra,md5,ver,flags,gid,platform) GAME_LVFPN(id,extra,"logdir",md5,-1,Common::EN_ANY,ver,flags,gid,platform,GType_V2,GAMEOPTIONS_DEFAULT) +#define GAME_FPO(id,extra,md5,ver,flags,gid,platform,guioptions) GAME_LVFPN(id,extra,"logdir",md5,-1,Common::EN_ANY,ver,flags,gid,platform,GType_V2,guioptions) #define GAME_F(id,extra,md5,ver,flags,gid) GAME_FP(id,extra,md5,ver,flags,gid,Common::kPlatformDOS) +#define GAME_FO(id,extra,md5,ver,flags,gid,guioptions) GAME_FPO(id,extra,md5,ver,flags,gid,Common::kPlatformDOS,guioptions) -#define GAME_PS(id,extra,md5,size,ver,gid,platform) GAME_LVFPN(id,extra,"logdir",md5,size,Common::EN_ANY,ver,0,gid,platform,GType_V2) +#define GAME_PS(id,extra,md5,size,ver,gid,platform) GAME_LVFPN(id,extra,"logdir",md5,size,Common::EN_ANY,ver,0,gid,platform,GType_V2,GAMEOPTIONS_DEFAULT) -#define GAME_LPS(id,extra,md5,size,lang,ver,gid,platform) GAME_LVFPN(id,extra,"logdir",md5,size,lang,ver,0,gid,platform,GType_V2) +#define GAME_LPS(id,extra,md5,size,lang,ver,gid,platform) GAME_LVFPN(id,extra,"logdir",md5,size,lang,ver,0,gid,platform,GType_V2,GAMEOPTIONS_DEFAULT) -#define GAME_LFPS(id,extra,md5,size,lang,ver,flags,gid,platform) GAME_LVFPN(id,extra,"logdir",md5,size,lang,ver,flags,gid,platform,GType_V2) +#define GAME_LFPS(id,extra,md5,size,lang,ver,flags,gid,platform) GAME_LVFPN(id,extra,"logdir",md5,size,lang,ver,flags,gid,platform,GType_V2,GAMEOPTIONS_DEFAULT) -#define GAME3_P(id,extra,fname,md5,ver,flags,gid,platform) GAME_LVFPN(id,extra,fname,md5,-1,Common::EN_ANY,ver,flags,gid,platform,GType_V3) +#define GAME3_P(id,extra,fname,md5,ver,flags,gid,platform) GAME_LVFPN(id,extra,fname,md5,-1,Common::EN_ANY,ver,flags,gid,platform,GType_V3,GAMEOPTIONS_DEFAULT) +#define GAME3_PO(id,extra,fname,md5,ver,flags,gid,platform,guioptions) GAME_LVFPN(id,extra,fname,md5,-1,Common::EN_ANY,ver,flags,gid,platform,GType_V3,guioptions) -#define GAMEpre_P(id,extra,fname,md5,ver,gid,platform) GAME_LVFPN(id,extra,fname,md5,-1,Common::EN_ANY,ver,0,gid,platform,GType_PreAGI) +#define GAMEpre_P(id,extra,fname,md5,ver,gid,platform) GAME_LVFPN(id,extra,fname,md5,-1,Common::EN_ANY,ver,0,gid,platform,GType_PreAGI,GAMEOPTIONS_DEFAULT) +#define GAMEpre_PO(id,extra,fname,md5,ver,gid,platform,guioptions) GAME_LVFPN(id,extra,fname,md5,-1,Common::EN_ANY,ver,0,gid,platform,GType_PreAGI,guioptions) -#define GAMEpre_PS(id,extra,fname,md5,size,ver,gid,platform) GAME_LVFPN(id,extra,fname,md5,size,Common::EN_ANY,ver,0,gid,platform,GType_PreAGI) +#define GAMEpre_PS(id,extra,fname,md5,size,ver,gid,platform) GAME_LVFPN(id,extra,fname,md5,size,Common::EN_ANY,ver,0,gid,platform,GType_PreAGI,GAMEOPTIONS_DEFAULT) -#define GAME3_PS(id,extra,fname,md5,size,ver,flags,gid,platform) GAME_LVFPN(id,extra,fname,md5,size,Common::EN_ANY,ver,flags,gid,platform,GType_V3) +#define GAME3_PS(id,extra,fname,md5,size,ver,flags,gid,platform) GAME_LVFPN(id,extra,fname,md5,size,Common::EN_ANY,ver,flags,gid,platform,GType_V3,GAMEOPTIONS_DEFAULT) +#define GAME3_PSO(id,extra,fname,md5,size,ver,flags,gid,platform,guioptions) GAME_LVFPN(id,extra,fname,md5,size,Common::EN_ANY,ver,flags,gid,platform,GType_V3,guioptions) -#define FANMADE_ILVF(id,name,md5,lang,ver,features) GAME_LVFPNF(id,name,"logdir",md5,-1,lang,ver,(GF_FANMADE|features),GID_FANMADE,Common::kPlatformDOS,GType_V2) +#define FANMADE_ILVFO(id,name,md5,lang,ver,features,guioptions) GAME_LVFPNF(id,name,"logdir",md5,-1,lang,ver,(GF_FANMADE|features),GID_FANMADE,Common::kPlatformDOS,GType_V2,guioptions) -#define FANMADE_ISVP(id,name,md5,size,ver,platform) GAME_LVFPNF(id,name,"logdir",md5,size,Common::EN_ANY,ver,GF_FANMADE,GID_FANMADE,platform,GType_V2) -#define FANMADE_SVP(name,md5,size,ver,platform) FANMADE_ISVP("agi-fanmade",name,md5,size,ver,platform) +#define FANMADE_ISVPO(id,name,md5,size,ver,platform,guioptions) GAME_LVFPNF(id,name,"logdir",md5,size,Common::EN_ANY,ver,GF_FANMADE,GID_FANMADE,platform,GType_V2,guioptions) +#define FANMADE_SVP(name,md5,size,ver,platform) FANMADE_ISVPO("agi-fanmade",name,md5,size,ver,platform,GAMEOPTIONS_DEFAULT) -#define FANMADE_LVF(name,md5,lang,ver,features) FANMADE_ILVF("agi-fanmade",name,md5,lang,ver,features) +#define FANMADE_LVFO(name,md5,lang,ver,features,guioptions) FANMADE_ILVFO("agi-fanmade",name,md5,lang,ver,features,guioptions) -#define FANMADE_LF(name,md5,lang,features) FANMADE_LVF(name,md5,lang,0x2917,features) +#define FANMADE_LF(name,md5,lang,features) FANMADE_LVFO(name,md5,lang,0x2917,features,GAMEOPTIONS_DEFAULT) +#define FANMADE_LFO(name,md5,lang,features,guioptions) FANMADE_LVFO(name,md5,lang,0x2917,features,guioptions) #define FANMADE_IF(id,name,md5,features) FANMADE_ILVF(id,name,md5,Common::EN_ANY,0x2917,features) -#define FANMADE_V(name,md5,ver) FANMADE_LVF(name,md5,Common::EN_ANY,ver,0) +#define FANMADE_V(name,md5,ver) FANMADE_LVFO(name,md5,Common::EN_ANY,ver,0,GAMEOPTIONS_DEFAULT) #define FANMADE_F(name,md5,features) FANMADE_LF(name,md5,Common::EN_ANY,features) +#define FANMADE_FO(name,md5,features,guioptions) FANMADE_LFO(name,md5,Common::EN_ANY,features,guioptions) #define FANMADE_L(name,md5,lang) FANMADE_LF(name,md5,lang,0) #define FANMADE_I(id,name,md5) FANMADE_IF(id,name,md5,0) +#define FANMADE_O(name,md5,guioptions) FANMADE_FO(name,md5,0,guioptions) #define FANMADE(name,md5) FANMADE_F(name,md5,0) @@ -130,7 +148,7 @@ static const AGIGameDescription gameDescriptions[] = { Common::EN_ANY, Common::kPlatformDOS, ADGF_NO_FLAGS, - GUIO0() + GAMEOPTIONS_DEFAULT }, GID_BC, GType_V1, @@ -151,7 +169,7 @@ static const AGIGameDescription gameDescriptions[] = { Common::EN_ANY, Common::kPlatformDOS, ADGF_NO_FLAGS, - GUIO0() + GAMEOPTIONS_DEFAULT }, GID_BC, GType_V1, @@ -172,7 +190,7 @@ static const AGIGameDescription gameDescriptions[] = { Common::EN_ANY, Common::kPlatformDOS, ADGF_NO_FLAGS, - GUIO0() + GAMEOPTIONS_DEFAULT }, GID_BC, GType_V1, @@ -181,7 +199,7 @@ static const AGIGameDescription gameDescriptions[] = { }, // Black Cauldron (Amiga) 2.00 6/14/87 - GAME_P("bc", "2.00 1987-06-14", "7b01694af21213b4727bb94476f64eb5", 0x2440, GID_BC, Common::kPlatformAmiga), + GAME_PO("bc", "2.00 1987-06-14", "7b01694af21213b4727bb94476f64eb5", 0x2440, GID_BC, Common::kPlatformAmiga, GAMEOPTIONS_AMIGA), // Black Cauldron (Apple IIgs) 1.0O 2/24/89 (CE) // Menus not tested @@ -210,7 +228,7 @@ static const AGIGameDescription gameDescriptions[] = { // Donald Duck's Playground (Amiga) 1.0C // Menus not tested - GAME_P("ddp", "1.0C 1987-04-27", "550971d196f65190a5c760d2479406ef", 0x2272, GID_DDP, Common::kPlatformAmiga), + GAME_PO("ddp", "1.0C 1987-04-27", "550971d196f65190a5c760d2479406ef", 0x2272, GID_DDP, Common::kPlatformAmiga, GAMEOPTIONS_AMIGA), // Donald Duck's Playground (ST) 1.0A 8/8/86 // Menus not tested @@ -221,7 +239,7 @@ static const AGIGameDescription gameDescriptions[] = { GAME_PS("ddp", "1.0C 1986-06-09", "550971d196f65190a5c760d2479406ef", 132, 0x2272, GID_DDP, Common::kPlatformDOS), // Gold Rush! (Amiga) 1.01 1/13/89 aka 2.05 3/9/89 # 2.316 - GAME3_PS("goldrush", "1.01 1989-01-13 aka 2.05 1989-03-09", "dirs", "a1d4de3e75c2688c1e2ca2634ffc3bd8", 2399, 0x3149, 0, GID_GOLDRUSH, Common::kPlatformAmiga), + GAME3_PSO("goldrush", "1.01 1989-01-13 aka 2.05 1989-03-09", "dirs", "a1d4de3e75c2688c1e2ca2634ffc3bd8", 2399, 0x3149, 0, GID_GOLDRUSH, Common::kPlatformAmiga, GAMEOPTIONS_AMIGA), // Gold Rush! (Apple IIgs) 1.0M 2/28/89 (CE) aka 2.01 12/22/88 // Menus not tested @@ -252,7 +270,7 @@ static const AGIGameDescription gameDescriptions[] = { Common::EN_ANY, Common::kPlatformMacintosh, ADGF_NO_FLAGS, - GUIO0() + GAMEOPTIONS_DEFAULT }, GID_GOLDRUSH, GType_V3, @@ -269,7 +287,7 @@ static const AGIGameDescription gameDescriptions[] = { // King's Quest 1 (Amiga) 1.0U # 2.082 // The original game did not have menus, they are enabled under ScummVM - GAME_FP("kq1", "1.0U 1986", "246c695324f1c514aee2b904fa352fad", 0x2440, GF_MENUS, GID_KQ1, Common::kPlatformAmiga), + GAME_FPO("kq1", "1.0U 1986", "246c695324f1c514aee2b904fa352fad", 0x2440, GF_MENUS, GID_KQ1, Common::kPlatformAmiga, GAMEOPTIONS_AMIGA), // King's Quest 1 (ST) 1.0V // The original game did not have menus, they are enabled under ScummVM @@ -298,7 +316,7 @@ static const AGIGameDescription gameDescriptions[] = { GAME_P("kq2", "2.0A 1988-06-16 (CE)", "5203c8b95250a2ecfee93ddb99414753", 0x2917, GID_KQ2, Common::kPlatformApple2GS), // King's Quest 2 (Amiga) 2.0J - GAME_P("kq2", "2.0J 1987-01-29", "b866f0fab2fad91433a637a828cfa410", 0x2440, GID_KQ2, Common::kPlatformAmiga), + GAME_PO("kq2", "2.0J 1987-01-29", "b866f0fab2fad91433a637a828cfa410", 0x2440, GID_KQ2, Common::kPlatformAmiga, GAMEOPTIONS_AMIGA), // King's Quest 2 (Mac) 2.0R GAME_P("kq2", "2.0R 1988-03-23", "cbdb0083317c8e7cfb7ac35da4bc7fdc", 0x2440, GID_KQ2, Common::kPlatformMacintosh), @@ -324,7 +342,7 @@ static const AGIGameDescription gameDescriptions[] = { // King's Quest 3 (Amiga) 1.01 11/8/86 // The original game did not have menus, they are enabled under ScummVM - GAME_FP("kq3", "1.01 1986-11-08", "8ab343306df0e2d98f136be4e8cfd0ef", 0x2440, GF_MENUS, GID_KQ3, Common::kPlatformAmiga), + GAME_FPO("kq3", "1.01 1986-11-08", "8ab343306df0e2d98f136be4e8cfd0ef", 0x2440, GF_MENUS, GID_KQ3, Common::kPlatformAmiga, GAMEOPTIONS_AMIGA), // King's Quest 3 (ST) 1.02 11/18/86 // Does not have menus, crashes if menus are enforced. Therefore, ESC pauses the game @@ -340,7 +358,7 @@ static const AGIGameDescription gameDescriptions[] = { // Original pauses with ESC, has menus accessible with mouse. // ver = 0x3086 -> menus accessible with ESC or mouse, bug #2835581 (KQ3: Game Crash When Leaving Tavern as Fly). // ver = 0x3149 -> menus accessible with mouse, ESC pauses game, bug #2835581 disappears. - GAME3_PS("kq3", "2.15 1989-11-15", "dirs", "8e35bded2bc5cf20f5eec2b15523b155", 1805, 0x3149, 0, GID_KQ3, Common::kPlatformAmiga), + GAME3_PSO("kq3", "2.15 1989-11-15", "dirs", "8e35bded2bc5cf20f5eec2b15523b155", 1805, 0x3149, 0, GID_KQ3, Common::kPlatformAmiga, GAMEOPTIONS_AMIGA), // King's Quest 3 (PC) 1.01 11/08/86 [AGI 2.272] // Does not have menus, crashes if menus are enforced. Therefore, ESC pauses the game @@ -405,7 +423,7 @@ static const AGIGameDescription gameDescriptions[] = { GAME_P("lsl1", "1.04 1987-06-18", "8b579f8673fe9448c2538f5ed9887cf0", 0x2440, GID_LSL1, Common::kPlatformAtariST), // Leisure Suit Larry 1 (Amiga) 1.05 6/26/87 # x.yyy - GAME_P("lsl1", "1.05 1987-06-26", "3f5d26d8834ca49c147fb60936869d56", 0x2440, GID_LSL1, Common::kPlatformAmiga), + GAME_PO("lsl1", "1.05 1987-06-26", "3f5d26d8834ca49c147fb60936869d56", 0x2440, GID_LSL1, Common::kPlatformAmiga, GAMEOPTIONS_AMIGA), // Leisure Suit Larry 1 (IIgs) 1.0E GAME_P("lsl1", "1.0E 1987", "5f9e1dd68d626c6d303131c119582ad4", 0x2440, GID_LSL1, Common::kPlatformApple2GS), @@ -423,7 +441,7 @@ static const AGIGameDescription gameDescriptions[] = { GAME3_P("mh1", "2.0E 1988-10-05 (CE)", "mhdir", "2f1509f76f24e6e7d213f2dadebbf156", 0x3149, 0, GID_MH1, Common::kPlatformApple2GS), // Manhunter NY (Amiga) 1.06 3/18/89 - GAME3_P("mh1", "1.06 1989-03-18", "dirs", "92c6183042d1c2bb76236236a7d7a847", 0x3149, GF_OLDAMIGAV20, GID_MH1, Common::kPlatformAmiga), + GAME3_PO("mh1", "1.06 1989-03-18", "dirs", "92c6183042d1c2bb76236236a7d7a847", 0x3149, GF_OLDAMIGAV20, GID_MH1, Common::kPlatformAmiga, GAMEOPTIONS_AMIGA), // reported by Filippos (thebluegr) in bugreport #1654500 // Manhunter NY (PC 5.25") 1.22 8/31/88 [AGI 3.002.107] @@ -442,7 +460,7 @@ static const AGIGameDescription gameDescriptions[] = { GAME3_P("mh2", "1.0 1989-07-29", "mh2dir", "5e3581495708b952fea24438a6c7e040", 0x3149, 0, GID_MH1, Common::kPlatformAtariST), // Manhunter SF (Amiga) 3.06 8/17/89 # 2.333 - GAME3_PS("mh2", "3.06 1989-08-17", "dirs", "b412e8a126368b76696696f7632d4c16", 2573, 0x3086, GF_OLDAMIGAV20, GID_MH2, Common::kPlatformAmiga), + GAME3_PSO("mh2", "3.06 1989-08-17", "dirs", "b412e8a126368b76696696f7632d4c16", 2573, 0x3086, GF_OLDAMIGAV20, GID_MH2, Common::kPlatformAmiga, GAMEOPTIONS_AMIGA), // Manhunter SF (PC 5.25") 3.03 8/17/89 [AGI 3.002.149] GAME3("mh2", "3.03 1989-08-17 5.25\"", "mh2dir", "b90e4795413c43de469a715fb3c1fa93", 0x3149, GID_MH2), @@ -464,7 +482,7 @@ static const AGIGameDescription gameDescriptions[] = { // Mixed-Up Mother Goose (Amiga) 1.1 // Problematic: crashes // Menus not tested - GAME3_PS("mixedup", "1.1 1986-12-10", "dirs", "5c1295fe6daaf95831195ba12894dbd9", 2021, 0x3086, 0, GID_MIXEDUP, Common::kPlatformAmiga), + GAME3_PSO("mixedup", "1.1 1986-12-10", "dirs", "5c1295fe6daaf95831195ba12894dbd9", 2021, 0x3086, 0, GID_MIXEDUP, Common::kPlatformAmiga, GAMEOPTIONS_AMIGA), #endif // Mixed Up Mother Goose (IIgs) @@ -486,7 +504,7 @@ static const AGIGameDescription gameDescriptions[] = { GAME_P("pq1", "2.0B 1988-04-21", "e7c175918372336461e3811d594f482f", 0x2917, GID_PQ1, Common::kPlatformApple2GS), // Police Quest 1 (Amiga) 2.0B 2/22/89 # 2.310 - GAME3_PS("pq1", "2.0B 1989-02-22", "dirs", "cfa93e5f2aa7378bddd10ad6746a2ffb", 1613, 0x3149, 0, GID_PQ1, Common::kPlatformAmiga), + GAME3_PSO("pq1", "2.0B 1989-02-22", "dirs", "cfa93e5f2aa7378bddd10ad6746a2ffb", 1613, 0x3149, 0, GID_PQ1, Common::kPlatformAmiga, GAMEOPTIONS_AMIGA), // Police Quest 1 (IIgs) 2.0A-88318 GAME_P("pq1", "2.0A 1988-03-18", "8994e39d0901de3d07cecfb954075bb5", 0x2917, GID_PQ1, Common::kPlatformApple2GS), @@ -524,7 +542,7 @@ static const AGIGameDescription gameDescriptions[] = { // Space Quest 1 (Amiga) 1.2 # 2.082 // The original game did not have menus, they are enabled under ScummVM - GAME_FP("sq1", "1.2 1986", "0b216d931e95750f1f4837d6a4b821e5", 0x2440, GF_MENUS | GF_OLDAMIGAV20, GID_SQ1, Common::kPlatformAmiga), + GAME_FPO("sq1", "1.2 1986", "0b216d931e95750f1f4837d6a4b821e5", 0x2440, GF_MENUS | GF_OLDAMIGAV20, GID_SQ1, Common::kPlatformAmiga, GAMEOPTIONS_AMIGA), // Space Quest 1 (Mac) 1.5D GAME_P("sq1", "1.5D 1987-04-02", "ce88419aadd073d1c6682d859b3d8aa2", 0x2440, GID_SQ1, Common::kPlatformMacintosh), @@ -570,7 +588,7 @@ static const AGIGameDescription gameDescriptions[] = { Common::EN_ANY, Common::kPlatformAmiga, ADGF_NO_FLAGS, - GUIO0() + GAMEOPTIONS_AMIGA }, GID_SQ2, GType_V2, @@ -616,7 +634,7 @@ static const AGIGameDescription gameDescriptions[] = { GAMEpre_P("winnie", "", "title.pic", "2e7900c1ccaa7671d65405f6d1efed30", 0x0000, GID_WINNIE, Common::kPlatformDOS), // Winnie the Pooh in the Hundred Acre Wood (Amiga) - GAMEpre_P("winnie", "", "title", "2e7900c1ccaa7671d65405f6d1efed30", 0x0000, GID_WINNIE, Common::kPlatformAmiga), + GAMEpre_PO("winnie", "", "title", "2e7900c1ccaa7671d65405f6d1efed30", 0x0000, GID_WINNIE, Common::kPlatformAmiga, GAMEOPTIONS_AMIGA), // Winnie the Pooh in the Hundred Acre Wood (C64) GAMEpre_P("winnie", "", "title.pic", "d4eb97cffc866110f71e1ec9f84fe643", 0x0000, GID_WINNIE, Common::kPlatformC64), @@ -630,15 +648,15 @@ static const AGIGameDescription gameDescriptions[] = { // Xmas Card 1986 (CoCo3 360k) [AGI 2.072] GAME_PS("xmascard", "", "25ad35e9628fc77e5e0dd35852a272b6", 768, 0x2440, GID_XMASCARD, Common::kPlatformCoCo3), - FANMADE_F("2 Player Demo", "4279f46b3cebd855132496476b1d2cca", GF_AGIMOUSE), + FANMADE_FO("2 Player Demo", "4279f46b3cebd855132496476b1d2cca", GF_AGIMOUSE, GAMEOPTIONS_FANMADE_MOUSE), FANMADE("AGI Combat", "0be6a8a9e19203dcca0067d280798871"), FANMADE("AGI Contest 1 Template", "d879aed25da6fc655564b29567358ae2"), FANMADE("AGI Contest 2 Template", "5a2fb2894207eff36c72f5c1b08bcc07"), - FANMADE("AGI Mouse Demo 0.60 demo 1", "c07e2519de674c67386cb2cc6f2e3904"), - FANMADE("AGI Mouse Demo 0.60 demo 2", "cc49d8b88ed6faf4f53ce92c84e0fe1b"), - FANMADE("AGI Mouse Demo 0.70", "3497c291e4afb6f758e61740678a2aec"), - FANMADE_F("AGI Mouse Demo 1.00", "20397f0bf0ef936f416bb321fb768fc7", GF_AGIMOUSE), - FANMADE_F("AGI Mouse Demo 1.10", "f4ad396b496d6167635ad0b410312ab8", GF_AGIMOUSE|GF_AGIPAL), + FANMADE_O("AGI Mouse Demo 0.60 demo 1", "c07e2519de674c67386cb2cc6f2e3904", GAMEOPTIONS_FANMADE_MOUSE), + FANMADE_O("AGI Mouse Demo 0.60 demo 2", "cc49d8b88ed6faf4f53ce92c84e0fe1b", GAMEOPTIONS_FANMADE_MOUSE), + FANMADE_O("AGI Mouse Demo 0.70", "3497c291e4afb6f758e61740678a2aec", GAMEOPTIONS_FANMADE_MOUSE), + FANMADE_FO("AGI Mouse Demo 1.00", "20397f0bf0ef936f416bb321fb768fc7", GF_AGIMOUSE, GAMEOPTIONS_FANMADE_MOUSE), + FANMADE_FO("AGI Mouse Demo 1.10", "f4ad396b496d6167635ad0b410312ab8", GF_AGIMOUSE|GF_AGIPAL, GAMEOPTIONS_FANMADE_MOUSE), FANMADE("AGI Piano (v1.0)", "8778b3d89eb93c1d50a70ef06ef10310"), FANMADE("AGI Quest (v1.46-TJ0)", "1cf1a5307c1a0a405f5039354f679814"), GAME("tetris", "", "7a874e2db2162e7a4ce31c9130248d8a", 0x2917, GID_FANMADE), @@ -657,15 +675,15 @@ static const AGIGameDescription gameDescriptions[] = { FANMADE("Al Pond 1 - Al Lives Forever (v1.0)", "e8921c3043b749b056ff51f56d1b451b"), FANMADE("Al Pond 1 - Al Lives Forever (v1.3)", "fb4699474054962e0dbfb4cf12ca52f6"), FANMADE("Apocalyptic Quest (v0.03 Teaser)", "42ced528b67965d3bc3b52c635f94a57"), - FANMADE_F("Apocalyptic Quest (v4.00 Alpha 1)", "e15581628d84949b8d352d224ec3184b", GF_AGIMOUSE), - FANMADE_F("Apocalyptic Quest (v4.00 Alpha 2)", "0eee850005860e46345b38fea093d194", GF_AGIMOUSE), - FANMADE_F("Band Quest (Demo)", "7326abefd793571cc17ed0db647bdf34", GF_AGIMOUSE), - FANMADE_F("Band Quest (Early Demo)", "de4758dd34676b248c8301b32d93bc6f", GF_AGIMOUSE), + FANMADE_FO("Apocalyptic Quest (v4.00 Alpha 1)", "e15581628d84949b8d352d224ec3184b", GF_AGIMOUSE, GAMEOPTIONS_FANMADE_MOUSE), + FANMADE_FO("Apocalyptic Quest (v4.00 Alpha 2)", "0eee850005860e46345b38fea093d194", GF_AGIMOUSE, GAMEOPTIONS_FANMADE_MOUSE), + FANMADE_FO("Band Quest (Demo)", "7326abefd793571cc17ed0db647bdf34", GF_AGIMOUSE, GAMEOPTIONS_FANMADE_MOUSE), + FANMADE_FO("Band Quest (Early Demo)", "de4758dd34676b248c8301b32d93bc6f", GF_AGIMOUSE, GAMEOPTIONS_FANMADE_MOUSE), FANMADE("Beyond the Titanic 2", "9b8de38dc64ffb3f52b7877ea3ebcef9"), FANMADE("Biri Quest 1", "1b08f34f2c43e626c775c9d6649e2f17"), FANMADE("Bob The Farmboy", "e4b7df9d0830addee5af946d380e66d7"), - FANMADE_F("Boring Man 1: The Toad to Robinland", "d74481cbd227f67ace37ce6a5493039f", GF_AGIMOUSE), - FANMADE_F("Boring Man 2: Ho Man! This Game Sucks!", "250032ba105bdf7c1bc4fed767c2d37e", GF_AGIMOUSE), + FANMADE_FO("Boring Man 1: The Toad to Robinland", "d74481cbd227f67ace37ce6a5493039f", GF_AGIMOUSE, GAMEOPTIONS_FANMADE_MOUSE), + FANMADE_FO("Boring Man 2: Ho Man! This Game Sucks!", "250032ba105bdf7c1bc4fed767c2d37e", GF_AGIMOUSE, GAMEOPTIONS_FANMADE_MOUSE), FANMADE("Botz", "a8fabe4e807adfe5ec02bfec6d983695"), FANMADE("Brian's Quest (v1.0)", "0964aa79b9cdcff7f33a12b1d7e04b9c"), FANMADE("CPU-21 (v1.0)", "35b7cdb4d17e890e4c52018d96e9cbf4"), @@ -676,12 +694,12 @@ static const AGIGameDescription gameDescriptions[] = { FANMADE("Coco Coq (English) - Coco Coq In Grostesteing's Base (v.1.0.3)", "97631f8e710544a58bd6da9e780f9320"), FANMADE_L("Coco Coq (French) - Coco Coq Dans la Base de Grostesteing (v1.0.2)", "ef579ebccfe5e356f9a557eb3b2d8649", Common::FR_FRA), FANMADE("Corby's Murder Mystery (v1.0)", "4ebe62ac24c5a8c7b7898c8eb070efe5"), - FANMADE_F("DG: The AGIMouse Adventure (English v1.1)", "efe453b92bc1487ea69fbebede4d5f26", GF_AGIMOUSE|GF_AGIPAL), - FANMADE_LF("DG: The AGIMouse Adventure (French v1.1)", "eb3d17ca466d672cbb95947e8d6e846a", Common::FR_FRA, GF_AGIMOUSE|GF_AGIPAL), + FANMADE_FO("DG: The AGIMouse Adventure (English v1.1)", "efe453b92bc1487ea69fbebede4d5f26", GF_AGIMOUSE|GF_AGIPAL, GAMEOPTIONS_FANMADE_MOUSE), + FANMADE_LFO("DG: The AGIMouse Adventure (French v1.1)", "eb3d17ca466d672cbb95947e8d6e846a", Common::FR_FRA, GF_AGIMOUSE|GF_AGIPAL, GAMEOPTIONS_FANMADE_MOUSE), FANMADE("DG: The Adventure Game (English v1.1)", "0d6376d493fa7a21ec4da1a063e12b25"), FANMADE_L("DG: The Adventure Game (French v1.1)", "258bdb3bb8e61c92b71f2f456cc69e23", Common::FR_FRA), FANMADE("Dashiki (16 Colors)", "9b2c7b9b0283ab9f12bedc0cb6770a07"), - FANMADE_F("Dashiki (256 Colors)", "c68052bb209e23b39b55ff3d759958e6", GF_AGIMOUSE|GF_AGI256), + FANMADE_FO("Dashiki (256 Colors)", "c68052bb209e23b39b55ff3d759958e6", GF_AGIMOUSE|GF_AGI256, GAMEOPTIONS_FANMADE_MOUSE), FANMADE("Date Quest 1 (v1.0)", "ba3dcb2600645be53a13170aa1a12e69"), FANMADE("Date Quest 2 (v1.0 Demo)", "1602d6a2874856e928d9a8c8d2d166e9"), FANMADE("Date Quest 2 (v1.0)", "f13f6fc85aa3e6e02b0c20408fb63b47"), @@ -708,20 +726,20 @@ static const AGIGameDescription gameDescriptions[] = { FANMADE("Good Man (demo v3.41)", "3facd8a8f856b7b6e0f6c3200274d88c"), GAME_LVFPNF("agi-fanmade", "Groza (russian) [AGDS sample]", "logdir", "421da3a18004122a966d64ab6bd86d2e", -1, - Common::RU_RUS, 0x2440, GF_AGDS, GID_FANMADE, Common::kPlatformDOS,GType_V2), + Common::RU_RUS, 0x2440, GF_AGDS, GID_FANMADE, Common::kPlatformDOS,GType_V2,GAMEOPTIONS_DEFAULT), GAME_LVFPNF("agi-fanmade", "Get Outta Space Quest", "logdir", "aaea5b4a348acb669d13b0e6f22d4dc9", -1, - Common::EN_ANY, 0x2440, GF_FANMADE, GID_GETOUTTASQ, Common::kPlatformDOS,GType_V2), + Common::EN_ANY, 0x2440, GF_FANMADE, GID_GETOUTTASQ, Common::kPlatformDOS,GType_V2,GAMEOPTIONS_DEFAULT), - FANMADE_F("Half-Death - Terror At White-Mesa", "b62c05d0ace878261392073f57ae788c", GF_AGIMOUSE), + FANMADE_FO("Half-Death - Terror At White-Mesa", "b62c05d0ace878261392073f57ae788c", GF_AGIMOUSE, GAMEOPTIONS_FANMADE_MOUSE), FANMADE("Hank's Quest (v1.0 English) - Victim of Society", "64c15b3d0483d17888129100dc5af213"), FANMADE("Hank's Quest (v1.1 English) - Victim of Society", "86d1f1dd9b0c4858d096e2a60cca8a14"), FANMADE_L("Hank's Quest (v1.81 Dutch) - Slachtoffer Van Het Gebeuren", "41e53972d55ff3dff9e90d15fe1b659f", Common::NL_NLD), FANMADE("Hank's Quest (v1.81 English) - Victim of Society", "7a776383282f62a57c3a960dafca62d1"), FANMADE("Herbao (v0.2)", "6a5186fc8383a9060517403e85214fc2"), - FANMADE_F("Hitler's Legacy (v.0004q)", "a412881269ba34584bd0a3268e5a9863", GF_AGIMOUSE), + FANMADE_FO("Hitler's Legacy (v.0004q)", "a412881269ba34584bd0a3268e5a9863", GF_AGIMOUSE, GAMEOPTIONS_FANMADE_MOUSE), FANMADE("Hobbits", "4a1c1ef3a7901baf0ab45fde0cfadd89"), - FANMADE_F("Isabella Coq - A Present For My Dad", "55c6819f2330c4d5d6459874c9f123d9", GF_AGIMOUSE), + FANMADE_FO("Isabella Coq - A Present For My Dad", "55c6819f2330c4d5d6459874c9f123d9", GF_AGIMOUSE, GAMEOPTIONS_FANMADE_MOUSE), FANMADE("Jack & Julia - VAMPYR", "8aa0b9a26f8d5a4421067ab8cc3706f6"), FANMADE("Jeff's Quest (v.5 alpha Jun 1)", "10f1720eed40c12b02a0f32df3e72ded"), FANMADE("Jeff's Quest (v.5 alpha May 31)", "51ff71c0ed90db4e987a488ed3bf0551"), @@ -730,8 +748,8 @@ static const AGIGameDescription gameDescriptions[] = { FANMADE("Jiggy Jiggy Uh! Uh!", "bc331588a71e7a1c8840f6cc9b9487e4"), FANMADE("Jimmy In: The Alien Attack (v0.1)", "a4e9db0564a494728de7873684a4307c"), FANMADE("Joe McMuffin In \"What's Cooking, Doc\" (v1.0)", "8a3de7e61a99cb605fa6d233dd91c8e1"), - FANMADE_LVF("Jolimie, le Village Maudit (v0.5)", "21818501636b3cb8ad5de5c1a66de5c2", Common::FR_FRA, 0x2936, GF_AGIMOUSE|GF_AGIPAL), - FANMADE_LVF("Jolimie, le Village Maudit (v1.1)", "68d7aef1161bb5972fe03efdf29ccb7f", Common::FR_FRA, 0x2936, GF_AGIMOUSE|GF_AGIPAL), + FANMADE_LVFO("Jolimie, le Village Maudit (v0.5)", "21818501636b3cb8ad5de5c1a66de5c2", Common::FR_FRA, 0x2936, GF_AGIMOUSE|GF_AGIPAL, GAMEOPTIONS_FANMADE_MOUSE), + FANMADE_LVFO("Jolimie, le Village Maudit (v1.1)", "68d7aef1161bb5972fe03efdf29ccb7f", Common::FR_FRA, 0x2936, GF_AGIMOUSE|GF_AGIPAL, GAMEOPTIONS_FANMADE_MOUSE), FANMADE("Journey Of Chef", "aa0a0b5a6364801ae65fdb96d6741df5"), FANMADE("Jukebox (v1.0)", "c4b9c5528cc67f6ba777033830de7751"), FANMADE("Justin Quest (v1.0 in development)", "103050989da7e0ffdc1c5e1793a4e1ec"), @@ -747,18 +765,18 @@ static const AGIGameDescription gameDescriptions[] = { FANMADE("MD Quest - The Search for Michiel (v0.10)", "2a6fcb21d2b5e4144c38ed817fabe8ee"), FANMADE("Maale Adummin Quest", "ddfbeb33feb7cf78504fe4dba14ec63b"), FANMADE("Monkey Man", "2322d03f997e8cc235d4578efff69cfa"), - FANMADE_F("Napalm Quest (v0.5)", "b659afb491d967bb34810d1c6ce22093", GF_AGIMOUSE), + FANMADE_FO("Napalm Quest (v0.5)", "b659afb491d967bb34810d1c6ce22093", GF_AGIMOUSE, GAMEOPTIONS_FANMADE_MOUSE), FANMADE("Naturette 1 (English v1.2)", "0a75884e7f010974a230bdf269651117"), FANMADE("Naturette 1 (English v1.3)", "f15bbf999ac55ebd404aa1eb84f7c1d9"), FANMADE_L("Naturette 1 (French v1.2)", "d3665622cc41aeb9c7ecf4fa43f20e53", Common::FR_FRA), - FANMADE_F("Naturette 2: Daughter of the Moon (v1.0)", "bdf76a45621c7f56d1c9d40292c6137a", GF_AGIMOUSE|GF_AGIPAL), - FANMADE_F("Naturette 3: Adventure in Treeworld (v1.0a)", "6dbb0e7fc75fec442e6d9e5a06f1530e", GF_AGIMOUSE|GF_AGIPAL), - FANMADE_F("Naturette 4: From a Planet to Another Planet (Not Finished)", "13be8cd9cf35aeff0a39b8757057fbc8", GF_AGIMOUSE), + FANMADE_FO("Naturette 2: Daughter of the Moon (v1.0)", "bdf76a45621c7f56d1c9d40292c6137a", GF_AGIMOUSE|GF_AGIPAL, GAMEOPTIONS_FANMADE_MOUSE), + FANMADE_FO("Naturette 3: Adventure in Treeworld (v1.0a)", "6dbb0e7fc75fec442e6d9e5a06f1530e", GF_AGIMOUSE|GF_AGIPAL, GAMEOPTIONS_FANMADE_MOUSE), + FANMADE_FO("Naturette 4: From a Planet to Another Planet (Not Finished)", "13be8cd9cf35aeff0a39b8757057fbc8", GF_AGIMOUSE, GAMEOPTIONS_FANMADE_MOUSE), // FIXME: Actually Naturette 4 has both English and French language support built into it. How to add that information? - FANMADE_F("Naturette 4: From a Planet to Another Planet (2007-10-05)", "8253706b6ef5423a79413b216760297c", GF_AGIMOUSE|GF_AGIPAL), + FANMADE_FO("Naturette 4: From a Planet to Another Planet (2007-10-05)", "8253706b6ef5423a79413b216760297c", GF_AGIMOUSE|GF_AGIPAL, GAMEOPTIONS_FANMADE_MOUSE), FANMADE("New AGI Hangman Test", "d69c0e9050ccc29fd662b74d9fc73a15"), FANMADE("Nick's Quest - In Pursuit of QuakeMovie (v2.1 Gold)", "e29cbf9222551aee40397fabc83eeca0"), - FANMADE_F("Open Mic Night (v0.1)", "70000a2f67aac27d1133d019df70246d", GF_AGIMOUSE|GF_AGIPAL), + FANMADE_FO("Open Mic Night (v0.1)", "70000a2f67aac27d1133d019df70246d", GF_AGIMOUSE|GF_AGIPAL, GAMEOPTIONS_FANMADE_MOUSE), FANMADE("Operation: Recon", "0679ce8405411866ccffc8a6743370d0"), FANMADE("Patrick's Quest (Demo v1.0)", "f254f5b894b98fec5f92acc07fb62841"), FANMADE("Phantasmagoria", "87d20c1c11aee99a4baad3797b63146b"), @@ -802,14 +820,14 @@ static const AGIGameDescription gameDescriptions[] = { FANMADE("Save Santa (v1.3)", "f8afdb6efc5af5e7c0228b44633066af"), FANMADE("Schiller (preview 1)", "ade39dea968c959cfebe1cf935d653e9"), FANMADE("Schiller (preview 2)", "62cd1f8fc758bf6b4aa334e553624cef"), - GAME_F("serguei1", "v1.0", "b86725f067e456e10cdbdf5f58e01dec", 0x2917, GF_FANMADE|GF_AGIMOUSE|GF_AGIPAL, GID_FANMADE), + GAME_FO("serguei1", "v1.0", "b86725f067e456e10cdbdf5f58e01dec", 0x2917, GF_FANMADE|GF_AGIMOUSE|GF_AGIPAL, GID_FANMADE, GAMEOPTIONS_FANMADE_MOUSE), // FIXME: The following two entries have identical MD5 checksums? - GAME_F("serguei1", "v1.1 2002 Sep 5", "91975c1fb4b13b0f9a8e9ff74731030d", 0x2917, GF_FANMADE|GF_AGIMOUSE|GF_AGIPAL, GID_FANMADE), - GAME_F("serguei1", "v1.1 2003 Apr 10", "91975c1fb4b13b0f9a8e9ff74731030d", 0x2917, GF_FANMADE|GF_AGIMOUSE|GF_AGIPAL, GID_FANMADE), - GAME_F("serguei2", "v0.1.1 Demo", "906ccbc2ddedb29b63141acc6d10cd28", 0x2917, GF_FANMADE|GF_AGIMOUSE, GID_FANMADE), - GAME_F("serguei2", "v1.3.1 Demo (March 22nd 2008)", "ad1308fcb8f48723cd388e012ebf5e20", 0x2917, GF_FANMADE|GF_AGIMOUSE|GF_AGIPAL, GID_FANMADE), + GAME_FO("serguei1", "v1.1 2002 Sep 5", "91975c1fb4b13b0f9a8e9ff74731030d", 0x2917, GF_FANMADE|GF_AGIMOUSE|GF_AGIPAL, GID_FANMADE, GAMEOPTIONS_FANMADE_MOUSE), + GAME_FO("serguei1", "v1.1 2003 Apr 10", "91975c1fb4b13b0f9a8e9ff74731030d", 0x2917, GF_FANMADE|GF_AGIMOUSE|GF_AGIPAL, GID_FANMADE, GAMEOPTIONS_FANMADE_MOUSE), + GAME_FO("serguei2", "v0.1.1 Demo", "906ccbc2ddedb29b63141acc6d10cd28", 0x2917, GF_FANMADE|GF_AGIMOUSE, GID_FANMADE, GAMEOPTIONS_FANMADE_MOUSE), + GAME_FO("serguei2", "v1.3.1 Demo (March 22nd 2008)", "ad1308fcb8f48723cd388e012ebf5e20", 0x2917, GF_FANMADE|GF_AGIMOUSE|GF_AGIPAL, GID_FANMADE, GAMEOPTIONS_FANMADE_MOUSE), FANMADE("Shifty (v1.0)", "2a07984d27b938364bf6bd243ac75080"), - FANMADE_F("Sliding Tile Game (v1.00)", "949bfff5d8a81c3139152eed4d84ca75", GF_AGIMOUSE), + FANMADE_FO("Sliding Tile Game (v1.00)", "949bfff5d8a81c3139152eed4d84ca75", GF_AGIMOUSE, GAMEOPTIONS_FANMADE_MOUSE), FANMADE("Snowboarding Demo (v1.0)", "24bb8f29f1eddb5c0a099705267c86e4"), FANMADE("Solar System Tour", "b5a3d0f392dfd76a6aa63f3d5f578403"), FANMADE("Sorceror's Appraisal", "fe62615557b3cb7b08dd60c9d35efef1"), @@ -819,7 +837,7 @@ static const AGIGameDescription gameDescriptions[] = { GAME("sqx", "v10.0 Feb 05", "c992ae2f8ab18360404efdf16fa9edd1", 0x2917, GID_FANMADE), GAME("sqx", "v10.0 Jul 18", "812edec45cefad559d190ffde2f9c910", 0x2917, GID_FANMADE), GAME_PS("sqx", "", "f0a59044475a5fa37c055d8c3eb4d1a7", 768, 0x2440, GID_FANMADE, Common::kPlatformCoCo3), - FANMADE_F("Space Quest 3.5", "c077bc28d7b36213dd99dc9ecb0147fc", GF_AGIMOUSE|GF_AGIPAL), + FANMADE_FO("Space Quest 3.5", "c077bc28d7b36213dd99dc9ecb0147fc", GF_AGIMOUSE|GF_AGIPAL, GAMEOPTIONS_FANMADE_MOUSE), FANMADE_F("Space Trek (v1.0)", "807a1aeadb2ace6968831d36ab5ea37a", GF_CLIPCOORDS), FANMADE("Special Delivery", "88764dfe61126b8e73612c851b510a33"), FANMADE("Speeder Bike Challenge (v1.0)", "2deb25bab379285ca955df398d96c1e7"), @@ -862,7 +880,7 @@ static const AGIGameDescription gameDescriptions[] = { Common::EN_ANY, Common::kPlatformDOS, ADGF_USEEXTRAASTITLE, - GUIO0() + GAMEOPTIONS_DEFAULT }, GID_FANMADE, GType_V3, @@ -890,7 +908,7 @@ static AGIGameDescription g_fallbackDesc = { Common::UNK_LANG, Common::kPlatformDOS, ADGF_NO_FLAGS, - GUIO0() + GAMEOPTIONS_DEFAULT }, GID_FANMADE, GType_V2, diff --git a/engines/agi/font.h b/engines/agi/font.h index c77d8cf0c3..0e6b15f06b 100644 --- a/engines/agi/font.h +++ b/engines/agi/font.h @@ -26,7 +26,7 @@ namespace Agi { // 8x8 font patterns -static const uint8 curFont[] = { +static const uint8 fontData_Sierra[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x7E, // cursor hollow 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, // cursor solid @@ -59,6 +59,267 @@ static const uint8 curFont[] = { 0x00, 0x24, 0x42, 0xFF, 0x42, 0x24, 0x00, 0x00, 0x00, 0x10, 0x38, 0x7C, 0xFE, 0xFE, 0x00, 0x00, 0x00, 0xFE, 0xFE, 0x7C, 0x38, 0x10, 0x00, 0x00, + // original sierra font starts here + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x20 Space + 0x30, 0x78, 0x78, 0x30, 0x30, 0x00, 0x30, 0x00, + 0x6C, 0x6C, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x6C, 0x6C, 0xFE, 0x6C, 0xFE, 0x6C, 0x6C, 0x00, + 0x30, 0x7C, 0xC0, 0x78, 0x0C, 0xF8, 0x30, 0x00, + 0x00, 0xC6, 0xCC, 0x18, 0x30, 0x66, 0xC6, 0x00, + 0x38, 0x6C, 0x38, 0x76, 0xDC, 0xCC, 0x76, 0x00, + 0x60, 0x60, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x30, 0x60, 0x60, 0x60, 0x30, 0x18, 0x00, + 0x60, 0x30, 0x18, 0x18, 0x18, 0x30, 0x60, 0x00, + 0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00, + 0x00, 0x30, 0x30, 0xFC, 0x30, 0x30, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x60, + 0x00, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x00, + 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC0, 0x80, 0x00, + 0x7C, 0xC6, 0xCE, 0xDE, 0xF6, 0xE6, 0x7C, 0x00, // 0x30 + 0x30, 0x70, 0x30, 0x30, 0x30, 0x30, 0xFC, 0x00, + 0x78, 0xCC, 0x0C, 0x38, 0x60, 0xCC, 0xFC, 0x00, + 0x78, 0xCC, 0x0C, 0x38, 0x0C, 0xCC, 0x78, 0x00, + 0x1C, 0x3C, 0x6C, 0xCC, 0xFE, 0x0C, 0x1E, 0x00, + 0xFC, 0xC0, 0xF8, 0x0C, 0x0C, 0xCC, 0x78, 0x00, + 0x38, 0x60, 0xC0, 0xF8, 0xCC, 0xCC, 0x78, 0x00, + 0xFC, 0xCC, 0x0C, 0x18, 0x30, 0x30, 0x30, 0x00, + 0x78, 0xCC, 0xCC, 0x78, 0xCC, 0xCC, 0x78, 0x00, + 0x78, 0xCC, 0xCC, 0x7C, 0x0C, 0x18, 0x70, 0x00, + 0x00, 0x30, 0x30, 0x00, 0x00, 0x30, 0x30, 0x00, + 0x00, 0x30, 0x30, 0x00, 0x00, 0x30, 0x30, 0x60, + 0x18, 0x30, 0x60, 0xC0, 0x60, 0x30, 0x18, 0x00, + 0x00, 0x00, 0xFC, 0x00, 0x00, 0xFC, 0x00, 0x00, + 0x60, 0x30, 0x18, 0x0C, 0x18, 0x30, 0x60, 0x00, + 0x78, 0xCC, 0x0C, 0x18, 0x30, 0x00, 0x30, 0x00, + 0x7C, 0xC6, 0xDE, 0xDE, 0xDE, 0xC0, 0x78, 0x00, // 0x40 + 0x30, 0x78, 0xCC, 0xCC, 0xFC, 0xCC, 0xCC, 0x00, + 0xFC, 0x66, 0x66, 0x7C, 0x66, 0x66, 0xFC, 0x00, + 0x3C, 0x66, 0xC0, 0xC0, 0xC0, 0x66, 0x3C, 0x00, + 0xF8, 0x6C, 0x66, 0x66, 0x66, 0x6C, 0xF8, 0x00, + 0xFE, 0x62, 0x68, 0x78, 0x68, 0x62, 0xFE, 0x00, + 0xFE, 0x62, 0x68, 0x78, 0x68, 0x60, 0xF0, 0x00, + 0x3C, 0x66, 0xC0, 0xC0, 0xCE, 0x66, 0x3E, 0x00, + 0xCC, 0xCC, 0xCC, 0xFC, 0xCC, 0xCC, 0xCC, 0x00, + 0x78, 0x30, 0x30, 0x30, 0x30, 0x30, 0x78, 0x00, + 0x1E, 0x0C, 0x0C, 0x0C, 0xCC, 0xCC, 0x78, 0x00, + 0xE6, 0x66, 0x6C, 0x78, 0x6C, 0x66, 0xE6, 0x00, + 0xF0, 0x60, 0x60, 0x60, 0x62, 0x66, 0xFE, 0x00, + 0xC6, 0xEE, 0xFE, 0xFE, 0xD6, 0xC6, 0xC6, 0x00, + 0xC6, 0xE6, 0xF6, 0xDE, 0xCE, 0xC6, 0xC6, 0x00, + 0x38, 0x6C, 0xC6, 0xC6, 0xC6, 0x6C, 0x38, 0x00, + 0xFC, 0x66, 0x66, 0x7C, 0x60, 0x60, 0xF0, 0x00, // 0x50 + 0x78, 0xCC, 0xCC, 0xCC, 0xDC, 0x78, 0x1C, 0x00, + 0xFC, 0x66, 0x66, 0x7C, 0x6C, 0x66, 0xE6, 0x00, + 0x78, 0xCC, 0xE0, 0x70, 0x1C, 0xCC, 0x78, 0x00, + 0xFC, 0xB4, 0x30, 0x30, 0x30, 0x30, 0x78, 0x00, + 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xFC, 0x00, + 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x78, 0x30, 0x00, + 0xC6, 0xC6, 0xC6, 0xD6, 0xFE, 0xEE, 0xC6, 0x00, + 0xC6, 0xC6, 0x6C, 0x38, 0x38, 0x6C, 0xC6, 0x00, + 0xCC, 0xCC, 0xCC, 0x78, 0x30, 0x30, 0x78, 0x00, + 0xFE, 0xC6, 0x8C, 0x18, 0x32, 0x66, 0xFE, 0x00, + 0x78, 0x60, 0x60, 0x60, 0x60, 0x60, 0x78, 0x00, + 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x02, 0x00, + 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x78, 0x00, + 0x10, 0x38, 0x6C, 0xC6, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x30, 0x30, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x60 + 0x00, 0x00, 0x78, 0x0C, 0x7C, 0xCC, 0x76, 0x00, + 0xE0, 0x60, 0x60, 0x7C, 0x66, 0x66, 0xDC, 0x00, + 0x00, 0x00, 0x78, 0xCC, 0xC0, 0xCC, 0x78, 0x00, + 0x1C, 0x0C, 0x0C, 0x7C, 0xCC, 0xCC, 0x76, 0x00, + 0x00, 0x00, 0x78, 0xCC, 0xFC, 0xC0, 0x78, 0x00, + 0x38, 0x6C, 0x60, 0xF0, 0x60, 0x60, 0xF0, 0x00, + 0x00, 0x00, 0x76, 0xCC, 0xCC, 0x7C, 0x0C, 0xF8, + 0xE0, 0x60, 0x6C, 0x76, 0x66, 0x66, 0xE6, 0x00, + 0x30, 0x00, 0x70, 0x30, 0x30, 0x30, 0x78, 0x00, + 0x0C, 0x00, 0x0C, 0x0C, 0x0C, 0xCC, 0xCC, 0x78, + 0xE0, 0x60, 0x66, 0x6C, 0x78, 0x6C, 0xE6, 0x00, + 0x70, 0x30, 0x30, 0x30, 0x30, 0x30, 0x78, 0x00, + 0x00, 0x00, 0xCC, 0xFE, 0xFE, 0xD6, 0xC6, 0x00, + 0x00, 0x00, 0xF8, 0xCC, 0xCC, 0xCC, 0xCC, 0x00, + 0x00, 0x00, 0x78, 0xCC, 0xCC, 0xCC, 0x78, 0x00, + 0x00, 0x00, 0xDC, 0x66, 0x66, 0x7C, 0x60, 0xF0, // 0x70 + 0x00, 0x00, 0x76, 0xCC, 0xCC, 0x7C, 0x0C, 0x1E, + 0x00, 0x00, 0xDC, 0x76, 0x66, 0x60, 0xF0, 0x00, + 0x00, 0x00, 0x7C, 0xC0, 0x78, 0x0C, 0xF8, 0x00, + 0x10, 0x30, 0x7C, 0x30, 0x30, 0x34, 0x18, 0x00, + 0x00, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0x76, 0x00, + 0x00, 0x00, 0xCC, 0xCC, 0xCC, 0x78, 0x30, 0x00, + 0x00, 0x00, 0xC6, 0xD6, 0xFE, 0xFE, 0x6C, 0x00, + 0x00, 0x00, 0xC6, 0x6C, 0x38, 0x6C, 0xC6, 0x00, + 0x00, 0x00, 0xCC, 0xCC, 0xCC, 0x7C, 0x0C, 0xF8, + 0x00, 0x00, 0xFC, 0x98, 0x30, 0x64, 0xFC, 0x00, + 0x1C, 0x30, 0x30, 0xE0, 0x30, 0x30, 0x1C, 0x00, + 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00, + 0xE0, 0x30, 0x30, 0x1C, 0x30, 0x30, 0xE0, 0x00, + 0x76, 0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + // custom font starting here at 0x80 + 0x1E, 0x36, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x00, // 0x80 + 0x7C, 0x60, 0x60, 0x7C, 0x66, 0x66, 0x7C, 0x00, + 0x7C, 0x66, 0x66, 0x7C, 0x66, 0x66, 0x7C, 0x00, + 0x7E, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x00, + 0x38, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0xFE, 0xC6, + 0x7E, 0x60, 0x60, 0x7C, 0x60, 0x60, 0x7E, 0x00, + 0xDB, 0xDB, 0x7E, 0x3C, 0x7E, 0xDB, 0xDB, 0x00, + 0x3C, 0x66, 0x06, 0x1C, 0x06, 0x66, 0x3C, 0x00, + 0x66, 0x66, 0x6E, 0x7E, 0x76, 0x66, 0x66, 0x00, + 0x3C, 0x66, 0x6E, 0x7E, 0x76, 0x66, 0x66, 0x00, + 0x66, 0x6C, 0x78, 0x70, 0x78, 0x6C, 0x66, 0x00, + 0x1E, 0x36, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, + 0xC6, 0xEE, 0xFE, 0xFE, 0xD6, 0xC6, 0xC6, 0x00, + 0x66, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x66, 0x00, + 0x3C, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00, + 0x7E, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, + 0x7C, 0x66, 0x66, 0x66, 0x7C, 0x60, 0x60, 0x00, + 0x3C, 0x66, 0x60, 0x60, 0x60, 0x66, 0x3C, 0x00, + 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, + 0x66, 0x66, 0x66, 0x3E, 0x06, 0x66, 0x3C, 0x00, + 0x7E, 0xDB, 0xDB, 0xDB, 0x7E, 0x18, 0x18, 0x00, + 0x66, 0x66, 0x3C, 0x18, 0x3C, 0x66, 0x66, 0x00, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7F, 0x03, + 0x66, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x06, 0x00, + 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xFF, 0x00, + 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xFF, 0x03, + 0xE0, 0x60, 0x60, 0x7C, 0x66, 0x66, 0x7C, 0x00, + 0xC6, 0xC6, 0xC6, 0xF6, 0xDE, 0xDE, 0xF6, 0x00, + 0x60, 0x60, 0x60, 0x7C, 0x66, 0x66, 0x7C, 0x00, + 0x78, 0x8C, 0x06, 0x3E, 0x06, 0x8C, 0x78, 0x00, + 0xCE, 0xDB, 0xDB, 0xFB, 0xDB, 0xDB, 0xCE, 0x00, + 0x3E, 0x66, 0x66, 0x66, 0x3E, 0x36, 0x66, 0x00, + 0x00, 0x00, 0x3C, 0x06, 0x3E, 0x66, 0x3A, 0x00, + 0x00, 0x3C, 0x60, 0x3C, 0x66, 0x66, 0x3C, 0x00, + 0x00, 0x00, 0x7C, 0x66, 0x7C, 0x66, 0x7C, 0x00, + 0x00, 0x00, 0x7E, 0x60, 0x60, 0x60, 0x60, 0x00, + 0x00, 0x00, 0x3C, 0x6C, 0x6C, 0x6C, 0xFE, 0xC6, + 0x00, 0x00, 0x3C, 0x66, 0x7E, 0x60, 0x3C, 0x00, + 0x00, 0x00, 0xDB, 0x7E, 0x3C, 0x7E, 0xDB, 0x00, + 0x00, 0x00, 0x3C, 0x66, 0x0C, 0x66, 0x3C, 0x00, + 0x00, 0x00, 0x66, 0x6E, 0x7E, 0x76, 0x66, 0x00, + 0x00, 0x18, 0x66, 0x6E, 0x7E, 0x76, 0x66, 0x00, + 0x00, 0x00, 0x66, 0x6C, 0x78, 0x6C, 0x66, 0x00, + 0x00, 0x00, 0x1E, 0x36, 0x66, 0x66, 0x66, 0x00, + 0x00, 0x00, 0xC6, 0xFE, 0xFE, 0xD6, 0xC6, 0x00, + 0x00, 0x00, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x00, + 0x00, 0x00, 0x3C, 0x66, 0x66, 0x66, 0x3C, 0x00, + 0x00, 0x00, 0x7E, 0x66, 0x66, 0x66, 0x66, 0x00, + 0x11, 0x44, 0x11, 0x44, 0x11, 0x44, 0x11, 0x44, + 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, + 0xDD, 0x77, 0xDD, 0x77, 0xDD, 0x77, 0xDD, 0x77, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0xF8, 0x18, 0x18, 0x18, 0x18, + 0x18, 0xF8, 0x18, 0xF8, 0x18, 0x18, 0x18, 0x18, + 0x36, 0x36, 0x36, 0xF6, 0x36, 0x36, 0x36, 0x36, + 0x00, 0x00, 0x00, 0xFE, 0x36, 0x36, 0x36, 0x36, + 0x00, 0xF8, 0x18, 0xF8, 0x18, 0x18, 0x18, 0x18, + 0x36, 0xF6, 0x06, 0xF6, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x00, 0xFE, 0x06, 0xF6, 0x36, 0x36, 0x36, 0x36, + 0x36, 0xF6, 0x06, 0xFE, 0x00, 0x00, 0x00, 0x00, + 0x36, 0x36, 0x36, 0xFE, 0x00, 0x00, 0x00, 0x00, + 0x18, 0xF8, 0x18, 0xF8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xF8, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x1F, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x18, 0x18, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xFF, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x1F, 0x18, 0x18, 0x18, 0x18, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x18, 0x18, 0xFF, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x1F, 0x18, 0x1F, 0x18, 0x18, 0x18, 0x18, + 0x36, 0x36, 0x36, 0x37, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x37, 0x30, 0x3F, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x3F, 0x30, 0x37, 0x36, 0x36, 0x36, 0x36, + 0x36, 0xF7, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0x00, 0xF7, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x37, 0x30, 0x37, 0x36, 0x36, 0x36, 0x36, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x36, 0xF7, 0x00, 0xF7, 0x36, 0x36, 0x36, 0x36, + 0x18, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x36, 0x36, 0x36, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0x00, 0xFF, 0x18, 0x18, 0x18, 0x18, + 0x00, 0x00, 0x00, 0xFF, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x3F, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x1F, 0x18, 0x1F, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x1F, 0x18, 0x1F, 0x18, 0x18, 0x18, 0x18, + 0x00, 0x00, 0x00, 0x3F, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0xFF, 0x36, 0x36, 0x36, 0x36, + 0x18, 0xFF, 0x18, 0xFF, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0xF8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x1F, 0x18, 0x18, 0x18, 0x18, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7C, 0x66, 0x66, 0x7C, 0x60, 0x00, + 0x00, 0x00, 0x3C, 0x66, 0x60, 0x66, 0x3C, 0x00, + 0x00, 0x00, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x00, + 0x00, 0x00, 0x66, 0x66, 0x3E, 0x06, 0x7C, 0x00, + 0x00, 0x00, 0x7E, 0xDB, 0xDB, 0x7E, 0x18, 0x00, + 0x00, 0x00, 0x66, 0x3C, 0x18, 0x3C, 0x66, 0x00, + 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x7F, 0x03, + 0x00, 0x00, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x00, + 0x00, 0x00, 0xDB, 0xDB, 0xDB, 0xDB, 0xFF, 0x00, + 0x00, 0x00, 0xDB, 0xDB, 0xDB, 0xDB, 0xFF, 0x03, + 0x00, 0x00, 0xE0, 0x60, 0x7C, 0x66, 0x7C, 0x00, + 0x00, 0x00, 0xC6, 0xC6, 0xF6, 0xDE, 0xF6, 0x00, + 0x00, 0x00, 0x60, 0x60, 0x7C, 0x66, 0x7C, 0x00, + 0x00, 0x00, 0x7C, 0x06, 0x3E, 0x06, 0x7C, 0x00, + 0x00, 0x00, 0xCE, 0xDB, 0xFB, 0xDB, 0xCE, 0x00, + 0x00, 0x00, 0x3E, 0x66, 0x3E, 0x36, 0x66, 0x00, + 0x00, 0x00, 0xFE, 0x00, 0xFE, 0x00, 0xFE, 0x00, + 0x10, 0x10, 0x7C, 0x10, 0x10, 0x00, 0x7C, 0x00, + 0x00, 0x30, 0x18, 0x0C, 0x06, 0x0C, 0x18, 0x30, + 0x00, 0x0C, 0x18, 0x30, 0x60, 0x30, 0x18, 0x0C, + 0x0E, 0x1B, 0x1B, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0xD8, 0xD8, 0x70, + 0x00, 0x18, 0x18, 0x00, 0x7E, 0x00, 0x18, 0x18, + 0x00, 0x76, 0xDC, 0x00, 0x76, 0xDC, 0x00, 0x00, + 0x00, 0x38, 0x6C, 0x6C, 0x38, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x38, 0x38, 0x00, 0x00, 0x00, + 0x03, 0x02, 0x06, 0x04, 0xCC, 0x68, 0x38, 0x10, + 0x3C, 0x42, 0x99, 0xA1, 0xA1, 0x99, 0x42, 0x3C, + 0x30, 0x48, 0x10, 0x20, 0x78, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7C, 0x7C, 0x7C, 0x7C, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x7E, 0x00 +}; + +static const uint8 fontData_FanGames[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x7E, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x7E, /* cursor hollow */ + 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, /* cursor solid */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* cursor empty */ + 0x10, 0x38, 0x7C, 0xFE, 0x7C, 0x38, 0x10, 0x00, + 0x3C, 0x3C, 0x18, 0xFF, 0xE7, 0x18, 0x3C, 0x00, + 0x10, 0x38, 0x7C, 0xFE, 0xEE, 0x10, 0x38, 0x00, + 0x00, 0x00, 0x18, 0x3C, 0x3C, 0x18, 0x00, 0x00, + 0xFF, 0xFF, 0xE7, 0xC3, 0xC3, 0xE7, 0xFF, 0xFF, + 0x00, 0x3C, 0x66, 0x42, 0x42, 0x66, 0x3C, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* \n */ + 0x0F, 0x07, 0x0F, 0x7D, 0xCC, 0xCC, 0xCC, 0x78, + 0x3C, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x7E, 0x18, + 0x08, 0x0C, 0x0A, 0x0A, 0x08, 0x78, 0xF0, 0x00, + 0x18, 0x14, 0x1A, 0x16, 0x72, 0xE2, 0x0E, 0x1C, + 0x10, 0x54, 0x38, 0xEE, 0x38, 0x54, 0x10, 0x00, + 0x80, 0xE0, 0xF8, 0xFE, 0xF8, 0xE0, 0x80, 0x00, + 0x02, 0x0E, 0x3E, 0xFE, 0x3E, 0x0E, 0x02, 0x00, + 0x18, 0x3C, 0x5A, 0x18, 0x5A, 0x3C, 0x18, 0x00, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x66, 0x00, + 0x7F, 0xDB, 0xDB, 0xDB, 0x7B, 0x1B, 0x1B, 0x00, + 0x1C, 0x22, 0x38, 0x44, 0x44, 0x38, 0x88, 0x70, + 0x00, 0x00, 0x00, 0x00, 0x7E, 0x7E, 0x7E, 0x00, + 0x18, 0x3C, 0x5A, 0x18, 0x5A, 0x3C, 0x18, 0x7E, + 0x18, 0x3C, 0x5A, 0x18, 0x18, 0x18, 0x18, 0x00, + 0x18, 0x18, 0x18, 0x18, 0x5A, 0x3C, 0x18, 0x00, + 0x00, 0x18, 0x0C, 0xFE, 0x0C, 0x18, 0x00, 0x00, + 0x00, 0x30, 0x60, 0xFE, 0x60, 0x30, 0x00, 0x00, + 0x00, 0x00, 0xC0, 0xC0, 0xC0, 0xFE, 0x00, 0x00, + 0x00, 0x24, 0x42, 0xFF, 0x42, 0x24, 0x00, 0x00, + 0x00, 0x10, 0x38, 0x7C, 0xFE, 0xFE, 0x00, 0x00, + 0x00, 0xFE, 0xFE, 0x7C, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, 0x00, 0x6C, 0x24, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -154,9 +415,7 @@ static const uint8 curFont[] = { 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00, 0xE0, 0x30, 0x30, 0x18, 0x30, 0x30, 0xE0, 0x00, 0x76, 0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - //0x00, 0x10, 0x38, 0x6C, 0xC6, 0xC6, 0xFE, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, //replacement 0x7F - + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /*replacement 0x7F */ 0x1E, 0x36, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x00, 0x7C, 0x60, 0x60, 0x7C, 0x66, 0x66, 0x7C, 0x00, 0x7C, 0x66, 0x66, 0x7C, 0x66, 0x66, 0x7C, 0x00, @@ -287,7 +546,7 @@ static const uint8 curFont[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x7E, 0x00 }; -static const uint8 mickey_fontdata[] = { +static const uint8 fontData_Mickey[] = { 0x00, 0x36, 0x7F, 0x7F, 0x3E, 0x1C, 0x08, 0x00, 0x00, 0x00, 0x3F, 0x20, 0x2F, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, @@ -546,7 +805,7 @@ static const uint8 mickey_fontdata[] = { 0x10, 0x18, 0x1C, 0x1E, 0x1C, 0x18, 0x10, 0x00, }; -static const uint8 ibm_fontdata[] = { +static const uint8 fontData_IBM[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x81, 0xA5, 0x81, 0xBD, 0x99, 0x81, 0x7E, 0x7E, 0xFF, 0xDB, 0xFF, 0xC3, 0xE7, 0xFF, 0x7E, diff --git a/engines/agi/graphics.cpp b/engines/agi/graphics.cpp index 2b1bd8c829..32d0fdc06a 100644 --- a/engines/agi/graphics.cpp +++ b/engines/agi/graphics.cpp @@ -616,6 +616,8 @@ void GfxMgr::putTextCharacter(int l, int x, int y, unsigned char c, int fg, int int x1, y1, xx, yy, cc; const uint8 *p; + assert(font); + p = font + ((unsigned int)c * CHAR_LINES); for (y1 = 0; y1 < CHAR_LINES; y1++) { for (x1 = 0; x1 < CHAR_COLS; x1++) { @@ -699,7 +701,7 @@ void GfxMgr::printCharacter(int x, int y, char c, int fg, int bg) { x *= CHAR_COLS; y *= CHAR_LINES; - putTextCharacter(0, x, y, c, fg, bg); + putTextCharacter(0, x, y, c, fg, bg, false, _vm->getFontData()); // redundant! already inside put_text_character! // flush_block (x, y, x + CHAR_COLS - 1, y + CHAR_LINES - 1); } @@ -754,7 +756,7 @@ void GfxMgr::rawDrawButton(int x, int y, const char *s, int fgcolor, int bgcolor drawRectangle(x1, y1, x2, y2, border ? BUTTON_BORDER : MSG_BOX_COLOR); while (*s) { - putTextCharacter(0, x + textOffset, y + textOffset, *s++, fgcolor, bgcolor); + putTextCharacter(0, x + textOffset, y + textOffset, *s++, fgcolor, bgcolor, false, _vm->getFontData()); x += CHAR_COLS; } diff --git a/engines/agi/graphics.h b/engines/agi/graphics.h index 15668fbed3..506a9d93d6 100644 --- a/engines/agi/graphics.h +++ b/engines/agi/graphics.h @@ -56,7 +56,7 @@ public: void gfxPutBlock(int x1, int y1, int x2, int y2); - void putTextCharacter(int, int, int, unsigned char, int, int, bool checkerboard = false, const uint8 *font = curFont); + void putTextCharacter(int, int, int, unsigned char, int, int, bool checkerboard = false, const uint8 *font = fontData_Sierra); void shakeScreen(int); void shakeStart(); void shakeEnd(); diff --git a/engines/agi/preagi.cpp b/engines/agi/preagi.cpp index daadb5ffef..c368c7b195 100644 --- a/engines/agi/preagi.cpp +++ b/engines/agi/preagi.cpp @@ -59,6 +59,12 @@ void PreAgiEngine::initialize() { _gfx = new GfxMgr(this); _picture = new PictureMgr(this, _gfx); + if (getGameID() == GID_MICKEY) { + _fontData = fontData_Mickey; + } else { + _fontData = fontData_IBM; + } + _gfx->initMachine(); _game.gameFlags = 0; @@ -137,7 +143,7 @@ void PreAgiEngine::drawStr(int row, int col, int attr, const char *buffer) { break; default: - _gfx->putTextCharacter(1, col * 8 , row * 8, static_cast<char>(code), attr & 0x0f, (attr & 0xf0) / 0x10, false, getGameID() == GID_MICKEY ? mickey_fontdata : ibm_fontdata); + _gfx->putTextCharacter(1, col * 8 , row * 8, static_cast<char>(code), attr & 0x0f, (attr & 0xf0) / 0x10, false, _fontData); if (++col == 320 / 8) { col = 0; diff --git a/engines/agi/sound_pcjr.cpp b/engines/agi/sound_pcjr.cpp index 51b2d067a4..ea7a2789e0 100644 --- a/engines/agi/sound_pcjr.cpp +++ b/engines/agi/sound_pcjr.cpp @@ -120,8 +120,6 @@ SoundGenPCJr::SoundGenPCJr(AgiBase *vm, Audio::Mixer *pMixer) : SoundGen(vm, pMi else _dissolveMethod = 0; - _dissolveMethod = 3; - memset(_channel, 0, sizeof(_channel)); memset(_tchannel, 0, sizeof(_tchannel)); @@ -207,9 +205,6 @@ int SoundGenPCJr::volumeCalc(SndGenChan *chan) { chan->attenuationCopy = attenuation; attenuation &= 0x0F; - attenuation += _mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType) / 17; - if (attenuation > 0x0F) - attenuation = 0x0F; } } //if (computer_type == 2) && (attenuation < 8) @@ -411,7 +406,7 @@ int SoundGenPCJr::chanGen(int chan, int16 *stream, int len) { if (tpcm->noteCount <= 0) { // get new tone data if ((tpcm->avail) && (getNextNote(chan) == 0)) { - tpcm->atten = _channel[chan].attenuation; + tpcm->atten = volumeCalc(&_channel[chan]); tpcm->freqCount = _channel[chan].freqCount; tpcm->genType = _channel[chan].genType; @@ -477,8 +472,9 @@ int SoundGenPCJr::fillSquare(ToneChan *t, int16 *buf, int len) { count = len; + int16 amp = (int16)(volTable[t->atten] * _mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType) / Audio::Mixer::kMaxMixerVolume); while (count > 0) { - *(buf++) = t->sign ? volTable[t->atten] : -volTable[t->atten]; + *(buf++) = t->sign ? amp : -amp; count--; // get next sample @@ -515,8 +511,9 @@ int SoundGenPCJr::fillNoise(ToneChan *t, int16 *buf, int len) { count = len; + int16 amp = (int16)(volTable[t->atten] * _mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType) / Audio::Mixer::kMaxMixerVolume); while (count > 0) { - *(buf++) = t->sign ? volTable[t->atten] : -volTable[t->atten]; + *(buf++) = t->sign ? amp : -amp; count--; // get next sample diff --git a/engines/agi/text.cpp b/engines/agi/text.cpp index eb48857bf2..16c8284ce0 100644 --- a/engines/agi/text.cpp +++ b/engines/agi/text.cpp @@ -50,7 +50,7 @@ void AgiEngine::printText2(int l, const char *msg, int foff, int xoff, int yoff, // FR: strings with len == 1 were not printed if (len == 1) { - _gfx->putTextCharacter(l, xoff + foff, yoff, *msg, fg, bg, checkerboard); + _gfx->putTextCharacter(l, xoff + foff, yoff, *msg, fg, bg, checkerboard, _fontData); maxx = 1; minx = 0; ofoff = foff; @@ -74,7 +74,7 @@ void AgiEngine::printText2(int l, const char *msg, int foff, int xoff, int yoff, if (xpos >= GFX_WIDTH) continue; - _gfx->putTextCharacter(l, xpos, ypos, *m, fg, bg, checkerboard); + _gfx->putTextCharacter(l, xpos, ypos, *m, fg, bg, checkerboard, _fontData); if (x1 > maxx) maxx = x1; diff --git a/engines/agos/agos.cpp b/engines/agos/agos.cpp index 6eda2eb9aa..8952e649fd 100644 --- a/engines/agos/agos.cpp +++ b/engines/agos/agos.cpp @@ -585,7 +585,9 @@ Common::Error AGOSEngine::init() { ((getFeatures() & GF_TALKIE) && getPlatform() == Common::kPlatformAcorn) || (getPlatform() == Common::kPlatformDOS)) { - int ret = _midi->open(getGameType()); + bool isDemo = (getFeatures() & GF_DEMO) ? true : false; + + int ret = _midi->open(getGameType(), isDemo); if (ret) warning("MIDI Player init failed: \"%s\"", MidiDriver::getErrorName(ret)); diff --git a/engines/agos/drivers/accolade/adlib.cpp b/engines/agos/drivers/accolade/adlib.cpp new file mode 100644 index 0000000000..36a5f3e54e --- /dev/null +++ b/engines/agos/drivers/accolade/adlib.cpp @@ -0,0 +1,873 @@ +/* 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 "agos/agos.h" +#include "agos/drivers/accolade/mididriver.h" + +#include "common/file.h" +#include "common/mutex.h" +#include "common/system.h" +#include "common/textconsole.h" + +#include "audio/fmopl.h" +#include "audio/softsynth/emumidi.h" + +namespace AGOS { + +#define AGOS_ADLIB_VOICES_COUNT 11 +#define AGOS_ADLIB_VOICES_MELODIC_COUNT 6 +#define AGOS_ADLIB_VOICES_PERCUSSION_START 6 +#define AGOS_ADLIB_VOICES_PERCUSSION_COUNT 5 +#define AGOS_ADLIB_VOICES_PERCUSSION_CYMBAL 9 + +// 5 instruments on top of the regular MIDI ones +// used by the MUSIC.DRV variant for percussion instruments +#define AGOS_ADLIB_EXTRA_INSTRUMENT_COUNT 5 + +const byte operator1Register[AGOS_ADLIB_VOICES_COUNT] = { + 0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x14, 0x12, 0x15, 0x11 +}; + +const byte operator2Register[AGOS_ADLIB_VOICES_COUNT] = { + 0x03, 0x04, 0x05, 0x0B, 0x0C, 0x0D, 0x13, 0xFF, 0xFF, 0xFF, 0xFF +}; + +// percussion: +// voice 6 - base drum - also uses operator 13h +// voice 7 - snare drum +// voice 8 - tom tom +// voice 9 - cymbal +// voice 10 - hi hat +const byte percussionBits[AGOS_ADLIB_VOICES_PERCUSSION_COUNT] = { + 0x10, 0x08, 0x04, 0x02, 0x01 +}; + +// hardcoded, dumped from Accolade music system +// same for INSTR.DAT + MUSIC.DRV, except that MUSIC.DRV does the lookup differently +const byte percussionKeyNoteChannelTable[] = { + 0x06, 0x07, 0x07, 0x07, 0x07, 0x08, 0x0A, 0x08, 0x0A, 0x08, + 0x0A, 0x08, 0x08, 0x09, 0x08, 0x09, 0x0F, 0x0F, 0x0A, 0x0F, + 0x0A, 0x0F, 0x0F, 0x0F, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x0A, 0x0F, 0x0F, 0x08, 0x0F, 0x08 +}; + +struct InstrumentEntry { + byte reg20op1; // Amplitude Modulation / Vibrato / Envelope Generator Type / Keyboard Scaling Rate / Modulator Frequency Multiple + byte reg40op1; // Level Key Scaling / Total Level + byte reg60op1; // Attack Rate / Decay Rate + byte reg80op1; // Sustain Level / Release Rate + byte reg20op2; // Amplitude Modulation / Vibrato / Envelope Generator Type / Keyboard Scaling Rate / Modulator Frequency Multiple + byte reg40op2; // Level Key Scaling / Total Level + byte reg60op2; // Attack Rate / Decay Rate + byte reg80op2; // Sustain Level / Release Rate + byte regC0; // Feedback / Algorithm, bit 0 - set -> both operators in use +}; + +// hardcoded, dumped from Accolade music system (INSTR.DAT variant) +const uint16 frequencyLookUpTable[12] = { + 0x02B2, 0x02DB, 0x0306, 0x0334, 0x0365, 0x0399, 0x03CF, + 0xFE05, 0xFE23, 0xFE44, 0xFE67, 0xFE8B +}; + +// hardcoded, dumped from Accolade music system (MUSIC.DRV variant) +const uint16 frequencyLookUpTableMusicDrv[12] = { + 0x0205, 0x0223, 0x0244, 0x0267, 0x028B, 0x02B2, 0x02DB, + 0x0306, 0x0334, 0x0365, 0x0399, 0x03CF +}; + +// +// Accolade adlib music driver +// +// Remarks: +// +// There are at least 2 variants of this sound system. +// One for the games Elvira 1 + Elvira 2 +// It seems it was also used for the game "Altered Destiny" +// Another one for the games Waxworks + Simon, the Sorcerer 1 Demo +// +// First one uses the file INSTR.DAT for instrument data, channel mapping etc. +// Second one uses the file MUSIC.DRV, which actually contains driver code + instrument data + channel mapping, etc. +// +// The second variant supported dynamic channel allocation for the FM voice channels, but this +// feature was at least definitely disabled for Simon, the Sorcerer 1 demo and for the Waxworks demo too. +// +// I have currently not implemented dynamic channel allocation. + +class MidiDriver_Accolade_AdLib : public MidiDriver_Emulated { +public: + MidiDriver_Accolade_AdLib(Audio::Mixer *mixer); + virtual ~MidiDriver_Accolade_AdLib(); + + // MidiDriver + int open(); + void close(); + void send(uint32 b); + MidiChannel *allocateChannel() { return NULL; } + MidiChannel *getPercussionChannel() { return NULL; } + + // AudioStream + bool isStereo() const { return false; } + int getRate() const { return _mixer->getOutputRate(); } + int getPolyphony() const { return AGOS_ADLIB_VOICES_COUNT; } + bool hasRhythmChannel() const { return false; } + + // MidiDriver_Emulated + void generateSamples(int16 *buf, int len); + + void setVolume(byte volume); + virtual uint32 property(int prop, uint32 param); + + bool setupInstruments(byte *instrumentData, uint16 instrumentDataSize, bool useMusicDrvFile); + +private: + bool _musicDrvMode; + + // from INSTR.DAT/MUSIC.DRV - simple mapping between MIDI channel and MT32 channel + byte _channelMapping[AGOS_MIDI_CHANNEL_COUNT]; + // from INSTR.DAT/MUSIC.DRV - simple mapping between MIDI instruments and MT32 instruments + byte _instrumentMapping[AGOS_MIDI_INSTRUMENT_COUNT]; + // from INSTR.DAT/MUSIC.DRV - volume adjustment per instrument + signed char _instrumentVolumeAdjust[AGOS_MIDI_INSTRUMENT_COUNT]; + // simple mapping between MIDI key notes and MT32 key notes + byte _percussionKeyNoteMapping[AGOS_MIDI_KEYNOTE_COUNT]; + + // from INSTR.DAT/MUSIC.DRV - adlib instrument data + InstrumentEntry *_instrumentTable; + byte _instrumentCount; + + struct ChannelEntry { + const InstrumentEntry *currentInstrumentPtr; + byte currentNote; + byte currentA0hReg; + byte currentB0hReg; + int16 volumeAdjust; + + ChannelEntry() : currentInstrumentPtr(NULL), currentNote(0), + currentA0hReg(0), currentB0hReg(0), volumeAdjust(0) { } + }; + + byte _percussionReg; + + OPL::OPL *_opl; + int _masterVolume; + + // points to a MIDI channel for each of the new voice channels + byte _voiceChannelMapping[AGOS_ADLIB_VOICES_COUNT]; + + // stores information about all FM voice channels + ChannelEntry _channels[AGOS_ADLIB_VOICES_COUNT]; + +protected: + void onTimer(); + +private: + void resetAdLib(); + void resetAdLib_OperatorRegisters(byte baseRegister, byte value); + void resetAdLib_FMVoiceChannelRegisters(byte baseRegister, byte value); + + void programChange(byte FMvoiceChannel, byte mappedInstrumentNr, byte MIDIinstrumentNr); + void programChangeSetInstrument(byte FMvoiceChannel, byte mappedInstrumentNr, byte MIDIinstrumentNr); + void setRegister(int reg, int value); + void noteOn(byte FMvoiceChannel, byte note, byte velocity); + void noteOnSetVolume(byte FMvoiceChannel, byte operatorReg, byte adjustedVelocity); + void noteOff(byte FMvoiceChannel, byte note, bool dontCheckNote); +}; + +MidiDriver_Accolade_AdLib::MidiDriver_Accolade_AdLib(Audio::Mixer *mixer) + : MidiDriver_Emulated(mixer), _masterVolume(15), _opl(0) { + memset(_channelMapping, 0, sizeof(_channelMapping)); + memset(_instrumentMapping, 0, sizeof(_instrumentMapping)); + memset(_instrumentVolumeAdjust, 0, sizeof(_instrumentVolumeAdjust)); + memset(_percussionKeyNoteMapping, 0, sizeof(_percussionKeyNoteMapping)); + + _instrumentTable = NULL; + _instrumentCount = 0; + _musicDrvMode = false; + _percussionReg = 0x20; +} + +MidiDriver_Accolade_AdLib::~MidiDriver_Accolade_AdLib() { + if (_instrumentTable) { + delete[] _instrumentTable; + _instrumentCount = 0; + } +} + +int MidiDriver_Accolade_AdLib::open() { + int rate = _mixer->getOutputRate(); + +// debugC(kDebugLevelAdLibDriver, "AdLib: starting driver"); + + _opl = OPL::Config::create(OPL::Config::kOpl2); + + if (!_opl) + return -1; + + _opl->init(rate); + + MidiDriver_Emulated::open(); + + _mixer->playStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1, _mixer->kMaxChannelVolume, 0, DisposeAfterUse::NO); + + resetAdLib(); + + // Finally set up default instruments + for (byte FMvoiceNr = 0; FMvoiceNr < AGOS_ADLIB_VOICES_COUNT; FMvoiceNr++) { + if (FMvoiceNr < AGOS_ADLIB_VOICES_PERCUSSION_START) { + // Regular FM voices with instrument 0 + programChangeSetInstrument(FMvoiceNr, 0, 0); + } else { + byte percussionInstrument; + if (!_musicDrvMode) { + // INSTR.DAT: percussion voices with instrument 1, 2, 3, 4 and 5 + percussionInstrument = FMvoiceNr - AGOS_ADLIB_VOICES_PERCUSSION_START + 1; + } else { + // MUSIC.DRV: percussion voices with instrument 0x80, 0x81, 0x82, 0x83 and 0x84 + percussionInstrument = FMvoiceNr - AGOS_ADLIB_VOICES_PERCUSSION_START + 0x80; + } + programChangeSetInstrument(FMvoiceNr, percussionInstrument, percussionInstrument); + } + } + + // driver initialization does this here: + // INSTR.DAT + // noteOn(9, 0x29, 0); + // noteOff(9, 0x26, false); + // MUSIC.DRV + // noteOn(9, 0x26, 0); + // noteOff(9, 0x26, false); + + return 0; +} + +void MidiDriver_Accolade_AdLib::close() { + _mixer->stopHandle(_mixerSoundHandle); + + delete _opl; +} + +void MidiDriver_Accolade_AdLib::setVolume(byte volume) { + _masterVolume = volume; + //renewNotes(-1, true); +} + +void MidiDriver_Accolade_AdLib::onTimer() { +} + +void MidiDriver_Accolade_AdLib::resetAdLib() { + // The original driver sent 0x00 to register 0x00 up to 0xF5 + setRegister(0xBD, 0x00); // Disable rhythm + + // reset FM voice instrument data + resetAdLib_OperatorRegisters(0x20, 0); + resetAdLib_OperatorRegisters(0x60, 0); + resetAdLib_OperatorRegisters(0x80, 0); + resetAdLib_FMVoiceChannelRegisters(0xA0, 0); + resetAdLib_FMVoiceChannelRegisters(0xB0, 0); + resetAdLib_FMVoiceChannelRegisters(0xC0, 0); + resetAdLib_OperatorRegisters(0xE0, 0); + resetAdLib_OperatorRegisters(0x40, 0x3F); // original driver sent 0x00 + + setRegister(0x01, 0x20); // enable waveform control on both operators + setRegister(0x04, 0x60); // Timer control + + setRegister(0x08, 0); // select FM music mode + setRegister(0xBD, 0x20); // Enable rhythm + + // reset our percussion register + _percussionReg = 0x20; +} + +void MidiDriver_Accolade_AdLib::resetAdLib_OperatorRegisters(byte baseRegister, byte value) { + byte operatorIndex; + + for (operatorIndex = 0; operatorIndex < 0x16; operatorIndex++) { + switch (operatorIndex) { + case 0x06: + case 0x07: + case 0x0E: + case 0x0F: + break; + default: + setRegister(baseRegister + operatorIndex, value); + } + } +} + +void MidiDriver_Accolade_AdLib::resetAdLib_FMVoiceChannelRegisters(byte baseRegister, byte value) { + byte FMvoiceChannel; + + for (FMvoiceChannel = 0; FMvoiceChannel < AGOS_ADLIB_VOICES_COUNT; FMvoiceChannel++) { + setRegister(baseRegister + FMvoiceChannel, value); + } +} + +// MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php +void MidiDriver_Accolade_AdLib::send(uint32 b) { + byte command = b & 0xf0; + byte channel = b & 0xf; + byte op1 = (b >> 8) & 0xff; + byte op2 = (b >> 16) & 0xff; + + byte mappedChannel = _channelMapping[channel]; + byte mappedInstrument = 0; + + // Ignore everything that is outside of our channel range + if (mappedChannel >= AGOS_ADLIB_VOICES_COUNT) + return; + + switch (command) { + case 0x80: + noteOff(mappedChannel, op1, false); + break; + case 0x90: + // Convert noteOn with velocity 0 to a noteOff + if (op2 == 0) + return noteOff(mappedChannel, op1, false); + + noteOn(mappedChannel, op1, op2); + break; + case 0xb0: // Control change + // Doesn't seem to be implemented + break; + case 0xc0: // Program Change + mappedInstrument = _instrumentMapping[op1]; + programChange(mappedChannel, mappedInstrument, op1); + break; + case 0xa0: // Polyphonic key pressure (aftertouch) + case 0xd0: // Channel pressure (aftertouch) + // Aftertouch doesn't seem to be implemented + break; + case 0xe0: + // No pitch bend change + break; + case 0xf0: // SysEx + warning("ADLIB: SysEx: %x", b); + break; + default: + warning("ADLIB: Unknown event %02x", command); + } +} + +void MidiDriver_Accolade_AdLib::generateSamples(int16 *data, int len) { + _opl->readBuffer(data, len); +} + +void MidiDriver_Accolade_AdLib::noteOn(byte FMvoiceChannel, byte note, byte velocity) { + byte adjustedNote = note; + byte adjustedVelocity = velocity; + byte regValueA0h = 0; + byte regValueB0h = 0; + + // adjust velocity + int16 channelVolumeAdjust = _channels[FMvoiceChannel].volumeAdjust; + channelVolumeAdjust += adjustedVelocity; + channelVolumeAdjust = CLIP<int16>(channelVolumeAdjust, 0, 0x7F); + + // TODO: adjust to global volume + // original drivers had a global volume variable, which was 0 for full volume, -64 for half volume + // and -128 for mute + + adjustedVelocity = channelVolumeAdjust; + + if (!_musicDrvMode) { + // INSTR.DAT + // force note-off + noteOff(FMvoiceChannel, note, true); + + } else { + // MUSIC.DRV + if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_START) { + // force note-off, but only for actual FM voice channels + noteOff(FMvoiceChannel, note, true); + } + } + + if (FMvoiceChannel != 9) { + // regular FM voice + + if (!_musicDrvMode) { + // INSTR.DAT: adjust key note + while (adjustedNote < 24) + adjustedNote += 12; + adjustedNote -= 12; + } + + } else { + // percussion channel + // MUSIC.DRV variant didn't do this adjustment, it directly used a pointer + adjustedNote -= 36; + if (adjustedNote > 40) { // Security check + warning("ADLIB: bad percussion channel note"); + return; + } + + byte percussionChannel = percussionKeyNoteChannelTable[adjustedNote]; + if (percussionChannel >= AGOS_ADLIB_VOICES_COUNT) + return; // INSTR.DAT variant checked for ">" instead of ">=", which seems to have been a bug + + // Map the keynote accordingly + adjustedNote = _percussionKeyNoteMapping[adjustedNote]; + // Now overwrite the FM voice channel + FMvoiceChannel = percussionChannel; + } + + if (!_musicDrvMode) { + // INSTR.DAT + + // Save this key note + _channels[FMvoiceChannel].currentNote = adjustedNote; + + adjustedVelocity += 24; + if (adjustedVelocity > 120) + adjustedVelocity = 120; + adjustedVelocity = adjustedVelocity >> 1; // divide by 2 + + } else { + // MUSIC.DRV + adjustedVelocity = adjustedVelocity >> 1; // divide by 2 + } + + // Set volume of voice channel + noteOnSetVolume(FMvoiceChannel, 1, adjustedVelocity); + if (FMvoiceChannel <= AGOS_ADLIB_VOICES_PERCUSSION_START) { + // Set second operator for FM voices + first percussion + noteOnSetVolume(FMvoiceChannel, 2, adjustedVelocity); + } + + if (FMvoiceChannel >= AGOS_ADLIB_VOICES_PERCUSSION_START) { + // Percussion + byte percussionIdx = FMvoiceChannel - AGOS_ADLIB_VOICES_PERCUSSION_START; + + // Enable bit of the requested percussion type + assert(percussionIdx < AGOS_ADLIB_VOICES_PERCUSSION_COUNT); + _percussionReg |= percussionBits[percussionIdx]; + setRegister(0xBD, _percussionReg); + } + + if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_CYMBAL) { + // FM voice, Base Drum, Snare Drum + Tom Tom + byte adlibNote = adjustedNote; + byte adlibOctave = 0; + byte adlibFrequencyIdx = 0; + uint16 adlibFrequency = 0; + + if (!_musicDrvMode) { + // INSTR.DAT + if (adlibNote >= 0x60) + adlibNote = 0x5F; + + adlibOctave = (adlibNote / 12) - 1; + adlibFrequencyIdx = adlibNote % 12; + adlibFrequency = frequencyLookUpTable[adlibFrequencyIdx]; + + if (adlibFrequency & 0x8000) + adlibOctave++; + if (adlibOctave & 0x80) { + adlibOctave++; + adlibFrequency = adlibFrequency >> 1; + } + + } else { + // MUSIC.DRV variant + if (adlibNote >= 19) + adlibNote -= 19; + + adlibOctave = (adlibNote / 12); + adlibFrequencyIdx = adlibNote % 12; + // additional code, that will lookup octave and do a multiplication with it + // noteOn however calls the frequency calculation in a way that it multiplies with 0 + adlibFrequency = frequencyLookUpTableMusicDrv[adlibFrequencyIdx]; + } + + regValueA0h = adlibFrequency & 0xFF; + regValueB0h = ((adlibFrequency & 0x300) >> 8) | (adlibOctave << 2); + if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_START) { + // set Key-On flag for regular FM voices, but not for percussion + regValueB0h |= 0x20; + } + + setRegister(0xA0 + FMvoiceChannel, regValueA0h); + setRegister(0xB0 + FMvoiceChannel, regValueB0h); + _channels[FMvoiceChannel].currentA0hReg = regValueA0h; + _channels[FMvoiceChannel].currentB0hReg = regValueB0h; + + if (_musicDrvMode) { + // MUSIC.DRV + if (FMvoiceChannel < AGOS_ADLIB_VOICES_MELODIC_COUNT) { + _channels[FMvoiceChannel].currentNote = adjustedNote; + } + } + } +} + +// 100% the same for INSTR.DAT and MUSIC.DRV variants +// except for a bug, that was introduced for MUSIC.DRV +void MidiDriver_Accolade_AdLib::noteOnSetVolume(byte FMvoiceChannel, byte operatorNr, byte adjustedVelocity) { + byte operatorReg = 0; + byte regValue40h = 0; + const InstrumentEntry *curInstrument = NULL; + + regValue40h = (63 - adjustedVelocity) & 0x3F; + + if ((operatorNr == 1) && (FMvoiceChannel <= AGOS_ADLIB_VOICES_PERCUSSION_START)) { + // first operator of FM voice channels or first percussion channel + curInstrument = _channels[FMvoiceChannel].currentInstrumentPtr; + if (!(curInstrument->regC0 & 0x01)) { // check, if both operators produce sound + // only one does, instrument wants fixed volume + if (operatorNr == 1) { + regValue40h = curInstrument->reg40op1; + } else { + regValue40h = curInstrument->reg40op2; + } + + // not sure, if we are supposed to implement these bugs, or not +#if 0 + if (!_musicDrvMode) { + // Table is 16 bytes instead of 18 bytes + if ((FMvoiceChannel == 7) || (FMvoiceChannel == 9)) { + regValue40h = 0; + warning("volume set bug (original)"); + } + } + if (_musicDrvMode) { + // MUSIC.DRV variant has a bug, which will overwrite these registers + // for all operators above 11 / 0Bh, which means percussion will always + // get a value of 0 (the table holding those bytes was 12 bytes instead of 18 + if (FMvoiceChannel >= AGOS_ADLIB_VOICES_PERCUSSION_START) { + regValue40h = 0; + warning("volume set bug (original)"); + } + } +#endif + } + } + + if (operatorNr == 1) { + operatorReg = operator1Register[FMvoiceChannel]; + } else { + operatorReg = operator2Register[FMvoiceChannel]; + } + assert(operatorReg != 0xFF); // Security check + setRegister(0x40 + operatorReg, regValue40h); +} + +void MidiDriver_Accolade_AdLib::noteOff(byte FMvoiceChannel, byte note, bool dontCheckNote) { + byte adjustedNote = note; + byte regValueB0h = 0; + + if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_START) { + // regular FM voice + + if (!_musicDrvMode) { + // INSTR.DAT: adjust key note + while (adjustedNote < 24) + adjustedNote += 12; + adjustedNote -= 12; + } + + if (!dontCheckNote) { + // check, if current note is also the current actually playing channel note + if (_channels[FMvoiceChannel].currentNote != adjustedNote) + return; // not the same -> ignore this note off command + } + + regValueB0h = _channels[FMvoiceChannel].currentB0hReg & 0xDF; // Remove "key on" bit + setRegister(0xB0 + FMvoiceChannel, regValueB0h); + + } else { + // percussion + adjustedNote -= 36; + if (adjustedNote > 40) { // Security check + warning("ADLIB: bad percussion channel note"); + return; + } + + byte percussionChannel = percussionKeyNoteChannelTable[adjustedNote]; + if (percussionChannel > AGOS_ADLIB_VOICES_COUNT) + return; + + byte percussionIdx = percussionChannel - AGOS_ADLIB_VOICES_PERCUSSION_START; + + // Disable bit of the requested percussion type + assert(percussionIdx < AGOS_ADLIB_VOICES_PERCUSSION_COUNT); + _percussionReg &= ~percussionBits[percussionIdx]; + setRegister(0xBD, _percussionReg); + } +} + +void MidiDriver_Accolade_AdLib::programChange(byte FMvoiceChannel, byte mappedInstrumentNr, byte MIDIinstrumentNr) { + if (mappedInstrumentNr >= _instrumentCount) { + warning("ADLIB: tried to set non-existent instrument"); + return; // out of range + } + + // setup instrument + //warning("ADLIB: program change for FM voice channel %d, instrument id %d", FMvoiceChannel, mappedInstrumentNr); + + if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_START) { + // Regular FM voice + programChangeSetInstrument(FMvoiceChannel, mappedInstrumentNr, MIDIinstrumentNr); + + } else { + // Percussion + // set default instrument (again) + byte percussionInstrumentNr = 0; + const InstrumentEntry *instrumentPtr; + + if (!_musicDrvMode) { + // INSTR.DAT: percussion default instruments start at instrument 1 + percussionInstrumentNr = FMvoiceChannel - AGOS_ADLIB_VOICES_PERCUSSION_START + 1; + } else { + // MUSIC.DRV: percussion default instruments start at instrument 0x80 + percussionInstrumentNr = FMvoiceChannel - AGOS_ADLIB_VOICES_PERCUSSION_START + 0x80; + } + if (percussionInstrumentNr >= _instrumentCount) { + warning("ADLIB: tried to set non-existent instrument"); + return; + } + instrumentPtr = &_instrumentTable[percussionInstrumentNr]; + _channels[FMvoiceChannel].currentInstrumentPtr = instrumentPtr; + _channels[FMvoiceChannel].volumeAdjust = _instrumentVolumeAdjust[percussionInstrumentNr]; + } +} + +void MidiDriver_Accolade_AdLib::programChangeSetInstrument(byte FMvoiceChannel, byte mappedInstrumentNr, byte MIDIinstrumentNr) { + const InstrumentEntry *instrumentPtr; + byte op1Reg = 0; + byte op2Reg = 0; + + if (mappedInstrumentNr >= _instrumentCount) { + warning("ADLIB: tried to set non-existent instrument"); + return; // out of range + } + + // setup instrument + instrumentPtr = &_instrumentTable[mappedInstrumentNr]; + //warning("set instrument for FM voice channel %d, instrument id %d", FMvoiceChannel, mappedInstrumentNr); + + op1Reg = operator1Register[FMvoiceChannel]; + op2Reg = operator2Register[FMvoiceChannel]; + + setRegister(0x20 + op1Reg, instrumentPtr->reg20op1); + setRegister(0x40 + op1Reg, instrumentPtr->reg40op1); + setRegister(0x60 + op1Reg, instrumentPtr->reg60op1); + setRegister(0x80 + op1Reg, instrumentPtr->reg80op1); + + if (FMvoiceChannel <= AGOS_ADLIB_VOICES_PERCUSSION_START) { + // set 2nd operator as well for FM voices and first percussion voice + setRegister(0x20 + op2Reg, instrumentPtr->reg20op2); + setRegister(0x40 + op2Reg, instrumentPtr->reg40op2); + setRegister(0x60 + op2Reg, instrumentPtr->reg60op2); + setRegister(0x80 + op2Reg, instrumentPtr->reg80op2); + + if (!_musicDrvMode) { + // set Feedback / Algorithm as well + setRegister(0xC0 + FMvoiceChannel, instrumentPtr->regC0); + } else { + if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_START) { + // set Feedback / Algorithm as well for regular FM voices only + setRegister(0xC0 + FMvoiceChannel, instrumentPtr->regC0); + } + } + } + + // Remember instrument + _channels[FMvoiceChannel].currentInstrumentPtr = instrumentPtr; + _channels[FMvoiceChannel].volumeAdjust = _instrumentVolumeAdjust[MIDIinstrumentNr]; +} + +void MidiDriver_Accolade_AdLib::setRegister(int reg, int value) { + _opl->writeReg(reg, value); + //warning("OPL %x %x (%d)", reg, value, value); +} + +uint32 MidiDriver_Accolade_AdLib::property(int prop, uint32 param) { + return 0; +} + +// Called right at the start, we get an INSTR.DAT entry +bool MidiDriver_Accolade_AdLib::setupInstruments(byte *driverData, uint16 driverDataSize, bool useMusicDrvFile) { + uint16 channelMappingOffset = 0; + uint16 channelMappingSize = 0; + uint16 instrumentMappingOffset = 0; + uint16 instrumentMappingSize = 0; + uint16 instrumentVolumeAdjustOffset = 0; + uint16 instrumentVolumeAdjustSize = 0; + uint16 keyNoteMappingOffset = 0; + uint16 keyNoteMappingSize = 0; + uint16 instrumentCount = 0; + uint16 instrumentDataOffset = 0; + uint16 instrumentDataSize = 0; + uint16 instrumentEntrySize = 0; + + if (!useMusicDrvFile) { + // INSTR.DAT: we expect at least 354 bytes + if (driverDataSize < 354) + return false; + + // Data is like this: + // 128 bytes instrument mapping + // 128 bytes instrument volume adjust (signed!) + // 16 bytes unknown + // 16 bytes channel mapping + // 64 bytes key note mapping (not used for MT32) + // 1 byte instrument count + // 1 byte bytes per instrument + // x bytes no instruments used for MT32 + + channelMappingOffset = 256 + 16; + channelMappingSize = 16; + instrumentMappingOffset = 0; + instrumentMappingSize = 128; + instrumentVolumeAdjustOffset = 128; + instrumentVolumeAdjustSize = 128; + keyNoteMappingOffset = 256 + 16 + 16; + keyNoteMappingSize = 64; + + byte instrDatInstrumentCount = driverData[256 + 16 + 16 + 64]; + byte instrDatBytesPerInstrument = driverData[256 + 16 + 16 + 64 + 1]; + + // We expect 9 bytes per instrument + if (instrDatBytesPerInstrument != 9) + return false; + // And we also expect at least one adlib instrument + if (!instrDatInstrumentCount) + return false; + + instrumentCount = instrDatInstrumentCount; + instrumentDataOffset = 256 + 16 + 16 + 64 + 2; + instrumentDataSize = instrDatBytesPerInstrument * instrDatInstrumentCount; + instrumentEntrySize = instrDatBytesPerInstrument; + + } else { + // MUSIC.DRV: we expect at least 468 bytes + if (driverDataSize < 468) + return false; + + // music.drv is basically a driver, but with a few fixed locations for certain data + + channelMappingOffset = 396; + channelMappingSize = 16; + instrumentMappingOffset = 140; + instrumentMappingSize = 128; + instrumentVolumeAdjustOffset = 140 + 128; + instrumentVolumeAdjustSize = 128; + keyNoteMappingOffset = 376 + 36; // adjust by 36, because we adjust keyNote before mapping (see noteOn) + keyNoteMappingSize = 64; + + // seems to have used 128 + 5 instruments + // 128 regular ones and an additional 5 for percussion + instrumentCount = 128 + AGOS_ADLIB_EXTRA_INSTRUMENT_COUNT; + instrumentDataOffset = 722; + instrumentEntrySize = 9; + instrumentDataSize = instrumentCount * instrumentEntrySize; + } + + // Channel mapping + if (channelMappingSize) { + // Get these 16 bytes for MIDI channel mapping + if (channelMappingSize != sizeof(_channelMapping)) + return false; + + memcpy(_channelMapping, driverData + channelMappingOffset, sizeof(_channelMapping)); + } else { + // Set up straight mapping + for (uint16 channelNr = 0; channelNr < sizeof(_channelMapping); channelNr++) { + _channelMapping[channelNr] = channelNr; + } + } + + if (instrumentMappingSize) { + // And these for instrument mapping + if (instrumentMappingSize > sizeof(_instrumentMapping)) + return false; + + memcpy(_instrumentMapping, driverData + instrumentMappingOffset, instrumentMappingSize); + } + // Set up straight mapping for the remaining data + for (uint16 instrumentNr = instrumentMappingSize; instrumentNr < sizeof(_instrumentMapping); instrumentNr++) { + _instrumentMapping[instrumentNr] = instrumentNr; + } + + if (instrumentVolumeAdjustSize) { + if (instrumentVolumeAdjustSize != sizeof(_instrumentVolumeAdjust)) + return false; + + memcpy(_instrumentVolumeAdjust, driverData + instrumentVolumeAdjustOffset, instrumentVolumeAdjustSize); + } + + // Get key note mapping, if available + if (keyNoteMappingSize) { + if (keyNoteMappingSize != sizeof(_percussionKeyNoteMapping)) + return false; + + memcpy(_percussionKeyNoteMapping, driverData + keyNoteMappingOffset, keyNoteMappingSize); + } + + // Check, if there are enough bytes left to hold all instrument data + if (driverDataSize < (instrumentDataOffset + instrumentDataSize)) + return false; + + // We release previous instrument data, just in case + if (_instrumentTable) + delete[] _instrumentTable; + + _instrumentTable = new InstrumentEntry[instrumentCount]; + _instrumentCount = instrumentCount; + + byte *instrDATReadPtr = driverData + instrumentDataOffset; + InstrumentEntry *instrumentWritePtr = _instrumentTable; + + for (uint16 instrumentNr = 0; instrumentNr < _instrumentCount; instrumentNr++) { + memcpy(instrumentWritePtr, instrDATReadPtr, sizeof(InstrumentEntry)); + instrDATReadPtr += instrumentEntrySize; + instrumentWritePtr++; + } + + // Enable MUSIC.DRV-Mode (slightly different behaviour) + if (useMusicDrvFile) + _musicDrvMode = true; + + if (_musicDrvMode) { + // Extra code for MUSIC.DRV + + // This was done during "programChange" in the original driver + instrumentWritePtr = _instrumentTable; + for (uint16 instrumentNr = 0; instrumentNr < _instrumentCount; instrumentNr++) { + instrumentWritePtr->reg80op1 |= 0x03; // set release rate + instrumentWritePtr->reg80op2 |= 0x03; + instrumentWritePtr++; + } + } + return true; +} + +MidiDriver *MidiDriver_Accolade_AdLib_create() { + return new MidiDriver_Accolade_AdLib(g_system->getMixer()); +} + +bool MidiDriver_Accolade_AdLib_setupInstruments(MidiDriver *driver, byte *driverData, uint16 driverDataSize, bool useMusicDrvFile) { + return static_cast<MidiDriver_Accolade_AdLib *>(driver)->setupInstruments(driverData, driverDataSize, useMusicDrvFile); +} + +} // End of namespace AGOS diff --git a/engines/agos/drivers/accolade/mididriver.h b/engines/agos/drivers/accolade/mididriver.h new file mode 100644 index 0000000000..96637ed1f2 --- /dev/null +++ b/engines/agos/drivers/accolade/mididriver.h @@ -0,0 +1,45 @@ +/* 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 AGOS_DRIVERS_ACCOLADE_MIDIDRIVER_H +#define AGOS_DRIVERS_ACCOLADE_MIDIDRIVER_H + +#include "agos/agos.h" +#include "audio/mididrv.h" +#include "common/error.h" + +namespace AGOS { + +#define AGOS_MIDI_CHANNEL_COUNT 16 +#define AGOS_MIDI_INSTRUMENT_COUNT 128 + +#define AGOS_MIDI_KEYNOTE_COUNT 64 + +extern MidiDriver *MidiDriver_Accolade_AdLib_create(); +extern bool MidiDriver_Accolade_AdLib_setupInstruments(MidiDriver *driver, byte *instrDATData, uint16 instrDATDataSize, bool useMusicDrvFile); + +extern MidiDriver *MidiDriver_Accolade_MT32_create(); +extern bool MidiDriver_Accolade_MT32_setupInstruments(MidiDriver *driver, byte *instrDATData, uint16 instrDATDataSize, bool useMusicDrvFile); + +} // End of namespace AGOS + +#endif // AGOS_DRIVERS_ACCOLADE_MIDIDRIVER_H diff --git a/engines/agos/drivers/accolade/mt32.cpp b/engines/agos/drivers/accolade/mt32.cpp new file mode 100644 index 0000000000..f863ffba4c --- /dev/null +++ b/engines/agos/drivers/accolade/mt32.cpp @@ -0,0 +1,265 @@ +/* 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 "agos/agos.h" +#include "agos/drivers/accolade/mididriver.h" + +#include "audio/mididrv.h" + +#include "common/config-manager.h" +#include "common/file.h" +#include "common/mutex.h" +#include "common/system.h" +#include "common/textconsole.h" + +namespace AGOS { + +class MidiDriver_Accolade_MT32 : public MidiDriver { +public: + MidiDriver_Accolade_MT32(); + virtual ~MidiDriver_Accolade_MT32(); + + // MidiDriver + int open(); + void close(); + bool isOpen() const { return _isOpen; } + + void send(uint32 b); + + MidiChannel *allocateChannel() { + if (_driver) + return _driver->allocateChannel(); + return NULL; + } + MidiChannel *getPercussionChannel() { + if (_driver) + return _driver->getPercussionChannel(); + return NULL; + } + + void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) { + if (_driver) + _driver->setTimerCallback(timer_param, timer_proc); + } + + uint32 getBaseTempo() { + if (_driver) { + return _driver->getBaseTempo(); + } + return 1000000 / _baseFreq; + } + +protected: + Common::Mutex _mutex; + MidiDriver *_driver; + bool _nativeMT32; // native MT32, may also be our MUNT, or MUNT over MIDI + + bool _isOpen; + int _baseFreq; + +private: + // simple mapping between MIDI channel and MT32 channel + byte _channelMapping[AGOS_MIDI_CHANNEL_COUNT]; + // simple mapping between MIDI instruments and MT32 instruments + byte _instrumentMapping[AGOS_MIDI_INSTRUMENT_COUNT]; + +public: + bool setupInstruments(byte *instrumentData, uint16 instrumentDataSize, bool useMusicDrvFile); +}; + +MidiDriver_Accolade_MT32::MidiDriver_Accolade_MT32() { + _driver = NULL; + _isOpen = false; + _nativeMT32 = false; + _baseFreq = 250; + + memset(_channelMapping, 0, sizeof(_channelMapping)); + memset(_instrumentMapping, 0, sizeof(_instrumentMapping)); +} + +MidiDriver_Accolade_MT32::~MidiDriver_Accolade_MT32() { + Common::StackLock lock(_mutex); + if (_driver) { + _driver->setTimerCallback(0, 0); + _driver->close(); + delete _driver; + } + _driver = NULL; +} + +int MidiDriver_Accolade_MT32::open() { + assert(!_driver); + +// debugC(kDebugLevelMT32Driver, "MT32: starting driver"); + + // Setup midi driver + MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_PREFER_MT32); + MusicType musicType = MidiDriver::getMusicType(dev); + + // check, if we got a real MT32 (or MUNT, or MUNT over MIDI) + switch (musicType) { + case MT_MT32: + _nativeMT32 = true; + break; + case MT_GM: + if (ConfMan.getBool("native_mt32")) { + _nativeMT32 = true; + } + break; + default: + break; + } + + _driver = MidiDriver::createMidi(dev); + if (!_driver) + return 255; + + int ret = _driver->open(); + if (ret) + return ret; + + if (_nativeMT32) + _driver->sendMT32Reset(); + else + _driver->sendGMReset(); + + return 0; +} + +void MidiDriver_Accolade_MT32::close() { + if (_driver) { + _driver->close(); + } +} + +// MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php +void MidiDriver_Accolade_MT32::send(uint32 b) { + byte command = b & 0xf0; + byte channel = b & 0xf; + + if (command == 0xF0) { + if (_driver) { + _driver->send(b); + } + return; + } + + byte mappedChannel = _channelMapping[channel]; + + if (mappedChannel < AGOS_MIDI_CHANNEL_COUNT) { + // channel mapped to an actual MIDI channel, so use that one + b = (b & 0xFFFFFFF0) | mappedChannel; + if (command == 0xC0) { + // Program change + // Figure out the requested instrument + byte midiInstrument = (b >> 8) & 0xFF; + byte mappedInstrument = _instrumentMapping[midiInstrument]; + + // If there is no actual MT32 (or MUNT), we make a second mapping to General MIDI instruments + if (!_nativeMT32) { + mappedInstrument = (MidiDriver::_mt32ToGm[mappedInstrument]); + } + // And replace it + b = (b & 0xFFFF00FF) | (mappedInstrument << 8); + } + + if (_driver) { + _driver->send(b); + } + } +} + +// Called right at the start, we get an INSTR.DAT entry +bool MidiDriver_Accolade_MT32::setupInstruments(byte *driverData, uint16 driverDataSize, bool useMusicDrvFile) { + uint16 channelMappingOffset = 0; + uint16 channelMappingSize = 0; + uint16 instrumentMappingOffset = 0; + uint16 instrumentMappingSize = 0; + + if (!useMusicDrvFile) { + // INSTR.DAT: we expect at least 354 bytes + if (driverDataSize < 354) + return false; + + // Data is like this: + // 128 bytes instrument mapping + // 128 bytes instrument volume adjust (signed!) (not used for MT32) + // 16 bytes unknown + // 16 bytes channel mapping + // 64 bytes key note mapping (not really used for MT32) + // 1 byte instrument count + // 1 byte bytes per instrument + // x bytes no instruments used for MT32 + + channelMappingOffset = 256 + 16; + channelMappingSize = 16; + instrumentMappingOffset = 0; + instrumentMappingSize = 128; + + } else { + // MUSIC.DRV: we expect at least 468 bytes + if (driverDataSize < 468) + return false; + + channelMappingOffset = 396; + channelMappingSize = 16; + instrumentMappingOffset = 140; + instrumentMappingSize = 128; + } + + // Channel mapping + if (channelMappingSize) { + // Get these 16 bytes for MIDI channel mapping + if (channelMappingSize != sizeof(_channelMapping)) + return false; + + memcpy(_channelMapping, driverData + channelMappingOffset, sizeof(_channelMapping)); + } else { + // Set up straight mapping + for (uint16 channelNr = 0; channelNr < sizeof(_channelMapping); channelNr++) { + _channelMapping[channelNr] = channelNr; + } + } + + if (instrumentMappingSize) { + // And these for instrument mapping + if (instrumentMappingSize > sizeof(_instrumentMapping)) + return false; + + memcpy(_instrumentMapping, driverData + instrumentMappingOffset, instrumentMappingSize); + } + // Set up straight mapping for the remaining data + for (uint16 instrumentNr = instrumentMappingSize; instrumentNr < sizeof(_instrumentMapping); instrumentNr++) { + _instrumentMapping[instrumentNr] = instrumentNr; + } + return true; +} + +MidiDriver *MidiDriver_Accolade_MT32_create() { + return new MidiDriver_Accolade_MT32(); +} + +bool MidiDriver_Accolade_MT32_setupInstruments(MidiDriver *driver, byte *instrumentData, uint16 instrumentDataSize, bool useMusicDrvFile) { + return static_cast<MidiDriver_Accolade_MT32 *>(driver)->setupInstruments(instrumentData, instrumentDataSize, useMusicDrvFile); +} + +} // End of namespace AGOS diff --git a/engines/agos/midi.cpp b/engines/agos/midi.cpp index 045fd9dac5..604824777e 100644 --- a/engines/agos/midi.cpp +++ b/engines/agos/midi.cpp @@ -27,6 +27,10 @@ #include "agos/agos.h" #include "agos/midi.h" +#include "agos/drivers/accolade/mididriver.h" + +#include "gui/message.h" + namespace AGOS { @@ -42,6 +46,8 @@ MidiPlayer::MidiPlayer() { _driver = 0; _map_mt32_to_gm = false; + _adlibPatches = NULL; + _enable_sfx = true; _current = 0; @@ -55,6 +61,8 @@ MidiPlayer::MidiPlayer() { _loopTrackDefault = false; _queuedTrack = 255; _loopQueuedTrack = 0; + + _accoladeMode = false; } MidiPlayer::~MidiPlayer() { @@ -68,13 +76,254 @@ MidiPlayer::~MidiPlayer() { } _driver = NULL; clearConstructs(); + unloadAdlibPatches(); } -int MidiPlayer::open(int gameType) { +int MidiPlayer::open(int gameType, bool isDemo) { // Don't call open() twice! assert(!_driver); - // Setup midi driver + bool accoladeUseMusicDrvFile = false; + MusicType accoladeMusicType = MT_INVALID; + + switch (gameType) { + case GType_ELVIRA1: + _accoladeMode = true; + break; + case GType_ELVIRA2: + case GType_WW: + // Attention: Elvira 2 shipped with INSTR.DAT and MUSIC.DRV + // MUSIC.DRV is the correct one. INSTR.DAT seems to be a left-over + _accoladeMode = true; + accoladeUseMusicDrvFile = true; + break; + case GType_SIMON1: + if (isDemo) { + _accoladeMode = true; + accoladeUseMusicDrvFile = true; + } + break; + default: + break; + } + + if (_accoladeMode) { + MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MT32); + accoladeMusicType = MidiDriver::getMusicType(dev); + + switch (accoladeMusicType) { + case MT_ADLIB: + case MT_MT32: + break; + case MT_GM: + if (!ConfMan.getBool("native_mt32")) { + // Not a real MT32 / no MUNT + ::GUI::MessageDialog dialog(("You appear to be using a General MIDI device,\n" + "but your game only supports Roland MT32 MIDI.\n" + "We try to map the Roland MT32 instruments to\n" + "General MIDI ones. It is still possible that\n" + "some tracks sound incorrect.")); + dialog.runModal(); + } + // Switch to MT32 driver in any case + accoladeMusicType = MT_MT32; + break; + default: + _accoladeMode = false; + break; + } + } + + if (_accoladeMode) { + // Setup midi driver + switch (accoladeMusicType) { + case MT_ADLIB: + _driver = MidiDriver_Accolade_AdLib_create(); + break; + case MT_MT32: + _driver = MidiDriver_Accolade_MT32_create(); + break; + default: + assert(0); + break; + } + if (!_driver) + return 255; + + byte *instrumentData = NULL; + uint16 instrumentDataSize = 0; + + if (!accoladeUseMusicDrvFile) { + // Elvira 1 / Elvira 2: read INSTR.DAT + Common::File *instrDatStream = new Common::File(); + + if (!instrDatStream->open("INSTR.DAT")) { + error("INSTR.DAT: unable to open file"); + } + + uint32 streamSize = instrDatStream->size(); + uint32 streamLeft = streamSize; + uint16 skipChunks = 0; // 1 for MT32, 0 for AdLib + uint16 chunkSize = 0; + + switch (accoladeMusicType) { + case MT_ADLIB: + skipChunks = 0; + break; + case MT_MT32: + skipChunks = 1; // Skip one entry for MT32 + break; + default: + assert(0); + break; + } + + do { + if (streamLeft < 2) + error("INSTR.DAT: unexpected EOF"); + + chunkSize = instrDatStream->readUint16LE(); + streamLeft -= 2; + + if (streamLeft < chunkSize) + error("INSTR.DAT: unexpected EOF"); + + if (skipChunks) { + // Skip the chunk + instrDatStream->skip(chunkSize); + streamLeft -= chunkSize; + + skipChunks--; + } + } while (skipChunks); + + // Seek over the ASCII string until there is a NUL terminator + byte curByte = 0; + + do { + if (chunkSize == 0) + error("INSTR.DAT: no actual instrument data found"); + + curByte = instrDatStream->readByte(); + chunkSize--; + } while (curByte); + + instrumentDataSize = chunkSize; + + // Read the requested instrument data entry + instrumentData = new byte[instrumentDataSize]; + instrDatStream->read(instrumentData, instrumentDataSize); + + instrDatStream->close(); + delete instrDatStream; + + } else { + // Waxworks / Simon 1 demo: Read MUSIC.DRV + Common::File *musicDrvStream = new Common::File(); + + if (!musicDrvStream->open("MUSIC.DRV")) { + error("MUSIC.DRV: unable to open file"); + } + + uint32 streamSize = musicDrvStream->size(); + uint32 streamLeft = streamSize; + uint16 getChunk = 0; // 4 for MT32, 2 for AdLib + + switch (accoladeMusicType) { + case MT_ADLIB: + getChunk = 2; + break; + case MT_MT32: + getChunk = 4; + break; + default: + assert(0); + break; + } + + if (streamLeft < 2) + error("MUSIC.DRV: unexpected EOF"); + + uint16 chunkCount = musicDrvStream->readUint16LE(); + streamLeft -= 2; + + if (getChunk >= chunkCount) + error("MUSIC.DRV: required chunk not available"); + + uint16 headerOffset = 2 + (28 * getChunk); + streamLeft -= (28 * getChunk); + + if (streamLeft < 28) + error("MUSIC.DRV: unexpected EOF"); + + // Seek to required chunk + musicDrvStream->seek(headerOffset); + musicDrvStream->skip(20); // skip over name + streamLeft -= 20; + + uint16 musicDrvSignature = musicDrvStream->readUint16LE(); + uint16 musicDrvType = musicDrvStream->readUint16LE(); + uint16 chunkOffset = musicDrvStream->readUint16LE(); + uint16 chunkSize = musicDrvStream->readUint16LE(); + + // Security checks + if (musicDrvSignature != 0xFEDC) + error("MUSIC.DRV: chunk signature mismatch"); + if (musicDrvType != 1) + error("MUSIC.DRV: not a music driver"); + if (chunkOffset >= streamSize) + error("MUSIC.DRV: driver chunk points outside of file"); + + streamLeft = streamSize - chunkOffset; + if (streamLeft < chunkSize) + error("MUSIC.DRV: driver chunk is larger than file"); + + instrumentDataSize = chunkSize; + + // Read the requested instrument data entry + instrumentData = new byte[instrumentDataSize]; + + musicDrvStream->seek(chunkOffset); + musicDrvStream->read(instrumentData, instrumentDataSize); + + musicDrvStream->close(); + delete musicDrvStream; + } + + // Pass the instrument data to the driver + bool instrumentSuccess = false; + + switch (accoladeMusicType) { + case MT_ADLIB: + instrumentSuccess = MidiDriver_Accolade_AdLib_setupInstruments(_driver, instrumentData, instrumentDataSize, accoladeUseMusicDrvFile); + break; + case MT_MT32: + instrumentSuccess = MidiDriver_Accolade_MT32_setupInstruments(_driver, instrumentData, instrumentDataSize, accoladeUseMusicDrvFile); + break; + default: + assert(0); + break; + } + delete[] instrumentData; + + if (!instrumentSuccess) { + // driver did not like the contents + if (!accoladeUseMusicDrvFile) + error("INSTR.DAT: contents not acceptable"); + else + error("MUSIC.DRV: contents not acceptable"); + } + + int ret = _driver->open(); + if (ret == 0) { + // Reset is done inside our MIDI driver + _driver->setTimerCallback(this, &onTimer); + } + + //setTimerRate(_driver->getBaseTempo()); + return 0; + } + MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_ADLIB | MDT_MIDI | (gameType == GType_SIMON1 ? MDT_PREFER_MT32 : MDT_PREFER_GM)); _nativeMT32 = ((MidiDriver::getMusicType(dev) == MT_MT32) || ConfMan.getBool("native_mt32")); @@ -85,6 +334,12 @@ int MidiPlayer::open(int gameType) { if (_nativeMT32) _driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE); + /* Disabled due to not sounding right, and low volume level + if (gameType == GType_SIMON1 && MidiDriver::getMusicType(dev) == MT_ADLIB) { + loadAdlibPatches(); + } + */ + _map_mt32_to_gm = (gameType != GType_SIMON2 && !_nativeMT32); int ret = _driver->open(); @@ -104,6 +359,12 @@ void MidiPlayer::send(uint32 b) { if (!_current) return; + if (_accoladeMode) { + // Send directly to Accolade driver + _driver->send(b); + return; + } + byte channel = (byte)(b & 0x0F); if ((b & 0xFFF0) == 0x07B0) { // Adjust volume changes by master music and master sfx volume. @@ -114,8 +375,10 @@ void MidiPlayer::send(uint32 b) { else if (_current == &_music) volume = volume * _musicVolume / 255; b = (b & 0xFF00FFFF) | (volume << 16); - } else if ((b & 0xF0) == 0xC0 && _map_mt32_to_gm) { - b = (b & 0xFFFF00FF) | (MidiDriver::_mt32ToGm[(b >> 8) & 0xFF] << 8); + } else if ((b & 0xF0) == 0xC0) { + if (_map_mt32_to_gm && !_adlibPatches) { + b = (b & 0xFFFF00FF) | (MidiDriver::_mt32ToGm[(b >> 8) & 0xFF] << 8); + } } else if ((b & 0xFFF0) == 0x007BB0) { // Only respond to an All Notes Off if this channel // has already been allocated. @@ -135,8 +398,10 @@ void MidiPlayer::send(uint32 b) { _current->volume[channel] = 127; } + // Allocate channels if needed if (!_current->channel[channel]) _current->channel[channel] = (channel == 9) ? _driver->getPercussionChannel() : _driver->allocateChannel(); + if (_current->channel[channel]) { if (channel == 9) { if (_current == &_sfx) @@ -144,7 +409,16 @@ void MidiPlayer::send(uint32 b) { else if (_current == &_music) _current->channel[9]->volume(_current->volume[9] * _musicVolume / 255); } - _current->channel[channel]->send(b); + + if ((b & 0xF0) == 0xC0 && _adlibPatches) { + // NOTE: In the percussion channel, this function is a + // no-op. Any percussion instruments you hear may + // be the stock ones from adlib.cpp. + _driver->sysEx_customInstrument(_current->channel[channel]->getNumber(), 'ADL ', _adlibPatches + 30 * ((b >> 8) & 0xFF)); + } else { + _current->channel[channel]->send(b); + } + if ((b & 0xFFF0) == 0x79B0) { // We have received a "Reset All Controllers" message // and passed it on to the MIDI driver. This may or may @@ -355,6 +629,47 @@ void MidiPlayer::resetVolumeTable() { } } +void MidiPlayer::loadAdlibPatches() { + Common::File ibk; + + if (!ibk.open("mt_fm.ibk")) + return; + + if (ibk.readUint32BE() == 0x49424b1a) { + _adlibPatches = new byte[128 * 30]; + byte *ptr = _adlibPatches; + + memset(_adlibPatches, 0, 128 * 30); + + for (int i = 0; i < 128; i++) { + byte instr[16]; + + ibk.read(instr, 16); + + ptr[0] = instr[0]; // Modulator Sound Characteristics + ptr[1] = instr[2]; // Modulator Scaling/Output Level + ptr[2] = ~instr[4]; // Modulator Attack/Decay + ptr[3] = ~instr[6]; // Modulator Sustain/Release + ptr[4] = instr[8]; // Modulator Wave Select + ptr[5] = instr[1]; // Carrier Sound Characteristics + ptr[6] = instr[3]; // Carrier Scaling/Output Level + ptr[7] = ~instr[5]; // Carrier Attack/Delay + ptr[8] = ~instr[7]; // Carrier Sustain/Release + ptr[9] = instr[9]; // Carrier Wave Select + ptr[10] = instr[10]; // Feedback/Connection + + // The remaining six bytes are reserved for future use + + ptr += 30; + } + } +} + +void MidiPlayer::unloadAdlibPatches() { + delete[] _adlibPatches; + _adlibPatches = NULL; +} + static const int simon1_gmf_size[] = { 8900, 12166, 2848, 3442, 4034, 4508, 7064, 9730, 6014, 4742, 3138, 6570, 5384, 8909, 6457, 16321, 2742, 8968, 4804, 8442, 7717, diff --git a/engines/agos/midi.h b/engines/agos/midi.h index 398e445535..de0c239719 100644 --- a/engines/agos/midi.h +++ b/engines/agos/midi.h @@ -77,11 +77,15 @@ protected: byte _queuedTrack; bool _loopQueuedTrack; + byte *_adlibPatches; + protected: static void onTimer(void *data); void clearConstructs(); void clearConstructs(MusicInfo &info); void resetVolumeTable(); + void loadAdlibPatches(); + void unloadAdlibPatches(); public: bool _enable_sfx; @@ -109,12 +113,14 @@ public: void setVolume(int musicVol, int sfxVol); public: - int open(int gameType); + int open(int gameType, bool isDemo); // MidiDriver_BASE interface implementation virtual void send(uint32 b); virtual void metaEvent(byte type, byte *data, uint16 length); +private: + bool _accoladeMode; }; } // End of namespace AGOS diff --git a/engines/agos/midiparser_s1d.cpp b/engines/agos/midiparser_s1d.cpp index c2c08bf451..7b9a058efc 100644 --- a/engines/agos/midiparser_s1d.cpp +++ b/engines/agos/midiparser_s1d.cpp @@ -179,12 +179,43 @@ void MidiParser_S1D::parseNextEvent(EventInfo &info) { bool MidiParser_S1D::loadMusic(byte *data, uint32 size) { unloadMusic(); + if (!size) + return false; + // The original actually just ignores the first two bytes. byte *pos = data; - if (*(pos++) != 0xFC) - debug(1, "Expected 0xFC header but found 0x%02X instead", (int) *pos); - - pos += 1; + if (*pos == 0xFC) { + // SysEx found right at the start + // this seems to happen since Elvira 2, we ignore it + // 3rd byte after the SysEx seems to be saved into a global + + // We expect at least 4 bytes in total + if (size < 4) + return false; + + byte skipOffset = pos[2]; // get second byte after the SysEx + // pos[1] seems to have been ignored + // pos[3] is saved into a global inside the original interpreters + + // Waxworks + Simon 1 demo typical header is: + // 0xFC 0x29 0x07 0x01 [0x00/0x01] + // Elvira 2 typical header is: + // 0xFC 0x04 0x06 0x06 + + if (skipOffset >= 6) { + // should be at least 6, so that we skip over the 2 size bytes and the + // smallest SysEx possible + skipOffset -= 2; // 2 size bytes were already read by previous code outside of this method + + if (size <= skipOffset) // Skip to the end of file? -> something is not correct + return false; + + // Do skip over the bytes + pos += skipOffset; + } else { + warning("MidiParser_S1D: unexpected skip offset in music file"); + } + } // And now we're at the actual data. Only one track. _numTracks = 1; diff --git a/engines/agos/module.mk b/engines/agos/module.mk index 7069d8005b..ae9d5cb108 100644 --- a/engines/agos/module.mk +++ b/engines/agos/module.mk @@ -1,6 +1,8 @@ MODULE := engines/agos MODULE_OBJS := \ + drivers/accolade/adlib.o \ + drivers/accolade/mt32.o \ agos.o \ charset.o \ charset-fontdata.o \ diff --git a/engines/agos/rooms.cpp b/engines/agos/rooms.cpp index 6185653d42..d1d6f2b99d 100644 --- a/engines/agos/rooms.cpp +++ b/engines/agos/rooms.cpp @@ -383,7 +383,7 @@ bool AGOSEngine::loadRoomItems(uint16 room) { for (uint16 z = minNum; z <= maxNum; z++) { uint16 itemNum = z + 2; item = derefItem(itemNum); - item->parent = 0; + _itemArrayPtr[itemNum] = 0; uint16 num = (itemNum - _itemArrayInited); _roomStates[num].state = item->state; @@ -453,6 +453,7 @@ bool AGOSEngine::loadRoomItems(uint16 room) { item->classFlags = _roomStates[num].classFlags; SubRoom *subRoom = (SubRoom *)findChildOfType(item, kRoomType); subRoom->roomExitStates = _roomStates[num].roomExitStates; + } in.close(); diff --git a/engines/agos/saveload.cpp b/engines/agos/saveload.cpp index 5d5e2d7b03..b968ae645c 100644 --- a/engines/agos/saveload.cpp +++ b/engines/agos/saveload.cpp @@ -1261,7 +1261,6 @@ bool AGOSEngine_Elvira2::loadGame(const Common::String &filename, bool restartMo uint16 room = _currentRoom; _currentRoom = f->readUint16BE(); - if (_roomsListPtr) { byte *p = _roomsListPtr; if (room == _currentRoom) { @@ -1293,8 +1292,7 @@ bool AGOSEngine_Elvira2::loadGame(const Common::String &filename, bool restartMo for (uint16 z = minNum; z <= maxNum; z++) { uint16 itemNum = z + 2; - Item *item = derefItem(itemNum); - item->parent = 0; + _itemArrayPtr[itemNum] = 0; } } } @@ -1318,6 +1316,9 @@ bool AGOSEngine_Elvira2::loadGame(const Common::String &filename, bool restartMo uint parent = f->readUint16BE(); uint next = f->readUint16BE(); + if (getGameType() == GType_WW && getPlatform() == Common::kPlatformDOS && derefItem(item->parent) == NULL) + item->parent = 0; + parent_item = derefItem(parent); setItemParent(item, parent_item); diff --git a/engines/agos/sound.cpp b/engines/agos/sound.cpp index 812f46504f..78c67b2080 100644 --- a/engines/agos/sound.cpp +++ b/engines/agos/sound.cpp @@ -684,7 +684,7 @@ void Sound::playRawData(byte *soundData, uint sound, uint size, uint freq) { memcpy(buffer, soundData, size); byte flags = 0; - if (_vm->getPlatform() == Common::kPlatformDOS) + if (_vm->getPlatform() == Common::kPlatformDOS && _vm->getGameId() != GID_ELVIRA2) flags = Audio::FLAG_UNSIGNED; Audio::AudioStream *stream = Audio::makeRawStream(buffer, size, freq, flags); diff --git a/engines/agos/zones.cpp b/engines/agos/zones.cpp index 1644213579..5a753d9b4b 100644 --- a/engines/agos/zones.cpp +++ b/engines/agos/zones.cpp @@ -94,8 +94,7 @@ void AGOSEngine::loadZone(uint16 zoneNum, bool useError) { vpe->sfxFile = NULL; - if ((getPlatform() == Common::kPlatformAmiga || getPlatform() == Common::kPlatformAtariST) && - getGameType() == GType_ELVIRA2) { + if (getGameType() == GType_ELVIRA2) { // A singe sound file is used for Amiga and AtariST versions if (loadVGASoundFile(1, 3)) { vpe->sfxFile = _block; diff --git a/engines/bbvs/sound.h b/engines/bbvs/sound.h index 4e44c2b962..4d3253c48e 100644 --- a/engines/bbvs/sound.h +++ b/engines/bbvs/sound.h @@ -38,7 +38,7 @@ public: void stop(); bool isPlaying(); protected: - Audio::SeekableAudioStream *_stream; + Audio::RewindableAudioStream *_stream; Audio::SoundHandle _handle; // Keep the filename for debugging purposes Common::String _filename; diff --git a/engines/cine/detection_tables.h b/engines/cine/detection_tables.h index 6069e3a99b..1188deb1a6 100644 --- a/engines/cine/detection_tables.h +++ b/engines/cine/detection_tables.h @@ -379,6 +379,20 @@ static const CINEGameDescription gameDescriptions[] = { { { "os", + "Demo", + AD_ENTRY1("demo_os", "043859e4cfe3977ad95b6efd00b21c62"), + Common::EN_GRB, + Common::kPlatformDOS, + ADGF_DEMO | ADGF_UNSTABLE, + GUIO0() + }, + GType_OS, + GF_DEMO, + }, + + { + { + "os", "", AD_ENTRY1("procs0", "a9da5531ead0ebf9ad387fa588c0cbb0"), Common::EN_GRB, diff --git a/engines/fullpipe/scene.cpp b/engines/fullpipe/scene.cpp index 00dd70c1b2..5a3fbe34b6 100644 --- a/engines/fullpipe/scene.cpp +++ b/engines/fullpipe/scene.cpp @@ -672,11 +672,6 @@ void Scene::drawContent(int minPri, int maxPri, bool drawBg) { debug(1, "_bigPict: %d objlist: %d", _bigPictureArray1Count, _picObjList.size()); - for (uint i = 0; i < _picObjList.size(); i++) { - debug(1, "%d: %d", i, ((PictureObject *)_picObjList[i])->_priority); - } - - if (drawBg && _bigPictureArray1Count && _picObjList.size()) { _bigPictureArray[0][0]->getDimensions(&point); @@ -743,7 +738,6 @@ void Scene::drawContent(int minPri, int maxPri, bool drawBg) { for (uint i = 1; i < _picObjList.size(); i++) { PictureObject *obj = (PictureObject *)_picObjList[i]; - debug(8, "pri: %d", obj->_priority); if (obj->_priority < minPri || obj->_priority >= maxPri) continue; diff --git a/engines/gob/surface.cpp b/engines/gob/surface.cpp index 42ac2b0d74..ed83e8255c 100644 --- a/engines/gob/surface.cpp +++ b/engines/gob/surface.cpp @@ -470,7 +470,7 @@ void Surface::blitScaled(const Surface &from, Common::Rational scale, int32 tran blitScaled(from, 0, 0, from._width - 1, from._height - 1, 0, 0, scale, transp); } -void Surface::fillRect(uint16 left, uint16 top, uint16 right, uint16 bottom, uint32 color) { +void Surface::fillRect(int16 left, int16 top, int16 right, int16 bottom, uint32 color) { // Just in case those are swapped if (left > right) SWAP(left, right); @@ -481,6 +481,11 @@ void Surface::fillRect(uint16 left, uint16 top, uint16 right, uint16 bottom, uin // Nothing to do return; + left = CLIP<int32>(left , 0, _width - 1); + top = CLIP<int32>(top , 0, _height - 1); + right = CLIP<int32>(right , 0, _width - 1); + bottom = CLIP<int32>(bottom, 0, _height - 1); + // Area to actually fill uint16 width = CLIP<int32>(right - left + 1, 0, _width - left); uint16 height = CLIP<int32>(bottom - top + 1, 0, _height - top); diff --git a/engines/gob/surface.h b/engines/gob/surface.h index c931731908..eb0a5bf1a4 100644 --- a/engines/gob/surface.h +++ b/engines/gob/surface.h @@ -121,7 +121,7 @@ public: void blitScaled(const Surface &from, int16 x, int16 y, Common::Rational scale, int32 transp = -1); void blitScaled(const Surface &from, Common::Rational scale, int32 transp = -1); - void fillRect(uint16 left, uint16 top, uint16 right, uint16 bottom, uint32 color); + void fillRect(int16 left, int16 top, int16 right, int16 bottom, uint32 color); void fill(uint32 color); void clear(); diff --git a/engines/kyra/sound_adlib.cpp b/engines/kyra/sound_adlib.cpp index 89ee41e859..c0e0f67b8e 100644 --- a/engines/kyra/sound_adlib.cpp +++ b/engines/kyra/sound_adlib.cpp @@ -947,15 +947,10 @@ void AdLibDriver::unkOutput2(uint8 chan) { // // This is very strange behavior, and causes problems with the ancient // FMOPL code we borrowed from AdPlug. I've added a workaround. See - // fmopl.cpp for more details. + // audio/softsynth/opl/mame.cpp for more details. // - // More recent versions of the MAME FMOPL don't seem to have this - // problem, but cannot currently be used because of licensing and - // performance issues. - // - // Ken Silverman's AdLib emulator (which can be found on his Web page - - // http://www.advsys.net/ken - and as part of AdPlug) also seems to be - // immune, but is apparently not as feature complete as MAME's. + // Fortunately, the more modern DOSBox FMOPL code does not seem to have + // any trouble with this. writeOPL(0xB0 + chan, 0x20); } diff --git a/engines/lure/res.h b/engines/lure/res.h index 9002ca3056..19fbde1aad 100644 --- a/engines/lure/res.h +++ b/engines/lure/res.h @@ -100,7 +100,6 @@ public: static Resources &getReference(); void reset(); - byte *getResource(uint16 resId); RoomDataList &roomData() { return _roomData; } RoomData *getRoom(uint16 roomNumber); bool checkHotspotExtent(HotspotData *hotspot); diff --git a/engines/mads/action.cpp b/engines/mads/action.cpp index 628f03526f..f1c562675f 100644 --- a/engines/mads/action.cpp +++ b/engines/mads/action.cpp @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/action.h b/engines/mads/action.h index cfd5a3be3f..3ea10cd964 100644 --- a/engines/mads/action.h +++ b/engines/mads/action.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/animation.cpp b/engines/mads/animation.cpp index cf02e7b0b5..e4f44fc308 100644 --- a/engines/mads/animation.cpp +++ b/engines/mads/animation.cpp @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/animation.h b/engines/mads/animation.h index 1c87273e62..46ef85c5eb 100644 --- a/engines/mads/animation.h +++ b/engines/mads/animation.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/assets.cpp b/engines/mads/assets.cpp index a2d495f311..1d4634e383 100644 --- a/engines/mads/assets.cpp +++ b/engines/mads/assets.cpp @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/assets.h b/engines/mads/assets.h index 155590f9bd..8a0dc2cd44 100644 --- a/engines/mads/assets.h +++ b/engines/mads/assets.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/audio.cpp b/engines/mads/audio.cpp index def2cd6c62..8f33f22243 100644 --- a/engines/mads/audio.cpp +++ b/engines/mads/audio.cpp @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/audio.h b/engines/mads/audio.h index 13c540bf85..5c3cd5e682 100644 --- a/engines/mads/audio.h +++ b/engines/mads/audio.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/compression.cpp b/engines/mads/compression.cpp index 79cd1786de..1f6f1ee202 100644 --- a/engines/mads/compression.cpp +++ b/engines/mads/compression.cpp @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/compression.h b/engines/mads/compression.h index f7381e4af3..b560ed33c1 100644 --- a/engines/mads/compression.h +++ b/engines/mads/compression.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/debugger.cpp b/engines/mads/debugger.cpp index 99251f9fbb..a6a4d3edbc 100644 --- a/engines/mads/debugger.cpp +++ b/engines/mads/debugger.cpp @@ -8,12 +8,12 @@ * 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 + * 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. diff --git a/engines/mads/debugger.h b/engines/mads/debugger.h index c8fee5f5b2..70b2cadc65 100644 --- a/engines/mads/debugger.h +++ b/engines/mads/debugger.h @@ -8,12 +8,12 @@ * 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 + * 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. diff --git a/engines/mads/detection.cpp b/engines/mads/detection.cpp index ea71fc8539..57f4776e82 100644 --- a/engines/mads/detection.cpp +++ b/engines/mads/detection.cpp @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/detection_tables.h b/engines/mads/detection_tables.h index faf73996ac..56df09577c 100644 --- a/engines/mads/detection_tables.h +++ b/engines/mads/detection_tables.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/dialogs.cpp b/engines/mads/dialogs.cpp index 7f0b02bc65..d9b27ce926 100644 --- a/engines/mads/dialogs.cpp +++ b/engines/mads/dialogs.cpp @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/dialogs.h b/engines/mads/dialogs.h index 317c7bd792..efd2871d89 100644 --- a/engines/mads/dialogs.h +++ b/engines/mads/dialogs.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/dragonsphere/dragonsphere_scenes.cpp b/engines/mads/dragonsphere/dragonsphere_scenes.cpp index f32d17d9c9..ff01c32174 100644 --- a/engines/mads/dragonsphere/dragonsphere_scenes.cpp +++ b/engines/mads/dragonsphere/dragonsphere_scenes.cpp @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/dragonsphere/dragonsphere_scenes.h b/engines/mads/dragonsphere/dragonsphere_scenes.h index a6c778eca7..173cc667ce 100644 --- a/engines/mads/dragonsphere/dragonsphere_scenes.h +++ b/engines/mads/dragonsphere/dragonsphere_scenes.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/dragonsphere/game_dragonsphere.cpp b/engines/mads/dragonsphere/game_dragonsphere.cpp index 3836adb6d2..b07eab9daa 100644 --- a/engines/mads/dragonsphere/game_dragonsphere.cpp +++ b/engines/mads/dragonsphere/game_dragonsphere.cpp @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/dragonsphere/game_dragonsphere.h b/engines/mads/dragonsphere/game_dragonsphere.h index 7869dc87b4..b57f8833c6 100644 --- a/engines/mads/dragonsphere/game_dragonsphere.h +++ b/engines/mads/dragonsphere/game_dragonsphere.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/events.cpp b/engines/mads/events.cpp index 52569af375..7ba9098935 100644 --- a/engines/mads/events.cpp +++ b/engines/mads/events.cpp @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/events.h b/engines/mads/events.h index 1a92754f10..1a2579cae0 100644 --- a/engines/mads/events.h +++ b/engines/mads/events.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/font.cpp b/engines/mads/font.cpp index f7c1c52703..3e6d23fe6f 100644 --- a/engines/mads/font.cpp +++ b/engines/mads/font.cpp @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/font.h b/engines/mads/font.h index 47df647637..486cadcfff 100644 --- a/engines/mads/font.h +++ b/engines/mads/font.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/game.cpp b/engines/mads/game.cpp index 3d1c194612..91f6cd5630 100644 --- a/engines/mads/game.cpp +++ b/engines/mads/game.cpp @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/game.h b/engines/mads/game.h index d246aa501e..95b54b0d1a 100644 --- a/engines/mads/game.h +++ b/engines/mads/game.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/game_data.cpp b/engines/mads/game_data.cpp index 70e9e6c30b..6421d057e8 100644 --- a/engines/mads/game_data.cpp +++ b/engines/mads/game_data.cpp @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/game_data.h b/engines/mads/game_data.h index 65a9ae1553..e9bf45d8a5 100644 --- a/engines/mads/game_data.h +++ b/engines/mads/game_data.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/globals.cpp b/engines/mads/globals.cpp index 1d088992ea..e4a681d551 100644 --- a/engines/mads/globals.cpp +++ b/engines/mads/globals.cpp @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/globals.h b/engines/mads/globals.h index a6c9b628dd..27553a2b06 100644 --- a/engines/mads/globals.h +++ b/engines/mads/globals.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/hotspots.cpp b/engines/mads/hotspots.cpp index 2af421a112..bd28645504 100644 --- a/engines/mads/hotspots.cpp +++ b/engines/mads/hotspots.cpp @@ -8,12 +8,12 @@ * 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 + * 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. diff --git a/engines/mads/hotspots.h b/engines/mads/hotspots.h index f9334eace8..902275bb21 100644 --- a/engines/mads/hotspots.h +++ b/engines/mads/hotspots.h @@ -8,12 +8,12 @@ * 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 + * 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. diff --git a/engines/mads/inventory.cpp b/engines/mads/inventory.cpp index ca05575ec5..fe1d24baea 100644 --- a/engines/mads/inventory.cpp +++ b/engines/mads/inventory.cpp @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/inventory.h b/engines/mads/inventory.h index cf82de59f1..2897f950e4 100644 --- a/engines/mads/inventory.h +++ b/engines/mads/inventory.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/mads.cpp b/engines/mads/mads.cpp index 374e373035..8c7b6b1ce3 100644 --- a/engines/mads/mads.cpp +++ b/engines/mads/mads.cpp @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/mads.h b/engines/mads/mads.h index 1d641e7c87..901035320a 100644 --- a/engines/mads/mads.h +++ b/engines/mads/mads.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/menu_views.cpp b/engines/mads/menu_views.cpp index 319f5b0f87..cfc3b09461 100644 --- a/engines/mads/menu_views.cpp +++ b/engines/mads/menu_views.cpp @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/menu_views.h b/engines/mads/menu_views.h index f39ea4c3b5..6c8a2a8bdd 100644 --- a/engines/mads/menu_views.h +++ b/engines/mads/menu_views.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/messages.cpp b/engines/mads/messages.cpp index 304c79aa46..d88806150d 100644 --- a/engines/mads/messages.cpp +++ b/engines/mads/messages.cpp @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/messages.h b/engines/mads/messages.h index 22ae0b24b5..764477a7fc 100644 --- a/engines/mads/messages.h +++ b/engines/mads/messages.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/msurface.cpp b/engines/mads/msurface.cpp index 702b9e5f08..f768624278 100644 --- a/engines/mads/msurface.cpp +++ b/engines/mads/msurface.cpp @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/msurface.h b/engines/mads/msurface.h index 754e70bf7f..80891afb83 100644 --- a/engines/mads/msurface.h +++ b/engines/mads/msurface.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/nebular/dialogs_nebular.cpp b/engines/mads/nebular/dialogs_nebular.cpp index 5b9942db07..960a2cc2f4 100644 --- a/engines/mads/nebular/dialogs_nebular.cpp +++ b/engines/mads/nebular/dialogs_nebular.cpp @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/nebular/dialogs_nebular.h b/engines/mads/nebular/dialogs_nebular.h index 0f086f6ec1..4935ee4b8c 100644 --- a/engines/mads/nebular/dialogs_nebular.h +++ b/engines/mads/nebular/dialogs_nebular.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/nebular/game_nebular.cpp b/engines/mads/nebular/game_nebular.cpp index e9a3d0b716..e8e0a4f42c 100644 --- a/engines/mads/nebular/game_nebular.cpp +++ b/engines/mads/nebular/game_nebular.cpp @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/nebular/game_nebular.h b/engines/mads/nebular/game_nebular.h index 1b89d11412..3cf7aefc18 100644 --- a/engines/mads/nebular/game_nebular.h +++ b/engines/mads/nebular/game_nebular.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/nebular/globals_nebular.cpp b/engines/mads/nebular/globals_nebular.cpp index 9f8b8a7888..c44506e546 100644 --- a/engines/mads/nebular/globals_nebular.cpp +++ b/engines/mads/nebular/globals_nebular.cpp @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/nebular/globals_nebular.h b/engines/mads/nebular/globals_nebular.h index 8d0c26d96d..7c7069892e 100644 --- a/engines/mads/nebular/globals_nebular.h +++ b/engines/mads/nebular/globals_nebular.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/nebular/menu_nebular.cpp b/engines/mads/nebular/menu_nebular.cpp index 2fdef3443d..6fe17f3beb 100644 --- a/engines/mads/nebular/menu_nebular.cpp +++ b/engines/mads/nebular/menu_nebular.cpp @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/nebular/menu_nebular.h b/engines/mads/nebular/menu_nebular.h index 77b8b6fc6e..35af0bb34f 100644 --- a/engines/mads/nebular/menu_nebular.h +++ b/engines/mads/nebular/menu_nebular.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/nebular/nebular_scenes.cpp b/engines/mads/nebular/nebular_scenes.cpp index 14cf71d0fc..eb6f7a5610 100644 --- a/engines/mads/nebular/nebular_scenes.cpp +++ b/engines/mads/nebular/nebular_scenes.cpp @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/nebular/nebular_scenes.h b/engines/mads/nebular/nebular_scenes.h index db7738f13b..58a6d1c98f 100644 --- a/engines/mads/nebular/nebular_scenes.h +++ b/engines/mads/nebular/nebular_scenes.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/nebular/nebular_scenes1.cpp b/engines/mads/nebular/nebular_scenes1.cpp index c9eda08859..fd97f71727 100644 --- a/engines/mads/nebular/nebular_scenes1.cpp +++ b/engines/mads/nebular/nebular_scenes1.cpp @@ -8,12 +8,12 @@ * 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 + * 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. diff --git a/engines/mads/nebular/nebular_scenes1.h b/engines/mads/nebular/nebular_scenes1.h index 1afa7fccc1..d8c9059846 100644 --- a/engines/mads/nebular/nebular_scenes1.h +++ b/engines/mads/nebular/nebular_scenes1.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/nebular/nebular_scenes2.cpp b/engines/mads/nebular/nebular_scenes2.cpp index 84910c4de3..1cbd6f56ef 100644 --- a/engines/mads/nebular/nebular_scenes2.cpp +++ b/engines/mads/nebular/nebular_scenes2.cpp @@ -8,12 +8,12 @@ * 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 + * 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. diff --git a/engines/mads/nebular/nebular_scenes2.h b/engines/mads/nebular/nebular_scenes2.h index c860db9470..0ea4702eea 100644 --- a/engines/mads/nebular/nebular_scenes2.h +++ b/engines/mads/nebular/nebular_scenes2.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/nebular/nebular_scenes3.cpp b/engines/mads/nebular/nebular_scenes3.cpp index 4e7781e7a2..5a6edbf995 100644 --- a/engines/mads/nebular/nebular_scenes3.cpp +++ b/engines/mads/nebular/nebular_scenes3.cpp @@ -8,12 +8,12 @@ * 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 + * 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. diff --git a/engines/mads/nebular/nebular_scenes3.h b/engines/mads/nebular/nebular_scenes3.h index 9efd38e9a4..cf925b3867 100644 --- a/engines/mads/nebular/nebular_scenes3.h +++ b/engines/mads/nebular/nebular_scenes3.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/nebular/nebular_scenes4.cpp b/engines/mads/nebular/nebular_scenes4.cpp index 29b17c42c0..c981f6a6e4 100644 --- a/engines/mads/nebular/nebular_scenes4.cpp +++ b/engines/mads/nebular/nebular_scenes4.cpp @@ -8,12 +8,12 @@ * 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 + * 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. diff --git a/engines/mads/nebular/nebular_scenes4.h b/engines/mads/nebular/nebular_scenes4.h index fbd5ce81f0..de11bd4129 100644 --- a/engines/mads/nebular/nebular_scenes4.h +++ b/engines/mads/nebular/nebular_scenes4.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/nebular/nebular_scenes5.cpp b/engines/mads/nebular/nebular_scenes5.cpp index 2470137b2e..95eb429193 100644 --- a/engines/mads/nebular/nebular_scenes5.cpp +++ b/engines/mads/nebular/nebular_scenes5.cpp @@ -8,12 +8,12 @@ * 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 + * 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. diff --git a/engines/mads/nebular/nebular_scenes5.h b/engines/mads/nebular/nebular_scenes5.h index 2face26508..f314ae8513 100644 --- a/engines/mads/nebular/nebular_scenes5.h +++ b/engines/mads/nebular/nebular_scenes5.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/nebular/nebular_scenes6.cpp b/engines/mads/nebular/nebular_scenes6.cpp index d94fb17fd4..d97e37ea0b 100644 --- a/engines/mads/nebular/nebular_scenes6.cpp +++ b/engines/mads/nebular/nebular_scenes6.cpp @@ -8,12 +8,12 @@ * 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 + * 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. diff --git a/engines/mads/nebular/nebular_scenes6.h b/engines/mads/nebular/nebular_scenes6.h index c5cac56626..4fc4a2e8ae 100644 --- a/engines/mads/nebular/nebular_scenes6.h +++ b/engines/mads/nebular/nebular_scenes6.h @@ -8,12 +8,12 @@ * 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 + * 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. diff --git a/engines/mads/nebular/nebular_scenes7.cpp b/engines/mads/nebular/nebular_scenes7.cpp index b40a8054f7..c2a249e5f8 100644 --- a/engines/mads/nebular/nebular_scenes7.cpp +++ b/engines/mads/nebular/nebular_scenes7.cpp @@ -8,12 +8,12 @@ * 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 + * 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. diff --git a/engines/mads/nebular/nebular_scenes7.h b/engines/mads/nebular/nebular_scenes7.h index dfb3c0f16e..b5aeba818c 100644 --- a/engines/mads/nebular/nebular_scenes7.h +++ b/engines/mads/nebular/nebular_scenes7.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/nebular/nebular_scenes8.cpp b/engines/mads/nebular/nebular_scenes8.cpp index 5f8417cee1..a904569624 100644 --- a/engines/mads/nebular/nebular_scenes8.cpp +++ b/engines/mads/nebular/nebular_scenes8.cpp @@ -8,12 +8,12 @@ * 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 + * 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. diff --git a/engines/mads/nebular/nebular_scenes8.h b/engines/mads/nebular/nebular_scenes8.h index 7f2c34a843..439815f05c 100644 --- a/engines/mads/nebular/nebular_scenes8.h +++ b/engines/mads/nebular/nebular_scenes8.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/nebular/sound_nebular.cpp b/engines/mads/nebular/sound_nebular.cpp index 9716e6d522..240c18f6dc 100644 --- a/engines/mads/nebular/sound_nebular.cpp +++ b/engines/mads/nebular/sound_nebular.cpp @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/nebular/sound_nebular.h b/engines/mads/nebular/sound_nebular.h index d2fc552eec..9bc1a49458 100644 --- a/engines/mads/nebular/sound_nebular.h +++ b/engines/mads/nebular/sound_nebular.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/palette.cpp b/engines/mads/palette.cpp index b5ea136abd..7651fe8e65 100644 --- a/engines/mads/palette.cpp +++ b/engines/mads/palette.cpp @@ -8,12 +8,12 @@ * 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. @@ -497,7 +497,7 @@ void Fader::fadeIn(byte palette[PALETTE_SIZE], byte destPalette[PALETTE_SIZE], int baseColor, int numColors, int baseGrey, int numGreys, int tickDelay, int steps) { GreyEntry map[PALETTE_COUNT]; - byte tempPal[PALETTE_SIZE];; + byte tempPal[PALETTE_SIZE]; int8 signs[PALETTE_COUNT][3]; byte palIndex[PALETTE_COUNT][3]; int intensity; diff --git a/engines/mads/palette.h b/engines/mads/palette.h index 5d3bc3a82e..6c98947384 100644 --- a/engines/mads/palette.h +++ b/engines/mads/palette.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/phantom/game_phantom.cpp b/engines/mads/phantom/game_phantom.cpp index 0b2531ef65..cbeb6b0d26 100644 --- a/engines/mads/phantom/game_phantom.cpp +++ b/engines/mads/phantom/game_phantom.cpp @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/phantom/game_phantom.h b/engines/mads/phantom/game_phantom.h index 99cc2c1230..5504054bcf 100644 --- a/engines/mads/phantom/game_phantom.h +++ b/engines/mads/phantom/game_phantom.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/phantom/phantom_scenes.cpp b/engines/mads/phantom/phantom_scenes.cpp index 7fd7ce642d..c2cec47bd9 100644 --- a/engines/mads/phantom/phantom_scenes.cpp +++ b/engines/mads/phantom/phantom_scenes.cpp @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/phantom/phantom_scenes.h b/engines/mads/phantom/phantom_scenes.h index cd295a28b6..0e22610086 100644 --- a/engines/mads/phantom/phantom_scenes.h +++ b/engines/mads/phantom/phantom_scenes.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/player.cpp b/engines/mads/player.cpp index f61a5a1592..bb747f4b52 100644 --- a/engines/mads/player.cpp +++ b/engines/mads/player.cpp @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/player.h b/engines/mads/player.h index 671ac9d16e..e5765a9bca 100644 --- a/engines/mads/player.h +++ b/engines/mads/player.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/rails.cpp b/engines/mads/rails.cpp index 322e6e7cb3..9b2ec71de6 100644 --- a/engines/mads/rails.cpp +++ b/engines/mads/rails.cpp @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/rails.h b/engines/mads/rails.h index e6cab08f85..c95f5c47a6 100644 --- a/engines/mads/rails.h +++ b/engines/mads/rails.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/resources.cpp b/engines/mads/resources.cpp index 1fb75e6ba2..d5352fb205 100644 --- a/engines/mads/resources.cpp +++ b/engines/mads/resources.cpp @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/resources.h b/engines/mads/resources.h index 8d9ab1e39f..95ea17d3c4 100644 --- a/engines/mads/resources.h +++ b/engines/mads/resources.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/scene.cpp b/engines/mads/scene.cpp index 463d4a62fd..5662d8349a 100644 --- a/engines/mads/scene.cpp +++ b/engines/mads/scene.cpp @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/scene.h b/engines/mads/scene.h index 5927f4e70f..c0784c3812 100644 --- a/engines/mads/scene.h +++ b/engines/mads/scene.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/screen.cpp b/engines/mads/screen.cpp index 3e2fe22611..3c27001ae0 100644 --- a/engines/mads/screen.cpp +++ b/engines/mads/screen.cpp @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/screen.h b/engines/mads/screen.h index 85c55419ca..d910e88633 100644 --- a/engines/mads/screen.h +++ b/engines/mads/screen.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/sequence.cpp b/engines/mads/sequence.cpp index ba0111a844..e5bf1631c2 100644 --- a/engines/mads/sequence.cpp +++ b/engines/mads/sequence.cpp @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/sequence.h b/engines/mads/sequence.h index 8b236af15e..c3a277c5a5 100644 --- a/engines/mads/sequence.h +++ b/engines/mads/sequence.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/sound.cpp b/engines/mads/sound.cpp index 4036ee8112..7b9388eee3 100644 --- a/engines/mads/sound.cpp +++ b/engines/mads/sound.cpp @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/sound.h b/engines/mads/sound.h index 5884323474..16128f8284 100644 --- a/engines/mads/sound.h +++ b/engines/mads/sound.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/sprites.cpp b/engines/mads/sprites.cpp index f15d6456d3..0a1c0b710d 100644 --- a/engines/mads/sprites.cpp +++ b/engines/mads/sprites.cpp @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/sprites.h b/engines/mads/sprites.h index bb5fdbe964..3db922c40b 100644 --- a/engines/mads/sprites.h +++ b/engines/mads/sprites.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/staticres.cpp b/engines/mads/staticres.cpp index 189e5f72e7..b659d9a27c 100644 --- a/engines/mads/staticres.cpp +++ b/engines/mads/staticres.cpp @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/staticres.h b/engines/mads/staticres.h index 560fd12e67..b805729327 100644 --- a/engines/mads/staticres.h +++ b/engines/mads/staticres.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/user_interface.cpp b/engines/mads/user_interface.cpp index 1e5a1d80d2..62fd036c03 100644 --- a/engines/mads/user_interface.cpp +++ b/engines/mads/user_interface.cpp @@ -8,12 +8,12 @@ * 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. diff --git a/engines/mads/user_interface.h b/engines/mads/user_interface.h index 71c6f64c2a..1366aa2c32 100644 --- a/engines/mads/user_interface.h +++ b/engines/mads/user_interface.h @@ -8,12 +8,12 @@ * 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. diff --git a/engines/pegasus/sound.cpp b/engines/pegasus/sound.cpp index 5b437b81d4..ddcb2be010 100644 --- a/engines/pegasus/sound.cpp +++ b/engines/pegasus/sound.cpp @@ -59,7 +59,15 @@ void Sound::initFromAIFFFile(const Common::String &fileName) { return; } - _stream = Audio::makeAIFFStream(file, DisposeAfterUse::YES); + Audio::RewindableAudioStream *stream = Audio::makeAIFFStream(file, DisposeAfterUse::YES); + + _stream = dynamic_cast<Audio::SeekableAudioStream *>(stream); + + if (!_stream) { + delete stream; + warning("AIFF stream '%s' is not seekable", fileName.c_str()); + return; + } } void Sound::initFromQuickTime(const Common::String &fileName) { diff --git a/engines/queen/walk.cpp b/engines/queen/walk.cpp index dd7e46c765..17d12b0b9f 100644 --- a/engines/queen/walk.cpp +++ b/engines/queen/walk.cpp @@ -130,7 +130,7 @@ void Walk::animateJoe() { _vm->logic()->joeScale(pbs->scale); pbs->scaleWalkSpeed(6); _vm->update(true); - if (_vm->input()->cutawayQuit() || _vm->logic()->joeWalk() == JWM_EXECUTE) { + if (_vm->input()->cutawayQuit() || _vm->logic()->joeWalk() == JWM_EXECUTE || _vm->shouldQuit()) { stopJoe(); break; } @@ -249,7 +249,7 @@ void Walk::animatePerson(const MovePersonData *mpd, uint16 image, uint16 bobNum, _vm->update(); pbs->scale = pwd->area->calcScale(pbs->y); pbs->scaleWalkSpeed(mpd->moveSpeed); - if (_vm->input()->cutawayQuit()) { + if (_vm->input()->cutawayQuit() || _vm->shouldQuit()) { stopPerson(bobNum); break; } diff --git a/engines/saga/scene.cpp b/engines/saga/scene.cpp index f19645dd99..4fa15d09e5 100644 --- a/engines/saga/scene.cpp +++ b/engines/saga/scene.cpp @@ -835,13 +835,14 @@ void Scene::loadScene(LoadSceneParams &loadSceneParams) { loadSceneParams.sceneProc(SCENE_BEGIN, this); } - // We probably don't want "followers" to go into scene -1 , 0. At the very - // least we don't want garbage to be drawn that early in the ITE intro. - if (_sceneNumber > 0 && _sceneNumber != ITE_SCENE_PUZZLE) - _vm->_actor->updateActorsScene(loadSceneParams.actorsEntrance); - - if (_sceneNumber == ITE_SCENE_PUZZLE) + if (_vm->getGameId() == GID_ITE && _sceneNumber == ITE_SCENE_PUZZLE) { _vm->_puzzle->execute(); + } else { + // We probably don't want "followers" to go into scene -1 , 0. At the very + // least we don't want garbage to be drawn that early in the ITE intro. + if (_sceneNumber > 0) + _vm->_actor->updateActorsScene(loadSceneParams.actorsEntrance); + } if (getFlags() & kSceneFlagShowCursor) { // Activate user interface diff --git a/engines/saga/sndres.cpp b/engines/saga/sndres.cpp index 39578e96f0..b8d03c9c08 100644 --- a/engines/saga/sndres.cpp +++ b/engines/saga/sndres.cpp @@ -327,9 +327,18 @@ bool SndRes::load(ResourceContext *context, uint32 resourceId, SoundBuffer &buff result = true; } break; case kSoundAIFF: { - Audio::SeekableAudioStream *audStream = Audio::makeAIFFStream(READ_STREAM(soundResourceLength), DisposeAfterUse::YES); - buffer.stream = audStream; - buffer.streamLength = audStream->getLength(); + Audio::RewindableAudioStream *audStream = Audio::makeAIFFStream(READ_STREAM(soundResourceLength), DisposeAfterUse::YES); + Audio::SeekableAudioStream *seekStream = dynamic_cast<Audio::SeekableAudioStream *>(audStream); + + if (!seekStream) { + warning("AIFF file is not seekable"); + delete audStream; + result = false; + break; + } + + buffer.stream = seekStream; + buffer.streamLength = seekStream->getLength(); result = true; } break; case kSoundVOC: { diff --git a/engines/sci/console.cpp b/engines/sci/console.cpp index 3f5548aac7..1e95393e4d 100644 --- a/engines/sci/console.cpp +++ b/engines/sci/console.cpp @@ -209,6 +209,11 @@ Console::Console(SciEngine *engine) : GUI::Debugger(), registerCmd("bpe", WRAP_METHOD(Console, cmdBreakpointFunction)); // alias // VM registerCmd("script_steps", WRAP_METHOD(Console, cmdScriptSteps)); + registerCmd("script_objects", WRAP_METHOD(Console, cmdScriptObjects)); + registerCmd("scro", WRAP_METHOD(Console, cmdScriptObjects)); + registerCmd("script_strings", WRAP_METHOD(Console, cmdScriptStrings)); + registerCmd("scrs", WRAP_METHOD(Console, cmdScriptStrings)); + registerCmd("script_said", WRAP_METHOD(Console, cmdScriptSaid)); registerCmd("vm_varlist", WRAP_METHOD(Console, cmdVMVarlist)); registerCmd("vmvarlist", WRAP_METHOD(Console, cmdVMVarlist)); // alias registerCmd("vl", WRAP_METHOD(Console, cmdVMVarlist)); // alias @@ -2828,6 +2833,186 @@ bool Console::cmdScriptSteps(int argc, const char **argv) { return true; } +bool Console::cmdScriptObjects(int argc, const char **argv) { + int curScriptNr = -1; + + if (argc < 2) { + debugPrintf("Shows all objects inside a specified script.\n"); + debugPrintf("Usage: %s <script number>\n", argv[0]); + debugPrintf("Example: %s 999\n", argv[0]); + debugPrintf("<script number> may be * to show objects inside all loaded scripts\n"); + return true; + } + + if (strcmp(argv[1], "*") == 0) { + // get said-strings of all currently loaded scripts + curScriptNr = -1; + } else { + curScriptNr = atoi(argv[1]); + } + + printOffsets(curScriptNr, SCI_SCR_OFFSET_TYPE_OBJECT); + return true; +} + +bool Console::cmdScriptStrings(int argc, const char **argv) { + int curScriptNr = -1; + + if (argc < 2) { + debugPrintf("Shows all strings inside a specified script.\n"); + debugPrintf("Usage: %s <script number>\n", argv[0]); + debugPrintf("Example: %s 999\n", argv[0]); + debugPrintf("<script number> may be * to show strings inside all loaded scripts\n"); + return true; + } + + if (strcmp(argv[1], "*") == 0) { + // get strings of all currently loaded scripts + curScriptNr = -1; + } else { + curScriptNr = atoi(argv[1]); + } + + printOffsets(curScriptNr, SCI_SCR_OFFSET_TYPE_STRING); + return true; +} + +bool Console::cmdScriptSaid(int argc, const char **argv) { + int curScriptNr = -1; + + if (argc < 2) { + debugPrintf("Shows all said-strings inside a specified script.\n"); + debugPrintf("Usage: %s <script number>\n", argv[0]); + debugPrintf("Example: %s 999\n", argv[0]); + debugPrintf("<script number> may be * to show said-strings inside all loaded scripts\n"); + return true; + } + + if (strcmp(argv[1], "*") == 0) { + // get said-strings of all currently loaded scripts + curScriptNr = -1; + } else { + curScriptNr = atoi(argv[1]); + } + + printOffsets(curScriptNr, SCI_SCR_OFFSET_TYPE_SAID); + return true; +} + +void Console::printOffsets(int scriptNr, uint16 showType) { + SegManager *segMan = _engine->_gamestate->_segMan; + Vocabulary *vocab = _engine->_vocabulary; + SegmentId curSegmentNr; + Common::List<SegmentId> segmentNrList; + + SegmentType curSegmentType = SEG_TYPE_INVALID; + SegmentObj *curSegmentObj = NULL; + Script *curScriptObj = NULL; + const byte *curScriptData = NULL; + + segmentNrList.clear(); + if (scriptNr < 0) { + // get offsets of all currently loaded scripts + for (curSegmentNr = 0; curSegmentNr < segMan->_heap.size(); curSegmentNr++) { + curSegmentObj = segMan->_heap[curSegmentNr]; + if (curSegmentObj && curSegmentObj->getType() == SEG_TYPE_SCRIPT) { + segmentNrList.push_back(curSegmentNr); + } + } + + } else { + curSegmentNr = segMan->getScriptSegment(scriptNr); + if (!curSegmentNr) { + debugPrintf("Script %d is currently not loaded/available\n", scriptNr); + return; + } + segmentNrList.push_back(curSegmentNr); + } + + const offsetLookupArrayType *scriptOffsetLookupArray; + offsetLookupArrayType::const_iterator arrayIterator; + int showTypeCount = 0; + + reg_t objectPos; + const char *objectNamePtr = NULL; + const byte *stringPtr = NULL; + const byte *saidPtr = NULL; + + Common::List<SegmentId>::iterator it; + const Common::List<SegmentId>::iterator end = segmentNrList.end(); + + for (it = segmentNrList.begin(); it != end; it++) { + curSegmentNr = *it; + // get object of this segment + curSegmentObj = segMan->getSegmentObj(curSegmentNr); + if (!curSegmentObj) + continue; + + curSegmentType = curSegmentObj->getType(); + if (curSegmentType != SEG_TYPE_SCRIPT) // safety check + continue; + + curScriptObj = (Script *)curSegmentObj; + debugPrintf("=== SCRIPT %d inside Segment %d ===\n", curScriptObj->getScriptNumber(), curSegmentNr); + debugN("=== SCRIPT %d inside Segment %d ===\n", curScriptObj->getScriptNumber(), curSegmentNr); + + // now print the list + scriptOffsetLookupArray = curScriptObj->getOffsetArray(); + curScriptData = curScriptObj->getBuf(); + showTypeCount = 0; + + for (arrayIterator = scriptOffsetLookupArray->begin(); arrayIterator != scriptOffsetLookupArray->end(); arrayIterator++) { + if (arrayIterator->type == showType) { + switch (showType) { + case SCI_SCR_OFFSET_TYPE_OBJECT: + objectPos = make_reg(curSegmentNr, arrayIterator->offset); + objectNamePtr = segMan->getObjectName(objectPos); + debugPrintf(" %03d:%04x: %s\n", arrayIterator->id, arrayIterator->offset, objectNamePtr); + debugN(" %03d:%04x: %s\n", arrayIterator->id, arrayIterator->offset, objectNamePtr); + break; + case SCI_SCR_OFFSET_TYPE_STRING: + stringPtr = curScriptData + arrayIterator->offset; + debugPrintf(" %03d:%04x: '%s' (size %d)\n", arrayIterator->id, arrayIterator->offset, stringPtr, arrayIterator->stringSize); + debugN(" %03d:%04x: '%s' (size %d)\n", arrayIterator->id, arrayIterator->offset, stringPtr, arrayIterator->stringSize); + break; + case SCI_SCR_OFFSET_TYPE_SAID: + saidPtr = curScriptData + arrayIterator->offset; + debugPrintf(" %03d:%04x:\n", arrayIterator->id, arrayIterator->offset); + debugN(" %03d:%04x: ", arrayIterator->id, arrayIterator->offset); + vocab->debugDecipherSaidBlock(saidPtr); + debugN("\n"); + break; + default: + break; + } + showTypeCount++; + } + } + + if (showTypeCount == 0) { + switch (showType) { + case SCI_SCR_OFFSET_TYPE_OBJECT: + debugPrintf(" no objects\n"); + debugN(" no objects\n"); + break; + case SCI_SCR_OFFSET_TYPE_STRING: + debugPrintf(" no strings\n"); + debugN(" no strings\n"); + break; + case SCI_SCR_OFFSET_TYPE_SAID: + debugPrintf(" no said-strings\n"); + debugN(" no said-strings\n"); + break; + default: + break; + } + } + + debugPrintf("\n"); + debugN("\n"); + } +} + bool Console::cmdBacktrace(int argc, const char **argv) { debugPrintf("Call stack (current base: 0x%x):\n", _engine->_gamestate->executionStackBase); Common::List<ExecStack>::const_iterator iter; @@ -3307,6 +3492,7 @@ bool Console::cmdSend(int argc, const char **argv) { // We call run_engine explictly so we can restore the value of r_acc // after execution. run_vm(_engine->_gamestate); + _engine->_gamestate->xs = old_xstack; } diff --git a/engines/sci/console.h b/engines/sci/console.h index 6d024082b5..8b10912fbe 100644 --- a/engines/sci/console.h +++ b/engines/sci/console.h @@ -147,6 +147,9 @@ private: bool cmdBreakpointFunction(int argc, const char **argv); // VM bool cmdScriptSteps(int argc, const char **argv); + bool cmdScriptObjects(int argc, const char **argv); + bool cmdScriptStrings(int argc, const char **argv); + bool cmdScriptSaid(int argc, const char **argv); bool cmdVMVarlist(int argc, const char **argv); bool cmdVMVars(int argc, const char **argv); bool cmdStack(int argc, const char **argv); @@ -166,6 +169,7 @@ private: void printList(List *list); int printNode(reg_t addr); void hexDumpReg(const reg_t *data, int len, int regsPerLine = 4, int startOffset = 0, bool isArray = false); + void printOffsets(int scriptNr, uint16 showType); private: /** diff --git a/engines/sci/detection.cpp b/engines/sci/detection.cpp index 9a41127f6d..bac9b3467a 100644 --- a/engines/sci/detection.cpp +++ b/engines/sci/detection.cpp @@ -371,8 +371,8 @@ static const ADExtraGuiOptionsMap optionsList[] = { { GAMEOPTION_EGA_UNDITHER, { - _s("EGA undithering"), - _s("Enable undithering in EGA games"), + _s("Skip EGA dithering pass (full color backgrounds)"), + _s("Skip dithering pass in EGA games, graphics are shown with full colors"), "disable_dithering", false } diff --git a/engines/sci/detection_tables.h b/engines/sci/detection_tables.h index 312069025b..7cadcfc27e 100644 --- a/engines/sci/detection_tables.h +++ b/engines/sci/detection_tables.h @@ -904,16 +904,14 @@ static const struct ADGameDescription SciGameDescriptions[] = { AD_LISTEND}, Common::EN_ANY, Common::kPlatformDOS, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, -#if 0 // TODO: unknown if these files are corrupt - // Hoyle 1 - English Amiga (from www.back2roots.org) - // SCI interpreter version 0.000.519 - FIXME: some have 0.000.530, others x.yyy.zzz + // Hoyle 1 - English Amiga (from www.back2roots.org - verified by waltervn in bug report #6870) + // Game version 1.000.139, SCI interpreter version x.yyy.zzz {"hoyle1", "", { {"resource.map", 0, "2a72b1aba65fa6e339370eb86d8601d1", 5166}, {"resource.001", 0, "e0dd44069a62a463fd124974b915f10d", 218755}, {"resource.002", 0, "e0dd44069a62a463fd124974b915f10d", 439502}, AD_LISTEND}, Common::EN_ANY, Common::kPlatformAmiga, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, -#endif // Hoyle 2 - English DOS // SCI interpreter version 0.000.572 diff --git a/engines/sci/engine/kparse.cpp b/engines/sci/engine/kparse.cpp index aa89b963cc..f85f33e3e8 100644 --- a/engines/sci/engine/kparse.cpp +++ b/engines/sci/engine/kparse.cpp @@ -117,6 +117,8 @@ reg_t kParse(EngineState *s, int argc, reg_t *argv) { } #endif + voc->replacePronouns(words); + int syntax_fail = voc->parseGNF(words); if (syntax_fail) { @@ -130,6 +132,7 @@ reg_t kParse(EngineState *s, int argc, reg_t *argv) { } else { voc->parserIsValid = true; + voc->storePronounReference(); writeSelectorValue(segMan, event, SELECTOR(claimed), 0); #ifdef DEBUG_PARSER diff --git a/engines/sci/engine/savegame.cpp b/engines/sci/engine/savegame.cpp index 8fca7eabca..93b3a997cc 100644 --- a/engines/sci/engine/savegame.cpp +++ b/engines/sci/engine/savegame.cpp @@ -923,7 +923,7 @@ void gamestate_restore(EngineState *s, Common::SeekableReadStream *fh) { g_sci->_gfxPorts->reset(); // clear screen if (g_sci->_gfxScreen) - g_sci->_gfxScreen->clear(); + g_sci->_gfxScreen->clearForRestoreGame(); #ifdef ENABLE_SCI32 // Also clear any SCI32 planes/screen items currently showing so they // don't show up after the load. diff --git a/engines/sci/engine/script.cpp b/engines/sci/engine/script.cpp index 88becc82cc..6034378ef6 100644 --- a/engines/sci/engine/script.cpp +++ b/engines/sci/engine/script.cpp @@ -20,6 +20,7 @@ * */ +#include "sci/console.h" #include "sci/sci.h" #include "sci/resource.h" #include "sci/util.h" @@ -64,6 +65,11 @@ void Script::freeScript() { _lockers = 1; _markedAsDeleted = false; _objects.clear(); + + _offsetLookupArray.clear(); + _offsetLookupObjectCount = 0; + _offsetLookupStringCount = 0; + _offsetLookupSaidCount = 0; } void Script::load(int script_nr, ResourceManager *resMan, ScriptPatcher *scriptPatcher) { @@ -204,6 +210,397 @@ void Script::load(int script_nr, ResourceManager *resMan, ScriptPatcher *scriptP //_localsCount = (_bufSize - _localsOffset) >> 1; } } + + // find all strings of this script + identifyOffsets(); +} + +void Script::identifyOffsets() { + offsetLookupArrayEntry arrayEntry; + const byte *scriptDataPtr = NULL; + const byte *stringStartPtr = NULL; + const byte *stringDataPtr = NULL; + uint32 scriptDataLeft = 0; + uint32 stringDataLeft = 0; + byte stringDataByte = 0; + uint16 typeObject_id = 0; + uint16 typeString_id = 0; + uint16 typeSaid_id = 0; + + uint16 blockType = 0; + uint16 blockSize = 0; + + _offsetLookupArray.clear(); + _offsetLookupObjectCount = 0; + _offsetLookupStringCount = 0; + _offsetLookupSaidCount = 0; + + if (getSciVersion() < SCI_VERSION_1_1) { + // SCI0 + SCI1 + scriptDataPtr = _buf; + scriptDataLeft = _bufSize; + + // Go through all blocks + if (getSciVersion() == SCI_VERSION_0_EARLY) { + if (scriptDataLeft < 2) + error("Script::identifyOffsets(): unexpected end of script %d", _nr); + scriptDataPtr += 2; + scriptDataLeft -= 2; + } + + do { + if (scriptDataLeft < 2) + error("Script::identifyOffsets(): unexpected end of script %d", _nr); + + blockType = READ_LE_UINT16(scriptDataPtr); + scriptDataPtr += 2; + scriptDataLeft -= 2; + if (blockType == 0) // end of blocks detected + break; + + if (scriptDataLeft < 2) + error("Script::identifyOffsets(): unexpected end of script %d", _nr); + + blockSize = READ_LE_UINT16(scriptDataPtr); + if (blockSize < 4) + error("Script::identifyOffsets(): invalid block size in script %d", _nr); + blockSize -= 4; // block size includes block-type UINT16 and block-size UINT16 + scriptDataPtr += 2; + scriptDataLeft -= 2; + + if (scriptDataLeft < blockSize) + error("Script::identifyOffsets(): invalid block size in script %d", _nr); + + switch (blockType) { + case SCI_OBJ_OBJECT: + case SCI_OBJ_CLASS: + typeObject_id++; + arrayEntry.type = SCI_SCR_OFFSET_TYPE_OBJECT; + arrayEntry.id = typeObject_id; + arrayEntry.offset = scriptDataPtr - _buf + 8; // Calculate offset inside script data (VM uses +8) + arrayEntry.stringSize = 0; + _offsetLookupArray.push_back(arrayEntry); + _offsetLookupObjectCount++; + break; + + case SCI_OBJ_STRINGS: + // string block detected, we now grab all NUL terminated strings out of this block + stringDataPtr = scriptDataPtr; + stringDataLeft = blockSize; + + arrayEntry.type = SCI_SCR_OFFSET_TYPE_STRING; + + do { + if (stringDataLeft < 1) // no more bytes left + break; + + stringStartPtr = stringDataPtr; + + if (stringDataLeft == 1) { + // only 1 byte left and that byte is a [00], in that case we also exit + stringDataByte = *stringStartPtr; + if (stringDataByte == 0x00) + break; + } + + // now look for terminating [NUL] + do { + stringDataByte = *stringDataPtr; + stringDataPtr++; + stringDataLeft--; + if (!stringDataByte) // NUL found, exit this loop + break; + if (stringDataLeft < 1) { + // no more bytes left + warning("Script::identifyOffsets(): string without terminating NUL in script %d", _nr); + break; + } + } while (1); + + if (stringDataByte) + break; + + typeString_id++; + arrayEntry.id = typeString_id; + arrayEntry.offset = stringStartPtr - _buf; // Calculate offset inside script data + arrayEntry.stringSize = stringDataPtr - stringStartPtr; + _offsetLookupArray.push_back(arrayEntry); + _offsetLookupStringCount++; + } while (1); + break; + + case SCI_OBJ_SAID: + // said block detected, we now try to find every single said "string" inside this block + // said strings are terminated with a 0xFF, the string itself may contain words (2 bytes), where + // the second byte of a word may also be a 0xFF. + stringDataPtr = scriptDataPtr; + stringDataLeft = blockSize; + + arrayEntry.type = SCI_SCR_OFFSET_TYPE_SAID; + + do { + if (stringDataLeft < 1) // no more bytes left + break; + + stringStartPtr = stringDataPtr; + if (stringDataLeft == 1) { + // only 1 byte left and that byte is a [00], in that case we also exit + // happens in some scripts, for example Conquests of Camelot, script 997 + // may have been a bug in the compiler or just an intentional filler byte + stringDataByte = *stringStartPtr; + if (stringDataByte == 0x00) + break; + } + + // now look for terminating 0xFF + do { + stringDataByte = *stringDataPtr; + stringDataPtr++; + stringDataLeft--; + if (stringDataByte == 0xFF) // Terminator found, exit this loop + break; + if (stringDataLeft < 1) // no more bytes left + error("Script::identifyOffsets(): said-string without terminator in script %d", _nr); + if (stringDataByte < 0xF0) { + // Part of a word, skip second byte + stringDataPtr++; + stringDataLeft--; + if (stringDataLeft < 1) // no more bytes left + error("Script::identifyOffsets(): said-string without terminator in script %d", _nr); + } + } while (1); + + typeSaid_id++; + arrayEntry.id = typeSaid_id; + arrayEntry.offset = stringStartPtr - _buf; // Calculate offset inside script data + arrayEntry.stringSize = 0; + _offsetLookupArray.push_back(arrayEntry); + _offsetLookupSaidCount++; + } while (1); + break; + + default: + break; + } + + scriptDataPtr += blockSize; + scriptDataLeft -= blockSize; + } while (1); + + } else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1) { + // Strings in SCI1.1 up to SCI2 come after the object instances + scriptDataPtr = _heapStart; + scriptDataLeft = _heapSize; + + if (scriptDataLeft < 4) + error("Script::identifyOffsets(): unexpected end of script in script %d", _nr); + + uint16 endOfStringOffset = READ_SCI11ENDIAN_UINT16(scriptDataPtr); + uint16 objectStartOffset = READ_SCI11ENDIAN_UINT16(scriptDataPtr + 2) * 2 + 4; + + if (scriptDataLeft < objectStartOffset) + error("Script::identifyOffsets(): object start is beyond heap size in script %d", _nr); + if (scriptDataLeft < endOfStringOffset) + error("Script::identifyOffsets(): end of string is beyond heap size in script %d", _nr); + + const byte *endOfStringPtr = scriptDataPtr + endOfStringOffset; + + scriptDataPtr += objectStartOffset; + scriptDataLeft -= objectStartOffset; + + // go through all objects + do { + if (scriptDataLeft < 2) + error("Script::identifyOffsets(): unexpected end of script %d", _nr); + + blockType = READ_SCI11ENDIAN_UINT16(scriptDataPtr); + scriptDataPtr += 2; + scriptDataLeft -= 2; + if (blockType != SCRIPT_OBJECT_MAGIC_NUMBER) + break; + + // Object found, add offset of object + typeObject_id++; + arrayEntry.type = SCI_SCR_OFFSET_TYPE_OBJECT; + arrayEntry.id = typeObject_id; + arrayEntry.offset = scriptDataPtr - _buf - 2; // the VM uses a pointer to the Magic-Number + arrayEntry.stringSize = 0; + _offsetLookupArray.push_back(arrayEntry); + _offsetLookupObjectCount++; + + if (scriptDataLeft < 2) + error("Script::identifyOffsets(): unexpected end of script in script %d", _nr); + + blockSize = READ_SCI11ENDIAN_UINT16(scriptDataPtr) * 2; + if (blockSize < 4) + error("Script::identifyOffsets(): invalid block size in script %d", _nr); + scriptDataPtr += 2; + scriptDataLeft -= 2; + blockSize -= 4; // blocksize contains UINT16 type and UINT16 size + if (scriptDataLeft < blockSize) + error("Script::identifyOffsets(): invalid block size in script %d", _nr); + + scriptDataPtr += blockSize; + scriptDataLeft -= blockSize; + } while (1); + + // now scriptDataPtr points to right at the start of the strings + if (scriptDataPtr > endOfStringPtr) + error("Script::identifyOffsets(): string block / end-of-string block mismatch in script %d", _nr); + + stringDataPtr = scriptDataPtr; + stringDataLeft = endOfStringPtr - scriptDataPtr; // Calculate byte count within string-block + + arrayEntry.type = SCI_SCR_OFFSET_TYPE_STRING; + do { + if (stringDataLeft < 1) // no more bytes left + break; + + stringStartPtr = stringDataPtr; + // now look for terminating [NUL] + do { + stringDataByte = *stringDataPtr; + stringDataPtr++; + stringDataLeft--; + if (!stringDataByte) // NUL found, exit this loop + break; + if (stringDataLeft < 1) { + // no more bytes left + warning("Script::identifyOffsets(): string without terminating NUL in script %d", _nr); + break; + } + } while (1); + + if (stringDataByte) + break; + + typeString_id++; + arrayEntry.id = typeString_id; + arrayEntry.offset = stringStartPtr - _buf; // Calculate offset inside script data + arrayEntry.stringSize = stringDataPtr - stringStartPtr; + _offsetLookupArray.push_back(arrayEntry); + _offsetLookupStringCount++; + } while (1); + + } else if (getSciVersion() == SCI_VERSION_3) { + // SCI3 + uint32 sci3StringOffset = 0; + uint32 sci3RelocationOffset = 0; + uint32 sci3BoundaryOffset = 0; + + if (_bufSize < 22) + error("Script::identifyOffsets(): script %d smaller than expected SCI3-header", _nr); + + sci3StringOffset = READ_LE_UINT32(_buf + 4); + sci3RelocationOffset = READ_LE_UINT32(_buf + 8); + + if (sci3RelocationOffset > _bufSize) + error("Script::identifyOffsets(): relocation offset is beyond end of script %d", _nr); + + // First we get all the objects + scriptDataPtr = getSci3ObjectsPointer(); + scriptDataLeft = _bufSize - (scriptDataPtr - _buf); + do { + if (scriptDataLeft < 2) + error("Script::identifyOffsets(): unexpected end of script %d", _nr); + + blockType = READ_SCI11ENDIAN_UINT16(scriptDataPtr); + scriptDataPtr += 2; + scriptDataLeft -= 2; + if (blockType != SCRIPT_OBJECT_MAGIC_NUMBER) + break; + + // Object found, add offset of object + typeObject_id++; + arrayEntry.type = SCI_SCR_OFFSET_TYPE_OBJECT; + arrayEntry.id = typeObject_id; + arrayEntry.offset = scriptDataPtr - _buf - 2; // the VM uses a pointer to the Magic-Number + arrayEntry.stringSize = 0; + _offsetLookupArray.push_back(arrayEntry); + _offsetLookupObjectCount++; + + if (scriptDataLeft < 2) + error("Script::identifyOffsets(): unexpected end of script in script %d", _nr); + + blockSize = READ_SCI11ENDIAN_UINT16(scriptDataPtr); + if (blockSize < 4) + error("Script::identifyOffsets(): invalid block size in script %d", _nr); + scriptDataPtr += 2; + scriptDataLeft -= 2; + blockSize -= 4; // blocksize contains UINT16 type and UINT16 size + if (scriptDataLeft < blockSize) + error("Script::identifyOffsets(): invalid block size in script %d", _nr); + + scriptDataPtr += blockSize; + scriptDataLeft -= blockSize; + } while (1); + + // And now we get all the strings + if (sci3StringOffset > 0) { + // string offset set, we expect strings + if (sci3StringOffset > _bufSize) + error("Script::identifyOffsets(): string offset is beyond end of script %d", _nr); + + if (sci3RelocationOffset < sci3StringOffset) + error("Script::identifyOffsets(): string offset points beyond relocation offset in script %d", _nr); + + stringDataPtr = _buf + sci3StringOffset; + stringDataLeft = sci3RelocationOffset - sci3StringOffset; + + arrayEntry.type = SCI_SCR_OFFSET_TYPE_STRING; + + do { + if (stringDataLeft < 1) // no more bytes left + break; + + stringStartPtr = stringDataPtr; + + if (stringDataLeft == 1) { + // only 1 byte left and that byte is a [00], in that case we also exit + stringDataByte = *stringStartPtr; + if (stringDataByte == 0x00) + break; + } + + // now look for terminating [NUL] + do { + stringDataByte = *stringDataPtr; + stringDataPtr++; + stringDataLeft--; + if (!stringDataByte) // NUL found, exit this loop + break; + if (stringDataLeft < 1) { + // no more bytes left + warning("Script::identifyOffsets(): string without terminating NUL in script %d", _nr); + break; + } + } while (1); + + if (stringDataByte) + break; + + typeString_id++; + arrayEntry.id = typeString_id; + arrayEntry.offset = stringStartPtr - _buf; // Calculate offset inside script data + arrayEntry.stringSize = stringDataPtr - stringStartPtr; + _offsetLookupArray.push_back(arrayEntry); + _offsetLookupStringCount++; + + // SCI3 seems to have aligned all string on DWORD boundaries + sci3BoundaryOffset = stringDataPtr - _buf; // Calculate current offset inside script data + sci3BoundaryOffset = sci3BoundaryOffset & 3; // Check boundary offset + if (sci3BoundaryOffset) { + // lower 2 bits are set? Then we have to adjust the offset + sci3BoundaryOffset = 4 - sci3BoundaryOffset; + if (stringDataLeft < sci3BoundaryOffset) + error("Script::identifyOffsets(): SCI3 string boundary adjustment goes beyond end of string block in script %d", _nr); + stringDataLeft -= sci3BoundaryOffset; + stringDataPtr += sci3BoundaryOffset; + } + } while (1); + } + return; + } } const byte *Script::getSci3ObjectsPointer() { diff --git a/engines/sci/engine/script.h b/engines/sci/engine/script.h index 46d6ace917..755e2f3698 100644 --- a/engines/sci/engine/script.h +++ b/engines/sci/engine/script.h @@ -49,6 +49,21 @@ enum ScriptObjectTypes { typedef Common::HashMap<uint16, Object> ObjMap; +enum ScriptOffsetEntryTypes { + SCI_SCR_OFFSET_TYPE_OBJECT = 0, // classes are handled by this type as well + SCI_SCR_OFFSET_TYPE_STRING, + SCI_SCR_OFFSET_TYPE_SAID +}; + +struct offsetLookupArrayEntry { + uint16 type; // type of entry + uint16 id; // id of this type, first item inside script data is 1, second item is 2, etc. + uint32 offset; // offset of entry within script resource data + uint16 stringSize; // size of string, including terminating [NUL] +}; + +typedef Common::Array<offsetLookupArrayEntry> offsetLookupArrayType; + class Script : public SegmentObj { private: int _nr; /**< Script number */ @@ -75,6 +90,14 @@ private: ObjMap _objects; /**< Table for objects, contains property variables */ +protected: + offsetLookupArrayType _offsetLookupArray; // Table of all elements of currently loaded script, that may get pointed to + +private: + uint16 _offsetLookupObjectCount; + uint16 _offsetLookupStringCount; + uint16 _offsetLookupSaidCount; + public: int getLocalsOffset() const { return _localsOffset; } uint16 getLocalsCount() const { return _localsCount; } @@ -248,6 +271,14 @@ public: */ int getCodeBlockOffsetSci3() { return READ_SCI11ENDIAN_UINT32(_buf); } + /** + * Get the offset array + */ + const offsetLookupArrayType *getOffsetArray() { return &_offsetLookupArray; }; + uint16 getOffsetObjectCount() { return _offsetLookupObjectCount; }; + uint16 getOffsetStringCount() { return _offsetLookupStringCount; }; + uint16 getOffsetSaidCount() { return _offsetLookupSaidCount; }; + private: /** * Processes a relocation block within a SCI0-SCI2.1 script @@ -294,6 +325,11 @@ private: void initializeObjectsSci3(SegManager *segMan, SegmentId segmentId); LocalVariables *allocLocalsSegment(SegManager *segMan); + + /** + * Identifies certain offsets within script data and set up lookup-table + */ + void identifyOffsets(); }; } // End of namespace Sci diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp index 173641f7ee..6915e12a0e 100644 --- a/engines/sci/engine/script_patches.cpp +++ b/engines/sci/engine/script_patches.cpp @@ -1425,6 +1425,37 @@ static const SciScriptPatcherEntry larry2Signatures[] = { // =========================================================================== // Leisure Suit Larry 5 +// In Miami the player can call the green card telephone number and get +// green card including limo at the same time in the English 1.000 PC release. +// This results later in a broken game in case the player doesn't read +// the second telephone number for the actual limousine service, because +// in that case it's impossible for the player to get back to the airport. +// +// We disable the code, that is responsible to make the limo arrive. +// +// This bug was fixed in the European (dual language) versions of the game. +// +// Applies to at least: English PC floppy (1.000) +// Responsible method: sPhone::changeState(40) +static const uint16 larry5SignatureGreenCardLimoBug[] = { + 0x7a, // push2 + SIG_MAGICDWORD, + 0x39, 0x07, // pushi 07 + 0x39, 0x0c, // pushi 0Ch + 0x45, 0x0a, 0x04, // call export 10 of script 0 + 0x78, // push1 + 0x39, 0x26, // pushi 26h (limo arrived flag) + 0x45, 0x07, 0x02, // call export 7 of script 0 (sets flag) + SIG_END +}; + +static const uint16 larry5PatchGreenCardLimoBug[] = { + PATCH_ADDTOOFFSET(+8), + 0x34, PATCH_UINT16(0), // ldi 0000 (dummy) + 0x34, PATCH_UINT16(0), // ldi 0000 (dummy) + PATCH_END +}; + // In one of the conversations near the end (to be exact - room 380 and the text // about using champagne on Reverse Biaz - only used when you actually did that // in the game), the German text is too large, causing the textbox to get too large. @@ -1448,6 +1479,7 @@ static const uint16 larry5PatchGermanEndingPattiTalker[] = { // script, description, signature patch static const SciScriptPatcherEntry larry5Signatures[] = { + { true, 280, "English-only: fix green card limo bug", 1, larry5SignatureGreenCardLimoBug, larry5PatchGreenCardLimoBug }, { true, 380, "German-only: Enlarge Patti Textbox", 1, larry5SignatureGermanEndingPattiTalker, larry5PatchGermanEndingPattiTalker }, SCI_SIGNATUREENTRY_TERMINATOR }; @@ -2091,21 +2123,30 @@ static const uint16 qfg1vgaPatchMoveToCrusher[] = { // Same pathfinding bug as above, where Ego is set to move to an impossible // spot when sneaking. In GuardsTrumpet::changeState, we change the final -// location where Ego is moved from 111, 111 to 114, 114. +// location where Ego is moved from 111, 111 to 116, 116. +// target coordinate is really problematic here. +// +// 114, 114 works when the speed slider is all the way up, but doesn't work +// when the speed slider is not. +// +// It seems that this bug was fixed by Sierra for the Macintosh version. +// +// Applies to at least: English PC floppy +// Responsible method: GuardsTrumpet::changeState(8) // Fixes bug: #6248 static const uint16 qfg1vgaSignatureMoveToCastleGate[] = { + 0x51, SIG_ADDTOOFFSET(+1), // class MoveTo SIG_MAGICDWORD, - 0x51, 0x1f, // class MoveTo 0x36, // push - 0x39, 0x6f, // pushi 6f (111 - x) - 0x3c, // dup (111 - y) + 0x39, 0x6f, // pushi 6f (111d) + 0x3c, // dup (111d) - coordinates 111, 111 0x7c, // pushSelf SIG_END }; static const uint16 qfg1vgaPatchMoveToCastleGate[] = { PATCH_ADDTOOFFSET(+3), - 0x39, 0x72, // pushi 72 (114 - x) + 0x39, 0x74, // pushi 74 (116d), changes coordinates to 116, 116 PATCH_END }; @@ -2303,29 +2344,37 @@ static const SciScriptPatcherEntry qfg1vgaSignatures[] = { // which finally re-enables controls // // A fix is difficult to implement. The code in script 20 is generic and used by multiple objects -// That's why I have decided to change the responsible globals (66h and A1h) during mountSaurus::changeState(5) // -// This fix could cause issues in case there is a cutscene, that contains ego getting on a saurus and -// requires controls not getting re-enabled after getting back up on the saurus. +// Originally I decided to change the responsible globals (66h and A1h) during mountSaurus::changeState(5). +// This worked as far as for controls, but mountSaurus::init changes a few selectors of ego as well, which +// won't get restored in that situation, which then messes up room changes and other things. +// +// I have now decided to change sheepScript::changeState(2) in script 665 instead. +// +// This fix could cause issues in case there is a cutscene, where ego is supposed to get onto the saurus using +// sheepScript. // // Applies to at least: English PC Floppy, English Amiga Floppy // Responsible method: mountSaurus::changeState(), mountSaurus::init(), mountSaurus::dispose() // Fixes bug: #5156 static const uint16 qfg2SignatureSaurusFreeze[] = { 0x3c, // dup - 0x35, 0x05, // ldi 5 + 0x35, 0x02, // ldi 5 SIG_MAGICDWORD, 0x1a, // eq? - 0x30, SIG_UINT16(0x004e), // bnt [ret] - 0x39, SIG_SELECTOR8(contains), // pushi [selector contains] - 0x78, // push1 + 0x30, SIG_UINT16(0x0043), // bnt [ret] + 0x76, // push0 + SIG_ADDTOOFFSET(+61), // skip to dispose code + 0x39, SIG_SELECTOR8(dispose), // pushi "dispose" + 0x76, // push0 + 0x54, 0x04, // self 04 SIG_END }; static const uint16 qfg2PatchSaurusFreeze[] = { - 0x35, 0x01, // ldi 1 - 0xa1, 0x66, // sag 66h - 0xa0, SIG_UINT16(0x00a1), // sag 00A1h + 0x81, 0x66, // lag 66h + 0x2e, SIG_UINT16(0x0040), // bt [to dispose code] + 0x35, 0x00, // ldi 0 (waste 2 bytes) PATCH_END }; @@ -2409,7 +2458,7 @@ static const uint16 qfg2PatchImportCharType[] = { // script, description, signature patch static const SciScriptPatcherEntry qfg2Signatures[] = { - { true, 660, "getting back on saurus freeze fix", 1, qfg2SignatureSaurusFreeze, qfg2PatchSaurusFreeze }, + { true, 665, "getting back on saurus freeze fix", 1, qfg2SignatureSaurusFreeze, qfg2PatchSaurusFreeze }, { true, 805, "import character type fix", 1, qfg2SignatureImportCharType, qfg2PatchImportCharType }, { true, 944, "import dialog continuous calls", 1, qfg2SignatureImportDialog, qfg2PatchImportDialog }, SCI_SIGNATUREENTRY_TERMINATOR @@ -2726,9 +2775,64 @@ static const uint16 sq4CdSignatureWalkInFromBelowRoom45[] = { static const uint16 sq4CdPatchWalkInFromBelowRoom45[] = { PATCH_ADDTOOFFSET(+2), - 0x38, PATCH_UINT16(0x00bc), // pushi 00BCh + 0x38, PATCH_UINT16(0x00bc), // pushi 00BCh PATCH_ADDTOOFFSET(+15), - 0x38, PATCH_UINT16(0x00bb), // pushi 00BBh + 0x38, PATCH_UINT16(0x00bb), // pushi 00BBh + PATCH_END +}; + +// It seems that Sierra forgot to set a script flag, when cleaning out the bank account +// in Space Quest 4 CD. This was probably caused by the whole bank account interaction +// getting a rewrite and polish in the CD version. +// +// Because of this bug, points for changing back clothes will not get awarded, which +// makes it impossible to get a perfect point score in the CD version of the game. +// The points are awarded by rm371::doit in script 371. +// +// We fix this. Bug also happened, when using the original interpreter. +// Bug does not happen for PC floppy. +// +// Attention: Some Let's Plays on youtube show that points are in fact awarded. Which is true. +// But those Let's Plays were actually created by playing a hacked Space Quest 4 version +// (which is part Floppy, part CD version - we consider it to be effectively pirated) +// and not the actual CD version of Space Quest 4. +// It's easy to identify - talkie + store called "Radio Shack" -> is hacked version. +// +// Applies to at least: English PC CD +// Responsible method: but2Script::changeState(2) +// Fixes bug: #6866 +static const uint16 sq4CdSignatureGetPointsForChangingBackClothes[] = { + 0x35, 0x02, // ldi 02 + SIG_MAGICDWORD, + 0x1a, // eq? + 0x30, SIG_UINT16(0x006a), // bnt [state 3] + 0x76, + SIG_ADDTOOFFSET(+46), // jump over "withdraw funds" code + 0x33, 0x33, // jmp [end of state 2, set cycles code] + SIG_ADDTOOFFSET(+51), // jump over "clean bank account" code + 0x35, 0x02, // ldi 02 + 0x65, 0x1a, // aTop cycles + 0x33, 0x0b, // jmp [toss/ret] + 0x3c, // dup + 0x35, 0x03, // ldi 03 + 0x1a, // eq? + 0x31, 0x05, // bnt [toss/ret] + SIG_END +}; + +static const uint16 sq4CdPatchGetPointsForChangingBackClothes[] = { + PATCH_ADDTOOFFSET(+3), + 0x30, PATCH_UINT16(0x0070), // bnt [state 3] + PATCH_ADDTOOFFSET(+47), // "withdraw funds" code + 0x33, 0x39, // jmp [end of state 2, set cycles code] + PATCH_ADDTOOFFSET(+51), + 0x78, // push1 + 0x39, 0x1d, // ldi 1Dh + 0x45, 0x07, 0x02, // call export 7 of script 0 (set flag) -> effectively sets global 73h, bit 2 + 0x35, 0x02, // ldi 02 + 0x65, 0x1c, // aTop cycles + 0x33, 0x05, // jmp [toss/ret] + // check for state 3 code removed to save 6 bytes PATCH_END }; @@ -2827,6 +2931,7 @@ static const SciScriptPatcherEntry sq4Signatures[] = { { true, 298, "Floppy: endless flight", 1, sq4FloppySignatureEndlessFlight, sq4FloppyPatchEndlessFlight }, { true, 700, "Floppy: throw stuff at sequel police bug", 1, sq4FloppySignatureThrowStuffAtSequelPoliceBug, sq4FloppyPatchThrowStuffAtSequelPoliceBug }, { true, 45, "CD: walk in from below for room 45 fix", 1, sq4CdSignatureWalkInFromBelowRoom45, sq4CdPatchWalkInFromBelowRoom45 }, + { true, 396, "CD: get points for changing back clothes fix",1, sq4CdSignatureGetPointsForChangingBackClothes, sq4CdPatchGetPointsForChangingBackClothes }, { true, 0, "CD: Babble icon speech and subtitles fix", 1, sq4CdSignatureBabbleIcon, sq4CdPatchBabbleIcon }, { true, 818, "CD: Speech and subtitles option", 1, sq4CdSignatureTextOptions, sq4CdPatchTextOptions }, { true, 818, "CD: Speech and subtitles option button", 1, sq4CdSignatureTextOptionsButton, sq4CdPatchTextOptionsButton }, diff --git a/engines/sci/engine/workarounds.cpp b/engines/sci/engine/workarounds.cpp index c5d85b8c5b..aab32032f7 100644 --- a/engines/sci/engine/workarounds.cpp +++ b/engines/sci/engine/workarounds.cpp @@ -333,6 +333,7 @@ const SciWorkaroundEntry uninitializedReadWorkarounds[] = { { GID_QFG1VGA, 16, 16, 0, "lassoFailed", "changeState", NULL, -1, { WORKAROUND_FAKE, 0 } }, // qfg1vga: casting the "fetch" spell in the screen with the flowers, temps 0 and 1 - bug #5309 { GID_QFG1VGA, -1, 210, 0, "Encounter", "init", sig_uninitread_qfg1vga_1, 0, { WORKAROUND_FAKE, 0 } }, // qfg1vga: going to the brigands hideout - bug #5515 { GID_QFG2, -1, 71, 0, "theInvSheet", "doit", NULL, 1, { WORKAROUND_FAKE, 0 } }, // accessing the inventory + { GID_QFG2, -1, 79, 0, "TryToMoveTo", "onTarget", NULL, 0, { WORKAROUND_FAKE, 0 } }, // when throwing pot at air elemental, happens when client coordinates are the same as airElemental coordinates. happened to me right after room change - bug #6859 { GID_QFG2, -1, 701, -1, "Alley", "at", NULL, 0, { WORKAROUND_FAKE, 0 } }, // when walking inside the alleys in the town - bug #5019 & #5106 { GID_QFG2, -1, 990, 0, "Restore", "doit", NULL, 364, { WORKAROUND_FAKE, 0 } }, // when pressing enter in restore dialog w/o any saved games present { GID_QFG2, 260, 260, 0, "abdulS", "changeState", sig_uninitread_qfg2_1, -1, { WORKAROUND_FAKE, 0 } }, // During the thief's first mission (in the house), just before Abdul is about to enter the house (where you have to hide in the wardrobe), bug #5153, temps 1 and 2 @@ -602,8 +603,8 @@ const SciWorkaroundEntry kGraphRedrawBox_workarounds[] = { { GID_SQ4, 405, 405, 0, "swimAfterEgo", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // skateOrama when "swimming" in the air - accidental additional parameter specified { GID_SQ4, 405, 405, 0, "", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // skateOrama when "swimming" in the air... Russian version - bug #5573 { GID_SQ4, 406, 406, 0, "egoFollowed", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // FLOPPY: when getting shot by the police - accidental additional parameter specified - { GID_SQ4, 406, 406, 0, "swimAndShoot", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // skateOrama when "swimming" in the air - accidental additional parameter specified - { GID_SQ4, 406, 406, 0, "", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // skateOrama when "swimming" in the air... Russian version - bug #5573 (is for both egoFollowed and swimAndShoot) + { GID_SQ4, -1, 406, 0, "swimAndShoot", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // skateOrama when "swimming" in the air - accidental additional parameter specified + { GID_SQ4, -1, 406, 0, "", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // skateOrama when "swimming" in the air... Russian version - bug #5573 (is for both egoFollowed and swimAndShoot) { GID_SQ4, 410, 410, 0, "swimAfterEgo", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // skateOrama when "swimming" in the air - accidental additional parameter specified { GID_SQ4, 410, 410, 0, "", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // skateOrama when "swimming" in the air... Russian version - bug #5573 { GID_SQ4, 411, 411, 0, "swimAndShoot", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // skateOrama when "swimming" in the air - accidental additional parameter specified diff --git a/engines/sci/graphics/screen.cpp b/engines/sci/graphics/screen.cpp index dfcb5dbc14..ca5b5b3b8c 100644 --- a/engines/sci/graphics/screen.cpp +++ b/engines/sci/graphics/screen.cpp @@ -251,8 +251,8 @@ GfxScreen::~GfxScreen() { free(_displayScreen); } -// should not used regulary, only meant for restore game -void GfxScreen::clear() { +// should not be used regularly; only meant for restore game +void GfxScreen::clearForRestoreGame() { // reset all screen data memset(_visualScreen, 0, _pixels); memset(_priorityScreen, 0, _pixels); diff --git a/engines/sci/graphics/screen.h b/engines/sci/graphics/screen.h index 587d2ef3bc..1c946ef02f 100644 --- a/engines/sci/graphics/screen.h +++ b/engines/sci/graphics/screen.h @@ -76,7 +76,7 @@ public: byte getColorWhite() { return _colorWhite; } byte getColorDefaultVectorData() { return _colorDefaultVectorData; } - void clear(); + void clearForRestoreGame(); void copyToScreen(); void copyFromScreen(byte *buffer); void kernelSyncWithFramebuffer(); diff --git a/engines/sci/parser/vocabulary.cpp b/engines/sci/parser/vocabulary.cpp index 000b037b44..828a57abeb 100644 --- a/engines/sci/parser/vocabulary.cpp +++ b/engines/sci/parser/vocabulary.cpp @@ -74,6 +74,8 @@ Vocabulary::Vocabulary(ResourceManager *resMan, bool foreign) : _resMan(resMan), parser_event = NULL_REG; parserIsValid = false; + + _pronounReference = 0x1000; // Non-existent word } Vocabulary::~Vocabulary() { @@ -738,4 +740,79 @@ int Vocabulary::parseNodes(int *i, int *pos, int type, int nr, int argc, const c return oldPos; } + +// FIXME: Duplicated from said.cpp +static int node_major(ParseTreeNode* node) { + assert(node->type == kParseTreeBranchNode); + assert(node->left->type == kParseTreeLeafNode); + return node->left->value; +} +static bool node_is_terminal(ParseTreeNode* node) { + return (node->right->right && + node->right->right->type != kParseTreeBranchNode); +} +static int node_terminal_value(ParseTreeNode* node) { + assert(node_is_terminal(node)); + return node->right->right->value; +} + +static ParseTreeNode* scanForMajor(ParseTreeNode *tree, int major) { + assert(tree); + + if (node_is_terminal(tree)) { + if (node_major(tree) == major) + return tree; + else + return 0; + } + + ParseTreeNode* ptr = tree->right; + + // Scan children + while (ptr->right) { + ptr = ptr->right; + + if (node_major(ptr->left) == major) + return ptr->left; + } + + if (major == 0x141) + return 0; + + // If not found, go into a 0x141 and try again + tree = scanForMajor(tree, 0x141); + if (!tree) + return 0; + return scanForMajor(tree, major); +} + +bool Vocabulary::storePronounReference() { + assert(parserIsValid); + + ParseTreeNode *ptr = scanForMajor(_parserNodes, 0x142); // 0x142 = object? + + while (ptr && !node_is_terminal(ptr)) + ptr = scanForMajor(ptr, 0x141); + + if (!ptr) + return false; + + _pronounReference = node_terminal_value(ptr); + + debugC(kDebugLevelParser, "Stored pronoun reference: %x", _pronounReference); + return true; +} + +void Vocabulary::replacePronouns(ResultWordListList &words) { + if (_pronounReference == 0x1000) + return; + + for (ResultWordListList::iterator i = words.begin(); i != words.end(); ++i) + for (ResultWordList::iterator j = i->begin(); j != i->end(); ++j) + if (j->_class & (VOCAB_CLASS_PRONOUN << 4)) { + j->_class = VOCAB_CLASS_NOUN << 4; + j->_group = _pronounReference; + } +} + } // End of namespace Sci diff --git a/engines/sci/parser/vocabulary.h b/engines/sci/parser/vocabulary.h index 09499946cb..f4adee6e55 100644 --- a/engines/sci/parser/vocabulary.h +++ b/engines/sci/parser/vocabulary.h @@ -233,6 +233,16 @@ public: int parseGNF(const ResultWordListList &words, bool verbose = false); /** + * Find and store reference for future pronouns + */ + bool storePronounReference(); + + /** + * Replace pronouns by stored reference + */ + void replacePronouns(ResultWordListList &words); + + /** * Constructs the Greibach Normal Form of the grammar supplied in 'branches'. * @param verbose Set to true for debugging. If true, the list is * freed before the function ends @@ -360,6 +370,8 @@ private: SynonymList _synonyms; /**< The list of synonyms */ Common::Array<Common::List<AltInput> > _altInputs; + int _pronounReference; + public: // Accessed by said() ParseTreeNode _parserNodes[VOCAB_TREE_NODES]; /**< The parse tree */ diff --git a/engines/sci/sound/audio.cpp b/engines/sci/sound/audio.cpp index 8e35d6b055..fb9a3f17b9 100644 --- a/engines/sci/sound/audio.cpp +++ b/engines/sci/sound/audio.cpp @@ -391,18 +391,13 @@ Audio::RewindableAudioStream *AudioPlayer::getAudioStream(uint32 number, uint32 } else if (audioRes->size > 4 && READ_BE_UINT32(audioRes->data) == MKTAG('F','O','R','M')) { // AIFF detected Common::SeekableReadStream *waveStream = new Common::MemoryReadStream(audioRes->data, audioRes->size, DisposeAfterUse::NO); + Audio::RewindableAudioStream *rewindStream = Audio::makeAIFFStream(waveStream, DisposeAfterUse::YES); + audioSeekStream = dynamic_cast<Audio::SeekableAudioStream *>(rewindStream); - // Calculate samplelen from AIFF header - int waveSize = 0, waveRate = 0; - byte waveFlags = 0; - bool ret = Audio::loadAIFFFromStream(*waveStream, waveSize, waveRate, waveFlags); - if (!ret) - error("Failed to load AIFF from stream"); - - *sampleLen = (waveFlags & Audio::FLAG_16BITS ? waveSize >> 1 : waveSize) * 60 / waveRate; - - waveStream->seek(0, SEEK_SET); - audioStream = Audio::makeAIFFStream(waveStream, DisposeAfterUse::YES); + if (!audioSeekStream) { + warning("AIFF file is not seekable"); + delete rewindStream; + } } else if (audioRes->size > 14 && READ_BE_UINT16(audioRes->data) == 1 && READ_BE_UINT16(audioRes->data + 2) == 1 && READ_BE_UINT16(audioRes->data + 4) == 5 && READ_BE_UINT32(audioRes->data + 10) == 0x00018051) { // Mac snd detected diff --git a/engines/scumm/scumm-md5.h b/engines/scumm/scumm-md5.h index bc3548c150..1cbefee105 100644 --- a/engines/scumm/scumm-md5.h +++ b/engines/scumm/scumm-md5.h @@ -1,5 +1,5 @@ /* - This file was generated by the md5table tool on Thu Apr 16 04:45:24 2015 + This file was generated by the md5table tool on Sun May 10 14:23:43 2015 DO NOT EDIT MANUALLY! */ @@ -68,6 +68,7 @@ static const MD5Table md5table[] = { { "114acdc2659a273c220f86ee9edb24c1", "maniac", "V2", "V2", -1, Common::FR_FRA, Common::kPlatformDOS }, { "11ddf1fde76e3156eb3a38da213f484e", "monkey2", "", "", -1, Common::IT_ITA, Common::kPlatformAmiga }, { "11e6e244078ff09b0f3832e35420e0a7", "catalog", "", "Demo", -1, Common::EN_ANY, Common::kPlatformWindows }, + { "12cdc256eae5a461bcc9a49975999841", "atlantis", "Floppy", "Demo", -1, Common::EN_ANY, Common::kPlatformDOS }, { "132bff65e6367c09cc69318ce1b59333", "monkey2", "", "", 11155, Common::EN_ANY, Common::kPlatformAmiga }, { "1387d16aa620dc1c2d1fd87f8a9e7a09", "puttcircus", "", "Demo", -1, Common::FR_FRA, Common::kPlatformWindows }, { "13d2a86a7290813a1c386490447d72db", "fbear", "HE 62", "", -1, Common::EN_ANY, Common::kPlatform3DO }, @@ -120,6 +121,7 @@ static const MD5Table md5table[] = { { "22c9eb04455440131ffc157aeb8d40a8", "fbear", "HE 70", "Demo", -1, Common::EN_ANY, Common::kPlatformWindows }, { "22de86b2f7ec6e5db745ed1123310b44", "spyfox2", "", "Demo", 15832, Common::FR_FRA, Common::kPlatformWindows }, { "22f4ea88a09da12df9308ba30bcb7d0f", "loom", "EGA", "EGA", -1, Common::EN_ANY, Common::kPlatformDOS }, + { "2328be0317008ef047eed7912a4b0850", "pajama2", "HE 98.5", "", -1, Common::EN_GRB, Common::kPlatformWindows }, { "23394c8d29cc63c61313959431a12476", "spyfox", "HE 100", "Updated", -1, Common::EN_ANY, Common::kPlatformWindows }, { "24942a4200d99bdb4bdb78f9c7e07027", "pajama3", "", "Mini Game", 13911, Common::NL_NLD, Common::kPlatformWindows }, { "254fede2f15dbb32a23760d601b01816", "zak", "V1", "", -1, Common::EN_ANY, Common::kPlatformC64 }, @@ -178,7 +180,7 @@ static const MD5Table md5table[] = { { "3a03dab514e4038df192d8a8de469788", "atlantis", "Floppy", "Floppy", -1, Common::EN_ANY, Common::kPlatformAmiga }, { "3a0c35f3c147b98a2bdf8d400cfc4ab5", "indy3", "FM-TOWNS", "", -1, Common::JA_JPN, Common::kPlatformFMTowns }, { "3a3e592b074f595489f7f11e150c398d", "puttzoo", "HE 99", "Updated", -1, Common::EN_USA, Common::kPlatformWindows }, - { "3a5d13675e9a23aedac0bac7730f0ac1", "samnmax", "", "CD", -1, Common::FR_FRA, Common::kPlatformMacintosh }, + { "3a5d13675e9a23aedac0bac7730f0ac1", "samnmax", "", "CD", 228446581, Common::FR_FRA, Common::kPlatformMacintosh }, { "3a5ec90d556d4920976c5578bfbfaf79", "maniac", "NES", "", -1, Common::DE_DEU, Common::kPlatformNES }, { "3ae7f002d9256b8bdf76aaf8a3a069f8", "freddi", "HE 100", "", 34837, Common::EN_GRB, Common::kPlatformWii }, { "3af61c5edf8e15b43dbafd285b2e9777", "puttcircus", "", "Demo", -1, Common::HE_ISR, Common::kPlatformWindows }, @@ -191,6 +193,7 @@ static const MD5Table md5table[] = { { "3df6ead57930488bc61e6e41901d0e97", "fbear", "HE 62", "", -1, Common::EN_ANY, Common::kPlatformMacintosh }, { "3e48298920fab9b7aec5a971e1bd1fab", "pajama3", "", "Demo", -1, Common::EN_GRB, Common::kPlatformWindows }, { "3e861421f494711bc6f619d4aba60285", "airport", "", "", 93231, Common::RU_RUS, Common::kPlatformWindows }, + { "403d2ec4d60d3cdae925e6cbf67716d6", "ft", "", "", 489436643, Common::FR_FRA, Common::kPlatformMacintosh }, { "40564ec47da48a67787d1f9bd043902a", "maniac", "V2 Demo", "V2 Demo", 1988, Common::EN_ANY, Common::kPlatformDOS }, { "4167a92a1d46baa4f4127d918d561f88", "tentacle", "", "CD", 7932, Common::EN_ANY, Common::kPlatformUnknown }, { "41958e24d03181ff9a381a66d048a581", "ft", "", "", -1, Common::PT_BRA, Common::kPlatformUnknown }, @@ -252,6 +255,7 @@ static const MD5Table md5table[] = { { "55f4e9402bec2bded383843123f37c5c", "pajama2", "HE 98.5", "", -1, Common::DE_DEU, Common::kPlatformWindows }, { "566165a7338fa11029e7c14d94fa70d0", "freddi", "HE 73", "Demo", 9800, Common::EN_ANY, Common::kPlatformWindows }, { "56b5922751be7ffd771b38dda56b028b", "freddi", "HE 100", "", 34837, Common::NL_NLD, Common::kPlatformWii }, + { "56e8c37a0a08c3a7076f82417461a877", "indy3", "EGA", "EGA", -1, Common::EN_ANY, Common::kPlatformDOS }, { "5719fc8a13b4638b78d9d8d12f091f94", "puttrace", "HE 99", "", -1, Common::FR_FRA, Common::kPlatformWindows }, { "5798972220cd458be2626d54c80f71d7", "atlantis", "Floppy", "Floppy", -1, Common::IT_ITA, Common::kPlatformAmiga }, { "57a17febe2183f521250e55d55b83e60", "PuttTime", "HE 99", "", -1, Common::FR_FRA, Common::kPlatformWindows }, @@ -640,7 +644,7 @@ static const MD5Table md5table[] = { { "ecc4340c2b801f5af8da4e00c0e432d9", "puttcircus", "", "", -1, Common::NL_NLD, Common::kPlatformUnknown }, { "ed2b074bc3166087a747acb2a3c6abb0", "freddi3", "HE 98.5", "Demo", -1, Common::DE_DEU, Common::kPlatformUnknown }, { "ed361270102e355afe5236954216aba2", "lost", "", "", -1, Common::EN_USA, Common::kPlatformUnknown }, - { "ede149fda3edfc1dbd7347e0737cb583", "tentacle", "", "CD", -1, Common::FR_FRA, Common::kPlatformMacintosh }, + { "ede149fda3edfc1dbd7347e0737cb583", "tentacle", "", "CD", 282830409, Common::FR_FRA, Common::kPlatformMacintosh }, { "edfdb24a499d92c59f824c52987c0eec", "atlantis", "Floppy", "Floppy", -1, Common::FR_FRA, Common::kPlatformDOS }, { "ee41f6afbc5b26fa475754b56fe92048", "puttputt", "HE 61", "", 8032, Common::JA_JPN, Common::kPlatform3DO }, { "ee785fe2569bc9965526e774f7ab86f1", "spyfox", "HE 99", "", -1, Common::FR_FRA, Common::kPlatformMacintosh }, @@ -654,6 +658,7 @@ static const MD5Table md5table[] = { { "f049e38c1f8302b5db6170f1872af89a", "monkey", "CD", "CD", 8955, Common::ES_ESP, Common::kPlatformDOS }, { "f06e66fd45b2f8b0f4a2833ff4476050", "fbpack", "", "", -1, Common::HE_ISR, Common::kPlatformDOS }, { "f08145577e4f13584cc90b3d6e9caa55", "pajama3", "", "Demo", -1, Common::NL_NLD, Common::kPlatformUnknown }, + { "f0ccc12a8704bf57706b42a37f877128", "tentacle", "Floppy", "Floppy", -1, Common::EN_ANY, Common::kPlatformDOS }, { "f1b0e0d587b85052de5534a3847e68fe", "water", "HE 99", "Updated", -1, Common::EN_ANY, Common::kPlatformUnknown }, { "f237bf8a5ef9af78b2a6a4f3901da341", "pajama", "", "Demo", 18354, Common::EN_ANY, Common::kPlatformUnknown }, { "f27b1ba0eadaf2a6617b2b58192d1dbf", "samnmax", "Floppy", "Floppy", -1, Common::DE_DEU, Common::kPlatformDOS }, diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp index 99b4e695bb..c2e0cb2e05 100644 --- a/engines/scumm/scumm.cpp +++ b/engines/scumm/scumm.cpp @@ -316,6 +316,7 @@ ScummEngine::ScummEngine(OSystem *syst, const DetectorResult &dr) _NES_lastTalkingActor = 0; _NES_talkColor = 0; _keepText = false; + _msgCount = 0; _costumeLoader = NULL; _costumeRenderer = NULL; _2byteFontPtr = 0; diff --git a/engines/scumm/scumm.h b/engines/scumm/scumm.h index 30b4d61880..6e0adc3ff3 100644 --- a/engines/scumm/scumm.h +++ b/engines/scumm/scumm.h @@ -1182,6 +1182,7 @@ protected: byte _charsetBuffer[512]; bool _keepText; + byte _msgCount; int _nextLeft, _nextTop; diff --git a/engines/scumm/string.cpp b/engines/scumm/string.cpp index d60c4c6a50..3049fbcf62 100644 --- a/engines/scumm/string.cpp +++ b/engines/scumm/string.cpp @@ -283,6 +283,7 @@ bool ScummEngine::handleNextCharsetCode(Actor *a, int *code) { switch (c) { case 1: c = 13; // new line + _msgCount = _screenWidth; endLoop = true; break; case 2: @@ -293,6 +294,7 @@ bool ScummEngine::handleNextCharsetCode(Actor *a, int *code) { case 3: _haveMsg = (_game.version >= 7) ? 1 : 0xFF; _keepText = false; + _msgCount = 0; endLoop = true; break; case 8: @@ -573,6 +575,9 @@ void ScummEngine::CHARSET_1() { #endif restoreCharsetBg(); } + _msgCount = 0; + } else if (_game.version <= 2) { + _talkDelay += _msgCount * _defaultTalkDelay; } if (_game.version > 3) { @@ -600,6 +605,7 @@ void ScummEngine::CHARSET_1() { // End of text reached, set _haveMsg accordingly _haveMsg = (_game.version >= 7) ? 2 : 1; _keepText = false; + _msgCount = 0; break; } @@ -648,6 +654,7 @@ void ScummEngine::CHARSET_1() { } if (_game.version <= 3) { _charset->printChar(c, false); + _msgCount += 1; } else { if (_game.features & GF_16BIT_COLOR) { // HE games which use sprites for subtitles diff --git a/engines/sherlock/animation.cpp b/engines/sherlock/animation.cpp new file mode 100644 index 0000000000..bbf7c913b7 --- /dev/null +++ b/engines/sherlock/animation.cpp @@ -0,0 +1,324 @@ +/* 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 "sherlock/animation.h" +#include "sherlock/sherlock.h" +#include "common/algorithm.h" + +namespace Sherlock { + +static const int NO_FRAMES = FRAMES_END; + +Animation::Animation(SherlockEngine *vm) : _vm(vm) { +} + +bool Animation::play(const Common::String &filename, bool intro, int minDelay, int fade, + bool setPalette, int speed) { + Events &events = *_vm->_events; + Screen &screen = *_vm->_screen; + Sound &sound = *_vm->_sound; + int soundNumber = 0; + + // Check for any any sound frames for the given animation + const int *soundFrames = checkForSoundFrames(filename, intro); + + // Add on the VDX extension + Common::String vdxName = filename + ".vdx"; + + // Load the animation + Common::SeekableReadStream *stream; + if (!_gfxLibraryFilename.empty()) + stream = _vm->_res->load(vdxName, _gfxLibraryFilename); + else if (_vm->_useEpilogue2) + stream = _vm->_res->load(vdxName, "epilog2.lib"); + else + stream = _vm->_res->load(vdxName, "epilogue.lib"); + + // Load initial image + Common::String vdaName = filename + ".vda"; + ImageFile images(vdaName, true, true); + + events.wait(minDelay); + if (fade != 0 && fade != 255) + screen.fadeToBlack(); + + if (setPalette) { + if (fade != 255) + screen.setPalette(images._palette); + } + + int frameNumber = 0; + Common::Point pt; + bool skipped = false; + while (!_vm->shouldQuit()) { + // Get the next sprite to display + int imageFrame = stream->readSint16LE(); + + if (imageFrame == -2) { + // End of animation reached + break; + } else if (imageFrame != -1) { + // Read position from either animation stream or the sprite frame itself + if (imageFrame < 0) { + imageFrame += 32768; + pt.x = stream->readUint16LE(); + pt.y = stream->readUint16LE(); + } else { + pt = images[imageFrame]._offset; + } + + // Draw the sprite. Note that we explicitly use the raw frame below, rather than the ImageFrame, + // since we don't want the offsets in the image file to be used, just the explicit position we specify + screen.transBlitFrom(images[imageFrame]._frame, pt); + } else { + // At this point, either the sprites for the frame has been complete, or there weren't any sprites + // at all to draw for the frame + if (fade == 255) { + // Gradual fade in + if (screen.equalizePalette(images._palette) == 0) + fade = 0; + } + + // Check if we've reached a frame with sound + if (frameNumber++ == *soundFrames) { + ++soundNumber; + ++soundFrames; + + Common::String sampleFilename; + + if (!intro) { + // regular animation, append 1-digit number + sampleFilename = Common::String::format("%s%01d", filename.c_str(), soundNumber); + } else { + // intro animation, append 2-digit number + sampleFilename = Common::String::format("%s%02d", filename.c_str(), soundNumber); + } + + if (sound._voices) + sound.playSound(sampleFilename, WAIT_RETURN_IMMEDIATELY, 100, _soundLibraryFilename.c_str()); + } + + events.wait(speed * 3); + } + + if (events.kbHit()) { + Common::KeyState keyState = events.getKey(); + if (keyState.keycode == Common::KEYCODE_ESCAPE || + keyState.keycode == Common::KEYCODE_SPACE) { + skipped = true; + break; + } + } else if (events._pressed) { + skipped = true; + break; + } + } + + events.clearEvents(); + sound.stopSound(); + delete stream; + + return !skipped && !_vm->shouldQuit(); +} + +bool Animation::play3DO(const Common::String &filename, bool intro, int minDelay, bool fadeFromGrey, + int speed) { + Events &events = *_vm->_events; + Screen &screen = *_vm->_screen; + Sound &sound = *_vm->_sound; + int soundNumber = 0; + + bool fadeActive = false; + uint16 fadeLimitColor = 0; + uint16 fadeLimitColorRed = 0; + uint16 fadeLimitColorGreen = 0; + uint16 fadeLimitColorBlue = 0; + + // Check for any any sound frames for the given animation + const int *soundFrames = checkForSoundFrames(filename, intro); + + // Add the VDX extension + Common::String indexName = "prologue/" + filename + ".3dx"; + + // Load the animation + Common::File *indexStream = new Common::File(); + + if (!indexStream->open(indexName)) { + warning("unable to open %s\n", indexName.c_str()); + return false; + } + + // Load initial image + Common::String graphicsName = "prologue/" + filename + ".3da"; + ImageFile3DO images(graphicsName, kImageFile3DOType_Animation); + + events.wait(minDelay); + + if (fadeFromGrey) { + fadeActive = true; + fadeLimitColor = 0xCE59; // RGB565: 25, 50, 25 -> "grey" + } + + int frameNumber = 0; + Common::Point pt; + bool skipped = false; + while (!_vm->shouldQuit()) { + // Get the next sprite to display + int imageFrame = indexStream->readSint16BE(); + + if (imageFrame == -2) { + // End of animation reached + break; + } else if (imageFrame != -1) { + // Read position from either animation stream or the sprite frame itself + if (imageFrame < 0) { + imageFrame += 32768; + pt.x = indexStream->readUint16BE(); + pt.y = indexStream->readUint16BE(); + } else { + pt = images[imageFrame]._offset; + } + + // Draw the sprite. Note that we explicitly use the raw frame below, rather than the ImageFrame, + // since we don't want the offsets in the image file to be used, just the explicit position we specify + if (!fadeActive) { + screen.transBlitFrom(images[imageFrame]._frame, pt); + } else { + // Fade active, blit to backbuffer1 + screen._backBuffer1.transBlitFrom(images[imageFrame]._frame, pt); + } + } else { + // At this point, either the sprites for the frame has been complete, or there weren't any sprites + // at all to draw for the frame + + if (fadeActive) { + // process fading + screen.blitFrom3DOcolorLimit(fadeLimitColor); + + if (!fadeLimitColor) { + // we are at the end, so stop + fadeActive = false; + } else { + // decrease limit color + fadeLimitColorRed = fadeLimitColor & 0xF800; + fadeLimitColorGreen = fadeLimitColor & 0x07E0; + fadeLimitColorBlue = fadeLimitColor & 0x001F; + if (fadeLimitColorRed) + fadeLimitColor -= 0x0800; + if (fadeLimitColorGreen) + fadeLimitColor -= 0x0040; // -2 because we are using RGB565, sherlock uses RGB555 + if (fadeLimitColorBlue) + fadeLimitColor -= 0x0001; + } + } + + // Check if we've reached a frame with sound + if (frameNumber++ == *soundFrames) { + ++soundNumber; + ++soundFrames; + + Common::String sampleFilename; + + // append 1-digit number + sampleFilename = Common::String::format("prologue/sounds/%s%01d", filename.c_str(), soundNumber); + + if (sound._voices) + sound.playSound(sampleFilename, WAIT_RETURN_IMMEDIATELY, 100); // no sound library + } + events.wait(speed * 3); + } + + if (events.kbHit()) { + Common::KeyState keyState = events.getKey(); + if (keyState.keycode == Common::KEYCODE_ESCAPE || + keyState.keycode == Common::KEYCODE_SPACE) { + skipped = true; + break; + } + } else if (events._pressed) { + skipped = true; + break; + } + } + + events.clearEvents(); + sound.stopSound(); + delete indexStream; + + return !skipped && !_vm->shouldQuit(); +} + +void Animation::setPrologueNames(const char *const *names, int count) { + for (int idx = 0; idx < count; ++idx, ++names) { + _prologueNames.push_back(*names); + } +} + +void Animation::setPrologueFrames(const int *frames, int count, int maxFrames) { + _prologueFrames.resize(count); + + for (int idx = 0; idx < count; ++idx, frames += maxFrames) { + _prologueFrames[idx].resize(maxFrames); + Common::copy(frames, frames + maxFrames, &_prologueFrames[idx][0]); + } +} + +void Animation::setTitleNames(const char *const *names, int count) { + for (int idx = 0; idx < count; ++idx, ++names) { + _titleNames.push_back(*names); + } +} + +void Animation::setTitleFrames(const int *frames, int count, int maxFrames) { + _titleFrames.resize(count); + + for (int idx = 0; idx < count; ++idx, frames += maxFrames) { + _titleFrames[idx].resize(maxFrames); + Common::copy(frames, frames + maxFrames, &_titleFrames[idx][0]); + } +} + +const int *Animation::checkForSoundFrames(const Common::String &filename, bool intro) { + const int *frames = &NO_FRAMES; + + if (!intro) { + // regular animation is playing + for (uint idx = 0; idx < _prologueNames.size(); ++idx) { + if (filename.equalsIgnoreCase(_prologueNames[idx])) { + frames = &_prologueFrames[idx][0]; + break; + } + } + } else { + // intro-animation is playing + for (uint idx = 0; idx < _titleNames.size(); ++idx) { + if (filename.equalsIgnoreCase(_titleNames[idx])) { + frames = &_titleFrames[idx][0]; + break; + } + } + } + + return frames; +} + +} // End of namespace Sherlock diff --git a/engines/sherlock/animation.h b/engines/sherlock/animation.h new file mode 100644 index 0000000000..f3c95d4027 --- /dev/null +++ b/engines/sherlock/animation.h @@ -0,0 +1,86 @@ +/* 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 SHERLOCK_ANIMATION_H +#define SHERLOCK_ANIMATION_H + +#include "common/scummsys.h" +#include "common/str.h" +#include "common/array.h" + +namespace Sherlock { + +#define FRAMES_END 32000 + +class SherlockEngine; + +class Animation { +private: + SherlockEngine *_vm; + + Common::Array<const char *> _prologueNames; + Common::Array<Common::Array<int> > _prologueFrames; + Common::Array<const char *> _titleNames; + Common::Array<Common::Array<int> > _titleFrames; + + /** + * Checks for whether an animation is being played that has associated sound + */ + const int *checkForSoundFrames(const Common::String &filename, bool intro); +public: + Common::String _soundLibraryFilename; + Common::String _gfxLibraryFilename; + +public: + Animation(SherlockEngine *vm); + + /** + * Load the prologue name array + */ + void setPrologueNames(const char *const *names, int count); + + /** + * Load the prologue frame array + */ + void setPrologueFrames(const int *frames, int count, int maxFrames); + + /** + * Load the title name array + */ + void setTitleNames(const char *const *names, int count); + + /** + * Load the title frame array + */ + void setTitleFrames(const int *frames, int count, int maxFrames); + + /** + * Play a full-screen animation + */ + bool play(const Common::String &filename, bool intro, int minDelay, int fade, bool setPalette, int speed); + + bool play3DO(const Common::String &filename, bool intro, int minDelay, bool fadeFromGrey, int speed); +}; + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/configure.engine b/engines/sherlock/configure.engine new file mode 100644 index 0000000000..a56129a8f0 --- /dev/null +++ b/engines/sherlock/configure.engine @@ -0,0 +1,3 @@ +# This file is included from the main "configure" script +# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] +add_engine sherlock "The Lost Files of Sherlock Holmes" no diff --git a/engines/sherlock/debugger.cpp b/engines/sherlock/debugger.cpp new file mode 100644 index 0000000000..8b7bdaaa53 --- /dev/null +++ b/engines/sherlock/debugger.cpp @@ -0,0 +1,173 @@ +/* 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 "sherlock/debugger.h" +#include "sherlock/sherlock.h" +#include "sherlock/music.h" + +#include "sherlock/scalpel/3do/movie_decoder.h" + +#include "audio/mixer.h" +#include "audio/decoders/aiff.h" +#include "audio/decoders/wave.h" +#include "audio/decoders/3do.h" + +namespace Sherlock { + +Debugger::Debugger(SherlockEngine *vm) : GUI::Debugger(), _vm(vm) { + registerCmd("continue", WRAP_METHOD(Debugger, cmdExit)); + registerCmd("scene", WRAP_METHOD(Debugger, cmdScene)); + registerCmd("3do_playmovie", WRAP_METHOD(Debugger, cmd3DO_PlayMovie)); + registerCmd("3do_playaudio", WRAP_METHOD(Debugger, cmd3DO_PlayAudio)); + registerCmd("song", WRAP_METHOD(Debugger, cmdSong)); + registerCmd("dumpfile", WRAP_METHOD(Debugger, cmdDumpFile)); +} + +void Debugger::postEnter() { + if (!_3doPlayMovieFile.empty()) { + Scalpel3DOMoviePlay(_3doPlayMovieFile.c_str(), Common::Point(0, 0)); + + _3doPlayMovieFile.clear(); + } + + _vm->pauseEngine(false); +} + +int Debugger::strToInt(const char *s) { + if (!*s) + // No string at all + return 0; + else if (toupper(s[strlen(s) - 1]) != 'H') + // Standard decimal string + return atoi(s); + + // Hexadecimal string + uint tmp = 0; + int read = sscanf(s, "%xh", &tmp); + if (read < 1) + error("strToInt failed on string \"%s\"", s); + return (int)tmp; +} + +bool Debugger::cmdScene(int argc, const char **argv) { + if (argc != 2) { + debugPrintf("Format: scene <room>\n"); + return true; + } else { + _vm->_scene->_goToScene = strToInt(argv[1]); + return false; + } +} + +bool Debugger::cmd3DO_PlayMovie(int argc, const char **argv) { + if (argc != 2) { + debugPrintf("Format: 3do_playmovie <3do-movie-file>\n"); + return true; + } + + // play gets postboned until debugger is closed + Common::String filename = argv[1]; + _3doPlayMovieFile = filename; + + return cmdExit(0, 0); +} + +bool Debugger::cmd3DO_PlayAudio(int argc, const char **argv) { + if (argc != 2) { + debugPrintf("Format: 3do_playaudio <3do-audio-file>\n"); + return true; + } + + Common::File *file = new Common::File(); + if (!file->open(argv[1])) { + debugPrintf("can not open specified audio file\n"); + return true; + } + + Audio::AudioStream *testStream; + Audio::SoundHandle testHandle; + + // Try to load the given file as AIFF/AIFC + testStream = Audio::makeAIFFStream(file, DisposeAfterUse::YES); + + if (testStream) { + g_system->getMixer()->playStream(Audio::Mixer::kPlainSoundType, &testHandle, testStream); + _vm->_events->clearEvents(); + + while ((!_vm->shouldQuit()) && g_system->getMixer()->isSoundHandleActive(testHandle)) { + _vm->_events->pollEvents(); + g_system->delayMillis(10); + if (_vm->_events->kbHit()) { + break; + } + } + + debugPrintf("playing completed\n"); + g_system->getMixer()->stopHandle(testHandle); + } + + return true; +} + +bool Debugger::cmdSong(int argc, const char **argv) { + if (argc != 2) { + debugPrintf("Format: song <room>\n"); + return true; + } + + if (!_vm->_music->loadSong(strToInt(argv[1]))) { + debugPrintf("Invalid song number.\n"); + return true; + } + return false; +} + +bool Debugger::cmdDumpFile(int argc, const char **argv) { + if (argc != 2) { + debugPrintf("Format: dumpfile <resource name>\n"); + return true; + } + + Common::SeekableReadStream *s = _vm->_res->load(argv[1]); + if (!s) { + debugPrintf("Invalid resource.\n"); + return true; + } + + byte *buffer = new byte[s->size()]; + s->read(buffer, s->size()); + + Common::DumpFile dumpFile; + dumpFile.open(argv[1]); + + dumpFile.write(buffer, s->size()); + dumpFile.flush(); + dumpFile.close(); + + delete[] buffer; + + debugPrintf("Resource %s has been dumped to disk.\n", argv[1]); + + return true; +} + +} // End of namespace Sherlock diff --git a/engines/sherlock/debugger.h b/engines/sherlock/debugger.h new file mode 100644 index 0000000000..2252f6d8ea --- /dev/null +++ b/engines/sherlock/debugger.h @@ -0,0 +1,79 @@ +/* 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 SHERLOCK_DEBUGGER_H +#define SHERLOCK_DEBUGGER_H + +#include "common/scummsys.h" +#include "gui/debugger.h" + +namespace Sherlock { + +class SherlockEngine; + +class Debugger : public GUI::Debugger { +public: + Debugger(SherlockEngine *vm); + virtual ~Debugger() {} + + void postEnter(); + +private: + SherlockEngine *_vm; + + /** + * Converts a decimal or hexadecimal string into a number + */ + int strToInt(const char *s); + + /** + * Switch to another scene + */ + bool cmdScene(int argc, const char **argv); + + /** + * Plays a 3DO movie + */ + bool cmd3DO_PlayMovie(int argc, const char **argv); + + /** + * Plays a 3DO audio + */ + bool cmd3DO_PlayAudio(int argc, const char **argv); + + /** + * Plays a song + */ + bool cmdSong(int argc, const char **argv); + + /** + * Dumps a file to disk + */ + bool cmdDumpFile(int argc, const char **argv); + +private: + Common::String _3doPlayMovieFile; +}; + +} // End of namespace Sherlock + +#endif /* SHERLOCK_DEBUGGER_H */ diff --git a/engines/sherlock/decompress.cpp b/engines/sherlock/decompress.cpp new file mode 100644 index 0000000000..8e02da3212 --- /dev/null +++ b/engines/sherlock/decompress.cpp @@ -0,0 +1,128 @@ +/* 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 "sherlock/decompress.h" + +namespace Sherlock { + +/** + * Decompresses an LZW compressed resource. If no outSize is specified, it will + * decompress the entire resource. If, however, an explicit size is specified, + * then it means we're already within a resource, and only want to decompress + * part of it. + */ +Common::SeekableReadStream *decompressLZ(Common::SeekableReadStream &source, int32 outSize) { + if (outSize == -1) { + source.seek(5); + outSize = source.readSint32LE(); + } + + byte lzWindow[4096]; + uint16 lzWindowPos; + uint16 cmd; + + byte *outBuffer = new byte[outSize]; + byte *outBufferEnd = outBuffer + outSize; + Common::MemoryReadStream *outS = new Common::MemoryReadStream(outBuffer, outSize, DisposeAfterUse::YES); + + memset(lzWindow, 0xFF, 0xFEE); + lzWindowPos = 0xFEE; + cmd = 0; + + do { + cmd >>= 1; + if (!(cmd & 0x100)) + cmd = source.readByte() | 0xFF00; + + if (cmd & 1) { + byte literal = source.readByte(); + *outBuffer++ = literal; + lzWindow[lzWindowPos] = literal; + lzWindowPos = (lzWindowPos + 1) & 0x0FFF; + } else { + int copyPos, copyLen; + copyPos = source.readByte(); + copyLen = source.readByte(); + copyPos = copyPos | ((copyLen & 0xF0) << 4); + copyLen = (copyLen & 0x0F) + 3; + while (copyLen--) { + byte literal = lzWindow[copyPos]; + copyPos = (copyPos + 1) & 0x0FFF; + *outBuffer++ = literal; + lzWindow[lzWindowPos] = literal; + lzWindowPos = (lzWindowPos + 1) & 0x0FFF; + } + } + } while (outBuffer < outBufferEnd); + + return outS; +} + + +/** + * Decompresses a Rose Tattoo resource + * +Common::SeekableReadStream *decompress32(Common::SeekableReadStream &source, int32 outSize) { + if (outSize == -1) { + outSize = source.readSint32LE(); + } + + byte lzWindow[8192]; + byte *outBuffer = new byte[outSize]; + byte *outBufferEnd = outBuffer + outSize; + Common::MemoryReadStream *outS = new Common::MemoryReadStream(outBuffer, outSize, DisposeAfterUse::YES); + + memset(lzWindow, 0xFF, 8192); + int lzWindowPos = 0xFEE; + int cmd = 0; + + do { + cmd >>= 1; + if (!(cmd & 0x100)) + cmd = source.readByte() | 0xFF00; + + if (cmd & 1) { + byte literal = source.readByte(); + *outBuffer++ = literal; + lzWindow[lzWindowPos] = literal; + lzWindowPos = (lzWindowPos + 1) & 0x0FFF; + } else { + int copyPos, copyLen; + copyPos = source.readByte(); + copyLen = source.readByte(); + copyPos = copyPos | ((copyLen & 0xF0) << 4); + copyLen = (copyLen & 0x0F) + 3; + while (copyLen--) { + byte literal = lzWindow[copyPos]; + copyPos = (copyPos + 1) & 0x0FFF; + *outBuffer++ = literal; + lzWindow[lzWindowPos] = literal; + lzWindowPos = (lzWindowPos + 1) & 0x0FFF; + } + } + } while (outBuffer < outBufferEnd); + + return outS; +} +*/ + +} // namespace Sherlock diff --git a/engines/sherlock/detection.cpp b/engines/sherlock/detection.cpp new file mode 100644 index 0000000000..35a810efb1 --- /dev/null +++ b/engines/sherlock/detection.cpp @@ -0,0 +1,245 @@ +/* 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 "sherlock/sherlock.h" +#include "sherlock/saveload.h" +#include "sherlock/scalpel/scalpel.h" +#include "sherlock/tattoo/tattoo.h" +#include "common/system.h" +#include "common/translation.h" +#include "engines/advancedDetector.h" + +namespace Sherlock { + +struct SherlockGameDescription { + ADGameDescription desc; + + GameType gameID; +}; + +GameType SherlockEngine::getGameID() const { + return _gameDescription->gameID; +} + +Common::Platform SherlockEngine::getPlatform() const { + return _gameDescription->desc.platform; +} + +Common::Language SherlockEngine::getLanguage() const { + return _gameDescription->desc.language; +} + +} // End of namespace Sherlock + +static const PlainGameDescriptor sherlockGames[] = { + { "scalpel", "The Case of the Serrated Scalpel" }, + { "rosetattoo", "The Case of the Rose Tattoo" }, + {0, 0} +}; + + +#define GAMEOPTION_ORIGINAL_SAVES GUIO_GAMEOPTIONS1 +#define GAMEOPTION_FADE_STYLE GUIO_GAMEOPTIONS2 +#define GAMEOPTION_HELP_STYLE GUIO_GAMEOPTIONS3 +#define GAMEOPTION_PORTRAITS_ON GUIO_GAMEOPTIONS4 +#define GAMEOPTION_WINDOW_STYLE GUIO_GAMEOPTIONS5 + +static const ADExtraGuiOptionsMap optionsList[] = { + { + GAMEOPTION_ORIGINAL_SAVES, + { + _s("Use original savegame dialog"), + _s("Files button in-game shows original savegame dialog rather than the ScummVM menu"), + "originalsaveload", + false + } + }, + + { + GAMEOPTION_FADE_STYLE, + { + _s("Pixellated scene transitions"), + _s("When changing scenes, a randomized pixel transition is done"), + "fade_style", + true + } + }, + + { + GAMEOPTION_HELP_STYLE, + { + _s("Don't show hotspots when moving mouse"), + _s("Only show hotspot names after you actually click on a hotspot or action button"), + "help_style", + false + } + }, + + { + GAMEOPTION_PORTRAITS_ON, + { + _s("Show character portraits"), + _s("Show portraits for the characters when conversing"), + "portraits_on", + true + } + }, + + { + GAMEOPTION_WINDOW_STYLE, + { + _s("Slide dialogs into view"), + _s("Slide UI dialogs into view, rather than simply showing them immediately"), + "window_style", + true + } + }, + + AD_EXTRA_GUI_OPTIONS_TERMINATOR +}; + + +#include "sherlock/detection_tables.h" + +class SherlockMetaEngine : public AdvancedMetaEngine { +public: + SherlockMetaEngine() : AdvancedMetaEngine(Sherlock::gameDescriptions, sizeof(Sherlock::SherlockGameDescription), + sherlockGames, optionsList) {} + + virtual const char *getName() const { + return "Sherlock Engine"; + } + + virtual const char *getOriginalCopyright() const { + return "Sherlock Engine (C) 1992-1996 Mythos Software, 1992-1996 (C) Electronic Arts"; + } + + /** + * Creates an instance of the game engine + */ + virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const; + + /** + * Returns a list of features the game's MetaEngine support + */ + virtual bool hasFeature(MetaEngineFeature f) const; + + /** + * Return a list of savegames + */ + virtual SaveStateList listSaves(const char *target) const; + + /** + * Returns the maximum number of allowed save slots + */ + virtual int getMaximumSaveSlot() const; + + /** + * Deletes a savegame in the specified slot + */ + virtual void removeSaveState(const char *target, int slot) const; + + /** + * Given a specified savegame slot, returns extended information for the save + */ + SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const; +}; + +bool SherlockMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const { + const Sherlock::SherlockGameDescription *gd = (const Sherlock::SherlockGameDescription *)desc; + if (gd) { + switch (gd->gameID) { + case Sherlock::GType_SerratedScalpel: + *engine = new Sherlock::Scalpel::ScalpelEngine(syst, gd); + break; + case Sherlock::GType_RoseTattoo: + *engine = new Sherlock::Tattoo::TattooEngine(syst, gd); + break; + default: + error("Unknown game"); + break; + } + } + return gd != 0; +} + +bool SherlockMetaEngine::hasFeature(MetaEngineFeature f) const { + return + (f == kSupportsListSaves) || + (f == kSupportsLoadingDuringStartup) || + (f == kSupportsDeleteSave) || + (f == kSavesSupportMetaInfo) || + (f == kSavesSupportThumbnail); +} + +bool Sherlock::SherlockEngine::hasFeature(EngineFeature f) const { + return + (f == kSupportsRTL) || + (f == kSupportsLoadingDuringRuntime) || + (f == kSupportsSavingDuringRuntime); +} + +bool Sherlock::SherlockEngine::isDemo() const { + return _gameDescription->desc.flags & ADGF_DEMO; +} + +SaveStateList SherlockMetaEngine::listSaves(const char *target) const { + return Sherlock::SaveManager::getSavegameList(target); +} + +int SherlockMetaEngine::getMaximumSaveSlot() const { + return MAX_SAVEGAME_SLOTS; +} + +void SherlockMetaEngine::removeSaveState(const char *target, int slot) const { + Common::String filename = Sherlock::SaveManager(nullptr, target).generateSaveName(slot); + g_system->getSavefileManager()->removeSavefile(filename); +} + +SaveStateDescriptor SherlockMetaEngine::querySaveMetaInfos(const char *target, int slot) const { + Common::String filename = Sherlock::SaveManager(nullptr, target).generateSaveName(slot); + Common::InSaveFile *f = g_system->getSavefileManager()->openForLoading(filename); + + if (f) { + Sherlock::SherlockSavegameHeader header; + Sherlock::SaveManager::readSavegameHeader(f, header); + delete f; + + // Create the return descriptor + SaveStateDescriptor desc(slot, header._saveName); + desc.setThumbnail(header._thumbnail); + desc.setSaveDate(header._year, header._month, header._day); + desc.setSaveTime(header._hour, header._minute); + desc.setPlayTime(header._totalFrames * GAME_FRAME_TIME); + + return desc; + } + + return SaveStateDescriptor(); +} + + +#if PLUGIN_ENABLED_DYNAMIC(SHERLOCK) + REGISTER_PLUGIN_DYNAMIC(SHERLOCK, PLUGIN_TYPE_ENGINE, SherlockMetaEngine); +#else + REGISTER_PLUGIN_STATIC(SHERLOCK, PLUGIN_TYPE_ENGINE, SherlockMetaEngine); +#endif diff --git a/engines/sherlock/detection_tables.h b/engines/sherlock/detection_tables.h new file mode 100644 index 0000000000..991fc2055d --- /dev/null +++ b/engines/sherlock/detection_tables.h @@ -0,0 +1,171 @@ +/* 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 Sherlock { + +static const SherlockGameDescription gameDescriptions[] = { + { + // Case of the Serrated Scalpel - English 3.5" Floppy + // The HitSquad CD version has the same MD5 + { + "scalpel", + 0, + AD_ENTRY1s("talk.lib", "ad0c4d6865edf15da4e9204c08815875", 238928), + Common::EN_ANY, + Common::kPlatformDOS, + ADGF_UNSTABLE, + GUIO6(GUIO_NOSPEECH, GAMEOPTION_ORIGINAL_SAVES, GAMEOPTION_FADE_STYLE, GAMEOPTION_HELP_STYLE, + GAMEOPTION_PORTRAITS_ON, GAMEOPTION_WINDOW_STYLE) + }, + GType_SerratedScalpel, + }, + + { + // Case of the Serrated Scalpel - German CD (from multilingual CD) + // Provided by m_kiewitz + { + "scalpel", + 0, { + {"talk.lib", 0, "40a5f9f37c0e0d2ad48d8f44d8e393c9", 284278}, + {"music.lib", 0, "68ae2f7684ecf903bd60a00bb6bae195", 366465}, + AD_LISTEND}, + Common::DE_DEU, + Common::kPlatformDOS, + ADGF_UNSTABLE, + GUIO6(GUIO_NOSPEECH, GAMEOPTION_ORIGINAL_SAVES, GAMEOPTION_FADE_STYLE, GAMEOPTION_HELP_STYLE, + GAMEOPTION_PORTRAITS_ON, GAMEOPTION_WINDOW_STYLE) + }, + GType_SerratedScalpel, + }, + + { + // Case of the Serrated Scalpel - Spanish CD (from multilingual CD) + // Provided by m_kiewitz + { + "scalpel", + 0, { + {"talk.lib", 0, "27697804b637a7f3b77234bf16f15dce", 171419}, + {"music.lib", 0, "68ae2f7684ecf903bd60a00bb6bae195", 366465}, + AD_LISTEND}, + Common::ES_ESP, + Common::kPlatformDOS, + ADGF_UNSTABLE, + GUIO6(GUIO_NOSPEECH, GAMEOPTION_ORIGINAL_SAVES, GAMEOPTION_FADE_STYLE, GAMEOPTION_HELP_STYLE, + GAMEOPTION_PORTRAITS_ON, GAMEOPTION_WINDOW_STYLE) + }, + GType_SerratedScalpel, + }, + + { + // Case of the Serrated Scalpel - English 3DO + { + "scalpel", + 0, + AD_ENTRY1s("talk.lib", "20f74a29f2db6475e85b029ac9fc03bc", 240610), + Common::EN_ANY, + Common::kPlatform3DO, + ADGF_UNSTABLE, + GUIO6(GUIO_NOSPEECH, GAMEOPTION_ORIGINAL_SAVES, GAMEOPTION_FADE_STYLE, GAMEOPTION_HELP_STYLE, + GAMEOPTION_PORTRAITS_ON, GAMEOPTION_WINDOW_STYLE) + }, + GType_SerratedScalpel, + }, + + { + // Case of the Serrated Scalpel - Interactive English Demo + // Provided by Strangerke + { + "scalpel", + "Interactive Demo", + AD_ENTRY1s("talk.lib", "dbdc8a20c96900aa7e4d02f3fe8a274c", 121102), + Common::EN_ANY, + Common::kPlatformDOS, + ADGF_UNSTABLE | ADGF_DEMO, + GUIO1(GUIO_NOSPEECH) + }, + GType_SerratedScalpel, + }, + + { + // Case of the Serrated Scalpel - Non-Interactive English Demo + // Provided by Strangerke + { + "scalpel", + "Non Interactive Demo", + AD_ENTRY1s("music.lib", "ec19a09b7fef6fd90b1ab812ce6e9739", 38563), + Common::EN_ANY, + Common::kPlatformDOS, + ADGF_UNSTABLE | ADGF_DEMO, + GUIO1(GUIO_NOSPEECH) + }, + GType_SerratedScalpel, + }, + + { + // Case of the Rose Tattoo - French CD + // Provided by Strangerke + { + "rosetattoo", + "CD", + AD_ENTRY1s("talk.lib", "22e8e6406dd2fbbb238c9898928df42e", 770756), + Common::EN_ANY, + Common::kPlatformDOS, + ADGF_UNSTABLE, + GUIO0() + }, + GType_RoseTattoo + }, + + { + // Case of the Rose Tattoo - English CD + // Provided by dreammaster + { + "rosetattoo", + "CD", + AD_ENTRY1s("talk.lib", "9639a756b0993ebd71cb5f4d8b78b2dc", 765134), + Common::EN_ANY, + Common::kPlatformDOS, + ADGF_UNSTABLE, + GUIO0() + }, + GType_RoseTattoo, + }, + + { + // Case of the Rose Tattoo - German CD + // Provided by m_kiewitz + { + "rosetattoo", + "CD", + AD_ENTRY1s("talk.lib", "5027aa72f0d263ed3b1c764a6c397911", 873864), + Common::DE_DEU, + Common::kPlatformDOS, + ADGF_UNSTABLE, + GUIO0() + }, + GType_RoseTattoo, + }, + + { AD_TABLE_END_MARKER, (GameType)0 } +}; + +} // End of namespace Sherlock diff --git a/engines/sherlock/events.cpp b/engines/sherlock/events.cpp new file mode 100644 index 0000000000..fdfd77ef74 --- /dev/null +++ b/engines/sherlock/events.cpp @@ -0,0 +1,280 @@ +/* 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 "common/scummsys.h" +#include "common/events.h" +#include "common/system.h" +#include "engines/util.h" +#include "graphics/cursorman.h" +#include "sherlock/sherlock.h" +#include "sherlock/events.h" + +namespace Sherlock { + +enum ButtonFlag { LEFT_BUTTON = 1, RIGHT_BUTTON = 2 }; + +Events::Events(SherlockEngine *vm): _vm(vm) { + _cursorImages = nullptr; + _cursorId = INVALID_CURSOR; + _frameCounter = 1; + _priorFrameTime = 0; + _mouseButtons = 0; + _pressed = _released = false; + _rightPressed = _rightReleased = false; + _oldButtons = _oldRightButton = false; + _firstPress = false; + + if (_vm->_interactiveFl) + loadCursors("rmouse.vgs"); +} + +Events::~Events() { + delete _cursorImages; +} + +void Events::loadCursors(const Common::String &filename) { + hideCursor(); + delete _cursorImages; + + if (!IS_3DO) { + // PC + _cursorImages = new ImageFile(filename); + } else { + // 3DO + _cursorImages = new ImageFile3DO(filename, kImageFile3DOType_RoomFormat); + } + _cursorId = INVALID_CURSOR; +} + +void Events::setCursor(CursorId cursorId) { + if (cursorId == _cursorId) + return; + + int hotspotX, hotspotY; + + if (cursorId == MAGNIFY) { + hotspotX = 8; + hotspotY = 8; + } else { + hotspotX = 0; + hotspotY = 0; + } + + // Set the cursor data + Graphics::Surface &s = (*_cursorImages)[cursorId]._frame; + + setCursor(s, hotspotX, hotspotY); + + _cursorId = cursorId; +} + +void Events::setCursor(const Graphics::Surface &src, int hotspotX, int hotspotY) { + _cursorId = INVALID_CURSOR; + if (!IS_3DO) { + // PC 8-bit palettized + CursorMan.replaceCursor(src.getPixels(), src.w, src.h, hotspotX, hotspotY, 0xff); + } else { + // 3DO RGB565 + CursorMan.replaceCursor(src.getPixels(), src.w, src.h, hotspotX, hotspotY, 0x0000, false, &src.format); + } + showCursor(); +} + +void Events::animateCursorIfNeeded() { + if (_cursorId >= WAIT && _cursorId < (WAIT + 3)) { + CursorId newId = (_cursorId == WAIT + 2) ? WAIT : (CursorId)((int)_cursorId + 1); + setCursor(newId); + } +} + + +void Events::showCursor() { + CursorMan.showMouse(true); +} + +void Events::hideCursor() { + CursorMan.showMouse(false); +} + +CursorId Events::getCursor() const { + return _cursorId; +} + +bool Events::isCursorVisible() const { + return CursorMan.isVisible(); +} + +void Events::moveMouse(const Common::Point &pt) { + g_system->warpMouse(pt.x, pt.y); +} + +void Events::pollEvents() { + checkForNextFrameCounter(); + + Common::Event event; + while (g_system->getEventManager()->pollEvent(event)) { + // Handle keypress + switch (event.type) { + case Common::EVENT_QUIT: + case Common::EVENT_RTL: + 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(); + } else { + _pendingKeys.push(event.kbd); + } + return; + case Common::EVENT_KEYUP: + return; + case Common::EVENT_LBUTTONDOWN: + _mouseButtons |= LEFT_BUTTON; + return; + case Common::EVENT_RBUTTONDOWN: + _mouseButtons |= RIGHT_BUTTON; + return; + case Common::EVENT_LBUTTONUP: + _mouseButtons &= ~LEFT_BUTTON; + return; + case Common::EVENT_RBUTTONUP: + _mouseButtons &= ~RIGHT_BUTTON; + return; + default: + break; + } + } +} + +void Events::pollEventsAndWait() { + pollEvents(); + g_system->delayMillis(10); +} + +void Events::warpMouse(const Common::Point &pt) { + g_system->warpMouse(pt.x, pt.y); +} + +bool Events::checkForNextFrameCounter() { + // Check for next game frame + uint32 milli = g_system->getMillis(); + if ((milli - _priorFrameTime) >= GAME_FRAME_TIME) { + ++_frameCounter; + _priorFrameTime = milli; + + // Give time to the debugger + _vm->_debugger->onFrame(); + + // Display the frame + _vm->_screen->update(); + + return true; + } + + return false; +} + +Common::Point Events::mousePos() const { + return g_system->getEventManager()->getMousePos(); +} + +Common::KeyState Events::getKey() { + return _pendingKeys.pop(); +} + +void Events::clearEvents() { + _pendingKeys.clear(); + _mouseButtons = 0; + _pressed = _released = false; + _rightPressed = _rightReleased = false; + _oldButtons = _oldRightButton = false; + _firstPress = false; +} + +void Events::clearKeyboard() { + _pendingKeys.clear(); +} + +void Events::wait(int numFrames) { + uint32 totalMilli = numFrames * 1000 / GAME_FRAME_RATE; + delay(totalMilli); +} + +bool Events::delay(uint32 time, bool interruptable) { + // Different handling for really short versus extended times + if (time < 10) { + // For really short periods, simply delay by the desired amount + pollEvents(); + g_system->delayMillis(time); + bool result = !(interruptable && (kbHit() || _pressed || _vm->shouldQuit())); + + clearEvents(); + return result; + } else { + // For long periods go into a loop where we delay by 10ms at a time and then + // check for events. This ensures for longer delays that responsiveness is + // maintained + uint32 delayEnd = g_system->getMillis() + time; + + while (!_vm->shouldQuit() && g_system->getMillis() < delayEnd) { + pollEventsAndWait(); + + if (interruptable && (kbHit() || _pressed)) { + clearEvents(); + return false; + } + } + + return !_vm->shouldQuit(); + } +} + +void Events::setButtonState() { + _firstPress = ((_mouseButtons & 1) && !_pressed) || ((_mouseButtons & 2) && !_rightPressed); + + _released = _rightReleased = false; + if (_mouseButtons & LEFT_BUTTON) + _pressed = _oldButtons = true; + + if ((_mouseButtons & LEFT_BUTTON) == 0 && _oldButtons) { + _pressed = _oldButtons = false; + _released = true; + } + + if (_mouseButtons & RIGHT_BUTTON) + _rightPressed = _oldRightButton = true; + + if ((_mouseButtons & RIGHT_BUTTON) == 0 && _oldRightButton) { + _rightPressed = _oldRightButton = false; + _rightReleased = true; + } +} + +bool Events::checkInput() { + setButtonState(); + return kbHit() || _pressed || _released || _rightPressed || _rightReleased; +} + +} // End of namespace Sherlock diff --git a/engines/sherlock/events.h b/engines/sherlock/events.h new file mode 100644 index 0000000000..b0dda37607 --- /dev/null +++ b/engines/sherlock/events.h @@ -0,0 +1,177 @@ +/* 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 SHERLOCK_EVENTS_H +#define SHERLOCK_EVENTS_H + +#include "common/scummsys.h" +#include "common/events.h" +#include "common/stack.h" +#include "sherlock/image_file.h" + +namespace Sherlock { + +#define GAME_FRAME_RATE 60 +#define GAME_FRAME_TIME (1000 / GAME_FRAME_RATE) + +enum CursorId { ARROW = 0, MAGNIFY = 1, WAIT = 2, EXIT_ZONES_START = 5, INVALID_CURSOR = -1 }; + +class SherlockEngine; + +class Events { +private: + SherlockEngine *_vm; + uint32 _frameCounter; + uint32 _priorFrameTime; + ImageFile *_cursorImages; + int _mouseButtons; + + /** + * Check whether it's time to display the next screen frame + */ + bool checkForNextFrameCounter(); +public: + CursorId _cursorId; + bool _pressed; + bool _released; + bool _rightPressed; + bool _rightReleased; + bool _oldButtons; + bool _oldRightButton; + bool _firstPress; + Common::Stack<Common::KeyState> _pendingKeys; +public: + Events(SherlockEngine *vm); + ~Events(); + + /** + * Load a set of cursors from the specified file + */ + void loadCursors(const Common::String &filename); + + /** + * Set the cursor to show + */ + void setCursor(CursorId cursorId); + + /** + * Set the cursor to show from a passed frame + */ + void setCursor(const Graphics::Surface &src, int hotspotX = 0, int hotspotY = 0); + + /** + * Animates the mouse cursor if the Wait cursor is showing + */ + void animateCursorIfNeeded(); + + /** + * Show the mouse cursor + */ + void showCursor(); + + /** + * Hide the mouse cursor + */ + void hideCursor(); + + /** + * Returns the cursor + */ + CursorId getCursor() const; + + /** + * Returns true if the mouse cursor is visible + */ + bool isCursorVisible() const; + + /** + * Move the mouse + */ + void moveMouse(const Common::Point &pt); + + /** + * Check for any pending events + */ + void pollEvents(); + + /** + * Poll for events and introduce a small delay, to allow the system to + * yield to other running programs + */ + void pollEventsAndWait(); + + /** + * Move the mouse cursor + */ + void warpMouse(const Common::Point &pt); + + /** + * Get the current mouse position + */ + Common::Point mousePos() const; + + uint32 getFrameCounter() const { return _frameCounter; } + + bool kbHit() const { return !_pendingKeys.empty(); } + + /** + * Get a pending keypress + */ + Common::KeyState getKey(); + + /** + * Clear any current keypress or mouse click + */ + void clearEvents(); + + /** + * Clear any pending keyboard inputs + */ + void clearKeyboard(); + + /** + * Delay for a given number of game frames, where each frame is 1/60th of a second + */ + void wait(int numFrames); + + /** + * Does a delay of the specified number of milliseconds + */ + bool delay(uint32 time, bool interruptable = false); + + /** + * Sets the pressed and released button flags on the raw button state previously set in pollEvents calls. + * @remarks The events manager has separate variables for the raw immediate and old button state + * versus the current buttons states for the frame. This method is expected to be called only once + * per game frame + */ + void setButtonState(); + + /** + * Checks to see to see if a key or a mouse button is pressed. + */ + bool checkInput(); +}; + +} // End of namespace Sherlock + +#endif /* SHERLOCK_EVENTS_H */ diff --git a/engines/sherlock/fixed_text.cpp b/engines/sherlock/fixed_text.cpp new file mode 100644 index 0000000000..09e38bfb5d --- /dev/null +++ b/engines/sherlock/fixed_text.cpp @@ -0,0 +1,380 @@ +/* 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 "sherlock/sherlock.h" +#include "sherlock/fixed_text.h" + +namespace Sherlock { + +static const char *const fixedTextEN[] = { + // SH1: Window buttons + "Exit", + "Up", + "Down", + // SH1: Inventory buttons + "Exit", + "Look", + "Use", + "Give", + // SH1: Journal text + "Watson's Journal", + "Page %d", + // SH1: Journal buttons + "Exit", + "Back 10", + "Up", + "Down", + "Ahead 10", + "Search", + "First Page", + "Last Page", + "Print Text", + // SH1: Journal search + "Exit", + "Backward", + "Forward", + "Text Not Found !", + // SH1: Initial Inventory + "A message requesting help", + "A number of business cards", + "Opera Tickets", + "Cuff Link", + "Wire Hook", + "Note", + "An open pocket watch", + "A piece of paper with numbers on it", + "A letter folded many times", + "Tarot Cards", + "An ornate key", + "A pawn ticket", + // SH2: Verbs + "Open", + "Look", + "Talk", + "Journal" +}; + +// sharp-s : 0xE1 / octal 341 +// small a-umlaut: 0x84 / octal 204 +// small o-umlaut: 0x94 / octal 224 +// small u-umlaut: 0x81 / octal 201 +static const char *const fixedTextDE[] = { + // SH1: Window buttons + "Zur\201ck", + "Hoch", + "Runter", + // SH1: Inventory buttons + "Zur\201ck", + "Schau", + "Benutze", + "Gib", + // SH1: Journal text + "Watsons Tagebuch", + "Seite %d", + // SH1: Journal buttons + "Zur\201ck", + "10 hoch", + "Hoch", + "Runter", + "10 runter", + "Suche", + "Erste Seite", + "Letzte Seite", + "Drucke Text", + // SH1: Journal search + "Zur\201ck", + "R\201ckw\204rts", // original: "Backward" + "Vorw\204rts", // original: "Forward" + "Text nicht gefunden!", + // SH1: Initial Inventory + "Ein Hilferuf von Lestrade", + "Holmes' Visitenkarten", + "Karten f\201rs Opernhaus", + "Manschettenkn\224pfe", + "Zum Haken verbogener Drahtkorb", + "Mitteilung am Epstein", + "Eine offene Taschenuhr", + "Ein Zettel mit Zahlen drauf", + "Ein mehrfach gefalteter Briefbogen", + "Ein Tarock-Kartenspiel", // [sic] + "Ein verzierter Schl\201ssel", + "Ein Pfandschein", + // SH2: Verbs + "\231ffne", + "Schau", + "Rede", + "Tagebuch" +}; + +// up-side down exclamation mark - 0xAD / octal 255 +// up-side down question mark - 0xA8 / octal 250 +// n with a wave on top - 0xA4 / octal 244 +static const char *const fixedTextES[] = { + // SH1: Window buttons + "Exit", + "Subir", + "Bajar", + // SH1: Inventory buttons + "Exit", + "Mirar", + "Usar", + "Dar", + // SH1: Journal text + "Diario de Watson", + "Pagina %d", + // SH1: Journal buttons + "Exit", + "Retroceder", + "Subir", + "baJar", + "Adelante", + "Buscar", + "1a pagina", + "Ult pagina", + "Imprimir", + // SH1: Journal search + "Exit", + "Retroceder", + "Avanzar", + "Texto no encontrado!", + // SH1: Initial Inventory + "Un mensaje solicitando ayuda", + "Unas cuantas tarjetas de visita", + "Entradas para la opera", + "Unos gemelos", + "Un gancho de alambre", + "Una nota", + "Un reloj de bolsillo abierto", + "Un trozo de papel con unos numeros", + "Un carta muy plegada", + "Unas cartas de Tarot", + "Una llave muy vistosa", + "Una papeleta de empe\244o", + // SH2: Verbs --- TODO: not known at the moment + "Open", + "Look", + "Talk", + "Journal" +}; + +// ========================================= + +// === Sherlock Holmes 1: Serrated Scalpel === +static const char *const fixedTextEN_ActionOpen[] = { + "This cannot be opened", + "It is already open", + "It is locked", + "Wait for Watson", + " ", + "." +}; + +static const char *const fixedTextDE_ActionOpen[] = { + "Das kann man nicht \224ffnen", + "Ist doch schon offen!", + "Leider verschlossen", + "Warte auf Watson", + " ", + "." +}; + +static const char *const fixedTextES_ActionOpen[] = { + "No puede ser abierto", + "Ya esta abierto", + "Esta cerrado", + "Espera a Watson", + " ", + "." +}; + +static const char *const fixedTextEN_ActionClose[] = { + "This cannot be closed", + "It is already closed", + "The safe door is in the way" +}; + +static const char *const fixedTextDE_ActionClose[] = { + "Das kann man nicht schlie\341en", + "Ist doch schon zu!", + "Die safet\201r ist Weg" +}; + +static const char *const fixedTextES_ActionClose[] = { + "No puede ser cerrado", + "Ya esta cerrado", + "La puerta de seguridad esta entre medias" +}; + +static const char *const fixedTextEN_ActionMove[] = { + "This cannot be moved", + "It is bolted to the floor", + "It is too heavy", + "The other crate is in the way" +}; + + +static const char *const fixedTextDE_ActionMove[] = { + "L\204\341t sich nicht bewegen", + "Festged\201belt in der Erde...", + "Oha, VIEL zu schwer", + "Der andere Kiste ist im Weg" // [sic] +}; + +static const char *const fixedTextES_ActionMove[] = { + "No puede moverse", + "Esta sujeto a la pared", + "Es demasiado pesado", + "El otro cajon esta en mitad" +}; + +static const char *const fixedTextEN_ActionPick[] = { + "Nothing of interest here", + "It is bolted down", + "It is too big to carry", + "It is too heavy", + "I think a girl would be more your type", + "Those flowers belong to Penny", + "She's far too young for you!", + "I think a girl would be more your type!", + "Government property for official use only" +}; + +static const char *const fixedTextDE_ActionPick[] = { + "Nichts Interessantes da", + "Zu gut befestigt", + "Ist ja wohl ein bi\341chen zu gro\341, oder ?", + "Oha, VIEL zu schwer", + "Ich denke, Du stehst mehr auf M\204dchen ?", + "Diese Blumen geh\224ren Penny", + "Sie ist doch viel zu jung f\201r Dich!", + "Ich denke, Du stehst mehr auf M\204dchen ?", + "Staatseigentum - Nur für den Dienstgebrauch !" +}; + +static const char *const fixedTextES_ActionPick[] = { + "No hay nada interesante", + "Esta anclado al suelo", + "Es muy grande para llevarlo", + "Pesa demasiado", + "Creo que una chica sera mas tu tipo", + "Esas flores pertenecen a Penny", + "\255Es demasiado joven para ti!" + "\255Creo que una chica sera mas tu tipo!", + "Propiedad del gobierno para uso oficial" +}; + +static const char *const fixedTextEN_ActionUse[] = { + "You can't do that", + "It had no effect", + "You can't reach it", + "OK, the door looks bigger! Happy?" + "Doors don't smoke" +}; + +static const char *const fixedTextDE_ActionUse[] = { + "Nein, das geht wirklich nicht", + "Tja keinerlei Wirkung", + "Da kommst du nicht dran", + "Na gut, die Tür sieht jetzt gr\224\341er aus. Zufrieden?" + "Türen sind Nichtraucher!" +}; + +static const char *const fixedTextES_ActionUse[] = { + "No puedes hacerlo", + "No tuvo ningun efecto", + "No puedes alcanzarlo", + "Bien, \255es enorme! \250Feliz?" + "Las puertas no fuman" +}; + +#define FIXEDTEXT_GETCOUNT(_name_) sizeof(_name_) / sizeof(byte *) +#define FIXEDTEXT_ENTRY(_name_) _name_, FIXEDTEXT_GETCOUNT(_name_) + +static const FixedTextActionEntry fixedTextEN_Actions[] = { + { FIXEDTEXT_ENTRY(fixedTextEN_ActionOpen) }, + { FIXEDTEXT_ENTRY(fixedTextEN_ActionClose) }, + { FIXEDTEXT_ENTRY(fixedTextEN_ActionMove) }, + { FIXEDTEXT_ENTRY(fixedTextEN_ActionPick) }, + { FIXEDTEXT_ENTRY(fixedTextEN_ActionUse) } +}; + +static const FixedTextActionEntry fixedTextDE_Actions[] = { + { FIXEDTEXT_ENTRY(fixedTextDE_ActionOpen) }, + { FIXEDTEXT_ENTRY(fixedTextDE_ActionClose) }, + { FIXEDTEXT_ENTRY(fixedTextDE_ActionMove) }, + { FIXEDTEXT_ENTRY(fixedTextDE_ActionPick) }, + { FIXEDTEXT_ENTRY(fixedTextDE_ActionUse) } +}; + +static const FixedTextActionEntry fixedTextES_Actions[] = { + { FIXEDTEXT_ENTRY(fixedTextES_ActionOpen) }, + { FIXEDTEXT_ENTRY(fixedTextES_ActionClose) }, + { FIXEDTEXT_ENTRY(fixedTextES_ActionMove) }, + { FIXEDTEXT_ENTRY(fixedTextES_ActionPick) }, + { FIXEDTEXT_ENTRY(fixedTextES_ActionUse) } +}; + +// ========================================= + +// TODO: +// It seems there was a French version of Sherlock Holmes 2 +static const FixedTextLanguageEntry fixedTextLanguages[] = { + { Common::DE_DEU, fixedTextDE, fixedTextDE_Actions }, + { Common::ES_ESP, fixedTextES, fixedTextES_Actions }, + { Common::EN_ANY, fixedTextEN, fixedTextEN_Actions }, + { Common::UNK_LANG, fixedTextEN, fixedTextEN_Actions } +}; + +// ========================================= + +// ========================================= + +// TODO: split this class up into 2 classes. One for each Sherlock Holmes game +// We definitely do not want to share fixed text between both, simply because translations may not be the same +FixedText::FixedText(SherlockEngine *vm) : _vm(vm) { + // Figure out which fixed texts to use + Common::Language curLanguage = _vm->getLanguage(); + + const FixedTextLanguageEntry *curLanguageEntry = fixedTextLanguages; + + while (curLanguageEntry->language != Common::UNK_LANG) { + if (curLanguageEntry->language == curLanguage) + break; // found current language + curLanguageEntry++; + } + _curLanguageEntry = curLanguageEntry; +} + +const Common::String FixedText::getText(FixedTextId fixedTextId) { + return Common::String(_curLanguageEntry->fixedTextArray[fixedTextId]); +} + +const Common::String FixedText::getActionMessage(FixedTextActionId actionId, int messageIndex) { + assert(actionId >= 0); + assert(messageIndex >= 0); + const FixedTextActionEntry *curActionEntry = &_curLanguageEntry->actionArray[actionId]; + + assert(messageIndex < curActionEntry->fixedTextArrayCount); + return Common::String(curActionEntry->fixedTextArray[messageIndex]); +} + +} // End of namespace Sherlock diff --git a/engines/sherlock/fixed_text.h b/engines/sherlock/fixed_text.h new file mode 100644 index 0000000000..50b0d5b96d --- /dev/null +++ b/engines/sherlock/fixed_text.h @@ -0,0 +1,119 @@ +/* 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 SHERLOCK_FIXED_TEXT_H +#define SHERLOCK_FIXED_TEXT_H + +#include "common/scummsys.h" +#include "common/language.h" + +namespace Sherlock { + +enum FixedTextId { + // Window buttons + kFixedText_Window_Exit = 0, + kFixedText_Window_Up, + kFixedText_Window_Down, + // Inventory buttons + kFixedText_Inventory_Exit, + kFixedText_Inventory_Look, + kFixedText_Inventory_Use, + kFixedText_Inventory_Give, + // Journal text + kFixedText_Journal_WatsonsJournal, + kFixedText_Journal_Page, + // Journal buttons + kFixedText_Journal_Exit, + kFixedText_Journal_Back10, + kFixedText_Journal_Up, + kFixedText_Journal_Down, + kFixedText_Journal_Ahead10, + kFixedText_Journal_Search, + kFixedText_Journal_FirstPage, + kFixedText_Journal_LastPage, + kFixedText_Journal_PrintText, + // Journal search + kFixedText_JournalSearch_Exit, + kFixedText_JournalSearch_Backward, + kFixedText_JournalSearch_Forward, + kFixedText_JournalSearch_NotFound, + // Initial inventory + kFixedText_InitInventory_Message, + kFixedText_InitInventory_HolmesCard, + kFixedText_InitInventory_Tickets, + kFixedText_InitInventory_CuffLink, + kFixedText_InitInventory_WireHook, + kFixedText_InitInventory_Note, + kFixedText_InitInventory_OpenWatch, + kFixedText_InitInventory_Paper, + kFixedText_InitInventory_Letter, + kFixedText_InitInventory_Tarot, + kFixedText_InitInventory_OrnateKey, + kFixedText_InitInventory_PawnTicket, + // Verbs + kFixedText_Verb_Open, + kFixedText_Verb_Look, + kFixedText_Verb_Talk, + kFixedText_Verb_Journal +}; + +enum FixedTextActionId { + kFixedTextAction_Invalid = -1, + kFixedTextAction_Open = 0, + kFixedTextAction_Close, + kFixedTextAction_Move, + kFixedTextAction_Pick, + kFixedTextAction_Use +}; + +struct FixedTextActionEntry { + const char *const *fixedTextArray; + int fixedTextArrayCount; +}; + +struct FixedTextLanguageEntry { + Common::Language language; + const char *const *fixedTextArray; + const FixedTextActionEntry *actionArray; +}; + +class FixedText { +private: + SherlockEngine *_vm; + + const FixedTextLanguageEntry *_curLanguageEntry; + +public: + FixedText(SherlockEngine *vm); + ~FixedText() {} + + /** + * Gets hardcoded text + */ + const Common::String getText(FixedTextId fixedTextId); + + const Common::String getActionMessage(FixedTextActionId actionId, int messageIndex); +}; + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/fonts.cpp b/engines/sherlock/fonts.cpp new file mode 100644 index 0000000000..50d05def3b --- /dev/null +++ b/engines/sherlock/fonts.cpp @@ -0,0 +1,204 @@ +/* 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 "common/system.h" +#include "common/platform.h" +#include "sherlock/fonts.h" +#include "sherlock/image_file.h" +#include "sherlock/surface.h" +#include "sherlock/sherlock.h" + +namespace Sherlock { + +SherlockEngine *Fonts::_vm; +ImageFile *Fonts::_font; +int Fonts::_fontNumber; +int Fonts::_fontHeight; +int Fonts::_widestChar; +uint16 Fonts::_charCount; +byte Fonts::_yOffsets[255]; + +void Fonts::setVm(SherlockEngine *vm) { + _vm = vm; + _font = nullptr; + _charCount = 0; +} + +void Fonts::free() { + delete _font; +} + +void Fonts::setFont(int fontNum) { + _fontNumber = fontNum; + + // Discard previous font + delete _font; + + Common::String fontFilename; + + if (_vm->getPlatform() != Common::kPlatform3DO) { + // PC + // use FONT[number].VGS, which is a regular sherlock graphic file + fontFilename = Common::String::format("FONT%d.VGS", fontNum + 1); + + // load font data + _font = new ImageFile(fontFilename); + } else { + // 3DO + switch (fontNum) { + case 0: + case 1: + fontFilename = "helvetica14.font"; + break; + case 2: + fontFilename = "darts.font"; + break; + default: + error("setFont(): unsupported 3DO font number"); + } + + // load font data + _font = new ImageFile3DO(fontFilename, kImageFile3DOType_Font); + } + + _charCount = _font->size(); + + // Iterate through the frames to find the widest aand tallest font characters + _fontHeight = _widestChar = 0; + for (uint idx = 0; idx < _charCount; ++idx) { + _fontHeight = MAX((uint16)_fontHeight, (*_font)[idx]._frame.h); + _widestChar = MAX((uint16)_widestChar, (*_font)[idx]._frame.w); + } + + // Initialize the Y offset table for the extended character set + for (int idx = 0; idx < 255; ++idx) { + _yOffsets[idx] = 0; + + if (IS_ROSE_TATTOO) { + if ((idx >= 129 && idx < 135) || (idx >= 136 && idx < 143) || (idx >= 147 && idx < 155) || + (idx >= 156 && idx < 165)) + _yOffsets[idx] = 1; + else if ((idx >= 143 && idx < 146) || idx == 165) + _yOffsets[idx] = 2; + } + } +} + +inline byte Fonts::translateChar(byte c) { + switch (c) { + case ' ': + return 0; // translate to first actual character + case 225: + // This was done in the German interpreter + // happens when talking to the kid in the 2nd room + return 135; // special handling for 0xE1 + default: + if (c >= 0x80) { // German SH1 version did this + c--; + } + // Spanish SH1 did this (reverse engineered code) + //if ((c >= 0xA0) && (c <= 0xAD) || (c == 0x82)) { + // c--; + //} + assert(c > 32); // anything above space is allowed + return c - 33; + } +} + +void Fonts::writeString(Surface *surface, const Common::String &str, + const Common::Point &pt, int overrideColor) { + Common::Point charPos = pt; + + if (!_font) + return; + + for (const char *curCharPtr = str.c_str(); *curCharPtr; ++curCharPtr) { + byte curChar = *curCharPtr; + + if (curChar == ' ') { + charPos.x += 5; // hardcoded space + continue; + } + curChar = translateChar(curChar); + + assert(curChar < _charCount); + ImageFrame &frame = (*_font)[curChar]; + surface->transBlitFrom(frame, Common::Point(charPos.x, charPos.y + _yOffsets[curChar]), false, overrideColor); + charPos.x += frame._frame.w + 1; + } +} + +int Fonts::stringWidth(const Common::String &str) { + int width = 0; + + if (!_font) + return 0; + + for (const char *c = str.c_str(); *c; ++c) + width += charWidth(*c); + + return width; +} + +int Fonts::stringHeight(const Common::String &str) { + int height = 0; + + if (!_font) + return 0; + + for (const char *c = str.c_str(); *c; ++c) + height = MAX(height, charHeight(*c)); + + return height; +} + +int Fonts::charWidth(unsigned char c) { + byte curChar; + + if (!_font) + return 0; + + if (c == ' ') { + return 5; // hardcoded space + } + curChar = translateChar(c); + + if (curChar < _charCount) + return (*_font)[curChar]._frame.w + 1; + return 0; +} + +int Fonts::charHeight(unsigned char c) { + byte curChar; + + if (!_font) + return 0; + + // Space is supposed to be handled like the first actual character (which is decimal 33) + curChar = translateChar(c); + + assert(curChar < _charCount); + const ImageFrame &img = (*_font)[curChar]; + return img._height + img._offset.y + 1; +} + +} // End of namespace Sherlock diff --git a/engines/sherlock/fonts.h b/engines/sherlock/fonts.h new file mode 100644 index 0000000000..a527cc73c0 --- /dev/null +++ b/engines/sherlock/fonts.h @@ -0,0 +1,105 @@ +/* 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 SHERLOCK_FONTS_H +#define SHERLOCK_FONTS_H + +#include "common/rect.h" +#include "common/platform.h" +#include "graphics/surface.h" + +namespace Sherlock { + +class SherlockEngine; +class ImageFile; +class Surface; + +class Fonts { +private: + static ImageFile *_font; + static byte _yOffsets[255]; +protected: + static SherlockEngine *_vm; + static int _fontNumber; + static int _fontHeight; + static int _widestChar; + static uint16 _charCount; + + static void writeString(Surface *surface, const Common::String &str, + const Common::Point &pt, int overrideColor = 0); + + static inline byte translateChar(byte c); +public: + /** + * Initialise the font manager + */ + static void setVm(SherlockEngine *vm); + + /** + * Frees the font manager + */ + static void free(); + + /** + * Set the font to use for writing text on the screen + */ + void setFont(int fontNum); + + /** + * Returns the width of a string in pixels + */ + int stringWidth(const Common::String &str); + + /** + * Returns the height of a string in pixels (i.e. the tallest displayed character) + */ + int stringHeight(const Common::String &str); + + /** + * Returns the width of a character in pixels + */ + int charWidth(unsigned char c); + + /** + * Returns the width of a character in pixels + */ + int charHeight(unsigned char c); + + /** + * Return the font height + */ + int fontHeight() const { return _fontHeight; } + + /** + * Return the width of the widest character in the font + */ + int widestChar() const { return _widestChar; } + + /** + * Return the currently active font number + */ + int fontNumber() const { return _fontNumber; } +}; + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/image_file.cpp b/engines/sherlock/image_file.cpp new file mode 100644 index 0000000000..78284e416d --- /dev/null +++ b/engines/sherlock/image_file.cpp @@ -0,0 +1,1010 @@ +/* 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 "sherlock/image_file.h" +#include "sherlock/screen.h" +#include "sherlock/sherlock.h" +#include "common/debug.h" +#include "common/memstream.h" + +namespace Sherlock { + +SherlockEngine *ImageFile::_vm; + +void ImageFile::setVm(SherlockEngine *vm) { + _vm = vm; +} + +ImageFile::ImageFile() { +} + +ImageFile::ImageFile(const Common::String &name, bool skipPal, bool animImages) { + Common::SeekableReadStream *stream = _vm->_res->load(name); + + Common::fill(&_palette[0], &_palette[PALETTE_SIZE], 0); + load(*stream, skipPal, animImages); + + delete stream; +} + +ImageFile::ImageFile(Common::SeekableReadStream &stream, bool skipPal) { + Common::fill(&_palette[0], &_palette[PALETTE_SIZE], 0); + load(stream, skipPal, false); +} + +ImageFile::~ImageFile() { + for (uint idx = 0; idx < size(); ++idx) + (*this)[idx]._frame.free(); +} + +void ImageFile::load(Common::SeekableReadStream &stream, bool skipPalette, bool animImages) { + loadPalette(stream); + + int streamSize = stream.size(); + while (stream.pos() < streamSize) { + ImageFrame frame; + frame._width = stream.readUint16LE() + 1; + frame._height = stream.readUint16LE() + 1; + frame._paletteBase = stream.readByte(); + + if (animImages) { + // Animation cutscene image files use a 16-bit x offset + frame._offset.x = stream.readUint16LE(); + frame._rleEncoded = (frame._offset.x & 0xff) == 1; + frame._offset.y = stream.readByte(); + } else { + // Standard image files have a separate byte for the RLE flag, and an 8-bit X offset + frame._rleEncoded = stream.readByte() == 1; + frame._offset.x = stream.readByte(); + frame._offset.y = stream.readByte(); + } + + frame._rleEncoded = !skipPalette && frame._rleEncoded; + + if (frame._paletteBase) { + // Nibble packed frame data + frame._size = (frame._width * frame._height) / 2; + } else if (frame._rleEncoded) { + // This size includes the header size, which we subtract + frame._size = stream.readUint16LE() - 11; + frame._rleMarker = stream.readByte(); + } else { + // Uncompressed data + frame._size = frame._width * frame._height; + } + + // Load data for frame and decompress it + byte *data = new byte[frame._size]; + stream.read(data, frame._size); + decompressFrame(frame, data); + delete[] data; + + push_back(frame); + } +} + +void ImageFile::loadPalette(Common::SeekableReadStream &stream) { + // Check for palette + uint16 width = stream.readUint16LE() + 1; + uint16 height = stream.readUint16LE() + 1; + byte paletteBase = stream.readByte(); + byte rleEncoded = stream.readByte(); + byte offsetX = stream.readByte(); + byte offsetY = stream.readByte(); + uint32 palSignature = 0; + + if ((width == 390) && (height == 2) && (!paletteBase) && (!rleEncoded) && (!offsetX) && (!offsetY)) { + // We check for these specific values + // We can't do "width * height", because at least the first German+Spanish menu bar is 60 x 13 + // which is 780, which is the size of the palette. We obviously don't want to detect it as palette. + + // As another security measure, we also check for the signature text + palSignature = stream.readUint32BE(); + if (palSignature != MKTAG('V', 'G', 'A', ' ')) { + // signature mismatch, rewind + stream.seek(-12, SEEK_CUR); + return; + } + // Found palette, so read it in + stream.seek(8, SEEK_CUR); // Skip over the rest of the signature text "VGA palette" + for (int idx = 0; idx < PALETTE_SIZE; ++idx) + _palette[idx] = VGA_COLOR_TRANS(stream.readByte()); + } else { + // Not a palette, so rewind to start of frame data for normal frame processing + stream.seek(-8, SEEK_CUR); + } +} + +void ImageFile::decompressFrame(ImageFrame &frame, const byte *src) { + frame._frame.create(frame._width, frame._height, Graphics::PixelFormat::createFormatCLUT8()); + byte *dest = (byte *)frame._frame.getPixels(); + Common::fill(dest, dest + frame._width * frame._height, 0xff); + + if (frame._paletteBase) { + // Nibble-packed + for (uint idx = 0; idx < frame._size; ++idx, ++src) { + *dest++ = *src & 0xF; + *dest++ = (*src >> 4); + } + } else if (frame._rleEncoded && _vm->getGameID() == GType_RoseTattoo) { + // Rose Tattoo run length encoding doesn't use the RLE marker byte + for (int yp = 0; yp < frame._height; ++yp) { + int xSize = frame._width; + while (xSize > 0) { + // Skip a given number of pixels + byte skip = *src++; + dest += skip; + xSize -= skip; + if (!xSize) + break; + + // Get a run length, and copy the following number of pixels + int rleCount = *src++; + xSize -= rleCount; + while (rleCount-- > 0) + *dest++ = *src++; + } + assert(xSize == 0); + } + } else if (frame._rleEncoded) { + // RLE encoded + int frameSize = frame._width * frame._height; + while (frameSize > 0) { + if (*src == frame._rleMarker) { + byte rleColor = src[1]; + byte rleCount = src[2]; + src += 3; + frameSize -= rleCount; + while (rleCount--) + *dest++ = rleColor; + } else { + *dest++ = *src++; + --frameSize; + } + } + assert(frameSize == 0); + } else { + // Uncompressed frame + Common::copy(src, src + frame._width * frame._height, dest); + } +} + +/*----------------------------------------------------------------*/ + +int ImageFrame::sDrawXSize(int scaleVal) const { + int width = _width; + int scale = scaleVal == 0 ? 1 : scaleVal; + + if (scaleVal >= SCALE_THRESHOLD) + --width; + + int result = width * SCALE_THRESHOLD / scale; + if (scaleVal >= SCALE_THRESHOLD) + ++result; + + return result; +} + +int ImageFrame::sDrawYSize(int scaleVal) const { + int height = _height; + int scale = scaleVal == 0 ? 1 : scaleVal; + + if (scaleVal >= SCALE_THRESHOLD) + --height; + + int result = height * SCALE_THRESHOLD / scale; + if (scaleVal >= SCALE_THRESHOLD) + ++result; + + return result; +} + +int ImageFrame::sDrawXOffset(int scaleVal) const { + int width = _offset.x; + int scale = scaleVal == 0 ? 1 : scaleVal; + + if (scaleVal >= SCALE_THRESHOLD) + --width; + + int result = width * SCALE_THRESHOLD / scale; + if (scaleVal >= SCALE_THRESHOLD) + ++result; + + return result; +} + +int ImageFrame::sDrawYOffset(int scaleVal) const { + int height = _offset.y; + int scale = scaleVal == 0 ? 1 : scaleVal; + + if (scaleVal >= SCALE_THRESHOLD) + --height; + + int result = height * SCALE_THRESHOLD / scale; + if (scaleVal >= SCALE_THRESHOLD) + ++result; + + return result; +} + +// ******************************************************* + +/*----------------------------------------------------------------*/ + +SherlockEngine *ImageFile3DO::_vm; + +void ImageFile3DO::setVm(SherlockEngine *vm) { + _vm = vm; +} + +ImageFile3DO::ImageFile3DO(const Common::String &name, ImageFile3DOType imageFile3DOType) { +#if 0 + Common::File *dataStream = new Common::File(); + + if (!dataStream->open(name)) { + error("unable to open %s\n", name.c_str()); + } +#endif + Common::SeekableReadStream *dataStream = _vm->_res->load(name); + + switch(imageFile3DOType) { + case kImageFile3DOType_Animation: + loadAnimationFile(*dataStream); + break; + case kImageFile3DOType_Cel: + case kImageFile3DOType_CelAnimation: + load3DOCelFile(*dataStream); + break; + case kImageFile3DOType_RoomFormat: + load3DOCelRoomData(*dataStream); + break; + case kImageFile3DOType_Font: + loadFont(*dataStream); + break; + default: + error("unknown Imagefile-3DO-Type"); + break; + } + + delete dataStream; +} + +ImageFile3DO::ImageFile3DO(Common::SeekableReadStream &stream, bool isRoomData) { + if (!isRoomData) { + load(stream, isRoomData); + } else { + load3DOCelRoomData(stream); + } +} + +ImageFile3DO::~ImageFile3DO() { + // already done in ImageFile destructor + //for (uint idx = 0; idx < size(); ++idx) + // (*this)[idx]._frame.free(); +} + +void ImageFile3DO::load(Common::SeekableReadStream &stream, bool isRoomData) { + uint32 headerId = 0; + + if (isRoomData) { + load3DOCelRoomData(stream); + return; + } + + headerId = stream.readUint32BE(); + assert(!stream.eos()); + + // Seek back to the start + stream.seek(-4, SEEK_CUR); + + // Identify type of file + switch (headerId) { + case MKTAG('C', 'C', 'B', ' '): + case MKTAG('A', 'N', 'I', 'M'): + case MKTAG('O', 'F', 'S', 'T'): // 3DOSplash.cel + // 3DO .cel (title1a.cel, etc.) or animation file (walk.anim) + load3DOCelFile(stream); + break; + + default: + // Sherlock animation file (.3da files) + loadAnimationFile(stream); + break; + } +} + +// 3DO uses RGB555, we use RGB565 internally so that more platforms are able to run us +inline uint16 ImageFile3DO::convertPixel(uint16 pixel3DO) { + byte red = (pixel3DO >> 10) & 0x1F; + byte green = (pixel3DO >> 5) & 0x1F; + byte blue = pixel3DO & 0x1F; + + return ((red << 11) | (green << 6) | (blue)); +} + +void ImageFile3DO::loadAnimationFile(Common::SeekableReadStream &stream) { + uint32 streamLeft = stream.size() - stream.pos(); + uint32 celDataSize = 0; + + while (streamLeft > 0) { + ImageFrame frame; + + // We expect a basic header of 8 bytes + if (streamLeft < 8) + error("load3DOAnimationFile: expected animation header, not enough bytes"); + + celDataSize = stream.readUint16BE(); + + frame._width = stream.readUint16BE() + 1; // 2 bytes BE width + frame._height = stream.readByte() + 1; // 1 byte BE height + frame._paletteBase = 0; + + frame._rleEncoded = true; // always compressed + if (frame._width & 0x8000) { + frame._width &= 0x7FFF; + celDataSize += 0x10000; + } + + frame._offset.x = stream.readUint16BE(); + frame._offset.y = stream.readByte(); + frame._size = 0; + // Got header + streamLeft -= 8; + + // cel data follows + if (streamLeft < celDataSize) + error("load3DOAnimationFile: expected cel data, not enough bytes"); + + // + // Load data for frame and decompress it + byte *data = new byte[celDataSize]; + stream.read(data, celDataSize); + streamLeft -= celDataSize; + + // always 16 bits per pixel (RGB555) + decompress3DOCelFrame(frame, data, celDataSize, 16, NULL); + + delete[] data; + + push_back(frame); + } +} + +static byte imagefile3DO_cel_bitsPerPixelLookupTable[8] = { + 0, 1, 2, 4, 6, 8, 16, 0 +}; + +// Reads a 3DO .cel/.anim file +void ImageFile3DO::load3DOCelFile(Common::SeekableReadStream &stream) { + int32 streamSize = stream.size(); + int32 chunkStartPos = 0; + uint32 chunkTag = 0; + uint32 chunkSize = 0; + byte *chunkDataPtr = NULL; + + // ANIM chunk (animation header for animation files) + bool animFound = false; + uint32 animVersion = 0; + uint32 animType = 0; + uint32 animFrameCount = 1; // we expect 1 frame without an ANIM header + // CCB chunk (cel control block) + bool ccbFound = false; + uint32 ccbVersion = 0; + uint32 ccbFlags = 0; + bool ccbFlags_compressed = false; + uint16 ccbPPMP0 = 0; + uint16 ccbPPMP1 = 0; + uint32 ccbPRE0 = 0; + uint16 ccbPRE0_height = 0; + byte ccbPRE0_bitsPerPixel = 0; + uint32 ccbPRE1 = 0; + uint16 ccbPRE1_width = 0; + uint32 ccbWidth = 0; + uint32 ccbHeight = 0; + // pixel lookup table + bool plutFound = false; + uint32 plutCount = 0; + ImageFile3DOPixelLookupTable plutRGBlookupTable; + + memset(&plutRGBlookupTable, 0, sizeof(plutRGBlookupTable)); + + while (!stream.err() && (stream.pos() < streamSize)) { + chunkStartPos = stream.pos(); + chunkTag = stream.readUint32BE(); + chunkSize = stream.readUint32BE(); + + if (stream.eos() || stream.err()) + break; + + if (chunkSize < 8) + error("load3DOCelFile: Invalid chunk size"); + + uint32 dataSize = chunkSize - 8; + + switch (chunkTag) { + case MKTAG('A', 'N', 'I', 'M'): + // animation header + assert(dataSize >= 24); + + if (animFound) + error("load3DOCelFile: multiple ANIM chunks not supported"); + + animFound = true; + animVersion = stream.readUint32BE(); + animType = stream.readUint32BE(); + animFrameCount = stream.readUint32BE(); + // UINT32 - framerate (0x2000 in walk.anim???) + // UINT32 - starting frame (0 for walk.anim) + // UINT32 - number of loops (0 for walk.anim) + + if (animVersion != 0) + error("load3DOCelFile: Unsupported animation file version"); + if (animType != 1) + error("load3DOCelFile: Only single CCB animation files are supported"); + break; + + case MKTAG('C', 'C', 'B', ' '): + // CEL control block + assert(dataSize >= 72); + + if (ccbFound) + error("load3DOCelFile: multiple CCB chunks not supported"); + + ccbFound = true; + ccbVersion = stream.readUint32BE(); + ccbFlags = stream.readUint32BE(); + stream.skip(3 * 4); // skip over 3 pointer fields, which are used in memory only by 3DO hardware + stream.skip(8 * 4); // skip over 8 offset fields + ccbPPMP0 = stream.readUint16BE(); + ccbPPMP1 = stream.readUint16BE(); + ccbPRE0 = stream.readUint32BE(); + ccbPRE1 = stream.readUint32BE(); + ccbWidth = stream.readUint32BE(); + ccbHeight = stream.readUint32BE(); + + if (ccbVersion != 0) + error("load3DOCelFile: Unsupported CCB version"); + + if (ccbFlags & 0x200) // bit 9 + ccbFlags_compressed = true; + + // bit 5 of ccbFlags defines how RGB-black (0, 0, 0) will get treated + // = false -> RGB-black is treated as transparent + // = true -> RGB-black is treated as actual black + // atm we are always treating it as transparent + // it seems this bit is not set for any data of Sherlock Holmes + + // PRE0 first 3 bits define how many bits per encoded pixel are used + ccbPRE0_bitsPerPixel = imagefile3DO_cel_bitsPerPixelLookupTable[ccbPRE0 & 0x07]; + if (!ccbPRE0_bitsPerPixel) + error("load3DOCelFile: Invalid CCB PRE0 bits per pixel"); + + ccbPRE0_height = ((ccbPRE0 >> 6) & 0x03FF) + 1; + ccbPRE1_width = (ccbPRE1 & 0x03FF) + 1; + assert(ccbPRE0_height == ccbHeight); + assert(ccbPRE1_width == ccbWidth); + break; + + case MKTAG('P', 'L', 'U', 'T'): + // pixel lookup table + // optional, not required for at least 16-bit pixel data + assert(dataSize >= 6); + + if (!ccbFound) + error("load3DOCelFile: PLUT chunk found without CCB chunk"); + if (plutFound) + error("load3DOCelFile: multiple PLUT chunks currently not supported"); + + plutFound = true; + plutCount = stream.readUint32BE(); + // table follows, each entry is 16bit RGB555 + assert(dataSize >= 4 + (plutCount * 2)); // security check + assert(plutCount <= 256); // security check + + assert(plutCount <= 32); // PLUT should never contain more than 32 entries + + for (uint32 plutColorNr = 0; plutColorNr < plutCount; plutColorNr++) { + plutRGBlookupTable.pixelColor[plutColorNr] = stream.readUint16BE(); + } + + if (ccbPRE0_bitsPerPixel == 8) { + // In case we are getting 8-bits per pixel, we calculate the shades accordingly + // I'm not 100% sure if the calculation is correct. It's difficult to find information + // on this topic. + // The map uses this type of cel + assert(plutCount == 32); // And we expect 32 entries inside PLUT chunk + + uint16 plutColorRGB = 0; + for (uint32 plutColorNr = 0; plutColorNr < plutCount; plutColorNr++) { + plutColorRGB = plutRGBlookupTable.pixelColor[plutColorNr]; + + // Extract RGB values + byte plutColorRed = (plutColorRGB >> 10) & 0x1F; + byte plutColorGreen = (plutColorRGB >> 5) & 0x1F; + byte plutColorBlue = plutColorRGB & 0x1F; + + byte shadeMultiplier = 2; + for (uint32 plutShadeNr = 1; plutShadeNr < 8; plutShadeNr++) { + uint16 shadedColorRGB; + byte shadedColorRed = (plutColorRed * shadeMultiplier) >> 3; + byte shadedColorGreen = (plutColorGreen * shadeMultiplier) >> 3; + byte shadedColorBlue = (plutColorBlue * shadeMultiplier) >> 3; + + shadedColorRed = CLIP<byte>(shadedColorRed, 0, 0x1F); + shadedColorGreen = CLIP<byte>(shadedColorGreen, 0, 0x1F); + shadedColorBlue = CLIP<byte>(shadedColorBlue, 0, 0x1F); + shadedColorRGB = (shadedColorRed << 10) | (shadedColorGreen << 5) | shadedColorBlue; + + plutRGBlookupTable.pixelColor[plutColorNr + (plutShadeNr << 5)] = shadedColorRGB; + shadeMultiplier++; + } + } + } + break; + + case MKTAG('X', 'T', 'R', 'A'): + // Unknown contents, occurs right before PDAT + break; + + case MKTAG('P', 'D', 'A', 'T'): { + // pixel data for one frame + // may be compressed or uncompressed pixels + + if (ccbPRE0_bitsPerPixel != 16) { + // We require a pixel lookup table in case bits-per-pixel is lower than 16 + if (!plutFound) + error("load3DOCelFile: bits per pixel < 16, but no pixel lookup table was found"); + } else { + // But we don't like it in case bits-per-pixel is 16 and we find one + if (plutFound) + error("load3DOCelFile: bits per pixel == 16, but pixel lookup table was found as well"); + } + // read data into memory + chunkDataPtr = new byte[dataSize]; + + stream.read(chunkDataPtr, dataSize); + + // Set up frame + ImageFrame imageFrame; + + imageFrame._width = ccbWidth; + imageFrame._height = ccbHeight; + imageFrame._paletteBase = 0; + imageFrame._offset.x = 0; + imageFrame._offset.y = 0; + imageFrame._rleEncoded = ccbFlags_compressed; + imageFrame._size = 0; + + // Decompress/copy this frame + if (!plutFound) { + decompress3DOCelFrame(imageFrame, chunkDataPtr, dataSize, ccbPRE0_bitsPerPixel, NULL); + } else { + decompress3DOCelFrame(imageFrame, chunkDataPtr, dataSize, ccbPRE0_bitsPerPixel, &plutRGBlookupTable); + } + + delete[] chunkDataPtr; + + push_back(imageFrame); + break; + } + + case MKTAG('O', 'F', 'S', 'T'): // 3DOSplash.cel + // unknown contents + break; + + default: + error("Unsupported '%s' chunk in 3DO cel file", tag2str(chunkTag)); + } + + // Seek to end of chunk + stream.seek(chunkStartPos + chunkSize); + } +} + +// Reads 3DO .cel data (room file format) +void ImageFile3DO::load3DOCelRoomData(Common::SeekableReadStream &stream) { + uint32 streamLeft = stream.size() - stream.pos(); + uint16 roomDataHeader_size = 0; + byte roomDataHeader_offsetX = 0; + byte roomDataHeader_offsetY = 0; + + // CCB chunk (cel control block) + uint32 ccbFlags = 0; + bool ccbFlags_compressed = false; + uint16 ccbPPMP0 = 0; + uint16 ccbPPMP1 = 0; + uint32 ccbPRE0 = 0; + uint16 ccbPRE0_height = 0; + byte ccbPRE0_bitsPerPixel = 0; + uint32 ccbPRE1 = 0; + uint16 ccbPRE1_width = 0; + uint32 ccbWidth = 0; + uint32 ccbHeight = 0; + // cel data + uint32 celDataSize = 0; + + while (streamLeft > 0) { + // We expect at least 8 bytes basic header + if (streamLeft < 8) + error("load3DOCelRoomData: expected room data header, not enough bytes"); + + // 3DO sherlock holmes room data header + stream.skip(4); // Possibly UINT16 width, UINT16 height?!?! + roomDataHeader_size = stream.readUint16BE(); + roomDataHeader_offsetX = stream.readByte(); + roomDataHeader_offsetY = stream.readByte(); + streamLeft -= 8; + + // We expect the header size specified in the basic header to be at least a raw CCB + if (roomDataHeader_size < 68) + error("load3DOCelRoomData: header size is too small"); + // Check, that enough bytes for CCB are available + if (streamLeft < 68) + error("load3DOCelRoomData: expected raw cel control block, not enough bytes"); + + // 3DO raw cel control block + ccbFlags = stream.readUint32BE(); + stream.skip(3 * 4); // skip over 3 pointer fields, which are used in memory only by 3DO hardware + stream.skip(8 * 4); // skip over 8 offset fields + ccbPPMP0 = stream.readUint16BE(); + ccbPPMP1 = stream.readUint16BE(); + ccbPRE0 = stream.readUint32BE(); + ccbPRE1 = stream.readUint32BE(); + ccbWidth = stream.readUint32BE(); + ccbHeight = stream.readUint32BE(); + + if (ccbFlags & 0x200) // bit 9 + ccbFlags_compressed = true; + + // PRE0 first 3 bits define how many bits per encoded pixel are used + ccbPRE0_bitsPerPixel = imagefile3DO_cel_bitsPerPixelLookupTable[ccbPRE0 & 0x07]; + if (!ccbPRE0_bitsPerPixel) + error("load3DOCelRoomData: Invalid CCB PRE0 bits per pixel"); + + ccbPRE0_height = ((ccbPRE0 >> 6) & 0x03FF) + 1; + ccbPRE1_width = (ccbPRE1 & 0x03FF) + 1; + assert(ccbPRE0_height == ccbHeight); + assert(ccbPRE1_width == ccbWidth); + + if (ccbPRE0_bitsPerPixel != 16) { + // We currently support 16-bits per pixel in here + error("load3DOCelRoomData: bits per pixel < 16?!?!?"); + } + // Got the raw CCB + streamLeft -= 68; + + // cel data follows + // size field does not include the 8 byte header + celDataSize = roomDataHeader_size - 68; + + if (streamLeft < celDataSize) + error("load3DOCelRoomData: expected cel data, not enough bytes"); + + // read data into memory + byte *celDataPtr = new byte[celDataSize]; + + stream.read(celDataPtr, celDataSize); + streamLeft -= celDataSize; + + // Set up frame + { + ImageFrame imageFrame; + + imageFrame._width = ccbWidth; + imageFrame._height = ccbHeight; + imageFrame._paletteBase = 0; + imageFrame._offset.x = roomDataHeader_offsetX; + imageFrame._offset.y = roomDataHeader_offsetY; + imageFrame._rleEncoded = ccbFlags_compressed; + imageFrame._size = 0; + + // Decompress/copy this frame + decompress3DOCelFrame(imageFrame, celDataPtr, celDataSize, ccbPRE0_bitsPerPixel, NULL); + + delete[] celDataPtr; + + push_back(imageFrame); + } + } +} + +static uint16 imagefile3DO_cel_bitsMask[17] = { + 0, + 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, + 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF +}; + +// gets [bitCount] bits from dataPtr, going from MSB to LSB +inline uint16 ImageFile3DO::celGetBits(const byte *&dataPtr, byte bitCount, byte &dataBitsLeft) { + byte resultBitsLeft = bitCount; + uint16 result = 0; + byte currentByte = *dataPtr; + + // Get bits of current byte + while (resultBitsLeft) { + if (resultBitsLeft < dataBitsLeft) { + // we need less than we have left + result |= (currentByte >> (dataBitsLeft - resultBitsLeft)) & imagefile3DO_cel_bitsMask[resultBitsLeft]; + dataBitsLeft -= resultBitsLeft; + resultBitsLeft = 0; + + } else { + // we need as much as we have left or more + resultBitsLeft -= dataBitsLeft; + result |= (currentByte & imagefile3DO_cel_bitsMask[dataBitsLeft]) << resultBitsLeft; + + // Go to next byte + dataPtr++; + dataBitsLeft = 8; + if (resultBitsLeft) { + currentByte = *dataPtr; + } + } + } + return result; +} + +// decompress/copy 3DO cel data +void ImageFile3DO::decompress3DOCelFrame(ImageFrame &frame, const byte *dataPtr, uint32 dataSize, byte bitsPerPixel, ImageFile3DOPixelLookupTable *pixelLookupTable) { + frame._frame.create(frame._width, frame._height, Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0)); + uint16 *dest = (uint16 *)frame._frame.getPixels(); + Common::fill(dest, dest + frame._width * frame._height, 0); + + int frameHeightLeft = frame._height; + int frameWidthLeft = frame._width; + uint16 pixelCount = 0; + uint16 pixel = 0; + + const byte *srcLineStart = dataPtr; + const byte *srcLineData = dataPtr; + byte srcLineDataBitsLeft = 0; + uint16 lineDWordSize = 0; + uint16 lineByteSize = 0; + + if (bitsPerPixel == 16) { + // Must not use pixel lookup table on 16-bits-per-pixel data + assert(!pixelLookupTable); + } + + if (frame._rleEncoded) { + // compressed + byte compressionType = 0; + byte compressionPixels = 0; + + while (frameHeightLeft > 0) { + frameWidthLeft = frame._width; + + if (bitsPerPixel >= 8) { + lineDWordSize = READ_BE_UINT16(srcLineStart); + srcLineData = srcLineStart + 2; + } else { + lineDWordSize = *srcLineStart; + srcLineData = srcLineStart + 1; + } + srcLineDataBitsLeft = 8; + + lineDWordSize += 2; + lineByteSize = lineDWordSize * 4; // calculate compressed data size in bytes for current line + + // debug + //warning("offset %d: decoding line, size %d, bytesize %d", srcSeeker - src, dwordSize, lineByteSize); + + while (frameWidthLeft > 0) { + // get 2 bits -> compressionType + // get 6 bits -> pixel count (0 = 1 pixel) + compressionType = celGetBits(srcLineData, 2, srcLineDataBitsLeft); + // 6 bits == length (0 = 1 pixel) + compressionPixels = celGetBits(srcLineData, 6, srcLineDataBitsLeft) + 1; + + if (!compressionType) // end of line + break; + + switch(compressionType) { + case 1: // simple copy + for (pixelCount = 0; pixelCount < compressionPixels; pixelCount++) { + pixel = celGetBits(srcLineData, bitsPerPixel, srcLineDataBitsLeft); + if (pixelLookupTable) { + pixel = pixelLookupTable->pixelColor[pixel]; + } + *dest++ = convertPixel(pixel); + } + break; + case 2: // transparent + for (pixelCount = 0; pixelCount < compressionPixels; pixelCount++) { + *dest++ = 0; + } + break; + case 3: // duplicate pixels + pixel = celGetBits(srcLineData, bitsPerPixel, srcLineDataBitsLeft); + if (pixelLookupTable) { + pixel = pixelLookupTable->pixelColor[pixel]; + } + pixel = convertPixel(pixel); + for (pixelCount = 0; pixelCount < compressionPixels; pixelCount++) { + *dest++ = pixel; + } + break; + default: + break; + } + frameWidthLeft -= compressionPixels; + } + + assert(frameWidthLeft >= 0); + + if (frameWidthLeft > 0) { + // still pixels left? skip them + dest += frameWidthLeft; + } + + frameHeightLeft--; + + // Seek to next line start + srcLineStart += lineByteSize; + } + } else { + // uncompressed + srcLineDataBitsLeft = 8; + lineDWordSize = ((frame._width * bitsPerPixel) + 31) >> 5; + lineByteSize = lineDWordSize * 4; + uint32 totalExpectedSize = lineByteSize * frame._height; + + assert(totalExpectedSize <= dataSize); // security check + + while (frameHeightLeft > 0) { + srcLineData = srcLineStart; + frameWidthLeft = frame._width; + + while (frameWidthLeft > 0) { + pixel = celGetBits(srcLineData, bitsPerPixel, srcLineDataBitsLeft); + if (pixelLookupTable) { + pixel = pixelLookupTable->pixelColor[pixel]; + } + *dest++ = convertPixel(pixel); + + frameWidthLeft--; + } + frameHeightLeft--; + + // Seek to next line start + srcLineStart += lineByteSize; + } + } +} + +// Reads Sherlock Holmes 3DO font file +void ImageFile3DO::loadFont(Common::SeekableReadStream &stream) { + uint32 streamSize = stream.size(); + uint32 header_offsetWidthTable = 0; + uint32 header_offsetBitsTable = 0; + uint32 header_fontHeight = 0; + uint32 header_bytesPerLine = 0; + uint32 header_maxChar = 0; + uint32 header_charCount = 0; + + byte *widthTablePtr = NULL; + uint32 bitsTableSize = 0; + byte *bitsTablePtr = NULL; + + stream.skip(2); // Unknown bytes + stream.skip(2); // Unknown bytes (0x000E) + header_offsetWidthTable = stream.readUint32BE(); + header_offsetBitsTable = stream.readUint32BE(); + stream.skip(4); // Unknown bytes (0x00000004) + header_fontHeight = stream.readUint32BE(); + header_bytesPerLine = stream.readUint32BE(); + header_maxChar = stream.readUint32BE(); + + assert(header_maxChar <= 255); + header_charCount = header_maxChar + 1; + + // Allocate memory for width table + widthTablePtr = new byte[header_charCount]; + + stream.seek(header_offsetWidthTable); + stream.read(widthTablePtr, header_charCount); + + // Allocate memory for the bits + assert(header_offsetBitsTable < streamSize); // Security check + bitsTableSize = streamSize - header_offsetBitsTable; + bitsTablePtr = new byte[bitsTableSize]; + stream.read(bitsTablePtr, bitsTableSize); + + // Now extract all characters + uint16 curChar = 0; + const byte *curBitsLinePtr = bitsTablePtr; + const byte *curBitsPtr = NULL; + byte curBitsLeft = 0; + uint32 curCharHeightLeft = 0; + uint32 curCharWidthLeft = 0; + byte curBits = 0; + byte curBitsReversed = 0; + byte curPosX = 0; + + assert(bitsTableSize >= (header_maxChar * header_fontHeight * header_bytesPerLine)); // Security + + // first frame needs to be "!" (33 decimal) + // our font code is subtracting 33 from the actual character code + curBitsLinePtr += (33 * (header_fontHeight * header_bytesPerLine)); + + for (curChar = 33; curChar < header_charCount; curChar++) { + // create frame + { + ImageFrame imageFrame; + + imageFrame._width = widthTablePtr[curChar]; + imageFrame._height = header_fontHeight; + imageFrame._paletteBase = 0; + imageFrame._offset.x = 0; + imageFrame._offset.y = 0; + imageFrame._rleEncoded = false; + imageFrame._size = 0; + + // Extract pixels + imageFrame._frame.create(imageFrame._width, imageFrame._height, Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0)); + uint16 *dest = (uint16 *)imageFrame._frame.getPixels(); + Common::fill(dest, dest + imageFrame._width * imageFrame._height, 0); + + curCharHeightLeft = header_fontHeight; + while (curCharHeightLeft) { + curCharWidthLeft = widthTablePtr[curChar]; + curBitsPtr = curBitsLinePtr; + curBitsLeft = 8; + curPosX = 0; + + while (curCharWidthLeft) { + if (!(curPosX & 1)) { + curBits = *curBitsPtr >> 4; + } else { + curBits = *curBitsPtr & 0x0F; + curBitsPtr++; + } + // doing this properly is complicated + // the 3DO has built-in anti-aliasing + // this here at least results in somewhat readable text + // TODO: make it better + if (curBits) { + curBitsReversed = (curBits >> 3) | ((curBits & 0x04) >> 1) | ((curBits & 0x02) << 1) | ((curBits & 0x01) << 3); + curBits = 20 - curBits; + } + + byte curIntensity = curBits; + *dest = (curIntensity << 11) | (curIntensity << 6) | curIntensity; + dest++; + + curCharWidthLeft--; + curPosX++; + } + + curCharHeightLeft--; + curBitsLinePtr += header_bytesPerLine; + } + + push_back(imageFrame); + } + } + + delete[] bitsTablePtr; + delete[] widthTablePtr; +} + +} // End of namespace Sherlock diff --git a/engines/sherlock/image_file.h b/engines/sherlock/image_file.h new file mode 100644 index 0000000000..f24e831440 --- /dev/null +++ b/engines/sherlock/image_file.h @@ -0,0 +1,159 @@ +/* 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 SHERLOCK_IMAGE_FILE_H +#define SHERLOCK_IMAGE_FILE_H + +#include "common/array.h" +#include "common/file.h" +#include "common/hashmap.h" +#include "common/hash-str.h" +#include "common/rect.h" +#include "common/str.h" +#include "common/stream.h" +#include "graphics/surface.h" + +namespace Sherlock { + +class SherlockEngine; + +struct ImageFrame { + uint32 _size; + uint16 _width, _height; + int _paletteBase; + bool _rleEncoded; + Common::Point _offset; + byte _rleMarker; + Graphics::Surface _frame; + + /** + * Return the frame width adjusted by a specified scale amount + */ + int sDrawXSize(int scaleVal) const; + + /** + * Return the frame height adjusted by a specified scale amount + */ + int sDrawYSize(int scaleVal) const; + + /** + * Return the frame offset x adjusted by a specified scale amount + */ + int sDrawXOffset(int scaleVal) const; + + /** + * Return the frame offset y adjusted by a specified scale amount + */ + int sDrawYOffset(int scaleVal) const; +}; + +class ImageFile : public Common::Array<ImageFrame> { +private: + static SherlockEngine *_vm; + + /** + * Load the data of the sprite + */ + void load(Common::SeekableReadStream &stream, bool skipPalette, bool animImages); + + /** + * Gets the palette at the start of the sprite file + */ + void loadPalette(Common::SeekableReadStream &stream); + + /** + * Decompress a single frame for the sprite + */ + void decompressFrame(ImageFrame &frame, const byte *src); +public: + byte _palette[256 * 3]; +public: + ImageFile(); + ImageFile(const Common::String &name, bool skipPal = false, bool animImages = false); + ImageFile(Common::SeekableReadStream &stream, bool skipPal = false); + ~ImageFile(); + static void setVm(SherlockEngine *vm); +}; + +enum ImageFile3DOType { + kImageFile3DOType_Animation = 0, + kImageFile3DOType_Cel = 1, + kImageFile3DOType_CelAnimation = 2, + kImageFile3DOType_RoomFormat = 3, + kImageFile3DOType_Font = 4 +}; + +struct ImageFile3DOPixelLookupTable { + uint16 pixelColor[256]; +}; + +class ImageFile3DO : public ImageFile { // Common::Array<ImageFrame> { +private: + static SherlockEngine *_vm; + + /** + * Load the data of the sprite + */ + void load(Common::SeekableReadStream &stream, bool isRoomData); + + /** + * convert pixel RGB values from RGB555 to RGB565 + */ + inline uint16 convertPixel(uint16 pixel3DO); + + /** + * Load 3DO cel file + */ + void load3DOCelFile(Common::SeekableReadStream &stream); + + /** + * Load 3DO cel data (room file format) + */ + void load3DOCelRoomData(Common::SeekableReadStream &stream); + + inline uint16 celGetBits(const byte *&dataPtr, byte bitCount, byte &dataBitsLeft); + + /** + * Decompress a single frame of a 3DO cel file + */ + void decompress3DOCelFrame(ImageFrame &frame, const byte *dataPtr, uint32 dataSize, byte bitsPerPixel, ImageFile3DOPixelLookupTable *pixelLookupTable); + + /** + * Load animation graphics file + */ + void loadAnimationFile(Common::SeekableReadStream &stream); + + /** + * Load Sherlock Holmes 3DO font file + */ + void loadFont(Common::SeekableReadStream &stream); + +public: + ImageFile3DO(const Common::String &name, ImageFile3DOType imageFile3DOType); + ImageFile3DO(Common::SeekableReadStream &stream, bool isRoomData = false); + ~ImageFile3DO(); + static void setVm(SherlockEngine *vm); +}; + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/inventory.cpp b/engines/sherlock/inventory.cpp new file mode 100644 index 0000000000..692b05773b --- /dev/null +++ b/engines/sherlock/inventory.cpp @@ -0,0 +1,488 @@ +/* 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 "sherlock/inventory.h" +#include "sherlock/sherlock.h" +#include "sherlock/scalpel/scalpel_user_interface.h" + +namespace Sherlock { + +InventoryItem::InventoryItem(int requiredFlag, const Common::String &name, + const Common::String &description, const Common::String &examine) : + _requiredFlag(requiredFlag), _name(name), _description(description), + _examine(examine), _lookFlag(0) { +} + +void InventoryItem::synchronize(Serializer &s) { + s.syncAsSint16LE(_requiredFlag); + s.syncAsSint16LE(_lookFlag); + s.syncString(_name); + s.syncString(_description); + s.syncString(_examine); +} + +/*----------------------------------------------------------------*/ + +Inventory::Inventory(SherlockEngine *vm) : Common::Array<InventoryItem>(), _vm(vm) { + Common::fill(&_invShapes[0], &_invShapes[MAX_VISIBLE_INVENTORY], (ImageFile *)nullptr); + _invGraphicsLoaded = false; + _invIndex = 0; + _holdings = 0; + _invMode = INVMODE_EXIT; + for (int i = 0; i < 6; ++i) + _invShapes[i] = nullptr; +} + +Inventory::~Inventory() { + freeGraphics(); +} + +void Inventory::freeInv() { + freeGraphics(); + + _names.clear(); + _invGraphicsLoaded = false; +} + +void Inventory::freeGraphics() { + for (uint idx = 0; idx < MAX_VISIBLE_INVENTORY; ++idx) + delete _invShapes[idx]; + + Common::fill(&_invShapes[0], &_invShapes[MAX_VISIBLE_INVENTORY], (ImageFile *)nullptr); + _invGraphicsLoaded = false; +} + +void Inventory::loadInv() { + // Exit if the inventory names are already loaded + if (_names.size() > 0) + return; + + // Load the inventory names + Common::SeekableReadStream *stream = _vm->_res->load("invent.txt"); + + int streamSize = stream->size(); + while (stream->pos() < streamSize) { + Common::String name; + char c; + while ((c = stream->readByte()) != 0) + name += c; + + _names.push_back(name); + } + + delete stream; + + loadGraphics(); +} + +void Inventory::loadGraphics() { + if (_invGraphicsLoaded) + return; + + // Default all inventory slots to empty + Common::fill(&_invShapes[0], &_invShapes[MAX_VISIBLE_INVENTORY], (ImageFile *)nullptr); + + for (int idx = _invIndex; (idx < _holdings) && (idx - _invIndex) < MAX_VISIBLE_INVENTORY; ++idx) { + // Get the name of the item to be displayed, figure out its accompanying + // .VGS file with its picture, and then load it + int invNum = findInv((*this)[idx]._name); + Common::String filename = Common::String::format("item%02d.vgs", invNum + 1); + + if (!IS_3DO) { + // PC + _invShapes[idx - _invIndex] = new ImageFile(filename); + } else { + _invShapes[idx - _invIndex] = new ImageFile3DO(filename, kImageFile3DOType_RoomFormat); + } + } + + _invGraphicsLoaded = true; +} + +int Inventory::findInv(const Common::String &name) { + for (int idx = 0; idx < (int)_names.size(); ++idx) { + if (name.equalsIgnoreCase(_names[idx])) + return idx; + } + + // Couldn't find the desired item + error("Couldn't find inventory item - %s", name.c_str()); +} + +void Inventory::putInv(InvSlamMode slamIt) { + Screen &screen = *_vm->_screen; + UserInterface &ui = *_vm->_ui; + + // If an inventory item has disappeared (due to using it or giving it), + // a blank space slot may have appeared. If so, adjust the inventory + if (_invIndex > 0 && _invIndex > (_holdings - 6)) { + --_invIndex; + freeGraphics(); + loadGraphics(); + } + + if (slamIt != SLAM_SECONDARY_BUFFER) { + screen.makePanel(Common::Rect(6, 163, 54, 197)); + screen.makePanel(Common::Rect(58, 163, 106, 197)); + screen.makePanel(Common::Rect(110, 163, 158, 197)); + screen.makePanel(Common::Rect(162, 163, 210, 197)); + screen.makePanel(Common::Rect(214, 163, 262, 197)); + screen.makePanel(Common::Rect(266, 163, 314, 197)); + } + + // Iterate through displaying up to 6 objects at a time + for (int idx = _invIndex; idx < _holdings && (idx - _invIndex) < MAX_VISIBLE_INVENTORY; ++idx) { + int itemNum = idx - _invIndex; + Surface &bb = slamIt == SLAM_SECONDARY_BUFFER ? screen._backBuffer2 : screen._backBuffer1; + Common::Rect r(8 + itemNum * 52, 165, 51 + itemNum * 52, 194); + + // Draw the background + if (idx == ui._selector) { + bb.fillRect(r, BUTTON_BACKGROUND); + } else if (slamIt == SLAM_SECONDARY_BUFFER) { + bb.fillRect(r, BUTTON_MIDDLE); + } + + // Draw the item image + ImageFrame &frame = (*_invShapes[itemNum])[0]; + bb.transBlitFrom(frame, Common::Point(6 + itemNum * 52 + ((47 - frame._width) / 2), + 163 + ((33 - frame._height) / 2))); + } + + if (slamIt == SLAM_DISPLAY) + screen.slamArea(6, 163, 308, 34); + + if (slamIt != SLAM_SECONDARY_BUFFER) + ui.clearInfo(); + + if (slamIt == 0) { + invCommands(0); + } else if (slamIt == SLAM_SECONDARY_BUFFER) { + screen._backBuffer = &screen._backBuffer2; + invCommands(0); + screen._backBuffer = &screen._backBuffer1; + } +} + +void Inventory::drawInventory(InvNewMode mode) { + FixedText &fixedText = *_vm->_fixedText; + Screen &screen = *_vm->_screen; + UserInterface &ui = *_vm->_ui; + InvNewMode tempMode = mode; + + loadInv(); + + if (mode == INVENTORY_DONT_DISPLAY) { + screen._backBuffer = &screen._backBuffer2; + } + + // Draw the window background + Surface &bb = *screen._backBuffer; + bb.fillRect(Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH, CONTROLS_Y1 + 10), BORDER_COLOR); + bb.fillRect(Common::Rect(0, CONTROLS_Y1 + 10, 2, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR); + bb.fillRect(Common::Rect(SHERLOCK_SCREEN_WIDTH - 2, CONTROLS_Y1 + 10, + SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR); + bb.fillRect(Common::Rect(0, SHERLOCK_SCREEN_HEIGHT - 2, SHERLOCK_SCREEN_WIDTH, + SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR); + bb.fillRect(Common::Rect(2, CONTROLS_Y1 + 10, SHERLOCK_SCREEN_WIDTH - 2, SHERLOCK_SCREEN_HEIGHT - 2), + INV_BACKGROUND); + + // Draw the buttons + Common::String fixedText_Exit = fixedText.getText(kFixedText_Inventory_Exit); + Common::String fixedText_Look = fixedText.getText(kFixedText_Inventory_Look); + Common::String fixedText_Use = fixedText.getText(kFixedText_Inventory_Use); + Common::String fixedText_Give = fixedText.getText(kFixedText_Inventory_Give); + + screen.makeButton(Common::Rect(Scalpel::INVENTORY_POINTS[0][0], CONTROLS_Y1, Scalpel::INVENTORY_POINTS[0][1], + CONTROLS_Y1 + 10), Scalpel::INVENTORY_POINTS[0][2] - screen.stringWidth(fixedText_Exit) / 2, fixedText_Exit); + screen.makeButton(Common::Rect(Scalpel::INVENTORY_POINTS[1][0], CONTROLS_Y1, Scalpel::INVENTORY_POINTS[1][1], + CONTROLS_Y1 + 10), Scalpel::INVENTORY_POINTS[1][2] - screen.stringWidth(fixedText_Look) / 2, fixedText_Look); + screen.makeButton(Common::Rect(Scalpel::INVENTORY_POINTS[2][0], CONTROLS_Y1, Scalpel::INVENTORY_POINTS[2][1], + CONTROLS_Y1 + 10), Scalpel::INVENTORY_POINTS[2][2] - screen.stringWidth(fixedText_Use) / 2, fixedText_Use); + screen.makeButton(Common::Rect(Scalpel::INVENTORY_POINTS[3][0], CONTROLS_Y1, Scalpel::INVENTORY_POINTS[3][1], + CONTROLS_Y1 + 10), Scalpel::INVENTORY_POINTS[3][2] - screen.stringWidth(fixedText_Give) / 2, fixedText_Give); + screen.makeButton(Common::Rect(Scalpel::INVENTORY_POINTS[4][0], CONTROLS_Y1, Scalpel::INVENTORY_POINTS[4][1], + CONTROLS_Y1 + 10), Scalpel::INVENTORY_POINTS[4][2], "^^"); // 2 arrows pointing to the left + screen.makeButton(Common::Rect(Scalpel::INVENTORY_POINTS[5][0], CONTROLS_Y1, Scalpel::INVENTORY_POINTS[5][1], + CONTROLS_Y1 + 10), Scalpel::INVENTORY_POINTS[5][2], "^"); // 1 arrow pointing to the left + screen.makeButton(Common::Rect(Scalpel::INVENTORY_POINTS[6][0], CONTROLS_Y1, Scalpel::INVENTORY_POINTS[6][1], + CONTROLS_Y1 + 10), Scalpel::INVENTORY_POINTS[6][2], "_"); // 1 arrow pointing to the right + screen.makeButton(Common::Rect(Scalpel::INVENTORY_POINTS[7][0], CONTROLS_Y1, Scalpel::INVENTORY_POINTS[7][1], + CONTROLS_Y1 + 10), Scalpel::INVENTORY_POINTS[7][2], "__"); // 2 arrows pointing to the right + + if (tempMode == INVENTORY_DONT_DISPLAY) + mode = LOOK_INVENTORY_MODE; + _invMode = (InvMode)mode; + + if (mode != PLAIN_INVENTORY) { + ui._oldKey = Scalpel::INVENTORY_COMMANDS[(int)mode]; + } else { + ui._oldKey = -1; + } + + invCommands(0); + putInv(SLAM_DONT_DISPLAY); + + if (tempMode != INVENTORY_DONT_DISPLAY) { + if (!ui._slideWindows) { + screen.slamRect(Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); + } else { + ui.summonWindow(false, CONTROLS_Y1); + } + + ui._windowOpen = true; + } else { + // Reset the screen back buffer to the first buffer now that drawing is done + screen._backBuffer = &screen._backBuffer1; + } + + assert(IS_SERRATED_SCALPEL); + ((Scalpel::ScalpelUserInterface *)_vm->_ui)->_oldUse = -1; +} + +void Inventory::invCommands(bool slamIt) { + FixedText &fixedText = *_vm->_fixedText; + Screen &screen = *_vm->_screen; + UserInterface &ui = *_vm->_ui; + + Common::String fixedText_Exit = fixedText.getText(kFixedText_Inventory_Exit); + Common::String fixedText_Look = fixedText.getText(kFixedText_Inventory_Look); + Common::String fixedText_Use = fixedText.getText(kFixedText_Inventory_Use); + Common::String fixedText_Give = fixedText.getText(kFixedText_Inventory_Give); + + if (slamIt) { + screen.buttonPrint(Common::Point(Scalpel::INVENTORY_POINTS[0][2], CONTROLS_Y1), + _invMode == INVMODE_EXIT ? COMMAND_HIGHLIGHTED :COMMAND_FOREGROUND, + true, fixedText_Exit); + screen.buttonPrint(Common::Point(Scalpel::INVENTORY_POINTS[1][2], CONTROLS_Y1), + _invMode == INVMODE_LOOK ? COMMAND_HIGHLIGHTED :COMMAND_FOREGROUND, + true, fixedText_Look); + screen.buttonPrint(Common::Point(Scalpel::INVENTORY_POINTS[2][2], CONTROLS_Y1), + _invMode == INVMODE_USE ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND, + true, fixedText_Use); + screen.buttonPrint(Common::Point(Scalpel::INVENTORY_POINTS[3][2], CONTROLS_Y1), + _invMode == INVMODE_GIVE ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND, + true, fixedText_Give); + screen.print(Common::Point(Scalpel::INVENTORY_POINTS[4][2], CONTROLS_Y1 + 1), + _invIndex == 0 ? COMMAND_NULL : COMMAND_FOREGROUND, + "^^"); // 2 arrows pointing to the left + screen.print(Common::Point(Scalpel::INVENTORY_POINTS[5][2], CONTROLS_Y1 + 1), + _invIndex == 0 ? COMMAND_NULL : COMMAND_FOREGROUND, + "^"); // 2 arrows pointing to the left + screen.print(Common::Point(Scalpel::INVENTORY_POINTS[6][2], CONTROLS_Y1 + 1), + (_holdings - _invIndex <= 6) ? COMMAND_NULL : COMMAND_FOREGROUND, + "_"); // 1 arrow pointing to the right + screen.print(Common::Point(Scalpel::INVENTORY_POINTS[7][2], CONTROLS_Y1 + 1), + (_holdings - _invIndex <= 6) ? COMMAND_NULL : COMMAND_FOREGROUND, + "__"); // 2 arrows pointing to the right + if (_invMode != INVMODE_LOOK) + ui.clearInfo(); + } else { + screen.buttonPrint(Common::Point(Scalpel::INVENTORY_POINTS[0][2], CONTROLS_Y1), + _invMode == INVMODE_EXIT ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND, + false, fixedText_Exit); + screen.buttonPrint(Common::Point(Scalpel::INVENTORY_POINTS[1][2], CONTROLS_Y1), + _invMode == INVMODE_LOOK ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND, + false, fixedText_Look); + screen.buttonPrint(Common::Point(Scalpel::INVENTORY_POINTS[2][2], CONTROLS_Y1), + _invMode == INVMODE_USE ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND, + false, fixedText_Use); + screen.buttonPrint(Common::Point(Scalpel::INVENTORY_POINTS[3][2], CONTROLS_Y1), + _invMode == INVMODE_GIVE ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND, + false, fixedText_Give); + screen.gPrint(Common::Point(Scalpel::INVENTORY_POINTS[4][2], CONTROLS_Y1), + _invIndex == 0 ? COMMAND_NULL : COMMAND_FOREGROUND, + "^^"); // 2 arrows pointing to the left + screen.gPrint(Common::Point(Scalpel::INVENTORY_POINTS[5][2], CONTROLS_Y1), + _invIndex == 0 ? COMMAND_NULL : COMMAND_FOREGROUND, + "^"); // 1 arrow pointing to the left + screen.gPrint(Common::Point(Scalpel::INVENTORY_POINTS[6][2], CONTROLS_Y1), + (_holdings - _invIndex < 7) ? COMMAND_NULL : COMMAND_FOREGROUND, + "_"); // 1 arrow pointing to the right + screen.gPrint(Common::Point(Scalpel::INVENTORY_POINTS[7][2], CONTROLS_Y1), + (_holdings - _invIndex < 7) ? COMMAND_NULL : COMMAND_FOREGROUND, + "__"); // 2 arrows pointing to the right + } +} + +void Inventory::highlight(int index, byte color) { + Screen &screen = *_vm->_screen; + Surface &bb = *screen._backBuffer; + int slot = index - _invIndex; + ImageFrame &frame = (*_invShapes[slot])[0]; + + bb.fillRect(Common::Rect(8 + slot * 52, 165, (slot + 1) * 52, 194), color); + bb.transBlitFrom(frame, Common::Point(6 + slot * 52 + ((47 - frame._width) / 2), + 163 + ((33 - frame._height) / 2))); + screen.slamArea(8 + slot * 52, 165, 44, 30); +} + +void Inventory::refreshInv() { + if (IS_ROSE_TATTOO) + return; + + Screen &screen = *_vm->_screen; + Talk &talk = *_vm->_talk; + Scalpel::ScalpelUserInterface &ui = *(Scalpel::ScalpelUserInterface *)_vm->_ui; + + ui._invLookFlag = true; + freeInv(); + + ui._infoFlag = true; + ui.clearInfo(); + + screen._backBuffer2.blitFrom(screen._backBuffer1, Common::Point(0, CONTROLS_Y), + Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); + ui.examine(); + + if (!talk._talkToAbort) { + screen._backBuffer2.blitFrom((*ui._controlPanel)[0], Common::Point(0, CONTROLS_Y)); + loadInv(); + } +} + +int Inventory::putNameInInventory(const Common::String &name) { + Scene &scene = *_vm->_scene; + int matches = 0; + + for (uint idx = 0; idx < scene._bgShapes.size(); ++idx) { + Object &o = scene._bgShapes[idx]; + if (name.equalsIgnoreCase(o._name) && o._type != INVALID) { + putItemInInventory(o); + ++matches; + } + } + + return matches; +} + +int Inventory::putItemInInventory(Object &obj) { + Scene &scene = *_vm->_scene; + int matches = 0; + bool pickupFound = false; + + if (obj._pickupFlag) + _vm->setFlags(obj._pickupFlag); + + for (int useNum = 0; useNum < USE_COUNT; ++useNum) { + if (obj._use[useNum]._target.equalsIgnoreCase("*PICKUP*")) { + pickupFound = true; + + for (int namesNum = 0; namesNum < NAMES_COUNT; ++namesNum) { + for (uint bgNum = 0; bgNum < scene._bgShapes.size(); ++bgNum) { + Object &bgObj = scene._bgShapes[bgNum]; + if (obj._use[useNum]._names[namesNum].equalsIgnoreCase(bgObj._name)) { + copyToInventory(bgObj); + if (bgObj._pickupFlag) + _vm->setFlags(bgObj._pickupFlag); + + if (bgObj._type == ACTIVE_BG_SHAPE || bgObj._type == NO_SHAPE || bgObj._type == HIDE_SHAPE) { + if (bgObj._imageFrame == nullptr || bgObj._frameNumber < 0) + // No shape to erase, so flag as hidden + bgObj._type = INVALID; + else + bgObj._type = REMOVE; + } else if (bgObj._type == HIDDEN) { + bgObj._type = INVALID; + } + + ++matches; + } + } + } + } + } + + if (!pickupFound) { + // No pickup item found, so add the passed item + copyToInventory(obj); + matches = 0; + } + + if (matches == 0) { + if (!pickupFound) + matches = 1; + + if (obj._type == ACTIVE_BG_SHAPE || obj._type == NO_SHAPE || obj._type == HIDE_SHAPE) { + if (obj._imageFrame == nullptr || obj._frameNumber < 0) + // No shape to erase, so flag as hidden + obj._type = INVALID; + else + obj._type = REMOVE; + } else if (obj._type == HIDDEN) { + obj._type = INVALID; + } + } + + return matches; +} + +void Inventory::copyToInventory(Object &obj) { + InventoryItem invItem; + invItem._name = obj._name; + invItem._description = obj._description; + invItem._examine = obj._examine; + invItem._lookFlag = obj._lookFlag; + invItem._requiredFlag = obj._requiredFlag; + + insert_at(_holdings, invItem); + ++_holdings; +} + +int Inventory::deleteItemFromInventory(const Common::String &name) { + int invNum = -1; + + for (int idx = 0; idx < (int)size() && invNum == -1; ++idx) { + if (name.equalsIgnoreCase((*this)[idx]._name)) + invNum = idx; + } + + if (invNum == -1) + // Item not present + return 0; + + // Item found, so delete it + remove_at(invNum); + --_holdings; + + return 1; +} + +void Inventory::synchronize(Serializer &s) { + s.syncAsSint16LE(_holdings); + + uint count = size(); + s.syncAsUint16LE(count); + if (s.isLoading()) { + resize(count); + + // Reset inventory back to start + _invIndex = 0; + } + + for (uint idx = 0; idx < size(); ++idx) { + (*this)[idx].synchronize(s); + + } +} + +} // End of namespace Sherlock diff --git a/engines/sherlock/inventory.h b/engines/sherlock/inventory.h new file mode 100644 index 0000000000..019f5ed6c9 --- /dev/null +++ b/engines/sherlock/inventory.h @@ -0,0 +1,175 @@ +/* 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 SHERLOCK_INVENTORY_H +#define SHERLOCK_INVENTORY_H + +#include "common/scummsys.h" +#include "common/array.h" +#include "common/str-array.h" +#include "sherlock/objects.h" +#include "sherlock/resources.h" +#include "sherlock/saveload.h" + +namespace Sherlock { + +#define MAX_VISIBLE_INVENTORY 6 + +enum InvMode { + INVMODE_EXIT = 0, + INVMODE_LOOK = 1, + INVMODE_USE = 2, + INVMODE_GIVE = 3, + INVMODE_FIRST = 4, + INVMODE_PREVIOUS = 5, + INVMODE_NEXT = 6, + INVMODE_LAST = 7, + INVMODE_INVALID = 8, + INVMODE_USE55 = 255 +}; + +enum InvNewMode { + PLAIN_INVENTORY = 0, LOOK_INVENTORY_MODE = 1, USE_INVENTORY_MODE = 2, + GIVE_INVENTORY_MODE = 3, INVENTORY_DONT_DISPLAY = 128 +}; + +enum InvSlamMode { SLAM_DONT_DISPLAY, SLAM_DISPLAY = 1, SLAM_SECONDARY_BUFFER }; + + +struct InventoryItem { + int _requiredFlag; + Common::String _name; + Common::String _description; + Common::String _examine; + int _lookFlag; + + // Rose Tattoo fields + int _requiredFlag1; + UseType _verb; + + InventoryItem() : _requiredFlag(0), _lookFlag(0), _requiredFlag1(0) {} + InventoryItem(int requiredFlag, const Common::String &name, + const Common::String &description, const Common::String &examine); + + /** + * Synchronize the data for an inventory item + */ + void synchronize(Serializer &s); +}; + +class Inventory : public Common::Array<InventoryItem> { +private: + SherlockEngine *_vm; + Common::StringArray _names; + + /** + * Copy the passed object into the inventory + */ + void copyToInventory(Object &obj); +public: + ImageFile *_invShapes[MAX_VISIBLE_INVENTORY]; + bool _invGraphicsLoaded; + InvMode _invMode; + int _invIndex; + int _holdings; // Used to hold number of visible items in active inventory. + // Since Inventory array also contains some special hidden items + /** + * Free any loaded inventory graphics + */ + void freeGraphics(); +public: + Inventory(SherlockEngine *vm); + ~Inventory(); + + /** + * Free inventory data + */ + void freeInv(); + + /** + * Load the list of names the inventory items correspond to, if not already loaded, + * and then calls loadGraphics to load the associated graphics + */ + void loadInv(); + + /** + * Load the list of names of graphics for the inventory + */ + void loadGraphics(); + + /** + * Searches through the list of names that correspond to the inventory items + * and returns the number that matches the passed name + */ + int findInv(const Common::String &name); + + /** + * Display the character's inventory. The slamIt parameter specifies: + */ + void putInv(InvSlamMode slamIt); + + /** + * Put the game into inventory mode and open the interface window. + */ + void drawInventory(InvNewMode flag); + + /** + * Prints the line of inventory commands at the top of an inventory window with + * the correct highlighting + */ + void invCommands(bool slamIt); + + /** + * Set the highlighting color of a given inventory item + */ + void highlight(int index, byte color); + + /** + * Support method for refreshing the display of the inventory + */ + void refreshInv(); + + /** + * Adds a shape from the scene to the player's inventory + */ + int putNameInInventory(const Common::String &name); + + /** + * Moves a specified item into the player's inventory If the item has a *PICKUP* use action, + * then the item in the use action are added to the inventory. + */ + int putItemInInventory(Object &obj); + + /** + * Deletes a specified item from the player's inventory + */ + int deleteItemFromInventory(const Common::String &name); + + /** + * Synchronize the data for a savegame + */ + void synchronize(Serializer &s); +}; + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/journal.cpp b/engines/sherlock/journal.cpp new file mode 100644 index 0000000000..acc99e462d --- /dev/null +++ b/engines/sherlock/journal.cpp @@ -0,0 +1,678 @@ +/* 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 "sherlock/journal.h" +#include "sherlock/sherlock.h" +#include "sherlock/scalpel/scalpel_journal.h" +#include "sherlock/tattoo/tattoo_journal.h" + +namespace Sherlock { + +Journal *Journal::init(SherlockEngine *vm) { + if (vm->getGameID() == GType_SerratedScalpel) + return new Scalpel::ScalpelJournal(vm); + else + return new Tattoo::TattooJournal(vm); +} + +Journal::Journal(SherlockEngine *vm) : _vm(vm) { +} + +bool Journal::drawJournal(int direction, int howFar) { + Events &events = *_vm->_events; + FixedText &fixedText = *_vm->_fixedText; + Screen &screen = *_vm->_screen; + Talk &talk = *_vm->_talk; + int yp = 37; + int startPage = _page; + bool endJournal = false; + bool firstOccurance = true; + bool searchSuccessful = false; + bool endFlag = false; + int lineNum = 0; + int savedIndex; + int temp; + const char *matchP; + int width; + + talk._converseNum = -1; + _down = true; + + do { + // Get the number of lines for the current journal entry + loadJournalFile(false); + if (_lines.empty()) { + // Entry has no text, so it must be a stealth eny. Move onto further journal entries + // until an entry with text is found + if (++_index == (int)_journal.size()) { + endJournal = true; + } else { + _sub = 0; + loadJournalFile(false); + } + } + } while (!endJournal && _lines.empty()); + + // Check if there no further pages with text until the end of the journal + if (endJournal) { + // If moving forward or backwards, clear the page before printing + if (direction) + drawFrame(); + + screen.gPrint(Common::Point(235, 21), PEN_COLOR, "Page %d", _page); + return false; + } + + // If the journal page is being changed, set the wait cursor + if (direction) + events.setCursor(WAIT); + + switch (direction) { + case 1: + case 4: + // Move backwards howFar number of lines unless either the start of the journal is reached, + // or a searched for keyword is found + do { + // Move backwards through the journal file a line at a time + if (--_sub < 0) { + do { + if (--_index < 0) { + _index = 0; + _sub = 0; + endJournal = true; + } + else { + loadJournalFile(false); + _sub = _lines.size() - 1; + } + } while (!endJournal && _lines.empty()); + } + + // If it's search mode, check each line for the given keyword + if (direction >= 3 && !_lines.empty() && !endJournal && !searchSuccessful) { + Common::String line = _lines[_sub]; + line.toUppercase(); + if (strstr(line.c_str(), _find.c_str()) != nullptr) { + // Found a match. Reset howFar so that the start of page that the match + // was found on will be displayed + searchSuccessful = true; + howFar = ((lineNum / LINES_PER_PAGE) + 1) * LINES_PER_PAGE; + } + } + + ++lineNum; + } while (lineNum < howFar && !endJournal); + + if (!_index && !_sub) + _page = 1; + else + _page -= howFar / LINES_PER_PAGE; + break; + + case 2: + case 3: + // Move howFar lines ahead unless the end of the journal is reached, + // or a searched for keyword is found + for (temp = 0; (temp < (howFar / LINES_PER_PAGE)) && !endJournal && !searchSuccessful; ++temp) { + // Handle animating mouse cursor + int cursorNum = (int)events.getCursor() + 1; + if (cursorNum >(WAIT + 2)) + cursorNum = WAIT; + events.setCursor((CursorId)cursorNum); + + lineNum = 0; + savedIndex = _index; + int savedSub = _sub; + + // Move a single page ahead + do { + // If in search mode, check for keyword + if (direction >= 3 && _page != startPage) { + Common::String line = _lines[_sub]; + line.toUppercase(); + if (strstr(line.c_str(), _find.c_str()) != nullptr) + searchSuccessful = true; + } + + // Move forwards a line at a time, unless search word was found + if (!searchSuccessful) { + if (++_sub == (int)_lines.size()) { + // Reached end of page + do { + if (++_index == (int)_journal.size()) { + _index = savedIndex; + _sub = savedSub; + loadJournalFile(false); + endJournal = true; + } else { + _sub = 0; + loadJournalFile(false); + } + } while (!endJournal && _lines.empty()); + } + + ++lineNum; + } + } while ((lineNum < LINES_PER_PAGE) && !endJournal && !searchSuccessful); + + if (!endJournal && !searchSuccessful) + // Move to next page + ++_page; + + if (searchSuccessful) { + // Search found, so show top of the page it was found on + _index = savedIndex; + _sub = savedSub; + loadJournalFile(false); + } + } + break; + + default: + break; + } + + if (direction) { + events.setCursor(ARROW); + drawFrame(); + } + + Common::String fixedText_Page = fixedText.getText(kFixedText_Journal_Page); + + screen.gPrint(Common::Point(235, 21), PEN_COLOR, fixedText_Page.c_str(), _page); + + temp = _sub; + savedIndex = _index; + lineNum = 0; + + do { + bool inc = true; + + // If there wasn't any line to print at the top of the page, we won't need to + // increment the y position + if (_lines[temp].empty() && yp == 37) + inc = false; + + // If there's a searched for keyword in the line, it will need to be highlighted + if (searchSuccessful && firstOccurance) { + // Check if line has the keyword + Common::String line = _lines[temp]; + line.toUppercase(); + if ((matchP = strstr(line.c_str(), _find.c_str())) != nullptr) { + matchP = _lines[temp].c_str() + (matchP - line.c_str()); + firstOccurance = false; + + // Print out the start of the line before the matching keyword + Common::String lineStart(_lines[temp].c_str(), matchP); + if (lineStart.hasPrefix("@")) { + width = screen.stringWidth(lineStart.c_str() + 1); + screen.gPrint(Common::Point(53, yp), 15, "%s", lineStart.c_str() + 1); + } else { + width = screen.stringWidth(lineStart.c_str()); + screen.gPrint(Common::Point(53, yp), PEN_COLOR, "%s", lineStart.c_str()); + } + + // Print out the found keyword + Common::String lineMatch(matchP, matchP + _find.size()); + screen.gPrint(Common::Point(53 + width, yp), INV_FOREGROUND, "%s", lineMatch.c_str()); + width += screen.stringWidth(lineMatch.c_str()); + + // Print remainder of line + screen.gPrint(Common::Point(53 + width, yp), PEN_COLOR, "%s", matchP + _find.size()); + } else if (_lines[temp].hasPrefix("@")) { + screen.gPrint(Common::Point(53, yp), 15, "%s", _lines[temp].c_str() + 1); + } else { + screen.gPrint(Common::Point(53, yp), PEN_COLOR, "%s", _lines[temp].c_str()); + } + } else { + if (_lines[temp].hasPrefix("@")) { + screen.gPrint(Common::Point(53, yp), 15, "%s", _lines[temp].c_str() + 1); + } else { + screen.gPrint(Common::Point(53, yp), PEN_COLOR, "%s", _lines[temp].c_str()); + } + } + + if (++temp == (int)_lines.size()) { + // Move to next page + do { + if (_index < ((int)_journal.size() - 1) && lineNum < (LINES_PER_PAGE - 1)) { + ++_index; + loadJournalFile(false); + temp = 0; + } else { + if (_index == ((int)_journal.size() - 1)) + _down = false; + endFlag = true; + } + } while (!endFlag && _lines.empty()); + } + + if (inc) { + // Move to next line + ++lineNum; + yp += 13; + } + } while (lineNum < LINES_PER_PAGE && !endFlag); + + _index = savedIndex; + _up = _index || _sub; + + return direction >= 3 && searchSuccessful; +} + +void Journal::loadJournalFile(bool alreadyLoaded) { + People &people = *_vm->_people; + Screen &screen = *_vm->_screen; + Talk &talk = *_vm->_talk; + JournalEntry &journalEntry = _journal[_index]; + const byte *opcodes = talk._opcodes; + + Common::String dirFilename = _directory[journalEntry._converseNum]; + bool replyOnly = journalEntry._replyOnly; + + // Get the location number from within the filename + Common::String locStr(dirFilename.c_str() + 4, dirFilename.c_str() + 6); + int newLocation = atoi(locStr.c_str()); + + // If not flagged as already loaded, load the conversation into script variables + if (!alreadyLoaded) { + // See if the file to be used is already loaded + if (journalEntry._converseNum != talk._converseNum) { + // Nope. Free any previously loaded talk + talk.freeTalkVars(); + + // Find the person being referred to + talk._talkTo = -1; + for (int idx = 0; idx < (int)people._characters.size(); ++idx) { + Common::String portrait = people._characters[idx]._portrait; + Common::String numStr(portrait.c_str(), portrait.c_str() + 4); + + if (locStr == numStr) { + talk._talkTo = idx; + break; + } + } + + // Load their talk file + talk.loadTalkFile(dirFilename); + } + } + + if (talk[0]._statement.hasPrefix("*") || talk[0]._statement.hasPrefix("^")) + replyOnly = true; + + // If this isn't the first journal entry, see if the previous journal entry + // was in the same scene to see if we need to include the scene header + int oldLocation = -1; + if (_index != 0) { + // Get the scene number of the prior journal entry + Common::String priorEntry = _directory[_journal[_index - 1]._converseNum]; + oldLocation = atoi(Common::String(priorEntry.c_str() + 4, priorEntry.c_str() + 6).c_str()); + } + + // Start building journal string + Statement &statement = talk[journalEntry._statementNum]; + Common::String journalString; + + if (newLocation != oldLocation) { + // Add in scene title + journalString = "@"; + if (IS_SERRATED_SCALPEL || newLocation - 1 < 100) + journalString += _locations[newLocation - 1]; + journalString += ":"; + + // See if title can fit into a single line, or requires splitting on 2 lines + int width = screen.stringWidth(journalString.c_str() + 1); + if (width > JOURNAL_MAX_WIDTH) { + // Scan backwards from end of title to find a space between a word + // where the width is less than the maximum allowed for the line + const char *lineP = journalString.c_str() + journalString.size() - 1; + while (width > JOURNAL_MAX_WIDTH || *lineP != ' ') + width -= screen.charWidth(*lineP--); + + // Split the header into two lines, and add a '@' prefix + // to the second line as well + journalString = Common::String(journalString.c_str(), lineP) + "\n@" + + Common::String(lineP + 1); + } + + // Add a newline at the end of the title + journalString += '\n'; + } + + // If Holmes has something to say first, then take care of it + if (!replyOnly) { + // Handle the grammar + journalString += "Holmes "; + if (talk[journalEntry._statementNum]._statement.hasSuffix("?")) + journalString += "asked "; + else + journalString += "said to "; + + if (talk._talkTo == 1) { + journalString += "me"; + } else if ((talk._talkTo == 2 && IS_SERRATED_SCALPEL) || (talk._talkTo == 18 && IS_ROSE_TATTOO)) { + journalString += "the Inspector"; + } else { + journalString += people._characters[talk._talkTo]._name; + } + journalString += ", \""; + + // Add the statement + journalString += statement._statement; + } + + // Handle including the reply + bool startOfReply = true; + bool ctrlSpace = false; + bool commentFlag = false; + bool commentJustPrinted = false; + const byte *replyP = (const byte *)statement._reply.c_str(); + const int inspectorId = (IS_SERRATED_SCALPEL) ? 2 : 18; + + while (*replyP) { + byte c = *replyP++; + + if (IS_ROSE_TATTOO) { + // Ignore commented out data + if (c == '/' && *(replyP + 1) == '*') { + replyP++; // skip * + while (*replyP++ != '*') {} // empty loop on purpose + replyP++; // skip / + c = *replyP; + } + } + + // Is it a control character? + if (c < opcodes[0]) { + // Nope. Set flag for allowing control codes to insert spaces + ctrlSpace = true; + assert(c >= ' '); + + // Check for embedded comments + if (c == '{' || c == '}') { + + // TODO: Rose Tattoo checks if no text was added for the last + // comment here. In such a case, the last "XXX said" string is + // removed here. + + // Comment characters. If we're starting a comment and there's + // already text displayed, add a closing quote + if (c == '{' && !startOfReply && !commentJustPrinted) + journalString += '"'; + + // If a reply isn't just being started, and we didn't just end + // a comment (which would have added a line), add a carriage return + if (!startOfReply && ((!commentJustPrinted && c == '{') || c == '}')) + journalString += '\n'; + startOfReply = false; + + // Handle setting or clearing comment state + if (c == '{') { + commentFlag = true; + commentJustPrinted = false; + } else { + commentFlag = false; + commentJustPrinted = true; + } + } else { + if (startOfReply) { + if (!replyOnly) { + journalString += "\"\n"; + + if (talk._talkTo == 1) + journalString += "I replied, \""; + else + journalString += "The reply was, \""; + } else { + if (talk._talkTo == 1) + journalString += "I"; + else if (talk._talkTo == inspectorId) + journalString += "The Inspector"; + else + journalString += people._characters[talk._talkTo]._name; + + const byte *strP = replyP + 1; + byte v; + do { + v = *strP++; + } while (v && (v < opcodes[0]) && (v != '.') && (v != '!') && (v != '?')); + + if (v == '?') + journalString += " asked, \""; + else + journalString += " said, \""; + } + + startOfReply = false; + } + + // Copy text from the place until either the reply ends, a comment + // {} block is started, or a control character is encountered + journalString += c; + do { + journalString += *replyP++; + } while (*replyP && *replyP < opcodes[0] && *replyP != '{' && *replyP != '}'); + + commentJustPrinted = false; + } + } else if (c == opcodes[OP_SWITCH_SPEAKER]) { + if (!startOfReply) { + if (!commentFlag && !commentJustPrinted) + journalString += "\"\n"; + + journalString += "Then "; + commentFlag = false; + } else if (!replyOnly) { + journalString += "\"\n"; + } + + startOfReply = false; + c = *replyP++ - 1; + if (IS_ROSE_TATTOO) + replyP++; + + if (c == 0) + journalString += "Holmes"; + else if (c == 1) + journalString += "I"; + else if (c == inspectorId) + journalString += "the Inspector"; + else + journalString += people._characters[c]._name; + + const byte *strP = replyP; + byte v; + do { + v = *strP++; + } while (v && v < opcodes[0] && v != '.' && v != '!' && v != '?'); + + if (v == '?') + journalString += " asked, \""; + else + journalString += " said, \""; + } else { + if (IS_SERRATED_SCALPEL) { + // Control code, so move past it and any parameters + if (c == opcodes[OP_RUN_CANIMATION] || + c == opcodes[OP_ASSIGN_PORTRAIT_LOCATION] || + c == opcodes[OP_PAUSE] || + c == opcodes[OP_PAUSE_WITHOUT_CONTROL] || + c == opcodes[OP_WALK_TO_CANIMATION]) { + // These commands have a single parameter + ++replyP; + } else if (c == opcodes[OP_ADJUST_OBJ_SEQUENCE]) { + replyP += (replyP[0] & 127) + replyP[1] + 2; + } else if (c == opcodes[OP_WALK_TO_COORDS] || c == opcodes[OP_MOVE_MOUSE]) { + replyP += 4; + } else if (c == opcodes[OP_SET_FLAG] || c == opcodes[OP_IF_STATEMENT]) { + replyP += 2; + } else if (c == opcodes[OP_SFX_COMMAND] || c == opcodes[OP_PLAY_PROLOGUE] || + c == opcodes[OP_CALL_TALK_FILE]) { + replyP += 8; + break; + } else if ( + c == opcodes[OP_TOGGLE_OBJECT] || + c == opcodes[OP_ADD_ITEM_TO_INVENTORY] || + c == opcodes[OP_SET_OBJECT] || + c == opcodes[OP_DISPLAY_INFO_LINE] || + c == opcodes[OP_REMOVE_ITEM_FROM_INVENTORY]) { + replyP += (*replyP & 127) + 1; + } else if (c == opcodes[OP_GOTO_SCENE]) { + replyP += 5; + } else if (c == opcodes[OP_CARRIAGE_RETURN]) { + journalString += "\n"; + } + } else { + if (c == opcodes[OP_RUN_CANIMATION] || + c == opcodes[OP_PAUSE] || + c == opcodes[OP_MOUSE_OFF_ON] || + c == opcodes[OP_SET_WALK_CONTROL] || + c == opcodes[OP_PAUSE_WITHOUT_CONTROL] || + c == opcodes[OP_WALK_TO_CANIMATION] || + c == opcodes[OP_TURN_NPC_OFF] || + c == opcodes[OP_TURN_NPC_ON] || + c == opcodes[OP_RESTORE_PEOPLE_SEQUENCE]) + ++replyP; + else if ( + c == opcodes[OP_SET_TALK_SEQUENCE] || + c == opcodes[OP_SET_FLAG] || + c == opcodes[OP_WALK_NPC_TO_CANIM] || + c == opcodes[OP_WALK_HOLMES_AND_NPC_TO_CANIM] || + c == opcodes[OP_NPC_PATH_LABEL] || + c == opcodes[OP_PATH_GOTO_LABEL]) + replyP += 2; + else if ( + c == opcodes[OP_SET_NPC_PATH_PAUSE] || + c == opcodes[OP_NPC_PATH_PAUSE_TAKING_NOTES] || + c == opcodes[OP_NPC_PATH_PAUSE_LOOKING_HOLMES] || + c == opcodes[OP_NPC_VERB_CANIM]) + replyP += 3; + else if ( + c == opcodes[OP_SET_SCENE_ENTRY_FLAG] || + c == opcodes[OP_PATH_IF_FLAG_GOTO_LABEL]) + replyP += 4; + else if ( + c == opcodes[OP_WALK_TO_COORDS]) + replyP += 5; + else if ( + c == opcodes[OP_WALK_NPC_TO_COORDS] || + c == opcodes[OP_GOTO_SCENE] || + c == opcodes[OP_SET_NPC_PATH_DEST] || + c == opcodes[OP_SET_NPC_POSITION]) + replyP += 6; + else if ( + c == opcodes[OP_PLAY_SONG] || + c == opcodes[OP_NEXT_SONG]) + replyP += 8; + else if ( + c == opcodes[OP_CALL_TALK_FILE] || + c == opcodes[OP_SET_NPC_TALK_FILE] || + c == opcodes[OP_NPC_WALK_GRAPHICS]) + replyP += 9; + else if ( + c == opcodes[OP_NPC_VERB_SCRIPT]) + replyP += 10; + else if ( + c == opcodes[OP_WALK_HOLMES_AND_NPC_TO_COORDS]) + replyP += 11; + else if ( + c == opcodes[OP_NPC_VERB] || + c == opcodes[OP_NPC_VERB_TARGET]) + replyP += 14; + else if ( + c == opcodes[OP_ADJUST_OBJ_SEQUENCE]) + replyP += (replyP[0] & 127) + replyP[1] + 2; + else if ( + c == opcodes[OP_TOGGLE_OBJECT] || + c == opcodes[OP_ADD_ITEM_TO_INVENTORY] || + c == opcodes[OP_SET_OBJECT] || + c == opcodes[OP_REMOVE_ITEM_FROM_INVENTORY]) + replyP += (*replyP & 127) + 1; + else if ( + c == opcodes[OP_END_TEXT_WINDOW]) { + journalString += '\n'; + } else if ( + c == opcodes[OP_NPC_DESC_ON_OFF]) { + replyP++; + while (replyP[0] && replyP[0] != opcodes[OP_NPC_DESC_ON_OFF]) + replyP++; + replyP++; + } else if ( + c == opcodes[OP_SET_NPC_INFO_LINE]) + replyP += replyP[1] + 2; + } + + // Put a space in the output for a control character, unless it's + // immediately coming after another control character + if (ctrlSpace && c != opcodes[OP_ASSIGN_PORTRAIT_LOCATION] && c != opcodes[OP_CARRIAGE_RETURN] && + !commentJustPrinted) { + journalString += " "; + ctrlSpace = false; + } + } + } + + if (!startOfReply && !commentJustPrinted) + journalString += '"'; + + // Finally finished building the journal text. Need to process the text to + // word wrap it to fit on-screen. The resulting lines are stored in the + // _lines array + _lines.clear(); + + while (!journalString.empty()) { + const char *startP = journalString.c_str(); + + // If the first character is a '@' flagging a title line, then move + // past it, so the @ won't be included in the line width calculation + if (*startP == '@') + ++startP; + + // Build up chacters until a full line is found + int width = 0; + const char *endP = startP; + while (width < JOURNAL_MAX_WIDTH && *endP && *endP != '\n' && (endP - startP) < (JOURNAL_MAX_CHARS - 1)) + width += screen.charWidth(*endP++); + + // If word wrapping, move back to end of prior word + if (width >= JOURNAL_MAX_WIDTH || (endP - startP) >= (JOURNAL_MAX_CHARS - 1)) { + while (*--endP != ' ') + ; + } + + // Add in the line + _lines.push_back(Common::String(journalString.c_str(), endP)); + + // Strip line off from string being processed + journalString = *endP ? Common::String(endP + 1) : ""; + } + + // Add a blank line at the end of the text as long as text was present + if (!startOfReply) { + _lines.push_back(""); + } else { + _lines.clear(); + } +} + +} // End of namespace Sherlock diff --git a/engines/sherlock/journal.h b/engines/sherlock/journal.h new file mode 100644 index 0000000000..93fdf25098 --- /dev/null +++ b/engines/sherlock/journal.h @@ -0,0 +1,105 @@ +/* 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 SHERLOCK_JOURNAL_H +#define SHERLOCK_JOURNAL_H + +#include "common/scummsys.h" +#include "common/array.h" +#include "common/rect.h" +#include "common/str-array.h" +#include "common/stream.h" +#include "sherlock/saveload.h" + +namespace Sherlock { + +#define LINES_PER_PAGE (IS_SERRATED_SCALPEL ? 11 : 17) + +class SherlockEngine; + +struct JournalEntry { + int _converseNum; + bool _replyOnly; + int _statementNum; + + JournalEntry() : _converseNum(0), _replyOnly(false), _statementNum(0) {} + JournalEntry(int converseNum, int statementNum, bool replyOnly = false) : + _converseNum(converseNum), _statementNum(statementNum), _replyOnly(replyOnly) {} +}; + +class Journal { +protected: + SherlockEngine *_vm; + Common::StringArray _directory; + Common::StringArray _locations; + Common::Array<JournalEntry> _journal; + Common::StringArray _lines; + bool _up, _down; + int _index; + int _page; + int _maxPage; + int _sub; + Common::String _find; + + Journal(SherlockEngine *vm); + + /** + * Loads the description for the current display index in the journal, and then + * word wraps the result to prepare it for being displayed + * @param alreadyLoaded Indicates whether the journal file is being loaded for the + * first time, or being reloaded + */ + void loadJournalFile(bool alreadyLoaded); +public: + static Journal *init(SherlockEngine *vm); + virtual ~Journal() {} + + /** + * Displays a page of the journal at the current index + */ + bool drawJournal(int direction, int howFar); +public: + /** + * Draw the journal background, frame, and interface buttons + */ + virtual void drawFrame() = 0; + + /** + * Records statements that are said, in the order which they are said. The player + * can then read the journal to review them + */ + virtual void record(int converseNum, int statementNum, bool replyOnly = false) {} + + /** + * Reset viewing position to the start of the journal + */ + virtual void resetPosition() {} + + /** + * Synchronize the data for a savegame + */ + virtual void synchronize(Serializer &s) = 0; +}; + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/map.cpp b/engines/sherlock/map.cpp new file mode 100644 index 0000000000..fbccaf244f --- /dev/null +++ b/engines/sherlock/map.cpp @@ -0,0 +1,52 @@ +/* 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 "common/system.h" +#include "sherlock/map.h" +#include "sherlock/sherlock.h" +#include "sherlock/scalpel/scalpel_map.h" +#include "sherlock/tattoo/tattoo_map.h" + +namespace Sherlock { + +Map *Map::init(SherlockEngine *vm) { + if (vm->getGameID() == GType_SerratedScalpel) + return new Scalpel::ScalpelMap(vm); + else + return new Tattoo::TattooMap(vm); +} + +Map::Map(SherlockEngine *vm) : _vm(vm) { + _charPoint = _oldCharPoint = 0; + _active = _frameChangeFlag = false; +} + +void Map::synchronize(Serializer &s) { + s.syncAsSint32LE(_bigPos.x); + s.syncAsSint32LE(_bigPos.y); + s.syncAsSint32LE(_overPos.x); + s.syncAsSint16LE(_overPos.y); + s.syncAsSint16LE(_oldCharPoint); +} + + +} // End of namespace Sherlock diff --git a/engines/sherlock/map.h b/engines/sherlock/map.h new file mode 100644 index 0000000000..104f5e9c8a --- /dev/null +++ b/engines/sherlock/map.h @@ -0,0 +1,61 @@ +/* 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 SHERLOCK_MAP_H +#define SHERLOCK_MAP_H + +#include "sherlock/objects.h" +#include "sherlock/saveload.h" + +namespace Sherlock { + +class SherlockEngine; + +class Map { +protected: + SherlockEngine *_vm; + + Map(SherlockEngine *vm); +public: + Point32 _overPos; + Point32 _bigPos; + int _charPoint, _oldCharPoint; + bool _active; + bool _frameChangeFlag; +public: + static Map *init(SherlockEngine *vm); + virtual ~Map() {} + + /** + * Show the map + */ + virtual int show() = 0; + + /** + * Synchronize the data for a savegame + */ + void synchronize(Serializer &s); +}; + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/module.mk b/engines/sherlock/module.mk new file mode 100644 index 0000000000..8bbee9d701 --- /dev/null +++ b/engines/sherlock/module.mk @@ -0,0 +1,61 @@ +MODULE := engines/sherlock + +MODULE_OBJS = \ + scalpel/darts.o \ + scalpel/scalpel.o \ + scalpel/3do/movie_decoder.o \ + scalpel/drivers/adlib.o \ + scalpel/drivers/mt32.o \ + scalpel/tsage/logo.o \ + scalpel/tsage/resources.o \ + scalpel/scalpel_journal.o \ + scalpel/scalpel_map.o \ + scalpel/scalpel_people.o \ + scalpel/scalpel_scene.o \ + scalpel/scalpel_talk.o \ + scalpel/scalpel_user_interface.o \ + scalpel/settings.o \ + tattoo/tattoo.o \ + tattoo/tattoo_journal.o \ + tattoo/tattoo_map.o \ + tattoo/tattoo_people.o \ + tattoo/tattoo_resources.o \ + tattoo/tattoo_scene.o \ + tattoo/tattoo_talk.o \ + tattoo/tattoo_user_interface.o \ + tattoo/widget_base.o \ + tattoo/widget_inventory.o \ + tattoo/widget_talk.o \ + tattoo/widget_text.o \ + tattoo/widget_tooltip.o \ + tattoo/widget_verbs.o \ + animation.o \ + debugger.o \ + detection.o \ + events.o \ + fixed_text.o \ + fonts.o \ + image_file.o \ + inventory.o \ + journal.o \ + map.o \ + music.o \ + objects.o \ + people.o \ + resources.o \ + saveload.o \ + scene.o \ + screen.o \ + sherlock.o \ + sound.o \ + surface.o \ + talk.o \ + user_interface.o + +# This module can be built as a plugin +ifeq ($(ENABLE_SHERLOCK), DYNAMIC_PLUGIN) +PLUGIN := 1 +endif + +# Include common rules +include $(srcdir)/rules.mk diff --git a/engines/sherlock/music.cpp b/engines/sherlock/music.cpp new file mode 100644 index 0000000000..63e4a840ba --- /dev/null +++ b/engines/sherlock/music.cpp @@ -0,0 +1,580 @@ +/* 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 "common/config-manager.h" +#include "common/mutex.h" +#include "sherlock/sherlock.h" +#include "sherlock/music.h" +#include "sherlock/scalpel/drivers/mididriver.h" +// for 3DO digital music +#include "audio/decoders/aiff.h" + +namespace Sherlock { + +#define NUM_SONGS 45 + +/* This tells which song to play in each room, 0 = no song played */ +static const char ROOM_SONG[62] = { + 0, 20, 43, 6, 11, 2, 8, 15, 6, 28, + 6, 38, 7, 32, 16, 5, 8, 41, 9, 22, + 10, 23, 4, 39, 19, 24, 13, 27, 0, 30, + 3, 21, 26, 25, 16, 29, 1, 1, 18, 12, + 1, 17, 17, 31, 17, 34, 36, 7, 20, 20, + 33, 8, 44, 40, 42, 35, 0, 0, 0, 12, + 12 +}; + +static const char *const SONG_NAMES[NUM_SONGS] = { + "SINGERF", "CHEMIST", "TOBAC", "EQUEST", "MORTUARY", "DOCKS", "LSTUDY", + "LORD", "BOY", "PERFUM1", "BAKER1", "BAKER2", "OPERA1", "HOLMES", + "FFLAT", "OP1FLAT", "ZOO", "SROOM", "FLOWERS", "YARD", "TAXID", + "PUB1", "VICTIM", "RUGBY", "DORM", "SHERMAN", "LAWYER", "THEATRE", + "DETECT", "OPERA4", "POOL", "SOOTH", "ANNA1", "ANNA2", "PROLOG3", + "PAWNSHOP", "MUSICBOX", "MOZART1", "ROBHUNT", "PANCRAS1", "PANCRAS2", "LORDKILL", + "BLACKWEL", "RESCUE", "MAP" +}; + +MidiParser_SH::MidiParser_SH() { + _ppqn = 1; + setTempo(16667); + _data = nullptr; + _beats = 0; + _lastEvent = 0; + _trackEnd = nullptr; + + _musData = nullptr; + _musDataSize = 0; +} + +MidiParser_SH::~MidiParser_SH() { + Common::StackLock lock(_mutex); + unloadMusic(); + _driver = NULL; +} + +void MidiParser_SH::parseNextEvent(EventInfo &info) { + Common::StackLock lock(_mutex); + +// warning("parseNextEvent"); + + // there is no delta right at the start of the music data + // this order is essential, otherwise notes will get delayed or even go missing + if (_position._playPos != _tracks[0]) { + info.delta = *(_position._playPos++); + } else { + info.delta = 0; + } + + info.start = _position._playPos; + + info.event = *_position._playPos++; + //warning("Event %x", info.event); + _position._runningStatus = info.event; + + switch (info.command()) { + case 0xC: { // program change + int idx = *_position._playPos++; + info.basic.param1 = idx & 0x7f; + info.basic.param2 = 0; + } + break; + case 0xD: + info.basic.param1 = *_position._playPos++; + info.basic.param2 = 0; + break; + + case 0xB: + info.basic.param1 = *_position._playPos++; + info.basic.param2 = *_position._playPos++; + info.length = 0; + break; + + case 0x8: + case 0x9: + case 0xA: + case 0xE: + info.basic.param1 = *(_position._playPos++); + info.basic.param2 = *(_position._playPos++); + if (info.command() == 0x9 && info.basic.param2 == 0) { + // NoteOn with param2==0 is a NoteOff + info.event = info.channel() | 0x80; + } + info.length = 0; + break; + case 0xF: + if (info.event == 0xFF) { + error("SysEx META event 0xFF"); + + byte type = *(_position._playPos++); + switch(type) { + case 0x2F: + // End of Track + allNotesOff(); + stopPlaying(); + unloadMusic(); + return; + case 0x51: + warning("TODO: 0xFF / 0x51"); + return; + default: + warning("TODO: 0xFF / %x Unknown", type); + break; + } + } else if (info.event == 0xFC) { + // Official End-Of-Track signal + debugC(kDebugLevelMusic, "Music: System META event 0xFC"); + + byte type = *(_position._playPos++); + switch (type) { + case 0x80: // end of track, triggers looping + debugC(kDebugLevelMusic, "Music: META event triggered looping"); + jumpToTick(0, true, true, false); + break; + case 0x81: // end of track, stop playing + debugC(kDebugLevelMusic, "Music: META event triggered music stop"); + stopPlaying(); + unloadMusic(); + break; + default: + error("MidiParser_SH::parseNextEvent: Unknown META event 0xFC type %x", type); + break; + } + } else { + warning("TODO: %x / Unknown", info.event); + break; + } + break; + default: + warning("MidiParser_SH::parseNextEvent: Unsupported event code %x", info.event); + break; + }// switch (info.command()) +} + +bool MidiParser_SH::loadMusic(byte *musData, uint32 musDataSize) { + Common::StackLock lock(_mutex); + + debugC(kDebugLevelMusic, "Music: loadMusic()"); + unloadMusic(); + + _musData = musData; + _musDataSize = musDataSize; + + byte *headerPtr = _musData + 12; // skip over the already checked SPACE header + byte *pos = headerPtr; + + uint16 headerSize = READ_LE_UINT16(headerPtr); + assert(headerSize == 0x7F); // Security check + + // Skip over header + pos += headerSize; + + _lastEvent = 0; + _trackEnd = _musData + _musDataSize; + + _numTracks = 1; + _tracks[0] = pos; + + _ppqn = 1; + setTempo(16667); + setTrack(0); + + return true; +} + +void MidiParser_SH::unloadMusic() { + Common::StackLock lock(_mutex); + + if (_musData) { + delete[] _musData; + _musData = NULL; + _musDataSize = 0; + } +} + +/*----------------------------------------------------------------*/ + +Music::Music(SherlockEngine *vm, Audio::Mixer *mixer) : _vm(vm), _mixer(mixer) { + _midiDriver = NULL; + _midiParser = NULL; + _musicType = MT_NULL; + _musicPlaying = false; + _musicOn = false; + _midiOption = false; + _musicVolume = 0; + + if (IS_3DO) { + // 3DO - uses digital samples for music + _musicOn = true; + return; + } + + if (_vm->_interactiveFl) + _vm->_res->addToCache("MUSIC.LIB"); + + MidiDriver::DeviceHandle dev; + + if (IS_SERRATED_SCALPEL) { + // Serrated Scalpel: used an internal Electronic Arts .MUS music engine + _midiParser = new MidiParser_SH(); + dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MT32); + _musicType = MidiDriver::getMusicType(dev); + + switch (_musicType) { + case MT_ADLIB: + _midiDriver = MidiDriver_AdLib_create(); + break; + case MT_MT32: + _midiDriver = MidiDriver_MT32_create(); + break; + case MT_GM: + if (ConfMan.getBool("native_mt32")) { + _midiDriver = MidiDriver_MT32_create(); + _musicType = MT_MT32; + } + break; + default: + // Create default one + // I guess we shouldn't do this anymore + //_midiDriver = MidiDriver::createMidi(dev); + break; + } + } else { + // Rose Tattooo: seems to use Miles Audio 3 + + // TODO: AdLib support uses ScummVM's builtin GM to AdLib + // conversion. This won't match the original. I'm also + // uncertain about the MT-32 case, but it plays at least. + + _midiParser = MidiParser::createParser_XMIDI(); + dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_GM); + _musicType = MT_GM; + _midiDriver = MidiDriver::createMidi(dev); + } + + if (_midiDriver) { + int ret = _midiDriver->open(); + if (ret == 0) { + // Reset is done inside our MIDI driver + _midiDriver->setTimerCallback(_midiParser, &_midiParser->timerCallback); + } + _midiParser->setMidiDriver(_midiDriver); + _midiParser->setTimerRate(_midiDriver->getBaseTempo()); + + if (_musicType == MT_MT32) { + // Upload patches + Common::SeekableReadStream *MT32driverStream = _vm->_res->load("MTHOM.DRV", "MUSIC.LIB"); + + byte *MT32driverData = new byte[MT32driverStream->size()]; + int32 MT32driverDataSize = MT32driverStream->size(); + assert(MT32driverData); + + MT32driverStream->read(MT32driverData, MT32driverDataSize); + delete MT32driverStream; + + assert(MT32driverDataSize > 12); + byte *MT32driverDataPtr = MT32driverData + 12; + MT32driverDataSize -= 12; + + MidiDriver_MT32_uploadPatches(_midiDriver, MT32driverDataPtr, MT32driverDataSize); + delete[] MT32driverData; + } + + _musicOn = true; + } +} + +Music::~Music() { + stopMusic(); + if (_midiDriver) { + _midiDriver->setTimerCallback(this, NULL); + } + if (_midiParser) { + _midiParser->stopPlaying(); + delete _midiParser; + _midiParser = nullptr; + } + if (_midiDriver) { + _midiDriver->close(); + delete _midiDriver; + } +} + +bool Music::loadSong(int songNumber) { + debugC(kDebugLevelMusic, "Music: loadSong()"); + + if(songNumber == 100) + songNumber = 55; + else if(songNumber == 70) + songNumber = 54; + + if((songNumber > 60) || (songNumber < 1)) + return false; + + songNumber = ROOM_SONG[songNumber]; + + if(songNumber == 0) + songNumber = 12; + + if((songNumber > NUM_SONGS) || (songNumber < 1)) + return false; + + Common::String songName = Common::String(SONG_NAMES[songNumber - 1]); + + freeSong(); // free any song that is currently loaded + stopMusic(); + + if (!playMusic(songName)) + return false; + + startSong(); + return true; +} + +bool Music::loadSong(const Common::String &songName) { + freeSong(); // free any song that is currently loaded + stopMusic(); + + if (!playMusic(songName)) + return false; + + startSong(); + return true; +} + +void Music::syncMusicSettings() { + _musicOn = !ConfMan.getBool("mute") && !ConfMan.getBool("music_mute"); +} + +bool Music::playMusic(const Common::String &name) { + if (!_musicOn) + return false; + + debugC(kDebugLevelMusic, "Music: playMusic('%s')", name.c_str()); + + if (!IS_3DO) { + // MIDI based + if (!_midiDriver) + return false; + + Common::String midiMusicName = (IS_SERRATED_SCALPEL) ? name + ".MUS" : name + ".XMI"; + Common::SeekableReadStream *stream = _vm->_res->load(midiMusicName, "MUSIC.LIB"); + + byte *midiMusicData = new byte[stream->size()]; + int32 midiMusicDataSize = stream->size(); + + stream->read(midiMusicData, midiMusicDataSize); + delete stream; + + // for dumping the music tracks +#if 0 + Common::DumpFile outFile; + outFile.open(name + ".RAW"); + outFile.write(data, stream->size()); + outFile.flush(); + outFile.close(); +#endif + + if (midiMusicDataSize < 14) { + warning("Music: not enough data in music file"); + delete[] midiMusicData; + return false; + } + + byte *dataPos = midiMusicData; + uint32 dataSize = midiMusicDataSize; + + if (IS_SERRATED_SCALPEL) { + if (memcmp(" ", dataPos, 12)) { + warning("Music: expected header not found in music file"); + delete[] midiMusicData; + return false; + } + dataPos += 12; + dataSize -= 12; + + if (dataSize < 0x7F) { + warning("Music: expected music header not found in music file"); + delete[] midiMusicData; + return false; + } + + uint16 headerSize = READ_LE_UINT16(dataPos); + if (headerSize != 0x7F) { + warning("Music: header is not as expected"); + delete[] midiMusicData; + return false; + } + } else { + if (dataSize < 4) { + warning("Music: expected music header not found in music file"); + delete[] midiMusicData; + return false; + } + + if (memcmp("FORM", dataPos, 4)) { + warning("Music: expected header not found in music file"); + delete[] midiMusicData; + return false; + } + } + + switch (_musicType) { + case MT_ADLIB: + MidiDriver_AdLib_newMusicData(_midiDriver, dataPos, dataSize); + break; + + case MT_MT32: + MidiDriver_MT32_newMusicData(_midiDriver, dataPos, dataSize); + break; + + case MT_GM: + break; + + default: + // should never happen + break; + } + + _midiParser->loadMusic(midiMusicData, midiMusicDataSize); + + } else { + // 3DO: sample based + Audio::AudioStream *musicStream; + Common::String digitalMusicName = "music/" + name + "_MW22.aifc"; + + if (isPlaying()) { + _mixer->stopHandle(_digitalMusicHandle); + } + + Common::File *digitalMusicFile = new Common::File(); + if (!digitalMusicFile->open(digitalMusicName)) { + warning("playMusic: can not open 3DO music '%s'", digitalMusicName.c_str()); + return false; + } + + // Try to load the given file as AIFF/AIFC + musicStream = Audio::makeAIFFStream(digitalMusicFile, DisposeAfterUse::YES); + if (!musicStream) { + warning("playMusic: can not load 3DO music '%s'", digitalMusicName.c_str()); + return false; + } + _mixer->playStream(Audio::Mixer::kMusicSoundType, &_digitalMusicHandle, musicStream); + } + return true; +} + +void Music::stopMusic() { + if (isPlaying()) { + if (!IS_3DO) + _midiParser->stopPlaying(); + else + _mixer->stopHandle(_digitalMusicHandle); + } + + _musicPlaying = false; +} + +void Music::startSong() { + if (!_musicOn) + return; + + // TODO + warning("TODO: Sound::startSong"); + _musicPlaying = true; +} + +void Music::freeSong() { + if (!IS_3DO) { + if (_midiParser->isPlaying()) + _midiParser->stopPlaying(); + + // Free the MIDI MUS data buffer + _midiParser->unloadMusic(); + } +} + +void Music::waitTimerRoland(uint time) { + // TODO + warning("TODO: Sound::waitTimerRoland"); +} + +bool Music::isPlaying() { + if (!IS_3DO) { + // MIDI based + return _midiParser->isPlaying(); + } else { + // 3DO: sample based + return _mixer->isSoundHandleActive(_digitalMusicHandle); + } +} + +// Returns the current music position in milliseconds +uint32 Music::getCurrentPosition() { + if (!IS_3DO) { + // MIDI based + return (_midiParser->getTick() * 1000) / 60; // translate tick to millisecond + } else { + // 3DO: sample based + return _mixer->getSoundElapsedTime(_digitalMusicHandle); + } +} + +// This is used to wait for the music in certain situations like especially the intro +// Note: the original game didn't do this, instead it just waited for certain amounts of time +// We do this, so that the intro graphics + music work together even on faster/slower hardware. +bool Music::waitUntilMSec(uint32 msecTarget, uint32 msecMax, uint32 additionalDelay, uint32 noMusicDelay) { + uint32 msecCurrent = 0; + + if (!isPlaying()) { + return _vm->_events->delay(noMusicDelay, true); + } + while (1) { + if (!isPlaying()) { // Music is not playing anymore -> we are done + if (additionalDelay > 0) { + if (!_vm->_events->delay(additionalDelay, true)) + return false; + } + return true; + } + + msecCurrent = getCurrentPosition(); + //warning("waitUntilMSec: %lx", msecCurrent); + + if ((!msecMax) || (msecCurrent <= msecMax)) { + if (msecCurrent >= msecTarget) { + if (additionalDelay > 0) { + if (!_vm->_events->delay(additionalDelay, true)) + return false; + } + return true; + } + } + if (!_vm->_events->delay(10, true)) + return false; + } +} + +void Music::setMIDIVolume(int volume) { + warning("TODO: Music::setMIDIVolume"); +} + +} // End of namespace Sherlock diff --git a/engines/sherlock/music.h b/engines/sherlock/music.h new file mode 100644 index 0000000000..da1bca83e1 --- /dev/null +++ b/engines/sherlock/music.h @@ -0,0 +1,130 @@ +/* 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 SHERLOCK_MUSIC_H +#define SHERLOCK_MUSIC_H + +#include "audio/midiplayer.h" +#include "audio/midiparser.h" +//#include "audio/mididrv.h" +#include "sherlock/scalpel/drivers/mididriver.h" +// for 3DO digital music +#include "audio/audiostream.h" +#include "audio/mixer.h" +#include "common/mutex.h" + +namespace Sherlock { + +class SherlockEngine; + +class MidiParser_SH : public MidiParser { +public: + MidiParser_SH(); + ~MidiParser_SH(); + +protected: + Common::Mutex _mutex; + void parseNextEvent(EventInfo &info); + + uint8 _beats; + uint8 _lastEvent; + byte *_data; + byte *_trackEnd; + +public: + bool loadMusic(byte *musData, uint32 musSize); + void unloadMusic(); + +private: + byte *_musData; + uint32 _musDataSize; +}; + +class Music { +private: + SherlockEngine *_vm; + Audio::Mixer *_mixer; + MidiParser *_midiParser; + MidiDriver *_midiDriver; + Audio::SoundHandle _digitalMusicHandle; + MusicType _musicType; +public: + bool _musicPlaying; + bool _musicOn; + int _musicVolume; + bool _midiOption; +public: + Music(SherlockEngine *vm, Audio::Mixer *mixer); + ~Music(); + + /** + * Saves sound-related settings + */ + void syncMusicSettings(); + + /** + * Load a specified song + */ + bool loadSong(int songNumber); + + /** + * Load a specified song + */ + bool loadSong(const Common::String &songName); + + /** + * Start playing a song + */ + void startSong(); + + /** + * Free any currently loaded song + */ + void freeSong(); + + /** + * Play the specified music resource + */ + bool playMusic(const Common::String &name); + + /** + * Stop playing the music + */ + void stopMusic(); + + void waitTimerRoland(uint time); + + bool isPlaying(); + uint32 getCurrentPosition(); + + bool waitUntilMSec(uint32 msecTarget, uint32 maxMSec, uint32 additionalDelay, uint32 noMusicDelay); + + /** + * Sets the volume of the MIDI music with a value ranging from 0 to 127 + */ + void setMIDIVolume(int volume); +}; + +} // End of namespace Sherlock + +#endif + diff --git a/engines/sherlock/objects.cpp b/engines/sherlock/objects.cpp new file mode 100644 index 0000000000..ac5296c79e --- /dev/null +++ b/engines/sherlock/objects.cpp @@ -0,0 +1,1542 @@ +/* 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 "common/util.h" +#include "sherlock/sherlock.h" +#include "sherlock/objects.h" +#include "sherlock/people.h" +#include "sherlock/scene.h" +#include "sherlock/scalpel/scalpel_map.h" +#include "sherlock/scalpel/scalpel_people.h" + +namespace Sherlock { + +#define START_FRAME 0 + +#define NUM_ADJUSTED_WALKS 21 + +// Distance to walk around WALK_AROUND boxes +#define CLEAR_DIST_X 5 +#define CLEAR_DIST_Y 0 + +SherlockEngine *BaseObject::_vm; +bool BaseObject::_countCAnimFrames; + +/*----------------------------------------------------------------*/ + +void BaseObject::setVm(SherlockEngine *vm) { + _vm = vm; + _countCAnimFrames = false; +} + +BaseObject::BaseObject() { + _type = INVALID; + _sequences = nullptr; + _images = nullptr; + _imageFrame = nullptr; + _walkCount = 0; + _allow = 0; + _frameNumber = 0; + _lookFlag = 0; + _requiredFlag = 0; + _status = 0; + _misc = 0; + _maxFrames = 0; + _flags = 0; + _aType = OBJECT; + _lookFrames = 0; + _seqCounter = 0; + _lookFacing = 0; + _lookcAnim = 0; + _seqStack = 0; + _seqTo = 0; + _descOffset = 0; + _seqCounter2 = 0; + _seqSize = 0; + _quickDraw = 0; + _scaleVal = 0; + _requiredFlags1 = 0; + _gotoSeq = 0; + _talkSeq = 0; + _restoreSlot = 0; +} + +bool BaseObject::hasAborts() const { + int seqNum = _talkSeq; + + // See if the object is in it's regular sequence + bool startChecking = !seqNum || _type == CHARACTER; + + uint idx = 0; + do + { + // Get the Frame value + int v = _sequences[idx++]; + + // See if we found an Allow Talk Interrupt Code + if (startChecking && v == ALLOW_TALK_CODE) + return true; + + // If we've started checking and we've encountered another Talk or Listen Sequence Code, + // then we're done checking this sequence because this is where it would repeat + if (startChecking && (v == TALK_SEQ_CODE || v == TALK_LISTEN_CODE)) + return false; + + // See if we've found the beginning of a Talk Sequence + if ((v == TALK_SEQ_CODE && seqNum < 128) || (v == TALK_LISTEN_CODE && seqNum >= 128)) { + // If checking was already on and we came across one of these codes, then there couldn't + // have been an Allow Talk Interrupt code in the sequence we were checking, so we're done. + if (startChecking) + return false; + + seqNum--; + // See if we're at the correct Talk Sequence Number + if (!(seqNum & 127)) + { + // Correct Sequence, Start Checking Now + startChecking = true; + } + } else { + // Move ahead any extra because of special control codes + switch (v) { + case 0: idx++; break; + case MOVE_CODE: + case TELEPORT_CODE: idx += 4; break; + case CALL_TALK_CODE:idx += 8; break; + case HIDE_CODE: idx += 2; break; + } + } + } while (idx < _seqSize); + + return true; +} + +void BaseObject::checkObject() { + Scene &scene = *_vm->_scene; + Sound &sound = *_vm->_sound; + Talk &talk = *_vm->_talk; + int checkFrame = _allow ? MAX_FRAME : FRAMES_END; + bool codeFound; + + if (_seqTo) { + byte *ptr = &_sequences[_frameNumber]; + if (*ptr == _seqTo) { + // The sequence is completed. Reset to normal + *ptr = _seqTo + (IS_ROSE_TATTOO ? 0 : SEQ_TO_CODE + 128); + _seqTo = 0; + } else { + // Continue doing sequence + if (*ptr > _seqTo) + *ptr -= 1; + else + *ptr += 1; + + return; + } + } + + ++_frameNumber; + + do { + if (!_sequences) { + warning("checkObject: _sequences is not set"); + break; + } + + // Check for end of sequence + codeFound = checkEndOfSequence(); + + if (_sequences[_frameNumber] >= 128 && _frameNumber < checkFrame) { + codeFound = true; + int v = _sequences[_frameNumber]; + + // Check for a Talk or Listen Sequence + if (IS_ROSE_TATTOO && v == ALLOW_TALK_CODE) { + if (_gotoSeq) { + setObjTalkSequence(_gotoSeq); + } else { + ++_frameNumber; + } + } else if (IS_ROSE_TATTOO && (v == TALK_SEQ_CODE || v == TALK_LISTEN_CODE)) { + if (_talkSeq) + setObjTalkSequence(_talkSeq); + else + setObjSequence(0, false); + } else if (v >= GOTO_CODE) { + // Goto code found + v -= GOTO_CODE; + _seqCounter2 = _seqCounter; + _seqStack = _frameNumber + 1; + setObjSequence(v, false); + } else if (v >= SOUND_CODE && (v < (SOUND_CODE + 30))) { + codeFound = true; + ++_frameNumber; + v -= SOUND_CODE + (IS_SERRATED_SCALPEL ? 1 : 0); + + if (sound._soundOn && !_countCAnimFrames) { + if (!scene._sounds[v]._name.empty() && sound._digitized) + sound.playLoadedSound(v, WAIT_RETURN_IMMEDIATELY); + } + } else if (v >= FLIP_CODE && v < (FLIP_CODE + 3)) { + // Flip code + codeFound = true; + ++_frameNumber; + v -= FLIP_CODE; + + // Alter the flipped status + switch (v) { + case 0: + // Clear the flag + _flags &= ~OBJ_FLIPPED; + break; + case 1: + // Set the flag + _flags |= OBJ_FLIPPED; + break; + case 2: + // Toggle the flag + _flags ^= OBJ_FLIPPED; + break; + default: + break; + } + } else if (IS_ROSE_TATTOO && v == TELEPORT_CODE) { + _position.x = READ_LE_UINT16(&_sequences[_frameNumber + 1]); + _position.y = READ_LE_UINT16(&_sequences[_frameNumber + 3]); + + _frameNumber += 5; + } else if (IS_ROSE_TATTOO && v == CALL_TALK_CODE) { + Common::String filename; + for (int idx = 0; idx < 8; ++idx) { + if (_sequences[_frameNumber + 1 + idx] != 1) + filename += (char)_sequences[_frameNumber + 1 + idx]; + else + break; + } + + _frameNumber += 8; + talk.talkTo(filename); + + } else if (IS_ROSE_TATTOO && v == HIDE_CODE) { + switch (_sequences[_frameNumber + 2]) { + case 1: + // Hide Object + if (scene._bgShapes[_sequences[_frameNumber + 1] - 1]._type != HIDDEN) + scene._bgShapes[_sequences[_frameNumber + 1] - 1].toggleHidden(); + break; + + case 2: + // Activate Object + if (scene._bgShapes[_sequences[_frameNumber + 1] - 1]._type == HIDDEN) + scene._bgShapes[_sequences[_frameNumber + 1] - 1].toggleHidden(); + break; + + case 3: + // Toggle Object + scene._bgShapes[_sequences[_frameNumber + 1] - 1].toggleHidden(); + break; + + default: + break; + } + _frameNumber += 3; + + } else { + v -= 128; + + // 68-99 is a sequence code + if (v > SEQ_TO_CODE) { + if (IS_ROSE_TATTOO) { + ++_frameNumber; + byte *p = &_sequences[_frameNumber]; + _seqTo = *p; + *p = *(p - 2); + + if (*p > _seqTo) + *p -= 1; + else + *p += 1; + + --_frameNumber; + } else { + byte *p = &_sequences[_frameNumber]; + v -= SEQ_TO_CODE; // # from 1-32 + _seqTo = v; + *p = *(p - 1); + + if (*p > 128) + // If the high bit is set, convert to a real frame + *p -= (byte)(SEQ_TO_CODE - 128); + + if (*p > _seqTo) + *p -= 1; + else + *p += 1; + + // Will be incremented below to return back to original value + --_frameNumber; + v = 0; + } + } else if (IS_ROSE_TATTOO && v == 10) { + // Set delta for objects + _delta = Common::Point(READ_LE_UINT16(&_sequences[_frameNumber + 1]), + READ_LE_UINT16(&_sequences[_frameNumber + 3])); + _noShapeSize = Common::Point(0, 0); + _frameNumber += 4; + + } else if (v == 10) { + // Set delta for objects + Common::Point pt(_sequences[_frameNumber + 1], _sequences[_frameNumber + 2]); + if (pt.x > 128) + pt.x = (pt.x - 128) * -1; + else + pt.x--; + + if (pt.y > 128) + pt.y = (pt.y - 128) * -1; + else + pt.y--; + + _delta = pt; + _frameNumber += 2; + + } else if (v < USE_COUNT) { + for (int idx = 0; idx < NAMES_COUNT; ++idx) { + checkNameForCodes(_use[v]._names[idx]); + } + + if (_use[v]._useFlag) + _vm->setFlags(_use[v]._useFlag); + } + + ++_frameNumber; + } + } + } while (codeFound); +} + +bool BaseObject::checkEndOfSequence() { + Screen &screen = *_vm->_screen; + int checkFrame = _allow ? MAX_FRAME : FRAMES_END; + bool result = false; + + if (_type == REMOVE || _type == INVALID) + return false; + + if (_sequences[_frameNumber] == 0 || _frameNumber >= checkFrame) { + result = true; + + if (_frameNumber >= (checkFrame - 1)) { + _frameNumber = START_FRAME; + } else { + // Determine next sequence to use + int seq = _sequences[_frameNumber + 1]; + + if (seq == 99) { + --_frameNumber; + screen._backBuffer1.transBlitFrom(*_imageFrame, _position); + screen._backBuffer2.transBlitFrom(*_imageFrame, _position); + _type = INVALID; + } else if (IS_ROSE_TATTOO && _talkSeq && seq == 0) { + setObjTalkSequence(_talkSeq); + } else { + setObjSequence(seq, false); + } + } + + if (_allow && _frameNumber == 0) { + // canimation just ended + if (_type != NO_SHAPE && _type != REMOVE) { + _type = REMOVE; + + if (!_countCAnimFrames) { + // Save details before shape is removed + _delta.x = _imageFrame->_frame.w; + _delta.y = _imageFrame->_frame.h; + _position += _imageFrame->_offset; + + // Free the images + delete _images; + _images = nullptr; + _imageFrame = nullptr; + } + } else { + _type = INVALID; + } + } + } + + return result; +} + +void BaseObject::setObjSequence(int seq, bool wait) { + Scene &scene = *_vm->_scene; + int checkFrame = _allow ? MAX_FRAME : FRAMES_END; + + if (seq >= 128) { + // Loop the sequence until the count exceeded + seq -= 128; + + ++_seqCounter; + if (_seqCounter >= seq) { + // Go to next sequence + if (_seqStack) { + _frameNumber = _seqStack; + _seqStack = 0; + _seqCounter = _seqCounter2; + _seqCounter2 = 0; + if (_frameNumber >= checkFrame) + _frameNumber = START_FRAME; + + return; + } + + _frameNumber += 2; + if (_frameNumber >= checkFrame) + _frameNumber = 0; + + _seqCounter = 0; + if (_sequences[_frameNumber] == 0) + seq = _sequences[_frameNumber + 1]; + else + return; + } else { + // Find beginning of sequence + do { + --_frameNumber; + } while (_frameNumber > 0 && _sequences[_frameNumber] != 0); + + if (_frameNumber != 0) + _frameNumber += 2; + + return; + } + } else { + // Reset sequence counter + _seqCounter = 0; + } + + int idx = 0; + int seqCc = 0; + + while (seqCc < seq && idx < checkFrame) { + ++idx; + if (_sequences[idx] == 0) { + ++seqCc; + idx += 2; + } + } + + if (idx >= checkFrame) + idx = 0; + _frameNumber = idx; + + if (wait) { + seqCc = idx; + while (_sequences[idx] != 0) + ++idx; + + idx = idx - seqCc + 2; + for (; idx > 0; --idx) + scene.doBgAnim(); + } +} + +int BaseObject::checkNameForCodes(const Common::String &name, FixedTextActionId fixedTextActionId) { + FixedText &fixedText = *_vm->_fixedText; + People &people = *_vm->_people; + Scene &scene = *_vm->_scene; + Screen &screen = *_vm->_screen; + Talk &talk = *_vm->_talk; + UserInterface &ui = *_vm->_ui; + bool printed = false; + + scene.toggleObject(name); + + if (name.hasPrefix("*")) { + // A code was found + printed = true; + char ch = (name == "*") ? 0 : toupper(name[1]); + + switch (ch) { + case 'C': + talk.talkTo(name.c_str() + 2); + break; + + case 'T': + case 'B': + case 'F': + case 'W': + // Nothing: action was already done before canimation + break; + + case 'G': + case 'A': { + // G: Have object go somewhere + // A: Add onto existing co-ordinates + Common::String sx(name.c_str() + 2, name.c_str() + 5); + Common::String sy(name.c_str() + 6, name.c_str() + 9); + + if (ch == 'G') + _position = Common::Point(atoi(sx.c_str()), atoi(sy.c_str())); + else + _position += Common::Point(atoi(sx.c_str()), atoi(sy.c_str())); + break; + } + + default: + if (ch >= '0' && ch <= '9') { + scene._goToScene = atoi(name.c_str() + 1); + + if (IS_SERRATED_SCALPEL && scene._goToScene < 97) { + Scalpel::ScalpelMap &map = *(Scalpel::ScalpelMap *)_vm->_map; + if (map[scene._goToScene].x) { + map._overPos.x = (map[scene._goToScene].x - 6) * FIXED_INT_MULTIPLIER; + map._overPos.y = (map[scene._goToScene].y + 9) * FIXED_INT_MULTIPLIER; + } + } + + const char *p; + if ((p = strchr(name.c_str(), ',')) != nullptr) { + ++p; + + Common::String s(p, p + 3); + people._hSavedPos.x = atoi(s.c_str()); + + s = Common::String(p + 3, p + 6); + people._hSavedPos.y = atoi(s.c_str()); + + s = Common::String(p + 6, p + 9); + people._hSavedFacing = atoi(s.c_str()); + if (people._hSavedFacing == 0) + people._hSavedFacing = 10; + } else if ((p = strchr(name.c_str(), '/')) != nullptr) { + people._hSavedPos = Common::Point(1, 0); + people._hSavedFacing = 100 + atoi(p + 1); + } + } else { + scene._goToScene = 100; + } + + people[HOLMES]._position = Point32(0, 0); + break; + } + } else if (name.hasPrefix("!")) { + // Message attached to canimation + int messageNum = atoi(name.c_str() + 1); + ui._infoFlag = true; + ui.clearInfo(); + Common::String errorMessage = fixedText.getActionMessage(fixedTextActionId, messageNum); + screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", errorMessage.c_str()); + ui._menuCounter = 25; + } else if (name.hasPrefix("@")) { + // Message attached to canimation + ui._infoFlag = true; + ui.clearInfo(); + screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", name.c_str() + 1); + printed = true; + ui._menuCounter = 25; + } + + return printed; +} + +/*----------------------------------------------------------------*/ + +void Sprite::clear() { + _name = ""; + _description = ""; + _examine.clear(); + _pickUp = ""; + _walkSequences.clear(); + _sequences = nullptr; + _images = nullptr; + _imageFrame = nullptr; + _walkCount = 0; + _allow = 0; + _frameNumber = _sequenceNumber = 0; + _position.x = _position.y = 0; + _delta.x = _delta.y = 0; + _oldPosition.x = _oldPosition.y = 0; + _oldSize.x = _oldSize.y = 0; + _goto.x = _goto.y = 0; + _type = INVALID; + _pickUp.clear(); + _noShapeSize.x = _noShapeSize.y = 0; + _status = 0; + _misc = 0; + _altImages = nullptr; + _altSeq = 0; + Common::fill(&_stopFrames[0], &_stopFrames[8], (ImageFrame *)nullptr); +} + +void Sprite::setImageFrame() { + int frameNum = MAX(_frameNumber, 0); + int imageNumber = _walkSequences[_sequenceNumber][frameNum]; + + if (IS_SERRATED_SCALPEL) + imageNumber = imageNumber + _walkSequences[_sequenceNumber][0] - 2; + else if (imageNumber > _maxFrames) + imageNumber = 1; + + // Get the images to use + ImageFile *images = _altSeq ? _altImages : _images; + assert(images); + + if (IS_3DO) { + // only do this to the image-array with 110 entries + // map uses another image-array and this code + if (images->size() == 110) { + // 3DO has 110 animation frames inside walk.anim + // PC has 55 + // this adjusts the framenumber accordingly + // sort of HACK + imageNumber *= 2; + } + } else if (IS_ROSE_TATTOO) { + --imageNumber; + } + + // Set the frame pointer + _imageFrame = &(*images)[imageNumber]; +} + +void Sprite::checkSprite() { + Events &events = *_vm->_events; + People &people = *_vm->_people; + Scene &scene = *_vm->_scene; + Screen &screen = *_vm->_screen; + Talk &talk = *_vm->_talk; + Point32 pt; + Common::Rect objBounds; + Common::Point spritePt(_position.x / FIXED_INT_MULTIPLIER, _position.y / FIXED_INT_MULTIPLIER); + + if (_type != CHARACTER || (IS_SERRATED_SCALPEL && talk._talkCounter)) + return; + + pt = _walkCount ? _position + _delta : _position; + pt.x /= FIXED_INT_MULTIPLIER; + pt.y /= FIXED_INT_MULTIPLIER; + + if (IS_ROSE_TATTOO) { + checkObject(); + + // For Rose Tattoo, we only do the further processing for Sherlock + if (this != &people[HOLMES]) + return; + } + + for (uint idx = 0; idx < scene._bgShapes.size() && !talk._talkToAbort; ++idx) { + Object &obj = scene._bgShapes[idx]; + if (obj._aType <= PERSON || obj._type == INVALID || obj._type == HIDDEN) + continue; + + if (obj._type == NO_SHAPE) { + objBounds = Common::Rect(obj._position.x, obj._position.y, + obj._position.x + obj._noShapeSize.x + 1, obj._position.y + obj._noShapeSize.y + 1); + } else { + int xp = obj._position.x + obj._imageFrame->_offset.x; + int yp = obj._position.y + obj._imageFrame->_offset.y; + objBounds = Common::Rect(xp, yp, + xp + obj._imageFrame->_frame.w + 1, yp + obj._imageFrame->_frame.h + 1); + } + + if (objBounds.contains(pt)) { + if (objBounds.contains(spritePt)) { + // Current point is already inside the the bounds, so impact occurred + // on a previous call. So simply do nothing until we're clear of the box + switch (obj._aType) { + case TALK_MOVE: + if (_walkCount) { + // Holmes is moving + obj._type = HIDDEN; + obj.setFlagsAndToggles(); + talk.talkTo(obj._use[0]._target); + } + break; + + case PAL_CHANGE: + case PAL_CHANGE2: + if (_walkCount) { + int palStart = atoi(obj._use[0]._names[0].c_str()) * 3; + int palLength = atoi(obj._use[0]._names[1].c_str()) * 3; + int templ = atoi(obj._use[0]._names[2].c_str()) * 3; + if (templ == 0) + templ = 100; + + // Ensure only valid palette change data found + if (palLength > 0) { + // Figure out how far into the shape Holmes is so that we + // can figure out what percentage of the original palette + // to set the current palette to + int palPercent = (pt.x - objBounds.left) * 100 / objBounds.width(); + palPercent = palPercent * templ / 100; + if (obj._aType == PAL_CHANGE) + // Invert percentage + palPercent = 100 - palPercent; + + for (int i = palStart; i < (palStart + palLength); ++i) + screen._sMap[i] = screen._cMap[i] * palPercent / 100; + + events.pollEvents(); + screen.setPalette(screen._sMap); + } + } + break; + + case TALK: + case TALK_EVERY: + obj._type = HIDDEN; + obj.setFlagsAndToggles(); + talk.talkTo(obj._use[0]._target); + break; + + default: + break; + } + } else { + // New impact just occurred + switch (obj._aType) { + case BLANK_ZONE: + // A blank zone masks out all other remaining zones underneath it. + // If this zone is hit, exit the outer loop so we do not check anymore + return; + + case SOLID: + case TALK: + // Stop walking + if (obj._aType == TALK) { + obj.setFlagsAndToggles(); + talk.talkTo(obj._use[0]._target); + } else { + gotoStand(); + } + break; + + case TALK_EVERY: + if (obj._aType == TALK_EVERY) { + obj._type = HIDDEN; + obj.setFlagsAndToggles(); + talk.talkTo(obj._use[0]._target); + } else { + gotoStand(); + } + break; + + case FLAG_SET: + obj.setFlagsAndToggles(); + obj._type = HIDDEN; + break; + + case WALK_AROUND: + if (objBounds.contains(people[HOLMES]._walkTo.front())) { + // Reached zone + gotoStand(); + } else { + // Destination not within box, walk to best corner + Common::Point walkPos; + + if (spritePt.x >= objBounds.left && spritePt.x < objBounds.right) { + // Impact occurred due to vertical movement. Determine whether to + // travel to the left or right side + if (_delta.x > 0) + // Go to right side + walkPos.x = objBounds.right + CLEAR_DIST_X; + else if (_delta.x < 0) { + // Go to left side + walkPos.x = objBounds.left - CLEAR_DIST_X; + } else { + // Going straight up or down. So choose best side + if (spritePt.x >= (objBounds.left + objBounds.width() / 2)) + walkPos.x = objBounds.right + CLEAR_DIST_X; + else + walkPos.x = objBounds.left - CLEAR_DIST_X; + } + + walkPos.y = (_delta.y >= 0) ? objBounds.top - CLEAR_DIST_Y : + objBounds.bottom + CLEAR_DIST_Y; + } else { + // Impact occurred due to horizontal movement + if (_delta.y > 0) + // Go to bottom of box + walkPos.y = objBounds.bottom + CLEAR_DIST_Y; + else if (_delta.y < 0) + // Go to top of box + walkPos.y = objBounds.top - CLEAR_DIST_Y; + else { + // Going straight horizontal, so choose best side + if (spritePt.y >= (objBounds.top + objBounds.height() / 2)) + walkPos.y = objBounds.bottom + CLEAR_DIST_Y; + else + walkPos.y = objBounds.top - CLEAR_DIST_Y; + } + + walkPos.x = (_delta.x >= 0) ? objBounds.left - CLEAR_DIST_X : + objBounds.right + CLEAR_DIST_X; + } + + walkPos.x += people[HOLMES]._imageFrame->_frame.w / 2; + people[HOLMES]._walkDest = walkPos; + people[HOLMES]._walkTo.push(walkPos); + people[HOLMES].setWalking(); + } + break; + + case DELTA: + _position.x += 200; + break; + + default: + break; + } + } + } + } +} + +const Common::Rect Sprite::getOldBounds() const { + return Common::Rect(_oldPosition.x, _oldPosition.y, _oldPosition.x + _oldSize.x, _oldPosition.y + _oldSize.y); +} + +/*----------------------------------------------------------------*/ + +void WalkSequence::load(Common::SeekableReadStream &s) { + char buffer[9]; + s.read(buffer, 9); + _vgsName = Common::String(buffer); + _horizFlip = s.readByte() != 0; + + _sequences.resize(s.readUint16LE()); + s.skip(4); // Skip over pointer field of structure + + s.read(&_sequences[0], _sequences.size()); +} + +/*----------------------------------------------------------------*/ + +WalkSequences &WalkSequences::operator=(const WalkSequences &src) { + resize(src.size()); + for (uint idx = 0; idx < size(); ++idx) { + const WalkSequence &wSrc = src[idx]; + WalkSequence &wDest = (*this)[idx]; + wDest._horizFlip = wSrc._horizFlip; + + wDest._sequences.resize(wSrc._sequences.size()); + Common::copy(&wSrc._sequences[0], &wSrc._sequences[0] + wSrc._sequences.size(), &wDest._sequences[0]); + } + + return *this; +} + +/*----------------------------------------------------------------*/ + +ActionType::ActionType() { + _cAnimNum = _cAnimSpeed = 0; +} + +void ActionType::load(Common::SeekableReadStream &s) { + char buffer[12]; + + _cAnimNum = s.readByte(); + _cAnimSpeed = s.readByte(); + if (_cAnimSpeed & 0x80) + _cAnimSpeed = -(_cAnimSpeed & 0x7f); + + for (int idx = 0; idx < NAMES_COUNT; ++idx) { + s.read(buffer, 12); + _names[idx] = Common::String(buffer); + } +} + +/*----------------------------------------------------------------*/ + +UseType::UseType(): ActionType() { + _useFlag = 0; +} + +void UseType::load(Common::SeekableReadStream &s, bool isRoseTattoo) { + char buffer[12]; + + if (isRoseTattoo) { + s.read(buffer, 12); + _verb = Common::String(buffer); + } + + ActionType::load(s); + + _useFlag = s.readSint16LE(); + + if (!isRoseTattoo) + s.skip(6); + + s.read(buffer, 12); + _target = Common::String(buffer); +} + +void UseType::load3DO(Common::SeekableReadStream &s) { + char buffer[12]; + + _cAnimNum = s.readByte(); + _cAnimSpeed = s.readByte(); + if (_cAnimSpeed & 0x80) + _cAnimSpeed = -(_cAnimSpeed & 0x7f); + + for (int idx = 0; idx < NAMES_COUNT; ++idx) { + s.read(buffer, 12); + _names[idx] = Common::String(buffer); + } + + _useFlag = s.readSint16BE(); + + s.skip(6); + + s.read(buffer, 12); + _target = Common::String(buffer); +} + +/*----------------------------------------------------------------*/ + +Object::Object(): BaseObject() { + _sequenceNumber = 0; + _sequenceOffset = 0; + _pickup = 0; + _defaultCommand = 0; + _pickupFlag = 0; +} + +void Object::load(Common::SeekableReadStream &s, bool isRoseTattoo) { + char buffer[41]; + s.read(buffer, 12); + _name = Common::String(buffer); + s.read(buffer, 41); + _description = Common::String(buffer); + + _examine.clear(); + _sequences = nullptr; + _images = nullptr; + _imageFrame = nullptr; + + s.skip(4); + _sequenceOffset = s.readUint16LE(); + s.seek(10, SEEK_CUR); + + _walkCount = s.readByte(); + _allow = s.readByte(); + _frameNumber = s.readSint16LE(); + _sequenceNumber = s.readSint16LE(); + _position.x = s.readSint16LE(); + _position.y = s.readSint16LE(); + _delta.x = s.readSint16LE(); + _delta.y = s.readSint16LE(); + _type = (SpriteType)s.readUint16LE(); + _oldPosition.x = s.readSint16LE(); + _oldPosition.y = s.readSint16LE(); + _oldSize.x = s.readUint16LE(); + _oldSize.y = s.readUint16LE(); + + _goto.x = s.readSint16LE(); + _goto.y = s.readSint16LE(); + if (!isRoseTattoo) { + _goto.x = _goto.x * FIXED_INT_MULTIPLIER / 100; + _goto.y = _goto.y * FIXED_INT_MULTIPLIER / 100; + } + + _pickup = isRoseTattoo ? 0 : s.readByte(); + _defaultCommand = isRoseTattoo ? 0 : s.readByte(); + _lookFlag = s.readSint16LE(); + _pickupFlag = isRoseTattoo ? 0 : s.readSint16LE(); + _requiredFlag = s.readSint16LE(); + _noShapeSize.x = s.readUint16LE(); + _noShapeSize.y = s.readUint16LE(); + _status = s.readUint16LE(); + _misc = s.readByte(); + _maxFrames = s.readUint16LE(); + _flags = s.readByte(); + + if (!isRoseTattoo) + _aOpen.load(s); + + _aType = (AType)s.readByte(); + _lookFrames = s.readByte(); + _seqCounter = s.readByte(); + _lookPosition.x = s.readUint16LE() * FIXED_INT_MULTIPLIER / 100; + _lookPosition.y = (isRoseTattoo ? s.readSint16LE() : s.readByte()) * FIXED_INT_MULTIPLIER; + _lookFacing = s.readByte(); + _lookcAnim = s.readByte(); + + if (!isRoseTattoo) + _aClose.load(s); + + _seqStack = s.readByte(); + _seqTo = s.readByte(); + _descOffset = s.readUint16LE(); + _seqCounter2 = s.readByte(); + _seqSize = s.readUint16LE(); + + if (isRoseTattoo) { + for (int idx = 0; idx < 6; ++idx) + _use[idx].load(s, true); + + _quickDraw = s.readByte(); + _scaleVal = s.readUint16LE(); + _requiredFlags1 = s.readSint16LE(); + _gotoSeq = s.readByte(); + _talkSeq = s.readByte(); + _restoreSlot = s.readByte(); + } else { + s.skip(1); + _aMove.load(s); + s.skip(8); + + for (int idx = 0; idx < 4; ++idx) + _use[idx].load(s, false); + } + //warning("object %s, useAnim %d", _name.c_str(), _use[0]._cAnimNum); +} + +void Object::load3DO(Common::SeekableReadStream &s) { + int32 streamStartPos = s.pos(); + char buffer[41]; + + _examine.clear(); + _sequences = nullptr; + _images = nullptr; + _imageFrame = nullptr; + + // on 3DO all of this data is reordered!!! + // it seems that possibly the 3DO compiler reordered the global struct + // 3DO size for 1 object is 588 bytes + s.skip(4); + _sequenceOffset = s.readUint16LE(); // weird that this seems to be LE + s.seek(10, SEEK_CUR); + + // Offset 16 + _frameNumber = s.readSint16BE(); + _sequenceNumber = s.readSint16BE(); + _position.x = s.readSint16BE(); + _position.y = s.readSint16BE(); + _delta.x = s.readSint16BE(); + _delta.y = s.readSint16BE(); + _type = (SpriteType)s.readUint16BE(); + _oldPosition.x = s.readSint16BE(); + _oldPosition.y = s.readSint16BE(); + _oldSize.x = s.readUint16BE(); + _oldSize.y = s.readUint16BE(); + + _goto.x = s.readSint16BE(); + _goto.y = s.readSint16BE(); + _goto.x = _goto.x * FIXED_INT_MULTIPLIER / 100; + _goto.y = _goto.y * FIXED_INT_MULTIPLIER / 100; + + // Offset 42 + warning("pos %d", s.pos()); + + // Unverified + _lookFlag = s.readSint16BE(); + _pickupFlag = s.readSint16BE(); + _requiredFlag = s.readSint16BE(); + _noShapeSize.x = s.readUint16BE(); + _noShapeSize.y = s.readUint16BE(); + _status = s.readUint16BE(); + // Unverified END + + _maxFrames = s.readUint16BE(); + // offset 56 + _lookPosition.x = s.readUint16BE() * FIXED_INT_MULTIPLIER / 100; + // offset 58 + _descOffset = s.readUint16BE(); + _seqSize = s.readUint16BE(); + + s.skip(2); // boundary filler + + // 288 bytes + for (int idx = 0; idx < 4; ++idx) { + _use[idx].load3DO(s); + s.skip(2); // Filler + } + + // 158 bytes + _aOpen.load(s); // 2 + 12*4 bytes = 50 bytes + s.skip(2); // Boundary filler + _aClose.load(s); + s.skip(2); // Filler + _aMove.load(s); + s.skip(2); // Filler + + // offset 508 + // 3DO: name is at the end + s.read(buffer, 12); + _name = Common::String(buffer); + s.read(buffer, 41); + _description = Common::String(buffer); + + // Unverified + _walkCount = s.readByte(); + _allow = s.readByte(); + _pickup = s.readByte(); + _defaultCommand = s.readByte(); + // Unverified END + + // Probably those here?!?! + _misc = s.readByte(); + _flags = s.readByte(); + + // Unverified + _aType = (AType)s.readByte(); + _lookFrames = s.readByte(); + _seqCounter = s.readByte(); + // Unverified END + + _lookPosition.y = s.readByte() * FIXED_INT_MULTIPLIER; + _lookFacing = s.readByte(); + + // Unverified + _lookcAnim = s.readByte(); + _seqStack = s.readByte(); + _seqTo = s.readByte(); + _seqCounter2 = s.readByte(); + // Unverified END + + s.skip(12); // Unknown + + //warning("object %s, offset %d", _name.c_str(), streamPos); + //warning("object %s, lookPosX %d, lookPosY %d", _name.c_str(), _lookPosition.x, _lookPosition.y); + //warning("object %s, defCmd %d", _name.c_str(), _defaultCommand); + int32 dataSize = s.pos() - streamStartPos; + assert(dataSize == 588); +} + +void Object::toggleHidden() { + if (_type != HIDDEN && _type != HIDE_SHAPE && _type != INVALID) { + if (_seqTo != 0) + _sequences[_frameNumber] = _seqTo + SEQ_TO_CODE + 128; + _seqTo = 0; + + if (_images == nullptr || _images->size() == 0) + // No shape to erase, so flag as hidden + _type = HIDDEN; + else + // Otherwise, flag it to be hidden after it gets erased + _type = HIDE_SHAPE; + } else if (_type != INVALID) { + if (_seqTo != 0) + _sequences[_frameNumber] = _seqTo + SEQ_TO_CODE + 128; + _seqTo = 0; + + _seqCounter = _seqCounter2 = 0; + _seqStack = 0; + _frameNumber = -1; + + if (_images == nullptr || _images->size() == 0) { + _type = NO_SHAPE; + } else { + _type = ACTIVE_BG_SHAPE; + int idx = _sequences[0]; + if (idx >= _maxFrames) + // Turn on: set up first frame + idx = 0; + + _imageFrame = &(*_images)[idx]; + } + } +} + +void Object::setObjTalkSequence(int seq) { + Talk &talk = *_vm->_talk; + + // See if we're supposed to restore the object's sequence from the talk sequence stack + if (seq == -1) { + TalkSequence &ts = talk._talkSequenceStack[_restoreSlot]; + if (_seqTo != 0) + _sequences[_frameNumber] = _seqTo; + _frameNumber = ts._frameNumber; + _sequenceNumber = ts._sequenceNumber; + _seqStack = ts._seqStack; + _seqTo = ts._seqTo; + _seqCounter = ts._seqCounter; + _seqCounter2 = ts._seqCounter2; + _talkSeq = 0; + + // Flag this slot as free again + ts._obj = nullptr; + + return; + } + + assert(_type != CHARACTER); + + talk.pushTalkSequence(this); + int talkSeqNum = seq; + + // Find where the talk sequence data begins in the object + int idx = 0; + for (;;) { + // Get the Frame value + byte f = _sequences[idx++]; + + // See if we've found the beginning of a Talk Sequence + if ((f == TALK_SEQ_CODE && seq < 128) || (f == TALK_LISTEN_CODE && seq > 128)) { + --seq; + + // See if we're at the correct Talk Sequence Number + if (!(seq & 127)) + { + // Correct Sequence, Start Talking Here + if (_seqTo != 0) + _sequences[_frameNumber] = _seqTo; + _frameNumber = idx; + _seqTo = 0; + _seqStack = 0; + _seqCounter = 0; + _seqCounter2 = 0; + _talkSeq = talkSeqNum; + break; + } + } else { + // Move ahead any extra because of special control codes + switch (f) { + case 0: idx++; break; + case MOVE_CODE: + case TELEPORT_CODE: idx += 4; break; + case CALL_TALK_CODE: idx += 8; break; + case HIDE_CODE: idx += 2; break; + } + } + + // See if we're out of sequence data + if (idx >= (int)_seqSize) + break; + } +} + +void Object::setFlagsAndToggles() { + Scene &scene = *_vm->_scene; + Talk &talk = *_vm->_talk; + + for (int useIdx = 0; useIdx < USE_COUNT; ++useIdx) { + if (_use[useIdx]._useFlag) { + if (!_vm->readFlags(_use[useIdx]._useFlag)) + _vm->setFlags(_use[useIdx]._useFlag); + } + + if (_use[useIdx]._cAnimSpeed) { + if (_use[useIdx]._cAnimNum == 0) + // 0 is really a 10 + scene.startCAnim(9, _use[useIdx]._cAnimSpeed); + else + scene.startCAnim(_use[useIdx]._cAnimNum - 1, _use[useIdx]._cAnimSpeed); + } + + if (!talk._talkToAbort) { + for (int idx = 0; idx < NAMES_COUNT; ++idx) + scene.toggleObject(_use[useIdx]._names[idx]); + } + } +} + +void Object::adjustObject() { + if (_type == REMOVE) + return; + + if (IS_ROSE_TATTOO && (_delta.x || _delta.y)) { + // The shape position is in pixels, and the delta is in fixed integer amounts + int t; + _noShapeSize.x += _delta.x; + t = _noShapeSize.x / (FIXED_INT_MULTIPLIER / 10); + _noShapeSize.x -= t * (FIXED_INT_MULTIPLIER / 10); + _position.x += t; + + _noShapeSize.y += _delta.y; + t = _noShapeSize.y / (FIXED_INT_MULTIPLIER / 10); + _noShapeSize.y -= t * (FIXED_INT_MULTIPLIER / 10); + _position.y += t; + } else if (IS_SERRATED_SCALPEL) { + // The delta is in whole pixels, so simply adjust the position with it + _position += _delta; + } + + if (_position.y > LOWER_LIMIT) + _position.y = LOWER_LIMIT; + + if (_type != NO_SHAPE) { + int frame = _frameNumber; + if (frame == -1) + frame = 0; + + int imgNum = _sequences[frame]; + if (imgNum > _maxFrames) + imgNum = 1; + + _imageFrame = &(*_images)[imgNum - 1]; + } +} + +int Object::pickUpObject(FixedTextActionId fixedTextActionId) { + FixedText &fixedText = *_vm->_fixedText; + Inventory &inv = *_vm->_inventory; + People &people = *_vm->_people; + Scene &scene = *_vm->_scene; + Screen &screen = *_vm->_screen; + Talk &talk = *_vm->_talk; + UserInterface &ui = *_vm->_ui; + int pickup = _pickup & 0x7f; + bool printed = false; + int numObjects = 0; + + if (pickup == 99) { + for (int idx = 0; idx < NAMES_COUNT && !talk._talkToAbort; ++idx) { + if (checkNameForCodes(_use[0]._names[idx], kFixedTextAction_Invalid)) { + if (!talk._talkToAbort) + printed = true; + } + } + + return 0; + } + + if (!pickup || (pickup > 50 && pickup <= 80)) { + int message = _pickup; + if (message > 50) + message -= 50; + + ui._infoFlag = true; + ui.clearInfo(); + Common::String errorMessage = fixedText.getActionMessage(fixedTextActionId, message); + screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", errorMessage.c_str()); + ui._menuCounter = 30; + } else { + // Pick it up + bool takeFlag = true; + if ((_pickup & 0x80) == 0) { + // Play an animation + if (pickup > 80) { + takeFlag = false; // Don't pick it up + scene.startCAnim(pickup - 81, 1); + if (_pickupFlag) + _vm->setFlags(_pickupFlag); + } else { + scene.startCAnim(pickup - 1, 1); + if (!talk._talkToAbort) { + // Erase the shape + _type = _type == NO_SHAPE ? INVALID : REMOVE; + } + } + + if (talk._talkToAbort) + return 0; + } else { + // Play generic pickup sequence + // Original moved cursor position here + people[HOLMES].goAllTheWay(); + ui._menuCounter = 25; + ui._temp1 = 1; + } + + for (int idx = 0; idx < NAMES_COUNT && !talk._talkToAbort; ++idx) { + if (checkNameForCodes(_use[0]._names[idx], kFixedTextAction_Invalid)) { + if (!talk._talkToAbort) + printed = true; + } + } + if (talk._talkToAbort) + return 0; + + // Add the item to the player's inventory + if (takeFlag) + numObjects = inv.putItemInInventory(*this); + + if (!printed) { + ui._infoFlag = true; + ui.clearInfo(); + + Common::String itemName = _description; + itemName.setChar(tolower(itemName[0]), 0); + screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "Picked up %s", itemName.c_str()); + ui._menuCounter = 25; + } + } + + return numObjects; +} + +const Common::Rect Object::getNewBounds() const { + Point32 pt = _position; + if (_imageFrame) + pt += _imageFrame->_offset; + + return Common::Rect(pt.x, pt.y, pt.x + frameWidth(), pt.y + frameHeight()); +} + +const Common::Rect Object::getNoShapeBounds() const { + return Common::Rect(_position.x, _position.y, + _position.x + _noShapeSize.x, _position.y + _noShapeSize.y); +} + +const Common::Rect Object::getOldBounds() const { + return Common::Rect(_oldPosition.x, _oldPosition.y, + _oldPosition.x + _oldSize.x, _oldPosition.y + _oldSize.y); +} + +/*----------------------------------------------------------------*/ + +void CAnim::load(Common::SeekableReadStream &s, bool isRoseTattoo, uint32 dataOffset) { + char buffer[12]; + s.read(buffer, 12); + _name = Common::String(buffer); + + if (isRoseTattoo) { + Common::fill(&_sequences[0], &_sequences[30], 0); + _dataSize = s.readUint32LE(); + } else { + s.read(_sequences, 30); + } + + _position.x = s.readSint16LE(); + _position.y = s.readSint16LE(); + + if (isRoseTattoo) { + _flags = s.readByte(); + _scaleVal = s.readSint16LE(); + } else { + _dataSize = s.readUint32LE(); + _type = (SpriteType)s.readUint16LE(); + _flags = s.readByte(); + } + + _goto[0].x = s.readSint16LE(); + _goto[0].y = s.readSint16LE(); + _goto[0]._facing = s.readSint16LE(); + + if (isRoseTattoo) { + // Get Goto position and facing for second NPC + _goto[1].x = s.readSint16LE(); + _goto[1].y = s.readSint16LE(); + _goto[1]._facing = s.readSint16LE(); + } else { + // For Serrated Scalpel, adjust the loaded co-ordinates + _goto[0].x = _goto[0].x * FIXED_INT_MULTIPLIER / 100; + _goto[0].y = _goto[0].y * FIXED_INT_MULTIPLIER / 100; + + } + + _teleport[0].x = s.readSint16LE(); + _teleport[0].y = s.readSint16LE(); + _teleport[0]._facing = s.readSint16LE(); + + if (isRoseTattoo) { + // Get Teleport position and facing for second NPC + _teleport[1].x = s.readSint16LE(); + _teleport[1].y = s.readSint16LE(); + _teleport[1]._facing = s.readSint16LE(); + } else { + // For Serrated Scalpel, adjust the loaded co-ordinates + _teleport[0].x = _teleport[0].x * FIXED_INT_MULTIPLIER / 100; + _teleport[0].y = _teleport[0].y * FIXED_INT_MULTIPLIER / 100; + } + + // Save offset of data, which is actually inside another table inside the room data file + // This table is at offset 44 for Serrated Scalpel + // TODO: find it for the other game + _dataOffset = dataOffset; +} + +void CAnim::load3DO(Common::SeekableReadStream &s, uint32 dataOffset) { + // this got reordered on 3DO + // maybe it was the 3DO compiler + + _dataSize = s.readUint32BE(); + // Save offset of data, which is inside another table inside the room data file + _dataOffset = dataOffset; + + _position.x = s.readSint16BE(); + _position.y = s.readSint16BE(); + + _type = (SpriteType)s.readUint16BE(); + + _goto[0].x = s.readSint16BE(); + _goto[0].y = s.readSint16BE(); + _goto[0]._facing = s.readSint16BE(); + + _teleport[0].x = s.readSint16BE(); + _teleport[0].y = s.readSint16BE(); + _teleport[0]._facing = s.readSint16BE(); + + char buffer[12]; + s.read(buffer, 12); + _name = Common::String(buffer); + + s.read(_sequences, 30); + _flags = s.readByte(); + + s.skip(3); // Filler + + _goto[0].x = _goto[0].x * FIXED_INT_MULTIPLIER / 100; + _goto[0].y = _goto[0].y * FIXED_INT_MULTIPLIER / 100; + _teleport[0].x = _teleport[0].x * FIXED_INT_MULTIPLIER / 100; + _teleport[0].y = _teleport[0].y * FIXED_INT_MULTIPLIER / 100; +} + +/*----------------------------------------------------------------*/ + +CAnimStream::CAnimStream() { + _images = nullptr; + _imageFrame = nullptr; + _frameNumber = 0; + _flags = 0; + _scaleVal = 0; + _zPlacement = 0; +} + +CAnimStream::~CAnimStream() { + delete _images; +} + +void CAnimStream::load(Common::SeekableReadStream *stream) { + delete _images; + _images = new ImageFile(*stream, false); + _imageFrame = &(*_images)[0]; + _frameNumber = 0; +} + +void CAnimStream::close() { + delete _images; + _images = nullptr; + _imageFrame = nullptr; + _frameNumber = 0; +} + +void CAnimStream::getNextFrame() { + if (++_frameNumber < (int)_images->size()) + _imageFrame = &(*_images)[_frameNumber]; + else + _imageFrame = nullptr; +} + +/*----------------------------------------------------------------*/ + +SceneImage::SceneImage() { + _images = nullptr; + _maxFrames = 0; + _filesize = 0; +} + +} // End of namespace Sherlock diff --git a/engines/sherlock/objects.h b/engines/sherlock/objects.h new file mode 100644 index 0000000000..f29c7890ab --- /dev/null +++ b/engines/sherlock/objects.h @@ -0,0 +1,509 @@ +/* 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 SHERLOCK_OBJECTS_H +#define SHERLOCK_OBJECTS_H + +#include "common/scummsys.h" +#include "common/rect.h" +#include "common/str-array.h" +#include "common/str.h" +#include "sherlock/image_file.h" +#include "sherlock/fixed_text.h" + +namespace Sherlock { + +class SherlockEngine; + +enum ObjectAllow { + ALLOW_MOVE = 1, ALLOW_OPEN = 2, ALLOW_CLOSE = 4 +}; + +enum SpriteType { + INVALID = 0, + CHARACTER = 1, + CURSOR = 2, + STATIC_BG_SHAPE = 3, // Background shape that doesn't animate + ACTIVE_BG_SHAPE = 4, // Background shape that animates + REMOVE = 5, // Object should be removed next frame + NO_SHAPE = 6, // Background object with no shape + HIDDEN = 7, // Hidden backgruond object + HIDE_SHAPE = 8, // Object needs to be hidden + + // Rose Tattoo + HIDDEN_CHARACTER = 128 +}; + +enum AType { + OBJECT = 0, + PERSON = 1, + SOLID = 2, + TALK = 3, // Standard talk zone + FLAG_SET = 4, + DELTA = 5, + WALK_AROUND = 6, + TALK_EVERY = 7, // Talk zone that turns on every room visit + TALK_MOVE = 8, // Talk zone that only activates when Holmes moves + PAL_CHANGE = 9, // Changes the palette down so that it gets darker + PAL_CHANGE2 = 10, // Same as PAL_CHANGE, except that it goes up + SCRIPT_ZONE = 11, // If this is clicked in, it is activated + BLANK_ZONE = 12, // This masks out other objects when entered + NOWALK_ZONE = 13 // Player cannot walk here +}; + +// Different levels for sprites to be at +enum { + BEHIND = 0, NORMAL_BEHIND = 1, NORMAL_FORWARD = 2, FORWARD = 3 +}; + +#define MAX_HOLMES_SEQUENCE 16 +#define MAX_FRAME 30 +#define FIXED_INT_MULTIPLIER 1000 + +// code put into sequences to defines 1-10 type seqs +#define SEQ_TO_CODE 67 +#define FLIP_CODE (64 + 128) +#define SOUND_CODE (34 + 128) +#define HIDE_CODE (7+128) // Code for hiding/unhiding an object from a Sequence +#define CALL_TALK_CODE (8+128) // Code for call a Talk File from a Sequence +#define TELEPORT_CODE (9+128) // Code for setting Teleport Data (X,Y) +#define MOVE_CODE (10+128) // Code for setting Movement Delta (X,Y) + +#define GOTO_CODE 228 +#define TALK_SEQ_CODE 252 // Code specifying start of talk sequence frames in a Sequence +#define TALK_LISTEN_CODE 251 // Code specifying start of talk listen frames in a Sequence +#define ALLOW_TALK_CODE 250 + +#define UPPER_LIMIT 0 +#define LOWER_LIMIT (IS_SERRATED_SCALPEL ? CONTROLS_Y : SHERLOCK_SCREEN_HEIGHT) +#define LEFT_LIMIT 0 +#define RIGHT_LIMIT SHERLOCK_SCREEN_WIDTH + +class Point32 { +public: + int x; + int y; + + Point32() : x(0), y(0) {} + Point32(int x1, int y1) : x(x1), y(y1) {} + Point32(const Common::Point &pt) : x(pt.x), y(pt.y) {} + + bool operator==(const Point32 &p) const { return x == p.x && y == p.y; } + bool operator!=(const Point32 &p) const { return x != p.x || y != p.y; } + Point32 operator+(const Point32 &delta) const { return Point32(x + delta.x, y + delta.y); } + Point32 operator-(const Point32 &delta) const { return Point32(x - delta.x, y - delta.y); } + operator Common::Point() { return Common::Point(x, y); } + + void operator+=(const Point32 &delta) { x += delta.x; y += delta.y; } + void operator-=(const Point32 &delta) { x -= delta.x; y -= delta.y; } +}; + + +struct WalkSequence { + Common::String _vgsName; + bool _horizFlip; + Common::Array<byte> _sequences; + + WalkSequence() : _horizFlip(false) {} + const byte &operator[](int idx) { return _sequences[idx]; } + + /** + * Load data for the sequence from a stream + */ + void load(Common::SeekableReadStream &s); +}; + +class WalkSequences : public Common::Array < WalkSequence > { +public: + WalkSequences &operator=(const WalkSequences &src); +}; + +enum { REVERSE_DIRECTION = 0x80 }; +#define NAMES_COUNT 4 + +struct ActionType { + int _cAnimNum; + int _cAnimSpeed; + Common::String _names[NAMES_COUNT]; + + ActionType(); + + /** + * Load the data for the action + */ + void load(Common::SeekableReadStream &s); +}; + +struct UseType: public ActionType { + int _useFlag; // Which flag USE will set (if any) + Common::String _target; + Common::String _verb; + + UseType(); + + /** + * Load the data for the UseType + */ + void load(Common::SeekableReadStream &s, bool isRoseTattoo); + void load3DO(Common::SeekableReadStream &s); +}; + +class BaseObject { +protected: + static SherlockEngine *_vm; +protected: + /** + * This will check to see if the object has reached the end of a sequence. + * If it has, it switch to whichever next sequence should be started. + * @returns true if the end of a sequence was reached + */ + bool checkEndOfSequence(); + + /** + * Scans through the sequences array and finds the designated sequence. + * It then sets the frame number of the start of that sequence + */ + void setObjSequence(int seq, bool wait); +public: + static bool _countCAnimFrames; +public: + SpriteType _type; // Type of object/sprite + Common::String _description; // Description lines + byte *_sequences; // Holds animation sequences + ImageFile *_images; // Sprite images + ImageFrame *_imageFrame; // Pointer to shape in the images + int _walkCount; // Walk counter + int _allow; // Allowed UI commands + int _frameNumber; // Frame number in rame sequence to draw + Point32 _position; // Current position + Point32 _delta; // Momvement amount + Common::Point _oldPosition; // Old position + Common::Point _oldSize; // Image's old size + Point32 _goto; // Walk destination + + int _lookFlag; // Which flag LOOK will set (if any) + int _requiredFlag; // Object will be hidden if not set + Common::Point _noShapeSize; // Size of a NO_SHAPE + int _status; // Status (open/closed, moved/not) + int8 _misc; // Misc field -- use varies with type + int _maxFrames; // Number of frames + int _flags; // Tells if object can be walked behind + AType _aType; // Tells if this is an object, person, talk, etc. + int _lookFrames; // How many frames to play of the look anim before pausing + int _seqCounter; // How many times this sequence has been executed + Point32 _lookPosition; // Where to walk when examining object + int _lookFacing; // Direction to face when examining object + int _lookcAnim; + int _seqStack; // Allows gosubs to return to calling frame + int _seqTo; // Allows 1-5, 8-3 type sequences encoded in 2 bytes + uint _descOffset; // Tells where description starts in DescText + int _seqCounter2; // Counter of calling frame sequence + uint _seqSize; // Tells where description starts + UseType _use[6]; // Serrated Scalpel uses 4, Rose Tattoo 6 + int _quickDraw; // Flag telling whether to use quick draw routine or not + int _scaleVal; // Tells how to scale the sprite + int _requiredFlags1; // This flag must also be set, or the sprite is hidden + int _gotoSeq; // Used by Talk to tell which sequence to goto when able + int _talkSeq; // Tells which talk sequence currently in use (Talk or Listen) + int _restoreSlot; // Used when talk returns to the previous sequence +public: + BaseObject(); + virtual ~BaseObject() {} + static void setVm(SherlockEngine *vm); + + /** + * Returns true if the the object has an Allow Talk Code in the sequence that it's + * currently running, specified by the _talkSeq field of the object. If it's 0, + * then it's a regular sequence. If it's not 0 but below 128, then it's a Talk Sequence. + * If it's above 128, then it's one of the Listen sequences. + */ + bool hasAborts() const; + + /** + * Check the state of the object + */ + void checkObject(); + + /** + * Checks for codes + * @param name The name to check for codes + * @param messages Provides a lookup list of messages that can be printed + * @returns 0 if no codes are found, 1 if codes were found + */ + int checkNameForCodes(const Common::String &name, FixedTextActionId fixedTextActionId = kFixedTextAction_Invalid); + + /** + * Adjusts the frame and sequence variables of a sprite that corresponds to the current speaker + * so that it points to the beginning of the sequence number's talk sequence in the object's + * sequence buffer + * @param seq Which sequence to use (if there's more than 1) + * @remarks 1: First talk seq, 2: second talk seq, etc. + */ + virtual void setObjTalkSequence(int seq) {} +}; + +class Sprite: public BaseObject { +public: + Common::String _name; + Common::String _examine; // Examine in-depth description + Common::String _pickUp; // Message for if you can't pick up object + + WalkSequences _walkSequences; // Holds animation sequences + int _sequenceNumber; // Sequence being used + Common::Point _noShapeSize; // Size of a NO_SHAPE + int _status; // Status: open/closed, moved/not moved + int8 _misc; // Miscellaneous use + + // Rose Tattoo fields + int _startSeq; // Frame sequence starts at + ImageFrame *_stopFrames[8]; // Stop/rest frame for each direction + ImageFile *_altImages; // Images used for alternate NPC sequences + int _altSeq; // Which of the sequences the alt graphics apply to (0: main, 1=NPC seq) + int _centerWalk; // Flag telling the walk code to offset the walk destination + Common::Point _adjust; // Fine tuning adjustment to position when drawn + int _oldWalkSequence; +public: + Sprite(): BaseObject() { clear(); } + virtual ~Sprite() {} + + static void setVm(SherlockEngine *vm) { _vm = vm; } + + /** + * Reset the data for the sprite + */ + void clear(); + + /** + * Updates the image frame poiner for the sprite + */ + void setImageFrame(); + + /** + * Checks the sprite's position to see if it's collided with any special objects + */ + void checkSprite(); + + /** + * Adjusts the frame and sequence variables of a sprite that corresponds to the current speaker + * so that it points to the beginning of the sequence number's talk sequence in the object's + * sequence buffer + * @param seq Which sequence to use (if there's more than 1) + * @remarks 1: First talk seq, 2: second talk seq, etc. + */ + virtual void setObjTalkSequence(int seq) {} + + /** + * Return frame width + */ + int frameWidth() const { return _imageFrame ? _imageFrame->_frame.w : 0; } + + /** + * Return frame height + */ + int frameHeight() const { return _imageFrame ? _imageFrame->_frame.h : 0; } + + /** + * Returns the old bounsd for the sprite from the previous frame + */ + const Common::Rect getOldBounds() const; + + /** + * This adjusts the sprites position, as well as it's animation sequence: + */ + virtual void adjustSprite() = 0; + + /** + * Bring a moving character using the sprite to a standing position + */ + virtual void gotoStand() = 0; + + /** + * Set the variables for moving a character from one poisition to another + * in a straight line + */ + virtual void setWalking() = 0; +}; + +enum { OBJ_BEHIND = 1, OBJ_FLIPPED = 2, OBJ_FORWARD = 4, TURNON_OBJ = 0x20, TURNOFF_OBJ = 0x40 }; +#define USE_COUNT 4 + +class Object: public BaseObject { +public: + Common::String _name; // Name + Common::String _examine; // Examine in-depth description + int _sequenceNumber; + int _sequenceOffset; + int _pickup; + int _defaultCommand; // Default right-click command + + // Serrated Scalpel fields + int _pickupFlag; // Which flag PICKUP will set (if any) + ActionType _aOpen; // Holds data for moving object + ActionType _aClose; + ActionType _aMove; + + Object(); + virtual ~Object() {} + + /** + * Load the data for the object + */ + void load(Common::SeekableReadStream &s, bool isRoseTattoo); + void load3DO(Common::SeekableReadStream &s); + + /** + * Toggle the type of an object between hidden and active + */ + void toggleHidden(); + + /** + * Handle setting any flags associated with the object + */ + void setFlagsAndToggles(); + + /** + * Adjusts the sprite's position and animation sequence, advancing by 1 frame. + * If the end of the sequence is reached, the appropriate action is taken. + */ + void adjustObject(); + + /** + * Handles trying to pick up an object. If allowed, plays an y necessary animation for picking + * up the item, and then adds it to the player's inventory + */ + int pickUpObject(FixedTextActionId fixedTextActionId = kFixedTextAction_Invalid); + + /** + * Return the frame width + */ + int frameWidth() const { return _imageFrame ? _imageFrame->_frame.w : 0; } + + /** + * Return the frame height + */ + int frameHeight() const { return _imageFrame ? _imageFrame->_frame.h : 0; } + + /** + * Returns the current bounds for the sprite + */ + const Common::Rect getNewBounds() const; + + /** + * Returns the bounds for a sprite without a shape + */ + const Common::Rect getNoShapeBounds() const; + + /** + * Returns the old bounsd for the sprite from the previous frame + */ + const Common::Rect getOldBounds() const; + + /** + * Adjusts the frame and sequence variables of a sprite that corresponds to the current speaker + * so that it points to the beginning of the sequence number's talk sequence in the object's + * sequence buffer + * @param seq Which sequence to use (if there's more than 1) + * @remarks 1: First talk seq, 2: second talk seq, etc. + */ + virtual void setObjTalkSequence(int seq); +}; + + +class PositionFacing : public Point32 { +public: + int _facing; + + PositionFacing() : Point32(), _facing(0) {} +}; + +struct CAnim { + Common::String _name; // Name + Common::Point _position; // Position + int _dataSize; // Size of uncompressed animation data + uint32 _dataOffset; // offset within room file of animation data + int _flags; // Tells if can be walked behind + PositionFacing _goto[2]; // Position Holmes (and NPC in Rose Tattoo) should walk to before anim starts + PositionFacing _teleport[2]; // Location Holmes (and NPC) shoul teleport to after playing canim + + // Scalpel specific + byte _sequences[MAX_FRAME]; // Animation sequences + SpriteType _type; + + // Rose Tattoo specific + int _scaleVal; // How much the canim is scaled + + /** + * Load the data for the animation + */ + void load(Common::SeekableReadStream &s, bool isRoseTattoo, uint32 dataOffset); + void load3DO(Common::SeekableReadStream &s, uint32 dataOffset); +}; + +class CAnimStream { + ImageFile *_images; + int _frameNumber; +public: + ImageFrame *_imageFrame; + + Common::Point _position; // Animation position + Common::Rect _oldBounds; // Bounds of previous frame + Common::Rect _removeBounds; // Remove area for just drawn frame + + int _flags; // Flags + int _scaleVal; // Specifies the scale amount + int _zPlacement; // Used by doBgAnim for determining Z order +public: + CAnimStream(); + ~CAnimStream(); + + /** + * Load the animation's images + */ + void load(Common::SeekableReadStream *stream); + + /** + * Close any currently active animation + */ + void close(); + + /** + * Get the next frame of the animation + */ + void getNextFrame(); + + /** + * Returns whether the animation is active + */ + bool active() const { return _imageFrame != nullptr; } +}; + +struct SceneImage { + ImageFile *_images; // Object images + int _maxFrames; // How many frames in object + int _filesize; // File size + + SceneImage(); +} ; + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/people.cpp b/engines/sherlock/people.cpp new file mode 100644 index 0000000000..cab4abff89 --- /dev/null +++ b/engines/sherlock/people.cpp @@ -0,0 +1,344 @@ +/* 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 "sherlock/people.h" +#include "sherlock/sherlock.h" +#include "sherlock/scalpel/scalpel_people.h" +#include "sherlock/tattoo/tattoo_people.h" + +namespace Sherlock { + +// Characer animation sequences +static const uint8 CHARACTER_SEQUENCES[MAX_HOLMES_SEQUENCE][MAX_FRAME] = { + { 29, 1, 2, 3, 4, 5, 6, 7, 0 }, // Walk Right + { 22, 1, 2, 3, 4, 5, 6, 7, 0 }, // Walk Down + { 29, 1, 2, 3, 4, 5, 6, 7, 0 }, // Walk Left + { 15, 1, 2, 3, 4, 5, 6, 7, 0 }, // Walk Up + { 42, 1, 2, 3, 4, 5, 0 }, // Goto Stand Right + { 47, 1, 2, 3, 4, 5, 0 }, // Goto Stand Down + { 42, 1, 2, 3, 4, 5, 0 }, // Goto Stand Left + { 36, 1, 0 }, // Goto Stand Up + { 8, 1, 2, 3, 4, 5, 6, 7, 0 }, // Walk Up Right + { 1, 1, 2, 3, 4, 5, 6, 7, 0 }, // Walk Down Right + { 8, 1, 2, 3, 4, 5, 6, 7, 0 }, // Walk Up Left + { 1, 1, 2, 3, 4, 5, 6, 7, 0 }, // Walk Down Left + { 37, 1, 2, 3, 4, 5, 0 }, // Goto Stand Up Right + { 37, 1, 2, 3, 4, 5, 0 }, // Goto Stand Up Left + { 52, 1, 2, 3, 4, 0 }, // Goto Stand Down Right + { 52, 1, 2, 3, 4, 0 } // Goto Stand Down Left +}; + +// Rose Tattoo walk image libraries +// Walk resources within WALK.LIB +const char *const WALK_LIB_NAMES[NUM_IN_WALK_LIB] = { + "SVGAWALK.VGS", + "COATWALK.VGS", + "WATSON.VGS", + "NOHAT.VGS", + "TUPRIGHT.VGS", + "TRIGHT.VGS", + "TDOWNRG.VGS", + "TWUPRIGH.VGS", + "TWRIGHT.VGS", + "TWDOWNRG.VGS" +}; + +/*----------------------------------------------------------------*/ + +Person::Person() : Sprite() { + _walkLoaded = false; + _oldWalkSequence = -1; + _srcZone = _destZone = 0; +} + +void Person::goAllTheWay() { + Scene &scene = *_vm->_scene; + Common::Point srcPt = getSourcePoint(); + + // Get the zone the player is currently in + _srcZone = scene.whichZone(srcPt); + if (_srcZone == -1) + _srcZone = scene.closestZone(srcPt); + + // Get the zone of the destination + _destZone = scene.whichZone(_walkDest); + if (_destZone == -1) { + _destZone = scene.closestZone(_walkDest); + + // The destination isn't in a zone + if (_walkDest.x >= (SHERLOCK_SCREEN_WIDTH - 1)) + _walkDest.x = SHERLOCK_SCREEN_WIDTH - 2; + + // Trace a line between the centroid of the found closest zone to + // the destination, to find the point at which the zone will be left + const Common::Rect &destRect = scene._zones[_destZone]; + const Common::Point destCenter((destRect.left + destRect.right) / 2, + (destRect.top + destRect.bottom) / 2); + const Common::Point delta = _walkDest - destCenter; + Point32 pt(destCenter.x * FIXED_INT_MULTIPLIER, destCenter.y * FIXED_INT_MULTIPLIER); + + // Move along the line until the zone is left + do { + pt += delta; + } while (destRect.contains(pt.x / FIXED_INT_MULTIPLIER, pt.y / FIXED_INT_MULTIPLIER)); + + // Set the new walk destination to the last point that was in the + // zone just before it was left + _walkDest = Common::Point((pt.x - delta.x * 2) / FIXED_INT_MULTIPLIER, + (pt.y - delta.y * 2) / FIXED_INT_MULTIPLIER); + } + + // Only do a walk if both zones are acceptable + if (_srcZone == -2 || _destZone == -2) + return; + + // If the start and dest zones are the same, walk directly to the dest point + if (_srcZone == _destZone) { + setWalking(); + } else { + // Otherwise a path needs to be formed from the path information + int i = scene._walkDirectory[_srcZone][_destZone]; + + // See if we need to use a reverse path + if (i == -1) + i = scene._walkDirectory[_destZone][_srcZone]; + + const WalkArray &points = scene._walkPoints[i]; + + // See how many points there are between the src and dest zones + if (!points._pointsCount || points._pointsCount == -1) { + // There are none, so just walk to the new zone + setWalking(); + } else { + // There are points, so set up a multi-step path between points + // to reach the given destination + _walkTo.clear(); + + if (scene._walkDirectory[_srcZone][_destZone] != -1) { + for (int idx = (int)points.size() - 1; idx >= 0; --idx) + _walkTo.push(points[idx]); + } else { + for (int idx = 0; idx < (int)points.size(); ++idx) { + _walkTo.push(points[idx]); + } + } + + // Final position + _walkTo.push(_walkDest); + + // Start walking + _walkDest = _walkTo.pop(); + setWalking(); + } + } +} + +/*----------------------------------------------------------------*/ + +People *People::init(SherlockEngine *vm) { + if (vm->getGameID() == GType_SerratedScalpel) + return new Scalpel::ScalpelPeople(vm); + else + return new Tattoo::TattooPeople(vm); +} + +People::People(SherlockEngine *vm) : _vm(vm) { + _holmesOn = true; + _allowWalkAbort = true; + _portraitLoaded = false; + _portraitsOn = true; + _clearingThePortrait = false; + _talkPics = nullptr; + _portraitSide = 0; + _speakerFlip = false; + _holmesFlip = false; + _holmesQuotient = 0; + _hSavedPos = Point32(-1, -1); + _hSavedFacing = -1; + _forceWalkReload = false; + _useWalkLib = false; + _walkControl = 0; + + _portrait._sequences = new byte[32]; +} + +People::~People() { + for (uint idx = 0; idx < _data.size(); ++idx) { + if (_data[idx]->_walkLoaded) + delete _data[idx]->_images; + delete _data[idx]; + } + + delete _talkPics; + delete[] _portrait._sequences; +} + +void People::reset() { + _data[HOLMES]->_description = "Sherlock Holmes!"; + + // Note: Serrated Scalpel only uses a single Person slot for Sherlock.. Watson is handled by scene sprites + int count = IS_SERRATED_SCALPEL ? 1 : MAX_CHARACTERS; + for (int idx = 0; idx < count; ++idx) { + Person &p = *_data[idx]; + + p._type = (idx == 0) ? CHARACTER : INVALID; + if (IS_SERRATED_SCALPEL) + p._position = Point32(100 * FIXED_INT_MULTIPLIER, 110 * FIXED_INT_MULTIPLIER); + else + p._position = Point32(36 * FIXED_INT_MULTIPLIER, 29 * FIXED_INT_MULTIPLIER); + + p._sequenceNumber = IS_SERRATED_SCALPEL ? (int)Scalpel::STOP_DOWNRIGHT : (int)Tattoo::STOP_DOWNRIGHT; + p._imageFrame = nullptr; + p._frameNumber = 1; + p._delta = Point32(0, 0); + p._oldPosition = Common::Point(0, 0); + p._oldSize = Common::Point(0, 0); + p._misc = 0; + p._walkCount = 0; + p._pickUp = ""; + p._allow = 0; + p._noShapeSize = Common::Point(0, 0); + p._goto = Common::Point(0, 0); + p._status = 0; + p._seqTo = 0; + p._seqCounter = p._seqCounter2 = 0; + p._seqStack = 0; + p._gotoSeq = p._talkSeq = 0; + p._restoreSlot = 0; + p._startSeq = 0; + p._altImages = nullptr; + p._altSeq = 0; + p._centerWalk = true; + p._adjust = Common::Point(0, 0); + + // Load the default walk sequences + p._walkTo.clear(); + p._oldWalkSequence = -1; + p._walkSequences.clear(); + if (IS_SERRATED_SCALPEL) { + p._walkSequences.resize(MAX_HOLMES_SEQUENCE); + for (int seqIdx = 0; seqIdx < MAX_HOLMES_SEQUENCE; ++seqIdx) { + p._walkSequences[seqIdx]._sequences.clear(); + + const byte *pSrc = &CHARACTER_SEQUENCES[seqIdx][0]; + do { + p._walkSequences[seqIdx]._sequences.push_back(*pSrc); + } while (*pSrc++); + } + } + } +} + +bool People::freeWalk() { + bool result = false; + + for (int idx = 0; idx < MAX_CHARACTERS; ++idx) { + if (_data[idx]->_walkLoaded) { + delete _data[idx]->_images; + _data[idx]->_images = nullptr; + + _data[idx]->_walkLoaded = false; + result = true; + } + } + + return result; +} + +int People::findSpeaker(int speaker) { + Scene &scene = *_vm->_scene; + const char *portrait = _characters[speaker]._portrait; + + for (int idx = 0; idx < (int)scene._bgShapes.size(); ++idx) { + Object &obj = scene._bgShapes[idx]; + + if (obj._type == ACTIVE_BG_SHAPE) { + Common::String name(obj._name.c_str(), obj._name.c_str() + 4); + + if (name.equalsIgnoreCase(portrait) + && obj._name[4] >= '0' && obj._name[4] <= '9') + return idx; + } + } + + return -1; +} + +void People::clearTalking() { + Scene &scene = *_vm->_scene; + Screen &screen = *_vm->_screen; + Talk &talk = *_vm->_talk; + + if (_portraitsOn) { + Common::Point pt = _portrait._position; + int width, height; + _portrait._imageFrame = _talkPics ? &(*_talkPics)[0] : (ImageFrame *)nullptr; + + // Flag portrait for removal, and save the size of the frame to use erasing it + _portrait._type = REMOVE; + _portrait._delta.x = width = _portrait.frameWidth(); + _portrait._delta.y = height = _portrait.frameHeight(); + + delete _talkPics; + _talkPics = nullptr; + + // Flag to let the talk code know not to interrupt on the next doBgAnim + _clearingThePortrait = true; + scene.doBgAnim(); + _clearingThePortrait = false; + + screen.slamArea(pt.x, pt.y, width, height); + + if (!talk._talkToAbort) + _portraitLoaded = false; + } +} + +void People::synchronize(Serializer &s) { + s.syncAsByte(_holmesOn); + + if (IS_SERRATED_SCALPEL) { + s.syncAsSint16LE(_data[HOLMES]->_position.x); + s.syncAsSint16LE(_data[HOLMES]->_position.y); + s.syncAsSint16LE(_data[HOLMES]->_sequenceNumber); + } else { + for (uint idx = 0; idx < _data.size(); ++idx) { + Person &p = *_data[idx]; + s.syncAsSint16LE(p._position.x); + s.syncAsSint16LE(p._position.y); + s.syncAsSint16LE(p._sequenceNumber); + s.syncAsSint16LE(p._type); + s.syncString(p._walkVGSName); + s.syncString(p._description); + s.syncString(p._examine); + } + } + + s.syncAsSint16LE(_holmesQuotient); + + if (s.isLoading()) { + _hSavedPos = _data[HOLMES]->_position; + _hSavedFacing = _data[HOLMES]->_sequenceNumber; + } +} + +} // End of namespace Sherlock diff --git a/engines/sherlock/people.h b/engines/sherlock/people.h new file mode 100644 index 0000000000..257c9b3987 --- /dev/null +++ b/engines/sherlock/people.h @@ -0,0 +1,164 @@ +/* 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 SHERLOCK_PEOPLE_H +#define SHERLOCK_PEOPLE_H + +#include "common/scummsys.h" +#include "common/queue.h" +#include "sherlock/objects.h" +#include "sherlock/saveload.h" + +namespace Sherlock { + +enum PeopleId { + HOLMES = 0, + WATSON = 1, + MAX_NPC_PATH = 200 +}; + +enum { + MAP_UP = 1, MAP_UPRIGHT = 2, MAP_RIGHT = 1, MAP_DOWNRIGHT = 4, + MAP_DOWN = 5, MAP_DOWNLEFT = 6, MAP_LEFT = 2, MAP_UPLEFT = 8 +}; + +#define NUM_IN_WALK_LIB 10 +extern const char *const WALK_LIB_NAMES[10]; + +#define MAX_CHARACTERS (IS_SERRATED_SCALPEL ? 1 : 6) + +struct PersonData { + const char *_name; + const char *_portrait; + const byte *_stillSequences; + const byte *_talkSequences; + + PersonData(const char *name, const char *portrait, const byte *stillSequences, const byte *talkSequences) : + _name(name), _portrait(portrait), _stillSequences(stillSequences), _talkSequences(talkSequences) {} +}; + +class Person : public Sprite { +protected: + /** + * Get the source position for a character potentially affected by scaling + */ + virtual Common::Point getSourcePoint() const = 0; +public: + Common::Queue<Common::Point> _walkTo; + int _srcZone, _destZone; + bool _walkLoaded; + Common::String _portrait; + Common::Point _walkDest; + + // Rose Tattoo fields + Common::String _walkVGSName; // Name of walk library person is using +public: + Person(); + virtual ~Person() {} + + /** + * Called to set the character walking to the current cursor location. + * It uses the zones and the inter-zone points to determine a series + * of steps to walk to get to that position. + */ + void goAllTheWay(); + + /** + * Walk to the co-ordinates passed, and then face the given direction + */ + virtual void walkToCoords(const Point32 &destPos, int destDir) = 0; +}; + +class SherlockEngine; + +class People { +protected: + SherlockEngine *_vm; + Common::Array<Person *> _data; + + People(SherlockEngine *vm); +public: + Common::Array<PersonData> _characters; + ImageFile *_talkPics; + Point32 _hSavedPos; + int _hSavedFacing; + bool _holmesOn; + bool _portraitLoaded; + bool _portraitsOn; + Object _portrait; + bool _clearingThePortrait; + bool _allowWalkAbort; + int _portraitSide; + bool _speakerFlip; + bool _holmesFlip; + int _holmesQuotient; + bool _forceWalkReload; + bool _useWalkLib; + + int _walkControl; +public: + static People *init(SherlockEngine *vm); + virtual ~People(); + + Person &operator[](PeopleId id) { return *_data[id]; } + Person &operator[](int idx) { return *_data[idx]; } + + /** + * Reset the player data + */ + void reset(); + + /** + * If the walk data has been loaded, then it will be freed + */ + bool freeWalk(); + + /** + * Turn off any currently active portraits, and removes them from being drawn + */ + void clearTalking(); + + /** + * Finds the scene background object corresponding to a specified speaker + */ + virtual int findSpeaker(int speaker); + + /** + * Synchronize the data for a savegame + */ + virtual void synchronize(Serializer &s) = 0; + + /** + * Change the sequence of the scene background object associated with the current speaker. + */ + virtual void setTalkSequence(int speaker, int sequenceNum = 1) = 0; + + /** + * Load the walking images for Sherlock + */ + virtual bool loadWalk() = 0; + +}; + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/resources.cpp b/engines/sherlock/resources.cpp new file mode 100644 index 0000000000..5aea8cc071 --- /dev/null +++ b/engines/sherlock/resources.cpp @@ -0,0 +1,372 @@ +/* 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 "sherlock/resources.h" +#include "sherlock/screen.h" +#include "sherlock/sherlock.h" +#include "common/debug.h" +#include "common/memstream.h" + +namespace Sherlock { + +Cache::Cache(SherlockEngine *vm) : _vm(vm) { +} + +bool Cache::isCached(const Common::String &filename) const { + return _resources.contains(filename); +} + +void Cache::load(const Common::String &name) { + // First check if the entry already exists + if (_resources.contains(name)) + return; + + // Open the file for reading + Common::File f; + if (!f.open(name)) + error("Could not read file - %s", name.c_str()); + + load(name, f); + + f.close(); +} + +void Cache::load(const Common::String &name, Common::SeekableReadStream &stream) { + // First check if the entry already exists + if (_resources.contains(name)) + return; + + int32 signature = stream.readUint32BE(); + stream.seek(0); + + // Allocate a new cache entry + _resources[name] = CacheEntry(); + CacheEntry &cacheEntry = _resources[name]; + + // Check whether the file is compressed + if (signature == MKTAG('L', 'Z', 'V', 26)) { + // It's compressed, so decompress the file and store it's data in the cache entry + Common::SeekableReadStream *decompressed = _vm->_res->decompress(stream); + cacheEntry.resize(decompressed->size()); + decompressed->read(&cacheEntry[0], decompressed->size()); + + delete decompressed; + } else { + // It's not, so read the raw data of the file into the cache entry + cacheEntry.resize(stream.size()); + stream.read(&cacheEntry[0], stream.size()); + } +} + +Common::SeekableReadStream *Cache::get(const Common::String &filename) const { + // Return a memory stream that encapsulates the data + const CacheEntry &cacheEntry = _resources[filename]; + return new Common::MemoryReadStream(&cacheEntry[0], cacheEntry.size()); +} + +/*----------------------------------------------------------------*/ + +Resources::Resources(SherlockEngine *vm) : _vm(vm), _cache(vm) { + _resourceIndex = -1; + + if (_vm->_interactiveFl) { + if (!IS_3DO) { + addToCache("vgs.lib"); + addToCache("talk.lib"); + addToCache("journal.txt"); + + if (IS_SERRATED_SCALPEL) { + addToCache("sequence.txt"); + addToCache("portrait.lib"); + } else { + addToCache("walk.lib"); + } + } else { + // 3DO + + // ITEM data from VGS.LIB is in ITEM.LIB + addToCache("item.lib"); + + // talk.lib - resources themselves seem to be the same, although a few texts were slightly changed + addToCache("talk.lib"); + + // remaining files are missing + // portraits were replaced with FMV + } + } +} + +void Resources::addToCache(const Common::String &filename) { + _cache.load(filename); + + // Check to see if the file is a library + Common::SeekableReadStream *stream = load(filename); + uint32 header = stream->readUint32BE(); + + if (header == MKTAG('L', 'I', 'B', 26)) + loadLibraryIndex(filename, stream, false); + else if (header == MKTAG('L', 'I', 'C', 26)) + loadLibraryIndex(filename, stream, true); + + delete stream; +} + +void Resources::addToCache(const Common::String &filename, const Common::String &libFilename) { + // Get the resource + Common::SeekableReadStream *stream = load(filename, libFilename); + + _cache.load(filename, *stream); + + delete stream; +} + +void Resources::addToCache(const Common::String &filename, Common::SeekableReadStream &stream) { + _cache.load(filename, stream); +} + +Common::SeekableReadStream *Resources::load(const Common::String &filename) { + // First check if the file is directly in the cache + if (_cache.isCached(filename)) + return _cache.get(filename); + + // Secondly, iterate through any loaded library file looking for a resource + // that has the same name + for (LibraryIndexes::iterator i = _indexes.begin(); i != _indexes.end(); ++i) { + if (i->_value.contains(filename)) { + // Get a stream reference to the given library file + Common::SeekableReadStream *stream = load(i->_key); + LibraryEntry &entry = i->_value[filename]; + _resourceIndex = entry._index; + + stream->seek(entry._offset); + Common::SeekableReadStream *resStream = stream->readStream(entry._size); + decompressIfNecessary(resStream); + + delete stream; + return resStream; + } + } + + // At this point, fall back on a physical file with the given name + Common::File f; + if (!f.open(filename)) + error("Could not load file - %s", filename.c_str()); + + Common::SeekableReadStream *stream = f.readStream(f.size()); + f.close(); + decompressIfNecessary(stream); + + return stream; +} + +void Resources::decompressIfNecessary(Common::SeekableReadStream *&stream) { + bool isCompressed = stream->readUint32BE() == MKTAG('L', 'Z', 'V', 26); + + if (isCompressed) { + int outSize = stream->readUint32LE(); + Common::SeekableReadStream *newStream = decompressLZ(*stream, outSize); + delete stream; + stream = newStream; + } else { + stream->seek(-4, SEEK_CUR); + } +} + +Common::SeekableReadStream *Resources::load(const Common::String &filename, const Common::String &libraryFile) { + // Open up the library for access + Common::SeekableReadStream *libStream = load(libraryFile); + + // Check if the library has already had it's index read, and if not, load it + if (!_indexes.contains(libraryFile)) + loadLibraryIndex(libraryFile, libStream, false); + + // Extract the data for the specified resource and return it + LibraryEntry &entry = _indexes[libraryFile][filename]; + libStream->seek(entry._offset); + Common::SeekableReadStream *stream = libStream->readStream(entry._size); + decompressIfNecessary(stream); + + delete libStream; + return stream; +} + +bool Resources::exists(const Common::String &filename) const { + Common::File f; + return f.exists(filename) || _cache.isCached(filename); +} + +void Resources::loadLibraryIndex(const Common::String &libFilename, + Common::SeekableReadStream *stream, bool isNewStyle) { + uint32 offset, nextOffset; + + // Create an index entry + _indexes[libFilename] = LibraryIndex(); + LibraryIndex &index = _indexes[libFilename]; + + // Read in the number of resources + stream->seek(4); + int count = 0; + + if (!IS_3DO) { + // PC + count = stream->readUint16LE(); + + if (isNewStyle) + stream->seek((count + 1) * 8, SEEK_CUR); + + // Loop through reading in the entries + for (int idx = 0; idx < count; ++idx) { + // Read the name of the resource + char resName[13]; + stream->read(resName, 13); + resName[12] = '\0'; + + // Read the offset + offset = stream->readUint32LE(); + + if (idx == (count - 1)) { + nextOffset = stream->size(); + } else { + // Read the size by jumping forward to read the next entry's offset + stream->seek(13, SEEK_CUR); + nextOffset = stream->readUint32LE(); + stream->seek(-17, SEEK_CUR); + } + + // Add the entry to the index + index[resName] = LibraryEntry(idx, offset, nextOffset - offset); + } + + } else { + // 3DO + count = stream->readUint16BE(); + + // 3DO header + // Loop through reading in the entries + + // Read offset of first entry + offset = stream->readUint32BE(); + + for (int idx = 0; idx < count; ++idx) { + + // Read the name of the resource + char resName[13]; + stream->read(resName, 13); + resName[12] = '\0'; + + stream->skip(3); // filler + + if (idx == (count - 1)) { + nextOffset = stream->size(); + } else { + // Read the offset of the next entry + nextOffset = stream->readUint32BE(); + } + + // Add the entry to the index + index[resName] = LibraryEntry(idx, offset, nextOffset - offset); + + // use next offset as current offset + offset = nextOffset; + } + } +} + +int Resources::resourceIndex() const { + return _resourceIndex; +} + +Common::SeekableReadStream *Resources::decompress(Common::SeekableReadStream &source) { + // This variation can't be used by Rose Tattoo, since compressed resources include the input size, + // not the output size. Which means their decompression has to be done via passed buffers + assert(_vm->getGameID() == GType_SerratedScalpel); + + uint32 id = source.readUint32BE(); + assert(id == MKTAG('L', 'Z', 'V', 0x1A)); + + uint32 outputSize = source.readUint32LE(); + return decompressLZ(source, outputSize); +} + +Common::SeekableReadStream *Resources::decompress(Common::SeekableReadStream &source, uint32 outSize) { + int inSize = (_vm->getGameID() == GType_RoseTattoo) ? source.readSint32LE() : -1; + byte *outBuffer = (byte *)malloc(outSize); + Common::MemoryReadStream *outStream = new Common::MemoryReadStream(outBuffer, outSize, DisposeAfterUse::YES); + + decompressLZ(source, outBuffer, outSize, inSize); + + return outStream; +} + +void Resources::decompress(Common::SeekableReadStream &source, byte *buffer, uint32 outSize) { + int inputSize = (_vm->getGameID() == GType_RoseTattoo) ? source.readSint32LE() : -1; + + decompressLZ(source, buffer, outSize, inputSize); +} + +Common::SeekableReadStream *Resources::decompressLZ(Common::SeekableReadStream &source, uint32 outSize) { + byte *dataOut = (byte *)malloc(outSize); + decompressLZ(source, dataOut, outSize, -1); + + return new Common::MemoryReadStream(dataOut, outSize, DisposeAfterUse::YES); +} + +void Resources::decompressLZ(Common::SeekableReadStream &source, byte *outBuffer, int32 outSize, int32 inSize) { + byte lzWindow[4096]; + uint16 lzWindowPos; + uint16 cmd; + + byte *outBufferEnd = outBuffer + outSize; + int endPos = source.pos() + inSize; + + memset(lzWindow, 0xFF, 0xFEE); + lzWindowPos = 0xFEE; + cmd = 0; + + do { + cmd >>= 1; + if (!(cmd & 0x100)) + cmd = source.readByte() | 0xFF00; + + if (cmd & 1) { + byte literal = source.readByte(); + *outBuffer++ = literal; + lzWindow[lzWindowPos] = literal; + lzWindowPos = (lzWindowPos + 1) & 0x0FFF; + } else { + int copyPos, copyLen; + copyPos = source.readByte(); + copyLen = source.readByte(); + copyPos = copyPos | ((copyLen & 0xF0) << 4); + copyLen = (copyLen & 0x0F) + 3; + while (copyLen--) { + byte literal = lzWindow[copyPos]; + copyPos = (copyPos + 1) & 0x0FFF; + *outBuffer++ = literal; + lzWindow[lzWindowPos] = literal; + lzWindowPos = (lzWindowPos + 1) & 0x0FFF; + } + } + } while ((outSize == -1 || outBuffer < outBufferEnd) && (inSize == -1 || source.pos() < endPos)); +} + +} // End of namespace Sherlock diff --git a/engines/sherlock/resources.h b/engines/sherlock/resources.h new file mode 100644 index 0000000000..8e0216d69d --- /dev/null +++ b/engines/sherlock/resources.h @@ -0,0 +1,170 @@ +/* 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 SHERLOCK_RESOURCES_H +#define SHERLOCK_RESOURCES_H + +#include "common/array.h" +#include "common/file.h" +#include "common/hashmap.h" +#include "common/hash-str.h" +#include "common/rect.h" +#include "common/str.h" +#include "common/stream.h" +#include "graphics/surface.h" + +namespace Sherlock { + +typedef Common::Array<byte> CacheEntry; +typedef Common::HashMap<Common::String, CacheEntry, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> CacheHash; + +struct LibraryEntry { + uint32 _offset, _size; + int _index; + + LibraryEntry() : _index(0), _offset(0), _size(0) {} + LibraryEntry(int index, uint32 offset, uint32 size) : + _index(index), _offset(offset), _size(size) {} +}; +typedef Common::HashMap<Common::String, LibraryEntry, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> LibraryIndex; +typedef Common::HashMap<Common::String, LibraryIndex, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> LibraryIndexes; + +class SherlockEngine; + +class Cache { +private: + SherlockEngine *_vm; + CacheHash _resources; +public: + Cache(SherlockEngine *_vm); + + /** + * Returns true if a given file is currently being cached + */ + bool isCached(const Common::String &filename) const; + + /** + * Loads a file into the cache if it's not already present, and returns it. + * If the file is LZW compressed, automatically decompresses it and loads + * the uncompressed version into memory + */ + void load(const Common::String &name); + + /** + * Load a cache entry based on a passed stream + */ + void load(const Common::String &name, Common::SeekableReadStream &stream); + + /** + * Get a file from the cache + */ + Common::SeekableReadStream *get(const Common::String &filename) const; +}; + +class Resources { +private: + SherlockEngine *_vm; + Cache _cache; + LibraryIndexes _indexes; + int _resourceIndex; + + /** + * Reads in the index from a library file, and caches it's index for later use + */ + void loadLibraryIndex(const Common::String &libFilename, Common::SeekableReadStream *stream, bool isNewStyle); +public: + Resources(SherlockEngine *vm); + + /** + * Adds the specified file to the cache. If it's a library file, takes care of + * loading it's index for future use + */ + void addToCache(const Common::String &filename); + + /** + * Adds a resource from a library file to the cache + */ + void addToCache(const Common::String &filename, const Common::String &libFilename); + + /** + * Adds a given stream to the cache under the given name + */ + void addToCache(const Common::String &filename, Common::SeekableReadStream &stream); + + bool isInCache(const Common::String &filename) const { return _cache.isCached(filename); } + + /** + * Checks the passed stream, and if is compressed, deletes it and replaces it with it's uncompressed data + */ + void decompressIfNecessary(Common::SeekableReadStream *&stream); + + /** + * Returns a stream for a given file + */ + Common::SeekableReadStream *load(const Common::String &filename); + + /** + * Loads a specific resource from a given library file + */ + Common::SeekableReadStream *load(const Common::String &filename, const Common::String &libraryFile); + + /** + * Returns true if the given file exists on disk or in the cache + */ + bool exists(const Common::String &filename) const; + + /** + * Returns the index of the last loaded resource in it's given library file. + * This will be used primarily when loading talk files, so the engine can + * update the given conversation number in the journal + */ + int resourceIndex() const; + + /** + * Decompresses LZW compressed data + */ + Common::SeekableReadStream *decompress(Common::SeekableReadStream &source); + + /** + * Decompresses LZW compressed data + */ + Common::SeekableReadStream *decompress(Common::SeekableReadStream &source, uint32 outSize); + + /** + * Decompresses LZW compressed data + */ + void decompress(Common::SeekableReadStream &source, byte *buffer, uint32 outSize); + + /** + * Decompresses LZW compressed data + */ + static Common::SeekableReadStream *decompressLZ(Common::SeekableReadStream &source, uint32 outSize); + + /** + * Decompresses LZW compressed data + */ + static void decompressLZ(Common::SeekableReadStream &source, byte *outBuffer, int32 outSize, int32 inSize); +}; + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/saveload.cpp b/engines/sherlock/saveload.cpp new file mode 100644 index 0000000000..c863f8c4ee --- /dev/null +++ b/engines/sherlock/saveload.cpp @@ -0,0 +1,493 @@ +/* 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 "sherlock/saveload.h" +#include "sherlock/surface.h" +#include "sherlock/sherlock.h" +#include "common/system.h" +#include "graphics/scaler.h" +#include "graphics/thumbnail.h" + +namespace Sherlock { + +const int ENV_POINTS[6][3] = { + { 41, 80, 61 }, // Exit + { 81, 120, 101 }, // Load + { 121, 160, 141 }, // Save + { 161, 200, 181 }, // Up + { 201, 240, 221 }, // Down + { 241, 280, 261 } // Quit +}; + +static const char *const EMPTY_SAVEGAME_SLOT = "-EMPTY-"; +static const char *const SAVEGAME_STR = "SHLK"; +#define SAVEGAME_STR_SIZE 4 + +/*----------------------------------------------------------------*/ + +SaveManager::SaveManager(SherlockEngine *vm, const Common::String &target) : + _vm(vm), _target(target) { + _saveThumb = nullptr; + _envMode = SAVEMODE_NONE; + _justLoaded = false; + _savegameIndex = 0; +} + +SaveManager::~SaveManager() { + if (_saveThumb) { + _saveThumb->free(); + delete _saveThumb; + } +} + +void SaveManager::drawInterface() { + Screen &screen = *_vm->_screen; + UserInterface &ui = *_vm->_ui; + + // Create a list of savegame slots + createSavegameList(); + + screen._backBuffer1.fillRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, CONTROLS_Y + 10), BORDER_COLOR); + screen._backBuffer1.fillRect(Common::Rect(0, CONTROLS_Y + 10, 2, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR); + screen._backBuffer1.fillRect(Common::Rect(318, CONTROLS_Y + 10, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR); + screen._backBuffer1.fillRect(Common::Rect(0, 199, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR); + screen._backBuffer1.fillRect(Common::Rect(2, CONTROLS_Y + 10, SHERLOCK_SCREEN_WIDTH - 2, SHERLOCK_SCREEN_HEIGHT - 2), INV_BACKGROUND); + + screen.makeButton(Common::Rect(ENV_POINTS[0][0], CONTROLS_Y, ENV_POINTS[0][1], CONTROLS_Y + 10), + ENV_POINTS[0][2] - screen.stringWidth("Exit") / 2, "Exit"); + screen.makeButton(Common::Rect(ENV_POINTS[1][0], CONTROLS_Y, ENV_POINTS[1][1], CONTROLS_Y + 10), + ENV_POINTS[1][2] - screen.stringWidth("Load") / 2, "Load"); + screen.makeButton(Common::Rect(ENV_POINTS[2][0], CONTROLS_Y, ENV_POINTS[2][1], CONTROLS_Y + 10), + ENV_POINTS[2][2] - screen.stringWidth("Save") / 2, "Save"); + screen.makeButton(Common::Rect(ENV_POINTS[3][0], CONTROLS_Y, ENV_POINTS[3][1], CONTROLS_Y + 10), + ENV_POINTS[3][2] - screen.stringWidth("Up") / 2, "Up"); + screen.makeButton(Common::Rect(ENV_POINTS[4][0], CONTROLS_Y, ENV_POINTS[4][1], CONTROLS_Y + 10), + ENV_POINTS[4][2] - screen.stringWidth("Down") / 2, "Down"); + screen.makeButton(Common::Rect(ENV_POINTS[5][0], CONTROLS_Y, ENV_POINTS[5][1], CONTROLS_Y + 10), + ENV_POINTS[5][2] - screen.stringWidth("Quit") / 2, "Quit"); + + if (!_savegameIndex) + screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), COMMAND_NULL, 0, "Up"); + + if (_savegameIndex == MAX_SAVEGAME_SLOTS - ONSCREEN_FILES_COUNT) + screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), COMMAND_NULL, 0, "Down"); + + for (int idx = _savegameIndex; idx < _savegameIndex + ONSCREEN_FILES_COUNT; ++idx) { + screen.gPrint(Common::Point(6, CONTROLS_Y + 11 + (idx - _savegameIndex) * 10), + INV_FOREGROUND, "%d.", idx + 1); + screen.gPrint(Common::Point(24, CONTROLS_Y + 11 + (idx - _savegameIndex) * 10), + INV_FOREGROUND, "%s", _savegames[idx].c_str()); + } + + if (!ui._slideWindows) { + screen.slamRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); + } else { + ui.summonWindow(); + } + + _envMode = SAVEMODE_NONE; +} + +void SaveManager::createSavegameList() { + Screen &screen = *_vm->_screen; + + _savegames.clear(); + for (int idx = 0; idx < MAX_SAVEGAME_SLOTS; ++idx) + _savegames.push_back(EMPTY_SAVEGAME_SLOT); + + SaveStateList saveList = getSavegameList(_target); + for (uint idx = 0; idx < saveList.size(); ++idx) { + int slot = saveList[idx].getSaveSlot() - 1; + if (slot >= 0 && slot < MAX_SAVEGAME_SLOTS) + _savegames[slot] = saveList[idx].getDescription(); + } + + // Ensure the names will fit on the screen + for (uint idx = 0; idx < _savegames.size(); ++idx) { + int width = screen.stringWidth(_savegames[idx]) + 24; + if (width > 308) { + // It won't fit in, so remove characters until it does + do { + width -= screen.charWidth(_savegames[idx].lastChar()); + _savegames[idx].deleteLastChar(); + } while (width > 300); + } + } +} + +SaveStateList SaveManager::getSavegameList(const Common::String &target) { + Common::SaveFileManager *saveFileMan = g_system->getSavefileManager(); + Common::StringArray filenames; + Common::String saveDesc; + Common::String pattern = Common::String::format("%s.0??", target.c_str()); + SherlockSavegameHeader header; + + filenames = saveFileMan->listSavefiles(pattern); + sort(filenames.begin(), filenames.end()); // Sort to get the files in numerical order + + SaveStateList saveList; + 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_SAVEGAME_SLOTS) { + Common::InSaveFile *in = g_system->getSavefileManager()->openForLoading(*file); + + if (in) { + if (!readSavegameHeader(in, header)) + continue; + + saveList.push_back(SaveStateDescriptor(slot, header._saveName)); + + header._thumbnail->free(); + delete header._thumbnail; + delete in; + } + } + } + + return saveList; +} + +bool SaveManager::readSavegameHeader(Common::InSaveFile *in, SherlockSavegameHeader &header) { + char saveIdentBuffer[SAVEGAME_STR_SIZE + 1]; + header._thumbnail = nullptr; + + // Validate the header Id + in->read(saveIdentBuffer, SAVEGAME_STR_SIZE + 1); + if (strncmp(saveIdentBuffer, SAVEGAME_STR, SAVEGAME_STR_SIZE)) + return false; + + header._version = in->readByte(); + if (header._version < MINIMUM_SAVEGAME_VERSION || header._version > CURRENT_SAVEGAME_VERSION) + return false; + + // Read in the string + header._saveName.clear(); + char ch; + while ((ch = (char)in->readByte()) != '\0') header._saveName += ch; + + // Get the thumbnail + header._thumbnail = Graphics::loadThumbnail(*in); + if (!header._thumbnail) + return false; + + // Read in save date/time + header._year = in->readSint16LE(); + header._month = in->readSint16LE(); + header._day = in->readSint16LE(); + header._hour = in->readSint16LE(); + header._minute = in->readSint16LE(); + header._totalFrames = in->readUint32LE(); + + return true; +} + +void SaveManager::writeSavegameHeader(Common::OutSaveFile *out, SherlockSavegameHeader &header) { + // Write out a savegame header + out->write(SAVEGAME_STR, SAVEGAME_STR_SIZE + 1); + + out->writeByte(CURRENT_SAVEGAME_VERSION); + + // Write savegame name + out->write(header._saveName.c_str(), header._saveName.size()); + out->writeByte('\0'); + + // Handle the thumbnail. If there's already one set by the game, create one + if (!_saveThumb) + createThumbnail(); + Graphics::saveThumbnail(*out, *_saveThumb); + + _saveThumb->free(); + delete _saveThumb; + _saveThumb = nullptr; + + // Write out the save date/time + TimeDate td; + g_system->getTimeAndDate(td); + out->writeSint16LE(td.tm_year + 1900); + out->writeSint16LE(td.tm_mon + 1); + out->writeSint16LE(td.tm_mday); + out->writeSint16LE(td.tm_hour); + out->writeSint16LE(td.tm_min); + out->writeUint32LE(_vm->_events->getFrameCounter()); +} + +void SaveManager::createThumbnail() { + if (_saveThumb) { + _saveThumb->free(); + delete _saveThumb; + } + + _saveThumb = new Graphics::Surface(); + + if (!IS_3DO) { + uint8 thumbPalette[PALETTE_SIZE]; + _vm->_screen->getPalette(thumbPalette); + ::createThumbnail(_saveThumb, (const byte *)_vm->_screen->getPixels(), SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT, thumbPalette); + } else { + ::createThumbnailFromScreen(_saveThumb); + } +} + +int SaveManager::getHighlightedButton() const { + Common::Point pt = _vm->_events->mousePos(); + + for (int idx = 0; idx < 6; ++idx) { + if (pt.x > ENV_POINTS[idx][0] && pt.x < ENV_POINTS[idx][1] && pt.y > CONTROLS_Y + && pt.y < (CONTROLS_Y + 10)) + return idx; + } + + return -1; +} + +void SaveManager::highlightButtons(int btnIndex) { + Screen &screen = *_vm->_screen; + byte color = (btnIndex == 0) ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND; + + screen.buttonPrint(Common::Point(ENV_POINTS[0][2], CONTROLS_Y), color, 1, "Exit"); + + if ((btnIndex == 1) || ((_envMode == SAVEMODE_LOAD) && (btnIndex != 2))) + screen.buttonPrint(Common::Point(ENV_POINTS[1][2], CONTROLS_Y), COMMAND_HIGHLIGHTED, true, "Load"); + else + screen.buttonPrint(Common::Point(ENV_POINTS[1][2], CONTROLS_Y), COMMAND_FOREGROUND, true, "Load"); + + if ((btnIndex == 2) || ((_envMode == SAVEMODE_SAVE) && (btnIndex != 1))) + screen.buttonPrint(Common::Point(ENV_POINTS[2][2], CONTROLS_Y), COMMAND_HIGHLIGHTED, true, "Save"); + else + screen.buttonPrint(Common::Point(ENV_POINTS[2][2], CONTROLS_Y), COMMAND_FOREGROUND, true, "Save"); + + if (btnIndex == 3 && _savegameIndex) + screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), COMMAND_HIGHLIGHTED, true, "Up"); + else + if (_savegameIndex) + screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), COMMAND_FOREGROUND, true, "Up"); + + if ((btnIndex == 4) && (_savegameIndex < MAX_SAVEGAME_SLOTS - 5)) + screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), COMMAND_HIGHLIGHTED, true, "Down"); + else if (_savegameIndex < (MAX_SAVEGAME_SLOTS - 5)) + screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), COMMAND_FOREGROUND, true, "Down"); + + color = (btnIndex == 5) ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND; + screen.buttonPrint(Common::Point(ENV_POINTS[5][2], CONTROLS_Y), color, 1, "Quit"); +} + +void SaveManager::loadGame(int slot) { + Common::InSaveFile *saveFile = g_system->getSavefileManager()->openForLoading( + generateSaveName(slot)); + if (!saveFile) + return; + + // Load the savaegame header + SherlockSavegameHeader header; + if (!readSavegameHeader(saveFile, header)) + error("Invalid savegame"); + + if (header._thumbnail) { + header._thumbnail->free(); + delete header._thumbnail; + } + + // Synchronize the savegame data + Serializer s(saveFile, nullptr); + s.setSaveVersion(header._version); + synchronize(s); + + delete saveFile; +} + +void SaveManager::saveGame(int slot, const Common::String &name) { + Common::OutSaveFile *out = g_system->getSavefileManager()->openForSaving( + generateSaveName(slot)); + + SherlockSavegameHeader header; + header._saveName = name; + writeSavegameHeader(out, header); + + // Synchronize the savegame data + Serializer s(nullptr, out); + s.setSaveVersion(CURRENT_SAVEGAME_VERSION); + synchronize(s); + + out->finalize(); + delete out; +} + +Common::String SaveManager::generateSaveName(int slot) { + return Common::String::format("%s.%03d", _target.c_str(), slot); +} + +void SaveManager::synchronize(Serializer &s) { + Inventory &inv = *_vm->_inventory; + Journal &journal = *_vm->_journal; + Map &map = *_vm->_map; + People &people = *_vm->_people; + Scene &scene = *_vm->_scene; + Screen &screen = *_vm->_screen; + Talk &talk = *_vm->_talk; + + int oldFont = screen.fontNumber(); + + inv.synchronize(s); + journal.synchronize(s); + people.synchronize(s); + map.synchronize(s); + scene.synchronize(s); + screen.synchronize(s); + talk.synchronize(s); + _vm->synchronize(s); + + if (screen.fontNumber() != oldFont) + journal.resetPosition(); + + _justLoaded = true; +} + +bool SaveManager::checkGameOnScreen(int slot) { + Screen &screen = *_vm->_screen; + + // Check if it's already on-screen + if (slot != -1 && (slot < _savegameIndex || slot >= (_savegameIndex + ONSCREEN_FILES_COUNT))) { + _savegameIndex = slot; + + screen._backBuffer1.fillRect(Common::Rect(3, CONTROLS_Y + 11, SHERLOCK_SCREEN_WIDTH - 2, + SHERLOCK_SCREEN_HEIGHT - 1), INV_BACKGROUND); + + for (int idx = _savegameIndex; idx < (_savegameIndex + 5); ++idx) { + screen.gPrint(Common::Point(6, CONTROLS_Y + 11 + (idx - _savegameIndex) * 10), + INV_FOREGROUND, "%d.", idx + 1); + screen.gPrint(Common::Point(24, CONTROLS_Y + 11 + (idx - _savegameIndex) * 10), + INV_FOREGROUND, "%s", _savegames[idx].c_str()); + } + + screen.slamRect(Common::Rect(3, CONTROLS_Y + 11, 318, SHERLOCK_SCREEN_HEIGHT)); + + byte color = !_savegameIndex ? COMMAND_NULL : COMMAND_FOREGROUND; + screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), color, 1, "Up"); + + color = (_savegameIndex == (MAX_SAVEGAME_SLOTS - 5)) ? COMMAND_NULL : COMMAND_FOREGROUND; + screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), color, 1, "Down"); + + return true; + } + + return false; +} + +bool SaveManager::promptForDescription(int slot) { + Events &events = *_vm->_events; + Scene &scene = *_vm->_scene; + Screen &screen = *_vm->_screen; + Talk &talk = *_vm->_talk; + int xp, yp; + bool flag = false; + + screen.buttonPrint(Common::Point(ENV_POINTS[0][2], CONTROLS_Y), COMMAND_NULL, true, "Exit"); + screen.buttonPrint(Common::Point(ENV_POINTS[1][2], CONTROLS_Y), COMMAND_NULL, true, "Load"); + screen.buttonPrint(Common::Point(ENV_POINTS[2][2], CONTROLS_Y), COMMAND_NULL, true, "Save"); + screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), COMMAND_NULL, true, "Up"); + screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), COMMAND_NULL, true, "Down"); + screen.buttonPrint(Common::Point(ENV_POINTS[5][2], CONTROLS_Y), COMMAND_NULL, true, "Quit"); + + Common::String saveName = _savegames[slot]; + if (isSlotEmpty(slot)) { + // It's an empty slot, so start off with an empty save name + saveName = ""; + + yp = CONTROLS_Y + 12 + (slot - _savegameIndex) * 10; + screen.vgaBar(Common::Rect(24, yp, 85, yp + 9), INV_BACKGROUND); + } + + screen.print(Common::Point(6, CONTROLS_Y + 12 + (slot - _savegameIndex) * 10), TALK_FOREGROUND, "%d.", slot + 1); + screen.print(Common::Point(24, CONTROLS_Y + 12 + (slot - _savegameIndex) * 10), TALK_FOREGROUND, "%s", saveName.c_str()); + xp = 24 + screen.stringWidth(saveName); + yp = CONTROLS_Y + 12 + (slot - _savegameIndex) * 10; + + int done = 0; + do { + while (!_vm->shouldQuit() && !events.kbHit()) { + scene.doBgAnim(); + + if (talk._talkToAbort) + return false; + + // Allow event processing + events.pollEventsAndWait(); + events.setButtonState(); + + flag = !flag; + if (flag) + screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_FOREGROUND); + else + screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_BACKGROUND); + } + if (_vm->shouldQuit()) + return false; + + // Get the next keypress + Common::KeyState keyState = events.getKey(); + + if (keyState.keycode == Common::KEYCODE_BACKSPACE && saveName.size() > 0) { + // Delete character of save name + screen.vgaBar(Common::Rect(xp - screen.charWidth(saveName.lastChar()), yp - 1, + xp + 8, yp + 9), INV_BACKGROUND); + xp -= screen.charWidth(saveName.lastChar()); + screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_FOREGROUND); + saveName.deleteLastChar(); + + } else if (keyState.keycode == Common::KEYCODE_RETURN && saveName.compareToIgnoreCase(EMPTY_SAVEGAME_SLOT)) { + done = 1; + + } else if (keyState.keycode == Common::KEYCODE_ESCAPE) { + screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_BACKGROUND); + done = -1; + + } else if (keyState.ascii >= ' ' && keyState.ascii <= 'z' && saveName.size() < 50 + && (xp + screen.charWidth(keyState.ascii)) < 308) { + char c = (char)keyState.ascii; + + screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_BACKGROUND); + screen.print(Common::Point(xp, yp), TALK_FOREGROUND, "%c", c); + xp += screen.charWidth(c); + screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_FOREGROUND); + saveName += c; + } + } while (!done); + + if (done == 1) { + // Enter key perssed + _savegames[slot] = saveName; + } else { + done = 0; + _envMode = SAVEMODE_NONE; + highlightButtons(-1); + } + + return done == 1; +} + +bool SaveManager::isSlotEmpty(int slot) const { + return _savegames[slot].equalsIgnoreCase(EMPTY_SAVEGAME_SLOT); +} + +} // End of namespace Sherlock diff --git a/engines/sherlock/saveload.h b/engines/sherlock/saveload.h new file mode 100644 index 0000000000..49ccc508ef --- /dev/null +++ b/engines/sherlock/saveload.h @@ -0,0 +1,165 @@ +/* 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 SHERLOCK_SAVELOAD_H +#define SHERLOCK_SAVELOAD_H + +#include "common/scummsys.h" +#include "common/savefile.h" +#include "common/serializer.h" +#include "common/str-array.h" +#include "engines/savestate.h" +#include "graphics/surface.h" + +namespace Sherlock { + +#define MAX_SAVEGAME_SLOTS 99 +#define ONSCREEN_FILES_COUNT 5 + +enum { + CURRENT_SAVEGAME_VERSION = 2, + MINIMUM_SAVEGAME_VERSION = 2 +}; + +enum SaveMode { SAVEMODE_NONE = 0, SAVEMODE_LOAD = 1, SAVEMODE_SAVE = 2 }; + +extern const int ENV_POINTS[6][3]; + +struct SherlockSavegameHeader { + uint8 _version; + Common::String _saveName; + Graphics::Surface *_thumbnail; + int _year, _month, _day; + int _hour, _minute; + int _totalFrames; +}; + +class SherlockEngine; + + +/** + * Derived serializer class with extra synchronization types + */ +class Serializer : public Common::Serializer { +public: + Serializer(Common::SeekableReadStream *in, Common::WriteStream *out) : Common::Serializer(in, out) {} + + /** + * New method to allow setting the version + */ + void setSaveVersion(byte version) { _version = version; } +}; + +class SaveManager { +private: + SherlockEngine *_vm; + Common::String _target; + Graphics::Surface *_saveThumb; + + /** + * Build up a savegame list, with empty slots given an explicit Empty message + */ + void createSavegameList(); + + /** + * Synchronize the data for a savegame + */ + void synchronize(Serializer &s); +public: + Common::StringArray _savegames; + int _savegameIndex; + SaveMode _envMode; + bool _justLoaded; +public: + SaveManager(SherlockEngine *vm, const Common::String &target); + ~SaveManager(); + + /** + * Shows the in-game dialog interface for loading and saving games + */ + void drawInterface(); + + /** + * Creates a thumbnail for the current on-screen contents + */ + void createThumbnail(); + + /** + * Load a list of savegames + */ + static SaveStateList getSavegameList(const Common::String &target); + + /** + * Support method that generates a savegame name + * @param slot Slot number + */ + Common::String generateSaveName(int slot); + + /** + * Write out the header information for a savegame + */ + void writeSavegameHeader(Common::OutSaveFile *out, SherlockSavegameHeader &header); + + /** + * Read in the header information for a savegame + */ + static bool readSavegameHeader(Common::InSaveFile *in, SherlockSavegameHeader &header); + + /** + * Return the index of the button the mouse is over, if any + */ + int getHighlightedButton() const; + + /** + * Handle highlighting buttons + */ + void highlightButtons(int btnIndex); + + /** + * Load the game in the specified slot + */ + void loadGame(int slot); + + /** + * Save the game in the specified slot with the given name + */ + void saveGame(int slot, const Common::String &name); + + /** + * Make sure that the selected savegame is on-screen + */ + bool checkGameOnScreen(int slot); + + /** + * Prompts the user to enter a description in a given slot + */ + bool promptForDescription(int slot); + + /** + * Returns true if the given save slot is empty + */ + bool isSlotEmpty(int slot) const; +}; + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/scalpel/3do/movie_decoder.cpp b/engines/sherlock/scalpel/3do/movie_decoder.cpp new file mode 100644 index 0000000000..8e8f99bc19 --- /dev/null +++ b/engines/sherlock/scalpel/3do/movie_decoder.cpp @@ -0,0 +1,510 @@ +/* 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 "common/scummsys.h" +#include "common/stream.h" +#include "common/textconsole.h" + +#include "audio/audiostream.h" +#include "audio/decoders/raw.h" +#include "audio/decoders/3do.h" + +#include "sherlock/scalpel/3do/movie_decoder.h" +#include "image/codecs/cinepak.h" + +// for Test-Code +#include "common/system.h" +#include "common/events.h" +#include "common/keyboard.h" +#include "engines/engine.h" +#include "engines/util.h" +#include "graphics/palette.h" +#include "graphics/pixelformat.h" +#include "graphics/surface.h" + +namespace Sherlock { + +Scalpel3DOMovieDecoder::Scalpel3DOMovieDecoder() + : _stream(0), _videoTrack(0), _audioTrack(0) { + _streamVideoOffset = 0; + _streamAudioOffset = 0; +} + +Scalpel3DOMovieDecoder::~Scalpel3DOMovieDecoder() { + close(); +} + +bool Scalpel3DOMovieDecoder::loadStream(Common::SeekableReadStream *stream) { + uint32 videoSubType = 0; + uint32 videoCodecTag = 0; + uint32 videoHeight = 0; + uint32 videoWidth = 0; + uint32 videoFrameCount = 0; + uint32 audioSubType = 0; + uint32 audioCodecTag = 0; + uint32 audioChannels = 0; + uint32 audioSampleRate = 0; + + close(); + + _stream = stream; + _streamVideoOffset = 0; + _streamAudioOffset = 0; + + // Look for packets that we care about + static const int maxPacketCheckCount = 20; + for (int i = 0; i < maxPacketCheckCount; i++) { + uint32 chunkTag = _stream->readUint32BE(); + uint32 chunkSize = _stream->readUint32BE() - 8; + + // Bail out if done + if (_stream->eos()) + break; + + uint32 dataStartOffset = _stream->pos(); + + switch (chunkTag) { + case MKTAG('F','I','L','M'): { + // See if this is a FILM header + _stream->skip(4); // time stamp (based on 240 per second) + _stream->skip(4); // Unknown 0x00000000 + videoSubType = _stream->readUint32BE(); + + switch (videoSubType) { + case MKTAG('F', 'H', 'D', 'R'): + // FILM header found + if (_videoTrack) { + warning("Sherlock 3DO movie: Multiple FILM headers found"); + close(); + return false; + } + _stream->readUint32BE(); + videoCodecTag = _stream->readUint32BE(); + videoHeight = _stream->readUint32BE(); + videoWidth = _stream->readUint32BE(); + _stream->skip(4); // time scale + videoFrameCount = _stream->readUint32BE(); + + _videoTrack = new StreamVideoTrack(videoWidth, videoHeight, videoCodecTag, videoFrameCount); + addTrack(_videoTrack); + break; + + case MKTAG('F', 'R', 'M', 'E'): + break; + + default: + warning("Sherlock 3DO movie: Unknown subtype inside FILM packet"); + close(); + return false; + } + break; + } + + case MKTAG('S','N','D','S'): { + _stream->skip(8); + audioSubType = _stream->readUint32BE(); + + switch (audioSubType) { + case MKTAG('S', 'H', 'D', 'R'): + // Audio header + + // Bail if we already have a track + if (_audioTrack) { + warning("Sherlock 3DO movie: Multiple SNDS headers found"); + close(); + return false; + } + + // OK, this is the start of a audio stream + _stream->readUint32BE(); // Version, always 0x00000000 + _stream->readUint32BE(); // Unknown 0x00000008 ?! + _stream->readUint32BE(); // Unknown 0x00007500 + _stream->readUint32BE(); // Unknown 0x00004000 + _stream->readUint32BE(); // Unknown 0x00000000 + _stream->readUint32BE(); // Unknown 0x00000010 + audioSampleRate = _stream->readUint32BE(); + audioChannels = _stream->readUint32BE(); + audioCodecTag = _stream->readUint32BE(); + _stream->readUint32BE(); // Unknown 0x00000004 compression ratio? + _stream->readUint32BE(); // Unknown 0x00000A2C + + _audioTrack = new StreamAudioTrack(audioCodecTag, audioSampleRate, audioChannels); + addTrack(_audioTrack); + break; + + case MKTAG('S', 'S', 'M', 'P'): + // Audio data + break; + default: + warning("Sherlock 3DO movie: Unknown subtype inside FILM packet"); + close(); + return false; + } + break; + } + + case MKTAG('C','T','R','L'): + case MKTAG('F','I','L','L'): // filler chunk, fills to certain boundary + case MKTAG('D','A','C','Q'): + case MKTAG('J','O','I','N'): // add cel data (not used in sherlock) + // Ignore these chunks + break; + + case MKTAG('S','H','D','R'): + // Happens for EA logo, seems to be garbage data right at the start of the file + break; + + default: + warning("Unknown chunk-tag '%s' inside Sherlock 3DO movie", tag2str(chunkTag)); + close(); + return false; + } + + if ((_videoTrack) && (_audioTrack)) + break; + + // Seek to next chunk + _stream->seek(dataStartOffset + chunkSize); + } + + // Bail if we didn't find video + audio + if ((!_videoTrack) || (!_audioTrack)) { + close(); + return false; + } + + // Rewind back to the beginning + _stream->seek(0); + + return true; +} + +void Scalpel3DOMovieDecoder::close() { + Video::VideoDecoder::close(); + + delete _stream; _stream = 0; + _videoTrack = 0; +} + +// We try to at least decode 1 frame +// and also try to get at least 0.5 seconds of audio queued up +void Scalpel3DOMovieDecoder::readNextPacket() { + uint32 currentMovieTime = getTime(); + uint32 wantedAudioQueued = currentMovieTime + 500; // always try to be 0.500 seconds in front of movie time + + int32 chunkOffset = 0; + int32 dataStartOffset = 0; + int32 nextChunkOffset = 0; + uint32 chunkTag = 0; + uint32 chunkSize = 0; + + uint32 videoSubType = 0; + uint32 videoTimeStamp = 0; + uint32 videoFrameSize = 0; + uint32 audioSubType = 0; + uint32 audioBytes = 0; + bool videoGotFrame = false; + bool videoDone = false; + bool audioDone = false; + + // Seek to smallest stream offset + if (_streamVideoOffset <= _streamAudioOffset) { + _stream->seek(_streamVideoOffset); + } else { + _stream->seek(_streamAudioOffset); + } + + if (wantedAudioQueued <= _audioTrack->getTotalAudioQueued()) { + // already got enough audio queued up + audioDone = true; + } + + while (1) { + chunkOffset = _stream->pos(); + assert(chunkOffset >= 0); + + // Read chunk header + chunkTag = _stream->readUint32BE(); + chunkSize = _stream->readUint32BE() - 8; + + // Calculate offsets + dataStartOffset = _stream->pos(); + assert(dataStartOffset >= 0); + nextChunkOffset = dataStartOffset + chunkSize; + + //warning("offset %lx - tag %lx", dataStartOffset, tag); + + if (_stream->eos()) + break; + + switch (chunkTag) { + case MKTAG('F','I','L','M'): + videoTimeStamp = _stream->readUint32BE(); + _stream->skip(4); // Unknown + videoSubType = _stream->readUint32BE(); + + switch (videoSubType) { + case MKTAG('F', 'H', 'D', 'R'): + // Ignore video header + break; + + case MKTAG('F', 'R', 'M', 'E'): + // Found frame data + if (_streamVideoOffset <= chunkOffset) { + // We are at an offset that is still relevant to video decoding + if (!videoDone) { + if (!videoGotFrame) { + // We haven't decoded any frame yet, so do so now + _stream->readUint32BE(); + videoFrameSize = _stream->readUint32BE(); + _videoTrack->decodeFrame(_stream->readStream(videoFrameSize), videoTimeStamp); + + _streamVideoOffset = nextChunkOffset; + videoGotFrame = true; + + } else { + // Already decoded a frame, so get timestamp of follow-up frame + // and then we are done with video + + // Calculate next frame time + // 3DO clock time for movies runs at 240Hh, that's why timestamps are based on 240. + uint32 currentFrameStartTime = _videoTrack->getNextFrameStartTime(); + uint32 nextFrameStartTime = videoTimeStamp * 1000 / 240; + assert(currentFrameStartTime <= nextFrameStartTime); + _videoTrack->setNextFrameStartTime(nextFrameStartTime); + + // next time we want to start at the current chunk + _streamVideoOffset = chunkOffset; + videoDone = true; + } + } + } + break; + + default: + error("Sherlock 3DO movie: Unknown subtype inside FILM packet"); + break; + } + break; + + case MKTAG('S','N','D','S'): + _stream->skip(8); + audioSubType = _stream->readUint32BE(); + + switch (audioSubType) { + case MKTAG('S', 'H', 'D', 'R'): + // Ignore the audio header + break; + + case MKTAG('S', 'S', 'M', 'P'): + // Got audio chunk + if (_streamAudioOffset <= chunkOffset) { + // We are at an offset that is still relevant to audio decoding + if (!audioDone) { + audioBytes = _stream->readUint32BE(); + _audioTrack->queueAudio(_stream, audioBytes); + + _streamAudioOffset = nextChunkOffset; + if (wantedAudioQueued <= _audioTrack->getTotalAudioQueued()) { + // Got enough audio + audioDone = true; + } + } + } + break; + + default: + error("Sherlock 3DO movie: Unknown subtype inside SNDS packet"); + break; + } + break; + + case MKTAG('C','T','R','L'): + case MKTAG('F','I','L','L'): // filler chunk, fills to certain boundary + case MKTAG('D','A','C','Q'): + case MKTAG('J','O','I','N'): // add cel data (not used in sherlock) + // Ignore these chunks + break; + + case MKTAG('S','H','D','R'): + // Happens for EA logo, seems to be garbage data right at the start of the file + break; + + default: + error("Unknown chunk-tag '%s' inside Sherlock 3DO movie", tag2str(chunkTag)); + } + + // Always seek to end of chunk + // Sometimes not all of the chunk is filled with audio + _stream->seek(nextChunkOffset); + + if ((videoDone) && (audioDone)) { + return; + } + } +} + +Scalpel3DOMovieDecoder::StreamVideoTrack::StreamVideoTrack(uint32 width, uint32 height, uint32 codecTag, uint32 frameCount) { + _width = width; + _height = height; + _frameCount = frameCount; + _curFrame = -1; + _nextFrameStartTime = 0; + + // Create the Cinepak decoder, if we're using it + if (codecTag == MKTAG('c', 'v', 'i', 'd')) + _codec = new Image::CinepakDecoder(); + else + error("Unsupported Sherlock 3DO movie video codec tag '%s'", tag2str(codecTag)); +} + +Scalpel3DOMovieDecoder::StreamVideoTrack::~StreamVideoTrack() { + delete _codec; +} + +bool Scalpel3DOMovieDecoder::StreamVideoTrack::endOfTrack() const { + return getCurFrame() >= getFrameCount() - 1; +} + +Graphics::PixelFormat Scalpel3DOMovieDecoder::StreamVideoTrack::getPixelFormat() const { + return _codec->getPixelFormat(); +} + +void Scalpel3DOMovieDecoder::StreamVideoTrack::decodeFrame(Common::SeekableReadStream *stream, uint32 videoTimeStamp) { + _surface = _codec->decodeFrame(*stream); + _curFrame++; +} + +Scalpel3DOMovieDecoder::StreamAudioTrack::StreamAudioTrack(uint32 codecTag, uint32 sampleRate, uint32 channels) { + switch (codecTag) { + case MKTAG('A','D','P','4'): + case MKTAG('S','D','X','2'): + // ADP4 + SDX2 are both allowed + break; + + default: + error("Unsupported Sherlock 3DO movie audio codec tag '%s'", tag2str(codecTag)); + } + + _totalAudioQueued = 0; // currently 0 milliseconds queued + + _codecTag = codecTag; + _sampleRate = sampleRate; + switch (channels) { + case 1: + _stereo = false; + break; + case 2: + _stereo = true; + break; + default: + error("Unsupported Sherlock 3DO movie audio channels %d", channels); + } + + _audioStream = Audio::makeQueuingAudioStream(sampleRate, _stereo); + + // reset audio decoder persistent spaces + memset(&_ADP4_PersistentSpace, 0, sizeof(_ADP4_PersistentSpace)); + memset(&_SDX2_PersistentSpace, 0, sizeof(_SDX2_PersistentSpace)); +} + +Scalpel3DOMovieDecoder::StreamAudioTrack::~StreamAudioTrack() { + delete _audioStream; +// free(_ADP4_PersistentSpace); +// free(_SDX2_PersistentSpace); +} + +void Scalpel3DOMovieDecoder::StreamAudioTrack::queueAudio(Common::SeekableReadStream *stream, uint32 size) { + Common::SeekableReadStream *compressedAudioStream = 0; + Audio::RewindableAudioStream *audioStream = 0; + uint32 audioLengthMSecs = 0; + + // Read the specified chunk into memory + compressedAudioStream = stream->readStream(size); + + switch(_codecTag) { + case MKTAG('A','D','P','4'): + audioStream = Audio::make3DO_ADP4AudioStream(compressedAudioStream, _sampleRate, _stereo, &audioLengthMSecs, DisposeAfterUse::YES, &_ADP4_PersistentSpace); + break; + case MKTAG('S','D','X','2'): + audioStream = Audio::make3DO_SDX2AudioStream(compressedAudioStream, _sampleRate, _stereo, &audioLengthMSecs, DisposeAfterUse::YES, &_SDX2_PersistentSpace); + break; + default: + break; + } + if (audioStream) { + _totalAudioQueued += audioLengthMSecs; + _audioStream->queueAudioStream(audioStream, DisposeAfterUse::YES); + } else { + // in case there was an error + delete compressedAudioStream; + } +} + +Audio::AudioStream *Scalpel3DOMovieDecoder::StreamAudioTrack::getAudioStream() const { + return _audioStream; +} + +// Test-code + +// Code for showing a movie. Only meant for testing/debug purposes +bool Scalpel3DOMoviePlay(const char *filename, Common::Point pos) { + Scalpel3DOMovieDecoder *videoDecoder = new Scalpel3DOMovieDecoder(); + + if (!videoDecoder->loadFile(filename)) { + warning("Scalpel3DOMoviePlay: could not open '%s'", filename); + return false; + } + + bool skipVideo = false; + //byte bytesPerPixel = videoDecoder->getPixelFormat().bytesPerPixel; + uint16 width = videoDecoder->getWidth(); + uint16 height = videoDecoder->getHeight(); + //uint16 pitch = videoDecoder->getWidth() * bytesPerPixel; + + videoDecoder->start(); + + while (!g_engine->shouldQuit() && !videoDecoder->endOfVideo() && (!skipVideo)) { + if (videoDecoder->needsUpdate()) { + const Graphics::Surface *frame = videoDecoder->decodeNextFrame(); + + if (frame) { + g_system->copyRectToScreen(frame->getPixels(), frame->pitch, pos.x, pos.y, width, height); + g_system->updateScreen(); + } + } + + Common::Event event; + while (g_system->getEventManager()->pollEvent(event)) { + if ((event.type == Common::EVENT_KEYDOWN && event.kbd.keycode == Common::KEYCODE_ESCAPE)) + skipVideo = true; + } + + g_system->delayMillis(10); + } + videoDecoder->close(); + delete videoDecoder; + + return !skipVideo; +} + +} // End of namespace Sherlock diff --git a/engines/sherlock/scalpel/3do/movie_decoder.h b/engines/sherlock/scalpel/3do/movie_decoder.h new file mode 100644 index 0000000000..9f1670fc6c --- /dev/null +++ b/engines/sherlock/scalpel/3do/movie_decoder.h @@ -0,0 +1,127 @@ +/* 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 SHERLOCK_SCALPEL_3DO_MOVIE_DECODER_H +#define SHERLOCK_SCALPEL_3DO_MOVIE_DECODER_H + +#include "common/rect.h" +#include "video/video_decoder.h" +#include "audio/decoders/3do.h" + +namespace Audio { +class QueuingAudioStream; +} + +namespace Common { +class SeekableReadStream; +} + +namespace Image { +class Codec; +} + +namespace Sherlock { + +class Scalpel3DOMovieDecoder : public Video::VideoDecoder { +public: + Scalpel3DOMovieDecoder(); + ~Scalpel3DOMovieDecoder(); + + bool loadStream(Common::SeekableReadStream *stream); + void close(); + +protected: + void readNextPacket(); + +private: + int32 _streamVideoOffset; /* current stream offset for video decoding */ + int32 _streamAudioOffset; /* current stream offset for audio decoding */ + +private: + class StreamVideoTrack : public VideoTrack { + public: + StreamVideoTrack(uint32 width, uint32 height, uint32 codecTag, uint32 frameCount); + ~StreamVideoTrack(); + + bool endOfTrack() const; + + uint16 getWidth() const { return _width; } + uint16 getHeight() const { return _height; } + Graphics::PixelFormat getPixelFormat() const; + int getCurFrame() const { return _curFrame; } + int getFrameCount() const { return _frameCount; } + void setNextFrameStartTime(uint32 nextFrameStartTime) { _nextFrameStartTime = nextFrameStartTime; } + uint32 getNextFrameStartTime() const { return _nextFrameStartTime; } + const Graphics::Surface *decodeNextFrame() { return _surface; } + + void decodeFrame(Common::SeekableReadStream *stream, uint32 videoTimeStamp); + + private: + const Graphics::Surface *_surface; + + int _curFrame; + uint32 _frameCount; + uint32 _nextFrameStartTime; + + Image::Codec *_codec; + uint16 _width, _height; + }; + + class StreamAudioTrack : public AudioTrack { + public: + StreamAudioTrack(uint32 codecTag, uint32 sampleRate, uint32 channels); + ~StreamAudioTrack(); + + void queueAudio(Common::SeekableReadStream *stream, uint32 size); + + protected: + Audio::AudioStream *getAudioStream() const; + + private: + Audio::QueuingAudioStream *_audioStream; + uint32 _totalAudioQueued; /* total amount of milliseconds of audio, that we queued up already */ + + public: + uint32 getTotalAudioQueued() const { return _totalAudioQueued; } + + private: + int16 decodeSample(uint8 dataNibble); + + uint32 _codecTag; + uint16 _sampleRate; + bool _stereo; + + Audio::audio_3DO_ADP4_PersistentSpace _ADP4_PersistentSpace; + Audio::audio_3DO_SDX2_PersistentSpace _SDX2_PersistentSpace; + }; + + Common::SeekableReadStream *_stream; + StreamVideoTrack *_videoTrack; + StreamAudioTrack *_audioTrack; +}; + +// Testing +extern bool Scalpel3DOMoviePlay(const char *filename, Common::Point pos); + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/scalpel/darts.cpp b/engines/sherlock/scalpel/darts.cpp new file mode 100644 index 0000000000..a24af4e444 --- /dev/null +++ b/engines/sherlock/scalpel/darts.cpp @@ -0,0 +1,553 @@ +/* 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 "sherlock/scalpel/darts.h" +#include "sherlock/scalpel/scalpel.h" + +namespace Sherlock { + +namespace Scalpel { + +enum { + STATUS_INFO_X = 218, + STATUS_INFO_Y = 53, + DART_INFO_X = 218, + DART_INFO_Y = 103, + DARTBARHX = 35, + DARTHORIZY = 190, + DARTBARVX = 1, + DARTHEIGHTY = 25, + DARTBARSIZE = 150, + DART_BAR_FORE = 8 +}; + +enum { + DART_COL_FORE = 5, + PLAYER_COLOR = 11 +}; +#define OPPONENTS_COUNT 4 + +const char *const OPPONENT_NAMES[OPPONENTS_COUNT] = { + "Skipper", "Willy", "Micky", "Tom" +}; + +/*----------------------------------------------------------------*/ + +Darts::Darts(ScalpelEngine *vm) : _vm(vm) { + _dartImages = nullptr; + _level = 0; + _computerPlayer = 1; + _playerDartMode = false; + _dartScore1 = _dartScore2 = 0; + _roundNumber = 0; + _playerDartMode = false; + _roundScore = 0; + _oldDartButtons = false; +} + +void Darts::playDarts() { + Events &events = *_vm->_events; + Screen &screen = *_vm->_screen; + int playerNumber = 0; + int lastDart; + + // Change the font + int oldFont = screen.fontNumber(); + screen.setFont(2); + + loadDarts(); + initDarts(); + + bool done = false; + do { + int score, roundStartScore; + roundStartScore = score = playerNumber == 0 ? _dartScore1 : _dartScore2; + + // Show player details + showNames(playerNumber); + showStatus(playerNumber); + _roundScore = 0; + + if (_vm->shouldQuit()) + return; + + for (int idx = 0; idx < 3; ++idx) { + // Throw a single dart + if (_computerPlayer == 1) + lastDart = throwDart(idx + 1, playerNumber * 2); + else if (_computerPlayer == 2) + lastDart = throwDart(idx + 1, playerNumber + 1); + else + lastDart = throwDart(idx + 1, 0); + + score -= lastDart; + _roundScore += lastDart; + + screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(DART_INFO_X, DART_INFO_Y - 1), + Common::Rect(DART_INFO_X, DART_INFO_Y - 1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); + screen.print(Common::Point(DART_INFO_X, DART_INFO_Y), DART_COL_FORE, "Dart # %d", idx + 1); + screen.print(Common::Point(DART_INFO_X, DART_INFO_Y + 10), DART_COL_FORE, "Scored %d points", lastDart); + + if (score != 0 && playerNumber == 0) + screen.print(Common::Point(DART_INFO_X, DART_INFO_Y + 30), DART_COL_FORE, "Press a key"); + + if (score == 0) { + // Some-one has won + screen.print(Common::Point(DART_INFO_X, DART_INFO_Y + 20), PLAYER_COLOR, "GAME OVER!"); + + if (playerNumber == 0) { + screen.print(Common::Point(DART_INFO_X, DART_INFO_Y + 30), PLAYER_COLOR, "Holmes Wins!"); + if (_level < OPPONENTS_COUNT) + _vm->setFlagsDirect(318 + _level); + } else { + screen.print(Common::Point(DART_INFO_X, DART_INFO_Y + 30), PLAYER_COLOR, "%s Wins!", _opponent.c_str()); + } + + screen.print(Common::Point(DART_INFO_X, DART_INFO_Y + 4), DART_COL_FORE, "Press a key"); + + idx = 10; + done = true; + } else if (score < 0) { + screen.print(Common::Point(DART_INFO_X, DART_INFO_Y + 20), PLAYER_COLOR, "BUSTED!"); + + idx = 10; + score = roundStartScore; + } + + if (playerNumber == 0) + _dartScore1 = score; + else + _dartScore2 = score; + + showStatus(playerNumber); + events.clearKeyboard(); + + if ((playerNumber == 0 && _computerPlayer == 1) || _computerPlayer == 0 || done) { + int dartKey; + while (!(dartKey = dartHit()) && !_vm->shouldQuit()) + events.delay(10); + + if (dartKey == Common::KEYCODE_ESCAPE) { + idx = 10; + done = true; + } + } else { + events.wait(20); + } + + screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(DART_INFO_X, DART_INFO_Y - 1), + Common::Rect(DART_INFO_X, DART_INFO_Y - 1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + } + + playerNumber ^= 1; + if (!playerNumber) + ++_roundNumber; + + done |= _vm->shouldQuit(); + + if (!done) { + screen._backBuffer2.blitFrom((*_dartImages)[0], Common::Point(0, 0)); + screen._backBuffer1.blitFrom(screen._backBuffer2); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + } + } while (!done); + + closeDarts(); + screen.fadeToBlack(); + + // Restore font + screen.setFont(oldFont); +} + +void Darts::loadDarts() { + Screen &screen = *_vm->_screen; + + _dartImages = new ImageFile("darts.vgs"); + screen.setPalette(_dartImages->_palette); + + screen._backBuffer1.blitFrom((*_dartImages)[0], Common::Point(0, 0)); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); +} + +void Darts::initDarts() { + _dartScore1 = _dartScore2 = 301; + _roundNumber = 1; + + if (_level == 9) { + // No computer players + _computerPlayer = 0; + _level = 0; + } else if (_level == 8) { + _level = _vm->getRandomNumber(3); + _computerPlayer = 2; + } else { + // Check flags for opponents + for (int idx = 0; idx < OPPONENTS_COUNT; ++idx) { + if (_vm->readFlags(314 + idx)) + _level = idx; + } + } + + _opponent = OPPONENT_NAMES[_level]; +} + +void Darts::closeDarts() { + delete _dartImages; + _dartImages = nullptr; +} + +void Darts::showNames(int playerNum) { + Screen &screen = *_vm->_screen; + byte color = playerNum == 0 ? PLAYER_COLOR : DART_COL_FORE; + + // Print Holmes first + if (playerNum == 0) + screen.print(Common::Point(STATUS_INFO_X, STATUS_INFO_Y), PLAYER_COLOR + 3, "Holmes"); + else + screen.print(Common::Point(STATUS_INFO_X, STATUS_INFO_Y), color, "Holmes"); + + screen._backBuffer1.fillRect(Common::Rect(STATUS_INFO_X, STATUS_INFO_Y + 10, + STATUS_INFO_X + 31, STATUS_INFO_Y + 12), color); + screen.slamArea(STATUS_INFO_X, STATUS_INFO_Y + 10, 31, 12); + + // Second player + color = playerNum == 1 ? PLAYER_COLOR : DART_COL_FORE; + + if (playerNum != 0) + screen.print(Common::Point(STATUS_INFO_X + 50, STATUS_INFO_Y), PLAYER_COLOR + 3, + "%s", _opponent.c_str()); + else + screen.print(Common::Point(STATUS_INFO_X + 50, STATUS_INFO_Y), color, + "%s", _opponent.c_str()); + + screen._backBuffer1.fillRect(Common::Rect(STATUS_INFO_X + 50, STATUS_INFO_Y + 10, + STATUS_INFO_X + 81, STATUS_INFO_Y + 12), color); + screen.slamArea(STATUS_INFO_X + 50, STATUS_INFO_Y + 10, 81, 12); + + // Make a copy of the back buffer to the secondary one + screen._backBuffer2.blitFrom(screen._backBuffer1); +} + +void Darts::showStatus(int playerNum) { + Screen &screen = *_vm->_screen; + byte color; + + // Copy scoring screen from secondary back buffer. This will erase any previously displayed status/score info + screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(STATUS_INFO_X, STATUS_INFO_Y + 10), + Common::Rect(STATUS_INFO_X, STATUS_INFO_Y + 10, SHERLOCK_SCREEN_WIDTH, STATUS_INFO_Y + 48)); + + color = (playerNum == 0) ? PLAYER_COLOR : DART_COL_FORE; + screen.print(Common::Point(STATUS_INFO_X + 6, STATUS_INFO_Y + 13), color, "%d", _dartScore1); + + color = (playerNum == 1) ? PLAYER_COLOR : DART_COL_FORE; + screen.print(Common::Point(STATUS_INFO_X + 56, STATUS_INFO_Y + 13), color, "%d", _dartScore2); + screen.print(Common::Point(STATUS_INFO_X, STATUS_INFO_Y + 25), PLAYER_COLOR, "Round: %d", _roundNumber); + screen.print(Common::Point(STATUS_INFO_X, STATUS_INFO_Y + 35), PLAYER_COLOR, "Turn Total: %d", _roundScore); + screen.slamRect(Common::Rect(STATUS_INFO_X, STATUS_INFO_Y + 10, SHERLOCK_SCREEN_WIDTH, STATUS_INFO_Y + 48)); +} + +int Darts::throwDart(int dartNum, int computer) { + Events &events = *_vm->_events; + Screen &screen = *_vm->_screen; + Common::Point targetNum; + int width, height; + + events.clearKeyboard(); + + erasePowerBars(); + screen.print(Common::Point(DART_INFO_X, DART_INFO_Y), DART_COL_FORE, "Dart # %d", dartNum); + + if (!computer) { + screen.print(Common::Point(DART_INFO_X, DART_INFO_Y + 10), DART_COL_FORE, "Hit a key"); + screen.print(Common::Point(DART_INFO_X, DART_INFO_Y + 18), DART_COL_FORE, "to start"); + } + + if (!computer) { + while (!_vm->shouldQuit() && !dartHit()) + ; + } else { + events.delay(10); + } + + if (_vm->shouldQuit()) + return 0; + + screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(DART_INFO_X, DART_INFO_Y - 1), + Common::Rect(DART_INFO_X, DART_INFO_Y - 1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); + screen.slamRect(Common::Rect(DART_INFO_X, DART_INFO_Y - 1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); + + // If it's a computer player, choose a dart destination + if (computer) + targetNum = getComputerDartDest(computer - 1); + + width = doPowerBar(Common::Point(DARTBARHX, DARTHORIZY), DART_BAR_FORE, targetNum.x, false); + height = 101 - doPowerBar(Common::Point(DARTBARVX, DARTHEIGHTY), DART_BAR_FORE, targetNum.y, true); + + // For human players, slight y adjustment + if (computer == 0) + height += 2; + + // Copy the bars to the secondary back buffer so that they remain fixed at their selected values + // whilst the dart is being animated at being thrown at the board + screen._backBuffer2.blitFrom(screen._backBuffer1, Common::Point(DARTBARHX - 1, DARTHORIZY - 1), + Common::Rect(DARTBARHX - 1, DARTHORIZY - 1, DARTBARHX + DARTBARSIZE + 3, DARTHORIZY + 10)); + screen._backBuffer2.blitFrom(screen._backBuffer1, Common::Point(DARTBARVX - 1, DARTHEIGHTY - 1), + Common::Rect(DARTBARVX - 1, DARTHEIGHTY - 1, DARTBARVX + 11, DARTHEIGHTY + DARTBARSIZE + 3)); + + // Convert height and width to relative range of -50 to 50, where 0,0 is the exact centre of the board + height -= 50; + width -= 50; + + Common::Point dartPos(111 + width * 2, 99 + height * 2); + drawDartThrow(dartPos); + + return dartScore(dartPos); +} + +void Darts::drawDartThrow(const Common::Point &pt) { + Events &events = *_vm->_events; + Screen &screen = *_vm->_screen; + Common::Point pos(pt.x, pt.y + 2); + Common::Rect oldDrawBounds; + int delta = 9; + + for (int idx = 4; idx < 23; ++idx) { + ImageFrame &frame = (*_dartImages)[idx]; + + // Adjust draw position for animating dart + if (idx < 13) + pos.y -= delta--; + else if (idx == 13) + delta = 1; + else + pos.y += delta++; + + // Draw the dart + Common::Point drawPos(pos.x - frame._width / 2, pos.y - frame._height); + screen._backBuffer1.transBlitFrom(frame, drawPos); + screen.slamArea(drawPos.x, drawPos.y, frame._width, frame._height); + + // Handle erasing old dart frame area + if (!oldDrawBounds.isEmpty()) + screen.slamRect(oldDrawBounds); + + oldDrawBounds = Common::Rect(drawPos.x, drawPos.y, drawPos.x + frame._width, drawPos.y + frame._height); + screen._backBuffer1.blitFrom(screen._backBuffer2, drawPos, oldDrawBounds); + + events.wait(2); + } + + // Draw dart in final "stuck to board" form + screen._backBuffer1.transBlitFrom((*_dartImages)[22], Common::Point(oldDrawBounds.left, oldDrawBounds.top)); + screen._backBuffer2.transBlitFrom((*_dartImages)[22], Common::Point(oldDrawBounds.left, oldDrawBounds.top)); + screen.slamRect(oldDrawBounds); +} + +void Darts::erasePowerBars() { + Screen &screen = *_vm->_screen; + + screen._backBuffer1.fillRect(Common::Rect(DARTBARHX, DARTHORIZY, DARTBARHX + DARTBARSIZE, DARTHORIZY + 10), BLACK); + screen._backBuffer1.fillRect(Common::Rect(DARTBARVX, DARTHEIGHTY, DARTBARVX + 10, DARTHEIGHTY + DARTBARSIZE), BLACK); + screen._backBuffer1.transBlitFrom((*_dartImages)[2], Common::Point(DARTBARHX - 1, DARTHORIZY - 1)); + screen._backBuffer1.transBlitFrom((*_dartImages)[3], Common::Point(DARTBARVX - 1, DARTHEIGHTY - 1)); + screen.slamArea(DARTBARHX - 1, DARTHORIZY - 1, DARTBARSIZE + 3, 11); + screen.slamArea(DARTBARVX - 1, DARTHEIGHTY - 1, 11, DARTBARSIZE + 3); +} + +int Darts::doPowerBar(const Common::Point &pt, byte color, int goToPower, bool isVertical) { + Events &events = *_vm->_events; + Screen &screen = *_vm->_screen; + Music &music = *_vm->_music; + bool done; + int idx = 0; + + events.clearEvents(); + if (music._musicOn) + music.waitTimerRoland(10); + else + events.delay(100); + + // Display loop + do { + done = _vm->shouldQuit() || idx >= DARTBARSIZE; + + if (idx == (goToPower - 1)) + // Reached target power for a computer player + done = true; + else if (goToPower == 0) { + // Check for pres + if (dartHit()) + done = true; + } + + if (isVertical) { + screen._backBuffer1.hLine(pt.x, pt.y + DARTBARSIZE - 1 - idx, pt.x + 8, color); + screen._backBuffer1.transBlitFrom((*_dartImages)[3], Common::Point(pt.x - 1, pt.y - 1)); + screen.slamArea(pt.x, pt.y + DARTBARSIZE - 1 - idx, 8, 2); + } else { + screen._backBuffer1.vLine(pt.x + idx, pt.y, pt.y + 8, color); + screen._backBuffer1.transBlitFrom((*_dartImages)[2], Common::Point(pt.x - 1, pt.y - 1)); + screen.slamArea(pt.x + idx, pt.y, 1, 8); + } + + if (music._musicOn) { + if (!(idx % 3)) + music.waitTimerRoland(1); + } else if (!(idx % 8)) + events.wait(1); + + ++idx; + } while (!done); + + return MIN(idx * 100 / DARTBARSIZE, 100); +} + +bool Darts::dartHit() { + Events &events = *_vm->_events; + + // Process pending events + events.pollEventsAndWait(); + + if (events.kbHit()) { + // Key was pressed, so discard it and return true + events.clearKeyboard(); + return true; + } + + _oldDartButtons = events._pressed; + events.setButtonState(); + + // Only return true if the mouse button is newly pressed + return (events._pressed && !_oldDartButtons) ? 1 : 0; +} + +int Darts::dartScore(const Common::Point &pt) { + Common::Point pos(pt.x - 37, pt.y - 33); + Graphics::Surface &scoreImg = (*_dartImages)[1]._frame; + + if (pos.x < 0 || pos.y < 0 || pos.x >= scoreImg.w || pos.y >= scoreImg.h) + // Not on the board + return 0; + + // On board, so get the score from the pixel at that position + int score = *(const byte *)scoreImg.getBasePtr(pos.x, pos.y); + return score; +} + +Common::Point Darts::getComputerDartDest(int playerNum) { + Common::Point target; + int score = playerNum == 0 ? _dartScore1 : _dartScore2; + + if (score > 50) { + // Aim for the bullseye + target.x = target.y = 76; + + if (_level <= 1 && _vm->getRandomNumber(1) == 1) { + // Introduce margin of error + target.x += _vm->getRandomNumber(21) - 10; + target.y += _vm->getRandomNumber(21) - 10; + } + } else { + int aim = score; + + bool done; + Common::Point pt; + do { + done = findNumberOnBoard(aim, pt); + --aim; + } while (!done); + + target.x = 75 + ((target.x - 75) * 20 / 27); + target.y = 75 + ((target.y - 75) * 2 / 3); + } + + // Pick a level of accuracy. The higher the level, the more accurate their throw will be + int accuracy = _vm->getRandomNumber(10) + _level * 2; + + if (accuracy <= 2) { + target.x += _vm->getRandomNumber(71) - 35; + target.y += _vm->getRandomNumber(71) - 35; + } else if (accuracy <= 4) { + target.x += _vm->getRandomNumber(51) - 25; + target.y += _vm->getRandomNumber(51) - 25; + } else if (accuracy <= 6) { + target.x += _vm->getRandomNumber(31) - 15; + target.y += _vm->getRandomNumber(31) - 15; + } else if (accuracy <= 8) { + target.x += _vm->getRandomNumber(21) - 10; + target.y += _vm->getRandomNumber(21) - 10; + } else if (accuracy <= 10) { + target.x += _vm->getRandomNumber(11) - 5; + target.y += _vm->getRandomNumber(11) - 5; + } + + if (target.x < 1) + target.x = 1; + if (target.y < 1) + target.y = 1; + + return target; +} + +bool Darts::findNumberOnBoard(int aim, Common::Point &pt) { + ImageFrame &board = (*_dartImages)[1]; + + // Scan board image for the special "center" pixels + bool done = false; + for (int yp = 0; yp < 132 && !done; ++yp) { + const byte *srcP = (const byte *)board._frame.getBasePtr(0, yp); + for (int xp = 0; xp < 147 && !done; ++xp, ++srcP) { + int score = *srcP; + + // Check for match + if (score == aim) { + done = true; + + // Aim at non-double/triple numbers where possible + if (aim < 21) { + pt.x = xp + 5; + pt.y = yp + 5; + + score = *(const byte *)board._frame.getBasePtr(xp + 10, yp + 10); + if (score != aim) + // Not aiming at non-double/triple number yet + done = false; + } else { + // Aiming at a double or triple + pt.x = xp + 3; + pt.y = yp + 3; + } + } + } + } + + if (aim == 3) + pt.x += 15; + pt.y = 132 - pt.y; + + return done; +} + +} // End of namespace Scalpel + +} // End of namespace Sherlock diff --git a/engines/sherlock/scalpel/darts.h b/engines/sherlock/scalpel/darts.h new file mode 100644 index 0000000000..4368954814 --- /dev/null +++ b/engines/sherlock/scalpel/darts.h @@ -0,0 +1,130 @@ +/* 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 SHERLOCK_DARTS_H +#define SHERLOCK_DARTS_H + +#include "sherlock/image_file.h" + +namespace Sherlock { + +namespace Scalpel { + +class ScalpelEngine; + +class Darts { +private: + ScalpelEngine *_vm; + ImageFile *_dartImages; + int _dartScore1, _dartScore2; + int _roundNumber; + int _level; + int _computerPlayer; + Common::String _opponent; + bool _playerDartMode; + int _roundScore; + bool _oldDartButtons; + + /** + * Load the graphics needed for the dart game + */ + void loadDarts(); + + /** + * Initializes the variables needed for the dart game + */ + void initDarts(); + + /** + * Frees the images used by the dart game + */ + void closeDarts(); + + /** + * Show the names of the people playing, Holmes and his opponent + */ + void showNames(int playerNum); + + /** + * Show the player score and game status + */ + void showStatus(int playerNum); + + /** + * Throws a single dart. + * @param dartNum Dart number + * @param computer 0 = Player, 1 = 1st player computer, 2 = 2nd player computer + * @returns Score for what dart hit + */ + int throwDart(int dartNum, int computer); + + /** + * Draw a dart moving towards the board + */ + void drawDartThrow(const Common::Point &pt); + + /** + * Erases the power bars + */ + void erasePowerBars(); + + /** + * Show a gradually incrementing incrementing power that bar. If goToPower is provided, it will + * increment to that power level ignoring all keyboard input (ie. for computer throws). + * Otherwise, it will increment until either a key/mouse button is pressed, or it reaches the end + */ + int doPowerBar(const Common::Point &pt, byte color, int goToPower, bool isVertical); + + /** + * Returns true if a mouse button or key is pressed. + */ + bool dartHit(); + + /** + * Return the score of the given location on the dart-board + */ + int dartScore(const Common::Point &pt); + + /** + * Calculates where a computer player is trying to throw their dart, and choose the actual + * point that was hit with some margin of error + */ + Common::Point getComputerDartDest(int playerNum); + + /** + * Returns the center position for the area of the dartboard with a given number + */ + bool findNumberOnBoard(int aim, Common::Point &pt); +public: + Darts(ScalpelEngine *vm); + + /** + * Main method for playing darts game + */ + void playDarts(); +}; + +} // End of namespace Scalpel + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/scalpel/drivers/adlib.cpp b/engines/sherlock/scalpel/drivers/adlib.cpp new file mode 100644 index 0000000000..372db5eb55 --- /dev/null +++ b/engines/sherlock/scalpel/drivers/adlib.cpp @@ -0,0 +1,638 @@ +/* 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 "sherlock/sherlock.h" +#include "sherlock/scalpel/drivers/mididriver.h" + +#include "common/file.h" +#include "common/system.h" +#include "common/textconsole.h" + +#include "audio/fmopl.h" +#include "audio/softsynth/emumidi.h" + +namespace Sherlock { + +#define SHERLOCK_ADLIB_VOICES_COUNT 9 +#define SHERLOCK_ADLIB_NOTES_COUNT 96 + +byte adlib_Operator1Register[SHERLOCK_ADLIB_VOICES_COUNT] = { + 0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12 +}; + +byte adlib_Operator2Register[SHERLOCK_ADLIB_VOICES_COUNT] = { + 0x03, 0x04, 0x05, 0x0B, 0x0C, 0x0D, 0x13, 0x14, 0x15 +}; + +struct adlib_percussionChannelEntry { + byte requiredNote; + byte replacementNote; +}; + +// hardcoded, dumped from ADHOM.DRV +const adlib_percussionChannelEntry adlib_percussionChannelTable[SHERLOCK_ADLIB_VOICES_COUNT] = { + { 0x00, 0x00 }, + { 0x00, 0x00 }, + { 0x00, 0x00 }, + { 0x00, 0x00 }, + { 0x00, 0x00 }, + { 0x00, 0x00 }, + { 0x24, 0x0C }, + { 0x38, 0x01 }, + { 0x26, 0x1E } +}; + +struct adlib_InstrumentEntry { + byte reg20op1; + byte reg40op1; + byte reg60op1; + byte reg80op1; + byte regE0op1; + byte reg20op2; + byte reg40op2; + byte reg60op2; + byte reg80op2; + byte regE0op2; + byte regC0; + byte frequencyAdjust; +}; + +// hardcoded, dumped from ADHOM.DRV +const adlib_InstrumentEntry adlib_instrumentTable[] = { + { 0x71, 0x89, 0x51, 0x11, 0x00, 0x61, 0x23, 0x42, 0x15, 0x01, 0x02, 0xF4 }, + { 0x22, 0x20, 0x97, 0x89, 0x00, 0xA2, 0x1F, 0x70, 0x07, 0x00, 0x0A, 0xF4 }, + { 0x70, 0x1A, 0x64, 0x13, 0x00, 0x20, 0x1F, 0x53, 0x46, 0x00, 0x0E, 0xF4 }, + { 0xB6, 0x4A, 0xB6, 0x32, 0x00, 0x11, 0x2B, 0xD1, 0x31, 0x00, 0x0E, 0xE8 }, + { 0x71, 0x8B, 0x51, 0x11, 0x00, 0x61, 0x20, 0x32, 0x35, 0x01, 0x02, 0xF4 }, + { 0x71, 0x8A, 0x51, 0x11, 0x00, 0x61, 0x20, 0x32, 0x25, 0x01, 0x02, 0xF4 }, + { 0x23, 0x0F, 0xF4, 0x04, 0x02, 0x2F, 0x25, 0xF0, 0x43, 0x00, 0x06, 0xE8 }, + { 0x71, 0x1C, 0x71, 0x03, 0x00, 0x21, 0x1F, 0x54, 0x17, 0x00, 0x0E, 0xF4 }, + { 0x71, 0x8A, 0x6E, 0x17, 0x00, 0x25, 0x27, 0x6B, 0x0E, 0x00, 0x02, 0xF4 }, + { 0x71, 0x1D, 0x81, 0x03, 0x00, 0x21, 0x1F, 0x64, 0x17, 0x00, 0x0E, 0xF4 }, + { 0x01, 0x4B, 0xF1, 0x50, 0x00, 0x01, 0x23, 0xD2, 0x76, 0x00, 0x06, 0xF4 }, + { 0x2F, 0xCA, 0xF8, 0xE5, 0x00, 0x21, 0x1F, 0xC0, 0xFF, 0x00, 0x00, 0xF4 }, + { 0x29, 0xCD, 0xF0, 0x91, 0x00, 0x21, 0x1F, 0xE0, 0x86, 0x00, 0x02, 0xF4 }, + { 0x24, 0xD0, 0xF0, 0x01, 0x00, 0x21, 0x1F, 0xE0, 0x86, 0x00, 0x02, 0xF4 }, + { 0x23, 0xC8, 0xF0, 0x01, 0x00, 0x21, 0x1F, 0xE0, 0x86, 0x00, 0x02, 0xF4 }, + { 0x64, 0xC9, 0xB0, 0x01, 0x00, 0x61, 0x1F, 0xF0, 0x86, 0x00, 0x02, 0xF4 }, + { 0x33, 0x85, 0xA1, 0x10, 0x00, 0x15, 0x9F, 0x72, 0x23, 0x00, 0x08, 0xF4 }, + { 0x31, 0x85, 0xA1, 0x10, 0x00, 0x15, 0x9F, 0x73, 0x33, 0x00, 0x08, 0xF4 }, + { 0x31, 0x81, 0xA1, 0x30, 0x00, 0x16, 0x9F, 0xC2, 0x74, 0x00, 0x08, 0xF4 }, + { 0x03, 0x8A, 0xF0, 0x7B, 0x00, 0x02, 0x9F, 0xF4, 0x7B, 0x00, 0x08, 0xF4 }, + { 0x03, 0x8A, 0xF0, 0x7B, 0x00, 0x01, 0x9F, 0xF4, 0x7B, 0x00, 0x08, 0xF4 }, + { 0x23, 0x8A, 0xF2, 0x7B, 0x00, 0x01, 0x9F, 0xF4, 0x7B, 0x00, 0x08, 0xF4 }, + { 0x32, 0x80, 0x01, 0x10, 0x00, 0x12, 0x9F, 0x72, 0x33, 0x00, 0x08, 0xF4 }, + { 0x32, 0x80, 0x01, 0x10, 0x00, 0x14, 0x9F, 0x73, 0x33, 0x00, 0x08, 0xF4 }, + { 0x31, 0x16, 0x73, 0x8E, 0x00, 0x21, 0x1F, 0x80, 0x9E, 0x00, 0x0E, 0xF4 }, + { 0x30, 0x16, 0x73, 0x7E, 0x00, 0x21, 0x1F, 0x80, 0x9E, 0x00, 0x0E, 0x00 }, + { 0x31, 0x94, 0x33, 0x73, 0x00, 0x21, 0x1F, 0xA0, 0x97, 0x00, 0x0E, 0xF4 }, + { 0x31, 0x94, 0xD3, 0x73, 0x00, 0x21, 0x20, 0xA0, 0x97, 0x00, 0x0E, 0xF4 }, + { 0x31, 0x45, 0xF1, 0x53, 0x00, 0x32, 0x1F, 0xF2, 0x27, 0x00, 0x06, 0xF4 }, + { 0x13, 0x0C, 0xF2, 0x01, 0x00, 0x15, 0x2F, 0xF2, 0xB6, 0x00, 0x08, 0xF4 }, + { 0x11, 0x0C, 0xF2, 0x01, 0x00, 0x11, 0x1F, 0xF2, 0xB6, 0x00, 0x08, 0xF4 }, + { 0x11, 0x0A, 0xFE, 0x04, 0x00, 0x11, 0x1F, 0xF2, 0xBD, 0x00, 0x08, 0xF4 }, + { 0x16, 0x4D, 0xFA, 0x11, 0x00, 0xE1, 0x20, 0xF1, 0xF1, 0x00, 0x08, 0xF4 }, + { 0x16, 0x40, 0xBA, 0x11, 0x00, 0xF1, 0x20, 0x24, 0x31, 0x00, 0x08, 0xF4 }, + { 0x61, 0xA7, 0x72, 0x8E, 0x00, 0xE1, 0x9F, 0x50, 0x1A, 0x00, 0x02, 0xF4 }, + { 0x18, 0x4D, 0x32, 0x13, 0x00, 0xE1, 0x20, 0x51, 0xE3, 0x00, 0x08, 0xF4 }, + { 0x17, 0xC0, 0x12, 0x41, 0x00, 0x31, 0x9F, 0x13, 0x31, 0x00, 0x06, 0xF4 }, + { 0x03, 0x8F, 0xF5, 0x55, 0x00, 0x21, 0x9F, 0xF3, 0x33, 0x00, 0x00, 0xF4 }, + { 0x13, 0x4D, 0xFA, 0x11, 0x00, 0xE1, 0x20, 0xF1, 0xF1, 0x00, 0x08, 0xF4 }, + { 0x11, 0x43, 0x20, 0x15, 0x00, 0xF1, 0x20, 0x31, 0xF8, 0x00, 0x08, 0xF4 }, + { 0x11, 0x03, 0x82, 0x97, 0x00, 0xE4, 0x60, 0xF0, 0xF2, 0x00, 0x08, 0xF4 }, + { 0x05, 0x40, 0xD1, 0x53, 0x00, 0x14, 0x1F, 0x51, 0x71, 0x00, 0x06, 0xF4 }, + { 0xF1, 0x01, 0x77, 0x17, 0x00, 0x21, 0x1F, 0x81, 0x18, 0x00, 0x02, 0xF4 }, + { 0xF1, 0x18, 0x32, 0x11, 0x00, 0xE1, 0x1F, 0xF1, 0x13, 0x00, 0x00, 0xF4 }, + { 0x73, 0x48, 0xF1, 0x53, 0x00, 0x71, 0x1F, 0xF1, 0x06, 0x00, 0x08, 0xF4 }, + { 0x71, 0x8D, 0x71, 0x11, 0x00, 0x61, 0x5F, 0x72, 0x15, 0x00, 0x06, 0xF4 }, + { 0xD7, 0x4F, 0xF2, 0x61, 0x00, 0xD2, 0x1F, 0xF1, 0xB2, 0x00, 0x08, 0xF4 }, + { 0x01, 0x11, 0xF0, 0xFF, 0x00, 0x01, 0x1F, 0xF0, 0xF8, 0x00, 0x0A, 0xF4 }, + { 0x31, 0x8B, 0x41, 0x11, 0x00, 0x61, 0x1F, 0x22, 0x13, 0x00, 0x06, 0xF4 }, + { 0x71, 0x1C, 0x71, 0x03, 0x00, 0x21, 0x1F, 0x64, 0x07, 0x00, 0x0E, 0xF4 }, + { 0x31, 0x8B, 0x41, 0x11, 0x00, 0x61, 0x1F, 0x32, 0x15, 0x00, 0x02, 0xF4 }, + { 0x71, 0x1C, 0xFD, 0x13, 0x00, 0x21, 0x1F, 0xE7, 0xD6, 0x00, 0x0E, 0xF4 }, + { 0x71, 0x1C, 0x51, 0x03, 0x00, 0x21, 0x1F, 0x54, 0x67, 0x00, 0x0E, 0xF4 }, + { 0x71, 0x1C, 0x51, 0x03, 0x00, 0x21, 0x1F, 0x54, 0x17, 0x00, 0x0E, 0xF4 }, + { 0x71, 0x1C, 0x54, 0x15, 0x00, 0x21, 0x1F, 0x53, 0x49, 0x00, 0x0E, 0xF4 }, + { 0x71, 0x56, 0x51, 0x03, 0x00, 0x61, 0x1F, 0x54, 0x17, 0x00, 0x0E, 0xF4 }, + { 0x71, 0x1C, 0x51, 0x03, 0x00, 0x21, 0x1F, 0x54, 0x17, 0x00, 0x0E, 0xF4 }, + { 0x02, 0x29, 0xF5, 0x75, 0x00, 0x01, 0x9F, 0xF2, 0xF3, 0x00, 0x00, 0xF4 }, + { 0x02, 0x29, 0xF0, 0x75, 0x00, 0x01, 0x9F, 0xF4, 0x33, 0x00, 0x00, 0xF4 }, + { 0x01, 0x49, 0xF1, 0x53, 0x00, 0x11, 0x1F, 0xF1, 0x74, 0x00, 0x06, 0xF4 }, + { 0x01, 0x89, 0xF1, 0x53, 0x00, 0x11, 0x1F, 0xF1, 0x74, 0x00, 0x06, 0xF4 }, + { 0x02, 0x89, 0xF1, 0x53, 0x00, 0x11, 0x1F, 0xF1, 0x74, 0x00, 0x06, 0xF4 }, + { 0x02, 0x80, 0xF1, 0x53, 0x00, 0x11, 0x1F, 0xF1, 0x74, 0x00, 0x06, 0xF4 }, + { 0x01, 0x40, 0xF1, 0x53, 0x00, 0x08, 0x5F, 0xF1, 0x53, 0x00, 0x00, 0xF4 }, + { 0x21, 0x15, 0xD3, 0x2C, 0x00, 0x21, 0x9F, 0xC3, 0x2C, 0x00, 0x0A, 0xF4 }, + { 0x01, 0x18, 0xD4, 0xF2, 0x00, 0x21, 0x9F, 0xC4, 0x8A, 0x00, 0x0A, 0xF4 }, + { 0x01, 0x4E, 0xF0, 0x7B, 0x00, 0x11, 0x1F, 0xF4, 0xC8, 0x00, 0x04, 0xF4 }, + { 0x01, 0x44, 0xF0, 0xAB, 0x00, 0x11, 0x1F, 0xF3, 0xAB, 0x00, 0x04, 0xF4 }, + { 0x53, 0x0E, 0xF4, 0xC8, 0x00, 0x11, 0x1F, 0xF1, 0xBB, 0x00, 0x04, 0xF4 }, + { 0x53, 0x0B, 0xF2, 0xC8, 0x00, 0x11, 0x1F, 0xF2, 0xC5, 0x00, 0x04, 0xF4 }, + { 0x21, 0x15, 0xB4, 0x4C, 0x00, 0x21, 0x1F, 0x94, 0xAC, 0x00, 0x0A, 0xF4 }, + { 0x21, 0x15, 0x94, 0x1C, 0x00, 0x21, 0x1F, 0x64, 0xAC, 0x00, 0x0A, 0xF4 }, + { 0x22, 0x1B, 0x97, 0x89, 0x00, 0xA2, 0x1F, 0x70, 0x07, 0x00, 0x0A, 0xF4 }, + { 0x21, 0x19, 0x77, 0xBF, 0x00, 0xA1, 0x9F, 0x60, 0x2A, 0x00, 0x06, 0xF4 }, + { 0xA1, 0x13, 0xD6, 0xAF, 0x00, 0xE2, 0x9F, 0x60, 0x2A, 0x00, 0x02, 0xF4 }, + { 0xA2, 0x1D, 0x95, 0x24, 0x00, 0xE2, 0x9F, 0x60, 0x2A, 0x00, 0x02, 0xF4 }, + { 0x32, 0x9A, 0x51, 0x19, 0x00, 0x61, 0x9F, 0x60, 0x39, 0x00, 0x0C, 0xF4 }, + { 0xA4, 0x12, 0xF4, 0x30, 0x00, 0xE2, 0x9F, 0x60, 0x2A, 0x00, 0x02, 0xF4 }, + { 0x21, 0x16, 0x63, 0x0E, 0x00, 0x21, 0x1F, 0x63, 0x0E, 0x00, 0x0C, 0xF4 }, + { 0x31, 0x16, 0x63, 0x0A, 0x00, 0x21, 0x1F, 0x63, 0x0B, 0x00, 0x0C, 0xF4 }, + { 0x21, 0x1B, 0x63, 0x0A, 0x00, 0x21, 0x1F, 0x63, 0x0B, 0x00, 0x0C, 0xF4 }, + { 0x20, 0x1B, 0x63, 0x0A, 0x00, 0x21, 0x1F, 0x63, 0x0B, 0x00, 0x0C, 0xF4 }, + { 0x32, 0x1C, 0x82, 0x18, 0x00, 0x61, 0x9F, 0x60, 0x07, 0x00, 0x0C, 0xF4 }, + { 0x32, 0x18, 0x61, 0x14, 0x00, 0xE1, 0x9F, 0x72, 0x16, 0x00, 0x0C, 0xF4 }, + { 0x31, 0xC0, 0x77, 0x17, 0x00, 0x22, 0x1F, 0x6B, 0x09, 0x00, 0x02, 0xF4 }, + { 0x71, 0xC3, 0x8E, 0x17, 0x00, 0x22, 0x24, 0x8B, 0x0E, 0x00, 0x02, 0xF4 }, + { 0x70, 0x8D, 0x6E, 0x17, 0x00, 0x22, 0x1F, 0x6B, 0x0E, 0x00, 0x02, 0xF4 }, + { 0x24, 0x4F, 0xF2, 0x06, 0x00, 0x31, 0x1F, 0x52, 0x06, 0x00, 0x0E, 0xF4 }, + { 0x31, 0x1B, 0x64, 0x07, 0x00, 0x61, 0x1F, 0xD0, 0x67, 0x00, 0x0E, 0xF4 }, + { 0x31, 0x1B, 0x61, 0x06, 0x00, 0x61, 0x1F, 0xD2, 0x36, 0x00, 0x0C, 0xF4 }, + { 0x31, 0x1F, 0x31, 0x06, 0x00, 0x61, 0x1F, 0x50, 0x36, 0x00, 0x0C, 0xF4 }, + { 0x31, 0x1F, 0x41, 0x06, 0x00, 0x61, 0x1F, 0xA0, 0x36, 0x00, 0x0C, 0xF4 }, + { 0x21, 0x9A, 0x53, 0x56, 0x00, 0x21, 0x9F, 0xA0, 0x16, 0x00, 0x0E, 0xF4 }, + { 0x21, 0x9A, 0x53, 0x56, 0x00, 0x21, 0x9F, 0xA0, 0x16, 0x00, 0x0E, 0xF4 }, + { 0x61, 0x19, 0x53, 0x58, 0x00, 0x21, 0x1F, 0xA0, 0x18, 0x00, 0x0C, 0xF4 }, + { 0x61, 0x19, 0x73, 0x57, 0x00, 0x21, 0x1F, 0xA0, 0x17, 0x00, 0x0C, 0xF4 }, + { 0x21, 0x1B, 0x71, 0xA6, 0x00, 0x21, 0x1F, 0xA1, 0x96, 0x00, 0x0E, 0xF4 }, + { 0x85, 0x91, 0xF5, 0x44, 0x00, 0xA1, 0x1F, 0xF0, 0x45, 0x00, 0x06, 0xF4 }, + { 0x07, 0x51, 0xF5, 0x33, 0x00, 0x61, 0x1F, 0xF0, 0x25, 0x00, 0x06, 0xF4 }, + { 0x13, 0x8C, 0xFF, 0x21, 0x00, 0x11, 0x9F, 0xFF, 0x03, 0x00, 0x0E, 0xF4 }, + { 0x38, 0x8C, 0xF3, 0x0D, 0x00, 0xB1, 0x5F, 0xF5, 0x33, 0x00, 0x0E, 0xF4 }, + { 0x87, 0x91, 0xF5, 0x55, 0x00, 0x22, 0x1F, 0xF0, 0x54, 0x00, 0x06, 0xF4 }, + { 0xB6, 0x4A, 0xB6, 0x32, 0x00, 0x11, 0x2B, 0xD1, 0x31, 0x00, 0x0E, 0xF4 }, + { 0x04, 0x00, 0xFE, 0xF0, 0x00, 0xC2, 0x1F, 0xF6, 0xB5, 0x00, 0x0E, 0xF4 }, + { 0x05, 0x4E, 0xDA, 0x15, 0x00, 0x01, 0x9F, 0xF0, 0x13, 0x00, 0x0A, 0xF4 }, + { 0x31, 0x44, 0xF2, 0x9A, 0x00, 0x32, 0x1F, 0xF0, 0x27, 0x00, 0x06, 0xF4 }, + { 0xB0, 0xC4, 0xA4, 0x02, 0x00, 0xD7, 0x9F, 0x40, 0x42, 0x00, 0x00, 0xF4 }, + { 0xCA, 0x84, 0xF0, 0xF0, 0x00, 0xCF, 0x1F, 0x59, 0x62, 0x00, 0x0C, 0xF4 }, + { 0x30, 0x35, 0xF5, 0xF0, 0x00, 0x35, 0x1F, 0xF0, 0x9B, 0x00, 0x02, 0xF4 }, + { 0x63, 0x0F, 0xF4, 0x04, 0x02, 0x6F, 0x1F, 0xF0, 0x43, 0x00, 0x06, 0xF4 }, + { 0x07, 0x40, 0x09, 0x53, 0x00, 0x05, 0x1F, 0xF6, 0x94, 0x00, 0x0E, 0xF4 }, + { 0x09, 0x4E, 0xDA, 0x25, 0x00, 0x01, 0x1F, 0xF1, 0x15, 0x00, 0x0A, 0xF4 }, + { 0x04, 0x00, 0xF3, 0xA0, 0x02, 0x04, 0x1F, 0xF8, 0x46, 0x00, 0x0E, 0xF4 }, + { 0x07, 0x00, 0xF0, 0xF0, 0x00, 0x00, 0x1F, 0x5C, 0xDC, 0x00, 0x0E, 0xF4 }, + { 0x1F, 0x1E, 0xE5, 0x5B, 0x00, 0x0F, 0x1F, 0x5D, 0xFA, 0x00, 0x0E, 0xF4 }, + { 0x11, 0x8A, 0xF1, 0x11, 0x00, 0x01, 0x5F, 0xF1, 0xB3, 0x00, 0x06, 0xF4 }, + { 0x00, 0x40, 0xD1, 0x53, 0x00, 0x00, 0x1F, 0xF2, 0x56, 0x00, 0x0E, 0xF4 }, + { 0x32, 0x44, 0xF8, 0xFF, 0x00, 0x11, 0x1F, 0xF5, 0x7F, 0x00, 0x0E, 0xF4 }, + { 0x00, 0x40, 0x09, 0x53, 0x00, 0x02, 0x1F, 0xF7, 0x94, 0x00, 0x0E, 0xF4 }, + { 0x11, 0x86, 0xF2, 0xA8, 0x00, 0x01, 0x9F, 0xA0, 0xA8, 0x00, 0x08, 0xF4 }, + { 0x00, 0x50, 0xF2, 0x70, 0x00, 0x13, 0x1F, 0xF2, 0x72, 0x00, 0x0E, 0xF4 }, + { 0xF0, 0x00, 0x11, 0x11, 0x00, 0xE0, 0xDF, 0x11, 0x11, 0x00, 0x0E, 0xF4 } +}; + +// hardcoded, dumped from ADHOM.DRV +uint16 adlib_FrequencyLookUpTable[SHERLOCK_ADLIB_NOTES_COUNT] = { + 0x0158, 0x016C, 0x0182, 0x0199, 0x01B1, 0x01CB, 0x01E6, 0x0203, 0x0222, 0x0242, + 0x0265, 0x0289, 0x0558, 0x056C, 0x0582, 0x0599, 0x05B1, 0x05CB, 0x05E6, 0x0603, + 0x0622, 0x0642, 0x0665, 0x0689, 0x0958, 0x096C, 0x0982, 0x0999, 0x09B1, 0x09CB, + 0x09E6, 0x0A03, 0x0A22, 0x0A42, 0x0A65, 0x0A89, 0x0D58, 0x0D6C, 0x0D82, 0x0D99, + 0x0DB1, 0x0DCB, 0x0DE6, 0x0E03, 0x0E22, 0x0E42, 0x0E65, 0x0E89, 0x1158, 0x116C, + 0x1182, 0x1199, 0x11B1, 0x11CB, 0x11E6, 0x1203, 0x1222, 0x1242, 0x1265, 0x1289, + 0x1558, 0x156C, 0x1582, 0x1599, 0x15B1, 0x15CB, 0x15E6, 0x1603, 0x1622, 0x1642, + 0x1665, 0x1689, 0x1958, 0x196C, 0x1982, 0x1999, 0x19B1, 0x19CB, 0x19E6, 0x1A03, + 0x1A22, 0x1A42, 0x1A65, 0x1A89, 0x1D58, 0x1D6C, 0x1D82, 0x1D99, 0x1DB1, 0x1DCB, + 0x1DE6, 0x1E03, 0x1E22, 0x1E42, 0x1E65, 0x1E89 +}; + +class MidiDriver_AdLib : public MidiDriver_Emulated { +public: + MidiDriver_AdLib(Audio::Mixer *mixer) + : MidiDriver_Emulated(mixer), _masterVolume(15), _opl(0) { + memset(_voiceChannelMapping, 0, sizeof(_voiceChannelMapping)); + } + virtual ~MidiDriver_AdLib() { } + + // MidiDriver + int open(); + void close(); + void send(uint32 b); + MidiChannel *allocateChannel() { return NULL; } + MidiChannel *getPercussionChannel() { return NULL; } + + // AudioStream + bool isStereo() const { return false; } + int getRate() const { return _mixer->getOutputRate(); } + int getPolyphony() const { return SHERLOCK_ADLIB_VOICES_COUNT; } + bool hasRhythmChannel() const { return false; } + + // MidiDriver_Emulated + void generateSamples(int16 *buf, int len); + + void setVolume(byte volume); + virtual uint32 property(int prop, uint32 param); + + void newMusicData(byte *musicData, int32 musicDataSize); + +private: + struct adlib_ChannelEntry { + bool inUse; + uint16 inUseTimer; + const adlib_InstrumentEntry *currentInstrumentPtr; + byte currentNote; + byte currentA0hReg; + byte currentB0hReg; + + adlib_ChannelEntry() : inUse(false), inUseTimer(0), currentInstrumentPtr(NULL), currentNote(0), + currentA0hReg(0), currentB0hReg(0) { } + }; + + OPL::OPL *_opl; + int _masterVolume; + + // points to a MIDI channel for each of the new voice channels + byte _voiceChannelMapping[SHERLOCK_ADLIB_VOICES_COUNT]; + + // stores information about all FM voice channels + adlib_ChannelEntry _channels[SHERLOCK_ADLIB_VOICES_COUNT]; + +protected: + void onTimer(); + +private: + void resetAdLib(); + void resetAdLib_OperatorRegisters(byte baseRegister, byte value); + void resetAdLib_FMVoiceChannelRegisters(byte baseRegister, byte value); + + void programChange(byte MIDIchannel, byte parameter); + void setRegister(int reg, int value); + void noteOn(byte MIDIchannel, byte note, byte velocity); + void noteOff(byte MIDIchannel, byte note); + void voiceOnOff(byte FMVoiceChannel, bool KeyOn, byte note, byte velocity); + + void pitchBendChange(byte MIDIchannel, byte parameter1, byte parameter2); +}; + +int MidiDriver_AdLib::open() { + int rate = _mixer->getOutputRate(); + + debugC(kDebugLevelAdLibDriver, "AdLib: starting driver"); + + _opl = OPL::Config::create(OPL::Config::kOpl2); + + if (!_opl) + return -1; + + _opl->init(rate); + + MidiDriver_Emulated::open(); + + _mixer->playStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1, _mixer->kMaxChannelVolume, 0, DisposeAfterUse::NO); + + return 0; +} + +void MidiDriver_AdLib::close() { + _mixer->stopHandle(_mixerSoundHandle); + + delete _opl; +} + +void MidiDriver_AdLib::setVolume(byte volume) { + _masterVolume = volume; + //renewNotes(-1, true); +} + +// this should/must get called per tick +// original driver did this before MIDI data processing on each tick +// we do it atm after MIDI data processing +void MidiDriver_AdLib::onTimer() { + for (byte FMvoiceChannel = 0; FMvoiceChannel < SHERLOCK_ADLIB_VOICES_COUNT; FMvoiceChannel++) { + if (_channels[FMvoiceChannel].inUse) { + _channels[FMvoiceChannel].inUseTimer++; + } + } +} + +// Called when a music track got loaded into memory +void MidiDriver_AdLib::newMusicData(byte *musicData, int32 musicDataSize) { + assert(musicDataSize >= 0x7F); + // MIDI Channel <-> FM Voice Channel mapping at offset 0x22 of music data + memcpy(&_voiceChannelMapping, musicData + 0x22, 9); + + // reset OPL + resetAdLib(); + + // reset current channel data + memset(&_channels, 0, sizeof(_channels)); +} + +void MidiDriver_AdLib::resetAdLib() { + + setRegister(0x01, 0x20); // enable waveform control on both operators + setRegister(0x04, 0xE0); // Timer control + + setRegister(0x08, 0); // select FM music mode + setRegister(0xBD, 0); // disable Rhythm + + // reset FM voice instrument data + resetAdLib_OperatorRegisters(0x20, 0); + resetAdLib_OperatorRegisters(0x60, 0); + resetAdLib_OperatorRegisters(0x80, 0); + resetAdLib_FMVoiceChannelRegisters(0xA0, 0); + resetAdLib_FMVoiceChannelRegisters(0xB0, 0); + resetAdLib_FMVoiceChannelRegisters(0xC0, 0); + resetAdLib_OperatorRegisters(0xE0, 0); + resetAdLib_OperatorRegisters(0x40, 0x3F); +} + +void MidiDriver_AdLib::resetAdLib_OperatorRegisters(byte baseRegister, byte value) { + byte operatorIndex; + + for (operatorIndex = 0; operatorIndex < 0x16; operatorIndex++) { + switch (operatorIndex) { + case 0x06: + case 0x07: + case 0x0E: + case 0x0F: + break; + default: + setRegister(baseRegister + operatorIndex, value); + } + } +} + +void MidiDriver_AdLib::resetAdLib_FMVoiceChannelRegisters(byte baseRegister, byte value) { + byte FMvoiceChannel; + + for (FMvoiceChannel = 0; FMvoiceChannel < SHERLOCK_ADLIB_VOICES_COUNT; FMvoiceChannel++) { + setRegister(baseRegister + FMvoiceChannel, value); + } +} + +// MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php +void MidiDriver_AdLib::send(uint32 b) { + byte command = b & 0xf0; + byte channel = b & 0xf; + byte op1 = (b >> 8) & 0xff; + byte op2 = (b >> 16) & 0xff; + + switch (command) { + case 0x80: + noteOff(channel, op1); + break; + case 0x90: + noteOn(channel, op1, op2); + break; + case 0xb0: // Control change + // Doesn't seem to be implemented in the Sherlock Holmes adlib driver + break; + case 0xc0: // Program Change + programChange(channel, op1); + break; + case 0xa0: // Polyphonic key pressure (aftertouch) + case 0xd0: // Channel pressure (aftertouch) + // Aftertouch doesn't seem to be implemented in the Sherlock Holmes adlib driver + break; + case 0xe0: + debugC(kDebugLevelAdLibDriver, "AdLib: pitch bend change"); + pitchBendChange(channel, op1, op2); + break; + case 0xf0: // SysEx + warning("ADLIB: SysEx: %x", b); + break; + default: + warning("ADLIB: Unknown event %02x", command); + } +} + +void MidiDriver_AdLib::generateSamples(int16 *data, int len) { + _opl->readBuffer(data, len); +} + +void MidiDriver_AdLib::noteOn(byte MIDIchannel, byte note, byte velocity) { + int16 oldestInUseChannel = -1; + uint16 oldestInUseTimer = 0; + + if (velocity == 0) + return noteOff(MIDIchannel, note); + + if (MIDIchannel != 9) { + // Not Percussion + for (byte FMvoiceChannel = 0; FMvoiceChannel < SHERLOCK_ADLIB_VOICES_COUNT; FMvoiceChannel++) { + if (_voiceChannelMapping[FMvoiceChannel] == MIDIchannel) { + if (!_channels[FMvoiceChannel].inUse) { + _channels[FMvoiceChannel].inUse = true; + _channels[FMvoiceChannel].currentNote = note; + + voiceOnOff(FMvoiceChannel, true, note, velocity); + return; + } + } + } + + // Look for oldest in-use channel + for (byte FMvoiceChannel = 0; FMvoiceChannel < SHERLOCK_ADLIB_VOICES_COUNT; FMvoiceChannel++) { + if (_voiceChannelMapping[FMvoiceChannel] == MIDIchannel) { + if (_channels[FMvoiceChannel].inUseTimer > oldestInUseTimer) { + oldestInUseTimer = _channels[FMvoiceChannel].inUseTimer; + oldestInUseChannel = FMvoiceChannel; + } + } + } + if (oldestInUseChannel >= 0) { + // channel found + debugC(kDebugLevelAdLibDriver, "AdLib: used In-Use channel"); + // original driver used note 0, we use the current note + // because using note 0 could create a bad note (out of index) and we check that. Original driver didn't. + voiceOnOff(oldestInUseChannel, false, _channels[oldestInUseChannel].currentNote, 0); + + _channels[oldestInUseChannel].inUse = true; + _channels[oldestInUseChannel].inUseTimer = 0; // safety, original driver also did this + _channels[oldestInUseChannel].currentNote = note; + voiceOnOff(oldestInUseChannel, true, note, velocity); + return; + } + debugC(kDebugLevelAdLibDriver, "AdLib: MIDI channel not mapped/all FM voice channels busy %d", MIDIchannel); + + } else { + // Percussion channel + for (byte FMvoiceChannel = 0; FMvoiceChannel < SHERLOCK_ADLIB_VOICES_COUNT; FMvoiceChannel++) { + if (_voiceChannelMapping[FMvoiceChannel] == MIDIchannel) { + if (note == adlib_percussionChannelTable[FMvoiceChannel].requiredNote) { + _channels[FMvoiceChannel].inUse = true; + _channels[FMvoiceChannel].currentNote = note; + + voiceOnOff(FMvoiceChannel, true, adlib_percussionChannelTable[FMvoiceChannel].replacementNote, velocity); + return; + } + } + } + debugC(kDebugLevelAdLibDriver, "AdLib: percussion MIDI channel not mapped/all FM voice channels busy"); + } +} + +void MidiDriver_AdLib::noteOff(byte MIDIchannel, byte note) { + for (byte FMvoiceChannel = 0; FMvoiceChannel < SHERLOCK_ADLIB_VOICES_COUNT; FMvoiceChannel++) { + if (_voiceChannelMapping[FMvoiceChannel] == MIDIchannel) { + if (_channels[FMvoiceChannel].currentNote == note) { + _channels[FMvoiceChannel].inUse = false; + _channels[FMvoiceChannel].inUseTimer = 0; + _channels[FMvoiceChannel].currentNote = 0; + + if (MIDIchannel != 9) { + // not-percussion + voiceOnOff(FMvoiceChannel, false, note, 0); + } else { + voiceOnOff(FMvoiceChannel, false, adlib_percussionChannelTable[FMvoiceChannel].replacementNote, 0); + } + return; + } + } + } +} + +void MidiDriver_AdLib::voiceOnOff(byte FMvoiceChannel, bool keyOn, byte note, byte velocity) { + byte frequencyOffset = 0; + uint16 frequency = 0; + byte op2RegAdjust = 0; + byte regValue40h = 0; + byte regValueA0h = 0; + byte regValueB0h = 0; + + // Look up frequency + if (_channels[FMvoiceChannel].currentInstrumentPtr) { + frequencyOffset = note + _channels[FMvoiceChannel].currentInstrumentPtr->frequencyAdjust; + } else { + frequencyOffset = note; + } + if (frequencyOffset >= SHERLOCK_ADLIB_NOTES_COUNT) { + warning("CRITICAL - AdLib driver: bad note!!!"); + return; + } + frequency = adlib_FrequencyLookUpTable[frequencyOffset]; + + if (keyOn) { + // adjust register 40h + if (_channels[FMvoiceChannel].currentInstrumentPtr) { + regValue40h = _channels[FMvoiceChannel].currentInstrumentPtr->reg40op2; + } + regValue40h = regValue40h - (velocity >> 3); + op2RegAdjust = adlib_Operator2Register[FMvoiceChannel]; + setRegister(0x40 + op2RegAdjust, regValue40h); + } + + regValueA0h = frequency & 0xFF; + regValueB0h = frequency >> 8; + if (keyOn) { + regValueB0h |= 0x20; // set Key-On flag + } + + setRegister(0xA0 + FMvoiceChannel, regValueA0h); + setRegister(0xB0 + FMvoiceChannel, regValueB0h); + _channels[FMvoiceChannel].currentA0hReg = regValueA0h; + _channels[FMvoiceChannel].currentB0hReg = regValueB0h; +} + +void MidiDriver_AdLib::pitchBendChange(byte MIDIchannel, byte parameter1, byte parameter2) { + uint16 channelFrequency = 0; + byte channelRegB0hWithoutFrequency = 0; + uint16 parameter = 0; + byte regValueA0h = 0; + byte regValueB0h = 0; + + for (byte FMvoiceChannel = 0; FMvoiceChannel < SHERLOCK_ADLIB_VOICES_COUNT; FMvoiceChannel++) { + if (_voiceChannelMapping[FMvoiceChannel] == MIDIchannel) { + if (_channels[FMvoiceChannel].inUse) { + // FM voice channel found and it's currently in use -> apply pitch bend change + + // Remove frequency bits from current channel B0h-register + channelFrequency = ((_channels[FMvoiceChannel].currentB0hReg << 8) | (_channels[FMvoiceChannel].currentA0hReg)) & 0x3FF; + channelRegB0hWithoutFrequency = _channels[FMvoiceChannel].currentB0hReg & 0xFC; + + if (parameter2 < 0x40) { + channelFrequency = channelFrequency / 2; + } else { + parameter2 = parameter2 - 0x40; + } + parameter1 = parameter1 * 2; + parameter = parameter1 | (parameter2 << 8); + parameter = parameter * 4; + + parameter = (parameter >> 8) + 0xFF; + channelFrequency = channelFrequency * parameter; + channelFrequency = (channelFrequency >> 8) | (parameter << 8); + + regValueA0h = channelFrequency & 0xFF; + regValueB0h = (channelFrequency >> 8) | channelRegB0hWithoutFrequency; + + setRegister(0xA0 + FMvoiceChannel, regValueA0h); + setRegister(0xB0 + FMvoiceChannel, regValueB0h); + } + } + } +} + +void MidiDriver_AdLib::programChange(byte MIDIchannel, byte op1) { + const adlib_InstrumentEntry *instrumentPtr; + byte op1Reg = 0; + byte op2Reg = 0; + + // setup instrument + instrumentPtr = &adlib_instrumentTable[op1]; + //warning("program change for MIDI channel %d, instrument id %d", MIDIchannel, op1); + + for (byte FMvoiceChannel = 0; FMvoiceChannel < SHERLOCK_ADLIB_VOICES_COUNT; FMvoiceChannel++) { + if (_voiceChannelMapping[FMvoiceChannel] == MIDIchannel) { + + op1Reg = adlib_Operator1Register[FMvoiceChannel]; + op2Reg = adlib_Operator2Register[FMvoiceChannel]; + + setRegister(0x20 + op1Reg, instrumentPtr->reg20op1); + setRegister(0x40 + op1Reg, instrumentPtr->reg40op1); + setRegister(0x60 + op1Reg, instrumentPtr->reg60op1); + setRegister(0x80 + op1Reg, instrumentPtr->reg80op1); + setRegister(0xE0 + op1Reg, instrumentPtr->regE0op1); + + setRegister(0x20 + op2Reg, instrumentPtr->reg20op2); + setRegister(0x40 + op2Reg, instrumentPtr->reg40op2); + setRegister(0x60 + op2Reg, instrumentPtr->reg60op2); + setRegister(0x80 + op2Reg, instrumentPtr->reg80op2); + setRegister(0xE0 + op2Reg, instrumentPtr->regE0op2); + + setRegister(0xC0 + FMvoiceChannel, instrumentPtr->regC0); + + // Remember instrument + _channels[FMvoiceChannel].currentInstrumentPtr = instrumentPtr; + } + } +} +void MidiDriver_AdLib::setRegister(int reg, int value) { + _opl->write(0x220, reg); + _opl->write(0x221, value); +} + +uint32 MidiDriver_AdLib::property(int prop, uint32 param) { + return 0; +} + +MidiDriver *MidiDriver_AdLib_create() { + return new MidiDriver_AdLib(g_system->getMixer()); +} + +void MidiDriver_AdLib_newMusicData(MidiDriver *driver, byte *musicData, int32 musicDataSize) { + static_cast<MidiDriver_AdLib *>(driver)->newMusicData(musicData, musicDataSize); +} + +} // End of namespace Sherlock diff --git a/engines/sherlock/scalpel/drivers/mididriver.h b/engines/sherlock/scalpel/drivers/mididriver.h new file mode 100644 index 0000000000..3cdc321487 --- /dev/null +++ b/engines/sherlock/scalpel/drivers/mididriver.h @@ -0,0 +1,41 @@ +/* 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 SHERLOCK_SOFTSEQ_MIDIDRIVER_H +#define SHERLOCK_SOFTSEQ_MIDIDRIVER_H + +#include "sherlock/sherlock.h" +#include "audio/mididrv.h" +#include "common/error.h" + +namespace Sherlock { + +extern MidiDriver *MidiDriver_AdLib_create(); +extern void MidiDriver_AdLib_newMusicData(MidiDriver *driver, byte *musicData, int32 musicDataSize); + +extern MidiDriver *MidiDriver_MT32_create(); +extern void MidiDriver_MT32_uploadPatches(MidiDriver *driver, byte *driverData, int32 driverSize); +extern void MidiDriver_MT32_newMusicData(MidiDriver *driver, byte *musicData, int32 musicDataSize); + +} // End of namespace Sherlock + +#endif // SHERLOCK_SOFTSEQ_MIDIDRIVER_H diff --git a/engines/sherlock/scalpel/drivers/mt32.cpp b/engines/sherlock/scalpel/drivers/mt32.cpp new file mode 100644 index 0000000000..feabb89c68 --- /dev/null +++ b/engines/sherlock/scalpel/drivers/mt32.cpp @@ -0,0 +1,286 @@ +/* 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 "sherlock/sherlock.h" +#include "sherlock/scalpel/drivers/mididriver.h" + +#include "common/config-manager.h" +#include "common/file.h" +#include "common/system.h" +#include "common/textconsole.h" + +//#include "audio/mididrv.h" + +namespace Sherlock { + +#define SHERLOCK_MT32_CHANNEL_COUNT 16 + +const byte mt32_reverbDataSysEx[] = { + 0x10, 0x00, 0x01, 0x01, 0x05, 0x05, 0xFF +}; + +class MidiDriver_MT32 : public MidiDriver { +public: + MidiDriver_MT32() { + _driver = NULL; + _isOpen = false; + _MT32 = false; + _nativeMT32 = false; + _baseFreq = 250; + + memset(_MIDIchannelActive, 1, sizeof(_MIDIchannelActive)); + } + virtual ~MidiDriver_MT32(); + + // MidiDriver + int open(); + void close(); + bool isOpen() const { return _isOpen; } + + void send(uint32 b); + + void newMusicData(byte *musicData, int32 musicDataSize); + + MidiChannel *allocateChannel() { + if (_driver) + return _driver->allocateChannel(); + return NULL; + } + MidiChannel *getPercussionChannel() { + if (_driver) + return _driver->getPercussionChannel(); + return NULL; + } + + void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) { + if (_driver) + _driver->setTimerCallback(timer_param, timer_proc); + } + + uint32 getBaseTempo() { + if (_driver) { + return _driver->getBaseTempo(); + } + return 1000000 / _baseFreq; + } + +protected: + Common::Mutex _mutex; + MidiDriver *_driver; + bool _MT32; + bool _nativeMT32; + + bool _isOpen; + int _baseFreq; + +private: + // points to a MIDI channel for each of the new voice channels + byte _MIDIchannelActive[SHERLOCK_MT32_CHANNEL_COUNT]; + +public: + void uploadMT32Patches(byte *driverData, int32 driverSize); + + void MT32SysEx(const byte *&dataPtr, int32 &bytesLeft); +}; + +MidiDriver_MT32::~MidiDriver_MT32() { + Common::StackLock lock(_mutex); + if (_driver) { + _driver->setTimerCallback(0, 0); + _driver->close(); + delete _driver; + } + _driver = NULL; +} + +int MidiDriver_MT32::open() { + assert(!_driver); + + debugC(kDebugLevelMT32Driver, "MT32: starting driver"); + + // Setup midi driver + MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_PREFER_MT32); + MusicType musicType = MidiDriver::getMusicType(dev); + + switch (musicType) { + case MT_MT32: + _MT32 = true; + _nativeMT32 = false; + break; + case MT_GM: + if (ConfMan.getBool("native_mt32")) { + _MT32 = true; + _nativeMT32 = true; + } + break; + default: + break; + } + + _driver = MidiDriver::createMidi(dev); + if (!_driver) + return 255; + + if (_nativeMT32) + _driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE); + + int ret = _driver->open(); + if (ret) + return ret; + + if (_MT32) + _driver->sendMT32Reset(); + else + _driver->sendGMReset(); + + return 0; +} + +void MidiDriver_MT32::close() { + if (_driver) { + _driver->close(); + } +} + +// Called when a music track got loaded into memory +void MidiDriver_MT32::newMusicData(byte *musicData, int32 musicDataSize) { + assert(musicDataSize >= 0x7F); // Security check + + // MIDI Channel Enable/Disable bytes at offset 0x2 of music data + memcpy(&_MIDIchannelActive, musicData + 0x2, SHERLOCK_MT32_CHANNEL_COUNT); + + // Send 16 bytes from offset 0x12 to MT32 + // All the music tracks of Sherlock seem to contain dummy data + // probably a feature, that was used in the game "Ski or Die" + // that's why we don't implement this + + // Also send these bytes to MT32 (SysEx) - seems to be reverb configuration + if (_MT32) { + const byte *reverbData = mt32_reverbDataSysEx; + int32 reverbDataSize = sizeof(mt32_reverbDataSysEx); + MT32SysEx(reverbData, reverbDataSize); + } +} + +void MidiDriver_MT32::uploadMT32Patches(byte *driverData, int32 driverSize) { + if (!_driver) + return; + + if (!_MT32) + return; + + // patch data starts at offset 0x863 + assert(driverSize == 0x13B9); // Security check + assert(driverData[0x863] == 0x7F); // another security check + + const byte *patchPtr = driverData + 0x863; + int32 bytesLeft = driverSize - 0x863; + + while(1) { + MT32SysEx(patchPtr, bytesLeft); + + assert(bytesLeft); + if (*patchPtr == 0x80) // List terminator + break; + } +} + +void MidiDriver_MT32::MT32SysEx(const byte *&dataPtr, int32 &bytesLeft) { + byte sysExMessage[270]; + uint16 sysExPos = 0; + byte sysExByte = 0; + uint16 sysExChecksum = 0; + + memset(&sysExMessage, 0, sizeof(sysExMessage)); + + sysExMessage[0] = 0x41; // Roland + sysExMessage[1] = 0x10; + sysExMessage[2] = 0x16; // Model MT32 + sysExMessage[3] = 0x12; // Command DT1 + + sysExPos = 4; + sysExChecksum = 0; + while (1) { + assert(bytesLeft); + + sysExByte = *dataPtr++; + bytesLeft--; + if (sysExByte == 0xff) + break; // Message done + + assert(sysExPos < sizeof(sysExMessage)); + sysExMessage[sysExPos++] = sysExByte; + sysExChecksum -= sysExByte; + } + + // Calculate checksum + assert(sysExPos < sizeof(sysExMessage)); + sysExMessage[sysExPos++] = sysExChecksum & 0x7f; + + debugC(kDebugLevelMT32Driver, "MT32: uploading patch data, size %d", sysExPos); + + // Send SysEx + _driver->sysEx(sysExMessage, sysExPos); + + // Wait the time it takes to send the SysEx data + uint32 delay = (sysExPos + 2) * 1000 / 3125; + + // Plus an additional delay for the MT-32 rev00 + if (_nativeMT32) + delay += 40; + + g_system->delayMillis(delay); +} + +// MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php +void MidiDriver_MT32::send(uint32 b) { + byte command = b & 0xf0; + byte channel = b & 0xf; + + if (command == 0xF0) { + if (_driver) { + _driver->send(b); + } + return; + } + + if (_MIDIchannelActive[channel]) { + // Only forward MIDI-data in case the channel is currently enabled via music-data + if (_driver) { + _driver->send(b); + } + } +} + +MidiDriver *MidiDriver_MT32_create() { + return new MidiDriver_MT32(); +} + +void MidiDriver_MT32_newMusicData(MidiDriver *driver, byte *musicData, int32 musicDataSize) { + static_cast<MidiDriver_MT32 *>(driver)->newMusicData(musicData, musicDataSize); +} + +void MidiDriver_MT32_uploadPatches(MidiDriver *driver, byte *driverData, int32 driverSize) { + static_cast<MidiDriver_MT32 *>(driver)->uploadMT32Patches(driverData, driverSize); +} + +} // End of namespace Sherlock diff --git a/engines/sherlock/scalpel/scalpel.cpp b/engines/sherlock/scalpel/scalpel.cpp new file mode 100644 index 0000000000..faead3be07 --- /dev/null +++ b/engines/sherlock/scalpel/scalpel.cpp @@ -0,0 +1,1159 @@ +/* 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 "engines/util.h" +#include "sherlock/scalpel/scalpel.h" +#include "sherlock/scalpel/scalpel_map.h" +#include "sherlock/scalpel/scalpel_people.h" +#include "sherlock/scalpel/scalpel_scene.h" +#include "sherlock/scalpel/tsage/logo.h" +#include "sherlock/sherlock.h" +#include "sherlock/music.h" +#include "sherlock/animation.h" +// for 3DO +#include "sherlock/scalpel/3do/movie_decoder.h" + +namespace Sherlock { + +namespace Scalpel { + +#define PROLOGUE_NAMES_COUNT 6 + +// The following are a list of filenames played in the prologue that have +// special effects associated with them at specific frames +static const char *const PROLOGUE_NAMES[PROLOGUE_NAMES_COUNT] = { + "subway1", "subway2", "finale2", "suicid", "coff3", "coff4" +}; + +static const int PROLOGUE_FRAMES[6][9] = { + { 4, 26, 54, 72, 92, 134, FRAMES_END }, + { 2, 80, 95, 117, 166, FRAMES_END }, + { 1, FRAMES_END }, + { 42, FRAMES_END }, + { FRAMES_END }, + { FRAMES_END } +}; + +#define TITLE_NAMES_COUNT 7 + +// Title animations file list +static const char *const TITLE_NAMES[TITLE_NAMES_COUNT] = { + "27pro1", "14note", "coff1", "coff2", "coff3", "coff4", "14kick" +}; + +static const int TITLE_FRAMES[7][9] = { + { 29, 131, FRAMES_END }, + { 55, 80, 95, 117, 166, FRAMES_END }, + { 15, FRAMES_END }, + { 4, 37, 92, FRAMES_END }, + { 2, 43, FRAMES_END }, + { 2, FRAMES_END }, + { 10, 50, FRAMES_END } +}; + +#define NUM_PLACES 100 + +static const int MAP_X[NUM_PLACES] = { + 0, 368, 0, 219, 0, 282, 0, 43, 0, 0, 396, 408, 0, 0, 0, 568, 37, 325, + 28, 0, 263, 36, 148, 469, 342, 143, 443, 229, 298, 0, 157, 260, 432, + 174, 0, 351, 0, 528, 0, 136, 0, 0, 0, 555, 165, 0, 506, 0, 0, 344, 0, 0 +}; +static const int MAP_Y[NUM_PLACES] = { + 0, 147, 0, 166, 0, 109, 0, 61, 0, 0, 264, 70, 0, 0, 0, 266, 341, 30, 275, + 0, 294, 146, 311, 230, 184, 268, 133, 94, 207, 0, 142, 142, 330, 255, 0, + 37, 0, 70, 0, 116, 0, 0, 0, 50, 21, 0, 303, 0, 0, 229, 0, 0 +}; + +static const int MAP_TRANSLATE[NUM_PLACES] = { + 0, 0, 0, 1, 0, 2, 0, 3, 4, 0, 4, 6, 0, 0, 0, 8, 9, 10, 11, 0, 12, 13, 14, 7, + 15, 16, 17, 18, 19, 0, 20, 21, 22, 23, 0, 24, 0, 25, 0, 26, 0, 0, 0, 27, + 28, 0, 29, 0, 0, 30, 0 +}; + +static const byte MAP_SEQUENCES[3][MAX_FRAME] = { + { 1, 1, 2, 3, 4, 0 }, // Overview Still + { 5, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 0 }, + { 5, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 0 } +}; + +#define MAX_PEOPLE 66 + +struct PeopleData { + const char *portrait; + const char *name; + byte stillSequences[MAX_TALK_SEQUENCES]; + byte talkSequences[MAX_TALK_SEQUENCES]; +}; + +const PeopleData PEOPLE_DATA[MAX_PEOPLE] = { + { "HOLM", "Sherlock Holmes", { 1, 0, 0 }, { 1, 0, 0 } }, + { "WATS", "Dr. Watson", { 6, 0, 0 }, { 5, 5, 6, 7, 8, 7, 8, 6, 0, 0 } }, + { "LEST", "Inspector Lestrade", { 4, 0, 0 }, { 2, 0, 0 } }, + { "CON1", "Constable O'Brien", { 2, 0, 0 }, { 1, 0, 0 } }, + { "CON2", "Constable Lewis", { 2, 0, 0 }, { 1, 0, 0 } }, + { "SHEI", "Sheila Parker", { 2, 0, 0 }, { 2, 3, 0, 0 } }, + { "HENR", "Henry Carruthers", { 3, 0, 0 }, { 3, 0, 0 } }, + { "LESL", "Lesley", { 9, 0, 0 }, { 1, 2, 3, 2, 1, 2, 3, 0, 0 } }, + { "USH1", "An Usher", { 13, 0, 0 }, { 13, 14, 0, 0 } }, + { "USH2", "An Usher", { 2, 0, 0 }, { 2, 0, 0 } }, + { "FRED", "Fredrick Epstein", { 4, 0, 0 }, { 1, 2, 3, 4, 3, 4, 3, 2, 0, 0 } }, + { "WORT", "Mrs. Worthington", { 9, 0, 0 }, { 8, 0, 0 } }, + { "COAC", "The Coach", { 2, 0, 0 }, { 1, 2, 3, 4, 5, 4, 3, 2, 0, 0 } }, + { "PLAY", "A Player", { 8, 0, 0 }, { 7, 8, 0, 0 } }, + { "WBOY", "Tim", { 13, 0, 0 }, { 12, 13, 0, 0 } }, + { "JAME", "James Sanders", { 6, 0, 0 }, { 3, 4, 0, 0 } }, + { "BELL", "Belle", { 1, 0, 0 }, { 4, 5, 0, 0 } }, + { "GIRL", "Cleaning Girl", { 20, 0, 0 }, { 14, 15, 16, 17, 18, 19, 20, 20, 20, 0, 0 } }, + { "EPST", "Fredrick Epstein", { 17, 0, 0 }, { 16, 17, 18, 18, 18, 17, 17, 0, 0 } }, + { "WIGG", "Wiggins", { 3, 0, 0 }, { 2, 3, 0, 0 } }, + { "PAUL", "Paul", { 2, 0, 0 }, { 1, 2, 0, 0 } }, + { "BART", "The Bartender", { 1, 0, 0 }, { 1, 0, 0 } }, + { "DIRT", "A Dirty Drunk", { 1, 0, 0 }, { 1, 0, 0 } }, + { "SHOU", "A Shouting Drunk", { 1, 0, 0 }, { 1, 0, 0 } }, + { "STAG", "A Staggering Drunk", { 1, 0, 0 }, { 1, 0, 0 } }, + { "BOUN", "The Bouncer", { 1, 0, 0 }, { 1, 0, 0 } }, + { "SAND", "James Sanders", { 6, 0, 0 }, { 5, 6, 0, 0 } }, + { "CORO", "The Coroner", { 6, 0, 0 }, { 4, 5, 0, 0 } }, + { "EQUE", "Reginald Snipes", { 1, 0, 0 }, { 1, 0, 0 } }, + { "GEOR", "George Blackwood", { 1, 0, 0 }, { 1, 0, 0 } }, + { "LARS", "Lars", { 7, 0, 0 }, { 5, 6, 0, 0 } }, + { "PARK", "Sheila Parker", { 1, 0, 0 }, { 1, 0, 0 } }, + { "CHEM", "The Chemist", { 8, 0, 0 }, { 8, 9, 0, 0 } }, + { "GREG", "Inspector Gregson", { 6, 0, 0 }, { 5, 6, 0, 0 } }, + { "LAWY", "Jacob Farthington", { 1, 0, 0 }, { 1, 0, 0 } }, + { "MYCR", "Mycroft", { 1, 0, 0 }, { 1, 0, 0 } }, + { "SHER", "Old Sherman", { 7, 0, 0 }, { 7, 8, 0, 0 } }, + { "CHMB", "Richard", { 1, 0, 0 }, { 1, 0, 0 } }, + { "BARM", "The Barman", { 1, 0, 0 }, { 1, 0, 0 } }, + { "DAND", "A Dandy Player", { 1, 0, 0 }, { 1, 0, 0 } }, + { "ROUG", "A Rough-looking Player", { 1, 0, 0 }, { 1, 0, 0 } }, + { "SPEC", "A Spectator", { 1, 0, 0 }, { 1, 0, 0 } }, + { "HUNT", "Robert Hunt", { 1, 0, 0 }, { 1, 0, 0 } }, + { "VIOL", "Violet", { 3, 0, 0 }, { 3, 4, 0, 0 } }, + { "PETT", "Pettigrew", { 1, 0, 0 }, { 1, 0, 0 } }, + { "APPL", "Augie", { 8, 0, 0 }, { 14, 15, 0, 0 } }, + { "ANNA", "Anna Carroway", { 16, 0, 0 }, { 3, 4, 5, 6, 0, 0 } }, + { "GUAR", "A Guard", { 1, 0, 0 }, { 4, 5, 6, 0, 0 } }, + { "ANTO", "Antonio Caruso", { 8, 0, 0 }, { 7, 8, 0, 0 } }, + { "TOBY", "Toby the Dog", { 1, 0, 0 }, { 1, 0, 0 } }, + { "KING", "Simon Kingsley", { 13, 0, 0 }, { 13, 14, 0, 0 } }, + { "ALFR", "Alfred", { 2, 0, 0 }, { 2, 3, 0, 0 } }, + { "LADY", "Lady Brumwell", { 1, 0, 0 }, { 3, 4, 0, 0 } }, + { "ROSA", "Madame Rosa", { 1, 0, 0 }, { 1, 30, 0, 0 } }, + { "LADB", "Lady Brumwell", { 1, 0, 0 }, { 3, 4, 0, 0 } }, + { "MOOR", "Joseph Moorehead", { 1, 0, 0 }, { 1, 0, 0 } }, + { "BEAL", "Mrs. Beale", { 5, 0, 0 }, { 14, 15, 16, 17, 18, 19, 20, 0, 0 } }, + { "LION", "Felix", { 1, 0, 0 }, { 1, 0, 0 } }, + { "HOLL", "Hollingston", { 1, 0, 0 }, { 1, 0, 0 } }, + { "CALL", "Constable Callaghan", { 1, 0, 0 }, { 1, 0, 0 } }, + { "JERE", "Sergeant Duncan", { 2, 0, 0 }, { 1, 1, 2, 2, 0, 0 } }, + { "LORD", "Lord Brumwell", { 1, 0, 0 }, { 9, 10, 0, 0 } }, + { "NIGE", "Nigel Jaimeson", { 1, 0, 0 }, { 1, 2, 0, 138, 3, 4, 0, 138, 0, 0 } }, + { "JONA", "Jonas", { 1, 0, 0 }, { 1, 8, 0, 0 } }, + { "DUGA", "Constable Dugan", { 1, 0, 0 }, { 1, 0, 0 } }, + { "INSP", "Inspector Lestrade", { 4, 0, 0 }, { 2, 0, 0 } } +}; + +/*----------------------------------------------------------------*/ + +ScalpelEngine::ScalpelEngine(OSystem *syst, const SherlockGameDescription *gameDesc) : + SherlockEngine(syst, gameDesc) { + _darts = nullptr; + _mapResult = 0; +} + +ScalpelEngine::~ScalpelEngine() { + delete _darts; +} + +void ScalpelEngine::initialize() { + // 3DO actually uses RGB555, but some platforms of ours only support RGB565, so we use that + + if (getPlatform() == Common::kPlatform3DO) { + const Graphics::PixelFormat pixelFormatRGB565 = Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0); + // 320x200 16-bit RGB565 for 3DO support + initGraphics(320, 200, false, &pixelFormatRGB565); + } else { + // 320x200 palettized + initGraphics(320, 200, false); + } + + // Let the base engine intialize + SherlockEngine::initialize(); + + _darts = new Darts(this); + + _flags.resize(100 * 8); + _flags[3] = true; // Turn on Alley + _flags[39] = true; // Turn on Baker Street + + if (!isDemo()) { + // Load the map co-ordinates for each scene and sequence data + ScalpelMap &map = *(ScalpelMap *)_map; + map.loadPoints(NUM_PLACES, &MAP_X[0], &MAP_Y[0], &MAP_TRANSLATE[0]); + map.loadSequences(3, &MAP_SEQUENCES[0][0]); + map._oldCharPoint = BAKER_ST_EXTERIOR; + } + + // Load the inventory + loadInventory(); + + // Set up list of people + for (int idx = 0; idx < MAX_PEOPLE; ++idx) + _people->_characters.push_back(PersonData(PEOPLE_DATA[idx].name, PEOPLE_DATA[idx].portrait, + PEOPLE_DATA[idx].stillSequences, PEOPLE_DATA[idx].talkSequences)); + + _animation->setPrologueNames(&PROLOGUE_NAMES[0], PROLOGUE_NAMES_COUNT); + _animation->setPrologueFrames(&PROLOGUE_FRAMES[0][0], 6, 9); + + _animation->setTitleNames(&TITLE_NAMES[0], TITLE_NAMES_COUNT); + _animation->setTitleFrames(&TITLE_FRAMES[0][0], 7, 9); + + // Starting scene + if (isDemo() && _interactiveFl) + _scene->_goToScene = 3; + else + _scene->_goToScene = 4; +} + +void ScalpelEngine::showOpening() { + bool finished = true; + + if (isDemo() && _interactiveFl) + return; + + if (getPlatform() == Common::kPlatform3DO) { + show3DOSplash(); + + finished = showCityCutscene3DO(); + if (finished) + finished = showAlleyCutscene3DO(); + if (finished) + finished = showStreetCutscene3DO(); + if (finished) + showOfficeCutscene3DO(); + + _events->clearEvents(); + _music->stopMusic(); + return; + } + + TsAGE::Logo::show(this); + finished = showCityCutscene(); + if (finished) + finished = showAlleyCutscene(); + if (finished) + finished = showStreetCutscene(); + if (finished) + showOfficeCutscene(); + + _events->clearEvents(); + _music->stopMusic(); +} + +bool ScalpelEngine::showCityCutscene() { + byte greyPalette[PALETTE_SIZE]; + byte palette[PALETTE_SIZE]; + + // Demo fades from black into grey and then fades from grey into the scene + Common::fill(&greyPalette[0], &greyPalette[PALETTE_SIZE], 142); + _screen->fadeIn((const byte *)greyPalette, 3); + + _music->playMusic("prolog1"); + _animation->_gfxLibraryFilename = "title.lib"; + _animation->_soundLibraryFilename = "title.snd"; + bool finished = _animation->play("26open1", true, 1, 255, true, 2); + + if (finished) { + ImageFile titleImages_LondonNovember("title2.vgs", true); + _screen->_backBuffer1.blitFrom(*_screen); + _screen->_backBuffer2.blitFrom(*_screen); + + Common::Point londonPosition; + + if ((titleImages_LondonNovember[0]._width == 302) && (titleImages_LondonNovember[0]._height == 39)) { + // Spanish + londonPosition = Common::Point(9, 8); + } else { + // English (German uses the same English graphics), width 272, height 37 + // In the German version this is placed differently, check against German floppy version TODO + londonPosition = Common::Point(30, 50); + } + + // London, England + _screen->_backBuffer1.transBlitFrom(titleImages_LondonNovember[0], londonPosition); + _screen->randomTransition(); + finished = _events->delay(1000, true); + + // November, 1888 + if (finished) { + _screen->_backBuffer1.transBlitFrom(titleImages_LondonNovember[1], Common::Point(100, 100)); + _screen->randomTransition(); + finished = _events->delay(5000, true); + } + + // Transition out the title + _screen->_backBuffer1.blitFrom(_screen->_backBuffer2); + _screen->randomTransition(); + } + + if (finished) + finished = _animation->play("26open2", true, 1, 0, false, 2); + + if (finished) { + ImageFile titleImages_SherlockHolmesTitle("title.vgs", true); + _screen->_backBuffer1.blitFrom(*_screen); + _screen->_backBuffer2.blitFrom(*_screen); + + Common::Point lostFilesPosition; + Common::Point sherlockHolmesPosition; + Common::Point copyrightPosition; + + if ((titleImages_SherlockHolmesTitle[0]._width == 306) && (titleImages_SherlockHolmesTitle[0]._height == 39)) { + // Spanish + lostFilesPosition = Common::Point(5, 5); + sherlockHolmesPosition = Common::Point(24, 40); + copyrightPosition = Common::Point(3, 190); + } else { + // English (German uses the same English graphics), width 208, height 39 + lostFilesPosition = Common::Point(75, 6); + sherlockHolmesPosition = Common::Point(34, 21); + copyrightPosition = Common::Point(4, 190); + } + + // The Lost Files of + _screen->_backBuffer1.transBlitFrom(titleImages_SherlockHolmesTitle[0], lostFilesPosition); + // Sherlock Holmes + _screen->_backBuffer1.transBlitFrom(titleImages_SherlockHolmesTitle[1], sherlockHolmesPosition); + // copyright + _screen->_backBuffer1.transBlitFrom(titleImages_SherlockHolmesTitle[2], copyrightPosition); + + _screen->verticalTransition(); + finished = _events->delay(4000, true); + + if (finished) { + _screen->_backBuffer1.blitFrom(_screen->_backBuffer2); + _screen->randomTransition(); + finished = _events->delay(2000); + } + + if (finished) { + _screen->getPalette(palette); + _screen->fadeToBlack(2); + } + + if (finished) { + // In the alley... + Common::Point alleyPosition; + + if ((titleImages_SherlockHolmesTitle[3]._width == 105) && (titleImages_SherlockHolmesTitle[3]._height == 16)) { + // German + alleyPosition = Common::Point(72, 50); + } else if ((titleImages_SherlockHolmesTitle[3]._width == 166) && (titleImages_SherlockHolmesTitle[3]._height == 36)) { + // Spanish + alleyPosition = Common::Point(71, 50); + } else { + // English, width 175, height 38 + alleyPosition = Common::Point(72, 51); + } + _screen->transBlitFrom(titleImages_SherlockHolmesTitle[3], alleyPosition); + _screen->fadeIn(palette, 3); + + // Wait until the track got looped and the first few notes were played + finished = _music->waitUntilMSec(4300, 21300, 0, 2500); // ticks 0x104 / ticks 0x500 + } + } + + _animation->_gfxLibraryFilename = ""; + _animation->_soundLibraryFilename = ""; + return finished; +} + +bool ScalpelEngine::showAlleyCutscene() { + byte palette[PALETTE_SIZE]; + _music->playMusic("prolog2"); + + _animation->_gfxLibraryFilename = "TITLE.LIB"; + _animation->_soundLibraryFilename = "TITLE.SND"; + + // Fade "In The Alley..." text to black + _screen->fadeToBlack(2); + + bool finished = _animation->play("27PRO1", true, 1, 3, true, 2); + if (finished) { + _screen->getPalette(palette); + _screen->fadeToBlack(2); + + // wait until second lower main note + finished = _music->waitUntilMSec(26800, 0xFFFFFFFF, 0, 1000); // ticks 0x64A + } + + if (finished) { + _screen->setPalette(palette); + finished = _animation->play("27PRO2", true, 1, 0, false, 2); + } + + if (finished) { + showLBV("scream.lbv"); + + // wait until first "scream" in music happened + finished = _music->waitUntilMSec(45800, 0xFFFFFFFF, 0, 6000); // ticks 0xABE + } + + if (finished) { + // quick fade out + _screen->fadeToBlack(1); + + // wait until after third "scream" in music happened + finished = _music->waitUntilMSec(49000, 0xFFFFFFFF, 0, 2000); // ticks 0xB80 + } + + if (finished) + finished = _animation->play("27PRO3", true, 1, 0, true, 2); + + if (finished) { + _screen->getPalette(palette); + _screen->fadeToBlack(2); + } + + if (finished) { + ImageFile titleImages_EarlyTheFollowingMorning("title3.vgs", true); + // "Early the following morning on Baker Street..." + Common::Point earlyTheFollowingMorningPosition; + + if ((titleImages_EarlyTheFollowingMorning[0]._width == 164) && (titleImages_EarlyTheFollowingMorning[0]._height == 19)) { + // German + earlyTheFollowingMorningPosition = Common::Point(35, 50); + } else if ((titleImages_EarlyTheFollowingMorning[0]._width == 171) && (titleImages_EarlyTheFollowingMorning[0]._height == 32)) { + // Spanish + earlyTheFollowingMorningPosition = Common::Point(35, 50); + } else { + // English, width 218, height 31 + earlyTheFollowingMorningPosition = Common::Point(35, 52); + } + + _screen->transBlitFrom(titleImages_EarlyTheFollowingMorning[0], earlyTheFollowingMorningPosition); + + // fast fade-in + _screen->fadeIn(palette, 1); + + // wait for music to end and wait an additional 2.5 seconds + finished = _music->waitUntilMSec(0xFFFFFFFF, 0xFFFFFFFF, 2500, 3000); + } + + _animation->_gfxLibraryFilename = ""; + _animation->_soundLibraryFilename = ""; + return finished; +} + +bool ScalpelEngine::showStreetCutscene() { + _animation->_gfxLibraryFilename = "TITLE.LIB"; + _animation->_soundLibraryFilename = "TITLE.SND"; + + _music->playMusic("prolog3"); + + // wait a bit + bool finished = _events->delay(500); + + if (finished) { + // fade out "Early the following morning..." + _screen->fadeToBlack(2); + + // wait for music a bit + finished = _music->waitUntilMSec(3800, 0xFFFFFFFF, 0, 1000); // ticks 0xE4 + } + + if (finished) + finished = _animation->play("14KICK", true, 1, 3, true, 2); + + // Constable animation plays slower than speed 2 + // If we play it with speed 2, music gets obviously out of sync + if (finished) + finished = _animation->play("14NOTE", true, 1, 0, false, 3); + + // Fade to black + if (finished) + _screen->fadeToBlack(1); + + _animation->_gfxLibraryFilename = ""; + _animation->_soundLibraryFilename = ""; + return finished; +} + +bool ScalpelEngine::showOfficeCutscene() { + _music->playMusic("prolog4"); + _animation->_gfxLibraryFilename = "TITLE2.LIB"; + _animation->_soundLibraryFilename = "TITLE.SND"; + + bool finished = _animation->play("COFF1", true, 1, 3, true, 3); + if (finished) + finished = _animation->play("COFF2", true, 1, 0, false, 3); + if (finished) { + showLBV("note.lbv"); + + if (_sound->_voices) { + finished = _sound->playSound("NOTE1", WAIT_KBD_OR_FINISH); + if (finished) + finished = _sound->playSound("NOTE2", WAIT_KBD_OR_FINISH); + if (finished) + finished = _sound->playSound("NOTE3", WAIT_KBD_OR_FINISH); + if (finished) + finished = _sound->playSound("NOTE4", WAIT_KBD_OR_FINISH); + } else + finished = _events->delay(19000); + + if (finished) { + _events->clearEvents(); + finished = _events->delay(500); + } + } + + if (finished) + finished = _animation->play("COFF3", true, 1, 0, true, 3); + + if (finished) + finished = _animation->play("COFF4", true, 1, 0, false, 3); + + if (finished) + finished = scrollCredits(); + + if (finished) + _screen->fadeToBlack(3); + + _animation->_gfxLibraryFilename = ""; + _animation->_soundLibraryFilename = ""; + return finished; +} + +bool ScalpelEngine::scrollCredits() { + // Load the images for displaying credit text + Common::SeekableReadStream *stream = _res->load("credits.vgs", "title.lib"); + ImageFile creditsImages(*stream); + + // Demo fades slowly from the scene into credits palette + _screen->fadeIn(creditsImages._palette, 3); + + delete stream; + + // Save a copy of the screen background for use in drawing each credit frame + _screen->_backBuffer1.blitFrom(*_screen); + + // Loop for showing the credits + for(int idx = 0; idx < 600 && !_events->kbHit() && !shouldQuit(); ++idx) { + // Copy the entire screen background before writing text + _screen->blitFrom(_screen->_backBuffer1); + + // Write the text appropriate for the next frame + if (idx < 400) + _screen->transBlitFrom(creditsImages[0], Common::Point(10, 200 - idx), false, 0); + if (idx > 200) + _screen->transBlitFrom(creditsImages[1], Common::Point(10, 400 - idx), false, 0); + + // Don't show credit text on the top and bottom ten rows of the screen + _screen->blitFrom(_screen->_backBuffer1, Common::Point(0, 0), Common::Rect(0, 0, _screen->w(), 10)); + _screen->blitFrom(_screen->_backBuffer1, Common::Point(0, _screen->h() - 10), + Common::Rect(0, _screen->h() - 10, _screen->w(), _screen->h())); + + _events->delay(100); + } + + return true; +} + +// 3DO variant +bool ScalpelEngine::show3DOSplash() { + // 3DO EA Splash screen + ImageFile3DO titleImage_3DOSplash("3DOSplash.cel", kImageFile3DOType_Cel); + + _screen->transBlitFrom(titleImage_3DOSplash[0]._frame, Common::Point(0, -20)); + bool finished = _events->delay(3000, true); + + if (finished) { + _screen->clear(); + finished = _events->delay(500, true); + } + + if (finished) { + // EA logo movie + Scalpel3DOMoviePlay("EAlogo.stream", Common::Point(20, 0)); + } + + // Always clear screen + _screen->clear(); + return finished; +} + +bool ScalpelEngine::showCityCutscene3DO() { + _animation->_soundLibraryFilename = "TITLE.SND"; + + _screen->clear(); + bool finished = _events->delay(2500, true); + + // rain.aiff seems to be playing in an endless loop until + // sherlock logo fades away TODO + + if (finished) { + finished = _events->delay(2500, true); + + // Play intro music + _music->playMusic("prolog"); + + // Fade screen to grey + _screen->_backBuffer1.fill(0xCE59); // RGB565: 25, 50, 25 (grey) + _screen->fadeIntoScreen3DO(2); + } + + if (finished) { + finished = _music->waitUntilMSec(3400, 0, 0, 3400); + } + + if (finished) { + _screen->_backBuffer1.fill(0); // fill backbuffer with black to avoid issues during fade from white + finished = _animation->play3DO("26open1", true, 1, true, 2); + } + + if (finished) { + _screen->_backBuffer1.blitFrom(*_screen); // save into backbuffer 1, used for fade + _screen->_backBuffer2.blitFrom(*_screen); // save into backbuffer 2, for restoring later + + // "London, England" + ImageFile3DO titleImage_London("title2a.cel", kImageFile3DOType_Cel); + _screen->_backBuffer1.transBlitFrom(titleImage_London[0]._frame, Common::Point(30, 50)); + + _screen->fadeIntoScreen3DO(1); + finished = _events->delay(1500, true); + + if (finished) { + // "November, 1888" + ImageFile3DO titleImage_November("title2b.cel", kImageFile3DOType_Cel); + _screen->_backBuffer1.transBlitFrom(titleImage_November[0]._frame, Common::Point(100, 100)); + + _screen->fadeIntoScreen3DO(1); + finished = _music->waitUntilMSec(14700, 0, 0, 5000); + } + + if (finished) { + // Restore screen + _screen->blitFrom(_screen->_backBuffer2); + } + } + + if (finished) + finished = _animation->play3DO("26open2", true, 1, false, 2); + + if (finished) { + _screen->_backBuffer1.blitFrom(*_screen); // save into backbuffer 1, used for fade + + // "Sherlock Holmes" (title) + ImageFile3DO titleImage_SherlockHolmesTitle("title1ab.cel", kImageFile3DOType_Cel); + _screen->_backBuffer1.transBlitFrom(titleImage_SherlockHolmesTitle[0]._frame, Common::Point(34, 5)); + + // Blend in + _screen->fadeIntoScreen3DO(2); + finished = _events->delay(500, true); + + // Title should fade in, Copyright should be displayed a bit after that + if (finished) { + ImageFile3DO titleImage_Copyright("title1c.cel", kImageFile3DOType_Cel); + + _screen->transBlitFrom(titleImage_Copyright[0]._frame, Common::Point(20, 190)); + finished = _events->delay(3500, true); + } + } + + if (finished) + finished = _music->waitUntilMSec(33600, 0, 0, 2000); + + if (finished) { + // Fade to black + _screen->_backBuffer1.clear(); + _screen->fadeIntoScreen3DO(3); + } + + if (finished) { + // "In the alley behind the Regency Theatre..." + ImageFile3DO titleImage_InTheAlley("title1d.cel", kImageFile3DOType_Cel); + _screen->_backBuffer1.transBlitFrom(titleImage_InTheAlley[0]._frame, Common::Point(72, 51)); + + // Fade in + _screen->fadeIntoScreen3DO(4); + finished = _music->waitUntilMSec(39900, 0, 0, 2500); + + // Fade out + _screen->_backBuffer1.clear(); + _screen->fadeIntoScreen3DO(4); + } + return finished; +} + +bool ScalpelEngine::showAlleyCutscene3DO() { + bool finished = _music->waitUntilMSec(43500, 0, 0, 1000); + + if (finished) + finished = _animation->play3DO("27PRO1", true, 1, false, 2); + + if (finished) { + // Fade out... + _screen->_backBuffer1.clear(); + _screen->fadeIntoScreen3DO(3); + + finished = _music->waitUntilMSec(67100, 0, 0, 1000); // 66700 + } + + if (finished) + finished = _animation->play3DO("27PRO2", true, 1, false, 2); + + if (finished) + finished = _music->waitUntilMSec(76000, 0, 0, 1000); + + if (finished) { + // Show screaming victim + ImageFile3DO titleImage_ScreamingVictim("scream.cel", kImageFile3DOType_Cel); + + _screen->clear(); + _screen->transBlitFrom(titleImage_ScreamingVictim[0]._frame, Common::Point(0, 0)); + + // Play "scream.aiff" + if (_sound->_voices) + _sound->playSound("prologue/sounds/scream.aiff", WAIT_RETURN_IMMEDIATELY, 100); + + finished = _music->waitUntilMSec(81600, 0, 0, 6000); + } + + if (finished) { + // Fade out + _screen->_backBuffer1.clear(); + _screen->fadeIntoScreen3DO(5); + + finished = _music->waitUntilMSec(84400, 0, 0, 2000); + } + + if (finished) + finished = _animation->play3DO("27PRO3", true, 1, false, 2); + + if (finished) { + // Fade out + _screen->_backBuffer1.clear(); + _screen->fadeIntoScreen3DO(5); + } + + if (finished) { + // "Early the following morning on Baker Street..." + ImageFile3DO titleImage_EarlyTheFollowingMorning("title3.cel", kImageFile3DOType_Cel); + _screen->_backBuffer1.transBlitFrom(titleImage_EarlyTheFollowingMorning[0]._frame, Common::Point(35, 51)); + + // Fade in + _screen->fadeIntoScreen3DO(4); + finished = _music->waitUntilMSec(96700, 0, 0, 3000); + } + + return finished; +} + +bool ScalpelEngine::showStreetCutscene3DO() { + bool finished = true; + + if (finished) { + // fade out "Early the following morning..." + _screen->_backBuffer1.clear(); + _screen->fadeIntoScreen3DO(4); + + // wait for music a bit + finished = _music->waitUntilMSec(100300, 0, 0, 1000); + } + + if (finished) + finished = _animation->play3DO("14KICK", true, 1, false, 2); + + // note: part of the constable is sticking to the door during the following + // animation, when he walks away. This is a bug of course, but it actually happened on 3DO! + // I'm not sure if it happens because the door is pure black (0, 0, 0) and it's because + // of transparency - or if the animation itself is bad. We will definitely have to adjust + // the animation data to fix it. + if (finished) + finished = _animation->play3DO("14NOTE", true, 1, false, 3); + + if (finished) { + // Fade out + _screen->_backBuffer1.clear(); + _screen->fadeIntoScreen3DO(4); + } + + return finished; +} + +bool ScalpelEngine::showOfficeCutscene3DO() { + bool finished = _music->waitUntilMSec(151000, 0, 0, 1000); + + if (finished) + finished = _animation->play3DO("COFF1", true, 1, false, 3); + + if (finished) + finished = _animation->play3DO("COFF2", true, 1, false, 3); + + if (finished) + finished = _music->waitUntilMSec(182400, 0, 0, 1000); + + if (finished) { + // Show the note + ImageFile3DO titleImage_CoffeeNote("note.cel", kImageFile3DOType_Cel); + + _screen->clear(); + _screen->transBlitFrom(titleImage_CoffeeNote[0]._frame, Common::Point(0, 0)); + + if (_sound->_voices) { + finished = _sound->playSound("prologue/sounds/note.aiff", WAIT_KBD_OR_FINISH); + } else + finished = _events->delay(19000); + + if (finished) + finished = _music->waitUntilMSec(218800, 0, 0, 1000); + + // Fade out + _screen->clear(); + } + + if (finished) + finished = _music->waitUntilMSec(222200, 0, 0, 1000); + + if (finished) + finished = _animation->play3DO("COFF3", true, 1, false, 3); + + if (finished) + finished = _animation->play3DO("COFF4", true, 1, false, 3); + + if (finished) { + finished = _music->waitUntilMSec(244500, 0, 0, 2000); + + // TODO: Brighten the image, possibly by doing a partial fade + // to white. + + _screen->_backBuffer1.blitFrom(*_screen); + + for (int nr = 1; finished && nr <= 4; nr++) { + char filename[15]; + sprintf(filename, "credits%d.cel", nr); + ImageFile3DO *creditsImage = new ImageFile3DO(filename, kImageFile3DOType_Cel); + ImageFrame *creditsFrame = &(*creditsImage)[0]; + for (int i = 0; finished && i < 200 + creditsFrame->_height; i++) { + _screen->blitFrom(_screen->_backBuffer1); + _screen->transBlitFrom(creditsFrame->_frame, Common::Point((320 - creditsFrame->_width) / 2, 200 - i)); + if (!_events->delay(70, true)) + finished = false; + } + delete creditsImage; + } + } + + return finished; +} + +void ScalpelEngine::loadInventory() { + FixedText &fixedText = *_fixedText; + Inventory &inv = *_inventory; + + Common::String fixedText_Message = fixedText.getText(kFixedText_InitInventory_Message); + Common::String fixedText_HolmesCard = fixedText.getText(kFixedText_InitInventory_HolmesCard); + Common::String fixedText_Tickets = fixedText.getText(kFixedText_InitInventory_Tickets); + Common::String fixedText_CuffLink = fixedText.getText(kFixedText_InitInventory_CuffLink); + Common::String fixedText_WireHook = fixedText.getText(kFixedText_InitInventory_WireHook); + Common::String fixedText_Note = fixedText.getText(kFixedText_InitInventory_Note); + Common::String fixedText_OpenWatch = fixedText.getText(kFixedText_InitInventory_OpenWatch); + Common::String fixedText_Paper = fixedText.getText(kFixedText_InitInventory_Paper); + Common::String fixedText_Letter = fixedText.getText(kFixedText_InitInventory_Letter); + Common::String fixedText_Tarot = fixedText.getText(kFixedText_InitInventory_Tarot); + Common::String fixedText_OrnateKey = fixedText.getText(kFixedText_InitInventory_OrnateKey); + Common::String fixedText_PawnTicket = fixedText.getText(kFixedText_InitInventory_PawnTicket); + + // Initial inventory + inv._holdings = 2; + inv.push_back(InventoryItem(0, "Message", fixedText_Message, "_ITEM03A")); + inv.push_back(InventoryItem(0, "Holmes Card", fixedText_HolmesCard, "_ITEM07A")); + + // Hidden items + inv.push_back(InventoryItem(95, "Tickets", fixedText_Tickets, "_ITEM10A")); + inv.push_back(InventoryItem(138, "Cuff Link", fixedText_CuffLink, "_ITEM04A")); + inv.push_back(InventoryItem(138, "Wire Hook", fixedText_WireHook, "_ITEM06A")); + inv.push_back(InventoryItem(150, "Note", fixedText_Note, "_ITEM13A")); + inv.push_back(InventoryItem(481, "Open Watch", fixedText_OpenWatch, "_ITEM62A")); + inv.push_back(InventoryItem(481, "Paper", fixedText_Paper, "_ITEM44A")); + inv.push_back(InventoryItem(532, "Letter", fixedText_Letter, "_ITEM68A")); + inv.push_back(InventoryItem(544, "Tarot", fixedText_Tarot, "_ITEM71A")); + inv.push_back(InventoryItem(544, "Ornate Key", fixedText_OrnateKey, "_ITEM70A")); + inv.push_back(InventoryItem(586, "Pawn ticket", fixedText_PawnTicket, "_ITEM16A")); +} + +void ScalpelEngine::showLBV(const Common::String &filename) { + Common::SeekableReadStream *stream = _res->load(filename, "title.lib"); + ImageFile images(*stream); + delete stream; + + _screen->setPalette(images._palette); + _screen->_backBuffer1.blitFrom(images[0]); + _screen->verticalTransition(); +} + +void ScalpelEngine::startScene() { + if (_scene->_goToScene == OVERHEAD_MAP || _scene->_goToScene == OVERHEAD_MAP2) { + // Show the map + if (_music->_musicOn && _music->loadSong(100)) + _music->startSong(); + + _scene->_goToScene = _map->show(); + + _music->freeSong(); + _people->_hSavedPos = Common::Point(-1, -1); + _people->_hSavedFacing = -1; + } + + // Some rooms are prologue cutscenes, rather than normal game scenes. These are: + // 2: Blackwood's capture + // 52: Rescuing Anna + // 53: Moorehead's death / subway train + // 55: Fade out and exit + // 70: Brumwell suicide + switch (_scene->_goToScene) { + case BLACKWOOD_CAPTURE: + case RESCUE_ANNA: + case MOOREHEAD_DEATH: + case BRUMWELL_SUICIDE: + if (_music->_musicOn && _music->loadSong(_scene->_goToScene)) + _music->startSong(); + + switch (_scene->_goToScene) { + case BLACKWOOD_CAPTURE: + // Blackwood's capture + _res->addToCache("final2.vda", "epilogue.lib"); + _res->addToCache("final2.vdx", "epilogue.lib"); + _animation->play("final1", false, 1, 3, true, 4); + _animation->play("final2", false, 1, 0, false, 4); + break; + + case RESCUE_ANNA: + // Rescuing Anna + _res->addToCache("finalr2.vda", "epilogue.lib"); + _res->addToCache("finalr2.vdx", "epilogue.lib"); + _res->addToCache("finale1.vda", "epilogue.lib"); + _res->addToCache("finale1.vdx", "epilogue.lib"); + _res->addToCache("finale2.vda", "epilogue.lib"); + _res->addToCache("finale2.vdx", "epilogue.lib"); + _res->addToCache("finale3.vda", "epilogue.lib"); + _res->addToCache("finale3.vdx", "epilogue.lib"); + _res->addToCache("finale4.vda", "EPILOG2.lib"); + _res->addToCache("finale4.vdx", "EPILOG2.lib"); + + _animation->play("finalr1", false, 1, 3, true, 4); + _animation->play("finalr2", false, 1, 0, false, 4); + + if (!_res->isInCache("finale2.vda")) { + // Finale file isn't cached + _res->addToCache("finale2.vda", "epilogue.lib"); + _res->addToCache("finale2.vdx", "epilogue.lib"); + _res->addToCache("finale3.vda", "epilogue.lib"); + _res->addToCache("finale3.vdx", "epilogue.lib"); + _res->addToCache("finale4.vda", "EPILOG2.lib"); + _res->addToCache("finale4.vdx", "EPILOG2.lib"); + } + + _animation->play("finale1", false, 1, 0, false, 4); + _animation->play("finale2", false, 1, 0, false, 4); + _animation->play("finale3", false, 1, 0, false, 4); + + _useEpilogue2 = true; + _animation->play("finale4", false, 1, 0, false, 4); + _useEpilogue2 = false; + break; + + case MOOREHEAD_DEATH: + // Moorehead's death / subway train + _res->addToCache("SUBWAY2.vda", "epilogue.lib"); + _res->addToCache("SUBWAY2.vdx", "epilogue.lib"); + _res->addToCache("SUBWAY3.vda", "epilogue.lib"); + _res->addToCache("SUBWAY3.vdx", "epilogue.lib"); + + _animation->play("SUBWAY1", false, 1, 3, true, 4); + _animation->play("SUBWAY2", false, 1, 0, false, 4); + _animation->play("SUBWAY3", false, 1, 0, false, 4); + + // Set fading to direct fade temporary so the transition goes quickly. + _scene->_tempFadeStyle = _screen->_fadeStyle ? 257 : 256; + _screen->_fadeStyle = false; + break; + + case BRUMWELL_SUICIDE: + // Brumwell suicide + _animation->play("suicid", false, 1, 3, true, 4); + break; + default: + break; + } + + // Except for the Moorehead Murder scene, fade to black first + if (_scene->_goToScene != MOOREHEAD_DEATH) { + _events->wait(40); + _screen->fadeToBlack(3); + } + + switch (_scene->_goToScene) { + case 52: + _scene->_goToScene = LAWYER_OFFICE; // Go to the Lawyer's Office + _map->_bigPos = Common::Point(0, 0); // Overland scroll position + _map->_overPos = Common::Point(22900 - 600, 9400 + 900); // Overland position + _map->_oldCharPoint = LAWYER_OFFICE; + break; + + case 53: + _scene->_goToScene = STATION; // Go to St. Pancras Station + _map->_bigPos = Common::Point(0, 0); // Overland scroll position + _map->_overPos = Common::Point(32500 - 600, 3000 + 900); // Overland position + _map->_oldCharPoint = STATION; + break; + + default: + _scene->_goToScene = BAKER_STREET; // Back to Baker st. + _map->_bigPos = Common::Point(0, 0); // Overland scroll position + _map->_overPos = Common::Point(14500 - 600, 8400 + 900); // Overland position + _map->_oldCharPoint = BAKER_STREET; + break; + } + + // Free any song from the previous scene + _music->freeSong(); + break; + + case EXIT_GAME: + // Exit game + _screen->fadeToBlack(3); + quitGame(); + return; + + default: + break; + } + + _events->setCursor(ARROW); + + if (_scene->_goToScene == 99) { + // Darts Board minigame + _darts->playDarts(); + _mapResult = _scene->_goToScene = PUB_INTERIOR; + } + + _mapResult = _scene->_goToScene; +} + +void ScalpelEngine::eraseMirror12() { + Common::Point pt((*_people)[HOLMES]._position.x / FIXED_INT_MULTIPLIER, (*_people)[HOLMES]._position.y / FIXED_INT_MULTIPLIER); + + // If player is in range of the mirror, then restore background from the secondary back buffer + if (Common::Rect(70, 100, 200, 200).contains(pt)) { + _screen->_backBuffer1.blitFrom(_screen->_backBuffer2, Common::Point(137, 18), + Common::Rect(137, 18, 184, 74)); + } +} + +void ScalpelEngine::doMirror12() { + People &people = *_people; + Person &player = people[HOLMES]; + + Common::Point pt((*_people)[HOLMES]._position.x / FIXED_INT_MULTIPLIER, (*_people)[HOLMES]._position.y / FIXED_INT_MULTIPLIER); + int frameNum = player._walkSequences[player._sequenceNumber][player._frameNumber] + + player._walkSequences[player._sequenceNumber][0] - 2; + + switch ((*_people)[HOLMES]._sequenceNumber) { + case WALK_DOWN: + frameNum -= 7; + break; + case WALK_UP: + frameNum += 7; + break; + case WALK_DOWNRIGHT: + frameNum += 7; + break; + case WALK_UPRIGHT: + frameNum -= 7; + break; + case WALK_DOWNLEFT: + frameNum += 7; + break; + case WALK_UPLEFT: + frameNum -= 7; + break; + case STOP_DOWN: + frameNum -= 10; + break; + case STOP_UP: + frameNum += 11; + break; + case STOP_DOWNRIGHT: + frameNum -= 15; + break; + case STOP_DOWNLEFT: + frameNum -= 15; + break; + case STOP_UPRIGHT: + case STOP_UPLEFT: + frameNum += 15; + if (frameNum == 55) + frameNum = 54; + break; + default: + break; + } + + if (Common::Rect(80, 100, 145, 138).contains(pt)) { + // Get the frame of Sherlock to draw + ImageFrame &imageFrame = (*people[HOLMES]._images)[frameNum]; + + // Draw the mirror image of Holmes + bool flipped = people[HOLMES]._sequenceNumber == WALK_LEFT || people[HOLMES]._sequenceNumber == STOP_LEFT + || people[HOLMES]._sequenceNumber == WALK_UPRIGHT || people[HOLMES]._sequenceNumber == STOP_UPRIGHT + || people[HOLMES]._sequenceNumber == WALK_DOWNLEFT || people[HOLMES]._sequenceNumber == STOP_DOWNLEFT; + _screen->_backBuffer1.transBlitFrom(imageFrame, pt + Common::Point(38, -imageFrame._frame.h - 25), flipped); + + // Redraw the mirror borders to prevent the drawn image of Holmes from appearing outside of the mirror + _screen->_backBuffer1.blitFrom(_screen->_backBuffer2, Common::Point(114, 18), + Common::Rect(114, 18, 137, 114)); + _screen->_backBuffer1.blitFrom(_screen->_backBuffer2, Common::Point(137, 70), + Common::Rect(137, 70, 142, 114)); + _screen->_backBuffer1.blitFrom(_screen->_backBuffer2, Common::Point(142, 71), + Common::Rect(142, 71, 159, 114)); + _screen->_backBuffer1.blitFrom(_screen->_backBuffer2, Common::Point(159, 72), + Common::Rect(159, 72, 170, 116)); + _screen->_backBuffer1.blitFrom(_screen->_backBuffer2, Common::Point(170, 73), + Common::Rect(170, 73, 184, 114)); + _screen->_backBuffer1.blitFrom(_screen->_backBuffer2, Common::Point(184, 18), + Common::Rect(184, 18, 212, 114)); + } +} + +void ScalpelEngine::flushMirror12() { + Common::Point pt((*_people)[HOLMES]._position.x / FIXED_INT_MULTIPLIER, (*_people)[HOLMES]._position.y / FIXED_INT_MULTIPLIER); + + // If player is in range of the mirror, then draw the entire mirror area to the screen + if (Common::Rect(70, 100, 200, 200).contains(pt)) + _screen->slamArea(137, 18, 47, 56); +} + +} // End of namespace Scalpel + +} // End of namespace Sherlock diff --git a/engines/sherlock/scalpel/scalpel.h b/engines/sherlock/scalpel/scalpel.h new file mode 100644 index 0000000000..8a10094a52 --- /dev/null +++ b/engines/sherlock/scalpel/scalpel.h @@ -0,0 +1,119 @@ +/* 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 SHERLOCK_SCALPEL_H +#define SHERLOCK_SCALPEL_H + +#include "sherlock/sherlock.h" +#include "sherlock/scalpel/darts.h" + +namespace Sherlock { + +namespace Scalpel { + +class ScalpelEngine : public SherlockEngine { +private: + Darts *_darts; + int _mapResult; + + bool show3DOSplash(); + + /** + * Show the starting city cutscene which shows the game title + */ + bool showCityCutscene(); + bool showCityCutscene3DO(); + + /** + * Show the back alley where the initial murder takes place + */ + bool showAlleyCutscene(); + bool showAlleyCutscene3DO(); + + /** + * Show the Baker Street outside cutscene + */ + bool showStreetCutscene(); + bool showStreetCutscene3DO(); + + /** + * Show Holmes and Watson at the breakfast table, lestrade's note, and then the scrolling credits + */ + bool showOfficeCutscene(); + bool showOfficeCutscene3DO(); + + /** + * Show the game credits + */ + bool scrollCredits(); + + /** + * Load the default inventory for the game, which includes both the initial active inventory, + * as well as special pending inventory items which can appear automatically in the player's + * inventory once given required flags are set + */ + void loadInventory(); + + /** + * Transition to show an image + */ + void showLBV(const Common::String &filename); +protected: + /** + * Game initialization + */ + virtual void initialize(); + + /** + * Show the opening sequence + */ + virtual void showOpening(); + + /** + * Starting a scene within the game + */ + virtual void startScene(); +public: + ScalpelEngine(OSystem *syst, const SherlockGameDescription *gameDesc); + virtual ~ScalpelEngine(); + + /** + * Takes care of clearing the mirror in scene 12 (mansion drawing room), in case anything drew over it + */ + void eraseMirror12(); + + /** + * Takes care of drawing Holme's reflection onto the mirror in scene 12 (mansion drawing room) + */ + void doMirror12(); + + /** + * This clears the mirror in scene 12 (mansion drawing room) in case anything messed draw over it + */ + void flushMirror12(); +}; + +} // End of namespace Scalpel + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/scalpel/scalpel_journal.cpp b/engines/sherlock/scalpel/scalpel_journal.cpp new file mode 100644 index 0000000000..12ebe6f0f2 --- /dev/null +++ b/engines/sherlock/scalpel/scalpel_journal.cpp @@ -0,0 +1,684 @@ +/* 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 "sherlock/journal.h" +#include "sherlock/sherlock.h" +#include "sherlock/scalpel/scalpel_journal.h" +#include "sherlock/tattoo/tattoo_journal.h" + +namespace Sherlock { + +namespace Scalpel { + +#define JOURNAL_BUTTONS_Y 178 +#define JOURNAL_SEARCH_LEFT 15 +#define JOURNAL_SEARCH_TOP 186 +#define JOURNAL_SEARCH_RIGHT 296 +#define JOURNAL_SEACRH_MAX_CHARS 50 + +// Positioning of buttons in the journal view +static const int JOURNAL_POINTS[9][3] = { + { 6, 68, 37 }, + { 69, 131, 100 }, + { 132, 192, 162 }, + { 193, 250, 221 }, + { 251, 313, 281 }, + { 6, 82, 44 }, + { 83, 159, 121 }, + { 160, 236, 198 }, + { 237, 313, 275 } +}; + +static const int SEARCH_POINTS[3][3] = { + { 51, 123, 86 }, + { 124, 196, 159 }, + { 197, 269, 232 } +}; + +/*----------------------------------------------------------------*/ + +ScalpelJournal::ScalpelJournal(SherlockEngine *vm) : Journal(vm) { + // Initialize fields + _maxPage = 0; + _index = 0; + _sub = 0; + _up = _down = false; + _page = 1; + + if (_vm->_interactiveFl) { + // Load the journal directory and location names + loadLocations(); + } +} + +void ScalpelJournal::record(int converseNum, int statementNum, bool replyOnly) { + int saveIndex = _index; + int saveSub = _sub; + + if (IS_3DO) { + // there seems to be no journal in the 3DO version + return; + } + + // Record the entry into the list + _journal.push_back(JournalEntry(converseNum, statementNum, replyOnly)); + _index = _journal.size() - 1; + + // Load the text for the new entry to get the number of lines it will have + loadJournalFile(true); + + // Restore old state + _index = saveIndex; + _sub = saveSub; + + // If new lines were added to the ournal, update the total number of lines + // the journal continues + if (!_lines.empty()) { + _maxPage += _lines.size(); + } else { + // No lines in entry, so remove the new entry from the journal + _journal.remove_at(_journal.size() - 1); + } +} + +void ScalpelJournal::loadLocations() { + Resources &res = *_vm->_res; + + _directory.clear(); + _locations.clear(); + + + Common::SeekableReadStream *dir = res.load("talk.lib"); + dir->skip(4); // Skip header + + // Get the numer of entries + _directory.resize(dir->readUint16LE()); + + // Read in each entry + char buffer[17]; + for (uint idx = 0; idx < _directory.size(); ++idx) { + dir->read(buffer, 17); + buffer[16] = '\0'; + + _directory[idx] = Common::String(buffer); + } + + delete dir; + + if (IS_3DO) { + // 3DO: storage of locations is currently unknown TODO + return; + } + + // Load in the locations stored in journal.txt + Common::SeekableReadStream *loc = res.load("journal.txt"); + + while (loc->pos() < loc->size()) { + Common::String line; + char c; + while ((c = loc->readByte()) != 0) + line += c; + + _locations.push_back(line); + } + + delete loc; +} + +void ScalpelJournal::drawFrame() { + FixedText &fixedText = *_vm->_fixedText; + Resources &res = *_vm->_res; + Screen &screen = *_vm->_screen; + byte palette[PALETTE_SIZE]; + + // Load in the journal background + Common::SeekableReadStream *bg = res.load("journal.lbv"); + bg->read(screen._backBuffer1.getPixels(), SHERLOCK_SCREEN_WIDTH * SHERLOCK_SCREEN_HEIGHT); + bg->read(palette, PALETTE_SIZE); + delete bg; + + // Translate the palette for display + for (int idx = 0; idx < PALETTE_SIZE; ++idx) + palette[idx] = VGA_COLOR_TRANS(palette[idx]); + + Common::String fixedText_WatsonsJournal = fixedText.getText(kFixedText_Journal_WatsonsJournal); + Common::String fixedText_Exit = fixedText.getText(kFixedText_Journal_Exit); + Common::String fixedText_Back10 = fixedText.getText(kFixedText_Journal_Back10); + Common::String fixedText_Up = fixedText.getText(kFixedText_Journal_Up); + Common::String fixedText_Down = fixedText.getText(kFixedText_Journal_Down); + Common::String fixedText_Ahead10 = fixedText.getText(kFixedText_Journal_Ahead10); + Common::String fixedText_Search = fixedText.getText(kFixedText_Journal_Search); + Common::String fixedText_FirstPage = fixedText.getText(kFixedText_Journal_FirstPage); + Common::String fixedText_LastPage = fixedText.getText(kFixedText_Journal_LastPage); + Common::String fixedText_PrintText = fixedText.getText(kFixedText_Journal_PrintText); + + // Set the palette and print the title + screen.setPalette(palette); + screen.gPrint(Common::Point(111, 18), BUTTON_BOTTOM, "%s", fixedText_WatsonsJournal.c_str()); + screen.gPrint(Common::Point(110, 17), INV_FOREGROUND, "%s", fixedText_WatsonsJournal.c_str()); + + // Draw the buttons + screen.makeButton(Common::Rect(JOURNAL_POINTS[0][0], JOURNAL_BUTTONS_Y, + JOURNAL_POINTS[0][1], JOURNAL_BUTTONS_Y + 10), + JOURNAL_POINTS[0][2] - screen.stringWidth(fixedText_Exit) / 2, fixedText_Exit); + screen.makeButton(Common::Rect(JOURNAL_POINTS[1][0], JOURNAL_BUTTONS_Y, + JOURNAL_POINTS[1][1], JOURNAL_BUTTONS_Y + 10), + JOURNAL_POINTS[1][2] - screen.stringWidth(fixedText_Back10) / 2, fixedText_Back10); + screen.makeButton(Common::Rect(JOURNAL_POINTS[2][0], JOURNAL_BUTTONS_Y, + JOURNAL_POINTS[2][1], JOURNAL_BUTTONS_Y + 10), + JOURNAL_POINTS[2][2] - screen.stringWidth(fixedText_Up) / 2, fixedText_Up); + screen.makeButton(Common::Rect(JOURNAL_POINTS[3][0], JOURNAL_BUTTONS_Y, + JOURNAL_POINTS[3][1], JOURNAL_BUTTONS_Y + 10), + JOURNAL_POINTS[3][2] - screen.stringWidth(fixedText_Down) / 2, fixedText_Down); + screen.makeButton(Common::Rect(JOURNAL_POINTS[4][0], JOURNAL_BUTTONS_Y, + JOURNAL_POINTS[4][1], JOURNAL_BUTTONS_Y + 10), + JOURNAL_POINTS[4][2] - screen.stringWidth(fixedText_Ahead10) / 2, fixedText_Ahead10); + screen.makeButton(Common::Rect(JOURNAL_POINTS[5][0], JOURNAL_BUTTONS_Y + 11, + JOURNAL_POINTS[5][1], JOURNAL_BUTTONS_Y + 21), + JOURNAL_POINTS[5][2] - screen.stringWidth(fixedText_Search) / 2, fixedText_Search); + screen.makeButton(Common::Rect(JOURNAL_POINTS[6][0], JOURNAL_BUTTONS_Y + 11, + JOURNAL_POINTS[6][1], JOURNAL_BUTTONS_Y + 21), + JOURNAL_POINTS[6][2] - screen.stringWidth(fixedText_FirstPage) / 2, fixedText_FirstPage); + screen.makeButton(Common::Rect(JOURNAL_POINTS[7][0], JOURNAL_BUTTONS_Y + 11, + JOURNAL_POINTS[7][1], JOURNAL_BUTTONS_Y + 21), + JOURNAL_POINTS[7][2] - screen.stringWidth(fixedText_LastPage) / 2, fixedText_LastPage); + + // WORKAROUND: Draw Print Text button as disabled, since we don't support it in ScummVM + screen.makeButton(Common::Rect(JOURNAL_POINTS[8][0], JOURNAL_BUTTONS_Y + 11, + JOURNAL_POINTS[8][1], JOURNAL_BUTTONS_Y + 21), + JOURNAL_POINTS[8][2] - screen.stringWidth(fixedText_PrintText) / 2, fixedText_PrintText); + screen.buttonPrint(Common::Point(JOURNAL_POINTS[8][2], JOURNAL_BUTTONS_Y + 11), + COMMAND_NULL, false, fixedText_PrintText); +} + +void ScalpelJournal::drawInterface() { + Screen &screen = *_vm->_screen; + + drawFrame(); + + if (_journal.empty()) { + _up = _down = 0; + } else { + drawJournal(0, 0); + } + + doArrows(); + + // Show the entire screen + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); +} + +void ScalpelJournal::doArrows() { + FixedText &fixedText = *_vm->_fixedText; + Screen &screen = *_vm->_screen; + byte color; + + Common::String fixedText_Back10 = fixedText.getText(kFixedText_Journal_Back10); + Common::String fixedText_Up = fixedText.getText(kFixedText_Journal_Up); + Common::String fixedText_Down = fixedText.getText(kFixedText_Journal_Down); + Common::String fixedText_Ahead10 = fixedText.getText(kFixedText_Journal_Ahead10); + Common::String fixedText_Search = fixedText.getText(kFixedText_Journal_Search); + Common::String fixedText_FirstPage = fixedText.getText(kFixedText_Journal_FirstPage); + Common::String fixedText_LastPage = fixedText.getText(kFixedText_Journal_LastPage); + Common::String fixedText_PrintText = fixedText.getText(kFixedText_Journal_PrintText); + + color = (_page > 1) ? COMMAND_FOREGROUND : COMMAND_NULL; + screen.buttonPrint(Common::Point(JOURNAL_POINTS[1][2], JOURNAL_BUTTONS_Y), color, false, fixedText_Back10); + screen.buttonPrint(Common::Point(JOURNAL_POINTS[2][2], JOURNAL_BUTTONS_Y), color, false, fixedText_Up); + + color = _down ? COMMAND_FOREGROUND : COMMAND_NULL; + screen.buttonPrint(Common::Point(JOURNAL_POINTS[3][2], JOURNAL_BUTTONS_Y), color, false, fixedText_Down); + screen.buttonPrint(Common::Point(JOURNAL_POINTS[4][2], JOURNAL_BUTTONS_Y), color, false, fixedText_Ahead10); + screen.buttonPrint(Common::Point(JOURNAL_POINTS[7][2], JOURNAL_BUTTONS_Y + 11), color, false, fixedText_LastPage); + + color = _journal.size() > 0 ? COMMAND_FOREGROUND : COMMAND_NULL; + screen.buttonPrint(Common::Point(JOURNAL_POINTS[5][2], JOURNAL_BUTTONS_Y + 11), color, false, fixedText_Search); + screen.buttonPrint(Common::Point(JOURNAL_POINTS[8][2], JOURNAL_BUTTONS_Y + 11), COMMAND_NULL, false, fixedText_PrintText); + + color = _page > 1 ? COMMAND_FOREGROUND : COMMAND_NULL; + screen.buttonPrint(Common::Point(JOURNAL_POINTS[6][2], JOURNAL_BUTTONS_Y + 11), color, false, fixedText_FirstPage); +} + +JournalButton ScalpelJournal::getHighlightedButton(const Common::Point &pt) { + if (pt.x > JOURNAL_POINTS[0][0] && pt.x < JOURNAL_POINTS[0][1] && pt.y >= JOURNAL_BUTTONS_Y && + pt.y < (JOURNAL_BUTTONS_Y + 10)) + return BTN_EXIT; + + if (pt.x > JOURNAL_POINTS[1][0] && pt.x < JOURNAL_POINTS[1][1] && pt.y >= JOURNAL_BUTTONS_Y && + pt.y < (JOURNAL_BUTTONS_Y + 10) && _page > 1) + return BTN_BACK10; + + if (pt.x > JOURNAL_POINTS[2][0] && pt.x < JOURNAL_POINTS[2][1] && pt.y >= JOURNAL_BUTTONS_Y && + pt.y < (JOURNAL_BUTTONS_Y + 10) && _up) + return BTN_UP; + + if (pt.x > JOURNAL_POINTS[3][0] && pt.x < JOURNAL_POINTS[3][1] && pt.y >= JOURNAL_BUTTONS_Y && + pt.y < (JOURNAL_BUTTONS_Y + 10) && _down) + return BTN_DOWN; + + if (pt.x > JOURNAL_POINTS[4][0] && pt.x < JOURNAL_POINTS[4][1] && pt.y >= JOURNAL_BUTTONS_Y && + pt.y < (JOURNAL_BUTTONS_Y + 10) && _down) + return BTN_AHEAD110; + + if (pt.x > JOURNAL_POINTS[5][0] && pt.x < JOURNAL_POINTS[5][1] && pt.y >= (JOURNAL_BUTTONS_Y + 11) && + pt.y < (JOURNAL_BUTTONS_Y + 20) && !_journal.empty()) + return BTN_SEARCH; + + if (pt.x > JOURNAL_POINTS[6][0] && pt.x < JOURNAL_POINTS[6][1] && pt.y >= (JOURNAL_BUTTONS_Y + 11) && + pt.y < (JOURNAL_BUTTONS_Y + 20) && _up) + return BTN_FIRST_PAGE; + + if (pt.x > JOURNAL_POINTS[7][0] && pt.x < JOURNAL_POINTS[7][1] && pt.y >= (JOURNAL_BUTTONS_Y + 11) && + pt.y < (JOURNAL_BUTTONS_Y + 20) && _down) + return BTN_LAST_PAGE; + + if (pt.x > JOURNAL_POINTS[8][0] && pt.x < JOURNAL_POINTS[8][1] && pt.y >= (JOURNAL_BUTTONS_Y + 11) && + pt.y < (JOURNAL_BUTTONS_Y + 20) && !_journal.empty()) + return BTN_PRINT_TEXT; + + return BTN_NONE; +} + +bool ScalpelJournal::handleEvents(int key) { + Events &events = *_vm->_events; + FixedText &fixedText = *_vm->_fixedText; + Screen &screen = *_vm->_screen; + bool doneFlag = false; + + Common::Point pt = events.mousePos(); + JournalButton btn = getHighlightedButton(pt); + byte color; + + if (events._pressed || events._released) { + Common::String fixedText_Exit = fixedText.getText(kFixedText_Journal_Exit); + Common::String fixedText_Back10 = fixedText.getText(kFixedText_Journal_Back10); + Common::String fixedText_Up = fixedText.getText(kFixedText_Journal_Up); + Common::String fixedText_Down = fixedText.getText(kFixedText_Journal_Down); + Common::String fixedText_Ahead10 = fixedText.getText(kFixedText_Journal_Ahead10); + Common::String fixedText_Search = fixedText.getText(kFixedText_Journal_Search); + Common::String fixedText_FirstPage = fixedText.getText(kFixedText_Journal_FirstPage); + Common::String fixedText_LastPage = fixedText.getText(kFixedText_Journal_LastPage); + Common::String fixedText_PrintText = fixedText.getText(kFixedText_Journal_PrintText); + + // Exit button + color = (btn == BTN_EXIT) ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND; + screen.buttonPrint(Common::Point(JOURNAL_POINTS[0][2], JOURNAL_BUTTONS_Y), color, true, fixedText_Exit); + + // Back 10 button + if (btn == BTN_BACK10) { + screen.buttonPrint(Common::Point(JOURNAL_POINTS[1][2], JOURNAL_BUTTONS_Y), COMMAND_HIGHLIGHTED, true, fixedText_Back10); + } else if (_page > 1) { + screen.buttonPrint(Common::Point(JOURNAL_POINTS[1][2], JOURNAL_BUTTONS_Y), COMMAND_FOREGROUND, true, fixedText_Back10); + } + + // Up button + if (btn == BTN_UP) { + screen.buttonPrint(Common::Point(JOURNAL_POINTS[2][2], JOURNAL_BUTTONS_Y), COMMAND_HIGHLIGHTED, true, fixedText_Up); + } else if (_up) { + screen.buttonPrint(Common::Point(JOURNAL_POINTS[2][2], JOURNAL_BUTTONS_Y), COMMAND_FOREGROUND, true, fixedText_Up); + } + + // Down button + if (btn == BTN_DOWN) { + screen.buttonPrint(Common::Point(JOURNAL_POINTS[3][2], JOURNAL_BUTTONS_Y), COMMAND_HIGHLIGHTED, true, fixedText_Down); + } else if (_down) { + screen.buttonPrint(Common::Point(JOURNAL_POINTS[3][2], JOURNAL_BUTTONS_Y), COMMAND_FOREGROUND, true, fixedText_Down); + } + + // Ahead 10 button + if (btn == BTN_AHEAD110) { + screen.buttonPrint(Common::Point(JOURNAL_POINTS[4][2], JOURNAL_BUTTONS_Y), COMMAND_HIGHLIGHTED, true, fixedText_Ahead10); + } else if (_down) { + screen.buttonPrint(Common::Point(JOURNAL_POINTS[4][2], JOURNAL_BUTTONS_Y), COMMAND_FOREGROUND, true, fixedText_Ahead10); + } + + // Search button + if (btn == BTN_SEARCH) { + color = COMMAND_HIGHLIGHTED; + } else if (_journal.empty()) { + color = COMMAND_NULL; + } else { + color = COMMAND_FOREGROUND; + } + screen.buttonPrint(Common::Point(JOURNAL_POINTS[5][2], JOURNAL_BUTTONS_Y + 11), color, true, fixedText_Search); + + // First Page button + if (btn == BTN_FIRST_PAGE) { + color = COMMAND_HIGHLIGHTED; + } else if (_up) { + color = COMMAND_FOREGROUND; + } else { + color = COMMAND_NULL; + } + screen.buttonPrint(Common::Point(JOURNAL_POINTS[6][2], JOURNAL_BUTTONS_Y + 11), color, true, fixedText_FirstPage); + + // Last Page button + if (btn == BTN_LAST_PAGE) { + color = COMMAND_HIGHLIGHTED; + } else if (_down) { + color = COMMAND_FOREGROUND; + } else { + color = COMMAND_NULL; + } + screen.buttonPrint(Common::Point(JOURNAL_POINTS[7][2], JOURNAL_BUTTONS_Y + 11), color, true, fixedText_LastPage); + + // Print Text button + screen.buttonPrint(Common::Point(JOURNAL_POINTS[8][2], JOURNAL_BUTTONS_Y + 11), COMMAND_NULL, true, fixedText_PrintText); + } + + if (btn == BTN_EXIT && events._released) { + // Exit button pressed + doneFlag = true; + + } else if (((btn == BTN_BACK10 && events._released) || key == 'B') && (_page > 1)) { + // Scrolll up 10 pages + if (_page < 11) + drawJournal(1, (_page - 1) * LINES_PER_PAGE); + else + drawJournal(1, 10 * LINES_PER_PAGE); + + doArrows(); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + + } else if (((btn == BTN_UP && events._released) || key == 'U') && _up) { + // Scroll up + drawJournal(1, LINES_PER_PAGE); + doArrows(); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + + } else if (((btn == BTN_DOWN && events._released) || key == 'D') && _down) { + // Scroll down + drawJournal(2, LINES_PER_PAGE); + doArrows(); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + + } else if (((btn == BTN_AHEAD110 && events._released) || key == 'A') && _down) { + // Scroll down 10 pages + if ((_page + 10) > _maxPage) + drawJournal(2, (_maxPage - _page) * LINES_PER_PAGE); + else + drawJournal(2, 10 * LINES_PER_PAGE); + + doArrows(); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + + } else if (((btn == BTN_SEARCH && events._released) || key == 'S') && !_journal.empty()) { + screen.buttonPrint(Common::Point(JOURNAL_POINTS[5][2], JOURNAL_BUTTONS_Y + 11), COMMAND_FOREGROUND, true, "Search"); + bool notFound = false; + + do { + int dir; + if ((dir = getSearchString(notFound)) != 0) { + int savedIndex = _index; + int savedSub = _sub; + int savedPage = _page; + + if (drawJournal(dir + 2, 1000 * LINES_PER_PAGE) == 0) { + _index = savedIndex; + _sub = savedSub; + _page = savedPage; + + drawFrame(); + drawJournal(0, 0); + notFound = true; + } else { + doneFlag = true; + } + + doArrows(); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + } else { + doneFlag = true; + } + } while (!doneFlag); + doneFlag = false; + + } else if (((btn == BTN_FIRST_PAGE && events._released) || key == 'F') && _up) { + // First page + _index = _sub = 0; + _up = _down = false; + _page = 1; + + drawFrame(); + drawJournal(0, 0); + doArrows(); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + + } else if (((btn == BTN_LAST_PAGE && events._released) || key == 'L') && _down) { + // Last page + if ((_page + 10) > _maxPage) + drawJournal(2, (_maxPage - _page) * LINES_PER_PAGE); + else + drawJournal(2, 1000 * LINES_PER_PAGE); + + doArrows(); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + } + + events.wait(2); + + return doneFlag; +} + +int ScalpelJournal::getSearchString(bool printError) { + enum Button { BTN_NONE, BTN_EXIT, BTN_BACKWARD, BTN_FORWARD }; + + Events &events = *_vm->_events; + FixedText &fixedText = *_vm->_fixedText; + Screen &screen = *_vm->_screen; + Talk &talk = *_vm->_talk; + int xp; + int yp = 174; + bool flag = false; + Common::String name; + int done = 0; + byte color; + + Common::String fixedText_Exit = fixedText.getText(kFixedText_JournalSearch_Exit); + Common::String fixedText_Backward = fixedText.getText(kFixedText_JournalSearch_Backward); + Common::String fixedText_Forward = fixedText.getText(kFixedText_JournalSearch_Forward); + Common::String fixedText_NotFound = fixedText.getText(kFixedText_JournalSearch_NotFound); + + // Draw search panel + screen.makePanel(Common::Rect(6, 171, 313, 199)); + screen.makeButton(Common::Rect(SEARCH_POINTS[0][0], yp, SEARCH_POINTS[0][1], yp + 10), + SEARCH_POINTS[0][2] - screen.stringWidth(fixedText_Exit) / 2, fixedText_Exit); + screen.makeButton(Common::Rect(SEARCH_POINTS[1][0], yp, SEARCH_POINTS[1][1], yp + 10), + SEARCH_POINTS[1][2] - screen.stringWidth(fixedText_Backward) / 2, fixedText_Backward); + screen.makeButton(Common::Rect(SEARCH_POINTS[2][0], yp, SEARCH_POINTS[2][1], yp + 10), + SEARCH_POINTS[2][2] - screen.stringWidth(fixedText_Forward) / 2, fixedText_Forward); + + screen.gPrint(Common::Point(SEARCH_POINTS[0][2] - screen.stringWidth(fixedText_Exit) / 2, yp), + COMMAND_HIGHLIGHTED, "%c", fixedText_Exit[0]); + screen.gPrint(Common::Point(SEARCH_POINTS[1][2] - screen.stringWidth(fixedText_Backward) / 2, yp), + COMMAND_HIGHLIGHTED, "%c", fixedText_Backward[0]); + screen.gPrint(Common::Point(SEARCH_POINTS[2][2] - screen.stringWidth(fixedText_Forward) / 2, yp), + COMMAND_HIGHLIGHTED, "%c", fixedText_Forward[0]); + + screen.makeField(Common::Rect(12, 185, 307, 196)); + + screen.fillRect(Common::Rect(12, 185, 307, 186), BUTTON_BOTTOM); + screen.vLine(12, 185, 195, BUTTON_BOTTOM); + screen.hLine(13, 195, 306, BUTTON_TOP); + screen.hLine(306, 186, 195, BUTTON_TOP); + + if (printError) { + screen.gPrint(Common::Point((SHERLOCK_SCREEN_WIDTH - screen.stringWidth(fixedText_NotFound)) / 2, 185), + INV_FOREGROUND, "%s", fixedText_NotFound.c_str()); + } else if (!_find.empty()) { + // There's already a search term, display it already + screen.gPrint(Common::Point(15, 185), TALK_FOREGROUND, "%s", _find.c_str()); + name = _find; + } + + screen.slamArea(6, 171, 307, 28); + + if (printError) { + // Give time for user to see the message + events.setButtonState(); + for (int idx = 0; idx < 40 && !_vm->shouldQuit() && !events.kbHit() && !events._released; ++idx) { + events.pollEvents(); + events.setButtonState(); + events.wait(2); + } + + events.clearKeyboard(); + screen._backBuffer1.fillRect(Common::Rect(13, 186, 306, 195), BUTTON_MIDDLE); + + if (!_find.empty()) { + screen.gPrint(Common::Point(15, 185), TALK_FOREGROUND, "%s", _find.c_str()); + name = _find; + } + + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + } + + xp = JOURNAL_SEARCH_LEFT + screen.stringWidth(name); + yp = JOURNAL_SEARCH_TOP; + + do { + events._released = false; + Button found = BTN_NONE; + + while (!_vm->shouldQuit() && !events.kbHit() && !events._released) { + found = BTN_NONE; + if (talk._talkToAbort) + return 0; + + // Check if key or mouse button press has occurred + events.setButtonState(); + Common::Point pt = events.mousePos(); + + flag = !flag; + screen.vgaBar(Common::Rect(xp, yp, xp + 8, yp + 9), flag ? INV_FOREGROUND : BUTTON_MIDDLE); + + if (events._pressed || events._released) { + if (pt.x > SEARCH_POINTS[0][0] && pt.x < SEARCH_POINTS[0][1] && pt.y > 174 && pt.y < 183) { + found = BTN_EXIT; + color = COMMAND_HIGHLIGHTED; + } else { + color = COMMAND_FOREGROUND; + } + screen.print(Common::Point(SEARCH_POINTS[0][2] - screen.stringWidth(fixedText_Exit) / 2, 175), color, "%s", fixedText_Exit.c_str()); + + if (pt.x > SEARCH_POINTS[1][0] && pt.x < SEARCH_POINTS[1][1] && pt.y > 174 && pt.y < 183) { + found = BTN_BACKWARD; + color = COMMAND_HIGHLIGHTED; + } else { + color = COMMAND_FOREGROUND; + } + screen.print(Common::Point(SEARCH_POINTS[1][2] - screen.stringWidth(fixedText_Backward) / 2, 175), color, "%s", fixedText_Backward.c_str()); + + if (pt.x > SEARCH_POINTS[2][0] && pt.x < SEARCH_POINTS[2][1] && pt.y > 174 && pt.y < 183) { + found = BTN_FORWARD; + color = COMMAND_HIGHLIGHTED; + } else { + color = COMMAND_FOREGROUND; + } + screen.print(Common::Point(SEARCH_POINTS[2][2] - screen.stringWidth(fixedText_Forward) / 2, 175), color, "%s", fixedText_Forward.c_str()); + } + + events.wait(2); + } + + if (events.kbHit()) { + Common::KeyState keyState = events.getKey(); + + if ((keyState.keycode == Common::KEYCODE_BACKSPACE) && (name.size() > 0)) { + screen.vgaBar(Common::Rect(xp - screen.charWidth(name.lastChar()), yp, xp + 8, yp + 9), BUTTON_MIDDLE); + xp -= screen.charWidth(name.lastChar()); + screen.vgaBar(Common::Rect(xp, yp, xp + 8, yp + 9), INV_FOREGROUND); + name.deleteLastChar(); + + } else if (keyState.keycode == Common::KEYCODE_RETURN) { + done = 1; + + } else if (keyState.keycode == Common::KEYCODE_ESCAPE) { + screen.vgaBar(Common::Rect(xp, yp, xp + 8, yp + 9), BUTTON_MIDDLE); + done = -1; + + } else if (keyState.ascii >= ' ' && keyState.ascii <= 'z' && keyState.keycode != Common::KEYCODE_AT && + name.size() < JOURNAL_SEACRH_MAX_CHARS && (xp + screen.charWidth(keyState.ascii)) < JOURNAL_SEARCH_RIGHT) { + char ch = toupper(keyState.ascii); + screen.vgaBar(Common::Rect(xp, yp, xp + 8, yp + 9), BUTTON_MIDDLE); + screen.print(Common::Point(xp, yp), TALK_FOREGROUND, "%c", ch); + xp += screen.charWidth(ch); + name += ch; + } + } + + if (events._released) { + switch (found) { + case BTN_EXIT: + done = -1; break; + case BTN_BACKWARD: + done = 2; break; + case BTN_FORWARD: + done = 1; break; + default: + break; + } + } + } while (!done && !_vm->shouldQuit()); + + if (done != -1) { + _find = name; + } else { + done = 0; + } + + // Redisplay the journal screen + drawFrame(); + drawJournal(0, 0); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + + return done; +} + +void ScalpelJournal::resetPosition() { + _index = _sub = _up = _down = 0; + _page = 1; +} + +void ScalpelJournal::synchronize(Serializer &s) { + s.syncAsSint16LE(_index); + s.syncAsSint16LE(_sub); + s.syncAsSint16LE(_page); + s.syncAsSint16LE(_maxPage); + + int journalCount = _journal.size(); + s.syncAsUint16LE(journalCount); + if (s.isLoading()) + _journal.resize(journalCount); + + for (uint idx = 0; idx < _journal.size(); ++idx) { + JournalEntry &je = _journal[idx]; + + s.syncAsSint16LE(je._converseNum); + s.syncAsByte(je._replyOnly); + s.syncAsSint16LE(je._statementNum); + } +} + +} // End of namespace Scalpel + +} // End of namespace Sherlock diff --git a/engines/sherlock/scalpel/scalpel_journal.h b/engines/sherlock/scalpel/scalpel_journal.h new file mode 100644 index 0000000000..9790461dbe --- /dev/null +++ b/engines/sherlock/scalpel/scalpel_journal.h @@ -0,0 +1,107 @@ +/* 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 SHERLOCK_SCALPEL_JOURNAL_H +#define SHERLOCK_SCALPEL_JOURNAL_H + +#include "sherlock/journal.h" +#include "sherlock/saveload.h" +#include "common/scummsys.h" +#include "common/array.h" +#include "common/rect.h" +#include "common/str-array.h" +#include "common/stream.h" + +namespace Sherlock { + +namespace Scalpel { + +#define JOURNAL_MAX_WIDTH 230 +#define JOURNAL_MAX_CHARS 80 + +enum JournalButton { + BTN_NONE, BTN_EXIT, BTN_BACK10, BTN_UP, BTN_DOWN, BTN_AHEAD110, BTN_SEARCH, + BTN_FIRST_PAGE, BTN_LAST_PAGE, BTN_PRINT_TEXT +}; + +class ScalpelJournal: public Journal { +private: + /** + * Load the list of journal locations + */ + void loadLocations(); + + /** + * Display the arrows that can be used to scroll up and down pages + */ + void doArrows(); + + /** + * Show the search submenu and allow the player to enter a search string + */ + int getSearchString(bool printError); + + /** + * Returns the button, if any, that is under the specified position + */ + JournalButton getHighlightedButton(const Common::Point &pt); +public: + ScalpelJournal(SherlockEngine *vm); + virtual ~ScalpelJournal() {} + + /** + * Display the journal + */ + void drawInterface(); + + /** + * Handle events whilst the journal is being displayed + */ + bool handleEvents(int key); +public: + /** + * Draw the journal background, frame, and interface buttons + */ + virtual void drawFrame(); + + /** + * Records statements that are said, in the order which they are said. The player + * can then read the journal to review them + */ + virtual void record(int converseNum, int statementNum, bool replyOnly = false); + + /** + * Reset viewing position to the start of the journal + */ + virtual void resetPosition(); + + /** + * Synchronize the data for a savegame + */ + virtual void synchronize(Serializer &s); +}; + +} // End of namespace Scalpel + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/scalpel/scalpel_map.cpp b/engines/sherlock/scalpel/scalpel_map.cpp new file mode 100644 index 0000000000..efb5e80d0f --- /dev/null +++ b/engines/sherlock/scalpel/scalpel_map.cpp @@ -0,0 +1,587 @@ +/* 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 "common/system.h" +#include "sherlock/scalpel/scalpel_map.h" +#include "sherlock/events.h" +#include "sherlock/people.h" +#include "sherlock/screen.h" +#include "sherlock/sherlock.h" + +namespace Sherlock { + +namespace Scalpel { + +MapPaths::MapPaths() { + _numLocations = 0; +} + +void MapPaths::load(int numLocations, Common::SeekableReadStream &s) { + _numLocations = numLocations; + _paths.resize(_numLocations * _numLocations); + + for (int idx = 0; idx < (numLocations * numLocations); ++idx) { + Common::Array<byte> &path = _paths[idx]; + int v; + + do { + v = s.readByte(); + path.push_back(v); + } while (v && v < 254); + } +} + +const byte *MapPaths::getPath(int srcLocation, int destLocation) { + return &_paths[srcLocation * _numLocations + destLocation][0]; +} + +/*----------------------------------------------------------------*/ + +ScalpelMap::ScalpelMap(SherlockEngine *vm): Map(vm), _topLine(g_system->getWidth(), 12) { + _mapCursors = nullptr; + _shapes = nullptr; + _iconShapes = nullptr; + _point = 0; + _placesShown = false; + _cursorIndex = -1; + _drawMap = false; + _overPos = Point32(130 * FIXED_INT_MULTIPLIER, 126 * FIXED_INT_MULTIPLIER); + _frameChangeFlag = false; + + // Initialise the initial walk sequence set + _walkSequences.resize(MAX_HOLMES_SEQUENCE); + for (int idx = 0; idx < MAX_HOLMES_SEQUENCE; ++idx) { + _walkSequences[idx]._sequences.resize(MAX_FRAME); + Common::fill(&_walkSequences[idx]._sequences[0], &_walkSequences[idx]._sequences[0] + MAX_FRAME, 0); + } + + if (!_vm->isDemo()) + loadData(); +} + +void ScalpelMap::loadPoints(int count, const int *xList, const int *yList, const int *transList) { + for (int idx = 0; idx < count; ++idx, ++xList, ++yList, ++transList) { + _points.push_back(MapEntry(*xList, *yList, *transList)); + } +} + +void ScalpelMap::loadSequences(int count, const byte *seq) { + for (int idx = 0; idx < count; ++idx, seq += MAX_FRAME) + Common::copy(seq, seq + MAX_FRAME, &_walkSequences[idx]._sequences[0]); +} + +void ScalpelMap::loadData() { + // TODO: Remove this + if (_vm->getGameID() == GType_RoseTattoo) + return; + + // Load the list of location names + Common::SeekableReadStream *txtStream = _vm->_res->load("chess.txt"); + + int streamSize = txtStream->size(); + while (txtStream->pos() < streamSize) { + Common::String line; + char c; + while ((c = txtStream->readByte()) != '\0') + line += c; + + _locationNames.push_back(line); + } + + delete txtStream; + + // Load the path data + Common::SeekableReadStream *pathStream = _vm->_res->load("chess.pth"); + + // Get routes between different locations on the map + _paths.load(31, *pathStream); + + // Load in the co-ordinates that the paths refer to + _pathPoints.resize(208); + for (uint idx = 0; idx < _pathPoints.size(); ++idx) { + _pathPoints[idx].x = pathStream->readSint16LE(); + _pathPoints[idx].y = pathStream->readSint16LE(); + } + + delete pathStream; +} + +int ScalpelMap::show() { + Events &events = *_vm->_events; + People &people = *_vm->_people; + Screen &screen = *_vm->_screen; + bool changed = false, exitFlag = false; + _active = true; + + // Set font and custom cursor for the map + int oldFont = screen.fontNumber(); + screen.setFont(0); + + // Initial screen clear + screen._backBuffer1.clear(); + screen.clear(); + + // Load the entire map + ImageFile *bigMap = NULL; + if (!IS_3DO) { + // PC + bigMap = new ImageFile("bigmap.vgs"); + screen.setPalette(bigMap->_palette); + } else { + // 3DO + bigMap = new ImageFile3DO("overland.cel", kImageFile3DOType_Cel); + } + + // Load need sprites + setupSprites(); + + if (!IS_3DO) { + screen._backBuffer1.blitFrom((*bigMap)[0], Common::Point(-_bigPos.x, -_bigPos.y)); + screen._backBuffer1.blitFrom((*bigMap)[1], Common::Point(-_bigPos.x, SHERLOCK_SCREEN_HEIGHT - _bigPos.y)); + screen._backBuffer1.blitFrom((*bigMap)[2], Common::Point(SHERLOCK_SCREEN_WIDTH - _bigPos.x, -_bigPos.y)); + screen._backBuffer1.blitFrom((*bigMap)[3], Common::Point(SHERLOCK_SCREEN_WIDTH - _bigPos.x, SHERLOCK_SCREEN_HEIGHT - _bigPos.y)); + } else { + screen._backBuffer1.blitFrom((*bigMap)[0], Common::Point(-_bigPos.x, -_bigPos.y)); + screen.blitFrom((*bigMap)[0], Common::Point(-_bigPos.x, -_bigPos.y)); + } + + _drawMap = true; + _charPoint = -1; + _point = -1; + people[HOLMES]._position = _lDrawnPos = _overPos; + + // Show place icons + showPlaces(); + saveTopLine(); + _placesShown = true; + + // Keep looping until either a location is picked, or the game is ended + while (!_vm->shouldQuit() && !exitFlag) { + events.pollEventsAndWait(); + events.setButtonState(); + + // Keyboard handling + if (events.kbHit()) { + Common::KeyState keyState = events.getKey(); + + if (keyState.keycode == Common::KEYCODE_RETURN || keyState.keycode == Common::KEYCODE_SPACE) { + // Both space and enter simulate a mouse release + events._pressed = false; + events._released = true; + events._oldButtons = 0; + } + } + + // Ignore scrolling attempts until the screen is drawn + if (!_drawMap) { + Common::Point pt = events.mousePos(); + + // Check for vertical map scrolling + if ((pt.y > (SHERLOCK_SCREEN_HEIGHT - 10) && _bigPos.y < 200) || (pt.y < 10 && _bigPos.y > 0)) { + if (pt.y > (SHERLOCK_SCREEN_HEIGHT - 10)) + _bigPos.y += 10; + else + _bigPos.y -= 10; + + changed = true; + } + + // Check for horizontal map scrolling + if ((pt.x > (SHERLOCK_SCREEN_WIDTH - 10) && _bigPos.x < 315) || (pt.x < 10 && _bigPos.x > 0)) { + if (pt.x > (SHERLOCK_SCREEN_WIDTH - 10)) + _bigPos.x += 15; + else + _bigPos.x -= 15; + + changed = true; + } + } + + if (changed) { + // Map has scrolled, so redraw new map view + changed = false; + + if (!IS_3DO) { + screen._backBuffer1.blitFrom((*bigMap)[0], Common::Point(-_bigPos.x, -_bigPos.y)); + screen._backBuffer1.blitFrom((*bigMap)[1], Common::Point(-_bigPos.x, SHERLOCK_SCREEN_HEIGHT - _bigPos.y)); + screen._backBuffer1.blitFrom((*bigMap)[2], Common::Point(SHERLOCK_SCREEN_WIDTH - _bigPos.x, -_bigPos.y)); + screen._backBuffer1.blitFrom((*bigMap)[3], Common::Point(SHERLOCK_SCREEN_WIDTH - _bigPos.x, SHERLOCK_SCREEN_HEIGHT - _bigPos.y)); + } else { + screen._backBuffer1.blitFrom((*bigMap)[0], Common::Point(-_bigPos.x, -_bigPos.y)); + } + + showPlaces(); + _placesShown = false; + + saveTopLine(); + _savedPos.x = -1; + updateMap(true); + } else if (!_drawMap) { + if (!_placesShown) { + showPlaces(); + _placesShown = true; + } + + if (_cursorIndex == 0) { + Common::Point pt = events.mousePos(); + highlightIcon(Common::Point(pt.x - 4 + _bigPos.x, pt.y + _bigPos.y)); + } + updateMap(false); + } + + if ((events._released || events._rightReleased) && _point != -1) { + if (people[HOLMES]._walkCount == 0) { + people[HOLMES]._walkDest = _points[_point] + Common::Point(4, 9); + _charPoint = _point; + + // Start walking to selected location + walkTheStreets(); + + // Show wait cursor + _cursorIndex = 1; + events.setCursor((*_mapCursors)[_cursorIndex]._frame); + } + } + + // Check if a scene has beeen selected and we've finished "moving" to it + if (people[HOLMES]._walkCount == 0) { + if (_charPoint >= 1 && _charPoint < (int)_points.size()) + exitFlag = true; + } + + if (_drawMap) { + _drawMap = false; + + if (screen._fadeStyle) + screen.randomTransition(); + else + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + } + + // Wait for a frame + events.wait(1); + } + + freeSprites(); + _overPos = people[HOLMES]._position; + + // Reset font + screen.setFont(oldFont); + + // Free map graphic + delete bigMap; + + _active = false; + return _charPoint; +} + +void ScalpelMap::setupSprites() { + Events &events = *_vm->_events; + People &people = *_vm->_people; + Scene &scene = *_vm->_scene; + _savedPos.x = -1; + + if (!IS_3DO) { + // PC + _mapCursors = new ImageFile("omouse.vgs"); + _shapes = new ImageFile("mapicon.vgs"); + _iconShapes = new ImageFile("overicon.vgs"); + } else { + // 3DO + _mapCursors = new ImageFile3DO("omouse.vgs", kImageFile3DOType_RoomFormat); + _shapes = new ImageFile3DO("mapicon.vgs", kImageFile3DOType_RoomFormat); + _iconShapes = new ImageFile3DO("overicon.vgs", kImageFile3DOType_RoomFormat); + } + + _cursorIndex = 0; + events.setCursor((*_mapCursors)[_cursorIndex]._frame); + + _iconSave.create((*_shapes)[4]._width, (*_shapes)[4]._height); + Person &p = people[HOLMES]; + p._description = " "; + p._type = CHARACTER; + p._position = Common::Point(12400, 5000); + p._sequenceNumber = 0; + p._images = _shapes; + p._imageFrame = nullptr; + p._frameNumber = 0; + p._delta = Common::Point(0, 0); + p._oldSize = Common::Point(0, 0); + p._oldSize = Common::Point(0, 0); + p._misc = 0; + p._walkCount = 0; + p._allow = 0; + p._noShapeSize = Common::Point(0, 0); + p._goto = Common::Point(28000, 15000); + p._status = 0; + p._walkSequences = _walkSequences; + p.setImageFrame(); + scene._bgShapes.clear(); +} + +void ScalpelMap::freeSprites() { + delete _mapCursors; + delete _shapes; + delete _iconShapes; + _iconSave.free(); +} + +void ScalpelMap::showPlaces() { + Screen &screen = *_vm->_screen; + + for (uint idx = 0; idx < _points.size(); ++idx) { + const MapEntry &pt = _points[idx]; + + if (pt.x != 0 && pt.y != 0) { + if (pt.x >= _bigPos.x && (pt.x - _bigPos.x) < SHERLOCK_SCREEN_WIDTH + && pt.y >= _bigPos.y && (pt.y - _bigPos.y) < SHERLOCK_SCREEN_HEIGHT) { + if (_vm->readFlags(idx)) { + screen._backBuffer1.transBlitFrom((*_iconShapes)[pt._translate], + Common::Point(pt.x - _bigPos.x - 6, pt.y - _bigPos.y - 12)); + } + } + } + } +} + +void ScalpelMap::saveTopLine() { + _topLine.blitFrom(_vm->_screen->_backBuffer1, Common::Point(0, 0), Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, 12)); +} + +void ScalpelMap::eraseTopLine() { + Screen &screen = *_vm->_screen; + screen._backBuffer1.blitFrom(_topLine, Common::Point(0, 0)); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, _topLine.h()); +} + +void ScalpelMap::showPlaceName(int idx, bool highlighted) { + People &people = *_vm->_people; + Screen &screen = *_vm->_screen; + + Common::String name = _locationNames[idx]; + int width = screen.stringWidth(name); + + if (!_cursorIndex) { + saveIcon(people[HOLMES]._imageFrame, _lDrawnPos); + + bool flipped = people[HOLMES]._sequenceNumber == MAP_DOWNLEFT || people[HOLMES]._sequenceNumber == MAP_LEFT + || people[HOLMES]._sequenceNumber == MAP_UPLEFT; + screen._backBuffer1.transBlitFrom(*people[HOLMES]._imageFrame, _lDrawnPos, flipped); + } + + if (highlighted) { + int xp = (SHERLOCK_SCREEN_WIDTH - screen.stringWidth(name)) / 2; + screen.gPrint(Common::Point(xp + 2, 2), BLACK, "%s", name.c_str()); + screen.gPrint(Common::Point(xp + 1, 1), BLACK, "%s", name.c_str()); + screen.gPrint(Common::Point(xp, 0), 12, "%s", name.c_str()); + + screen.slamArea(xp, 0, width + 2, 15); + } +} + +void ScalpelMap::updateMap(bool flushScreen) { + Events &events = *_vm->_events; + People &people = *_vm->_people; + Screen &screen = *_vm->_screen; + Common::Point osPos = _savedPos; + Common::Point osSize = _savedSize; + Common::Point hPos; + + if (_cursorIndex >= 1) { + if (++_cursorIndex > (1 + 8)) + _cursorIndex = 1; + + events.setCursor((*_mapCursors)[(_cursorIndex + 1) / 2]._frame); + } + + if (!_drawMap && !flushScreen) + restoreIcon(); + else + _savedPos.x = -1; + + people[HOLMES].adjustSprite(); + + _lDrawnPos.x = hPos.x = people[HOLMES]._position.x / FIXED_INT_MULTIPLIER - _bigPos.x; + _lDrawnPos.y = hPos.y = people[HOLMES]._position.y / FIXED_INT_MULTIPLIER - people[HOLMES].frameHeight() - _bigPos.y; + + // Draw the person icon + saveIcon(people[HOLMES]._imageFrame, hPos); + if (people[HOLMES]._sequenceNumber == MAP_DOWNLEFT || people[HOLMES]._sequenceNumber == MAP_LEFT + || people[HOLMES]._sequenceNumber == MAP_UPLEFT) + screen._backBuffer1.transBlitFrom(*people[HOLMES]._imageFrame, hPos, true); + else + screen._backBuffer1.transBlitFrom(*people[HOLMES]._imageFrame, hPos, false); + + if (flushScreen) { + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + } else if (!_drawMap) { + if (hPos.x > 0 && hPos.y >= 0 && hPos.x < SHERLOCK_SCREEN_WIDTH && hPos.y < SHERLOCK_SCREEN_HEIGHT) + screen.flushImage(people[HOLMES]._imageFrame, Common::Point(people[HOLMES]._position.x / FIXED_INT_MULTIPLIER - _bigPos.x, + people[HOLMES]._position.y / FIXED_INT_MULTIPLIER - people[HOLMES].frameHeight() - _bigPos.y), + &people[HOLMES]._oldPosition.x, &people[HOLMES]._oldPosition.y, &people[HOLMES]._oldSize.x, &people[HOLMES]._oldSize.y); + + if (osPos.x != -1) + screen.slamArea(osPos.x, osPos.y, osSize.x, osSize.y); + } +} + +void ScalpelMap::walkTheStreets() { + People &people = *_vm->_people; + Common::Array<Common::Point> tempPath; + + // Get indexes into the path lists for the start and destination scenes + int start = _points[_oldCharPoint]._translate; + int dest = _points[_charPoint]._translate; + + // Get pointer to start of path + const byte *path = _paths.getPath(start, dest); + + // Add in destination position + people[HOLMES]._walkTo.clear(); + Common::Point destPos = people[HOLMES]._walkDest; + + // Check for any intermediate points between the two locations + if (path[0] || _charPoint > 50 || _oldCharPoint > 50) { + people[HOLMES]._sequenceNumber = -1; + + if (_charPoint == 51 || _oldCharPoint == 51) { + people[HOLMES].setWalking(); + } else { + bool reversePath = false; + + // Check for moving the path backwards or forwards + if (path[0] == 255) { + reversePath = true; + SWAP(start, dest); + path = _paths.getPath(start, dest); + } + + do { + int idx = *path++; + tempPath.push_back(_pathPoints[idx - 1] + Common::Point(4, 4)); + } while (*path != 254); + + // Load up the path to use + people[HOLMES]._walkTo.clear(); + + if (reversePath) { + for (int idx = (int)tempPath.size() - 1; idx >= 0; --idx) + people[HOLMES]._walkTo.push(tempPath[idx]); + } else { + for (int idx = 0; idx < (int)tempPath.size(); ++idx) + people[HOLMES]._walkTo.push(tempPath[idx]); + } + + people[HOLMES]._walkDest = people[HOLMES]._walkTo.pop() + Common::Point(12, 6); + people[HOLMES].setWalking(); + } + } else { + people[HOLMES]._walkCount = 0; + } + + // Store the final destination icon position + people[HOLMES]._walkTo.push(destPos); +} + +void ScalpelMap::saveIcon(ImageFrame *src, const Common::Point &pt) { + Screen &screen = *_vm->_screen; + Common::Point size(src->_width, src->_height); + Common::Point pos = pt; + + if (pos.x < 0) { + size.x += pos.x; + pos.x = 0; + } + + if (pos.y < 0) { + size.y += pos.y; + pos.y = 0; + } + + if ((pos.x + size.x) > SHERLOCK_SCREEN_WIDTH) + size.x -= (pos.x + size.x) - SHERLOCK_SCREEN_WIDTH; + + if ((pos.y + size.y) > SHERLOCK_SCREEN_HEIGHT) + size.y -= (pos.y + size.y) - SHERLOCK_SCREEN_HEIGHT; + + if (size.x < 1 || size.y < 1 || pos.x >= SHERLOCK_SCREEN_WIDTH || pos.y >= SHERLOCK_SCREEN_HEIGHT || _drawMap) { + // Flag as the area not needing to be saved + _savedPos.x = -1; + return; + } + + assert(size.x <= _iconSave.w() && size.y <= _iconSave.h()); + _iconSave.blitFrom(screen._backBuffer1, Common::Point(0, 0), + Common::Rect(pos.x, pos.y, pos.x + size.x, pos.y + size.y)); + _savedPos = pos; + _savedSize = size; +} + +void ScalpelMap::restoreIcon() { + Screen &screen = *_vm->_screen; + + if (_savedPos.x >= 0 && _savedPos.y >= 0 && _savedPos.x <= SHERLOCK_SCREEN_WIDTH + && _savedPos.y < SHERLOCK_SCREEN_HEIGHT) + screen._backBuffer1.blitFrom(_iconSave, _savedPos, Common::Rect(0, 0, _savedSize.x, _savedSize.y)); +} + +void ScalpelMap::highlightIcon(const Common::Point &pt) { + int oldPoint = _point; + + // Iterate through the icon list + bool done = false; + for (int idx = 0; idx < (int)_points.size(); ++idx) { + const MapEntry &entry = _points[idx]; + + // Check whether the mouse is over a given icon + if (entry.x != 0 && entry.y != 0) { + if (Common::Rect(entry.x - 8, entry.y - 8, entry.x + 9, entry.y + 9).contains(pt)) { + done = true; + + if (_point != idx && _vm->readFlags(idx)) { + // Changed to a new valid (visible) location + eraseTopLine(); + showPlaceName(idx, true); + _point = idx; + } + } + } + } + + if (!done) { + // No icon was highlighted + if (_point != -1) { + // No longer highlighting previously highlighted icon, so erase it + showPlaceName(_point, false); + eraseTopLine(); + } + + _point = -1; + } else if (oldPoint != -1 && oldPoint != _point) { + showPlaceName(oldPoint, false); + eraseTopLine(); + } +} + +} // End of namespace Scalpel + +} // End of namespace Sherlock diff --git a/engines/sherlock/scalpel/scalpel_map.h b/engines/sherlock/scalpel/scalpel_map.h new file mode 100644 index 0000000000..b17677725c --- /dev/null +++ b/engines/sherlock/scalpel/scalpel_map.h @@ -0,0 +1,173 @@ +/* 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 SHERLOCK_SCALPEL_MAP_H +#define SHERLOCK_SCALPEL_MAP_H + +#include "common/scummsys.h" +#include "common/array.h" +#include "common/rect.h" +#include "common/str-array.h" +#include "sherlock/surface.h" +#include "sherlock/map.h" +#include "sherlock/resources.h" + +namespace Sherlock { + +class SherlockEngine; + +namespace Scalpel { + + +struct MapEntry : Common::Point { + int _translate; + + MapEntry() : Common::Point(), _translate(-1) {} + + MapEntry(int posX, int posY, int translate) : Common::Point(posX, posY), _translate(translate) {} +}; + +class MapPaths { +private: + int _numLocations; + Common::Array< Common::Array<byte> > _paths; + +public: + MapPaths(); + + /** + * Load the data for the paths between locations on the map + */ + void load(int numLocations, Common::SeekableReadStream &s); + + /** + * Get the path between two locations on the map + */ + const byte *getPath(int srcLocation, int destLocation); +}; + +class ScalpelMap: public Map { +private: + Common::Array<MapEntry> _points; // Map locations for each scene + Common::StringArray _locationNames; + MapPaths _paths; + Common::Array<Common::Point> _pathPoints; + Common::Point _savedPos; + Common::Point _savedSize; + Surface _topLine; + ImageFile *_mapCursors; + ImageFile *_shapes; + ImageFile *_iconShapes; + WalkSequences _walkSequences; + Point32 _lDrawnPos; + int _point; + bool _placesShown; + int _cursorIndex; + bool _drawMap; + Surface _iconSave; +protected: + /** + * Load data needed for the map + */ + void loadData(); + + /** + * Load and initialize all the sprites that are needed for the map display + */ + void setupSprites(); + + /** + * Free the sprites and data used by the map + */ + void freeSprites(); + + /** + * Draws an icon for every place that's currently known + */ + void showPlaces(); + + /** + * Makes a copy of the top rows of the screen that are used to display location names + */ + void saveTopLine(); + + /** + * Erases anything shown in the top line by restoring the previously saved original map background + */ + void eraseTopLine(); + + /** + * Prints the name of the specified icon + */ + void showPlaceName(int idx, bool highlighted); + + /** + * Update all on-screen sprites to account for any scrolling of the map + */ + void updateMap(bool flushScreen); + + /** + * Handle moving icon for player from their previous location on the map to a destination location + */ + void walkTheStreets(); + + /** + * Save the area under the player's icon + */ + void saveIcon(ImageFrame *src, const Common::Point &pt); + + /** + * Restore the area under the player's icon + */ + void restoreIcon(); + + /** + * Handles highlighting map icons, showing their names + */ + void highlightIcon(const Common::Point &pt); +public: + ScalpelMap(SherlockEngine *vm); + virtual ~ScalpelMap() {} + + const MapEntry &operator[](int idx) { return _points[idx]; } + + /** + * Loads the list of points for locations on the map for each scene + */ + void loadPoints(int count, const int *xList, const int *yList, const int *transList); + + /** + * Load the sequence data for player icon animations + */ + void loadSequences(int count, const byte *seq); + + /** + * Show the map + */ + virtual int show(); +}; + +} // End of namespace Scalpel + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/scalpel/scalpel_people.cpp b/engines/sherlock/scalpel/scalpel_people.cpp new file mode 100644 index 0000000000..94042e7a13 --- /dev/null +++ b/engines/sherlock/scalpel/scalpel_people.cpp @@ -0,0 +1,506 @@ +/* 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 "sherlock/scalpel/scalpel_people.h" +#include "sherlock/scalpel/scalpel_map.h" +#include "sherlock/sherlock.h" + +namespace Sherlock { + +namespace Scalpel { + +// Walk speeds +#define MWALK_SPEED 2 +#define XWALK_SPEED 4 +#define YWALK_SPEED 1 + +/*----------------------------------------------------------------*/ + +void ScalpelPerson::adjustSprite() { + Map &map = *_vm->_map; + People &people = *_vm->_people; + Scene &scene = *_vm->_scene; + Talk &talk = *_vm->_talk; + + if (_type == INVALID || (_type == CHARACTER && scene._animating)) + return; + + if (!talk._talkCounter && _type == CHARACTER && _walkCount) { + // Handle active movement for the sprite + _position += _delta; + --_walkCount; + + if (!_walkCount) { + // If there any points left for the character to walk to along the + // route to a destination, then move to the next point + if (!people[HOLMES]._walkTo.empty()) { + _walkDest = people[HOLMES]._walkTo.pop(); + setWalking(); + } else { + gotoStand(); + } + } + } + + if (_type == CHARACTER && !map._active) { + if ((_position.y / FIXED_INT_MULTIPLIER) > LOWER_LIMIT) { + _position.y = LOWER_LIMIT * FIXED_INT_MULTIPLIER; + gotoStand(); + } + + if ((_position.y / FIXED_INT_MULTIPLIER) < UPPER_LIMIT) { + _position.y = UPPER_LIMIT * FIXED_INT_MULTIPLIER; + gotoStand(); + } + + if ((_position.x / FIXED_INT_MULTIPLIER) < LEFT_LIMIT) { + _position.x = LEFT_LIMIT * FIXED_INT_MULTIPLIER; + gotoStand(); + } + + if ((_position.x / FIXED_INT_MULTIPLIER) > RIGHT_LIMIT) { + _position.x = RIGHT_LIMIT * FIXED_INT_MULTIPLIER; + gotoStand(); + } + } else if (!map._active) { + _position.y = CLIP((int)_position.y, (int)UPPER_LIMIT, (int)LOWER_LIMIT); + _position.x = CLIP((int)_position.x, (int)LEFT_LIMIT, (int)RIGHT_LIMIT); + } + + if (!map._active || (map._frameChangeFlag = !map._frameChangeFlag)) + ++_frameNumber; + + if (_frameNumber >= (int)_walkSequences[_sequenceNumber]._sequences.size() || + _walkSequences[_sequenceNumber][_frameNumber] == 0) { + switch (_sequenceNumber) { + case STOP_UP: + case STOP_DOWN: + case STOP_LEFT: + case STOP_RIGHT: + case STOP_UPRIGHT: + case STOP_UPLEFT: + case STOP_DOWNRIGHT: + case STOP_DOWNLEFT: + // We're in a stop sequence, so reset back to the last frame, so + // the character is shown as standing still + --_frameNumber; + break; + + default: + // Move 1 past the first frame - we need to compensate, since we + // already passed the frame increment + _frameNumber = 1; + break; + } + } + + // Update the _imageFrame to point to the new frame's image + setImageFrame(); + + // Check to see if character has entered an exit zone + if (!_walkCount && scene._walkedInScene && scene._goToScene == -1) { + Common::Rect charRect(_position.x / FIXED_INT_MULTIPLIER - 5, _position.y / FIXED_INT_MULTIPLIER - 2, + _position.x / FIXED_INT_MULTIPLIER + 5, _position.y / FIXED_INT_MULTIPLIER + 2); + Exit *exit = scene.checkForExit(charRect); + + if (exit) { + scene._goToScene = exit->_scene; + + if (exit->_newPosition.x != 0) { + people._hSavedPos = exit->_newPosition; + people._hSavedFacing = exit->_newFacing; + + if (people._hSavedFacing > 100 && people._hSavedPos.x < 1) + people._hSavedPos.x = 100; + } + } + } +} + +void ScalpelPerson::gotoStand() { + ScalpelMap &map = *(ScalpelMap *)_vm->_map; + People &people = *_vm->_people; + _walkTo.clear(); + _walkCount = 0; + + switch (_sequenceNumber) { + case Scalpel::WALK_UP: + _sequenceNumber = STOP_UP; + break; + case WALK_DOWN: + _sequenceNumber = STOP_DOWN; + break; + case TALK_LEFT: + case WALK_LEFT: + _sequenceNumber = STOP_LEFT; + break; + case TALK_RIGHT: + case WALK_RIGHT: + _sequenceNumber = STOP_RIGHT; + break; + case WALK_UPRIGHT: + _sequenceNumber = STOP_UPRIGHT; + break; + case WALK_UPLEFT: + _sequenceNumber = STOP_UPLEFT; + break; + case WALK_DOWNRIGHT: + _sequenceNumber = STOP_DOWNRIGHT; + break; + case WALK_DOWNLEFT: + _sequenceNumber = STOP_DOWNLEFT; + break; + default: + break; + } + + // Only restart frame at 0 if the sequence number has changed + if (_oldWalkSequence != -1 || _sequenceNumber == Scalpel::STOP_UP) + _frameNumber = 0; + + if (map._active) { + _sequenceNumber = 0; + people[HOLMES]._position.x = (map[map._charPoint].x - 6) * FIXED_INT_MULTIPLIER; + people[HOLMES]._position.y = (map[map._charPoint].y + 10) * FIXED_INT_MULTIPLIER; + } + + _oldWalkSequence = -1; + people._allowWalkAbort = true; +} + +void ScalpelPerson::setWalking() { + Map &map = *_vm->_map; + Scene &scene = *_vm->_scene; + int oldDirection, oldFrame; + Common::Point speed, delta; + + // Flag that player has now walked in the scene + scene._walkedInScene = true; + + // Stop any previous walking, since a new dest is being set + _walkCount = 0; + oldDirection = _sequenceNumber; + oldFrame = _frameNumber; + + // Set speed to use horizontal and vertical movement + if (map._active) { + speed = Common::Point(MWALK_SPEED, MWALK_SPEED); + } else { + speed = Common::Point(XWALK_SPEED, YWALK_SPEED); + } + + // If the player is already close to the given destination that no + // walking is needed, move to the next straight line segment in the + // overall walking route, if there is one + for (;;) { + // Since we want the player to be centered on the destination they + // clicked, but characters draw positions start at their left, move + // the destination half the character width to draw him centered + int temp; + if (_walkDest.x >= (temp = _imageFrame->_frame.w / 2)) + _walkDest.x -= temp; + + delta = Common::Point( + ABS(_position.x / FIXED_INT_MULTIPLIER - _walkDest.x), + ABS(_position.y / FIXED_INT_MULTIPLIER - _walkDest.y) + ); + + // If we're ready to move a sufficient distance, that's it. Otherwise, + // move onto the next portion of the walk path, if there is one + if ((delta.x > 3 || delta.y > 0) || _walkTo.empty()) + break; + + // Pop next walk segment off the walk route stack + _walkDest = _walkTo.pop(); + } + + // If a sufficient move is being done, then start the move + if (delta.x > 3 || delta.y) { + // See whether the major movement is horizontal or vertical + if (delta.x >= delta.y) { + // Set the initial frame sequence for the left and right, as well + // as setting the delta x depending on direction + if (_walkDest.x < (_position.x / FIXED_INT_MULTIPLIER)) { + _sequenceNumber = (map._active ? (int)MAP_LEFT : (int)WALK_LEFT); + _delta.x = speed.x * -FIXED_INT_MULTIPLIER; + } else { + _sequenceNumber = (map._active ? (int)MAP_RIGHT : (int)WALK_RIGHT); + _delta.x = speed.x * FIXED_INT_MULTIPLIER; + } + + // See if the x delta is too small to be divided by the speed, since + // this would cause a divide by zero error + if (delta.x >= speed.x) { + // Det the delta y + _delta.y = (delta.y * FIXED_INT_MULTIPLIER) / (delta.x / speed.x); + if (_walkDest.y < (_position.y / FIXED_INT_MULTIPLIER)) + _delta.y = -_delta.y; + + // Set how many times we should add the delta to the player's position + _walkCount = delta.x / speed.x; + } else { + // The delta x was less than the speed (ie. we're really close to + // the destination). So set delta to 0 so the player won't move + _delta = Point32(0, 0); + _position = Point32(_walkDest.x * FIXED_INT_MULTIPLIER, _walkDest.y * FIXED_INT_MULTIPLIER); + + _walkCount = 1; + } + + // See if the sequence needs to be changed for diagonal walking + if (_delta.y > 150) { + if (!map._active) { + switch (_sequenceNumber) { + case WALK_LEFT: + _sequenceNumber = WALK_DOWNLEFT; + break; + case WALK_RIGHT: + _sequenceNumber = WALK_DOWNRIGHT; + break; + } + } + } else if (_delta.y < -150) { + if (!map._active) { + switch (_sequenceNumber) { + case WALK_LEFT: + _sequenceNumber = WALK_UPLEFT; + break; + case WALK_RIGHT: + _sequenceNumber = WALK_UPRIGHT; + break; + } + } + } + } else { + // Major movement is vertical, so set the sequence for up and down, + // and set the delta Y depending on the direction + if (_walkDest.y < (_position.y / FIXED_INT_MULTIPLIER)) { + _sequenceNumber = WALK_UP; + _delta.y = speed.y * -FIXED_INT_MULTIPLIER; + } else { + _sequenceNumber = WALK_DOWN; + _delta.y = speed.y * FIXED_INT_MULTIPLIER; + } + + // If we're on the overhead map, set the sequence so we keep moving + // in the same direction + if (map._active) + _sequenceNumber = (oldDirection == -1) ? MAP_RIGHT : oldDirection; + + // Set the delta x + _delta.x = (delta.x * FIXED_INT_MULTIPLIER) / (delta.y / speed.y); + if (_walkDest.x < (_position.x / FIXED_INT_MULTIPLIER)) + _delta.x = -_delta.x; + + _walkCount = delta.y / speed.y; + } + } + + // See if the new walk sequence is the same as the old. If it's a new one, + // we need to reset the frame number to zero so it's animation starts at + // it's beginning. Otherwise, if it's the same sequence, we can leave it + // as is, so it keeps the animation going at wherever it was up to + if (_sequenceNumber != _oldWalkSequence) + _frameNumber = 0; + _oldWalkSequence = _sequenceNumber; + + if (!_walkCount) + gotoStand(); + + // If the sequence is the same as when we started, then Holmes was + // standing still and we're trying to re-stand him, so reset Holmes' + // rame to the old frame number from before it was reset to 0 + if (_sequenceNumber == oldDirection) + _frameNumber = oldFrame; +} + +void ScalpelPerson::walkToCoords(const Point32 &destPos, int destDir) { + Events &events = *_vm->_events; + People &people = *_vm->_people; + Scene &scene = *_vm->_scene; + Talk &talk = *_vm->_talk; + + CursorId oldCursor = events.getCursor(); + events.setCursor(WAIT); + + _walkDest = Common::Point(destPos.x / FIXED_INT_MULTIPLIER + 10, destPos.y / FIXED_INT_MULTIPLIER); + people._allowWalkAbort = true; + goAllTheWay(); + + // Keep calling doBgAnim until the walk is done + do { + events.pollEventsAndWait(); + scene.doBgAnim(); + } while (!_vm->shouldQuit() && _walkCount); + + if (!talk._talkToAbort) { + // Put character exactly on destination position, and set direction + _position = destPos; + _sequenceNumber = destDir; + gotoStand(); + + // Draw Holmes facing the new direction + scene.doBgAnim(); + + if (!talk._talkToAbort) + events.setCursor(oldCursor); + } +} + +Common::Point ScalpelPerson::getSourcePoint() const { + return Common::Point(_position.x / FIXED_INT_MULTIPLIER + frameWidth() / 2, + _position.y / FIXED_INT_MULTIPLIER); +} + +/*----------------------------------------------------------------*/ + +ScalpelPeople::ScalpelPeople(SherlockEngine *vm) : People(vm) { + _data.push_back(new ScalpelPerson()); +} + +void ScalpelPeople::setTalking(int speaker) { + Resources &res = *_vm->_res; + + // If no speaker is specified, then we can exit immediately + if (speaker == -1) + return; + + if (_portraitsOn) { + delete _talkPics; + Common::String filename = Common::String::format("%s.vgs", _characters[speaker]._portrait); + _talkPics = new ImageFile(filename); + + // Load portrait sequences + Common::SeekableReadStream *stream = res.load("sequence.txt"); + stream->seek(speaker * MAX_FRAME); + + int idx = 0; + do { + _portrait._sequences[idx] = stream->readByte(); + ++idx; + } while (idx < 2 || _portrait._sequences[idx - 2] || _portrait._sequences[idx - 1]); + + delete stream; + + _portrait._maxFrames = idx; + _portrait._frameNumber = 0; + _portrait._sequenceNumber = 0; + _portrait._images = _talkPics; + _portrait._imageFrame = &(*_talkPics)[0]; + _portrait._position = Common::Point(_portraitSide, 10); + _portrait._delta = Common::Point(0, 0); + _portrait._oldPosition = Common::Point(0, 0); + _portrait._goto = Common::Point(0, 0); + _portrait._flags = 5; + _portrait._status = 0; + _portrait._misc = 0; + _portrait._allow = 0; + _portrait._type = ACTIVE_BG_SHAPE; + _portrait._name = " "; + _portrait._description = " "; + _portrait._examine = " "; + _portrait._walkCount = 0; + + if (_holmesFlip || _speakerFlip) { + _portrait._flags |= 2; + + _holmesFlip = false; + _speakerFlip = false; + } + + if (_portraitSide == 20) + _portraitSide = 220; + else + _portraitSide = 20; + + _portraitLoaded = true; + } +} + +void ScalpelPeople::synchronize(Serializer &s) { + s.syncAsByte(_holmesOn); + s.syncAsSint32LE(_data[HOLMES]->_position.x); + s.syncAsSint32LE(_data[HOLMES]->_position.y); + s.syncAsSint16LE(_data[HOLMES]->_sequenceNumber); + s.syncAsSint16LE(_holmesQuotient); + + if (s.isLoading()) { + _hSavedPos = _data[HOLMES]->_position; + _hSavedFacing = _data[HOLMES]->_sequenceNumber; + } +} + +void ScalpelPeople::setTalkSequence(int speaker, int sequenceNum) { + People &people = *_vm->_people; + Scene &scene = *_vm->_scene; + + // If no speaker is specified, then nothing needs to be done + if (speaker == -1) + return; + + if (speaker) { + int objNum = people.findSpeaker(speaker); + if (objNum != -1) { + Object &obj = scene._bgShapes[objNum]; + + if (obj._seqSize < MAX_TALK_SEQUENCES) { + warning("Tried to copy too many talk frames"); + } + else { + for (int idx = 0; idx < MAX_TALK_SEQUENCES; ++idx) { + obj._sequences[idx] = people._characters[speaker]._talkSequences[idx]; + if (idx > 0 && !obj._sequences[idx] && !obj._sequences[idx - 1]) + return; + + obj._frameNumber = 0; + obj._sequenceNumber = 0; + } + } + } + } +} + +bool ScalpelPeople::loadWalk() { + bool result = false; + + if (_data[HOLMES]->_walkLoaded) { + return false; + } else { + if (!IS_3DO) { + _data[HOLMES]->_images = new ImageFile("walk.vgs"); + } else { + // Load walk.anim on 3DO, which is a cel animation file + _data[HOLMES]->_images = new ImageFile3DO("walk.anim", kImageFile3DOType_CelAnimation); + } + _data[HOLMES]->setImageFrame(); + _data[HOLMES]->_walkLoaded = true; + + result = true; + } + + _forceWalkReload = false; + return result; +} + +} // End of namespace Scalpel + +} // End of namespace Sherlock diff --git a/engines/sherlock/scalpel/scalpel_people.h b/engines/sherlock/scalpel/scalpel_people.h new file mode 100644 index 0000000000..0f8aa9dcc7 --- /dev/null +++ b/engines/sherlock/scalpel/scalpel_people.h @@ -0,0 +1,110 @@ +/* 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 SHERLOCK_SCALPEL_PEOPLE_H +#define SHERLOCK_SCALPEL_PEOPLE_H + +#include "common/scummsys.h" +#include "sherlock/people.h" + +namespace Sherlock { + +class SherlockEngine; + +namespace Scalpel { + +// Animation sequence identifiers for characters +enum ScalpelSequences { + WALK_RIGHT = 0, WALK_DOWN = 1, WALK_LEFT = 2, WALK_UP = 3, STOP_LEFT = 4, + STOP_DOWN = 5, STOP_RIGHT = 6, STOP_UP = 7, WALK_UPRIGHT = 8, + WALK_DOWNRIGHT = 9, WALK_UPLEFT = 10, WALK_DOWNLEFT = 11, + STOP_UPRIGHT = 12, STOP_UPLEFT = 13, STOP_DOWNRIGHT = 14, + STOP_DOWNLEFT = 15, TALK_RIGHT = 6, TALK_LEFT = 4 +}; + +class ScalpelPerson : public Person { +protected: + /** + * Get the source position for a character potentially affected by scaling + */ + virtual Common::Point getSourcePoint() const; +public: + ScalpelPerson() : Person() {} + virtual ~ScalpelPerson() {} + + /** + * This adjusts the sprites position, as well as it's animation sequence: + */ + virtual void adjustSprite(); + + /** + * Bring a moving character to a standing position + */ + virtual void gotoStand(); + + /** + * Set the variables for moving a character from one poisition to another + * in a straight line + */ + virtual void setWalking(); + + /** + * Walk to the co-ordinates passed, and then face the given direction + */ + virtual void walkToCoords(const Point32 &destPos, int destDir); + +}; + +class ScalpelPeople : public People { +public: + ScalpelPeople(SherlockEngine *vm); + virtual ~ScalpelPeople() {} + + ScalpelPerson &operator[](PeopleId id) { return *(ScalpelPerson *)_data[id]; } + ScalpelPerson &operator[](int idx) { return *(ScalpelPerson *)_data[idx]; } + + /** + * Setup the data for an animating speaker portrait at the top of the screen + */ + void setTalking(int speaker); + + /** + * Synchronize the data for a savegame + */ + virtual void synchronize(Serializer &s); + + /** + * Change the sequence of the scene background object associated with the specified speaker. + */ + virtual void setTalkSequence(int speaker, int sequenceNum = 1); + + /** + * Load the walking images for Sherlock + */ + virtual bool loadWalk(); +}; + +} // End of namespace Scalpel + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/scalpel/scalpel_scene.cpp b/engines/sherlock/scalpel/scalpel_scene.cpp new file mode 100644 index 0000000000..bbe6674837 --- /dev/null +++ b/engines/sherlock/scalpel/scalpel_scene.cpp @@ -0,0 +1,707 @@ +/* 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 "sherlock/scalpel/scalpel_scene.h" +#include "sherlock/scalpel/scalpel_map.h" +#include "sherlock/scalpel/scalpel_people.h" +#include "sherlock/scalpel/scalpel_user_interface.h" +#include "sherlock/scalpel/scalpel.h" +#include "sherlock/events.h" +#include "sherlock/people.h" +#include "sherlock/screen.h" +#include "sherlock/sherlock.h" + +namespace Sherlock { + +namespace Scalpel { + +bool ScalpelScene::loadScene(const Common::String &filename) { + ScalpelMap &map = *(ScalpelMap *)_vm->_map; + bool result = Scene::loadScene(filename); + + if (!_vm->isDemo()) { + // Reset the previous map location and position on overhead map + map._oldCharPoint = _currentScene; + + map._overPos.x = (map[_currentScene].x - 6) * FIXED_INT_MULTIPLIER; + map._overPos.y = (map[_currentScene].y + 9) * FIXED_INT_MULTIPLIER; + + } + + return result; +} + +void ScalpelScene::drawAllShapes() { + People &people = *_vm->_people; + Screen &screen = *_vm->_screen; + + // Restrict drawing window + screen.setDisplayBounds(Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCENE_HEIGHT)); + + // Draw all active shapes which are behind the person + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + if (_bgShapes[idx]._type == ACTIVE_BG_SHAPE && _bgShapes[idx]._misc == BEHIND) + screen._backBuffer->transBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position, _bgShapes[idx]._flags & OBJ_FLIPPED); + } + + // Draw all canimations which are behind the person + for (uint idx = 0; idx < _canimShapes.size(); ++idx) { + if (_canimShapes[idx]._type == ACTIVE_BG_SHAPE && _canimShapes[idx]._misc == BEHIND) + screen._backBuffer->transBlitFrom(*_canimShapes[idx]._imageFrame, + _canimShapes[idx]._position, _canimShapes[idx]._flags & OBJ_FLIPPED); + } + + // Draw all active shapes which are normal and behind the person + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + if (_bgShapes[idx]._type == ACTIVE_BG_SHAPE && _bgShapes[idx]._misc == NORMAL_BEHIND) + screen._backBuffer->transBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position, _bgShapes[idx]._flags & OBJ_FLIPPED); + } + + // Draw all canimations which are normal and behind the person + for (uint idx = 0; idx < _canimShapes.size(); ++idx) { + if (_canimShapes[idx]._type == ACTIVE_BG_SHAPE && _canimShapes[idx]._misc == NORMAL_BEHIND) + screen._backBuffer->transBlitFrom(*_canimShapes[idx]._imageFrame, _canimShapes[idx]._position, + _canimShapes[idx]._flags & OBJ_FLIPPED); + } + + // Draw any active characters + for (int idx = 0; idx < MAX_CHARACTERS; ++idx) { + Person &p = people[idx]; + if (p._type == CHARACTER && p._walkLoaded) { + bool flipped = IS_SERRATED_SCALPEL && ( + p._sequenceNumber == WALK_LEFT || p._sequenceNumber == STOP_LEFT || + p._sequenceNumber == WALK_UPLEFT || p._sequenceNumber == STOP_UPLEFT || + p._sequenceNumber == WALK_DOWNRIGHT || p._sequenceNumber == STOP_DOWNRIGHT); + + screen._backBuffer->transBlitFrom(*p._imageFrame, Common::Point(p._position.x / FIXED_INT_MULTIPLIER, + p._position.y / FIXED_INT_MULTIPLIER - p.frameHeight()), flipped); + } + } + + // Draw all static and active shapes that are NORMAL and are in front of the player + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + if ((_bgShapes[idx]._type == ACTIVE_BG_SHAPE || _bgShapes[idx]._type == STATIC_BG_SHAPE) && + _bgShapes[idx]._misc == NORMAL_FORWARD) + screen._backBuffer->transBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position, + _bgShapes[idx]._flags & OBJ_FLIPPED); + } + + // Draw all static and active canimations that are NORMAL and are in front of the player + for (uint idx = 0; idx < _canimShapes.size(); ++idx) { + if ((_canimShapes[idx]._type == ACTIVE_BG_SHAPE || _canimShapes[idx]._type == STATIC_BG_SHAPE) && + _canimShapes[idx]._misc == NORMAL_FORWARD) + screen._backBuffer->transBlitFrom(*_canimShapes[idx]._imageFrame, _canimShapes[idx]._position, + _canimShapes[idx]._flags & OBJ_FLIPPED); + } + + // Draw all static and active shapes that are FORWARD + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + _bgShapes[idx]._oldPosition = _bgShapes[idx]._position; + _bgShapes[idx]._oldSize = Common::Point(_bgShapes[idx].frameWidth(), + _bgShapes[idx].frameHeight()); + + if ((_bgShapes[idx]._type == ACTIVE_BG_SHAPE || _bgShapes[idx]._type == STATIC_BG_SHAPE) && + _bgShapes[idx]._misc == FORWARD) + screen._backBuffer->transBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position, + _bgShapes[idx]._flags & OBJ_FLIPPED); + } + + // Draw all static and active canimations that are forward + for (uint idx = 0; idx < _canimShapes.size(); ++idx) { + if ((_canimShapes[idx]._type == ACTIVE_BG_SHAPE || _canimShapes[idx]._type == STATIC_BG_SHAPE) && + _canimShapes[idx]._misc == FORWARD) + screen._backBuffer->transBlitFrom(*_canimShapes[idx]._imageFrame, _canimShapes[idx]._position, + _canimShapes[idx]._flags & OBJ_FLIPPED); + } + + screen.resetDisplayBounds(); +} + +void ScalpelScene::checkBgShapes() { + People &people = *_vm->_people; + Person &holmes = people[HOLMES]; + Common::Point pt(holmes._position.x / FIXED_INT_MULTIPLIER, holmes._position.y / FIXED_INT_MULTIPLIER); + + // Call the base scene method to handle bg shapes + Scene::checkBgShapes(); + + // Iterate through the canim list + for (uint idx = 0; idx < _canimShapes.size(); ++idx) { + Object &obj = _canimShapes[idx]; + if (obj._type == STATIC_BG_SHAPE || obj._type == ACTIVE_BG_SHAPE) { + if ((obj._flags & 5) == 1) { + obj._misc = (pt.y < (obj._position.y + obj._imageFrame->_frame.h - 1)) ? + NORMAL_FORWARD : NORMAL_BEHIND; + } else if (!(obj._flags & 1)) { + obj._misc = BEHIND; + } else if (obj._flags & 4) { + obj._misc = FORWARD; + } + } + } +} + +void ScalpelScene::doBgAnimCheckCursor() { + Inventory &inv = *_vm->_inventory; + Events &events = *_vm->_events; + Sound &sound = *_vm->_sound; + UserInterface &ui = *_vm->_ui; + Common::Point mousePos = events.mousePos(); + events.animateCursorIfNeeded(); + + if (ui._menuMode == LOOK_MODE) { + if (mousePos.y > CONTROLS_Y1) + events.setCursor(ARROW); + else if (mousePos.y < CONTROLS_Y) + events.setCursor(MAGNIFY); + } + + // Check for setting magnifying glass cursor + if (ui._menuMode == INV_MODE || ui._menuMode == USE_MODE || ui._menuMode == GIVE_MODE) { + if (inv._invMode == INVMODE_LOOK) { + // Only show Magnifying glass cursor if it's not on the inventory command line + if (mousePos.y < CONTROLS_Y || mousePos.y >(CONTROLS_Y1 + 13)) + events.setCursor(MAGNIFY); + else + events.setCursor(ARROW); + } else { + events.setCursor(ARROW); + } + } + + if (sound._diskSoundPlaying && !*sound._soundIsOn) { + // Loaded sound just finished playing + sound.freeDigiSound(); + } +} + +void ScalpelScene::doBgAnim() { + ScalpelEngine &vm = *((ScalpelEngine *)_vm); + Events &events = *_vm->_events; + People &people = *_vm->_people; + Screen &screen = *_vm->_screen; + Talk &talk = *_vm->_talk; + + doBgAnimCheckCursor(); + + screen.setDisplayBounds(Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCENE_HEIGHT)); + talk._talkToAbort = false; + + if (_restoreFlag) { + for (int idx = 0; idx < MAX_CHARACTERS; ++idx) { + if (people[idx]._type == CHARACTER) + people[idx].checkSprite(); + } + + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + if (_bgShapes[idx]._type == ACTIVE_BG_SHAPE) + _bgShapes[idx].checkObject(); + } + + if (people._portraitLoaded && people._portrait._type == ACTIVE_BG_SHAPE) + people._portrait.checkObject(); + + for (uint idx = 0; idx < _canimShapes.size(); ++idx) { + if (_canimShapes[idx]._type != INVALID && _canimShapes[idx]._type != REMOVE) + _canimShapes[idx].checkObject(); + } + + if (_currentScene == 12) + vm.eraseMirror12(); + + // Restore the back buffer from the back buffer 2 in the changed area + Common::Rect bounds(people[HOLMES]._oldPosition.x, people[HOLMES]._oldPosition.y, + people[HOLMES]._oldPosition.x + people[HOLMES]._oldSize.x, + people[HOLMES]._oldPosition.y + people[HOLMES]._oldSize.y); + Common::Point pt(bounds.left, bounds.top); + + if (people[HOLMES]._type == CHARACTER) + screen.restoreBackground(bounds); + else if (people[HOLMES]._type == REMOVE) + screen._backBuffer->blitFrom(screen._backBuffer2, pt, bounds); + + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &o = _bgShapes[idx]; + if (o._type == ACTIVE_BG_SHAPE || o._type == HIDE_SHAPE || o._type == REMOVE) + screen.restoreBackground(o.getOldBounds()); + } + + if (people._portraitLoaded) + screen.restoreBackground(Common::Rect( + people._portrait._oldPosition.x, people._portrait._oldPosition.y, + people._portrait._oldPosition.x + people._portrait._oldSize.x, + people._portrait._oldPosition.y + people._portrait._oldSize.y + )); + + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &o = _bgShapes[idx]; + if (o._type == NO_SHAPE && ((o._flags & OBJ_BEHIND) == 0)) { + // Restore screen area + screen._backBuffer->blitFrom(screen._backBuffer2, o._position, + Common::Rect(o._position.x, o._position.y, + o._position.x + o._noShapeSize.x, o._position.y + o._noShapeSize.y)); + + o._oldPosition = o._position; + o._oldSize = o._noShapeSize; + } + } + + for (uint idx = 0; idx < _canimShapes.size(); ++idx) { + Object &o = _canimShapes[idx]; + if (o._type == ACTIVE_BG_SHAPE || o._type == HIDE_SHAPE || o._type == REMOVE) + screen.restoreBackground(Common::Rect(o._oldPosition.x, o._oldPosition.y, + o._oldPosition.x + o._oldSize.x, o._oldPosition.y + o._oldSize.y)); + } + } + + // + // Update the background objects and canimations + // + + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &o = _bgShapes[idx]; + if (o._type == ACTIVE_BG_SHAPE || o._type == NO_SHAPE) + o.adjustObject(); + } + + if (people._portraitLoaded && people._portrait._type == ACTIVE_BG_SHAPE) + people._portrait.adjustObject(); + + for (uint idx = 0; idx < _canimShapes.size(); ++idx) { + if (_canimShapes[idx]._type != INVALID) + _canimShapes[idx].adjustObject(); + } + + if (people[HOLMES]._type == CHARACTER && people._holmesOn) + people[HOLMES].adjustSprite(); + + // Flag the bg shapes which need to be redrawn + checkBgShapes(); + + if (_currentScene == 12) + vm.doMirror12(); + + // Draw all active shapes which are behind the person + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &o = _bgShapes[idx]; + if (o._type == ACTIVE_BG_SHAPE && o._misc == BEHIND) + screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); + } + + // Draw all canimations which are behind the person + for (uint idx = 0; idx < _canimShapes.size(); ++idx) { + Object &o = _canimShapes[idx]; + if (o._type == ACTIVE_BG_SHAPE && o._misc == BEHIND) { + screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); + } + } + + // Draw all active shapes which are HAPPEN and behind the person + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &o = _bgShapes[idx]; + if (o._type == ACTIVE_BG_SHAPE && o._misc == NORMAL_BEHIND) + screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); + } + + // Draw all canimations which are NORMAL and behind the person + for (uint idx = 0; idx < _canimShapes.size(); ++idx) { + Object &o = _canimShapes[idx]; + if (o._type == ACTIVE_BG_SHAPE && o._misc == NORMAL_BEHIND) { + screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); + } + } + + // Draw the person if not animating + if (people[HOLMES]._type == CHARACTER && people[HOLMES]._walkLoaded) { + // If Holmes is too far to the right, move him back so he's on-screen + int xRight = SHERLOCK_SCREEN_WIDTH - 2 - people[HOLMES]._imageFrame->_frame.w; + int tempX = MIN(people[HOLMES]._position.x / FIXED_INT_MULTIPLIER, xRight); + + bool flipped = people[HOLMES]._sequenceNumber == WALK_LEFT || people[HOLMES]._sequenceNumber == STOP_LEFT || + people[HOLMES]._sequenceNumber == WALK_UPLEFT || people[HOLMES]._sequenceNumber == STOP_UPLEFT || + people[HOLMES]._sequenceNumber == WALK_DOWNRIGHT || people[HOLMES]._sequenceNumber == STOP_DOWNRIGHT; + screen._backBuffer->transBlitFrom(*people[HOLMES]._imageFrame, + Common::Point(tempX, people[HOLMES]._position.y / FIXED_INT_MULTIPLIER - people[HOLMES]._imageFrame->_frame.h), flipped); + } + + // Draw all static and active shapes are NORMAL and are in front of the person + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &o = _bgShapes[idx]; + if ((o._type == ACTIVE_BG_SHAPE || o._type == STATIC_BG_SHAPE) && o._misc == NORMAL_FORWARD) + screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); + } + + // Draw all static and active canimations that are NORMAL and are in front of the person + for (uint idx = 0; idx < _canimShapes.size(); ++idx) { + Object &o = _canimShapes[idx]; + if ((o._type == ACTIVE_BG_SHAPE || o._type == STATIC_BG_SHAPE) && o._misc == NORMAL_FORWARD) { + screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); + } + } + + // Draw all static and active shapes that are in front of the person + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &o = _bgShapes[idx]; + if ((o._type == ACTIVE_BG_SHAPE || o._type == STATIC_BG_SHAPE) && o._misc == FORWARD) + screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); + } + + // Draw any active portrait + if (people._portraitLoaded && people._portrait._type == ACTIVE_BG_SHAPE) + screen._backBuffer->transBlitFrom(*people._portrait._imageFrame, + people._portrait._position, people._portrait._flags & OBJ_FLIPPED); + + // Draw all static and active canimations that are in front of the person + for (uint idx = 0; idx < _canimShapes.size(); ++idx) { + Object &o = _canimShapes[idx]; + if ((o._type == ACTIVE_BG_SHAPE || o._type == STATIC_BG_SHAPE) && o._misc == FORWARD) { + screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); + } + } + + // Draw all NO_SHAPE shapes which have flag bit 0 clear + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &o = _bgShapes[idx]; + if (o._type == NO_SHAPE && (o._flags & OBJ_BEHIND) == 0) + screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); + } + + // Bring the newly built picture to the screen + if (_animating == 2) { + _animating = 0; + screen.slamRect(Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCENE_HEIGHT)); + } else { + if (people[HOLMES]._type != INVALID && ((_goToScene == -1 || _canimShapes.empty()))) { + if (people[HOLMES]._type == REMOVE) { + screen.slamRect(Common::Rect( + people[HOLMES]._oldPosition.x, people[HOLMES]._oldPosition.y, + people[HOLMES]._oldPosition.x + people[HOLMES]._oldSize.x, + people[HOLMES]._oldPosition.y + people[HOLMES]._oldSize.y + )); + people[HOLMES]._type = INVALID; + } else { + screen.flushImage(people[HOLMES]._imageFrame, + Common::Point(people[HOLMES]._position.x / FIXED_INT_MULTIPLIER, + people[HOLMES]._position.y / FIXED_INT_MULTIPLIER - people[HOLMES].frameHeight()), + &people[HOLMES]._oldPosition.x, &people[HOLMES]._oldPosition.y, + &people[HOLMES]._oldSize.x, &people[HOLMES]._oldSize.y); + } + } + + if (_currentScene == 12) + vm.flushMirror12(); + + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &o = _bgShapes[idx]; + if ((o._type == ACTIVE_BG_SHAPE || o._type == REMOVE) && _goToScene == -1) { + screen.flushImage(o._imageFrame, o._position, + &o._oldPosition.x, &o._oldPosition.y, &o._oldSize.x, &o._oldSize.y); + } + } + + if (people._portraitLoaded) { + if (people._portrait._type == REMOVE) + screen.slamRect(Common::Rect( + people._portrait._position.x, people._portrait._position.y, + people._portrait._position.x + people._portrait._delta.x, + people._portrait._position.y + people._portrait._delta.y + )); + else + screen.flushImage(people._portrait._imageFrame, people._portrait._position, + &people._portrait._oldPosition.x, &people._portrait._oldPosition.y, + &people._portrait._oldSize.x, &people._portrait._oldSize.y); + + if (people._portrait._type == REMOVE) + people._portrait._type = INVALID; + } + + if (_goToScene == -1) { + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &o = _bgShapes[idx]; + if (o._type == NO_SHAPE && (o._flags & OBJ_BEHIND) == 0) { + screen.slamArea(o._position.x, o._position.y, o._oldSize.x, o._oldSize.y); + screen.slamArea(o._oldPosition.x, o._oldPosition.y, o._oldSize.x, o._oldSize.y); + } else if (o._type == HIDE_SHAPE) { + // Hiding shape, so flush it out and mark it as hidden + screen.flushImage(o._imageFrame, o._position, + &o._oldPosition.x, &o._oldPosition.y, &o._oldSize.x, &o._oldSize.y); + o._type = HIDDEN; + } + } + } + + for (int idx = _canimShapes.size() - 1; idx >= 0; --idx) { + Object &o = _canimShapes[idx]; + + if (o._type == INVALID) { + // Anim shape was invalidated by checkEndOfSequence, so at this point we can remove it + _canimShapes.remove_at(idx); + } else if (o._type == REMOVE) { + if (_goToScene == -1) + screen.slamArea(o._position.x, o._position.y, o._delta.x, o._delta.y); + + // Shape for an animation is no longer needed, so remove it completely + _canimShapes.remove_at(idx); + } else if (o._type == ACTIVE_BG_SHAPE) { + screen.flushImage(o._imageFrame, o._position, + &o._oldPosition.x, &o._oldPosition.y, &o._oldSize.x, &o._oldSize.y); + } + } + } + + _restoreFlag = true; + _doBgAnimDone = true; + + events.wait(3); + screen.resetDisplayBounds(); + + // Check if the method was called for calling a portrait, and a talk was + // interrupting it. This talk file would not have been executed at the time, + // since we needed to finish the 'doBgAnim' to finish clearing the portrait + if (people._clearingThePortrait && talk._scriptMoreFlag == 3) { + // Reset the flags and call to talk + people._clearingThePortrait = false; + talk._scriptMoreFlag = 0; + talk.talkTo(talk._scriptName); + } +} + +int ScalpelScene::startCAnim(int cAnimNum, int playRate) { + Events &events = *_vm->_events; + ScalpelMap &map = *(ScalpelMap *)_vm->_map; + People &people = *_vm->_people; + Resources &res = *_vm->_res; + Talk &talk = *_vm->_talk; + ScalpelUserInterface &ui = *(ScalpelUserInterface *)_vm->_ui; + Point32 tpPos, walkPos; + int tpDir, walkDir; + int tFrames = 0; + int gotoCode = -1; + + // Validation + if (cAnimNum >= (int)_cAnim.size()) + // number out of bounds + return -1; + if (_canimShapes.size() >= 3 || playRate == 0) + // Too many active animations, or invalid play rate + return 0; + + CAnim &cAnim = _cAnim[cAnimNum]; + if (playRate < 0) { + // Reverse direction + walkPos = cAnim._teleport[0]; + walkDir = cAnim._teleport[0]._facing; + tpPos = cAnim._goto[0]; + tpDir = cAnim._goto[0]._facing; + } else { + // Forward direction + walkPos = cAnim._goto[0]; + walkDir = cAnim._goto[0]._facing; + tpPos = cAnim._teleport[0]; + tpDir = cAnim._teleport[0]._facing; + } + + CursorId oldCursor = events.getCursor(); + events.setCursor(WAIT); + + if (walkPos.x != -1) { + // Holmes must walk to the walk point before the cAnimation is started + if (people[HOLMES]._position != walkPos) + people[HOLMES].walkToCoords(walkPos, walkDir); + } + + if (talk._talkToAbort) + return 1; + + // Add new anim shape entry for displaying the animation + _canimShapes.push_back(Object()); + Object &cObj = _canimShapes[_canimShapes.size() - 1]; + + // Copy the canimation into the bgShapes type canimation structure so it can be played + cObj._allow = cAnimNum + 1; // Keep track of the parent structure + cObj._name = _cAnim[cAnimNum]._name; // Copy name + + // Remove any attempt to draw object frame + if (cAnim._type == NO_SHAPE && cAnim._sequences[0] < 100) + cAnim._sequences[0] = 0; + + cObj._sequences = cAnim._sequences; + cObj._images = nullptr; + cObj._position = cAnim._position; + cObj._delta = Common::Point(0, 0); + cObj._type = cAnim._type; + cObj._flags = cAnim._flags; + + cObj._maxFrames = 0; + cObj._frameNumber = -1; + cObj._sequenceNumber = cAnimNum; + cObj._oldPosition = Common::Point(0, 0); + cObj._oldSize = Common::Point(0, 0); + cObj._goto = Common::Point(0, 0); + cObj._status = 0; + cObj._misc = 0; + cObj._imageFrame = nullptr; + + if (cAnim._name.size() > 0 && cAnim._type != NO_SHAPE) { + if (tpPos.x != -1) + people[HOLMES]._type = REMOVE; + + Common::String fname = cAnim._name + ".vgs"; + if (!res.isInCache(fname)) { + // Set up RRM scene data + Common::SeekableReadStream *roomStream = res.load(_roomFilename); + roomStream->seek(cAnim._dataOffset); + //rrmStream->seek(44 + cAnimNum * 4); + //rrmStream->seek(rrmStream->readUint32LE()); + + // Load the canimation into the cache + Common::SeekableReadStream *imgStream = !_lzwMode ? roomStream->readStream(cAnim._dataSize) : + Resources::decompressLZ(*roomStream, cAnim._dataSize); + res.addToCache(fname, *imgStream); + + delete imgStream; + delete roomStream; + } + + // Now load the resource as an image + if (!IS_3DO) { + cObj._images = new ImageFile(fname); + } else { + cObj._images = new ImageFile3DO(fname, kImageFile3DOType_RoomFormat); + } + cObj._imageFrame = &(*cObj._images)[0]; + cObj._maxFrames = cObj._images->size(); + + int frames = 0; + if (playRate < 0) { + // Reverse direction + // Count number of frames + while (frames < MAX_FRAME && cObj._sequences[frames]) + ++frames; + } else { + // Forward direction + BaseObject::_countCAnimFrames = true; + + while (cObj._type == ACTIVE_BG_SHAPE) { + cObj.checkObject(); + ++frames; + + if (frames >= 1000) + error("CAnim has infinite loop sequence"); + } + + if (frames > 1) + --frames; + + BaseObject::_countCAnimFrames = false; + + cObj._type = cAnim._type; + cObj._frameNumber = -1; + cObj._position = cAnim._position; + cObj._delta = Common::Point(0, 0); + } + + // Return if animation has no frames in it + if (frames == 0) + return -2; + + ++frames; + int repeat = ABS(playRate); + int dir; + + if (playRate < 0) { + // Play in reverse + dir = -2; + cObj._frameNumber = frames - 3; + } else { + dir = 0; + } + + tFrames = frames - 1; + int pauseFrame = (_cAnimFramePause) ? frames - _cAnimFramePause : -1; + + while (--frames) { + if (frames == pauseFrame) + ui.printObjectDesc(); + + doBgAnim(); + + // Repeat same frame + int temp = repeat; + while (--temp > 0) { + cObj._frameNumber--; + doBgAnim(); + + if (_vm->shouldQuit()) + return 0; + } + + cObj._frameNumber += dir; + } + + people[HOLMES]._type = CHARACTER; + } + + // Teleport to ending coordinates if necessary + if (tpPos.x != -1) { + people[HOLMES]._position = tpPos; // Place the player + people[HOLMES]._sequenceNumber = tpDir; + people[HOLMES].gotoStand(); + } + + if (playRate < 0) + // Reverse direction - set to end sequence + cObj._frameNumber = tFrames - 1; + + if (cObj._frameNumber <= 26) + gotoCode = cObj._sequences[cObj._frameNumber + 3]; + + // Unless anim shape has already been freed, set it to REMOVE so doBgAnim can free it + if (_canimShapes.indexOf(cObj) != -1) + cObj.checkObject(); + + if (gotoCode > 0 && !talk._talkToAbort) { + _goToScene = gotoCode; + + if (_goToScene < 97 && map[_goToScene].x) { + map._overPos = map[_goToScene]; + } + } + + people.loadWalk(); + + if (tpPos.x != -1 && !talk._talkToAbort) { + // Teleport to ending coordinates + people[HOLMES]._position = tpPos; + people[HOLMES]._sequenceNumber = tpDir; + + people[HOLMES].gotoStand(); + } + + events.setCursor(oldCursor); + + return 1; +} + +} // End of namespace Scalpel + +} // End of namespace Sherlock diff --git a/engines/sherlock/scalpel/scalpel_scene.h b/engines/sherlock/scalpel/scalpel_scene.h new file mode 100644 index 0000000000..77e86cf9cf --- /dev/null +++ b/engines/sherlock/scalpel/scalpel_scene.h @@ -0,0 +1,91 @@ +/* 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 SHERLOCK_SCALPEL_SCENE_H +#define SHERLOCK_SCALPEL_SCENE_H + +#include "common/scummsys.h" +#include "common/array.h" +#include "common/rect.h" +#include "common/serializer.h" +#include "sherlock/objects.h" +#include "sherlock/scene.h" +#include "sherlock/screen.h" + +namespace Sherlock { + +namespace Scalpel { + +enum { BLACKWOOD_CAPTURE = 2, BAKER_STREET = 4, DRAWING_ROOM = 12, STATION = 17, PUB_INTERIOR = 19, + LAWYER_OFFICE = 27, BAKER_ST_EXTERIOR = 39, RESCUE_ANNA = 52, MOOREHEAD_DEATH = 53, EXIT_GAME = 55, + BRUMWELL_SUICIDE = 70, OVERHEAD_MAP2 = 98, DARTS_GAME = 99, OVERHEAD_MAP = 100 }; + +class ScalpelScene : public Scene { +private: + void doBgAnimCheckCursor(); +protected: + /** + * Loads the data associated for a given scene. The room resource file's format is: + * BGHEADER: Holds an index for the rest of the file + * STRUCTS: The objects for the scene + * IMAGES: The graphic information for the structures + * + * The _misc field of the structures contains the number of the graphic image + * that it should point to after loading; _misc is then set to 0. + */ + virtual bool loadScene(const Common::String &filename); + + /** + * Checks all the background shapes. If a background shape is animating, + * it will flag it as needing to be drawn. If a non-animating shape is + * colliding with another shape, it will also flag it as needing drawing + */ + virtual void checkBgShapes(); + + /** + * Draw all the shapes, people and NPCs in the correct order + */ + virtual void drawAllShapes(); +public: + ScalpelScene(SherlockEngine *vm) : Scene(vm) {} + + /** + * Draw all objects and characters. + */ + virtual void doBgAnim(); + + /** + * Attempt to start a canimation sequence. It will load the requisite graphics, and + * then copy the canim object into the _canimShapes array to start the animation. + * + * @param cAnimNum The canim object within the current scene + * @param playRate Play rate. 0 is invalid; 1=normal speed, 2=1/2 speed, etc. + * A negative playRate can also be specified to play the animation in reverse + */ + virtual int startCAnim(int cAnimNum, int playRate = 1); +}; + +} // End of namespace Scalpel + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/scalpel/scalpel_talk.cpp b/engines/sherlock/scalpel/scalpel_talk.cpp new file mode 100644 index 0000000000..5a2427cac2 --- /dev/null +++ b/engines/sherlock/scalpel/scalpel_talk.cpp @@ -0,0 +1,602 @@ +/* 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 "sherlock/scalpel/scalpel_talk.h" +#include "sherlock/scalpel/scalpel_map.h" +#include "sherlock/scalpel/scalpel_people.h" +#include "sherlock/scalpel/scalpel_scene.h" +#include "sherlock/scalpel/scalpel_user_interface.h" +#include "sherlock/sherlock.h" +#include "sherlock/screen.h" +#include "sherlock/scalpel/3do/movie_decoder.h" + +namespace Sherlock { + +namespace Scalpel { + +const byte SCALPEL_OPCODES[] = { + 128, // OP_SWITCH_SPEAKER + 129, // OP_RUN_CANIMATION + 130, // OP_ASSIGN_PORTRAIT_LOCATION + 131, // OP_PAUSE + 132, // OP_REMOVE_PORTRAIT + 133, // OP_CLEAR_WINDOW + 134, // OP_ADJUST_OBJ_SEQUENCE + 135, // OP_WALK_TO_COORDS + 136, // OP_PAUSE_WITHOUT_CONTROL + 137, // OP_BANISH_WINDOW + 138, // OP_SUMMON_WINDOW + 139, // OP_SET_FLAG + 140, // OP_SFX_COMMAND + 141, // OP_TOGGLE_OBJECT + 142, // OP_STEALTH_MODE_ACTIVE + 143, // OP_IF_STATEMENT + 144, // OP_ELSE_STATEMENT + 145, // OP_END_IF_STATEMENT + 146, // OP_STEALTH_MODE_DEACTIVATE + 147, // OP_TURN_HOLMES_OFF + 148, // OP_TURN_HOLMES_ON + 149, // OP_GOTO_SCENE + 150, // OP_PLAY_PROLOGUE + 151, // OP_ADD_ITEM_TO_INVENTORY + 152, // OP_SET_OBJECT + 153, // OP_CALL_TALK_FILE + 143, // OP_MOVE_MOUSE + 155, // OP_DISPLAY_INFO_LINE + 156, // OP_CLEAR_INFO_LINE + 157, // OP_WALK_TO_CANIMATION + 158, // OP_REMOVE_ITEM_FROM_INVENTORY + 159, // OP_ENABLE_END_KEY + 160, // OP_DISABLE_END_KEY + 161, // OP_CARRIAGE_RETURN + 0, // OP_MOUSE_ON_OFF + 0, // OP_SET_WALK_CONTROL + 0, // OP_SET_TALK_SEQUENCE + 0, // OP_PLAY_SONG + 0, // OP_WALK_HOLMES_AND_NPC_TO_CANIM + 0, // OP_SET_NPC_PATH_DEST + 0, // OP_NEXT_SONG + 0, // OP_SET_NPC_PATH_PAUSE + 0, // OP_PASSWORD + 0, // OP_SET_SCENE_ENTRY_FLAG + 0, // OP_WALK_NPC_TO_CANIM + 0, // OP_WALK_HOLMES_AND_NPC_TO_COORDS + 0, // OP_WALK_HOLMES_AND_NPC_TO_COORDS + 0, // OP_SET_NPC_TALK_FILE + 0, // OP_TURN_NPC_OFF + 0, // OP_TURN_NPC_ON + 0, // OP_NPC_DESC_ON_OFF + 0, // OP_NPC_PATH_PAUSE_TAKING_NOTES + 0, // OP_NPC_PATH_PAUSE_LOOKING_HOLMES + 0, // OP_ENABLE_TALK_INTERRUPTS + 0, // OP_DISABLE_TALK_INTERRUPTS + 0, // OP_SET_NPC_INFO_LINE + 0, // OP_SET_NPC_POSITION + 0, // OP_NPC_PATH_LABEL + 0, // OP_PATH_GOTO_LABEL + 0, // OP_PATH_IF_FLAG_GOTO_LABEL + 0, // OP_NPC_WALK_GRAPHICS + 0, // OP_NPC_VERB + 0, // OP_NPC_VERB_CANIM + 0, // OP_NPC_VERB_SCRIPT + 0, // OP_RESTORE_PEOPLE_SEQUENCE + 0, // OP_NPC_VERB_TARGET + 0, // OP_TURN_SOUNDS_OFF + 0, // OP_NULL + 0 // OP_END_TEXT_WINDOW +}; + +/*----------------------------------------------------------------*/ + +ScalpelTalk::ScalpelTalk(SherlockEngine *vm) : Talk(vm) { + static OpcodeMethod OPCODE_METHODS[] = { + (OpcodeMethod)&ScalpelTalk::cmdSwitchSpeaker, + (OpcodeMethod)&ScalpelTalk::cmdRunCAnimation, + (OpcodeMethod)&ScalpelTalk::cmdAssignPortraitLocation, + + (OpcodeMethod)&ScalpelTalk::cmdPause, + (OpcodeMethod)&ScalpelTalk::cmdRemovePortrait, + (OpcodeMethod)&ScalpelTalk::cmdClearWindow, + (OpcodeMethod)&ScalpelTalk::cmdAdjustObjectSequence, + (OpcodeMethod)&ScalpelTalk::cmdWalkToCoords, + (OpcodeMethod)&ScalpelTalk::cmdPauseWithoutControl, + (OpcodeMethod)&ScalpelTalk::cmdBanishWindow, + (OpcodeMethod)&ScalpelTalk::cmdSummonWindow, + (OpcodeMethod)&ScalpelTalk::cmdSetFlag, + (OpcodeMethod)&ScalpelTalk::cmdSfxCommand, + + (OpcodeMethod)&ScalpelTalk::cmdToggleObject, + (OpcodeMethod)&ScalpelTalk::cmdStealthModeActivate, + (OpcodeMethod)&ScalpelTalk::cmdIf, + (OpcodeMethod)&ScalpelTalk::cmdElse, + nullptr, + (OpcodeMethod)&ScalpelTalk::cmdStealthModeDeactivate, + (OpcodeMethod)&ScalpelTalk::cmdHolmesOff, + (OpcodeMethod)&ScalpelTalk::cmdHolmesOn, + (OpcodeMethod)&ScalpelTalk::cmdGotoScene, + (OpcodeMethod)&ScalpelTalk::cmdPlayPrologue, + + (OpcodeMethod)&ScalpelTalk::cmdAddItemToInventory, + (OpcodeMethod)&ScalpelTalk::cmdSetObject, + (OpcodeMethod)&ScalpelTalk::cmdCallTalkFile, + (OpcodeMethod)&ScalpelTalk::cmdMoveMouse, + (OpcodeMethod)&ScalpelTalk::cmdDisplayInfoLine, + (OpcodeMethod)&ScalpelTalk::cmdClearInfoLine, + (OpcodeMethod)&ScalpelTalk::cmdWalkToCAnimation, + (OpcodeMethod)&ScalpelTalk::cmdRemoveItemFromInventory, + (OpcodeMethod)&ScalpelTalk::cmdEnableEndKey, + (OpcodeMethod)&ScalpelTalk::cmdDisableEndKey, + + (OpcodeMethod)&ScalpelTalk::cmdCarriageReturn, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr + }; + + _opcodeTable = OPCODE_METHODS; + _opcodes = SCALPEL_OPCODES; + + if (vm->getLanguage() == Common::DE_DEU || vm->getLanguage() == Common::ES_ESP) { + // The German and Spanish versions use a different opcode range + static byte opcodes[sizeof(SCALPEL_OPCODES)]; + for (uint idx = 0; idx < sizeof(SCALPEL_OPCODES); ++idx) + opcodes[idx] = SCALPEL_OPCODES[idx] ? SCALPEL_OPCODES[idx] + 47 : 0; + + _opcodes = opcodes; + } + +} + +void ScalpelTalk::talkInterface(const byte *&str) { + FixedText &fixedText = *_vm->_fixedText; + People &people = *_vm->_people; + Screen &screen = *_vm->_screen; + UserInterface &ui = *_vm->_ui; + + // If the window isn't yet open, draw the window before printing starts + if (!ui._windowOpen && _noTextYet) { + _noTextYet = false; + drawInterface(); + + if (_talkTo != -1) { + Common::String fixedText_Exit = fixedText.getText(kFixedText_Window_Exit); + Common::String fixedText_Up = fixedText.getText(kFixedText_Window_Up); + Common::String fixedText_Down = fixedText.getText(kFixedText_Window_Down); + screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_NULL, false, fixedText_Exit); + screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_NULL, false, fixedText_Up); + screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_NULL, false, fixedText_Down); + } + } + + // If it's the first line, display the speaker + if (!_line && _speaker >= 0 && _speaker < (int)people._characters.size()) { + // If the window is open, display the name directly on-screen. + // Otherwise, simply draw it on the back buffer + if (ui._windowOpen) { + screen.print(Common::Point(16, _yp), TALK_FOREGROUND, "%s", + people._characters[_speaker & 127]._name); + } + else { + screen.gPrint(Common::Point(16, _yp - 1), TALK_FOREGROUND, "%s", + people._characters[_speaker & 127]._name); + _openTalkWindow = true; + } + + _yp += 9; + } + + // Find amount of text that will fit on the line + int width = 0, idx = 0; + do { + width += screen.charWidth(str[idx]); + ++idx; + ++_charCount; + } while (width < 298 && str[idx] && str[idx] != '{' && (!isOpcode(str[idx]))); + + if (str[idx] || width >= 298) { + if ((!isOpcode(str[idx])) && str[idx] != '{') { + --idx; + --_charCount; + } + } + else { + _endStr = true; + } + + // If word wrap is needed, find the start of the current word + if (width >= 298) { + while (str[idx] != ' ') { + --idx; + --_charCount; + } + } + + // Print the line + Common::String lineStr((const char *)str, (const char *)str + idx); + + // If the speaker indicates a description file, print it in yellow + if (_speaker != -1) { + if (ui._windowOpen) { + screen.print(Common::Point(16, _yp), COMMAND_FOREGROUND, "%s", lineStr.c_str()); + } + else { + screen.gPrint(Common::Point(16, _yp - 1), COMMAND_FOREGROUND, "%s", lineStr.c_str()); + _openTalkWindow = true; + } + } + else { + if (ui._windowOpen) { + screen.print(Common::Point(16, _yp), COMMAND_FOREGROUND, "%s", lineStr.c_str()); + } + else { + screen.gPrint(Common::Point(16, _yp - 1), COMMAND_FOREGROUND, "%s", lineStr.c_str()); + _openTalkWindow = true; + } + } + + // Move to end of displayed line + str += idx; + + // If line wrap occurred, then move to after the separating space between the words + if ((!isOpcode(str[0])) && str[0] != '{') + ++str; + + _yp += 9; + ++_line; + + // Certain different conditions require a wait + if ((_line == 4 && str < _scriptEnd && str[0] != _opcodes[OP_SFX_COMMAND] && str[0] != _opcodes[OP_PAUSE] && _speaker != -1) || + (_line == 5 && str < _scriptEnd && str[0] != _opcodes[OP_PAUSE] && _speaker == -1) || + _endStr) { + _wait = 1; + } + + byte v = (str >= _scriptEnd ? 0 : str[0]); + if (v == _opcodes[OP_SWITCH_SPEAKER] || v == _opcodes[OP_ASSIGN_PORTRAIT_LOCATION] || + v == _opcodes[OP_BANISH_WINDOW] || v == _opcodes[OP_IF_STATEMENT] || + v == _opcodes[OP_ELSE_STATEMENT] || v == _opcodes[OP_END_IF_STATEMENT] || + v == _opcodes[OP_GOTO_SCENE] || v == _opcodes[OP_CALL_TALK_FILE]) { + _wait = 1; + } +} + +OpcodeReturn ScalpelTalk::cmdSwitchSpeaker(const byte *&str) { + ScalpelPeople &people = *(ScalpelPeople *)_vm->_people; + UserInterface &ui = *_vm->_ui; + + if (!(_speaker & SPEAKER_REMOVE)) + people.clearTalking(); + if (_talkToAbort) + return RET_EXIT; + + ui.clearWindow(); + _yp = CONTROLS_Y + 12; + _charCount = _line = 0; + + _speaker = *++str - 1; + people.setTalking(_speaker); + pullSequence(); + pushSequence(_speaker); + people.setTalkSequence(_speaker); + + return RET_SUCCESS; +} + +OpcodeReturn ScalpelTalk::cmdGotoScene(const byte *&str) { + ScalpelMap &map = *(ScalpelMap *)_vm->_map; + People &people = *_vm->_people; + Scene &scene = *_vm->_scene; + scene._goToScene = str[1] - 1; + + if (scene._goToScene != OVERHEAD_MAP) { + // Not going to the map overview + map._oldCharPoint = scene._goToScene; + map._overPos.x = (map[scene._goToScene].x - 6) * FIXED_INT_MULTIPLIER; + map._overPos.y = (map[scene._goToScene].y + 9) * FIXED_INT_MULTIPLIER; + + // Run a canimation? + if (str[2] > 100) { + people._hSavedFacing = str[2]; + people._hSavedPos = Point32(160, 100); + } else { + people._hSavedFacing = str[2] - 1; + int32 posX = (str[3] - 1) * 256 + str[4] - 1; + int32 posY = str[5] - 1; + people._hSavedPos = Point32(posX, posY); + } + } // if (scene._goToScene != 100) + + str += 6; + + _scriptMoreFlag = (scene._goToScene == 100) ? 2 : 1; + _scriptSaveIndex = str - _scriptStart; + _endStr = true; + _wait = 0; + + return RET_SUCCESS; +} + +OpcodeReturn ScalpelTalk::cmdAssignPortraitLocation(const byte *&str) { + People &people = *_vm->_people; + + ++str; + switch (str[0] & 15) { + case 1: + people._portraitSide = 20; + break; + case 2: + people._portraitSide = 220; + break; + case 3: + people._portraitSide = 120; + break; + default: + break; + } + + if (str[0] > 15) + people._speakerFlip = true; + + return RET_SUCCESS; +} + +OpcodeReturn ScalpelTalk::cmdClearInfoLine(const byte *&str) { + UserInterface &ui = *_vm->_ui; + + ui._infoFlag = true; + ui.clearInfo(); + + return RET_SUCCESS; +} + +OpcodeReturn ScalpelTalk::cmdClearWindow(const byte *&str) { + UserInterface &ui = *_vm->_ui; + + ui.clearWindow(); + _yp = CONTROLS_Y + 12; + _charCount = _line = 0; + + return RET_SUCCESS; +} + +OpcodeReturn ScalpelTalk::cmdDisplayInfoLine(const byte *&str) { + Screen &screen = *_vm->_screen; + UserInterface &ui = *_vm->_ui; + Common::String tempString; + + ++str; + for (int idx = 0; idx < str[0]; ++idx) + tempString += str[idx + 1]; + str += str[0]; + + screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", tempString.c_str()); + ui._menuCounter = 30; + + return RET_SUCCESS; +} + +OpcodeReturn ScalpelTalk::cmdElse(const byte *&str) { + // If this is encountered here, it means that a preceeding IF statement was found, + // and evaluated to true. Now all the statements for the true block are finished, + // so skip over the block of code that would have executed if the result was false + _wait = 0; + do { + ++str; + } while (str[0] && str[0] != _opcodes[OP_END_IF_STATEMENT]); + + return RET_SUCCESS; +} + +OpcodeReturn ScalpelTalk::cmdIf(const byte *&str) { + ++str; + int flag = (str[0] - 1) * 256 + str[1] - 1 - (str[1] == 1 ? 1 : 0); + ++str; + _wait = 0; + + bool result = flag < 0x8000; + if (_vm->readFlags(flag & 0x7fff) != result) { + do { + ++str; + } while (str[0] && str[0] != _opcodes[OP_ELSE_STATEMENT] && str[0] != _opcodes[OP_END_IF_STATEMENT]); + + if (!str[0]) + _endStr = true; + } + + return RET_SUCCESS; +} + +OpcodeReturn ScalpelTalk::cmdMoveMouse(const byte *&str) { + Events &events = *_vm->_events; + + ++str; + events.moveMouse(Common::Point((str[0] - 1) * 256 + str[1] - 1, str[2])); + if (_talkToAbort) + return RET_EXIT; + str += 3; + + return RET_SUCCESS; +} + +OpcodeReturn ScalpelTalk::cmdPlayPrologue(const byte *&str) { + Animation &anim = *_vm->_animation; + Common::String tempString; + + ++str; + for (int idx = 0; idx < 8 && str[idx] != '~'; ++idx) + tempString += str[idx]; + + anim.play(tempString, false, 1, 3, true, 4); + + return RET_SUCCESS; +} + +OpcodeReturn ScalpelTalk::cmdRemovePortrait(const byte *&str) { + People &people = *_vm->_people; + + if (_speaker >= 0 && _speaker < SPEAKER_REMOVE) + people.clearTalking(); + pullSequence(); + if (_talkToAbort) + return RET_EXIT; + + _speaker |= SPEAKER_REMOVE; + return RET_SUCCESS; +} + +OpcodeReturn ScalpelTalk::cmdWalkToCoords(const byte *&str) { + People &people = *_vm->_people; + ++str; + + people[HOLMES].walkToCoords(Point32(((str[0] - 1) * 256 + str[1] - 1) * FIXED_INT_MULTIPLIER, + str[2] * FIXED_INT_MULTIPLIER), str[3] - 1); + if (_talkToAbort) + return RET_EXIT; + + str += 3; + return RET_SUCCESS; +} + +OpcodeReturn ScalpelTalk::cmdSfxCommand(const byte *&str) { + Sound &sound = *_vm->_sound; + Common::String tempString; + + ++str; + if (sound._voices) { + for (int idx = 0; idx < 8 && str[idx] != '~'; ++idx) + tempString += str[idx]; + sound.playSound(tempString, WAIT_RETURN_IMMEDIATELY); + + // Set voices to wait for more + sound._voices = 2; + sound._speechOn = (*sound._soundIsOn); + } + + _wait = 1; + str += 7; + + return RET_SUCCESS; +} + +OpcodeReturn ScalpelTalk::cmdSummonWindow(const byte *&str) { + Events &events = *_vm->_events; + FixedText &fixedText = *_vm->_fixedText; + Screen &screen = *_vm->_screen; + + drawInterface(); + events._pressed = events._released = false; + events.clearKeyboard(); + _noTextYet = false; + + if (_speaker != -1) { + Common::String fixedText_Exit = fixedText.getText(kFixedText_Window_Exit); + Common::String fixedText_Up = fixedText.getText(kFixedText_Window_Up); + Common::String fixedText_Down = fixedText.getText(kFixedText_Window_Down); + screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_NULL, false, fixedText_Exit); + screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_NULL, false, fixedText_Up); + screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_NULL, false, fixedText_Down); + } + + return RET_SUCCESS; +} + +OpcodeReturn ScalpelTalk::cmdCarriageReturn(const byte *&str) { + return RET_SUCCESS; +} + +void ScalpelTalk::talkWait(const byte *&str) { + UserInterface &ui = *_vm->_ui; + bool pauseFlag = _pauseFlag; + + Talk::talkWait(str); + + // Clear the window unless the wait was due to a PAUSE command + if (!pauseFlag && _wait != -1 && str < _scriptEnd && str[0] != _opcodes[OP_SFX_COMMAND]) { + if (!_talkStealth) + ui.clearWindow(); + _yp = CONTROLS_Y + 12; + _charCount = _line = 0; + } +} + +void ScalpelTalk::talk3DOMovieTrigger(int subIndex) { + if (!IS_3DO) { + // No 3DO? No movie! + return; + } + + // Find out a few things that we need + int userSelector = _vm->_ui->_selector; + int scriptSelector = _scriptSelect; + int selector = 0; + int roomNr = _vm->_scene->_currentScene; + + if (userSelector >= 0) { + // User-selected dialog + selector = userSelector; + } else { + if (scriptSelector >= 0) { + // Script-selected dialog + selector = scriptSelector; + subIndex--; // for scripts we adjust subIndex, b/c we won't get called from doTalkControl() + } else { + warning("talk3DOMovieTrigger: unable to find selector"); + return; + } + } + + // Make a quick update, so that current text is shown on screen + _vm->_screen->update(); + + // Figure out that movie filename + Common::String movieFilename; + + movieFilename = _scriptName; + movieFilename.deleteChar(1); // remove 2nd character of scriptname + // cut scriptname to 6 characters + while (movieFilename.size() > 6) { + movieFilename.deleteChar(6); + } + + movieFilename.insertChar(selector + 'a', movieFilename.size()); + movieFilename.insertChar(subIndex + 'a', movieFilename.size()); + movieFilename = Common::String::format("movies/%02d/%s.stream", roomNr, movieFilename.c_str()); + + warning("3DO movie player:"); + warning("room: %d", roomNr); + warning("script: %s", _scriptName.c_str()); + warning("selector: %d", selector); + warning("subindex: %d", subIndex); + + Scalpel3DOMoviePlay(movieFilename.c_str(), Common::Point(5, 5)); + + // Restore screen HACK + _vm->_screen->makeAllDirty(); +} + +} // End of namespace Scalpel + +} // End of namespace Sherlock diff --git a/engines/sherlock/scalpel/scalpel_talk.h b/engines/sherlock/scalpel/scalpel_talk.h new file mode 100644 index 0000000000..2f8e412795 --- /dev/null +++ b/engines/sherlock/scalpel/scalpel_talk.h @@ -0,0 +1,80 @@ +/* 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 SHERLOCK_SCALPEL_TALK_H +#define SHERLOCK_SCALPEL_TALK_H + +#include "common/scummsys.h" +#include "common/array.h" +#include "common/rect.h" +#include "common/serializer.h" +#include "common/stream.h" +#include "common/stack.h" +#include "sherlock/talk.h" + +namespace Sherlock { + +namespace Scalpel { + +class ScalpelTalk : public Talk { +private: + OpcodeReturn cmdSwitchSpeaker(const byte *&str); + OpcodeReturn cmdAssignPortraitLocation(const byte *&str); + OpcodeReturn cmdGotoScene(const byte *&str); + OpcodeReturn cmdClearInfoLine(const byte *&str); + OpcodeReturn cmdClearWindow(const byte *&str); + OpcodeReturn cmdDisplayInfoLine(const byte *&str); + OpcodeReturn cmdElse(const byte *&str); + OpcodeReturn cmdIf(const byte *&str); + OpcodeReturn cmdMoveMouse(const byte *&str); + OpcodeReturn cmdPlayPrologue(const byte *&str); + OpcodeReturn cmdRemovePortrait(const byte *&str); + OpcodeReturn cmdSfxCommand(const byte *&str); + OpcodeReturn cmdSummonWindow(const byte *&str); + OpcodeReturn cmdCarriageReturn(const byte *&str); + OpcodeReturn cmdWalkToCoords(const byte *&str); +protected: + /** + * Display the talk interface window + */ + virtual void talkInterface(const byte *&str); + + /** + * Pause when displaying a talk dialog on-screen + */ + virtual void talkWait(const byte *&str); + + /** + * Trigger to play a 3DO talk dialog movie + */ + virtual void talk3DOMovieTrigger(int subIndex); + +public: + ScalpelTalk(SherlockEngine *vm); + virtual ~ScalpelTalk() {} +}; + +} // End of namespace Scalpel + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/scalpel/scalpel_user_interface.cpp b/engines/sherlock/scalpel/scalpel_user_interface.cpp new file mode 100644 index 0000000000..f85c95e25b --- /dev/null +++ b/engines/sherlock/scalpel/scalpel_user_interface.cpp @@ -0,0 +1,2184 @@ +/* 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 "sherlock/scalpel/scalpel_user_interface.h" +#include "sherlock/scalpel/scalpel_journal.h" +#include "sherlock/scalpel/scalpel_people.h" +#include "sherlock/scalpel/settings.h" +#include "sherlock/sherlock.h" + +namespace Sherlock { + +namespace Scalpel { + +// Main user interface menu control locations +const int MENU_POINTS[12][4] = { + { 13, 153, 72, 165 }, + { 13, 169, 72, 181 }, + { 13, 185, 72, 197 }, + { 88, 153, 152, 165 }, + { 88, 169, 152, 181 }, + { 88, 185, 152, 197 }, + { 165, 153, 232, 165 }, + { 165, 169, 232, 181 }, + { 165, 185, 233, 197 }, + { 249, 153, 305, 165 }, + { 249, 169, 305, 181 }, + { 249, 185, 305, 197 } +}; + +// Inventory control locations */ +const int INVENTORY_POINTS[8][3] = { + { 4, 50, 29 }, + { 52, 99, 77 }, + { 101, 140, 123 }, + { 142, 187, 166 }, + { 189, 219, 198 }, + { 221, 251, 234 }, + { 253, 283, 266 }, + { 285, 315, 294 } +}; + +const char COMMANDS[13] = "LMTPOCIUGJFS"; +const char INVENTORY_COMMANDS[9] = { "ELUG-+,." }; +const char *const PRESS_KEY_FOR_MORE = "Press any Key for More."; +const char *const PRESS_KEY_TO_CONTINUE = "Press any Key to Continue."; +const int UI_OFFSET_3DO = 16; // (320 - 288) / 2 + +/*----------------------------------------------------------------*/ + + +ScalpelUserInterface::ScalpelUserInterface(SherlockEngine *vm): UserInterface(vm) { + if (_vm->_interactiveFl) { + if (!IS_3DO) { + // PC + _controls = new ImageFile("menu.all"); + _controlPanel = new ImageFile("controls.vgs"); + } else { + // 3DO + _controls = new ImageFile3DO("menu.all", kImageFile3DOType_RoomFormat); + _controlPanel = new ImageFile3DO("controls.vgs", kImageFile3DOType_RoomFormat); + } + } else { + _controls = nullptr; + _controlPanel = nullptr; + } + + _keyPress = '\0'; + _lookHelp = 0; + _help = _oldHelp = 0; + _key = _oldKey = '\0'; + _temp = _oldTemp = 0; + _oldLook = 0; + _keyboardInput = false; + _pause = false; + _cNum = 0; + _find = 0; + _oldUse = 0; +} + +ScalpelUserInterface::~ScalpelUserInterface() { + delete _controls; + delete _controlPanel; +} + +void ScalpelUserInterface::reset() { + UserInterface::reset(); + _help = _oldHelp = -1; +} + +void ScalpelUserInterface::drawInterface(int bufferNum) { + Screen &screen = *_vm->_screen; + + const ImageFrame &src = (*_controlPanel)[0]; + int16 x = (!IS_3DO) ? 0 : UI_OFFSET_3DO; + + if (bufferNum & 1) + screen._backBuffer1.transBlitFrom(src, Common::Point(x, CONTROLS_Y)); + if (bufferNum & 2) + screen._backBuffer2.transBlitFrom(src, Common::Point(x, CONTROLS_Y)); + if (bufferNum == 3) + screen._backBuffer2.fillRect(0, INFO_LINE, SHERLOCK_SCREEN_WIDTH, INFO_LINE + 10, INFO_BLACK); +} + +void ScalpelUserInterface::handleInput() { + Events &events = *_vm->_events; + Inventory &inv = *_vm->_inventory; + People &people = *_vm->_people; + Scene &scene = *_vm->_scene; + Screen &screen = *_vm->_screen; + Talk &talk = *_vm->_talk; + + if (_menuCounter) + whileMenuCounter(); + + Common::Point pt = events.mousePos(); + _bgFound = scene.findBgShape(pt); + _keyPress = '\0'; + + // Check kbd and set the mouse released flag if Enter or space is pressed. + // Otherwise, the pressed _key is stored for later use + if (events.kbHit()) { + Common::KeyState keyState = events.getKey(); + _keyPress = keyState.ascii; + + if (keyState.keycode == Common::KEYCODE_x && keyState.flags & Common::KBD_ALT) { + _vm->quitGame(); + events.pollEvents(); + return; + } + } + + // Do button highlighting check + if (!talk._scriptMoreFlag) { // Don't if scripts are running + if (((events._rightPressed || events._rightReleased) && _helpStyle) || + (!_helpStyle && !_menuCounter)) { + // Handle any default commands if we're in STD_MODE + if (_menuMode == STD_MODE) { + if (pt.y < CONTROLS_Y && + (events._rightPressed || (!_helpStyle && !events._released)) && + (_bgFound != -1) && (_bgFound < 1000) && + (scene._bgShapes[_bgFound]._defaultCommand || + !scene._bgShapes[_bgFound]._description.empty())) { + // If there is no default command, so set it to Look + if (scene._bgShapes[_bgFound]._defaultCommand) + _help = scene._bgShapes[_bgFound]._defaultCommand - 1; + else + _help = 0; + + // Reset 'help' if it is an invalid command + if (_help > 5) + _help = -1; + } else if (pt.y < CONTROLS_Y && + ((events._rightReleased && _helpStyle) || (events._released && !_helpStyle)) && + (_bgFound != -1 && _bgFound < 1000) && + (scene._bgShapes[_bgFound]._defaultCommand || + !scene._bgShapes[_bgFound]._description.empty())) { + // If there is no default command, set it to Look + if (scene._bgShapes[_bgFound]._defaultCommand) + _menuMode = (MenuMode)scene._bgShapes[_bgFound]._defaultCommand; + else + _menuMode = LOOK_MODE; + events._released = true; + events._pressed = events._oldButtons = false; + _help = _oldHelp = -1; + + if (_menuMode == LOOK_MODE) { + // Set the flag to tell the game that this was a right-click + // call to look and should exit without the look button being pressed + _lookHelp = true; + } + } else { + _help = -1; + } + + // Check if highlighting a different button than last time + if (_help != _oldHelp) { + // If another button was highlighted previously, restore it + if (_oldHelp != -1) + restoreButton(_oldHelp); + + // If we're highlighting a new button, then draw it pressed + if (_help != -1) + depressButton(_help); + + _oldHelp = _help; + } + + if (_bgFound != _oldBgFound || _oldBgFound == -1) { + _infoFlag = true; + clearInfo(); + + if (_help != -1 && !scene._bgShapes[_bgFound]._description.empty() + && scene._bgShapes[_bgFound]._description[0] != ' ') + screen.print(Common::Point(0, INFO_LINE + 1), + INFO_FOREGROUND, "%s", scene._bgShapes[_bgFound]._description.c_str()); + + _oldBgFound = _bgFound; + } + } else { + // We're not in STD_MODE + // If there isn't a window open, then revert back to STD_MODE + if (!_windowOpen && events._rightReleased) { + // Restore all buttons + for (int idx = 0; idx < 12; ++idx) + restoreButton(idx); + + _menuMode = STD_MODE; + _key = _oldKey = -1; + _temp = _oldTemp = _lookHelp = _invLookFlag = 0; + events.clearEvents(); + } + } + } + } + + // Reset the old bgshape number if the mouse button is released, so that + // it can e re-highlighted when we come back here + if ((events._rightReleased && _helpStyle) || (events._released && !_helpStyle)) + _oldBgFound = -1; + + // Do routines that should be done before input processing + switch (_menuMode) { + case LOOK_MODE: + if (!_windowOpen) { + if (events._released && _bgFound >= 0 && _bgFound < 1000) { + if (!scene._bgShapes[_bgFound]._examine.empty()) + examine(); + } else { + lookScreen(pt); + } + } + break; + + case MOVE_MODE: + case OPEN_MODE: + case CLOSE_MODE: + case PICKUP_MODE: + lookScreen(pt); + break; + + case TALK_MODE: + if (!_windowOpen) { + bool personFound; + + if (_bgFound >= 1000) { + personFound = false; + if (!events._released) + lookScreen(pt); + } else { + personFound = _bgFound != -1 && scene._bgShapes[_bgFound]._aType == PERSON; + } + + if (events._released && personFound) + talk.talk(_bgFound); + else if (personFound) + lookScreen(pt); + else if (_bgFound < 1000) + clearInfo(); + } + break; + + case USE_MODE: + case GIVE_MODE: + case INV_MODE: + if (inv._invMode == INVMODE_LOOK || inv._invMode == INVMODE_USE || inv._invMode == INVMODE_GIVE) { + if (pt.y > CONTROLS_Y) + lookInv(); + else + lookScreen(pt); + } + break; + + default: + break; + } + + // + // Do input processing + // + if (events._pressed || events._released || events._rightPressed || _keyPress || _pause) { + if (((events._released && (_helpStyle || _help == -1)) || (events._rightReleased && !_helpStyle)) && + (pt.y <= CONTROLS_Y) && (_menuMode == STD_MODE)) { + // The mouse was clicked in the playing area with no action buttons down. + // Check if the mouse was clicked in a script zone. If it was, + // then execute the script. Otherwise, walk to the given position + if (scene.checkForZones(pt, SCRIPT_ZONE) != 0 || + scene.checkForZones(pt, NOWALK_ZONE) != 0) { + // Mouse clicked in script zone + events._pressed = events._released = false; + } else { + people._allowWalkAbort = false; + people[HOLMES]._walkDest = pt; + people[HOLMES].goAllTheWay(); + } + + if (_oldKey != -1) { + restoreButton(_oldTemp); + _oldKey = -1; + } + } + + // Handle action depending on selected mode + switch (_menuMode) { + case LOOK_MODE: + if (_windowOpen) + doLookControl(); + break; + + case MOVE_MODE: + doMiscControl(ALLOW_MOVE); + break; + + case TALK_MODE: + if (_windowOpen) + doTalkControl(); + break; + + case OPEN_MODE: + doMiscControl(ALLOW_OPEN); + break; + + case CLOSE_MODE: + doMiscControl(ALLOW_CLOSE); + break; + + case PICKUP_MODE: + doPickControl(); + break; + + case USE_MODE: + case GIVE_MODE: + case INV_MODE: + doInvControl(); + break; + + case FILES_MODE: + doEnvControl(); + break; + + default: + break; + } + + // As long as there isn't an open window, do main input processing. + // Windows are opened when in TALK, USE, INV, and GIVE modes + if ((!_windowOpen && !_menuCounter && pt.y > CONTROLS_Y) || + _keyPress) { + if (events._pressed || events._released || _pause || _keyPress) + doMainControl(); + } + + if (pt.y < CONTROLS_Y && events._pressed && _oldTemp != (int)(_menuMode - 1) && _oldKey != -1) + restoreButton(_oldTemp); + } +} + +void ScalpelUserInterface::depressButton(int num) { + Screen &screen = *_vm->_screen; + Common::Point pt(MENU_POINTS[num][0], MENU_POINTS[num][1]); + offsetButton3DO(pt, num); + + ImageFrame &frame = (*_controls)[num]; + screen._backBuffer1.transBlitFrom(frame, pt); + screen.slamArea(pt.x, pt.y, pt.x + frame._width, pt.y + frame._height); +} + +void ScalpelUserInterface::restoreButton(int num) { + Events &events = *_vm->_events; + Screen &screen = *_vm->_screen; + Common::Point pt(MENU_POINTS[num][0], MENU_POINTS[num][1]); + offsetButton3DO(pt, num); + + Graphics::Surface &frame = (*_controls)[num]._frame; + + // Reset the cursor + events.setCursor(ARROW); + + // Restore the UI on the back buffer + screen._backBuffer1.blitFrom(screen._backBuffer2, pt, + Common::Rect(pt.x, pt.y, pt.x + 90, pt.y + 19)); + screen.slamArea(pt.x, pt.y, pt.x + frame.w, pt.y + frame.h); + + if (!_menuCounter) { + _infoFlag = true; + clearInfo(); + } +} + +void ScalpelUserInterface::pushButton(int num) { + Events &events = *_vm->_events; + _oldKey = -1; + + if (!events._released) { + if (_oldHelp != -1) + restoreButton(_oldHelp); + if (_help != -1) + restoreButton(_help); + + depressButton(num); + events.wait(6); + } + + restoreButton(num); +} + +void ScalpelUserInterface::toggleButton(int num) { + Screen &screen = *_vm->_screen; + + if (_menuMode != (MenuMode)(num + 1)) { + _menuMode = (MenuMode)(num + 1); + _oldKey = COMMANDS[num]; + _oldTemp = num; + + if (_keyboardInput) { + if (_oldHelp != -1 && _oldHelp != num) + restoreButton(_oldHelp); + if (_help != -1 && _help != num) + restoreButton(_help); + + _keyboardInput = false; + + ImageFrame &frame = (*_controls)[num]; + Common::Point pt(MENU_POINTS[num][0], MENU_POINTS[num][1]); + offsetButton3DO(pt, num); + screen._backBuffer1.transBlitFrom(frame, pt); + screen.slamArea(pt.x, pt.y, pt.x + frame._width, pt.y + frame._height); + } + } else { + _menuMode = STD_MODE; + _oldKey = -1; + restoreButton(num); + } +} + +void ScalpelUserInterface::clearInfo() { + if (_infoFlag) { + _vm->_screen->vgaBar(Common::Rect(16, INFO_LINE, SHERLOCK_SCREEN_WIDTH - 19, + INFO_LINE + 10), INFO_BLACK); + _infoFlag = false; + _oldLook = -1; + } +} + +void ScalpelUserInterface::clearWindow() { + if (_windowOpen) { + _vm->_screen->vgaBar(Common::Rect(3, CONTROLS_Y + 11, SHERLOCK_SCREEN_WIDTH - 2, + SHERLOCK_SCREEN_HEIGHT - 2), INV_BACKGROUND); + } +} + +void ScalpelUserInterface::whileMenuCounter() { + if (!(--_menuCounter) || _vm->_events->checkInput()) { + _menuCounter = 0; + _infoFlag = true; + clearInfo(); + } +} + +void ScalpelUserInterface::examine() { + Events &events = *_vm->_events; + Inventory &inv = *_vm->_inventory; + People &people = *_vm->_people; + Scene &scene = *_vm->_scene; + Talk &talk = *_vm->_talk; + Common::Point pt = events.mousePos(); + + if (pt.y < (CONTROLS_Y + 9)) { + Object &obj = scene._bgShapes[_bgFound]; + + if (obj._lookcAnim != 0) { + int canimSpeed = ((obj._lookcAnim & 0xe0) >> 5) + 1; + scene._cAnimFramePause = obj._lookFrames; + _cAnimStr = obj._examine; + _cNum = (obj._lookcAnim & 0x1f) - 1; + + scene.startCAnim(_cNum, canimSpeed); + } else if (obj._lookPosition.y != 0) { + // Need to walk to the object to be examined + people[HOLMES].walkToCoords(obj._lookPosition, obj._lookFacing); + } + + if (!talk._talkToAbort) { + _cAnimStr = obj._examine; + if (obj._lookFlag) + _vm->setFlags(obj._lookFlag); + } + } else { + // Looking at an inventory item + _cAnimStr = inv[_selector]._examine; + if (inv[_selector]._lookFlag) + _vm->setFlags(inv[_selector]._lookFlag); + } + + if (_invLookFlag) { + // Don't close the inventory window when starting an examine display, since its + // window will slide up to replace the inventory display + _windowOpen = false; + _menuMode = LOOK_MODE; + } + + if (!talk._talkToAbort) { + if (!scene._cAnimFramePause) + printObjectDesc(_cAnimStr, true); + else + // description was already printed in startCAnimation + scene._cAnimFramePause = 0; + } +} + +void ScalpelUserInterface::lookScreen(const Common::Point &pt) { + Events &events = *_vm->_events; + Inventory &inv = *_vm->_inventory; + Scene &scene = *_vm->_scene; + Screen &screen = *_vm->_screen; + Common::Point mousePos = events.mousePos(); + int temp; + Common::String tempStr; + + // Don't display anything for right button command + if ((events._rightPressed || events._rightReleased) && !events._pressed) + return; + + if (mousePos.y < CONTROLS_Y && (temp = _bgFound) != -1) { + if (temp != _oldLook) { + _infoFlag = true; + clearInfo(); + + if (temp < 1000) + tempStr = scene._bgShapes[temp]._description; + else + tempStr = scene._bgShapes[temp - 1000]._description; + + _infoFlag = true; + clearInfo(); + + // Only print description if there is one + if (!tempStr.empty() && tempStr[0] != ' ') { + // If inventory is active and an item is selected for a Use or Give action + if ((_menuMode == INV_MODE || _menuMode == USE_MODE || _menuMode == GIVE_MODE) && + (inv._invMode == INVMODE_USE || inv._invMode == INVMODE_GIVE)) { + int width1 = 0, width2 = 0; + int x, width; + if (inv._invMode == INVMODE_USE) { + // Using an object + x = width = screen.stringWidth("Use "); + + if (temp < 1000 && scene._bgShapes[temp]._aType != PERSON) + // It's not a person, so make it lowercase + tempStr.setChar(tolower(tempStr[0]), 0); + + x += screen.stringWidth(tempStr); + + // If we're using an inventory object, add in the width + // of the object name and the " on " + if (_selector != -1) { + width1 = screen.stringWidth(inv[_selector]._name); + x += width1; + width2 = screen.stringWidth(" on "); + x += width2; + } + + // If the line will be too long, keep cutting off characters + // until the string will fit + while (x > 280) { + x -= screen.charWidth(tempStr.lastChar()); + tempStr.deleteLastChar(); + } + + int xStart = (SHERLOCK_SCREEN_WIDTH - x) / 2; + screen.print(Common::Point(xStart, INFO_LINE + 1), + INFO_FOREGROUND, "Use "); + + if (_selector != -1) { + screen.print(Common::Point(xStart + width, INFO_LINE + 1), + TALK_FOREGROUND, "%s", inv[_selector]._name.c_str()); + screen.print(Common::Point(xStart + width + width1, INFO_LINE + 1), + INFO_FOREGROUND, " on "); + screen.print(Common::Point(xStart + width + width1 + width2, INFO_LINE + 1), + INFO_FOREGROUND, "%s", tempStr.c_str()); + } else { + screen.print(Common::Point(xStart + width, INFO_LINE + 1), + INFO_FOREGROUND, "%s", tempStr.c_str()); + } + } else if (temp >= 0 && temp < 1000 && _selector != -1 && + scene._bgShapes[temp]._aType == PERSON) { + // Giving an object to a person + width1 = screen.stringWidth(inv[_selector]._name); + x = width = screen.stringWidth("Give "); + x += width1; + width2 = screen.stringWidth(" to "); + x += width2; + x += screen.stringWidth(tempStr); + + // Ensure string will fit on-screen + while (x > 280) { + x -= screen.charWidth(tempStr.lastChar()); + tempStr.deleteLastChar(); + } + + int xStart = (SHERLOCK_SCREEN_WIDTH - x) / 2; + screen.print(Common::Point(xStart, INFO_LINE + 1), + INFO_FOREGROUND, "Give "); + screen.print(Common::Point(xStart + width, INFO_LINE + 1), + TALK_FOREGROUND, "%s", inv[_selector]._name.c_str()); + screen.print(Common::Point(xStart + width + width1, INFO_LINE + 1), + INFO_FOREGROUND, " to "); + screen.print(Common::Point(xStart + width + width1 + width2, INFO_LINE + 1), + INFO_FOREGROUND, "%s", tempStr.c_str()); + } + } else { + screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", tempStr.c_str()); + } + + _infoFlag = true; + _oldLook = temp; + } + } + } else { + clearInfo(); + } +} + +void ScalpelUserInterface::lookInv() { + Events &events = *_vm->_events; + Inventory &inv = *_vm->_inventory; + Screen &screen = *_vm->_screen; + Common::Point mousePos = events.mousePos(); + + if (mousePos.x > 15 && mousePos.x < 314 && mousePos.y > (CONTROLS_Y1 + 11) + && mousePos.y < (SHERLOCK_SCREEN_HEIGHT - 2)) { + int temp = (mousePos.x - 6) / 52 + inv._invIndex; + if (temp < inv._holdings) { + if (temp < inv._holdings) { + clearInfo(); + screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, + "%s", inv[temp]._description.c_str()); + _infoFlag = true; + _oldLook = temp; + } + } else { + clearInfo(); + } + } else { + clearInfo(); + } +} + +void ScalpelUserInterface::doEnvControl() { + Events &events = *_vm->_events; + SaveManager &saves = *_vm->_saves; + Scene &scene = *_vm->_scene; + Screen &screen = *_vm->_screen; + Talk &talk = *_vm->_talk; + Common::Point mousePos = events.mousePos(); + static const char ENV_COMMANDS[7] = "ELSUDQ"; + + byte color; + + _key = _oldKey = -1; + _keyboardInput = false; + int found = saves.getHighlightedButton(); + + if (events._pressed || events._released) { + events.clearKeyboard(); + + // Check for a filename entry being highlighted + if ((events._pressed || events._released) && mousePos.y > (CONTROLS_Y + 10)) { + int found1 = 0; + for (_selector = 0; (_selector < ONSCREEN_FILES_COUNT) && !found1; ++_selector) + if (mousePos.y > (CONTROLS_Y + 11 + _selector * 10) && mousePos.y < (CONTROLS_Y + 21 + _selector * 10)) + found1 = 1; + + if (_selector + saves._savegameIndex - 1 < MAX_SAVEGAME_SLOTS + (saves._envMode != SAVEMODE_LOAD)) + _selector = _selector + saves._savegameIndex - 1; + else + _selector = -1; + + if (!found1) + _selector = -1; + } + + // Handle selecting buttons, if any + saves.highlightButtons(found); + + if (found == 0 || found == 5) + saves._envMode = SAVEMODE_NONE; + } + + if (_keyPress) { + _key = toupper(_keyPress); + + // Escape _key will close the dialog + if (_key == Common::KEYCODE_ESCAPE) + _key = 'E'; + + if (_key == 'E' || _key == 'L' || _key == 'S' || _key == 'U' || _key == 'D' || _key == 'Q') { + const char *chP = strchr(ENV_COMMANDS, _key); + int btnIndex = !chP ? -1 : chP - ENV_COMMANDS; + saves.highlightButtons(btnIndex); + _keyboardInput = true; + + if (_key == 'E' || _key == 'Q') { + saves._envMode = SAVEMODE_NONE; + } else if (_key >= '1' && _key <= '9') { + _keyboardInput = true; + _selector = _key - '1'; + if (_selector >= MAX_SAVEGAME_SLOTS + (saves._envMode == SAVEMODE_LOAD ? 0 : 1)) + _selector = -1; + + if (saves.checkGameOnScreen(_selector)) + _oldSelector = _selector; + } else { + _selector = -1; + } + } + } + + if (_selector != _oldSelector) { + if (_oldSelector != -1 && _oldSelector >= saves._savegameIndex && _oldSelector < (saves._savegameIndex + ONSCREEN_FILES_COUNT)) { + screen.print(Common::Point(6, CONTROLS_Y + 12 + (_oldSelector - saves._savegameIndex) * 10), + INV_FOREGROUND, "%d.", _oldSelector + 1); + screen.print(Common::Point(24, CONTROLS_Y + 12 + (_oldSelector - saves._savegameIndex) * 10), + INV_FOREGROUND, "%s", saves._savegames[_oldSelector].c_str()); + } + + if (_selector != -1) { + screen.print(Common::Point(6, CONTROLS_Y + 12 + (_selector - saves._savegameIndex) * 10), + TALK_FOREGROUND, "%d.", _selector + 1); + screen.print(Common::Point(24, CONTROLS_Y + 12 + (_selector - saves._savegameIndex) * 10), + TALK_FOREGROUND, "%s", saves._savegames[_selector].c_str()); + } + + _oldSelector = _selector; + } + + if (events._released || _keyboardInput) { + if ((found == 0 && events._released) || _key == 'E') { + banishWindow(); + _windowBounds.top = CONTROLS_Y1; + + events._pressed = events._released = _keyboardInput = false; + _keyPress = '\0'; + } else if ((found == 1 && events._released) || _key == 'L') { + saves._envMode = SAVEMODE_LOAD; + if (_selector != -1) { + saves.loadGame(_selector + 1); + } + } else if ((found == 2 && events._released) || _key == 'S') { + saves._envMode = SAVEMODE_SAVE; + if (_selector != -1) { + if (saves.checkGameOnScreen(_selector)) + _oldSelector = _selector; + + if (saves.promptForDescription(_selector)) { + saves.saveGame(_selector + 1, saves._savegames[_selector]); + + banishWindow(1); + _windowBounds.top = CONTROLS_Y1; + _key = _oldKey = -1; + _keyPress = '\0'; + _keyboardInput = false; + } else { + if (!talk._talkToAbort) { + screen._backBuffer1.fillRect(Common::Rect(6, CONTROLS_Y + 11 + (_selector - saves._savegameIndex) * 10, + SHERLOCK_SCREEN_WIDTH - 2, CONTROLS_Y + 20 + (_selector - saves._savegameIndex) * 10), INV_BACKGROUND); + screen.gPrint(Common::Point(6, CONTROLS_Y + 11 + (_selector - saves._savegameIndex) * 10), INV_FOREGROUND, + "%d.", _selector + 1); + screen.gPrint(Common::Point(24, CONTROLS_Y + 11 + (_selector - saves._savegameIndex) * 10), INV_FOREGROUND, + "%s", saves._savegames[_selector].c_str()); + + screen.slamArea(6, CONTROLS_Y + 11 + (_selector - saves._savegameIndex) * 10, 311, 10); + _selector = _oldSelector = -1; + } + } + } + } else if (((found == 3 && events._released) || _key == 'U') && saves._savegameIndex) { + bool moreKeys; + do { + saves._savegameIndex--; + screen._backBuffer1.fillRect(Common::Rect(3, CONTROLS_Y + 11, SHERLOCK_SCREEN_WIDTH - 2, + SHERLOCK_SCREEN_HEIGHT - 1), INV_BACKGROUND); + + for (int idx = saves._savegameIndex; idx < (saves._savegameIndex + ONSCREEN_FILES_COUNT); ++idx) { + color = INV_FOREGROUND; + if (idx == _selector && idx >= saves._savegameIndex && idx < (saves._savegameIndex + ONSCREEN_FILES_COUNT)) + color = TALK_FOREGROUND; + + screen.gPrint(Common::Point(6, CONTROLS_Y + 11 + (idx - saves._savegameIndex) * 10), color, "%d.", idx + 1); + screen.gPrint(Common::Point(24, CONTROLS_Y + 11 + (idx - saves._savegameIndex) * 10), color, "%s", saves._savegames[idx].c_str()); + } + + screen.slamRect(Common::Rect(3, CONTROLS_Y + 11, SHERLOCK_SCREEN_WIDTH - 2, SHERLOCK_SCREEN_HEIGHT)); + + color = !saves._savegameIndex ? COMMAND_NULL : COMMAND_FOREGROUND; + screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), color, true, "Up"); + color = (saves._savegameIndex == MAX_SAVEGAME_SLOTS - ONSCREEN_FILES_COUNT) ? COMMAND_NULL : COMMAND_FOREGROUND; + screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), color, true, "Down"); + + // Check whether there are more pending U keys pressed + moreKeys = false; + if (events.kbHit()) { + Common::KeyState keyState = events.getKey(); + + _key = toupper(keyState.keycode); + moreKeys = _key == 'U'; + } + } while ((saves._savegameIndex) && moreKeys); + } else if (((found == 4 && events._released) || _key == 'D') && saves._savegameIndex < (MAX_SAVEGAME_SLOTS - ONSCREEN_FILES_COUNT)) { + bool moreKeys; + do { + saves._savegameIndex++; + screen._backBuffer1.fillRect(Common::Rect(3, CONTROLS_Y + 11, SHERLOCK_SCREEN_WIDTH - 2, + SHERLOCK_SCREEN_HEIGHT - 1), INV_BACKGROUND); + + for (int idx = saves._savegameIndex; idx < (saves._savegameIndex + ONSCREEN_FILES_COUNT); ++idx) { + if (idx == _selector && idx >= saves._savegameIndex && idx < (saves._savegameIndex + ONSCREEN_FILES_COUNT)) + color = TALK_FOREGROUND; + else + color = INV_FOREGROUND; + + screen.gPrint(Common::Point(6, CONTROLS_Y + 11 + (idx - saves._savegameIndex) * 10), color, + "%d.", idx + 1); + screen.gPrint(Common::Point(24, CONTROLS_Y + 11 + (idx - saves._savegameIndex) * 10), color, + "%s", saves._savegames[idx].c_str()); + } + + screen.slamRect(Common::Rect(3, CONTROLS_Y + 11, SHERLOCK_SCREEN_WIDTH - 2, SHERLOCK_SCREEN_HEIGHT)); + + color = (!saves._savegameIndex) ? COMMAND_NULL : COMMAND_FOREGROUND; + screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), color, true, "Up"); + + color = (saves._savegameIndex == MAX_SAVEGAME_SLOTS - ONSCREEN_FILES_COUNT) ? COMMAND_NULL : COMMAND_FOREGROUND; + screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), color, true, "Down"); + + // Check whether there are more pending D keys pressed + moreKeys = false; + if (events.kbHit()) { + Common::KeyState keyState; + _key = toupper(keyState.keycode); + + moreKeys = _key == 'D'; + } + } while (saves._savegameIndex < (MAX_SAVEGAME_SLOTS - ONSCREEN_FILES_COUNT) && moreKeys); + } else if ((found == 5 && events._released) || _key == 'Q') { + clearWindow(); + screen.print(Common::Point(0, CONTROLS_Y + 20), INV_FOREGROUND, "Are you sure you wish to Quit ?"); + screen.vgaBar(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, CONTROLS_Y + 10), BORDER_COLOR); + + screen.makeButton(Common::Rect(112, CONTROLS_Y, 160, CONTROLS_Y + 10), 136 - screen.stringWidth("Yes") / 2, "Yes"); + screen.makeButton(Common::Rect(161, CONTROLS_Y, 209, CONTROLS_Y + 10), 184 - screen.stringWidth("No") / 2, "No"); + screen.slamArea(112, CONTROLS_Y, 97, 10); + + do { + scene.doBgAnim(); + + if (talk._talkToAbort) + return; + + events.pollEventsAndWait(); + events.setButtonState(); + mousePos = events.mousePos(); + + if (events.kbHit()) { + Common::KeyState keyState = events.getKey(); + _key = toupper(keyState.keycode); + + if (_key == 'X' && (keyState.flags & Common::KBD_ALT) != 0) { + _vm->quitGame(); + events.pollEvents(); + return; + } + + if (_key == Common::KEYCODE_ESCAPE) + _key = 'N'; + + if (_key == Common::KEYCODE_RETURN || _key == ' ') { + events._pressed = false; + events._released = true; + events._oldButtons = 0; + _keyPress = '\0'; + } + } + + if (events._pressed || events._released) { + if (mousePos.x > 112 && mousePos.x < 159 && mousePos.y > CONTROLS_Y && mousePos.y < (CONTROLS_Y + 9)) + color = COMMAND_HIGHLIGHTED; + else + color = COMMAND_FOREGROUND; + screen.buttonPrint(Common::Point(136, CONTROLS_Y), color, true, "Yes"); + + if (mousePos.x > 161 && mousePos.x < 208 && mousePos.y > CONTROLS_Y && mousePos.y < (CONTROLS_Y + 9)) + color = COMMAND_HIGHLIGHTED; + else + color = COMMAND_FOREGROUND; + screen.buttonPrint(Common::Point(184, CONTROLS_Y), color, true, "No"); + } + + if (mousePos.x > 112 && mousePos.x < 159 && mousePos.y > CONTROLS_Y && mousePos.y < (CONTROLS_Y + 9) && events._released) + _key = 'Y'; + + if (mousePos.x > 161 && mousePos.x < 208 && mousePos.y > CONTROLS_Y && mousePos.y < (CONTROLS_Y + 9) && events._released) + _key = 'N'; + } while (!_vm->shouldQuit() && _key != 'Y' && _key != 'N'); + + if (_key == 'Y') { + _vm->quitGame(); + events.pollEvents(); + return; + } else { + screen.buttonPrint(Common::Point(184, CONTROLS_Y), COMMAND_HIGHLIGHTED, true, "No"); + banishWindow(1); + _windowBounds.top = CONTROLS_Y1; + _key = -1; + } + } else { + if (_selector != -1) { + // Are we already in Load mode? + if (saves._envMode == SAVEMODE_LOAD) { + saves.loadGame(_selector + 1); + } else if (saves._envMode == SAVEMODE_SAVE || saves.isSlotEmpty(_selector)) { + // We're already in save mode, or pointing to an empty save slot + if (saves.checkGameOnScreen(_selector)) + _oldSelector = _selector; + + if (saves.promptForDescription(_selector)) { + saves.saveGame(_selector + 1, saves._savegames[_selector]); + banishWindow(); + _windowBounds.top = CONTROLS_Y1; + _key = _oldKey = -1; + _keyPress = '\0'; + _keyboardInput = false; + } else { + if (!talk._talkToAbort) { + screen._backBuffer1.fillRect(Common::Rect(6, CONTROLS_Y + 11 + (_selector - saves._savegameIndex) * 10, + 317, CONTROLS_Y + 20 + (_selector - saves._savegameIndex) * 10), INV_BACKGROUND); + screen.gPrint(Common::Point(6, CONTROLS_Y + 11 + (_selector - saves._savegameIndex) * 10), + INV_FOREGROUND, "%d.", _selector + 1); + screen.gPrint(Common::Point(24, CONTROLS_Y + 11 + (_selector - saves._savegameIndex) * 10), + INV_FOREGROUND, "%s", saves._savegames[_selector].c_str()); + screen.slamArea(6, CONTROLS_Y + 11 + (_selector - saves._savegameIndex) * 10, 311, 10); + _selector = _oldSelector = -1; + } + } + } + } + } + } +} + +void ScalpelUserInterface::doInvControl() { + Events &events = *_vm->_events; + FixedText &fixedText = *_vm->_fixedText; + Inventory &inv = *_vm->_inventory; + Scene &scene = *_vm->_scene; + Screen &screen = *_vm->_screen; + Talk &talk = *_vm->_talk; + int colors[8]; + Common::Point mousePos = events.mousePos(); + + _key = _oldKey = -1; + _keyboardInput = false; + + // Check whether any inventory slot is highlighted + int found = -1; + Common::fill(&colors[0], &colors[8], (int)COMMAND_FOREGROUND); + for (int idx = 0; idx < 8; ++idx) { + Common::Rect r(INVENTORY_POINTS[idx][0], CONTROLS_Y1, + INVENTORY_POINTS[idx][1], CONTROLS_Y1 + 10); + if (r.contains(mousePos)) { + found = idx; + break; + } + } + + if (events._pressed || events._released) { + events.clearKeyboard(); + + Common::String fixedText_Exit = fixedText.getText(kFixedText_Inventory_Exit); + Common::String fixedText_Look = fixedText.getText(kFixedText_Inventory_Look); + Common::String fixedText_Use = fixedText.getText(kFixedText_Inventory_Use); + Common::String fixedText_Give = fixedText.getText(kFixedText_Inventory_Give); + + if (found != -1) + // If a slot highlighted, set its color + colors[found] = COMMAND_HIGHLIGHTED; + screen.buttonPrint(Common::Point(INVENTORY_POINTS[0][2], CONTROLS_Y1), colors[0], true, fixedText_Exit); + + if (found >= 0 && found <= 3) { + screen.buttonPrint(Common::Point(INVENTORY_POINTS[1][2], CONTROLS_Y1), colors[1], true, fixedText_Look); + screen.buttonPrint(Common::Point(INVENTORY_POINTS[2][2], CONTROLS_Y1), colors[2], true, fixedText_Use); + screen.buttonPrint(Common::Point(INVENTORY_POINTS[3][2], CONTROLS_Y1), colors[3], true, fixedText_Give); + inv._invMode = (InvMode)found; + _selector = -1; + } + + if (inv._invIndex) { + screen.print(Common::Point(INVENTORY_POINTS[4][2], CONTROLS_Y1 + 1), colors[4], "^^"); + screen.print(Common::Point(INVENTORY_POINTS[5][2], CONTROLS_Y1 + 1), colors[5], "^"); + } + + if ((inv._holdings - inv._invIndex) > 6) { + screen.print(Common::Point(INVENTORY_POINTS[6][2], CONTROLS_Y1 + 1), colors[6], "_"); + screen.print(Common::Point(INVENTORY_POINTS[7][2], CONTROLS_Y1 + 1), colors[7], "__"); + } + + bool flag = false; + if (inv._invMode == INVMODE_LOOK || inv._invMode == INVMODE_USE || inv._invMode == INVMODE_GIVE) { + Common::Rect r(15, CONTROLS_Y1 + 11, 314, SHERLOCK_SCREEN_HEIGHT - 2); + if (r.contains(mousePos)) { + _selector = (mousePos.x - 6) / 52 + inv._invIndex; + if (_selector < inv._holdings) + flag = true; + } + } + + if (!flag && mousePos.y >(CONTROLS_Y1 + 11)) + _selector = -1; + } + + if (_keyPress) { + _key = toupper(_keyPress); + + if (_key == Common::KEYCODE_ESCAPE) + // Escape will also 'E'xit out of inventory display + _key = 'E'; + + if (_key == 'E' || _key == 'L' || _key == 'U' || _key == 'G' + || _key == '-' || _key == '+') { + InvMode temp = inv._invMode; + + const char *chP = strchr(INVENTORY_COMMANDS, _key); + inv._invMode = !chP ? INVMODE_INVALID : (InvMode)(chP - INVENTORY_COMMANDS); + inv.invCommands(true); + + inv._invMode = temp; + _keyboardInput = true; + if (_key == 'E') + inv._invMode = INVMODE_EXIT; + _selector = -1; + } else { + _selector = -1; + } + } + + if (_selector != _oldSelector) { + if (_oldSelector != -1) { + // Un-highlight + if (_oldSelector >= inv._invIndex && _oldSelector < (inv._invIndex + 6)) + inv.highlight(_oldSelector, BUTTON_MIDDLE); + } + + if (_selector != -1) + inv.highlight(_selector, BUTTON_BACKGROUND); + + _oldSelector = _selector; + } + + if (events._released || _keyboardInput) { + if ((found == 0 && events._released) || _key == 'E') { + inv.freeInv(); + _infoFlag = true; + clearInfo(); + banishWindow(false); + _key = -1; + events.clearEvents(); + events.setCursor(ARROW); + } else if ((found == 1 && events._released) || (_key == 'L')) { + inv._invMode = INVMODE_LOOK; + } else if ((found == 2 && events._released) || (_key == 'U')) { + inv._invMode = INVMODE_USE; + } else if ((found == 3 && events._released) || (_key == 'G')) { + inv._invMode = INVMODE_GIVE; + } else if (((found == 4 && events._released) || _key == ',') && inv._invIndex) { + if (inv._invIndex >= 6) + inv._invIndex -= 6; + else + inv._invIndex = 0; + + screen.print(Common::Point(INVENTORY_POINTS[4][2], CONTROLS_Y1 + 1), + COMMAND_HIGHLIGHTED, "^^"); + inv.freeGraphics(); + inv.loadGraphics(); + inv.putInv(SLAM_DISPLAY); + inv.invCommands(true); + } else if (((found == 5 && events._released) || _key == '-') && inv._invIndex > 0) { + --inv._invIndex; + screen.print(Common::Point(INVENTORY_POINTS[4][2], CONTROLS_Y1 + 1), COMMAND_HIGHLIGHTED, "^"); + inv.freeGraphics(); + inv.loadGraphics(); + inv.putInv(SLAM_DISPLAY); + inv.invCommands(true); + } else if (((found == 6 && events._released) || _key == '+') && (inv._holdings - inv._invIndex) > 6) { + ++inv._invIndex; + screen.print(Common::Point(INVENTORY_POINTS[6][2], CONTROLS_Y1 + 1), COMMAND_HIGHLIGHTED, "_"); + inv.freeGraphics(); + inv.loadGraphics(); + inv.putInv(SLAM_DISPLAY); + inv.invCommands(true); + } else if (((found == 7 && events._released) || _key == '.') && (inv._holdings - inv._invIndex) > 6) { + inv._invIndex += 6; + if ((inv._holdings - 6) < inv._invIndex) + inv._invIndex = inv._holdings - 6; + + screen.print(Common::Point(INVENTORY_POINTS[7][2], CONTROLS_Y1 + 1), COMMAND_HIGHLIGHTED, "_"); + inv.freeGraphics(); + inv.loadGraphics(); + inv.putInv(SLAM_DISPLAY); + inv.invCommands(true); + } else { + // If something is being given, make sure it's being given to a person + if (inv._invMode == INVMODE_GIVE) { + if (_bgFound != -1 && scene._bgShapes[_bgFound]._aType == PERSON) + _find = _bgFound; + else + _find = -1; + } else { + _find = _bgFound; + } + + if ((mousePos.y < CONTROLS_Y1) && (inv._invMode == INVMODE_LOOK) && (_find >= 0) && (_find < 1000)) { + if (!scene._bgShapes[_find]._examine.empty() && + scene._bgShapes[_find]._examine[0] >= ' ') + inv.refreshInv(); + } else if (_selector != -1 || _find >= 0) { + // Selector is the inventory object that was clicked on, or selected. + // If it's -1, then no inventory item is highlighted yet. Otherwise, + // an object in the scene has been clicked. + + if (_selector != -1 && inv._invMode == INVMODE_LOOK + && mousePos.y >(CONTROLS_Y1 + 11)) + inv.refreshInv(); + + if (talk._talkToAbort) + return; + + // Now check for the Use and Give actions. If inv_mode is INVMODE_GIVE, + // that means GIVE is in effect, _selector is the object being + // given, and _find is the target. + // The same applies to USE, except if _selector is -1, then USE + // is being tried on an object in the scene without an inventory + // object being highlighted first. + + if ((inv._invMode == INVMODE_USE || (_selector != -1 && inv._invMode == INVMODE_GIVE)) && _find >= 0) { + events._pressed = events._released = false; + _infoFlag = true; + clearInfo(); + + int tempSel = _selector; // Save the selector + _selector = -1; + + inv.putInv(SLAM_DISPLAY); + _selector = tempSel; // Restore it + InvMode tempMode = inv._invMode; + inv._invMode = INVMODE_USE55; + inv.invCommands(true); + + _infoFlag = true; + clearInfo(); + banishWindow(false); + _key = -1; + + inv.freeInv(); + + bool giveFl = (tempMode >= INVMODE_GIVE); + if (_selector >= 0) + // Use/Give inv object with scene object + checkUseAction(&scene._bgShapes[_find]._use[0], inv[_selector]._name, kFixedTextAction_Use, _find, giveFl); + else + // Now inv object has been highlighted + checkUseAction(&scene._bgShapes[_find]._use[0], "*SELF*", kFixedTextAction_Use, _find, giveFl); + + _selector = _oldSelector = -1; + } + } + } + } +} + +void ScalpelUserInterface::doLookControl() { + Events &events = *_vm->_events; + Inventory &inv = *_vm->_inventory; + Screen &screen = *_vm->_screen; + + _key = _oldKey = -1; + _keyboardInput = (_keyPress != '\0'); + + if (events._released || events._rightReleased || _keyboardInput) { + // Is an inventory object being looked at? + if (!_invLookFlag) { + // Is there any remaining text to display? + if (!_descStr.empty()) { + printObjectDesc(_descStr, false); + } else if (!_lookHelp) { + // Need to close the window and depress the Look button + Common::Point pt(MENU_POINTS[0][0], MENU_POINTS[0][1]); + offsetButton3DO(pt, 0); + screen._backBuffer2.blitFrom((*_controls)[0], pt); + banishWindow(true); + + _windowBounds.top = CONTROLS_Y1; + _key = _oldKey = COMMANDS[LOOK_MODE - 1]; + _temp = _oldTemp = 0; + _menuMode = LOOK_MODE; + events.clearEvents(); + + // Restore UI + drawInterface(); + } else { + events.setCursor(ARROW); + banishWindow(true); + _windowBounds.top = CONTROLS_Y1; + _key = _oldKey = -1; + _temp = _oldTemp = 0; + _menuMode = STD_MODE; + events.clearEvents(); + } + } else { + // Looking at an inventory object + // Backup the user interface + Surface tempSurface(SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT - CONTROLS_Y1); + tempSurface.blitFrom(screen._backBuffer2, Common::Point(0, 0), + Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); + + inv.drawInventory(INVENTORY_DONT_DISPLAY); + banishWindow(true); + + // Restore the ui + screen._backBuffer2.blitFrom(tempSurface, Common::Point(0, CONTROLS_Y1)); + + _windowBounds.top = CONTROLS_Y1; + _key = _oldKey = COMMANDS[LOOK_MODE - 1]; + _temp = _oldTemp = 0; + events.clearEvents(); + _invLookFlag = false; + _menuMode = INV_MODE; + _windowOpen = true; + } + } +} + +void ScalpelUserInterface::doMainControl() { + Events &events = *_vm->_events; + Inventory &inv = *_vm->_inventory; + SaveManager &saves = *_vm->_saves; + Common::Point pt = events.mousePos(); + + if ((events._pressed || events._released) && pt.y > CONTROLS_Y) { + events.clearKeyboard(); + _key = -1; + + // Check whether the mouse is in any of the command areas + for (_temp = 0; (_temp < 12) && (_key == -1); ++_temp) { + Common::Rect r(MENU_POINTS[_temp][0], MENU_POINTS[_temp][1], + MENU_POINTS[_temp][2], MENU_POINTS[_temp][3]); + if (IS_3DO && _temp >= 0 && _temp <= 2) { + r.left += UI_OFFSET_3DO - 1; + r.right += UI_OFFSET_3DO - 1; + } + if (r.contains(pt)) + _key = COMMANDS[_temp]; + } + --_temp; + } else if (_keyPress) { + // Keyboard control + _keyboardInput = true; + + if (_keyPress >= 'A' && _keyPress <= 'Z') { + const char *c = strchr(COMMANDS, _keyPress); + _temp = !c ? 12 : c - COMMANDS; + } else { + _temp = 12; + } + + if (_temp == 12) + _key = -1; + + if (events._rightPressed) { + _temp = 12; + _key = -1; + } + } else if (!events._released) { + _key = -1; + } + + // Check if the button being pointed to has changed + if (_oldKey != _key && !_windowOpen) { + // Clear the info line + _infoFlag = true; + clearInfo(); + + // If there was an old button selected, restore it + if (_oldKey != -1) { + _menuMode = STD_MODE; + restoreButton(_oldTemp); + } + + // If a new button is being pointed to, highlight it + if (_key != -1 && _temp < 12 && !_keyboardInput) + depressButton(_temp); + + // Save the new button selection + _oldKey = _key; + _oldTemp = _temp; + } + + if (!events._pressed && !_windowOpen) { + switch (_key) { + case 'L': + toggleButton(0); + break; + case 'M': + toggleButton(1); + break; + case 'T': + toggleButton(2); + break; + case 'P': + toggleButton(3); + break; + case 'O': + toggleButton(4); + break; + case 'C': + toggleButton(5); + break; + case 'I': + pushButton(6); + _selector = _oldSelector = -1; + _menuMode = INV_MODE; + inv.drawInventory(PLAIN_INVENTORY); + break; + case 'U': + pushButton(7); + _selector = _oldSelector = -1; + _menuMode = USE_MODE; + inv.drawInventory(USE_INVENTORY_MODE); + break; + case 'G': + pushButton(8); + _selector = _oldSelector = -1; + _menuMode = GIVE_MODE; + inv.drawInventory(GIVE_INVENTORY_MODE); + break; + case 'J': + pushButton(9); + _menuMode = JOURNAL_MODE; + journalControl(); + break; + case 'F': + pushButton(10); + + // Create a thumbnail of the current screen before the files dialog is shown, in case + // the user saves the game + saves.createThumbnail(); + + _selector = _oldSelector = -1; + + if (_vm->_showOriginalSavesDialog) { + // Show the original dialog + _menuMode = FILES_MODE; + saves.drawInterface(); + _windowOpen = true; + } else { + // Show the ScummVM GMM instead + _vm->_canLoadSave = true; + _vm->openMainMenuDialog(); + _vm->_canLoadSave = false; + } + break; + case 'S': + pushButton(11); + _menuMode = SETUP_MODE; + Settings::show(_vm); + break; + default: + break; + } + + _help = _oldHelp = _oldBgFound = -1; + } +} + +void ScalpelUserInterface::doMiscControl(int allowed) { + Events &events = *_vm->_events; + Scene &scene = *_vm->_scene; + Talk &talk = *_vm->_talk; + + if (events._released) { + _temp = _bgFound; + if (_bgFound != -1) { + // Only allow pointing to objects, not people + if (_bgFound < 1000) { + events.clearEvents(); + Object &obj = scene._bgShapes[_bgFound]; + + switch (allowed) { + case ALLOW_OPEN: + checkAction(obj._aOpen, _temp, kFixedTextAction_Open); + if (_menuMode != TALK_MODE && !talk._talkToAbort) { + _menuMode = STD_MODE; + restoreButton(OPEN_MODE - 1); + _key = _oldKey = -1; + } + break; + + case ALLOW_CLOSE: + checkAction(obj._aClose, _temp, kFixedTextAction_Close); + if (_menuMode != TALK_MODE && !talk._talkToAbort) { + _menuMode = STD_MODE; + restoreButton(CLOSE_MODE - 1); + _key = _oldKey = -1; + } + break; + + case ALLOW_MOVE: + checkAction(obj._aMove, _temp, kFixedTextAction_Move); + if (_menuMode != TALK_MODE && !talk._talkToAbort) { + _menuMode = STD_MODE; + restoreButton(MOVE_MODE - 1); + _key = _oldKey = -1; + } + break; + + default: + break; + } + } + } + } +} + +void ScalpelUserInterface::doPickControl() { + Events &events = *_vm->_events; + Scene &scene = *_vm->_scene; + Talk &talk = *_vm->_talk; + + if (events._released) { + if ((_temp = _bgFound) != -1) { + events.clearEvents(); + + // Don't allow characters to be picked up + if (_bgFound < 1000) { + scene._bgShapes[_bgFound].pickUpObject(kFixedTextAction_Pick); + + if (!talk._talkToAbort && _menuMode != TALK_MODE) { + _key = _oldKey = -1; + _menuMode = STD_MODE; + restoreButton(PICKUP_MODE - 1); + } + } + } + } +} + +void ScalpelUserInterface::doTalkControl() { + Events &events = *_vm->_events; + FixedText &fixedText = *_vm->_fixedText; + ScalpelJournal &journal = *(ScalpelJournal *)_vm->_journal; + ScalpelPeople &people = *(ScalpelPeople *)_vm->_people; + Screen &screen = *_vm->_screen; + Sound &sound = *_vm->_sound; + Talk &talk = *_vm->_talk; + Common::Point mousePos = events.mousePos(); + + _key = _oldKey = -1; + _keyboardInput = false; + + Common::String fixedText_Exit = fixedText.getText(kFixedText_Window_Exit); + Common::String fixedText_Up = fixedText.getText(kFixedText_Window_Up); + Common::String fixedText_Down = fixedText.getText(kFixedText_Window_Down); + + if (events._pressed || events._released) { + events.clearKeyboard(); + + // Handle button printing + if (mousePos.x > 99 && mousePos.x < 138 && mousePos.y > CONTROLS_Y && mousePos.y < (CONTROLS_Y + 10) && !_endKeyActive) + screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_HIGHLIGHTED, true, fixedText_Exit); + else if (_endKeyActive) + screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_FOREGROUND, true, fixedText_Exit); + + if (mousePos.x > 140 && mousePos.x < 170 && mousePos.y > CONTROLS_Y && mousePos.y < (CONTROLS_Y + 10) && talk._moreTalkUp) + screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_HIGHLIGHTED, true, fixedText_Up); + else if (talk._moreTalkUp) + screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_FOREGROUND, true, fixedText_Up); + + if (mousePos.x > 181&& mousePos.x < 220 && mousePos.y > CONTROLS_Y && mousePos.y < (CONTROLS_Y + 10) && talk._moreTalkDown) + screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_HIGHLIGHTED, true, fixedText_Down); + else if (talk._moreTalkDown) + screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_FOREGROUND, true, fixedText_Down); + + bool found = false; + for (_selector = talk._talkIndex; _selector < (int)talk._statements.size() && !found; ++_selector) { + if (mousePos.y > talk._statements[_selector]._talkPos.top && + mousePos.y < talk._statements[_selector]._talkPos.bottom) + found = true; + } + --_selector; + if (!found) + _selector = -1; + } + + if (_keyPress) { + _key = toupper(_keyPress); + if (_key == Common::KEYCODE_ESCAPE) + _key = 'E'; + + // Check for number press indicating reply line + if (_key >= '1' && _key <= ('1' + (int)talk._statements.size() - 1)) { + for (uint idx = 0; idx < talk._statements.size(); ++idx) { + if (talk._statements[idx]._talkMap == (_key - '1')) { + // Found the given statement + _selector = idx; + _key = -1; + _keyboardInput = true; + break; + } + } + } else if (_key == 'E' || _key == 'U' || _key == 'D') { + _keyboardInput = true; + } else { + _selector = -1; + } + } + + if (_selector != _oldSelector) { + // Remove highlighting from previous line, if any + if (_oldSelector != -1) { + if (!((talk._talkHistory[talk._converseNum][_oldSelector] >> (_oldSelector & 7)) & 1)) + talk.talkLine(_oldSelector, talk._statements[_oldSelector]._talkMap, INV_FOREGROUND, + talk._statements[_oldSelector]._talkPos.top, true); + else + talk.talkLine(_oldSelector, talk._statements[_oldSelector]._talkMap, TALK_NULL, + talk._statements[_oldSelector]._talkPos.top, true); + } + + // Add highlighting to new line, if any + if (_selector != -1) + talk.talkLine(_selector, talk._statements[_selector]._talkMap, TALK_FOREGROUND, + talk._statements[_selector]._talkPos.top, true); + + _oldSelector = _selector; + } + + if (events._released || _keyboardInput) { + if (((Common::Rect(99, CONTROLS_Y, 138, CONTROLS_Y + 10).contains(mousePos) && events._released) + || _key == 'E') && _endKeyActive) { + talk.freeTalkVars(); + talk.pullSequence(); + + drawInterface(2); + banishWindow(); + _windowBounds.top = CONTROLS_Y1; + } else if (((Common::Rect(140, CONTROLS_Y, 179, CONTROLS_Y + 10).contains(mousePos) && events._released) + || _key == 'U') && talk._moreTalkUp) { + while (talk._statements[--talk._talkIndex]._talkMap == -1) + ; + screen._backBuffer1.fillRect(Common::Rect(5, CONTROLS_Y + 11, SHERLOCK_SCREEN_WIDTH - 2, + SHERLOCK_SCREEN_HEIGHT - 1), INV_BACKGROUND); + talk.displayTalk(false); + + screen.slamRect(Common::Rect(5, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH - 5, SHERLOCK_SCREEN_HEIGHT - 2)); + } else if (((Common::Rect(181, CONTROLS_Y, 220, CONTROLS_Y + 10).contains(mousePos) && events._released) + || _key == 'D') && talk._moreTalkDown) { + do { + ++talk._talkIndex; + } while (talk._talkIndex < (int)talk._statements.size() && talk._statements[talk._talkIndex]._talkMap == -1); + + screen._backBuffer1.fillRect(Common::Rect(5, CONTROLS_Y + 11, SHERLOCK_SCREEN_WIDTH - 2, + SHERLOCK_SCREEN_HEIGHT - 1), INV_BACKGROUND); + talk.displayTalk(false); + + screen.slamRect(Common::Rect(5, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH - 5, SHERLOCK_SCREEN_HEIGHT - 2)); + } else if (_selector != -1) { + screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_NULL, true, fixedText_Exit); + screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_NULL, true, fixedText_Up); + screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_NULL, true, fixedText_Down); + + // If the reply is new, add it to the journal + if (!talk._talkHistory[talk._converseNum][_selector]) { + journal.record(talk._converseNum, _selector); + + // Add any Holmes point to Holmes' total, if any + if (talk._statements[_selector]._quotient) + people._holmesQuotient += talk._statements[_selector]._quotient; + } + + // Flag the response as having been used + talk._talkHistory[talk._converseNum][_selector] = true; + + clearWindow(); + screen.print(Common::Point(16, CONTROLS_Y + 12), TALK_FOREGROUND, "Sherlock Holmes"); + talk.talkLine(_selector + 128, talk._statements[_selector]._talkMap, COMMAND_FOREGROUND, CONTROLS_Y + 21, true); + + switch (talk._statements[_selector]._portraitSide & 3) { + case 0: + case 1: + people._portraitSide = 20; + break; + case 2: + people._portraitSide = 220; + break; + case 3: + people._portraitSide = 120; + break; + } + + // Check for flipping Holmes + if (talk._statements[_selector]._portraitSide & REVERSE_DIRECTION) + people._holmesFlip = true; + + talk._speaker = 0; + people.setTalking(0); + + if (!talk._statements[_selector]._voiceFile.empty() && sound._voices) { + sound.playSound(talk._statements[_selector]._voiceFile, WAIT_RETURN_IMMEDIATELY); + + // Set voices as an indicator for waiting + sound._voices = 2; + sound._speechOn = *sound._soundIsOn; + } else { + sound._speechOn = false; + } + + // Trigger to play 3DO movie + talk.talk3DOMovieTrigger(0); + + talk.waitForMore(talk._statements[_selector]._statement.size()); + if (talk._talkToAbort) + return; + + people.clearTalking(); + if (talk._talkToAbort) + return; + + while (!_vm->shouldQuit()) { + talk._scriptSelect = _selector; + talk._speaker = talk._talkTo; + talk.doScript(talk._statements[_selector]._reply); + + if (!talk._talkToAbort) { + if (!talk._talkStealth) + clearWindow(); + + if (!talk._statements[_selector]._modified.empty()) { + for (uint idx = 0; idx < talk._statements[_selector]._modified.size(); ++idx) { + _vm->setFlags(talk._statements[_selector]._modified[idx]); + } + + talk.setTalkMap(); + } + + // Check for another linked talk file + Common::String linkFilename = talk._statements[_selector]._linkFile; + if (!linkFilename.empty() && !talk._scriptMoreFlag) { + talk.freeTalkVars(); + talk.loadTalkFile(linkFilename); + + // Find the first new statement + int select = _selector = _oldSelector = -1; + for (uint idx = 0; idx < talk._statements.size() && select == -1; ++idx) { + if (!talk._statements[idx]._talkMap) + select = talk._talkIndex = idx; + } + + // See if the new statement is a stealth reply + talk._talkStealth = talk._statements[select]._statement.hasPrefix("^") ? 2 : 0; + + // Is the new talk file a standard file, reply first file, or a stealth file + if (!talk._statements[select]._statement.hasPrefix("*") && + !talk._statements[select]._statement.hasPrefix("^")) { + // Not a reply first file, so display the new selections + if (_endKeyActive) + screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_FOREGROUND, true, fixedText_Exit); + else + screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_NULL, true, fixedText_Exit); + + talk.displayTalk(true); + events.setCursor(ARROW); + break; + } else { + _selector = select; + + if (!talk._talkHistory[talk._converseNum][_selector]) + journal.record(talk._converseNum, _selector); + + talk._talkHistory[talk._converseNum][_selector] = true; + } + } else { + talk.freeTalkVars(); + talk.pullSequence(); + banishWindow(); + _windowBounds.top = CONTROLS_Y1; + break; + } + } else { + break; + } + } + + events._pressed = events._released = false; + events._oldButtons = 0; + talk._talkStealth = 0; + + // If a script was pushed onto the script stack, restore it + if (!talk._scriptStack.empty()) { + ScriptStackEntry stackEntry = talk._scriptStack.pop(); + talk._scriptName = stackEntry._name; + talk._scriptSaveIndex = stackEntry._currentIndex; + talk._scriptSelect = stackEntry._select; + } + } + } +} + +void ScalpelUserInterface::journalControl() { + Events &events = *_vm->_events; + ScalpelJournal &journal = *(ScalpelJournal *)_vm->_journal; + Scene &scene = *_vm->_scene; + Screen &screen = *_vm->_screen; + bool doneFlag = false; + + // Draw the journal screen + journal.drawInterface(); + + // Handle journal events + do { + _key = -1; + events.setButtonState(); + + // Handle keypresses + if (events.kbHit()) { + Common::KeyState keyState = events.getKey(); + if (keyState.keycode == Common::KEYCODE_x && (keyState.flags & Common::KBD_ALT)) { + _vm->quitGame(); + return; + } else if (keyState.keycode == Common::KEYCODE_e || keyState.keycode == Common::KEYCODE_ESCAPE) { + doneFlag = true; + } else { + _key = toupper(keyState.keycode); + } + } + + if (!doneFlag) + doneFlag = journal.handleEvents(_key); + } while (!_vm->shouldQuit() && !doneFlag); + + // Finish up + _infoFlag = _keyboardInput = false; + _keyPress = '\0'; + _windowOpen = false; + _windowBounds.top = CONTROLS_Y1; + _key = -1; + _menuMode = STD_MODE; + + // Reset the palette + screen.setPalette(screen._cMap); + + screen._backBuffer1.blitFrom(screen._backBuffer2); + scene.updateBackground(); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); +} + +void ScalpelUserInterface::printObjectDesc(const Common::String &str, bool firstTime) { + Events &events = *_vm->_events; + Inventory &inv = *_vm->_inventory; + Screen &screen = *_vm->_screen; + Talk &talk = *_vm->_talk; + + if (str.hasPrefix("_")) { + _lookScriptFlag = true; + events.setCursor(MAGNIFY); + int savedSelector = _selector; + talk.talkTo(str.c_str() + 1); + _lookScriptFlag = false; + + if (talk._talkToAbort) { + events.setCursor(ARROW); + return; + } + + // Check if looking at an inventory object + if (!_invLookFlag) { + // See if this look was called by a right button click or not + if (!_lookHelp) { + // If it wasn't a right button click, then we need depress + // the look button before we close the window. So save a copy of the + // menu area, and draw the controls onto it + Surface tempSurface((*_controls)[0]._frame.w, (*_controls)[0]._frame.h); + Common::Point pt(MENU_POINTS[0][0], MENU_POINTS[0][1]); + offsetButton3DO(pt, 0); + + tempSurface.blitFrom(screen._backBuffer2, Common::Point(0, 0), + Common::Rect(pt.x, pt.y, pt.x + tempSurface.w(), pt.y + tempSurface.h())); + screen._backBuffer2.transBlitFrom((*_controls)[0], pt); + + banishWindow(1); + events.setCursor(MAGNIFY); + _windowBounds.top = CONTROLS_Y1; + _key = _oldKey = COMMANDS[LOOK_MODE - 1]; + _temp = _oldTemp = 0; + _menuMode = LOOK_MODE; + events.clearEvents(); + + screen._backBuffer2.blitFrom(tempSurface, pt); + } else { + events.setCursor(ARROW); + banishWindow(true); + _windowBounds.top = CONTROLS_Y1; + _key = _oldKey = -1; + _temp = _oldTemp = 0; + _menuMode = STD_MODE; + _lookHelp = 0; + events.clearEvents(); + } + } else { + // Looking at an inventory object + _selector = _oldSelector = savedSelector; + + // Reload the inventory graphics and draw the inventory + inv.loadInv(); + inv.putInv(SLAM_SECONDARY_BUFFER); + inv.freeInv(); + banishWindow(1); + + _windowBounds.top = CONTROLS_Y1; + _key = _oldKey = COMMANDS[INV_MODE - 1]; + _temp = _oldTemp = 0; + events.clearEvents(); + + _invLookFlag = 0; + _menuMode = INV_MODE; + _windowOpen = true; + } + + return; + } + + Surface &bb = *screen._backBuffer; + if (firstTime) { + // Only draw the border on the first call + _infoFlag = true; + clearInfo(); + + bb.fillRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, + CONTROLS_Y1 + 10), BORDER_COLOR); + bb.fillRect(Common::Rect(0, CONTROLS_Y + 10, 1, SHERLOCK_SCREEN_HEIGHT - 1), + BORDER_COLOR); + bb.fillRect(Common::Rect(SHERLOCK_SCREEN_WIDTH - 2, CONTROLS_Y + 10, + SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR); + bb.fillRect(Common::Rect(0, SHERLOCK_SCREEN_HEIGHT - 1, SHERLOCK_SCREEN_WIDTH, + SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR); + } + + // Clear background + bb.fillRect(Common::Rect(2, CONTROLS_Y + 10, SHERLOCK_SCREEN_WIDTH - 2, + SHERLOCK_SCREEN_HEIGHT - 2), INV_BACKGROUND); + + _windowBounds.top = CONTROLS_Y; + events.clearEvents(); + + // Loop through displaying up to five lines + bool endOfStr = false; + const char *msgP = str.c_str(); + for (int lineNum = 0; lineNum < ONSCREEN_FILES_COUNT && !endOfStr; ++lineNum) { + int width = 0; + const char *lineStartP = msgP; + + // Determine how much can be displayed on the line + do { + width += screen.charWidth(*msgP++); + } while (width < 300 && *msgP); + + if (*msgP) + --msgP; + else + endOfStr = true; + + // If the line needs to be wrapped, scan backwards to find + // the end of the previous word as a splitting point + if (width >= 300) { + while (*msgP != ' ') + --msgP; + endOfStr = false; + } + + // Print out the line + Common::String line(lineStartP, msgP); + screen.gPrint(Common::Point(16, CONTROLS_Y + 12 + lineNum * 9), + INV_FOREGROUND, "%s", line.c_str()); + + if (!endOfStr) + // Start next line at start of the nxet word after space + ++msgP; + } + + // Handle display depending on whether all the message was shown + if (!endOfStr) { + screen.makeButton(Common::Rect(46, CONTROLS_Y, 272, CONTROLS_Y + 10), + (SHERLOCK_SCREEN_WIDTH - screen.stringWidth(PRESS_KEY_FOR_MORE)) / 2, + PRESS_KEY_FOR_MORE); + screen.gPrint(Common::Point((SHERLOCK_SCREEN_WIDTH - + screen.stringWidth(PRESS_KEY_FOR_MORE)) / 2, CONTROLS_Y), + COMMAND_FOREGROUND, "P"); + _descStr = msgP; + } else { + screen.makeButton(Common::Rect(46, CONTROLS_Y, 272, CONTROLS_Y + 10), + (SHERLOCK_SCREEN_WIDTH - screen.stringWidth(PRESS_KEY_TO_CONTINUE)) / 2, + PRESS_KEY_TO_CONTINUE); + screen.gPrint(Common::Point((SHERLOCK_SCREEN_WIDTH - + screen.stringWidth(PRESS_KEY_TO_CONTINUE)) / 2, CONTROLS_Y), + COMMAND_FOREGROUND, "P"); + _descStr = ""; + } + + if (firstTime) { + if (!_slideWindows) { + screen.slamRect(Common::Rect(0, CONTROLS_Y, + SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); + } else { + // Display the window + summonWindow(); + } + + _selector = _oldSelector = -1; + _windowOpen = true; + } else { + screen.slamRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, + SHERLOCK_SCREEN_HEIGHT)); + } +} + +void ScalpelUserInterface::printObjectDesc() { + printObjectDesc(_cAnimStr, true); +} + +void ScalpelUserInterface::summonWindow(const Surface &bgSurface, bool slideUp) { + Events &events = *_vm->_events; + Screen &screen = *_vm->_screen; + + if (_windowOpen) + // A window is already open, so can't open another one + return; + + if (slideUp) { + // Gradually slide up the display of the window + for (int idx = 1; idx <= bgSurface.h(); idx += 2) { + screen._backBuffer->blitFrom(bgSurface, Common::Point(0, SHERLOCK_SCREEN_HEIGHT - idx), + Common::Rect(0, 0, bgSurface.w(), idx)); + screen.slamRect(Common::Rect(0, SHERLOCK_SCREEN_HEIGHT - idx, + SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); + + events.delay(10); + } + } else { + // Gradually slide down the display of the window + for (int idx = 1; idx <= bgSurface.h(); idx += 2) { + screen._backBuffer->blitFrom(bgSurface, + Common::Point(0, SHERLOCK_SCREEN_HEIGHT - bgSurface.h()), + Common::Rect(0, bgSurface.h() - idx, bgSurface.w(), bgSurface.h())); + screen.slamRect(Common::Rect(0, SHERLOCK_SCREEN_HEIGHT - bgSurface.h(), + SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT - bgSurface.h() + idx)); + + events.delay(10); + } + } + + // Final display of the entire window + screen._backBuffer->blitFrom(bgSurface, Common::Point(0, SHERLOCK_SCREEN_HEIGHT - bgSurface.h()), + Common::Rect(0, 0, bgSurface.w(), bgSurface.h())); + screen.slamArea(0, SHERLOCK_SCREEN_HEIGHT - bgSurface.h(), bgSurface.w(), bgSurface.h()); + + _windowOpen = true; +} + +void ScalpelUserInterface::summonWindow(bool slideUp, int height) { + Screen &screen = *_vm->_screen; + + // Extract the window that's been drawn on the back buffer + Surface tempSurface(SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT - height); + Common::Rect r(0, height, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + tempSurface.blitFrom(screen._backBuffer1, Common::Point(0, 0), r); + + // Remove drawn window with original user interface + screen._backBuffer1.blitFrom(screen._backBuffer2, + Common::Point(0, height), r); + + // Display the window gradually on-screen + summonWindow(tempSurface, slideUp); +} + +void ScalpelUserInterface::banishWindow(bool slideUp) { + Events &events = *_vm->_events; + Screen &screen = *_vm->_screen; + + if (_windowOpen) { + if (slideUp || !_slideWindows) { + // Slide window down + // Only slide the window if the window style allows it + if (_slideWindows) { + for (int idx = 2; idx < (SHERLOCK_SCREEN_HEIGHT - CONTROLS_Y); idx += 2) { + // Shift the window down by 2 lines + byte *pSrc = (byte *)screen._backBuffer1.getBasePtr(0, CONTROLS_Y + idx - 2); + byte *pSrcEnd = (byte *)screen._backBuffer1.getBasePtr(0, SHERLOCK_SCREEN_HEIGHT - 2); + byte *pDest = (byte *)screen._backBuffer1.getBasePtr(0, SHERLOCK_SCREEN_HEIGHT); + Common::copy_backward(pSrc, pSrcEnd, pDest); + + // Restore lines from the ui in the secondary back buffer + screen._backBuffer1.blitFrom(screen._backBuffer2, + Common::Point(0, CONTROLS_Y), + Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, CONTROLS_Y + idx)); + + screen.slamArea(0, CONTROLS_Y + idx - 2, SHERLOCK_SCREEN_WIDTH, + SHERLOCK_SCREEN_HEIGHT - CONTROLS_Y - idx + 2); + events.delay(10); + } + + // Restore final two old lines + screen._backBuffer1.blitFrom(screen._backBuffer2, + Common::Point(0, SHERLOCK_SCREEN_HEIGHT - 2), + Common::Rect(0, SHERLOCK_SCREEN_HEIGHT - 2, + SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); + screen.slamArea(0, SHERLOCK_SCREEN_HEIGHT - 2, SHERLOCK_SCREEN_WIDTH, 2); + } else { + // Restore old area to completely erase window + screen._backBuffer1.blitFrom(screen._backBuffer2, + Common::Point(0, CONTROLS_Y), + Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); + screen.slamRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, + SHERLOCK_SCREEN_HEIGHT)); + } + } else { + // Slide the original user interface up to cover the dialog + for (int idx = 1; idx < (SHERLOCK_SCREEN_HEIGHT - CONTROLS_Y1); idx += 2) { + byte *pSrc = (byte *)screen._backBuffer2.getBasePtr(0, CONTROLS_Y1); + byte *pSrcEnd = (byte *)screen._backBuffer2.getBasePtr(0, CONTROLS_Y1 + idx); + byte *pDest = (byte *)screen._backBuffer1.getBasePtr(0, SHERLOCK_SCREEN_HEIGHT - idx); + Common::copy(pSrc, pSrcEnd, pDest); + + screen.slamArea(0, SHERLOCK_SCREEN_HEIGHT - idx, SHERLOCK_SCREEN_WIDTH, + SHERLOCK_SCREEN_HEIGHT); + events.delay(10); + } + + // Show entire final area + screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(0, CONTROLS_Y1), + Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); + screen.slamRect(Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); + } + + _infoFlag = false; + _windowOpen = false; + } + + _menuMode = STD_MODE; +} + +void ScalpelUserInterface::checkUseAction(const UseType *use, const Common::String &invName, + FixedTextActionId fixedTextActionId, int objNum, bool giveMode) { + Events &events = *_vm->_events; + FixedText &fixedText = *_vm->_fixedText; + Inventory &inv = *_vm->_inventory; + Scene &scene = *_vm->_scene; + Screen &screen = *_vm->_screen; + Talk &talk = *_vm->_talk; + bool printed = fixedTextActionId == kFixedTextAction_Invalid; + + if (objNum >= 1000) { + // Holmes was specified, so do nothing + _infoFlag = true; + clearInfo(); + _infoFlag = true; + + // Display error message + _menuCounter = 30; + screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "You can't do that to yourself."); + return; + } + + // Scan for target item + int targetNum = -1; + if (giveMode) { + for (int idx = 0; idx < USE_COUNT && targetNum == -1; ++idx) { + if ((use[idx]._target.equalsIgnoreCase("*GIVE*") || use[idx]._target.equalsIgnoreCase("*GIVEP*")) + && use[idx]._names[0].equalsIgnoreCase(invName)) { + // Found a match + targetNum = idx; + if (use[idx]._target.equalsIgnoreCase("*GIVE*")) + inv.deleteItemFromInventory(invName); + } + } + } else { + for (int idx = 0; idx < USE_COUNT && targetNum == -1; ++idx) { + if (use[idx]._target.equalsIgnoreCase(invName)) + targetNum = idx; + } + } + + if (targetNum != -1) { + // Found a target, so do the action + const UseType &action = use[targetNum]; + + events.setCursor(WAIT); + + if (action._useFlag) + _vm->setFlags(action._useFlag); + + if (action._cAnimNum != 99) { + if (action._cAnimNum == 0) + scene.startCAnim(9, action._cAnimSpeed); + else + scene.startCAnim(action._cAnimNum - 1, action._cAnimSpeed); + } + + if (!talk._talkToAbort) { + Object &obj = scene._bgShapes[objNum]; + for (int idx = 0; idx < NAMES_COUNT && !talk._talkToAbort; ++idx) { + if (obj.checkNameForCodes(action._names[idx], fixedTextActionId)) { + if (!talk._talkToAbort) + printed = true; + } + } + + // Print "Done..." as an ending, unless flagged for leaving scene or otherwise flagged + if (scene._goToScene != 1 && !printed && !talk._talkToAbort) { + _infoFlag = true; + clearInfo(); + screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "Done..."); + _menuCounter = 25; + } + } + } else { + // Couldn't find target, so print error + _infoFlag = true; + clearInfo(); + + if (giveMode) { + screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "No, thank you."); + } else if (fixedTextActionId == kFixedTextAction_Invalid) { + screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "You can't do that."); + } else { + Common::String errorMessage = fixedText.getActionMessage(fixedTextActionId, 0); + screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", errorMessage.c_str()); + } + + _infoFlag = true; + _menuCounter = 30; + } + + events.setCursor(ARROW); +} + +void ScalpelUserInterface::offsetButton3DO(Common::Point &pt, int num) { + if (IS_3DO) { + if (num >= 0 && num <= 2) + pt.x += 15; + else if (num >= 6 && num <= 8) + pt.x -= 4; + else if (num >= 9 && num <= 11) + pt.x -= 8; + } +} + +} // End of namespace Scalpel + +} // End of namespace Sherlock diff --git a/engines/sherlock/scalpel/scalpel_user_interface.h b/engines/sherlock/scalpel/scalpel_user_interface.h new file mode 100644 index 0000000000..7829ffca9f --- /dev/null +++ b/engines/sherlock/scalpel/scalpel_user_interface.h @@ -0,0 +1,223 @@ +/* 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 SHERLOCK_SCALPEL_UI_H +#define SHERLOCK_SCALPEL_UI_H + +#include "common/scummsys.h" +#include "sherlock/user_interface.h" + +namespace Sherlock { + +class Inventory; +class Talk; + +namespace Scalpel { + +extern const char COMMANDS[13]; +extern const int MENU_POINTS[12][4]; + +extern const int INVENTORY_POINTS[8][3]; +extern const char INVENTORY_COMMANDS[9]; +extern const char *const PRESS_KEY_FOR_MORE; +extern const char *const PRESS_KEY_TO_CONTINUE; + +class Settings; + +class ScalpelUserInterface: public UserInterface { + friend class Settings; + friend class Talk; +private: + char _keyPress; + int _lookHelp; + int _help, _oldHelp; + int _key, _oldKey; + int _temp, _oldTemp; + int _oldLook; + bool _keyboardInput; + bool _pause; + int _cNum; + Common::String _cAnimStr; + Common::String _descStr; + int _find; +private: + /** + * Draws the image for a user interface button in the down/pressed state. + */ + void depressButton(int num); + + /** + * If he mouse button is pressed, then calls depressButton to draw the button + * as pressed; if not, it will show it as released with a call to "restoreButton". + */ + void pushButton(int num); + + /** + * By the time this method has been called, the graphics for the button change + * have already been drawn. This simply takes care of switching the mode around + * accordingly + */ + void toggleButton(int num); + + /** + * Print the name of an object in the scene + */ + void lookScreen(const Common::Point &pt); + + /** + * Gets the item in the inventory the mouse is on and display's it's description + */ + void lookInv(); + + /** + * Handles input when the file list window is being displayed + */ + void doEnvControl(); + + /** + * Handle input whilst the inventory is active + */ + void doInvControl(); + + /** + * Handles waiting whilst an object's description window is open. + */ + void doLookControl(); + + /** + * Handles input until one of the user interface buttons/commands is selected + */ + void doMainControl(); + + /** + * Handles the input for the MOVE, OPEN, and CLOSE commands + */ + void doMiscControl(int allowed); + + /** + * Handles input for picking up items + */ + void doPickControl(); + + /** + * Handles input when in talk mode. It highlights the buttons and available statements, + * and handles allowing the user to click on them + */ + void doTalkControl(); + + /** + * Handles events when the Journal is active. + * @remarks Whilst this would in theory be better in the Journal class, since it displays in + * the user interface, it uses so many internal UI fields, that it sort of made some sense + * to put it in the UserInterface class. + */ + void journalControl(); + + /** + * Checks to see whether a USE action is valid on the given object + */ + void checkUseAction(const UseType *use, const Common::String &invName, FixedTextActionId fixedTextActionId, + int objNum, bool giveMode); + + /** + * Print the previously selected object's decription + */ + void printObjectDesc(const Common::String &str, bool firstTime); +public: + ImageFile *_controlPanel; + ImageFile *_controls; + int _oldUse; +public: + ScalpelUserInterface(SherlockEngine *vm); + virtual ~ScalpelUserInterface(); + + /** + * Handles counting down whilst checking for input, then clears the info line. + */ + void whileMenuCounter(); + + /** + * Draws the image for the given user interface button in the up + * (not selected) position + */ + void restoreButton(int num); + + /** + * Creates a text window and uses it to display the in-depth description + * of the highlighted object + */ + void examine(); + + void offsetButton3DO(Common::Point &pt, int num); +public: + /** + * Resets the user interface + */ + virtual void reset(); + + /** + * Main input handler for the user interface + */ + virtual void handleInput(); + + /** + * Draw the user interface onto the screen's back buffers + */ + virtual void drawInterface(int bufferNum = 3); + + /** + * Displays a passed window by gradually scrolling it vertically on-screen + */ + virtual void summonWindow(const Surface &bgSurface, bool slideUp = true); + + /** + * Slide the window stored in the back buffer onto the screen + */ + virtual void summonWindow(bool slideUp = true, int height = CONTROLS_Y); + + /** + * Close a currently open window + * @param flag 0 = slide old window down, 1 = slide prior UI back up + */ + virtual void banishWindow(bool slideUp = true); + + /** + * Clears the info line of the screen + */ + virtual void clearInfo(); + + /** + * Clear any active text window + */ + virtual void clearWindow(); + + /** + * Print the previously selected object's decription + */ + virtual void printObjectDesc(); +}; + +} // End of namespace Scalpel + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/scalpel/settings.cpp b/engines/sherlock/scalpel/settings.cpp new file mode 100644 index 0000000000..aa8033d25e --- /dev/null +++ b/engines/sherlock/scalpel/settings.cpp @@ -0,0 +1,341 @@ +/* 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 "sherlock/sherlock.h" +#include "sherlock/scalpel/settings.h" +#include "sherlock/scalpel/scalpel_user_interface.h" + +namespace Sherlock { + +namespace Scalpel { + +static const int SETUP_POINTS[12][4] = { + { 4, 154, 101, 53 }, // Exit + { 4, 165, 101, 53 }, // Music Toggle + { 219, 165, 316, 268 }, // Voice Toggle + { 103, 165, 217, 160 }, // Sound Effects Toggle + { 219, 154, 316, 268 }, // Help Button Left/Right + { 103, 154, 217, 160 }, // New Font Style + { 4, 187, 101, 53 }, // Joystick Toggle + { 103, 187, 217, 160 }, // Calibrate Joystick + { 219, 176, 316, 268 }, // Fade Style + { 103, 176, 217, 160 }, // Window Open Style + { 4, 176, 101, 53 }, // Portraits Toggle + { 219, 187, 316, 268 } // _key Pad Accel. Toggle +}; + +static const char *const SETUP_STRS0[2] = { "off", "on" }; +static const char *const SETUP_STRS1[2] = { "Directly", "by Pixel" }; +static const char *const SETUP_STRS2[2] = { "Left", "Right" }; +static const char *const SETUP_STRS3[2] = { "Appear", "Slide" }; +static const char *const SETUP_STRS5[2] = { "Left", "Right" }; +static const char *const SETUP_NAMES[12] = { + "Exit", "M", "V", "S", "B", "New Font Style", "J", "Calibrate Joystick", "F", "W", "P", "K" +}; + +/*----------------------------------------------------------------*/ + +void Settings::drawInteface(bool flag) { + People &people = *_vm->_people; + Screen &screen = *_vm->_screen; + Sound &sound = *_vm->_sound; + Music &music = *_vm->_music; + UserInterface &ui = *_vm->_ui; + Common::String tempStr; + + if (!flag) { + screen._backBuffer1.fillRect(Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH, CONTROLS_Y1 + 1), BORDER_COLOR); + screen._backBuffer1.fillRect(Common::Rect(0, CONTROLS_Y1 + 1, 2, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR); + screen._backBuffer1.fillRect(Common::Rect(SHERLOCK_SCREEN_WIDTH - 2, CONTROLS_Y1 + 1, SHERLOCK_SCREEN_WIDTH, + SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR); + screen._backBuffer1.hLine(0, SHERLOCK_SCREEN_HEIGHT - 1, SHERLOCK_SCREEN_WIDTH - 1, BORDER_COLOR); + screen._backBuffer1.fillRect(Common::Rect(2, CONTROLS_Y1 + 1, SHERLOCK_SCREEN_WIDTH - 2, + SHERLOCK_SCREEN_HEIGHT - 2), INV_BACKGROUND); + } + + screen.makeButton(Common::Rect(SETUP_POINTS[0][0], SETUP_POINTS[0][1], SETUP_POINTS[0][2], SETUP_POINTS[0][1] + 10), + SETUP_POINTS[0][3] - screen.stringWidth("Exit") / 2, "Exit"); + + tempStr = Common::String::format("Music %s", SETUP_STRS0[music._musicOn]); + screen.makeButton(Common::Rect(SETUP_POINTS[1][0], SETUP_POINTS[1][1], SETUP_POINTS[1][2], SETUP_POINTS[1][1] + 10), + SETUP_POINTS[1][3] - screen.stringWidth(tempStr) / 2, tempStr); + + tempStr = Common::String::format("Voices %s", SETUP_STRS0[sound._voices]); + screen.makeButton(Common::Rect(SETUP_POINTS[2][0], SETUP_POINTS[2][1], SETUP_POINTS[2][2], SETUP_POINTS[2][1] + 10), + SETUP_POINTS[2][3] - screen.stringWidth(tempStr) / 2, tempStr); + + tempStr = Common::String::format("Sound Effects %s", SETUP_STRS0[sound._digitized]); + screen.makeButton(Common::Rect(SETUP_POINTS[3][0], SETUP_POINTS[3][1], SETUP_POINTS[3][2], SETUP_POINTS[3][1] + 10), + SETUP_POINTS[3][3] - screen.stringWidth(tempStr) / 2, tempStr); + + tempStr = Common::String::format("Auto Help %s", SETUP_STRS5[ui._helpStyle]); + screen.makeButton(Common::Rect(SETUP_POINTS[4][0], SETUP_POINTS[4][1], SETUP_POINTS[4][2], SETUP_POINTS[4][1] + 10), + SETUP_POINTS[4][3] - screen.stringWidth(tempStr) / 2, tempStr); + screen.makeButton(Common::Rect(SETUP_POINTS[5][0], SETUP_POINTS[5][1], SETUP_POINTS[5][2], SETUP_POINTS[5][1] + 10), + SETUP_POINTS[5][3] - screen.stringWidth("New Font Style") / 2, "New Font Style"); + + // WORKAROUND: We don't support the joystick in ScummVM, so draw the next two buttons as disabled + tempStr = "Joystick Off"; + screen.makeButton(Common::Rect(SETUP_POINTS[6][0], SETUP_POINTS[6][1], SETUP_POINTS[6][2], SETUP_POINTS[6][1] + 10), + SETUP_POINTS[6][3] - screen.stringWidth(tempStr) / 2, tempStr); + screen.buttonPrint(Common::Point(SETUP_POINTS[6][3], SETUP_POINTS[6][1]), COMMAND_NULL, false, tempStr); + + tempStr = "Calibrate Joystick"; + screen.makeButton(Common::Rect(SETUP_POINTS[7][0], SETUP_POINTS[7][1], SETUP_POINTS[7][2], SETUP_POINTS[7][1] + 10), + SETUP_POINTS[7][3] - screen.stringWidth(tempStr) / 2, tempStr); + screen.buttonPrint(Common::Point(SETUP_POINTS[7][3], SETUP_POINTS[7][1]), COMMAND_NULL, false, tempStr); + + tempStr = Common::String::format("Fade %s", screen._fadeStyle ? "by Pixel" : "Directly"); + screen.makeButton(Common::Rect(SETUP_POINTS[8][0], SETUP_POINTS[8][1], SETUP_POINTS[8][2], SETUP_POINTS[8][1] + 10), + SETUP_POINTS[8][3] - screen.stringWidth(tempStr) / 2, tempStr); + + tempStr = Common::String::format("Windows %s", ui._slideWindows ? "Slide" : "Appear"); + screen.makeButton(Common::Rect(SETUP_POINTS[9][0], SETUP_POINTS[9][1], SETUP_POINTS[9][2], SETUP_POINTS[9][1] + 10), + SETUP_POINTS[9][3] - screen.stringWidth(tempStr) / 2, tempStr); + + tempStr = Common::String::format("Portraits %s", SETUP_STRS0[people._portraitsOn]); + screen.makeButton(Common::Rect(SETUP_POINTS[10][0], SETUP_POINTS[10][1], SETUP_POINTS[10][2], SETUP_POINTS[10][1] + 10), + SETUP_POINTS[10][3] - screen.stringWidth(tempStr) / 2, tempStr); + + tempStr = "Key Pad Slow"; + screen.makeButton(Common::Rect(SETUP_POINTS[11][0], SETUP_POINTS[11][1], SETUP_POINTS[11][2], SETUP_POINTS[11][1] + 10), + SETUP_POINTS[11][3] - screen.stringWidth(tempStr) / 2, tempStr); + screen.buttonPrint(Common::Point(SETUP_POINTS[11][3], SETUP_POINTS[11][1]), COMMAND_NULL, false, tempStr); + + // Show the window immediately, or slide it on-screen + if (!flag) { + if (!ui._slideWindows) { + screen.slamRect(Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); + } else { + ui.summonWindow(true, CONTROLS_Y1); + } + + ui._windowOpen = true; + } else { + screen.slamRect(Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); + } +} + +int Settings::drawButtons(const Common::Point &pt, int _key) { + Events &events = *_vm->_events; + People &people = *_vm->_people; + Screen &screen = *_vm->_screen; + Music &music = *_vm->_music; + Sound &sound = *_vm->_sound; + UserInterface &ui = *_vm->_ui; + int found = -1; + byte color; + Common::String tempStr; + + for (int idx = 0; idx < 12; ++idx) { + if ((pt.x > SETUP_POINTS[idx][0] && pt.x < SETUP_POINTS[idx][2] && pt.y > SETUP_POINTS[idx][1] + && pt.y < (SETUP_POINTS[idx][1] + 10) && (events._pressed || events._released)) + || (_key == SETUP_NAMES[idx][0])) { + found = idx; + color = COMMAND_HIGHLIGHTED; + } else { + color = COMMAND_FOREGROUND; + } + + // Print the button text + switch (idx) { + case 1: + tempStr = Common::String::format("Music %s", SETUP_STRS0[music._musicOn]); + screen.buttonPrint(Common::Point(SETUP_POINTS[idx][3], SETUP_POINTS[idx][1]), color, true, tempStr); + break; + case 2: + tempStr = Common::String::format("Voices %s", SETUP_STRS0[sound._voices]); + screen.buttonPrint(Common::Point(SETUP_POINTS[idx][3], SETUP_POINTS[idx][1]), color, true, tempStr); + break; + case 3: + tempStr = Common::String::format("Sound Effects %s", SETUP_STRS0[sound._digitized]); + screen.buttonPrint(Common::Point(SETUP_POINTS[idx][3], SETUP_POINTS[idx][1]), color, true, tempStr); + break; + case 4: + tempStr = Common::String::format("Auto Help %s", SETUP_STRS2[ui._helpStyle]); + screen.buttonPrint(Common::Point(SETUP_POINTS[idx][3], SETUP_POINTS[idx][1]), color, true, tempStr); + break; + case 6: + tempStr = "Joystick Off"; + screen.buttonPrint(Common::Point(SETUP_POINTS[idx][3], SETUP_POINTS[idx][1]), COMMAND_NULL, true, tempStr); + break; + case 7: + tempStr = "Calibrate Joystick"; + screen.buttonPrint(Common::Point(SETUP_POINTS[idx][3], SETUP_POINTS[idx][1]), COMMAND_NULL, true, tempStr); + break; + case 8: + tempStr = Common::String::format("Fade %s", SETUP_STRS1[screen._fadeStyle]); + screen.buttonPrint(Common::Point(SETUP_POINTS[idx][3], SETUP_POINTS[idx][1]), color, true, tempStr); + break; + case 9: + tempStr = Common::String::format("Windows %s", SETUP_STRS3[ui._slideWindows]); + screen.buttonPrint(Common::Point(SETUP_POINTS[idx][3], SETUP_POINTS[idx][1]), color, true, tempStr); + break; + case 10: + tempStr = Common::String::format("Portraits %s", SETUP_STRS0[people._portraitsOn]); + screen.buttonPrint(Common::Point(SETUP_POINTS[idx][3], SETUP_POINTS[idx][1]), color, true, tempStr); + break; + case 11: + tempStr = "Key Pad Slow"; + screen.buttonPrint(Common::Point(SETUP_POINTS[idx][3], SETUP_POINTS[idx][1]), COMMAND_NULL, true, tempStr); + break; + default: + screen.buttonPrint(Common::Point(SETUP_POINTS[idx][3], SETUP_POINTS[idx][1]), color, true, SETUP_NAMES[idx]); + break; + } + } + + return found; +} + +void Settings::show(SherlockEngine *vm) { + Events &events = *vm->_events; + People &people = *vm->_people; + Scene &scene = *vm->_scene; + Screen &screen = *vm->_screen; + Sound &sound = *vm->_sound; + Music &music = *vm->_music; + Talk &talk = *vm->_talk; + ScalpelUserInterface &ui = *(ScalpelUserInterface *)vm->_ui; + bool updateConfig = false; + + assert(vm->getGameID() == GType_SerratedScalpel); + Settings settings(vm); + settings.drawInteface(false); + + do { + if (ui._menuCounter) + ui.whileMenuCounter(); + + int found = -1; + ui._key = -1; + + scene.doBgAnim(); + if (talk._talkToAbort) + return; + + events.setButtonState(); + Common::Point pt = events.mousePos(); + + if (events._pressed || events._released || events.kbHit()) { + ui.clearInfo(); + ui._key = -1; + + if (events.kbHit()) { + Common::KeyState keyState = events.getKey(); + ui._key = toupper(keyState.keycode); + + if (ui._key == Common::KEYCODE_RETURN || ui._key == Common::KEYCODE_SPACE) { + events._pressed = false; + events._oldButtons = 0; + ui._keyPress = '\0'; + events._released = true; + } + } + + // Handle highlighting button under mouse + found = settings.drawButtons(pt, ui._key); + } + + if ((found == 0 && events._released) || (ui._key == 'E' || ui._key == Common::KEYCODE_ESCAPE)) + // Exit + break; + + if ((found == 1 && events._released) || ui._key == 'M') { + // Toggle music + music._musicOn = !music._musicOn; + if (!music._musicOn) + music.stopMusic(); + else + music.startSong(); + + updateConfig = true; + settings.drawInteface(true); + } + + if ((found == 2 && events._released) || ui._key == 'V') { + sound._voices = !sound._voices; + updateConfig = true; + settings.drawInteface(true); + } + + if ((found == 3 && events._released) || ui._key == 'S') { + // Toggle sound effects + sound._digitized = !sound._digitized; + updateConfig = true; + settings.drawInteface(true); + } + + if ((found == 4 && events._released) || ui._key == 'A') { + // Help button style + ui._helpStyle = !ui._helpStyle; + updateConfig = true; + settings.drawInteface(true); + } + + if ((found == 5 && events._released) || ui._key == 'N') { + // New font style + int fontNum = screen.fontNumber() + 1; + if (fontNum == 3) + fontNum = 0; + + screen.setFont(fontNum); + updateConfig = true; + settings.drawInteface(true); + } + + if ((found == 8 && events._released) || ui._key == 'F') { + // Toggle fade style + screen._fadeStyle = !screen._fadeStyle; + updateConfig = true; + settings.drawInteface(true); + } + + if ((found == 9 && events._released) || ui._key == 'W') { + // Window style + ui._slideWindows = !ui._slideWindows; + updateConfig = true; + settings.drawInteface(true); + } + + if ((found == 10 && events._released) || ui._key == 'P') { + // Toggle portraits being shown + people._portraitsOn = !people._portraitsOn; + updateConfig = true; + settings.drawInteface(true); + } + } while (!vm->shouldQuit()); + + ui.banishWindow(); + + if (updateConfig) + vm->saveConfig(); + + ui._keyPress = '\0'; + ui._keyboardInput = false; + ui._windowBounds.top = CONTROLS_Y1; + ui._key = -1; +} + +} // End of namespace Scalpel + +} // End of namespace Sherlock diff --git a/engines/sherlock/scalpel/settings.h b/engines/sherlock/scalpel/settings.h new file mode 100644 index 0000000000..ff2e647a62 --- /dev/null +++ b/engines/sherlock/scalpel/settings.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 SHERLOCK_SETTINGS_H +#define SHERLOCK_SETTINGS_H + +#include "common/scummsys.h" + +namespace Sherlock { + +class SherlockEngine; + +namespace Scalpel { + +class Settings { +private: + SherlockEngine *_vm; + + Settings(SherlockEngine *vm) : _vm(vm) {} + + /** + * Draws the interface for the settings window + */ + void drawInteface(bool flag); + + /** + * Draws the buttons for the settings dialog + */ + int drawButtons(const Common::Point &pt, int key); +public: + /** + * Handles input when the settings window is being shown + * @remarks Whilst this would in theory be better in the Journal class, since it displays in + * the user interface, it uses so many internal UI fields, that it sort of made some sense + * to put it in the UserInterface class. + */ + static void show(SherlockEngine *vm); +}; + +} // End of namespace Scalpel + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/scalpel/tsage/logo.cpp b/engines/sherlock/scalpel/tsage/logo.cpp new file mode 100644 index 0000000000..4eab01947a --- /dev/null +++ b/engines/sherlock/scalpel/tsage/logo.cpp @@ -0,0 +1,684 @@ +/* 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 "common/scummsys.h" +#include "sherlock/scalpel/tsage/logo.h" +#include "sherlock/scalpel/scalpel.h" + +namespace Sherlock { +namespace Scalpel { +namespace TsAGE { + +TLib *Visage::_tLib; + +Visage::Visage() { + _resNum = -1; + _rlbNum = -1; + _stream = nullptr; +} + +void Visage::setVisage(int resNum, int rlbNum) { + if ((_resNum != resNum) || (_rlbNum != rlbNum)) { + _resNum = resNum; + _rlbNum = rlbNum; + delete _stream; + + // Games after Ringworld have an extra indirection via the visage index file + Common::SeekableReadStream *stream = _tLib->getResource(RES_VISAGE, resNum, 9999); + if (rlbNum == 0) + rlbNum = 1; + + // Check how many slots there are + uint16 count = stream->readUint16LE(); + if (rlbNum > count) + rlbNum = count; + + // Get the flags/rlbNum to use + stream->seek((rlbNum - 1) * 4 + 2); + uint32 v = stream->readUint32LE(); + int flags = v >> 30; + + if (flags & 3) { + rlbNum = (int)(v & 0xff); + } + assert((flags & 3) == 0); + delete stream; + + _stream = _tLib->getResource(RES_VISAGE, resNum, rlbNum); + } +} + +void Visage::clear() { + delete _stream; + _stream = nullptr; +} + +Visage::~Visage() { + delete _stream; +} + +void Visage::getFrame(ObjectSurface &s, int frameNum) { + _stream->seek(0); + int numFrames = _stream->readUint16LE(); + if (frameNum > numFrames) + frameNum = numFrames; + if (frameNum > 0) + --frameNum; + + _stream->seek(frameNum * 4 + 2); + int offset = _stream->readUint32LE(); + _stream->seek(offset); + + surfaceFromRes(s); +} + +int Visage::getFrameCount() const { + _stream->seek(0); + return _stream->readUint16LE(); +} + +bool Visage::isLoaded() const { + return _stream != nullptr; +} + +void Visage::surfaceFromRes(ObjectSurface &s) { + int frameWidth = _stream->readUint16LE(); + int frameHeight = _stream->readUint16LE(); + Common::Rect r(0, 0, frameWidth, frameHeight); + s.create(r.width(), r.height()); + + s._centroid.x = _stream->readSint16LE(); + s._centroid.y = _stream->readSint16LE(); + + _stream->skip(1); + byte flags = _stream->readByte(); + bool rleEncoded = (flags & 2) != 0; + + byte *destP = (byte *)s.getPixels(); + + if (!rleEncoded) { + _stream->read(destP, r.width() * r.height()); + } else { + Common::fill(destP, destP + (r.width() * r.height()), 0xff); + + for (int yp = 0; yp < r.height(); ++yp) { + int width = r.width(); + destP = (byte *)s.getBasePtr(0, yp); + + while (width > 0) { + uint8 controlVal = _stream->readByte(); + if ((controlVal & 0x80) == 0) { + // Copy specified number of bytes + _stream->read(destP, controlVal); + width -= controlVal; + destP += controlVal; + } else if ((controlVal & 0x40) == 0) { + // Skip a specified number of output pixels + destP += controlVal & 0x3f; + width -= controlVal & 0x3f; + } else { + // Copy a specified pixel a given number of times + controlVal &= 0x3f; + int pixel = _stream->readByte(); + + Common::fill(destP, destP + controlVal, pixel); + destP += controlVal; + width -= controlVal; + } + } + assert(width == 0); + } + } +} + +/*--------------------------------------------------------------------------*/ + +ScalpelEngine *Object::_vm; + +Object::Object() { + _vm = nullptr; + _isAnimating = _finished = false; + _frame = 0; + _numFrames = 0; + _frameChange = 0; + _angle = _changeCtr = 0; + _walkStartFrame = 0; + _majorDiff = _minorDiff = 0; +} + +void Object::setVisage(int visage, int strip) { + _visage.setVisage(visage, strip); +} + +void Object::setAnimMode(bool isAnimating) { + _isAnimating = isAnimating; + _finished = false; + + _updateStartFrame = _vm->_events->getFrameCounter(); + if (_numFrames) + _updateStartFrame += 60 / _numFrames; + _frameChange = 1; +} + +void Object::setDestination(const Common::Point &pt) { + _destination = pt; + + int moveRate = 10; + _walkStartFrame = _vm->_events->getFrameCounter(); + _walkStartFrame += 60 / moveRate; + + calculateMoveAngle(); + + // Get the difference + int diffX = _destination.x - _position.x; + int diffY = _destination.y - _position.y; + int xSign = (diffX < 0) ? -1 : (diffX > 0 ? 1 : 0); + int ySign = (diffY < 0) ? -1 : (diffY > 0 ? 1 : 0); + diffX = ABS(diffX); + diffY = ABS(diffY); + + if (diffX < diffY) { + _minorDiff = diffX / 2; + _majorDiff = diffY; + } else { + _minorDiff = diffY / 2; + _majorDiff = diffX; + } + + // Set the destination position + _moveDelta = Common::Point(diffX, diffY); + _moveSign = Common::Point(xSign, ySign); + _changeCtr = 0; + + assert(diffX || diffY); +} + +void Object::erase() { + Screen &screen = *_vm->_screen; + + if (_visage.isLoaded() && !_oldBounds.isEmpty()) + screen.blitFrom(screen._backBuffer1, Common::Point(_oldBounds.left, _oldBounds.top), _oldBounds); +} + +void Object::update() { + Screen &screen = *_vm->_screen; + + if (_visage.isLoaded()) { + if (isMoving()) { + uint32 currTime = _vm->_events->getFrameCounter(); + if (_walkStartFrame <= currTime) { + int moveRate = 10; + int frameInc = 60 / moveRate; + _walkStartFrame = currTime + frameInc; + move(); + } + } + + if (_isAnimating) { + if (_frame < _visage.getFrameCount()) + _frame = changeFrame(); + else + _finished = true; + } + + // Get the new frame + ObjectSurface s; + _visage.getFrame(s, _frame); + + // Display the frame + _oldBounds = Common::Rect(_position.x, _position.y, _position.x + s.w(), _position.y + s.h()); + _oldBounds.translate(-s._centroid.x, -s._centroid.y); + screen.transBlitFrom(s, Common::Point(_oldBounds.left, _oldBounds.top)); + } +} + +int Object::changeFrame() { + int frameNum = _frame; + uint32 currentFrame = _vm->_events->getFrameCounter(); + + if (_updateStartFrame <= currentFrame) { + if (_numFrames > 0) { + int v = 60 / _numFrames; + _updateStartFrame = currentFrame + v; + + frameNum = getNewFrame(); + } + } + + return frameNum; +} + +int Object::getNewFrame() { + int frameNum = _frame + _frameChange; + + if (_frameChange > 0) { + if (frameNum > _visage.getFrameCount()) { + frameNum = 1; + } + } else if (frameNum < 1) { + frameNum = _visage.getFrameCount(); + } + + return frameNum; +} + +void Object::calculateMoveAngle() { + int xDiff = _destination.x - _position.x, yDiff = _position.y - _destination.y; + + if (!xDiff && !yDiff) + _angle = 0; + else if (!xDiff) + _angle = (_destination.y >= _position.y) ? 180 : 0; + else if (!yDiff) + _angle = (_destination.x >= _position.x) ? 90 : 270; + else { + int result = (((xDiff * 100) / ((abs(xDiff) + abs(yDiff))) * 90) / 100); + + if (yDiff < 0) + result = 180 - result; + else if (xDiff < 0) + result += 360; + + _angle = result; + } +} + +bool Object::isAnimEnded() const { + return _finished; +} + +bool Object::isMoving() const { + return (_destination.x != 0) && (_destination != _position); +} + +void Object::move() { + Common::Point currPos = _position; + Common::Point moveDiff(5, 3); + int percent = 100; + + if (dontMove()) + return; + + if (_moveDelta.x >= _moveDelta.y) { + int xAmount = _moveSign.x * moveDiff.x * percent / 100; + if (!xAmount) + xAmount = _moveSign.x; + currPos.x += xAmount; + + int yAmount = ABS(_destination.y - currPos.y); + int yChange = _majorDiff / ABS(xAmount); + int ySign; + + if (!yChange) + ySign = _moveSign.y; + else { + int v = yAmount / yChange; + _changeCtr += yAmount % yChange; + if (_changeCtr >= yChange) { + ++v; + _changeCtr -= yChange; + } + + ySign = _moveSign.y * v; + } + + currPos.y += ySign; + _majorDiff -= ABS(xAmount); + } else { + int yAmount = _moveSign.y * moveDiff.y * percent / 100; + if (!yAmount) + yAmount = _moveSign.y; + currPos.y += yAmount; + + int xAmount = ABS(_destination.x - currPos.x); + int xChange = _majorDiff / ABS(yAmount); + int xSign; + + if (!xChange) + xSign = _moveSign.x; + else { + int v = xAmount / xChange; + _changeCtr += xAmount % xChange; + if (_changeCtr >= xChange) { + ++v; + _changeCtr -= xChange; + } + + xSign = _moveSign.x * v; + } + + currPos.x += xSign; + _majorDiff -= ABS(yAmount); + } + + _position = currPos; + if (dontMove()) + _position = _destination; +} + +bool Object::dontMove() const { + return (_majorDiff <= 0); +} + +void Object::endMove() { + _position = _destination; +} + +/*----------------------------------------------------------------*/ + +bool Logo::show(ScalpelEngine *vm) { + Events &events = *vm->_events; + Logo *logo = new Logo(vm); + bool interrupted = false; + + while (!logo->finished()) { + logo->nextFrame(); + + // Erase areas from previous frame, and update and re-draw objects + for (int idx = 0; idx < 4; ++idx) + logo->_objects[idx].erase(); + for (int idx = 0; idx < 4; ++idx) + logo->_objects[idx].update(); + + events.wait(2); + events.setButtonState(); + + interrupted = vm->shouldQuit() || events.kbHit() || events._pressed; + if (interrupted) { + // Keyboard or mouse button pressed, so break out of logo display + events.clearEvents(); + break; + } + } + + delete logo; + return !interrupted; +} + +Logo::Logo(ScalpelEngine *vm) : _vm(vm), _lib("sf3.rlb") { + Object::_vm = vm; + Visage::_tLib = &_lib; + + _finished = false; + + // Initialize counter + _counter = 0; + + // Initialize wait frame counters + _waitFrames = 0; + _waitStartFrame = 0; + + // Initialize animation counters + _animateObject = 0; + _animateStartFrame = 0; + _animateFrameDelay = 0; + _animateFrames = NULL; + _animateFrame = 0; + + // Save a copy of the original palette + _vm->_screen->getPalette(_originalPalette); + + // Set up the palettes + Common::fill(&_palette1[0], &_palette1[PALETTE_SIZE], 0); + Common::fill(&_palette1[0], &_palette2[PALETTE_SIZE], 0); + Common::fill(&_palette1[0], &_palette3[PALETTE_SIZE], 0); + + _lib.getPalette(_palette1, 1111); + _lib.getPalette(_palette1, 10); + _lib.getPalette(_palette2, 1111); + _lib.getPalette(_palette2, 1); + _lib.getPalette(_palette3, 1111); + _lib.getPalette(_palette3, 14); +} + +Logo::~Logo() { + // Restore the original palette + _vm->_screen->setPalette(_originalPalette); +} + +bool Logo::finished() const { + return _finished; +} + +const AnimationFrame handFrames[] = { + { 1, 33, 91 }, { 2, 44, 124 }, { 3, 64, 153 }, { 4, 87, 174 }, + { 5, 114, 191 }, { 6, 125, 184 }, { 7, 154, 187 }, { 8, 181, 182 }, + { 9, 191, 167 }, { 10, 190, 150 }, { 11, 182, 139 }, { 11, 170, 130 }, + { 11, 158, 121 }, { 0, 0, 0 } +}; + +const AnimationFrame companyFrames[] = { + { 1, 155, 94 }, { 2, 155, 94 }, { 3, 155, 94 }, { 4, 155, 94 }, + { 5, 155, 94 }, { 6, 155, 94 }, { 7, 155, 94 }, { 8, 155, 94 }, + { 0, 0, 0 } +}; + +void Logo::nextFrame() { + Screen &screen = *_vm->_screen; + + if (_waitFrames) { + uint32 currFrame = _vm->_events->getFrameCounter(); + if (currFrame - _waitStartFrame < _waitFrames) { + return; + } + _waitStartFrame = 0; + _waitFrames = 0; + } + + if (_animateFrames) { + uint32 currFrame = _vm->_events->getFrameCounter(); + if (currFrame > _animateStartFrame + _animateFrameDelay) { + AnimationFrame animationFrame = _animateFrames[_animateFrame]; + if (animationFrame.frame) { + _objects[_animateObject]._frame = animationFrame.frame; + _objects[_animateObject]._position = Common::Point(animationFrame.x, animationFrame.y); + _animateStartFrame += _animateFrameDelay; + _animateFrame++; + } else { + _animateObject = 0; + _animateFrameDelay = 0; + _animateFrames = NULL; + _animateStartFrame = 0; + _animateFrame = 0; + } + } + if (_animateFrames) + return; + } + + switch (_counter++) { + case 0: + // Load the background and fade it in + loadBackground(); + fade(_palette1); + break; + + case 1: + // First half of square, circle, and triangle arranging themselves + _objects[0].setVisage(16, 1); + _objects[0]._frame = 1; + _objects[0]._position = Common::Point(169, 107); + _objects[0]._numFrames = 7; + _objects[0].setAnimMode(true); + break; + + case 2: + // Keep waiting until first animation ends + if (!_objects[0].isAnimEnded()) { + --_counter; + } else { + // Start second half of the shapes animation + _objects[0].setVisage(16, 2); + _objects[0]._frame = 1; + _objects[0]._numFrames = 11; + _objects[0].setAnimMode(true); + } + break; + + case 3: + // Keep waiting until second animation of shapes ordering themselves ends + if (!_objects[0].isAnimEnded()) { + --_counter; + } else { + // Fade out the background but keep the shapes visible + fade(_palette2); + screen._backBuffer1.clear(); + } + waitFrames(10); + break; + + case 4: + // Load the new palette + byte palette[PALETTE_SIZE]; + Common::copy(&_palette2[0], &_palette2[PALETTE_SIZE], &palette[0]); + _lib.getPalette(palette, 12); + screen.clear(); + screen.setPalette(palette); + + // Morph into the EA logo + _objects[0].setVisage(12, 1); + _objects[0]._frame = 1; + _objects[0]._numFrames = 7; + _objects[0].setAnimMode(true); + _objects[0]._position = Common::Point(170, 142); + _objects[0].setDestination(Common::Point(158, 71)); + break; + + case 5: + // Wait until the logo has expanded upwards to form EA logo + if (_objects[0].isMoving()) + --_counter; + break; + + case 6: + fade(_palette3, 40); + break; + + case 7: + // Show the 'Electronic Arts' company name + _objects[1].setVisage(14, 1); + _objects[1]._frame = 1; + _objects[1]._position = Common::Point(152, 98); + waitFrames(120); + break; + + case 8: + // Start sequence of positioning and size hand cursor in an arc + _objects[2].setVisage(18, 1); + startAnimation(2, 5, &handFrames[0]); + break; + + case 9: + // Show a highlighting of the company name + _objects[1].remove(); + _objects[2].erase(); + _objects[2].remove(); + _objects[3].setVisage(19, 1); + startAnimation(3, 8, &companyFrames[0]); + break; + + case 10: + waitFrames(180); + break; + + case 11: + _finished = true; + break; + + default: + break; + } +} + +void Logo::waitFrames(uint frames) { + _waitFrames = frames; + _waitStartFrame = _vm->_events->getFrameCounter(); +} + +void Logo::startAnimation(uint object, uint frameDelay, const AnimationFrame *frames) { + _animateObject = object; + _animateFrameDelay = frameDelay; + _animateFrames = frames; + _animateStartFrame = _vm->_events->getFrameCounter(); + _animateFrame = 1; + + _objects[object]._frame = frames[0].frame; + _objects[object]._position = Common::Point(frames[0].x, frames[0].y); +} + +void Logo::loadBackground() { + Screen &screen = *_vm->_screen; + + for (int idx = 0; idx < 4; ++idx) { + // Get the portion of the screen + Common::SeekableReadStream *stream = _lib.getResource(RES_BITMAP, 10, idx); + + // Load it onto the surface + Common::Point pt((idx / 2) * (SHERLOCK_SCREEN_WIDTH / 2), (idx % 2) * (SHERLOCK_SCREEN_HEIGHT / 2)); + for (int y = 0; y < (SHERLOCK_SCREEN_HEIGHT / 2); ++y, ++pt.y) { + byte *pDest = (byte *)screen._backBuffer1.getBasePtr(pt.x, pt.y); + stream->read(pDest, SHERLOCK_SCREEN_WIDTH / 2); + } + + // _backgroundBounds = Rect(0, 0, READ_LE_UINT16(data), READ_LE_UINT16(data + 2)); + delete stream; + } + + // Default to a blank palette + byte palette[PALETTE_SIZE]; + Common::fill(&palette[0], &palette[PALETTE_SIZE], 0); + screen.setPalette(palette); + + // Copy the surface to the screen + screen.blitFrom(screen._backBuffer1); +} + +void Logo::fade(const byte palette[PALETTE_SIZE], int step) { + Events &events = *_vm->_events; + Screen &screen = *_vm->_screen; + byte startPalette[PALETTE_SIZE]; + byte tempPalette[PALETTE_SIZE]; + + screen.getPalette(startPalette); + + for (int percent = 0; percent < 100; percent += step) { + for (int palIndex = 0; palIndex < 256; ++palIndex) { + const byte *pal1P = (const byte *)&startPalette[palIndex * 3]; + const byte *pal2P = (const byte *)&palette[palIndex * 3]; + byte *destP = &tempPalette[palIndex * 3]; + + for (int rgbIndex = 0; rgbIndex < 3; ++rgbIndex, ++pal1P, ++pal2P, ++destP) { + *destP = (int)*pal1P + ((int)*pal2P - (int)*pal1P) * percent / 100; + } + } + + screen.setPalette(tempPalette); + events.wait(1); + } + + // Set final palette + screen.setPalette(palette); +} + +} // end of namespace TsAGE +} // end of namespace Scalpel +} // end of namespace Sherlock diff --git a/engines/sherlock/scalpel/tsage/logo.h b/engines/sherlock/scalpel/tsage/logo.h new file mode 100644 index 0000000000..c9fac00d9c --- /dev/null +++ b/engines/sherlock/scalpel/tsage/logo.h @@ -0,0 +1,250 @@ +/* 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 SHERLOCK_SCALPEL_TSAGE_LOGO_H +#define SHERLOCK_SCALPEL_TSAGE_LOGO_H + +#include "common/scummsys.h" +#include "common/array.h" +#include "common/file.h" +#include "common/list.h" +#include "common/str.h" +#include "common/str-array.h" +#include "common/util.h" +#include "graphics/surface.h" +#include "sherlock/scalpel/tsage/resources.h" +#include "sherlock/screen.h" + +namespace Sherlock { +namespace Scalpel { + +class ScalpelEngine; + +namespace TsAGE { + +class ObjectSurface : public Surface { +public: + Common::Point _centroid; +public: + ObjectSurface() : Surface() {} + virtual ~ObjectSurface() {} +}; + +class Visage { +private: + Common::SeekableReadStream *_stream; + + /** + * Translates a raw image resource into a graphics surface + */ + void surfaceFromRes(ObjectSurface &s); +public: + static TLib *_tLib; + int _resNum; + int _rlbNum; +public: + Visage(); + ~Visage(); + + /** + * Set the visage number + */ + void setVisage(int resNum, int rlbNum = 9999); + + /** + * Clear the visage + */ + void clear(); + + /** + * Get a frame from the visage + */ + void getFrame(ObjectSurface &s, int frameNum); + + /** + * Return the number of frames + */ + int getFrameCount() const; + + /** + * Returns whether the visage is loaded + */ + bool isLoaded() const; +}; + +class Object { +private: + Visage _visage; + uint32 _updateStartFrame; + bool _isAnimating; + bool _finished; + uint32 _walkStartFrame; + int _angle; + int _changeCtr; + int _majorDiff, _minorDiff; + Common::Point _moveDelta; + Common::Point _moveSign; + + /** + * Return the next frame when the object is animating + */ + int changeFrame(); + + /** + * Gets the next frame in the sequence + */ + int getNewFrame(); + + /** + * Calculate the angle between the current position and a designated destination + */ + void calculateMoveAngle(); + + /** + * Handle any object movement + */ + void move(); + + /** + * Returns whether not to make any movement + */ + bool dontMove() const; + + /** + * Ends any current movement + */ + void endMove(); +public: + static ScalpelEngine *_vm; + Common::Point _position; + Common::Point _destination; + Common::Rect _oldBounds; + int _frame; + int _numFrames; + int _frameChange; +public: + Object(); + + /** + * Load the data for the object + */ + void setVisage(int visage, int strip); + + /** + * Sets whether the object is animating + */ + void setAnimMode(bool isAnimating); + + /** + * Starts an object moving to a given destination + */ + void setDestination(const Common::Point &pt); + + /** + * Returns true if an animation is ended + */ + bool isAnimEnded() const; + + /** + * Return true if object is moving + */ + bool isMoving() const; + + /** + * Erase the area the object was previously drawn at, by restoring the background + */ + void erase(); + + /** + * Update the frame + */ + void update(); + + /** + * Remove an object from being displayed + */ + void remove() { _visage.clear(); } +}; + +struct AnimationFrame { + int frame; + int x; + int y; +}; + +class Logo { +private: + ScalpelEngine *_vm; + TLib _lib; + int _counter; + bool _finished; + byte _originalPalette[PALETTE_SIZE]; + byte _palette1[PALETTE_SIZE]; + byte _palette2[PALETTE_SIZE]; + byte _palette3[PALETTE_SIZE]; + Object _objects[4]; + uint _waitFrames; + uint32 _waitStartFrame; + int _animateObject; + uint32 _animateStartFrame; + uint _animateFrameDelay; + const AnimationFrame *_animateFrames; + uint _animateFrame; + + Logo(ScalpelEngine *vm); + ~Logo(); + + void nextFrame(); + + bool finished() const; + + /** + * Wait for a number of frames. Note that the frame count in _events is + * not the same as the number of calls to nextFrame(). + */ + void waitFrames(uint frames); + + /** + * Start an animation sequence. Used for sequences that are described + * one frame at a time because they do unusual things, or run at + * unusual rates. + */ + void startAnimation(uint object, uint frameDelay, const AnimationFrame *frames); + + /** + * Load the background for the scene + */ + void loadBackground(); + + /** + * Fade from the current palette to a new one + */ + void fade(const byte palette[PALETTE_SIZE], int step = 6); +public: + static bool show(ScalpelEngine *vm); +}; + +} // end of namespace TsAGE +} // end of namespace Scalpel +} // end of namespace Sherlock + +#endif diff --git a/engines/sherlock/scalpel/tsage/resources.cpp b/engines/sherlock/scalpel/tsage/resources.cpp new file mode 100644 index 0000000000..56b7021563 --- /dev/null +++ b/engines/sherlock/scalpel/tsage/resources.cpp @@ -0,0 +1,373 @@ +/* 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 "common/scummsys.h" +#include "common/memstream.h" +#include "common/stack.h" +#include "sherlock/scalpel/tsage/resources.h" + +namespace Sherlock { +namespace Scalpel { +namespace TsAGE { + +static uint16 bitMasks[4] = {0x1ff, 0x3ff, 0x7ff, 0xfff}; + +uint16 BitReader::readToken() { + assert((numBits >= 9) && (numBits <= 12)); + uint16 result = _remainder; + int bitsLeft = numBits - _bitsLeft; + int bitOffset = _bitsLeft; + _bitsLeft = 0; + + while (bitsLeft >= 0) { + _remainder = readByte(); + result |= _remainder << bitOffset; + bitsLeft -= 8; + bitOffset += 8; + } + + _bitsLeft = -bitsLeft; + _remainder >>= 8 - _bitsLeft; + return result & bitMasks[numBits - 9]; +} + +/*-------------------------------------------------------------------------*/ + +TLib::TLib(const Common::String &filename) : _filename(filename) { + + // If the resource strings list isn't yet loaded, load them + if (_resStrings.size() == 0) { + Common::File f; + if (f.open("tsage.cfg")) { + while (!f.eos()) { + _resStrings.push_back(f.readLine()); + } + f.close(); + } + } + + if (!_file.open(filename)) + error("Missing file %s", filename.c_str()); + + loadIndex(); +} + +TLib::~TLib() { + _resStrings.clear(); +} + +/** + * Load a section index from the given position in the file + */ +void TLib::loadSection(uint32 fileOffset) { + _resources.clear(); + _file.seek(fileOffset); + _sections.fileOffset = fileOffset; + + loadSection(_file, _resources); +} + +struct DecodeReference { + uint16 vWord; + uint8 vByte; +}; + +/** + * Gets a resource from the currently loaded section + */ +Common::SeekableReadStream *TLib::getResource(uint16 id, bool suppressErrors) { + // Scan for an entry for the given Id + ResourceEntry *re = nullptr; + ResourceList::iterator iter; + for (iter = _resources.begin(); iter != _resources.end(); ++iter) { + if ((*iter).id == id) { + re = &(*iter); + break; + } + } + if (!re) { + if (suppressErrors) + return nullptr; + error("Could not find resource Id #%d", id); + } + + if (!re->isCompressed) { + // Read in the resource data and return it + byte *dataP = (byte *)malloc(re->size); + _file.seek(_sections.fileOffset + re->fileOffset); + _file.read(dataP, re->size); + + return new Common::MemoryReadStream(dataP, re->size, DisposeAfterUse::YES); + } + + /* + * Decompress the data block + */ + + _file.seek(_sections.fileOffset + re->fileOffset); + Common::ReadStream *compStream = _file.readStream(re->size); + BitReader bitReader(*compStream); + + byte *dataOut = (byte *)malloc(re->uncompressedSize); + byte *destP = dataOut; + uint bytesWritten = 0; + + uint16 ctrCurrent = 0x102, ctrMax = 0x200; + uint16 word_48050 = 0, currentToken = 0, word_48054 =0; + byte byte_49068 = 0, byte_49069 = 0; + + const uint tableSize = 0x1000; + DecodeReference *table = (DecodeReference *)malloc(tableSize * sizeof(DecodeReference)); + if (!table) + error("[TLib::getResource] Cannot allocate table buffer"); + + for (uint i = 0; i < tableSize; ++i) { + table[i].vByte = table[i].vWord = 0; + } + Common::Stack<uint16> tokenList; + + for (;;) { + // Get the next decode token + uint16 token = bitReader.readToken(); + + // Handle the token + if (token == 0x101) { + // End of compressed stream + break; + } else if (token == 0x100) { + // Reset bit-rate + bitReader.numBits = 9; + ctrMax = 0x200; + ctrCurrent = 0x102; + + // Set variables with next token + currentToken = word_48050 = bitReader.readToken(); + byte_49069 = byte_49068 = (byte)currentToken; + + ++bytesWritten; + assert(bytesWritten <= re->uncompressedSize); + *destP++ = byte_49069; + } else { + word_48054 = word_48050 = token; + + if (token >= ctrCurrent) { + word_48050 = currentToken; + tokenList.push(byte_49068); + } + + while (word_48050 >= 0x100) { + assert(word_48050 < 0x1000); + tokenList.push(table[word_48050].vByte); + word_48050 = table[word_48050].vWord; + } + + byte_49069 = byte_49068 = (byte)word_48050; + tokenList.push(word_48050); + + // Write out any cached tokens + while (!tokenList.empty()) { + ++bytesWritten; + assert(bytesWritten <= re->uncompressedSize); + *destP++ = tokenList.pop(); + } + + assert(ctrCurrent < 0x1000); + table[ctrCurrent].vByte = byte_49069; + table[ctrCurrent].vWord = currentToken; + ++ctrCurrent; + + currentToken = word_48054; + if ((ctrCurrent >= ctrMax) && (bitReader.numBits != 12)) { + // Move to the next higher bit-rate + ++bitReader.numBits; + ctrMax <<= 1; + } + } + } + + free(table); + + assert(bytesWritten == re->uncompressedSize); + delete compStream; + return new Common::MemoryReadStream(dataOut, re->uncompressedSize, DisposeAfterUse::YES); +} + +/** + * Finds the correct section and loads the specified resource within it + */ +Common::SeekableReadStream *TLib::getResource(ResourceType resType, uint16 resNum, uint16 rlbNum, bool suppressErrors) { + SectionList::iterator i = _sections.begin(); + while ((i != _sections.end()) && ((*i).resType != resType || (*i).resNum != resNum)) + ++i; + if (i == _sections.end()) { + if (suppressErrors) + return nullptr; + error("Unknown resource type %d num %d", resType, resNum); + } + + loadSection((*i).fileOffset); + + return getResource(rlbNum, suppressErrors); +} + +/** + * Gets the offset of the start of a resource in the resource file + */ +uint32 TLib::getResourceStart(ResourceType resType, uint16 resNum, uint16 rlbNum, ResourceEntry &entry) { + // Find the correct section + SectionList::iterator i = _sections.begin(); + while ((i != _sections.end()) && ((*i).resType != resType || (*i).resNum != resNum)) + ++i; + if (i == _sections.end()) { + error("Unknown resource type %d num %d", resType, resNum); + } + + // Load in the section index + loadSection((*i).fileOffset); + + // Scan for an entry for the given Id + ResourceEntry *re = nullptr; + ResourceList::iterator iter; + for (iter = _resources.begin(); iter != _resources.end(); ++iter) { + if ((*iter).id == rlbNum) { + re = &(*iter); + break; + } + } + + // Throw an error if no resource was found, or the resource is compressed + if (!re || re->isCompressed) + error("Invalid resource Id #%d", rlbNum); + + // Return the resource entry as well as the file offset + entry = *re; + return _sections.fileOffset + entry.fileOffset; +} + +void TLib::loadIndex() { + uint16 resNum, configId, fileOffset; + + // Load the root resources section + loadSection(0); + + // Get the single resource from it + Common::SeekableReadStream *stream = getResource(0); + + _sections.clear(); + + // Loop through reading the entries + while ((resNum = stream->readUint16LE()) != 0xffff) { + configId = stream->readUint16LE(); + fileOffset = stream->readUint16LE(); + + SectionEntry se; + se.resNum = resNum; + se.resType = (ResourceType)(configId & 0x1f); + se.fileOffset = (((configId >> 5) & 0x7ff) << 16) | fileOffset; + + _sections.push_back(se); + } + + delete stream; +} + +/** + * Retrieves the specified palette resource and returns it's data + * + * @paletteNum Specefies the palette number + */ +void TLib::getPalette(byte palette[PALETTE_SIZE], int paletteNum) { + // Get the specified palette + Common::SeekableReadStream *stream = getResource(RES_PALETTE, paletteNum, 0, true); + if (!stream) + return; + + int startNum = stream->readUint16LE(); + int numEntries = stream->readUint16LE(); + assert((startNum < 256) && ((startNum + numEntries) <= 256)); + stream->skip(2); + + // Copy over the data + stream->read(&palette[startNum * 3], numEntries * 3); + + delete stream; +} + +/** + * Open up the given resource file using a passed file object. If the desired entry is found + * in the index, return the index entry for it, and move the file to the start of the resource + */ +bool TLib::scanIndex(Common::File &f, ResourceType resType, int rlbNum, int resNum, + ResourceEntry &resEntry) { + // Load the root section index + ResourceList resList; + loadSection(f, resList); + + // Loop through the index for the desired entry + ResourceList::iterator iter; + for (iter = resList.begin(); iter != resList.end(); ++iter) { + ResourceEntry &re = *iter; + if (re.id == resNum) { + // Found it, so exit + resEntry = re; + f.seek(re.fileOffset); + return true; + } + } + + // No matching entry found + return false; +} + +/** + * Inner logic for decoding a section index into a passed resource list object + */ +void TLib::loadSection(Common::File &f, ResourceList &resources) { + if (f.readUint32BE() != 0x544D492D) + error("Data block is not valid Rlb data"); + + /*uint8 unknown1 = */f.readByte(); + uint16 numEntries = f.readByte(); + + for (uint i = 0; i < numEntries; ++i) { + uint16 id = f.readUint16LE(); + uint16 size = f.readUint16LE(); + uint16 uncSize = f.readUint16LE(); + uint8 sizeHi = f.readByte(); + uint8 type = f.readByte() >> 5; + assert(type <= 1); + uint32 offset = f.readUint32LE(); + + ResourceEntry re; + re.id = id; + re.fileOffset = offset; + re.isCompressed = type != 0; + re.size = ((sizeHi & 0xF) << 16) | size; + re.uncompressedSize = ((sizeHi & 0xF0) << 12) | uncSize; + + resources.push_back(re); + } +} + +} // end of namespace TsAGE +} // end of namespace Scalpel +} // end of namespace Sherlock diff --git a/engines/sherlock/scalpel/tsage/resources.h b/engines/sherlock/scalpel/tsage/resources.h new file mode 100644 index 0000000000..3e09b6b0b1 --- /dev/null +++ b/engines/sherlock/scalpel/tsage/resources.h @@ -0,0 +1,139 @@ +/* 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 SHERLOCK_SCALPEL_TSAGE_RESOURCES_H +#define SHERLOCK_SCALPEL_TSAGE_RESOURCES_H + +#include "common/scummsys.h" +#include "common/array.h" +#include "common/file.h" +#include "common/list.h" +#include "common/str.h" +#include "common/str-array.h" +#include "common/util.h" +#include "graphics/surface.h" +#include "sherlock/screen.h" + +namespace Sherlock { +namespace Scalpel { +namespace TsAGE { + +// Magic number used by original game to identify valid memory blocks +const uint32 MEMORY_ENTRY_ID = 0xE11DA722; + +const int MEMORY_POOL_SIZE = 1000; + +enum ResourceType { RES_LIBRARY, RES_STRIP, RES_IMAGE, RES_PALETTE, RES_VISAGE, RES_SOUND, RES_MESSAGE, + RES_FONT, RES_POINTER, RES_BANK, RES_SND_DRIVER, RES_PRIORITY, RES_CONTROL, RES_WALKRGNS, + RES_BITMAP, RES_SAVE, RES_SEQUENCE, + // Return to Ringworld specific resource types + RT17, RT18, RT19, RT20, RT21, RT22, RT23, RT24, RT25, RT26, RT27, RT28, RT29, RT30, RT31 +}; + +class SectionEntry { +public: + ResourceType resType; + uint16 resNum; + uint32 fileOffset; + + SectionEntry() { + resType = RES_LIBRARY; + resNum = 0; + fileOffset = 0; + } +}; + +class ResourceEntry { +public: + uint16 id; + bool isCompressed; + uint32 fileOffset; + uint32 size; + uint32 uncompressedSize; + + ResourceEntry() { + id = 0; + isCompressed = false; + fileOffset = 0; + size = 0; + uncompressedSize = 0; + } +}; + +typedef Common::List<ResourceEntry> ResourceList; + +class SectionList : public Common::List<SectionEntry> { +public: + uint32 fileOffset; + + SectionList() { + fileOffset = 0; + } +}; + +class BitReader { +private: + Common::ReadStream &_stream; + uint8 _remainder, _bitsLeft; + byte readByte() { return _stream.eos() ? 0 : _stream.readByte(); } +public: + BitReader(Common::ReadStream &s) : _stream(s) { + numBits = 9; + _remainder = 0; + _bitsLeft = 0; + } + uint16 readToken(); + + int numBits; +}; + +class TLib { +private: + Common::StringArray _resStrings; +private: + Common::File _file; + Common::String _filename; + ResourceList _resources; + SectionList _sections; + + void loadSection(uint32 fileOffset); + void loadIndex(); + + static bool scanIndex(Common::File &f, ResourceType resType, int rlbNum, int resNum, ResourceEntry &resEntry); + static void loadSection(Common::File &f, ResourceList &resources); +public: + TLib(const Common::String &filename); + ~TLib(); + + const Common::String &getFilename() { return _filename; } + const SectionList &getSections() { return _sections; } + Common::SeekableReadStream *getResource(uint16 id, bool suppressErrors = false); + Common::SeekableReadStream *getResource(ResourceType resType, uint16 resNum, uint16 rlbNum, bool suppressErrors = false); + uint32 getResourceStart(ResourceType resType, uint16 resNum, uint16 rlbNum, ResourceEntry &entry); + void getPalette(byte palette[PALETTE_SIZE], int paletteNum); +}; + +} // end of namespace TsAGE +} // end of namespace Scalpel +} // end of namespace Sherlock + +#endif diff --git a/engines/sherlock/scene.cpp b/engines/sherlock/scene.cpp new file mode 100644 index 0000000000..3edac3c27d --- /dev/null +++ b/engines/sherlock/scene.cpp @@ -0,0 +1,1439 @@ +/* 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 "sherlock/scene.h" +#include "sherlock/sherlock.h" +#include "sherlock/screen.h" +#include "sherlock/scalpel/scalpel.h" +#include "sherlock/scalpel/scalpel_people.h" +#include "sherlock/scalpel/scalpel_scene.h" +#include "sherlock/tattoo/tattoo.h" +#include "sherlock/tattoo/tattoo_scene.h" +#include "sherlock/tattoo/tattoo_user_interface.h" + +namespace Sherlock { + +static const int FS_TRANS[8] = { + Scalpel::STOP_UP, Scalpel::STOP_UPRIGHT, Scalpel::STOP_RIGHT, Scalpel::STOP_DOWNRIGHT, + Scalpel::STOP_DOWN, Scalpel::STOP_DOWNLEFT, Scalpel::STOP_LEFT, Scalpel::STOP_UPLEFT +}; + +/*----------------------------------------------------------------*/ + +BgFileHeader::BgFileHeader() { + _numStructs = -1; + _numImages = -1; + _numcAnimations = -1; + _descSize = -1; + _seqSize = -1; + + // Serrated Scalpel + _fill = -1; + + // Rose Tattoo + _scrollSize = -1; + _bytesWritten = -1; + _fadeStyle = -1; + Common::fill(&_palette[0], &_palette[PALETTE_SIZE], 0); +} + +void BgFileHeader::load(Common::SeekableReadStream &s, bool isRoseTattoo) { + _numStructs = s.readUint16LE(); + _numImages = s.readUint16LE(); + _numcAnimations = s.readUint16LE(); + _descSize = s.readUint16LE(); + _seqSize = s.readUint16LE(); + + if (isRoseTattoo) { + _scrollSize = s.readUint16LE(); + _bytesWritten = s.readUint32LE(); + _fadeStyle = s.readByte(); + } else { + _fill = s.readUint16LE(); + + } +} + +/*----------------------------------------------------------------*/ + +void BgFileHeaderInfo::load(Common::SeekableReadStream &s) { + _filesize = s.readUint32LE(); + _maxFrames = s.readByte(); + + char buffer[9]; + s.read(buffer, 9); + _filename = Common::String(buffer); +} + +void BgFileHeaderInfo::load3DO(Common::SeekableReadStream &s) { + _filesize = s.readUint32BE(); + _maxFrames = s.readByte(); + + char buffer[9]; + s.read(buffer, 9); + _filename = Common::String(buffer); + s.skip(2); // only on 3DO! +} + +/*----------------------------------------------------------------*/ + +void Exit::load(Common::SeekableReadStream &s, bool isRoseTattoo) { + if (isRoseTattoo) { + char buffer[41]; + s.read(buffer, 41); + _dest = Common::String(buffer); + } + + left = s.readSint16LE(); + top = s.readSint16LE(); + setWidth(s.readUint16LE()); + setHeight(s.readUint16LE()); + + _image = isRoseTattoo ? s.readByte() : 0; + _scene = s.readSint16LE(); + + if (!isRoseTattoo) + _allow = s.readSint16LE(); + + _newPosition.x = s.readSint16LE(); + _newPosition.y = s.readSint16LE(); + _newFacing = s.readUint16LE(); + + if (isRoseTattoo) + _allow = s.readSint16LE(); +} + +void Exit::load3DO(Common::SeekableReadStream &s) { + left = s.readSint16BE(); + top = s.readSint16BE(); + setWidth(s.readUint16BE()); + setHeight(s.readUint16BE()); + + _image = 0; + _scene = s.readSint16BE(); + + _allow = s.readSint16BE(); + + _newPosition.x = s.readSint16BE(); + _newPosition.y = s.readSint16BE(); + _newFacing = s.readUint16BE(); + s.skip(2); // Filler +} + +/*----------------------------------------------------------------*/ + +void SceneEntry::load(Common::SeekableReadStream &s) { + _startPosition.x = s.readSint16LE(); + _startPosition.y = s.readSint16LE(); + _startDir = s.readByte(); + _allow = s.readByte(); +} + +void SceneEntry::load3DO(Common::SeekableReadStream &s) { + _startPosition.x = s.readSint16BE(); + _startPosition.y = s.readSint16BE(); + _startDir = s.readByte(); + _allow = s.readByte(); +} + +void SceneSound::load(Common::SeekableReadStream &s) { + char buffer[9]; + s.read(buffer, 8); + buffer[8] = '\0'; + + _name = Common::String(buffer); + _priority = s.readByte(); +} + +void SceneSound::load3DO(Common::SeekableReadStream &s) { + load(s); +} + +/*----------------------------------------------------------------*/ + +int ObjectArray::indexOf(const Object &obj) const { + for (uint idx = 0; idx < size(); ++idx) { + if (&(*this)[idx] == &obj) + return idx; + } + + return -1; +} + +/*----------------------------------------------------------------*/ + +void ScaleZone::load(Common::SeekableReadStream &s) { + left = s.readSint16LE(); + top = s.readSint16LE(); + setWidth(s.readUint16LE()); + setHeight(s.readUint16LE()); + + _topNumber = s.readByte(); + _bottomNumber = s.readByte(); +} + +/*----------------------------------------------------------------*/ + +void WalkArray::load(Common::SeekableReadStream &s, bool isRoseTattoo) { + _pointsCount = (int8)s.readByte(); + + for (int idx = 0; idx < _pointsCount; ++idx) { + int x = s.readSint16LE(); + int y = isRoseTattoo ? s.readSint16LE() : s.readByte(); + push_back(Common::Point(x, y)); + } +} + +/*----------------------------------------------------------------*/ + +Scene *Scene::init(SherlockEngine *vm) { + if (vm->getGameID() == GType_SerratedScalpel) + return new Scalpel::ScalpelScene(vm); + else + return new Tattoo::TattooScene(vm); +} + +Scene::Scene(SherlockEngine *vm): _vm(vm) { + _sceneStats = new bool *[SCENES_COUNT]; + _sceneStats[0] = new bool[SCENES_COUNT * 65]; + Common::fill(&_sceneStats[0][0], &_sceneStats[0][SCENES_COUNT * 65], false); + for (int idx = 1; idx < SCENES_COUNT; ++idx) { + _sceneStats[idx] = _sceneStats[idx - 1] + 65; + } + _currentScene = -1; + _goToScene = -1; + _loadingSavedGame = false; + _walkedInScene = false; + _version = 0; + _lzwMode = false; + _invGraphicItems = 0; + _cAnimFramePause = 0; + _restoreFlag = false; + _animating = 0; + _doBgAnimDone = true; + _tempFadeStyle = 0; + _exitZone = -1; + _doBgAnimDone = false; +} + +Scene::~Scene() { + freeScene(); + delete[] _sceneStats[0]; + delete[] _sceneStats; +} + +void Scene::selectScene() { + Events &events = *_vm->_events; + People &people = *_vm->_people; + Screen &screen = *_vm->_screen; + Talk &talk = *_vm->_talk; + UserInterface &ui = *_vm->_ui; + + // Reset fields + ui._windowOpen = ui._infoFlag = false; + ui._menuMode = STD_MODE; + + // Free any previous scene + freeScene(); + + // Load the scene + Common::String sceneFile = Common::String::format("res%02d", _goToScene); + // _rrmName gets set during loadScene() + // _rrmName is for ScalpelScene::startCAnim + _currentScene = _goToScene; + _goToScene = -1; + + loadScene(sceneFile); + + // If the fade style was changed from running a movie, then reset it + if (_tempFadeStyle) { + screen._fadeStyle = _tempFadeStyle; + _tempFadeStyle = 0; + } + + people[HOLMES]._walkDest = Common::Point(people[HOLMES]._position.x / FIXED_INT_MULTIPLIER, + people[HOLMES]._position.y / FIXED_INT_MULTIPLIER); + + _restoreFlag = true; + events.clearEvents(); + + // If there were any scripts waiting to be run, but were interrupt by a running + // canimation (probably the last scene's exit canim), clear the _scriptMoreFlag + if (talk._scriptMoreFlag == 3) + talk._scriptMoreFlag = 0; +} + +void Scene::freeScene() { + if (_currentScene == -1) + return; + + _vm->_talk->freeTalkVars(); + _vm->_inventory->freeInv(); + _vm->_music->freeSong(); + _vm->_sound->freeLoadedSounds(); + + if (!_loadingSavedGame) + saveSceneStatus(); + else + _loadingSavedGame = false; + + _sequenceBuffer.clear(); + _descText.clear(); + _walkPoints.clear(); + _cAnim.clear(); + _bgShapes.clear(); + _zones.clear(); + _canimShapes.clear(); + + for (uint idx = 0; idx < _images.size(); ++idx) + delete _images[idx]._images; + _images.clear(); + + _currentScene = -1; +} + +bool Scene::loadScene(const Common::String &filename) { + Events &events = *_vm->_events; + Music &music = *_vm->_music; + People &people = *_vm->_people; + Resources &res = *_vm->_res; + SaveManager &saves = *_vm->_saves; + Screen &screen = *_vm->_screen; + UserInterface &ui = *_vm->_ui; + bool flag; + + _walkedInScene = false; + + // Reset the list of walkable areas + _zones.clear(); + _zones.push_back(Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); + + _descText.clear(); + _comments = ""; + _bgShapes.clear(); + _cAnim.clear(); + _sequenceBuffer.clear(); + + // + // Load the room resource file for the scene + // + + if (!IS_3DO) { + // PC version + Common::String roomFilename = filename + ".rrm"; + _roomFilename = roomFilename; + + flag = _vm->_res->exists(roomFilename); + if (flag) { + Common::SeekableReadStream *rrmStream = _vm->_res->load(roomFilename); + + rrmStream->seek(39); + if (IS_SERRATED_SCALPEL) { + _version = rrmStream->readByte(); + _lzwMode = _version == 10; + } else { + _lzwMode = rrmStream->readByte() > 0; + } + + // Go to header and read it in + rrmStream->seek(rrmStream->readUint32LE()); + + BgFileHeader bgHeader; + bgHeader.load(*rrmStream, IS_ROSE_TATTOO); + _invGraphicItems = bgHeader._numImages + 1; + + if (IS_ROSE_TATTOO) { + screen.initPaletteFade(bgHeader._bytesWritten); + rrmStream->read(screen._cMap, PALETTE_SIZE); + screen.translatePalette(screen._cMap); + + paletteLoaded(); + + // Read in background + if (_lzwMode) { + res.decompress(*rrmStream, (byte *)screen._backBuffer1.getPixels(), SHERLOCK_SCREEN_WIDTH * SHERLOCK_SCREEN_HEIGHT); + } else { + rrmStream->read(screen._backBuffer1.getPixels(), SHERLOCK_SCREEN_WIDTH * SHERLOCK_SCREEN_HEIGHT); + } + } + + // Read in the shapes header info + Common::Array<BgFileHeaderInfo> bgInfo; + bgInfo.resize(bgHeader._numStructs); + + for (uint idx = 0; idx < bgInfo.size(); ++idx) + bgInfo[idx].load(*rrmStream); + + // Read information + if (IS_ROSE_TATTOO) { + // Load shapes + Common::SeekableReadStream *infoStream = !_lzwMode ? rrmStream : res.decompress(*rrmStream, bgHeader._numStructs * 625); + + _bgShapes.resize(bgHeader._numStructs); + for (int idx = 0; idx < bgHeader._numStructs; ++idx) + _bgShapes[idx].load(*infoStream, _vm->getGameID() == GType_RoseTattoo); + + if (_lzwMode) + delete infoStream; + + // Load description text + _descText.resize(bgHeader._descSize); + if (_lzwMode) + res.decompress(*rrmStream, (byte *)&_descText[0], bgHeader._descSize); + else + rrmStream->read(&_descText[0], bgHeader._descSize); + + // Load sequences + _sequenceBuffer.resize(bgHeader._seqSize); + if (_lzwMode) + res.decompress(*rrmStream, &_sequenceBuffer[0], bgHeader._seqSize); + else + rrmStream->read(&_sequenceBuffer[0], bgHeader._seqSize); + } else if (!_lzwMode) { + // Serrated Scalpel uncompressed info + _bgShapes.resize(bgHeader._numStructs); + for (int idx = 0; idx < bgHeader._numStructs; ++idx) + _bgShapes[idx].load(*rrmStream, false); + + if (bgHeader._descSize) { + _descText.resize(bgHeader._descSize); + rrmStream->read(&_descText[0], bgHeader._descSize); + } + + if (bgHeader._seqSize) { + _sequenceBuffer.resize(bgHeader._seqSize); + rrmStream->read(&_sequenceBuffer[0], bgHeader._seqSize); + } + } else { + // Serrated Scalpel compressed info + Common::SeekableReadStream *infoStream; + + // Read shapes + infoStream = Resources::decompressLZ(*rrmStream, bgHeader._numStructs * 569); + + _bgShapes.resize(bgHeader._numStructs); + for (int idx = 0; idx < bgHeader._numStructs; ++idx) + _bgShapes[idx].load(*infoStream, false); + + delete infoStream; + + // Read description texts + if (bgHeader._descSize) { + infoStream = Resources::decompressLZ(*rrmStream, bgHeader._descSize); + + _descText.resize(bgHeader._descSize); + infoStream->read(&_descText[0], bgHeader._descSize); + + delete infoStream; + } + + // Read sequences + if (bgHeader._seqSize) { + infoStream = Resources::decompressLZ(*rrmStream, bgHeader._seqSize); + + _sequenceBuffer.resize(bgHeader._seqSize); + infoStream->read(&_sequenceBuffer[0], bgHeader._seqSize); + + delete infoStream; + } + } + + // Set up the list of images used by the scene + _images.resize(bgHeader._numImages + 1); + for (int idx = 0; idx < bgHeader._numImages; ++idx) { + _images[idx + 1]._filesize = bgInfo[idx]._filesize; + _images[idx + 1]._maxFrames = bgInfo[idx]._maxFrames; + + // Read in the image data + Common::SeekableReadStream *imageStream = _lzwMode ? + res.decompress(*rrmStream, bgInfo[idx]._filesize) : + rrmStream->readStream(bgInfo[idx]._filesize); + + _images[idx + 1]._images = new ImageFile(*imageStream); + + delete imageStream; + } + + // Set up the bgShapes + for (int idx = 0; idx < bgHeader._numStructs; ++idx) { + _bgShapes[idx]._images = _images[_bgShapes[idx]._misc]._images; + _bgShapes[idx]._imageFrame = !_bgShapes[idx]._images ? (ImageFrame *)nullptr : + &(*_bgShapes[idx]._images)[0]; + + _bgShapes[idx]._examine = Common::String(&_descText[_bgShapes[idx]._descOffset]); + _bgShapes[idx]._sequences = &_sequenceBuffer[_bgShapes[idx]._sequenceOffset]; + _bgShapes[idx]._misc = 0; + _bgShapes[idx]._seqCounter = 0; + _bgShapes[idx]._seqCounter2 = 0; + _bgShapes[idx]._seqStack = 0; + _bgShapes[idx]._frameNumber = -1; + _bgShapes[idx]._oldPosition = Common::Point(0, 0); + _bgShapes[idx]._oldSize = Common::Point(1, 1); + } + + // Load in cAnim list + _cAnim.clear(); + if (bgHeader._numcAnimations) { + int animSize = IS_SERRATED_SCALPEL ? 65 : 47; + Common::SeekableReadStream *cAnimStream = _lzwMode ? + res.decompress(*rrmStream, animSize * bgHeader._numcAnimations) : + rrmStream->readStream(animSize * bgHeader._numcAnimations); + + // Load cAnim offset table as well + uint32 *cAnimOffsetTablePtr = new uint32[bgHeader._numcAnimations]; + uint32 *cAnimOffsetPtr = cAnimOffsetTablePtr; + memset(cAnimOffsetTablePtr, 0, bgHeader._numcAnimations * sizeof(uint32)); + if (IS_SERRATED_SCALPEL) { + // Save current stream offset + int32 curOffset = rrmStream->pos(); + rrmStream->seek(44); // Seek to cAnim-Offset-Table + for (uint16 curCAnim = 0; curCAnim < bgHeader._numcAnimations; curCAnim++) { + *cAnimOffsetPtr = rrmStream->readUint32LE(); + cAnimOffsetPtr++; + } + // Seek back to original stream offset + rrmStream->seek(curOffset); + } + // TODO: load offset table for Rose Tattoo as well + + // Go to the start of the cAnimOffsetTable + cAnimOffsetPtr = cAnimOffsetTablePtr; + + _cAnim.resize(bgHeader._numcAnimations); + for (uint idx = 0; idx < _cAnim.size(); ++idx) { + _cAnim[idx].load(*cAnimStream, IS_ROSE_TATTOO, *cAnimOffsetPtr); + cAnimOffsetPtr++; + } + + delete cAnimStream; + delete[] cAnimOffsetTablePtr; + } + + + + // Read in the room bounding areas + int size = rrmStream->readUint16LE(); + Common::SeekableReadStream *boundsStream = !_lzwMode ? rrmStream : + res.decompress(*rrmStream, size); + + _zones.resize(size / 10); + for (uint idx = 0; idx < _zones.size(); ++idx) { + _zones[idx].left = boundsStream->readSint16LE(); + _zones[idx].top = boundsStream->readSint16LE(); + _zones[idx].setWidth(boundsStream->readSint16LE() + 1); + _zones[idx].setHeight(boundsStream->readSint16LE() + 1); + boundsStream->skip(2); // Skip unused scene number field + } + + if (_lzwMode) + delete boundsStream; + + // Ensure we've reached the path version byte + if (rrmStream->readByte() != (IS_SERRATED_SCALPEL ? 254 : 251)) + error("Invalid scene path data"); + + // Load the walk directory and walk data + assert(_zones.size() < MAX_ZONES); + + + for (uint idx1 = 0; idx1 < _zones.size(); ++idx1) { + Common::fill(&_walkDirectory[idx1][0], &_walkDirectory[idx1][MAX_ZONES], 0); + for (uint idx2 = 0; idx2 < _zones.size(); ++idx2) + _walkDirectory[idx1][idx2] = rrmStream->readSint16LE(); + } + + // Read in the walk data + size = rrmStream->readUint16LE(); + Common::SeekableReadStream *walkStream = !_lzwMode ? rrmStream : + res.decompress(*rrmStream, size); + + int startPos = walkStream->pos(); + while ((walkStream->pos() - startPos) < size) { + _walkPoints.push_back(WalkArray()); + _walkPoints[_walkPoints.size() - 1]._fileOffset = walkStream->pos() - startPos; + _walkPoints[_walkPoints.size() - 1].load(*walkStream, IS_ROSE_TATTOO); + } + + if (_lzwMode) + delete walkStream; + + // Translate the file offsets of the walk directory to indexes in the loaded walk data + for (uint idx1 = 0; idx1 < _zones.size(); ++idx1) { + for (uint idx2 = 0; idx2 < _zones.size(); ++idx2) { + int fileOffset = _walkDirectory[idx1][idx2]; + if (fileOffset == -1) + continue; + + uint dataIndex = 0; + while (dataIndex < _walkPoints.size() && _walkPoints[dataIndex]._fileOffset != fileOffset) + ++dataIndex; + assert(dataIndex < _walkPoints.size()); + _walkDirectory[idx1][idx2] = dataIndex; + } + } + + if (IS_ROSE_TATTOO) { + // Read in the entrance + _entrance.load(*rrmStream); + + // Load scale zones + _scaleZones.resize(rrmStream->readByte()); + for (uint idx = 0; idx < _scaleZones.size(); ++idx) + _scaleZones[idx].load(*rrmStream); + } + + // Read in the exits + _exitZone = -1; + int numExits = rrmStream->readByte(); + _exits.resize(numExits); + + for (int idx = 0; idx < numExits; ++idx) + _exits[idx].load(*rrmStream, IS_ROSE_TATTOO); + + if (IS_SERRATED_SCALPEL) + // Read in the entrance + _entrance.load(*rrmStream); + + // Initialize sound list + int numSounds = rrmStream->readByte(); + _sounds.resize(numSounds); + + for (int idx = 0; idx < numSounds; ++idx) + _sounds[idx].load(*rrmStream); + + loadSceneSounds(); + + if (IS_ROSE_TATTOO) { + // Load the object sound list + char buffer[27]; + + _objSoundList.resize(rrmStream->readUint16LE()); + for (uint idx = 0; idx < _objSoundList.size(); ++idx) { + rrmStream->read(buffer, 27); + _objSoundList[idx] = Common::String(buffer); + } + } else { + // Read in palette + rrmStream->read(screen._cMap, PALETTE_SIZE); + screen.translatePalette(screen._cMap); + Common::copy(screen._cMap, screen._cMap + PALETTE_SIZE, screen._sMap); + + // Read in the background + Common::SeekableReadStream *bgStream = !_lzwMode ? rrmStream : + res.decompress(*rrmStream, SHERLOCK_SCREEN_WIDTH * SHERLOCK_SCENE_HEIGHT); + + bgStream->read(screen._backBuffer1.getPixels(), SHERLOCK_SCREEN_WIDTH * SHERLOCK_SCENE_HEIGHT); + + if (_lzwMode) + delete bgStream; + } + + // Backup the image and set the palette + screen._backBuffer2.blitFrom(screen._backBuffer1); + screen.setPalette(screen._cMap); + + delete rrmStream; + } + + } else { + // === 3DO version === + _roomFilename = "rooms/" + filename + ".rrm"; + flag = _vm->_res->exists(_roomFilename); + if (!flag) + error("loadScene: 3DO room data file not found"); + + Common::SeekableReadStream *roomStream = _vm->_res->load(_roomFilename); + uint32 roomStreamSize = roomStream->size(); + + // there should be at least all bytes of the header data + if (roomStreamSize < 128) + error("loadScene: 3DO room data file is too small"); + + // Read 3DO header + roomStream->skip(4); // UINT32: offset graphic data? + uint16 header3DO_numStructs = roomStream->readUint16BE(); + uint16 header3DO_numImages = roomStream->readUint16BE(); + uint16 header3DO_numAnimations = roomStream->readUint16BE(); + roomStream->skip(6); + + uint32 header3DO_bgInfo_offset = roomStream->readUint32BE() + 0x80; + uint32 header3DO_bgInfo_size = roomStream->readUint32BE(); + uint32 header3DO_bgShapes_offset = roomStream->readUint32BE() + 0x80; + uint32 header3DO_bgShapes_size = roomStream->readUint32BE(); + uint32 header3DO_descriptions_offset = roomStream->readUint32BE() + 0x80; + uint32 header3DO_descriptions_size = roomStream->readUint32BE(); + uint32 header3DO_sequence_offset = roomStream->readUint32BE() + 0x80; + uint32 header3DO_sequence_size = roomStream->readUint32BE(); + uint32 header3DO_cAnim_offset = roomStream->readUint32BE() + 0x80; + uint32 header3DO_cAnim_size = roomStream->readUint32BE(); + uint32 header3DO_roomBounding_offset = roomStream->readUint32BE() + 0x80; + uint32 header3DO_roomBounding_size = roomStream->readUint32BE(); + uint32 header3DO_walkDirectory_offset = roomStream->readUint32BE() + 0x80; + uint32 header3DO_walkDirectory_size = roomStream->readUint32BE(); + uint32 header3DO_walkData_offset = roomStream->readUint32BE() + 0x80; + uint32 header3DO_walkData_size = roomStream->readUint32BE(); + uint32 header3DO_exits_offset = roomStream->readUint32BE() + 0x80; + uint32 header3DO_exits_size = roomStream->readUint32BE(); + uint32 header3DO_entranceData_offset = roomStream->readUint32BE() + 0x80; + uint32 header3DO_entranceData_size = roomStream->readUint32BE(); + uint32 header3DO_soundList_offset = roomStream->readUint32BE() + 0x80; + uint32 header3DO_soundList_size = roomStream->readUint32BE(); + //uint32 header3DO_unknown_offset = roomStream->readUint32BE() + 0x80; + //uint32 header3DO_unknown_size = roomStream->readUint32BE(); + roomStream->skip(8); // Skip over unknown offset+size + uint32 header3DO_bgGraphicData_offset = roomStream->readUint32BE() + 0x80; + uint32 header3DO_bgGraphicData_size = roomStream->readUint32BE(); + + // Calculate amount of entries + int32 header3DO_soundList_count = header3DO_soundList_size / 9; + + _invGraphicItems = header3DO_numImages + 1; + + // Verify all offsets + if (header3DO_bgInfo_offset >= roomStreamSize) + error("loadScene: 3DO bgInfo offset points outside of room file"); + if (header3DO_bgInfo_size > (roomStreamSize - header3DO_bgInfo_offset)) + error("loadScene: 3DO bgInfo size goes beyond room file"); + if (header3DO_bgShapes_offset >= roomStreamSize) + error("loadScene: 3DO bgShapes offset points outside of room file"); + if (header3DO_bgShapes_size > (roomStreamSize - header3DO_bgShapes_offset)) + error("loadScene: 3DO bgShapes size goes beyond room file"); + if (header3DO_descriptions_offset >= roomStreamSize) + error("loadScene: 3DO descriptions offset points outside of room file"); + if (header3DO_descriptions_size > (roomStreamSize - header3DO_descriptions_offset)) + error("loadScene: 3DO descriptions size goes beyond room file"); + if (header3DO_sequence_offset >= roomStreamSize) + error("loadScene: 3DO sequence offset points outside of room file"); + if (header3DO_sequence_size > (roomStreamSize - header3DO_sequence_offset)) + error("loadScene: 3DO sequence size goes beyond room file"); + if (header3DO_cAnim_offset >= roomStreamSize) + error("loadScene: 3DO cAnim offset points outside of room file"); + if (header3DO_cAnim_size > (roomStreamSize - header3DO_cAnim_offset)) + error("loadScene: 3DO cAnim size goes beyond room file"); + if (header3DO_roomBounding_offset >= roomStreamSize) + error("loadScene: 3DO roomBounding offset points outside of room file"); + if (header3DO_roomBounding_size > (roomStreamSize - header3DO_roomBounding_offset)) + error("loadScene: 3DO roomBounding size goes beyond room file"); + if (header3DO_walkDirectory_offset >= roomStreamSize) + error("loadScene: 3DO walkDirectory offset points outside of room file"); + if (header3DO_walkDirectory_size > (roomStreamSize - header3DO_walkDirectory_offset)) + error("loadScene: 3DO walkDirectory size goes beyond room file"); + if (header3DO_walkData_offset >= roomStreamSize) + error("loadScene: 3DO walkData offset points outside of room file"); + if (header3DO_walkData_size > (roomStreamSize - header3DO_walkData_offset)) + error("loadScene: 3DO walkData size goes beyond room file"); + if (header3DO_exits_offset >= roomStreamSize) + error("loadScene: 3DO exits offset points outside of room file"); + if (header3DO_exits_size > (roomStreamSize - header3DO_exits_offset)) + error("loadScene: 3DO exits size goes beyond room file"); + if (header3DO_entranceData_offset >= roomStreamSize) + error("loadScene: 3DO entranceData offset points outside of room file"); + if (header3DO_entranceData_size > (roomStreamSize - header3DO_entranceData_offset)) + error("loadScene: 3DO entranceData size goes beyond room file"); + if (header3DO_soundList_offset >= roomStreamSize) + error("loadScene: 3DO soundList offset points outside of room file"); + if (header3DO_soundList_size > (roomStreamSize - header3DO_soundList_offset)) + error("loadScene: 3DO soundList size goes beyond room file"); + if (header3DO_bgGraphicData_offset >= roomStreamSize) + error("loadScene: 3DO bgGraphicData offset points outside of room file"); + if (header3DO_bgGraphicData_size > (roomStreamSize - header3DO_bgGraphicData_offset)) + error("loadScene: 3DO bgGraphicData size goes beyond room file"); + + // === BGINFO === read in the shapes header info + Common::Array<BgFileHeaderInfo> bgInfo; + + uint32 expected3DO_bgInfo_size = header3DO_numStructs * 16; + if (expected3DO_bgInfo_size != header3DO_bgInfo_size) // Security check + error("loadScene: 3DO bgInfo size mismatch"); + + roomStream->seek(header3DO_bgInfo_offset); + bgInfo.resize(header3DO_numStructs); + for (uint idx = 0; idx < bgInfo.size(); ++idx) + bgInfo[idx].load3DO(*roomStream); + + // === BGSHAPES === read in the shapes info + uint32 expected3DO_bgShapes_size = header3DO_numStructs * 588; + if (expected3DO_bgShapes_size != header3DO_bgShapes_size) // Security check + error("loadScene: 3DO bgShapes size mismatch"); + + roomStream->seek(header3DO_bgShapes_offset); + _bgShapes.resize(header3DO_numStructs); + for (int idx = 0; idx < header3DO_numStructs; ++idx) + _bgShapes[idx].load3DO(*roomStream); + + // === DESCRIPTION === read description text + if (header3DO_descriptions_size) { + roomStream->seek(header3DO_descriptions_offset); + _descText.resize(header3DO_descriptions_size); + roomStream->read(&_descText[0], header3DO_descriptions_size); + } + + // === SEQUENCE === read sequence buffer + if (header3DO_sequence_size) { + roomStream->seek(header3DO_sequence_offset); + _sequenceBuffer.resize(header3DO_sequence_size); + roomStream->read(&_sequenceBuffer[0], header3DO_sequence_size); + } + + // === IMAGES === set up the list of images used by the scene + roomStream->seek(header3DO_bgGraphicData_offset); + _images.resize(header3DO_numImages + 1); + for (int idx = 0; idx < header3DO_numImages; ++idx) { + _images[idx + 1]._filesize = bgInfo[idx]._filesize; + _images[idx + 1]._maxFrames = bgInfo[idx]._maxFrames; + + // Read image data into memory + Common::SeekableReadStream *imageStream = roomStream->readStream(bgInfo[idx]._filesize); + + // Load image data into an ImageFile array as room file data + // which is basically a fixed header, followed by a raw cel header, followed by regular cel data + _images[idx + 1]._images = new ImageFile3DO(*imageStream, true); + + delete imageStream; + } + + // === BGSHAPES === Set up the bgShapes + for (int idx = 0; idx < header3DO_numStructs; ++idx) { + _bgShapes[idx]._images = _images[_bgShapes[idx]._misc]._images; + _bgShapes[idx]._imageFrame = !_bgShapes[idx]._images ? (ImageFrame *)nullptr : + &(*_bgShapes[idx]._images)[0]; + + _bgShapes[idx]._examine = Common::String(&_descText[_bgShapes[idx]._descOffset]); + _bgShapes[idx]._sequences = &_sequenceBuffer[_bgShapes[idx]._sequenceOffset]; + _bgShapes[idx]._misc = 0; + _bgShapes[idx]._seqCounter = 0; + _bgShapes[idx]._seqCounter2 = 0; + _bgShapes[idx]._seqStack = 0; + _bgShapes[idx]._frameNumber = -1; + _bgShapes[idx]._oldPosition = Common::Point(0, 0); + _bgShapes[idx]._oldSize = Common::Point(1, 1); + } + + // === CANIM === read cAnim list + _cAnim.clear(); + if (header3DO_numAnimations) { + roomStream->seek(header3DO_cAnim_offset); + Common::SeekableReadStream *cAnimStream = roomStream->readStream(header3DO_cAnim_size); + + uint32 *cAnimOffsetTablePtr = new uint32[header3DO_numAnimations]; + uint32 *cAnimOffsetPtr = cAnimOffsetTablePtr; + uint32 cAnimOffset = 0; + memset(cAnimOffsetTablePtr, 0, header3DO_numAnimations * sizeof(uint32)); + + // Seek to end of graphics data and load cAnim offset table from there + roomStream->seek(header3DO_bgGraphicData_offset + header3DO_bgGraphicData_size); + for (uint16 curCAnim = 0; curCAnim < header3DO_numAnimations; curCAnim++) { + cAnimOffset = roomStream->readUint32BE(); + if (cAnimOffset >= roomStreamSize) + error("loadScene: 3DO cAnim entry offset points outside of room file"); + + *cAnimOffsetPtr = cAnimOffset; + cAnimOffsetPtr++; + } + + // Go to the start of the cAnimOffsetTable + cAnimOffsetPtr = cAnimOffsetTablePtr; + + _cAnim.resize(header3DO_numAnimations); + for (uint idx = 0; idx < _cAnim.size(); ++idx) { + _cAnim[idx].load3DO(*cAnimStream, *cAnimOffsetPtr); + cAnimOffsetPtr++; + } + + delete cAnimStream; + delete[] cAnimOffsetTablePtr; + } + + // === BOUNDING AREAS === Read in the room bounding areas + int roomBoundingCount = header3DO_roomBounding_size / 12; + uint32 expected3DO_roomBounding_size = roomBoundingCount * 12; + if (expected3DO_roomBounding_size != header3DO_roomBounding_size) + error("loadScene: 3DO roomBounding size mismatch"); + + roomStream->seek(header3DO_roomBounding_offset); + _zones.resize(roomBoundingCount); + for (uint idx = 0; idx < _zones.size(); ++idx) { + _zones[idx].left = roomStream->readSint16BE(); + _zones[idx].top = roomStream->readSint16BE(); + _zones[idx].setWidth(roomStream->readSint16BE() + 1); + _zones[idx].setHeight(roomStream->readSint16BE() + 1); + roomStream->skip(4); // skip UINT32 + } + + // === WALK DIRECTORY === Load the walk directory + uint32 expected3DO_walkDirectory_size = _zones.size() * _zones.size() * 2; + if (expected3DO_walkDirectory_size != header3DO_walkDirectory_size) + error("loadScene: 3DO walkDirectory size mismatch"); + + roomStream->seek(header3DO_walkDirectory_offset); + assert(_zones.size() < MAX_ZONES); + for (uint idx1 = 0; idx1 < _zones.size(); ++idx1) { + for (uint idx2 = 0; idx2 < _zones.size(); ++idx2) + _walkDirectory[idx1][idx2] = roomStream->readSint16BE(); + } + + // === WALK DATA === Read in the walk data + roomStream->seek(header3DO_walkData_offset); + + int startPos = roomStream->pos(); + while ((roomStream->pos() - startPos) < (int)header3DO_walkData_size) { + _walkPoints.push_back(WalkArray()); + _walkPoints[_walkPoints.size() - 1]._fileOffset = roomStream->pos() - startPos; + _walkPoints[_walkPoints.size() - 1].load(*roomStream, false); + } + + // Translate the file offsets of the walk directory to indexes in the loaded walk data + for (uint idx1 = 0; idx1 < _zones.size(); ++idx1) { + for (uint idx2 = 0; idx2 < _zones.size(); ++idx2) { + int fileOffset = _walkDirectory[idx1][idx2]; + if (fileOffset == -1) + continue; + + uint dataIndex = 0; + while (dataIndex < _walkPoints.size() && _walkPoints[dataIndex]._fileOffset != fileOffset) + ++dataIndex; + assert(dataIndex < _walkPoints.size()); + _walkDirectory[idx1][idx2] = dataIndex; + } + } + + // === EXITS === Read in the exits + roomStream->seek(header3DO_exits_offset); + + int exitsCount = header3DO_exits_size / 20; + + _exitZone = -1; + _exits.resize(exitsCount); + for (int idx = 0; idx < exitsCount; ++idx) + _exits[idx].load3DO(*roomStream); + + // === ENTRANCE === Read in the entrance + if (header3DO_entranceData_size != 8) + error("loadScene: 3DO entranceData size mismatch"); + + roomStream->seek(header3DO_entranceData_offset); + _entrance.load3DO(*roomStream); + + // === SOUND LIST === Initialize sound list + roomStream->seek(header3DO_soundList_offset); + _sounds.resize(header3DO_soundList_count); + for (int idx = 0; idx < header3DO_soundList_count; ++idx) + _sounds[idx].load3DO(*roomStream); + + delete roomStream; + + // === BACKGROUND PICTURE === + // load from file rooms\[filename].bg + // it's uncompressed 15-bit RGB555 data + + Common::String roomBackgroundFilename = "rooms/" + filename + ".bg"; + flag = _vm->_res->exists(roomBackgroundFilename); + if (!flag) + error("loadScene: 3DO room background file not found (%s)", roomBackgroundFilename.c_str()); + + Common::File roomBackgroundStream; + if (!roomBackgroundStream.open(roomBackgroundFilename)) + error("Could not open file - %s", roomBackgroundFilename.c_str()); + + int totalPixelCount = SHERLOCK_SCREEN_WIDTH * SHERLOCK_SCENE_HEIGHT; + uint16 *roomBackgroundDataPtr = NULL; + uint16 *pixelSourcePtr = NULL; + uint16 *pixelDestPtr = (uint16 *)screen._backBuffer1.getPixels(); + uint16 curPixel = 0; + uint32 roomBackgroundStreamSize = roomBackgroundStream.size(); + uint32 expectedBackgroundSize = totalPixelCount * 2; + + // Verify file size of background file + if (expectedBackgroundSize != roomBackgroundStreamSize) + error("loadScene: 3DO room background file not expected size"); + + roomBackgroundDataPtr = new uint16[totalPixelCount]; + roomBackgroundStream.read(roomBackgroundDataPtr, roomBackgroundStreamSize); + roomBackgroundStream.close(); + + // Convert data from RGB555 to RGB565 + pixelSourcePtr = roomBackgroundDataPtr; + for (int pixels = 0; pixels < totalPixelCount; pixels++) { + curPixel = READ_BE_UINT16(pixelSourcePtr++); + + byte curPixelRed = (curPixel >> 10) & 0x1F; + byte curPixelGreen = (curPixel >> 5) & 0x1F; + byte curPixelBlue = curPixel & 0x1F; + *pixelDestPtr = ((curPixelRed << 11) | (curPixelGreen << 6) | (curPixelBlue)); + pixelDestPtr++; + } + + delete[] roomBackgroundDataPtr; + +#if 0 + // code to show the background + screen.blitFrom(screen._backBuffer1); + _vm->_events->wait(10000); +#endif + + // Backup the image + screen._backBuffer2.blitFrom(screen._backBuffer1); + } + + // Handle drawing any on-screen interface + ui.drawInterface(); + + checkSceneStatus(); + + if (!saves._justLoaded) { + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + if (_bgShapes[idx]._type == HIDDEN && _bgShapes[idx]._aType == TALK_EVERY) + _bgShapes[idx].toggleHidden(); + } + + // Check for TURNON objects + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + if (_bgShapes[idx]._type == HIDDEN && (_bgShapes[idx]._flags & TURNON_OBJ)) + _bgShapes[idx].toggleHidden(); + } + + // Check for TURNOFF objects + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + if (_bgShapes[idx]._type != HIDDEN && (_bgShapes[idx]._flags & TURNOFF_OBJ) && + _bgShapes[idx]._type != INVALID) + _bgShapes[idx].toggleHidden(); + if (_bgShapes[idx]._type == HIDE_SHAPE) + // Hiding isn't needed, since objects aren't drawn yet + _bgShapes[idx]._type = HIDDEN; + } + } + + checkSceneFlags(false); + checkInventory(); + + // Handle starting any music for the scene + if (IS_SERRATED_SCALPEL && music._musicOn && music.loadSong(_currentScene)) + music.startSong(); + + // Load walking images if not already loaded + people.loadWalk(); + + // Transition to the scene and setup entrance co-ordinates and animations + transitionToScene(); + + // Player has not yet walked in this scene + _walkedInScene = false; + saves._justLoaded = false; + + events.clearEvents(); + return flag; +} + +void Scene::loadSceneSounds() { + Sound &sound = *_vm->_sound; + + for (uint idx = 0; idx < _sounds.size(); ++idx) + sound.loadSound(_sounds[idx]._name, _sounds[idx]._priority); +} + +void Scene::checkSceneStatus() { + if (_sceneStats[_currentScene][64]) { + for (uint idx = 0; idx < 64; ++idx) { + bool flag = _sceneStats[_currentScene][idx]; + + if (idx < _bgShapes.size()) { + Object &obj = _bgShapes[idx]; + + if (flag) { + // No shape to erase, so flag as hidden + obj._type = HIDDEN; + } else if (obj._images == nullptr || obj._images->size() == 0) { + // No shape + obj._type = NO_SHAPE; + } else { + obj._type = ACTIVE_BG_SHAPE; + } + } else { + // Finished checks + return; + } + } + } +} + +void Scene::saveSceneStatus() { + // Flag any objects for the scene that have been altered + int count = MIN((int)_bgShapes.size(), 64); + for (int idx = 0; idx < count; ++idx) { + Object &obj = _bgShapes[idx]; + _sceneStats[_currentScene][idx] = obj._type == HIDDEN || obj._type == REMOVE + || obj._type == HIDE_SHAPE || obj._type == INVALID; + } + + // Flag scene as having been visited + _sceneStats[_currentScene][64] = true; +} + +void Scene::checkSceneFlags(bool flag) { + SpriteType mode = flag ? HIDE_SHAPE : HIDDEN; + + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &o = _bgShapes[idx]; + + if (o._requiredFlag) { + if (!_vm->readFlags(_bgShapes[idx]._requiredFlag)) { + // Kill object + if (o._type != HIDDEN && o._type != INVALID) { + if (o._images == nullptr || o._images->size() == 0) + // No shape to erase, so flag as hidden + o._type = HIDDEN; + else + // Flag it as needing to be hidden after first erasing it + o._type = mode; + } + } else if (_bgShapes[idx]._requiredFlag > 0) { + // Restore object + if (o._images == nullptr || o._images->size() == 0) + o._type = NO_SHAPE; + else + o._type = ACTIVE_BG_SHAPE; + } + } + } + + // Check inventory for items to remove based on flag changes + for (int idx = 0; idx < _vm->_inventory->_holdings; ++idx) { + InventoryItem &ii = (*_vm->_inventory)[idx]; + if (ii._requiredFlag && !_vm->readFlags(ii._requiredFlag)) { + // Kill object: move it after the active holdings + InventoryItem tempItem = (*_vm->_inventory)[idx]; + _vm->_inventory->insert_at(_vm->_inventory->_holdings, tempItem); + _vm->_inventory->remove_at(idx); + _vm->_inventory->_holdings--; + } + } + + // Check inactive inventory items for ones to reactivate based on flag changes + for (uint idx = _vm->_inventory->_holdings; idx < _vm->_inventory->size(); ++idx) { + InventoryItem &ii = (*_vm->_inventory)[idx]; + if (ii._requiredFlag && _vm->readFlags(ii._requiredFlag)) { + // Restore object: move it after the active holdings + InventoryItem tempItem = (*_vm->_inventory)[idx]; + _vm->_inventory->remove_at(idx); + _vm->_inventory->insert_at(_vm->_inventory->_holdings, tempItem); + _vm->_inventory->_holdings++; + } + } +} + +void Scene::checkInventory() { + for (uint shapeIdx = 0; shapeIdx < _bgShapes.size(); ++shapeIdx) { + for (int invIdx = 0; invIdx < _vm->_inventory->_holdings; ++invIdx) { + if (_bgShapes[shapeIdx]._name.equalsIgnoreCase((*_vm->_inventory)[invIdx]._name)) { + _bgShapes[shapeIdx]._type = INVALID; + break; + } + } + } +} + +void Scene::transitionToScene() { + People &people = *_vm->_people; + SaveManager &saves = *_vm->_saves; + Screen &screen = *_vm->_screen; + Talk &talk = *_vm->_talk; + Point32 &hSavedPos = people._hSavedPos; + int &hSavedFacing = people._hSavedFacing; + + if (hSavedPos.x < 1) { + // No exit information from last scene-check entrance info + if (_entrance._startPosition.x < 1) { + // No entrance info either, so use defaults + if (IS_SERRATED_SCALPEL) { + hSavedPos = Point32(160 * FIXED_INT_MULTIPLIER, 100 * FIXED_INT_MULTIPLIER); + hSavedFacing = 4; + } else { + hSavedPos = people[HOLMES]._position; + hSavedFacing = people[HOLMES]._sequenceNumber; + } + } else { + // setup entrance info + hSavedPos.x = _entrance._startPosition.x * FIXED_INT_MULTIPLIER; + hSavedPos.y = _entrance._startPosition.y * FIXED_INT_MULTIPLIER; + if (IS_SERRATED_SCALPEL) { + hSavedPos.x /= 100; + hSavedPos.y /= 100; + } + + hSavedFacing = _entrance._startDir; + } + } else { + // Exit information exists, translate it to real sequence info + // Note: If a savegame was just loaded, then the data is already correct. + // Otherwise, this is a linked scene or entrance info, and must be translated + if (hSavedFacing < 8 && !saves._justLoaded) { + hSavedFacing = FS_TRANS[hSavedFacing]; + hSavedPos.x *= FIXED_INT_MULTIPLIER; + hSavedPos.y *= FIXED_INT_MULTIPLIER; + } + } + + int cAnimNum = -1; + + if (hSavedFacing < 101) { + // Standard info, so set it + people[HOLMES]._position = hSavedPos; + people[HOLMES]._sequenceNumber = hSavedFacing; + } else { + // It's canimation information + cAnimNum = hSavedFacing - 101; + } + + // Reset positioning for next load + hSavedPos = Common::Point(-1, -1); + hSavedFacing = -1; + + if (cAnimNum != -1) { + // Prevent Holmes from being drawn + people[HOLMES]._position = Common::Point(0, 0); + } + + for (uint objIdx = 0; objIdx < _bgShapes.size(); ++objIdx) { + Object &obj = _bgShapes[objIdx]; + + if (obj._aType > 1 && obj._type != INVALID && obj._type != HIDDEN) { + Common::Point topLeft = obj._position; + Common::Point bottomRight; + + if (obj._type != NO_SHAPE) { + topLeft += obj._imageFrame->_offset; + bottomRight.x = topLeft.x + obj._imageFrame->_frame.w; + bottomRight.y = topLeft.y + obj._imageFrame->_frame.h; + } else { + bottomRight = topLeft + obj._noShapeSize; + } + + if (Common::Rect(topLeft.x, topLeft.y, bottomRight.x, bottomRight.y).contains( + Common::Point(people[HOLMES]._position.x / FIXED_INT_MULTIPLIER, + people[HOLMES]._position.y / FIXED_INT_MULTIPLIER))) { + // Current point is already inside box - impact occurred on + // a previous call. So simply do nothing except talk until the + // player is clear of the box + switch (obj._aType) { + case FLAG_SET: + for (int useNum = 0; useNum < USE_COUNT; ++useNum) { + if (obj._use[useNum]._useFlag) { + if (!_vm->readFlags(obj._use[useNum]._useFlag)) + _vm->setFlags(obj._use[useNum]._useFlag); + } + + if (!talk._talkToAbort) { + for (int nameIdx = 0; nameIdx < NAMES_COUNT; ++nameIdx) { + toggleObject(obj._use[useNum]._names[nameIdx]); + } + } + } + + obj._type = HIDDEN; + break; + + default: + break; + } + } + } + } + + updateBackground(); + + // Actually do the transition + if (screen._fadeStyle) { + if (!IS_3DO) { + // do pixel-transition for PC + screen.randomTransition(); + } else { + // fade in for 3DO + screen.clear(); + screen.fadeIntoScreen3DO(3); + } + } else { + screen.blitFrom(screen._backBuffer1); + } + screen.update(); + + // Start any initial animation for the scene + if (cAnimNum != -1) { + CAnim &c = _cAnim[cAnimNum]; + PositionFacing pt = c._goto[0]; + + c._goto[0].x = c._goto[0].y = -1; + people[HOLMES]._position = Common::Point(0, 0); + + startCAnim(cAnimNum, 1); + c._goto[0] = pt; + } +} + +int Scene::toggleObject(const Common::String &name) { + int count = 0; + + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + if (name.equalsIgnoreCase(_bgShapes[idx]._name)) { + ++count; + _bgShapes[idx].toggleHidden(); + } + } + + return count; +} + +void Scene::updateBackground() { + People &people = *_vm->_people; + + // Update Holmes if he's turned on + for (int idx = 0; idx < MAX_CHARACTERS; ++idx) { + if (people[idx]._type == CHARACTER) + people[idx].adjustSprite(); + } + + // Flag the bg shapes which need to be redrawn + checkBgShapes(); + + // Draw the shapes for the scene + drawAllShapes(); +} + +Exit *Scene::checkForExit(const Common::Rect &r) { + for (uint idx = 0; idx < _exits.size(); ++idx) { + if (_exits[idx].intersects(r)) + return &_exits[idx]; + } + + return nullptr; +} + +int Scene::findBgShape(const Common::Rect &r) { + if (!_doBgAnimDone) + // New frame hasn't been drawn yet + return -1; + + for (int idx = (int)_bgShapes.size() - 1; idx >= 0; --idx) { + Object &o = _bgShapes[idx]; + if (o._type != INVALID && o._type != NO_SHAPE && o._type != HIDDEN + && o._aType <= PERSON) { + if (r.intersects(o.getNewBounds())) + return idx; + } else if (o._type == NO_SHAPE) { + if (r.intersects(o.getNoShapeBounds())) + return idx; + } + } + + return -1; +} + +int Scene::findBgShape(const Common::Point &pt) { + return findBgShape(Common::Rect(pt.x, pt.y, pt.x + 1, pt.y + 1)); +} + +int Scene::checkForZones(const Common::Point &pt, int zoneType) { + int matches = 0; + + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &o = _bgShapes[idx]; + if ((o._aType == zoneType && o._type != INVALID) && o._type != HIDDEN) { + Common::Rect r = o._type == NO_SHAPE ? o.getNoShapeBounds() : o.getNewBounds(); + + if (r.contains(pt)) { + ++matches; + o.setFlagsAndToggles(); + _vm->_talk->talkTo(o._use[0]._target); + } + } + } + + return matches; +} + +int Scene::whichZone(const Common::Point &pt) { + for (uint idx = 0; idx < _zones.size(); ++idx) { + if (_zones[idx].contains(pt)) + return idx; + } + + return -1; +} + +int Scene::closestZone(const Common::Point &pt) { + int dist = 1000; + int zone = -1; + + for (uint idx = 0; idx < _zones.size(); ++idx) { + Common::Point zc((_zones[idx].left + _zones[idx].right) / 2, + (_zones[idx].top + _zones[idx].bottom) / 2); + int d = ABS(zc.x - pt.x) + ABS(zc.y - pt.y); + + if (d < dist) { + // Found a closer zone + dist = d; + zone = idx; + } + } + + return zone; +} + +void Scene::synchronize(Serializer &s) { + if (s.isSaving()) + saveSceneStatus(); + + if (s.isSaving()) { + s.syncAsSint16LE(_currentScene); + } else { + s.syncAsSint16LE(_goToScene); + _loadingSavedGame = true; + } + + for (int sceneNum = 0; sceneNum < SCENES_COUNT; ++sceneNum) { + for (int flag = 0; flag < 65; ++flag) { + s.syncAsByte(_sceneStats[sceneNum][flag]); + } + } +} + +void Scene::checkBgShapes() { + People &people = *_vm->_people; + Person &holmes = people[HOLMES]; + Common::Point pt(holmes._position.x / FIXED_INT_MULTIPLIER, holmes._position.y / FIXED_INT_MULTIPLIER); + + // Iterate through the shapes + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &obj = _bgShapes[idx]; + if (obj._type == ACTIVE_BG_SHAPE || (IS_SERRATED_SCALPEL && obj._type == STATIC_BG_SHAPE)) { + if ((obj._flags & 5) == 1) { + obj._misc = (pt.y < (obj._position.y + obj.frameHeight() - 1)) ? + NORMAL_FORWARD : NORMAL_BEHIND; + } else if (!(obj._flags & OBJ_BEHIND)) { + obj._misc = BEHIND; + } else if (obj._flags & OBJ_FORWARD) { + obj._misc = FORWARD; + } + } + } +} + +} // End of namespace Sherlock diff --git a/engines/sherlock/scene.h b/engines/sherlock/scene.h new file mode 100644 index 0000000000..d4c88350cc --- /dev/null +++ b/engines/sherlock/scene.h @@ -0,0 +1,331 @@ +/* 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 SHERLOCK_SCENE_H +#define SHERLOCK_SCENE_H + +#include "common/scummsys.h" +#include "common/array.h" +#include "common/rect.h" +#include "common/serializer.h" +#include "sherlock/objects.h" +#include "sherlock/resources.h" +#include "sherlock/screen.h" + +namespace Sherlock { + +#define MAX_ZONES 40 +#define INFO_LINE 140 + +class SherlockEngine; + +struct BgFileHeader { + int _numStructs; + int _numImages; + int _numcAnimations; + int _descSize; + int _seqSize; + + // Serrated Scalpel + int _fill; + + // Rose Tattoo + int _scrollSize; + int _bytesWritten; // Size of the main body of the RRM + int _fadeStyle; // Fade style + byte _palette[PALETTE_SIZE]; // Palette + + + BgFileHeader(); + + /** + * Load the data for the object + */ + void load(Common::SeekableReadStream &s, bool isRoseTattoo); +}; + +struct BgFileHeaderInfo { + int _filesize; // How long images are + int _maxFrames; // How many unique frames in object + Common::String _filename; // Filename of object + + /** + * Load the data for the object + */ + void load(Common::SeekableReadStream &s); + void load3DO(Common::SeekableReadStream &s); +}; + +class Exit: public Common::Rect { +public: + int _scene; + int _allow; + Common::Point _newPosition; + int _newFacing; + + Common::String _dest; + int _image; // Arrow image to use + + /** + * Load the data for the object + */ + void load(Common::SeekableReadStream &s, bool isRoseTattoo); + void load3DO(Common::SeekableReadStream &s); +}; + +struct SceneEntry { + Common::Point _startPosition; + int _startDir; + int _allow; + + /** + * Load the data for the object + */ + void load(Common::SeekableReadStream &s); + void load3DO(Common::SeekableReadStream &s); +}; + +struct SceneSound { + Common::String _name; + int _priority; + + /** + * Load the data for the object + */ + void load(Common::SeekableReadStream &s); + void load3DO(Common::SeekableReadStream &s); +}; + +class ObjectArray : public Common::Array<Object> { +public: + /** + * Retuurn the index of the passed object in the array + */ + int indexOf(const Object &obj) const; +}; + +class ScaleZone: public Common::Rect { +public: + int _topNumber; // Numerator of scale size at the top of the zone + int _bottomNumber; // Numerator of scale size at the bottom of the zone + + void load(Common::SeekableReadStream &s); +}; + +class WalkArray : public Common::Array < Common::Point > { +public: + int _pointsCount; + int _fileOffset; + + WalkArray() : _pointsCount(0), _fileOffset(-1) {} + + /** + * Load data for the walk array entry + */ + void load(Common::SeekableReadStream &s, bool isRoseTattoo); +}; + +class Scene { +private: + bool _loadingSavedGame; + + /** + * Loads sounds for the scene + */ + void loadSceneSounds(); + + /** + * Set objects to their current persistent state. This includes things such as + * opening or moving them + */ + void checkSceneStatus(); + + /** + * Checks scene objects against the player's inventory items. If there are any + * matching names, it means the given item has already been picked up, and should + * be hidden in the scene. + */ + void checkInventory(); + + /** + * Set up any entrance co-ordinates or entrance canimations, and then transition + * in the scene + */ + void transitionToScene(); + + /** + * Restores objects to the correct status. This ensures that things like being opened or moved + * will remain the same on future visits to the scene + */ + void saveSceneStatus(); +protected: + SherlockEngine *_vm; + Common::String _roomFilename; + + /** + * Loads the data associated for a given scene. The room resource file's format is: + * BGHEADER: Holds an index for the rest of the file + * STRUCTS: The objects for the scene + * IMAGES: The graphic information for the structures + * + * The _misc field of the structures contains the number of the graphic image + * that it should point to after loading; _misc is then set to 0. + */ + virtual bool loadScene(const Common::String &filename); + + /** + * Checks all the background shapes. If a background shape is animating, + * it will flag it as needing to be drawn. If a non-animating shape is + * colliding with another shape, it will also flag it as needing drawing + */ + virtual void checkBgShapes(); + + /** + * Draw all the shapes, people and NPCs in the correct order + */ + virtual void drawAllShapes() = 0; + + /** + * Called by loadScene when the palette is loaded for Rose Tattoo + */ + virtual void paletteLoaded() {} + + Scene(SherlockEngine *vm); +public: + int _currentScene; + int _goToScene; + bool **_sceneStats; + bool _walkedInScene; + int _version; + bool _lzwMode; + int _invGraphicItems; + Common::String _comments; + Common::Array<char> _descText; + Common::Array<Common::Rect> _zones; + Common::Array<Object> _bgShapes; + Common::Array<CAnim> _cAnim; + Common::Array<byte> _sequenceBuffer; + Common::Array<SceneImage> _images; + int _walkDirectory[MAX_ZONES][MAX_ZONES]; + Common::Array<WalkArray> _walkPoints; + Common::Array<Exit> _exits; + int _exitZone; + SceneEntry _entrance; + Common::Array<SceneSound> _sounds; + ObjectArray _canimShapes; + Common::Array<ScaleZone> _scaleZones; + Common::StringArray _objSoundList; + bool _restoreFlag; + int _animating; + bool _doBgAnimDone; + int _tempFadeStyle; + int _cAnimFramePause; +public: + static Scene *init(SherlockEngine *vm); + virtual ~Scene(); + + /** + * Handles loading the scene specified by _goToScene + */ + void selectScene(); + + /** + * Fres all the graphics and other dynamically allocated data for the scene + */ + void freeScene(); + + /** + * Check the scene's objects against the game flags. If false is passed, + * it means the scene has just been loaded. A value of true means that the scene + * is in use (ie. not just loaded) + */ + void checkSceneFlags(bool mode); + + /** + * Check whether the passed area intersects with one of the scene's exits + */ + Exit *checkForExit(const Common::Rect &r); + + /** + * Scans through the object list to find one with a matching name, and will + * call toggleHidden with all matches found. Returns the numer of matches found + */ + int toggleObject(const Common::String &name); + + /** + * Attempts to find a background shape within the passed bounds. If found, + * it will return the shape number, or -1 on failure. + */ + int findBgShape(const Common::Rect &r); + + /** + * Attempts to find a background shape within the passed bounds. If found, + * it will return the shape number, or -1 on failure. + */ + int findBgShape(const Common::Point &pt); + + /** + * Checks to see if the given position in the scene belongs to a given zone type. + * If it is, the zone is activated and used just like a TAKL zone or aFLAG_SET zone. + */ + int checkForZones(const Common::Point &pt, int zoneType); + + /** + * Check which zone the the given position is located in. + */ + int whichZone(const Common::Point &pt); + + /** + * Returns the index of the closest zone to a given point. + */ + int closestZone(const Common::Point &pt); + + /** + * Synchronize the data for a savegame + */ + virtual void synchronize(Serializer &s); +public: + /** + * Draw all objects and characters. + */ + virtual void doBgAnim() = 0; + + /** + * Update the screen back buffer with all of the scene objects which need + * to be drawn + */ + virtual void updateBackground(); + + /** + * Attempt to start a canimation sequence. It will load the requisite graphics, and + * then copy the canim object into the _canimShapes array to start the animation. + * + * @param cAnimNum The canim object within the current scene + * @param playRate Play rate. 0 is invalid; 1=normal speed, 2=1/2 speed, etc. + * A negative playRate can also be specified to play the animation in reverse + */ + virtual int startCAnim(int cAnimNum, int playRate = 1) = 0; +}; + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/screen.cpp b/engines/sherlock/screen.cpp new file mode 100644 index 0000000000..fdc10fdf33 --- /dev/null +++ b/engines/sherlock/screen.cpp @@ -0,0 +1,591 @@ +/* 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 "sherlock/screen.h" +#include "sherlock/sherlock.h" +#include "common/system.h" +#include "common/util.h" +#include "graphics/palette.h" + +namespace Sherlock { + +Screen::Screen(SherlockEngine *vm) : Surface(g_system->getWidth(), g_system->getHeight()), _vm(vm), + _backBuffer1(g_system->getWidth(), g_system->getHeight()), + _backBuffer2(g_system->getWidth(), g_system->getHeight()), + _backBuffer(&_backBuffer1) { + _transitionSeed = 1; + _fadeStyle = false; + Common::fill(&_cMap[0], &_cMap[PALETTE_SIZE], 0); + Common::fill(&_sMap[0], &_sMap[PALETTE_SIZE], 0); + Common::fill(&_tMap[0], &_tMap[PALETTE_SIZE], 0); + + // Set up the initial font + setFont(IS_SERRATED_SCALPEL ? 1 : 4); + + // Rose Tattoo specific fields + _fadeBytesRead = _fadeBytesToRead = 0; + _oldFadePercent = 0; + _flushScreen = false; +} + +Screen::~Screen() { + Fonts::free(); +} + +void Screen::update() { + // Merge the dirty rects + mergeDirtyRects(); + + // Loop through copying dirty areas to the physical screen + Common::List<Common::Rect>::iterator i; + for (i = _dirtyRects.begin(); i != _dirtyRects.end(); ++i) { + const Common::Rect &r = *i; + const byte *srcP = (const byte *)getBasePtr(r.left, r.top); + g_system->copyRectToScreen(srcP, _surface.pitch, r.left, r.top, + r.width(), r.height()); + } + + // Signal the physical screen to update + g_system->updateScreen(); + _dirtyRects.clear(); +} + +void Screen::makeAllDirty() { + addDirtyRect(Common::Rect(0, 0, this->w(), this->h())); +} + +void Screen::getPalette(byte palette[PALETTE_SIZE]) { + g_system->getPaletteManager()->grabPalette(palette, 0, PALETTE_COUNT); +} + +void Screen::setPalette(const byte palette[PALETTE_SIZE]) { + g_system->getPaletteManager()->setPalette(palette, 0, PALETTE_COUNT); +} + +int Screen::equalizePalette(const byte palette[PALETTE_SIZE]) { + int total = 0; + byte tempPalette[PALETTE_SIZE]; + getPalette(tempPalette); + + // For any palette component that doesn't already match the given destination + // palette, change by 1 towards the reference palette component + for (int idx = 0; idx < PALETTE_SIZE; ++idx) { + if (tempPalette[idx] > palette[idx]) { + tempPalette[idx] = MAX((int)palette[idx], (int)tempPalette[idx] - 4); + ++total; + } else if (tempPalette[idx] < palette[idx]) { + tempPalette[idx] = MIN((int)palette[idx], (int)tempPalette[idx] + 4); + ++total; + } + } + + if (total > 0) + // Palette changed, so reload it + setPalette(tempPalette); + + return total; +} + +void Screen::fadeToBlack(int speed) { + byte tempPalette[PALETTE_SIZE]; + Common::fill(&tempPalette[0], &tempPalette[PALETTE_SIZE], 0); + + while (equalizePalette(tempPalette)) { + _vm->_events->delay(15 * speed); + } + + setPalette(tempPalette); + fillRect(Common::Rect(0, 0, _surface.w, _surface.h), 0); +} + +void Screen::fadeIn(const byte palette[PALETTE_SIZE], int speed) { + int count = 50; + while (equalizePalette(palette) && --count) { + _vm->_events->delay(15 * speed); + } + + setPalette(palette); +} + +void Screen::addDirtyRect(const Common::Rect &r) { + _dirtyRects.push_back(r); + assert(r.width() > 0 && r.height() > 0); +} + +void Screen::mergeDirtyRects() { + Common::List<Common::Rect>::iterator rOuter, rInner; + + // Process the dirty rect list to find any rects to merge + for (rOuter = _dirtyRects.begin(); rOuter != _dirtyRects.end(); ++rOuter) { + rInner = rOuter; + while (++rInner != _dirtyRects.end()) { + + if ((*rOuter).intersects(*rInner)) { + // these two rectangles overlap or + // are next to each other - merge them + + unionRectangle(*rOuter, *rOuter, *rInner); + + // remove the inner rect from the list + _dirtyRects.erase(rInner); + + // move back to beginning of list + rInner = rOuter; + } + } + } +} + +bool Screen::unionRectangle(Common::Rect &destRect, const Common::Rect &src1, const Common::Rect &src2) { + destRect = src1; + destRect.extend(src2); + + return !destRect.isEmpty(); +} + +void Screen::randomTransition() { + Events &events = *_vm->_events; + const int TRANSITION_MULTIPLIER = 0x15a4e35; + _dirtyRects.clear(); + assert(IS_SERRATED_SCALPEL); + + for (int idx = 0; idx <= 65535 && !_vm->shouldQuit(); ++idx) { + _transitionSeed = _transitionSeed * TRANSITION_MULTIPLIER + 1; + int offset = _transitionSeed & 0xFFFF; + + if (offset < (this->w() * this->h())) + *((byte *)getPixels() + offset) = *((const byte *)_backBuffer->getPixels() + offset); + + if (idx != 0 && (idx % 300) == 0) { + // Ensure there's a full screen dirty rect for the next frame update + if (_dirtyRects.empty()) + addDirtyRect(Common::Rect(0, 0, _surface.w, _surface.h)); + + events.pollEvents(); + events.delay(1); + } + } + + // Make sure everything has been transferred + blitFrom(*_backBuffer); +} + +void Screen::verticalTransition() { + Events &events = *_vm->_events; + + byte table[640]; + Common::fill(&table[0], &table[640], 0); + + for (int yp = 0; yp < this->h(); ++yp) { + for (int xp = 0; xp < this->w(); ++xp) { + int temp = (table[xp] >= (this->h() - 3)) ? this->h() - table[xp] : + _vm->getRandomNumber(3) + 1; + + if (temp) { + blitFrom(_backBuffer1, Common::Point(xp, table[xp]), + Common::Rect(xp, table[xp], xp + 1, table[xp] + temp)); + table[xp] += temp; + } + } + + events.delay(10); + } +} + +void Screen::fadeIntoScreen3DO(int speed) { + Events &events = *_vm->_events; + uint16 *currentScreenBasePtr = (uint16 *)getPixels(); + uint16 *targetScreenBasePtr = (uint16 *)_backBuffer->getPixels(); + uint16 currentScreenPixel = 0; + uint16 targetScreenPixel = 0; + + uint16 currentScreenPixelRed = 0; + uint16 currentScreenPixelGreen = 0; + uint16 currentScreenPixelBlue = 0; + + uint16 targetScreenPixelRed = 0; + uint16 targetScreenPixelGreen = 0; + uint16 targetScreenPixelBlue = 0; + + uint16 screenWidth = this->w(); + uint16 screenHeight = this->h(); + uint16 screenX = 0; + uint16 screenY = 0; + uint16 pixelsChanged = 0; + + _dirtyRects.clear(); + + do { + pixelsChanged = 0; + uint16 *currentScreenPtr = currentScreenBasePtr; + uint16 *targetScreenPtr = targetScreenBasePtr; + + for (screenY = 0; screenY < screenHeight; screenY++) { + for (screenX = 0; screenX < screenWidth; screenX++) { + currentScreenPixel = *currentScreenPtr; + targetScreenPixel = *targetScreenPtr; + + if (currentScreenPixel != targetScreenPixel) { + // pixel doesn't match, adjust accordingly + currentScreenPixelRed = currentScreenPixel & 0xF800; + currentScreenPixelGreen = currentScreenPixel & 0x07E0; + currentScreenPixelBlue = currentScreenPixel & 0x001F; + targetScreenPixelRed = targetScreenPixel & 0xF800; + targetScreenPixelGreen = targetScreenPixel & 0x07E0; + targetScreenPixelBlue = targetScreenPixel & 0x001F; + + if (currentScreenPixelRed != targetScreenPixelRed) { + if (currentScreenPixelRed < targetScreenPixelRed) { + currentScreenPixelRed += 0x0800; + } else { + currentScreenPixelRed -= 0x0800; + } + } + if (currentScreenPixelGreen != targetScreenPixelGreen) { + // Adjust +2/-2 because we are running RGB555 at RGB565 + if (currentScreenPixelGreen < targetScreenPixelGreen) { + currentScreenPixelGreen += 0x0040; + } else { + currentScreenPixelGreen -= 0x0040; + } + } + if (currentScreenPixelBlue != targetScreenPixelBlue) { + if (currentScreenPixelBlue < targetScreenPixelBlue) { + currentScreenPixelBlue += 0x0001; + } else { + currentScreenPixelBlue -= 0x0001; + } + } + *currentScreenPtr = currentScreenPixelRed | currentScreenPixelGreen | currentScreenPixelBlue; + pixelsChanged++; + } + + currentScreenPtr++; + targetScreenPtr++; + } + } + + // Too much considered dirty at the moment + addDirtyRect(Common::Rect(0, 0, screenWidth, screenHeight)); + + events.pollEvents(); + events.delay(10 * speed); + } while ((pixelsChanged) && (!_vm->shouldQuit())); +} + +void Screen::blitFrom3DOcolorLimit(uint16 limitColor) { + uint16 *currentScreenPtr = (uint16 *)getPixels(); + uint16 *targetScreenPtr = (uint16 *)_backBuffer->getPixels(); + uint16 currentScreenPixel = 0; + + uint16 screenWidth = this->w(); + uint16 screenHeight = this->h(); + uint16 screenX = 0; + uint16 screenY = 0; + + uint16 currentScreenPixelRed = 0; + uint16 currentScreenPixelGreen = 0; + uint16 currentScreenPixelBlue = 0; + + uint16 limitPixelRed = limitColor & 0xF800; + uint16 limitPixelGreen = limitColor & 0x07E0; + uint16 limitPixelBlue = limitColor & 0x001F; + + for (screenY = 0; screenY < screenHeight; screenY++) { + for (screenX = 0; screenX < screenWidth; screenX++) { + currentScreenPixel = *targetScreenPtr; + + currentScreenPixelRed = currentScreenPixel & 0xF800; + currentScreenPixelGreen = currentScreenPixel & 0x07E0; + currentScreenPixelBlue = currentScreenPixel & 0x001F; + + if (currentScreenPixelRed < limitPixelRed) + currentScreenPixelRed = limitPixelRed; + if (currentScreenPixelGreen < limitPixelGreen) + currentScreenPixelGreen = limitPixelGreen; + if (currentScreenPixelBlue < limitPixelBlue) + currentScreenPixelBlue = limitPixelBlue; + + *currentScreenPtr = currentScreenPixelRed | currentScreenPixelGreen | currentScreenPixelBlue; + currentScreenPtr++; + targetScreenPtr++; + } + } + + // Too much considered dirty at the moment + addDirtyRect(Common::Rect(0, 0, screenWidth, screenHeight)); +} + +void Screen::restoreBackground(const Common::Rect &r) { + if (r.width() > 0 && r.height() > 0) { + Common::Rect tempRect = r; + tempRect.clip(Common::Rect(0, 0, this->w(), SHERLOCK_SCENE_HEIGHT)); + + if (tempRect.isValidRect()) + _backBuffer1.blitFrom(_backBuffer2, Common::Point(tempRect.left, tempRect.top), tempRect); + } +} + +void Screen::slamArea(int16 xp, int16 yp, int16 width, int16 height) { + slamRect(Common::Rect(xp, yp, xp + width, yp + height)); +} + +void Screen::slamRect(const Common::Rect &r) { + if (r.width() && r.height() > 0) { + Common::Rect tempRect = r; + tempRect.clip(Common::Rect(0, 0, this->w(), this->h())); + + if (tempRect.isValidRect()) + blitFrom(*_backBuffer, Common::Point(tempRect.left, tempRect.top), tempRect); + } +} + +void Screen::flushImage(ImageFrame *frame, const Common::Point &pt, int16 *xp, int16 *yp, + int16 *width, int16 *height) { + Common::Point imgPos = pt + frame->_offset; + Common::Rect newBounds(imgPos.x, imgPos.y, imgPos.x + frame->_frame.w, imgPos.y + frame->_frame.h); + Common::Rect oldBounds(*xp, *yp, *xp + *width, *yp + *height); + + if (!_flushScreen) { + // See if the areas of the old and new overlap, and if so combine the areas + if (newBounds.intersects(oldBounds)) { + Common::Rect mergedBounds = newBounds; + mergedBounds.extend(oldBounds); + mergedBounds.right += 1; + mergedBounds.bottom += 1; + + slamRect(mergedBounds); + } else { + // The two areas are independent, so copy them both + slamRect(newBounds); + slamRect(oldBounds); + } + } + + *xp = newBounds.left; + *yp = newBounds.top; + *width = newBounds.width(); + *height = newBounds.height(); +} + +void Screen::flushScaleImage(ImageFrame *frame, const Common::Point &pt, int16 *xp, int16 *yp, + int16 *width, int16 *height, int scaleVal) { + Common::Point imgPos = pt + frame->_offset; + Common::Rect newBounds(imgPos.x, imgPos.y, imgPos.x + frame->sDrawXSize(scaleVal), + imgPos.y + frame->sDrawYSize(scaleVal)); + Common::Rect oldBounds(*xp, *yp, *xp + *width, *yp + *height); + + if (!_flushScreen) { + // See if the areas of the old and new overlap, and if so combine the areas + if (newBounds.intersects(oldBounds)) { + Common::Rect mergedBounds = newBounds; + mergedBounds.extend(oldBounds); + mergedBounds.right += 1; + mergedBounds.bottom += 1; + + slamRect(mergedBounds); + } else { + // The two areas are independent, so copy them both + slamRect(newBounds); + slamRect(oldBounds); + } + } + + *xp = newBounds.left; + *yp = newBounds.top; + *width = newBounds.width(); + *height = newBounds.height(); +} + +void Screen::flushImage(ImageFrame *frame, const Common::Point &pt, Common::Rect &newBounds, int scaleVal) { + Common::Point newPos, newSize; + + if (scaleVal == 256) + flushImage(frame, pt, &newPos.x, &newPos.y, &newSize.x, &newSize.y); + else + flushScaleImage(frame, pt, &newPos.x, &newPos.y, &newSize.x, &newSize.y, scaleVal); + + // Transfer the pos and size amounts into a single bounds rect + newBounds = Common::Rect(newPos.x, newPos.y, newPos.x + newSize.x, newPos.y + newSize.y); +} + +void Screen::blockMove(const Common::Rect &r, const Common::Point &scrollPos) { + Common::Rect bounds = r; + bounds.translate(scrollPos.x, scrollPos.y); + slamRect(bounds); +} + +void Screen::blockMove(const Common::Point &scrollPos) { + blockMove(Common::Rect(0, 0, w(), h()), scrollPos); +} + +void Screen::print(const Common::Point &pt, byte color, const char *formatStr, ...) { + // Create the string to display + va_list args; + va_start(args, formatStr); + Common::String str = Common::String::vformat(formatStr, args); + va_end(args); + + // Figure out area to draw text in + Common::Point pos = pt; + int width = stringWidth(str); + pos.y--; // Font is always drawing one line higher + if (!pos.x) + // Center text horizontally + pos.x = (this->w() - width) / 2; + + Common::Rect textBounds(pos.x, pos.y, pos.x + width, pos.y + _fontHeight); + if (textBounds.right > this->w()) + textBounds.moveTo(this->w() - width, textBounds.top); + if (textBounds.bottom > this->h()) + textBounds.moveTo(textBounds.left, this->h() - _fontHeight); + + // Write out the string at the given position + writeString(str, Common::Point(textBounds.left, textBounds.top), color); + + // Copy the affected area to the screen + slamRect(textBounds); +} + +void Screen::gPrint(const Common::Point &pt, byte color, const char *formatStr, ...) { + // Create the string to display + va_list args; + va_start(args, formatStr); + Common::String str = Common::String::vformat(formatStr, args); + va_end(args); + + // Print the text + writeString(str, pt, color); +} + +void Screen::writeString(const Common::String &str, const Common::Point &pt, byte overrideColor) { + Fonts::writeString(_backBuffer, str, pt, overrideColor); +} + +void Screen::vgaBar(const Common::Rect &r, int color) { + _backBuffer->fillRect(r, color); + slamRect(r); +} + +void Screen::makeButton(const Common::Rect &bounds, int textX, + const Common::String &str) { + + Surface &bb = *_backBuffer; + bb.fillRect(Common::Rect(bounds.left, bounds.top, bounds.right, bounds.top + 1), BUTTON_TOP); + bb.fillRect(Common::Rect(bounds.left, bounds.top, bounds.left + 1, bounds.bottom), BUTTON_TOP); + bb.fillRect(Common::Rect(bounds.right - 1, bounds.top, bounds.right, bounds.bottom), BUTTON_BOTTOM); + bb.fillRect(Common::Rect(bounds.left + 1, bounds.bottom - 1, bounds.right, bounds.bottom), BUTTON_BOTTOM); + bb.fillRect(Common::Rect(bounds.left + 1, bounds.top + 1, bounds.right - 1, bounds.bottom - 1), BUTTON_MIDDLE); + + gPrint(Common::Point(textX, bounds.top), COMMAND_HIGHLIGHTED, "%c", str[0]); + gPrint(Common::Point(textX + charWidth(str[0]), bounds.top), + COMMAND_FOREGROUND, "%s", str.c_str() + 1); +} + +void Screen::buttonPrint(const Common::Point &pt, byte color, bool slamIt, + const Common::String &str) { + int xStart = pt.x - stringWidth(str) / 2; + + if (color == COMMAND_FOREGROUND) { + // First character needs to be highlighted + if (slamIt) { + print(Common::Point(xStart, pt.y + 1), COMMAND_HIGHLIGHTED, "%c", str[0]); + print(Common::Point(xStart + charWidth(str[0]), pt.y + 1), + COMMAND_FOREGROUND, "%s", str.c_str() + 1); + } else { + gPrint(Common::Point(xStart, pt.y), COMMAND_HIGHLIGHTED, "%c", str[0]); + gPrint(Common::Point(xStart + charWidth(str[0]), pt.y), + COMMAND_FOREGROUND, "%s", str.c_str() + 1); + } + } else if (slamIt) { + print(Common::Point(xStart, pt.y + 1), color, "%s", str.c_str()); + } else { + gPrint(Common::Point(xStart, pt.y), color, "%s", str.c_str()); + } +} + +void Screen::makePanel(const Common::Rect &r) { + _backBuffer->fillRect(r, BUTTON_MIDDLE); + _backBuffer->hLine(r.left, r.top, r.right - 2, BUTTON_TOP); + _backBuffer->hLine(r.left + 1, r.top + 1, r.right - 3, BUTTON_TOP); + _backBuffer->vLine(r.left, r.top, r.bottom - 1, BUTTON_TOP); + _backBuffer->vLine(r.left + 1, r.top + 1, r.bottom - 2, BUTTON_TOP); + + _backBuffer->vLine(r.right - 1, r.top, r.bottom - 1, BUTTON_BOTTOM); + _backBuffer->vLine(r.right - 2, r.top + 1, r.bottom - 2, BUTTON_BOTTOM); + _backBuffer->hLine(r.left, r.bottom - 1, r.right - 1, BUTTON_BOTTOM); + _backBuffer->hLine(r.left + 1, r.bottom - 2, r.right - 1, BUTTON_BOTTOM); +} + +void Screen::makeField(const Common::Rect &r) { + _backBuffer->fillRect(r, BUTTON_MIDDLE); + _backBuffer->hLine(r.left, r.top, r.right - 1, BUTTON_BOTTOM); + _backBuffer->hLine(r.left + 1, r.bottom - 1, r.right - 1, BUTTON_TOP); + _backBuffer->vLine(r.left, r.top + 1, r.bottom - 1, BUTTON_BOTTOM); + _backBuffer->vLine(r.right - 1, r.top + 1, r.bottom - 2, BUTTON_TOP); +} + +void Screen::setDisplayBounds(const Common::Rect &r) { + assert(r.left == 0 && r.top == 0); + _sceneSurface.setPixels(_backBuffer1.getPixels(), r.width(), r.height(), _backBuffer1.getPixelFormat()); + + _backBuffer = &_sceneSurface; +} + +void Screen::resetDisplayBounds() { + _backBuffer = &_backBuffer1; +} + +Common::Rect Screen::getDisplayBounds() { + return (_backBuffer == &_sceneSurface) ? Common::Rect(0, 0, _sceneSurface.w(), _sceneSurface.h()) : + Common::Rect(0, 0, this->w(), this->h()); +} + +void Screen::synchronize(Serializer &s) { + int fontNumb = _fontNumber; + s.syncAsByte(fontNumb); + if (s.isLoading()) + setFont(fontNumb); +} + +void Screen::initPaletteFade(int bytesToRead) { + Common::copy(&_cMap[0], &_cMap[PALETTE_SIZE], &_sMap[0]); + Common::copy(&_cMap[0], &_cMap[PALETTE_SIZE], &_tMap[0]); + + // Set how many bytes need to be read / have been read + _fadeBytesRead = 0; + _fadeBytesToRead = bytesToRead; + _oldFadePercent = 0; +} + +int Screen::fadeRead(Common::SeekableReadStream &stream, byte *buf, int totalSize) { + warning("TODO: fadeRead"); + stream.read(buf, totalSize); + return totalSize; +} + +void Screen::translatePalette(byte palette[PALETTE_SIZE]) { + for (int idx = 0; idx < PALETTE_SIZE; ++idx) + palette[idx] = VGA_COLOR_TRANS(palette[idx]); +} + +} // End of namespace Sherlock diff --git a/engines/sherlock/screen.h b/engines/sherlock/screen.h new file mode 100644 index 0000000000..a4b1a837b8 --- /dev/null +++ b/engines/sherlock/screen.h @@ -0,0 +1,274 @@ +/* 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 SHERLOCK_SCREEN_H +#define SHERLOCK_SCREEN_H + +#include "common/list.h" +#include "common/rect.h" +#include "sherlock/surface.h" +#include "sherlock/resources.h" +#include "sherlock/saveload.h" + +namespace Sherlock { + +#define PALETTE_SIZE 768 +#define PALETTE_COUNT 256 +#define VGA_COLOR_TRANS(x) ((x) * 255 / 63) +#define BG_GREYSCALE_RANGE_END 229 + +enum { + BLACK = 0, + INFO_BLACK = 1, + INFO_FOREGROUND = 11, + INFO_BACKGROUND = 1, + BORDER_COLOR = 237, + INV_FOREGROUND = 14, + INV_BACKGROUND = 1, + COMMAND_HIGHLIGHTED = 10, + COMMAND_FOREGROUND = 15, + COMMAND_BACKGROUND = 4, + COMMAND_NULL = 248, + BUTTON_TOP = 233, + BUTTON_MIDDLE = 244, + BUTTON_BOTTOM = 248, + BUTTON_BACKGROUND = 235, + TALK_FOREGROUND = 12, + TALK_NULL = 16, + PEN_COLOR = 250 +}; + +class SherlockEngine; + +class Screen : public Surface { +private: + SherlockEngine *_vm; + Common::List<Common::Rect> _dirtyRects; + uint32 _transitionSeed; + Surface _sceneSurface; + + // Rose Tattoo fields + int _fadeBytesRead, _fadeBytesToRead; + int _oldFadePercent; +private: + /** + * Merges together overlapping dirty areas of the screen + */ + void mergeDirtyRects(); + + /** + * Returns the union of two dirty area rectangles + */ + bool unionRectangle(Common::Rect &destRect, const Common::Rect &src1, const Common::Rect &src2); +protected: + /** + * Adds a rectangle to the list of modified areas of the screen during the + * current frame + */ + virtual void addDirtyRect(const Common::Rect &r); +public: + Surface _backBuffer1, _backBuffer2; + Surface *_backBuffer; + bool _fadeStyle; + byte _cMap[PALETTE_SIZE]; + byte _sMap[PALETTE_SIZE]; + byte _tMap[PALETTE_SIZE]; + bool _flushScreen; +public: + Screen(SherlockEngine *vm); + virtual ~Screen(); + + /** + * Handles updating any dirty areas of the screen Surface object to the physical screen + */ + void update(); + + /** + * Makes the whole screen dirty, Hack for 3DO movie playing + */ + void makeAllDirty(); + + /** + * Return the currently active palette + */ + void getPalette(byte palette[PALETTE_SIZE]); + + /** + * Set the palette + */ + void setPalette(const byte palette[PALETTE_SIZE]); + + /** + * Fades from the currently active palette to the passed palette + */ + int equalizePalette(const byte palette[PALETTE_SIZE]); + + /** + * Fade out the palette to black + */ + void fadeToBlack(int speed = 2); + + /** + * Fade in a given palette + */ + void fadeIn(const byte palette[PALETTE_SIZE], int speed = 2); + + /** + * Do a random pixel transition in from _backBuffer surface to the screen + */ + void randomTransition(); + + /** + * Transition to the surface from _backBuffer using a vertical transition + */ + void verticalTransition(); + + /** + * Fade backbuffer 1 into screen (3DO RGB!) + */ + void fadeIntoScreen3DO(int speed); + + void blitFrom3DOcolorLimit(uint16 color); + + /** + * Prints the text passed onto the back buffer at the given position and color. + * The string is then blitted to the screen + */ + void print(const Common::Point &pt, byte color, const char *formatStr, ...) GCC_PRINTF(4, 5); + + /** + * Print a strings onto the back buffer without blitting it to the screen + */ + void gPrint(const Common::Point &pt, byte color, const char *formatStr, ...) GCC_PRINTF(4, 5); + + /** + * Copies a section of the second back buffer into the main back buffer + */ + void restoreBackground(const Common::Rect &r); + + /** + * Copies a given area to the screen + */ + void slamArea(int16 xp, int16 yp, int16 width, int16 height); + + /** + * Copies a given area to the screen + */ + void slamRect(const Common::Rect &r); + + /** + * Copy an image from the back buffer to the screen, taking care of both the + * new area covered by the shape as well as the old area, which must be restored + */ + void flushImage(ImageFrame *frame, const Common::Point &pt, int16 *xp, int16 *yp, + int16 *width, int16 *height); + + /** + * Similar to flushImage, this method takes in an extra parameter for the scale proporation, + * which affects the calculated bounds accordingly + */ + void flushScaleImage(ImageFrame *frame, const Common::Point &pt, int16 *xp, int16 *yp, + int16 *width, int16 *height, int scaleVal); + + /** + * Variation of flushImage/flushScaleImage that takes in and updates a rect + */ + void flushImage(ImageFrame *frame, const Common::Point &pt, Common::Rect &newBounds, int scaleVal); + + /** + * Copies data from the back buffer to the screen, taking into account scrolling position + */ + void blockMove(const Common::Rect &r, const Common::Point &scrollPos); + + /** + * Copies the entire screen from the back buffer, taking into account scrolling position + */ + void blockMove(const Common::Point &scorllPos); + + /** + * Fills an area on the back buffer, and then copies it to the screen + */ + void vgaBar(const Common::Rect &r, int color); + + /** + * Draws a button for use in the inventory, talk, and examine dialogs. + */ + void makeButton(const Common::Rect &bounds, int textX, const Common::String &str); + + /** + * Prints an interface command with the first letter highlighted to indicate + * what keyboard shortcut is associated with it + */ + void buttonPrint(const Common::Point &pt, byte color, bool slamIt, const Common::String &str); + + /** + * Draw a panel in the back buffer with a raised area effect around the edges + */ + void makePanel(const Common::Rect &r); + + /** + * Draw a field in the back buffer with a raised area effect around the edges, + * suitable for text input. + */ + void makeField(const Common::Rect &r); + + /** + * Sets the active back buffer pointer to a restricted sub-area of the first back buffer + */ + void setDisplayBounds(const Common::Rect &r); + + /** + * Resets the active buffer pointer to point back to the full first back buffer + */ + void resetDisplayBounds(); + + /** + * Return the size of the current display window + */ + Common::Rect getDisplayBounds(); + + /** + * Synchronize the data for a savegame + */ + void synchronize(Serializer &s); + + /** + * Draws the given string into the back buffer using the images stored in _font + */ + virtual void writeString(const Common::String &str, const Common::Point &pt, byte overrideColor); + + + // Rose Tattoo specific methods + void initPaletteFade(int bytesToRead); + + int fadeRead(Common::SeekableReadStream &stream, byte *buf, int totalSize); + + /** + * Translate a palette from 6-bit RGB values to full 8-bit values suitable for passing + * to the underlying palette manager + */ + static void translatePalette(byte palette[PALETTE_SIZE]); +}; + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/sherlock.cpp b/engines/sherlock/sherlock.cpp new file mode 100644 index 0000000000..aac6986ac8 --- /dev/null +++ b/engines/sherlock/sherlock.cpp @@ -0,0 +1,283 @@ +/* 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 "sherlock/sherlock.h" +#include "sherlock/surface.h" +#include "common/scummsys.h" +#include "common/config-manager.h" +#include "common/debug-channels.h" + +namespace Sherlock { + +SherlockEngine::SherlockEngine(OSystem *syst, const SherlockGameDescription *gameDesc) : + Engine(syst), _gameDescription(gameDesc), _randomSource("Sherlock") { + _animation = nullptr; + _debugger = nullptr; + _events = nullptr; + _fixedText = nullptr; + _inventory = nullptr; + _journal = nullptr; + _map = nullptr; + _music = nullptr; + _people = nullptr; + _res = nullptr; + _saves = nullptr; + _scene = nullptr; + _screen = nullptr; + _sound = nullptr; + _talk = nullptr; + _ui = nullptr; + _useEpilogue2 = false; + _loadGameSlot = -1; + _canLoadSave = false; + _showOriginalSavesDialog = false; + _interactiveFl = true; +} + +SherlockEngine::~SherlockEngine() { + delete _animation; + delete _debugger; + delete _events; + delete _fixedText; + delete _journal; + delete _map; + delete _people; + delete _saves; + delete _scene; + delete _screen; + delete _music; + delete _sound; + delete _talk; + delete _ui; + delete _inventory; + delete _res; +} + +void SherlockEngine::initialize() { + DebugMan.addDebugChannel(kDebugLevelScript, "scripts", "Script debug level"); + DebugMan.addDebugChannel(kDebugLevelAdLibDriver, "AdLib", "AdLib driver debugging"); + DebugMan.addDebugChannel(kDebugLevelMT32Driver, "MT32", "MT32 driver debugging"); + DebugMan.addDebugChannel(kDebugLevelMusic, "Music", "Music debugging"); + + Fonts::setVm(this); + ImageFile::setVm(this); + ImageFile3DO::setVm(this); + BaseObject::setVm(this); + + if (isDemo()) { + Common::File f; + // The interactive demo doesn't have an intro thus doesn't include TITLE.SND + // At the opposite, the non-interactive demo is only the intro. + if (f.exists("TITLE.SND")) + _interactiveFl = false; + } + + _res = new Resources(this); + _animation = new Animation(this); + _debugger = new Debugger(this); + _events = new Events(this); + _fixedText = new FixedText(this); + _inventory = new Inventory(this); + _map = Map::init(this); + _music = new Music(this, _mixer); + _journal = Journal::init(this); + _people = People::init(this); + _saves = new SaveManager(this, _targetName); + _scene = Scene::init(this); + _screen = new Screen(this); + _sound = new Sound(this, _mixer); + _talk = Talk::init(this); + _ui = UserInterface::init(this); + + // Load game settings + loadConfig(); + + if (getPlatform() == Common::kPlatform3DO) { + // Disable portraits on 3DO + // 3DO does not include portrait data + _people->_portraitsOn = false; + } +} + +Common::Error SherlockEngine::run() { + // Initialize the engine + initialize(); + + // Flag for whether to show original saves dialog rather than the ScummVM GMM + _showOriginalSavesDialog = ConfMan.getBool("originalsaveload"); + + // If requested, load a savegame instead of showing the intro + if (ConfMan.hasKey("save_slot")) { + int saveSlot = ConfMan.getInt("save_slot"); + if (saveSlot >= 1 && saveSlot <= MAX_SAVEGAME_SLOTS) + _loadGameSlot = saveSlot; + } + + if (_loadGameSlot != -1) { + _saves->loadGame(_loadGameSlot); + _loadGameSlot = -1; + } else { + do + showOpening(); + while (!shouldQuit() && !_interactiveFl); + } + + while (!shouldQuit()) { + // Prepare for scene, and handle any game-specific scenes. This allows + // for game specific cutscenes or mini-games that aren't standard scenes + startScene(); + if (shouldQuit()) + break; + + // Clear the screen + _screen->clear(); + + // Reset UI flags + _ui->reset(); + + // Reset the data for the player character (Sherlock) + _people->reset(); + + // Initialize and load the scene. + _scene->selectScene(); + + // Scene handling loop + sceneLoop(); + } + + return Common::kNoError; +} + +void SherlockEngine::sceneLoop() { + while (!shouldQuit() && _scene->_goToScene == -1) { + // See if a script needs to be completed from either a goto room code, + // or a script that was interrupted by another script + if (_talk->_scriptMoreFlag == 1 || _talk->_scriptMoreFlag == 3) + _talk->talkTo(_talk->_scriptName); + else + _talk->_scriptMoreFlag = 0; + + // Handle any input from the keyboard or mouse + handleInput(); + + if (_people->_hSavedPos.x == -1) { + _canLoadSave = true; + _scene->doBgAnim(); + _canLoadSave = false; + } + } + + _scene->freeScene(); + _people->freeWalk(); + +} + +void SherlockEngine::handleInput() { + _canLoadSave = true; + _events->pollEventsAndWait(); + _canLoadSave = false; + + // See if a key or mouse button is pressed + _events->setButtonState(); + + _ui->handleInput(); +} + +bool SherlockEngine::readFlags(int flagNum) { + bool value = _flags[ABS(flagNum)]; + if (flagNum < 0) + value = !value; + + return value; +} + +void SherlockEngine::setFlags(int flagNum) { + _flags[ABS(flagNum)] = flagNum >= 0; + + _scene->checkSceneFlags(true); +} + +void SherlockEngine::setFlagsDirect(int flagNum) { + _flags[ABS(flagNum)] = flagNum >= 0; +} + +void SherlockEngine::loadConfig() { + // Load sound settings + syncSoundSettings(); + + ConfMan.registerDefault("font", 1); + + _screen->setFont(ConfMan.getInt("font")); + if (getGameID() == GType_SerratedScalpel) + _screen->_fadeStyle = ConfMan.getBool("fade_style"); + + _ui->_helpStyle = ConfMan.getBool("help_style"); + _ui->_slideWindows = ConfMan.getBool("window_style"); + _people->_portraitsOn = ConfMan.getBool("portraits_on"); +} + +void SherlockEngine::saveConfig() { + ConfMan.setBool("mute", !_sound->_digitized); + ConfMan.setBool("music_mute", !_music->_musicOn); + ConfMan.setBool("speech_mute", !_sound->_voices); + + ConfMan.setInt("font", _screen->fontNumber()); + ConfMan.setBool("fade_style", _screen->_fadeStyle); + ConfMan.setBool("help_style", _ui->_helpStyle); + ConfMan.setBool("window_style", _ui->_slideWindows); + ConfMan.setBool("portraits_on", _people->_portraitsOn); + + ConfMan.flushToDisk(); +} + +void SherlockEngine::syncSoundSettings() { + Engine::syncSoundSettings(); + + // Load sound-related settings + _sound->syncSoundSettings(); + _music->syncMusicSettings(); +} + +void SherlockEngine::synchronize(Serializer &s) { + for (uint idx = 0; idx < _flags.size(); ++idx) + s.syncAsByte(_flags[idx]); +} + +bool SherlockEngine::canLoadGameStateCurrently() { + return _canLoadSave; +} + +bool SherlockEngine::canSaveGameStateCurrently() { + return _canLoadSave; +} + +Common::Error SherlockEngine::loadGameState(int slot) { + _saves->loadGame(slot); + return Common::kNoError; +} + +Common::Error SherlockEngine::saveGameState(int slot, const Common::String &desc) { + _saves->saveGame(slot, desc); + return Common::kNoError; +} + +} // End of namespace Comet diff --git a/engines/sherlock/sherlock.h b/engines/sherlock/sherlock.h new file mode 100644 index 0000000000..d0e7bf2e2c --- /dev/null +++ b/engines/sherlock/sherlock.h @@ -0,0 +1,226 @@ +/* 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 SHERLOCK_HOLMES_H +#define SHERLOCK_HOLMES_H + +#include "common/scummsys.h" +#include "common/array.h" +#include "common/endian.h" +#include "common/hash-str.h" +#include "common/serializer.h" +#include "common/random.h" +#include "common/savefile.h" +#include "common/util.h" +#include "engines/engine.h" +#include "sherlock/animation.h" +#include "sherlock/debugger.h" +#include "sherlock/events.h" +#include "sherlock/fixed_text.h" +#include "sherlock/inventory.h" +#include "sherlock/journal.h" +#include "sherlock/map.h" +#include "sherlock/music.h" +#include "sherlock/people.h" +#include "sherlock/resources.h" +#include "sherlock/saveload.h" +#include "sherlock/scene.h" +#include "sherlock/screen.h" +#include "sherlock/sound.h" +#include "sherlock/talk.h" +#include "sherlock/user_interface.h" + +namespace Sherlock { + +enum { + kDebugLevelScript = 1 << 0, + kDebugLevelAdLibDriver = 2 << 0, + kDebugLevelMT32Driver = 3 << 0, + kDebugLevelMusic = 4 << 0 +}; + +enum GameType { + GType_SerratedScalpel = 0, + GType_RoseTattoo = 1 +}; + +#define SHERLOCK_SCREEN_WIDTH _vm->_screen->w() +#define SHERLOCK_SCREEN_HEIGHT _vm->_screen->h() +#define SHERLOCK_SCENE_HEIGHT (IS_SERRATED_SCALPEL ? 138 : 480) +// TODO: Is this the correct scene count for The Case of the Rose Tattoo? +#define SCENES_COUNT (IS_SERRATED_SCALPEL ? 63 : 101) + +struct SherlockGameDescription; + +class Resource; + +class SherlockEngine : public Engine { +private: + /** + * Main loop for displaying a scene and handling all that occurs within it + */ + void sceneLoop(); + + /** + * Handle all player input + */ + void handleInput(); + + /** + * Load game configuration esttings + */ + void loadConfig(); +protected: + /** + * Does basic initialization of the game engine + */ + virtual void initialize(); + + virtual void showOpening() = 0; + + virtual void startScene() {} + + /** + * Returns a list of features the game itself supports + */ + virtual bool hasFeature(EngineFeature f) const; +public: + const SherlockGameDescription *_gameDescription; + Animation *_animation; + Debugger *_debugger; + Events *_events; + FixedText *_fixedText; + Inventory *_inventory; + Journal *_journal; + Map *_map; + Music *_music; + People *_people; + Resources *_res; + SaveManager *_saves; + Scene *_scene; + Screen *_screen; + Sound *_sound; + Talk *_talk; + UserInterface *_ui; + Common::RandomSource _randomSource; + Common::Array<bool> _flags; + bool _useEpilogue2; + int _loadGameSlot; + bool _canLoadSave; + bool _showOriginalSavesDialog; + bool _interactiveFl; +public: + SherlockEngine(OSystem *syst, const SherlockGameDescription *gameDesc); + virtual ~SherlockEngine(); + + /** + * Main method for running the game + */ + virtual Common::Error run(); + + /** + * Returns true if a savegame can be loaded + */ + virtual bool canLoadGameStateCurrently(); + + /** + * Returns true if the game can be saved + */ + virtual bool canSaveGameStateCurrently(); + + /** + * Called by the GMM to load a savegame + */ + virtual Common::Error loadGameState(int slot); + + /** + * Called by the GMM to save the game + */ + virtual Common::Error saveGameState(int slot, const Common::String &desc); + + /** + * Called by the engine when sound settings are updated + */ + virtual void syncSoundSettings(); + + /** + * Returns whether the version is a demo + */ + virtual bool isDemo() const; + + /** + * Returns the Id of the game + */ + GameType getGameID() const; + + /** + * Returns the platform the game's datafiles are for + */ + Common::Platform getPlatform() const; + + /** + * Return the game's language + */ + Common::Language getLanguage() const; + + /** + * Return a random number + */ + int getRandomNumber(int limit) { return _randomSource.getRandomNumber(limit - 1); } + + /** + * Read the state of a global flag + * @remarks If a negative value is specified, it will return the inverse value + * of the positive flag number + */ + bool readFlags(int flagNum); + + /** + * Sets a global flag to either true or false depending on whether the specified + * flag is positive or negative + */ + void setFlags(int flagNum); + + /** + * Set a global flag to 0 or 1 depending on whether the passed flag is negative or positive. + * @remarks We don't use the global setFlags method because we don't want to check scene flags + */ + void setFlagsDirect(int flagNum); + + /** + * Saves game configuration information + */ + void saveConfig(); + + /** + * Synchronize the data for a savegame + */ + void synchronize(Serializer &s); +}; + +#define IS_ROSE_TATTOO (_vm->getGameID() == GType_RoseTattoo) +#define IS_SERRATED_SCALPEL (_vm->getGameID() == GType_SerratedScalpel) +#define IS_3DO (_vm->getPlatform() == Common::kPlatform3DO) + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/sound.cpp b/engines/sherlock/sound.cpp new file mode 100644 index 0000000000..b46eb67b50 --- /dev/null +++ b/engines/sherlock/sound.cpp @@ -0,0 +1,269 @@ +/* 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 "sherlock/sherlock.h" +#include "sherlock/sound.h" +#include "common/config-manager.h" +#include "audio/audiostream.h" +#include "common/algorithm.h" +#include "audio/mixer.h" +#include "audio/decoders/raw.h" +#include "audio/decoders/aiff.h" +#include "audio/decoders/wave.h" + +namespace Sherlock { + +static const int8 creativeADPCM_ScaleMap[64] = { + 0, 1, 2, 3, 4, 5, 6, 7, 0, -1, -2, -3, -4, -5, -6, -7, + 1, 3, 5, 7, 9, 11, 13, 15, -1, -3, -5, -7, -9, -11, -13, -15, + 2, 6, 10, 14, 18, 22, 26, 30, -2, -6, -10, -14, -18, -22, -26, -30, + 4, 12, 20, 28, 36, 44, 52, 60, -4, -12, -20, -28, -36, -44, -52, -60 +}; + +static const uint8 creativeADPCM_AdjustMap[64] = { + 0, 0, 0, 0, 0, 16, 16, 16, + 0, 0, 0, 0, 0, 16, 16, 16, + 240, 0, 0, 0, 0, 16, 16, 16, + 240, 0, 0, 0, 0, 16, 16, 16, + 240, 0, 0, 0, 0, 16, 16, 16, + 240, 0, 0, 0, 0, 16, 16, 16, + 240, 0, 0, 0, 0, 0, 0, 0, + 240, 0, 0, 0, 0, 0, 0, 0 +}; + +/*----------------------------------------------------------------*/ + +Sound::Sound(SherlockEngine *vm, Audio::Mixer *mixer) : _vm(vm), _mixer(mixer) { + _digitized = false; + _voices = 0; + _diskSoundPlaying = false; + _soundPlaying = false; + _soundIsOn = &_soundPlaying; + _curPriority = 0; + _digiBuf = nullptr; + + _soundOn = true; + _speechOn = true; + + if (IS_3DO) { + // 3DO: we don't need to prepare anything for sound + return; + } + + _vm->_res->addToCache("MUSIC.LIB"); + if (!_vm->_interactiveFl) + _vm->_res->addToCache("TITLE.SND"); + else { + _vm->_res->addToCache("MUSIC.LIB"); + + if (IS_ROSE_TATTOO) { + _vm->_res->addToCache("SOUND.LIB"); + } else { + _vm->_res->addToCache("SND.SND"); + + if (!_vm->isDemo()) { + _vm->_res->addToCache("TITLE.SND"); + _vm->_res->addToCache("EPILOGUE.SND"); + } + } + } +} + +void Sound::syncSoundSettings() { + _digitized = !ConfMan.getBool("mute"); + _voices = !ConfMan.getBool("mute") && !ConfMan.getBool("speech_mute") ? 1 : 0; +} + +void Sound::loadSound(const Common::String &name, int priority) { + // No implementation required in ScummVM + //warning("loadSound"); +} + +byte Sound::decodeSample(byte sample, byte &reference, int16 &scale) { + int16 samp = sample + scale; + int16 ref = 0; + + // clip bad ADPCM-4 sample + samp = CLIP<int16>(samp, 0, 63); + + ref = reference + creativeADPCM_ScaleMap[samp]; + if (ref > 0xff) { + reference = 0xff; + } else { + if (ref < 0x00) { + reference = 0; + } else { + reference = (uint8)(ref & 0xff); + } + } + + scale = (scale + creativeADPCM_AdjustMap[samp]) & 0xff; + return reference; +} + +bool Sound::playSound(const Common::String &name, WaitType waitType, int priority, const char *libraryFilename) { + Resources &res = *_vm->_res; + stopSound(); + + Common::String filename = name; + if (!filename.contains('.')) { + if (!IS_3DO) { + if (IS_SERRATED_SCALPEL) { + filename += ".SND"; + } else { + filename += ".WAV"; + } + } else { + // 3DO uses .aiff extension + filename += ".AIFF"; + if (!filename.contains('/')) { + // if no directory was given, use the room sounds directory + filename = "rooms/sounds/" + filename; + } + } + } + + Common::String libFilename(libraryFilename); + Common::SeekableReadStream *stream = libFilename.empty() ? res.load(filename) : res.load(filename, libFilename); + + Audio::AudioStream *audioStream; + + if (!IS_3DO) { + if (IS_SERRATED_SCALPEL) { + stream->skip(2); + int size = stream->readUint32BE(); + int rate = stream->readUint16BE(); + byte *data = (byte *)malloc(size); + byte *ptr = data; + stream->read(ptr, size); + delete stream; + + assert(size > 2); + + byte *decoded = (byte *)malloc((size - 1) * 2); + + // Holmes uses Creative ADPCM 4-bit data + int counter = 0; + byte reference = ptr[0]; + int16 scale = 0; + + for(int i = 1; i < size; i++) { + decoded[counter++] = decodeSample((ptr[i]>>4)&0x0f, reference, scale); + decoded[counter++] = decodeSample((ptr[i]>>0)&0x0f, reference, scale); + } + + free(data); + +#if 0 + // Debug : used to dump files + Common::DumpFile outFile; + outFile.open(filename); + outFile.write(decoded, (size - 2) * 2); + outFile.flush(); + outFile.close(); +#endif + + audioStream = Audio::makeRawStream(decoded, (size - 2) * 2, rate, Audio::FLAG_UNSIGNED, DisposeAfterUse::YES); + } else { + audioStream = Audio::makeWAVStream(stream, DisposeAfterUse::YES); + } + } else { + // 3DO: AIFF file + audioStream = Audio::makeAIFFStream(stream, DisposeAfterUse::YES); + } + + Audio::SoundHandle effectsHandle = (IS_SERRATED_SCALPEL) ? _scalpelEffectsHandle : getFreeSoundHandle(); + + _mixer->playStream(Audio::Mixer::kPlainSoundType, &effectsHandle, audioStream, -1, Audio::Mixer::kMaxChannelVolume); + _soundPlaying = true; + _curPriority = priority; + + if (waitType == WAIT_RETURN_IMMEDIATELY) { + _diskSoundPlaying = true; + return true; + } + + bool retval = true; + do { + _vm->_events->pollEvents(); + g_system->delayMillis(10); + if ((waitType == WAIT_KBD_OR_FINISH) && _vm->_events->kbHit()) { + retval = false; + break; + } + } while (!_vm->shouldQuit() && _mixer->isSoundHandleActive(effectsHandle)); + + _soundPlaying = false; + _mixer->stopHandle(effectsHandle); + + return retval; +} + +void Sound::playLoadedSound(int bufNum, WaitType waitType) { + if (IS_SERRATED_SCALPEL) { + if (_mixer->isSoundHandleActive(_scalpelEffectsHandle) && (_curPriority > _vm->_scene->_sounds[bufNum]._priority)) + return; + + stopSound(); + } + + playSound(_vm->_scene->_sounds[bufNum]._name, waitType, _vm->_scene->_sounds[bufNum]._priority); +} + +void Sound::freeLoadedSounds() { + // As sounds are played with DisposeAfterUse::YES, stopping the sounds also + // frees them + stopSound(); +} + +void Sound::stopSound() { + if (IS_SERRATED_SCALPEL) { + _mixer->stopHandle(_scalpelEffectsHandle); + } else { + for (int i = 0; i < MAX_MIXER_CHANNELS; i++) + _mixer->stopHandle(_tattooEffectsHandle[i]); + } +} + +void Sound::stopSndFuncPtr(int v1, int v2) { + // TODO + warning("TODO: Sound::stopSndFuncPtr"); +} + +void Sound::freeDigiSound() { + delete[] _digiBuf; + _digiBuf = nullptr; + _diskSoundPlaying = false; + _soundPlaying = false; +} + +Audio::SoundHandle Sound::getFreeSoundHandle() { + for (int i = 0; i < MAX_MIXER_CHANNELS; i++) { + if (!_mixer->isSoundHandleActive(_tattooEffectsHandle[i])) + return _tattooEffectsHandle[i]; + } + + error("getFreeSoundHandle: No sound handle found"); +} + +} // End of namespace Sherlock + diff --git a/engines/sherlock/sound.h b/engines/sherlock/sound.h new file mode 100644 index 0000000000..e82d94bbe0 --- /dev/null +++ b/engines/sherlock/sound.h @@ -0,0 +1,105 @@ +/* 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 SHERLOCK_SOUND_H +#define SHERLOCK_SOUND_H + +#include "common/scummsys.h" +#include "common/str.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" +#include "access/files.h" +#include "audio/midiplayer.h" +#include "audio/midiparser.h" + +namespace Sherlock { + +class SherlockEngine; + +enum WaitType { + WAIT_RETURN_IMMEDIATELY = 0, WAIT_FINISH = 1, WAIT_KBD_OR_FINISH = 2 +}; + +#define MAX_MIXER_CHANNELS 10 + +class Sound { +private: + SherlockEngine *_vm; + Audio::Mixer *_mixer; + Audio::SoundHandle _scalpelEffectsHandle; + Audio::SoundHandle _tattooEffectsHandle[MAX_MIXER_CHANNELS]; + int _curPriority; + + byte decodeSample(byte sample, byte& reference, int16& scale); +public: + bool _digitized; + int _voices; + bool _soundOn; + bool _speechOn; + bool _diskSoundPlaying; + bool _soundPlaying; + bool *_soundIsOn; + byte *_digiBuf; + Common::String _currentSongName, _nextSongName; +public: + Sound(SherlockEngine *vm, Audio::Mixer *mixer); + + /** + * Saves sound-related settings + */ + void syncSoundSettings(); + + /** + * Load a sound + */ + void loadSound(const Common::String &name, int priority); + + /** + * Play the sound in the specified resource + */ + bool playSound(const Common::String &name, WaitType waitType, int priority = 100, const char *libraryFilename = nullptr); + + /** + * Play a previously loaded sound + */ + void playLoadedSound(int bufNum, WaitType waitType); + + /** + * Free any previously loaded sounds + */ + void freeLoadedSounds(); + + /** + * Stop playing any active sound + */ + void stopSound(); + + void stopSndFuncPtr(int v1, int v2); + void freeDigiSound(); + + Audio::SoundHandle getFreeSoundHandle(); +}; + +} // End of namespace Sherlock + +#endif + diff --git a/engines/sherlock/surface.cpp b/engines/sherlock/surface.cpp new file mode 100644 index 0000000000..276c83d14a --- /dev/null +++ b/engines/sherlock/surface.cpp @@ -0,0 +1,300 @@ +/* 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 "sherlock/surface.h" +#include "sherlock/sherlock.h" +#include "sherlock/resources.h" +#include "common/system.h" +#include "graphics/palette.h" + +namespace Sherlock { + +Surface::Surface(uint16 width, uint16 height) : Fonts(), _freePixels(true) { + create(width, height); +} + +Surface::Surface() : Fonts(), _freePixels(false) { +} + +Surface::~Surface() { + if (_freePixels) + _surface.free(); +} + +void Surface::create(uint16 width, uint16 height) { + if (_freePixels) + _surface.free(); + + if (_vm->getPlatform() == Common::kPlatform3DO) { + _surface.create(width, height, Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0)); + } else { + _surface.create(width, height, Graphics::PixelFormat::createFormatCLUT8()); + } + _freePixels = true; +} + +Graphics::PixelFormat Surface::getPixelFormat() { + return _surface.format; +} + +void Surface::blitFrom(const Surface &src) { + blitFrom(src, Common::Point(0, 0)); +} + +void Surface::blitFrom(const ImageFrame &src) { + blitFrom(src._frame, Common::Point(0, 0)); +} + +void Surface::blitFrom(const Graphics::Surface &src) { + blitFrom(src, Common::Point(0, 0)); +} + +void Surface::blitFrom(const Surface &src, const Common::Point &pt) { + blitFrom(src, pt, Common::Rect(0, 0, src._surface.w, src._surface.h)); +} + +void Surface::blitFrom(const ImageFrame &src, const Common::Point &pt) { + blitFrom(src._frame, pt, Common::Rect(0, 0, src._frame.w, src._frame.h)); +} + +void Surface::blitFrom(const Graphics::Surface &src, const Common::Point &pt) { + blitFrom(src, pt, Common::Rect(0, 0, src.w, src.h)); +} + +void Surface::blitFrom(const Graphics::Surface &src, const Common::Point &pt, const Common::Rect &srcBounds) { + Common::Rect srcRect = srcBounds; + Common::Rect destRect(pt.x, pt.y, pt.x + srcRect.width(), pt.y + srcRect.height()); + + if (srcRect.isValidRect() && clip(srcRect, destRect)) { + // Surface is at least partially or completely on-screen + addDirtyRect(destRect); + _surface.copyRectToSurface(src, destRect.left, destRect.top, srcRect); + } +} + +void Surface::blitFrom(const ImageFrame &src, const Common::Point &pt, const Common::Rect &srcBounds) { + blitFrom(src._frame, pt, srcBounds); +} + +void Surface::blitFrom(const Surface &src, const Common::Point &pt, const Common::Rect &srcBounds) { + blitFrom(src._surface, pt, srcBounds); +} + +void Surface::transBlitFrom(const ImageFrame &src, const Common::Point &pt, + bool flipped, int overrideColor, int scaleVal) { + transBlitFrom(src._frame, pt + src._offset, flipped, overrideColor, scaleVal); +} + +void Surface::transBlitFrom(const Surface &src, const Common::Point &pt, + bool flipped, int overrideColor, int scaleVal) { + const Graphics::Surface &s = src._surface; + transBlitFrom(s, pt, flipped, overrideColor, scaleVal); +} + +void Surface::transBlitFrom(const Graphics::Surface &src, const Common::Point &pt, + bool flipped, int overrideColor, int scaleVal) { + if (scaleVal == 256) { + transBlitFromUnscaled(src, pt, flipped, overrideColor); + return; + } + + int scaleX = SCALE_THRESHOLD * SCALE_THRESHOLD / scaleVal; + int scaleY = scaleX; + int scaleXCtr = 0, scaleYCtr = 0; + + for (int yCtr = 0, destY = pt.y; yCtr < src.h && destY < this->h(); ++yCtr) { + // Handle skipping lines if Y scaling + scaleYCtr += scaleY; + + while (scaleYCtr >= SCALE_THRESHOLD && destY < this->h()) { + scaleYCtr -= SCALE_THRESHOLD; + + if (destY >= 0) { + // Handle drawing the line + const byte *pSrc = (const byte *)src.getBasePtr(flipped ? src.w - 1 : 0, yCtr); + byte *pDest = (byte *)getBasePtr(pt.x, destY); + scaleXCtr = 0; + + for (int xCtr = 0, destX = pt.x; xCtr < src.w && destX < this->w(); ++xCtr) { + // Handle horizontal scaling + scaleXCtr += scaleX; + + while (scaleXCtr >= SCALE_THRESHOLD && destX < this->w()) { + scaleXCtr -= SCALE_THRESHOLD; + + // Only handle on-screen pixels + if (destX >= 0 && *pSrc != TRANSPARENCY) + *pDest = *pSrc; + + ++pDest; + ++destX; + } + + pSrc = pSrc + (flipped ? -1 : 1); + } + } + + ++destY; + } + } +} + +void Surface::transBlitFromUnscaled(const Graphics::Surface &src, const Common::Point &pt, + bool flipped, int overrideColor) { + Common::Rect drawRect(0, 0, src.w, src.h); + Common::Rect destRect(pt.x, pt.y, pt.x + src.w, pt.y + src.h); + + // Clip the display area to on-screen + if (!clip(drawRect, destRect)) + // It's completely off-screen + return; + + if (flipped) + drawRect = Common::Rect(src.w - drawRect.right, src.h - drawRect.bottom, + src.w - drawRect.left, src.h - drawRect.top); + + Common::Point destPt(destRect.left, destRect.top); + addDirtyRect(Common::Rect(destPt.x, destPt.y, destPt.x + drawRect.width(), + destPt.y + drawRect.height())); + + switch (src.format.bytesPerPixel) { + case 1: + // 8-bit palettized: Draw loop + assert(_surface.format.bytesPerPixel == 1); // Security check + for (int yp = 0; yp < drawRect.height(); ++yp) { + const byte *srcP = (const byte *)src.getBasePtr( + flipped ? drawRect.right - 1 : drawRect.left, drawRect.top + yp); + byte *destP = (byte *)getBasePtr(destPt.x, destPt.y + yp); + + for (int xp = 0; xp < drawRect.width(); ++xp, ++destP) { + if (*srcP != TRANSPARENCY) + *destP = overrideColor ? overrideColor : *srcP; + + srcP = flipped ? srcP - 1 : srcP + 1; + } + } + break; + case 2: + // 3DO 15-bit RGB565: Draw loop + assert(_surface.format.bytesPerPixel == 2); // Security check + for (int yp = 0; yp < drawRect.height(); ++yp) { + const uint16 *srcP = (const uint16 *)src.getBasePtr( + flipped ? drawRect.right - 1 : drawRect.left, drawRect.top + yp); + uint16 *destP = (uint16 *)getBasePtr(destPt.x, destPt.y + yp); + + for (int xp = 0; xp < drawRect.width(); ++xp, ++destP) { + if (*srcP) // RGB 0, 0, 0 -> transparent on 3DO + *destP = *srcP; // overrideColor ? overrideColor : *srcP; + + srcP = flipped ? srcP - 1 : srcP + 1; + } + } + break; + default: + error("Surface: unsupported bytesperpixel"); + break; + } +} + +void Surface::fillRect(int x1, int y1, int x2, int y2, byte color) { + fillRect(Common::Rect(x1, y1, x2, y2), color); +} + +void Surface::fillRect(const Common::Rect &r, byte color) { + _surface.fillRect(r, color); + addDirtyRect(r); +} + +void Surface::fill(uint16 color) { + _surface.fillRect(Common::Rect(_surface.w, _surface.h), color); +} + +bool Surface::clip(Common::Rect &srcBounds, Common::Rect &destBounds) { + if (destBounds.left >= _surface.w || destBounds.top >= _surface.h || + destBounds.right <= 0 || destBounds.bottom <= 0) + return false; + + // Clip the bounds if necessary to fit on-screen + if (destBounds.right > _surface.w) { + srcBounds.right -= destBounds.right - _surface.w; + destBounds.right = _surface.w; + } + + if (destBounds.bottom > _surface.h) { + srcBounds.bottom -= destBounds.bottom - _surface.h; + destBounds.bottom = _surface.h; + } + + if (destBounds.top < 0) { + srcBounds.top += -destBounds.top; + destBounds.top = 0; + } + + if (destBounds.left < 0) { + srcBounds.left += -destBounds.left; + destBounds.left = 0; + } + + return true; +} + +void Surface::clear() { + fillRect(Common::Rect(0, 0, _surface.w, _surface.h), 0); +} + +void Surface::free() { + if (_freePixels) { + _surface.free(); + _freePixels = false; + } +} + +void Surface::setPixels(byte *pixels, int width, int height, Graphics::PixelFormat pixelFormat) { + _surface.format = pixelFormat; + _surface.w = width; + _surface.h = height; + _surface.pitch = width * pixelFormat.bytesPerPixel; + _surface.setPixels(pixels); +} + +void Surface::writeString(const Common::String &str, const Common::Point &pt, byte overrideColor) { + Fonts::writeString(this, str, pt, overrideColor); +} + +void Surface::writeFancyString(const Common::String &str, const Common::Point &pt, byte overrideColor1, byte overrideColor2) { + writeString(str, Common::Point(pt.x, pt.y), overrideColor1); + writeString(str, Common::Point(pt.x + 1, pt.y), overrideColor1); + writeString(str, Common::Point(pt.x + 2, pt.y), overrideColor1); + writeString(str, Common::Point(pt.x, pt.y + 1), overrideColor1); + writeString(str, Common::Point(pt.x + 2, pt.y + 1), overrideColor1); + writeString(str, Common::Point(pt.x, pt.y + 2), overrideColor1); + writeString(str, Common::Point(pt.x + 1, pt.y + 2), overrideColor1); + writeString(str, Common::Point(pt.x + 2, pt.y + 2), overrideColor1); + writeString(str, Common::Point(pt.x + 1, pt.y + 1), overrideColor2); +} + +void Surface::maskArea(const ImageFrame &src, const Common::Point &pt, int scrollX) { + // TODO + error("TODO: maskArea"); +} + +} // End of namespace Sherlock diff --git a/engines/sherlock/surface.h b/engines/sherlock/surface.h new file mode 100644 index 0000000000..80cf0ff287 --- /dev/null +++ b/engines/sherlock/surface.h @@ -0,0 +1,185 @@ +/* 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 SHERLOCK_GRAPHICS_H +#define SHERLOCK_GRAPHICS_H + +#include "common/rect.h" +#include "common/platform.h" +#include "graphics/surface.h" +#include "sherlock/fonts.h" + +namespace Sherlock { + +#define SCALE_THRESHOLD 0x100 +#define TRANSPARENCY 255 + +struct ImageFrame; + +class Surface: public Fonts { +private: + bool _freePixels; + + /** + * Clips the given source bounds so the passed destBounds will be entirely on-screen + */ + bool clip(Common::Rect &srcBounds, Common::Rect &destBounds); + + /** + * Copy a surface into this one + */ + void blitFrom(const Graphics::Surface &src); + + /** + * Draws a surface at a given position within this surface + */ + void blitFrom(const Graphics::Surface &src, const Common::Point &pt); + + /** + * Draws a sub-section of a surface at a given position within this surface + */ + void blitFrom(const Graphics::Surface &src, const Common::Point &pt, const Common::Rect &srcBounds); + + /** + * Draws a surface at a given position within this surface with transparency + */ + void transBlitFromUnscaled(const Graphics::Surface &src, const Common::Point &pt, bool flipped, + int overrideColor); + +protected: + Graphics::Surface _surface; + + virtual void addDirtyRect(const Common::Rect &r) {} +public: + Surface(uint16 width, uint16 height); + Surface(); + virtual ~Surface(); + + /** + * Sets up an internal surface with the specified dimensions that will be automatically freed + * when the surface object is destroyed + */ + void create(uint16 width, uint16 height); + + Graphics::PixelFormat getPixelFormat(); + + /** + * Copy a surface into this one + */ + void blitFrom(const Surface &src); + + /** + * Copy an image frame into this surface + */ + void blitFrom(const ImageFrame &src); + + /** + * Draws a surface at a given position within this surface + */ + void blitFrom(const Surface &src, const Common::Point &pt); + + /** + * Copy an image frame onto this surface at a given position + */ + void blitFrom(const ImageFrame &src, const Common::Point &pt); + + /** + * Draws a sub-section of a surface at a given position within this surface + */ + void blitFrom(const Surface &src, const Common::Point &pt, const Common::Rect &srcBounds); + + /** + * Copy a sub-area of a source image frame into this surface at a given position + */ + void blitFrom(const ImageFrame &src, const Common::Point &pt, const Common::Rect &srcBounds); + + /** + * Draws an image frame at a given position within this surface with transparency + */ + void transBlitFrom(const ImageFrame &src, const Common::Point &pt, + bool flipped = false, int overrideColor = 0, int scaleVal = 256); + + /** + * Draws a surface at a given position within this surface with transparency + */ + void transBlitFrom(const Surface &src, const Common::Point &pt, + bool flipped = false, int overrideColor = 0, int scaleVal = 256); + + /** + * Draws a surface at a given position within this surface with transparency + */ + void transBlitFrom(const Graphics::Surface &src, const Common::Point &pt, + bool flipped = false, int overrideColor = 0, int scaleVal = 256); + + /** + * Fill a given area of the surface with a given color + */ + void fillRect(int x1, int y1, int x2, int y2, byte color); + + /** + * Fill a given area of the surface with a given color + */ + void fillRect(const Common::Rect &r, byte color); + + void fill(uint16 color); + + void maskArea(const ImageFrame &src, const Common::Point &pt, int scrollX); + + /** + * Clear the surface + */ + void clear(); + + /** + * Free the underlying surface + */ + void free(); + + /** + * Returns true if the surface is empty + */ + bool empty() const { return _surface.getPixels() == nullptr; } + + /** + * Set the pixels for the surface to an existing data block + */ + void setPixels(byte *pixels, int width, int height, Graphics::PixelFormat format); + + /** + * Draws the given string into the back buffer using the images stored in _font + */ + virtual void writeString(const Common::String &str, const Common::Point &pt, byte overrideColor); + void writeFancyString(const Common::String &str, const Common::Point &pt, byte overrideColor1, byte overrideColor2); + + inline uint16 w() const { return _surface.w; } + inline uint16 h() const { return _surface.h; } + inline const byte *getPixels() const { return (const byte *)_surface.getPixels(); } + inline byte *getPixels() { return (byte *)_surface.getPixels(); } + inline byte *getBasePtr(int x, int y) { return (byte *)_surface.getBasePtr(x, y); } + inline const byte *getBasePtr(int x, int y) const { return (const byte *)_surface.getBasePtr(x, y); } + inline void hLine(int x, int y, int x2, uint32 color) { _surface.hLine(x, y, x2, color); } + inline void vLine(int x, int y, int y2, uint32 color) { _surface.vLine(x, y, y2, color); } +}; + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/talk.cpp b/engines/sherlock/talk.cpp new file mode 100644 index 0000000000..050d319cfb --- /dev/null +++ b/engines/sherlock/talk.cpp @@ -0,0 +1,1514 @@ +/* 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 "sherlock/talk.h" +#include "sherlock/sherlock.h" +#include "sherlock/screen.h" +#include "sherlock/scalpel/scalpel_people.h" +#include "sherlock/scalpel/scalpel_talk.h" +#include "sherlock/scalpel/scalpel_user_interface.h" +#include "sherlock/tattoo/tattoo_people.h" +#include "sherlock/tattoo/tattoo_talk.h" + +namespace Sherlock { + +SequenceEntry::SequenceEntry() { + _objNum = 0; + _frameNumber = 0; + _seqTo = 0; +} + +/*----------------------------------------------------------------*/ + +void Statement::load(Common::SeekableReadStream &s, bool isRoseTattoo) { + int length; + + length = s.readUint16LE(); + for (int idx = 0; idx < length - 1; ++idx) + _statement += (char)s.readByte(); + s.readByte(); // Null ending + + length = s.readUint16LE(); + for (int idx = 0; idx < length - 1; ++idx) + _reply += (char)s.readByte(); + s.readByte(); // Null ending + + length = s.readUint16LE(); + for (int idx = 0; idx < length - 1; ++idx) + _linkFile += (char)s.readByte(); + s.readByte(); // Null ending + + length = s.readUint16LE(); + for (int idx = 0; idx < length - 1; ++idx) + _voiceFile += (char)s.readByte(); + s.readByte(); // Null ending + + _required.resize(s.readByte()); + _modified.resize(s.readByte()); + + // Read in flag required/modified data + for (uint idx = 0; idx < _required.size(); ++idx) + _required[idx] = s.readSint16LE(); + for (uint idx = 0; idx < _modified.size(); ++idx) + _modified[idx] = s.readSint16LE(); + + _portraitSide = s.readByte(); + _quotient = s.readUint16LE(); + _journal = isRoseTattoo ? s.readByte() : 0; +} + +/*----------------------------------------------------------------*/ + +TalkHistoryEntry::TalkHistoryEntry() { + Common::fill(&_data[0], &_data[16], false); +} + +/*----------------------------------------------------------------*/ + +TalkSequence::TalkSequence() { + _obj = nullptr; + _frameNumber = 0; + _sequenceNumber = 0; + _seqStack = 0; + _seqTo = 0; + _seqCounter = _seqCounter2 = 0; +} + +/*----------------------------------------------------------------*/ + +Talk *Talk::init(SherlockEngine *vm) { + if (vm->getGameID() == GType_SerratedScalpel) + return new Scalpel::ScalpelTalk(vm); + else + return new Tattoo::TattooTalk(vm); +} + +Talk::Talk(SherlockEngine *vm) : _vm(vm) { + _talkCounter = 0; + _talkToAbort = false; + _openTalkWindow = false; + _speaker = 0; + _talkIndex = 0; + _talkTo = 0; + _scriptSelect = 0; + _converseNum = -1; + _talkStealth = 0; + _talkToFlag = -1; + _moreTalkDown = _moreTalkUp = false; + _scriptMoreFlag = 0; + _scriptSaveIndex = -1; + _opcodes = nullptr; + _opcodeTable = nullptr; + + _charCount = 0; + _line = 0; + _yp = 0; + _wait = 0; + _pauseFlag = false; + _seqCount = 0; + _scriptStart = _scriptEnd = nullptr; + _endStr = _noTextYet = false; + + _talkHistory.resize(IS_ROSE_TATTOO ? 1500 : 500); +} + +void Talk::talkTo(const Common::String &filename) { + Events &events = *_vm->_events; + FixedText &fixedText = *_vm->_fixedText; + Inventory &inv = *_vm->_inventory; + Journal &journal = *_vm->_journal; + People &people = *_vm->_people; + Scene &scene = *_vm->_scene; + Screen &screen = *_vm->_screen; + UserInterface &ui = *_vm->_ui; + Common::Rect savedBounds = screen.getDisplayBounds(); + bool abortFlag = false; + + if (filename.empty()) + // No filename passed, so exit + return; + + // If there any canimations currently running, or a portrait is being cleared, + // save the filename for later executing when the canimation is done + if (scene._canimShapes.size() > 0 || people._clearingThePortrait) { + // Make sure we're not in the middle of a script + if (!_scriptMoreFlag) { + _scriptName = filename; + _scriptSaveIndex = 0; + + // Flag the selection, since we don't yet know which statement yet + _scriptSelect = 100; + _scriptMoreFlag = 3; + } + + return; + } + + // Save the ui mode temporarily and switch to talk mode + MenuMode savedMode = ui._menuMode; + ui._menuMode = TALK_MODE; + + // Turn on the Exit option + ui._endKeyActive = true; + + if (people[HOLMES]._walkCount || (people[HOLMES]._walkTo.size() > 0 && + (IS_SERRATED_SCALPEL || people._allowWalkAbort))) { + // Only interrupt if trying to do an action, and not just if player is walking around the scene + if (people._allowWalkAbort) + abortFlag = true; + + people[HOLMES].gotoStand(); + } + + if (_talkToAbort) + return; + + freeTalkVars(); + + // If any sequences have changed in the prior talk file, restore them + if (_savedSequences.size() > 0) { + for (uint idx = 0; idx < _savedSequences.size(); ++idx) { + SequenceEntry &ss = _savedSequences[idx]; + for (uint idx2 = 0; idx2 < ss._sequences.size(); ++idx2) + scene._bgShapes[ss._objNum]._sequences[idx2] = ss._sequences[idx2]; + + // Reset the object's frame to the beginning of the sequence + scene._bgShapes[ss._objNum]._frameNumber = 0; + } + } + + while (!_sequenceStack.empty()) + pullSequence(); + + if (IS_SERRATED_SCALPEL) { + // Restore any pressed button + if (!ui._windowOpen && savedMode != STD_MODE) + static_cast<Scalpel::ScalpelUserInterface *>(_vm->_ui)->restoreButton((int)(savedMode - 1)); + } else { + static_cast<Tattoo::TattooPeople *>(_vm->_people)->pullNPCPaths(); + } + + // Clear the ui counter so that anything displayed on the info line + // before the window was opened isn't cleared + ui._menuCounter = 0; + + // Close any previous window before starting the talk + if (ui._windowOpen) { + switch (savedMode) { + case LOOK_MODE: + events.setCursor(ARROW); + + if (ui._invLookFlag) { + screen.resetDisplayBounds(); + ui.drawInterface(2); + } + + ui.banishWindow(); + ui._windowBounds.top = CONTROLS_Y1; + ui._temp = ui._oldTemp = ui._lookHelp = 0; + ui._menuMode = STD_MODE; + events._pressed = events._released = events._oldButtons = 0; + ui._invLookFlag = false; + break; + + case TALK_MODE: + if (_speaker < SPEAKER_REMOVE) + people.clearTalking(); + if (_talkCounter) + return; + + // If we were in inventory mode looking at an object, restore the + // back buffers before closing the window, so we get the ui restored + // rather than the inventory again + if (ui._invLookFlag) { + screen.resetDisplayBounds(); + ui.drawInterface(2); + ui._invLookFlag = ui._lookScriptFlag = false; + } + + ui.banishWindow(); + ui._windowBounds.top = CONTROLS_Y1; + abortFlag = true; + break; + + case INV_MODE: + case USE_MODE: + case GIVE_MODE: + inv.freeInv(); + if (ui._invLookFlag) { + screen.resetDisplayBounds(); + ui.drawInterface(2); + ui._invLookFlag = ui._lookScriptFlag = false; + } + + ui._infoFlag = true; + ui.clearInfo(); + ui.banishWindow(false); + ui._key = -1; + break; + + case FILES_MODE: + ui.banishWindow(true); + ui._windowBounds.top = CONTROLS_Y1; + abortFlag = true; + break; + + case SETUP_MODE: + ui.banishWindow(true); + ui._windowBounds.top = CONTROLS_Y1; + ui._temp = ui._oldTemp = ui._lookHelp = ui._invLookFlag = false; + ui._menuMode = STD_MODE; + events._pressed = events._released = events._oldButtons = 0; + abortFlag = true; + break; + + default: + break; + } + } + + screen.resetDisplayBounds(); + events._pressed = events._released = false; + loadTalkFile(filename); + ui._selector = ui._oldSelector = ui._key = ui._oldKey = -1; + + // Find the first statement that has the correct flags + int select = -1; + for (uint idx = 0; idx < _statements.size() && select == -1; ++idx) { + if (_statements[idx]._talkMap == 0) + select = _talkIndex = idx; + } + + // If there's a pending automatic selection to be made, then use it + if (_scriptMoreFlag && _scriptSelect != 100) + select = _scriptSelect; + + if (select == -1) + error("Couldn't find statement to display"); + + // Add the statement into the journal and talk history + if (_talkTo != -1 && !_talkHistory[_converseNum][select]) + journal.record(_converseNum, select, true); + _talkHistory[_converseNum][select] = true; + + // Check if the talk file is meant to be a non-seen comment + if (filename.size() < 8 || filename[7] != '*') { + // Should we start in stealth mode? + if (_statements[select]._statement.hasPrefix("^")) { + _talkStealth = 2; + } else { + // Not in stealth mode, so bring up the ui window + _talkStealth = 0; + ++_talkToFlag; + events.setCursor(WAIT); + + ui._windowBounds.top = CONTROLS_Y; + ui._infoFlag = true; + ui.clearInfo(); + } + + // Handle replies until there's no further linked file, + // or the link file isn't a reply first cnversation + while (!_vm->shouldQuit()) { + clearSequences(); + _scriptSelect = select; + _speaker = _talkTo; + + Statement &statement = _statements[select]; + doScript(_statements[select]._reply); + + if (_talkToAbort) + return; + + if (!_talkStealth) + ui.clearWindow(); + + if (statement._modified.size() > 0) { + for (uint idx = 0; idx < statement._modified.size(); ++idx) + _vm->setFlags(statement._modified[idx]); + + setTalkMap(); + } + + // Check for a linked file + if (!statement._linkFile.empty() && !_scriptMoreFlag) { + Common::String linkFilename = statement._linkFile; + freeTalkVars(); + loadTalkFile(linkFilename); + + // Scan for the first valid statement in the newly loaded file + select = -1; + for (uint idx = 0; idx < _statements.size(); ++idx) { + if (_statements[idx]._talkMap == 0) { + select = idx; + break; + } + } + + if (_talkToFlag == 1) + pullSequence(); + + // Set the stealth mode for the new talk file + Statement &newStatement = _statements[select]; + _talkStealth = newStatement._statement.hasPrefix("^") ? 2 : 0; + + // If the new conversion is a reply first, then we don't need + // to display any choices, since the reply needs to be shown + if (!newStatement._statement.hasPrefix("*") && + !newStatement._statement.hasPrefix("^")) { + clearSequences(); + pushSequence(_talkTo); + setStillSeq(_talkTo); + _talkIndex = select; + ui._selector = ui._oldSelector = -1; + + if (!ui._windowOpen) { + // Draw the talk interface on the back buffer + drawInterface(); + displayTalk(false); + } else { + displayTalk(true); + } + + byte color = ui._endKeyActive ? COMMAND_FOREGROUND : COMMAND_NULL; + + // If the window is already open, simply draw. Otherwise, do it + // to the back buffer and then summon the window + Common::String fixedText_Exit = fixedText.getText(kFixedText_Window_Exit); + if (ui._windowOpen) { + screen.buttonPrint(Common::Point(119, CONTROLS_Y), color, true, fixedText_Exit); + } else { + screen.buttonPrint(Common::Point(119, CONTROLS_Y), color, false, fixedText_Exit); + + if (!ui._slideWindows) { + screen.slamRect(Common::Rect(0, CONTROLS_Y, + SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); + } else { + ui.summonWindow(); + } + + ui._windowOpen = true; + } + + // Break out of loop now that we're waiting for player input + events.setCursor(ARROW); + break; + } else { + // Add the statement into the journal and talk history + if (_talkTo != -1 && !_talkHistory[_converseNum][select]) + journal.record(_converseNum, select, true); + _talkHistory[_converseNum][select] = true; + + } + + ui._key = ui._oldKey = Scalpel::COMMANDS[TALK_MODE - 1]; + ui._temp = ui._oldTemp = 0; + ui._menuMode = TALK_MODE; + _talkToFlag = 2; + } else { + freeTalkVars(); + + if (!ui._lookScriptFlag) { + ui.drawInterface(2); + ui.banishWindow(); + ui._windowBounds.top = CONTROLS_Y1; + ui._menuMode = STD_MODE; + } + + break; + } + } + } + + _talkStealth = 0; + events._pressed = events._released = events._oldButtons = 0; + events.clearKeyboard(); + + if (savedBounds.bottom == SHERLOCK_SCREEN_HEIGHT) + screen.resetDisplayBounds(); + else + screen.setDisplayBounds(savedBounds); + + _talkToAbort = abortFlag; + + // If a script was added to the script stack, restore state so that the + // previous script can continue + popStack(); + + if (_vm->getGameID() == GType_SerratedScalpel && filename == "Tube59c") { + // WORKAROUND: Original game bug causes the results of testing the powdery substance + // to disappear too quickly. Introduce a delay to allow it to be properly displayed + ui._menuCounter = 30; + } + + events.setCursor(ARROW); +} + +void Talk::talk(int objNum) { + Events &events = *_vm->_events; + People &people = *_vm->_people; + Scene &scene = *_vm->_scene; + Screen &screen = *_vm->_screen; + UserInterface &ui = *_vm->_ui; + Object &obj = scene._bgShapes[objNum]; + + ui._windowBounds.top = CONTROLS_Y; + ui._infoFlag = true; + _speaker = SPEAKER_REMOVE; + loadTalkFile(scene._bgShapes[objNum]._name); + + // Find the first statement with the correct flags + int select = -1; + for (uint idx = 0; idx < _statements.size(); ++idx) { + if (_statements[idx]._talkMap == 0) { + select = idx; + break; + } + } + if (select == -1) + error("No entry matched all required flags"); + + // See if the statement is a stealth mode reply + Statement &statement = _statements[select]; + if (statement._statement.hasPrefix("^")) { + clearSequences(); + + // Start talk in stealth mode + _talkStealth = 2; + + talkTo(obj._name); + } else if (statement._statement.hasPrefix("*")) { + // Character being spoken to will speak first + clearSequences(); + pushSequence(_talkTo); + setStillSeq(_talkTo); + + events.setCursor(WAIT); + if (obj._lookPosition.y != 0) + // Need to walk to character first + people[HOLMES].walkToCoords(obj._lookPosition, obj._lookFacing); + events.setCursor(ARROW); + + if (!_talkToAbort) + talkTo(obj._name); + } else { + // Holmes will be speaking first + clearSequences(); + pushSequence(_talkTo); + setStillSeq(_talkTo); + + _talkToFlag = false; + events.setCursor(WAIT); + if (obj._lookPosition.y != 0) + // Walk over to person to talk to + people[HOLMES].walkToCoords(obj._lookPosition, obj._lookFacing); + events.setCursor(ARROW); + + if (!_talkToAbort) { + // See if walking over triggered a conversation + if (_talkToFlag) { + if (_talkToFlag == 1) { + events.setCursor(ARROW); + // _sequenceStack._count = 1; + pullSequence(); + } + } else { + drawInterface(); + + events._pressed = events._released = false; + _talkIndex = select; + displayTalk(false); + ui._selector = ui._oldSelector = -1; + + if (!ui._slideWindows) { + screen.slamRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, + SHERLOCK_SCREEN_HEIGHT)); + } else { + ui.summonWindow(); + } + + ui._windowOpen = true; + } + + _talkToFlag = -1; + } + } +} + +void Talk::freeTalkVars() { + _statements.clear(); +} + +void Talk::loadTalkFile(const Common::String &filename) { + People &people = *_vm->_people; + Resources &res = *_vm->_res; + Sound &sound = *_vm->_sound; + + // Save a copy of the talk filename + _scriptName = filename; + + // Check for an existing person being talked to + _talkTo = -1; + for (int idx = 0; idx < (int)people._characters.size(); ++idx) { + if (!scumm_strnicmp(filename.c_str(), people._characters[idx]._portrait, 4)) { + _talkTo = idx; + break; + } + } + + const char *chP = strchr(filename.c_str(), '.'); + Common::String talkFile = chP ? Common::String(filename.c_str(), chP) + ".tlk" : + Common::String(filename.c_str(), filename.c_str() + 7) + ".tlk"; + + // Open the talk file for reading + Common::SeekableReadStream *talkStream = res.load(talkFile); + _converseNum = res.resourceIndex(); + talkStream->skip(2); // Skip talk file version num + + _statements.resize(talkStream->readByte()); + for (uint idx = 0; idx < _statements.size(); ++idx) + _statements[idx].load(*talkStream, IS_ROSE_TATTOO); + + delete talkStream; + + if (!sound._voices) + stripVoiceCommands(); + setTalkMap(); +} + +void Talk::stripVoiceCommands() { + for (uint sIdx = 0; sIdx < _statements.size(); ++sIdx) { + Statement &statement = _statements[sIdx]; + + // Scan for an sound effect byte, which indicates to play a sound + for (uint idx = 0; idx < statement._reply.size(); ++idx) { + if (statement._reply[idx] == (char)_opcodes[OP_SFX_COMMAND]) { + // Replace instruction character with a space, and delete the + // rest of the name following it + statement._reply = Common::String(statement._reply.c_str(), + statement._reply.c_str() + idx) + " " + + Common::String(statement._reply.c_str() + 9); + } + } + + // Ensure the last character of the reply is not a space from the prior + // conversion loop, to avoid any issues with the space ever causing a page + // wrap, and ending up displaying another empty page + while (statement._reply.lastChar() == ' ') + statement._reply.deleteLastChar(); + } +} + +void Talk::setTalkMap() { + int statementNum = 0; + + for (uint sIdx = 0; sIdx < _statements.size(); ++sIdx) { + Statement &statement = _statements[sIdx]; + + // Set up talk map entry for the statement + bool valid = true; + for (uint idx = 0; idx < statement._required.size(); ++idx) { + if (!_vm->readFlags(statement._required[idx])) + valid = false; + } + + statement._talkMap = valid ? statementNum++ : -1; + } +} + +void Talk::drawInterface() { + FixedText &fixedText = *_vm->_fixedText; + Screen &screen = *_vm->_screen; + Surface &bb = *screen._backBuffer; + + bb.fillRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, CONTROLS_Y1 + 10), BORDER_COLOR); + bb.fillRect(Common::Rect(0, CONTROLS_Y + 10, 2, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR); + bb.fillRect(Common::Rect(SHERLOCK_SCREEN_WIDTH - 2, CONTROLS_Y + 10, + SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR); + bb.fillRect(Common::Rect(0, SHERLOCK_SCREEN_HEIGHT - 1, SHERLOCK_SCREEN_WIDTH - 2, + SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR); + bb.fillRect(Common::Rect(2, CONTROLS_Y + 10, SHERLOCK_SCREEN_WIDTH - 2, + SHERLOCK_SCREEN_HEIGHT - 2), INV_BACKGROUND); + + if (_talkTo != -1) { + Common::String fixedText_Exit = fixedText.getText(kFixedText_Window_Exit); + Common::String fixedText_Up = fixedText.getText(kFixedText_Window_Up); + Common::String fixedText_Down = fixedText.getText(kFixedText_Window_Down); + + screen.makeButton(Common::Rect(99, CONTROLS_Y, 139, CONTROLS_Y + 10), + 119 - screen.stringWidth(fixedText_Exit) / 2, fixedText_Exit); + screen.makeButton(Common::Rect(140, CONTROLS_Y, 180, CONTROLS_Y + 10), + 159 - screen.stringWidth(fixedText_Up) / 2, fixedText_Up); + screen.makeButton(Common::Rect(181, CONTROLS_Y, 221, CONTROLS_Y + 10), + 200 - screen.stringWidth(fixedText_Down) / 2, fixedText_Down); + } else { + int strWidth = screen.stringWidth(Scalpel::PRESS_KEY_TO_CONTINUE); + screen.makeButton(Common::Rect(46, CONTROLS_Y, 273, CONTROLS_Y + 10), + 160 - strWidth / 2, Scalpel::PRESS_KEY_TO_CONTINUE); + screen.gPrint(Common::Point(160 - strWidth / 2, CONTROLS_Y), COMMAND_FOREGROUND, "P"); + } +} + +bool Talk::displayTalk(bool slamIt) { + FixedText &fixedText = *_vm->_fixedText; + Screen &screen = *_vm->_screen; + int yp = CONTROLS_Y + 14; + int lineY = -1; + _moreTalkDown = _moreTalkUp = false; + + for (uint idx = 0; idx < _statements.size(); ++idx) { + _statements[idx]._talkPos.top = _statements[idx]._talkPos.bottom = -1; + } + + if (_talkIndex) { + for (int idx = 0; idx < _talkIndex && !_moreTalkUp; ++idx) { + if (_statements[idx]._talkMap != -1) + _moreTalkUp = true; + } + } + + // Display the up arrow and enable Up button if the first option is scrolled off-screen + Common::String fixedText_Up = fixedText.getText(kFixedText_Window_Up); + Common::String fixedText_Down = fixedText.getText(kFixedText_Window_Down); + if (_moreTalkUp) { + if (slamIt) { + screen.print(Common::Point(5, CONTROLS_Y + 13), INV_FOREGROUND, "~"); + screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_FOREGROUND, true, fixedText_Up); + } else { + screen.gPrint(Common::Point(5, CONTROLS_Y + 12), INV_FOREGROUND, "~"); + screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_FOREGROUND, false, fixedText_Up); + } + } else { + if (slamIt) { + screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_NULL, true, fixedText_Up); + screen.vgaBar(Common::Rect(5, CONTROLS_Y + 11, 15, CONTROLS_Y + 22), INV_BACKGROUND); + } else { + screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_NULL, false, fixedText_Up); + screen._backBuffer1.fillRect(Common::Rect(5, CONTROLS_Y + 11, + 15, CONTROLS_Y + 22), INV_BACKGROUND); + } + } + + // Loop through the statements + bool done = false; + for (uint idx = _talkIndex; idx < _statements.size() && !done; ++idx) { + Statement &statement = _statements[idx]; + + if (statement._talkMap != -1) { + bool flag = _talkHistory[_converseNum][idx]; + lineY = talkLine(idx, statement._talkMap, flag ? TALK_NULL : INV_FOREGROUND, + yp, slamIt); + + if (lineY != -1) { + statement._talkPos.top = yp; + yp = lineY; + statement._talkPos.bottom = yp; + + if (yp == SHERLOCK_SCREEN_HEIGHT) + done = true; + } else { + done = true; + } + } + } + + // Display the down arrow and enable down button if there are more statements available down off-screen + if (lineY == -1 || lineY == SHERLOCK_SCREEN_HEIGHT) { + _moreTalkDown = true; + + if (slamIt) { + screen.print(Common::Point(5, 190), INV_FOREGROUND, "|"); + screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_FOREGROUND, true, fixedText_Down); + } else { + screen.gPrint(Common::Point(5, 189), INV_FOREGROUND, "|"); + screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_FOREGROUND, false, fixedText_Down); + } + } else { + if (slamIt) { + screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_NULL, true, fixedText_Down); + screen.vgaBar(Common::Rect(5, 189, 16, 199), INV_BACKGROUND); + } else { + screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_NULL, false, fixedText_Down); + screen._backBuffer1.fillRect(Common::Rect(5, 189, 16, 199), INV_BACKGROUND); + } + } + + return done; +} + +int Talk::talkLine(int lineNum, int stateNum, byte color, int lineY, bool slamIt) { + Screen &screen = *_vm->_screen; + int idx = lineNum; + Common::String msg, number; + bool numberFlag = false; + + // Get the statement to display as well as optional number prefix + if (idx < SPEAKER_REMOVE) { + number = Common::String::format("%d.", stateNum + 1); + numberFlag = true; + } else { + idx -= SPEAKER_REMOVE; + } + msg = _statements[idx]._statement; + + // Handle potentially multiple lines needed to display entire statement + const char *lineStartP = msg.c_str(); + int maxWidth = 298 - (numberFlag ? 18 : 0); + for (;;) { + // Get as much of the statement as possible will fit on the + Common::String sLine; + const char *lineEndP = lineStartP; + int width = 0; + do { + width += screen.charWidth(*lineEndP); + } while (*++lineEndP && width < maxWidth); + + // Check if we need to wrap the line + if (width >= maxWidth) { + // Work backwards to the prior word's end + while (*--lineEndP != ' ') + ; + + sLine = Common::String(lineStartP, lineEndP++); + } else { + // Can display remainder of the statement on the current line + sLine = Common::String(lineStartP); + } + + + if (lineY <= (SHERLOCK_SCREEN_HEIGHT - 10)) { + // Need to directly display on-screen? + if (slamIt) { + // See if a numer prefix is needed or not + if (numberFlag) { + // Are we drawing the first line? + if (lineStartP == msg.c_str()) { + // We are, so print the number and then the text + screen.print(Common::Point(16, lineY), color, "%s", number.c_str()); + } + + // Draw the line with an indent + screen.print(Common::Point(30, lineY), color, "%s", sLine.c_str()); + } else { + screen.print(Common::Point(16, lineY), color, "%s", sLine.c_str()); + } + } else { + if (numberFlag) { + if (lineStartP == msg.c_str()) { + screen.gPrint(Common::Point(16, lineY - 1), color, "%s", number.c_str()); + } + + screen.gPrint(Common::Point(30, lineY - 1), color, "%s", sLine.c_str()); + } else { + screen.gPrint(Common::Point(16, lineY - 1), color, "%s", sLine.c_str()); + } + } + + // Move to next line, if any + lineY += 9; + lineStartP = lineEndP; + + if (!*lineEndP) + break; + } else { + // We're close to the bottom of the screen, so stop display + lineY = -1; + break; + } + } + + if (lineY == -1 && lineStartP != msg.c_str()) + lineY = SHERLOCK_SCREEN_HEIGHT; + + // Return the Y position of the next line to follow this one + return lineY; +} + +void Talk::clearSequences() { + _sequenceStack.clear(); +} + +void Talk::pullSequence() { + Scene &scene = *_vm->_scene; + + if (_sequenceStack.empty() || IS_ROSE_TATTOO) + return; + + SequenceEntry seq = _sequenceStack.pop(); + if (seq._objNum != -1) { + Object &obj = scene._bgShapes[seq._objNum]; + + if (obj._seqSize < MAX_TALK_SEQUENCES) { + warning("Tried to restore too few frames"); + } else { + for (int idx = 0; idx < MAX_TALK_SEQUENCES; ++idx) + obj._sequences[idx] = seq._sequences[idx]; + + obj._frameNumber = seq._frameNumber; + obj._seqTo = seq._seqTo; + } + } +} + +void Talk::pushSequence(int speaker) { + People &people = *_vm->_people; + Scene &scene = *_vm->_scene; + + // Only proceed if a speaker is specified + if (speaker == -1 || IS_ROSE_TATTOO) + return; + + SequenceEntry seqEntry; + if (!speaker) { + seqEntry._objNum = -1; + } else { + seqEntry._objNum = people.findSpeaker(speaker); + + if (seqEntry._objNum != -1) { + Object &obj = scene._bgShapes[seqEntry._objNum]; + for (uint idx = 0; idx < MAX_TALK_SEQUENCES; ++idx) + seqEntry._sequences.push_back(obj._sequences[idx]); + + seqEntry._frameNumber = obj._frameNumber; + seqEntry._seqTo = obj._seqTo; + } + } + + _sequenceStack.push(seqEntry); + if (_scriptStack.size() >= 5) + error("script stack overflow"); +} + +void Talk::pushTalkSequence(Object *obj) { + // Check if the shape is already on the stack + for (uint idx = 0; idx < TALK_SEQUENCE_STACK_SIZE; ++idx) { + if (_talkSequenceStack[idx]._obj == obj) + return; + } + + // Find a free slot and save the details in it + for (uint idx = 0; idx < TALK_SEQUENCE_STACK_SIZE; ++idx) { + TalkSequence &ts = _talkSequenceStack[idx]; + if (ts._obj == nullptr) { + ts._obj = obj; + ts._frameNumber = obj->_frameNumber; + ts._sequenceNumber = obj->_sequenceNumber; + ts._seqStack = obj->_seqStack; + ts._seqTo = obj->_seqTo; + ts._seqCounter = obj->_seqCounter; + ts._seqCounter2 = obj->_seqCounter2; + return; + } + } + + error("Ran out of talk sequence stack space"); +} + +void Talk::setStillSeq(int speaker) { + People &people = *_vm->_people; + Scene &scene = *_vm->_scene; + + // Don't bother doing anything if no specific speaker is specified + if (speaker == -1) + return; + + if (speaker) { + int objNum = people.findSpeaker(speaker); + if (objNum != -1) { + Object &obj = scene._bgShapes[objNum]; + + if (obj._seqSize < MAX_TALK_SEQUENCES) { + warning("Tried to copy too few still frames"); + } else { + for (uint idx = 0; idx < MAX_TALK_SEQUENCES; ++idx) { + obj._sequences[idx] = people._characters[speaker]._stillSequences[idx]; + if (idx > 0 && !people._characters[speaker]._talkSequences[idx] && + !people._characters[speaker]._talkSequences[idx - 1]) + break; + } + + obj._frameNumber = 0; + obj._seqTo = 0; + } + } + } +} + +void Talk::doScript(const Common::String &script) { + People &people = *_vm->_people; + Scene &scene = *_vm->_scene; + Screen &screen = *_vm->_screen; + UserInterface &ui = *_vm->_ui; + + _savedSequences.clear(); + + _scriptStart = (const byte *)script.c_str(); + _scriptEnd = _scriptStart + script.size(); + const byte *str = _scriptStart; + _charCount = 0; + _line = 0; + _wait = 0; + _pauseFlag = false; + _seqCount = 0; + _noTextYet = true; + _endStr = false; + _openTalkWindow = false; + + if (IS_SERRATED_SCALPEL) + _yp = CONTROLS_Y + 12; + else + _yp = (_talkTo == -1) ? 5 : screen.fontHeight() + 11; + + if (IS_ROSE_TATTOO) { + for (int idx = 0; idx < MAX_CHARACTERS; ++idx) { + Tattoo::TattooPerson &p = (*(Tattoo::TattooPeople *)_vm->_people)[idx]; + p._savedNpcSequence = p._sequenceNumber; + p._savedNpcFrame = p._frameNumber; + } + } + + if (_scriptMoreFlag) { + _scriptMoreFlag = 0; + str = _scriptStart + _scriptSaveIndex; + } + + // Check if the script begins with a Stealh Mode Active command + if (str[0] == _opcodes[OP_STEALTH_MODE_ACTIVE] || _talkStealth) { + _talkStealth = 2; + _speaker |= SPEAKER_REMOVE; + } else { + pushSequence(_speaker); + if (IS_SERRATED_SCALPEL || ui._windowOpen) + ui.clearWindow(); + + // Need to switch speakers? + if (str[0] == _opcodes[OP_SWITCH_SPEAKER]) { + _speaker = str[1] - 1; + str += IS_SERRATED_SCALPEL ? 2 : 3; + + pullSequence(); + pushSequence(_speaker); + people.setTalkSequence(_speaker); + } else { + people.setTalkSequence(_speaker); + } + + if (IS_SERRATED_SCALPEL) { + // Assign portrait location? + if (str[0] == _opcodes[OP_ASSIGN_PORTRAIT_LOCATION]) { + switch (str[1] & 15) { + case 1: + people._portraitSide = 20; + break; + case 2: + people._portraitSide = 220; + break; + case 3: + people._portraitSide = 120; + break; + default: + break; + + } + + if (str[1] > 15) + people._speakerFlip = true; + str += 2; + } + + if (IS_SERRATED_SCALPEL) { + // Remove portrait? + if ( str[0] == _opcodes[OP_REMOVE_PORTRAIT]) { + _speaker = -1; + } else { + // Nope, so set the first speaker + ((Scalpel::ScalpelPeople *)_vm->_people)->setTalking(_speaker); + } + } + } + } + + bool trigger3DOMovie = true; + uint16 subIndex = 1; + + do { + Common::String tempString; + _wait = 0; + + byte c = str[0]; + if (!c) { + _endStr = true; + } else if (c == '{') { + // Start of comment, so skip over it + while (*str++ != '}') + ; + } else if (isOpcode(c)) { + // Handle control code + switch ((this->*_opcodeTable[c - _opcodes[0]])(str)) { + case RET_EXIT: + return; + case RET_CONTINUE: + continue; + case OP_SWITCH_SPEAKER: + trigger3DOMovie = true; + break; + default: + break; + } + + ++str; + } else { + // Handle drawing the talk interface with the text + talkInterface(str); + } + + // Open window if it wasn't already open, and text has already been printed + if ((_openTalkWindow && _wait) || (_openTalkWindow && str[0] >= _opcodes[0] && str[0] != _opcodes[OP_CARRIAGE_RETURN])) { + if (!ui._slideWindows) { + screen.slamRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); + } else { + ui.summonWindow(); + } + + ui._windowOpen = true; + _openTalkWindow = false; + } + + if ((_wait) && (trigger3DOMovie)) { + // Trigger to play 3DO movie + talk3DOMovieTrigger(subIndex); + + trigger3DOMovie = false; // wait for next switch speaker opcode + subIndex++; + } + + if (_wait) + // Handling pausing + talkWait(str); + } while (!_vm->shouldQuit() && !_endStr); + + if (_wait != -1) { + for (int ssIndex = 0; ssIndex < (int)_savedSequences.size(); ++ssIndex) { + SequenceEntry &seq = _savedSequences[ssIndex]; + Object &object = scene._bgShapes[seq._objNum]; + + for (uint idx = 0; idx < seq._sequences.size(); ++idx) + object._sequences[idx] = seq._sequences[idx]; + object._frameNumber = seq._frameNumber; + object._seqTo = seq._seqTo; + } + + pullSequence(); + if (_speaker >= 0 && _speaker < SPEAKER_REMOVE) + people.clearTalking(); + + if (IS_ROSE_TATTOO) + static_cast<Tattoo::TattooPeople *>(_vm->_people)->pullNPCPaths(); + } +} + +int Talk::waitForMore(int delay) { + Events &events = *_vm->_events; + People &people = *_vm->_people; + Scene &scene = *_vm->_scene; + Sound &sound = *_vm->_sound; + UserInterface &ui = *_vm->_ui; + CursorId oldCursor = events.getCursor(); + int key2 = 254; + + // Unless we're in stealth mode, show the appropriate cursor + if (!_talkStealth) { + events.setCursor(ui._lookScriptFlag ? MAGNIFY : ARROW); + } + + do { + if (sound._speechOn && !*sound._soundIsOn) + people._portrait._frameNumber = -1; + + scene.doBgAnim(); + + // If talkTo call was done via doBgAnim, abort out of talk quietly + if (_talkToAbort) { + key2 = -1; + events._released = true; + } else { + // See if there's been a button press + events.setButtonState(); + + if (events.kbHit()) { + Common::KeyState keyState = events.getKey(); + if (Common::isPrint(keyState.ascii)) + key2 = keyState.keycode; + } + + if (_talkStealth) { + key2 = 254; + events._released = false; + } + } + + // Count down the delay + if ((delay > 0 && !ui._invLookFlag && !ui._lookScriptFlag) || _talkStealth) + --delay; + + // If there are voices playing, reset delay so that they keep playing + if (sound._voices == 2 && *sound._soundIsOn) + delay = 0; + } while (!_vm->shouldQuit() && key2 == 254 && (delay || (sound._voices == 2 && *sound._soundIsOn)) + && !events._released && !events._rightReleased); + + // If voices was set 2 to indicate a voice file was place, then reset it back to 1 + if (sound._voices == 2) + sound._voices = 1; + + if (delay > 0 && sound._diskSoundPlaying) + sound.stopSndFuncPtr(0, 0); + + // Adjust _talkStealth mode: + // mode 1 - It was by a pause without stealth being on before the pause, so reset back to 0 + // mode 3 - It was set by a pause with stealth being on before the pause, to set it to active + // mode 0/2 (Inactive/active) No change + switch (_talkStealth) { + case 1: + _talkStealth = 0; + break; + case 2: + _talkStealth = 2; + break; + default: + break; + } + + sound._speechOn = false; + events.setCursor(_talkToAbort ? ARROW : oldCursor); + events._pressed = events._released = false; + + return key2; +} + +bool Talk::isOpcode(byte checkCharacter) { + if ((checkCharacter < _opcodes[0]) || (checkCharacter >= (_opcodes[0] + 99))) + return false; // outside of range + if (_opcodeTable[checkCharacter - _opcodes[0]]) + return true; + return false; +} + +void Talk::popStack() { + if (!_scriptStack.empty()) { + ScriptStackEntry scriptEntry = _scriptStack.pop(); + _scriptName = scriptEntry._name; + _scriptSaveIndex = scriptEntry._currentIndex; + _scriptSelect = scriptEntry._select; + _scriptMoreFlag = 1; + } +} + +void Talk::synchronize(Serializer &s) { + for (uint idx = 0; idx < _talkHistory.size(); ++idx) { + TalkHistoryEntry &he = _talkHistory[idx]; + + for (int flag = 0; flag < 16; ++flag) + s.syncAsByte(he._data[flag]); + } +} + +OpcodeReturn Talk::cmdAddItemToInventory(const byte *&str) { + Inventory &inv = *_vm->_inventory; + Common::String tempString; + + ++str; + for (int idx = 0; idx < str[0]; ++idx) + tempString += str[idx + 1]; + str += str[0]; + + inv.putNameInInventory(tempString); + return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdAdjustObjectSequence(const byte *&str) { + Scene &scene = *_vm->_scene; + Common::String tempString; + + // Get the name of the object to adjust + ++str; + for (int idx = 0; idx < (str[0] & 127); ++idx) + tempString += str[idx + 2]; + + // Scan for object + int objId = -1; + for (uint idx = 0; idx < scene._bgShapes.size(); ++idx) { + if (tempString.equalsIgnoreCase(scene._bgShapes[idx]._name)) + objId = idx; + } + if (objId == -1) + error("Could not find object %s to change", tempString.c_str()); + + // Should the script be overwritten? + if (str[0] > 0x80) { + // Save the current sequence + _savedSequences.push(SequenceEntry()); + SequenceEntry &seqEntry = _savedSequences.top(); + seqEntry._objNum = objId; + seqEntry._seqTo = scene._bgShapes[objId]._seqTo; + for (uint idx = 0; idx < scene._bgShapes[objId]._seqSize; ++idx) + seqEntry._sequences.push_back(scene._bgShapes[objId]._sequences[idx]); + } + + // Get number of bytes to change + _seqCount = str[1]; + str += (str[0] & 127) + 2; + + // Copy in the new sequence + for (int idx = 0; idx < _seqCount; ++idx, ++str) + scene._bgShapes[objId]._sequences[idx] = str[0] - 1; + + // Reset object back to beginning of new sequence + scene._bgShapes[objId]._frameNumber = 0; + + return RET_CONTINUE; +} + +OpcodeReturn Talk::cmdBanishWindow(const byte *&str) { + People &people = *_vm->_people; + UserInterface &ui = *_vm->_ui; + + if (!(_speaker & SPEAKER_REMOVE)) + people.clearTalking(); + pullSequence(); + + if (_talkToAbort) + return RET_EXIT; + + _speaker |= SPEAKER_REMOVE; + ui.banishWindow(); + ui._menuMode = TALK_MODE; + _noTextYet = true; + + return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdCallTalkFile(const byte *&str) { + Common::String tempString; + + ++str; + for (int idx = 0; idx < 8 && str[idx] != '~'; ++idx) + tempString += str[idx]; + str += 8; + + int scriptCurrentIndex = str - _scriptStart; + + // Save the current script position and new talk file + if (_scriptStack.size() < 9) { + ScriptStackEntry rec1; + rec1._name = _scriptName; + rec1._currentIndex = scriptCurrentIndex; + rec1._select = _scriptSelect; + _scriptStack.push(rec1); + + // Push the new talk file onto the stack + ScriptStackEntry rec2; + rec2._name = tempString; + rec2._currentIndex = 0; + rec2._select = 100; + _scriptStack.push(rec2); + } + else { + error("Script stack overflow"); + } + + _scriptMoreFlag = 1; + _endStr = true; + _wait = 0; + + return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdDisableEndKey(const byte *&str) { + _vm->_ui->_endKeyActive = false; + return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdEnableEndKey(const byte *&str) { + _vm->_ui->_endKeyActive = true; + return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdHolmesOff(const byte *&str) { + People &people = *_vm->_people; + people[HOLMES]._type = REMOVE; + + return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdHolmesOn(const byte *&str) { + People &people = *_vm->_people; + people[HOLMES]._type = CHARACTER; + + return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdPause(const byte *&str) { + _charCount = *++str; + _wait = _pauseFlag = true; + + return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdPauseWithoutControl(const byte *&str) { + Events &events = *_vm->_events; + Scene &scene = *_vm->_scene; + ++str; + + for (int idx = 0; idx < (str[0] - 1); ++idx) { + scene.doBgAnim(); + if (_talkToAbort) + return RET_EXIT; + + // Check for button press + events.pollEvents(); + events.setButtonState(); + } + + return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdRemoveItemFromInventory(const byte *&str) { + Inventory &inv = *_vm->_inventory; + Common::String tempString; + + ++str; + for (int idx = 0; idx < str[0]; ++idx) + tempString += str[idx + 1]; + str += str[0]; + + inv.deleteItemFromInventory(tempString); + + return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdRunCAnimation(const byte *&str) { + Scene &scene = *_vm->_scene; + + ++str; + scene.startCAnim((str[0] - 1) & 127, (str[0] & 0x80) ? -1 : 1); + if (_talkToAbort) + return RET_EXIT; + + // Check if next character is changing side or changing portrait + if (_charCount && (str[1] == _opcodes[OP_SWITCH_SPEAKER] || str[1] == _opcodes[OP_ASSIGN_PORTRAIT_LOCATION])) + _wait = 1; + + return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdSetFlag(const byte *&str) { + ++str; + int flag1 = (str[0] - 1) * 256 + str[1] - 1 - (str[1] == 1 ? 1 : 0); + int flag = (flag1 & 0x3fff) * (flag1 >= 0x4000 ? -1 : 1); + _vm->setFlags(flag); + ++str; + + return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdSetObject(const byte *&str) { + Scene &scene = *_vm->_scene; + Common::String tempString; + + ++str; + for (int idx = 0; idx < (str[0] & 127); ++idx) + tempString += str[idx + 1]; + + // Set comparison state according to if we want to hide or unhide + bool state = (str[0] >= SPEAKER_REMOVE); + str += str[0] & 127; + + for (uint idx = 0; idx < scene._bgShapes.size(); ++idx) { + Object &object = scene._bgShapes[idx]; + if (tempString.equalsIgnoreCase(object._name)) { + // Only toggle the object if it's not in the desired state already + if ((object._type == HIDDEN && state) || (object._type != HIDDEN && !state)) + object.toggleHidden(); + } + } + + return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdStealthModeActivate(const byte *&str) { + _talkStealth = 2; + return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdStealthModeDeactivate(const byte *&str) { + Events &events = *_vm->_events; + + _talkStealth = 0; + events.clearKeyboard(); + + return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdToggleObject(const byte *&str) { + Scene &scene = *_vm->_scene; + Common::String tempString; + + ++str; + for (int idx = 0; idx < str[0]; ++idx) + tempString += str[idx + 1]; + + scene.toggleObject(tempString); + str += str[0]; + + return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdWalkToCAnimation(const byte *&str) { + People &people = *_vm->_people; + Scene &scene = *_vm->_scene; + + ++str; + CAnim &animation = scene._cAnim[str[0] - 1]; + people[HOLMES].walkToCoords(animation._goto[0], animation._goto[0]._facing); + + return _talkToAbort ? RET_EXIT : RET_SUCCESS; +} + +void Talk::talkWait(const byte *&str) { + if (!_pauseFlag && _charCount < 160) + _charCount = 160; + + _wait = waitForMore(_charCount); + if (_wait == -1) + _endStr = true; + + // If a key was pressed to finish the window, see if further voice files should be skipped + if (_wait >= 0 && _wait < 254) { + if (str[0] == _opcodes[OP_SFX_COMMAND]) + str += 9; + } + + _pauseFlag = false; +} + +} // End of namespace Sherlock diff --git a/engines/sherlock/talk.h b/engines/sherlock/talk.h new file mode 100644 index 0000000000..62a839e4ea --- /dev/null +++ b/engines/sherlock/talk.h @@ -0,0 +1,379 @@ +/* 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 SHERLOCK_TALK_H +#define SHERLOCK_TALK_H + +#include "common/scummsys.h" +#include "common/array.h" +#include "common/rect.h" +#include "common/stream.h" +#include "common/stack.h" +#include "sherlock/objects.h" +#include "sherlock/saveload.h" + +namespace Sherlock { + +#define SPEAKER_REMOVE 0x80 +#define MAX_TALK_SEQUENCES 11 +#define TALK_SEQUENCE_STACK_SIZE 20 + +enum { + OP_SWITCH_SPEAKER = 0, + OP_RUN_CANIMATION = 1, + OP_ASSIGN_PORTRAIT_LOCATION = 2, + OP_PAUSE = 3, + OP_REMOVE_PORTRAIT = 4, + OP_CLEAR_WINDOW = 5, + OP_ADJUST_OBJ_SEQUENCE = 6, + OP_WALK_TO_COORDS = 7, + OP_PAUSE_WITHOUT_CONTROL = 8, + OP_BANISH_WINDOW = 9, + OP_SUMMON_WINDOW = 10, + OP_SET_FLAG = 11, + OP_SFX_COMMAND = 12, + OP_TOGGLE_OBJECT = 13, + OP_STEALTH_MODE_ACTIVE = 14, + OP_IF_STATEMENT = 15, + OP_ELSE_STATEMENT = 16, + OP_END_IF_STATEMENT = 17, + OP_STEALTH_MODE_DEACTIVATE = 18, + OP_TURN_HOLMES_OFF = 19, + OP_TURN_HOLMES_ON = 20, + OP_GOTO_SCENE = 21, + OP_PLAY_PROLOGUE = 22, + OP_ADD_ITEM_TO_INVENTORY = 23, + OP_SET_OBJECT = 24, + OP_CALL_TALK_FILE = 25, + OP_MOVE_MOUSE = 26, + OP_DISPLAY_INFO_LINE = 27, + OP_CLEAR_INFO_LINE = 28, + OP_WALK_TO_CANIMATION = 29, + OP_REMOVE_ITEM_FROM_INVENTORY = 30, + OP_ENABLE_END_KEY = 31, + OP_DISABLE_END_KEY = 32, + OP_CARRIAGE_RETURN = 33, + + OP_MOUSE_OFF_ON = 34, + OP_SET_WALK_CONTROL = 35, + OP_SET_TALK_SEQUENCE = 36, + OP_PLAY_SONG = 37, + OP_WALK_HOLMES_AND_NPC_TO_CANIM = 38, + OP_SET_NPC_PATH_DEST = 39, + OP_NEXT_SONG = 40, + OP_SET_NPC_PATH_PAUSE = 41, + OP_NEED_PASSWORD = 42, + OP_SET_SCENE_ENTRY_FLAG = 43, + OP_WALK_NPC_TO_CANIM = 44, + OP_WALK_NPC_TO_COORDS = 45, + OP_WALK_HOLMES_AND_NPC_TO_COORDS = 46, + OP_SET_NPC_TALK_FILE = 47, + OP_TURN_NPC_OFF = 48, + OP_TURN_NPC_ON = 49, + OP_NPC_DESC_ON_OFF = 50, + OP_NPC_PATH_PAUSE_TAKING_NOTES = 51, + OP_NPC_PATH_PAUSE_LOOKING_HOLMES = 52, + OP_ENABLE_TALK_INTERRUPTS = 53, + OP_DISABLE_TALK_INTERRUPTS = 54, + OP_SET_NPC_INFO_LINE = 55, + OP_SET_NPC_POSITION = 56, + OP_NPC_PATH_LABEL = 57, + OP_PATH_GOTO_LABEL = 58, + OP_PATH_IF_FLAG_GOTO_LABEL = 59, + OP_NPC_WALK_GRAPHICS = 60, + OP_NPC_VERB = 61, + OP_NPC_VERB_CANIM = 62, + OP_NPC_VERB_SCRIPT = 63, + OP_RESTORE_PEOPLE_SEQUENCE = 64, + OP_NPC_VERB_TARGET = 65, + OP_TURN_SOUNDS_OFF = 66, + OP_NULL = 67, + OP_END_TEXT_WINDOW = 68 +}; + +enum OpcodeReturn { RET_EXIT = -1, RET_SUCCESS = 0, RET_CONTINUE = 1 }; + +class SherlockEngine; +class Talk; +namespace Scalpel { class ScalpelUserInterface; } + +typedef OpcodeReturn(Talk::*OpcodeMethod)(const byte *&str); + +struct SequenceEntry { + int _objNum; + Common::Array<byte> _sequences; + int _frameNumber; + int _seqTo; + + SequenceEntry(); +}; + +struct ScriptStackEntry { + Common::String _name; + int _currentIndex; + int _select; +}; + +struct Statement { + Common::String _statement; + Common::String _reply; + Common::String _linkFile; + Common::String _voiceFile; + Common::Array<int> _required; + Common::Array<int> _modified; + int _portraitSide; + int _quotient; + int _talkMap; + Common::Rect _talkPos; + int _journal; + + /** + * Load the data for a single statement within a talk file + */ + void load(Common::SeekableReadStream &s, bool isRoseTattoo); +}; + +struct TalkHistoryEntry { + bool _data[16]; + + TalkHistoryEntry(); + bool &operator[](int index) { return _data[index]; } +}; + +struct TalkSequence { + Object *_obj; // Pointer to the bgshape that these values go to + short _frameNumber; // Frame number in frame sequence to draw + short _sequenceNumber; // Start frame of sequences that are repeated + int _seqStack; // Allows gosubs to return to calling frame + int _seqTo; // Allows 1-5, 8-3 type sequences encoded + int _seqCounter; // How many times this sequence has been executed + int _seqCounter2; + + TalkSequence(); +}; + + +class Talk { + friend class Scalpel::ScalpelUserInterface; +private: + /** + * Remove any voice commands from a loaded statement list + */ + void stripVoiceCommands(); + + /** + * Form a table of the display indexes for statements + */ + void setTalkMap(); + + /** + * Display a list of statements in a window at the bottom of the screen that the + * player can select from. + */ + bool displayTalk(bool slamIt); + + /** + * Prints a single conversation option in the interface window + */ + int talkLine(int lineNum, int stateNum, byte color, int lineY, bool slamIt); + + /** + * Parses a reply for control codes and display text. The found text is printed within + * the text window, handles delays, animations, and animating portraits. + */ + void doScript(const Common::String &script); + + /** + * When the talk window has been displayed, waits a period of time proportional to + * the amount of text that's been displayed + */ + int waitForMore(int delay); + +protected: + SherlockEngine *_vm; + OpcodeMethod *_opcodeTable; + Common::Stack<SequenceEntry> _savedSequences; + Common::Stack<SequenceEntry> _sequenceStack; + Common::Stack<ScriptStackEntry> _scriptStack; + Common::Array<TalkHistoryEntry> _talkHistory; + int _speaker; + int _talkIndex; + int _scriptSelect; + int _talkStealth; + int _talkToFlag; + int _scriptSaveIndex; + + // These fields are used solely by doScript, but are fields because all the script opcodes are + // separate methods now, and need access to these fields + int _yp; + int _charCount; + int _line; + int _wait; + bool _pauseFlag; + bool _endStr, _noTextYet; + int _seqCount; + const byte *_scriptStart, *_scriptEnd; +protected: + Talk(SherlockEngine *vm); + + OpcodeReturn cmdAddItemToInventory(const byte *&str); + OpcodeReturn cmdAdjustObjectSequence(const byte *&str); + OpcodeReturn cmdBanishWindow(const byte *&str); + OpcodeReturn cmdCallTalkFile(const byte *&str); + OpcodeReturn cmdDisableEndKey(const byte *&str); + OpcodeReturn cmdEnableEndKey(const byte *&str); + OpcodeReturn cmdHolmesOff(const byte *&str); + OpcodeReturn cmdHolmesOn(const byte *&str); + OpcodeReturn cmdPause(const byte *&str); + OpcodeReturn cmdPauseWithoutControl(const byte *&str); + OpcodeReturn cmdRemoveItemFromInventory(const byte *&str); + OpcodeReturn cmdRunCAnimation(const byte *&str); + OpcodeReturn cmdSetFlag(const byte *&str); + OpcodeReturn cmdSetObject(const byte *&str); + OpcodeReturn cmdStealthModeActivate(const byte *&str); + OpcodeReturn cmdStealthModeDeactivate(const byte *&str); + OpcodeReturn cmdToggleObject(const byte *&str); + OpcodeReturn cmdWalkToCAnimation(const byte *&str); +protected: + /** + * Checks, if a character is an opcode + */ + bool isOpcode(byte checkCharacter); + + /** + * Display the talk interface window + */ + virtual void talkInterface(const byte *&str) = 0; + + /** + * Pause when displaying a talk dialog on-screen + */ + virtual void talkWait(const byte *&str); + + /** + * Trigger to play a 3DO talk dialog movie + */ + virtual void talk3DOMovieTrigger(int subIndex) {}; + +public: + TalkSequence _talkSequenceStack[TALK_SEQUENCE_STACK_SIZE]; + Common::Array<Statement> _statements; + bool _talkToAbort; + int _talkCounter; + int _talkTo; + int _scriptMoreFlag; + bool _openTalkWindow; + Common::String _scriptName; + bool _moreTalkUp, _moreTalkDown; + int _converseNum; + const byte *_opcodes; + +public: + static Talk *init(SherlockEngine *vm); + virtual ~Talk() {} + + /** + * Return a given talk statement + */ + Statement &operator[](int idx) { return _statements[idx]; } + + /** + * Called whenever a conversation or item script needs to be run. For standard conversations, + * it opens up a description window similar to how 'talk' does, but shows a 'reply' directly + * instead of waiting for a statement option. + * @remarks It seems that at some point, all item scripts were set up to use this as well. + * In their case, the conversation display is simply suppressed, and control is passed on to + * doScript to implement whatever action is required. + */ + void talkTo(const Common::String &filename); + + /** + * Main method for handling conversations when a character to talk to has been + * selected. It will make Holmes walk to the person to talk to, draws the + * interface window for the conversation and passes on control to give the + * player a list of options to make a selection from + */ + void talk(int objNum); + + /** + * Clear loaded talk data + */ + void freeTalkVars(); + + /** + * Draws the interface for conversation display + */ + void drawInterface(); + + /** + * Opens the talk file 'talk.tlk' and searches the index for the specified + * conversation. If found, the data for that conversation is loaded + */ + void loadTalkFile(const Common::String &filename); + + /** + * Change the sequence of a background object corresponding to a given speaker. + * The new sequence will display the character as "listening" + */ + void setStillSeq(int speaker); + + /** + * Clears the stack of pending object sequences associated with speakers in the scene + */ + void clearSequences(); + + /** + * Pulls a background object sequence from the sequence stack and restore's the + * object's sequence + */ + void pullSequence(); + + /** + * Push the sequence of a background object that's an NPC that needs to be + * saved onto the sequence stack. + */ + void pushSequence(int speaker); + + /** + * Push a given shape's sequence data onto the Rose Tattoo talk sequence stack + */ + void pushTalkSequence(Object *obj); + + /** + * Returns true if the script stack is empty + */ + bool isSequencesEmpty() const { return _scriptStack.empty(); } + + /** + * Pops an entry off of the script stack + */ + void popStack(); + + /** + * Synchronize the data for a savegame + */ + void synchronize(Serializer &s); +}; + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/tattoo/tattoo.cpp b/engines/sherlock/tattoo/tattoo.cpp new file mode 100644 index 0000000000..c6df9942a7 --- /dev/null +++ b/engines/sherlock/tattoo/tattoo.cpp @@ -0,0 +1,119 @@ +/* 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 "engines/util.h" +#include "sherlock/tattoo/tattoo.h" +#include "sherlock/tattoo/tattoo_resources.h" +#include "sherlock/tattoo/tattoo_scene.h" +#include "sherlock/tattoo/widget_base.h" +#include "sherlock/people.h" + +namespace Sherlock { + +namespace Tattoo { + +TattooEngine::TattooEngine(OSystem *syst, const SherlockGameDescription *gameDesc) : + SherlockEngine(syst, gameDesc) { + _creditsActive = false; + _runningProlog = false; + _fastMode = false; + _allowFastMode = true; + _transparentMenus = true; +} + +TattooEngine::~TattooEngine() { +} + +void TattooEngine::showOpening() { + // TODO +} + +void TattooEngine::initialize() { + initGraphics(640, 480, true); + + // Initialize the base engine + SherlockEngine::initialize(); + + // Initialise the global flags + _flags.resize(3200); + _flags[1] = _flags[4] = _flags[76] = true; + _runningProlog = true; + + // Add some more files to the cache + _res->addToCache("walk.lib"); + + // Set up list of people + for (int idx = 0; idx < TATTOO_MAX_PEOPLE; ++idx) { + _people->_characters.push_back(PersonData( + getLanguage() == Common::FR_FRA ? FRENCH_NAMES[idx] : ENGLISH_NAMES[idx], + PORTRAITS[idx], nullptr, nullptr)); + } + + // Starting scene + _scene->_goToScene = STARTING_INTRO_SCENE; + + // Load an initial palette + loadInitialPalette(); +} + +void TattooEngine::startScene() { + if (_scene->_goToScene == OVERHEAD_MAP || _scene->_goToScene == OVERHEAD_MAP2) { + // Show the map + _scene->_currentScene = OVERHEAD_MAP; + _scene->_goToScene = _map->show(); + + _people->_hSavedPos = Common::Point(-1, -1); + _people->_hSavedFacing = -1; + } + + // TODO +} + +void TattooEngine::loadInitialPalette() { + byte palette[768]; + Common::SeekableReadStream *stream = _res->load("room.pal"); + stream->read(palette, PALETTE_SIZE); + _screen->translatePalette(palette); + _screen->setPalette(palette); + + delete stream; +} + +void TattooEngine::drawCredits() { + // TODO +} + +void TattooEngine::blitCredits() { + // TODO +} + +void TattooEngine::eraseCredits() { + // TODO +} + +void TattooEngine::doHangManPuzzle() { + // TODO +} + +} // End of namespace Tattoo + +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/tattoo.h b/engines/sherlock/tattoo/tattoo.h new file mode 100644 index 0000000000..9ef2e0a563 --- /dev/null +++ b/engines/sherlock/tattoo/tattoo.h @@ -0,0 +1,88 @@ +/* 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 SHERLOCK_TATTOO_H +#define SHERLOCK_TATTOO_H + +#include "sherlock/sherlock.h" + +namespace Sherlock { + +namespace Tattoo { + +enum { + INFO_TOP = 185, + INFO_MIDDLE = 186, + INFO_BOTTOM = 188, + MENU_BACKGROUND = 225 +}; + +class TattooEngine : public SherlockEngine { +private: + /** + * Loads the initial palette for the game + */ + void loadInitialPalette(); +protected: + /** + * Initialize the engine + */ + virtual void initialize(); + + virtual void showOpening(); + + /** + * Starting a scene within the game + */ + virtual void startScene(); +public: + bool _creditsActive; + bool _runningProlog; + bool _fastMode, _allowFastMode; + bool _transparentMenus; +public: + TattooEngine(OSystem *syst, const SherlockGameDescription *gameDesc); + virtual ~TattooEngine(); + + /** + * Draw credits on the screen + */ + void drawCredits(); + + /** + * Blit the drawn credits to the screen + */ + void blitCredits(); + + /** + * Erase any area of the screen covered by credits + */ + void eraseCredits(); + + void doHangManPuzzle(); +}; + +} // End of namespace Tattoo + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/tattoo/tattoo_journal.cpp b/engines/sherlock/tattoo/tattoo_journal.cpp new file mode 100644 index 0000000000..e3f681bc3c --- /dev/null +++ b/engines/sherlock/tattoo/tattoo_journal.cpp @@ -0,0 +1,902 @@ +/* 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 "sherlock/tattoo/tattoo_journal.h" +#include "sherlock/tattoo/tattoo_scene.h" +#include "sherlock/tattoo/tattoo_user_interface.h" +#include "sherlock/tattoo/tattoo.h" + +namespace Sherlock { + +namespace Tattoo { + +#define JOURNAL_BAR_WIDTH 450 +#define S_NO_TEXT "Text Not Found !" + +static const char *const JOURNAL_COMMANDS[2] = { "Close Journal", "Search Journal" }; + +static const char *const JOURNAL_SEARCH_COMMANDS[3] = { "Abort Search", "Search Backwards", "Search Forwards" }; + +TattooJournal::TattooJournal(SherlockEngine *vm) : Journal(vm) { + _journalImages = nullptr; + _selector = _oldSelector = 0; + _wait = false; + _exitJournal = false; + _scrollingTimer = 0; + _savedIndex = _savedSub = _savedPage = 0; + + loadLocations(); +} + +void TattooJournal::show() { + Events &events = *_vm->_events; + Resources &res = *_vm->_res; + Screen &screen = *_vm->_screen; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + byte palette[PALETTE_SIZE]; + + // Load journal images + _journalImages = new ImageFile("journal.vgs"); + + // Load palette + Common::SeekableReadStream *stream = res.load("journal.pal"); + stream->read(palette, PALETTE_SIZE); + screen.translatePalette(palette); + ui.setupBGArea(palette); + + // Set screen to black, and set background + screen._backBuffer1.blitFrom((*_journalImages)[0], Common::Point(0, 0)); + screen.empty(); + screen.setPalette(palette); + + if (_journal.empty()) { + _up = _down = false; + } else { + drawJournal(0, 0); + } + drawControls(0); + screen.slamRect(Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); + + _exitJournal = false; + _scrollingTimer = 0; + + do { + events.pollEventsAndWait(); + Common::Point mousePos = events.mousePos(); + _wait = true; + + handleKeyboardEvents(); + highlightJournalControls(true); + + handleButtons(); + + if (_wait) + events.wait(2); + + } while (!_vm->shouldQuit() && !_exitJournal); + + // Clear events + events.clearEvents(); + + // Free the images + delete _journalImages; +} + +void TattooJournal::handleKeyboardEvents() { + Events &events = *_vm->_events; + Screen &screen = *_vm->_screen; + Common::Point mousePos = events.mousePos(); + + if (!events.kbHit()) + return; + + Common::KeyState keyState = events.getKey(); + + if (keyState.keycode == Common::KEYCODE_TAB && (keyState.flags & Common::KBD_SHIFT)) { + // Shift tab + Common::Rect r(JOURNAL_BAR_WIDTH, BUTTON_SIZE + screen.fontHeight() + 13); + r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, SHERLOCK_SCREEN_HEIGHT - r.height()); + + // See if mouse is over any of the journal controls + _selector = -1; + if (Common::Rect(r.left + 3, r.top + 3, r.right - 3, r.top + screen.fontHeight() + 4).contains(mousePos)) + _selector = (mousePos.x - r.left) / (r.width() / 3); + + // If the mouse is not over an option, move the mouse to that it points to the first option + if (_selector == -1) { + events.warpMouse(Common::Point(r.left + r.width() / 3 - 10, r.top + screen.fontHeight() + 2)); + } else { + if (_selector == 0) + _selector = 2; + else + --_selector; + + events.warpMouse(Common::Point(r.left + (r.width() / 3) * (_selector + 1) - 10, mousePos.y)); + } + + } else if (keyState.keycode == Common::KEYCODE_PAGEUP || keyState.keycode == Common::KEYCODE_KP9) { + // See if they have Shift held down to go forward 10 pages + if (keyState.flags & Common::KBD_SHIFT) { + if (_page > 1) { + // Scroll Up 10 pages if possible + if (_page < 11) + drawJournal(1, (_page - 1) * LINES_PER_PAGE); + else + drawJournal(1, 10 * LINES_PER_PAGE); + + drawScrollBar(); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + _wait = false; + } + } else { + if (_page > 1) { + // Scroll Up 1 page + drawJournal(1, LINES_PER_PAGE); + drawScrollBar(); + show(); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + _wait = false; + } + } + + } else if (keyState.keycode == Common::KEYCODE_PAGEDOWN || keyState.keycode == Common::KEYCODE_KP3) { + if (keyState.flags & Common::KBD_SHIFT) { + if (_down) { + // Scroll down 10 Pages + if (_page + 10 > _maxPage) + drawJournal(2, (_maxPage - _page) * LINES_PER_PAGE); + else + drawJournal(2, 10 * LINES_PER_PAGE); + drawScrollBar(); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + + _wait = false; + } + } else { + if (_down) { + // Scroll down 1 page + drawJournal(2, LINES_PER_PAGE); + drawScrollBar(); + show(); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + + _wait = false; + } + } + + } else if (keyState.keycode == Common::KEYCODE_HOME || keyState.keycode == Common::KEYCODE_KP7) { + // Scroll to start of journal + if (_page > 1) { + // Go to the beginning of the journal + _index = _sub = _up = _down = 0; + _page = 1; + + drawFrame(); + drawJournal(0, 0); + + drawScrollBar(); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + + _wait = false; + } + + } else if (keyState.keycode == Common::KEYCODE_END || keyState.keycode == Common::KEYCODE_KP1) { + // Scroll to end of journal + if (_down) { + // Go to the end of the journal + drawJournal(2, 100000); + drawScrollBar(); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + + _wait = false; + } + } else if (keyState.keycode == Common::KEYCODE_RETURN || keyState.keycode == Common::KEYCODE_KP_ENTER) { + events._pressed = false; + events._released = true; + events._oldButtons = 0; + } else if (keyState.keycode == Common::KEYCODE_ESCAPE) { + _exitJournal = true; + } else if (keyState.keycode == Common::KEYCODE_TAB) { + Common::Rect r(JOURNAL_BAR_WIDTH, BUTTON_SIZE + screen.fontHeight() + 13); + r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, SHERLOCK_SCENE_HEIGHT - r.height()); + + // See if the mouse is over any of the journal controls + _selector = -1; + if (Common::Rect(r.left + 3, r.top + 3, r.right - 3, r.top + screen.fontHeight() + 4).contains(mousePos)) + _selector = (mousePos.x - r.left) / (r.width() / 3); + + // If the mouse is not over any of the options, move the mouse so that it points to the first option + if (_selector == -1) { + events.warpMouse(Common::Point(r.left + r.width() / 3 - 10, r.top + screen.fontHeight() + 2)); + } else { + if (_selector == 2) + _selector = 0; + else + ++_selector; + + events.warpMouse(Common::Point(r.left + (r.width() / 3) * (_selector + 1) - 10, mousePos.y)); + } + } +} + +void TattooJournal::handleButtons() { + Events &events = *_vm->_events; + Screen &screen = *_vm->_screen; + uint32 frameCounter = events.getFrameCounter(); + + if (_selector != -1 && events._pressed) { + if (frameCounter >= _scrollingTimer) { + // Set next scrolling time + _scrollingTimer = frameCounter + 6; + + // Handle different scrolling actions + switch (_selector) { + case 3: + // Scroll left (1 page back) + if (_page > 1) { + // Scroll Up + drawJournal(1, LINES_PER_PAGE); + drawScrollBar(); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + _wait = false; + } + break; + + case 4: + // Page left (10 pages back) + if (_page > 1) { + // Scroll Up 10 Pages if possible + if (_page < 11) + drawJournal(1, (_page - 1) * LINES_PER_PAGE); + else + drawJournal(1, 10 * LINES_PER_PAGE); + drawScrollBar(); + show(); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + _wait = false; + } + break; + + case 5: + // Page right (10 pages ahead) + if (_down) { + // Scroll Down 10 Pages + if (_page + 10 > _maxPage) + drawJournal(2, (_maxPage - _page) * LINES_PER_PAGE); + else + drawJournal(2, 10 * LINES_PER_PAGE); + drawScrollBar(); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + _wait = false; + } + break; + + case 6: + // Scroll right (1 Page Ahead) + if (_down) { + // Scroll Down + drawJournal(2, LINES_PER_PAGE); + drawScrollBar(); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + _wait = false; + } + break; + + default: + break; + } + } + } + + if (events._released || events._rightReleased) { + _scrollingTimer = 0; + + switch (_selector) { + case 0: + _exitJournal = true; + break; + + case 1: { + // Search Journal + disableControls(); + + bool notFound = false; + int dir; + + do { + if ((dir = getFindName(notFound)) != 0) { + _savedIndex = _index; + _savedSub = _sub; + _savedPage = _page; + + if (drawJournal(dir + 2, 1000 * LINES_PER_PAGE) == 0) + { + _index = _savedIndex; + _sub = _savedSub; + _page = _savedPage; + + drawFrame(); + drawJournal(0, 0); + notFound = true; + } else { + break; + } + + highlightJournalControls(false); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + } else { + break; + } + } while (!_vm->shouldQuit()); + break; + } + + case 2: + // Print Journal - not implemented in ScummVM + break; + + default: + break; + } + } +} + +void TattooJournal::loadLocations() { + Resources &res = *_vm->_res; + + _directory.clear(); + _locations.clear(); + + Common::SeekableReadStream *dir = res.load("talk.lib"); + dir->skip(4); // Skip header + + // Get the numer of entries + _directory.resize(dir->readUint16LE()); + dir->seek((_directory.size() + 1) * 8, SEEK_CUR); + + // Read in each entry + char buffer[17]; + for (uint idx = 0; idx < _directory.size(); ++idx) { + dir->read(buffer, 17); + buffer[16] = '\0'; + + _directory[idx] = Common::String(buffer); + } + + delete dir; + + // Load in the locations stored in journal.txt + Common::SeekableReadStream *loc = res.load("journal.txt"); + + // Initialize locations + _locations.resize(100); + for (int idx = 0; idx < 100; ++idx) + _locations[idx] = "No Description"; + + while (loc->pos() < loc->size()) { + // In Rose Tattoo, each location line starts with the location + // number, followed by a dot, some spaces and its description + // in quotes + Common::String line = loc->readLine(); + Common::String locNumStr; + int locNum = 0; + int i = 0; + Common::String locDesc; + + // Get the location + while (Common::isDigit(line[i])) { + locNumStr += line[i]; + i++; + } + locNum = atoi(locNumStr.c_str()); + + // Skip the dot, spaces and initial quotation mark + while (line[i] == ' ' || line[i] == '.' || line[i] == '\"') + i++; + + do { + locDesc += line[i]; + i++; + } while (line[i] != '\"'); + + _locations[locNum] = locDesc; + } + + delete loc; +} + +void TattooJournal::drawFrame() { + Screen &screen = *_vm->_screen; + + screen._backBuffer1.blitFrom((*_journalImages)[0], Common::Point(0, 0)); + drawControls(0); + +} + +void TattooJournal::synchronize(Serializer &s) { + // TODO +} + +void TattooJournal::drawControls(int mode) { + TattooEngine &vm = *(TattooEngine *)_vm; + Screen &screen = *_vm->_screen; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + ImageFile &images = *ui._interfaceImages; + + Common::Rect r(JOURNAL_BAR_WIDTH, !mode ? (BUTTON_SIZE + screen.fontHeight() + 13) : + (screen.fontHeight() + 4) * 2 + 9); + r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, !mode ? (SHERLOCK_SCREEN_HEIGHT - r.height()) : + (SHERLOCK_SCREEN_HEIGHT - r.height()) / 2); + + Common::Rect inner = r; + inner.grow(-3); + + if (vm._transparentMenus) + ui.makeBGArea(inner); + else + screen._backBuffer1.fillRect(inner, MENU_BACKGROUND); + + // Draw the four corners of the info box + screen._backBuffer1.transBlitFrom(images[0], Common::Point(r.left, r.top)); + screen._backBuffer1.transBlitFrom(images[1], Common::Point(r.right - images[1]._width, r.top)); + screen._backBuffer1.transBlitFrom(images[1], Common::Point(r.left, r.bottom - images[1]._height)); + screen._backBuffer1.transBlitFrom(images[1], Common::Point(r.right - images[1]._width, r.bottom - images[1]._height)); + + // Draw the top of the info box + screen._backBuffer1.hLine(r.left + images[0]._width, r.top, r.right - images[0]._height, INFO_TOP); + screen._backBuffer1.hLine(r.left + images[0]._width, r.top + 1, r.right - images[0]._height, INFO_MIDDLE); + screen._backBuffer1.hLine(r.left + images[0]._width, r.top + 2, r.right - images[0]._height, INFO_BOTTOM); + + // Draw the bottom of the info box + screen._backBuffer1.hLine(r.left + images[0]._width, r.bottom - 3, r.right - images[0]._height, INFO_TOP); + screen._backBuffer1.hLine(r.left + images[0]._width, r.bottom - 2, r.right - images[0]._height, INFO_MIDDLE); + screen._backBuffer1.hLine(r.left + images[0]._width, r.bottom - 1, r.right - images[0]._height, INFO_BOTTOM); + + // Draw the left side of the info box + screen._backBuffer1.vLine(r.left, r.top + images[0]._height, r.bottom - images[2]._height, INFO_TOP); + screen._backBuffer1.vLine(r.left + 1, r.top + images[0]._height, r.bottom - images[2]._height, INFO_MIDDLE); + screen._backBuffer1.vLine(r.left + 2, r.top + images[0]._height, r.bottom - images[2]._height, INFO_BOTTOM); + + // Draw the right side of the info box + screen._backBuffer1.vLine(r.right - 3, r.top + images[0]._height, r.bottom - images[2]._height, INFO_TOP); + screen._backBuffer1.vLine(r.right - 2, r.top + images[0]._height, r.bottom - images[2]._height, INFO_MIDDLE); + screen._backBuffer1.vLine(r.right - 1, r.top + images[0]._height, r.bottom - images[2]._height, INFO_BOTTOM); + + // Draw the sides of the separator bar above the scroll bar + int yp = r.top + screen.fontHeight() + 7; + screen._backBuffer1.transBlitFrom(images[4], Common::Point(r.left, yp - 1)); + screen._backBuffer1.transBlitFrom(images[5], Common::Point(r.right - images[5]._width, yp - 1)); + + // Draw the bar above the scroll bar + screen._backBuffer1.hLine(r.left + images[4]._width, yp, r.right - images[5]._width, INFO_TOP); + screen._backBuffer1.hLine(r.left + images[4]._width, yp + 1, r.right - images[5]._width, INFO_MIDDLE); + screen._backBuffer1.hLine(r.left + images[4]._width, yp + 2, r.right - images[5]._width, INFO_BOTTOM); + + if (mode != 2) { + // Draw the Bars separating the Journal Commands + int xp = r.right / 3; + for (int idx = 0; idx < 2; ++idx) { + screen._backBuffer1.transBlitFrom(images[6], Common::Point(xp - 2, r.top + 1)); + screen._backBuffer1.transBlitFrom(images[7], Common::Point(xp - 2, yp - 1)); + + screen._backBuffer1.hLine(xp - 1, r.top + 4, yp - 2, INFO_TOP); + screen._backBuffer1.hLine(xp, r.top + 4, yp - 2, INFO_MIDDLE); + screen._backBuffer1.hLine(xp + 1, r.top + 4, yp - 2, INFO_BOTTOM); + xp = r.right / 3 * 2; + } + } + + int savedSelector = _oldSelector; + _oldSelector = 100; + + switch (mode) { + case 0: + highlightJournalControls(false); + break; + case 1: + highlightSearchControls(false); + break; + default: + break; + } + + _oldSelector = savedSelector; +} + +void TattooJournal::highlightJournalControls(bool slamIt) { + Events &events = *_vm->_events; + Screen &screen = *_vm->_screen; + Common::Point mousePos = events.mousePos(); + Common::Rect r(JOURNAL_BAR_WIDTH, BUTTON_SIZE + screen.fontHeight() + 13); + r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, SHERLOCK_SCREEN_HEIGHT - r.height()); + + // Calculate the Scroll Position Bar + int numPages = (_maxPage + LINES_PER_PAGE) / LINES_PER_PAGE; + int barWidth = (r.width() - BUTTON_SIZE * 2 - 6) / numPages; + barWidth = CLIP(barWidth, BUTTON_SIZE, r.width() - BUTTON_SIZE * 2 - 6); + + int barX = (numPages <= 1) ? r.left + 3 + BUTTON_SIZE : (r.width() - BUTTON_SIZE * 2 - 6 - barWidth) + * FIXED_INT_MULTIPLIER / (numPages - 1) * (_page - 1) / FIXED_INT_MULTIPLIER + r.left + 3 + BUTTON_SIZE; + + // See if the mouse is over any of the Journal Controls + Common::Rect bounds(r.left, r.top, r.right - 3, r.top + screen.fontHeight() + 7); + _selector = -1; + if (bounds.contains(mousePos)) + _selector = (mousePos.x - r.left) / (r.width() / 3); + + else if (events._pressed) { + if (Common::Rect(r.left, r.top + screen.fontHeight() + 10, r.left + BUTTON_SIZE, r.top + + screen.fontHeight() + 10 + BUTTON_SIZE).contains(mousePos)) + // Press on the Scroll Left button + _selector = 3; + else if (Common::Rect(r.left + BUTTON_SIZE + 3, r.top + screen.fontHeight() + 10, + r.left + BUTTON_SIZE + 3 + (barX - r.left - BUTTON_SIZE - 3), r.top + screen.fontHeight() + + 10 + BUTTON_SIZE).contains(mousePos)) + // Press on the Page Left button + _selector = 4; + else if (Common::Rect(barX + barWidth, r.top + screen.fontHeight() + 10, + barX + barWidth + (r.right - BUTTON_SIZE - 3 - barX - barWidth), + r.top + screen.fontHeight() + 10 + BUTTON_SIZE).contains(mousePos)) + // Press on the Page Right button + _selector = 5; + else if (Common::Rect(r.right - BUTTON_SIZE - 3, r.top + screen.fontHeight() + 10, r.right - 3, + r.top + screen.fontHeight() + 10 + BUTTON_SIZE).contains(mousePos)) + // Press of the Scroll Right button + _selector = 6; + } + + // See if the Search was selected, but is not available + if (_journal.empty() && (_selector == 1 || _selector == 2)) + _selector = -1; + + if (_selector == 4 && _oldSelector == 5) + _selector = 5; + else if (_selector == 5 && _oldSelector == 4) + _selector = 4; + + // See if they're pointing at a different control + if (_selector != _oldSelector) { + // Print the Journal commands + int xp = r.left + r.width() / 6; + byte color = (_selector == 0) ? COMMAND_HIGHLIGHTED : INFO_TOP; + + screen.gPrint(Common::Point(xp - screen.stringWidth(JOURNAL_COMMANDS[0]) / 2, r.top), + color, "%s", JOURNAL_COMMANDS[0]); + xp += r.width() / 3; + + if (!_journal.empty()) + color = (_selector == 1) ? COMMAND_HIGHLIGHTED : INFO_TOP; + else + color = INFO_BOTTOM; + screen.gPrint(Common::Point(xp - screen.stringWidth(JOURNAL_COMMANDS[0]) / 2, r.top + 5), + color, "%s", JOURNAL_COMMANDS[1]); + + drawScrollBar(); + + if (slamIt) + screen.slamRect(r); + + _oldSelector = _selector; + } +} + +void TattooJournal::highlightSearchControls(bool slamIt) { + Events &events = *_vm->_events; + Screen &screen = *_vm->_screen; + Common::Point mousePos = events.mousePos(); + Common::Rect r(JOURNAL_BAR_WIDTH, (screen.fontHeight() + 4) * 2 + 9); + r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, (SHERLOCK_SCREEN_HEIGHT - r.height()) / 2); + + // See if the mouse is over any of the Journal Controls + _selector = -1; + if (Common::Rect(r.left + 3, r.top + 3, r.right - 3, r.top + 7 + screen.fontHeight()).contains(mousePos)) + _selector = (mousePos.x - r.left) / (r.width() / 3); + + // See if they're pointing at a different control + if (_selector != _oldSelector) { + // Print the search commands + int xp = r.left + r.width() / 6; + + for (int idx = 0; idx < 3; ++idx) { + byte color = (_selector == idx) ? COMMAND_HIGHLIGHTED : INFO_TOP; + screen.gPrint(Common::Point(xp - screen.stringWidth(JOURNAL_SEARCH_COMMANDS[idx]) / 2, + r.top + 5), color, "%s", JOURNAL_SEARCH_COMMANDS[idx]); + xp += r.width() / 3; + } + + if (slamIt) + screen.slamRect(r); + + _oldSelector = _selector; + } +} + +void TattooJournal::drawScrollBar() { + Events &events = *_vm->_events; + Screen &screen = *_vm->_screen; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + Common::Point mousePos = events.mousePos(); + bool raised; + byte color; + + Common::Rect r(JOURNAL_BAR_WIDTH, BUTTON_SIZE + screen.fontHeight() + 13); + r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, SHERLOCK_SCREEN_HEIGHT - r.height()); + + // Calculate the Scroll Position Bar + int numPages = (_maxPage + LINES_PER_PAGE) / LINES_PER_PAGE; + int barWidth = (r.width() - BUTTON_SIZE * 2 - 6) / numPages; + barWidth = CLIP(barWidth, BUTTON_SIZE, r.width() - BUTTON_SIZE * 2 - 6); + int barX; + if (numPages <= 1) { + barX = r.left + 3 + BUTTON_SIZE; + } else { + barX = (r.width() - BUTTON_SIZE * 2 - 6 - barWidth) * FIXED_INT_MULTIPLIER / (numPages - 1) * + (_page - 1) / FIXED_INT_MULTIPLIER + r.left + 3 + BUTTON_SIZE; + if (barX + BUTTON_SIZE > r.left + r.width() - BUTTON_SIZE - 3) + barX = r.right - BUTTON_SIZE * 2 - 3; + } + + // Draw the scroll bar here + // Draw the Scroll Left button + raised = _selector != 3; + screen._backBuffer1.fillRect(Common::Rect(r.left, r.top + screen.fontHeight() + 12, r.left + BUTTON_SIZE, + r.top + screen.fontHeight() + BUTTON_SIZE + 9), INFO_MIDDLE); + ui.drawDialogRect(screen._backBuffer1, Common::Rect(r.left + 3, r.top + screen.fontHeight() + 10, r.left + 3 + BUTTON_SIZE, + r.top + screen.fontHeight() + 10 + BUTTON_SIZE), raised); + + color = (_page > 1) ? INFO_BOTTOM + 2 : INFO_BOTTOM; + screen._backBuffer1.vLine(r.left + 1 + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 10 + BUTTON_SIZE / 2, + r.top + screen.fontHeight() + 10 + BUTTON_SIZE / 2, color); + screen._backBuffer1.vLine(r.left + 2 + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 9 + BUTTON_SIZE / 2, + r.top + screen.fontHeight() + 11 + BUTTON_SIZE / 2, color); + screen._backBuffer1.vLine(r.left + 3 + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 8 + BUTTON_SIZE / 2, + r.top + screen.fontHeight() + 12 + BUTTON_SIZE / 2, color); + screen._backBuffer1.vLine(r.left + 4 + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 7 + BUTTON_SIZE / 2, + r.top + screen.fontHeight() + 13 + BUTTON_SIZE / 2, color); + + // Draw the Scroll Right button + raised = _selector != 6; + screen._backBuffer1.fillRect(Common::Rect(r.right - BUTTON_SIZE - 1, r.top + screen.fontHeight() + 12, + r.right - 5, r.top + screen.fontHeight() + BUTTON_SIZE + 9), INFO_MIDDLE); + ui.drawDialogRect(screen._backBuffer1, Common::Rect(r.right - BUTTON_SIZE - 3, r.top + screen.fontHeight() + 10, r.right - 3, + r.top + screen.fontHeight() + BUTTON_SIZE + 9), raised); + + color = _down ? INFO_BOTTOM + 2 : INFO_BOTTOM; + screen._backBuffer1.vLine(r.right - 1 - BUTTON_SIZE + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 10 + BUTTON_SIZE / 2, + r.top + screen.fontHeight() + 10 + BUTTON_SIZE / 2, color); + screen._backBuffer1.vLine(r.right - 2 - BUTTON_SIZE + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 9 + BUTTON_SIZE / 2, + r.top + screen.fontHeight() + 11 + BUTTON_SIZE / 2, color); + screen._backBuffer1.vLine(r.right - 3 - BUTTON_SIZE + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 8 + BUTTON_SIZE / 2, + r.top + screen.fontHeight() + 12 + BUTTON_SIZE / 2, color); + screen._backBuffer1.vLine(r.right - 4 - BUTTON_SIZE + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 7 + BUTTON_SIZE / 2, + r.top + screen.fontHeight() + 13 + BUTTON_SIZE / 2, color); + + // Draw the scroll bar + screen._backBuffer1.fillRect(Common::Rect(barX + 2, r.top + screen.fontHeight() + 12, barX + barWidth - 3, + r.top + screen.fontHeight() + BUTTON_SIZE + 9), INFO_MIDDLE); + ui.drawDialogRect(screen._backBuffer1, Common::Rect(barX, r.top + screen.fontHeight() + 10, barX + barWidth, + r.top + screen.fontHeight() + 10 + BUTTON_SIZE), true); +} + +void TattooJournal::disableControls() { + Screen &screen = *_vm->_screen; + Common::Rect r(JOURNAL_BAR_WIDTH, BUTTON_SIZE + screen.fontHeight() + 13); + r.moveTo((SHERLOCK_SCREEN_HEIGHT - r.width()) / 2, SHERLOCK_SCREEN_HEIGHT - r.height()); + + // Print the Journal commands + int xp = r.left + r.width() / 6; + for (int idx = 0; idx < 3; ++idx) { + screen.gPrint(Common::Point(xp - screen.stringWidth(JOURNAL_COMMANDS[idx]) / 2, r.top + 5), + INFO_BOTTOM, "%s", JOURNAL_COMMANDS[idx]); + + xp += r.width() / 3; + } + + screen.slamRect(r); +} + +int TattooJournal::getFindName(bool printError) { + Events &events = *_vm->_events; + Screen &screen = *_vm->_screen; + Talk &talk = *_vm->_talk; + int result = 0; + int done = 0; + Common::String name; + int cursorX, cursorY; + bool flag = false; + + Common::Rect r(JOURNAL_BAR_WIDTH, (screen.fontHeight() + 4) * 2 + 9); + r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, (SHERLOCK_SCREEN_HEIGHT - r.height()) / 2); + + // Set the cursors Y position + cursorY = r.top + screen.fontHeight() + 12; + + drawControls(1); + + // Backup the area under the text entry + Surface bgSurface(r.width() - 6, screen.fontHeight()); + bgSurface.blitFrom(screen._backBuffer1, Common::Point(0, 0), Common::Rect(r.left + 3, cursorY, + r.right - 3, cursorY + screen.fontHeight())); + + if (printError) { + screen.gPrint(Common::Point(0, cursorY), INFO_TOP, "%s", S_NO_TEXT); + } else { + // If there was a name already entered, copy it to name and display it + if (!_find.empty()) { + screen.gPrint(Common::Point(r.left + screen.widestChar() + 3, cursorY), COMMAND_HIGHLIGHTED, "%s", _find.c_str()); + name = _find; + } + } + + screen.slamRect(r); + + if (printError) { + // Pause to allow error to be shown + int timer = 0; + + do { + events.pollEvents(); + events.setButtonState(); + + ++timer; + events.wait(2); + } while (!_vm->shouldQuit() && !events.kbHit() && !events._released && !events._rightReleased && timer < 40); + + events.clearEvents(); + + // Restore the text background + screen._backBuffer1.blitFrom(bgSurface, Common::Point(r.left, cursorY)); + + // If there was a name already entered, copy it to name and display it + if (!_find.empty()) { + screen.gPrint(Common::Point(r.left + screen.widestChar() + 3, cursorY), COMMAND_HIGHLIGHTED, "%s", _find.c_str()); + name = _find; + } + + screen.slamArea(r.left + 3, cursorY, r.width() - 6, screen.fontHeight()); + } + + // Set the cursors X position + cursorX = r.left + screen.widestChar() + 3 + screen.stringWidth(name); + + do { + events._released = events._rightReleased = false; + + while (!events.kbHit() && !events._released && !events._rightReleased) { + if (talk._talkToAbort) + return 0; + + // See if a key or a mouse button is pressed + events.pollEventsAndWait(); + events.setButtonState(); + + // Handle blinking cursor + flag = !flag; + if (flag) { + // Draw cursor + screen._backBuffer1.fillRect(Common::Rect(cursorX, cursorY, cursorX + 7, cursorY + 8), COMMAND_HIGHLIGHTED); + screen.slamArea(cursorX, cursorY, 8, 9); + } else { + // Erase cursor by restoring background and writing current text + screen._backBuffer1.blitFrom(bgSurface, Common::Point(r.left + 3, cursorY)); + screen.gPrint(Common::Point(r.left + screen.widestChar() + 3, cursorY), COMMAND_HIGHLIGHTED, "%s", name.c_str()); + screen.slamArea(r.left + 3, r.top, r.width() - 3, screen.fontHeight()); + } + + highlightSearchControls(true); + + events.wait(2); + } + + if (events.kbHit()) { + Common::KeyState keyState = events.getKey(); + Common::Point mousePos = events.mousePos(); + + if (keyState.keycode == Common::KEYCODE_BACKSPACE && !name.empty()) { + cursorX -= screen.charWidth(name.lastChar()); + name.deleteLastChar(); + } + + if (keyState.keycode == Common::KEYCODE_RETURN) + done = 1; + + else if (keyState.keycode == Common::KEYCODE_ESCAPE) + done = -1; + + if (keyState.keycode == Common::KEYCODE_TAB) { + r = Common::Rect(JOURNAL_BAR_WIDTH, BUTTON_SIZE + screen.fontHeight() + 13); + r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, (SHERLOCK_SCREEN_HEIGHT - r.height()) / 2); + + // See if the mouse is over any of the journal controls + _selector = -1; + if (Common::Rect(r.left + 3, r.top + 3, r.right - 3, r.top + screen.fontHeight() + 4).contains(mousePos)) + _selector = (mousePos.x - r.left) / (r.width() / 3); + + // If the mouse is not over any of the options, move the mouse so that it points to the first option + if (_selector == -1) { + events.warpMouse(Common::Point(r.left + r.width() / 3, r.top + screen.fontHeight() + 2)); + } else { + if (keyState.keycode & Common::KBD_SHIFT) { + if (_selector == 0) + _selector = 2; + else + --_selector; + } else { + if (_selector == 2) + _selector = 0; + else + ++_selector; + } + + events.warpMouse(Common::Point(r.left + (r.width() / 3) * (_selector + 1) - 10, mousePos.y)); + } + } + + if (keyState.ascii && keyState.ascii != '@' && name.size() < 50) { + if ((cursorX + screen.charWidth(keyState.ascii)) < (r.right - screen.widestChar() * 3)) { + cursorX += screen.charWidth(keyState.ascii); + name += toupper(keyState.ascii); + } + } + + // Redraw the text + screen._backBuffer1.blitFrom(bgSurface, Common::Point(r.left + 3, cursorY)); + screen.gPrint(Common::Point(r.left + screen.widestChar() + 3, cursorY), COMMAND_HIGHLIGHTED, + "%s", name.c_str()); + screen.slamArea(r.left + 3, cursorY, r.right - 3, screen.fontHeight()); + } + + if (events._released || events._rightReleased) { + switch (_selector) + { + case 0: + done = -1; + break; + case 1: + done = 2; + break; + case 2: + done = 1; + break; + default: + break; + } + } + } while (!done); + + if (done != -1) { + _find = name; + result = done; + } else { + result = 0; + } + + drawFrame(); + drawJournal(0, 0); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + + return result; +} + +} // End of namespace Tattoo + +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/tattoo_journal.h b/engines/sherlock/tattoo/tattoo_journal.h new file mode 100644 index 0000000000..7169e60359 --- /dev/null +++ b/engines/sherlock/tattoo/tattoo_journal.h @@ -0,0 +1,108 @@ +/* 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 SHERLOCK_TATTOO_JOURNAL_H +#define SHERLOCK_TATTOO_JOURNAL_H + +#include "sherlock/journal.h" +#include "sherlock/image_file.h" + +namespace Sherlock { + +namespace Tattoo { + +class TattooJournal : public Journal { +private: + ImageFile *_journalImages; + int _selector, _oldSelector; + bool _wait; + bool _exitJournal; + uint32 _scrollingTimer; + int _savedIndex, _savedSub, _savedPage; + + /** + * Load the list of journal locations + */ + void loadLocations(); + + /** + * Displays the controls used by the journal + * @param mode 0: Normal journal buttons, 1: Search interface + */ + void drawControls(int mode); + + /** + * Draw the journal controls used by the journal + */ + void highlightJournalControls(bool slamIt); + + /** + * Draw the journal controls used in search mode + */ + void highlightSearchControls(bool slamIt); + + void drawScrollBar(); + + /** + * Check for and handle any pending keyboard events + */ + void handleKeyboardEvents(); + + /** + * Handle mouse presses on interface buttons + */ + void handleButtons(); + + /** + * Disable the journal controls + */ + void disableControls(); + + /** + * Get in a name to search through the journal for + */ + int getFindName(bool printError); +public: + TattooJournal(SherlockEngine *vm); + virtual ~TattooJournal() {} + + /** + * Show the journal + */ + void show(); +public: + /** + * Draw the journal background, frame, and interface buttons + */ + virtual void drawFrame(); + + /** + * Synchronize the data for a savegame + */ + virtual void synchronize(Serializer &s); +}; + +} // End of namespace Tattoo + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/tattoo/tattoo_map.cpp b/engines/sherlock/tattoo/tattoo_map.cpp new file mode 100644 index 0000000000..365c14a79c --- /dev/null +++ b/engines/sherlock/tattoo/tattoo_map.cpp @@ -0,0 +1,432 @@ +/* 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 "sherlock/tattoo/tattoo_map.h" +#include "sherlock/tattoo/tattoo_scene.h" +#include "sherlock/tattoo/tattoo.h" + +namespace Sherlock { + +namespace Tattoo { + +#define MAP_NAME_COLOR 131 +#define CLOSEUP_STEPS 30 +#define SCROLL_SPEED 16 + +/*-------------------------------------------------------------------------*/ + +void MapEntry::clear() { + _iconNum = -1; + _description = ""; +} + +/*-------------------------------------------------------------------------*/ + +TattooMap::TattooMap(SherlockEngine *vm) : Map(vm), _mapTooltip(vm) { + _iconImages = nullptr; + _bgFound = _oldBgFound = 0; + + loadData(); +} + +int TattooMap::show() { + Events &events = *_vm->_events; + Music &music = *_vm->_music; + Resources &res = *_vm->_res; + TattooScene &scene = *(TattooScene *)_vm->_scene; + Screen &screen = *_vm->_screen; + int result = 0; + + // Check if we need to keep track of how many times player has been to the map + for (uint idx = 0; idx < scene._sceneTripCounters.size(); ++idx) { + SceneTripEntry &entry = scene._sceneTripCounters[idx]; + + if (entry._sceneNumber == OVERHEAD_MAP || entry._sceneNumber == OVERHEAD_MAP2) { + if (--entry._numTimes == 0) { + _vm->setFlagsDirect(entry._flag); + scene._sceneTripCounters.remove_at(idx); + } + } + } + + if (music._midiOption) { + // See if Holmes or Watson is the active character + Common::String song; + if (_vm->readFlags(76)) + // Player is Holmes + song = "Cue9"; + else if (_vm->readFlags(525)) + song = "Cue8"; + else + song = "Cue7"; + + if (music.loadSong(song)) { + music.setMIDIVolume(music._musicVolume); + if (music._musicOn) + music.startSong(); + } + } + + screen.initPaletteFade(1364485); + + // Load the custom mouse cursors for the map + ImageFile cursors("omouse.vgs"); + events.setCursor(cursors[0]._frame); + + // Load the data for the map + _iconImages = new ImageFile("mapicons.vgs"); + loadData(); + + // Load the palette + Common::SeekableReadStream *stream = res.load("map.pal"); + stream->read(screen._cMap, PALETTE_SIZE); + screen.translatePalette(screen._cMap); + delete stream; + + // Load the map image and draw it to the back buffer + ImageFile *map = new ImageFile("map.vgs"); + screen._backBuffer1.create(SHERLOCK_SCREEN_WIDTH * 2, SHERLOCK_SCREEN_HEIGHT * 2); + screen._backBuffer1.blitFrom((*map)[0], Common::Point(0, 0)); + delete map; + + screen.clear(); + screen.setPalette(screen._cMap); + drawMapIcons(); + + // Copy the map drawn in the back buffer to the secondary back buffer + screen._backBuffer2.create(SHERLOCK_SCREEN_WIDTH * 2, SHERLOCK_SCREEN_HEIGHT * 2); + screen._backBuffer2.blitFrom(screen._backBuffer1); + + // Display the built map to the screen + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + + // Set initial scroll position + _targetScroll = _bigPos; + _currentScroll = Common::Point(-1, -1); + + do { + // Allow for event processing and get the current mouse position + events.pollEventsAndWait(); + events.setButtonState(); + Common::Point mousePos = events.mousePos(); + + checkMapNames(true); + + if (mousePos.x < (SHERLOCK_SCREEN_WIDTH / 6)) + _targetScroll.x -= 2 * SCROLL_SPEED * (SHERLOCK_SCREEN_WIDTH / 6 - mousePos.x) / (SHERLOCK_SCREEN_WIDTH / 6); + if (mousePos.x > (SHERLOCK_SCREEN_WIDTH * 5 / 6)) + _targetScroll.x += 2 * SCROLL_SPEED * (mousePos.x - (SHERLOCK_SCREEN_WIDTH * 5 / 6)) / (SHERLOCK_SCREEN_WIDTH / 6); + if (mousePos.y < (SHERLOCK_SCREEN_HEIGHT / 6)) + _targetScroll.y -= 2 * SCROLL_SPEED * (SHERLOCK_SCREEN_HEIGHT / 6 - mousePos.y) / (SHERLOCK_SCREEN_HEIGHT / 6); + if (mousePos.y > (SHERLOCK_SCREEN_HEIGHT * 5 / 6)) + _targetScroll.y += 2 * SCROLL_SPEED * (mousePos.y - SHERLOCK_SCREEN_HEIGHT * 5 / 6) / (SHERLOCK_SCREEN_HEIGHT / 6); + + if (_targetScroll.x < 0) + _targetScroll.x = 0; + if ((_targetScroll.x + SHERLOCK_SCREEN_WIDTH) > screen._backBuffer1.w()) + _targetScroll.x = screen._backBuffer1.w() - SHERLOCK_SCREEN_WIDTH; + if (_targetScroll.y < 0) + _targetScroll.y = 0; + if ((_targetScroll.y + SHERLOCK_SCREEN_HEIGHT) > screen._backBuffer1.h()) + _targetScroll.y = screen._backBuffer1.h() - SHERLOCK_SCREEN_HEIGHT; + + // Check the keyboard + if (events.kbHit()) { + Common::KeyState keyState = events.getKey(); + + switch (keyState.keycode) { + case Common::KEYCODE_HOME: + case Common::KEYCODE_KP7: + _targetScroll.x = 0; + _targetScroll.y = 0; + break; + + case Common::KEYCODE_END: + case Common::KEYCODE_KP1: + _targetScroll.x = screen._backBuffer1.w() - SHERLOCK_SCREEN_WIDTH; + _targetScroll.y = screen._backBuffer1.h() - SHERLOCK_SCREEN_HEIGHT; + break; + + case Common::KEYCODE_PAGEUP: + case Common::KEYCODE_KP9: + _targetScroll.y -= SHERLOCK_SCREEN_HEIGHT; + if (_targetScroll.y < 0) + _targetScroll.y = 0; + break; + + case Common::KEYCODE_PAGEDOWN: + case Common::KEYCODE_KP3: + _targetScroll.y += SHERLOCK_SCREEN_HEIGHT; + if (_targetScroll.y > (screen._backBuffer1.h() - SHERLOCK_SCREEN_HEIGHT)) + _targetScroll.y = screen._backBuffer1.h() - SHERLOCK_SCREEN_HEIGHT; + break; + + case Common::KEYCODE_SPACE: + events._pressed = false; + events._oldButtons = 0; + events._released = true; + break; + + default: + break; + } + } + + // Handle any scrolling of the map + if (_currentScroll != _targetScroll) { + // If there is a Text description being displayed, restore the area under it + _mapTooltip.erase(); + + _currentScroll = _targetScroll; + + checkMapNames(false); + slamRect(Common::Rect(_currentScroll.x, _currentScroll.y, _currentScroll.x + SHERLOCK_SCREEN_WIDTH, + _currentScroll.y + SHERLOCK_SCREEN_HEIGHT)); + } + + // Handling if a location has been clicked on + if (events._released && _bgFound != -1) { + // If there is a Text description being displayed, restore the area under it + _mapTooltip.erase(); + + // Save the current scroll position on the map + _bigPos = _currentScroll; + + showCloseUp(_bgFound); + result = _bgFound + 1; + } + } while (!result && !_vm->shouldQuit()); + + music.stopMusic(); + events.clearEvents(); + _mapTooltip.banishWindow(); + + // Reset the back buffers back to standard size + screen._backBuffer1.create(SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + screen._backBuffer2.create(SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + + return result; +} + +void TattooMap::loadData() { + Resources &res = *_vm->_res; + char c; + + Common::SeekableReadStream *stream = res.load("map.txt"); + + _data.resize(100); + for (uint idx = 0; idx < _data.size(); ++idx) + _data[idx].clear(); + + do + { + // Find the start of the number + do { + c = stream->readByte(); + if (stream->pos() >= stream->size()) + return; + } while (c < '0' || c > '9'); + + // Get the scene number + Common::String locStr; + locStr += c; + while ((c = stream->readByte()) != '.') + locStr += c; + MapEntry &mapEntry = _data[atoi(locStr.c_str()) - 1]; + + // Get the location name + while (stream->readByte() != '"') + ; + + while ((c = stream->readByte()) != '"') + mapEntry._description += c; + + // Find the ( specifying the (X,Y) position of the Icon + while (stream->readByte() != '(') + ; + + // Get the X Position of the icon + Common::String numStr; + while ((c = stream->readByte()) != ',') + numStr += c; + mapEntry.x = atoi(numStr.c_str()); + + // Get the Y position of the icon + numStr = ""; + while ((c = stream->readByte()) != ')') + numStr += c; + mapEntry.y = atoi(numStr.c_str()); + + // Find and get the location's icon number + while (stream->readByte() != '#') + ; + + Common::String iconStr; + while (stream->pos() < stream->size() && (c = stream->readByte()) != '\r') + iconStr += c; + + mapEntry._iconNum = atoi(iconStr.c_str()) - 1; + } while (stream->pos() < stream->size()); + + delete stream; +} + +void TattooMap::drawMapIcons() { + Screen &screen = *_vm->_screen; + + for (uint idx = 0; idx < _data.size(); ++idx) { + _vm->setFlagsDirect(idx + 1); //***DEBUG*** + if (_data[idx]._iconNum != -1 && _vm->readFlags(idx + 1)) { + MapEntry &mapEntry = _data[idx]; + ImageFrame &img = (*_iconImages)[mapEntry._iconNum]; + screen._backBuffer1.transBlitFrom(img._frame, Common::Point(mapEntry.x - img._width / 2, + mapEntry.y - img._height / 2)); + } + } +} + +void TattooMap::checkMapNames(bool slamIt) { + Events &events = *_vm->_events; + Common::Point mousePos = events.mousePos() + _currentScroll; + + // See if the mouse is pointing at any of the map locations + _bgFound = -1; + + for (uint idx = 0; idx < _data.size(); ++idx) { + if (_data[idx]._iconNum != -1 && _vm->readFlags(idx + 1)) { + MapEntry &mapEntry = _data[idx]; + ImageFrame &img = (*_iconImages)[mapEntry._iconNum]; + Common::Rect r(mapEntry.x - img._width / 2, mapEntry.y - img._height / 2, + mapEntry.x + img._width / 2, mapEntry.y + img._height / 2); + + if (r.contains(mousePos)) { + _bgFound = idx; + break; + } + } + } + + // Handle updating the tooltip + if (_bgFound != _oldBgFound) { + if (_bgFound == -1) { + _mapTooltip.setText(""); + } else { + const Common::String &desc = _data[_bgFound]._description; + _mapTooltip.setText(desc); + } + + _oldBgFound = _bgFound; + } + + _mapTooltip.handleEvents(); + if (slamIt) + _mapTooltip.draw(); +} + +void TattooMap::restoreArea(const Common::Rect &bounds) { + Screen &screen = *_vm->_screen; + + Common::Rect r = bounds; + r.clip(Common::Rect(0, 0, screen._backBuffer1.w(), screen._backBuffer1.h())); + + if (!r.isEmpty()) + screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(r.left, r.top), r); +} + +void TattooMap::showCloseUp(int closeUpNum) { + Events &events = *_vm->_events; + Screen &screen = *_vm->_screen; + + // Get the closeup images + Common::String fname = Common::String::format("res%02d.vgs", closeUpNum + 1); + ImageFile pic(fname); + + Point32 closeUp(_data[closeUpNum].x * 100, _data[closeUpNum].y * 100); + Point32 delta((SHERLOCK_SCREEN_WIDTH / 2 - closeUp.x / 100) * 100 / CLOSEUP_STEPS, + (SHERLOCK_SCREEN_HEIGHT / 2 - closeUp.y / 100) * 100 / CLOSEUP_STEPS); + Common::Rect oldBounds(closeUp.x / 100, closeUp.y / 100, closeUp.x / 100 + 1, closeUp.y / 100 + 1); + int size = 64; + int n = 256; + int deltaVal = 512; + bool minimize = false; + int scaleVal, newSize; + + do { + scaleVal = n; + newSize = pic[0].sDrawXSize(n); + + if (newSize > size) { + if (minimize) + deltaVal /= 2; + n += deltaVal; + } else { + minimize = true; + deltaVal /= 2; + n -= deltaVal; + if (n < 1) + n = 1; + } + } while (deltaVal && size != newSize); + + int deltaScale = (SCALE_THRESHOLD - scaleVal) / CLOSEUP_STEPS; + + for (int step = 0; step < CLOSEUP_STEPS; ++step) { + Common::Point picSize(pic[0].sDrawXSize(scaleVal), pic[0].sDrawYSize(scaleVal)); + Common::Point pt(closeUp.x / 100 - picSize.x / 2, closeUp.y / 100 - picSize.y / 2); + + restoreArea(oldBounds); + screen._backBuffer1.transBlitFrom(pic[0], pt, false, 0, scaleVal); + + screen.slamRect(oldBounds); + screen.slamArea(pt.x, pt.y, picSize.x, picSize.y); + + oldBounds = Common::Rect(pt.x, pt.y, pt.x + picSize.x + 1, pt.y + picSize.y + 1); + closeUp += delta; + scaleVal += deltaScale; + + events.wait(1); + } + + // Handle final drawing of closeup + // TODO: Handle scrolling + Common::Rect r(SHERLOCK_SCREEN_WIDTH / 2 - pic[0]._width / 2, SHERLOCK_SCREEN_HEIGHT / 2 - pic[0]._height / 2, + SHERLOCK_SCREEN_WIDTH / 2 - pic[0]._width / 2 + pic[0]._width, + SHERLOCK_SCREEN_HEIGHT / 2 - pic[0]._height / 2 + pic[0]._height); + + restoreArea(oldBounds); + screen._backBuffer1.transBlitFrom(pic[0], Common::Point(r.left, r.top)); + screen.slamRect(oldBounds); + screen.slamRect(r); + events.wait(2); +} + +void TattooMap::slamRect(const Common::Rect &bounds) { + Screen &screen = *_vm->_screen; + Common::Rect r = bounds; + r.translate(-_currentScroll.x, -_currentScroll.y); + + screen.blitFrom(screen._backBuffer1, Common::Point(r.left, r.top), bounds); +} + +} // End of namespace Tattoo + +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/tattoo_map.h b/engines/sherlock/tattoo/tattoo_map.h new file mode 100644 index 0000000000..86db7b04d3 --- /dev/null +++ b/engines/sherlock/tattoo/tattoo_map.h @@ -0,0 +1,100 @@ +/* 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 SHERLOCK_TATTOO_MAP_H +#define SHERLOCK_TATTOO_MAP_H + +#include "common/scummsys.h" +#include "sherlock/map.h" +#include "sherlock/resources.h" +#include "sherlock/surface.h" +#include "sherlock/tattoo/widget_tooltip.h" + +namespace Sherlock { + +class SherlockEngine; + +namespace Tattoo { + +struct MapEntry : Common::Point { + int _iconNum; + Common::String _description; + + MapEntry() : Common::Point(), _iconNum(-1) {} + MapEntry(int posX, int posY, int iconNum) : Common::Point(posX, posY), _iconNum(iconNum) {} + void clear(); +}; + +class TattooMap : public Map { +private: + Common::Array<MapEntry> _data; + ImageFile *_iconImages; + int _bgFound, _oldBgFound; + WidgetMapTooltip _mapTooltip; + Common::Point _targetScroll; + + /** + * Load data needed for the map + */ + void loadData(); + + /** + * Draws all available location icons onto the back buffer + */ + void drawMapIcons(); + + /** + * Draws the location names of whatever the mouse moves over on the map + */ + void checkMapNames(bool slamIt); + + /** + * Restores an area of the map background + */ + void restoreArea(const Common::Rect &bounds); + + /** + * This will load a specified close up and zoom it up to the middle of the screen + */ + void showCloseUp(int closeUpNum); + + /** + * Copies an area of the map to the screen, taking into account scrolling + */ + void slamRect(const Common::Rect &bounds); +public: + Common::Point _currentScroll; +public: + TattooMap(SherlockEngine *vm); + virtual ~TattooMap() {} + + /** + * Show the map + */ + virtual int show(); +}; + +} // End of namespace Tattoo + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/tattoo/tattoo_people.cpp b/engines/sherlock/tattoo/tattoo_people.cpp new file mode 100644 index 0000000000..217064fb4a --- /dev/null +++ b/engines/sherlock/tattoo/tattoo_people.cpp @@ -0,0 +1,1255 @@ +/* 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 "sherlock/tattoo/tattoo_people.h" +#include "sherlock/tattoo/tattoo_scene.h" +#include "sherlock/tattoo/tattoo_talk.h" +#include "sherlock/tattoo/tattoo.h" + +namespace Sherlock { + +namespace Tattoo { + +#define FACING_PLAYER 16 +#define NUM_ADJUSTED_WALKS 21 + +struct AdjustWalk { + char _vgsName[9]; + int _xAdjust; + int _flipXAdjust; + int _yAdjust; +} ; + +static const AdjustWalk ADJUST_WALKS[NUM_ADJUSTED_WALKS] = { + { "TUPRIGHT", -7, -19, 6 }, + { "TRIGHT", 8, -14, 0 }, + { "TDOWNRG", 14, -12, 0 }, + { "TWUPRIGH", 12, 4, 2 }, + { "TWRIGHT", 31, -14, 0 }, + { "TWDOWNRG", 6, -24, 0 }, + { "HTUPRIGH", 2, -20, 0 }, + { "HTRIGHT", 28, -20, 0 }, + { "HTDOWNRG", 8, -2, 0 }, + { "GTUPRIGH", 4, -12, 0 }, + { "GTRIGHT", 12, -16, 0 }, + { "GTDOWNRG", 10, -18, 0 }, + { "JTUPRIGH", 8, -10, 0 }, + { "JTRIGHT", 22, -6, 0 }, + { "JTDOWNRG", 4, -20, 0 }, + { "CTUPRIGH", 10, 0, 0 }, + { "CTRIGHT", 26, -22, 0 }, + { "CTDOWNRI", 16, 4, 0 }, + { "ITUPRIGH", 0, 0, 0 }, + { "ITRIGHT", 20, 0, 0 }, + { "ITDOWNRG", 8, 0, 0 } +}; + +static const int WALK_SPEED_X[99] = { + 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 98, 90, 90, 90, 90, 90, 91, 90, 90, + 90, 90,100, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90,100, 90, + 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90,103, 90, 90, 90, 90, 90, 90, 90, + 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, + 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90 +}; + +static const int WALK_SPEED_Y[99] = { + 28, 28, 28, 28, 28, 28, 28, 28, 28, 32, 32, 32, 28, 28, 28, 28, 28, 26, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 32, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 31, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28 +}; + +static const int WALK_SPEED_DIAG_X[99] = { + 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, + 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, + 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, + 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 90, 50, 50, 50, 50, 50, 50, 50, 50, 50, + 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50 +}; + +/*----------------------------------------------------------------*/ + +SavedNPCPath::SavedNPCPath() { + Common::fill(&_path[0], &_path[MAX_NPC_PATH], 0); + _npcIndex = 0; + _npcPause = 0; + _npcFacing = 0; + _lookHolmes = false; +} + +SavedNPCPath::SavedNPCPath(byte path[MAX_NPC_PATH], int npcIndex, int npcPause, const Common::Point &walkDest, + int npcFacing, bool lookHolmes) : _npcIndex(npcIndex), _npcPause(npcPause), _walkDest(walkDest), + _npcFacing(npcFacing), _lookHolmes(lookHolmes) { + Common::copy(&path[0], &path[MAX_NPC_PATH], &_path[0]); +} + +/*----------------------------------------------------------------*/ + +TattooPerson::TattooPerson() : Person() { + Common::fill(&_npcPath[0], &_npcPath[MAX_NPC_PATH], 0); + _tempX = _tempScaleVal = 0; + _npcIndex = 0; + _npcMoved = false; + _npcFacing = -1; + _resetNPCPath = true; + _savedNpcSequence = 0; + _savedNpcFrame = 0; + _updateNPCPath = true; + _npcPause = 0; + _lookHolmes = false; +} + +void TattooPerson::freeAltGraphics() { + if (_altImages != nullptr) { + delete _altImages; + _altImages = nullptr; + } + + _altSeq = 0; +} + +void TattooPerson::adjustSprite() { + People &people = *_vm->_people; + TattooScene &scene = *(TattooScene *)_vm->_scene; + + if (_type == INVALID) + return; + + if (_type == CHARACTER && _status) { + // Sprite waiting to move, so restart walk + _walkCount = _status; + _status = 0; + + _walkDest = _walkTo.front(); + setWalking(); + } else if (_type == CHARACTER && _walkCount) { + if (_walkCount > 10) { + _walkDest = _nextDest; + setWalking(); + } + + _position += _delta; + if (_walkCount) + --_walkCount; + + if (!_walkCount) { + // If there are remaining points to walk, move to the next one + if (!_walkTo.empty()) { + _walkDest = _walkTo.pop(); + setWalking(); + } else { + gotoStand(); + } + } + } + + if (_type != CHARACTER) { + if (_position.y > SHERLOCK_SCREEN_HEIGHT) + _position.y = SHERLOCK_SCREEN_HEIGHT; + + if (_position.y < UPPER_LIMIT) + _position.y = UPPER_LIMIT; + + if (_position.x < LEFT_LIMIT) + _position.x = LEFT_LIMIT; + + if (_position.x > RIGHT_LIMIT) + _position.x = RIGHT_LIMIT; + } + + int frameNum = _frameNumber; + if (frameNum == -1) + frameNum = 0; + int idx = _walkSequences[_sequenceNumber][frameNum]; + if (idx > _maxFrames) + idx = 1; + + // Set the image frame + if (_altSeq) + _imageFrame = &(*_altImages)[idx - 1]; + else + _imageFrame = &(*_images)[idx - 1]; + + // See if the player has come to a stop after clicking on an Arrow zone to leave the scene. + // If so, this will set up the exit information for the scene transition + if (!_walkCount && scene._exitZone != -1 && scene._walkedInScene && scene._goToScene != -1 && + !_description.compareToIgnoreCase(people[HOLMES]._description)) { + people._hSavedPos = scene._exits[scene._exitZone]._newPosition; + people._hSavedFacing = scene._exits[scene._exitZone]._newFacing; + + if (people._hSavedFacing > 100 && people._hSavedPos.x < 1) + people._hSavedPos.x = 100; + } +} + +void TattooPerson::gotoStand() { + TattooPeople &people = *(TattooPeople *)_vm->_people; + + // If the misc field is set, then we're running a special talk sequence, so don't interrupt it. + if (_misc) + return; + + _walkTo.clear(); + _walkCount = 0; + int oldFacing = _sequenceNumber; + + // If the person was talking or listening, just return it to the standing sequence + // in the direction they were pointing + if (_sequenceNumber >= TALK_UPRIGHT && _sequenceNumber <= LISTEN_UPLEFT) { + switch (_sequenceNumber) { + case TALK_UPRIGHT: + case LISTEN_UPRIGHT: + _sequenceNumber = STOP_UPRIGHT; + break; + case TALK_RIGHT: + case LISTEN_RIGHT: + _sequenceNumber = STOP_RIGHT; + break; + case TALK_DOWNRIGHT: + case LISTEN_DOWNRIGHT: + _sequenceNumber = STOP_DOWNRIGHT; + break; + case TALK_DOWNLEFT: + case LISTEN_DOWNLEFT: + _sequenceNumber = STOP_DOWNLEFT; + break; + case TALK_LEFT: + case LISTEN_LEFT: + _sequenceNumber = STOP_LEFT; + break; + case TALK_UPLEFT: + case LISTEN_UPLEFT: + _sequenceNumber = STOP_UPLEFT; + break; + default: + break; + } + + if (_seqTo) { + // Reset to previous value + _walkSequences[oldFacing]._sequences[_frameNumber] = _seqTo; + _seqTo = 0; + } + + // Set the Frame number to the last frame so we don't move + _frameNumber = 0; + + checkWalkGraphics(); + + _oldWalkSequence = -1; + people._allowWalkAbort = true; + return; + } + + // If the sprite that is stopping is an NPC and he is supposed to face a certain direction + // when he stops, set that direction here + int npc = -1; + for (int idx = 1; idx < MAX_CHARACTERS; ++idx) { + if (_imageFrame == people[idx]._imageFrame) + npc = idx; + } + + if (npc != -1 && people[npc]._npcFacing != -1) { + if (people[npc]._npcFacing == FACING_PLAYER) { + // See where Holmes is with respect to the NPC (x coords) + if (people[HOLMES]._position.x < people[npc]._position.x) + people[npc]._npcFacing = STOP_LEFT; + else + people[npc]._npcFacing = STOP_RIGHT; + + // See where Holmes is with respect to the NPC (y coords) + if (people[HOLMES]._position.y < people[npc]._position.y - (10 * FIXED_INT_MULTIPLIER)) { + // Holmes is above the NPC so reset the facing to the diagonal ups + if (people[npc]._npcFacing == STOP_RIGHT) + people[npc]._npcFacing = STOP_UPRIGHT; + else + people[npc]._npcFacing = STOP_UPLEFT; + } else { + if (people[HOLMES]._position.y > people[npc]._position.y + (10 * FIXED_INT_MULTIPLIER)) { + // Holmes is below the NPC so reset the facing to the diagonal downs + if (people[npc]._npcFacing == STOP_RIGHT) + people[npc]._npcFacing = STOP_DOWNRIGHT; + else + people[npc]._npcFacing = STOP_DOWNLEFT; + } + } + } + + _sequenceNumber = people[npc]._npcFacing; + } else { + switch (_sequenceNumber) { + case WALK_UP: _sequenceNumber = STOP_UP; break; + case WALK_UPRIGHT: _sequenceNumber = STOP_UPRIGHT; break; + case WALK_RIGHT: _sequenceNumber = STOP_RIGHT; break; + case WALK_DOWNRIGHT: _sequenceNumber = STOP_DOWNRIGHT; break; + case WALK_DOWN: _sequenceNumber = STOP_DOWN; break; + case WALK_DOWNLEFT: _sequenceNumber = STOP_DOWNLEFT;break; + case WALK_LEFT: _sequenceNumber = STOP_LEFT; break; + case WALK_UPLEFT: _sequenceNumber = STOP_UPLEFT; break; + } + } + + // Only restart the frame number at 0 if the new sequence is different from the last sequence + // so we don't let Holmes repeat standing. + if (_oldWalkSequence != -1) { + if (_seqTo) { + // Reset to previous value + _walkSequences[oldFacing]._sequences[_frameNumber] = _seqTo; + _seqTo = 0; + } + + _frameNumber = 0; + } + + checkWalkGraphics(); + + _oldWalkSequence = -1; + people._allowWalkAbort = true; +} + +void TattooPerson::setWalking() { + TattooScene &scene = *(TattooScene *)_vm->_scene; + int oldDirection, oldFrame; + Common::Point delta; + _nextDest = _walkDest; + + // Flag that player has now walked in the scene + scene._walkedInScene = true; + + // Stop any previous walking, since a new dest is being set + _walkCount = 0; + oldDirection = _sequenceNumber; + oldFrame = _frameNumber; + + // Set speed to use horizontal and vertical movement + int scaleVal = scene.getScaleVal(_position); + Common::Point speed(MAX(WALK_SPEED_X[scene._currentScene - 1] * SCALE_THRESHOLD / scaleVal, 2), + MAX(WALK_SPEED_Y[scene._currentScene - 1] * SCALE_THRESHOLD / scaleVal, 2)); + Common::Point diagSpeed(MAX(WALK_SPEED_DIAG_X[scene._currentScene - 1] * SCALE_THRESHOLD / scaleVal, 2), + MAX((WALK_SPEED_Y[scene._currentScene - 1] - 2) * SCALE_THRESHOLD / scaleVal, 2)); + + // If the player is already close to the given destination that no walking is needed, + // move to the next straight line segment in the overall walking route, if there is one + for (;;) { + if (_centerWalk || !_walkTo.empty()) { + // Since we want the player to be centered on the ultimate destination, and the player + // is drawn from the left side, move the cursor half the width of the player to center it + delta = Common::Point(_position.x / FIXED_INT_MULTIPLIER - _walkDest.x, + _position.y / FIXED_INT_MULTIPLIER - _walkDest.y); + + int dir; + if (ABS(delta.x) > ABS(delta.y)) + dir = (delta.x < 0) ? WALK_LEFT : WALK_RIGHT; + else + dir = (delta.y < 0) ? WALK_UP : WALK_DOWN; + + scaleVal = scene.getScaleVal(Point32(_walkDest.x * FIXED_INT_MULTIPLIER, + _walkDest.y * FIXED_INT_MULTIPLIER)); + _walkDest.x -= _stopFrames[dir]->sDrawXSize(scaleVal) / 2; + } + + delta = Common::Point( + ABS(_position.x / FIXED_INT_MULTIPLIER - _walkDest.x), + ABS(_position.y / FIXED_INT_MULTIPLIER - _walkDest.y) + ); + + // If we're ready to move a sufficient distance, that's it. Otherwise, + // move onto the next portion of the walk path, if there is one + if ((delta.x > 3 || delta.y > 0) || _walkTo.empty()) + break; + + // Pop next walk segment off the walk route stack + _walkDest = _walkTo.pop(); + } + + // If a sufficient move is being done, then start the move + if (delta.x > 3 || delta.y) { + // See whether the major movement is horizontal or vertical + if (delta.x >= delta.y) { + // Set the initial frame sequence for the left and right, as well + // as setting the delta x depending on direction + if (_walkDest.x < (_position.x / FIXED_INT_MULTIPLIER)) { + _sequenceNumber = WALK_LEFT; + _delta.x = speed.x * -(FIXED_INT_MULTIPLIER / 10); + } else { + _sequenceNumber = WALK_RIGHT; + _delta.x = speed.x * (FIXED_INT_MULTIPLIER / 10); + } + + // See if the x delta is too small to be divided by the speed, since + // this would cause a divide by zero error + if ((delta.x * 10) >= speed.x) { + // Det the delta y + _delta.y = (delta.y * FIXED_INT_MULTIPLIER) / ((delta.x * 10) / speed.x); + if (_walkDest.y < (_position.y / FIXED_INT_MULTIPLIER)) + _delta.y = -_delta.y; + + // Set how many times we should add the delta to the player's position + _walkCount = (delta.x * 10) / speed.x; + } else { + // The delta x was less than the speed (ie. we're really close to + // the destination). So set delta to 0 so the player won't move + _delta = Point32(0, 0); + _position = Point32(_walkDest.x * FIXED_INT_MULTIPLIER, _walkDest.y * FIXED_INT_MULTIPLIER); + + _walkCount = 1; + } + + // See if the sequence needs to be changed for diagonal walking + if (_delta.y > 1500) { + if (_sequenceNumber == WALK_LEFT || _sequenceNumber == WALK_RIGHT) { + _delta.x = _delta.x / speed.x * diagSpeed.x; + _delta.y = (delta.y * FIXED_INT_MULTIPLIER) / (delta.x * 10 / diagSpeed.x); + } + + switch (_sequenceNumber) { + case WALK_LEFT: + _sequenceNumber = WALK_DOWNLEFT; + break; + case WALK_RIGHT: + _sequenceNumber = WALK_DOWNRIGHT; + break; + } + } else if (_delta.y < -1500) { + if (_sequenceNumber == WALK_LEFT || _sequenceNumber == WALK_RIGHT) { + _delta.x = _delta.x / speed.x * diagSpeed.x; + _delta.y = -1 * (delta.y * FIXED_INT_MULTIPLIER) / (delta.x * 10 / diagSpeed.x); + _walkCount = (delta.x * 10) / diagSpeed.x; + } + + switch (_sequenceNumber) { + case WALK_LEFT: + _sequenceNumber = WALK_UPLEFT; + break; + case WALK_RIGHT: + _sequenceNumber = WALK_UPRIGHT; + break; + } + } + } else { + // Major movement is vertical, so set the sequence for up and down, + // and set the delta Y depending on the direction + if (_walkDest.y < (_position.y / FIXED_INT_MULTIPLIER)) { + _sequenceNumber = WALK_UP; + _delta.y = speed.y * -FIXED_INT_MULTIPLIER; + } else { + _sequenceNumber = WALK_DOWN; + _delta.y = speed.y * FIXED_INT_MULTIPLIER; + } + + // Set the delta x + _delta.x = (delta.x * FIXED_INT_MULTIPLIER) / (delta.y / speed.y); + if (_walkDest.x < (_position.x / FIXED_INT_MULTIPLIER)) + _delta.x = -_delta.x; + + _walkCount = delta.y / speed.y; + } + } + + // See if the new walk sequence is the same as the old. If it's a new one, + // we need to reset the frame number to zero so it's animation starts at + // it's beginning. Otherwise, if it's the same sequence, we can leave it + // as is, so it keeps the animation going at wherever it was up to + if (_sequenceNumber != _oldWalkSequence) { + if (_seqTo) { + // Reset to previous value + _walkSequences[oldDirection]._sequences[_frameNumber] = _seqTo; + _seqTo = 0; + } + _frameNumber = 0; + } + + checkWalkGraphics(); + _oldWalkSequence = _sequenceNumber; + + if (!_walkCount && _walkTo.empty()) + gotoStand(); + + // If the sequence is the same as when we started, then Holmes was standing still and we're trying + // to re-stand him, so reset Holmes' rame to the old frame number from before it was reset to 0 + if (_sequenceNumber == oldDirection) + _frameNumber = oldFrame; +} + +void TattooPerson::walkToCoords(const Point32 &destPos, int destDir) { + TattooEngine &vm = *(TattooEngine *)_vm; + Events &events = *_vm->_events; + TattooPeople &people = *(TattooPeople *)_vm->_people; + TattooScene &scene = *(TattooScene *)_vm->_scene; + Talk &talk = *_vm->_talk; + + CursorId oldCursor = events.getCursor(); + events.setCursor(WAIT); + + _walkDest = Common::Point(_position.x / FIXED_INT_MULTIPLIER, _position.y / FIXED_INT_MULTIPLIER); + + bool isHolmes = this == &people[HOLMES]; + if (isHolmes) { + people._allowWalkAbort = true; + } else { + // Clear the path Variables + _npcIndex = _npcPause; + Common::fill(_npcPath, _npcPath + 100, 0); + _npcFacing = destDir; + } + + _centerWalk = false; + + // Only move the person if they're going an appreciable distance + if (ABS(_walkDest.x - (_position.x / FIXED_INT_MULTIPLIER)) > 8 || + ABS(_walkDest.y - (_position.y / FIXED_INT_MULTIPLIER)) > 4) { + goAllTheWay(); + + do { + // Keep doing animations whilst walk is in progress + events.wait(1); + scene.doBgAnim(); + + if (events.kbHit()) { + Common::KeyState keyState = events.getKey(); + + if (keyState.keycode == Common::KEYCODE_ESCAPE && vm._runningProlog) { + vm.setFlags(-76); + vm.setFlags(396); + scene._goToScene = 1; + talk._talkToAbort = true; + } + } + } while (!_vm->shouldQuit() && _walkCount && !talk._talkToAbort); + } + + _centerWalk = true; + if (!isHolmes) + _updateNPCPath = true; + + if (!talk._talkToAbort) { + // put character exactly on right spot + _position = destPos; + + if (_sequenceNumber != destDir) { + // Facing character to correct ending direction + _sequenceNumber = destDir; + gotoStand(); + } + + if (!isHolmes) + _updateNPCPath = false; + + // Secondary walking wait loop + do { + events.wait(1); + scene.doBgAnim(); + + // See if we're past the initial goto stand sequence + for (int idx = 0; idx < _frameNumber; ++idx) { + if (_walkSequences[_sequenceNumber][idx] == 0) + break; + } + + if (events.kbHit()) { + Common::KeyState keyState = events.getKey(); + + if (keyState.keycode == Common::KEYCODE_ESCAPE && vm._runningProlog) { + vm.setFlags(-76); + vm.setFlags(396); + scene._goToScene = 1; + talk._talkToAbort = true; + } + } + } while (!_vm->shouldQuit()); + + if (!isHolmes) + _updateNPCPath = true; + + if (!talk._talkToAbort) + events.setCursor(oldCursor); + } +} + +void TattooPerson::clearNPC() { + Common::fill(&_npcPath[0], &_npcPath[MAX_NPC_PATH], 0); + _npcIndex = 0; + _pathStack.clear(); + _npcName = ""; +} + +void TattooPerson::updateNPC() { + People &people = *_vm->_people; + Talk &talk = *_vm->_talk; + + // If the NPC isn't on, or it's in Talk or Listen Mode, then return without doing anything + if (_type != CHARACTER || _sequenceNumber >= TALK_UPRIGHT) + return; + + // If the NPC is paused, just decrement his pause counter and exit + if (_npcPause) { + // Decrement counter + --_npcPause; + + // Now see if we need to update the NPC's frame sequence so that he faces Holmes + if (_lookHolmes) { + // See where Holmes is with respect to the NPC (x coordinate) + _npcFacing = (people[HOLMES]._position.x < _position.x) ? STOP_LEFT : STOP_RIGHT; + + // See where Holmes is with respect to the NPC (y coordinate) + if (people[HOLMES]._position.y < (_position.y - 10 * FIXED_INT_MULTIPLIER)) { + // Holmes is above the NPC so reset the facing to a diagonal up + _npcFacing = (_npcFacing == STOP_RIGHT) ? STOP_UPRIGHT : STOP_UPLEFT; + } else if (people[HOLMES]._position.y > (_position.y + 10 * FIXED_INT_MULTIPLIER)) { + // Holmes is below the NPC so reset the facing to a diagonal down + _npcFacing = (_npcFacing == STOP_RIGHT) ? STOP_DOWNRIGHT : STOP_DOWNLEFT; + } + + // See if we need to set the old_walk_sequence so the NPC will put his arms + // up if he turns another way + if (_sequenceNumber != _npcFacing) + _oldWalkSequence = _sequenceNumber; + + gotoStand(); + } + } else { + // Reset the look flag so the NPC won't face Holmes anymore + _lookHolmes = false; + + // See if the NPC is stopped or not. Don't do anything if he's moving + if (!_walkCount) { + // If there is no new command, reset the path back to the beginning + if (!_npcPath[_npcIndex]) + _npcIndex = 0; + + // The NPC is stopped and any pause he was doing is done. We can now see what + // the next command in the NPC path is. + + // Scan Past any NPC Path Labels since they do nothing except mark places for If's and Goto's + while (_npcPath[_npcIndex] == NPCPATH_PATH_LABEL) + _npcIndex += 2; + + if (_npcPath[_npcIndex]) { + _npcFacing = -1; + + switch (_npcPath[_npcIndex]) { + case NPCPATH_SET_DEST: { + // Set the NPC's new destination + int xp = (_npcPath[_npcIndex + 1] - 1) * 256 + _npcPath[_npcIndex + 2] - 1; + if (xp > 16384) + xp = -1 * (xp - 16384); + _walkDest.x = xp; + _walkDest.y = (_npcPath[_npcIndex + 3] - 1) * 256 + _npcPath[_npcIndex + 4] - 1; + _npcFacing = _npcPath[_npcIndex + 5] - 1; + + goAllTheWay(); + _npcIndex += 6; + break; + } + + case NPCPATH_PAUSE: + // Set the NPC to pause where he is + _npcPause = (_npcPath[_npcIndex + 1] - 1) * 256 + _npcPath[_npcIndex + 2] - 1; + _npcIndex += 3; + break; + + case NPCPATH_SET_TALK_FILE: { + // Set the NPC's Talk File to use if Holmes talks to them + ++_npcIndex; + + _npcName = ""; + for (int idx = 0; idx < 8; ++idx) { + if (_npcPath[_npcIndex + idx] != '~') + _npcName += _npcPath[_npcIndex + idx]; + else + break; + } + + _npcIndex += 8; + break; + } + + case NPCPATH_CALL_TALK_FILE: { + // Call a Talk File + ++_npcIndex; + + Common::String name; + for (int idx = 0; idx < 8; ++idx) { + if (_npcPath[_npcIndex + idx] != '~') + name += _npcPath[_npcIndex + idx]; + else + break; + } + + _npcIndex += 8; + talk.talkTo(name); + break; + } + + case NPCPATH_TAKE_NOTES: + // Set the NPC to Pause where he is and set his frame sequences + // so he takes notes while he's paused + _npcPause = (_npcPath[_npcIndex + 1] - 1) * 256 + _npcPath[_npcIndex + 2] - 1; + _npcIndex += 3; + break; + + case NPCPATH_FACE_HOLMES: + // Set the NPC to Pause where he is and set his look flag so he will always face Holmes + // while he is paused + _npcPause = (_npcPath[_npcIndex + 1] - 1) * 256 + _npcPath[_npcIndex + 2] - 1; + _lookHolmes = true; + _npcIndex += 3; + break; + + //case NPCPATH_PATH_LABEL: // No implementation needed here + + case NPCPATH_GOTO_LABEL: { + // Goto NPC Path Label + int label = _npcPath[_npcIndex + 1]; + _npcIndex = 0; + + // Scan through NPC path data to find the label + bool found = false; + while (!found) { + switch (_npcPath[_npcIndex]) { + case NPCPATH_SET_DEST: + _npcIndex += 6; + break; + case NPCPATH_PAUSE: + case NPCPATH_TAKE_NOTES: + case NPCPATH_FACE_HOLMES: + _npcIndex += 3; + break; + case NPCPATH_SET_TALK_FILE: + case NPCPATH_CALL_TALK_FILE: + _npcIndex += 8; + break; + case NPCPATH_PATH_LABEL: + if (_npcPath[_npcIndex + 1] == label) + found = true; + _npcIndex += 2; + break; + case NPCPATH_GOTO_LABEL: + _npcIndex += 2; + break; + case NPCPATH_IFFLAG_GOTO_LABEL: + _npcIndex += 4; + break; + } + } + break; + } + + case NPCPATH_IFFLAG_GOTO_LABEL: { + // If FLAG then Goto Label + int flag = (_npcPath[_npcIndex + 1] - 1) * 256 + _npcPath[_npcIndex + 2] - 1 - (_npcPath[_npcIndex + 2] == 1 ? 1 : 0); + + // Set the value the flag should be for the if statement to succeed + bool flagVal = flag < 16384; + + int label = _npcPath[_npcIndex + 3]; + _npcIndex += 4; + + // If the flag is set Correctly, move the NPC Index to the given label + if (_vm->readFlags(flag & 16383) == flagVal) { + _npcIndex = 0; + bool found = false; + while (!found) + { + switch (_npcPath[_npcIndex]) + { + case NPCPATH_SET_DEST: + _npcIndex += 6; + break; + case NPCPATH_PAUSE: + case NPCPATH_TAKE_NOTES: + case NPCPATH_FACE_HOLMES: + _npcIndex += 3; + break; + case NPCPATH_SET_TALK_FILE: + case NPCPATH_CALL_TALK_FILE: + _npcIndex += 8; + break; + case NPCPATH_PATH_LABEL: + if (_npcPath[_npcIndex + 1] == label) + found = true; + _npcIndex += 2; + break; + case NPCPATH_GOTO_LABEL: + _npcIndex += 2; + break; + case NPCPATH_IFFLAG_GOTO_LABEL: + _npcIndex += 4; + break; + } + } + } + + break; + } + + default: + break; + } + } + } + } +} + +void TattooPerson::pushNPCPath() { + assert(_pathStack.size() < 2); + SavedNPCPath savedPath(_npcPath, _npcIndex, _npcPause, _position, _sequenceNumber, _lookHolmes); + _pathStack.push(savedPath); +} + +void TattooPerson::pullNPCPath() { + // Pop the stack entry and restore the fields + SavedNPCPath path = _pathStack.pop(); + Common::copy(&path._path[0], &path._path[MAX_NPC_PATH], &_npcPath[0]); + _npcIndex = path._npcIndex; + _npcPause = path._npcPause; + + // Handle the first case if the NPC was paused + if (_npcPause) { + _walkDest = Common::Point(path._walkDest.x / FIXED_INT_MULTIPLIER, path._walkDest.y / FIXED_INT_MULTIPLIER); + _npcFacing = path._npcFacing; + _lookHolmes = path._lookHolmes; + + // See if the NPC was moved + if (_walkDest.x != (_position.x / FIXED_INT_MULTIPLIER) || + _walkDest.y != (_position.y / FIXED_INT_MULTIPLIER)) { + goAllTheWay(); + _npcPause = 0; + _npcIndex -= 3; + } else { + // See if we need to set the old walk sequence so the NPC will put his arms up if he turns another way + if (_npcFacing != _sequenceNumber) + _oldWalkSequence = _sequenceNumber; + + gotoStand(); + } + } else { + // Handle the second case if the NPC was in motion + _npcIndex -= 6; + } +} + +Common::Point TattooPerson::getSourcePoint() const { + TattooScene &scene = *(TattooScene *)_vm->_scene; + int scaleVal = scene.getScaleVal(_position); + + return Common::Point(_position.x / FIXED_INT_MULTIPLIER + _imageFrame->sDrawXSize(scaleVal) / 2, + _position.y / FIXED_INT_MULTIPLIER); +} + +void TattooPerson::setObjTalkSequence(int seq) { + assert(seq != -1 && _type == CHARACTER); + + if (_seqTo) { + // reset to previous value + _walkSequences[_sequenceNumber]._sequences[_frameNumber] = _seqTo; + _seqTo = 0; + } + + _sequenceNumber = _gotoSeq; + _frameNumber = 0; + checkWalkGraphics(); +} + +void TattooPerson::checkWalkGraphics() { + People &people = *_vm->_people; + + if (_images == nullptr) { + freeAltGraphics(); + return; + } + + Common::String filename = Common::String::format("%s.vgs", _walkSequences[_sequenceNumber]._vgsName.c_str()); + + // Set the adjust depending on if we have to fine tune the x position of this particular graphic + _adjust.x = _adjust.y = 0; + + for (int idx = 0; idx < NUM_ADJUSTED_WALKS; ++idx) { + if (!scumm_strnicmp(_walkSequences[_sequenceNumber]._vgsName.c_str(), ADJUST_WALKS[idx]._vgsName, + strlen(ADJUST_WALKS[idx]._vgsName))) { + if (_walkSequences[_sequenceNumber]._horizFlip) + _adjust.x = ADJUST_WALKS[idx]._flipXAdjust; + else + _adjust.x = ADJUST_WALKS[idx]._xAdjust; + + _adjust.y = ADJUST_WALKS[idx]._yAdjust; + break; + } + } + + // See if we're already using Alternate Graphics + if (_altSeq) { + // See if the VGS file called for is different than the alternate graphics already loaded + if (!_walkSequences[_sequenceNumber]._vgsName.compareToIgnoreCase(_walkSequences[_altSeq - 1]._vgsName)) { + // Different AltGraphics, Free the old ones + freeAltGraphics(); + } + } + + // If there is no Alternate Sequence set, see if we need to load a new one + if (!_altSeq) { + int npcNum = -1; + // Find which NPC this is so we can check the name of the graphics loaded + for (int idx = 0; idx < MAX_CHARACTERS; ++idx) { + if (this == &people[idx]) { + npcNum = idx; + break; + } + } + + if (npcNum != -1) { + // See if the VGS file called for is different than the main graphics which are already loaded + if (filename.compareToIgnoreCase(people[npcNum]._walkVGSName) != 0) { + // See if this is one of the more used Walk Graphics stored in WALK.LIB + for (int idx = 0; idx < NUM_IN_WALK_LIB; ++idx) { + if (!scumm_stricmp(filename.c_str(), WALK_LIB_NAMES[idx])) { + people._useWalkLib = true; + break; + } + } + + _altImages = new ImageFile(filename); + people._useWalkLib = false; + + _altSeq = _sequenceNumber + 1; + } + } + } + + // If this is a different seqeunce from the current sequence, reset the appropriate variables + if (_sequences != &_walkSequences[_sequenceNumber]._sequences[0]) { + _seqTo = _seqCounter = _seqCounter2 = _seqStack = _startSeq = 0; + _sequences = &_walkSequences[_sequenceNumber]._sequences[0]; + _seqSize = _walkSequences[_sequenceNumber]._sequences.size(); + } + + setImageFrame(); +} + +/*----------------------------------------------------------------*/ + +TattooPeople::TattooPeople(SherlockEngine *vm) : People(vm) { + for (int idx = 0; idx < 6; ++idx) + _data.push_back(new TattooPerson()); +} + +void TattooPeople::setListenSequence(int speaker, int sequenceNum) { + Scene &scene = *_vm->_scene; + + // If no speaker is specified, then nothing needs to be done + if (speaker == -1) + return; + + int objNum = findSpeaker(speaker); + if (objNum < 256 && objNum != -1) { + // See if the Object has to wait for an Abort Talk Code + Object &obj = scene._bgShapes[objNum]; + if (obj.hasAborts()) + obj._gotoSeq = sequenceNum; + else + obj.setObjTalkSequence(sequenceNum); + } else if (objNum != -1) { + objNum -= 256; + TattooPerson &person = (*this)[objNum]; + + int newDir = person._sequenceNumber; + switch (person._sequenceNumber) { + case WALK_UP: + case STOP_UP: + case WALK_UPRIGHT: + case STOP_UPRIGHT: + case TALK_UPRIGHT: + case LISTEN_UPRIGHT: + newDir = LISTEN_UPRIGHT; + break; + case WALK_RIGHT: + case STOP_RIGHT: + case TALK_RIGHT: + case LISTEN_RIGHT: + newDir = LISTEN_RIGHT; + break; + case WALK_DOWNRIGHT: + case STOP_DOWNRIGHT: + case TALK_DOWNRIGHT: + case LISTEN_DOWNRIGHT: + newDir = LISTEN_DOWNRIGHT; + break; + case WALK_DOWN: + case STOP_DOWN: + case WALK_DOWNLEFT: + case STOP_DOWNLEFT: + case TALK_DOWNLEFT: + case LISTEN_DOWNLEFT: + newDir = LISTEN_DOWNLEFT; + break; + case WALK_LEFT: + case STOP_LEFT: + case TALK_LEFT: + case LISTEN_LEFT: + newDir = LISTEN_LEFT; + break; + case WALK_UPLEFT: + case STOP_UPLEFT: + case TALK_UPLEFT: + case LISTEN_UPLEFT: + newDir = LISTEN_UPLEFT; + break; + + default: + break; + } + + // See if the NPC's Seq has to wait for an Abort Talk Code + if (person.hasAborts()) { + person._gotoSeq = newDir; + } else { + if (person._seqTo) { + // Reset to previous value + person._walkSequences[person._sequenceNumber]._sequences[person._frameNumber] = person._seqTo; + person._seqTo = 0; + } + + person._sequenceNumber = newDir; + person._frameNumber = 0; + person.checkWalkGraphics(); + } + } +} + +void TattooPeople::setTalkSequence(int speaker, int sequenceNum) { + TattooPeople &people = *(TattooPeople *)_vm->_people; + Scene &scene = *_vm->_scene; + TattooTalk &talk = *(TattooTalk *)_vm->_talk; + + // If no speaker is specified, then nothing needs to be done + if (speaker == -1) + return; + + int objNum = people.findSpeaker(speaker); + if (objNum != -1 && objNum < 256) { + Object &obj = scene._bgShapes[objNum]; + + // See if the Object has to wait for an Abort Talk Code + if (obj.hasAborts()) { + talk.pushTalkSequence(&obj); + obj._gotoSeq = sequenceNum; + } + else { + obj.setObjTalkSequence(sequenceNum); + } + } + else if (objNum != -1) { + objNum -= 256; + TattooPerson &person = people[objNum]; + int newDir = person._sequenceNumber; + + switch (newDir) { + case WALK_UP: + case STOP_UP: + case WALK_UPRIGHT: + case STOP_UPRIGHT: + case TALK_UPRIGHT: + case LISTEN_UPRIGHT: + newDir = TALK_UPRIGHT; + break; + case WALK_RIGHT: + case STOP_RIGHT: + case TALK_RIGHT: + case LISTEN_RIGHT: + newDir = TALK_RIGHT; + break; + case WALK_DOWNRIGHT: + case STOP_DOWNRIGHT: + case TALK_DOWNRIGHT: + case LISTEN_DOWNRIGHT: + newDir = TALK_DOWNRIGHT; + break; + case WALK_DOWN: + case STOP_DOWN: + case WALK_DOWNLEFT: + case STOP_DOWNLEFT: + case TALK_DOWNLEFT: + case LISTEN_DOWNLEFT: + newDir = TALK_DOWNLEFT; + break; + case WALK_LEFT: + case STOP_LEFT: + case TALK_LEFT: + case LISTEN_LEFT: + newDir = TALK_LEFT; + break; + case WALK_UPLEFT: + case STOP_UPLEFT: + case TALK_UPLEFT: + case LISTEN_UPLEFT: + newDir = TALK_UPLEFT; + break; + default: + break; + } + + // See if the NPC's sequence has to wait for an Abort Talk Code + if (person.hasAborts()) { + person._gotoSeq = newDir; + } else { + if (person._seqTo) { + // Reset to previous value + person._walkSequences[person._sequenceNumber]._sequences[person._frameNumber] = person._seqTo; + person._seqTo = 0; + } + + person._sequenceNumber = newDir; + person._frameNumber = 0; + person.checkWalkGraphics(); + } + } +} + + +int TattooPeople::findSpeaker(int speaker) { + int result = People::findSpeaker(speaker); + const char *portrait = _characters[speaker]._portrait; + + // Fallback that Rose Tattoo uses if no speaker was found + if (result == -1) { + bool flag = _vm->readFlags(76); + + if (_data[HOLMES]->_type == CHARACTER && ((speaker == 0 && flag) || (speaker == 1 && !flag))) + return -1; + + for (uint idx = 1; idx < _data.size(); ++idx) { + TattooPerson &p = (*this)[idx]; + + if (p._type == CHARACTER) { + Common::String name(p._name.c_str(), p._name.c_str() + 4); + + if (name.equalsIgnoreCase(portrait) && p._npcName[4] >= '0' && p._npcName[4] <= '9') + return idx + 256; + } + } + } + + return -1; +} + +void TattooPeople::synchronize(Serializer &s) { + s.syncAsByte(_holmesOn); + + for (uint idx = 0; idx < _data.size(); ++idx) { + Person &p = *_data[idx]; + s.syncAsSint32LE(p._position.x); + s.syncAsSint32LE(p._position.y); + s.syncAsSint16LE(p._sequenceNumber); + s.syncAsSint16LE(p._type); + s.syncString(p._walkVGSName); + s.syncString(p._description); + s.syncString(p._examine); + } + + s.syncAsSint16LE(_holmesQuotient); + + if (s.isLoading()) { + _hSavedPos = _data[HOLMES]->_position; + _hSavedFacing = _data[HOLMES]->_sequenceNumber; + } +} + +bool TattooPeople::loadWalk() { + Resources &res = *_vm->_res; + bool result = false; + + for (int idx = 0; idx < MAX_CHARACTERS; ++idx) { + Person &person = *_data[idx]; + + if (!person._walkLoaded && (person._type == CHARACTER || person._type == HIDDEN_CHARACTER)) { + if (person._type == HIDDEN_CHARACTER) + person._type = INVALID; + + // See if this is one of the more used Walk Graphics stored in WALK.LIB + for (int libNum = 0; libNum < NUM_IN_WALK_LIB; ++libNum) { + if (!person._walkVGSName.compareToIgnoreCase(WALK_LIB_NAMES[libNum])) { + _useWalkLib = true; + break; + } + } + + // Load the images for the character + person._images = new ImageFile(person._walkVGSName, false); + person._maxFrames = person._images->size(); + + // Load walk sequence data + Common::String fname = Common::String(person._walkVGSName.c_str(), strchr(person._walkVGSName.c_str(), '.')); + fname += ".SEQ"; + + // Load the walk sequence data + Common::SeekableReadStream *stream = res.load(fname, _useWalkLib ? "walk.lib" : "vgs.lib"); + + person._walkSequences.resize(stream->readByte()); + + for (uint seqNum = 0; seqNum < person._walkSequences.size(); ++seqNum) + person._walkSequences[seqNum].load(*stream); + + // Close the sequences resource + delete stream; + _useWalkLib = false; + + person._sequences = &person._walkSequences[person._sequenceNumber]._sequences[0]; + person._seqSize = person._walkSequences[person._sequenceNumber]._sequences.size(); + person._frameNumber = 0; + person.setImageFrame(); + + // Set the stop Frames pointers + for (int dirNum = 0; dirNum < 8; ++dirNum) { + int count = 0; + while (person._walkSequences[dirNum + 8][count] != 0) + ++count; + count += 2; + count = person._walkSequences[dirNum + 8][count] - 1; + person._stopFrames[dirNum] = &(*person._images)[count]; + } + + result = true; + person._walkLoaded = true; + } else if (person._type != CHARACTER) { + person._walkLoaded = false; + } + } + + _forceWalkReload = false; + return result; +} + + +void TattooPeople::pullNPCPaths() { + for (int idx = 1; idx < MAX_CHARACTERS; ++idx) { + TattooPerson &p = (*this)[idx]; + if (p._npcMoved) { + while (!p._pathStack.empty()) + p.pullNPCPath(); + } + } +} + +} // End of namespace Tattoo + +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/tattoo_people.h b/engines/sherlock/tattoo/tattoo_people.h new file mode 100644 index 0000000000..6883b9d0e6 --- /dev/null +++ b/engines/sherlock/tattoo/tattoo_people.h @@ -0,0 +1,256 @@ +/* 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 SHERLOCK_TATTOO_PEOPLE_H +#define SHERLOCK_TATTOO_PEOPLE_H + +#include "common/scummsys.h" +#include "common/stack.h" +#include "sherlock/people.h" + +namespace Sherlock { + +class SherlockEngine; + +namespace Tattoo { + +// Animation sequence identifiers for characters +enum TattooSequences { + // Walk Sequences Numbers for NPCs + WALK_UP = 0, + WALK_UPRIGHT = 1, + WALK_RIGHT = 2, + WALK_DOWNRIGHT = 3, + WALK_DOWN = 4, + WALK_DOWNLEFT = 5, + WALK_LEFT = 6, + WALK_UPLEFT = 7, + + // Stop Sequences Numbers for NPCs + STOP_UP = 8, + STOP_UPRIGHT = 9, + STOP_RIGHT = 10, + STOP_DOWNRIGHT = 11, + STOP_DOWN = 12, + STOP_DOWNLEFT = 13, + STOP_LEFT = 14, + STOP_UPLEFT = 15, + + // NPC Talk Sequence Numbers + TALK_UPRIGHT = 16, + TALK_RIGHT = 17, + TALK_DOWNRIGHT = 18, + TALK_DOWNLEFT = 19, + TALK_LEFT = 20, + TALK_UPLEFT = 21, + + // NPC Listen Sequence Numbers + LISTEN_UPRIGHT = 22, + LISTEN_RIGHT = 23, + LISTEN_DOWNRIGHT = 24, + LISTEN_DOWNLEFT = 25, + LISTEN_LEFT = 26, + LISTEN_UPLEFT = 27 +}; + +enum NpcPath { + NPCPATH_SET_DEST = 1, + NPCPATH_PAUSE = 2, + NPCPATH_SET_TALK_FILE = 3, + NPCPATH_CALL_TALK_FILE = 4, + NPCPATH_TAKE_NOTES = 5, + NPCPATH_FACE_HOLMES = 6, + NPCPATH_PATH_LABEL = 7, + NPCPATH_GOTO_LABEL = 8, + NPCPATH_IFFLAG_GOTO_LABEL = 9 +}; + +struct SavedNPCPath { + byte _path[MAX_NPC_PATH]; + int _npcIndex; + int _npcPause; + Common::Point _walkDest; + int _npcFacing; + bool _lookHolmes; + + SavedNPCPath(); + SavedNPCPath(byte path[MAX_NPC_PATH], int npcIndex, int npcPause, const Common::Point &walkDest, + int npcFacing, bool lookHolmes); +}; + +class TattooPerson: public Person { +private: + Point32 _nextDest; +private: + bool checkCollision() const; + + /** + * Free the alternate graphics used by NPCs + */ + void freeAltGraphics(); +protected: + /** + * Get the source position for a character potentially affected by scaling + */ + virtual Common::Point getSourcePoint() const; +public: + Common::Stack<SavedNPCPath> _pathStack; + int _npcIndex; + int _npcPause; + byte _npcPath[MAX_NPC_PATH]; + Common::String _npcName; + bool _npcMoved; + int _npcFacing; + bool _resetNPCPath; + int _savedNpcSequence; + int _savedNpcFrame; + int _tempX; + int _tempScaleVal; + bool _updateNPCPath; + bool _lookHolmes; +public: + TattooPerson(); + virtual ~TattooPerson() {} + + /** + * Clear the NPC related data + */ + void clearNPC(); + + /** + * Called from doBgAnim to move NPCs along any set paths. If an NPC is paused in his path, + * he will remain paused until his pause timer runs out. If he is walking somewhere, + * he will continue walking there until he reaches the dest position. When an NPC stops moving, + * the next element of his path is processed. + * + * The path is an array of bytes with control codes followed by their parameters as needed. + */ + void updateNPC(); + + /** + * Push the NPC's path data onto the path stack for when a talk file moves the NPC that + * has some control codes. + */ + void pushNPCPath(); + + /** + * Pull an NPC's path data that has been previously saved on the path stack for that character. + * There are two possibilities for when the NPC was interrupted, and both are handled differently: + * 1) The NPC was paused at a position + * If the NPC didn't move, we can just restore his pause counter and exit. But if he did move, + * he must return to that position, and the path index must be reset to the pause he was executing. + * This means that the index must be decremented by 3 + * 2) The NPC was in route to a position + * He must be set to walk to that position again. This is done by moving the path index + * so that it points to the code that set the NPC walking there in the first place. + * The regular calls to updateNPC will handle the rest + */ + void pullNPCPath(); + + /** + * Checks a sprite associated with an NPC to see if the frame sequence specified + * in the sequence number uses alternate graphics, and if so if they need to be loaded + */ + void checkWalkGraphics(); + + /** + * This adjusts the sprites position, as well as it's animation sequence: + */ + virtual void adjustSprite(); + + /** + * Bring a moving character to a standing position + */ + virtual void gotoStand(); + + /** + * Set the variables for moving a character from one poisition to another + * in a straight line + */ + virtual void setWalking(); + + /** + * Walk to the co-ordinates passed, and then face the given direction + */ + virtual void walkToCoords(const Point32 &destPos, int destDir); + + /** + * Adjusts the frame and sequence variables of a sprite that corresponds to the current speaker + * so that it points to the beginning of the sequence number's talk sequence in the object's + * sequence buffer + * @param seq Which sequence to use (if there's more than 1) + * @remarks 1: First talk seq, 2: second talk seq, etc. + */ + virtual void setObjTalkSequence(int seq); +}; + +class TattooPeople : public People { +public: + TattooPeople(SherlockEngine *vm); + virtual ~TattooPeople() {} + + TattooPerson &operator[](PeopleId id) { return *(TattooPerson *)_data[id]; } + TattooPerson &operator[](int idx) { return *(TattooPerson *)_data[idx]; } + + /** + * If the specified speaker is a background object, it will set it so that it uses + * the Listen Sequence (specified by the sequence number). If the current sequence + * has an Allow Talk Code in it, the _gotoSeq field will be set so that the object + * begins listening as soon as it hits the Allow Talk Code. If there is no Abort Code, + * the Listen Sequence will begin immediately. + * @param speaker Who is speaking + * @param sequenceNum Which listen sequence to use + */ + void setListenSequence(int speaker, int sequenceNum); + + /** + * Restore any saved NPC walk path data from any of the NPCs + */ + void pullNPCPaths(); + + /** + * Finds the scene background object corresponding to a specified speaker + */ + virtual int findSpeaker(int speaker); + + /** + * Synchronize the data for a savegame + */ + virtual void synchronize(Serializer &s); + + /** + * Change the sequence of the scene background object associated with the specified speaker. + */ + virtual void setTalkSequence(int speaker, int sequenceNum = 1); + + /** + * Load the walking images for Sherlock + */ + virtual bool loadWalk(); +}; + +} // End of namespace Scalpel + +} // End of namespace Sherlock + + +#endif diff --git a/engines/sherlock/tattoo/tattoo_resources.cpp b/engines/sherlock/tattoo/tattoo_resources.cpp new file mode 100644 index 0000000000..3be41e2650 --- /dev/null +++ b/engines/sherlock/tattoo/tattoo_resources.cpp @@ -0,0 +1,329 @@ +/* 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 "sherlock/tattoo/tattoo_resources.h" + +namespace Sherlock { + +namespace Tattoo { + +const char PORTRAITS[TATTOO_MAX_PEOPLE][5] = { + { "HOLM" }, // Sherlock Holmes + { "WATS" }, // Dr. Watson + { "HUDS" }, // Mrs. Hudson + { "FORB" }, // Stanley Forbes + { "MYCR" }, // Mycroft Holmes + { "WIGG" }, // Wiggins + { "BURN" }, // Police Constable Burns + { "TRIM" }, // Augustus Trimble + { "DALE" }, // Police Constable Daley + { "MATR" }, // Matron + { "GRAC" }, // Sister Grace + { "MCCA" }, // Preston McCabe + { "COLL" }, // Bob Colleran + { "JONA" }, // Jonas Rigby + { "ROAC" }, // Police Constable Roach + { "DEWA" }, // James Dewar + { "JERE" }, // Sergeant Jeremy Duncan + { "GREG" }, // Inspector Gregson + { "LEST" }, // Inspector Lestrade + { "NEED" }, // Jesse Needhem + { "FLEM" }, // Arthur Fleming + { "PRAT" }, // Mr. Thomas Pratt + { "TILL" }, // Mathilda (Tillie) Mason + { "RUSS" }, // Adrian Russell + { "WHIT" }, // Eldridge Whitney + { "HEPP" }, // Hepplethwaite + { "HORA" }, // Horace Silverbridge + { "SHER" }, // Old Sherman + { "VERN" }, // Maxwell Verner + { "REDD" }, // Millicent Redding + { "VIRG" }, // Virgil Silverbridge + { "GEOR" }, // George O'Keeffe + { "LAWT" }, // Lord Denys Lawton + { "JENK" }, // Jenkins + { "JOCK" }, // Jock Mahoney + { "BART" }, // Bartender + { "LADY" }, // Lady Cordelia Lockridge + { "PETT" }, // Pettigrew + { "FANS" }, // Sir Avery Fanshawe + { "HODG" }, // Hodgkins + { "WILB" }, // Wilbur "Birdy" Heywood + { "JACO" }, // Jacob Farthington + { "BLED" }, // Philip Bledsoe + { "FOWL" }, // Sidney Fowler + { "PROF" }, // Professor Theodore Totman + { "ROSE" }, // Rose Hinchem + { "TALL" }, // Tallboy + { "STIT" }, // Ethlebert "Stitch" Rumsey + { "FREE" }, // Charles Freedman + { "HEMM" }, // Nigel Hemmings + { "CART" }, // Fairfax Carter + { "WILH" }, // Wilhelm II + { "WACH" }, // Wachthund + { "WILS" }, // Jonathan Wilson + { "DAVE" }, // David Lloyd-Jones + { "HARG" }, // Edward Hargrove + { "MORI" }, // Professor James Moriarty + { "LASC" }, // The Lascar + { "PARR" }, // Parrot + { "SCAR" }, // Vincent Scarrett + { "ALEX" }, // Alexandra + { "QUEE" }, // Queen Victoria + { "JOHN" }, // John Brown + { "PAT1" }, // Patient #1 + { "PAT2" }, // Patient #2 + { "PATR" }, // Patron + { "QUEN" }, // Queen Victoria + { "WITE" }, // Patient in White + { "LUSH" }, // Lush + { "DRNK" }, // Drunk + { "PROS" }, // Prostitute + { "MUDL" }, // Mudlark + { "GRIN" }, // Grinder + { "BOUN" }, // Bouncer + { "RATC" }, // Agnes Ratchet + { "ALOY" }, // Aloysius Ratchet + { "REAL" }, // Real Estate Agent + { "CAND" }, // Candy Clerk + { "BEAD" }, // Beadle + { "PRUS" }, // Prussian + { "ROWB" }, // Mrs. Rowbottom + { "MSLJ" }, // Miss Lloyd-Jones + { "TPAT" }, // Tavern patron + { "USER" }, // User + { "TOBY" }, // Toby + { "STAT" }, // Stationer + { "CLRK" }, // Law Clerk + { "CLER" }, // Ministry Clerk + { "BATH" }, // Bather + { "MAID" }, // Maid + { "LADF" }, // Lady Fanshawe + { "SIDN" }, // Sidney Ratchet + { "BOYO" }, // Boy + { "PTR2" }, // Second Patron + { "BRIT" }, // Constable Brit + { "DROV" } // Wagon Driver +}; + +const char *const FRENCH_NAMES[TATTOO_MAX_PEOPLE] = { + "Sherlock Holmes", + "Dr. Watson", + "Mme. Hudson", + "Stanley Forbes", + "Mycroft Holmes", + "Wiggins", + "Sergent Burns", + "Augustus Trimble", + "Sergent Daley", + "Infirmi?re chef", + "Mme. Grace", + "Preston McCabe", + "Bob Colleran", + "Jonas Rigby", + "Sergent Roach", + "James Dewar", + "Sergent Jeremy Duncan", + "Inspecteur Gregson", + "Inspecteur Lestrade", + "Jesse Needhem", + "Arthur Fleming", + "M. Thomas Pratt", + "Mathilda (Tillie) Mason", + "Adrian Russell", + "Eldridge Whitney", + "Hepplethwaite", + "Horace Silverbridge", + "Sherman", + "Maxwell Verner", + "Millicent Redding", + "Virgil Silverbridge", + "George O'Keeffe", + "Lord Denys Lawton", + "Jenkins", + "Jock Mahoney", + "Serveur", + "Lady Cordelia Lockridge", + "Pettigrew", + "Sir Avery Fanshawe", + "Hodgkins", + "Wilbur \"Birdy\" Heywood", + "Jacob Farthington", + "Philip Bledsoe", + "Sidney Fowler", + "Professeur Theodore Totman", + "Rose Hinchem", + "Tallboy", + "Ethlebert \"Stitch\" Rumsey", + "Charles Freedman", + "Nigel Hemmings", + "Fairfax Carter", + "Wilhelm II", + "Wachthund", + "Jonathan Wilson", + "David Lloyd-Jones", + "Edward Hargrove", + "Misteray", + "Le Lascar", + "Oiseau", + "Vincent Scarrett", + "Alexandra", + "Queen Victoria", + "John Brown", + "Patient", + "Patient", + "Client", + "Queen Victoria", + "Patient en blanc", + "Ivrogne", + "Ivrogne", + "Belle femme", + "Mudlark", + "Broyeur", + "Videur", + "Agnes Ratchet", + "Aloysius Ratchet", + "Immobilier", + "Gar?on", + "Beadle", + "Prussian", + "Mme. Rowbottom", + "Mme Lloyd-Jones", + "Tavern Client", + "User", + "Toby", + "Papeterie", + "Law Clerc", + "Ministry Employ?", + "Clint du thermes", + "Bonne", + "Lady Fanshawe", + "Sidney Ratchet", + "Gar?on", + "Client", + "Sergent Brit", + "Wagon Driver" +}; + +const char *const ENGLISH_NAMES[TATTOO_MAX_PEOPLE] = { + "Sherlock Holmes", + "Dr. Watson", + "Mrs. Hudson", + "Stanley Forbes", + "Mycroft Holmes", + "Wiggins", + "Police Constable Burns", + "Augustus Trimble", + "Police Constable Daley", + "Matron", + "Sister Grace", + "Preston McCabe", + "Bob Colleran", + "Jonas Rigby", + "Police Constable Roach", + "James Dewar", + "Sergeant Jeremy Duncan", + "Inspector Gregson", + "Inspector Lestrade", + "Jesse Needhem", + "Arthur Fleming", + "Mr. Thomas Pratt", + "Mathilda (Tillie) Mason", + "Adrian Russell", + "Eldridge Whitney", + "Hepplethwaite", + "Horace Silverbridge", + "Old Sherman", + "Maxwell Verner", + "Millicent Redding", + "Virgil Silverbridge", + "George O'Keeffe", + "Lord Denys Lawton", + "Jenkins", + "Jock Mahoney", + "Bartender", + "Lady Cordelia Lockridge", + "Pettigrew", + "Sir Avery Fanshawe", + "Hodgkins", + "Wilbur \"Birdy\" Heywood", + "Jacob Farthington", + "Philip Bledsoe", + "Sidney Fowler", + "Professor Theodore Totman", + "Rose Hinchem", + "Tallboy", + "Ethlebert \"Stitch\" Rumsey", + "Charles Freedman", + "Nigel Hemmings", + "Fairfax Carter", + "Wilhelm II", + "Wachthund", + "Jonathan Wilson", + "David Lloyd-Jones", + "Edward Hargrove", + "Misteray", + "The Lascar", + "Parrot", + "Vincent Scarrett", + "Alexandra", + "Queen Victoria", + "John Brown", + "A Patient", + "A Patient", + "Patron", + "Queen Victoria", + "Patient in white", + "Lush", + "Drunk", + "Prostitute", + "Mudlark", + "Grinder", + "Bouncer", + "Agnes Ratchet", + "Aloysius Ratchet", + "Real Estate Agent", + "Candy Clerk", + "Beadle", + "Prussian", + "Mrs. Rowbottom", + "Miss Lloyd-Jones", + "Tavern patron", + "User", + "Toby", + "Stationer", + "Law Clerk", + "Ministry Clerk", + "Bather", + "Maid", + "Lady Fanshawe", + "Sidney Ratchet", + "Boy", + "Patron", + "Constable Brit", + "Wagon Driver" +}; + + +} // End of namespace Tattoo + +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/tattoo_resources.h b/engines/sherlock/tattoo/tattoo_resources.h new file mode 100644 index 0000000000..b706d90f2d --- /dev/null +++ b/engines/sherlock/tattoo/tattoo_resources.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. + * + */ + +#ifndef SHERLOCK_TATTOO_RESOURCES_H +#define SHERLOCK_TATTOO_RESOURCES_H + +#include "common/scummsys.h" + +namespace Sherlock { + +namespace Tattoo { + +#define TATTOO_MAX_PEOPLE 96 + +extern const char PORTRAITS[TATTOO_MAX_PEOPLE][5]; +extern const char *const FRENCH_NAMES[TATTOO_MAX_PEOPLE]; +extern const char *const ENGLISH_NAMES[TATTOO_MAX_PEOPLE]; + +} // End of namespace Tattoo + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/tattoo/tattoo_scene.cpp b/engines/sherlock/tattoo/tattoo_scene.cpp new file mode 100644 index 0000000000..1c6f9263e9 --- /dev/null +++ b/engines/sherlock/tattoo/tattoo_scene.cpp @@ -0,0 +1,735 @@ +/* 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 "sherlock/tattoo/tattoo_scene.h" +#include "sherlock/tattoo/tattoo_people.h" +#include "sherlock/tattoo/tattoo_user_interface.h" +#include "sherlock/tattoo/tattoo.h" +#include "sherlock/events.h" +#include "sherlock/people.h" + +namespace Sherlock { + +namespace Tattoo { + +struct ShapeEntry { + Object *_shape; + TattooPerson *_person; + bool _isAnimation; + int _yp; + + ShapeEntry(TattooPerson *person, int yp) : _shape(nullptr), _person(person), _yp(yp), _isAnimation(false) {} + ShapeEntry(Object *shape, int yp) : _shape(shape), _person(nullptr), _yp(yp), _isAnimation(false) {} + ShapeEntry(int yp) : _shape(nullptr), _person(nullptr), _yp(yp), _isAnimation(true) {} +}; +typedef Common::List<ShapeEntry> ShapeList; + +static bool sortImagesY(const ShapeEntry &s1, const ShapeEntry &s2) { + return s1._yp <= s2._yp; +} + +/*----------------------------------------------------------------*/ + +TattooScene::TattooScene(SherlockEngine *vm) : Scene(vm) { + _arrowZone = -1; + _labTableScene = false; +} + +bool TattooScene::loadScene(const Common::String &filename) { + TattooEngine &vm = *(TattooEngine *)_vm; + Events &events = *_vm->_events; + Music &music = *_vm->_music; + Sound &sound = *_vm->_sound; + Talk &talk = *_vm->_talk; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + + // If we're going to the first game scene after the intro sequence, flag it as finished + if (vm._runningProlog && _currentScene == STARTING_GAME_SCENE) { + vm._runningProlog = false; + events.showCursor(); + talk._talkToAbort = false; + } + + // Check if it's a scene we need to keep trakc track of how many times we've visited + for (int idx = (int)_sceneTripCounters.size() - 1; idx >= 0; --idx) { + if (_sceneTripCounters[idx]._sceneNumber == _currentScene) { + if (--_sceneTripCounters[idx]._numTimes == 0) { + _vm->setFlags(_sceneTripCounters[idx]._flag); + _sceneTripCounters.remove_at(idx); + } + } + } + + // Set the NPC paths for the scene + setNPCPath(0); + + // Handle loading music for the scene + if (music._musicOn) { + if (talk._scriptMoreFlag != 1 && talk._scriptMoreFlag != 3) + sound._nextSongName = Common::String::format("res%02d", _currentScene); + + // If it's a new song, then start it up + if (sound._currentSongName.compareToIgnoreCase(sound._nextSongName)) { + if (music.loadSong(sound._nextSongName)) { + music.setMIDIVolume(music._musicVolume); + if (music._musicOn) + music.startSong(); + } + } + } + + bool result = Scene::loadScene(filename); + + if (_currentScene != STARTING_INTRO_SCENE) { + // Set the menu/ui mode and whether we're in a lab table close-up scene + _labTableScene = _currentScene > 91 && _currentScene < 100; + ui._menuMode = _labTableScene ? LAB_MODE : STD_MODE; + } + + return result; +} + +void TattooScene::drawAllShapes() { + TattooPeople &people = *(TattooPeople *)_vm->_people; + Screen &screen = *_vm->_screen; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + ShapeList shapeList; + + // Draw all objects and animations that are set to behind + screen.setDisplayBounds(Common::Rect(ui._currentScroll.x, 0, ui._currentScroll.x + SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); + + // Draw all active shapes which are behind the person + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &obj = _bgShapes[idx]; + + if (obj._type == ACTIVE_BG_SHAPE && obj._misc == BEHIND) { + if (obj._quickDraw && obj._scaleVal == SCALE_THRESHOLD) + screen._backBuffer1.blitFrom(*obj._imageFrame, obj._position); + else + screen._backBuffer1.transBlitFrom(*obj._imageFrame, obj._position, obj._flags & OBJ_FLIPPED, 0, obj._scaleVal); + } + } + + // Draw the animation if it is behind the person + if (_activeCAnim._imageFrame != nullptr && _activeCAnim._zPlacement == BEHIND) + screen._backBuffer1.transBlitFrom(*_activeCAnim._imageFrame, _activeCAnim._position, + (_activeCAnim._flags & 4) >> 1, 0, _activeCAnim._scaleVal); + + screen.resetDisplayBounds(); + + // Queue drawing of all objects that are set to NORMAL. + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &obj = _bgShapes[idx]; + + if (obj._type == ACTIVE_BG_SHAPE && (obj._misc == NORMAL_BEHIND || obj._misc == NORMAL_FORWARD)) { + if (obj._scaleVal == SCALE_THRESHOLD) + shapeList.push_back(ShapeEntry(&obj, obj._position.y + obj._imageFrame->_offset.y + + obj._imageFrame->_height)); + else + shapeList.push_back(ShapeEntry(&obj, obj._position.y + obj._imageFrame->sDrawYOffset(obj._scaleVal) + + obj._imageFrame->sDrawYSize(obj._scaleVal))); + } + } + + // Queue drawing the animation if it is NORMAL and can fall in front of, or behind the people + if (_activeCAnim._imageFrame != nullptr && (_activeCAnim._zPlacement == NORMAL_BEHIND || _activeCAnim._zPlacement == NORMAL_FORWARD)) { + if (_activeCAnim._scaleVal == SCALE_THRESHOLD) + shapeList.push_back(ShapeEntry(_activeCAnim._position.y + _activeCAnim._imageFrame->_offset.y + + _activeCAnim._imageFrame->_height)); + else + shapeList.push_back(ShapeEntry(_activeCAnim._position.y + _activeCAnim._imageFrame->sDrawYOffset(_activeCAnim._scaleVal) + + _activeCAnim._imageFrame->sDrawYSize(_activeCAnim._scaleVal))); + } + + // Queue all active characters for drawing + for (int idx = 0; idx < MAX_CHARACTERS; ++idx) { + if (people[idx]._type == CHARACTER && people[idx]._walkLoaded) + shapeList.push_back(ShapeEntry(&people[idx], people[idx]._position.y / FIXED_INT_MULTIPLIER)); + } + + // Sort the list + Common::sort(shapeList.begin(), shapeList.end(), sortImagesY); + + // Draw the list of shapes in order + for (ShapeList::iterator i = shapeList.begin(); i != shapeList.end(); ++i) { + ShapeEntry &se = *i; + + if (se._shape) { + // it's a bg shape + if (se._shape->_quickDraw && se._shape->_scaleVal == SCALE_THRESHOLD) + screen._backBuffer1.blitFrom(*se._shape->_imageFrame, se._shape->_position); + else + screen._backBuffer1.transBlitFrom(*se._shape->_imageFrame, se._shape->_position, + se._shape->_flags & OBJ_FLIPPED, 0, se._shape->_scaleVal); + } else if (se._isAnimation) { + // It's an active animation + screen._backBuffer1.transBlitFrom(*_activeCAnim._imageFrame, _activeCAnim._position, + (_activeCAnim._flags & 4) >> 1, 0, _activeCAnim._scaleVal); + } else { + // Drawing person + TattooPerson &p = *se._person; + + p._tempX = p._position.x / FIXED_INT_MULTIPLIER; + p._tempScaleVal = getScaleVal(p._position); + Common::Point adjust = p._adjust; + + if (p._tempScaleVal == SCALE_THRESHOLD) { + p._tempX += adjust.x; + screen._backBuffer1.transBlitFrom(*p._imageFrame, Common::Point(p._tempX, p._position.y / FIXED_INT_MULTIPLIER + - p.frameHeight() - adjust.y), p._walkSequences[p._sequenceNumber]._horizFlip, 0, p._tempScaleVal); + } else { + if (adjust.x) { + if (!p._tempScaleVal) + ++p._tempScaleVal; + + if (p._tempScaleVal >= SCALE_THRESHOLD && adjust.x) + --adjust.x; + + adjust.x = adjust.x * SCALE_THRESHOLD / p._tempScaleVal; + + if (p._tempScaleVal >= SCALE_THRESHOLD) + ++adjust.x; + p._tempX += adjust.x; + } + + if (adjust.y) { + if (!p._tempScaleVal) + p._tempScaleVal++; + + if (p._tempScaleVal >= SCALE_THRESHOLD && adjust.y) + --adjust.y; + + adjust.y = adjust.y * SCALE_THRESHOLD / p._tempScaleVal; + + if (p._tempScaleVal >= SCALE_THRESHOLD) + ++adjust.y; + } + + screen._backBuffer1.transBlitFrom(*p._imageFrame, Common::Point(p._tempX, p._position.y / FIXED_INT_MULTIPLIER + - p._imageFrame->sDrawYSize(p._tempScaleVal) - adjust.y), p._walkSequences[p._sequenceNumber]._horizFlip, 0, p._tempScaleVal); + } + } + } + + // Draw all objects & canimations that are set to FORWARD. + // Draw all static and active shapes that are FORWARD + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &obj = _bgShapes[idx]; + + if (obj._type == ACTIVE_BG_SHAPE && obj._misc == FORWARD) { + if (obj._quickDraw && obj._scaleVal == SCALE_THRESHOLD) + screen._backBuffer1.blitFrom(*obj._imageFrame, obj._position); + else + screen._backBuffer1.transBlitFrom(*obj._imageFrame, obj._position, obj._flags & OBJ_FLIPPED, 0, obj._scaleVal); + } + } + + // Draw the canimation if it is set as FORWARD + if (_activeCAnim._imageFrame != nullptr && _activeCAnim._zPlacement == FORWARD) + screen._backBuffer1.transBlitFrom(*_activeCAnim._imageFrame, _activeCAnim._position, (_activeCAnim._flags & 4) >> 1, 0, _activeCAnim._scaleVal); + + // Draw all NO_SHAPE shapes which have their flag bits clear + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &obj = _bgShapes[idx]; + if (obj._type == NO_SHAPE && (obj._flags & 1) == 0) + screen._backBuffer1.fillRect(obj.getNoShapeBounds(), 15); + } +} + +void TattooScene::paletteLoaded() { + Screen &screen = *_vm->_screen; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + + ui.setupBGArea(screen._cMap); + ui.initScrollVars(); +} + +void TattooScene::checkBgShapes() { + // Call the base scene method to handle bg shapes + Scene::checkBgShapes(); + + // Check for any active playing animation + if (_activeCAnim._imageFrame && _activeCAnim._zPlacement != REMOVE) { + switch (_activeCAnim._flags & 3) { + case 0: + _activeCAnim._zPlacement = BEHIND; + break; + case 1: + _activeCAnim._zPlacement = ((_activeCAnim._position.y + _activeCAnim._imageFrame->_frame.h - 1)) ? + NORMAL_FORWARD : NORMAL_BEHIND; + break; + case 2: + _activeCAnim._zPlacement = FORWARD; + break; + default: + break; + } + } +} + +void TattooScene::doBgAnimCheckCursor() { + Events &events = *_vm->_events; + UserInterface &ui = *_vm->_ui; + Common::Point mousePos = events.mousePos(); + + // If we're in Look Mode, make sure the cursor is the magnifying glass + if (ui._menuMode == LOOK_MODE && events.getCursor() != MAGNIFY) + events.setCursor(MAGNIFY); + + // See if the mouse is over any of the arrow zones, and if so, change the cursor to the correct + // arrow cursor indicating the direcetion of the exit + if (events.getCursor() == ARROW || events.getCursor() >= EXIT_ZONES_START) { + CursorId cursorId = ARROW; + + if (ui._menuMode == STD_MODE && _arrowZone != -1 && _currentScene != 90) { + for (uint idx = 0; idx < _exits.size(); ++idx) { + Exit &exit = _exits[idx]; + if (exit.contains(mousePos)) + cursorId = (CursorId)(exit._image + EXIT_ZONES_START); + } + } + + events.setCursor(cursorId); + } else { + events.animateCursorIfNeeded(); + } +} + +void TattooScene::doBgAnim() { + TattooEngine &vm = *(TattooEngine *)_vm; + Events &events = *_vm->_events; + TattooPeople &people = *(TattooPeople *)_vm->_people; + Screen &screen = *_vm->_screen; + Talk &talk = *_vm->_talk; + TattooUserInterface &ui = *((TattooUserInterface *)_vm->_ui); + + doBgAnimCheckCursor(); + + talk._talkToAbort = false; + + // Check the characters and sprites for updates + for (int idx = 0; idx < MAX_CHARACTERS; ++idx) { + if (people[idx]._type == CHARACTER) + people[idx].checkSprite(); + } + + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + if (_bgShapes[idx]._type == ACTIVE_BG_SHAPE) + _bgShapes[idx].checkObject(); + } + + // If one of the objects has signalled a call to a talk file, to go to another scene, exit immediately + if (_goToScene != -1) + return; + + // Erase any affected background areas + ui.doBgAnimEraseBackground(); + + doBgAnimUpdateBgObjectsAndAnim(); + + ui.drawInterface(); + + doBgAnimDrawSprites(); + + if (vm._creditsActive) + vm.blitCredits(); + + if (!vm._fastMode) + events.wait(3); + + screen._flushScreen = false; + _doBgAnimDone = true; + ui._drawMenu = false; + + for (int idx = 1; idx < MAX_CHARACTERS; ++idx) { + if (people[idx]._updateNPCPath) + people[idx].updateNPC(); + } +} + +void TattooScene::doBgAnimUpdateBgObjectsAndAnim() { + People &people = *_vm->_people; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &obj = _bgShapes[idx]; + if (obj._type == ACTIVE_BG_SHAPE || obj._type == NO_SHAPE) + obj.adjustObject(); + } + + for (int idx = 0; idx < MAX_CHARACTERS; ++idx) { + if (people[idx]._type == CHARACTER) + people[idx].adjustSprite(); + } + + if (_activeCAnim._imageFrame != nullptr && _activeCAnim._zPlacement != REMOVE) { + _activeCAnim.getNextFrame(); + } + + // Flag the bg shapes which need to be redrawn + checkBgShapes(); + drawAllShapes(); + + ui.drawMaskArea(true); +} + +void TattooScene::updateBackground() { + TattooPeople &people = *(TattooPeople *)_vm->_people; + Screen &screen = *_vm->_screen; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + + Scene::updateBackground(); + + ui.drawMaskArea(false); + + screen._flushScreen = true; + + for (int idx = 0; idx < MAX_CHARACTERS; ++idx) { + TattooPerson &p = people[idx]; + + if (p._type != INVALID) { + if (_goToScene == -1 || _cAnim.size() == 0) { + if (p._type == REMOVE) { + screen.slamArea(p._oldPosition.x, p._oldPosition.y, p._oldSize.x, p._oldSize.y); + p._type = INVALID; + } else { + if (p._tempScaleVal == SCALE_THRESHOLD) { + screen.flushImage(p._imageFrame, Common::Point(p._tempX, p._position.y / FIXED_INT_MULTIPLIER + - p._imageFrame->_width), &p._oldPosition.x, &p._oldPosition.y, &p._oldSize.x, &p._oldSize.y); + } else { + int ts = p._imageFrame->sDrawYSize(p._tempScaleVal); + int ty = p._position.y / FIXED_INT_MULTIPLIER - ts; + screen.flushScaleImage(p._imageFrame, Common::Point(p._tempX, ty), + &p._oldPosition.x, &p._oldPosition.y, &p._oldSize.x, &p._oldSize.y, p._tempScaleVal); + } + } + } + } + } + + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &obj = _bgShapes[idx]; + + if (obj._type == ACTIVE_BG_SHAPE || obj._type == REMOVE) { + if (_goToScene == -1) { + if (obj._scaleVal == SCALE_THRESHOLD) + screen.flushImage(obj._imageFrame, obj._position, &obj._oldPosition.x, &obj._oldPosition.y, + &obj._oldSize.x, &obj._oldSize.y); + else + screen.flushScaleImage(obj._imageFrame, obj._position, &obj._oldPosition.x, &obj._oldPosition.y, + &obj._oldSize.x, &obj._oldSize.y, obj._scaleVal); + + if (obj._type == REMOVE) + obj._type = INVALID; + } + } + } + + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &obj = _bgShapes[idx]; + + if (_goToScene == -1) { + if (obj._type == NO_SHAPE && (obj._flags & 1) == 0) { + screen.slamRect(obj.getNoShapeBounds()); + screen.slamRect(obj.getOldBounds()); + } else if (obj._type == HIDE_SHAPE) { + if (obj._scaleVal == SCALE_THRESHOLD) + screen.flushImage(obj._imageFrame, obj._position, &obj._oldPosition.x, &obj._oldPosition.y, + &obj._oldSize.x, &obj._oldSize.y); + else + screen.flushScaleImage(obj._imageFrame, obj._position, &obj._oldPosition.x, &obj._oldPosition.y, + &obj._oldSize.x, &obj._oldSize.y, obj._scaleVal); + obj._type = HIDDEN; + } + } + } + + screen._flushScreen = false; +} + +void TattooScene::doBgAnimDrawSprites() { + TattooPeople &people = *(TattooPeople *)_vm->_people; + Screen &screen = *_vm->_screen; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + + for (int idx = 0; idx < MAX_CHARACTERS; ++idx) { + TattooPerson &person = people[idx]; + + if (person._type != INVALID) { + if (_goToScene == -1 || _cAnim.size() == 0) { + if (person._type == REMOVE) { + screen.slamRect(person.getOldBounds()); + person._type = INVALID; + } else { + if (person._tempScaleVal == SCALE_THRESHOLD) { + screen.flushImage(person._imageFrame, Common::Point(person._tempX, person._position.y / FIXED_INT_MULTIPLIER + - person.frameHeight()), &person._oldPosition.x, &person._oldPosition.y, &person._oldSize.x, &person._oldSize.y); + } else { + int ts = person._imageFrame->sDrawYSize(person._tempScaleVal); + int ty = person._position.y / FIXED_INT_MULTIPLIER - ts; + screen.flushScaleImage(person._imageFrame, Common::Point(person._tempX, ty), + &person._oldPosition.x, &person._oldPosition.y, &person._oldSize.x, &person._oldSize.y, person._tempScaleVal); + } + } + } + } + } + + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &obj = _bgShapes[idx]; + + if (obj._type == ACTIVE_BG_SHAPE || obj._type == REMOVE) { + if (_goToScene == -1) { + if (obj._scaleVal == SCALE_THRESHOLD) + screen.flushImage(obj._imageFrame, obj._position, &obj._oldPosition.x, &obj._oldPosition.y, + &obj._oldSize.x, &obj._oldSize.y); + else + screen.flushScaleImage(obj._imageFrame, obj._position, &obj._oldPosition.x, &obj._oldPosition.y, + &obj._oldSize.x, &obj._oldSize.y, obj._scaleVal); + + if (obj._type == REMOVE) + obj._type = INVALID; + } + } + } + + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &obj = _bgShapes[idx]; + + if (_goToScene == -1) { + if (obj._type == NO_SHAPE && (obj._flags & 1) == 0) { + screen.slamRect(obj.getNoShapeBounds()); + screen.slamRect(obj.getOldBounds()); + } else if (obj._type == HIDE_SHAPE) { + if (obj._scaleVal == SCALE_THRESHOLD) + screen.flushImage(obj._imageFrame, obj._position, &obj._oldPosition.x, &obj._oldPosition.y, + &obj._oldSize.x, &obj._oldSize.y); + else + screen.flushScaleImage(obj._imageFrame, obj._position, &obj._oldPosition.x, &obj._oldPosition.y, + &obj._oldSize.x, &obj._oldSize.y, obj._scaleVal); + obj._type = HIDDEN; + } + } + } + + if (_activeCAnim._imageFrame != nullptr || _activeCAnim._zPlacement == REMOVE) { + if (_activeCAnim._zPlacement != REMOVE) { + screen.flushImage(_activeCAnim._imageFrame, _activeCAnim._position, _activeCAnim._oldBounds, _activeCAnim._scaleVal); + } else { + screen.slamArea(_activeCAnim._removeBounds.left - ui._currentScroll.x, _activeCAnim._removeBounds.top, + _activeCAnim._removeBounds.width(), _activeCAnim._removeBounds.height()); + _activeCAnim._removeBounds.left = _activeCAnim._removeBounds.top = 0; + _activeCAnim._removeBounds.right = _activeCAnim._removeBounds.bottom = 0; + _activeCAnim._zPlacement = -1; // Reset _zPlacement so we don't REMOVE again + } + } +} + +int TattooScene::getScaleVal(const Point32 &pt) { + bool found = false; + int result = SCALE_THRESHOLD; + Common::Point pos(pt.x / FIXED_INT_MULTIPLIER, pt.y / FIXED_INT_MULTIPLIER); + + for (uint idx = 0; idx < _scaleZones.size() && !found; ++idx) { + ScaleZone &sz = _scaleZones[idx]; + if (sz.contains(pos)) { + int n = (sz._bottomNumber - sz._topNumber) * 100 / sz.height() * (pos.y - sz.top) / 100 + sz._topNumber; + result = 25600L / n; + // CHECKME: Shouldn't we set 'found' at this place? + } + } + + // If it wasn't found, we may be off screen to the left or right, so find the scale zone + // that would apply to the y val passed in disregarding the x + if (!found) { + for (uint idx = 0; idx < _scaleZones.size() && !found; ++idx) { + ScaleZone &sz = _scaleZones[idx]; + if (pos.y >= sz.top && pos.y < sz.bottom) { + int n = (sz._bottomNumber - sz._topNumber) * 100 / sz.height() * (pos.y - sz.top) / 100 + sz._topNumber; + result = 25600L / n; + } + } + } + + return result; +} + +#define ADJUST_COORD(COORD) \ + if (COORD.x != -1) \ + COORD.x *= FIXED_INT_MULTIPLIER; \ + if (COORD.y != -1) \ + COORD.y *= FIXED_INT_MULTIPLIER + +int TattooScene::startCAnim(int cAnimNum, int playRate) { + TattooEngine &vm = *(TattooEngine *)_vm; + Events &events = *_vm->_events; + TattooPeople &people = *(TattooPeople *)_vm->_people; + Resources &res = *_vm->_res; + Talk &talk = *_vm->_talk; + UserInterface &ui = *_vm->_ui; + + // Exit immediately if the anim number is out of range, or the anim doesn't have a position specified + if (cAnimNum < 0 || cAnimNum >= (int)_cAnim.size() || _cAnim[cAnimNum]._position.x == -1) + // Return out of range error + return -1; + + // Get the co-ordinates that the Player & NPC #1 must walk to and end on + CAnim &cAnim = _cAnim[cAnimNum]; + PositionFacing goto1 = cAnim._goto[0]; + PositionFacing goto2 = cAnim._goto[1]; + PositionFacing teleport1 = cAnim._teleport[0]; + PositionFacing teleport2 = cAnim._teleport[1]; + + // If the co-ordinates are valid (not -1), adjust them by the fixed int multiplier + ADJUST_COORD(goto1); + ADJUST_COORD(goto2); + ADJUST_COORD(teleport1); + ADJUST_COORD(teleport2); + + // See if the Player must walk to a position before the animation starts + SpriteType savedPlayerType = people[HOLMES]._type; + if (goto1.x != -1 && people[HOLMES]._type == CHARACTER) { + if (people[HOLMES]._position != goto1) + people[HOLMES].walkToCoords(goto1, goto1._facing); + } + + if (talk._talkToAbort) + return 1; + + // See if NPC #1 must walk to a position before the animation starts + SpriteType savedNPCType = people[WATSON]._type; + if (goto2.x != -1 && people[WATSON]._type == CHARACTER) { + if (people[WATSON]._position != goto2) + people[WATSON].walkToCoords(goto2, goto2._facing); + } + + if (talk._talkToAbort) + return 1; + + // Turn the player (and NPC #1 if neccessary) off before running the canimation + if (teleport1.x != -1 && savedPlayerType == CHARACTER) + people[HOLMES]._type = REMOVE; + + if (teleport2.x != -1 && savedNPCType == CHARACTER) + people[WATSON]._type = REMOVE; + + if (ui._windowOpen) + ui.banishWindow(); + + //_activeCAnim._filesize = cAnim._size; + + // Open up the room resource file and get the data for the animation + Common::SeekableReadStream *stream = res.load(_roomFilename); + stream->seek(44 + cAnimNum * 4); + stream->seek(stream->readUint32LE()); + Common::SeekableReadStream *animStream = stream->readStream(cAnim._dataSize); + delete stream; + + // Set up the active animation + _activeCAnim._position = cAnim._position; + _activeCAnim._oldBounds = Common::Rect(0, 0, 0, 0); + _activeCAnim._flags = cAnim._flags; + _activeCAnim._scaleVal = cAnim._scaleVal; + _activeCAnim._zPlacement = 0; + + _activeCAnim.load(animStream); + + while (_activeCAnim.active() && !_vm->shouldQuit()) { + doBgAnim(); + + events.pollEvents(); + if (events.kbHit()) { + Common::KeyState keyState = events.getKey(); + + if (keyState.keycode == Common::KEYCODE_ESCAPE && vm._runningProlog) { + _vm->setFlags(-76); + _vm->setFlags(396); + _goToScene = STARTING_GAME_SCENE; + talk._talkToAbort = true; + _activeCAnim.close(); + } + } + } + + // Turn the people back on + people[HOLMES]._type = savedPlayerType; + if (teleport2.x != -1) + people[WATSON]._type = savedNPCType; + + // Teleport the Player to the ending coordinates if necessary + if (teleport1.x != -1 && savedPlayerType == CHARACTER) { + people[HOLMES]._position = teleport1; + people[HOLMES]._sequenceNumber = teleport1._facing; + people[HOLMES].gotoStand(); + } + + // Teleport Watson to the ending coordinates if necessary + if (teleport2.x != -1 && savedNPCType == CHARACTER) { + people[WATSON]._position = teleport2; + people[WATSON]._sequenceNumber = teleport2._facing; + people[WATSON].gotoStand(); + } + + // Flag the Canimation to be cleared + _activeCAnim._zPlacement = REMOVE; + _activeCAnim._removeBounds = _activeCAnim._oldBounds; + + // Free up the animation + _activeCAnim.close(); + + return 1; +} + +#undef ADJUST_COORD + +void TattooScene::setNPCPath(int npc) { + TattooPeople &people = *(TattooPeople *)_vm->_people; + Talk &talk = *_vm->_talk; + + people[npc].clearNPC(); + people[npc]._name = Common::String::format("WATS%.2dA", _currentScene); + + // If we're in the middle of a script that will continue once the scene is loaded, + // return without calling the path script + if (talk._scriptMoreFlag == 1 || talk._scriptMoreFlag == 3) + return; + + // Turn off all the NPCs, since the talk script will turn them back on as needed + for (int idx = 1; idx < MAX_CHARACTERS; ++idx) + people[idx]._type = INVALID; + + // Call the path script for the scene + Common::String pathFile = Common::String::format("PATH%.2dA", _currentScene); + talk.talkTo(pathFile); +} + +void TattooScene::synchronize(Serializer &s) { + TattooEngine &vm = *(TattooEngine *)_vm; + Scene::synchronize(s); + + if (s.isLoading()) + vm._runningProlog = false; +} + +} // End of namespace Tattoo + +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/tattoo_scene.h b/engines/sherlock/tattoo/tattoo_scene.h new file mode 100644 index 0000000000..106903f076 --- /dev/null +++ b/engines/sherlock/tattoo/tattoo_scene.h @@ -0,0 +1,138 @@ +/* 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 SHERLOCK_TATTOO_SCENE_H +#define SHERLOCK_TATTOO_SCENE_H + +#include "common/scummsys.h" +#include "sherlock/scene.h" + +namespace Sherlock { + +namespace Tattoo { + +enum { + STARTING_GAME_SCENE = 1, STARTING_INTRO_SCENE = 91, OVERHEAD_MAP2 = 99, OVERHEAD_MAP = 100 +}; + +struct SceneTripEntry { + bool _flag; + int _sceneNumber; + int _numTimes; + + SceneTripEntry() : _flag(false), _sceneNumber(0), _numTimes(0) {} + SceneTripEntry(bool flag, int sceneNumber, int numTimes) : _flag(flag), + _sceneNumber(sceneNumber), _numTimes(numTimes) {} +}; + +class TattooScene : public Scene { +private: + int _arrowZone; +private: + void doBgAnimCheckCursor(); + + /** + * Update the background objects and canimations as part of doBgAnim + */ + void doBgAnimUpdateBgObjectsAndAnim(); + + void doBgAnimDrawSprites(); + + /** + * Resets the NPC path information when entering a new scene. + * @remarks The default talk file for the given NPC is set to WATS##A, where ## is + * the scene number being entered + */ + void setNPCPath(int npc); +protected: + /** + * Loads the data associated for a given scene. The room resource file's format is: + * BGHEADER: Holds an index for the rest of the file + * STRUCTS: The objects for the scene + * IMAGES: The graphic information for the structures + * + * The _misc field of the structures contains the number of the graphic image + * that it should point to after loading; _misc is then set to 0. + */ + virtual bool loadScene(const Common::String &filename); + + /** + * Checks all the background shapes. If a background shape is animating, + * it will flag it as needing to be drawn. If a non-animating shape is + * colliding with another shape, it will also flag it as needing drawing + */ + virtual void checkBgShapes(); + + /** + * Draw all the shapes, people and NPCs in the correct order + */ + virtual void drawAllShapes(); + + /** + * Called by loadScene when the palette is loaded for Rose Tattoo + */ + virtual void paletteLoaded(); + + /** + * Synchronize the data for a savegame + */ + virtual void synchronize(Serializer &s); +public: + CAnimStream _activeCAnim; + Common::Array<SceneTripEntry> _sceneTripCounters; + bool _labTableScene; +public: + TattooScene(SherlockEngine *vm); + + /** + * Returns the scale value for the passed co-ordinates. This is taken from the scene's + * scale zones, interpolating inbetween the top and bottom values of the zones as needed + */ + int getScaleVal(const Point32 &pt); + + /** + * Draw all objects and characters. + */ + virtual void doBgAnim(); + + /** + * Update the screen back buffer with all of the scene objects which need + * to be drawn + */ + virtual void updateBackground(); + + /** + * Attempt to start a canimation sequence. It will load the requisite graphics, and + * then copy the canim object into the _canimShapes array to start the animation. + * + * @param cAnimNum The canim object within the current scene + * @param playRate Play rate. 0 is invalid; 1=normal speed, 2=1/2 speed, etc. + * A negative playRate can also be specified to play the animation in reverse + */ + virtual int startCAnim(int cAnimNum, int playRate = 1); +}; + +} // End of namespace Tattoo + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/tattoo/tattoo_talk.cpp b/engines/sherlock/tattoo/tattoo_talk.cpp new file mode 100644 index 0000000000..ec75b31329 --- /dev/null +++ b/engines/sherlock/tattoo/tattoo_talk.cpp @@ -0,0 +1,826 @@ +/* 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 "sherlock/tattoo/tattoo_talk.h" +#include "sherlock/tattoo/tattoo_people.h" +#include "sherlock/tattoo/tattoo_user_interface.h" +#include "sherlock/sherlock.h" +#include "sherlock/screen.h" + +namespace Sherlock { + +namespace Tattoo { + +static const uint8 DIRECTION_CONVERSION[] = { + WALK_RIGHT, WALK_DOWN, WALK_LEFT, WALK_UP, STOP_RIGHT, STOP_DOWN, STOP_LEFT, STOP_UP, + WALK_UPRIGHT, WALK_DOWNRIGHT, WALK_UPLEFT, WALK_DOWNLEFT, STOP_UPRIGHT, STOP_UPLEFT, + STOP_DOWNRIGHT, STOP_DOWNLEFT +}; + +const byte TATTOO_OPCODES[] = { + 170, // OP_SWITCH_SPEAKER + 171, // OP_RUN_CANIMATION + 0, // OP_ASSIGN_PORTRAIT_LOCATION + 173, // OP_PAUSE + 0, // OP_REMOVE_PORTRAIT + 0, // OP_CLEAR_WINDOW + 176, // OP_ADJUST_OBJ_SEQUENCE + 177, // OP_WALK_HOlMES_TO_COORDS + 178, // OP_PAUSE_WITHOUT_CONTROL + 179, // OP_BANISH_WINDOW + 0, // OP_SUMMON_WINDOW + 181, // OP_SET_FLAG + 0, // OP_SFX_COMMAND + 183, // OP_TOGGLE_OBJECT + 184, // OP_STEALTH_MODE_ACTIVE + 0, // OP_IF_STATEMENT + 0, // OP_ELSE_STATEMENT + 0, // OP_END_IF_STATEMENT + 188, // OP_STEALTH_MODE_DEACTIVATE + 189, // OP_TURN_HOLMES_OFF + 190, // OP_TURN_HOLMES_ON + 191, // OP_GOTO_SCENE + 0, // OP_PLAY_PROLOGUE + 193, // OP_ADD_ITEM_TO_INVENTORY + 194, // OP_SET_OBJECT + 172, // OP_CALL_TALK_FILE + 0, // OP_MOVE_MOUSE + 0, // OP_DISPLAY_INFO_LINE + 0, // OP_CLEAR_INFO_LINE + 199, // OP_WALK_TO_CANIMATION + 200, // OP_REMOVE_ITEM_FROM_INVENTORY + 201, // OP_ENABLE_END_KEY + 202, // OP_DISABLE_END_KEY + 0, // OP_CARRIAGE_RETURN + 174, // OP_MOUSE_ON_OFF + 175, // OP_SET_WALK_CONTROL + 180, // OP_SET_TALK_SEQUENCE + 182, // OP_PLAY_SONG + 187, // OP_WALK_HOLMES_AND_NPC_TO_CANIM + 192, // OP_SET_NPC_PATH_DEST + 195, // OP_NEXT_SONG + 196, // OP_SET_NPC_PATH_PAUSE + 197, // OP_PASSWORD + 198, // OP_SET_SCENE_ENTRY_FLAG + 185, // OP_WALK_NPC_TO_CANIM + 186, // OP_WALK_NPC_TO_COORDS + 204, // OP_WALK_HOLMES_AND_NPC_TO_COORDS + 205, // OP_SET_NPC_TALK_FILE + 206, // OP_TURN_NPC_OFF + 207, // OP_TURN_NPC_ON + 208, // OP_NPC_DESC_ON_OFF + 209, // OP_NPC_PATH_PAUSE_TAKING_NOTES + 210, // OP_NPC_PATH_PAUSE_LOOKING_HOLMES + 211, // OP_ENABLE_TALK_INTERRUPTS + 212, // OP_DISABLE_TALK_INTERRUPTS + 213, // OP_SET_NPC_INFO_LINE + 214, // OP_SET_NPC_POSITION + 215, // OP_NPC_PATH_LABEL + 216, // OP_PATH_GOTO_LABEL + 217, // OP_PATH_IF_FLAG_GOTO_LABEL + 218, // OP_NPC_WALK_GRAPHICS + 220, // OP_NPC_VERB + 221, // OP_NPC_VERB_CANIM + 222, // OP_NPC_VERB_SCRIPT + 224, // OP_RESTORE_PEOPLE_SEQUENCE + 226, // OP_NPC_VERB_TARGET + 227, // OP_TURN_SOUNDS_OFF + 225, // OP_NULL + 203 // OP_END_TEXT_WINDOW +}; + +/*----------------------------------------------------------------*/ + +TattooTalk::TattooTalk(SherlockEngine *vm) : Talk(vm), _talkWidget(vm) { + static OpcodeMethod OPCODE_METHODS[] = { + (OpcodeMethod)&TattooTalk::cmdSwitchSpeaker, + + (OpcodeMethod)&TattooTalk::cmdRunCAnimation, + (OpcodeMethod)&TattooTalk::cmdCallTalkFile, + (OpcodeMethod)&TattooTalk::cmdPause, + (OpcodeMethod)&TattooTalk::cmdMouseOnOff, + (OpcodeMethod)&TattooTalk::cmdSetWalkControl, + (OpcodeMethod)&TattooTalk::cmdAdjustObjectSequence, + (OpcodeMethod)&TattooTalk::cmdWalkHolmesToCoords, + (OpcodeMethod)&TattooTalk::cmdPauseWithoutControl, + (OpcodeMethod)&TattooTalk::cmdBanishWindow, + (OpcodeMethod)&TattooTalk::cmdSetTalkSequence, + + (OpcodeMethod)&TattooTalk::cmdSetFlag, + (OpcodeMethod)&TattooTalk::cmdPlaySong, + (OpcodeMethod)&TattooTalk::cmdToggleObject, + (OpcodeMethod)&TattooTalk::cmdStealthModeActivate, + (OpcodeMethod)&TattooTalk::cmdWalkNPCToCAnimation, + (OpcodeMethod)&TattooTalk::cmdWalkNPCToCoords, + (OpcodeMethod)&TattooTalk::cmdWalkHomesAndNPCToCoords, + (OpcodeMethod)&TattooTalk::cmdStealthModeDeactivate, + (OpcodeMethod)&TattooTalk::cmdHolmesOff, + (OpcodeMethod)&TattooTalk::cmdHolmesOn, + + (OpcodeMethod)&TattooTalk::cmdGotoScene, + (OpcodeMethod)&TattooTalk::cmdSetNPCPathDest, + (OpcodeMethod)&TattooTalk::cmdAddItemToInventory, + (OpcodeMethod)&TattooTalk::cmdSetObject, + (OpcodeMethod)&TattooTalk::cmdNextSong, + (OpcodeMethod)&TattooTalk::cmdSetNPCPathPause, + (OpcodeMethod)&TattooTalk::cmdPassword, + (OpcodeMethod)&TattooTalk::cmdSetSceneEntryFlag, + (OpcodeMethod)&TattooTalk::cmdWalkToCAnimation, + (OpcodeMethod)&TattooTalk::cmdRemoveItemFromInventory, + + (OpcodeMethod)&TattooTalk::cmdEnableEndKey, + (OpcodeMethod)&TattooTalk::cmdDisableEndKey, + nullptr, + (OpcodeMethod)&TattooTalk::cmdWalkHomesAndNPCToCoords, + (OpcodeMethod)&TattooTalk::cmdSetNPCTalkFile, + (OpcodeMethod)&TattooTalk::cmdSetNPCOff, + (OpcodeMethod)&TattooTalk::cmdSetNPCOn, + (OpcodeMethod)&TattooTalk::cmdSetNPCDescOnOff, + (OpcodeMethod)&TattooTalk::cmdSetNPCPathPauseTakingNotes, + (OpcodeMethod)&TattooTalk::cmdSetNPCPathPauseLookingHolmes, + + (OpcodeMethod)&TattooTalk::cmdTalkInterruptsEnable, + (OpcodeMethod)&TattooTalk::cmdTalkInterruptsDisable, + (OpcodeMethod)&TattooTalk::cmdSetNPCInfoLine, + (OpcodeMethod)&TattooTalk::cmdSetNPCPosition, + (OpcodeMethod)&TattooTalk::cmdNPCLabelSet, + (OpcodeMethod)&TattooTalk::cmdNPCLabelGoto, + (OpcodeMethod)&TattooTalk::cmdNPCLabelIfFlagGoto, + (OpcodeMethod)&TattooTalk::cmdSetNPCWalkGraphics, + nullptr, + (OpcodeMethod)&TattooTalk::cmdSetNPCVerb, + + (OpcodeMethod)&TattooTalk::cmdSetNPCVerbCAnimation, + (OpcodeMethod)&TattooTalk::cmdSetNPCVerbScript, + nullptr, + (OpcodeMethod)&TattooTalk::cmdRestorePeopleSequence, + (OpcodeMethod)&TattooTalk::cmdSetNPCVerbTarget, + (OpcodeMethod)&TattooTalk::cmdTurnSoundsOff, + nullptr + }; + + _opcodes = TATTOO_OPCODES; + _opcodeTable = OPCODE_METHODS; +} + +void TattooTalk::talkInterface(const byte *&str) { + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + const char *s = (const char *)str; + + // Move to past the end of the text string + _charCount = 0; + while ((*str < TATTOO_OPCODES[0] || *str == TATTOO_OPCODES[OP_NULL]) && *str) { + ++_charCount; + ++str; + } + + // Display the text window + ui.banishWindow(); + ui._textWidget.load(s); + ui._textWidget.summonWindow(); + _wait = true; +} + +void TattooTalk::openTalkWindow() { + _talkWidget.load(); + _talkWidget.summonWindow(); +} + +OpcodeReturn TattooTalk::cmdSwitchSpeaker(const byte *&str) { + TattooPeople &people = *(TattooPeople *)_vm->_people; + Screen &screen = *_vm->_screen; + UserInterface &ui = *_vm->_ui; + + if (_talkToAbort) + return RET_EXIT; + + ui.clearWindow(); + + _yp = screen.fontHeight() + 11; + _charCount = _line = 0; + + people.setListenSequence(_speaker, 129); + _speaker = *++str - 1; + ++str; + + people.setTalkSequence(_speaker, 1); + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdMouseOnOff(const byte *&str) { + Events &events = *_vm->_events; + bool mouseOn = *++str == 2; + if (mouseOn) + events.showCursor(); + else + events.hideCursor(); + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdWalkHolmesToCoords(const byte *&str) { + People &people = *_vm->_people; + ++str; + + int x = (str[0] - 1) * 256 + str[1] - 1; + if (x > 16384) + x = -1 * (x - 16384); + warning("TODO: cmdWalkHolmesToCoords - call RT walkToCoords variant"); + people[HOLMES].walkToCoords( + Point32(x * FIXED_INT_MULTIPLIER, ((str[2] - 1) * 256 + str[3] - 1) * FIXED_INT_MULTIPLIER), + DIRECTION_CONVERSION[str[4] - 1] + //HOLMES + ); + if (_talkToAbort) + return RET_EXIT; + + str += 4; + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdGotoScene(const byte *&str) { + Map &map = *_vm->_map; + TattooPeople &people = *(TattooPeople *)_vm->_people; + Scene &scene = *_vm->_scene; + scene._goToScene = str[1] - 1; + + if (scene._goToScene != 100) { + // Not going to the map overview + map._oldCharPoint = scene._goToScene; + + // Run a canimation? + if (str[2] > 100) { + people._hSavedFacing = str[2]; + people._hSavedPos = Point32(160, 100); + } else { + people._hSavedFacing = str[2] - 1; + int32 posX = (str[3] - 1) * 256 + str[4] - 1; + if (posX > 16384) + posX = -1 * (posX - 16384); + int32 posY = (str[5] - 1) * 256 + str[6] - 1; + people._hSavedPos = Point32(posX, posY); + } + + _scriptMoreFlag = 1; + } // if (scene._goToScene != 100) + + str += 7; + if (scene._goToScene != 100) + _scriptSaveIndex = str - _scriptStart; + + _endStr = true; + _wait = 0; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdNextSong(const byte *&str) { + Sound &sound = *_vm->_sound; + + // Get the name of the next song to play + ++str; + sound._nextSongName = ""; + for (int idx = 0; idx < 8; ++idx) { + if (str[idx] != '~') + sound._nextSongName += str[idx]; + else + break; + } + str += 7; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdNPCLabelGoto(const byte *&str) { + int npcNum = *++str; + TattooPeople &people = *(TattooPeople *)_vm->_people; + TattooPerson &person = people[npcNum]; + + if (person._resetNPCPath) { + person._npcIndex = person._npcPause = 0; + person._resetNPCPath = false; + memset(person._npcPath, 0, 100); + } + + person._npcPath[person._npcIndex] = 8; + person._npcPath[person._npcIndex + 1] = str[1]; + person._npcIndex += 2; + str++; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdNPCLabelIfFlagGoto(const byte *&str) { + int npcNum = *++str; + TattooPeople &people = *(TattooPeople *)_vm->_people; + TattooPerson &person = people[npcNum]; + + if (person._resetNPCPath) { + person._npcIndex = person._npcPause = 0; + person._resetNPCPath = false; + memset(person._npcPath, 0, 100); + } + + person._npcPath[person._npcIndex] = 9; + for (int i = 1; i <= 3; i++) + person._npcPath[person._npcIndex + i] = str[i]; + + person._npcIndex += 4; + str += 3; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdNPCLabelSet(const byte *&str) { + int npcNum = *++str; + TattooPeople &people = *(TattooPeople *)_vm->_people; + TattooPerson &person = people[npcNum]; + + if (person._resetNPCPath) { + person._npcIndex = person._npcPause = 0; + person._resetNPCPath = false; + memset(person._npcPath, 0, 100); + } + + person._npcPath[person._npcIndex] = 7; + person._npcPath[person._npcIndex + 1] = str[1]; + person._npcIndex += 2; + str++; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdPassword(const byte *&str) { error("TODO: script opcode (cmdPassword)"); } +OpcodeReturn TattooTalk::cmdPlaySong(const byte *&str) { error("TODO: script opcode (cmdPlaySong)"); } + +OpcodeReturn TattooTalk::cmdRestorePeopleSequence(const byte *&str) { + int npcNum = *++str - 1; + TattooPeople &people = *(TattooPeople *)_vm->_people; + TattooPerson &person = people[npcNum]; + person._misc = 0; + + if (person._seqTo) { + person._walkSequences[person._sequenceNumber]._sequences[person._frameNumber] = person._seqTo; + person._seqTo = 0; + } + person._sequenceNumber = person._savedNpcSequence; + person._frameNumber = person._savedNpcFrame; + person.checkWalkGraphics(); + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdSetNPCDescOnOff(const byte *&str) { + int npcNum = *++str; + ++str; + TattooPeople &people = *(TattooPeople *)_vm->_people; + Person &person = people[npcNum]; + + // Copy over the NPC examine text until we reach a stop marker, which is + // the same as a start marker, or we reach the end of the file + while (*str && *str != _opcodes[OP_NPC_DESC_ON_OFF]) + person._examine += *str++; + + // Move past any leftover text till we reach a stop marker + while (*str && *str != _opcodes[OP_NPC_DESC_ON_OFF]) + str++; + + if (!*str) + // Reached end of file, so decrement pointer so outer loop will terminate on NULL + --str; + else + // Move past the ending OP_NPC_DEST_ON_OFF opcode + ++str; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdSetNPCInfoLine(const byte *&str) { + int npcNum = *++str; + TattooPeople &people = *(TattooPeople *)_vm->_people; + TattooPerson &person = people[npcNum]; + + person._description = ""; + int len = *++str; + for (int idx = 0; idx < len; ++idx) + person._description += str[idx + 1]; + + str += len; + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdSetNPCOff(const byte *&str) { + TattooPeople &people = *(TattooPeople *)_vm->_people; + int npcNum = *++str; + people[npcNum]._type = REMOVE; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdSetNPCOn(const byte *&str) { + TattooPeople &people = *(TattooPeople *)_vm->_people; + int npcNum = *++str; + people[npcNum]._type = CHARACTER; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdSetNPCPathDest(const byte *&str) { + int npcNum = *++str; + TattooPeople &people = *(TattooPeople *)_vm->_people; + TattooPerson &person = people[npcNum]; + + if (person._resetNPCPath) { + person._npcIndex = person._npcPause = 0; + person._resetNPCPath = false; + memset(person._npcPath, 0, 100); + } + + person._npcPath[person._npcIndex] = 1; + for (int i = 1; i <= 4; i++) + person._npcPath[person._npcIndex + i] = str[i]; + person._npcPath[person._npcIndex + 5] = DIRECTION_CONVERSION[str[5] - 1] + 1; + + person._npcIndex += 6; + str += 5; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdSetNPCPathPause(const byte *&str) { + int npcNum = *++str; + TattooPeople &people = *(TattooPeople *)_vm->_people; + TattooPerson &person = people[npcNum]; + + if (person._resetNPCPath) { + person._npcIndex = person._npcPause = 0; + person._resetNPCPath = false; + memset(person._npcPath, 0, 100); + } + + person._npcPath[person._npcIndex] = 2; + for (int i = 1; i <= 2; i++) + person._npcPath[person._npcIndex + i] = str[i]; + + person._npcIndex += 3; + str += 2; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdSetNPCPathPauseTakingNotes(const byte *&str) { + int npcNum = *++str; + TattooPeople &people = *(TattooPeople *)_vm->_people; + TattooPerson &person = people[npcNum]; + + if (person._resetNPCPath) { + person._npcIndex = person._npcPause = 0; + person._resetNPCPath = false; + memset(person._npcPath, 0, 100); + } + + person._npcPath[person._npcIndex] = 5; + for (int i = 1; i <= 2; i++) + person._npcPath[person._npcIndex + i] = str[i]; + + person._npcIndex += 3; + str += 2; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdSetNPCPathPauseLookingHolmes(const byte *&str) { + int npcNum = *++str; + TattooPeople &people = *(TattooPeople *)_vm->_people; + TattooPerson &person = people[npcNum]; + + if (person._resetNPCPath) { + person._npcIndex = person._npcPause = 0; + person._resetNPCPath = false; + memset(person._npcPath, 0, 100); + } + + person._npcPath[person._npcIndex] = 6; + for (int i = 1; i <= 2; i++) + person._npcPath[person._npcIndex + i] = str[i]; + + person._npcIndex += 3; + str += 2; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdSetNPCPosition(const byte *&str) { + int npcNum = *++str - 1; + ++str; + TattooPeople &people = *(TattooPeople *)_vm->_people; + TattooPerson &person = people[npcNum]; + int32 posX = (str[0] - 1) * 256 + str[1] - 1; + if (posX > 16384) + posX = -1 * (posX - 16384); + int32 posY = (str[2] - 1) * 256 + str[3] - 1; + + people[npcNum]._position = Point32(posX * FIXED_INT_MULTIPLIER, posY * FIXED_INT_MULTIPLIER); + if (person._seqTo && person._walkLoaded) { + person._walkSequences[person._sequenceNumber]._sequences[person._frameNumber] = person._seqTo; + person._seqTo = 0; + } + + assert(str[4] - 1 < 16); + person._sequenceNumber = DIRECTION_CONVERSION[str[4] - 1]; + person._frameNumber = 0; + + if (person._walkLoaded) + person.checkWalkGraphics(); + + if (person._walkLoaded && person._type == CHARACTER && + person._sequenceNumber >= STOP_UP && person._sequenceNumber <= STOP_UPLEFT) { + bool done = false; + do { + person.checkSprite(); + for (int x = 0; x < person._frameNumber; x++) { + if (person._walkSequences[person._sequenceNumber]._sequences[x] == 0) { + done = true; + break; + } + } + } while(!done); + } + + str += 4; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdSetNPCTalkFile(const byte *&str) { + int npcNum = *++str; + TattooPeople &people = *(TattooPeople *)_vm->_people; + TattooPerson &person = people[npcNum]; + + if (person._resetNPCPath) { + person._npcIndex = person._npcPause = 0; + person._resetNPCPath = false; + memset(person._npcPath, 0, 100); + } + + person._npcPath[person._npcIndex] = 3; + for (int i = 1; i <= 8; i++) + person._npcPath[person._npcIndex + i] = str[i]; + + person._npcIndex += 9; + str += 8; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdSetNPCVerb(const byte *&str) { + int npcNum = *++str; + int verbNum = *++str - 1; + TattooPeople &people = *(TattooPeople *)_vm->_people; + Common::String &verb = people[npcNum]._use[verbNum]._verb; + + for (int x = 0; x < 12; x++) { + if (str[x + 1] != '~') + verb.setChar(str[x + 1], x); + else + verb.setChar(0, x); + } + + verb.setChar(0, 11); + + uint len = verb.size() - 1; + while (verb[len] == ' ' && len) + len--; + verb.setChar(0, len + 1); + if (verb != " ") + verb.clear(); + str += 12; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdSetNPCVerbCAnimation(const byte *&str) { + int npcNum = *++str; + int verbNum = *++str - 1; + TattooPeople &people = *(TattooPeople *)_vm->_people; + UseType &useType = people[npcNum]._use[verbNum]; + + useType._cAnimNum = (str[1] - 1) & 127; + useType._cAnimSpeed = 1 + 128 * (str[1] >= 128); + str++; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdSetNPCVerbScript(const byte *&str) { + int npcNum = *++str; + int verbNum = *++str - 1; + TattooPeople &people = *(TattooPeople *)_vm->_people; + UseType &useType = people[npcNum]._use[verbNum]; + Common::String &name = useType._names[0]; + name.setChar('*', 0); + name.setChar('C', 1); + + for (int x = 0; x < 8; x++) { + if (str[x + 1] != '~') + name.setChar(str[x + 1], x + 2); + else + name.setChar(0, x + 2); + } + + name.setChar(0, 11); + useType._cAnimNum = 99; + useType._cAnimSpeed = 1; + str += 8; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdSetNPCVerbTarget(const byte *&str) { + int npcNum = *++str; + int verbNum = *++str - 1; + TattooPeople &people = *(TattooPeople *)_vm->_people; + Common::String &target = people[npcNum]._use[verbNum]._target; + + for (int x = 0; x < 12; x++) { + if (str[x + 1] != '~') + target.setChar(str[x + 1], x); + else + target.setChar(0, x); + } + + target.setChar(0, 11); + + uint len = target.size() - 1; + while (target[len] == ' ' && len) + len--; + target.setChar(0, len + 1); + str += 12; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdSetNPCWalkGraphics(const byte *&str) { + int npcNum = *++str - 1; + TattooPeople &people = *(TattooPeople *)_vm->_people; + Person &person = people[npcNum]; + + // Build up walk library name for the given NPC + person._walkVGSName = ""; + for (int idx = 0; idx < 8; ++idx) { + if (str[idx + 1] != '~') + person._walkVGSName += str[idx + 1]; + else + break; + } + person._walkVGSName += ".VGS"; + + people._forceWalkReload = true; + str += 8; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdSetSceneEntryFlag(const byte *&str) { error("TODO: script opcode (cmdSetSceneEntryFlag)"); } +OpcodeReturn TattooTalk::cmdSetTalkSequence(const byte *&str) { error("TODO: script opcode (cmdSetTalkSequence)"); } +OpcodeReturn TattooTalk::cmdSetWalkControl(const byte *&str) { error("TODO: script opcode (cmdSetWalkControl)"); } + +// Dummy opcode +OpcodeReturn TattooTalk::cmdTalkInterruptsDisable(const byte *&str) { error("Dummy opcode cmdTalkInterruptsDisable called"); } + +// Dummy opcode +OpcodeReturn TattooTalk::cmdTalkInterruptsEnable(const byte *&str) { error("Dummy opcode cmdTalkInterruptsEnable called"); } + +OpcodeReturn TattooTalk::cmdTurnSoundsOff(const byte *&str) { error("TODO: script opcode (cmdTurnSoundsOff)"); } + +OpcodeReturn TattooTalk::cmdWalkHolmesAndNPCToCAnimation(const byte *&str) { + int npcNum = *++str; + int cAnimNum = *++str; + TattooPeople &people = *(TattooPeople *)_vm->_people; + TattooPerson &person = people[npcNum]; + Scene &scene = *_vm->_scene; + CAnim &anim = scene._cAnim[cAnimNum]; + + if (person._pathStack.empty()) + person.pushNPCPath(); + person._npcMoved = true; + + warning("TODO: cmdWalkNPCToCAnimation - walkBothToCoords call"); + person.walkToCoords( + Point32(anim._goto[1].x * FIXED_INT_MULTIPLIER, anim._goto[1].y * FIXED_INT_MULTIPLIER), + anim._goto[1]._facing + //Point32(anim._goto[1].x * FIXED_INT_MULTIPLIER, anim._goto[1].y * FIXED_INT_MULTIPLIER), + //anim._goto[1]._facing, + //npcNum + 1 + ); + if (_talkToAbort) + return RET_EXIT; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdWalkNPCToCAnimation(const byte *&str) { + int npcNum = *++str; + int cAnimNum = *++str; + TattooPeople &people = *(TattooPeople *)_vm->_people; + TattooPerson &person = people[npcNum]; + Scene &scene = *_vm->_scene; + CAnim &anim = scene._cAnim[cAnimNum]; + + if (person._pathStack.empty()) + person.pushNPCPath(); + person._npcMoved = true; + + warning("TODO: cmdWalkNPCToCAnimation - call RT walkToCoords variant"); + person.walkToCoords( + Point32(anim._goto[1].x * FIXED_INT_MULTIPLIER, anim._goto[1].y * FIXED_INT_MULTIPLIER), + anim._goto[1]._facing + // npcNum + 1 + ); + if (_talkToAbort) + return RET_EXIT; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdWalkNPCToCoords(const byte *&str) { + int npcNum = *++str; + str++; + TattooPeople &people = *(TattooPeople *)_vm->_people; + TattooPerson &person = people[npcNum]; + + if (person._pathStack.empty()) + person.pushNPCPath(); + person._npcMoved = true; + + int x = (str[0] - 1) * 256 + str[1] - 1; + if (x > 16384) + x = -1 * (x - 16384); + + warning("TODO: cmdWalkNPCToCoords - call RT walkToCoords variant"); + person.walkToCoords( + Point32(x * FIXED_INT_MULTIPLIER, ((str[2] - 1) * 256 + str[3] - 1) * FIXED_INT_MULTIPLIER), + DIRECTION_CONVERSION[str[4] - 1] + // npcNum + 1 + ); + if (_talkToAbort) + return RET_EXIT; + + str += 4; + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdWalkHomesAndNPCToCoords(const byte *&str) { + int npcNum = *++str; + str++; + TattooPeople &people = *(TattooPeople *)_vm->_people; + TattooPerson &person = people[npcNum]; + + if (person._pathStack.empty()) + person.pushNPCPath(); + person._npcMoved = true; + + int x = (str[0] - 1) * 256 + str[1] - 1; + if (x > 16384) + x = -1 * (x - 16384); + //int x1 = (str[5] - 1) * 256 + str[6] - 1; + //if (x1 > 16384) + // x1 = -1 * (x1 - 16384); + + warning("TODO: cmdWalkHomesAndNPCToCoords - walkBothToCoords call"); + person.walkToCoords( + Point32(x * FIXED_INT_MULTIPLIER, ((str[2] - 1) * 256 + str[3] - 1) * FIXED_INT_MULTIPLIER), + DIRECTION_CONVERSION[str[4] - 1] + //Point32(x1 * FIXED_INT_MULTIPLIER, ((str[7] - 1) * 256 + str[8] - 1) * FIXED_INT_MULTIPLIER), + //DIRECTION_CONVERSION[str[9] - 1], + //npcNum + 1 + ); + if (_talkToAbort) + return RET_EXIT; + + str += 9; + return RET_SUCCESS; +} + +} // End of namespace Tattoo + +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/tattoo_talk.h b/engines/sherlock/tattoo/tattoo_talk.h new file mode 100644 index 0000000000..252518c462 --- /dev/null +++ b/engines/sherlock/tattoo/tattoo_talk.h @@ -0,0 +1,100 @@ +/* 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 SHERLOCK_TATTOO_TALK_H +#define SHERLOCK_TATTOO_TALK_H + +#include "common/scummsys.h" +#include "common/array.h" +#include "common/rect.h" +#include "common/serializer.h" +#include "common/stream.h" +#include "common/stack.h" +#include "sherlock/talk.h" +#include "sherlock/tattoo/widget_talk.h" + +namespace Sherlock { + +namespace Tattoo { + +class TattooTalk : public Talk { +private: + WidgetTalk _talkWidget; + + OpcodeReturn cmdSwitchSpeaker(const byte *&str); + OpcodeReturn cmdMouseOnOff(const byte *&str); + OpcodeReturn cmdGotoScene(const byte *&str); + OpcodeReturn cmdWalkHolmesToCoords(const byte *&str); + OpcodeReturn cmdNextSong(const byte *&str); + OpcodeReturn cmdPassword(const byte *&str); + OpcodeReturn cmdPlaySong(const byte *&str); + OpcodeReturn cmdRestorePeopleSequence(const byte *&str); + OpcodeReturn cmdSetNPCDescOnOff(const byte *&str); + OpcodeReturn cmdSetNPCInfoLine(const byte *&str); + OpcodeReturn cmdNPCLabelGoto(const byte *&str); + OpcodeReturn cmdNPCLabelIfFlagGoto(const byte *&str); + OpcodeReturn cmdNPCLabelSet(const byte *&str); + OpcodeReturn cmdSetNPCOff(const byte *&str); + OpcodeReturn cmdSetNPCOn(const byte *&str); + OpcodeReturn cmdSetNPCPathDest(const byte *&str); + OpcodeReturn cmdSetNPCPathPause(const byte *&str); + OpcodeReturn cmdSetNPCPathPauseTakingNotes(const byte *&str); + OpcodeReturn cmdSetNPCPathPauseLookingHolmes(const byte *&str); + OpcodeReturn cmdSetNPCPosition(const byte *&str); + OpcodeReturn cmdSetNPCTalkFile(const byte *&str); + OpcodeReturn cmdSetNPCVerb(const byte *&str); + OpcodeReturn cmdSetNPCVerbCAnimation(const byte *&str); + OpcodeReturn cmdSetNPCVerbScript(const byte *&str); + OpcodeReturn cmdSetNPCVerbTarget(const byte *&str); + OpcodeReturn cmdSetNPCWalkGraphics(const byte *&str); + OpcodeReturn cmdSetSceneEntryFlag(const byte *&str); + OpcodeReturn cmdSetTalkSequence(const byte *&str); + OpcodeReturn cmdSetWalkControl(const byte *&str); + OpcodeReturn cmdTalkInterruptsDisable(const byte *&str); + OpcodeReturn cmdTalkInterruptsEnable(const byte *&str); + OpcodeReturn cmdTurnSoundsOff(const byte *&str); + OpcodeReturn cmdWalkHolmesAndNPCToCAnimation(const byte *&str); + OpcodeReturn cmdWalkNPCToCAnimation(const byte *&str); + OpcodeReturn cmdWalkNPCToCoords(const byte *&str); + OpcodeReturn cmdWalkHomesAndNPCToCoords(const byte *&str); +private: + void drawTalk(const char *str); + + /** + * Open the talk window + */ + void openTalkWindow(); +protected: + /** + * Display the talk interface window + */ + virtual void talkInterface(const byte *&str); +public: + TattooTalk(SherlockEngine *vm); + virtual ~TattooTalk() {} +}; + +} // End of namespace Tattoo + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/tattoo/tattoo_user_interface.cpp b/engines/sherlock/tattoo/tattoo_user_interface.cpp new file mode 100644 index 0000000000..4005dbb89e --- /dev/null +++ b/engines/sherlock/tattoo/tattoo_user_interface.cpp @@ -0,0 +1,952 @@ +/* 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 "sherlock/tattoo/tattoo_user_interface.h" +#include "sherlock/tattoo/tattoo_journal.h" +#include "sherlock/tattoo/tattoo_scene.h" +#include "sherlock/tattoo/tattoo.h" + +namespace Sherlock { + +namespace Tattoo { + +TattooUserInterface::TattooUserInterface(SherlockEngine *vm): UserInterface(vm), + _inventoryWidget(vm), _messageWidget(vm), _textWidget(vm), _tooltipWidget(vm), _verbsWidget(vm) { + Common::fill(&_lookupTable[0], &_lookupTable[PALETTE_COUNT], 0); + Common::fill(&_lookupTable1[0], &_lookupTable1[PALETTE_COUNT], 0); + _menuBuffer = nullptr; + _invMenuBuffer = nullptr; + _invGraphic = nullptr; + _scrollSize = _scrollSpeed = 0; + _drawMenu = false; + _bgShape = nullptr; + _personFound = false; + _lockoutTimer = 0; + _fileMode = SAVEMODE_NONE; + _exitZone = -1; + _scriptZone = -1; + _arrowZone = _oldArrowZone = -1; + _activeObj = -1; + _cAnimFramePause = 0; + _widget = nullptr; + _scrollHighlight = 0; + _mask = _mask1 = nullptr; + _maskCounter = 0; + + _interfaceImages = new ImageFile("intrface.vgs"); +} + +TattooUserInterface::~TattooUserInterface() { + delete _interfaceImages; +} + +void TattooUserInterface::initScrollVars() { + _scrollSize = 0; + _currentScroll.x = _currentScroll.y = 0; + _targetScroll.x = _targetScroll.y = 0; +} + +void TattooUserInterface::lookAtObject() { + People &people = *_vm->_people; + Scene &scene = *_vm->_scene; + Sound &sound = *_vm->_sound; + Talk &talk = *_vm->_talk; + Common::String desc; + int cAnimSpeed = 0; + + if (_personFound) { + desc = people[_bgFound - 1000]._examine; + } else { + // Check if there is a Look animation + if (_bgShape->_lookcAnim != 0) { + cAnimSpeed = _bgShape->_lookcAnim & 0xe0; + cAnimSpeed >>= 5; + ++cAnimSpeed; + + _cAnimFramePause = _bgShape->_lookFrames; + desc = _bgShape->_examine; + + int cNum = (_bgShape->_lookcAnim & 0x1f) - 1; + scene.startCAnim(cNum); + } else if (_bgShape->_lookPosition.y != 0) { + // Need to walk to object before looking at it + people[HOLMES].walkToCoords(Common::Point(_bgShape->_lookPosition.x * FIXED_INT_MULTIPLIER, + _bgShape->_lookPosition.y * FIXED_INT_MULTIPLIER), _bgShape->_lookFacing); + } + + if (!talk._talkToAbort) { + desc = _bgShape->_examine; + + if (_bgShape->_lookFlag) + _vm->setFlags(_bgShape->_lookFlag); + + // Find the Sound File to Play if there is one + if (!desc.hasPrefix("_")) { + for (uint idx = 0; idx < scene._objSoundList.size(); ++idx) { + // Get the object name up to the equals + const char *p = strchr(scene._objSoundList[idx].c_str(), '='); + + // Form the name and remove any trailing spaces + Common::String name(scene._objSoundList[idx].c_str(), p); + while (name.hasSuffix(" ")) + name.deleteLastChar(); + + // See if this Object Sound List entry matches the object's name + if (_bgShape->_name.compareToIgnoreCase(name)) { + // Move forward to get the sound filename + while ((*p == ' ') || (*p == '=')) + ++p; + + // If it's not "NONE", play the Sound File + Common::String soundName(p); + if (soundName.compareToIgnoreCase("NONE")) { + soundName.toLowercase(); + if (!soundName.contains('.')) + soundName += ".wav"; + + sound.playSound(soundName, WAIT_RETURN_IMMEDIATELY); + } + + break; + } + } + } + } + } + + // Only show the desciption if the object has one, and if no talk file interrupted while walking to it + if (!talk._talkToAbort && !desc.empty()) { + if (_cAnimFramePause == 0) + printObjectDesc(desc, true); + else + // The description was already printed by an animation + _cAnimFramePause = 0; + } else if (desc.empty()) { + // There was no description to display, so reset back to STD_MODE + _menuMode = STD_MODE; + } +} + +void TattooUserInterface::printObjectDesc(const Common::String &str, bool firstTime) { + Events &events = *_vm->_events; + TattooScene &scene = *(TattooScene *)_vm->_scene; + Talk &talk = *_vm->_talk; + + if (str.hasPrefix("_")) { + // The passed string specifies a talk file + _lookScriptFlag = true; + events.setCursor(MAGNIFY); + int savedSelector = _selector; + + freeMenu(); + if (!_invLookFlag) + _windowOpen = false; + + talk.talkTo(str.c_str() + 1); + _lookScriptFlag = false; + + if (talk._talkToAbort) { + events.setCursor(ARROW); + return; + } + + // See if we're looking at an inventory item + if (_invLookFlag) { + _selector = _oldSelector = savedSelector; + doInventory(0); + _invLookFlag = false; + + } else { + // Nope + events.setCursor(ARROW); + _key = -1; + _menuMode = scene._labTableScene ? LAB_MODE : STD_MODE; + events._pressed = events._released = events._rightReleased = false; + events._oldButtons = 0; + } + } else { + events._pressed = events._released = events._rightReleased = false;; + + // Show text dialog + _textWidget.load(str); + + if (firstTime) + _selector = _oldSelector = -1; + + _drawMenu = _windowOpen = true; + } +} + +void TattooUserInterface::doJournal() { + TattooJournal &journal = *(TattooJournal *)_vm->_journal; + TattooScene &scene = *(TattooScene *)_vm->_scene; + Screen &screen = *_vm->_screen; + + _menuMode = JOURNAL_MODE; + journal.show(); + + _menuMode = STD_MODE; + _windowOpen = false; + _key = -1; + + setupBGArea(screen._cMap); + screen.clear(); + screen.setPalette(screen._cMap); + + screen._backBuffer1.blitFrom(screen._backBuffer2); + scene.updateBackground(); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); +} + +void TattooUserInterface::reset() { + UserInterface::reset(); + _lookPos = Common::Point(SHERLOCK_SCREEN_WIDTH / 2, SHERLOCK_SCREEN_HEIGHT / 2); +} + +void TattooUserInterface::handleInput() { + TattooEngine &vm = *(TattooEngine *)_vm; + Events &events = *_vm->_events; + TattooScene &scene = *(TattooScene *)_vm->_scene; + Common::Point mousePos = events.mousePos() + _currentScroll; + + events.pollEventsAndWait(); + _keyState.keycode = Common::KEYCODE_INVALID; + + // Check the mouse positioning + if (events.isCursorVisible()) + _bgFound = scene.findBgShape(mousePos); + _personFound = _bgFound >= 1000; + _bgShape = (_bgFound != -1 && _bgFound < 1000) ? &scene._bgShapes[_bgFound] : nullptr; + + if (_lockoutTimer) + --_lockoutTimer; + + // Key handling + if (events.kbHit()) { + _keyState = events.getKey(); + + if (_keyState.keycode == Common::KEYCODE_s && vm._allowFastMode) + vm._fastMode = !vm._fastMode; + + else if (_keyState.keycode == Common::KEYCODE_l && _bgFound != -1) { + // Beging used for testing that Look dialogs work + lookAtObject(); + + } else if (_keyState.keycode == Common::KEYCODE_ESCAPE && vm._runningProlog && !_lockoutTimer) { + vm.setFlags(-76); + vm.setFlags(396); + scene._goToScene = STARTING_GAME_SCENE; + } + } + + if (!events.isCursorVisible()) + _keyState.keycode = Common::KEYCODE_INVALID; + + // If there's an active widget/window, let it do event processing + if (_widget) + _widget->handleEvents(); + + // Handle input depending on what mode we're in + switch (_menuMode) { + case STD_MODE: + doStandardControl(); + break; + case LOOK_MODE: + doLookControl(); + break; + case FILES_MODE: + doFileControl(); + break; + case INV_MODE: + doInventoryControl(); + break; + case VERB_MODE: + doVerbControl(); + break; + case TALK_MODE: + doTalkControl(); + break; + case LAB_MODE: + doLabControl(); + break; + default: + break; + } +} + +void TattooUserInterface::drawInterface(int bufferNum) { + Screen &screen = *_vm->_screen; + TattooEngine &vm = *(TattooEngine *)_vm; + + if (_invMenuBuffer != nullptr) { + Common::Rect r = _invMenuBounds; + r.grow(-3); + r.translate(-_currentScroll.x, 0); + _grayAreas.clear(); + _grayAreas.push_back(r); + + drawGrayAreas(); + screen._backBuffer1.transBlitFrom(*_invMenuBuffer, Common::Point(_invMenuBounds.left, _invMenuBounds.top)); + } + + if (_menuBuffer != nullptr) { + Common::Rect r = _menuBounds; + r.grow(-3); + r.translate(-_currentScroll.x, 0); + _grayAreas.clear(); + _grayAreas.push_back(r); + + drawGrayAreas(); + screen._backBuffer1.transBlitFrom(*_menuBuffer, Common::Point(_invMenuBounds.left, _invMenuBounds.top)); + } + + // Handle drawing the text tooltip if necessary + _tooltipWidget.draw(); + + // See if we need to draw an Inventory Item Graphic floating with the cursor + if (_invGraphic != nullptr) + screen._backBuffer1.transBlitFrom(*_invGraphic, Common::Point(_invGraphicBounds.left, _invGraphicBounds.top)); + + if (vm._creditsActive) + vm.drawCredits(); + + // Bring the widgets to the screen + if (_mask != nullptr) + screen._flushScreen = true; + + if (screen._flushScreen) + screen.blockMove(_currentScroll); + + // If there are UI widgets open, slam the areas they were drawn on to the physical screen + if (_menuBuffer != nullptr) + screen.slamArea(_menuBounds.left - _currentScroll.x, _menuBounds.top, _menuBounds.width(), _menuBounds.height()); + + if (_invMenuBuffer != nullptr) + screen.slamArea(_invMenuBounds.left - _currentScroll.x, _invMenuBounds.top, _invMenuBounds.width(), _invMenuBounds.height()); + + // If therea re widgets being cleared, then restore that area of the screen + if (_oldMenuBounds.right) { + screen.slamArea(_oldMenuBounds.left - _currentScroll.x, _oldMenuBounds.top, _oldMenuBounds.width(), _oldMenuBounds.height()); + _oldMenuBounds.left = _oldMenuBounds.top = _oldMenuBounds.right = _oldMenuBounds.bottom = 0; + } + + if (_oldInvMenuBounds.left) { + screen.slamArea(_oldInvMenuBounds.left - _currentScroll.x, _oldInvMenuBounds.top, _oldInvMenuBounds.width(), _oldInvMenuBounds.height()); + _oldInvMenuBounds.left = _oldInvMenuBounds.top = _oldInvMenuBounds.right = _oldInvMenuBounds.bottom = 0; + } + + // See if we need to flush areas assocaited with the inventory graphic + if (_oldInvGraphicBounds.right) { + screen.slamArea(_oldInvGraphicBounds.left - _currentScroll.x, _oldInvGraphicBounds.top, + _oldInvGraphicBounds.width(), _oldInvGraphicBounds.height()); + + // If there's no graphic actually being displayed, then reset bounds so we don't keep restoring the area + if (_invGraphic == nullptr) { + _invGraphicBounds.left = _invGraphicBounds.top = _invGraphicBounds.right = _invGraphicBounds.bottom = 0; + _oldInvGraphicBounds.left = _oldInvGraphicBounds.top = _oldInvGraphicBounds.right = _oldInvGraphicBounds.bottom = 0; + } + } + if (_invGraphic != nullptr) + screen.slamArea(_invGraphicBounds.left - _currentScroll.x, _invGraphicBounds.top, _invGraphicBounds.width(), _invGraphicBounds.height()); +} + +void TattooUserInterface::doBgAnimRestoreUI() { + TattooScene &scene = *((TattooScene *)_vm->_scene); + Screen &screen = *_vm->_screen; + + // If _oldMenuBounds was set, then either a new menu has been opened or the current menu has been closed. + // Either way, we need to restore the area where the menu was displayed + if (_oldMenuBounds.width() > 0) + screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(_oldMenuBounds.left, _oldMenuBounds.top), + _oldMenuBounds); + + if (_oldInvMenuBounds.width() > 0) + screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(_oldInvMenuBounds.left, _oldInvMenuBounds.top), + _oldInvMenuBounds); + + if (_menuBuffer != nullptr) + screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(_menuBounds.left, _menuBounds.top), _menuBounds); + if (_invMenuBuffer != nullptr) + screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(_invMenuBounds.left, _invMenuBounds.top), _invMenuBounds); + + // If there is a Text Tag being display, restore the area underneath it + _tooltipWidget.erase(); + + // If there is an Inventory being shown, restore the graphics underneath it + if (_oldInvGraphicBounds.width() > 0) + screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(_oldInvGraphicBounds.left, _oldInvGraphicBounds.top), + _oldInvGraphicBounds); + + // If a canimation is active, restore the graphics underneath it + if (scene._activeCAnim._imageFrame != nullptr) + screen.restoreBackground(scene._activeCAnim._oldBounds); + + // If a canimation just ended, remove it's graphics from the backbuffer + if (scene._activeCAnim._removeBounds.width() > 0) + screen.restoreBackground(scene._activeCAnim._removeBounds); +} + +void TattooUserInterface::doScroll() { + Screen &screen = *_vm->_screen; + int oldScroll = _currentScroll.x; + + // If we're already at the target scroll position, nothing needs to be done + if (_targetScroll.x == _currentScroll.x) + return; + + screen._flushScreen = true; + if (_targetScroll.x > _currentScroll.x) { + _currentScroll.x += _scrollSpeed; + if (_currentScroll.x > _targetScroll.x) + _currentScroll.x = _targetScroll.x; + } else if (_targetScroll.x < _currentScroll.x) { + _currentScroll.x -= _scrollSpeed; + if (_currentScroll.x < _targetScroll.x) + _currentScroll.x = _targetScroll.x; + } + + if (_menuBuffer != nullptr) + _menuBounds.translate(_currentScroll.x - oldScroll, 0); + if (_invMenuBuffer != nullptr) + _invMenuBounds.translate(_currentScroll.x - oldScroll, 0); +} + +void TattooUserInterface::drawGrayAreas() { + // TODO +} + +void TattooUserInterface::doStandardControl() { + TattooEngine &vm = *(TattooEngine *)_vm; + Events &events = *_vm->_events; + People &people = *_vm->_people; + TattooScene &scene = *(TattooScene *)_vm->_scene; + Talk &talk = *_vm->_talk; + Common::Point mousePos = events.mousePos(); + bool noDesc = false; + + // Don't do any input processing whilst the prolog is running + if (vm._runningProlog) + return; + + // Display the names of any Objects the cursor is pointing at + displayObjectNames(); + + switch (_keyState.keycode) { + case Common::KEYCODE_F5: + // Save game + freeMenu(); + _fileMode = SAVEMODE_SAVE; + _menuBounds = Common::Rect(0, 0, 0, 0); + initFileMenu(); + return; + + case Common::KEYCODE_F7: + // Load game + freeMenu(); + _fileMode = SAVEMODE_LOAD; + _menuBounds = Common::Rect(0, 0, 0, 0); + initFileMenu(); + return; + + case Common::KEYCODE_F1: + // Display journal + if (vm.readFlags(76)) { + freeMenu(); + doJournal(); + + // See if we're in a Lab Table Room + _menuMode = (scene._labTableScene) ? LAB_MODE : STD_MODE; + return; + } + break; + + case Common::KEYCODE_TAB: + case Common::KEYCODE_F3: + // Display inventory + freeMenu(); + doInventory(2); + return; + + case Common::KEYCODE_F4: + // Display options + freeMenu(); + doControls(); + return; + + case Common::KEYCODE_F10: + // Quit menu + freeMenu(); + _menuBounds = Common::Rect(-1, -1, -1, -1); + doQuitMenu(); + return; + + default: + break; + } + + // See if a mouse button was released + if (events._released || events._rightReleased) { + // See if the mouse was released in an exit (Arrow) zone. Unless it's also pointing at an object + // within the zone, in which case the object gets precedence + _exitZone = -1; + if (_arrowZone != -1 && events._released) + _exitZone = _arrowZone; + + // Turn any Text display off + if (_arrowZone == -1 || events._rightReleased) + freeMenu(); + + if (_personFound) { + if (people[_bgFound - 1000]._description.empty() || people[_bgFound - 1000]._description.hasPrefix(" ")) + noDesc = true; + } else if (_bgFound != -1) { + if (_bgShape->_description.empty() || _bgShape->_description.hasPrefix(" ")) + noDesc = true; + } else { + noDesc = true; + } + + if (events._rightReleased) { + // Show the verbs menu for the highlighted object + _verbsWidget.activateVerbMenu(!noDesc); + } else if (_personFound || (_bgFound != -1 && _bgFound < 1000 && _bgShape->_aType == PERSON)) { + // The object found is a person (the default for people is TALK) + talk.talk(_bgFound); + _activeObj = -1; + } else if (!noDesc) { + // Either call the code to Look at it's Examine Field or call the Exit animation + // if the object is an exit, specified by the first four characters of the name being "EXIT" + Common::String name = _personFound ? people[_bgFound - 1000]._name : _bgShape->_name; + if (name.hasPrefix("EXIT")) { + lookAtObject(); + } else { + // Run the Exit animation and set which scene to go to next + for (int idx = 0; idx < 6; ++idx) { + if (!_bgShape->_use[idx]._verb.compareToIgnoreCase("Open")) { + checkAction(_bgShape->_use[idx], _bgFound); + _activeObj = -1; + } + } + } + } else { + // See if there are any Script Zones where they clicked + if (scene.checkForZones(mousePos, _scriptZone) != 0) { + // Mouse click in a script zone + events._pressed = events._released = false; + } else if (scene.checkForZones(mousePos, NOWALK_ZONE) != 0) { + events._pressed = events._released = false; + } else { + // Walk to where the mouse was clicked + people[HOLMES]._walkDest = Common::Point(mousePos.x + _currentScroll.x, mousePos.y); + people[HOLMES].goAllTheWay(); + } + } + } +} + +void TattooUserInterface::doLookControl() { + Events &events = *_vm->_events; + TattooScene &scene = *(TattooScene *)_vm->_scene; + Sound &sound = *_vm->_sound; + + // See if a mouse button was released or a key pressed, and we want to initiate an action + // TODO: Not sure about _soundOn.. should be check for speaking voice for text being complete + if (events._released || events._rightReleased || _keyState.keycode || (sound._voices && !sound._soundOn)) { + // See if we were looking at an inventory object + if (!_invLookFlag) { + // See if there is any more text to display + if (!_textWidget._remainingText.empty()) { + printObjectDesc(_textWidget._remainingText, false); + } else { + // Otherwise restore the background and go back into STD_MODE + freeMenu(); + _key = -1; + _menuMode = scene._labTableScene ? LAB_MODE : STD_MODE; + + events.setCursor(ARROW); + events._pressed = events._released = events._rightReleased = false; + events._oldButtons = 0; + } + } else { + // We were looking at a Inventory object + // Erase the text window, and then redraw the inventory window + _textWidget.banishWindow(); + doInventory(0); + + _invLookFlag = false; + _key = -1; + + events.setCursor(ARROW); + events._pressed = events._released = events._rightReleased = false; + events._oldButtons = 0; + } + } +} + +void TattooUserInterface::doFileControl() { + warning("TODO: ui control (file)"); +} + +void TattooUserInterface::doInventoryControl() { + _inventoryWidget.handleEvents(); +} + +void TattooUserInterface::doVerbControl() { + _verbsWidget.execute(); +} + +void TattooUserInterface::doTalkControl() { + warning("TODO: ui control (talk)"); +} + +void TattooUserInterface::doLabControl() { + warning("TODO: ui control (lab)"); +} + +void TattooUserInterface::displayObjectNames() { + Events &events = *_vm->_events; + Scene &scene = *_vm->_scene; + Common::Point mousePos = events.mousePos() + _currentScroll; + _arrowZone = -1; + + if (_bgFound == -1 || scene._currentScene == 90) { + for (uint idx = 0; idx < scene._exits.size() && _arrowZone == -1; ++idx) { + Exit &exit = scene._exits[idx]; + if (exit.contains(mousePos)) + _arrowZone = idx; + } + } + + _tooltipWidget.handleEvents(); + _oldArrowZone = _arrowZone; +} + +void TattooUserInterface::initFileMenu() { + // TODO +} + +void TattooUserInterface::doInventory(int mode) { + People &people = *_vm->_people; + people[HOLMES].gotoStand(); + + _inventoryWidget.load(mode); + + _menuMode = INV_MODE; +} + +void TattooUserInterface::doControls() { + // TODO +} + +void TattooUserInterface::pickUpObject(int objNum) { + // TOOD +} + +void TattooUserInterface::doQuitMenu() { + // TODO +} + +void TattooUserInterface::freeMenu() { + if (_widget != nullptr) { + _widget->banishWindow(); + _widget = nullptr; + } +} + +void TattooUserInterface::putMessage(const char *formatStr, ...) { + Events &events = *_vm->_events; + Screen &screen = *_vm->_screen; + Common::Point mousePos = events.mousePos(); + + // Create the string to display + va_list args; + va_start(args, formatStr); + Common::String str = Common::String::vformat(formatStr, args); + va_end(args); + + // Open the message widget + _messageWidget.load(str, 25); + _messageWidget.summonWindow(); +} + +void TattooUserInterface::setupBGArea(const byte cMap[PALETTE_SIZE]) { + Scene &scene = *_vm->_scene; + + // This requires that there is a 16 grayscale palette sequence in the palette that goes from lighter + // to darker as the palette numbers go up. The last palette entry in that run is specified by _bgColor + byte *p = &_lookupTable[0]; + for (int idx = 0; idx < PALETTE_COUNT; ++idx) + *p++ = BG_GREYSCALE_RANGE_END - (cMap[idx * 3] * 30 + cMap[idx * 3 + 1] * 59 + cMap[idx * 3 + 2] * 11) / 480; + + // If we're going to a scene with a haze special effect, initialize the translate table to lighten the colors + if (_mask != nullptr) { + p = &_lookupTable1[0]; + + for (int idx = 0; idx < PALETTE_COUNT; ++idx) { + int r, g, b; + switch (scene._currentScene) { + case 8: + r = cMap[idx * 3] * 4 / 5; + g = cMap[idx * 3 + 1] * 3 / 4; + b = cMap[idx * 3 + 2] * 3 / 4; + break; + + case 18: + case 68: + r = cMap[idx * 3] * 4 / 3; + g = cMap[idx * 3 + 1] * 4 / 3; + b = cMap[idx * 3 + 2] * 4 / 3; + break; + + case 7: + case 53: + r = cMap[idx * 3] * 4 / 3; + g = cMap[idx * 3 + 1] * 4 / 3; + b = cMap[idx * 3 + 2] * 4 / 3; + break; + + default: + r = g = b = 0; + break; + } + + byte c = 0; + int cd = (r - cMap[0]) * (r - cMap[0]) + (g - cMap[1]) * (g - cMap[1]) + (b - cMap[2]) * (b - cMap[2]); + + for (int pal = 0; pal < PALETTE_COUNT; ++pal) { + int d = (r - cMap[pal * 3]) * (r - cMap[pal * 3]) + (g - cMap[pal * 3 + 1]) * (g - cMap[pal * 3 + 1]) + + (b - cMap[pal * 3 + 2])*(b - cMap[pal * 3 + 2]); + + if (d < cd) { + c = pal; + cd = d; + if (!d) + break; + } + } + *p++ = c; + } + } +} + + +void TattooUserInterface::doBgAnimEraseBackground() { + TattooEngine &vm = *((TattooEngine *)_vm); + People &people = *_vm->_people; + Scene &scene = *_vm->_scene; + Screen &screen = *_vm->_screen; + + static const int16 OFFSETS[16] = { -1, -2, -3, -3, -2, -1, -1, 0, 1, 2, 3, 3, 2, 1, 0, 0 }; + + if (_mask != nullptr) { + if (screen._backBuffer1.w() > screen.w()) + screen.blitFrom(screen._backBuffer1, Common::Point(0, 0), Common::Rect(_currentScroll.x, 0, + _currentScroll.x + screen.w(), screen.h())); + else + screen.blitFrom(screen._backBuffer1); + + switch (scene._currentScene) { + case 7: + if (++_maskCounter == 2) { + _maskCounter = 0; + if (--_maskOffset.x < 0) + _maskOffset.x = SHERLOCK_SCREEN_WIDTH - 1; + } + break; + + case 8: + _maskOffset.x += 2; + if (_maskOffset.x >= SHERLOCK_SCREEN_WIDTH) + _maskOffset.x = 0; + break; + + case 18: + case 68: + ++_maskCounter; + if (_maskCounter / 4 >= 16) + _maskCounter = 0; + + _maskOffset.x = OFFSETS[_maskCounter / 4]; + break; + + case 53: + if (++_maskCounter == 2) { + _maskCounter = 0; + if (++_maskOffset.x == screen._backBuffer1.w()) + _maskOffset.x = 0; + } + break; + + default: + break; + } + } else { + // Standard scene without mask, so call user interface to erase any UI elements as necessary + doBgAnimRestoreUI(); + + // Restore background for any areas covered by characters and shapes + for (int idx = 0; idx < MAX_CHARACTERS; ++idx) + screen.restoreBackground(Common::Rect(people[idx]._oldPosition.x, people[idx]._oldPosition.y, + people[idx]._oldPosition.x + people[idx]._oldSize.x, people[idx]._oldPosition.y + people[idx]._oldSize.y)); + + for (uint idx = 0; idx < scene._bgShapes.size(); ++idx) { + Object &obj = scene._bgShapes[idx]; + + if ((obj._type == ACTIVE_BG_SHAPE && (obj._maxFrames > 1 || obj._delta.x != 0 || obj._delta.y != 0)) || + obj._type == HIDE_SHAPE || obj._type == REMOVE) + screen._backBuffer1.blitFrom(screen._backBuffer2, obj._oldPosition, + Common::Rect(obj._oldPosition.x, obj._oldPosition.y, obj._oldPosition.x + obj._oldSize.x, + obj._oldPosition.y + obj._oldSize.y)); + } + + // If credits are active, erase the area they cover + if (vm._creditsActive) + vm.eraseCredits(); + } + + for (uint idx = 0; idx < scene._bgShapes.size(); ++idx) { + Object &obj = scene._bgShapes[idx]; + + if (obj._type == NO_SHAPE && (obj._flags & 1) == 0) { + screen._backBuffer1.blitFrom(screen._backBuffer2, obj._position, obj.getNoShapeBounds()); + + obj._oldPosition = obj._position; + obj._oldSize = obj._noShapeSize; + } + } + + // Adjust the Target Scroll if needed + if ((people[people._walkControl]._position.x / FIXED_INT_MULTIPLIER - _currentScroll.x) < + (SHERLOCK_SCREEN_WIDTH / 8) && people[people._walkControl]._delta.x < 0) { + + _targetScroll.x = (short)(people[people._walkControl]._position.x / FIXED_INT_MULTIPLIER - + SHERLOCK_SCREEN_WIDTH / 8 - 250); + if (_targetScroll.x < 0) + _targetScroll.x = 0; + } + + if ((people[people._walkControl]._position.x / FIXED_INT_MULTIPLIER - _currentScroll.x) > (SHERLOCK_SCREEN_WIDTH / 4 * 3) + && people[people._walkControl]._delta.x > 0) + _targetScroll.x = (short)(people[people._walkControl]._position.x / FIXED_INT_MULTIPLIER - + SHERLOCK_SCREEN_WIDTH / 4 * 3 + 250); + + if (_targetScroll.x > _scrollSize) + _targetScroll.x = _scrollSize; + + doScroll(); +} + +void TattooUserInterface::drawMaskArea(bool mode) { + Scene &scene = *_vm->_scene; + Screen &screen = *_vm->_screen; + int xp = mode ? _maskOffset.x : 0; + + if (_mask != nullptr) { + switch (scene._currentScene) { + case 7: + screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x - SHERLOCK_SCREEN_WIDTH, 110), _currentScroll.x); + screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x, 110), _currentScroll.x); + screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x + SHERLOCK_SCREEN_WIDTH, 110), _currentScroll.x); + break; + + case 8: + screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x - SHERLOCK_SCREEN_WIDTH, 180), _currentScroll.x); + screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x, 180), _currentScroll.x); + screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x + SHERLOCK_SCREEN_WIDTH, 180), _currentScroll.x); + if (!_vm->readFlags(880)) + screen._backBuffer1.maskArea((*_mask1)[0], Common::Point(940, 300), _currentScroll.x); + break; + + case 18: + screen._backBuffer1.maskArea((*_mask)[0], Common::Point(xp, 203), _currentScroll.x); + if (!_vm->readFlags(189)) + screen._backBuffer1.maskArea((*_mask1)[0], Common::Point(124 + xp, 239), _currentScroll.x); + break; + + case 53: + screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x, 110), _currentScroll.x); + if (mode) + screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x - SHERLOCK_SCREEN_WIDTH, 110), _currentScroll.x); + break; + + case 68: + screen._backBuffer1.maskArea((*_mask)[0], Common::Point(xp, 203), _currentScroll.x); + screen._backBuffer1.maskArea((*_mask1)[0], Common::Point(124 + xp, 239), _currentScroll.x); + break; + } + } +} + +void TattooUserInterface::makeBGArea(const Common::Rect &r) { + Screen &screen = *_vm->_screen; + + for (int yp = r.top; yp < r.bottom; ++yp) { + byte *ptr = screen._backBuffer1.getBasePtr(r.left, yp); + + for (int xp = r.left; xp < r.right; ++xp, ++ptr) + *ptr = _lookupTable[*ptr]; + } +} + +void TattooUserInterface::drawDialogRect(Surface &s, const Common::Rect &r, bool raised) { + switch (raised) { + case true: + // Draw Left + s.vLine(r.left, r.top, r.bottom - 1, INFO_TOP); + s.vLine(r.left + 1, r.top, r.bottom - 2, INFO_TOP); + // Draw Top + s.hLine(r.left + 2, r.top, r.right - 1, INFO_TOP); + s.hLine(r.left + 2, r.top + 1, r.right - 2, INFO_TOP); + // Draw Right + s.vLine(r.right - 1, r.top + 1, r.bottom - 1, INFO_BOTTOM); + s.vLine(r.right - 2, r.top + 2, r.bottom - 1, INFO_BOTTOM); + // Draw Bottom + s.hLine(r.left + 1, r.bottom - 1, r.right - 3, INFO_BOTTOM); + s.hLine(r.left + 2, r.bottom - 2, r.right - 3, INFO_BOTTOM); + break; + + case false: + // Draw Left + s.vLine(r.left, r.top, r.bottom - 1, INFO_BOTTOM); + s.vLine(r.left + 1, r.top, r.bottom - 2, INFO_BOTTOM); + // Draw Top + s.hLine(r.left + 2, r.top, r.right - 1, INFO_BOTTOM); + s.hLine(r.left + 2, r.top + 1, r.right - 2, INFO_BOTTOM); + // Draw Right + s.vLine(r.right - 1, r.top + 1, r.bottom - 1, INFO_TOP); + s.vLine(r.right - 2, r.top + 2, r.bottom - 1, INFO_TOP); + // Draw Bottom + s.hLine(r.left + 1, r.bottom - 1, r.right - 3, INFO_TOP); + s.hLine(r.left + 2, r.bottom - 2, r.right - 3, INFO_TOP); + break; + } +} + +void TattooUserInterface::banishWindow() { + if (_widget != nullptr) + _widget->banishWindow(); + _widget = nullptr; +} + +} // End of namespace Tattoo + +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/tattoo_user_interface.h b/engines/sherlock/tattoo/tattoo_user_interface.h new file mode 100644 index 0000000000..f21e699538 --- /dev/null +++ b/engines/sherlock/tattoo/tattoo_user_interface.h @@ -0,0 +1,249 @@ +/* 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 SHERLOCK_TATTOO_UI_H +#define SHERLOCK_TATTOO_UI_H + +#include "common/scummsys.h" +#include "sherlock/saveload.h" +#include "sherlock/screen.h" +#include "sherlock/user_interface.h" +#include "sherlock/tattoo/widget_inventory.h" +#include "sherlock/tattoo/widget_text.h" +#include "sherlock/tattoo/widget_tooltip.h" +#include "sherlock/tattoo/widget_verbs.h" + +namespace Sherlock { + +namespace Tattoo { + +#define BUTTON_SIZE 15 // Button width/height + +class WidgetBase; + +class TattooUserInterface : public UserInterface { + friend class WidgetBase; +private: + Common::Rect _menuBounds; + Common::Rect _oldMenuBounds; + Common::Rect _invMenuBounds; + Common::Rect _oldInvMenuBounds; + Common::Rect _invGraphicBounds; + Common::Rect _oldInvGraphicBounds; + Surface *_menuBuffer; + Surface *_invMenuBuffer; + Surface *_invGraphic; + Common::Array<Common::Rect> _grayAreas; + int _lockoutTimer; + SaveMode _fileMode; + int _exitZone; + int _scriptZone; + int _cAnimFramePause; + WidgetInventory _inventoryWidget; + WidgetSceneTooltip _tooltipWidget; + WidgetVerbs _verbsWidget; + WidgetMessage _messageWidget; + WidgetBase *_widget; + byte _lookupTable[PALETTE_COUNT]; + byte _lookupTable1[PALETTE_COUNT]; +private: + /** + * Draws designated areas of the screen that are meant to be grayed out using grayscale colors + */ + void drawGrayAreas(); + + /** + * Handle any input when we're in standard mode (with no windows open) + */ + void doStandardControl(); + + /** + * Handle input when in look mode + */ + void doLookControl(); + + /** + * Handle input when the File window is open + */ + void doFileControl(); + + /** + * Handle input if an inventory command (INVENT, LOOK, or USE) has an open window and is active + */ + void doInventoryControl(); + + /** + * Handle input while the verb menu is open + */ + void doVerbControl(); + + /** + * Handles input when in talk mode. It highlights the buttons and response statements, + * and handles any actions for clicking on the buttons or statements. + */ + void doTalkControl(); + + /** + * Handles input when the player is in the Lab Table scene + */ + void doLabControl(); + + /** + * If the mouse cursor is point at the cursor, then display the name of the object on the screen. + * If there is no object being pointed it, clear any previously displayed name + */ + void displayObjectNames(); + + /** + * Set up to display the Files menu + */ + void initFileMenu(); + + /** + * Handle displaying the quit menu + */ + void doQuitMenu(); + + /** + * Free any active menu + */ + void freeMenu(); +public: + Common::Point _currentScroll, _targetScroll; + int _scrollSize, _scrollSpeed; + bool _drawMenu; + int _arrowZone, _oldArrowZone; + Object *_bgShape; + bool _personFound; + int _activeObj; + Common::KeyState _keyState; + Common::Point _lookPos; + int _scrollHighlight; + ImageFile *_mask, *_mask1; + Common::Point _maskOffset; + int _maskCounter; + ImageFile *_interfaceImages; + WidgetText _textWidget; +public: + TattooUserInterface(SherlockEngine *vm); + virtual ~TattooUserInterface(); + + /** + * Handles restoring any areas of the back buffer that were/are covered by UI elements + */ + void doBgAnimRestoreUI(); + + /** + * Checks to see if the screen needs to be scrolled. If so, scrolls it towards the target position + */ + void doScroll(); + + /** + * Initializes scroll variables + */ + void initScrollVars(); + + /** + * Display the long description for an object in a window + */ + void lookAtObject(); + + /** + * Display the passed long description for an object. If the flag firstTime is set, + * the window will be opened to accomodate the text. Otherwise, the remaining text + * will be printed in an already open window + */ + void printObjectDesc(const Common::String &str, bool firstTime); + + /** + * Handles displaying the journal + */ + void doJournal(); + + /** + * Put the game in inventory mode by opening the inventory dialog + */ + void doInventory(int mode); + + /** + * Handle the display of the options/setup menu + */ + void doControls(); + + /** + * Pick up the selected object + */ + void pickUpObject(int objNum); + + /** + * This will display a text message in a dialog at the bottom of the screen + */ + void putMessage(const char *formatStr, ...) GCC_PRINTF(2, 3); + + /** + * Makes a greyscale translation table for each palette entry in the table + */ + void setupBGArea(const byte cMap[PALETTE_SIZE]); + + /** + * Erase any background as needed before drawing frame + */ + void doBgAnimEraseBackground(); + + void drawMaskArea(bool mode); + + /** + * Translate a given area of the back buffer to greyscale shading + */ + void makeBGArea(const Common::Rect &r); + + /** + * Draws all the dialog rectangles for any items that need them + */ + void drawDialogRect(Surface &s, const Common::Rect &r, bool raised); + + /** + * Banish any active window + */ + void banishWindow(); +public: + /** + * Resets the user interface + */ + virtual void reset(); + + /** + * Main input handler for the user interface + */ + virtual void handleInput(); + + /** + * Draw the user interface onto the screen's back buffers + */ + virtual void drawInterface(int bufferNum = 3); +}; + +} // End of namespace Tattoo + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/tattoo/widget_base.cpp b/engines/sherlock/tattoo/widget_base.cpp new file mode 100644 index 0000000000..cd4f8e08a2 --- /dev/null +++ b/engines/sherlock/tattoo/widget_base.cpp @@ -0,0 +1,188 @@ +/* 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 "sherlock/tattoo/widget_base.h" +#include "sherlock/tattoo/tattoo.h" +#include "sherlock/tattoo/tattoo_talk.h" +#include "sherlock/tattoo/tattoo_user_interface.h" + +namespace Sherlock { + +namespace Tattoo { + +WidgetBase::WidgetBase(SherlockEngine *vm) : _vm(vm) { +} + +void WidgetBase::summonWindow() { + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + ui._widget = this; + _outsideMenu = false; + + draw(); +} + +void WidgetBase::banishWindow() { + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + + erase(); + _surface.free(); + ui._widget = nullptr; +} + +void WidgetBase::erase() { + Screen &screen = *_vm->_screen; + const Common::Point ¤tScroll = getCurrentScroll(); + + if (_oldBounds.width() > 0) { + // Get the bounds to copy from the back buffers, adjusted for scroll position + Common::Rect oldBounds = _oldBounds; + oldBounds.translate(currentScroll.x, currentScroll.y); + + // Restore the affected area from the secondary back buffer into the first one, and then copy to screen + screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(oldBounds.left, oldBounds.top), oldBounds); + screen.blitFrom(screen._backBuffer1, Common::Point(_oldBounds.left, _oldBounds.top), oldBounds); + + // Reset the old bounds so + _oldBounds = Common::Rect(0, 0, 0, 0); + } +} + +void WidgetBase::draw() { + Screen &screen = *_vm->_screen; + const Common::Point ¤tScroll = getCurrentScroll(); + + // If there was a previously drawn frame in a different position that hasn't yet been erased, then erase it + if (_oldBounds.width() > 0 && _oldBounds != _bounds) + erase(); + + if (_bounds.width() > 0 && !_surface.empty()) { + // Get the area to draw, adjusted for scroll position + Common::Rect bounds = _bounds; + bounds.translate(currentScroll.x, currentScroll.y); + + // Copy any area to be drawn on from the secondary back buffer, and then draw surface on top + screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(bounds.left, bounds.top), bounds); + screen._backBuffer1.transBlitFrom(_surface, Common::Point(bounds.left, bounds.top)); + screen.blitFrom(screen._backBuffer1, Common::Point(_bounds.left, _bounds.top), bounds); + + // Store a copy of the drawn area for later erasing + _oldBounds = _bounds; + } +} + +Common::String WidgetBase::splitLines(const Common::String &str, Common::StringArray &lines, int maxWidth, uint maxLines) { + Talk &talk = *_vm->_talk; + const char *strP = str.c_str(); + + // Loop counting up lines + lines.clear(); + do { + int width = 0; + const char *spaceP = nullptr; + const char *lineStartP = strP; + + // Find how many characters will fit on the next line + while (width < maxWidth && *strP && ((byte)*strP < talk._opcodes[OP_SWITCH_SPEAKER] || + (byte)*strP == talk._opcodes[OP_NULL])) { + width += _surface.charWidth(*strP); + + // Keep track of the last space + if (*strP == ' ') + spaceP = strP; + ++strP; + } + + // If the line was too wide to fit on a single line, go back to the last space + // if there was one, or otherwise simply break the line at this point + if (width >= maxWidth && spaceP != nullptr) + strP = spaceP; + + // Add the line to the output array + lines.push_back(Common::String(lineStartP, strP)); + + // Move the string ahead to the next line + if (*strP == ' ' || *strP == 13) + ++strP; + } while (*strP && ((int)lines.size() < maxLines) && ((byte)*strP < talk._opcodes[OP_SWITCH_SPEAKER] + || (byte)*strP == talk._opcodes[OP_NULL])); + + // Return any remaining text left over + return *strP ? Common::String(strP) : Common::String(); +} + +void WidgetBase::checkMenuPosition() { + if (_bounds.left < 0) + _bounds.moveTo(0, _bounds.top); + if (_bounds.top < 0) + _bounds.moveTo(_bounds.left, 0); + if (_bounds.right > SHERLOCK_SCREEN_WIDTH) + _bounds.moveTo(SHERLOCK_SCREEN_WIDTH - _bounds.width(), _bounds.top); + if (_bounds.bottom > SHERLOCK_SCREEN_HEIGHT) + _bounds.moveTo(_bounds.left, SHERLOCK_SCREEN_HEIGHT - _bounds.height()); +} + +void WidgetBase::makeInfoArea(Surface &s) { + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + ImageFile &images = *ui._interfaceImages; + + // Draw the four corners of the Info Box + s.transBlitFrom(images[0], Common::Point(0, 0)); + s.transBlitFrom(images[1], Common::Point(s.w() - images[1]._width, 0)); + s.transBlitFrom(images[2], Common::Point(0, s.h() - images[2]._height)); + s.transBlitFrom(images[3], Common::Point(s.w() - images[3]._width, s.h())); + + // Draw the top of the Info Box + s.hLine(images[0]._width, 0, s.w() - images[1]._width, INFO_TOP); + s.hLine(images[0]._width, 1, s.w() - images[1]._width, INFO_MIDDLE); + s.hLine(images[0]._width, 2, s.w() - images[1]._width, INFO_BOTTOM); + + // Draw the bottom of the Info Box + s.hLine(images[0]._width, s.h()- 3, s.w() - images[1]._width, INFO_TOP); + s.hLine(images[0]._width, s.h()- 2, s.w() - images[1]._width, INFO_MIDDLE); + s.hLine(images[0]._width, s.h()- 1, s.w() - images[1]._width, INFO_BOTTOM); + + // Draw the left Side of the Info Box + s.vLine(0, images[0]._height, s.h()- images[2]._height, INFO_TOP); + s.vLine(1, images[0]._height, s.h()- images[2]._height, INFO_MIDDLE); + s.vLine(2, images[0]._height, s.h()- images[2]._height, INFO_BOTTOM); + + // Draw the right Side of the Info Box + s.vLine(s.w() - 3, images[0]._height, s.h()- images[2]._height, INFO_TOP); + s.vLine(s.w() - 2, images[0]._height, s.h()- images[2]._height, INFO_MIDDLE); + s.vLine(s.w() - 1, images[0]._height, s.h()- images[2]._height, INFO_BOTTOM); +} + +void WidgetBase::makeInfoArea() { + makeInfoArea(_surface); +} + +const Common::Point &WidgetBase::getCurrentScroll() const { + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + return ui._currentScroll; +} + +void WidgetBase::checkTabbingKeys(int numOptions) { +} + +} // End of namespace Tattoo + +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/widget_base.h b/engines/sherlock/tattoo/widget_base.h new file mode 100644 index 0000000000..3455074964 --- /dev/null +++ b/engines/sherlock/tattoo/widget_base.h @@ -0,0 +1,110 @@ +/* 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 SHERLOCK_TATTOO_WIDGET_BASE_H +#define SHERLOCK_TATTOO_WIDGET_BASE_H + +#include "common/scummsys.h" +#include "common/rect.h" +#include "common/str-array.h" +#include "sherlock/surface.h" + +namespace Sherlock { + +class SherlockEngine; +class ImageFile; + +namespace Tattoo { + +class WidgetBase { +private: + Common::Rect _oldBounds; +protected: + SherlockEngine *_vm; + Common::Rect _bounds; + Surface _surface; + bool _outsideMenu; + + /** + * Used by descendent classes to split up long text for display across multiple lines + */ + Common::String splitLines(const Common::String &str, Common::StringArray &lines, int maxWidth, uint maxLines); + + /** + * Ensure that menu is drawn entirely on-screen + */ + void checkMenuPosition(); + + /** + * Draw a window frame around the dges of the passed surface + */ + void makeInfoArea(Surface &s); + + /** + * Draw a window frame around the widget's surface + */ + void makeInfoArea(); + + /** + * Returns the current scroll position + */ + virtual const Common::Point &getCurrentScroll() const; +public: + WidgetBase(SherlockEngine *vm); + virtual ~WidgetBase() {} + + /** + * Erase any previous display of the widget on the screen + */ + void erase(); + + /** + * Update the display of the widget on the screen + */ + void draw(); + + /** + * Used by some descendents to check for keys to mouse the mouse within the dialog + */ + void checkTabbingKeys(int numOptions); + + /** + * Summon the window + */ + virtual void summonWindow(); + + /** + * Close a currently active menu + */ + virtual void banishWindow(); + + /** + * Handle event processing + */ + virtual void handleEvents() {} +}; + +} // End of namespace Tattoo + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/tattoo/widget_inventory.cpp b/engines/sherlock/tattoo/widget_inventory.cpp new file mode 100644 index 0000000000..ac30fe3c9c --- /dev/null +++ b/engines/sherlock/tattoo/widget_inventory.cpp @@ -0,0 +1,519 @@ +/* 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 "sherlock/tattoo/widget_inventory.h" +#include "sherlock/tattoo/tattoo_scene.h" +#include "sherlock/tattoo/tattoo_user_interface.h" +#include "sherlock/tattoo/tattoo.h" + +namespace Sherlock { + +namespace Tattoo { + +#define INVENTORY_XSIZE 70 // Width of the box that surrounds inventory items +#define INVENTORY_YSIZE 70 // Height of the box that surrounds inventory items +#define NUM_INVENTORY_SHOWN 8 // Number of Inventory Items Shown +#define MAX_INV_COMMANDS 10 // Maximum elements in dialog + +// TODO: Refactor into FixedText +#define S_INV6 "Foolscap" +#define S_INV7 "Damp Paper" +#define S_SOLVE "Solve" +#define S_LOOK "Look" +#define S_NO_EFFECT "No effect..." +#define S_WITH "with" + +WidgetInventory::WidgetInventory(SherlockEngine *vm) : WidgetBase(vm), _tooltipWidget(vm) { + _invMode = 0; + _invVerbMode = 0; + _invSelect = _oldInvSelect = 0; + _selector = _oldSelector = 0; + _invVerbSelect = _oldInvVerbSelect = -1; + _dialogTimer = -1; + _scrollHighlight = 0; + _swapItems = false; +} + +void WidgetInventory::load(int mode) { + Events &events = *_vm->_events; + Inventory &inv = *_vm->_inventory; + Common::Point mousePos = events.mousePos(); + + if (mode != 0) + _invMode = mode; + _invVerbMode = 0; + _invSelect = _oldInvSelect = -1; + _selector = _oldSelector = -1; + _dialogTimer = -1; + + if (mode == 0) { + banishWindow(); + } else { + _bounds = Common::Rect((INVENTORY_XSIZE + 3) * NUM_INVENTORY_SHOWN / 2 + BUTTON_SIZE + 6, + (INVENTORY_YSIZE + 3) * 2 + 3); + _bounds.moveTo(mousePos.x - _bounds.width() / 2, mousePos.y - _bounds.height() / 2); + } + + // Ensure menu will be on-screen + checkMenuPosition(); + + // Load the inventory data + inv.loadInv(); + + // Redraw the inventory menu on the widget surface + _surface.create(_bounds.width(), _bounds.height()); + _surface.fill(TRANSPARENCY); + + // Draw the window background and then the inventory on top of it + makeInfoArea(_surface); + drawInventory(); +} + +void WidgetInventory::drawInventory() { + Inventory &inv = *_vm->_inventory; + + // TODO: Refactor _invIndexinto this widget class + for (int idx= 0, itemId = inv._invIndex; idx < NUM_INVENTORY_SHOWN; ++idx) { + // Figure out the drawing position + Common::Point pt(3 + (INVENTORY_XSIZE + 3) * (idx % (NUM_INVENTORY_SHOWN / 2)), + 3 + (INVENTORY_YSIZE + 3) * idx / (NUM_INVENTORY_SHOWN / 2)); + + // Draw the box to serve as the background for the item + _surface.hLine(pt.x + 1, pt.y, pt.x + INVENTORY_XSIZE - 2, TRANSPARENCY); + _surface.fillRect(Common::Rect(pt.x, pt.y + 1, pt.x + INVENTORY_XSIZE, pt.y + INVENTORY_YSIZE - 1), TRANSPARENCY); + _surface.hLine(pt.x + 1, pt.y + INVENTORY_YSIZE - 1, pt.x + INVENTORY_XSIZE - 2, TRANSPARENCY); + + // Draw the item + if (itemId < inv._holdings) { + ImageFrame &img = (*inv._invShapes[idx])[0]; + _surface.transBlitFrom(img, Common::Point(pt.x + (INVENTORY_XSIZE - img._width) / 2, + pt.y + (INVENTORY_YSIZE - img._height) / 2)); + } + } + + drawScrollBar(); +} + +void WidgetInventory::drawScrollBar() { + Inventory &inv = *_vm->_inventory; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + bool raised; + + // Fill the area with transparency + Common::Rect r(BUTTON_SIZE, _bounds.height() - 6); + r.moveTo(_bounds.width() - BUTTON_SIZE - 3, 3); + _surface.fillRect(r, TRANSPARENCY); + + raised = ui._scrollHighlight != 1; + _surface.fillRect(Common::Rect(r.left + 2, r.top + 2, r.right - 2, r.top + BUTTON_SIZE - 2), INFO_MIDDLE); + ui.drawDialogRect(_surface, Common::Rect(r.left, r.top, r.left + BUTTON_SIZE, r.top + BUTTON_SIZE), raised); + + raised = ui._scrollHighlight != 5; + _surface.fillRect(Common::Rect(r.left + 2, r.bottom - BUTTON_SIZE + 2, r.right - 2, r.bottom - 2), INFO_MIDDLE); + ui.drawDialogRect(_surface, Common::Rect(r.left, r.bottom - BUTTON_SIZE, r.right, r.bottom), raised); + + // Draw the arrows on the scroll buttons + byte color = inv._invIndex? INFO_BOTTOM + 2 : INFO_BOTTOM; + _surface.hLine(r.right / 2, r.top - 2 + BUTTON_SIZE / 2, r.right / 2, color); + _surface.fillRect(Common::Rect(r.right / 2 - 1, r.top - 1 + BUTTON_SIZE / 2, + r.right / 2 + 1, r.top - 1 + BUTTON_SIZE / 2), color); + _surface.fillRect(Common::Rect(r.right / 2 - 2, r.top + BUTTON_SIZE / 2, + r.right / 2 + 2, r.top + BUTTON_SIZE / 2), color); + _surface.fillRect(Common::Rect(r.right / 2 - 3, r.top + 1 + BUTTON_SIZE / 2, + r.right / 2 + 3, r.top + 1 + BUTTON_SIZE / 2), color); + + color = (inv._invIndex + NUM_INVENTORY_SHOWN) < inv._holdings ? INFO_BOTTOM + 2 : INFO_BOTTOM; + _surface.fillRect(Common::Rect(r.right / 2 - 3, r.bottom - 1 - BUTTON_SIZE + BUTTON_SIZE / 2, + r.right / 2 + 3, r.bottom - 1 - BUTTON_SIZE + BUTTON_SIZE / 2), color); + _surface.fillRect(Common::Rect(r.right / 2 - 2, r.bottom - 1 - BUTTON_SIZE + 1 + BUTTON_SIZE / 2, + r.right / 2 + 2, r.bottom - 1 - BUTTON_SIZE + 1 + BUTTON_SIZE / 2), color); + _surface.fillRect(Common::Rect(r.right / 2 - 1, r.bottom - 1 - BUTTON_SIZE + 2 + BUTTON_SIZE / 2, + r.right / 2 + 1, r.bottom - 1 - BUTTON_SIZE + 2 + BUTTON_SIZE / 2), color); + _surface.fillRect(Common::Rect(r.right / 2, r.bottom - 1 - BUTTON_SIZE + 3 + BUTTON_SIZE / 2, + r.right / 2, r.bottom - 1 - BUTTON_SIZE + 3 + BUTTON_SIZE / 2), color); + + // Draw the scroll position bar + int idx= inv._holdings; + if (idx% (NUM_INVENTORY_SHOWN / 2)) + idx= (idx + (NUM_INVENTORY_SHOWN / 2)) / (NUM_INVENTORY_SHOWN / 2)*(NUM_INVENTORY_SHOWN / 2); + int barHeight = NUM_INVENTORY_SHOWN * (_bounds.height() - BUTTON_SIZE * 2) / idx; + barHeight = CLIP(barHeight, BUTTON_SIZE, _bounds.height() - BUTTON_SIZE * 2); + + int barY = (idx<= NUM_INVENTORY_SHOWN) ? r.top + BUTTON_SIZE : + (r.height() - BUTTON_SIZE * 2 - barHeight) * FIXED_INT_MULTIPLIER / (idx- NUM_INVENTORY_SHOWN) + * inv._invIndex / FIXED_INT_MULTIPLIER + r.top + BUTTON_SIZE; + _surface.fillRect(Common::Rect(r.left + 2, barY + 2, r.right - 2, barY + barHeight - 3), INFO_MIDDLE); + ui.drawDialogRect(_surface, Common::Rect(r.left, barY, r.right, barY + barHeight), true); +} + +void WidgetInventory::handleEvents() { + TattooEngine &vm = *(TattooEngine *)_vm; + Events &events = *_vm->_events; + Inventory &inv = *_vm->_inventory; + People &people = *_vm->_people; + TattooScene &scene = *(TattooScene *)_vm->_scene; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + Common::Point mousePos = events.mousePos(); + + if (_invVerbMode == 1) + checkTabbingKeys(MAX_INV_COMMANDS); + else if (_invVerbMode == 0) + checkInvTabbingKeys(); + + if (_invVerbMode != 1) + updateDescription(); + + // Flag is they started pressing outside of the menu + if (events._firstPress && !_bounds.contains(mousePos)) + _outsideMenu = true; + + if (_invVerbMode != 3) + highlightControls(); + + // See if they released a mouse button button + if (events._released || events._rightReleased || ui._keyState.keycode == Common::KEYCODE_ESCAPE) { + _dialogTimer = -1; + _scrollHighlight = 0; + + // See if they have a Verb List open for an Inventry Item + if (_invVerbMode == 1) { + // An inventory item's Verb List is open + + // See if they want to close the menu (by clicking outside the menu) + Common::Rect innerBounds = _bounds; + innerBounds.grow(-3); + + if (_outsideMenu && !innerBounds.contains(mousePos)) { + banishWindow(); + _invVerbMode = 0; + } else if (innerBounds.contains(mousePos)) { + _outsideMenu = false; + + // Check if they are trying to solve the Foolscap puzzle, or looking at the completed puzzle + bool doHangman = !inv[_invSelect]._name.compareToIgnoreCase(S_INV6) && + !_inventCommands[_invVerbSelect].compareToIgnoreCase(S_SOLVE); + doHangman |= (!inv[_invSelect]._name.compareToIgnoreCase(S_INV6) || !inv[_invSelect]._name.compareToIgnoreCase(S_INV7)) + && _inventCommands[_invVerbSelect].compareToIgnoreCase(S_LOOK) && vm.readFlags(299); + + if (doHangman) { + // Close the entire Inventory and return to Standard Mode + banishWindow(); + _invVerbMode = 0; + + _tooltipWidget.banishWindow(); + banishWindow(); + inv.freeInv(); + + events.clearEvents(); + events.setCursor(ARROW); + ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE; + + scene.doBgAnim(); + vm.doHangManPuzzle(); + } else if (_invVerbSelect == 0) { + // They have released the mouse on the Look Verb command, so Look at the inventory item + ui._invLookFlag = true; + inv.freeInv(); + ui._windowOpen = false; + ui._lookPos = mousePos; + ui.printObjectDesc(inv[_invSelect]._examine, true); + } else { + // Clear the window + banishWindow(); + _invVerbMode = 3; + ui._oldBgFound = -1; + + // See if the selected Verb with the selected Iventory Item, is to be used by itself + if (!_inventCommands[_invVerbSelect].compareToIgnoreCase(inv[_invSelect]._verb._verb) || + !inv[_invSelect]._verb._target.compareToIgnoreCase("*SELF")) { + inv.freeInv(); + + ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE; + events.clearEvents(); + ui.checkAction(inv[_invSelect]._verb, 2000); + } else { + _invVerb = _inventCommands[_invVerbSelect]; + } + } + + // If we are still in Inventory Mode, setup the graphic to float in front of the mouse cursor + if (ui._menuMode == INV_MODE) { + ImageFrame &imgFrame = (*inv._invShapes[_invSelect - inv._invIndex])[0]; + _invGraphicBounds = Common::Rect(imgFrame._width, imgFrame._height); + _invGraphicBounds.moveTo(mousePos.x - _invGraphicBounds.width() / 2, + mousePos.y - _invGraphicBounds.height() / 2); + + // Constrain it to the screen + if (_invGraphicBounds.left < 0) + _invGraphicBounds.moveTo(0, _invGraphicBounds.top); + if (_invGraphicBounds.top < 0) + _invGraphicBounds.moveTo(_invGraphicBounds.left, 0); + if (_invGraphicBounds.right > SHERLOCK_SCREEN_WIDTH) + _invGraphicBounds.moveTo(SHERLOCK_SCREEN_WIDTH - _invGraphicBounds.width(), _invGraphicBounds.top); + if (_invGraphicBounds.bottom > SHERLOCK_SCREEN_HEIGHT) + _invGraphicBounds.moveTo(_invGraphicBounds.left, SHERLOCK_SCREEN_HEIGHT - _invGraphicBounds.height()); + + // Make a copy of the inventory image + _invGraphic.create(imgFrame._width, imgFrame._height); + _invGraphic.blitFrom(imgFrame, Common::Point(0, 0)); + } + } + } else if (_invVerbMode == 3) { + // Selecting object after inventory verb has been selected + _tooltipWidget.banishWindow(); + _invGraphic.free(); + inv.freeInv(); + + ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE; + events.clearEvents(); + + if (ui._keyState.keycode != Common::KEYCODE_ESCAPE) { + // If user pointed at an item, use the selected inventory item with this item + bool found = false; + if (ui._bgFound != -1) { + if (ui._personFound) { + for (int idx = 0; idx < 2; ++idx) { + if (!people[ui._bgFound - 1000]._use[idx]._verb.compareToIgnoreCase(_invVerb) && + !people[ui._bgFound - 1000]._use[idx]._target.compareToIgnoreCase(_invTarget)) { + ui.checkAction(people[ui._bgFound - 1000]._use[idx], ui._bgFound); + found = true; + } + } + } else { + for (int idx = 0; idx < 6; ++idx) { + if (!ui._bgShape->_use[idx]._verb.compareToIgnoreCase(_invVerb) && + !ui._bgShape->_use[idx]._target.compareToIgnoreCase(_invTarget)) { + ui.checkAction(ui._bgShape->_use[idx], ui._bgFound); + found = true; + } + } + } + } + + if (!found) + ui.putMessage(S_NO_EFFECT); + } + } else if ((_outsideMenu && !_bounds.contains(mousePos)) || ui._keyState.keycode == Common::KEYCODE_ESCAPE) { + // Want to close the window (clicked outside of it). So close the window and return to Standard + banishWindow(); + inv.freeInv(); + + events.clearEvents(); + events.setCursor(ARROW); + banishWindow(); + ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE; + } else if (_bounds.contains(mousePos)) { + // Mouse button was released inside the inventory window + _outsideMenu = false; + + // See if they are pointing at one of the inventory items + if (_invSelect != -1) { + // See if they are in Use Obj with Inv. Mode (they right clicked on an item + // in the room and selected "Use with Inv.") + if (_invMode == 1) { + _tooltipWidget.banishWindow(); + banishWindow(); + + // See if the item in the room that they started with was a person + bool found = false; + if (ui._activeObj >= 1000) { + // Object was a person, activate anything in his two verb fields + for (int idx = 0; idx < 2; ++idx) { + if (!people[ui._activeObj - 1000]._use[idx]._target.compareToIgnoreCase(inv[_invSelect]._name)) { + ui.checkAction(people[ui._activeObj - 1000]._use[idx], ui._activeObj); + found = true; + } + } + } else { + // Object was a regular object, activate anything in its verb fields + for (int idx = 0; idx < 6; ++idx) { + if (!scene._bgShapes[ui._activeObj]._use[idx]._target.compareToIgnoreCase(inv[_invSelect]._name)) { + ui.checkAction(scene._bgShapes[ui._activeObj]._use[idx], ui._activeObj); + found = true; + } + } + } + if (!found) + ui.putMessage(S_NO_EFFECT); + + } else { + // See if they right clicked on an item + if (events._rightReleased) { + _invVerbMode = 1; + _oldInvVerbSelect = -1; + _tooltipWidget.banishWindow(); + + // Keep track of the name of the inventory object so we can check it against the target fields + // of verbs when we activate it + _invTarget = inv[_invSelect]._name; + + // Make the Verb List for this Inventory Item + _inventCommands.clear(); + _inventCommands.push_back(S_LOOK); + + // Default the Action word to "with" + _action = _vm->getLanguage() == Common::GR_GRE ? "" : S_WITH; + _swapItems = false; + + // Search all the bgshapes for any matching Target Fields + for (uint idx = 0; idx < scene._bgShapes.size(); ++idx) { + Object &obj = scene._bgShapes[idx]; + + if (obj._type != INVALID && obj._type != HIDDEN) { + for (int useNum = 0; useNum < 6; ++useNum) { + if (obj._use[useNum]._verb.hasPrefix("*") && + !obj._use[useNum]._target.compareToIgnoreCase(inv[_invSelect]._name)) { + // Make sure the Verb is not already in the list + bool found1 = false; + for (uint cmdNum = 0; cmdNum < _inventCommands.size() && !found1; ++cmdNum) { + if (!_inventCommands[cmdNum].compareToIgnoreCase(obj._use[useNum]._verb)) + found1 = true; + } + + if (!found1) { + _inventCommands.push_back(obj._use[useNum]._verb); + + // Check for any Special Action commands + for (int nameNum = 0; nameNum < 4; ++nameNum) { + if (!scumm_strnicmp(obj._use[useNum]._names[nameNum].c_str(), "*V", 2)) { + if (!scumm_strnicmp(obj._use[useNum]._names[nameNum].c_str(), "*VSWAP", 6)) + _swapItems = true; + else + _action = Common::String(obj._use[useNum]._names[nameNum].c_str() + 2); + } + } + } + } + } + } + } + + // Search the NPCs for matches as well + for (int idx = 1; idx < MAX_CHARACTERS; ++idx) { + for (int useNum = 0; useNum < 2; ++useNum) { + if (!people[idx]._use[useNum]._target.compareToIgnoreCase(inv[_invSelect]._name) && + !people[idx]._use[useNum]._verb.empty() && !people[idx]._use[useNum]._verb.hasPrefix(" ")) { + bool found1 = false; + for (uint cmdNum = 0; cmdNum < _inventCommands.size() && !found1; ++cmdNum) { + if (!_inventCommands[cmdNum].compareToIgnoreCase(people[idx]._use[cmdNum]._verb)) + found1 = true; + } + + if (!found1) + _inventCommands.push_back(people[idx]._use[useNum]._verb); + } + } + } + + // Finally see if the item itself has a verb + if (!inv[_invSelect]._verb._verb.empty()) { + // Don't add "Solve" to the Foolscap if it's already been "Solved" + if (inv[_invSelect]._verb._verb.compareToIgnoreCase(S_SOLVE) || !vm.readFlags(299)) + _inventCommands.push_back(inv[_invSelect]._verb._verb); + } + + // Now find the widest command in the _inventCommands array + int width = 0; + for (uint idx = 0; idx < _inventCommands.size(); ++idx) + width = MAX(width, _surface.stringWidth(_inventCommands[idx])); + + // Set up bounds for the menu + _menuBounds = Common::Rect(width + _surface.widestChar() * 2 + 6, + (_surface.fontHeight() + 7) * _inventCommands.size() + 3); + _menuBounds.moveTo(mousePos.x + _menuBounds.width() / 2, mousePos.y + _menuBounds.height() / 2); + + // Create the surface + _menuSurface.create(_menuBounds.width(), _menuBounds.height()); + _surface.fill(TRANSPARENCY); + makeInfoArea(_menuSurface); + + // Draw the Verb commands and the lines separating them + ImageFile &images = *ui._interfaceImages; + for (int idx = 0; idx < (int)_inventCommands.size(); ++idx) { + _menuSurface.writeString(_inventCommands[idx], Common::Point((_menuBounds.width() - + _menuSurface.stringWidth(_inventCommands[idx])) / 2, (_menuSurface.fontHeight() + 7) * idx + 5), INFO_TOP); + + if (idx < (int)_inventCommands.size()- 1) { + _menuSurface.vLine(3, (_menuSurface.fontHeight() + 7) * (idx + 1), _menuBounds.right - 4, INFO_TOP); + _menuSurface.vLine(3, (_menuSurface.fontHeight() + 7) * (idx + 1) + 1, _menuBounds.right - 4, INFO_MIDDLE); + _menuSurface.vLine(3, (_menuSurface.fontHeight() + 7) * (idx + 1) + 2, _menuBounds.right - 4, INFO_BOTTOM); + + _menuSurface.transBlitFrom(images[4], Common::Point(0, (_menuSurface.fontHeight() + 7) * (idx + 1))); + _menuSurface.transBlitFrom(images[5], Common::Point(_menuBounds.width() - images[5]._width, + (_menuSurface.fontHeight() + 7) * (idx + 1) - 1)); + } + } + } else { + // They left clicked on an inventory item, so Look at it + + // Check if they are looking at the solved Foolscap + if ((!inv[_invSelect]._name.compareToIgnoreCase(S_INV6) || !inv[_invSelect]._name.compareToIgnoreCase(S_INV7)) + && vm.readFlags(299)) { + banishWindow(); + _tooltipWidget.erase(); + + _invVerbMode = 0; + inv.freeInv(); + + events.clearEvents(); + events.setCursor(ARROW); + ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE; + + scene.doBgAnim(); + vm.doHangManPuzzle(); + } else { + ui._invLookFlag = true; + inv.freeInv(); + + _tooltipWidget.banishWindow(); + ui._windowOpen = false; + ui._lookPos = mousePos; + ui.printObjectDesc(inv[_invSelect]._examine, true); + } + } + } + } + } + } +} + +void WidgetInventory::updateDescription() { + // TODO +} + +void WidgetInventory::checkInvTabbingKeys() { +} + +void WidgetInventory::highlightControls() { + // TODO +} + +void WidgetInventory::banishWindow() { + WidgetBase::banishWindow(); + + _menuSurface.free(); + _menuBounds = _oldMenuBounds = Common::Rect(0, 0, 0, 0); +} + +} // End of namespace Tattoo + +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/widget_inventory.h b/engines/sherlock/tattoo/widget_inventory.h new file mode 100644 index 0000000000..b3e914caf7 --- /dev/null +++ b/engines/sherlock/tattoo/widget_inventory.h @@ -0,0 +1,102 @@ +/* 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 SHERLOCK_TATTOO_WIDGET_INVENTORY_H +#define SHERLOCK_TATTOO_WIDGET_INVENTORY_H + +#include "common/scummsys.h" +#include "sherlock/tattoo/widget_base.h" +#include "sherlock/tattoo/widget_tooltip.h" + +namespace Sherlock { + +class SherlockEngine; + +namespace Tattoo { + +class WidgetInventory: public WidgetBase { +private: + int _invVerbMode; + int _invSelect, _oldInvSelect; + int _selector, _oldSelector; + int _invVerbSelect, _oldInvVerbSelect; + int _dialogTimer; + int _scrollHighlight; + Common::StringArray _inventCommands; + WidgetTooltip _tooltipWidget; + Common::String _invVerb; + Common::String _invTarget; + Common::String _action; + Common::Rect _invGraphicBounds; + Surface _invGraphic; + bool _swapItems; + Common::Rect _menuBounds, _oldMenuBounds; + Surface _menuSurface; + + /** + * Draw the scrollbar for the dialog + */ + void drawScrollBar(); + + /** + * Displays the description of any inventory item the moues cursor is over + */ + void updateDescription(); + + /** + * Check for keys to mouse the mouse within the inventory dialog + */ + void checkInvTabbingKeys(); + + /** + * Highlights the controls + */ + void highlightControls(); +public: + int _invMode; +public: + WidgetInventory(SherlockEngine *vm); + virtual ~WidgetInventory() {} + + void load(int mode); + + /** + * Draw the inventory on the surface + */ + void drawInventory(); + + /** + * Handle events whilst the widget is on-screen + */ + virtual void handleEvents(); + + /** + * Close a currently active menu + */ + virtual void banishWindow(); +}; + +} // End of namespace Tattoo + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/tattoo/widget_talk.cpp b/engines/sherlock/tattoo/widget_talk.cpp new file mode 100644 index 0000000000..ed0f2d35c8 --- /dev/null +++ b/engines/sherlock/tattoo/widget_talk.cpp @@ -0,0 +1,138 @@ +/* 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 "sherlock/tattoo/widget_talk.h" +#include "sherlock/tattoo/tattoo_people.h" +#include "sherlock/tattoo/tattoo_talk.h" +#include "sherlock/tattoo/tattoo_scene.h" +#include "sherlock/tattoo/tattoo_user_interface.h" +#include "sherlock/tattoo/tattoo.h" + +namespace Sherlock { + +namespace Tattoo { + +WidgetTalk::WidgetTalk(SherlockEngine *vm) : WidgetBase(vm) { + _talkScroll = false; +} + +void WidgetTalk::getTalkWindowSize() { + TattooTalk &talk = *(TattooTalk *)_vm->_talk; + Common::StringArray lines; + const char *const NUM_STR = "19."; + int width, height; + + // See how many statements are going to be available + int numStatements = 0; + for (uint idx = 0; idx < talk._statements.size(); ++idx) { + if (talk._statements[idx]._talkMap != -1) + ++numStatements; + } + + // Figure out the width, allowing room for both the text and the statement numbers on the side + width = SHERLOCK_SCREEN_WIDTH * 2 / 3; + int n = (numStatements < 10) ? 1 : 0; + width -= _surface.stringWidth(NUM_STR + n) + _surface.widestChar() / 2 + 9; + + // Now that we have a width, split up the text into individual lines + int numLines = 0; + for (uint idx = 0; idx < talk._statements.size(); ++idx) { + if (talk._statements[idx]._talkMap != -1) { + splitLines(talk._statements[idx]._statement, lines, width, 999); + numLines += lines.size(); + } + } + + // Make sure that the window does not get too big + if (numLines < 7) { + height = (_surface.fontHeight() + 1) * numLines + 9; + _talkScroll = false; + } else { + // Set up the height to a constrained amount, and add extra width for the scrollbar + width += BUTTON_SIZE + 3; + height = (_surface.fontHeight() + 1) * 6 + 9; + _talkScroll = false; + } + + _bounds = Common::Rect(width, height); + + // Allocate a surface for the window + _surface.create(_bounds.width(), _bounds.height()); + _surface.fill(TRANSPARENCY); + + // Form the background for the new window + makeInfoArea(); + + int yp = 5; + for (int lineNum = 0; yp < (_bounds.height() - _surface.fontHeight() / 2); ++lineNum) { + _surface.writeString(lines[lineNum], Common::Point(_surface.widestChar(), yp), INFO_TOP); + yp += _surface.fontHeight() + 1; + } +} + +void WidgetTalk::load() { + TattooPeople &people = *(TattooPeople *)_vm->_people; + TattooScene &scene = *(TattooScene *)_vm->_scene; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + ImageFile &images = *ui._interfaceImages; + + // Figure out the window size + getTalkWindowSize(); + + // Place the window centered above the player + Common::Point pt; + int scaleVal = scene.getScaleVal(people[HOLMES]._position); + pt.x = people[HOLMES]._position.x / FIXED_INT_MULTIPLIER; + + if (scaleVal == SCALE_THRESHOLD) { + pt.x += people[0].frameWidth() / 2; + pt.y = people[HOLMES]._position.y / FIXED_INT_MULTIPLIER - people[HOLMES].frameHeight() + - _bounds.height() - _surface.fontHeight(); + } else { + pt.x += people[HOLMES]._imageFrame->sDrawXSize(scaleVal) / 2; + pt.y = people[HOLMES]._position.y / FIXED_INT_MULTIPLIER - people[HOLMES]._imageFrame->sDrawYSize(scaleVal) + - _bounds.height() - _surface.fontHeight(); + } + + _bounds.moveTo(pt); + + // Set up the surface + _surface.create(_bounds.width(), _bounds.height()); + _surface.fill(TRANSPARENCY); + + // Form the background for the new window + makeInfoArea(); + + // If a scrollbar is needed, draw it in + if (_talkScroll) { + int xp = _surface.w() - BUTTON_SIZE - 6; + _surface.vLine(xp, 3, _surface.h() - 4, INFO_TOP); + _surface.vLine(xp + 1, 3, _surface.h() - 4, INFO_MIDDLE); + _surface.vLine(xp + 2, 3, _surface.h() - 4, INFO_BOTTOM); + _surface.transBlitFrom(images[6], Common::Point(xp - 1, 1)); + _surface.transBlitFrom(images[7], Common::Point(xp - 1, _surface.h() - 4)); + } +} + +} // End of namespace Tattoo + +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/widget_talk.h b/engines/sherlock/tattoo/widget_talk.h new file mode 100644 index 0000000000..12ac93bab9 --- /dev/null +++ b/engines/sherlock/tattoo/widget_talk.h @@ -0,0 +1,58 @@ +/* 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 SHERLOCK_TATTOO_WIDGET_TALK_H +#define SHERLOCK_TATTOO_WIDGET_TALK_H + +#include "common/scummsys.h" +#include "sherlock/tattoo/widget_base.h" + +namespace Sherlock { + +class SherlockEngine; + +namespace Tattoo { + +/** + * Handles displaying a dialog with conversation options the player can select from + */ +class WidgetTalk: public WidgetBase { +private: + bool _talkScroll; + + void getTalkWindowSize(); +public: + WidgetTalk(SherlockEngine *vm); + virtual ~WidgetTalk() {} + + /** + * Figures out how many lines the available talk lines will take up, and opens a text window + * of appropriate size + */ + void load(); +}; + +} // End of namespace Tattoo + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/tattoo/widget_text.cpp b/engines/sherlock/tattoo/widget_text.cpp new file mode 100644 index 0000000000..f4e4b6e9af --- /dev/null +++ b/engines/sherlock/tattoo/widget_text.cpp @@ -0,0 +1,143 @@ +/* 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 "sherlock/tattoo/widget_text.h" +#include "sherlock/tattoo/tattoo_user_interface.h" +#include "sherlock/tattoo/tattoo.h" + +namespace Sherlock { + +namespace Tattoo { + +WidgetText::WidgetText(SherlockEngine *vm) : WidgetBase(vm) { +} + +void WidgetText::load(const Common::String &str) { + Screen &screen = *_vm->_screen; + Talk &talk = *_vm->_talk; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + Common::StringArray lines; + + // If bounds for a window have not yet been calculated, figure them out + if (_surface.empty()) { + int width = SHERLOCK_SCREEN_WIDTH / 3; + int height; + + for (;;) { + _remainingText = splitLines(str, lines, width - _surface.widestChar() * 2, 100); + height = (screen.fontHeight() + 1) * lines.size() + 9; + + if ((width - _surface.widestChar() * 2 > height * 3 / 2) || (width - _surface.widestChar() * 2 + > SHERLOCK_SCREEN_WIDTH * 3 / 4)) + break; + + width += (width / 4); + } + + // See if it's only a single line long + if (height == _surface.fontHeight() + 10) { + width = _surface.widestChar() * 2 + 6; + + const char *strP = str.c_str(); + while (*strP && (*strP < talk._opcodes[OP_SWITCH_SPEAKER] || *strP == talk._opcodes[OP_NULL])) + width += _surface.charWidth(*strP++); + } + + _bounds.setWidth(width); + _bounds.setHeight(height); + _bounds.translate(ui._lookPos.x - width / 2, ui._lookPos.y - height / 2); + checkMenuPosition(); + } else { + // Split up the string into lines in preparation for drawing + _remainingText = splitLines(str, lines, _bounds.width() - _surface.widestChar() * 2, + (_bounds.height() - _surface.fontHeight() / 2) / (_surface.fontHeight() + 1)); + } +} + +void WidgetText::load(const Common::String &str, const Common::Rect &bounds) { + Common::StringArray lines; + _remainingText = splitLines(str, lines, bounds.width() - _surface.widestChar() * 2, + bounds.height() / (_surface.fontHeight() + 1)); + + // Allocate a surface for the window + _surface.create(_bounds.width(), _bounds.height()); + _surface.fill(TRANSPARENCY); + + // Form the background for the new window + makeInfoArea(); + + int yp = 5; + for (int lineNum = 0; yp < (_bounds.height() - _surface.fontHeight() / 2); ++lineNum) { + _surface.writeString(lines[lineNum], Common::Point(_surface.widestChar(), yp), INFO_TOP); + yp += _surface.fontHeight() + 1; + } +} + +/*----------------------------------------------------------------*/ + +WidgetMessage::WidgetMessage(SherlockEngine *vm) : WidgetBase(vm) { + _menuCounter = 0; +} + +void WidgetMessage::load(const Common::String &str, int time) { + Events &events = *_vm->_events; + Common::Point mousePos = events.mousePos(); + _menuCounter = time; + + // Set up the bounds for the dialog to be a single line + _bounds = Common::Rect(_surface.stringWidth(str) + _surface.widestChar() * 2 + 6, _surface.fontHeight() + 10); + _bounds.moveTo(mousePos.x - _bounds.width() / 2, mousePos.y - _bounds.height() / 2); + + // Allocate a surface for the window + _surface.create(_bounds.width(), _bounds.height()); + _surface.fill(TRANSPARENCY); + + // Form the background for the new window and write the line of text + makeInfoArea(); + _surface.writeString(str, Common::Point(_surface.widestChar() + 3, 5), INFO_TOP); +} + +void WidgetMessage::handleEvents() { + Events &events = *_vm->_events; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + WidgetBase::handleEvents(); + + --_menuCounter; + + // Check if a mouse or keypress has occurred, or the display counter has expired + if (events._pressed || events._released || events._rightPressed || events._rightReleased || + ui._keyState.keycode || !_menuCounter) { + // Close the window + banishWindow(); + + // Reset cursor and switch back to standard mode + events.setCursor(ARROW); + events.clearEvents(); + ui._key = -1; + ui._oldBgFound = -1; + ui._menuMode = STD_MODE; + } +} + +} // End of namespace Tattoo + +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/widget_text.h b/engines/sherlock/tattoo/widget_text.h new file mode 100644 index 0000000000..e77342531e --- /dev/null +++ b/engines/sherlock/tattoo/widget_text.h @@ -0,0 +1,75 @@ +/* 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 SHERLOCK_TATTOO_WIDGET_TEXT_H +#define SHERLOCK_TATTOO_WIDGET_TEXT_H + +#include "common/scummsys.h" +#include "sherlock/tattoo/widget_base.h" + +namespace Sherlock { + +class SherlockEngine; + +namespace Tattoo { + +class WidgetText: public WidgetBase { +private: + /** + * Display the passed text in a window of the given bounds + */ + void load(const Common::String &str, const Common::Rect &bounds); +public: + Common::String _remainingText; +public: + WidgetText(SherlockEngine *vm); + virtual ~WidgetText() {} + + /** + * Load the data for the text window + */ + void load(const Common::String &str); +}; + +class WidgetMessage : public WidgetBase { +private: + int _menuCounter; +public: + WidgetMessage(SherlockEngine *vm); + virtual ~WidgetMessage() {} + + /** + * Load the data for the text window + */ + void load(const Common::String &str, int time); + + /** + * Handle event processing + */ + virtual void handleEvents(); +}; + +} // End of namespace Tattoo + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/tattoo/widget_tooltip.cpp b/engines/sherlock/tattoo/widget_tooltip.cpp new file mode 100644 index 0000000000..69df7e0ede --- /dev/null +++ b/engines/sherlock/tattoo/widget_tooltip.cpp @@ -0,0 +1,190 @@ +/* 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 "sherlock/tattoo/widget_tooltip.h" +#include "sherlock/tattoo/tattoo_map.h" +#include "sherlock/tattoo/tattoo_user_interface.h" +#include "sherlock/tattoo/tattoo.h" + +namespace Sherlock { + +namespace Tattoo { + +#define MAX_TOOLTIP_WIDTH 150 + +WidgetTooltip::WidgetTooltip(SherlockEngine *vm) : WidgetBase(vm) { +} + +void WidgetTooltip::setText(const Common::String &str) { + Events &events = *_vm->_events; + Common::Point mousePos = events.mousePos(); + bool reset = false; + + // Make sure that the description is present + if (!str.empty()) { + int width = _surface.stringWidth(str) + 2; + int height = _surface.stringHeight(str) + 2; + Common::String line1 = str, line2 = ""; + + // See if we need to split it into two lines + if (width > MAX_TOOLTIP_WIDTH) { + // Go forward word by word to find out where to split the line + const char *s = str.c_str(); + const char *space = nullptr; + int dif = 10000; + + for (;;) { + // Find end of next word + s = strchr(s + 1, ' '); + + if (s == nullptr) { + // Reached end of string + if (space != nullptr) { + line1 = Common::String(str.c_str(), space); + line2 = Common::String(space + 1); + height = _surface.stringHeight(line1) + _surface.stringHeight(line2) + 4; + } + break; + } + + // Found space separating words, so see what width the string up to now is + Common::String tempLine1 = Common::String(str.c_str(), s); + Common::String tempLine2 = Common::String(s + 1); + int width1 = _surface.stringWidth(tempLine1); + int width2 = _surface.stringWidth(tempLine2); + + // See if we've found a split point that results in a less overall width + if (ABS(width1 - width2) < dif) { + // Found a better split point + dif = ABS(width1 - width2); + space = s; + line1 = tempLine1; + line2 = tempLine2; + } + } + } else { + // No line split needed + height = _surface.stringHeight(str) + 2; + } + + // Reallocate the text surface with the new size + _surface.create(width, height); + _surface.fill(TRANSPARENCY); + + if (line2.empty()) { + // Only a single line + _surface.writeFancyString(str, Common::Point(0, 0), BLACK, INFO_TOP); + } else { + // Two lines to display + int xp, yp; + xp = (width - _surface.stringWidth(line1) - 2) / 2; + _surface.writeFancyString(line1, Common::Point(xp, 0), BLACK, INFO_TOP); + + xp = (width - _surface.stringWidth(line2) - 2) / 2; + yp = _surface.stringHeight(line1) + 2; + _surface.writeFancyString(line2, Common::Point(xp, yp), BLACK, INFO_TOP); + } + + // Set the initial display position for the tooltip text + int tagX = CLIP(mousePos.x - width / 2, 0, SHERLOCK_SCREEN_WIDTH - width); + int tagY = MAX(mousePos.y - height, 0); + + _bounds = Common::Rect(tagX, tagY, tagX + width, tagY + height); + } else { + reset = true; + } + + if (reset && !_surface.empty()) { + _surface.free(); + } +} + +void WidgetTooltip::handleEvents() { + Events &events = *_vm->_events; + Common::Point mousePos = events.mousePos(); + + // Set the new position for the tooltip + int xp = CLIP(mousePos.x - _bounds.width() / 2, 0, SHERLOCK_SCREEN_WIDTH - _bounds.width()); + int yp = MAX(mousePos.y - _bounds.height(), 0); + + _bounds.moveTo(xp, yp); +} + +/*----------------------------------------------------------------*/ + +void WidgetSceneTooltip::handleEvents() { + Events &events = *_vm->_events; + People &people = *_vm->_people; + Scene &scene = *_vm->_scene; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + Common::Point mousePos = events.mousePos(); + + // See if thay are pointing at a different object and we need to regenerate the tooltip text + if (ui._bgFound != ui._oldBgFound || (ui._bgFound != -1 && _surface.empty()) || + ui._arrowZone != ui._oldArrowZone || (ui._arrowZone != -1 && _surface.empty())) { + // See if there is a new object to display text for + if ((ui._bgFound != -1 && (ui._bgFound != ui._oldBgFound || (ui._bgFound != -1 && _surface.empty()))) || + (ui._arrowZone != -1 && (ui._arrowZone != ui._oldArrowZone || (ui._arrowZone != -1 && _surface.empty())))) { + Common::String str; + if (ui._bgFound != -1) { + // Clear the Arrow Zone fields so it won't think we're displaying an Arrow Zone cursor + if (scene._currentScene != 90) // RRR Take out the cludge for room 90 + ui._arrowZone = ui._oldArrowZone = -1; + + // Get the description string + str = (ui._bgFound < 1000) ? scene._bgShapes[ui._bgFound]._description : + people[ui._bgFound - 1000]._description; + } else { + // Get the exit zone description + str = scene._exits[ui._arrowZone]._dest; + } + + setText(str.hasPrefix(" ") ? Common::String() : str); + } else if ((ui._bgFound == -1 && ui._oldBgFound != -1) || (ui._arrowZone == -1 && ui._oldArrowZone != -1)) { + setText(""); + } + + ui._oldBgFound = ui._bgFound; + } else { + + // Set the new position for the tooltip + int tagX = CLIP(mousePos.x - _bounds.width() / 2, 0, SHERLOCK_SCREEN_WIDTH - _bounds.width()); + int tagY = MAX(mousePos.y - _bounds.height(), 0); + + _bounds.moveTo(tagX, tagY); + } + + ui._oldArrowZone = ui._arrowZone; + + WidgetTooltip::handleEvents(); +} + +/*----------------------------------------------------------------*/ + +const Common::Point &WidgetMapTooltip::getCurrentScroll() const { + TattooMap &map = *(TattooMap *)_vm->_map; + return map._currentScroll; +} + +} // End of namespace Tattoo + +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/widget_tooltip.h b/engines/sherlock/tattoo/widget_tooltip.h new file mode 100644 index 0000000000..3003831882 --- /dev/null +++ b/engines/sherlock/tattoo/widget_tooltip.h @@ -0,0 +1,76 @@ +/* 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 SHERLOCK_TATTOO_WIDGET_TOOLTIP_H +#define SHERLOCK_TATTOO_WIDGET_TOOLTIP_H + +#include "common/scummsys.h" +#include "common/rect.h" +#include "sherlock/tattoo/widget_base.h" + +namespace Sherlock { + +class SherlockEngine; + +namespace Tattoo { + +class WidgetTooltip: public WidgetBase { +public: + WidgetTooltip(SherlockEngine *vm); + virtual ~WidgetTooltip() {} + + /** + * Set the text for the tooltip + */ + void setText(const Common::String &str); + + /** + * Handle updating the tooltip state + */ + virtual void handleEvents(); +}; + +class WidgetSceneTooltip : public WidgetTooltip { +public: + WidgetSceneTooltip(SherlockEngine *vm) : WidgetTooltip(vm) {} + + /** + * Handle updating the tooltip state + */ + virtual void handleEvents(); +}; + +class WidgetMapTooltip : public WidgetTooltip { +protected: + /** + * Returns the current scroll position + */ + virtual const Common::Point &getCurrentScroll() const; +public: + WidgetMapTooltip(SherlockEngine *vm) : WidgetTooltip(vm) {} +}; + +} // End of namespace Tattoo + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/tattoo/widget_verbs.cpp b/engines/sherlock/tattoo/widget_verbs.cpp new file mode 100644 index 0000000000..9cf4794fc0 --- /dev/null +++ b/engines/sherlock/tattoo/widget_verbs.cpp @@ -0,0 +1,260 @@ +/* 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 "sherlock/tattoo/widget_verbs.h" +#include "sherlock/tattoo/tattoo_scene.h" +#include "sherlock/tattoo/tattoo_user_interface.h" +#include "sherlock/tattoo/tattoo_people.h" +#include "sherlock/tattoo/tattoo.h" + +namespace Sherlock { + +namespace Tattoo { + +WidgetVerbs::WidgetVerbs(SherlockEngine *vm) : WidgetBase(vm) { + _selector = _oldSelector = -1; + _outsideMenu = false; +} + +void WidgetVerbs::activateVerbMenu(bool objectsOn) { + Talk &talk = *_vm->_talk; + FixedText &fixedText = *_vm->_fixedText; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + TattooPeople &people = *(TattooPeople *)_vm->_people; + bool isWatson = false; + Common::String strLook = fixedText.getText(kFixedText_Verb_Look); + Common::String strTalk = fixedText.getText(kFixedText_Verb_Talk); + Common::String strJournal = fixedText.getText(kFixedText_Verb_Journal); + + if (talk._talkToAbort) + return; + + _outsideMenu = false; + + _verbCommands.clear(); + + // Check if we need to show options for the highlighted object + if (objectsOn) { + // Set the verb list accordingly, depending on the target being a + // person or an object + if (ui._personFound) { + TattooPerson &person = people[ui._activeObj - 1000]; + TattooPerson &npc = people[ui._activeObj - 1001]; + + if (!scumm_strnicmp(npc._npcName.c_str(), "WATS", 4)) + isWatson = true; + + if (!scumm_strnicmp(person._examine.c_str(), "_EXIT", 5)) + _verbCommands.push_back(strLook); + + _verbCommands.push_back(strTalk); + + // Add any extra active verbs from the NPC's verb list + // TODO + } else { + if (!scumm_strnicmp(ui._bgShape->_name.c_str(), "WATS", 4)) + isWatson = true; + + if (!scumm_strnicmp(ui._bgShape->_examine.c_str(), "_EXIT", 5)) + _verbCommands.push_back(strLook); + + if (ui._bgShape->_aType == PERSON) + _verbCommands.push_back(strTalk); + + // Add any extra active verbs from the NPC's verb list + // TODO + } + } + + if (isWatson) + _verbCommands.push_back(strJournal); + + // Add the system commands + // TODO + + // Find the widest command + // TODO + + // TODO: Finish this +} + +void WidgetVerbs::execute() { + Events &events = *_vm->_events; + FixedText &fixedText = *_vm->_fixedText; + People &people = *_vm->_people; + TattooScene &scene = *(TattooScene *)_vm->_scene; + Talk &talk = *_vm->_talk; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + Common::Point mousePos = events.mousePos(); + Common::Point scenePos = mousePos + ui._currentScroll; + bool noDesc = false; + + Common::String strLook = fixedText.getText(kFixedText_Verb_Look); + Common::String strTalk = fixedText.getText(kFixedText_Verb_Talk); + Common::String strJournal = fixedText.getText(kFixedText_Verb_Journal); + + checkTabbingKeys(_verbCommands.size()); + + // Highlight verb display as necessary + highlightVerbControls(); + + // Flag if the user has started pressing the button with the cursor outsie the menu + if (events._firstPress && !_bounds.contains(mousePos)) + _outsideMenu = true; + + // See if they released the mouse button + if (events._released || events._released) { + // See if they want to close the menu (they clicked outside of the menu) + if (!_bounds.contains(mousePos)) { + if (_outsideMenu) { + // Free the current menu graphics & erase the menu + banishWindow(); + + if (events._rightReleased) { + // Reset the selected shape to what was clicked on + ui._bgFound = scene.findBgShape(scenePos); + ui._personFound = ui._bgFound >= 1000; + Object *_bgShape = ui._personFound ? nullptr : &scene._bgShapes[ui._bgFound]; + + if (ui._personFound) { + if (people[ui._bgFound - 1000]._description.empty() || people[ui._bgFound - 1000]._description.hasPrefix(" ")) + noDesc = true; + } else if (ui._bgFound != -1) { + if (_bgShape->_description.empty() || _bgShape->_description.hasPrefix(" ")) + noDesc = true; + } else { + noDesc = true; + } + + // Call the Routine to turn on the Commands for this Object + activateVerbMenu(!noDesc); + } else { + // See if we're in a Lab Table Room + ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE; + } + } + } else if (_bounds.contains(mousePos)) { + // Mouse is within the menu + // Erase the menu + banishWindow(); + + // See if they are activating the Look Command + if (!_verbCommands[_selector].compareToIgnoreCase(strLook)) { + ui._bgFound = ui._activeObj; + if (ui._activeObj >= 1000) { + ui._personFound = true; + } else { + ui._personFound = false; + ui._bgShape = &scene._bgShapes[ui._activeObj]; + } + + ui.lookAtObject(); + + } else if (!_verbCommands[_selector].compareToIgnoreCase(strTalk)) { + // Talk command is being activated + talk.talk(ui._activeObj); + ui._activeObj = -1; + + } else if (!_verbCommands[_selector].compareToIgnoreCase(strJournal)) { + ui.doJournal(); + + // See if we're in a Lab Table scene + ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE; + } else if (_selector >= ((int)_verbCommands.size() - 2)) { + switch (_selector - (int)_verbCommands.size() + 2) { + case 0: + // Inventory + ui.doInventory(2); + break; + + case 1: + // Options + ui.doControls(); + break; + + default: + ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE; + break; + } + } else { + // If they have selected anything else, process it + people[HOLMES].gotoStand(); + + if (ui._activeObj < 1000) { + for (int idx = 0; idx < 6; ++idx) { + if (!_verbCommands[_selector].compareToIgnoreCase(scene._bgShapes[ui._activeObj]._use[idx]._verb)) { + // See if they are Picking this object up + if (!scene._bgShapes[ui._activeObj]._use[idx]._target.compareToIgnoreCase("*PICKUP")) + ui.pickUpObject(ui._activeObj); + else + ui.checkAction(scene._bgShapes[ui._activeObj]._use[idx], ui._activeObj); + } + } + } else { + for (int idx = 0; idx < 2; ++idx) { + if (!_verbCommands[_selector].compareToIgnoreCase(people[ui._activeObj - 1000]._use[idx]._verb)) + ui.checkAction(people[ui._activeObj - 1000]._use[idx], ui._activeObj); + } + } + + ui._activeObj = -1; + if (ui._menuMode != MESSAGE_MODE) { + // See if we're in a Lab Table Room + ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE; + } + } + } + } else if (ui._keyState.keycode == Common::KEYCODE_ESCAPE) { + // User closing the menu with the ESC key + banishWindow(); + ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE; + } +} + +void WidgetVerbs::highlightVerbControls() { + Events &events = *_vm->_events; + Screen &screen = *_vm->_screen; + Common::Point mousePos = events.mousePos(); + + // Get highlighted verb + _selector = -1; + Common::Rect bounds = _bounds; + bounds.grow(-3); + if (bounds.contains(mousePos)) + _selector = (mousePos.y - bounds.top) / (screen.fontHeight() + 7); + + // See if a new verb is being pointed at + if (_selector != _oldSelector) { + // Redraw the verb list + for (int idx = 0; idx < (int)_verbCommands.size(); ++idx) { + byte color = (idx == _selector) ? (byte)COMMAND_HIGHLIGHTED : (byte)INFO_TOP; + _surface.writeString(_verbCommands[idx], Common::Point((_bounds.width() - screen.stringWidth(_verbCommands[idx])) / 2, + (screen.fontHeight() + 7) * idx + 5), color); + } + + _oldSelector = _selector; + } +} + +} // End of namespace Tattoo + +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/widget_verbs.h b/engines/sherlock/tattoo/widget_verbs.h new file mode 100644 index 0000000000..fa41b3e42c --- /dev/null +++ b/engines/sherlock/tattoo/widget_verbs.h @@ -0,0 +1,67 @@ +/* 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 SHERLOCK_TATTOO_WIDGET_VERBS_H +#define SHERLOCK_TATTOO_WIDGET_VERBS_H + +#include "common/scummsys.h" +#include "common/rect.h" +#include "common/str-array.h" +#include "sherlock/tattoo/widget_base.h" + +namespace Sherlock { + +class SherlockEngine; + +namespace Tattoo { + +class WidgetVerbs: public WidgetBase { +private: + int _selector, _oldSelector; + bool _outsideMenu; + + /** + * Highlights the controls for the verb list + */ + void highlightVerbControls(); +public: + Common::StringArray _verbCommands; +public: + WidgetVerbs(SherlockEngine *vm); + virtual ~WidgetVerbs() {} + + /** + * Turns on the menu with all the verbs that are available for the given object + */ + void activateVerbMenu(bool objectsOn); + + /** + * Process input for the dialog + */ + void execute(); +}; + +} // End of namespace Tattoo + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/user_interface.cpp b/engines/sherlock/user_interface.cpp new file mode 100644 index 0000000000..9e21be8928 --- /dev/null +++ b/engines/sherlock/user_interface.cpp @@ -0,0 +1,198 @@ +/* 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 "sherlock/user_interface.h" +#include "sherlock/sherlock.h" +#include "sherlock/scalpel/scalpel_user_interface.h" +#include "sherlock/tattoo/tattoo_user_interface.h" + +namespace Sherlock { + +UserInterface *UserInterface::init(SherlockEngine *vm) { + if (vm->getGameID() == GType_SerratedScalpel) + return new Scalpel::ScalpelUserInterface(vm); + else + return new Tattoo::TattooUserInterface(vm); +} + +UserInterface::UserInterface(SherlockEngine *vm) : _vm(vm) { + _menuMode = STD_MODE; + _menuCounter = 0; + _infoFlag = false; + _windowOpen = false; + _endKeyActive = true; + _invLookFlag = 0; + _slideWindows = true; + _helpStyle = false; + _windowBounds = Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH - 1, SHERLOCK_SCREEN_HEIGHT - 1); + _lookScriptFlag = false; + + _bgFound = _oldBgFound = -1; + _key = _oldKey = '\0'; + _selector = _oldSelector = -1; + _temp = _oldTemp = 0; + _temp1 = 0; + _lookHelp = 0; +} + +void UserInterface::checkAction(ActionType &action, int objNum, FixedTextActionId fixedTextActionId) { + Events &events = *_vm->_events; + FixedText &fixedText = *_vm->_fixedText; + People &people = *_vm->_people; + Scene &scene = *_vm->_scene; + Screen &screen = *_vm->_screen; + Talk &talk = *_vm->_talk; + Point32 pt(-1, -1); + + if (objNum >= 1000) + // Ignore actions done on characters + return; + + if (!action._cAnimSpeed) { + // Invalid action, to print error message + _infoFlag = true; + clearInfo(); + Common::String errorMessage = fixedText.getActionMessage(fixedTextActionId, action._cAnimNum); + screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", errorMessage.c_str()); + _infoFlag = true; + + // Set how long to show the message + _menuCounter = 30; + } else { + Object &obj = scene._bgShapes[objNum]; + + int cAnimNum; + if (action._cAnimNum == 0) + // Really a 10 + cAnimNum = 9; + else + cAnimNum = action._cAnimNum - 1; + + int dir = -1; + if (action._cAnimNum != 99) { + CAnim &anim = scene._cAnim[cAnimNum]; + + if (action._cAnimNum != 99) { + if (action._cAnimSpeed & REVERSE_DIRECTION) { + pt = anim._teleport[0]; + dir = anim._teleport[0]._facing; + } else { + pt = anim._goto[0]; + dir = anim._goto[0]._facing; + } + } + } else { + pt = Point32(-1, -1); + dir = -1; + } + + // Has a value, so do action + // Show wait cursor whilst walking to object and doing action + events.setCursor(WAIT); + bool printed = false; + + for (int nameIdx = 0; nameIdx < NAMES_COUNT; ++nameIdx) { + if (action._names[nameIdx].hasPrefix("*") && action._names[nameIdx].size() >= 2 + && toupper(action._names[nameIdx][1]) == 'W') { + if (obj.checkNameForCodes(Common::String(action._names[nameIdx].c_str() + 2), fixedTextActionId)) { + if (!talk._talkToAbort) + printed = true; + } + } + } + + bool doCAnim = true; + for (int nameIdx = 0; nameIdx < NAMES_COUNT; ++nameIdx) { + if (action._names[nameIdx].hasPrefix("*") && action._names[nameIdx].size() >= 2) { + char ch = toupper(action._names[nameIdx][1]); + + if (ch == 'T' || ch == 'B') { + printed = true; + if (pt.x != -1) + // Holmes needs to walk to object before the action is done + people[HOLMES].walkToCoords(pt, dir); + + if (!talk._talkToAbort) { + // Ensure Holmes is on the exact intended location + people[HOLMES]._position = pt; + people[HOLMES]._sequenceNumber = dir; + people[HOLMES].gotoStand(); + + talk.talkTo(action._names[nameIdx].c_str() + 2); + if (ch == 'T') + doCAnim = false; + } + } + } + } + + if (doCAnim && !talk._talkToAbort) { + if (pt.x != -1) + // Holmes needs to walk to object before the action is done + people[HOLMES].walkToCoords(pt, dir); + } + + for (int nameIdx = 0; nameIdx < NAMES_COUNT; ++nameIdx) { + if (action._names[nameIdx].hasPrefix("*") && action._names[nameIdx].size() >= 2 + && toupper(action._names[nameIdx][1]) == 'F') { + if (obj.checkNameForCodes(action._names[nameIdx].c_str() + 2, fixedTextActionId)) { + if (!talk._talkToAbort) + printed = true; + } + } + } + + if (doCAnim && !talk._talkToAbort && action._cAnimNum != 99) + scene.startCAnim(cAnimNum, action._cAnimSpeed); + + if (!talk._talkToAbort) { + for (int nameIdx = 0; nameIdx < NAMES_COUNT && !talk._talkToAbort; ++nameIdx) { + if (obj.checkNameForCodes(action._names[nameIdx], fixedTextActionId)) { + if (!talk._talkToAbort) + printed = true; + } + } + + // Unless we're leaving the scene, print a "Done" message unless the printed flag has been set + if (scene._goToScene != 1 && !printed && !talk._talkToAbort) { + _infoFlag = true; + clearInfo(); + screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "Done..."); + + // Set how long to show the message + _menuCounter = 30; + } + } + } + + // Reset cursor back to arrow + events.setCursor(ARROW); +} + +void UserInterface::reset() { + _bgFound = _oldBgFound = -1; + _oldKey = -1; + _oldTemp = _temp = -1; +} + + +} // End of namespace Sherlock diff --git a/engines/sherlock/user_interface.h b/engines/sherlock/user_interface.h new file mode 100644 index 0000000000..306e0216a3 --- /dev/null +++ b/engines/sherlock/user_interface.h @@ -0,0 +1,137 @@ +/* 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 SHERLOCK_UI_H +#define SHERLOCK_UI_H + +#include "common/scummsys.h" +#include "common/events.h" +#include "sherlock/surface.h" +#include "sherlock/objects.h" +#include "sherlock/resources.h" +#include "sherlock/fixed_text.h" + +namespace Sherlock { + +#define CONTROLS_Y 138 +#define CONTROLS_Y1 151 + +enum MenuMode { + STD_MODE = 0, + LOOK_MODE = 1, + MOVE_MODE = 2, + TALK_MODE = 3, + PICKUP_MODE = 4, + OPEN_MODE = 5, + CLOSE_MODE = 6, + INV_MODE = 7, + USE_MODE = 8, + GIVE_MODE = 9, + JOURNAL_MODE = 10, + FILES_MODE = 11, + SETUP_MODE = 12, + + // Rose Tattoo specific + LAB_MODE = 20, + MESSAGE_MODE = 21, + VERB_MODE = 22 +}; + +class UserInterface { +protected: + SherlockEngine *_vm; + + UserInterface(SherlockEngine *vm); +public: + MenuMode _menuMode; + int _menuCounter; + bool _infoFlag; + bool _windowOpen; + bool _endKeyActive; + int _invLookFlag; + bool _slideWindows; + bool _helpStyle; + Common::Rect _windowBounds; + bool _lookScriptFlag; + int _bgFound, _oldBgFound; + + // TODO: Not so sure these should be in the base class. May want to refactor them to SherlockEngine, or refactor + // various Scalpel dialogs to keep their own private state of key/selections + signed char _key, _oldKey; + int _selector, _oldSelector; + int _temp, _oldTemp; + int _temp1; + int _lookHelp; +public: + static UserInterface *init(SherlockEngine *vm); + virtual ~UserInterface() {} + + /** + * Called for OPEN, CLOSE, and MOVE actions are being done + */ + void checkAction(ActionType &action, int objNum, FixedTextActionId fixedTextActionId = kFixedTextAction_Invalid); +public: + /** + * Resets the user interface + */ + virtual void reset(); + + /** + * Draw the user interface onto the screen's back buffers + */ + virtual void drawInterface(int bufferNum = 3) {} + + /** + * Main input handler for the user interface + */ + virtual void handleInput() {} + + /** + * Displays a passed window by gradually scrolling it vertically on-screen + */ + virtual void summonWindow(const Surface &bgSurface, bool slideUp = true) {} + + /** + * Slide the window stored in the back buffer onto the screen + */ + virtual void summonWindow(bool slideUp = true, int height = CONTROLS_Y) {} + + /** + * Close a currently open window + * @param flag 0 = slide old window down, 1 = slide prior UI back up + */ + virtual void banishWindow(bool slideUp = true) {} + + /** + * Clears the info line of the screen + */ + virtual void clearInfo() {} + + /** + * Clear any active text window + */ + virtual void clearWindow() {} +}; + +} // End of namespace Sherlock + +#endif diff --git a/engines/toon/toon.cpp b/engines/toon/toon.cpp index 09f865f798..9e2905f454 100644 --- a/engines/toon/toon.cpp +++ b/engines/toon/toon.cpp @@ -1785,7 +1785,7 @@ int32 ToonEngine::runEventScript(int32 x, int32 y, int32 mode, int32 id, int32 s _currentScriptRegion++; _script->start(status, 1); - while (_script->run(status)) + while (_script->run(status) && !_shouldQuit) waitForScriptStep(); _currentScriptRegion--; diff --git a/engines/tsage/core.cpp b/engines/tsage/core.cpp index 3105a9008e..d4068c25c9 100644 --- a/engines/tsage/core.cpp +++ b/engines/tsage/core.cpp @@ -1465,7 +1465,7 @@ void ScenePalette::fade(const byte *adjustData, bool fullAdjust, int percent) { adjustData += 3; } - // Set the altered pale4tte + // Set the altered palette g_system->getPaletteManager()->setPalette((const byte *)&tempPalette[0], 0, 256); GLOBALS._screenSurface.updateScreen(); } @@ -1521,7 +1521,7 @@ void ScenePalette::changeBackground(const Rect &bounds, FadeMode fadeMode) { } Rect tempRect = bounds; - if (g_vm->getGameID() != GType_Ringworld) + if (g_vm->getGameID() != GType_Ringworld && g_vm->getGameID() != GType_Sherlock1) tempRect.setHeight(T2_GLOBALS._interfaceY); g_globals->_screenSurface.copyFrom(g_globals->_sceneManager._scene->_backSurface, @@ -2806,7 +2806,7 @@ void SceneObject::updateScreen() { srcRect.right = ((srcRect.right + 3) / 4) * 4; srcRect.clip(g_globals->_sceneManager._scene->_sceneBounds); - if (g_vm->getGameID() != GType_Ringworld) { + if (g_vm->getGameID() != GType_Ringworld && g_vm->getGameID() != GType_Sherlock1) { if (T2_GLOBALS._uiElements._visible) srcRect.bottom = MIN<int16>(srcRect.bottom, T2_GLOBALS._interfaceY); } diff --git a/engines/tsage/detection.cpp b/engines/tsage/detection.cpp index 9d61b4d182..388967931d 100644 --- a/engines/tsage/detection.cpp +++ b/engines/tsage/detection.cpp @@ -62,6 +62,7 @@ static const PlainGameDescriptor tSageGameTitles[] = { { "ringworld", "Ringworld: Revenge of the Patriarch" }, { "blueforce", "Blue Force" }, { "ringworld2", "Return to Ringworld" }, + { "sherlock-logo", "The Lost Files of Sherlock Holmes (Logo)" }, { 0, 0 } }; diff --git a/engines/tsage/detection_tables.h b/engines/tsage/detection_tables.h index da283a27e7..1dfc3e6fd2 100644 --- a/engines/tsage/detection_tables.h +++ b/engines/tsage/detection_tables.h @@ -185,6 +185,22 @@ static const tSageGameDescription gameDescriptions[] = { GType_Ringworld2, GF_CD | GF_ALT_REGIONS | GF_DEMO }, + + // The Lost Files of Sherlock Holmes - The Case of the Serrated Scalpel (Logo) + { + { + "sherlock-logo", + "", + AD_ENTRY1s("sf3.rlb", "153f9b93eda4e95578e31be30e69b5e5", 50419), + Common::EN_ANY, + Common::kPlatformDOS, + ADGF_NO_FLAGS, + GUIO0() + }, + GType_Sherlock1, + GF_FLOPPY + }, + { AD_TABLE_END_MARKER, 0, 0 } }; diff --git a/engines/tsage/globals.cpp b/engines/tsage/globals.cpp index e75febfdbc..1be3e2b6da 100644 --- a/engines/tsage/globals.cpp +++ b/engines/tsage/globals.cpp @@ -27,6 +27,7 @@ #include "tsage/ringworld/ringworld_logic.h" #include "tsage/ringworld2/ringworld2_logic.h" #include "tsage/ringworld2/ringworld2_scenes0.h" +#include "tsage/sherlock/sherlock_logo.h" #include "tsage/staticres.h" namespace TsAGE { @@ -156,6 +157,12 @@ Globals::Globals() : _dialogCenter(160, 140), _gfxManagerInstance(_screenSurface _game = new Ringworld2::Ringworld2Game(); _sceneHandler = new Ringworld2::SceneHandlerExt(); break; + + case GType_Sherlock1: + _inventory = nullptr; + _sceneHandler = new Sherlock::SherlockSceneHandler(); + _game = new Sherlock::SherlockLogo(); + break; } } diff --git a/engines/tsage/graphics.cpp b/engines/tsage/graphics.cpp index ce24c76290..156503fb51 100644 --- a/engines/tsage/graphics.cpp +++ b/engines/tsage/graphics.cpp @@ -1289,7 +1289,8 @@ void GfxManager::setDefaults() { _font._edgeSize = Common::Point(1, 1); _font._colors = g_globals->_fontColors; - _font.setFontNumber(g_globals->_gfxFontNumber); + if (g_globals->_gfxFontNumber >= 0) + _font.setFontNumber(g_globals->_gfxFontNumber); } void GfxManager::activate() { diff --git a/engines/tsage/graphics.h b/engines/tsage/graphics.h index d65d0bcf8b..25f7aea8cd 100644 --- a/engines/tsage/graphics.h +++ b/engines/tsage/graphics.h @@ -20,8 +20,8 @@ * */ -#ifndef RING_GRAPHICS_H -#define RING_GRAPHICS_H +#ifndef TSAGE_GRAPHICS_H +#define TSAGE_GRAPHICS_H #include "tsage/events.h" #include "tsage/saveload.h" diff --git a/engines/tsage/module.mk b/engines/tsage/module.mk index d62f398c20..e23b157a95 100644 --- a/engines/tsage/module.mk +++ b/engines/tsage/module.mk @@ -47,6 +47,7 @@ MODULE_OBJS := \ ringworld2/ringworld2_vampire.o \ saveload.o \ scenes.o \ + sherlock/sherlock_logo.o \ sound.o \ staticres.o \ tsage.o \ diff --git a/engines/tsage/resources.h b/engines/tsage/resources.h index a7536a3c2d..7aa0c49dd4 100644 --- a/engines/tsage/resources.h +++ b/engines/tsage/resources.h @@ -20,8 +20,8 @@ * */ -#ifndef RING_RESOURCES_H -#define RING_RESOURCES_H +#ifndef TSAGE_RESOURCES_H +#define TSAGE_RESOURCES_H #include "common/scummsys.h" #include "common/array.h" diff --git a/engines/tsage/sherlock/sherlock_logo.cpp b/engines/tsage/sherlock/sherlock_logo.cpp new file mode 100644 index 0000000000..2922a9938b --- /dev/null +++ b/engines/tsage/sherlock/sherlock_logo.cpp @@ -0,0 +1,356 @@ +/* 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 "tsage/sherlock/sherlock_logo.h" +#include "tsage/scenes.h" +#include "tsage/tsage.h" + +namespace TsAGE { + +namespace Sherlock { + +void SherlockLogo::start() { + GLOBALS._gfxFontNumber = -1; + GLOBALS.gfxManager().setDefaults(); + + // Start the demo's single scene + g_globals->_sceneManager.changeScene(1); + + g_globals->_events.setCursor(CURSOR_NONE); +} + +Scene *SherlockLogo::createScene(int sceneNumber) { + // The demo only has a single scene, so ignore the scene number and always return it + return new SherlockLogoScene(); +} + +bool SherlockLogo::canLoadGameStateCurrently() { + return false; +} + +bool SherlockLogo::canSaveGameStateCurrently() { + return false; +} + +void SherlockLogo::processEvent(Event &event) { + if (event.eventType == EVENT_BUTTON_DOWN || (event.eventType == EVENT_KEYPRESS && + event.kbd.keycode == Common::KEYCODE_ESCAPE)) + quitGame(); +} + +void SherlockLogo::quitGame() { + g_vm->quitGame(); +} + +/*--------------------------------------------------------------------------*/ + +void SherlockSceneHandler::postInit(SceneObjectList *OwnerList) { + _delayTicks = 2; + + GLOBALS._soundManager.postInit(); + GLOBALS._soundManager.buildDriverList(true); + GLOBALS._soundManager.installConfigDrivers(); + + GLOBALS._sceneManager.setNewScene(10); + GLOBALS._game->start(); +} + +/*--------------------------------------------------------------------------*/ + +void Object::setVisage(const Common::String &name) { + int visageNum = atoi(name.c_str()); + SceneObject::setVisage(visageNum); +} + +/*--------------------------------------------------------------------------*/ + +void SherlockLogoScene::Action1::signal() { + SherlockLogoScene &scene = *(SherlockLogoScene *)GLOBALS._sceneManager._scene; + + switch (_actionIndex++) { + case 0: + // Load scene palette + GLOBALS._scenePalette.loadPalette(1111); + GLOBALS._scenePalette.loadPalette(1); + GLOBALS._scenePalette.refresh(); + setDelay(1); + break; + + case 1: + // Fade in the spotlight background + GLOBALS._scenePalette.addFader(scene._palette1._palette, 256, 6, this); + break; + + case 2: + // First half of square, circle, and triangle bouncing + scene._object1.postInit(); + scene._object1.setVisage("0016.vis"); + scene._object1._strip = 1; + scene._object1._frame = 1; + scene._object1.setPosition(Common::Point(169, 107)); + scene._object1.changeZoom(100); + scene._object1._numFrames = 7; + scene._object1.animate(ANIM_MODE_5, this); + break; + + case 3: + // Remainder of bouncing square, circle, and triangle coming to rest + scene._object1._strip = 2; + scene._object1._frame = 1; + scene._object1.changeZoom(100); + scene._object1.animate(ANIM_MODE_4, 11, 1, this); + break; + + case 4: + // Fade out background without fading out the shapes + GLOBALS._scenePalette.addFader(scene._palette2._palette, 256, 6, this); + break; + + case 5: + scene._backSurface.fillRect(scene._sceneBounds, 0); + scene._gfxManager2.activate(); + scene._gfxManager2.fillRect(scene._sceneBounds, 0); + scene._gfxManager2.deactivate(); + //word_2B4AA = 3; + setDelay(10); + break; + + case 6: + GLOBALS._scenePalette.loadPalette(12); + GLOBALS._scenePalette.refresh(); + setDelay(1); + break; + + case 7: + // Animation of shapes expanding upwards to form larger EA logo + scene._object1.setVisage("0012.vis"); + scene._object1._strip = 1; + scene._object1._frame = 1; + scene._object1.changeZoom(100); + scene._object1.setPosition(Common::Point(170, 142)); + scene._object1._numFrames = 7; + scene._object1.animate(ANIM_MODE_5, (const void *)nullptr); + ADD_MOVER(scene._object1, 158, 71); + break; + + case 8: + GLOBALS._scenePalette.addFader(scene._palette3._palette, 256, 40, this); + break; + + case 9: + // Show 'Electronic Arts' company name + scene._object2.postInit(nullptr); + scene._object2.setVisage("0014.vis"); + scene._object2._strip = 1; + scene._object2._frame = 1; + scene._object2.setPosition(Common::Point(152, 98)); + scene._object2.changeZoom(100); + scene._object2.animate(ANIM_MODE_NONE, (const void *)nullptr); + setDelay(120); + break; + + case 10: + // Remainder of steps is positioning and sizing hand cursorin an arc + scene._object3.postInit(); + scene._object3.setVisage("0018.vis"); + scene._object3._strip = 1; + scene._object3._frame = 1; + scene._object3.setPosition(Common::Point(33, 91)); + scene._object3.changeZoom(100); + scene._object3.animate(ANIM_MODE_NONE, (const void *)nullptr); + setDelay(5); + break; + + case 11: + scene._object3._frame = 2; + scene._object3.setPosition(Common::Point(44, 124)); + setDelay(5); + break; + + case 12: + scene._object3._frame = 3; + scene._object3.setPosition(Common::Point(64, 153)); + setDelay(5); + break; + + case 13: + scene._object3._frame = 4; + scene._object3.setPosition(Common::Point(87, 174)); + setDelay(5); + break; + + case 14: + scene._object3._frame = 5; + scene._object3.setPosition(Common::Point(114, 191)); + setDelay(5); + break; + + case 15: + scene._object3._frame = 6; + scene._object3.setPosition(Common::Point(125, 184)); + setDelay(5); + break; + + case 16: + scene._object3._frame = 7; + scene._object3.setPosition(Common::Point(154, 187)); + setDelay(5); + break; + + case 17: + scene._object3._frame = 8; + scene._object3.setPosition(Common::Point(181, 182)); + setDelay(5); + break; + + case 18: + scene._object3._frame = 9; + scene._object3.setPosition(Common::Point(191, 167)); + setDelay(5); + break; + + case 19: + scene._object3._frame = 10; + scene._object3.setPosition(Common::Point(190, 150)); + setDelay(5); + break; + + case 20: + scene._object3._frame = 11; + scene._object3.setPosition(Common::Point(182, 139)); + setDelay(5); + break; + + case 21: + scene._object3._frame = 11; + scene._object3.setPosition(Common::Point(170, 130)); + setDelay(5); + break; + + case 22: + scene._object3._frame = 11; + scene._object3.setPosition(Common::Point(158, 121)); + setDelay(8); + break; + + case 23: + // Show a highlighting of the company name + scene._object3.hide(); + scene._object4.show(); + scene._object4.setPosition(Common::Point(155, 94)); + setDelay(8); + break; + + case 24: + scene._object4._frame = 2; + scene._object4.setPosition(Common::Point(155, 94)); + setDelay(8); + break; + + case 25: + scene._object2.remove(); + setDelay(1); + break; + + case 26: + scene._object4._frame = 3; + scene._object4.setPosition(Common::Point(155, 94)); + setDelay(8); + break; + + case 27: + scene._object4._frame = 4; + scene._object4.setPosition(Common::Point(155, 94)); + setDelay(8); + break; + break; + + case 28: + scene._object4._frame = 5; + scene._object4.setPosition(Common::Point(155, 94)); + setDelay(8); + break; + break; + + case 29: + scene._object4._frame = 6; + scene._object4.setPosition(Common::Point(155, 94)); + setDelay(8); + break; + break; + + case 30: + scene._object4._frame = 7; + scene._object4.setPosition(Common::Point(155, 94)); + setDelay(8); + break; + break; + + case 31: + scene._object4._frame = 8; + scene._object4.setPosition(Common::Point(155, 94)); + setDelay(8); + break; + + case 32: + setDelay(180); + break; + + default: + scene.finish(); + remove(); + break; + } +} + +/*--------------------------------------------------------------------------*/ + +void SherlockLogoScene::postInit(SceneObjectList *OwnerList) { + loadScene(10); + Scene::postInit(OwnerList); + + _palette1.loadPalette(1111); + _palette1.loadPalette(10); + _palette2.loadPalette(1111); + _palette2.loadPalette(1); + _palette3.loadPalette(1111); + _palette3.loadPalette(14); + + _object4.postInit(); + _object4.setVisage("0019.vis"); + _object4._strip = 1; + _object4._frame = 1; + _object4.setPosition(Common::Point(155, 94)); + _object4.changeZoom(100); + _object4.animate(ANIM_MODE_NONE, (const void *)nullptr); + _object4.hide(); + + setAction(&_action1); +} + +void SherlockLogoScene::finish() { + g_vm->quitGame(); +} + +} // End of namespace Sherlock + +} // End of namespace TsAGE diff --git a/engines/tsage/sherlock/sherlock_logo.h b/engines/tsage/sherlock/sherlock_logo.h new file mode 100644 index 0000000000..95fc0e272f --- /dev/null +++ b/engines/tsage/sherlock/sherlock_logo.h @@ -0,0 +1,78 @@ +/* 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 TSAGE_SHERLOCK_LOGO_H +#define TSAGE_SHERLOCK_LOGO_H + +#include "common/scummsys.h" +#include "tsage/events.h" +#include "tsage/core.h" +#include "tsage/scenes.h" +#include "tsage/globals.h" +#include "tsage/sound.h" + +namespace TsAGE { + +namespace Sherlock { + +using namespace TsAGE; + +class Object : public SceneObject { +public: + void setVisage(const Common::String &name); +}; + +class SherlockLogo: public Game { +public: + virtual void start(); + virtual Scene *createScene(int sceneNumber); + virtual void quitGame(); + virtual void processEvent(Event &event); + virtual bool canSaveGameStateCurrently(); + virtual bool canLoadGameStateCurrently(); +}; + +class SherlockSceneHandler : public SceneHandler { +public: + virtual void postInit(SceneObjectList *OwnerList); +}; + +class SherlockLogoScene: public Scene { + class Action1 : public Action { + public: + virtual void signal(); + }; +public: + ScenePalette _palette1, _palette2, _palette3; + Object _object1, _object2, _object3, _object4; + Action1 _action1; + GfxManager _gfxManager2; + + virtual void postInit(SceneObjectList *OwnerList = NULL); + void finish(); +}; + +} // End of namespace Sherlock + +} // End of namespace TsAGE + +#endif diff --git a/engines/tsage/tsage.cpp b/engines/tsage/tsage.cpp index 0b882d5cbf..4412d0670f 100644 --- a/engines/tsage/tsage.cpp +++ b/engines/tsage/tsage.cpp @@ -44,11 +44,12 @@ TSageEngine::TSageEngine(OSystem *system, const tSageGameDescription *gameDesc) _debugger = new DemoDebugger(); else _debugger = new RingworldDebugger(); - } - else if (g_vm->getGameID() == GType_BlueForce) + } else if (g_vm->getGameID() == GType_BlueForce) _debugger = new BlueForceDebugger(); else if (g_vm->getGameID() == GType_Ringworld2) _debugger = new Ringworld2Debugger(); + else if (g_vm->getGameID() == GType_Sherlock1) + _debugger = new DemoDebugger(); } Common::Error TSageEngine::init() { @@ -110,6 +111,11 @@ void TSageEngine::initialize() { // Reset all global variables R2_GLOBALS.reset(); + } else if (g_vm->getGameID() == GType_Sherlock1) { + g_resourceManager->addLib("SF3.RLB"); + g_globals = new Globals(); + + return; } g_globals->gfxManager().setDefaults(); diff --git a/engines/tsage/tsage.h b/engines/tsage/tsage.h index ea4f5da6ea..667a8daa59 100644 --- a/engines/tsage/tsage.h +++ b/engines/tsage/tsage.h @@ -42,7 +42,8 @@ namespace TsAGE { enum { GType_Ringworld = 0, GType_BlueForce = 1, - GType_Ringworld2 = 2 + GType_Ringworld2 = 2, + GType_Sherlock1 = 5 }; enum { diff --git a/engines/voyeur/events.cpp b/engines/voyeur/events.cpp index 320c92283c..34ef507ad3 100644 --- a/engines/voyeur/events.cpp +++ b/engines/voyeur/events.cpp @@ -76,7 +76,7 @@ EventsManager::EventsManager(VoyeurEngine *vm) : _intPtr(_gameData), _leftClick = _rightClick = false; _mouseClicked = _newMouseClicked = false; - _newLeftClick = _newRightClick = false;; + _newLeftClick = _newRightClick = false; _videoDead = 0; diff --git a/image/codecs/cinepak.cpp b/image/codecs/cinepak.cpp index 32f6be2cd5..4e858921ee 100644 --- a/image/codecs/cinepak.cpp +++ b/image/codecs/cinepak.cpp @@ -433,9 +433,11 @@ const Graphics::Surface *CinepakDecoder::decodeFrame(Common::SeekableReadStream for (uint16 j = 0; j < 256; j++) { _curFrame.strips[i].v1_codebook[j] = _curFrame.strips[i - 1].v1_codebook[j]; _curFrame.strips[i].v4_codebook[j] = _curFrame.strips[i - 1].v4_codebook[j]; - memcpy(_curFrame.strips[i].v1_dither, _curFrame.strips[i - 1].v1_dither, 256 * 4 * 4 * 4); - memcpy(_curFrame.strips[i].v4_dither, _curFrame.strips[i - 1].v4_dither, 256 * 4 * 4 * 4); } + + // Copy the QuickTime dither tables + memcpy(_curFrame.strips[i].v1_dither, _curFrame.strips[i - 1].v1_dither, 256 * 4 * 4 * 4); + memcpy(_curFrame.strips[i].v4_dither, _curFrame.strips[i - 1].v4_dither, 256 * 4 * 4 * 4); } _curFrame.strips[i].id = stream.readUint16BE(); diff --git a/image/codecs/cinepak.h b/image/codecs/cinepak.h index dc8172ea0f..3ca8f515d2 100644 --- a/image/codecs/cinepak.h +++ b/image/codecs/cinepak.h @@ -64,6 +64,9 @@ struct CinepakFrame { * Cinepak decoder. * * Used by BMP/AVI and PICT/QuickTime. + * + * Used in engines: + * - sherlock */ class CinepakDecoder : public Codec { public: |