diff options
Diffstat (limited to 'engines')
76 files changed, 3342 insertions, 790 deletions
diff --git a/engines/cge/events.cpp b/engines/cge/events.cpp index 1530c870ef..89802058f4 100644 --- a/engines/cge/events.cpp +++ b/engines/cge/events.cpp @@ -184,7 +184,7 @@ void Mouse::on() { step(0); if (_busy) _busy->step(0); - } + } } void Mouse::off() { diff --git a/engines/groovie/detection.cpp b/engines/groovie/detection.cpp index 895686b5e0..e06dace0d7 100644 --- a/engines/groovie/detection.cpp +++ b/engines/groovie/detection.cpp @@ -137,6 +137,36 @@ static const GroovieGameDescription gameDescriptions[] = { kGroovieV2, 1 }, + // The 11th Hour Macintosh English + { + { + "11h", "", + { + { "disk.1", 0, "5c0428cd3659fc7bbcd0aa16485ed5da", 227 }, + { "The 11th Hour Installer", 0, "bcdb4040b27f15b18f39fb9e496d384a", 1002987 }, + { 0, 0, 0, 0 } + }, + Common::EN_ANY, Common::kPlatformMacintosh, ADGF_UNSTABLE, + GUIO4(GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_MIDIGM, GUIO_NOASPECT) + }, + kGroovieV2, 1 + }, + + // The 11th Hour Macintosh English (Installed) + { + { + "11h", "Installed", + { + { "disk.1", 0, "5c0428cd3659fc7bbcd0aa16485ed5da", 227 }, + { "el01.mov", 0, "70f42dfc25b1488a08011dc45bb5145d", 6039 }, + { 0, 0, 0, 0 } + }, + Common::EN_ANY, Common::kPlatformMacintosh, ADGF_UNSTABLE, + GUIO4(GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_MIDIGM, GUIO_NOASPECT) + }, + kGroovieV2, 1 + }, + // The 11th Hour DOS Demo English { { @@ -159,6 +189,36 @@ static const GroovieGameDescription gameDescriptions[] = { kGroovieV2, 2 }, + // The Making of The 11th Hour Macintosh English + { + { + "11h", "Making Of", + { + { "disk.1", 0, "5c0428cd3659fc7bbcd0aa16485ed5da", 227 }, + { "The 11th Hour Installer", 0, "bcdb4040b27f15b18f39fb9e496d384a", 1002987 }, + { 0, 0, 0, 0 } + }, + Common::EN_ANY, Common::kPlatformMacintosh, ADGF_UNSTABLE, + GUIO4(GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_MIDIGM, GUIO_NOASPECT) + }, + kGroovieV2, 2 + }, + + // The Making of The 11th Hour Macintosh English (Installed) + { + { + "11h", "Making Of (Installed)", + { + { "disk.1", 0, "5c0428cd3659fc7bbcd0aa16485ed5da", 227 }, + { "el01.mov", 0, "70f42dfc25b1488a08011dc45bb5145d", 6039 }, + { 0, 0, 0, 0 } + }, + Common::EN_ANY, Common::kPlatformMacintosh, ADGF_UNSTABLE, + GUIO4(GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_MIDIGM, GUIO_NOASPECT) + }, + kGroovieV2, 2 + }, + // Clandestiny Trailer DOS English { { @@ -170,6 +230,36 @@ static const GroovieGameDescription gameDescriptions[] = { kGroovieV2, 3 }, + // Clandestiny Trailer Macintosh English + { + { + "clandestiny", "Trailer", + { + { "disk.1", 0, "5c0428cd3659fc7bbcd0aa16485ed5da", 227 }, + { "The 11th Hour Installer", 0, "bcdb4040b27f15b18f39fb9e496d384a", 1002987 }, + { 0, 0, 0, 0 } + }, + Common::EN_ANY, Common::kPlatformMacintosh, ADGF_UNSTABLE, + GUIO4(GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_MIDIGM, GUIO_NOASPECT) + }, + kGroovieV2, 3 + }, + + // Clandestiny Trailer Macintosh English (Installed) + { + { + "clandestiny", "Trailer (Installed)", + { + { "disk.1", 0, "5c0428cd3659fc7bbcd0aa16485ed5da", 227 }, + { "el01.mov", 0, "70f42dfc25b1488a08011dc45bb5145d", 6039 }, + { 0, 0, 0, 0 } + }, + Common::EN_ANY, Common::kPlatformMacintosh, ADGF_UNSTABLE, + GUIO4(GUIO_MIDIADLIB, GUIO_MIDIMT32, GUIO_MIDIGM, GUIO_NOASPECT) + }, + kGroovieV2, 3 + }, + // Clandestiny DOS English { { @@ -207,6 +297,11 @@ static const GroovieGameDescription gameDescriptions[] = { {AD_TABLE_END_MARKER, kGroovieT7G, 0} }; +static const char *directoryGlobs[] = { + "MIDI", + 0 +}; + class GroovieMetaEngine : public AdvancedMetaEngine { public: GroovieMetaEngine() : AdvancedMetaEngine(gameDescriptions, sizeof(GroovieGameDescription), groovieGames) { @@ -222,6 +317,10 @@ public: // replaced with an according explanation. _flags = kADFlagUseExtraAsHint; _guioptions = GUIO3(GUIO_NOSUBTITLES, GUIO_NOSFX, GUIO_NOASPECT); + + // Need MIDI directory to detect 11H Mac Installed + _maxScanDepth = 2; + _directoryGlobs = directoryGlobs; } const char *getName() const { diff --git a/engines/groovie/groovie.cpp b/engines/groovie/groovie.cpp index 726e7cbede..16358bfa28 100644 --- a/engines/groovie/groovie.cpp +++ b/engines/groovie/groovie.cpp @@ -30,6 +30,7 @@ #include "groovie/music.h" #include "groovie/resource.h" #include "groovie/roq.h" +#include "groovie/stuffit.h" #include "groovie/vdx.h" #include "common/config-manager.h" @@ -56,6 +57,7 @@ GroovieEngine::GroovieEngine(OSystem *syst, const GroovieGameDescription *gd) : SearchMan.addSubDirectoryMatching(gameDataDir, "groovie"); SearchMan.addSubDirectoryMatching(gameDataDir, "media"); SearchMan.addSubDirectoryMatching(gameDataDir, "system"); + SearchMan.addSubDirectoryMatching(gameDataDir, "MIDI"); _modeSpeed = kGroovieSpeedNormal; if (ConfMan.hasKey("t7g_speed")) { @@ -93,6 +95,15 @@ GroovieEngine::~GroovieEngine() { } Common::Error GroovieEngine::run() { + if (_gameDescription->version == kGroovieV2 && getPlatform() == Common::kPlatformMacintosh) { + // Load the Mac installer with the lowest priority (in case the user has installed + // the game and has the MIDI folder present; faster to just load them) + Common::Archive *archive = createStuffItArchive("The 11th Hour Installer"); + + if (archive) + SearchMan.add("The 11th Hour Installer", archive); + } + _script = new Script(this, _gameDescription->version); // Initialize the graphics @@ -160,10 +171,10 @@ Common::Error GroovieEngine::run() { // Create the music player switch (getPlatform()) { case Common::kPlatformMacintosh: - // TODO: The 11th Hour Mac uses QuickTime MIDI files - // Right now, since the XMIDI are present and it is still detected as - // the DOS version, we don't have to do anything here. - _musicPlayer = new MusicPlayerMac(this); + if (_gameDescription->version == kGroovieT7G) + _musicPlayer = new MusicPlayerMac_t7g(this); + else + _musicPlayer = new MusicPlayerMac_v2(this); break; case Common::kPlatformIOS: _musicPlayer = new MusicPlayerIOS(this); diff --git a/engines/groovie/module.mk b/engines/groovie/module.mk index 1e89ff66f5..b47eed912b 100644 --- a/engines/groovie/module.mk +++ b/engines/groovie/module.mk @@ -15,6 +15,7 @@ MODULE_OBJS := \ roq.o \ saveload.o \ script.o \ + stuffit.o \ vdx.o # This module can be built as a plugin diff --git a/engines/groovie/music.cpp b/engines/groovie/music.cpp index af929d439b..95637fc407 100644 --- a/engines/groovie/music.cpp +++ b/engines/groovie/music.cpp @@ -678,9 +678,9 @@ void MusicPlayerXMI::setTimbreMT(byte channel, const Timbre &timbre) { } -// MusicPlayerMac +// MusicPlayerMac_t7g -MusicPlayerMac::MusicPlayerMac(GroovieEngine *vm) : MusicPlayerMidi(vm) { +MusicPlayerMac_t7g::MusicPlayerMac_t7g(GroovieEngine *vm) : MusicPlayerMidi(vm) { // Create the parser _midiParser = MidiParser::createParser_SMF(); @@ -701,7 +701,7 @@ MusicPlayerMac::MusicPlayerMac(GroovieEngine *vm) : MusicPlayerMidi(vm) { assert(_vm->_macResFork); } -bool MusicPlayerMac::load(uint32 fileref, bool loop) { +bool MusicPlayerMac_t7g::load(uint32 fileref, bool loop) { debugC(1, kGroovieDebugMIDI | kGroovieDebugAll, "Groovie::Music: Starting the playback of song: %04X", fileref); // First try for compressed MIDI @@ -722,7 +722,7 @@ bool MusicPlayerMac::load(uint32 fileref, bool loop) { return loadParser(file, loop); } -Common::SeekableReadStream *MusicPlayerMac::decompressMidi(Common::SeekableReadStream *stream) { +Common::SeekableReadStream *MusicPlayerMac_t7g::decompressMidi(Common::SeekableReadStream *stream) { // Initialize an output buffer of the given size uint32 size = stream->readUint32BE(); byte *output = (byte *)malloc(size); @@ -768,6 +768,52 @@ Common::SeekableReadStream *MusicPlayerMac::decompressMidi(Common::SeekableReadS return new Common::MemoryReadStream(output, size, DisposeAfterUse::YES); } +// MusicPlayerMac_v2 + +MusicPlayerMac_v2::MusicPlayerMac_v2(GroovieEngine *vm) : MusicPlayerMidi(vm) { + // Create the parser + _midiParser = MidiParser::createParser_QT(); + + // Create the driver + MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_GM); + _driver = MidiDriver::createMidi(dev); + assert(_driver); + + _driver->open(); // TODO: Handle return value != 0 (indicating an error) + + // Set the parser's driver + _midiParser->setMidiDriver(this); + + // Set the timer rate + _midiParser->setTimerRate(_driver->getBaseTempo()); +} + +bool MusicPlayerMac_v2::load(uint32 fileref, bool loop) { + debugC(1, kGroovieDebugMIDI | kGroovieDebugAll, "Groovie::Music: Starting the playback of song: %04X", fileref); + + // Find correct filename + ResInfo info; + _vm->_resMan->getResInfo(fileref, info); + uint len = info.filename.size(); + if (len < 4) + return false; // This shouldn't actually occur + + // Remove the extension and add ".mov" + info.filename.deleteLastChar(); + info.filename.deleteLastChar(); + info.filename.deleteLastChar(); + info.filename += "mov"; + + Common::SeekableReadStream *file = SearchMan.createReadStreamForMember(info.filename); + + if (!file) { + warning("Could not find file '%s'", info.filename.c_str()); + return false; + } + + return loadParser(file, loop); +} + MusicPlayerIOS::MusicPlayerIOS(GroovieEngine *vm) : MusicPlayer(vm) { vm->getTimerManager()->installTimerProc(&onTimer, 50 * 1000, this, "groovieMusic"); } diff --git a/engines/groovie/music.h b/engines/groovie/music.h index cc852aa8dc..92e9c8b487 100644 --- a/engines/groovie/music.h +++ b/engines/groovie/music.h @@ -150,9 +150,9 @@ private: void setTimbreMT(byte channel, const Timbre &timbre); }; -class MusicPlayerMac : public MusicPlayerMidi { +class MusicPlayerMac_t7g : public MusicPlayerMidi { public: - MusicPlayerMac(GroovieEngine *vm); + MusicPlayerMac_t7g(GroovieEngine *vm); protected: bool load(uint32 fileref, bool loop); @@ -161,6 +161,14 @@ private: Common::SeekableReadStream *decompressMidi(Common::SeekableReadStream *stream); }; +class MusicPlayerMac_v2 : public MusicPlayerMidi { +public: + MusicPlayerMac_v2(GroovieEngine *vm); + +protected: + bool load(uint32 fileref, bool loop); +}; + class MusicPlayerIOS : public MusicPlayer { public: MusicPlayerIOS(GroovieEngine *vm); diff --git a/engines/groovie/stuffit.cpp b/engines/groovie/stuffit.cpp new file mode 100644 index 0000000000..37f12585e7 --- /dev/null +++ b/engines/groovie/stuffit.cpp @@ -0,0 +1,537 @@ +/* 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. + * + */ + +// Based on the StuffIt code in ResidualVM +// StuffIt parsing based on http://code.google.com/p/theunarchiver/wiki/StuffItFormat +// Compression 14 based on libxad (http://sourceforge.net/projects/libxad/) + +#include "groovie/stuffit.h" + +#include "common/archive.h" +#include "common/bitstream.h" +#include "common/debug.h" +#include "common/hash-str.h" +#include "common/hashmap.h" +#include "common/memstream.h" +#include "common/substream.h" + +namespace Groovie { + +struct SIT14Data; + +class StuffItArchive : public Common::Archive { +public: + StuffItArchive(); + ~StuffItArchive(); + + bool open(const Common::String &filename); + void close(); + bool isOpen() const { return _stream != 0; } + + // Common::Archive API implementation + bool hasFile(const Common::String &name) const; + int listMembers(Common::ArchiveMemberList &list) const; + const Common::ArchiveMemberPtr getMember(const Common::String &name) const; + Common::SeekableReadStream *createReadStreamForMember(const Common::String &name) const; + +private: + struct FileEntry { + byte compression; + uint32 uncompressedSize; + uint32 compressedSize; + uint32 offset; + }; + + Common::SeekableReadStream *_stream; + + typedef Common::HashMap<Common::String, FileEntry, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> FileMap; + FileMap _map; + + // Decompression Functions + Common::SeekableReadStream *decompress14(Common::SeekableReadStream *src, uint32 uncompressedSize) const; + + // Decompression Helpers + void update14(uint16 first, uint16 last, byte *code, uint16 *freq) const; + void readTree14(Common::BitStream *bits, SIT14Data *dat, uint16 codesize, uint16 *result) const; +}; + +StuffItArchive::StuffItArchive() : Common::Archive() { + _stream = 0; +} + +StuffItArchive::~StuffItArchive() { + close(); +} + +// Some known values of StuffIt FourCC's +// 11H Mac in particular uses ST46 +static const uint32 s_magicNumbers[] = { + MKTAG('S', 'I', 'T', '!'), MKTAG('S', 'T', '6', '5'), MKTAG('S', 'T', '5', '0'), + MKTAG('S', 'T', '6', '0'), MKTAG('S', 'T', 'i', 'n'), MKTAG('S', 'T', 'i', '2'), + MKTAG('S', 'T', 'i', '3'), MKTAG('S', 'T', 'i', '4'), MKTAG('S', 'T', '4', '6') +}; + +bool StuffItArchive::open(const Common::String &filename) { + close(); + + _stream = SearchMan.createReadStreamForMember(filename); + + if (!_stream) + return false; + + uint32 tag = _stream->readUint32BE(); + + // Check all the possible FourCC's + bool found = false; + for (int i = 0; i < ARRAYSIZE(s_magicNumbers); i++) { + if (tag == s_magicNumbers[i]) { + found = true; + break; + } + } + + // Didn't find one, let's bail out + if (!found) { + close(); + return false; + } + + /* uint16 fileCount = */ _stream->readUint16BE(); + /* uint32 archiveSize = */ _stream->readUint32BE(); + + // Some sort of second magic number + if (_stream->readUint32BE() != MKTAG('r', 'L', 'a', 'u')) { + close(); + return false; + } + + /* byte version = */ _stream->readByte(); // meaning not clear + + _stream->skip(7); // unknown + + while (_stream->pos() < _stream->size() && !_stream->eos()) { + byte resForkCompression = _stream->readByte(); + byte dataForkCompression = _stream->readByte(); + + byte fileNameLength = _stream->readByte(); + Common::String name; + + for (byte i = 0; i < fileNameLength; i++) + name += (char)_stream->readByte(); + + // Skip remaining bytes + _stream->skip(63 - fileNameLength); + + /* uint32 fileType = */ _stream->readUint32BE(); + /* uint32 fileCreator = */ _stream->readUint32BE(); + /* uint16 finderFlags = */ _stream->readUint16BE(); + /* uint32 creationDate = */ _stream->readUint32BE(); + /* uint32 modificationDate = */ _stream->readUint32BE(); + uint32 resForkUncompressedSize = _stream->readUint32BE(); + uint32 dataForkUncompressedSize = _stream->readUint32BE(); + uint32 resForkCompressedSize = _stream->readUint32BE(); + uint32 dataForkCompressedSize = _stream->readUint32BE(); + /* uint16 resForkCRC = */ _stream->readUint16BE(); + /* uint16 dataForkCRC = */ _stream->readUint16BE(); + _stream->skip(6); // unknown + /* uint16 headerCRC = */ _stream->readUint16BE(); + + // Ignore directories for now + if (dataForkCompression == 32 || dataForkCompression == 33) + continue; + + if (dataForkUncompressedSize != 0) { + // We have a data fork + + FileEntry entry; + entry.compression = dataForkCompression; + entry.uncompressedSize = dataForkUncompressedSize; + entry.compressedSize = dataForkCompressedSize; + entry.offset = _stream->pos() + resForkCompressedSize; + _map[name] = entry; + + debug(0, "StuffIt file '%s', Compression = %d", name.c_str(), entry.compression); + } + + if (resForkUncompressedSize != 0) { + // We have a resource fork + + // Add a .rsrc extension so we know it's the resource fork + name += ".rsrc"; + + FileEntry entry; + entry.compression = resForkCompression; + entry.uncompressedSize = resForkUncompressedSize; + entry.compressedSize = resForkCompressedSize; + entry.offset = _stream->pos(); + _map[name] = entry; + + debug(0, "StuffIt file '%s', Compression = %d", name.c_str(), entry.compression); + } + + // Go to the next entry + _stream->skip(dataForkCompressedSize + resForkCompressedSize); + } + + return true; +} + +void StuffItArchive::close() { + delete _stream; _stream = 0; + _map.clear(); +} + +bool StuffItArchive::hasFile(const Common::String &name) const { + return _map.contains(name); +} + +int StuffItArchive::listMembers(Common::ArchiveMemberList &list) const { + for (FileMap::const_iterator it = _map.begin(); it != _map.end(); it++) + list.push_back(getMember(it->_key)); + + return _map.size(); +} + +const Common::ArchiveMemberPtr StuffItArchive::getMember(const Common::String &name) const { + return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(name, this)); +} + +Common::SeekableReadStream *StuffItArchive::createReadStreamForMember(const Common::String &name) const { + if (!_stream || !_map.contains(name)) + return 0; + + const FileEntry &entry = _map[name]; + + if (entry.compression & 0xF0) + error("Unhandled StuffIt encryption"); + + Common::SeekableSubReadStream subStream(_stream, entry.offset, entry.offset + entry.compressedSize); + + // We currently only support type 14 compression + switch (entry.compression) { + case 0: // Uncompressed + return subStream.readStream(subStream.size()); + case 14: // Installer + return decompress14(&subStream, entry.uncompressedSize); + default: + error("Unhandled StuffIt compression %d", entry.compression); + } + + return 0; +} + +void StuffItArchive::update14(uint16 first, uint16 last, byte *code, uint16 *freq) const { + uint16 i, j; + + while (last - first > 1) { + i = first; + j = last; + + do { + while (++i < last && code[first] > code[i]) + ; + + while (--j > first && code[first] < code[j]) + ; + + if (j > i) { + SWAP(code[i], code[j]); + SWAP(freq[i], freq[j]); + } + } while (j > i); + + if (first != j) { + SWAP(code[first], code[j]); + SWAP(freq[first], freq[j]); + + i = j + 1; + + if (last - i <= j - first) { + update14(i, last, code, freq); + last = j; + } else { + update14(first, j, code, freq); + first = i; + } + } else { + ++first; + } + } +} + +struct SIT14Data { + byte code[308]; + byte codecopy[308]; + uint16 freq[308]; + uint32 buff[308]; + + byte var1[52]; + uint16 var2[52]; + uint16 var3[75 * 2]; + + byte var4[76]; + uint32 var5[75]; + byte var6[1024]; + uint16 var7[308 * 2]; + byte var8[0x4000]; + + byte window[0x40000]; +}; + +// Realign to a byte boundary +#define ALIGN_BITS(b) \ + if (b->pos() & 7) \ + b->skip(8 - (b->pos() & 7)) + +void StuffItArchive::readTree14(Common::BitStream *bits, SIT14Data *dat, uint16 codesize, uint16 *result) const { + uint32 i, l, n; + uint32 k = bits->getBit(); + uint32 j = bits->getBits(2) + 2; + uint32 o = bits->getBits(3) + 1; + uint32 size = 1 << j; + uint32 m = size - 1; + k = k ? (m - 1) : 0xFFFFFFFF; + + if (bits->getBits(2) & 1) { // skip 1 bit! + // requirements for this call: dat->buff[32], dat->code[32], dat->freq[32*2] + readTree14(bits, dat, size, dat->freq); + + for (i = 0; i < codesize; ) { + l = 0; + + do { + l = dat->freq[l + bits->getBit()]; + n = size << 1; + } while (n > l); + + l -= n; + + if (k != l) { + if (l == m) { + l = 0; + + do { + l = dat->freq[l + bits->getBit()]; + n = size << 1; + } while (n > l); + + l += 3 - n; + + while (l--) { + dat->code[i] = dat->code[i - 1]; + ++i; + } + } else { + dat->code[i++] = l + o; + } + } else { + dat->code[i++] = 0; + } + } + } else { + for (i = 0; i < codesize; ) { + l = bits->getBits(j); + + if (k != l) { + if (l == m) { + l = bits->getBits(j) + 3; + + while (l--) { + dat->code[i] = dat->code[i - 1]; + ++i; + } + } else { + dat->code[i++] = l+o; + } + } else { + dat->code[i++] = 0; + } + } + } + + for (i = 0; i < codesize; ++i) { + dat->codecopy[i] = dat->code[i]; + dat->freq[i] = i; + } + + update14(0, codesize, dat->codecopy, dat->freq); + + for (i = 0; i < codesize && !dat->codecopy[i]; ++i) + ; // find first nonempty + + for (j = 0; i < codesize; ++i, ++j) { + if (i) + j <<= (dat->codecopy[i] - dat->codecopy[i - 1]); + + k = dat->codecopy[i]; m = 0; + + for (l = j; k--; l >>= 1) + m = (m << 1) | (l & 1); + + dat->buff[dat->freq[i]] = m; + } + + for (i = 0; i < (uint32)codesize * 2; ++i) + result[i] = 0; + + j = 2; + + for (i = 0; i < codesize; ++i) { + l = 0; + m = dat->buff[i]; + + for (k = 0; k < dat->code[i]; ++k) { + l += (m & 1); + + if (dat->code[i] - 1 <= (int32)k) { + result[l] = codesize * 2 + i; + } else { + if (!result[l]) { + result[l] = j; + j += 2; + } + + l = result[l]; + } + + m >>= 1; + } + } + + ALIGN_BITS(bits); +} + +#define OUTPUT_VAL(x) \ + out.writeByte(x); \ + dat->window[j++] = x; \ + j &= 0x3FFFF + +Common::SeekableReadStream *StuffItArchive::decompress14(Common::SeekableReadStream *src, uint32 uncompressedSize) const { + byte *dst = (byte *)malloc(uncompressedSize); + Common::MemoryWriteStream out(dst, uncompressedSize); + + Common::BitStream *bits = new Common::BitStream8LSB(src); + + uint32 i, j, k, l, m, n; + + SIT14Data *dat = new SIT14Data(); + + // initialization + for (i = k = 0; i < 52; ++i) { + dat->var2[i] = k; + k += (1 << (dat->var1[i] = ((i >= 4) ? ((i - 4) >> 2) : 0))); + } + + for (i = 0; i < 4; ++i) + dat->var8[i] = i; + + for (m = 1, l = 4; i < 0x4000; m <<= 1) // i is 4 + for (n = l+4; l < n; ++l) + for (j = 0; j < m; ++j) + dat->var8[i++] = l; + + for (i = 0, k = 1; i < 75; ++i) { + dat->var5[i] = k; + k += (1 << (dat->var4[i] = (i >= 3 ? ((i - 3) >> 2) : 0))); + } + + for (i = 0; i < 4; ++i) + dat->var6[i] = i - 1; + + for (m = 1, l = 3; i < 0x400; m <<= 1) // i is 4 + for (n = l + 4; l < n; ++l) + for (j = 0; j < m; ++j) + dat->var6[i++] = l; + + m = bits->getBits(16); // number of blocks + j = 0; // window position + + while (m-- && !bits->eos()) { + bits->getBits(16); // skip crunched block size + bits->getBits(16); + n = bits->getBits(16); // number of uncrunched bytes + n |= bits->getBits(16) << 16; + readTree14(bits, dat, 308, dat->var7); + readTree14(bits, dat, 75, dat->var3); + + while (n && !bits->eos()) { + for (i = 0; i < 616;) + i = dat->var7[i + bits->getBit()]; + + i -= 616; + + if (i < 0x100) { + OUTPUT_VAL(i); + --n; + } else { + i -= 0x100; + k = dat->var2[i]+4; + i = dat->var1[i]; + + if (i) + k += bits->getBits(i); + + for (i = 0; i < 150;) + i = dat->var3[i + bits->getBit()]; + + i -= 150; + l = dat->var5[i]; + i = dat->var4[i]; + + if (i) + l += bits->getBits(i); + + n -= k; + l = j + 0x40000 - l; + + while (k--) { + l &= 0x3FFFF; + OUTPUT_VAL(dat->window[l]); + l++; + } + } + } + + ALIGN_BITS(bits); + } + + delete dat; + delete bits; + + return new Common::MemoryReadStream(dst, uncompressedSize, DisposeAfterUse::YES); +} + +#undef OUTPUT_VAL +#undef ALIGN_BITS + +Common::Archive *createStuffItArchive(const Common::String &fileName) { + StuffItArchive *archive = new StuffItArchive(); + + if (!archive->open(fileName)) { + delete archive; + return 0; + } + + return archive; +} + +} // End of namespace Groovie diff --git a/engines/groovie/stuffit.h b/engines/groovie/stuffit.h new file mode 100644 index 0000000000..44f593dbea --- /dev/null +++ b/engines/groovie/stuffit.h @@ -0,0 +1,43 @@ +/* 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 GROOVIE_STUFFIT_H +#define GROOVIE_STUFFIT_H + +namespace Common { +class Archive; +class String; +} + +namespace Groovie { + +/** + * This factory method creates an Archive instance corresponding to the content + * of the StuffIt compressed file. + * + * May return 0 in case of a failure. + */ +Common::Archive *createStuffItArchive(const Common::String &fileName); + +} // End of namespace Groovie + +#endif diff --git a/engines/hugo/parser.cpp b/engines/hugo/parser.cpp index 5fdb2026a7..2585c64fd8 100644 --- a/engines/hugo/parser.cpp +++ b/engines/hugo/parser.cpp @@ -235,7 +235,7 @@ void Parser::charHandler() { if (_cmdLineIndex >= kMaxLineSize) { //MessageBeep(MB_ICONASTERISK); warning("STUB: MessageBeep() - Command line too long"); - } else if (isprint(static_cast<unsigned char>(c))) { + } else if (Common::isPrint(c)) { _cmdLine[_cmdLineIndex++] = c; _cmdLine[_cmdLineIndex] = '\0'; } diff --git a/engines/mohawk/console.cpp b/engines/mohawk/console.cpp index a7a650d8ed..fc957e895e 100644 --- a/engines/mohawk/console.cpp +++ b/engines/mohawk/console.cpp @@ -75,7 +75,7 @@ bool MystConsole::Cmd_ChangeCard(int argc, const char **argv) { } _vm->_sound->stopSound(); - _vm->changeToCard((uint16)atoi(argv[1]), true); + _vm->changeToCard((uint16)atoi(argv[1]), kTransitionCopy); return false; } diff --git a/engines/mohawk/dialogs.cpp b/engines/mohawk/dialogs.cpp index 4461a30ad4..5f5a3b3800 100644 --- a/engines/mohawk/dialogs.cpp +++ b/engines/mohawk/dialogs.cpp @@ -137,12 +137,6 @@ void MystOptionsDialog::open() { void MystOptionsDialog::handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) { switch (cmd) { - case kZipCmd: - _vm->_gameState->_globals.zipMode = _zipModeCheckbox->getState(); - break; - case kTransCmd: - _vm->_gameState->_globals.transitions = _transitionsCheckbox->getState(); - break; case kDropCmd: _vm->_needsPageDrop = true; close(); @@ -155,8 +149,10 @@ void MystOptionsDialog::handleCommand(GUI::CommandSender *sender, uint32 cmd, ui _vm->_needsShowDemoMenu = true; close(); break; - case GUI::kCloseCmd: - close(); + case GUI::kOKCmd: + _vm->_gameState->_globals.zipMode = _zipModeCheckbox->getState(); + _vm->_gameState->_globals.transitions = _transitionsCheckbox->getState(); + GUI::OptionsDialog::handleCommand(sender, cmd, data); break; default: GUI::OptionsDialog::handleCommand(sender, cmd, data); diff --git a/engines/mohawk/myst.cpp b/engines/mohawk/myst.cpp index 9c0e642203..8140817eb3 100644 --- a/engines/mohawk/myst.cpp +++ b/engines/mohawk/myst.cpp @@ -523,7 +523,7 @@ void MohawkEngine_Myst::changeToStack(uint16 stack, uint16 card, uint16 linkSrcS _video->playMovieBlockingCentered(wrapMovieFilename(flyby, kMasterpieceOnly)); } - changeToCard(card, true); + changeToCard(card, kTransitionCopy); if (linkDstSound) _sound->playSoundBlocking(linkDstSound); @@ -549,7 +549,7 @@ void MohawkEngine_Myst::drawCardBackground() { _gfx->copyImageToBackBuffer(getCardBackgroundId(), Common::Rect(0, 0, 544, 332)); } -void MohawkEngine_Myst::changeToCard(uint16 card, bool updateScreen) { +void MohawkEngine_Myst::changeToCard(uint16 card, TransitionType transition) { debug(2, "changeToCard(%d)", card); _scriptParser->disablePersistentScripts(); @@ -629,9 +629,11 @@ void MohawkEngine_Myst::changeToCard(uint16 card, bool updateScreen) { } // Make sure the screen is updated - if (updateScreen) { - _gfx->copyBackBufferToScreen(Common::Rect(544, 333)); - _system->updateScreen(); + if (transition != kNoTransition) { + if (!_gameState->_globals.transitions) + transition = kTransitionCopy; + + _gfx->runTransition(transition, Common::Rect(544, 333), 10, 0); } // Make sure we have the right cursor showing @@ -1179,41 +1181,41 @@ bool MohawkEngine_Myst::canSaveGameStateCurrently() { } void MohawkEngine_Myst::dropPage() { - uint16 page = _gameState->_globals.heldPage; + uint16 page = _gameState->_globals.heldPage; bool whitePage = page == 13; bool bluePage = page - 1 < 6; - bool redPage = page - 7 < 6; - - // Play drop page sound - _sound->replaceSoundMyst(800); - - // Drop page - _gameState->_globals.heldPage = 0; - - // Redraw page area - if (whitePage && _gameState->_globals.currentAge == 2) { - redrawArea(41); - } else if (bluePage) { - if (page == 6) { - if (_gameState->_globals.currentAge == 2) - redrawArea(24); - } else { - redrawArea(103); - } - } else if (redPage) { - if (page == 12) { - if (_gameState->_globals.currentAge == 2) - redrawArea(25); - } else if (page == 10) { - if (_gameState->_globals.currentAge == 1) - redrawArea(35); - } else { - redrawArea(102); - } - } - - setMainCursor(kDefaultMystCursor); - checkCursorHints(); + bool redPage = page - 7 < 6; + + // Play drop page sound + _sound->replaceSoundMyst(800); + + // Drop page + _gameState->_globals.heldPage = 0; + + // Redraw page area + if (whitePage && _gameState->_globals.currentAge == 2) { + redrawArea(41); + } else if (bluePage) { + if (page == 6) { + if (_gameState->_globals.currentAge == 2) + redrawArea(24); + } else { + redrawArea(103); + } + } else if (redPage) { + if (page == 12) { + if (_gameState->_globals.currentAge == 2) + redrawArea(25); + } else if (page == 10) { + if (_gameState->_globals.currentAge == 1) + redrawArea(35); + } else { + redrawArea(102); + } + } + + setMainCursor(kDefaultMystCursor); + checkCursorHints(); } } // End of namespace Mohawk diff --git a/engines/mohawk/myst.h b/engines/mohawk/myst.h index 30770f7ec9..a268c19737 100644 --- a/engines/mohawk/myst.h +++ b/engines/mohawk/myst.h @@ -75,6 +75,23 @@ enum { kStoneshipStack // Stoneship Age }; +// Transitions +enum TransitionType { + kTransitionLeftToRight = 0, + kTransitionRightToLeft = 1, + kTransitionSlideToLeft = 2, + kTransitionSlideToRight = 3, + kTransitionDissolve = 4, + kTransitionTopToBottom = 5, + kTransitionBottomToTop = 6, + kTransitionSlideToTop = 7, + kTransitionSlideToBottom= 8, + kTransitionPartToRight = 9, + kTransitionPartToLeft = 10, + kTransitionCopy = 11, + kNoTransition = 999 +}; + const uint16 kMasterpieceOnly = 0xFFFF; struct MystCondition { @@ -154,7 +171,7 @@ public: void reloadSaveList(); void changeToStack(uint16 stack, uint16 card, uint16 linkSrcSound, uint16 linkDstSound); - void changeToCard(uint16 card, bool updateScreen); + void changeToCard(uint16 card, TransitionType transition); uint16 getCurCard() { return _curCard; } uint16 getCurStack() { return _curStack; } void setMainCursor(uint16 cursor); diff --git a/engines/mohawk/myst_areas.cpp b/engines/mohawk/myst_areas.cpp index a54b67bef4..12a2c7f44c 100644 --- a/engines/mohawk/myst_areas.cpp +++ b/engines/mohawk/myst_areas.cpp @@ -70,10 +70,30 @@ MystResource::~MystResource() { } void MystResource::handleMouseUp() { - if (_dest != 0) - _vm->changeToCard(_dest, true); - else + if (_dest == 0) { warning("Movement type resource with null destination at position (%d, %d), (%d, %d)", _rect.left, _rect.top, _rect.right, _rect.bottom); + return; + } + + uint16 opcode; + + switch (type) { + case kMystForwardArea: + opcode = 6; + break; + case kMystLeftArea: + opcode = 8; + break; + case kMystRightArea: + opcode = 7; + break; + default: + opcode = 48; + break; + } + + _vm->_scriptParser->setInvokingResource(this); + _vm->_scriptParser->runOpcode(opcode, 0); } bool MystResource::canBecomeActive() { @@ -202,22 +222,23 @@ VideoHandle MystResourceType6::playMovie() { // Check if the video is already running VideoHandle handle = _vm->_video->findVideoHandle(_videoFile); - if (_direction != 1) - warning("Playing QT movies backwards is not implemented"); - // If the video is not running, play it if (handle == NULL_VID_HANDLE || _vm->_video->endOfVideo(handle)) { - if (_playBlocking) { - _vm->_video->playMovieBlocking(_videoFile, _left, _top); - handle = NULL_VID_HANDLE; - } else { - handle = _vm->_video->playMovie(_videoFile, _left, _top, _loop); + handle = _vm->_video->playMovie(_videoFile, _left, _top, _loop); + if (_direction == -1) { + _vm->_video->seekToTime(handle, _vm->_video->getDuration(handle)); + _vm->_video->setVideoRate(handle, -1); } } else { // Resume the video _vm->_video->pauseMovie(handle, false); } + if (_playBlocking) { + _vm->_video->waitUntilMovieEnds(handle); + handle = NULL_VID_HANDLE; + } + return handle; } diff --git a/engines/mohawk/myst_graphics.cpp b/engines/mohawk/myst_graphics.cpp index 2df0f7e6ba..6a292c66e2 100644 --- a/engines/mohawk/myst_graphics.cpp +++ b/engines/mohawk/myst_graphics.cpp @@ -209,15 +209,15 @@ void MystGraphics::copyBackBufferToScreen(Common::Rect r) { _vm->_system->copyRectToScreen(_backBuffer->getBasePtr(r.left, r.top), _backBuffer->pitch, r.left, r.top, r.width(), r.height()); } -void MystGraphics::runTransition(uint16 type, Common::Rect rect, uint16 steps, uint16 delay) { +void MystGraphics::runTransition(TransitionType type, Common::Rect rect, uint16 steps, uint16 delay) { // Do not artificially delay during transitions int oldEnableDrawingTimeSimulation = _enableDrawingTimeSimulation; _enableDrawingTimeSimulation = 0; switch (type) { - case 0: { - debugC(kDebugScript, "Left to Right"); + case kTransitionLeftToRight: { + debugC(kDebugView, "Left to Right"); uint16 step = (rect.right - rect.left) / steps; Common::Rect area = rect; @@ -239,8 +239,8 @@ void MystGraphics::runTransition(uint16 type, Common::Rect rect, uint16 steps, u } } break; - case 1: { - debugC(kDebugScript, "Right to Left"); + case kTransitionRightToLeft: { + debugC(kDebugView, "Right to Left"); uint16 step = (rect.right - rect.left) / steps; Common::Rect area = rect; @@ -262,8 +262,25 @@ void MystGraphics::runTransition(uint16 type, Common::Rect rect, uint16 steps, u } } break; - case 5: { - debugC(kDebugScript, "Top to Bottom"); + case kTransitionSlideToLeft: + debugC(kDebugView, "Slide to left"); + transitionSlideToLeft(rect, steps, delay); + break; + case kTransitionSlideToRight: + debugC(kDebugView, "Slide to right"); + transitionSlideToRight(rect, steps, delay); + break; + case kTransitionDissolve: { + debugC(kDebugView, "Dissolve"); + + for (int16 step = 0; step < 8; step++) { + simulatePreviousDrawDelay(rect); + transitionDissolve(rect, step); + } + } + break; + case kTransitionTopToBottom: { + debugC(kDebugView, "Top to Bottom"); uint16 step = (rect.bottom - rect.top) / steps; Common::Rect area = rect; @@ -285,8 +302,8 @@ void MystGraphics::runTransition(uint16 type, Common::Rect rect, uint16 steps, u } } break; - case 6: { - debugC(kDebugScript, "Bottom to Top"); + case kTransitionBottomToTop: { + debugC(kDebugView, "Bottom to Top"); uint16 step = (rect.bottom - rect.top) / steps; Common::Rect area = rect; @@ -308,18 +325,260 @@ void MystGraphics::runTransition(uint16 type, Common::Rect rect, uint16 steps, u } } break; - default: - warning("Unknown Update Direction"); + case kTransitionSlideToTop: + debugC(kDebugView, "Slide to top"); + transitionSlideToTop(rect, steps, delay); + break; + case kTransitionSlideToBottom: + debugC(kDebugView, "Slide to bottom"); + transitionSlideToBottom(rect, steps, delay); + break; + case kTransitionPartToRight: { + debugC(kDebugView, "Partial left to right"); + + transitionPartialToRight(rect, 75, 3); + } + break; + case kTransitionPartToLeft: { + debugC(kDebugView, "Partial right to left"); - //TODO: Replace minimal implementation + transitionPartialToLeft(rect, 75, 3); + } + break; + case kTransitionCopy: copyBackBufferToScreen(rect); _vm->_system->updateScreen(); break; + default: + error("Unknown transition %d", type); } _enableDrawingTimeSimulation = oldEnableDrawingTimeSimulation; } +void MystGraphics::transitionDissolve(Common::Rect rect, uint step) { + static const bool pattern[][4][4] = { + { + { true, false, false, false }, + { false, false, false, false }, + { false, false, true, false }, + { false, false, false, false } + }, + { + { false, false, true, false }, + { false, false, false, false }, + { true, false, false, false }, + { false, false, false, false } + }, + { + { false, false, false, false }, + { false, true, false, false }, + { false, false, false, false }, + { false, false, false, true } + }, + { + { false, false, false, false }, + { false, false, false, true }, + { false, false, false, false }, + { false, true, false, false } + }, + { + { false, false, false, false }, + { false, false, true, false }, + { false, true, false, false }, + { false, false, false, false } + }, + { + { false, true, false, false }, + { false, false, false, false }, + { false, false, false, false }, + { false, false, true, false } + }, + { + { false, false, false, false }, + { true, false, false, false }, + { false, false, false, true }, + { false, false, false, false } + }, + { + { false, false, false, true }, + { false, false, false, false }, + { false, false, false, false }, + { true, false, false, false } + } + }; + + rect.clip(_viewport); + + Graphics::Surface *screen = _vm->_system->lockScreen(); + + for (uint16 y = rect.top; y < rect.bottom; y++) { + const bool *linePattern = pattern[step][y % 4]; + + if (!linePattern[0] && !linePattern[1] && !linePattern[2] && !linePattern[3]) + continue; + + for (uint16 x = rect.left; x < rect.right; x++) { + if (linePattern[x % 4]) { + if (_pixelFormat.bytesPerPixel == 2) { + uint16 *dst = (uint16 *)screen->getBasePtr(x, y); + *dst = *(const uint16 *)_backBuffer->getBasePtr(x, y); + } else { + uint32 *dst = (uint32 *)screen->getBasePtr(x, y); + *dst = *(const uint32 *)_backBuffer->getBasePtr(x, y); + } + } + } + } + + _vm->_system->unlockScreen(); + _vm->_system->updateScreen(); +} + +void MystGraphics::transitionSlideToLeft(Common::Rect rect, uint16 steps, uint16 delay) { + rect.clip(_viewport); + + uint32 stepWidth = (rect.right - rect.left) / steps; + Common::Rect srcRect = Common::Rect(rect.right, rect.top, rect.right, rect.bottom); + Common::Rect dstRect = Common::Rect(rect.left, rect.top, rect.left, rect.bottom); + + for (uint step = 1; step <= steps; step++) { + dstRect.right = dstRect.left + step * stepWidth; + srcRect.left = srcRect.right - step * stepWidth; + + _vm->_system->delayMillis(delay); + + simulatePreviousDrawDelay(dstRect); + _vm->_system->copyRectToScreen(_backBuffer->getBasePtr(dstRect.left, dstRect.top), + _backBuffer->pitch, srcRect.left, srcRect.top, srcRect.width(), srcRect.height()); + _vm->_system->updateScreen(); + } + + if (dstRect.right != rect.right) { + copyBackBufferToScreen(rect); + _vm->_system->updateScreen(); + } +} + +void MystGraphics::transitionSlideToRight(Common::Rect rect, uint16 steps, uint16 delay) { + rect.clip(_viewport); + + uint32 stepWidth = (rect.right - rect.left) / steps; + Common::Rect srcRect = Common::Rect(rect.left, rect.top, rect.left, rect.bottom); + Common::Rect dstRect = Common::Rect(rect.right, rect.top, rect.right, rect.bottom); + + for (uint step = 1; step <= steps; step++) { + dstRect.left = dstRect.right - step * stepWidth; + srcRect.right = srcRect.left + step * stepWidth; + + _vm->_system->delayMillis(delay); + + simulatePreviousDrawDelay(dstRect); + _vm->_system->copyRectToScreen(_backBuffer->getBasePtr(dstRect.left, dstRect.top), + _backBuffer->pitch, srcRect.left, srcRect.top, srcRect.width(), srcRect.height()); + _vm->_system->updateScreen(); + } + + if (dstRect.left != rect.left) { + copyBackBufferToScreen(rect); + _vm->_system->updateScreen(); + } +} + +void MystGraphics::transitionSlideToTop(Common::Rect rect, uint16 steps, uint16 delay) { + rect.clip(_viewport); + + uint32 stepWidth = (rect.bottom - rect.top) / steps; + Common::Rect srcRect = Common::Rect(rect.left, rect.bottom, rect.right, rect.bottom); + Common::Rect dstRect = Common::Rect(rect.left, rect.top, rect.right, rect.top); + + for (uint step = 1; step <= steps; step++) { + dstRect.bottom = dstRect.top + step * stepWidth; + srcRect.top = srcRect.bottom - step * stepWidth; + + _vm->_system->delayMillis(delay); + + simulatePreviousDrawDelay(dstRect); + _vm->_system->copyRectToScreen(_backBuffer->getBasePtr(dstRect.left, dstRect.top), + _backBuffer->pitch, srcRect.left, srcRect.top, srcRect.width(), srcRect.height()); + _vm->_system->updateScreen(); + } + + + if (dstRect.bottom < rect.bottom) { + copyBackBufferToScreen(rect); + _vm->_system->updateScreen(); + } +} + +void MystGraphics::transitionSlideToBottom(Common::Rect rect, uint16 steps, uint16 delay) { + rect.clip(_viewport); + + uint32 stepWidth = (rect.bottom - rect.top) / steps; + Common::Rect srcRect = Common::Rect(rect.left, rect.top, rect.right, rect.top); + Common::Rect dstRect = Common::Rect(rect.left, rect.bottom, rect.right, rect.bottom); + + for (uint step = 1; step <= steps; step++) { + dstRect.top = dstRect.bottom - step * stepWidth; + srcRect.bottom = srcRect.top + step * stepWidth; + + _vm->_system->delayMillis(delay); + + simulatePreviousDrawDelay(dstRect); + _vm->_system->copyRectToScreen(_backBuffer->getBasePtr(dstRect.left, dstRect.top), + _backBuffer->pitch, srcRect.left, srcRect.top, srcRect.width(), srcRect.height()); + _vm->_system->updateScreen(); + } + + + if (dstRect.top > rect.top) { + copyBackBufferToScreen(rect); + _vm->_system->updateScreen(); + } +} + +void MystGraphics::transitionPartialToRight(Common::Rect rect, uint32 width, uint32 steps) { + rect.clip(_viewport); + + uint32 stepWidth = width / steps; + Common::Rect srcRect = Common::Rect(rect.right, rect.top, rect.right, rect.bottom); + Common::Rect dstRect = Common::Rect(rect.left, rect.top, rect.left, rect.bottom); + + for (uint step = 1; step <= steps; step++) { + dstRect.right = dstRect.left + step * stepWidth; + srcRect.left = srcRect.right - step * stepWidth; + + simulatePreviousDrawDelay(dstRect); + _vm->_system->copyRectToScreen(_backBuffer->getBasePtr(dstRect.left, dstRect.top), + _backBuffer->pitch, srcRect.left, srcRect.top, srcRect.width(), srcRect.height()); + _vm->_system->updateScreen(); + } + + copyBackBufferToScreen(rect); + _vm->_system->updateScreen(); +} + +void MystGraphics::transitionPartialToLeft(Common::Rect rect, uint32 width, uint32 steps) { + rect.clip(_viewport); + + uint32 stepWidth = width / steps; + Common::Rect srcRect = Common::Rect(rect.left, rect.top, rect.left, rect.bottom); + Common::Rect dstRect = Common::Rect(rect.right, rect.top, rect.right, rect.bottom); + + for (uint step = 1; step <= steps; step++) { + dstRect.left = dstRect.right - step * stepWidth; + srcRect.right = srcRect.left + step * stepWidth; + + simulatePreviousDrawDelay(dstRect); + _vm->_system->copyRectToScreen(_backBuffer->getBasePtr(dstRect.left, dstRect.top), + _backBuffer->pitch, srcRect.left, srcRect.top, srcRect.width(), srcRect.height()); + _vm->_system->updateScreen(); + } + + copyBackBufferToScreen(rect); + _vm->_system->updateScreen(); +} + void MystGraphics::drawRect(Common::Rect rect, RectState state) { rect.clip(_viewport); diff --git a/engines/mohawk/myst_graphics.h b/engines/mohawk/myst_graphics.h index de8fe521e6..4bbc8d5b8c 100644 --- a/engines/mohawk/myst_graphics.h +++ b/engines/mohawk/myst_graphics.h @@ -48,7 +48,7 @@ public: void copyImageToScreen(uint16 image, Common::Rect dest); void copyImageToBackBuffer(uint16 image, Common::Rect dest); void copyBackBufferToScreen(Common::Rect r); - void runTransition(uint16 type, Common::Rect rect, uint16 steps, uint16 delay); + void runTransition(TransitionType type, Common::Rect rect, uint16 steps, uint16 delay); void drawRect(Common::Rect rect, RectState state); void drawLine(const Common::Point &p1, const Common::Point &p2, uint32 color); void enableDrawingTimeSimulation(bool enable); @@ -60,7 +60,13 @@ protected: MohawkEngine *getVM() { return (MohawkEngine *)_vm; } void simulatePreviousDrawDelay(const Common::Rect &dest); void copyBackBufferToScreenWithSaturation(int16 saturation); - + void transitionDissolve(Common::Rect rect, uint step); + void transitionSlideToLeft(Common::Rect rect, uint16 steps, uint16 delay); + void transitionSlideToRight(Common::Rect rect, uint16 steps, uint16 delay); + void transitionSlideToTop(Common::Rect rect, uint16 steps, uint16 delay); + void transitionSlideToBottom(Common::Rect rect, uint16 steps, uint16 delay); + void transitionPartialToRight(Common::Rect rect, uint32 width, uint32 steps); + void transitionPartialToLeft(Common::Rect rect, uint32 width, uint32 steps); private: MohawkEngine_Myst *_vm; MystBitmap *_bmpDecoder; diff --git a/engines/mohawk/myst_scripts.cpp b/engines/mohawk/myst_scripts.cpp index 107a8b03e9..c1b75df4cf 100644 --- a/engines/mohawk/myst_scripts.cpp +++ b/engines/mohawk/myst_scripts.cpp @@ -100,18 +100,18 @@ void MystScriptParser::setupCommonOpcodes() { // "Standard" Opcodes OPCODE(0, o_toggleVar); OPCODE(1, o_setVar); - OPCODE(2, o_changeCardSwitch); + OPCODE(2, o_changeCardSwitch4); OPCODE(3, o_takePage); OPCODE(4, o_redrawCard); // Opcode 5 Not Present - OPCODE(6, o_goToDest); - OPCODE(7, o_goToDest); - OPCODE(8, o_goToDest); + OPCODE(6, o_goToDestForward); + OPCODE(7, o_goToDestLeft); + OPCODE(8, o_goToDestRight); OPCODE(9, o_triggerMovie); OPCODE(10, o_toggleVarNoRedraw); // Opcode 11 Not Present - OPCODE(12, o_changeCardSwitch); - OPCODE(13, o_changeCardSwitch); + OPCODE(12, o_changeCardSwitchLtR); + OPCODE(13, o_changeCardSwitchRtL); OPCODE(14, o_drawAreaState); OPCODE(15, o_redrawAreaForVar); OPCODE(16, o_changeCardDirectional); @@ -120,7 +120,7 @@ void MystScriptParser::setupCommonOpcodes() { OPCODE(19, o_enableAreas); OPCODE(20, o_disableAreas); OPCODE(21, o_directionalUpdate); - OPCODE(22, o_goToDest); + OPCODE(22, o_goToDestUp); OPCODE(23, o_toggleAreasActivation); OPCODE(24, o_playSound); // Opcode 25 is unused; original calls replaceSoundMyst @@ -145,6 +145,7 @@ void MystScriptParser::setupCommonOpcodes() { OPCODE(44, o_restoreMainCursor); // Opcode 45 Not Present OPCODE(46, o_soundWaitStop); + OPCODE(48, o_goToDest); OPCODE(51, o_exitMap); // Opcodes 47 to 99 Not Present @@ -273,7 +274,7 @@ void MystScriptParser::animatedUpdate(uint16 argc, uint16 *argv, uint16 delay) { while (argsRead < argc) { Common::Rect rect = Common::Rect(argv[argsRead], argv[argsRead + 1], argv[argsRead + 2], argv[argsRead + 3]); - uint16 kind = argv[argsRead + 4]; + TransitionType kind = static_cast<TransitionType>(argv[argsRead + 4]); uint16 steps = argv[argsRead + 5]; debugC(kDebugScript, "\trect.left: %d", rect.left); @@ -323,16 +324,41 @@ void MystScriptParser::o_setVar(uint16 op, uint16 var, uint16 argc, uint16 *argv _vm->redrawArea(var); } -void MystScriptParser::o_changeCardSwitch(uint16 op, uint16 var, uint16 argc, uint16 *argv) { - // Opcodes 2, 12, and 13 are the same +void MystScriptParser::o_changeCardSwitch4(uint16 op, uint16 var, uint16 argc, uint16 *argv) { uint16 value = getVar(var); debugC(kDebugScript, "Opcode %d: changeCardSwitch var %d: %d", op, var, value); if (value) - _vm->changeToCard(argv[value -1 ], true); + _vm->changeToCard(argv[value -1 ], kTransitionDissolve); else if (_invokingResource != NULL) - _vm->changeToCard(_invokingResource->getDest(), true); + _vm->changeToCard(_invokingResource->getDest(), kTransitionDissolve); + else + warning("Missing invokingResource in altDest call"); +} + +void MystScriptParser::o_changeCardSwitchLtR(uint16 op, uint16 var, uint16 argc, uint16 *argv) { + uint16 value = getVar(var); + + debugC(kDebugScript, "Opcode %d: changeCardSwitch var %d: %d", op, var, value); + + if (value) + _vm->changeToCard(argv[value -1 ], kTransitionLeftToRight); + else if (_invokingResource != NULL) + _vm->changeToCard(_invokingResource->getDest(), kTransitionLeftToRight); + else + warning("Missing invokingResource in altDest call"); +} + +void MystScriptParser::o_changeCardSwitchRtL(uint16 op, uint16 var, uint16 argc, uint16 *argv) { + uint16 value = getVar(var); + + debugC(kDebugScript, "Opcode %d: changeCardSwitch var %d: %d", op, var, value); + + if (value) + _vm->changeToCard(argv[value -1 ], kTransitionRightToLeft); + else if (_invokingResource != NULL) + _vm->changeToCard(_invokingResource->getDest(), kTransitionRightToLeft); else warning("Missing invokingResource in altDest call"); } @@ -373,10 +399,47 @@ void MystScriptParser::o_goToDest(uint16 op, uint16 var, uint16 argc, uint16 *ar debugC(kDebugScript, "Opcode %d: Change To Dest of Invoking Resource", op); if (_invokingResource != NULL) - _vm->changeToCard(_invokingResource->getDest(), true); + _vm->changeToCard(_invokingResource->getDest(), kTransitionCopy); + else + warning("Opcode %d: Missing invokingResource", op); +} + +void MystScriptParser::o_goToDestForward(uint16 op, uint16 var, uint16 argc, uint16 *argv) { + debugC(kDebugScript, "Opcode %d: Change To Dest of Invoking Resource", op); + + if (_invokingResource != NULL) + _vm->changeToCard(_invokingResource->getDest(), kTransitionDissolve); + else + warning("Opcode %d: Missing invokingResource", op); +} + +void MystScriptParser::o_goToDestLeft(uint16 op, uint16 var, uint16 argc, uint16 *argv) { + debugC(kDebugScript, "Opcode %d: Change To Dest of Invoking Resource", op); + + if (_invokingResource != NULL) + _vm->changeToCard(_invokingResource->getDest(), kTransitionPartToRight); + else + warning("Opcode %d: Missing invokingResource", op); +} + +void MystScriptParser::o_goToDestRight(uint16 op, uint16 var, uint16 argc, uint16 *argv) { + debugC(kDebugScript, "Opcode %d: Change To Dest of Invoking Resource", op); + + if (_invokingResource != NULL) + _vm->changeToCard(_invokingResource->getDest(), kTransitionPartToLeft); else warning("Opcode %d: Missing invokingResource", op); } + +void MystScriptParser::o_goToDestUp(uint16 op, uint16 var, uint16 argc, uint16 *argv) { + debugC(kDebugScript, "Opcode %d: Change To Dest of Invoking Resource", op); + + if (_invokingResource != NULL) + _vm->changeToCard(_invokingResource->getDest(), kTransitionTopToBottom); + else + warning("Opcode %d: Missing invokingResource", op); +} + void MystScriptParser::o_triggerMovie(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Trigger Type 6 Resource Movie..", op); // TODO: If movie has sound, pause background music @@ -427,7 +490,7 @@ void MystScriptParser::o_changeCardDirectional(uint16 op, uint16 var, uint16 arg debugC(kDebugScript, "\tcardId: %d", cardId); debugC(kDebugScript, "\tdirectonal update data size: %d", directionalUpdateDataSize); - _vm->changeToCard(cardId, false); + _vm->changeToCard(cardId, kNoTransition); animatedUpdate(directionalUpdateDataSize, &argv[2], 0); } @@ -440,23 +503,23 @@ void MystScriptParser::o_changeCardPush(uint16 op, uint16 var, uint16 argc, uint debugC(kDebugScript, "Opcode %d: Jump to Card Id, Storing Current Card Id", op); _savedCardId = _vm->getCurCard(); - uint16 cardId = argv[0]; - // argv[1] is not used in the original engine + uint16 cardId = argv[0]; + TransitionType transition = static_cast<TransitionType>(argv[1]); debugC(kDebugScript, "\tCurrent CardId: %d", _savedCardId); debugC(kDebugScript, "\tJump to CardId: %d", cardId); - _vm->changeToCard(cardId, true); + _vm->changeToCard(cardId, transition); } void MystScriptParser::o_changeCardPop(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Return To Stored Card Id", op); debugC(kDebugScript, "\tCardId: %d", _savedCardId); - // argv[0] is not used in the original engine + TransitionType transition = static_cast<TransitionType>(argv[0]); - _vm->changeToCard(_savedCardId, true); + _vm->changeToCard(_savedCardId, transition); } void MystScriptParser::o_enableAreas(uint16 op, uint16 var, uint16 argc, uint16 *argv) { @@ -752,14 +815,11 @@ void MystScriptParser::o_changeCard(uint16 op, uint16 var, uint16 argc, uint16 * debugC(kDebugScript, "Opcode %d: Change Card", op); uint16 cardId = argv[0]; - - // Argument 1 if present is not used - // uint16 u0 = argv[1]; + TransitionType transition = static_cast<TransitionType>(argv[1]); debugC(kDebugScript, "\tTarget Card: %d", cardId); - //debugC(kDebugScript, "\tu0: %d", u0); // Unused data - _vm->changeToCard(cardId, true); + _vm->changeToCard(cardId, transition); } void MystScriptParser::o_drawImageChangeCard(uint16 op, uint16 var, uint16 argc, uint16 *argv) { @@ -767,7 +827,7 @@ void MystScriptParser::o_drawImageChangeCard(uint16 op, uint16 var, uint16 argc, uint16 imageId = argv[0]; uint16 cardId = argv[1]; - // argv[2] is not used in the original engine + TransitionType transition = static_cast<TransitionType>(argv[2]); debugC(kDebugScript, "\timageId: %d", imageId); debugC(kDebugScript, "\tcardId: %d", cardId); @@ -775,7 +835,7 @@ void MystScriptParser::o_drawImageChangeCard(uint16 op, uint16 var, uint16 argc, _vm->_gfx->copyImageToScreen(imageId, Common::Rect(0, 0, 544, 333)); _vm->_system->updateScreen(); - _vm->changeToCard(cardId, true); + _vm->changeToCard(cardId, transition); } void MystScriptParser::o_changeMainCursor(uint16 op, uint16 var, uint16 argc, uint16 *argv) { @@ -850,7 +910,7 @@ void MystScriptParser::o_changeCardPlaySoundDirectional(uint16 op, uint16 var, u if (soundId) _vm->_sound->replaceSoundMyst(soundId); - _vm->changeToCard(cardId, false); + _vm->changeToCard(cardId, kNoTransition); animatedUpdate(dataSize, &argv[4], delayBetweenSteps); } @@ -901,12 +961,12 @@ void MystScriptParser::o_quit(uint16 op, uint16 var, uint16 argc, uint16 *argv) void MystScriptParser::showMap() { if (_vm->getCurCard() != getMap()) { _savedMapCardId = _vm->getCurCard(); - _vm->changeToCard(getMap(), true); + _vm->changeToCard(getMap(), kTransitionCopy); } } void MystScriptParser::o_exitMap(uint16 op, uint16 var, uint16 argc, uint16 *argv) { - _vm->changeToCard(_savedMapCardId, true); + _vm->changeToCard(_savedMapCardId, kTransitionCopy); } } // End of namespace Mohawk diff --git a/engines/mohawk/myst_scripts.h b/engines/mohawk/myst_scripts.h index ccb76e0dc8..b75da0801a 100644 --- a/engines/mohawk/myst_scripts.h +++ b/engines/mohawk/myst_scripts.h @@ -86,10 +86,16 @@ public: // Common opcodes DECLARE_OPCODE(o_toggleVar); DECLARE_OPCODE(o_setVar); - DECLARE_OPCODE(o_changeCardSwitch); + DECLARE_OPCODE(o_changeCardSwitch4); + DECLARE_OPCODE(o_changeCardSwitchLtR); + DECLARE_OPCODE(o_changeCardSwitchRtL); DECLARE_OPCODE(o_takePage); DECLARE_OPCODE(o_redrawCard); DECLARE_OPCODE(o_goToDest); + DECLARE_OPCODE(o_goToDestForward); + DECLARE_OPCODE(o_goToDestLeft); + DECLARE_OPCODE(o_goToDestRight); + DECLARE_OPCODE(o_goToDestUp); DECLARE_OPCODE(o_triggerMovie); DECLARE_OPCODE(o_toggleVarNoRedraw); DECLARE_OPCODE(o_drawAreaState); diff --git a/engines/mohawk/myst_stacks/channelwood.cpp b/engines/mohawk/myst_stacks/channelwood.cpp index 069281f5dc..63ba5f7c85 100644 --- a/engines/mohawk/myst_stacks/channelwood.cpp +++ b/engines/mohawk/myst_stacks/channelwood.cpp @@ -341,7 +341,7 @@ void Channelwood::o_drawImageChangeCardAndVolume(uint16 op, uint16 var, uint16 a _vm->_gfx->copyImageToScreen(imageId, Common::Rect(0, 0, 544, 333)); _vm->_system->updateScreen(); - _vm->changeToCard(cardId, true); + _vm->changeToCard(cardId, kTransitionPartToLeft); if (argc == 3) { uint16 volume = argv[2]; @@ -476,9 +476,12 @@ void Channelwood::o_stairsDoorToggle(uint16 op, uint16 var, uint16 argc, uint16 MystResourceType6 *movie = static_cast<MystResourceType6 *>(_invokingResource); if (_state.stairsUpperDoorState) { - // TODO: Play backwards + // Close door, play the open movie backwards + movie->setDirection(-1); movie->playMovie(); } else { + // Open door + movie->setDirection(1); movie->playMovie(); } } diff --git a/engines/mohawk/myst_stacks/demo.cpp b/engines/mohawk/myst_stacks/demo.cpp index 29a12571fd..9f393ea401 100644 --- a/engines/mohawk/myst_stacks/demo.cpp +++ b/engines/mohawk/myst_stacks/demo.cpp @@ -104,14 +104,14 @@ void Demo::returnToMenu_run() { switch (_returnToMenuStep){ case 0: _vm->_gfx->fadeToBlack(); - _vm->changeToCard(2003, false); + _vm->changeToCard(2003, kNoTransition); _vm->_gfx->fadeFromBlack(); _returnToMenuStep++; break; case 1: _vm->_gfx->fadeToBlack(); - _vm->changeToCard(2001, false); + _vm->changeToCard(2001, kNoTransition); _vm->_gfx->fadeFromBlack(); _vm->_cursor->showCursor(); diff --git a/engines/mohawk/myst_stacks/intro.cpp b/engines/mohawk/myst_stacks/intro.cpp index 545b97d956..71733227ac 100644 --- a/engines/mohawk/myst_stacks/intro.cpp +++ b/engines/mohawk/myst_stacks/intro.cpp @@ -127,9 +127,9 @@ void Intro::introMovies_run() { break; default: if (_vm->getFeatures() & GF_DEMO) - _vm->changeToCard(2001, true); + _vm->changeToCard(2001, kTransitionRightToLeft); else - _vm->changeToCard(2, true); + _vm->changeToCard(2, kTransitionRightToLeft); } } @@ -148,7 +148,7 @@ void Intro::mystLinkBook_run() { _vm->_gfx->copyBackBufferToScreen(Common::Rect(544, 333)); } } else if (!_linkBookMovie->isPlaying()) { - _vm->changeToCard(5, true); + _vm->changeToCard(5, kTransitionRightToLeft); } } diff --git a/engines/mohawk/myst_stacks/mechanical.cpp b/engines/mohawk/myst_stacks/mechanical.cpp index 79de03308c..ce6f902851 100644 --- a/engines/mohawk/myst_stacks/mechanical.cpp +++ b/engines/mohawk/myst_stacks/mechanical.cpp @@ -39,8 +39,17 @@ Mechanical::Mechanical(MohawkEngine_Myst *vm) : MystScriptParser(vm), _state(vm->_gameState->_mechanical) { setupOpcodes(); + _elevatorGoingMiddle = false; + _mystStaircaseState = false; _fortressPosition = 0; + _fortressRotationSpeed = 0; + _fortressSimulationSpeed = 0; + _gearsWereRunning = false; + + _fortressRotationShortMovieWorkaround = false; + _fortressRotationShortMovieCount = 0; + _fortressRotationShortMovieLast = 0; } Mechanical::~Mechanical() { @@ -74,9 +83,9 @@ void Mechanical::setupOpcodes() { OPCODE(121, o_elevatorWindowMovie); OPCODE(122, o_elevatorGoMiddle); OPCODE(123, o_elevatorTopMovie); - OPCODE(124, opcode_124); + OPCODE(124, o_fortressRotationSetPosition); OPCODE(125, o_mystStaircaseMovie); - OPCODE(126, opcode_126); + OPCODE(126, o_elevatorWaitTimeout); OPCODE(127, o_crystalEnterYellow); OPCODE(128, o_crystalLeaveYellow); OPCODE(129, o_crystalEnterGreen); @@ -103,7 +112,6 @@ void Mechanical::setupOpcodes() { void Mechanical::disablePersistentScripts() { _fortressSimulationRunning = false; _elevatorRotationLeverMoving = false; - _elevatorGoingMiddle = false; _birdSinging = false; _fortressRotationRunning = false; } @@ -599,30 +607,33 @@ void Mechanical::elevatorGoMiddle_run() { _vm->_gfx->copyBackBufferToScreen(Common::Rect(10, 137, 61, 165)); _vm->_system->updateScreen(); } - } else if (_elevatorInCabin) { + } else { _elevatorTooLate = true; - - // Elevator going to middle animation - _vm->_cursor->hideCursor(); - _vm->_sound->playSoundBlocking(11120); - _vm->_gfx->copyImageToBackBuffer(6118, Common::Rect(544, 333)); - _vm->_sound->replaceSoundMyst(12120); - _vm->_gfx->runTransition(2, Common::Rect(177, 0, 370, 333), 25, 0); - _vm->_sound->playSoundBlocking(13120); - _vm->_sound->replaceSoundMyst(8120); - _vm->_gfx->copyImageToBackBuffer(6327, Common::Rect(544, 333)); - _vm->_system->delayMillis(500); - _vm->_sound->replaceSoundMyst(9120); - static uint16 moviePos[2] = { 3540, 5380 }; - o_elevatorWindowMovie(121, 0, 2, moviePos); - _vm->_gfx->copyBackBufferToScreen(Common::Rect(544, 333)); - _vm->_sound->replaceSoundMyst(10120); - _vm->_cursor->showCursor(); - _elevatorGoingMiddle = false; - _elevatorPosition = 1; - _vm->changeToCard(6327, true); + if (_elevatorInCabin) { + + // Elevator going to middle animation + _vm->_cursor->hideCursor(); + _vm->_sound->playSoundBlocking(11120); + _vm->_gfx->copyImageToBackBuffer(6118, Common::Rect(544, 333)); + _vm->_sound->replaceSoundMyst(12120); + _vm->_gfx->runTransition(kTransitionSlideToLeft, Common::Rect(177, 0, 370, 333), 25, 0); + _vm->_sound->playSoundBlocking(13120); + _vm->_sound->replaceSoundMyst(8120); + _vm->_gfx->copyImageToBackBuffer(6327, Common::Rect(544, 333)); + _vm->_system->delayMillis(500); + _vm->_sound->replaceSoundMyst(9120); + static uint16 moviePos[2] = { 3540, 5380 }; + o_elevatorWindowMovie(121, 0, 2, moviePos); + _vm->_gfx->copyBackBufferToScreen(Common::Rect(544, 333)); + _vm->_sound->replaceSoundMyst(10120); + _vm->_cursor->showCursor(); + + _elevatorPosition = 1; + + _vm->changeToCard(6327, kTransitionRightToLeft); + } } } } @@ -638,16 +649,18 @@ void Mechanical::o_elevatorTopMovie(uint16 op, uint16 var, uint16 argc, uint16 * _vm->_video->waitUntilMovieEnds(window); } -void Mechanical::opcode_124(uint16 op, uint16 var, uint16 argc, uint16 *argv) { - varUnusedCheck(op, var); +void Mechanical::o_fortressRotationSetPosition(uint16 op, uint16 var, uint16 argc, uint16 *argv) { + debugC(kDebugScript, "Opcode %d: Set fortress position", op); - if (argc == 0) { - // Used by Card 6156 (Fortress Rotation Controls) - // Called when Red Exit Button Pressed to raise Elevator + VideoHandle gears = _fortressRotationGears->playMovie(); + uint32 moviePosition = Audio::Timestamp(_vm->_video->getTime(gears), 600).totalNumberOfFrames(); + + // Myst ME short movie workaround, explained in o_fortressRotation_init + if (_fortressRotationShortMovieWorkaround) { + moviePosition += 3600 * _fortressRotationShortMovieCount; + } - // TODO: Fill in Code... - } else - unknown(op, var, argc, argv); + _fortressPosition = (moviePosition + 900) / 1800 % 4; } void Mechanical::o_mystStaircaseMovie(uint16 op, uint16 var, uint16 argc, uint16 *argv) { @@ -656,17 +669,14 @@ void Mechanical::o_mystStaircaseMovie(uint16 op, uint16 var, uint16 argc, uint16 _vm->_video->playMovieBlocking(_vm->wrapMovieFilename("sstairs", kMechanicalStack), 199, 108); } -void Mechanical::opcode_126(uint16 op, uint16 var, uint16 argc, uint16 *argv) { - varUnusedCheck(op, var); +void Mechanical::o_elevatorWaitTimeout(uint16 op, uint16 var, uint16 argc, uint16 *argv) { + debugC(kDebugScript, "Opcode %d: Wait for the elevator to go middle", op); - if (argc == 0) { - // Used by Card 6120 (Fortress Elevator) - // Called when Red Exit Button Pressed to raise Elevator and - // exit is clicked... - - // TODO: Fill in Code... - } else - unknown(op, var, argc, argv); + // Wait while the elevator times out + while (_elevatorGoingMiddle) { + runPersistentScripts(); + _vm->skippableWait(10); + } } void Mechanical::o_crystalEnterYellow(uint16 op, uint16 var, uint16 argc, uint16 *argv) { @@ -776,8 +786,76 @@ void Mechanical::o_elevatorRotation_init(uint16 op, uint16 var, uint16 argc, uin } void Mechanical::fortressRotation_run() { - // Used for Card 6156 (Fortress Rotation Controls) - // TODO: Fill in function... + VideoHandle gears = _fortressRotationGears->playMovie(); + + double oldRate = _vm->_video->getVideoRate(gears).toDouble(); + + uint32 moviePosition = Audio::Timestamp(_vm->_video->getTime(gears), 600).totalNumberOfFrames(); + + // Myst ME short movie workaround, explained in o_fortressRotation_init + if (_fortressRotationShortMovieWorkaround) { + // Detect if we just looped + if (ABS<int32>(_fortressRotationShortMovieLast - 3680) < 50 + && ABS<int32>(moviePosition) < 50) { + _fortressRotationShortMovieCount++; + } + + _fortressRotationShortMovieLast = moviePosition; + + // Simulate longer movie + moviePosition += 3600 * _fortressRotationShortMovieCount; + } + + int32 positionInQuarter = 900 - (moviePosition + 900) % 1800; + + // Are the gears moving? + if (oldRate >= 0.1 || ABS<int32>(positionInQuarter) >= 30 || _fortressRotationBrake) { + + double newRate = oldRate; + if (_fortressRotationBrake && (double)_fortressRotationBrake * 0.2 > oldRate) { + newRate += 0.1; + } + + // Don't let the gears get stuck between two fortress positions + if (ABS<double>(oldRate) <= 0.05) { + if (oldRate <= 0.0) { + newRate += oldRate; + } else { + newRate -= oldRate; + } + } else { + if (oldRate <= 0.0) { + newRate += 0.05; + } else { + newRate -= 0.05; + } + } + + // Adjust speed accordingly to acceleration lever + newRate += (double) (positionInQuarter / 1500.0) + * (double) (9 - _fortressRotationSpeed) / 9.0; + + newRate = CLIP<double>(newRate, -2.5, 2.5); + + _vm->_video->setVideoRate(gears, Common::Rational(newRate * 1000.0, 1000)); + + _gearsWereRunning = true; + } else if (_gearsWereRunning) { + // The fortress has stopped. Set its new position + _fortressPosition = (moviePosition + 900) / 1800 % 4; + + _vm->_video->setVideoRate(gears, 0); + + if (!_fortressRotationShortMovieWorkaround) { + _vm->_video->seekToTime(gears, Audio::Timestamp(0, 1800 * _fortressPosition, 600)); + } else { + _vm->_video->seekToTime(gears, Audio::Timestamp(0, 1800 * (_fortressPosition % 2), 600)); + } + + _vm->_sound->playSoundBlocking(_fortressRotationSounds[_fortressPosition]); + + _gearsWereRunning = false; + } } void Mechanical::o_fortressRotation_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) { @@ -785,6 +863,11 @@ void Mechanical::o_fortressRotation_init(uint16 op, uint16 var, uint16 argc, uin _fortressRotationGears = static_cast<MystResourceType6 *>(_invokingResource); + VideoHandle gears = _fortressRotationGears->playMovie(); + _vm->_video->setVideoLooping(gears, true); + _vm->_video->seekToTime(gears, Audio::Timestamp(0, 1800 * _fortressPosition, 600)); + _vm->_video->setVideoRate(gears, 0); + _fortressRotationSounds[0] = argv[0]; _fortressRotationSounds[1] = argv[1]; _fortressRotationSounds[2] = argv[2]; @@ -792,12 +875,113 @@ void Mechanical::o_fortressRotation_init(uint16 op, uint16 var, uint16 argc, uin _fortressRotationBrake = 0; + // WORKAROUND for the tower rotation bug in Myst ME. + // The original engine only allowed to visit two out of the three small islands, + // preventing the game from being fully completable. + // The fortress rotation is computed from the current position in the movie + // hcgears.mov. The version of this movie that shipped with the ME edition is + // too short to allow to visit all the islands. + // ScummVM simulates a longer movie by counting the number of times the movie + // looped and adding that time to the current movie position. + // Hence allowing the fortress position to be properly computed. + uint32 movieDuration = _vm->_video->getDuration(gears).convertToFramerate(600).totalNumberOfFrames(); + if (movieDuration == 3680) { + _fortressRotationShortMovieWorkaround = true; + _fortressRotationShortMovieCount = 0; + _fortressRotationShortMovieLast = 0; + } + _fortressRotationRunning = true; + _gearsWereRunning = false; } void Mechanical::fortressSimulation_run() { - // Used for Card 6044 (Fortress Rotation Simulator) - // TODO: Fill in function... + if (_fortressSimulationInit) { + // Init sequence + _vm->_sound->replaceBackgroundMyst(_fortressSimulationStartSound1, 65535); + _vm->skippableWait(5000); + _vm->_sound->replaceSoundMyst(_fortressSimulationStartSound2); + + // Update movie while the sound is playing + VideoHandle startup = _fortressSimulationStartup->playMovie(); + while (_vm->_sound->isPlaying(_fortressSimulationStartSound2)) { + if (_vm->_video->updateMovies()) + _vm->_system->updateScreen(); + + _vm->_system->delayMillis(10); + } + _vm->_sound->replaceBackgroundMyst(_fortressSimulationStartSound1, 65535); + _vm->_video->waitUntilMovieEnds(startup); + _vm->_sound->stopBackgroundMyst(); + _vm->_sound->replaceSoundMyst(_fortressSimulationStartSound2); + + + Common::Rect src = Common::Rect(0, 0, 176, 176); + Common::Rect dst = Common::Rect(187, 3, 363, 179); + _vm->_gfx->copyImageSectionToBackBuffer(6046, src, dst); + _vm->_gfx->copyBackBufferToScreen(dst); + _vm->_system->updateScreen(); + + _fortressSimulationStartup->pauseMovie(true); + VideoHandle holo = _fortressSimulationHolo->playMovie(); + _vm->_video->setVideoLooping(holo, true); + _vm->_video->setVideoRate(holo, 0); + + _vm->_cursor->showCursor(); + + _fortressSimulationInit = false; + } else { + VideoHandle holo = _fortressSimulationHolo->playMovie(); + + double oldRate = _vm->_video->getVideoRate(holo).toDouble(); + + uint32 moviePosition = Audio::Timestamp(_vm->_video->getTime(holo), 600).totalNumberOfFrames(); + + int32 positionInQuarter = 900 - (moviePosition + 900) % 1800; + + // Are the gears moving? + if (oldRate >= 0.1 || ABS<int32>(positionInQuarter) >= 30 || _fortressSimulationBrake) { + + double newRate = oldRate; + if (_fortressSimulationBrake && (double)_fortressSimulationBrake * 0.2 > oldRate) { + newRate += 0.1; + } + + // Don't let the gears get stuck between two fortress positions + if (ABS<double>(oldRate) <= 0.05) { + if (oldRate <= 0.0) { + newRate += oldRate; + } else { + newRate -= oldRate; + } + } else { + if (oldRate <= 0.0) { + newRate += 0.05; + } else { + newRate -= 0.05; + } + } + + // Adjust speed accordingly to acceleration lever + newRate += (double) (positionInQuarter / 1500.0) + * (double) (9 - _fortressSimulationSpeed) / 9.0; + + newRate = CLIP<double>(newRate, -2.5, 2.5); + + _vm->_video->setVideoRate(holo, Common::Rational(newRate * 1000.0, 1000)); + + _gearsWereRunning = true; + } else if (_gearsWereRunning) { + // The fortress has stopped. Set its new position + uint16 simulationPosition = (moviePosition + 900) / 1800 % 4; + + _vm->_video->setVideoRate(holo, 0); + _vm->_video->seekToTime(holo, Audio::Timestamp(0, 1800 * simulationPosition, 600)); + _vm->_sound->playSoundBlocking( _fortressRotationSounds[simulationPosition]); + + _gearsWereRunning = false; + } + } } void Mechanical::o_fortressSimulation_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) { @@ -816,6 +1000,10 @@ void Mechanical::o_fortressSimulation_init(uint16 op, uint16 var, uint16 argc, u _fortressSimulationBrake = 0; _fortressSimulationRunning = true; + _gearsWereRunning = false; + _fortressSimulationInit = true; + + _vm->_cursor->hideCursor(); } void Mechanical::o_fortressSimulationStartup_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) { diff --git a/engines/mohawk/myst_stacks/mechanical.h b/engines/mohawk/myst_stacks/mechanical.h index 3bd7f2d71b..7f3d5143e4 100644 --- a/engines/mohawk/myst_stacks/mechanical.h +++ b/engines/mohawk/myst_stacks/mechanical.h @@ -80,9 +80,9 @@ private: DECLARE_OPCODE(o_elevatorWindowMovie); DECLARE_OPCODE(o_elevatorGoMiddle); DECLARE_OPCODE(o_elevatorTopMovie); - DECLARE_OPCODE(opcode_124); + DECLARE_OPCODE(o_fortressRotationSetPosition); DECLARE_OPCODE(o_mystStaircaseMovie); - DECLARE_OPCODE(opcode_126); + DECLARE_OPCODE(o_elevatorWaitTimeout); DECLARE_OPCODE(o_crystalEnterYellow); DECLARE_OPCODE(o_crystalEnterGreen); DECLARE_OPCODE(o_crystalEnterRed); @@ -104,13 +104,19 @@ private: bool _mystStaircaseState; // 76 bool _fortressRotationRunning; + bool _gearsWereRunning; uint16 _fortressRotationSpeed; // 78 uint16 _fortressRotationBrake; // 80 uint16 _fortressPosition; // 82 uint16 _fortressRotationSounds[4]; // 86 to 92 MystResourceType6 *_fortressRotationGears; // 172 + bool _fortressRotationShortMovieWorkaround; + uint32 _fortressRotationShortMovieCount; + uint32 _fortressRotationShortMovieLast; + bool _fortressSimulationRunning; + bool _fortressSimulationInit; // 94 uint16 _fortressSimulationSpeed; // 96 uint16 _fortressSimulationBrake; // 98 uint16 _fortressSimulationStartSound1; // 102 diff --git a/engines/mohawk/myst_stacks/myst.cpp b/engines/mohawk/myst_stacks/myst.cpp index c1ddc74c82..f17d765c99 100644 --- a/engines/mohawk/myst_stacks/myst.cpp +++ b/engines/mohawk/myst_stacks/myst.cpp @@ -51,6 +51,8 @@ Myst::Myst(MohawkEngine_Myst *vm) : _dockVaultState = 0; _cabinDoorOpened = 0; _cabinMatchState = 2; + _cabinGaugeMovie = NULL_VID_HANDLE; + _cabinFireMovie = NULL_VID_HANDLE; _matchBurning = false; _tree = 0; _treeAlcove = 0; @@ -189,7 +191,7 @@ void Myst::setupOpcodes() { OPCODE(215, o_gulls2_init); OPCODE(216, o_treeCard_init); OPCODE(217, o_treeEntry_init); - OPCODE(218, opcode_218); + OPCODE(218, o_boilerMovies_init); OPCODE(219, o_rocketSliders_init); OPCODE(220, o_rocketLinkVideo_init); OPCODE(221, o_greenBook_init); @@ -202,7 +204,7 @@ void Myst::setupOpcodes() { OPCODE(303, NOP); OPCODE(304, o_treeCard_exit); OPCODE(305, o_treeEntry_exit); - OPCODE(306, NOP); + OPCODE(306, o_boiler_exit); OPCODE(307, o_generatorControlRoom_exit); OPCODE(308, NOP); OPCODE(309, NOP); @@ -608,6 +610,8 @@ uint16 Myst::getVar(uint16 var) { return 1; case 302: // Green Book Opened Before Flag return _state.greenBookOpenedBefore; + case 303: // Library Bookcase status changed + return _libraryBookcaseChanged; case 304: // Tower Rotation Map Initialized return _towerRotationMapInitialized; case 305: // Cabin Boiler Lit @@ -1041,7 +1045,7 @@ void Myst::o_bookGivePage(uint16 op, uint16 var, uint16 argc, uint16 *argv) { // No page or white page if (!_globals.heldPage || _globals.heldPage == 13) { - _vm->changeToCard(cardIdBookCover, true); + _vm->changeToCard(cardIdBookCover, kTransitionDissolve); return; } @@ -1083,7 +1087,7 @@ void Myst::o_bookGivePage(uint16 op, uint16 var, uint16 argc, uint16 *argv) { // Wrong book if (bookVar != var) { - _vm->changeToCard(cardIdBookCover, true); + _vm->changeToCard(cardIdBookCover, kTransitionDissolve); return; } @@ -1109,9 +1113,9 @@ void Myst::o_bookGivePage(uint16 op, uint16 var, uint16 argc, uint16 *argv) { else _globals.currentAge = 10; - _vm->changeToCard(cardIdLose, true); + _vm->changeToCard(cardIdLose, kTransitionDissolve); } else { - _vm->changeToCard(cardIdBookCover, true); + _vm->changeToCard(cardIdBookCover, kTransitionDissolve); } } @@ -1298,7 +1302,7 @@ void Myst::imagerValidation_run() { if (_imagerValidationStep == 11) { _imagerValidationStep = 0; - _vm->changeToCard(_imagerValidationCard, true); + _vm->changeToCard(_imagerValidationCard, kTransitionBottomToTop); } else { _startTime = time + 100; } @@ -1473,10 +1477,10 @@ void Myst::o_cabinSafeHandleMove(uint16 op, uint16 var, uint16 argc, uint16 *arg if (soundId) _vm->_sound->replaceSoundMyst(soundId); - _vm->changeToCard(4103, false); + _vm->changeToCard(4103, kNoTransition); Common::Rect screenRect = Common::Rect(544, 333); - _vm->_gfx->runTransition(0, screenRect, 2, 5); + _vm->_gfx->runTransition(kTransitionLeftToRight, screenRect, 2, 5); } _tempVar = 1; } else { @@ -1869,17 +1873,50 @@ void Myst::o_boilerLightPilot(uint16 op, uint16 var, uint16 argc, uint16 *argv) _state.cabinPilotLightLit = 1; _vm->redrawArea(98); + boilerFireUpdate(false); + // Put out match _matchGoOutTime = _vm->_system->getMillis(); if (_state.cabinValvePosition > 0) _vm->_sound->replaceBackgroundMyst(8098, 49152); - if (_state.cabinValvePosition > 12) + if (_state.cabinValvePosition > 12) { + // Compute the speed of the gauge to synchronize it with the next tree move + uint32 delay = treeNextMoveDelay(_state.cabinValvePosition); + Common::Rational rate = boilerComputeGaugeRate(_state.cabinValvePosition, delay); + boilerResetGauge(rate); + _state.treeLastMoveTime = _vm->_system->getMillis(); + } + } +} - // TODO: Complete. Play movies +Common::Rational Myst::boilerComputeGaugeRate(uint16 pressure, uint32 delay) { + Common::Rational rate = Common::Rational(2088, delay); + if (pressure < 12) + return -rate; + else + return rate; +} + +void Myst::boilerResetGauge(const Common::Rational &rate) { + if (_vm->_video->endOfVideo(_cabinGaugeMovie)) { + if (_vm->getCurCard() == 4098) { + _cabinGaugeMovie = _vm->_video->playMovie(_vm->wrapMovieFilename("cabingau", kMystStack), 243, 96); + } else { + _cabinGaugeMovie = _vm->_video->playMovie(_vm->wrapMovieFilename("cabcgfar", kMystStack), 254, 136); + } } + + Audio::Timestamp goTo; + if (rate > 0) + goTo = Audio::Timestamp(0, 0, 600); + else + goTo = _vm->_video->getDuration(_cabinGaugeMovie); + + _vm->_video->seekToTime(_cabinGaugeMovie, goTo); + _vm->_video->setVideoRate(_cabinGaugeMovie, rate); } void Myst::o_boilerIncreasePressureStop(uint16 op, uint16 var, uint16 argc, uint16 *argv) { @@ -1893,7 +1930,12 @@ void Myst::o_boilerIncreasePressureStop(uint16 op, uint16 var, uint16 argc, uint if (_state.cabinValvePosition > 0) _vm->_sound->replaceBackgroundMyst(8098, 49152); - // TODO: Play movies + if (!_vm->_video->endOfVideo(_cabinGaugeMovie)) { + uint16 delay = treeNextMoveDelay(_state.cabinValvePosition); + Common::Rational rate = boilerComputeGaugeRate(_state.cabinValvePosition, delay); + _vm->_video->setVideoRate(_cabinGaugeMovie, rate); + } + } else if (_state.cabinValvePosition > 0) _vm->_sound->replaceBackgroundMyst(4098, _state.cabinValvePosition << 10); } @@ -1903,7 +1945,8 @@ void Myst::boilerPressureIncrease_run() { if (!_vm->_sound->isPlaying(5098) && _state.cabinValvePosition < 25) { _state.cabinValvePosition++; if (_state.cabinValvePosition == 1) { - // TODO: Play fire movie + // Set fire to high + boilerFireUpdate(false); // Draw fire _vm->redrawArea(305); @@ -1927,7 +1970,8 @@ void Myst::boilerPressureDecrease_run() { if (!_vm->_sound->isPlaying(5098) && _state.cabinValvePosition > 0) { _state.cabinValvePosition--; if (_state.cabinValvePosition == 0) { - // TODO: Play fire movie + // Set fire to low + boilerFireUpdate(false); // Draw fire _vm->redrawArea(305); @@ -1961,7 +2005,12 @@ void Myst::o_boilerDecreasePressureStop(uint16 op, uint16 var, uint16 argc, uint if (_state.cabinValvePosition > 0) _vm->_sound->replaceBackgroundMyst(8098, 49152); - // TODO: Play movies + if (!_vm->_video->endOfVideo(_cabinGaugeMovie)) { + uint16 delay = treeNextMoveDelay(_state.cabinValvePosition); + Common::Rational rate = boilerComputeGaugeRate(_state.cabinValvePosition, delay); + _vm->_video->setVideoRate(_cabinGaugeMovie, rate); + } + } else { if (_state.cabinValvePosition > 0) _vm->_sound->replaceBackgroundMyst(4098, _state.cabinValvePosition << 10); @@ -2067,6 +2116,11 @@ void Myst::tree_run() { // Check if alcove is accessible treeSetAlcoveAccessible(); + if (_cabinGaugeMovie != NULL_VID_HANDLE) { + Common::Rational rate = boilerComputeGaugeRate(pressure, delay); + boilerResetGauge(rate); + } + _state.treeLastMoveTime = time; } } @@ -2968,7 +3022,12 @@ void Myst::clockReset() { _vm->_system->delayMillis(1000); _vm->_sound->replaceSoundMyst(7113); - // TODO: Play cl1wggat backwards + // Gear closing movie + VideoHandle handle = _vm->_video->playMovie(_vm->wrapMovieFilename("cl1wggat", kMystStack) , 195, 225); + _vm->_video->seekToTime(handle, _vm->_video->getDuration(handle)); + _vm->_video->setVideoRate(handle, -1); + _vm->_video->waitUntilMovieEnds(handle); + // Redraw gear _state.gearsOpen = 0; _vm->redrawArea(40); @@ -2980,16 +3039,9 @@ void Myst::clockReset() { void Myst::clockResetWeight() { _clockWeightVideo = _vm->_video->playMovie(_vm->wrapMovieFilename("cl1wlfch", kMystStack) , 124, 0); - if (!(_vm->getFeatures() & GF_ME)) { - // Set video bounds, weight going up - _vm->_video->setVideoBounds(_clockWeightVideo, - Audio::Timestamp(0, 2214 * 2 - _clockWeightPosition, 600), - Audio::Timestamp(0, 2214 * 2, 600)); - } else { - //FIXME: Needs QT backwards playing, for now just display the weight up - warning("Weight going back up not implemented"); - _vm->_video->drawVideoFrame(_clockWeightVideo, Audio::Timestamp(0, 0, 600)); - } + // Play the movie backwards, weight going up + _vm->_video->seekToTime(_clockWeightVideo, Audio::Timestamp(0, _clockWeightPosition, 600)); + _vm->_video->setVideoRate(_clockWeightVideo, -1); // Reset position _clockWeightPosition = 0; @@ -3246,7 +3298,7 @@ void Myst::libraryBookcaseTransform_run(void) { if (_state.libraryBookcaseDoor) { _vm->_gfx->copyImageSectionToBackBuffer(11179, Common::Rect(0, 0, 106, 81), Common::Rect(0, 72, 106, 153)); - _vm->_gfx->runTransition(6, Common::Rect(0, 72, 106, 153), 5, 10); + _vm->_gfx->runTransition(kTransitionBottomToTop, Common::Rect(0, 72, 106, 153), 5, 10); _vm->_sound->playSoundBlocking(7348); _vm->_sound->replaceBackgroundMyst(4348, 16384); } else { @@ -3510,22 +3562,60 @@ void Myst::o_treeEntry_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) { treeSetAlcoveAccessible(); } -void Myst::opcode_218(uint16 op, uint16 var, uint16 argc, uint16 *argv) { - varUnusedCheck(op, var); +void Myst::o_boilerMovies_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) { + debugC(kDebugScript, "Opcode %d: Boiler movies init", op); - // Used for Card 4097 (Cabin Boiler) - // TODO: Fill in logic - if (false) { - _vm->_video->playMovieBlocking(_vm->wrapMovieFilename("cabfirfr", kMystStack), 254, 244); - _vm->_video->playMovieBlocking(_vm->wrapMovieFilename("cabcgfar", kMystStack), 254, 138); + boilerFireInit(); + boilerGaugeInit(); +} + +void Myst::boilerFireInit() { + if (_vm->getCurCard() == 4098) { + _cabinFireMovie = _vm->_video->playMovie(_vm->wrapMovieFilename("cabfire", kMystStack), 240, 279, true); + _vm->_video->pauseMovie(_cabinFireMovie, true); + + _vm->redrawArea(305); + boilerFireUpdate(true); + } else { + if (_state.cabinPilotLightLit == 1 && _state.cabinValvePosition >= 1) { + _cabinFireMovie = _vm->_video->playMovie(_vm->wrapMovieFilename("cabfirfr", kMystStack), 254, 244, true); + } + } +} + +void Myst::boilerFireUpdate(bool init) { + uint position = _vm->_video->getTime(_cabinFireMovie); + + if (_state.cabinPilotLightLit == 1) { + if (_state.cabinValvePosition == 0) { + if (position > (uint)Audio::Timestamp(0, 200, 600).msecs() || init) { + _vm->_video->setVideoBounds(_cabinFireMovie, Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 100, 600)); + _vm->_video->pauseMovie(_cabinFireMovie, false); + } + } else { + if (position < (uint)Audio::Timestamp(0, 200, 600).msecs() || init) { + _vm->_video->setVideoBounds(_cabinFireMovie, Audio::Timestamp(0, 201, 600), Audio::Timestamp(0, 1900, 600)); + _vm->_video->pauseMovie(_cabinFireMovie, false); + } + } } +} - // Used for Card 4098 (Cabin Boiler) - // TODO: Fill in logic - if (false) { - _vm->_video->playMovieBlocking(_vm->wrapMovieFilename("cabfire", kMystStack), 240, 279); - _vm->_video->playMovieBlocking(_vm->wrapMovieFilename("cabingau", kMystStack), 243, 97); +void Myst::boilerGaugeInit() { + if (_vm->getCurCard() == 4098) { + _cabinGaugeMovie = _vm->_video->playMovie(_vm->wrapMovieFilename("cabingau", kMystStack), 243, 96); + } else { + _cabinGaugeMovie = _vm->_video->playMovie(_vm->wrapMovieFilename("cabcgfar", kMystStack), 254, 136); } + + Audio::Timestamp frame; + + if (_state.cabinPilotLightLit == 1 && _state.cabinValvePosition > 12) + frame = _vm->_video->getDuration(_cabinGaugeMovie); + else + frame = Audio::Timestamp(0, 0, 600); + + _vm->_video->drawVideoFrame(_cabinGaugeMovie, frame); } void Myst::o_rocketSliders_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) { @@ -3648,6 +3738,13 @@ void Myst::o_treeEntry_exit(uint16 op, uint16 var, uint16 argc, uint16 *argv) { _treeAlcove = 0; } +void Myst::o_boiler_exit(uint16 op, uint16 var, uint16 argc, uint16 *argv) { + debugC(kDebugScript, "Opcode %d: Exit boiler card", op); + + _cabinGaugeMovie = NULL_VID_HANDLE; + _cabinFireMovie = NULL_VID_HANDLE; +} + void Myst::o_generatorControlRoom_exit(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Generator room exit", op); diff --git a/engines/mohawk/myst_stacks/myst.h b/engines/mohawk/myst_stacks/myst.h index e9bff08cb4..de88843d59 100644 --- a/engines/mohawk/myst_stacks/myst.h +++ b/engines/mohawk/myst_stacks/myst.h @@ -43,7 +43,7 @@ public: virtual void disablePersistentScripts(); virtual void runPersistentScripts(); -private: +protected: void setupOpcodes(); uint16 getVar(uint16 var); void toggleVar(uint16 var); @@ -52,7 +52,7 @@ private: virtual uint16 getMap() { return 9934; } void towerRotationMap_run(); - void libraryBookcaseTransform_run(); + virtual void libraryBookcaseTransform_run(); void generatorControlRoom_run(); void opcode_212_run(); void libraryCombinationBook_run(); @@ -174,7 +174,7 @@ private: DECLARE_OPCODE(o_gulls2_init); DECLARE_OPCODE(o_treeCard_init); DECLARE_OPCODE(o_treeEntry_init); - DECLARE_OPCODE(opcode_218); + DECLARE_OPCODE(o_boilerMovies_init); DECLARE_OPCODE(o_rocketSliders_init); DECLARE_OPCODE(o_rocketLinkVideo_init); DECLARE_OPCODE(o_greenBook_init); @@ -183,6 +183,7 @@ private: DECLARE_OPCODE(o_bookAddSpecialPage_exit); DECLARE_OPCODE(o_treeCard_exit); DECLARE_OPCODE(o_treeEntry_exit); + DECLARE_OPCODE(o_boiler_exit); DECLARE_OPCODE(o_generatorControlRoom_exit); @@ -259,6 +260,9 @@ private: uint16 _cabinMatchState; // 60 uint32 _matchGoOutTime; // 144 + VideoHandle _cabinFireMovie; // 240 + VideoHandle _cabinGaugeMovie; // 244 + bool _boilerPressureIncreasing; bool _boilerPressureDecreasing; bool _basementPressureIncreasing; @@ -317,6 +321,12 @@ private: Common::Point towerRotationMapComputeCoords(const Common::Point ¢er, uint16 angle); void towerRotationMapDrawLine(const Common::Point ¢er, const Common::Point &end); + void boilerFireInit(); + void boilerFireUpdate(bool init); + void boilerGaugeInit(); + Common::Rational boilerComputeGaugeRate(uint16 pressure, uint32 delay); + void boilerResetGauge(const Common::Rational &rate); + void treeSetAlcoveAccessible(); uint32 treeNextMoveDelay(uint16 pressure); diff --git a/engines/mohawk/myst_stacks/preview.cpp b/engines/mohawk/myst_stacks/preview.cpp index 0b8dcf897a..75e870281e 100644 --- a/engines/mohawk/myst_stacks/preview.cpp +++ b/engines/mohawk/myst_stacks/preview.cpp @@ -60,6 +60,7 @@ void Preview::setupOpcodes() { OVERRIDE_OPCODE(199, o_speechStop); // "Init" Opcodes + OVERRIDE_OPCODE(209, o_libraryBookcaseTransformDemo_init); OPCODE(298, o_speech_init); OPCODE(299, o_library_init); } @@ -139,7 +140,7 @@ void Preview::speech_run() { break; case 1: // Open book if (_currentCue >= 1) { - _vm->changeToCard(3001, true); + _vm->changeToCard(3001, kTransitionDissolve); _speechStep++; } @@ -147,7 +148,7 @@ void Preview::speech_run() { case 2: // Go to Myst if (_currentCue >= 2) { _vm->_gfx->fadeToBlack(); - _vm->changeToCard(3002, false); + _vm->changeToCard(3002, kNoTransition); _vm->_gfx->fadeFromBlack(); _speechStep++; @@ -164,7 +165,7 @@ void Preview::speech_run() { if (_currentCue >= 4) { _library->drawConditionalDataToScreen(0); - _vm->changeToCard(3003, true); + _vm->changeToCard(3003, kTransitionDissolve); _speechNextTime = time + 2000; _speechStep++; @@ -181,7 +182,7 @@ void Preview::speech_run() { if (time < _speechNextTime) break; - _vm->changeToCard(3004, true); + _vm->changeToCard(3004, kTransitionDissolve); _speechNextTime = time + 2000; _speechStep++; break; @@ -190,7 +191,7 @@ void Preview::speech_run() { break; _vm->_gfx->fadeToBlack(); - _vm->changeToCard(3005, false); + _vm->changeToCard(3005, kNoTransition); _vm->_gfx->fadeFromBlack(); _speechNextTime = time + 1000; _speechStep++; @@ -205,7 +206,7 @@ void Preview::speech_run() { if (time < _speechNextTime) break; - _vm->changeToCard(3006 + _speechStep - 7, true); + _vm->changeToCard(3006 + _speechStep - 7, kTransitionDissolve); _speechNextTime = time + 2000; _speechStep++; break; @@ -213,7 +214,7 @@ void Preview::speech_run() { if (time < _speechNextTime) break; - _vm->changeToCard(4329, true); + _vm->changeToCard(4329, kTransitionDissolve); _speechRunning = false; _globals.currentAge = 2; @@ -241,5 +242,22 @@ void Preview::o_library_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) { _library = static_cast<MystResourceType8 *>(_invokingResource); } +void Preview::o_libraryBookcaseTransformDemo_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) { + if (_libraryBookcaseChanged) { + MystResourceType7 *resource = static_cast<MystResourceType7 *>(_invokingResource); + _libraryBookcaseMovie = static_cast<MystResourceType6 *>(resource->getSubResource(getVar(303))); + _libraryBookcaseSoundId = argv[0]; + _libraryBookcaseMoving = true; + } +} + +void Preview::libraryBookcaseTransform_run() { + if (_libraryBookcaseChanged) + _state.libraryBookcaseDoor = !_state.libraryBookcaseDoor; + + Myst::libraryBookcaseTransform_run(); +} + + } // End of namespace MystStacks } // End of namespace Mohawk diff --git a/engines/mohawk/myst_stacks/preview.h b/engines/mohawk/myst_stacks/preview.h index 1e4ff3efb4..706220e8ed 100644 --- a/engines/mohawk/myst_stacks/preview.h +++ b/engines/mohawk/myst_stacks/preview.h @@ -51,6 +51,7 @@ private: DECLARE_OPCODE(o_stayHere); DECLARE_OPCODE(o_speechStop); + DECLARE_OPCODE(o_libraryBookcaseTransformDemo_init); DECLARE_OPCODE(o_speech_init); DECLARE_OPCODE(o_library_init); @@ -65,6 +66,8 @@ private: void speech_run(); void speechUpdateCue(); + + void libraryBookcaseTransform_run(); }; } // End of namespace MystStacks diff --git a/engines/mohawk/myst_stacks/selenitic.cpp b/engines/mohawk/myst_stacks/selenitic.cpp index 1473742259..a941b14eaa 100644 --- a/engines/mohawk/myst_stacks/selenitic.cpp +++ b/engines/mohawk/myst_stacks/selenitic.cpp @@ -729,11 +729,11 @@ void Selenitic::o_mazeRunnerDoorButton(uint16 op, uint16 var, uint16 argc, uint1 uint16 cardIdEntry = argv[1]; if (_mazeRunnerPosition == 288) { - _vm->changeToCard(cardIdEntry, false); + _vm->changeToCard(cardIdEntry, kNoTransition); _vm->_sound->replaceSoundMyst(cardIdEntry); animatedUpdate(argv[2], &argv[3], 10); } else if (_mazeRunnerPosition == 289) { - _vm->changeToCard(cardIdExit, false); + _vm->changeToCard(cardIdExit, kNoTransition); _vm->_sound->replaceSoundMyst(cardIdExit); animatedUpdate(argv[2], &argv[3], 10); } @@ -895,9 +895,9 @@ void Selenitic::o_soundLockButton(uint16 op, uint16 var, uint16 argc, uint16 *ar uint16 cardIdClosed = argv[0]; uint16 cardIdOpen = argv[1]; - _vm->changeToCard(cardIdClosed, true); + _vm->changeToCard(cardIdClosed, kTransitionDissolve); - _vm->changeToCard(cardIdOpen, false); + _vm->changeToCard(cardIdOpen, kNoTransition); _vm->_sound->replaceSoundMyst(argv[2]); animatedUpdate(argv[4], &argv[5], argv[3]); diff --git a/engines/mohawk/myst_stacks/slides.cpp b/engines/mohawk/myst_stacks/slides.cpp index c0bb400db1..720926904a 100644 --- a/engines/mohawk/myst_stacks/slides.cpp +++ b/engines/mohawk/myst_stacks/slides.cpp @@ -63,7 +63,7 @@ void Slides::runPersistentScripts() { // Used on Cards... if (_vm->_system->getMillis() > _nextCardTime) { _vm->_gfx->fadeToBlack(); - _vm->changeToCard(_nextCardID, false); + _vm->changeToCard(_nextCardID, kNoTransition); _vm->_gfx->fadeFromBlack(); } } diff --git a/engines/mohawk/myst_stacks/stoneship.cpp b/engines/mohawk/myst_stacks/stoneship.cpp index ef228e62f3..1359685302 100644 --- a/engines/mohawk/myst_stacks/stoneship.cpp +++ b/engines/mohawk/myst_stacks/stoneship.cpp @@ -440,9 +440,9 @@ void Stoneship::o_drawerOpenSirius(uint16 op, uint16 var, uint16 argc, uint16 *a drawer->drawConditionalDataToScreen(0, 0); } - uint16 transition = 5; + TransitionType transition = kTransitionTopToBottom; if (argc == 2 && argv[1]) - transition = 11; + transition = kTransitionCopy; _vm->_gfx->runTransition(transition, drawer->getRect(), 25, 5); } @@ -579,7 +579,7 @@ void Stoneship::o_drawerOpenAchenar(uint16 op, uint16 var, uint16 argc, uint16 * MystResourceType8 *drawer = static_cast<MystResourceType8 *>(_vm->_resources[argv[0]]); drawer->drawConditionalDataToScreen(0, 0); - _vm->_gfx->runTransition(5, drawer->getRect(), 25, 5); + _vm->_gfx->runTransition(kTransitionTopToBottom, drawer->getRect(), 25, 5); } void Stoneship::o_hologramPlayback(uint16 op, uint16 var, uint16 argc, uint16 *argv) { @@ -692,9 +692,9 @@ void Stoneship::o_chestValveVideos(uint16 op, uint16 var, uint16 argc, uint16 *a _vm->_sound->resumeBackgroundMyst(); } else { // Valve opening - // TODO: Play backwards VideoHandle valve = _vm->_video->playMovie(movie, 97, 267); - _vm->_video->setVideoBounds(valve, Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 350, 600)); + _vm->_video->seekToTime(valve, Audio::Timestamp(0, 350, 600)); + _vm->_video->setVideoRate(valve, -1); _vm->_video->waitUntilMovieEnds(valve); } } @@ -777,7 +777,7 @@ void Stoneship::o_cloudOrbLeave(uint16 op, uint16 var, uint16 argc, uint16 *argv _cloudOrbMovie->pauseMovie(true); _vm->_sound->replaceSoundMyst(_cloudOrbStopSound); - _vm->_gfx->runTransition(5, _invokingResource->getRect(), 4, 0); + _vm->_gfx->runTransition(kTransitionTopToBottom, _invokingResource->getRect(), 4, 0); } void Stoneship::o_drawerCloseOpened(uint16 op, uint16 var, uint16 argc, uint16 *argv) { @@ -794,7 +794,7 @@ void Stoneship::drawerClose(uint16 drawer) { _vm->drawResourceImages(); MystResource *res = _vm->_resources[drawer]; - _vm->_gfx->runTransition(6, res->getRect(), 25, 5); + _vm->_gfx->runTransition(kTransitionBottomToTop, res->getRect(), 25, 5); } void Stoneship::o_hologramDisplay_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) { diff --git a/engines/mohawk/riven.cpp b/engines/mohawk/riven.cpp index 32613c6185..71aa371073 100644 --- a/engines/mohawk/riven.cpp +++ b/engines/mohawk/riven.cpp @@ -177,7 +177,7 @@ Common::Error MohawkEngine_Riven::run() { } } else { // Otherwise, start us off at aspit's card 1 (the main menu) - changeToStack(aspit); + changeToStack(aspit); changeToCard(1); } @@ -830,7 +830,7 @@ static void sunnersTopStairsTimer(MohawkEngine_Riven *vm) { } else if (sunnerTime < vm->getTotalPlayTime()) { VideoHandle handle = vm->_video->playMovieRiven(vm->_rnd->getRandomNumberRng(1, 3)); - timerTime = vm->_video->getDuration(handle) + vm->_rnd->getRandomNumberRng(2, 15) * 1000; + timerTime = vm->_video->getDuration(handle).msecs() + vm->_rnd->getRandomNumberRng(2, 15) * 1000; } sunnerTime = timerTime + vm->getTotalPlayTime(); @@ -868,7 +868,7 @@ static void sunnersMidStairsTimer(MohawkEngine_Riven *vm) { VideoHandle handle = vm->_video->playMovieRiven(movie); - timerTime = vm->_video->getDuration(handle) + vm->_rnd->getRandomNumberRng(1, 10) * 1000; + timerTime = vm->_video->getDuration(handle).msecs() + vm->_rnd->getRandomNumberRng(1, 10) * 1000; } sunnerTime = timerTime + vm->getTotalPlayTime(); @@ -898,7 +898,7 @@ static void sunnersLowerStairsTimer(MohawkEngine_Riven *vm) { } else if (sunnerTime < vm->getTotalPlayTime()) { VideoHandle handle = vm->_video->playMovieRiven(vm->_rnd->getRandomNumberRng(3, 5)); - timerTime = vm->_video->getDuration(handle) + vm->_rnd->getRandomNumberRng(1, 30) * 1000; + timerTime = vm->_video->getDuration(handle).msecs() + vm->_rnd->getRandomNumberRng(1, 30) * 1000; } sunnerTime = timerTime + vm->getTotalPlayTime(); @@ -932,7 +932,7 @@ static void sunnersBeachTimer(MohawkEngine_Riven *vm) { vm->_video->activateMLST(mlstID, vm->getCurCard()); VideoHandle handle = vm->_video->playMovieRiven(mlstID); - timerTime = vm->_video->getDuration(handle) + vm->_rnd->getRandomNumberRng(1, 30) * 1000; + timerTime = vm->_video->getDuration(handle).msecs() + vm->_rnd->getRandomNumberRng(1, 30) * 1000; } sunnerTime = timerTime + vm->getTotalPlayTime(); diff --git a/engines/mohawk/riven_external.cpp b/engines/mohawk/riven_external.cpp index 337a57e3e1..384e89a4cf 100644 --- a/engines/mohawk/riven_external.cpp +++ b/engines/mohawk/riven_external.cpp @@ -1467,7 +1467,7 @@ static void catherineViewerIdleTimer(MohawkEngine_Riven *vm) { VideoHandle videoHandle = vm->_video->playMovieRiven(30); // Reset the timer - vm->installTimer(&catherineViewerIdleTimer, vm->_video->getDuration(videoHandle) + vm->_rnd->getRandomNumber(60) * 1000); + vm->installTimer(&catherineViewerIdleTimer, vm->_video->getDuration(videoHandle).msecs() + vm->_rnd->getRandomNumber(60) * 1000); } void RivenExternal::xglview_prisonon(uint16 argc, uint16 *argv) { @@ -1507,7 +1507,7 @@ void RivenExternal::xglview_prisonon(uint16 argc, uint16 *argv) { _vm->_video->activateMLST(cathMovie, _vm->getCurCard()); VideoHandle videoHandle = _vm->_video->playMovieRiven(30); - timeUntilNextMovie = _vm->_video->getDuration(videoHandle) + _vm->_rnd->getRandomNumber(60) * 1000; + timeUntilNextMovie = _vm->_video->getDuration(videoHandle).msecs() + _vm->_rnd->getRandomNumber(60) * 1000; } else { // Otherwise, just redraw the imager timeUntilNextMovie = _vm->_rnd->getRandomNumberRng(10, 20) * 1000; @@ -2335,7 +2335,7 @@ static void rebelPrisonWindowTimer(MohawkEngine_Riven *vm) { VideoHandle handle = vm->_video->playMovieRiven(movie); // Ensure the next video starts after this one ends - uint32 timeUntilNextVideo = vm->_video->getDuration(handle) + vm->_rnd->getRandomNumberRng(38, 58) * 1000; + uint32 timeUntilNextVideo = vm->_video->getDuration(handle).msecs() + vm->_rnd->getRandomNumberRng(38, 58) * 1000; // Save the time in case we leave the card and return vm->_vars["rvillagetime"] = timeUntilNextVideo + vm->getTotalPlayTime(); diff --git a/engines/mohawk/video.cpp b/engines/mohawk/video.cpp index b1b99722d5..8b0130d711 100644 --- a/engines/mohawk/video.cpp +++ b/engines/mohawk/video.cpp @@ -493,9 +493,9 @@ uint32 VideoManager::getTime(VideoHandle handle) { return _videoStreams[handle]->getTime(); } -uint32 VideoManager::getDuration(VideoHandle handle) { +Audio::Timestamp VideoManager::getDuration(VideoHandle handle) { assert(handle != NULL_VID_HANDLE); - return _videoStreams[handle]->getDuration().msecs(); + return _videoStreams[handle]->getDuration(); } bool VideoManager::endOfVideo(VideoHandle handle) { @@ -536,6 +536,16 @@ void VideoManager::setVideoLooping(VideoHandle handle, bool loop) { _videoStreams[handle].loop = loop; } +Common::Rational VideoManager::getVideoRate(VideoHandle handle) const { + assert(handle != NULL_VID_HANDLE); + return _videoStreams[handle]->getRate(); +} + +void VideoManager::setVideoRate(VideoHandle handle, const Common::Rational &rate) { + assert(handle != NULL_VID_HANDLE); + _videoStreams[handle]->setRate(rate); +} + void VideoManager::pauseMovie(VideoHandle handle, bool pause) { assert(handle != NULL_VID_HANDLE); _videoStreams[handle]->pauseVideo(pause); diff --git a/engines/mohawk/video.h b/engines/mohawk/video.h index 6d2783936d..2c4c827aa8 100644 --- a/engines/mohawk/video.h +++ b/engines/mohawk/video.h @@ -101,12 +101,14 @@ public: int getCurFrame(VideoHandle handle); uint32 getFrameCount(VideoHandle handle); uint32 getTime(VideoHandle handle); - uint32 getDuration(VideoHandle videoHandle); + Audio::Timestamp getDuration(VideoHandle videoHandle); bool endOfVideo(VideoHandle handle); void setVideoBounds(VideoHandle handle, Audio::Timestamp start, Audio::Timestamp end); void drawVideoFrame(VideoHandle handle, Audio::Timestamp time); void seekToTime(VideoHandle handle, Audio::Timestamp time); void setVideoLooping(VideoHandle handle, bool loop); + Common::Rational getVideoRate(VideoHandle handle) const; + void setVideoRate(VideoHandle handle, const Common::Rational &rate); void waitUntilMovieEnds(VideoHandle videoHandle); void delayUntilMovieEnds(VideoHandle videoHandle); void pauseMovie(VideoHandle videoHandle, bool pause); diff --git a/engines/pegasus/neighborhood/norad/pressuredoor.cpp b/engines/pegasus/neighborhood/norad/pressuredoor.cpp index d1378567d3..a12e971d10 100644 --- a/engines/pegasus/neighborhood/norad/pressuredoor.cpp +++ b/engines/pegasus/neighborhood/norad/pressuredoor.cpp @@ -323,7 +323,8 @@ void PressureDoor::receiveNotification(Notification *notification, const Notific _robotState = kRobotDead; _levelsMovie.stop(); _levelsMovie.setSegment((kNormalSubRoomPressure + kPressureBase) * _levelsScale, - (GameState.getNoradSubRoomPressure() + kPressureBase) * _levelsScale); + (GameState.getNoradSubRoomPressure() + kPressureBase) * _levelsScale + 1); + _levelsMovie.setTime((GameState.getNoradSubRoomPressure() + kPressureBase) * _levelsScale); _pressureCallBack.setCallBackFlag(kPressureDroppingFlag); _pressureCallBack.scheduleCallBack(kTriggerAtStart, 0, 0); _typeMovie.stop(); @@ -335,7 +336,7 @@ void PressureDoor::receiveNotification(Notification *notification, const Notific _downButton.setCurrentFrameIndex(1); _gameState = kGameOver; allowInput(false); - _levelsMovie.setRate(Common::Rational(0x5555, 0x10000) - 1); // Should match door tracker. + _levelsMovie.setRate(Common::Rational(-4, 3)); // Should match door tracker. break; case kRobotDead: allowInput(true); diff --git a/engines/pegasus/neighborhood/tsa/fulltsa.cpp b/engines/pegasus/neighborhood/tsa/fulltsa.cpp index b598841b45..9b843da5d6 100644 --- a/engines/pegasus/neighborhood/tsa/fulltsa.cpp +++ b/engines/pegasus/neighborhood/tsa/fulltsa.cpp @@ -622,6 +622,13 @@ void RipTimer::draw(const Common::Rect &updateRect) { } void RipTimer::timeChanged(const TimeValue newTime) { + // WORKAROUND: If the timer isn't running, don't run the following code. + // Fixes use of the code when it shouldn't be running (since this is an + // IdlerAnimation, this is called on useIdleTime() but this specific + // timer only makes sense when used as an actual timer). + if (!isRunning()) + return; + Common::Rect bounds; getBounds(bounds); diff --git a/engines/pegasus/pegasus.cpp b/engines/pegasus/pegasus.cpp index 502a79ec25..98f0553783 100644 --- a/engines/pegasus/pegasus.cpp +++ b/engines/pegasus/pegasus.cpp @@ -1428,6 +1428,10 @@ void PegasusEngine::switchGameMode(const GameMode newMode, const GameMode oldMod } bool PegasusEngine::canSwitchGameMode(const GameMode newMode, const GameMode oldMode) { + // WORKAROUND: Don't allow game mode switches when the interface is not set up. + // Prevents segfaults when pressing 'i' when in the space chase. + if (!g_interface) + return false; if (newMode == kModeInventoryPick && oldMode == kModeBiochipPick) return false; if (newMode == kModeBiochipPick && oldMode == kModeInventoryPick) diff --git a/engines/queen/journal.cpp b/engines/queen/journal.cpp index db06d540f5..474f72eca5 100644 --- a/engines/queen/journal.cpp +++ b/engines/queen/journal.cpp @@ -559,7 +559,7 @@ void Journal::updateTextField(uint16 ascii, int keycode) { } break; default: - if (isprint((char)ascii) && + if (Common::isPrint((char)ascii) && _textField.textCharsCount < (sizeof(_textField.text) - 1) && _vm->display()->textWidth(_textField.text) < _textField.w) { _textField.text[_textField.textCharsCount] = (char)ascii; diff --git a/engines/saga/music.cpp b/engines/saga/music.cpp index 13850a0b6d..0eebf3f175 100644 --- a/engines/saga/music.cpp +++ b/engines/saga/music.cpp @@ -30,6 +30,7 @@ #include "audio/audiostream.h" #include "audio/mididrv.h" #include "audio/midiparser.h" +#include "audio/midiparser_qt.h" #include "audio/decoders/raw.h" #include "common/config-manager.h" #include "common/file.h" @@ -76,19 +77,14 @@ void MusicDriver::play(SagaEngine *vm, ByteArray *buffer, bool loop) { } // Check if the game is using XMIDI or SMF music - if (vm->getGameId() == GID_IHNM && vm->isMacResources()) { - // Just set an XMIDI parser for Mac IHNM for now + if (!memcmp(buffer->getBuffer(), "FORM", 4)) { _parser = MidiParser::createParser_XMIDI(); + // ITE had MT32 mapped instruments + _isGM = (vm->getGameId() != GID_ITE); } else { - if (!memcmp(buffer->getBuffer(), "FORM", 4)) { - _parser = MidiParser::createParser_XMIDI(); - // ITE had MT32 mapped instruments - _isGM = (vm->getGameId() != GID_ITE); - } else { - _parser = MidiParser::createParser_SMF(); - // ITE with standalone MIDI files is General MIDI - _isGM = (vm->getGameId() == GID_ITE); - } + _parser = MidiParser::createParser_SMF(); + // ITE with standalone MIDI files is General MIDI + _isGM = (vm->getGameId() == GID_ITE); } if (!_parser->loadMusic(buffer->getBuffer(), buffer->size())) @@ -107,6 +103,27 @@ void MusicDriver::play(SagaEngine *vm, ByteArray *buffer, bool loop) { _isPlaying = true; } +void MusicDriver::playQuickTime(const Common::String &musicName, bool loop) { + // IHNM Mac uses QuickTime MIDI + _parser = MidiParser::createParser_QT(); + _isGM = true; + + if (!((MidiParser_QT *)_parser)->loadFromContainerFile(musicName)) + error("MusicDriver::playQuickTime(): Failed to load file '%s'", musicName.c_str()); + + _parser->setTrack(0); + _parser->setMidiDriver(this); + _parser->setTimerRate(_driver->getBaseTempo()); + _parser->property(MidiParser::mpCenterPitchWheelOnUnload, 1); + _parser->property(MidiParser::mpSendSustainOffOnNotesOff, 1); + + // Handle music looping + _parser->property(MidiParser::mpAutoLoop, loop); +// _isLooping = loop; + + _isPlaying = true; +} + void MusicDriver::pause() { _isPlaying = false; } @@ -343,31 +360,19 @@ void Music::play(uint32 resourceId, MusicFlags flags) { // Load MIDI/XMI resource data if (_vm->getGameId() == GID_IHNM && _vm->isMacResources()) { - // Load the external music file for Mac IHNM -#if 0 - Common::File musicFile; - char musicFileName[40]; - sprintf(musicFileName, "Music/Music%02x", resourceId); - musicFile.open(musicFileName); - resourceSize = musicFile.size(); - resourceData = new byte[resourceSize]; - musicFile.read(resourceData, resourceSize); - musicFile.close(); - - // TODO: The Mac music format is unsupported (QuickTime MIDI) - // so stop here -#endif - return; + // Load the external music file for Mac IHNM + _player->playQuickTime(Common::String::format("Music/Music%02x", resourceId), flags & MUSIC_LOOP); } else { if (_currentMusicBuffer == &_musicBuffer[1]) { _currentMusicBuffer = &_musicBuffer[0]; } else { _currentMusicBuffer = &_musicBuffer[1]; } + _vm->_resource->loadResource(_musicContext, resourceId, *_currentMusicBuffer); + _player->play(_vm, _currentMusicBuffer, (flags & MUSIC_LOOP)); } - _player->play(_vm, _currentMusicBuffer, (flags & MUSIC_LOOP)); setVolume(_vm->_musicVolume); } diff --git a/engines/saga/music.h b/engines/saga/music.h index 5a4e662af4..081fab21f6 100644 --- a/engines/saga/music.h +++ b/engines/saga/music.h @@ -46,6 +46,7 @@ public: MusicDriver(); void play(SagaEngine *vm, ByteArray *buffer, bool loop); + void playQuickTime(const Common::String &musicName, bool loop); virtual void pause(); virtual void resume(); diff --git a/engines/scumm/imuse/imuse.cpp b/engines/scumm/imuse/imuse.cpp index 016ba89e7b..b69ce552bc 100644 --- a/engines/scumm/imuse/imuse.cpp +++ b/engines/scumm/imuse/imuse.cpp @@ -363,7 +363,7 @@ void IMuseInternal::pause(bool paused) { _paused = paused; } -int IMuseInternal::save_or_load(Serializer *ser, ScummEngine *scumm) { +int IMuseInternal::save_or_load(Serializer *ser, ScummEngine *scumm, bool fixAfterLoad) { Common::StackLock lock(_mutex, "IMuseInternal::save_or_load()"); const SaveLoadEntry mainEntries[] = { MKLINE(IMuseInternal, _queue_end, sleUint8, VER(8)), @@ -440,7 +440,16 @@ int IMuseInternal::save_or_load(Serializer *ser, ScummEngine *scumm) { for (i = 0; i < 8; ++i) ser->saveLoadEntries(0, volumeFaderEntries); - if (ser->isLoading()) { + // Normally, we have to fix up the data structures after loading a + // saved game. But there are cases where we don't. For instance, The + // Macintosh version of Monkey Island 1 used to convert the Mac0 music + // resources to General MIDI and play it through iMUSE as a rough + // approximation. Now it has its own player, but old savegame still + // have the iMUSE data in them. We have to skip that data, using a + // dummy iMUSE object, but since the resource is no longer recognizable + // to iMUSE, the fixup fails hard. So yes, this is a bit of a hack. + + if (ser->isLoading() && fixAfterLoad) { // Load all sounds that we need fix_players_after_load(scumm); fix_parts_after_load(); diff --git a/engines/scumm/imuse/imuse.h b/engines/scumm/imuse/imuse.h index 23449e470b..cce5309229 100644 --- a/engines/scumm/imuse/imuse.h +++ b/engines/scumm/imuse/imuse.h @@ -62,7 +62,7 @@ public: public: virtual void on_timer(MidiDriver *midi) = 0; virtual void pause(bool paused) = 0; - virtual int save_or_load(Serializer *ser, ScummEngine *scumm) = 0; + virtual int save_or_load(Serializer *ser, ScummEngine *scumm, bool fixAfterLoad = true) = 0; virtual bool get_sound_active(int sound) const = 0; virtual int32 doCommand(int numargs, int args[]) = 0; virtual int clear_queue() = 0; diff --git a/engines/scumm/imuse/imuse_internal.h b/engines/scumm/imuse/imuse_internal.h index 846e2d7545..6be564a517 100644 --- a/engines/scumm/imuse/imuse_internal.h +++ b/engines/scumm/imuse/imuse_internal.h @@ -518,7 +518,7 @@ protected: public: // IMuse interface void pause(bool paused); - int save_or_load(Serializer *ser, ScummEngine *scumm); + int save_or_load(Serializer *ser, ScummEngine *scumm, bool fixAfterLoad = true); bool get_sound_active(int sound) const; int32 doCommand(int numargs, int args[]); uint32 property(int prop, uint32 value); diff --git a/engines/scumm/module.mk b/engines/scumm/module.mk index 8499c9bad3..28884d7f78 100644 --- a/engines/scumm/module.mk +++ b/engines/scumm/module.mk @@ -36,6 +36,7 @@ MODULE_OBJS := \ object.o \ palette.o \ player_apple2.o \ + player_mac.o \ player_mod.o \ player_nes.o \ player_pce.o \ @@ -47,7 +48,9 @@ MODULE_OBJS := \ player_v2base.o \ player_v2cms.o \ player_v3a.o \ + player_v3m.o \ player_v4a.o \ + player_v5m.o \ resource_v2.o \ resource_v3.o \ resource_v4.o \ diff --git a/engines/scumm/music.h b/engines/scumm/music.h index a527c77b72..9fd14d830e 100644 --- a/engines/scumm/music.h +++ b/engines/scumm/music.h @@ -24,6 +24,7 @@ #define SCUMM_MUSIC_H #include "common/scummsys.h" +#include "engines/scumm/saveload.h" namespace Scumm { @@ -78,6 +79,11 @@ public: * @return the music timer */ virtual int getMusicTimer() { return 0; } + + /** + * Save or load the music state. + */ + virtual void saveLoadWithSerializer(Serializer *ser) {} }; } // End of namespace Scumm diff --git a/engines/scumm/player_mac.cpp b/engines/scumm/player_mac.cpp new file mode 100644 index 0000000000..c16c85bff3 --- /dev/null +++ b/engines/scumm/player_mac.cpp @@ -0,0 +1,415 @@ +/* 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/macresman.h" +#include "common/translation.h" +#include "engines/engine.h" +#include "gui/message.h" +#include "scumm/player_mac.h" +#include "scumm/resource.h" +#include "scumm/scumm.h" +#include "scumm/imuse/imuse.h" + +namespace Scumm { + +Player_Mac::Player_Mac(ScummEngine *scumm, Audio::Mixer *mixer, int numberOfChannels, int channelMask, bool fadeNoteEnds) + : _vm(scumm), + _mixer(mixer), + _sampleRate(_mixer->getOutputRate()), + _soundPlaying(-1), + _numberOfChannels(numberOfChannels), + _channelMask(channelMask), + _fadeNoteEnds(fadeNoteEnds) { + assert(scumm); + assert(mixer); +} + +void Player_Mac::init() { + _channel = new Player_Mac::Channel[_numberOfChannels]; + + int i; + + for (i = 0; i < _numberOfChannels; i++) { + _channel[i]._looped = false; + _channel[i]._length = 0; + _channel[i]._data = NULL; + _channel[i]._pos = 0; + _channel[i]._pitchModifier = 0; + _channel[i]._velocity = 0; + _channel[i]._remaining = 0; + _channel[i]._notesLeft = false; + _channel[i]._instrument._data = NULL; + _channel[i]._instrument._size = 0; + _channel[i]._instrument._rate = 0; + _channel[i]._instrument._loopStart = 0; + _channel[i]._instrument._loopEnd = 0; + _channel[i]._instrument._baseFreq = 0; + _channel[i]._instrument._pos = 0; + _channel[i]._instrument._subPos = 0; + } + + _pitchTable[116] = 1664510; + _pitchTable[117] = 1763487; + _pitchTable[118] = 1868350; + _pitchTable[119] = 1979447; + _pitchTable[120] = 2097152; + _pitchTable[121] = 2221855; + _pitchTable[122] = 2353973; + _pitchTable[123] = 2493948; + _pitchTable[124] = 2642246; + _pitchTable[125] = 2799362; + _pitchTable[126] = 2965820; + _pitchTable[127] = 3142177; + for (i = 115; i >= 0; --i) { + _pitchTable[i] = _pitchTable[i + 12] / 2; + } + + setMusicVolume(255); + + if (!checkMusicAvailable()) { + return; + } + + _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); +} + +Player_Mac::~Player_Mac() { + Common::StackLock lock(_mutex); + _mixer->stopHandle(_soundHandle); + stopAllSounds_Internal(); + delete[] _channel; +} + +void Player_Mac::saveLoadWithSerializer(Serializer *ser) { + Common::StackLock lock(_mutex); + if (ser->getVersion() < VER(94)) { + if (_vm->_game.id == GID_MONKEY && ser->isLoading()) { + IMuse *dummyImuse = IMuse::create(_vm->_system, NULL, NULL); + dummyImuse->save_or_load(ser, _vm, false); + delete dummyImuse; + } + } else { + static const SaveLoadEntry musicEntries[] = { + MKLINE(Player_Mac, _sampleRate, sleUint32, VER(94)), + MKLINE(Player_Mac, _soundPlaying, sleInt16, VER(94)), + MKEND() + }; + + static const SaveLoadEntry channelEntries[] = { + MKLINE(Channel, _pos, sleUint16, VER(94)), + MKLINE(Channel, _pitchModifier, sleInt32, VER(94)), + MKLINE(Channel, _velocity, sleUint8, VER(94)), + MKLINE(Channel, _remaining, sleUint32, VER(94)), + MKLINE(Channel, _notesLeft, sleUint8, VER(94)), + MKEND() + }; + + static const SaveLoadEntry instrumentEntries[] = { + MKLINE(Instrument, _pos, sleUint32, VER(94)), + MKLINE(Instrument, _subPos, sleUint32, VER(94)), + MKEND() + }; + + uint32 mixerSampleRate = _sampleRate; + int i; + + ser->saveLoadEntries(this, musicEntries); + + if (ser->isLoading() && _soundPlaying != -1) { + const byte *ptr = _vm->getResourceAddress(rtSound, _soundPlaying); + assert(ptr); + loadMusic(ptr); + } + + ser->saveLoadArrayOf(_channel, _numberOfChannels, sizeof(Channel), channelEntries); + for (i = 0; i < _numberOfChannels; i++) { + ser->saveLoadEntries(&_channel[i], instrumentEntries); + } + + if (ser->isLoading()) { + // If necessary, adjust the channel data to fit the + // current sample rate. + if (_soundPlaying != -1 && _sampleRate != mixerSampleRate) { + double mult = (double)_sampleRate / (double)mixerSampleRate; + for (i = 0; i < _numberOfChannels; i++) { + _channel[i]._pitchModifier = (int)((double)_channel[i]._pitchModifier * mult); + _channel[i]._remaining = (int)((double)_channel[i]._remaining / mult); + } + } + _sampleRate = mixerSampleRate; + } + } +} + +void Player_Mac::setMusicVolume(int vol) { + debug(5, "Player_Mac::setMusicVolume(%d)", vol); +} + +void Player_Mac::stopAllSounds_Internal() { + if (_soundPlaying != -1) { + _vm->_res->unlock(rtSound, _soundPlaying); + } + _soundPlaying = -1; + for (int i = 0; i < _numberOfChannels; i++) { + // The channel data is managed by the resource manager, so + // don't delete that. + delete[] _channel[i]._instrument._data; + _channel[i]._instrument._data = NULL; + + _channel[i]._remaining = 0; + _channel[i]._notesLeft = false; + } +} + +void Player_Mac::stopAllSounds() { + Common::StackLock lock(_mutex); + debug(5, "Player_Mac::stopAllSounds()"); + stopAllSounds_Internal(); +} + +void Player_Mac::stopSound(int nr) { + Common::StackLock lock(_mutex); + debug(5, "Player_Mac::stopSound(%d)", nr); + + if (nr == _soundPlaying) { + stopAllSounds(); + } +} + +void Player_Mac::startSound(int nr) { + Common::StackLock lock(_mutex); + debug(5, "Player_Mac::startSound(%d)", nr); + + stopAllSounds_Internal(); + + const byte *ptr = _vm->getResourceAddress(rtSound, nr); + assert(ptr); + + if (!loadMusic(ptr)) { + return; + } + + _vm->_res->lock(rtSound, nr); + _soundPlaying = nr; +} + +bool Player_Mac::Channel::loadInstrument(Common::SeekableReadStream *stream) { + uint16 soundType = stream->readUint16BE(); + if (soundType != 1) { + warning("Player_Mac::loadInstrument: Unsupported sound type %d", soundType); + return false; + } + uint16 typeCount = stream->readUint16BE(); + if (typeCount != 1) { + warning("Player_Mac::loadInstrument: Unsupported data type count %d", typeCount); + return false; + } + uint16 dataType = stream->readUint16BE(); + if (dataType != 5) { + warning("Player_Mac::loadInstrument: Unsupported data type %d", dataType); + return false; + } + + stream->readUint32BE(); // initialization option + + uint16 cmdCount = stream->readUint16BE(); + if (cmdCount != 1) { + warning("Player_Mac::loadInstrument: Unsupported command count %d", cmdCount); + return false; + } + uint16 command = stream->readUint16BE(); + if (command != 0x8050 && command != 0x8051) { + warning("Player_Mac::loadInstrument: Unsupported command 0x%04X", command); + return false; + } + + stream->readUint16BE(); // 0 + uint32 soundHeaderOffset = stream->readUint32BE(); + + stream->seek(soundHeaderOffset); + + uint32 soundDataOffset = stream->readUint32BE(); + uint32 size = stream->readUint32BE(); + uint32 rate = stream->readUint32BE() >> 16; + uint32 loopStart = stream->readUint32BE(); + uint32 loopEnd = stream->readUint32BE(); + byte encoding = stream->readByte(); + byte baseFreq = stream->readByte(); + + if (encoding != 0) { + warning("Player_Mac::loadInstrument: Unsupported encoding %d", encoding); + return false; + } + + stream->skip(soundDataOffset); + + byte *data = new byte[size]; + stream->read(data, size); + + _instrument._data = data; + _instrument._size = size; + _instrument._rate = rate; + _instrument._loopStart = loopStart; + _instrument._loopEnd = loopEnd; + _instrument._baseFreq = baseFreq; + + return true; +} + +int Player_Mac::getMusicTimer() { + return 0; +} + +int Player_Mac::getSoundStatus(int nr) const { + return _soundPlaying == nr; +} + +uint32 Player_Mac::durationToSamples(uint16 duration) { + // The correct formula should be: + // + // (duration * 473 * _sampleRate) / (4 * 480 * 480) + // + // But that's likely to cause integer overflow, so we do it in two + // steps and hope that the rounding error won't be noticeable. + // + // The original code is a bit unclear on if it should be 473 or 437, + // but since the comments indicated 473 I'm assuming 437 was a typo. + uint32 samples = (duration * _sampleRate) / (4 * 480); + samples = (samples * 473) / 480; + return samples; +} + +int Player_Mac::noteToPitchModifier(byte note, Instrument *instrument) { + if (note > 0) { + const int pitchIdx = note + 60 - instrument->_baseFreq; + // I don't want to use floating-point arithmetics here, but I + // ran into overflow problems with the church music in Monkey + // Island. It's only once per note, so it should be ok. + double mult = (double)instrument->_rate / (double)_sampleRate; + return (int)(mult * _pitchTable[pitchIdx]); + } else { + return 0; + } +} + +int Player_Mac::readBuffer(int16 *data, const int numSamples) { + Common::StackLock lock(_mutex); + + memset(data, 0, numSamples * 2); + if (_soundPlaying == -1) { + return numSamples; + } + + bool notesLeft = false; + + for (int i = 0; i < _numberOfChannels; i++) { + if (!(_channelMask & (1 << i))) { + continue; + } + + uint samplesLeft = numSamples; + int16 *ptr = data; + + while (samplesLeft > 0) { + int generated; + if (_channel[i]._remaining == 0) { + uint32 samples; + int pitchModifier; + byte velocity; + if (getNextNote(i, samples, pitchModifier, velocity)) { + _channel[i]._remaining = samples; + _channel[i]._pitchModifier = pitchModifier; + _channel[i]._velocity = velocity; + + } else { + _channel[i]._pitchModifier = 0; + _channel[i]._velocity = 0; + _channel[i]._remaining = samplesLeft; + } + } + generated = MIN<uint32>(_channel[i]._remaining, samplesLeft); + if (_channel[i]._velocity != 0) { + _channel[i]._instrument.generateSamples(ptr, _channel[i]._pitchModifier, _channel[i]._velocity, generated, _channel[i]._remaining, _fadeNoteEnds); + } + ptr += generated; + samplesLeft -= generated; + _channel[i]._remaining -= generated; + } + + if (_channel[i]._notesLeft) { + notesLeft = true; + } + } + + if (!notesLeft) { + stopAllSounds_Internal(); + } + + return numSamples; +} + +void Player_Mac::Instrument::generateSamples(int16 *data, int pitchModifier, int volume, int numSamples, int remainingSamplesOnNote, bool fadeNoteEnds) { + int samplesLeft = numSamples; + while (samplesLeft) { + _subPos += pitchModifier; + while (_subPos >= 0x10000) { + _subPos -= 0x10000; + _pos++; + if (_pos >= _loopEnd) { + _pos = _loopStart; + } + } + + int newSample = (((int16)((_data[_pos] << 8) ^ 0x8000)) * volume) / 255; + + if (fadeNoteEnds) { + // Fade out the last 100 samples on each note. Even at + // low output sample rates this is just a fraction of a + // second, but it gets rid of distracting "pops" at the + // end when the sample would otherwise go abruptly from + // something to nothing. This was particularly + // noticeable on the distaff notes in Loom. + // + // The reason it's conditional is that Monkey Island + // appears to have a "hold current note" command, and + // if we fade out the current note in that case we + // will actually introduce new "pops". + + remainingSamplesOnNote--; + if (remainingSamplesOnNote < 100) { + newSample = (newSample * remainingSamplesOnNote) / 100; + } + } + + int sample = *data + newSample; + if (sample > 32767) { + sample = 32767; + } else if (sample < -32768) { + sample = -32768; + } + + *data++ = sample; + samplesLeft--; + } +} + +} // End of namespace Scumm diff --git a/engines/scumm/player_mac.h b/engines/scumm/player_mac.h new file mode 100644 index 0000000000..09307b4e57 --- /dev/null +++ b/engines/scumm/player_mac.h @@ -0,0 +1,133 @@ +/* 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 SCUMM_PLAYER_MAC_H +#define SCUMM_PLAYER_MAC_H + +#include "common/scummsys.h" +#include "common/util.h" +#include "common/mutex.h" +#include "scumm/music.h" +#include "scumm/saveload.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" + +#define RES_SND MKTAG('s', 'n', 'd', ' ') + +class Mixer; + +namespace Scumm { + +class ScummEngine; + +/** + * Scumm Macintosh music driver, base class. + */ +class Player_Mac : public Audio::AudioStream, public MusicEngine { +public: + Player_Mac(ScummEngine *scumm, Audio::Mixer *mixer, int numberOfChannels, int channelMask, bool fadeNoteEnds); + virtual ~Player_Mac(); + + void init(); + + // MusicEngine API + virtual void setMusicVolume(int vol); + virtual void startSound(int sound); + virtual void stopSound(int sound); + virtual void stopAllSounds(); + virtual int getMusicTimer(); + virtual int getSoundStatus(int sound) const; + + // AudioStream API + virtual int readBuffer(int16 *buffer, const int numSamples); + virtual bool isStereo() const { return false; } + virtual bool endOfData() const { return false; } + virtual int getRate() const { return _sampleRate; } + + virtual void saveLoadWithSerializer(Serializer *ser); + +private: + Common::Mutex _mutex; + Audio::Mixer *const _mixer; + Audio::SoundHandle _soundHandle; + uint32 _sampleRate; + int _soundPlaying; + + void stopAllSounds_Internal(); + + struct Instrument { + byte *_data; + uint32 _size; + uint32 _rate; + uint32 _loopStart; + uint32 _loopEnd; + byte _baseFreq; + + uint _pos; + uint _subPos; + + void newNote() { + _pos = 0; + _subPos = 0; + } + + void generateSamples(int16 *data, int pitchModifier, int volume, int numSamples, int remainingSamplesOnNote, bool fadeNoteEnds); + }; + + int _pitchTable[128]; + int _numberOfChannels; + int _channelMask; + bool _fadeNoteEnds; + + virtual bool checkMusicAvailable() { return false; } + virtual bool loadMusic(const byte *ptr) { return false; } + virtual bool getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity) { return false; } + +protected: + struct Channel { + virtual ~Channel() {} + + Instrument _instrument; + bool _looped; + uint32 _length; + const byte *_data; + + uint _pos; + int _pitchModifier; + byte _velocity; + uint32 _remaining; + + bool _notesLeft; + + bool loadInstrument(Common::SeekableReadStream *stream); + }; + + ScummEngine *const _vm; + Channel *_channel; + + uint32 durationToSamples(uint16 duration); + int noteToPitchModifier(byte note, Instrument *instrument); +}; + +} // End of namespace Scumm + +#endif diff --git a/engines/scumm/player_v3m.cpp b/engines/scumm/player_v3m.cpp new file mode 100644 index 0000000000..e61463128a --- /dev/null +++ b/engines/scumm/player_v3m.cpp @@ -0,0 +1,202 @@ +/* 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. + * + */ + +/* + We have the following information from Lars Christensen (lechimp) and + Jamieson Christian (jamieson630): + + RESOURCE DATA + LE 2 bytes Resource size + 2 bytes Unknown + 2 bytes 'so' + 14 bytes Unknown + BE 2 bytes Instrument for Stream 1 + BE 2 bytes Instrument for Stream 2 + BE 2 bytes Instrument for Stream 3 + BE 2 bytes Instrument for Stream 4 + BE 2 bytes Instrument for Stream 5 + BE 2 bytes Offset to Stream 1 + BE 2 bytes Offset to Stream 2 + BE 2 bytes Offset to Stream 3 + BE 2 bytes Offset to Stream 4 + BE 2 bytes Offset to Stream 5 + ? bytes The streams + + STREAM DATA + BE 2 bytes Unknown (always 1?) + 2 bytes Unknown (always 0?) + BE 2 bytes Number of events in stream + ? bytes Stream data + + Each stream event is exactly 3 bytes, therefore one can + assert that numEvents == (streamSize - 6) / 3. The + polyphony of a stream appears to be 1; in other words, only + one note at a time can be playing in each stream. The next + event is not executed until the current note (or rest) is + finished playing; therefore, note duration also serves as the + time delta between events. + + FOR EACH EVENTS + BE 2 bytes Note duration + 1 byte Note number to play (0 = rest/silent) + + Oh, and quick speculation -- Stream 1 may be used for a + single-voice interleaved version of the music, where Stream 2- + 5 represent a version of the music in up to 4-voice + polyphony, one voice per stream. I postulate thus because + the first stream of the Mac Loom theme music contains + interleaved voices, whereas the second stream seemed to + contain only the pizzicato bottom-end harp. Stream 5, in this + example, is empty, so if my speculation is correct, this + particular musical number supports 3-voice polyphony at + most. I must check out Streams 3 and 4 to see what they + contain. + + ========== + + The instruments appear to be identified by their resource IDs: + + 1000 Dual Harp + 10895 harp1 + 11445 strings1 + 11548 silent + 13811 staff1 + 15703 brass1 + 16324 flute1 + 25614 accordian 1 + 28110 f horn1 + 29042 bassoon1 +*/ + +#include "common/macresman.h" +#include "common/translation.h" +#include "engines/engine.h" +#include "gui/message.h" +#include "scumm/player_v3m.h" +#include "scumm/scumm.h" + +namespace Scumm { + +Player_V3M::Player_V3M(ScummEngine *scumm, Audio::Mixer *mixer) + : Player_Mac(scumm, mixer, 5, 0x1E, true) { + assert(_vm->_game.id == GID_LOOM); + + // Channel 0 seems to be what was played on low-end macs, that couldn't + // handle multi-channel music and play the game at the same time. I'm + // not sure if stream 4 is ever used, but let's use it just in case. +} + +// \xAA is a trademark glyph in Mac OS Roman. We try that, but also the Windows +// version, the UTF-8 version, and just plain without in case the file system +// can't handle exotic characters like that. + +static const char *loomFileNames[] = { + "Loom\xAA", + "Loom\x99", + "Loom\xE2\x84\xA2", + "Loom" +}; + +bool Player_V3M::checkMusicAvailable() { + Common::MacResManager resource; + + for (int i = 0; i < ARRAYSIZE(loomFileNames); i++) { + if (resource.exists(loomFileNames[i])) { + return true; + } + } + + GUI::MessageDialog dialog(_( + "Could not find the 'Loom' Macintosh executable to read the\n" + "instruments from. Music will be disabled."), _("OK")); + dialog.runModal(); + return false; +} + +bool Player_V3M::loadMusic(const byte *ptr) { + Common::MacResManager resource; + bool found = false; + + for (int i = 0; i < ARRAYSIZE(loomFileNames); i++) { + if (resource.open(loomFileNames[i])) { + found = true; + break; + } + } + + if (!found) { + return false; + } + + assert(ptr[4] == 's' && ptr[5] == 'o'); + + uint i; + for (i = 0; i < 5; i++) { + int instrument = READ_BE_UINT16(ptr + 20 + 2 * i); + int offset = READ_BE_UINT16(ptr + 30 + 2 * i); + + _channel[i]._looped = false; + _channel[i]._length = READ_BE_UINT16(ptr + offset + 4) * 3; + _channel[i]._data = ptr + offset + 6; + _channel[i]._pos = 0; + _channel[i]._pitchModifier = 0; + _channel[i]._velocity = 0; + _channel[i]._remaining = 0; + _channel[i]._notesLeft = true; + + Common::SeekableReadStream *stream = resource.getResource(RES_SND, instrument); + if (_channel[i].loadInstrument(stream)) { + debug(6, "Player_V3M::loadMusic: Channel %d - Loaded Instrument %d (%s)", i, instrument, resource.getResName(RES_SND, instrument).c_str()); + } else { + resource.close(); + return false; + } + } + + resource.close(); + return true; +} + +bool Player_V3M::getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity) { + _channel[ch]._instrument.newNote(); + if (_channel[ch]._pos >= _channel[ch]._length) { + if (!_channel[ch]._looped) { + _channel[ch]._notesLeft = false; + return false; + } + _channel[ch]._pos = 0; + } + uint16 duration = READ_BE_UINT16(&_channel[ch]._data[_channel[ch]._pos]); + byte note = _channel[ch]._data[_channel[ch]._pos + 2]; + samples = durationToSamples(duration); + if (note > 0) { + pitchModifier = noteToPitchModifier(note, &_channel[ch]._instrument); + velocity = 127; + } else { + pitchModifier = 0; + velocity = 0; + } + _channel[ch]._pos += 3; + return true; +} + +} // End of namespace Scumm diff --git a/engines/scumm/player_v3m.h b/engines/scumm/player_v3m.h new file mode 100644 index 0000000000..359bab32a9 --- /dev/null +++ b/engines/scumm/player_v3m.h @@ -0,0 +1,54 @@ +/* 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 SCUMM_PLAYER_V3M_H +#define SCUMM_PLAYER_V3M_H + +#include "common/scummsys.h" +#include "common/util.h" +#include "common/mutex.h" +#include "scumm/music.h" +#include "scumm/player_mac.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" + +class Mixer; + +namespace Scumm { + +class ScummEngine; + +/** + * Scumm V3 Macintosh music driver. + */ +class Player_V3M : public Player_Mac { +public: + Player_V3M(ScummEngine *scumm, Audio::Mixer *mixer); + + virtual bool checkMusicAvailable(); + virtual bool loadMusic(const byte *ptr); + virtual bool getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity); +}; + +} // End of namespace Scumm + +#endif diff --git a/engines/scumm/player_v5m.cpp b/engines/scumm/player_v5m.cpp new file mode 100644 index 0000000000..500f3bbc40 --- /dev/null +++ b/engines/scumm/player_v5m.cpp @@ -0,0 +1,246 @@ +/* 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. + * + */ + +/* + From Markus Magnuson (superqult) we got this information: + Mac0 + --- + 4 bytes - 'SOUN' + BE 4 bytes - block length + + 4 bytes - 'Mac0' + BE 4 bytes - (blockLength - 27) + 28 bytes - ??? + + do this three times (once for each channel): + 4 bytes - 'Chan' + BE 4 bytes - channel length + 4 bytes - instrument name (e.g. 'MARI') + + do this for ((chanLength-24)/4) times: + 2 bytes - note duration + 1 byte - note value + 1 byte - note velocity + + 4 bytes - ??? + 4 bytes - 'Loop'/'Done' + 4 bytes - ??? + + 1 byte - 0x09 + --- + + The instruments presumably correspond to the snd resource names in the + Monkey Island executable: + + Instruments + "MARI" - MARIMBA + "PLUC" - PLUCK + "HARM" - HARMONIC + "PIPE" - PIPEORGAN + "TROM" - TROMBONE + "STRI" - STRINGS + "HORN" - HORN + "VIBE" - VIBES + "SHAK" - SHAKUHACHI + "PANP" - PANPIPE + "WHIS" - WHISTLE + "ORGA" - ORGAN3 + "BONG" - BONGO + "BASS" - BASS + + --- + + Note values <= 1 are silent. +*/ + +#include "common/macresman.h" +#include "common/translation.h" +#include "engines/engine.h" +#include "gui/message.h" +#include "scumm/player_v5m.h" +#include "scumm/scumm.h" + +namespace Scumm { + +Player_V5M::Player_V5M(ScummEngine *scumm, Audio::Mixer *mixer) + : Player_Mac(scumm, mixer, 3, 0x07, false) { + assert(_vm->_game.id == GID_MONKEY); +} + +// Try both with and without underscore in the filename, because hfsutils may +// turn the space into an underscore. At least, it did for me. + +static const char *monkeyIslandFileNames[] = { + "Monkey Island", + "Monkey_Island" +}; + +bool Player_V5M::checkMusicAvailable() { + Common::MacResManager resource; + + for (int i = 0; i < ARRAYSIZE(monkeyIslandFileNames); i++) { + if (resource.exists(monkeyIslandFileNames[i])) { + return true; + } + } + + GUI::MessageDialog dialog(_( + "Could not find the 'Monkey Island' Macintosh executable to read the\n" + "instruments from. Music will be disabled."), _("OK")); + dialog.runModal(); + return false; +} + +bool Player_V5M::loadMusic(const byte *ptr) { + Common::MacResManager resource; + bool found = false; + uint i; + + for (i = 0; i < ARRAYSIZE(monkeyIslandFileNames); i++) { + if (resource.open(monkeyIslandFileNames[i])) { + found = true; + break; + } + } + + if (!found) { + return false; + } + + ptr += 8; + // TODO: Decipher the unknown bytes in the header. For now, skip 'em + ptr += 28; + + Common::MacResIDArray idArray = resource.getResIDArray(RES_SND); + + // Load the three channels and their instruments + for (i = 0; i < 3; i++) { + assert(READ_BE_UINT32(ptr) == MKTAG('C', 'h', 'a', 'n')); + uint32 len = READ_BE_UINT32(ptr + 4); + uint32 instrument = READ_BE_UINT32(ptr + 8); + + _channel[i]._length = len - 20; + _channel[i]._data = ptr + 12; + _channel[i]._looped = (READ_BE_UINT32(ptr + len - 8) == MKTAG('L', 'o', 'o', 'p')); + _channel[i]._pos = 0; + _channel[i]._pitchModifier = 0; + _channel[i]._velocity = 0; + _channel[i]._remaining = 0; + _channel[i]._notesLeft = true; + + for (uint j = 0; j < idArray.size(); j++) { + Common::String name = resource.getResName(RES_SND, idArray[j]); + if (instrument == READ_BE_UINT32(name.c_str())) { + debug(6, "Player_V5M::loadMusic: Channel %d: Loading instrument '%s'", i, name.c_str()); + Common::SeekableReadStream *stream = resource.getResource(RES_SND, idArray[j]); + + if (!_channel[i].loadInstrument(stream)) { + resource.close(); + return false; + } + + break; + } + } + + ptr += len; + } + + resource.close(); + + // The last note of each channel is just zeroes. We will adjust this + // note so that all the channels end at the same time. + + uint32 samples[3]; + uint32 maxSamples = 0; + for (i = 0; i < 3; i++) { + samples[i] = 0; + for (uint j = 0; j < _channel[i]._length; j += 4) { + samples[i] += durationToSamples(READ_BE_UINT16(&_channel[i]._data[j])); + } + if (samples[i] > maxSamples) { + maxSamples = samples[i]; + } + } + + for (i = 0; i < 3; i++) { + _lastNoteSamples[i] = maxSamples - samples[i]; + } + + return true; +} + +bool Player_V5M::getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity) { + if (_channel[ch]._pos >= _channel[ch]._length) { + if (!_channel[ch]._looped) { + _channel[ch]._notesLeft = false; + return false; + } + // FIXME: Jamieson630: The jump seems to be happening + // too quickly! There should maybe be a pause after + // the last Note Off? But I couldn't find one in the + // MI1 Lookout music, where I was hearing problems. + _channel[ch]._pos = 0; + } + uint16 duration = READ_BE_UINT16(&_channel[ch]._data[_channel[ch]._pos]); + byte note = _channel[ch]._data[_channel[ch]._pos + 2]; + samples = durationToSamples(duration); + + if (note != 1) { + _channel[ch]._instrument.newNote(); + } + + if (note > 1) { + pitchModifier = noteToPitchModifier(note, &_channel[ch]._instrument); + velocity = _channel[ch]._data[_channel[ch]._pos + 3]; + } else if (note == 1) { + // This is guesswork, but Monkey Island uses two different + // "special" note values: 0, which is clearly a rest, and 1 + // which is... I thought at first it was a "soft" key off, to + // fade out the note, but listening to the music in a Mac + // emulator (which unfortunately doesn't work all that well), + // I hear no trace of fading out. + // + // It could mean "change the volume on the current note", but + // I can't hear that either, and it always seems to use the + // exact same velocity on this note. + // + // So it appears it really just is a "hold the current note", + // but why? Couldn't they just have made the original note + // longer? + + pitchModifier = _channel[ch]._pitchModifier; + velocity = _channel[ch]._velocity; + } else { + pitchModifier = 0; + velocity = 0; + } + + _channel[ch]._pos += 4; + + if (_channel[ch]._pos >= _channel[ch]._length) { + samples = _lastNoteSamples[ch]; + } + return true; +} + +} // End of namespace Scumm diff --git a/engines/scumm/player_v5m.h b/engines/scumm/player_v5m.h new file mode 100644 index 0000000000..b2079ee331 --- /dev/null +++ b/engines/scumm/player_v5m.h @@ -0,0 +1,57 @@ +/* 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 SCUMM_PLAYER_V5M_H +#define SCUMM_PLAYER_V5M_H + +#include "common/scummsys.h" +#include "common/util.h" +#include "common/mutex.h" +#include "scumm/music.h" +#include "scumm/player_mac.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" + +class Mixer; + +namespace Scumm { + +class ScummEngine; + +/** + * Scumm V5 Macintosh music driver. + */ +class Player_V5M : public Player_Mac { +public: + Player_V5M(ScummEngine *scumm, Audio::Mixer *mixer); + + virtual bool checkMusicAvailable(); + virtual bool loadMusic(const byte *ptr); + virtual bool getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity); + +private: + uint32 _lastNoteSamples[3]; +}; + +} // End of namespace Scumm + +#endif diff --git a/engines/scumm/saveload.cpp b/engines/scumm/saveload.cpp index 72896e097a..3453e53a18 100644 --- a/engines/scumm/saveload.cpp +++ b/engines/scumm/saveload.cpp @@ -1477,9 +1477,13 @@ void ScummEngine::saveOrLoad(Serializer *s) { } - // Save/load FM-Towns audio status - if (_townsPlayer) - _townsPlayer->saveLoadWithSerializer(s); + // + // Save/load music engine status + // + if (_musicEngine) { + _musicEngine->saveLoadWithSerializer(s); + } + // // Save/load the charset renderer state diff --git a/engines/scumm/saveload.h b/engines/scumm/saveload.h index a640bc1e17..4bfa7d0e71 100644 --- a/engines/scumm/saveload.h +++ b/engines/scumm/saveload.h @@ -47,7 +47,7 @@ namespace Scumm { * only saves/loads those which are valid for the version of the savegame * which is being loaded/saved currently. */ -#define CURRENT_VER 93 +#define CURRENT_VER 94 /** * An auxillary macro, used to specify savegame versions. We use this instead diff --git a/engines/scumm/script.cpp b/engines/scumm/script.cpp index d8c4948ea8..8587fb8092 100644 --- a/engines/scumm/script.cpp +++ b/engines/scumm/script.cpp @@ -1366,9 +1366,15 @@ void ScummEngine::runInputScript(int clickArea, int val, int mode) { // Clicks are handled differently in Indy3 mac: param 2 of the // input script is set to 0 for normal clicks, and to 1 for double clicks. + // The EGA DOS version of Loom also checks that the second click happens + // close enough to the first one, but that seems like overkill. uint32 time = _system->getMillis(); args[2] = (time < _lastInputScriptTime + 500); // 500 ms double click delay _lastInputScriptTime = time; + } else if (_game.id == GID_LOOM && _game.platform == Common::kPlatformMacintosh) { + uint32 time = _system->getMillis(); + VAR(52) = (time < _lastInputScriptTime + 500); // 500 ms double click delay + _lastInputScriptTime = time; } if (verbScript) diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp index 6a4fe238a1..3afeeda13d 100644 --- a/engines/scumm/scumm.cpp +++ b/engines/scumm/scumm.cpp @@ -61,7 +61,9 @@ #include "scumm/player_v2cms.h" #include "scumm/player_v2a.h" #include "scumm/player_v3a.h" +#include "scumm/player_v3m.h" #include "scumm/player_v4a.h" +#include "scumm/player_v5m.h" #include "scumm/resource.h" #include "scumm/he/resource_he.h" #include "scumm/scumm_v0.h" @@ -1819,6 +1821,12 @@ void ScummEngine::setupMusic(int midi) { #endif } else if (_game.platform == Common::kPlatformAmiga && _game.version <= 4) { _musicEngine = new Player_V4A(this, _mixer); + } else if (_game.platform == Common::kPlatformMacintosh && _game.id == GID_LOOM) { + _musicEngine = new Player_V3M(this, _mixer); + ((Player_V3M *)_musicEngine)->init(); + } else if (_game.platform == Common::kPlatformMacintosh && _game.id == GID_MONKEY) { + _musicEngine = new Player_V5M(this, _mixer); + ((Player_V5M *)_musicEngine)->init(); } else if (_game.id == GID_MANIAC && _game.version == 1) { _musicEngine = new Player_V1(this, _mixer, MidiDriver::getMusicType(dev) != MT_PCSPK); } else if (_game.version <= 2) { diff --git a/engines/scumm/sound.cpp b/engines/scumm/sound.cpp index a1cecfa0b3..2fe16c5441 100644 --- a/engines/scumm/sound.cpp +++ b/engines/scumm/sound.cpp @@ -346,29 +346,6 @@ void Sound::playSound(int soundID) { warning("Scumm::Sound::playSound: encountered audio resoure with chunk type 'SOUN' and sound type %d", type); } } - else if ((_vm->_game.id == GID_LOOM) && (_vm->_game.platform == Common::kPlatformMacintosh)) { - // Mac version of Loom uses yet another sound format - /* - playSound #9 (room 70) - 000000: 55 00 00 45 73 6f 00 64 01 00 00 00 00 00 00 00 |U..Eso.d........| - 000010: 00 05 00 8e 2a 8f 2d 1c 2a 8f 2a 8f 2d 1c 00 28 |....*.-.*.*.-..(| - 000020: 00 31 00 3a 00 43 00 4c 00 01 00 00 00 01 00 64 |.1.:.C.L.......d| - 000030: 5a 00 01 00 00 00 01 00 64 00 00 01 00 00 00 01 |Z.......d.......| - 000040: 00 64 5a 00 01 00 00 00 01 00 64 5a 00 01 00 00 |.dZ.......dZ....| - 000050: 00 01 00 64 00 00 00 00 00 00 00 07 00 00 00 64 |...d...........d| - 000060: 64 00 00 4e 73 6f 00 64 01 00 00 00 00 00 00 00 |d..Nso.d........| - 000070: 00 05 00 89 3d 57 2d 1c 3d 57 3d 57 2d 1c 00 28 |....=W-.=W=W-..(| - playSound #16 (room 69) - 000000: dc 00 00 a5 73 6f 00 64 01 00 00 00 00 00 00 00 |....so.d........| - 000010: 00 05 00 00 2a 8f 03 e8 03 e8 03 e8 03 e8 00 28 |....*..........(| - 000020: 00 79 00 7f 00 85 00 d6 00 01 00 00 00 19 01 18 |.y..............| - 000030: 2f 00 18 00 01 18 32 00 18 00 01 18 36 00 18 00 |/.....2.....6...| - 000040: 01 18 3b 00 18 00 01 18 3e 00 18 00 01 18 42 00 |..;.....>.....B.| - 000050: 18 00 01 18 47 00 18 00 01 18 4a 00 18 00 01 18 |....G.....J.....| - 000060: 4e 00 10 00 01 18 53 00 10 00 01 18 56 00 10 00 |N.....S.....V...| - 000070: 01 18 5a 00 10 00 02 28 5f 00 01 00 00 00 00 00 |..Z....(_.......| - */ - } else if ((_vm->_game.platform == Common::kPlatformMacintosh) && (_vm->_game.id == GID_INDY3) && READ_BE_UINT16(ptr + 8) == 0x1C) { // Sound format as used in Indy3 EGA Mac. // It seems to be closely related to the Amiga format, see player_v3a.cpp @@ -414,8 +391,7 @@ void Sound::playSound(int soundID) { } else { - if (_vm->_game.id == GID_MONKEY_VGA || _vm->_game.id == GID_MONKEY_EGA - || (_vm->_game.id == GID_MONKEY && _vm->_game.platform == Common::kPlatformMacintosh)) { + if (_vm->_game.id == GID_MONKEY_VGA || _vm->_game.id == GID_MONKEY_EGA) { // Works around the fact that in some places in MonkeyEGA/VGA, // the music is never explicitly stopped. // Rather it seems that starting a new music is supposed to @@ -1086,9 +1062,6 @@ void Sound::saveLoadWithSerializer(Serializer *ser) { #pragma mark --- Sound resource handling --- #pragma mark - -static void convertMac0Resource(ResourceManager *res, ResId idx, byte *src_ptr, int size); - - /* * TODO: The way we handle sound/music resources really is one huge hack. * We probably should reconsider how we do this, and maybe come up with a @@ -1208,11 +1181,9 @@ int ScummEngine::readSoundResource(ResId idx) { case MKTAG('M','a','c','0'): _fileHandle->seek(-12, SEEK_CUR); total_size = _fileHandle->readUint32BE() - 8; - ptr = (byte *)calloc(total_size, 1); + ptr = _res->createResource(rtSound, idx, total_size); _fileHandle->read(ptr, total_size); //dumpResource("sound-", idx, ptr); - convertMac0Resource(_res, idx, ptr, total_size); - free(ptr); return 1; case MKTAG('M','a','c','1'): @@ -1445,219 +1416,6 @@ static byte *writeVLQ(byte *ptr, int value) { return ptr; } -static byte Mac0ToGMInstrument(uint32 type, int &transpose) { - transpose = 0; - switch (type) { - case MKTAG('M','A','R','I'): return 12; - case MKTAG('P','L','U','C'): return 45; - case MKTAG('H','A','R','M'): return 22; - case MKTAG('P','I','P','E'): return 19; - case MKTAG('T','R','O','M'): transpose = -12; return 57; - case MKTAG('S','T','R','I'): return 48; - case MKTAG('H','O','R','N'): return 60; - case MKTAG('V','I','B','E'): return 11; - case MKTAG('S','H','A','K'): return 77; - case MKTAG('P','A','N','P'): return 75; - case MKTAG('W','H','I','S'): return 76; - case MKTAG('O','R','G','A'): return 17; - case MKTAG('B','O','N','G'): return 115; - case MKTAG('B','A','S','S'): transpose = -24; return 35; - default: - error("Unknown Mac0 instrument %s found", tag2str(type)); - } -} - -static void convertMac0Resource(ResourceManager *res, ResId idx, byte *src_ptr, int size) { - /* - From Markus Magnuson (superqult) we got this information: - Mac0 - --- - 4 bytes - 'SOUN' - BE 4 bytes - block length - - 4 bytes - 'Mac0' - BE 4 bytes - (blockLength - 27) - 28 bytes - ??? - - do this three times (once for each channel): - 4 bytes - 'Chan' - BE 4 bytes - channel length - 4 bytes - instrument name (e.g. 'MARI') - - do this for ((chanLength-24)/4) times: - 2 bytes - note duration - 1 byte - note value - 1 byte - note velocity - - 4 bytes - ??? - 4 bytes - 'Loop'/'Done' - 4 bytes - ??? - - 1 byte - 0x09 - --- - - Instruments (General Midi): - "MARI" - Marimba (12) - "PLUC" - Pizzicato Strings (45) - "HARM" - Harmonica (22) - "PIPE" - Church Organ? (19) or Flute? (73) or Bag Pipe (109) - "TROM" - Trombone (57) - "STRI" - String Ensemble (48 or 49) - "HORN" - French Horn? (60) or English Horn? (69) - "VIBE" - Vibraphone (11) - "SHAK" - Shakuhachi? (77) - "PANP" - Pan Flute (75) - "WHIS" - Whistle (78) / Bottle (76) - "ORGA" - Drawbar Organ (16; but could also be 17-20) - "BONG" - Woodblock? (115) - "BASS" - Bass (32-39) - - - Now the task could be to convert this into MIDI, to be fed into iMuse. - Or we do something similiar to what is done in Player_V3, assuming - we can identify SFX in the MI datafiles for each of the instruments - listed above. - */ - -#if 0 - byte *ptr = _res->createResource(rtSound, idx, size); - memcpy(ptr, src_ptr, size); -#else - const int ppqn = 480; - byte *ptr, *start_ptr; - - int total_size = 0; - total_size += kMIDIHeaderSize; // Header - total_size += 7; // Tempo META - total_size += 3 * 3; // Three program change mesages - total_size += 22; // Possible jump SysEx - total_size += 5; // EOT META - - int i, len; - byte track_instr[3]; - byte *track_data[3]; - int track_len[3]; - int track_transpose[3]; - bool looped = false; - - src_ptr += 8; - // TODO: Decipher the unknown bytes in the header. For now, skip 'em - src_ptr += 28; - - // Parse the three channels - for (i = 0; i < 3; i++) { - assert(READ_BE_UINT32(src_ptr) == MKTAG('C','h','a','n')); - len = READ_BE_UINT32(src_ptr + 4); - track_len[i] = len - 24; - track_instr[i] = Mac0ToGMInstrument(READ_BE_UINT32(src_ptr + 8), track_transpose[i]); - track_data[i] = src_ptr + 12; - src_ptr += len; - looped = (READ_BE_UINT32(src_ptr - 8) == MKTAG('L','o','o','p')); - - // For each note event, we need up to 6 bytes for the - // Note On (3 VLQ, 3 event), and 6 bytes for the Note - // Off (3 VLQ, 3 event). So 12 bytes total. - total_size += 12 * track_len[i]; - } - assert(*src_ptr == 0x09); - - // Create sound resource - start_ptr = res->createResource(rtSound, idx, total_size); - - // Insert MIDI header - ptr = writeMIDIHeader(start_ptr, "GMD ", ppqn, total_size); - - // Write a tempo change Meta event - // 473 / 4 Hz, convert to micro seconds. - uint32 dw = 1000000 * 437 / 4 / ppqn; // 1000000 * ppqn * 4 / 473; - memcpy(ptr, "\x00\xFF\x51\x03", 4); ptr += 4; - *ptr++ = (byte)((dw >> 16) & 0xFF); - *ptr++ = (byte)((dw >> 8) & 0xFF); - *ptr++ = (byte)(dw & 0xFF); - - // Insert program change messages - *ptr++ = 0; // VLQ - *ptr++ = 0xC0; - *ptr++ = track_instr[0]; - *ptr++ = 0; // VLQ - *ptr++ = 0xC1; - *ptr++ = track_instr[1]; - *ptr++ = 0; // VLQ - *ptr++ = 0xC2; - *ptr++ = track_instr[2]; - - // And now, the actual composition. Please turn all cell phones - // and pagers off during the performance. Thank you. - uint16 nextTime[3] = { 1, 1, 1 }; - int stage[3] = { 0, 0, 0 }; - - while (track_len[0] | track_len[1] | track_len[2]) { - int best = -1; - uint16 bestTime = 0xFFFF; - for (i = 0; i < 3; ++i) { - if (track_len[i] && nextTime[i] < bestTime) { - bestTime = nextTime[i]; - best = i; - } - } - assert (best != -1); - - if (!stage[best]) { - // We are STARTING this event. - if (track_data[best][2] > 1) { - // Note On - ptr = writeVLQ(ptr, nextTime[best]); - *ptr++ = 0x90 | best; - *ptr++ = track_data[best][2] + track_transpose[best]; - *ptr++ = track_data[best][3] * 127 / 100; // Scale velocity - for (i = 0; i < 3; ++i) - nextTime[i] -= bestTime; - } - nextTime[best] += READ_BE_UINT16 (track_data[best]); - stage[best] = 1; - } else { - // We are ENDING this event. - if (track_data[best][2] > 1) { - // There was a Note On, so do a Note Off - ptr = writeVLQ(ptr, nextTime[best]); - *ptr++ = 0x80 | best; - *ptr++ = track_data[best][2] + track_transpose[best]; - *ptr++ = track_data[best][3] * 127 / 100; // Scale velocity - for (i = 0; i < 3; ++i) - nextTime[i] -= bestTime; - } - track_data[best] += 4; - track_len[best] -= 4; - stage[best] = 0; - } - } - - // Is this a looped song? If so, effect a loop by - // using the S&M maybe_jump SysEx command. - // FIXME: Jamieson630: The jump seems to be happening - // too quickly! There should maybe be a pause after - // the last Note Off? But I couldn't find one in the - // MI1 Lookout music, where I was hearing problems. - if (looped) { - memcpy(ptr, "\x00\xf0\x13\x7d\x30\00", 6); ptr += 6; // maybe_jump - memcpy(ptr, "\x00\x00", 2); ptr += 2; // cmd -> 0 means always jump - memcpy(ptr, "\x00\x00\x00\x00", 4); ptr += 4; // track -> 0 (only track) - memcpy(ptr, "\x00\x00\x00\x01", 4); ptr += 4; // beat -> 1 (first beat) - memcpy(ptr, "\x00\x00\x00\x01", 4); ptr += 4; // tick -> 1 - memcpy(ptr, "\x00\xf7", 2); ptr += 2; // SysEx end marker - } - - // Insert end of song META - memcpy(ptr, "\x00\xff\x2f\x00\x00", 5); ptr += 5; - - assert(ptr <= start_ptr + total_size); - - // Rewrite MIDI header, this time with true size - total_size = ptr - start_ptr; - ptr = writeMIDIHeader(start_ptr, "GMD ", ppqn, total_size); -#endif -} - static void convertADResource(ResourceManager *res, const GameSettings& game, ResId idx, byte *src_ptr, int size) { // We will ignore the PPQN in the original resource, because // it's invalid anyway. We use a constant PPQN of 480. diff --git a/engines/sword25/util/lua/llex.cpp b/engines/sword25/util/lua/llex.cpp index f8433d3afa..423f0285ca 100644 --- a/engines/sword25/util/lua/llex.cpp +++ b/engines/sword25/util/lua/llex.cpp @@ -4,6 +4,8 @@ ** See Copyright Notice in lua.h */ +// FIXME: Do not directly use iscntrl from ctype.h. +#define FORBIDDEN_SYMBOL_EXCEPTION_iscntrl #include "common/util.h" diff --git a/engines/tinsel/detection_tables.h b/engines/tinsel/detection_tables.h index 631c2dce14..4762acfe2c 100644 --- a/engines/tinsel/detection_tables.h +++ b/engines/tinsel/detection_tables.h @@ -69,7 +69,7 @@ static const TinselGameDescription gameDescriptions[] = { 0, TINSEL_V1, }, -#if 0 + { // Macintosh CD Demo V1 version, with *.scn files, see tracker #3110936 { "dw", @@ -89,7 +89,7 @@ static const TinselGameDescription gameDescriptions[] = { GF_SCNFILES, TINSEL_V1, }, -#endif + { // Multilingual Floppy V1 with *.gra files. // Note: It contains no english subtitles. { @@ -474,7 +474,6 @@ static const TinselGameDescription gameDescriptions[] = { }, #endif -#if 0 { // Mac multilanguage CD { "dw", @@ -495,8 +494,6 @@ static const TinselGameDescription gameDescriptions[] = { TINSEL_V1, }, -#endif - { // German CD re-release "Neon Edition" // Note: This release has ENGLISH.TXT (with german content) instead of GERMAN.TXT { diff --git a/engines/tinsel/graphics.cpp b/engines/tinsel/graphics.cpp index 876d182a1a..91dfd76b98 100644 --- a/engines/tinsel/graphics.cpp +++ b/engines/tinsel/graphics.cpp @@ -212,6 +212,84 @@ static void t0WrtNonZero(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool apply } /** + * Straight rendering with transparency support, Mac variant + */ +static void MacDrawTiles(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool applyClipping) { + int yClip = 0; + + if (applyClipping) { + // Adjust the height down to skip any bottom clipping + pObj->height -= pObj->botClip; + yClip = pObj->topClip; + } + + // Simple RLE-like scheme: the two first bytes of each data chunk determine + // if bytes should be repeated or copied. + // Example: 10 00 00 20 will repeat byte 0x0 0x10 times, and will copy 0x20 + // bytes from the input stream afterwards + + // Vertical loop + for (int y = 0; y < pObj->height; ++y) { + // Get the start of the next line output + uint8 *tempDest = destP; + + int leftClip = applyClipping ? pObj->leftClip : 0; + int rightClip = applyClipping ? pObj->rightClip : 0; + + // Horizontal loop + for (int x = 0; x < pObj->width; ) { + byte repeatBytes = *srcP++; + + if (repeatBytes) { + uint clipAmount = MIN<int>(repeatBytes, leftClip); + leftClip -= clipAmount; + x += clipAmount; + + // Repeat of a given color + byte color = *srcP++; + int runLength = repeatBytes - clipAmount; + + int rptLength = MAX(MIN(runLength, pObj->width - rightClip - x), 0); + if (yClip == 0) { + if (color != 0) + memset(tempDest, color, rptLength); + tempDest += rptLength; + } + + x += runLength; + } else { + // Copy a specified sequence length of pixels + byte copyBytes = *srcP++; + + uint clipAmount = MIN<int>(copyBytes, leftClip); + leftClip -= clipAmount; + x += clipAmount; + + srcP += clipAmount; + + int runLength = copyBytes - clipAmount; + int rptLength = MAX(MIN(runLength, pObj->width - rightClip - x), 0); + if (yClip == 0) { + memmove(tempDest, srcP, rptLength); + tempDest += rptLength; + } + + int overflow = (copyBytes % 2) == 0 ? 0 : 2 - (copyBytes % 2); + x += runLength; + srcP += runLength + overflow; + } + } // horizontal loop + + // Move to next line + if (yClip > 0) + --yClip; + else + destP += SCREEN_WIDTH; + } // vertical loop +} + + +/** * Straight rendering with transparency support, PSX variant supporting also 4-BIT clut data */ static void PsxDrawTiles(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool applyClipping, bool fourBitClut, uint32 psxSkipBytes, byte *psxMapperTable, bool transparency) { @@ -272,7 +350,7 @@ static void PsxDrawTiles(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool apply assert(boxBounds.bottom >= boxBounds.top); assert(boxBounds.right >= boxBounds.left); - int16 indexVal = READ_16(srcP); + int16 indexVal = READ_LE_UINT16(srcP); srcP += sizeof(uint16); // Draw a 4x4 block based on the opcode as in index into the block list @@ -381,7 +459,7 @@ static void WrtNonZero(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool applyCl assert(boxBounds.bottom >= boxBounds.top); assert(boxBounds.right >= boxBounds.left); - int16 indexVal = READ_16(srcP); + int16 indexVal = READ_LE_UINT16(srcP); srcP += sizeof(uint16); if (indexVal >= 0) { @@ -763,8 +841,8 @@ void DrawObject(DRAWOBJECT *pObj) { byte *p = (byte *)LockMem(pObj->hBits & HANDLEMASK); srcPtr = p + (pObj->hBits & OFFSETMASK); - pObj->charBase = (char *)p + READ_32(p + 0x10); - pObj->transOffset = READ_32(p + 0x14); + pObj->charBase = (char *)p + READ_LE_UINT32(p + 0x10); + pObj->transOffset = READ_LE_UINT32(p + 0x14); // Decompress block indexes for Discworld PSX if (TinselV1PSX) { @@ -793,7 +871,7 @@ void DrawObject(DRAWOBJECT *pObj) { psxPaletteMapper(pObj->pPal, srcPtr + sizeof(uint16), psxMapperTable); psxFourBitClut = true; - psxSkipBytes = READ_32(p + sizeof(uint32) * 5) << 4; // Fetch number of bytes we have to skip + psxSkipBytes = READ_LE_UINT32(p + sizeof(uint32) * 5) << 4; // Fetch number of bytes we have to skip switch (indexType) { case 0xDD: // Normal uncompressed indexes psxRLEindex = false; @@ -822,95 +900,58 @@ void DrawObject(DRAWOBJECT *pObj) { // Handle various draw types uint8 typeId = pObj->flags & 0xff; + int packType = pObj->flags >> 14; // TinselV2 - if (TinselV2) { - // Tinsel v2 decoders - // Initial switch statement for the different bit packing types - int packType = pObj->flags >> 14; - - if (packType == 0) { - // No color packing - switch (typeId) { - case 0x01: - case 0x11: - case 0x41: - case 0x51: - case 0x81: - case 0xC1: - t2WrtNonZero(pObj, srcPtr, destPtr, typeId >= 0x40, (typeId & 0x10) != 0); - break; - case 0x02: - case 0x42: - // This renderer called 'RlWrtAll', but is the same as t2WrtNonZero - t2WrtNonZero(pObj, srcPtr, destPtr, typeId >= 0x40, false); - break; - case 0x04: - case 0x44: - // WrtConst with/without clipping - WrtConst(pObj, destPtr, typeId == 0x44); - break; - case 0x08: - case 0x48: - WrtAll(pObj, srcPtr, destPtr, typeId >= 0x40); - break; - case 0x84: - case 0xC4: - // WrtTrans with/without clipping - WrtTrans(pObj, destPtr, typeId == 0xC4); - break; - default: - error("Unknown drawing type %d", typeId); - } - } else { - // 1 = 16 from 240 - // 2 = 16 from 224 - // 3 = variable color - if (packType == 1) pObj->baseCol = 0xF0; - else if (packType == 2) pObj->baseCol = 0xE0; - - PackedWrtNonZero(pObj, srcPtr, destPtr, (pObj->flags & DMA_CLIP) != 0, - (pObj->flags & DMA_FLIPH), packType); - } + if (TinselV2 && packType != 0) { + // Color packing for TinselV2 + + if (packType == 1) + pObj->baseCol = 0xF0; // 16 from 240 + else if (packType == 2) + pObj->baseCol = 0xE0; // 16 from 224 + // 3 = variable color - } else { // TinselV1 + PackedWrtNonZero(pObj, srcPtr, destPtr, (pObj->flags & DMA_CLIP) != 0, + (pObj->flags & DMA_FLIPH), packType); + } else { switch (typeId) { - case 0x01: - case 0x41: - if (TinselV1PSX) { - PsxDrawTiles(pObj, srcPtr, destPtr, typeId >= 0x40, psxFourBitClut, psxSkipBytes, psxMapperTable, true); - } else if (TinselV1Mac) { - // TODO - } else if (TinselV1) { - WrtNonZero(pObj, srcPtr, destPtr, typeId >= 0x40); - } else if (TinselV0) { - t0WrtNonZero(pObj, srcPtr, destPtr, typeId >= 0x40); - } + case 0x01: // all versions, draw sprite without clipping + case 0x41: // all versions, draw sprite with clipping + case 0x02: // TinselV2, draw sprite without clipping + case 0x11: // TinselV2, draw sprite without clipping, flipped horizontally + case 0x42: // TinselV2, draw sprite with clipping + case 0x51: // TinselV2, draw sprite with clipping, flipped horizontally + case 0x81: // TinselV2, draw sprite with clipping + case 0xC1: // TinselV2, draw sprite with clipping + assert(TinselV2 || (typeId == 0x01 || typeId == 0x41)); + + if (TinselV2) + t2WrtNonZero(pObj, srcPtr, destPtr, typeId >= 0x40, (typeId & 0x10) != 0); + else if (TinselV1PSX) + PsxDrawTiles(pObj, srcPtr, destPtr, typeId == 0x41, psxFourBitClut, psxSkipBytes, psxMapperTable, true); + else if (TinselV1Mac) + MacDrawTiles(pObj, srcPtr, destPtr, typeId == 0x41); + else if (TinselV1) + WrtNonZero(pObj, srcPtr, destPtr, typeId == 0x41); + else if (TinselV0) + t0WrtNonZero(pObj, srcPtr, destPtr, typeId == 0x41); break; - case 0x08: - case 0x48: - if (TinselV1PSX) { - PsxDrawTiles(pObj, srcPtr, destPtr, typeId >= 0x40, psxFourBitClut, psxSkipBytes, psxMapperTable, false); - } else if (TinselV1Mac) { - WrtAll(pObj, srcPtr, destPtr, typeId >= 0x40); - } else if (TinselV1) { - WrtNonZero(pObj, srcPtr, destPtr, typeId >= 0x40); - } else if (TinselV0) { - WrtAll(pObj, srcPtr, destPtr, typeId >= 0x40); - } + case 0x08: // draw background without clipping + case 0x48: // draw background with clipping + if (TinselV2 || TinselV1Mac || TinselV0) + WrtAll(pObj, srcPtr, destPtr, typeId == 0x48); + else if (TinselV1PSX) + PsxDrawTiles(pObj, srcPtr, destPtr, typeId == 0x48, psxFourBitClut, psxSkipBytes, psxMapperTable, false); + else if (TinselV1) + WrtNonZero(pObj, srcPtr, destPtr, typeId == 0x48); break; - case 0x04: - case 0x44: - // WrtConst with/without clipping + case 0x04: // fill with constant color without clipping + case 0x44: // fill with constant color with clipping WrtConst(pObj, destPtr, typeId == 0x44); break; - case 0x84: - case 0xC4: - if (!TinselV0) { - // WrtTrans with/without clipping - WrtTrans(pObj, destPtr, typeId == 0xC4); - } else { - WrtTrans(pObj, destPtr, (typeId & 0x40) != 0); - } + case 0x84: // draw transparent surface without clipping + case 0xC4: // draw transparent surface with clipping + WrtTrans(pObj, destPtr, typeId == 0xC4); break; default: error("Unknown drawing type %d", typeId); diff --git a/engines/tinsel/music.cpp b/engines/tinsel/music.cpp index 91f0312101..dab2a897fc 100644 --- a/engines/tinsel/music.cpp +++ b/engines/tinsel/music.cpp @@ -135,10 +135,10 @@ bool PlayMidiSequence(uint32 dwFileOffset, bool bLoop) { if (ConfMan.hasKey("mute")) mute = ConfMan.getBool("mute"); - // TODO: The Macintosh version of DW1 does not use MIDI for music + // The Macintosh version of DW1 uses raw PCM for music if (TinselV1Mac) - return true; - + return _vm->_sound->playDW1MacMusic(dwFileOffset); + SetMidiVolume(mute ? 0 : _vm->_config->_musicVolume); // the index and length of the last tune loaded @@ -285,7 +285,8 @@ void OpenMidiFiles() { if (TinselV0 || TinselV2) return; - // TODO: The Macintosh version of DW1 does not use MIDI for music + // The Macintosh version of DW1 does not use MIDI for music. + // It uses PCM music instead, which is quite big to be preloaded here. if (TinselV1Mac) return; diff --git a/engines/tinsel/palette.cpp b/engines/tinsel/palette.cpp index c7c8cd0b63..04018172c0 100644 --- a/engines/tinsel/palette.cpp +++ b/engines/tinsel/palette.cpp @@ -170,6 +170,12 @@ void PalettesToVideoDAC() { pal[i * 3 + 2] = TINSEL_GetBValue(pColors[i]); } + // In DW1 Mac, color 254 should be black, like in the PC version. + // We fix it here. + if (TinselV1Mac) { + pal[254 * 3 + 0] = pal[254 * 3 + 1] = pal[254 * 3 + 2] = 0; + } + // update the system palette g_system->getPaletteManager()->setPalette(pal, pDACtail->destDACindex, pDACtail->numColors); diff --git a/engines/tinsel/scene.cpp b/engines/tinsel/scene.cpp index 986d54f59f..9181f85afb 100644 --- a/engines/tinsel/scene.cpp +++ b/engines/tinsel/scene.cpp @@ -159,7 +159,8 @@ static void SceneTinselProcess(CORO_PARAM, const void *param) { // The following myEscape value setting is used for enabling title screen skipping in DW1 if (TinselV1 && (g_sceneCtr == 1)) g_initialMyEscape = GetEscEvents(); // DW1 PSX has its own scene skipping script code for scenes 2 and 3 (bug #3541542). - _ctx->myEscape = (TinselV1 && (g_sceneCtr < (TinselV1PSX ? 2 : 4))) ? g_initialMyEscape : 0; + // Same goes for DW1 Mac. + _ctx->myEscape = (TinselV1 && (g_sceneCtr < ((TinselV1PSX || TinselV1Mac) ? 2 : 4))) ? g_initialMyEscape : 0; // get the stuff copied to process when it was created _ctx->pInit = (const TP_INIT *)param; diff --git a/engines/tinsel/sound.cpp b/engines/tinsel/sound.cpp index c7b295f654..03aa3767c4 100644 --- a/engines/tinsel/sound.cpp +++ b/engines/tinsel/sound.cpp @@ -74,7 +74,7 @@ SoundManager::~SoundManager() { */ // playSample for DiscWorld 1 bool SoundManager::playSample(int id, Audio::Mixer::SoundType type, Audio::SoundHandle *handle) { - // Floppy version has no sample file + // Floppy version has no sample file. if (!_vm->isCD()) return false; @@ -177,6 +177,48 @@ bool SoundManager::playSample(int id, Audio::Mixer::SoundType type, Audio::Sound return true; } +bool SoundManager::playDW1MacMusic(int dwFileOffset) { + Common::File s; + + if (!s.open("midi.dat")) + error(CANNOT_FIND_FILE, "midi.dat"); + + s.seek(dwFileOffset); + uint32 length = s.readUint32BE(); + + // TODO: It's a bad idea to load the music track in a buffer. + // We should use a SubReadStream instead, and keep midi.dat open. + // However, the track lengths aren't that big (about 1-4MB), + // so this shouldn't be a major issue. + byte *soundData = (byte *)malloc(length); + assert(soundData); + + // read all of the sample + if (s.read(soundData, length) != length) + error(FILE_IS_CORRUPT, "midi.dat"); + + Common::SeekableReadStream *memStream = new Common::MemoryReadStream(soundData, length); + + Audio::SoundHandle *handle = &_channels[kChannelDW1MacMusic].handle; + //_channels[kChannelDW1MacMusic].sampleNum = dwFileOffset; + + // Stop any previously playing music track + _vm->_mixer->stopHandle(*handle); + + // FIXME: Should set this in a different place ;) + _vm->_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, _vm->_config->_musicVolume); + + // TODO: Compression support (MP3/OGG/FLAC) for midi.dat in DW1 Mac + Audio::RewindableAudioStream *musicStream = Audio::makeRawStream(memStream, 22050, Audio::FLAG_UNSIGNED, DisposeAfterUse::YES); + + if (musicStream) + _vm->_mixer->playStream(Audio::Mixer::kMusicSoundType, handle, Audio::makeLoopingAudioStream(musicStream, 0)); + + s.close(); + + return true; +} + // playSample for DiscWorld 2 bool SoundManager::playSample(int id, int sub, bool bLooped, int x, int y, int priority, Audio::Mixer::SoundType type, Audio::SoundHandle *handle) { @@ -369,7 +411,6 @@ bool SoundManager::offscreenChecks(int x, int &y) { } int8 SoundManager::getPan(int x) { - if (x == -1) return 0; @@ -416,14 +457,13 @@ bool SoundManager::sampleExists(int id) { /** * Returns true if a sample is currently playing. */ -bool SoundManager::sampleIsPlaying(int id) { +bool SoundManager::sampleIsPlaying() { if (!TinselV2) return _vm->_mixer->isSoundHandleActive(_channels[kChannelTinsel1].handle); for (int i = 0; i < kNumChannels; i++) - if (_channels[i].sampleNum == id) - if (_vm->_mixer->isSoundHandleActive(_channels[i].handle)) - return true; + if (_vm->_mixer->isSoundHandleActive(_channels[i].handle)) + return true; return false; } @@ -432,8 +472,6 @@ bool SoundManager::sampleIsPlaying(int id) { * Stops any currently playing sample. */ void SoundManager::stopAllSamples() { - // stop currently playing sample - if (!TinselV2) { _vm->_mixer->stopHandle(_channels[kChannelTinsel1].handle); return; @@ -466,12 +504,21 @@ void SoundManager::setSFXVolumes(uint8 volume) { _vm->_mixer->setChannelVolume(_channels[i].handle, volume); } +void SoundManager::showSoundError(const char *errorMsg, const char *soundFile) { + Common::String msg; + msg = Common::String::format(errorMsg, soundFile); + GUI::MessageDialog dialog(msg, "OK"); + dialog.runModal(); + + error("%s", msg.c_str()); +} + /** * Opens and inits all sound sample files. */ void SoundManager::openSampleFiles() { // Floppy and demo versions have no sample files, except for the Discworld 2 demo - if (!_vm->isCD() || TinselV0) + if (!_vm->isCD()) return; TinselFile f; @@ -480,42 +527,26 @@ void SoundManager::openSampleFiles() { // already allocated return; - // open sample index file in binary mode + // Open sample index (*.idx) in binary mode if (f.open(_vm->getSampleIndex(g_sampleLanguage))) { - // get length of index file - f.seek(0, SEEK_END); // move to end of file - _sampleIndexLen = f.pos(); // get file pointer - f.seek(0, SEEK_SET); // back to beginning - + uint32 fileSize = f.size(); + _sampleIndex = (uint32 *)malloc(fileSize); if (_sampleIndex == NULL) { - // allocate a buffer for the indices - _sampleIndex = (uint32 *)malloc(_sampleIndexLen); - - // make sure memory allocated - if (_sampleIndex == NULL) { - // disable samples if cannot alloc buffer for indices - // TODO: Disabled sound if we can't load the sample index? - return; - } + showSoundError(NO_MEM, _vm->getSampleIndex(g_sampleLanguage)); + return; } - // load data - if (f.read(_sampleIndex, _sampleIndexLen) != (uint32)_sampleIndexLen) - // file must be corrupt if we get to here - error(FILE_IS_CORRUPT, _vm->getSampleFile(g_sampleLanguage)); + _sampleIndexLen = fileSize / 4; // total sample of indices (DWORDs) - // close the file - f.close(); - - // convert file size to size in DWORDs - _sampleIndexLen /= sizeof(uint32); - -#ifdef SCUMM_BIG_ENDIAN - // Convert all ids from LE to native format + // Load data for (int i = 0; i < _sampleIndexLen; ++i) { - _sampleIndex[i] = SWAP_BYTES_32(_sampleIndex[i]); + _sampleIndex[i] = f.readUint32LE(); + if (f.err()) { + showSoundError(FILE_READ_ERROR, _vm->getSampleIndex(g_sampleLanguage)); + } } -#endif + + f.close(); // Detect format of soundfile by looking at 1st sample-index switch (TO_BE_32(_sampleIndex[0])) { @@ -523,48 +554,31 @@ void SoundManager::openSampleFiles() { debugC(DEBUG_DETAILED, kTinselDebugSound, "Detected MP3 sound-data"); _soundMode = kMP3Mode; break; - case MKTAG('O','G','G',' '): debugC(DEBUG_DETAILED, kTinselDebugSound, "Detected OGG sound-data"); _soundMode = kVorbisMode; break; - case MKTAG('F','L','A','C'): debugC(DEBUG_DETAILED, kTinselDebugSound, "Detected FLAC sound-data"); _soundMode = kFLACMode; break; - default: debugC(DEBUG_DETAILED, kTinselDebugSound, "Detected original sound-data"); break; } - // Normally the 1st sample-index points to nothing at all + + // Normally the 1st sample index points to nothing at all. We use it to + // determine if the game's sample files have been compressed, thus restore + // it here _sampleIndex[0] = 0; } else { - char buf[50]; - sprintf(buf, CANNOT_FIND_FILE, _vm->getSampleIndex(g_sampleLanguage)); - GUI::MessageDialog dialog(buf, "OK"); - dialog.runModal(); - - error(CANNOT_FIND_FILE, _vm->getSampleIndex(g_sampleLanguage)); + showSoundError(FILE_READ_ERROR, _vm->getSampleIndex(g_sampleLanguage)); } - // open sample file in binary mode + // Open sample file (*.smp) in binary mode if (!_sampleStream.open(_vm->getSampleFile(g_sampleLanguage))) { - char buf[50]; - sprintf(buf, CANNOT_FIND_FILE, _vm->getSampleFile(g_sampleLanguage)); - GUI::MessageDialog dialog(buf, "OK"); - dialog.runModal(); - - error(CANNOT_FIND_FILE, _vm->getSampleFile(g_sampleLanguage)); + showSoundError(FILE_READ_ERROR, _vm->getSampleFile(g_sampleLanguage)); } - -/* - // gen length of the largest sample - sampleBuffer.size = _sampleStream.readUint32LE(); - if (_sampleStream.eos() || _sampleStream.err()) - error(FILE_IS_CORRUPT, _vm->getSampleFile(g_sampleLanguage)); -*/ } void SoundManager::closeSampleStream() { diff --git a/engines/tinsel/sound.h b/engines/tinsel/sound.h index d7083b3b21..8510c1618f 100644 --- a/engines/tinsel/sound.h +++ b/engines/tinsel/sound.h @@ -51,7 +51,8 @@ protected: enum { kChannelTalk = 0, kChannelTinsel1 = 0, // Always using this channel for DW1 - kChannelSFX = 1 + kChannelSFX = 1, + kChannelDW1MacMusic = 2 }; static const int kNumChannels = kChannelSFX + kNumSFX; @@ -108,6 +109,7 @@ public: bool playSample(int id, Audio::Mixer::SoundType type, Audio::SoundHandle *handle = 0); bool playSample(int id, int sub, bool bLooped, int x, int y, int priority, Audio::Mixer::SoundType type, Audio::SoundHandle *handle = 0); + bool playDW1MacMusic(int dwFileOffset); void stopAllSamples(); // Stops any currently playing sample void stopSpecSample(int id, int sub = 0); // Stops a specific sample @@ -115,11 +117,13 @@ public: void setSFXVolumes(uint8 volume); bool sampleExists(int id); - bool sampleIsPlaying(int id = -1); + bool sampleIsPlaying(); - // TODO: Internal method, make this protected? void openSampleFiles(); void closeSampleStream(); + +private: + void showSoundError(const char *errorMsg, const char *soundFile); }; } // End of namespace Tinsel diff --git a/engines/tinsel/tinlib.cpp b/engines/tinsel/tinlib.cpp index 058f8eb6fd..6a396b9b01 100644 --- a/engines/tinsel/tinlib.cpp +++ b/engines/tinsel/tinlib.cpp @@ -3684,7 +3684,7 @@ static void TranslucentIndex(unsigned index) { } /** - * Play a sample. + * Play a sample (DW1 only). */ static void TryPlaySample(CORO_PARAM, int sample, bool bComplete, bool escOn, int myEscape) { CORO_BEGIN_CONTEXT; diff --git a/engines/tinsel/tinsel.cpp b/engines/tinsel/tinsel.cpp index 944613bcb7..e836fdff1f 100644 --- a/engines/tinsel/tinsel.cpp +++ b/engines/tinsel/tinsel.cpp @@ -730,8 +730,7 @@ void LoadBasicChunks() { cptr = FindChunk(INV_OBJ_SCNHANDLE, CHUNK_OBJECTS); -#ifdef SCUMM_BIG_ENDIAN - //convert to native endianness + // Convert to native endianness INV_OBJECT *io = (INV_OBJECT *)cptr; for (int i = 0; i < numObjects; i++, io++) { io->id = FROM_32(io->id); @@ -739,7 +738,6 @@ void LoadBasicChunks() { io->hScript = FROM_32(io->hScript); io->attribute = FROM_32(io->attribute); } -#endif RegisterIcons(cptr, numObjects); diff --git a/engines/tinsel/tinsel.h b/engines/tinsel/tinsel.h index 32e21842a1..ec504b69cd 100644 --- a/engines/tinsel/tinsel.h +++ b/engines/tinsel/tinsel.h @@ -78,7 +78,7 @@ enum TinselGameFeatures { /** * The following is the ScummVM definitions of the various Tinsel versions: * TINSEL_V0 - This was an early engine version that was only used in the Discworld 1 - * demo. It is not currently supported. + * demo. * TINSEL_V1 - This was the engine version used by Discworld 1. Note that there were two * major releases: an earlier version that used *.gra files, and a later one that * used *.scn files, and contained certain script and engine bugfixes. In ScummVM, diff --git a/engines/touche/menu.cpp b/engines/touche/menu.cpp index 57882fa6aa..85ca519f05 100644 --- a/engines/touche/menu.cpp +++ b/engines/touche/menu.cpp @@ -103,7 +103,7 @@ struct MenuData { void addCharToDescription(int slot, char chr) { char *description = saveLoadDescriptionsTable[slot]; int descriptionLen = strlen(description); - if (descriptionLen < 32 && isprint(static_cast<unsigned char>(chr))) { + if (descriptionLen < 32 && Common::isPrint(chr)) { description[descriptionLen] = chr; description[descriptionLen + 1] = 0; } diff --git a/engines/wintermute/base/base_keyboard_state.cpp b/engines/wintermute/base/base_keyboard_state.cpp index e12583b5a4..072a1bb71b 100644 --- a/engines/wintermute/base/base_keyboard_state.cpp +++ b/engines/wintermute/base/base_keyboard_state.cpp @@ -200,9 +200,8 @@ const char *BaseKeyboardState::scToString() { bool BaseKeyboardState::readKey(Common::Event *event) { //_currentPrintable = (event->type == SDL_TEXTINPUT); // TODO _currentCharCode = keyCodeToVKey(event); - if ((_currentCharCode <= Common::KEYCODE_z && _currentCharCode >= Common::KEYCODE_a) || - (_currentCharCode <= Common::KEYCODE_9 && _currentCharCode >= Common::KEYCODE_0) || - (_currentCharCode == Common::KEYCODE_SPACE)) { + // Verify that this is a printable ISO-8859-character (including the upper charset) + if ((_currentCharCode <= 0x7E && _currentCharCode >= 0x20) || (_currentCharCode <= 0xFF && _currentCharCode >= 0xA0)) { _currentPrintable = true; } else { _currentPrintable = false; @@ -272,7 +271,7 @@ uint32 BaseKeyboardState::keyCodeToVKey(Common::Event *event) { case Common::KEYCODE_KP_ENTER: return Common::KEYCODE_RETURN; default: - return (uint32)event->kbd.keycode; + return (uint32)event->kbd.ascii; } } diff --git a/engines/wintermute/base/base_persistence_manager.cpp b/engines/wintermute/base/base_persistence_manager.cpp index 4cb67b87e1..bd53ed38e4 100644 --- a/engines/wintermute/base/base_persistence_manager.cpp +++ b/engines/wintermute/base/base_persistence_manager.cpp @@ -36,8 +36,10 @@ #include "engines/wintermute/math/vector2.h" #include "engines/wintermute/base/gfx/base_image.h" #include "engines/wintermute/base/sound/base_sound.h" +#include "engines/wintermute/graphics/transparent_surface.h" #include "engines/wintermute/wintermute.h" #include "graphics/decoders/bmp.h" +#include "graphics/scaler.h" #include "common/memstream.h" #include "common/str.h" #include "common/system.h" @@ -148,9 +150,13 @@ void BasePersistenceManager::getSaveStateDesc(int slot, SaveStateDescriptor &des Common::MemoryReadStream thumbStream(_thumbnailData, _thumbnailDataSize); Graphics::BitmapDecoder bmpDecoder; if (bmpDecoder.loadStream(thumbStream)) { - Graphics::Surface *surf = new Graphics::Surface; + Graphics::Surface *surf = NULL; surf = bmpDecoder.getSurface()->convertTo(g_system->getOverlayFormat()); - desc.setThumbnail(surf); + TransparentSurface *scaleableSurface = new TransparentSurface(*surf, false); + Graphics::Surface *scaled = scaleableSurface->scale(kThumbnailWidth, kThumbnailHeight2); + desc.setThumbnail(scaled); + delete scaleableSurface; + delete surf; } } diff --git a/engines/wintermute/base/gfx/osystem/base_render_osystem.cpp b/engines/wintermute/base/gfx/osystem/base_render_osystem.cpp index 3255156599..5097d2078f 100644 --- a/engines/wintermute/base/gfx/osystem/base_render_osystem.cpp +++ b/engines/wintermute/base/gfx/osystem/base_render_osystem.cpp @@ -28,6 +28,7 @@ #include "engines/wintermute/base/gfx/osystem/base_render_osystem.h" #include "engines/wintermute/base/gfx/osystem/base_surface_osystem.h" +#include "engines/wintermute/base/gfx/osystem/render_ticket.h" #include "engines/wintermute/base/base_surface_storage.h" #include "engines/wintermute/base/gfx/base_image.h" #include "engines/wintermute/math/math_util.h" @@ -40,56 +41,6 @@ namespace Wintermute { -RenderTicket::RenderTicket(BaseSurfaceOSystem *owner, const Graphics::Surface *surf, Common::Rect *srcRect, Common::Rect *dstRect, bool mirrorX, bool mirrorY, bool disableAlpha) : _owner(owner), - _srcRect(*srcRect), _dstRect(*dstRect), _drawNum(0), _isValid(true), _wantsDraw(true), _hasAlpha(!disableAlpha) { - _colorMod = 0; - _mirror = TransparentSurface::FLIP_NONE; - if (mirrorX) { - _mirror |= TransparentSurface::FLIP_V; - } - if (mirrorY) { - _mirror |= TransparentSurface::FLIP_H; - } - if (surf) { - _surface = new Graphics::Surface(); - _surface->create((uint16)srcRect->width(), (uint16)srcRect->height(), surf->format); - assert(_surface->format.bytesPerPixel == 4); - // Get a clipped copy of the surface - for (int i = 0; i < _surface->h; i++) { - memcpy(_surface->getBasePtr(0, i), surf->getBasePtr(srcRect->left, srcRect->top + i), srcRect->width() * _surface->format.bytesPerPixel); - } - // Then scale it if necessary - if (dstRect->width() != srcRect->width() || dstRect->height() != srcRect->height()) { - TransparentSurface src(*_surface, false); - Graphics::Surface *temp = src.scale(dstRect->width(), dstRect->height()); - _surface->free(); - delete _surface; - _surface = temp; - } - } else { - _surface = NULL; - } -} - -RenderTicket::~RenderTicket() { - if (_surface) { - _surface->free(); - delete _surface; - } -} - -bool RenderTicket::operator==(RenderTicket &t) { - if ((t._srcRect != _srcRect) || - (t._dstRect != _dstRect) || - (t._mirror != _mirror) || - (t._owner != _owner) || - (t._hasAlpha != _hasAlpha) || - (t._colorMod != _colorMod)) { - return false; - } - return true; -} - BaseRenderer *makeOSystemRenderer(BaseGame *inGame) { return new BaseRenderOSystem(inGame); } @@ -100,6 +51,8 @@ BaseRenderOSystem::BaseRenderOSystem(BaseGame *inGame) : BaseRenderer(inGame) { _blankSurface = new Graphics::Surface(); _drawNum = 1; _needsFlip = true; + _spriteBatch = false; + _batchNum = 0; _borderLeft = _borderRight = _borderTop = _borderBottom = 0; _ratioX = _ratioY = 1.0f; @@ -310,15 +263,23 @@ void BaseRenderOSystem::drawSurface(BaseSurfaceOSystem *owner, const Graphics::S if (owner) { // Fade-tickets are owner-less RenderTicket compare(owner, NULL, srcRect, dstRect, mirrorX, mirrorY, disableAlpha); + compare._batchNum = _batchNum; + if (_spriteBatch) + _batchNum++; compare._colorMod = _colorMod; RenderQueueIterator it; - for (it = _renderQueue.begin(); it != _renderQueue.end(); ++it) { - if ((*it)->_owner == owner && *(*it) == compare && (*it)->_isValid) { - (*it)->_colorMod = _colorMod; + // Avoid calling end() and operator* every time, when potentially going through + // LOTS of tickets. + RenderQueueIterator endIterator = _renderQueue.end(); + RenderTicket *compareTicket = NULL; + for (it = _renderQueue.begin(); it != endIterator; ++it) { + compareTicket = *it; + if (*(compareTicket) == compare && compareTicket->_isValid) { + compareTicket->_colorMod = _colorMod; if (_disableDirtyRects) { - drawFromSurface(*it, NULL); + drawFromSurface(compareTicket, NULL); } else { - drawFromTicket(*it); + drawFromTicket(compareTicket); } return; } @@ -465,7 +426,7 @@ void BaseRenderOSystem::drawTickets() { dstClip.translate(-offsetX, -offsetY); _colorMod = ticket->_colorMod; - drawFromSurface(ticket->getSurface(), &ticket->_srcRect, &pos, &dstClip, ticket->_mirror); + drawFromSurface(ticket, &ticket->_srcRect, &pos, &dstClip); _needsFlip = true; } // Some tickets want redraw but don't actually clip the dirty area (typically the ones that shouldnt become clear-color) @@ -511,20 +472,8 @@ void BaseRenderOSystem::drawFromSurface(RenderTicket *ticket, Common::Rect *clip delete clipRect; } } -void BaseRenderOSystem::drawFromSurface(const Graphics::Surface *surf, Common::Rect *srcRect, Common::Rect *dstRect, Common::Rect *clipRect, uint32 mirror) { - TransparentSurface src(*surf, false); - bool doDelete = false; - if (!clipRect) { - doDelete = true; - clipRect = new Common::Rect(); - clipRect->setWidth(surf->w); - clipRect->setHeight(surf->h); - } - - src.blit(*_renderSurface, dstRect->left, dstRect->top, mirror, clipRect, _colorMod, clipRect->width(), clipRect->height()); - if (doDelete) { - delete clipRect; - } +void BaseRenderOSystem::drawFromSurface(RenderTicket *ticket, Common::Rect *srcRect, Common::Rect *dstRect, Common::Rect *clipRect) { + ticket->drawToSurface(_renderSurface, srcRect, dstRect, clipRect); } ////////////////////////////////////////////////////////////////////////// @@ -642,4 +591,16 @@ void BaseRenderOSystem::endSaveLoad() { _drawNum = 1; } +bool BaseRenderOSystem::startSpriteBatch() { + _spriteBatch = true; + _batchNum = 1; + return STATUS_OK; +} + +bool BaseRenderOSystem::endSpriteBatch() { + _spriteBatch = false; + _batchNum = 0; + return STATUS_OK; +} + } // end of namespace Wintermute diff --git a/engines/wintermute/base/gfx/osystem/base_render_osystem.h b/engines/wintermute/base/gfx/osystem/base_render_osystem.h index 1e9b4ed2e2..e7f14470f4 100644 --- a/engines/wintermute/base/gfx/osystem/base_render_osystem.h +++ b/engines/wintermute/base/gfx/osystem/base_render_osystem.h @@ -36,27 +36,7 @@ namespace Wintermute { class BaseSurfaceOSystem; -class RenderTicket { - Graphics::Surface *_surface; -public: - RenderTicket(BaseSurfaceOSystem *owner, const Graphics::Surface *surf, Common::Rect *srcRect, Common::Rect *dstRest, bool mirrorX = false, bool mirrorY = false, bool disableAlpha = false); - RenderTicket() : _isValid(true), _wantsDraw(false), _drawNum(0) {} - ~RenderTicket(); - const Graphics::Surface *getSurface() { return _surface; } - Common::Rect _srcRect; - Common::Rect _dstRect; - uint32 _mirror; - bool _hasAlpha; - - bool _isValid; - bool _wantsDraw; - uint32 _drawNum; - uint32 _colorMod; - - BaseSurfaceOSystem *_owner; - bool operator==(RenderTicket &a); -}; - +class RenderTicket; class BaseRenderOSystem : public BaseRenderer { public: BaseRenderOSystem(BaseGame *inGame); @@ -97,14 +77,18 @@ public: float getScaleRatioY() const { return _ratioY; } + virtual bool startSpriteBatch(); + virtual bool endSpriteBatch(); void endSaveLoad(); void drawSurface(BaseSurfaceOSystem *owner, const Graphics::Surface *surf, Common::Rect *srcRect, Common::Rect *dstRect, bool mirrorX, bool mirrorY, bool disableAlpha = false); BaseSurface *createSurface(); private: void addDirtyRect(const Common::Rect &rect); void drawTickets(); + // Non-dirty-rects: void drawFromSurface(RenderTicket *ticket, Common::Rect *clipRect); - void drawFromSurface(const Graphics::Surface *surf, Common::Rect *srcRect, Common::Rect *dstRect, Common::Rect *clipRect, uint32 mirror); + // Dirty-rects: + void drawFromSurface(RenderTicket *ticket, Common::Rect *srcRect, Common::Rect *dstRect, Common::Rect *clipRect); typedef Common::List<RenderTicket *>::iterator RenderQueueIterator; Common::Rect *_dirtyRect; Common::List<RenderTicket *> _renderQueue; @@ -120,6 +104,8 @@ private: int _borderBottom; bool _disableDirtyRects; + bool _spriteBatch; + uint32 _batchNum; float _ratioX; float _ratioY; uint32 _colorMod; diff --git a/engines/wintermute/base/gfx/osystem/render_ticket.cpp b/engines/wintermute/base/gfx/osystem/render_ticket.cpp new file mode 100644 index 0000000000..8b513f8543 --- /dev/null +++ b/engines/wintermute/base/gfx/osystem/render_ticket.cpp @@ -0,0 +1,103 @@ +/* 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. + * + */ + +/* + * This file is based on WME Lite. + * http://dead-code.org/redir.php?target=wmelite + * Copyright (c) 2011 Jan Nedoma + */ + +#include "engines/wintermute/graphics/transparent_surface.h" +#include "engines/wintermute/base/gfx/osystem/render_ticket.h" + +namespace Wintermute { + +RenderTicket::RenderTicket(BaseSurfaceOSystem *owner, const Graphics::Surface *surf, Common::Rect *srcRect, Common::Rect *dstRect, bool mirrorX, bool mirrorY, bool disableAlpha) : _owner(owner), +_srcRect(*srcRect), _dstRect(*dstRect), _drawNum(0), _isValid(true), _wantsDraw(true), _hasAlpha(!disableAlpha) { + _colorMod = 0; + _batchNum = 0; + _mirror = TransparentSurface::FLIP_NONE; + if (mirrorX) { + _mirror |= TransparentSurface::FLIP_V; + } + if (mirrorY) { + _mirror |= TransparentSurface::FLIP_H; + } + if (surf) { + _surface = new Graphics::Surface(); + _surface->create((uint16)srcRect->width(), (uint16)srcRect->height(), surf->format); + assert(_surface->format.bytesPerPixel == 4); + // Get a clipped copy of the surface + for (int i = 0; i < _surface->h; i++) { + memcpy(_surface->getBasePtr(0, i), surf->getBasePtr(srcRect->left, srcRect->top + i), srcRect->width() * _surface->format.bytesPerPixel); + } + // Then scale it if necessary + if (dstRect->width() != srcRect->width() || dstRect->height() != srcRect->height()) { + TransparentSurface src(*_surface, false); + Graphics::Surface *temp = src.scale(dstRect->width(), dstRect->height()); + _surface->free(); + delete _surface; + _surface = temp; + } + } else { + _surface = NULL; + } +} + +RenderTicket::~RenderTicket() { + if (_surface) { + _surface->free(); + delete _surface; + } +} + +bool RenderTicket::operator==(RenderTicket &t) { + if ((t._owner != _owner) || + (t._batchNum != t._batchNum) || + (t._hasAlpha != _hasAlpha) || + (t._mirror != _mirror) || + (t._colorMod != _colorMod) || + (t._dstRect != _dstRect) || + (t._srcRect != _srcRect)) { + return false; + } + return true; +} + +void RenderTicket::drawToSurface(Graphics::Surface *_targetSurface, Common::Rect *srcRect, Common::Rect *dstRect, Common::Rect *clipRect) { + TransparentSurface src(*getSurface(), false); + bool doDelete = false; + if (!clipRect) { + doDelete = true; + clipRect = new Common::Rect(); + clipRect->setWidth(getSurface()->w); + clipRect->setHeight(getSurface()->h); + } + + src._enableAlphaBlit = _hasAlpha; + src.blit(*_targetSurface, dstRect->left, dstRect->top, _mirror, clipRect, _colorMod, clipRect->width(), clipRect->height()); + if (doDelete) { + delete clipRect; + } +} + +} // end of namespace Wintermute diff --git a/engines/wintermute/base/gfx/osystem/render_ticket.h b/engines/wintermute/base/gfx/osystem/render_ticket.h new file mode 100644 index 0000000000..242822c868 --- /dev/null +++ b/engines/wintermute/base/gfx/osystem/render_ticket.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. + * + */ + +/* + * This file is based on WME Lite. + * http://dead-code.org/redir.php?target=wmelite + * Copyright (c) 2011 Jan Nedoma + */ + +#ifndef WINTERMUTE_RENDER_TICKET_H +#define WINTERMUTE_RENDER_TICKET_H + +#include "graphics/surface.h" +#include "common/rect.h" + +namespace Wintermute { + +class BaseSurfaceOSystem; +class RenderTicket { + Graphics::Surface *_surface; +public: + RenderTicket(BaseSurfaceOSystem *owner, const Graphics::Surface *surf, Common::Rect *srcRect, Common::Rect *dstRest, bool mirrorX = false, bool mirrorY = false, bool disableAlpha = false); + RenderTicket() : _isValid(true), _wantsDraw(false), _drawNum(0) {} + ~RenderTicket(); + const Graphics::Surface *getSurface() { return _surface; } + void drawToSurface(Graphics::Surface *_targetSurface, Common::Rect *srcRect, Common::Rect *dstRect, Common::Rect *clipRect); + Common::Rect _srcRect; + Common::Rect _dstRect; + uint32 _mirror; + uint32 _batchNum; + bool _hasAlpha; + + bool _isValid; + bool _wantsDraw; + uint32 _drawNum; + uint32 _colorMod; + + BaseSurfaceOSystem *_owner; + bool operator==(RenderTicket &a); +}; + +} // end of namespace Wintermute + +#endif diff --git a/engines/wintermute/module.mk b/engines/wintermute/module.mk index 7c9cdfd0b2..2bd71f1b6b 100644 --- a/engines/wintermute/module.mk +++ b/engines/wintermute/module.mk @@ -53,6 +53,7 @@ MODULE_OBJS := \ base/gfx/base_surface.o \ base/gfx/osystem/base_surface_osystem.o \ base/gfx/osystem/base_render_osystem.o \ + base/gfx/osystem/render_ticket.o \ base/particles/part_particle.o \ base/particles/part_emitter.o \ base/particles/part_force.o \ diff --git a/engines/wintermute/ui/ui_tiled_image.h b/engines/wintermute/ui/ui_tiled_image.h index c413e7f129..edea84f346 100644 --- a/engines/wintermute/ui/ui_tiled_image.h +++ b/engines/wintermute/ui/ui_tiled_image.h @@ -46,6 +46,7 @@ public: bool display(int x, int y, int width, int height); UITiledImage(BaseGame *inGame = NULL); virtual ~UITiledImage(); +private: BaseSubFrame *_image; Rect32 _upLeft; Rect32 _upMiddle; |