diff options
101 files changed, 7544 insertions, 842 deletions
@@ -411,6 +411,9 @@ Other contributions Basque: Mikel Iturbe Urretxa + Belarusian: + Ivan Lukyanov + Catalan: Jordi Vilalta Prat @@ -14,6 +14,7 @@ For a more comprehensive changelog of the latest experimental code, see: - Rewrote VideoDecoder subsystem. - Added Galician translation. - Added Finnish translation. + - Added Belarusian translation. Cine: - Improved audio support for Amiga and AtariST versions of Future Wars. @@ -28,10 +29,19 @@ For a more comprehensive changelog of the latest experimental code, see: protection, this extra line can be toggled by the ScummVM copy protection command line option. + SAGA: + - Added music support for the Macintosh version of I Have No Mouth and, I + Must Scream. + SCUMM: - Implemented Monkey Island 2 Macintosh's audio driver. Now we properly support its sample based audio output. The same output is also used for the m68k Macintosh version of Indiana Jones and the Fate of Atlantis. + - Improved music support for the Macintosh version of Monkey Island 1. It + now uses the original instruments, rather than approximating them with + General MIDI instruments, and should sound a lot closer to the original. + - Added sound and music support for the Macintosh version of Loom. + - Handle double-clicking in the Macintosh version of Loom. 1.5.0 (2012-07-27) New Games: diff --git a/audio/decoders/quicktime.cpp b/audio/decoders/quicktime.cpp index 0588650ec6..787b547495 100644 --- a/audio/decoders/quicktime.cpp +++ b/audio/decoders/quicktime.cpp @@ -134,7 +134,7 @@ void QuickTimeAudioDecoder::init() { _audioTracks.push_back(new QuickTimeAudioTrack(this, _tracks[i])); } -Common::QuickTimeParser::SampleDesc *QuickTimeAudioDecoder::readSampleDesc(Track *track, uint32 format) { +Common::QuickTimeParser::SampleDesc *QuickTimeAudioDecoder::readSampleDesc(Track *track, uint32 format, uint32 descSize) { if (track->codecType == CODEC_TYPE_AUDIO) { debug(0, "Audio Codec FourCC: \'%s\'", tag2str(format)); diff --git a/audio/decoders/quicktime_intern.h b/audio/decoders/quicktime_intern.h index f1ab037d89..bb5ff0cf5c 100644 --- a/audio/decoders/quicktime_intern.h +++ b/audio/decoders/quicktime_intern.h @@ -131,7 +131,7 @@ protected: }; // Common::QuickTimeParser API - virtual Common::QuickTimeParser::SampleDesc *readSampleDesc(Track *track, uint32 format); + virtual Common::QuickTimeParser::SampleDesc *readSampleDesc(Track *track, uint32 format, uint32 descSize); void init(); diff --git a/audio/midiparser.h b/audio/midiparser.h index a4dbf174e1..bb9749b97f 100644 --- a/audio/midiparser.h +++ b/audio/midiparser.h @@ -394,6 +394,7 @@ public: static MidiParser *createParser_SMF(); static MidiParser *createParser_XMIDI(XMidiCallbackProc proc = defaultXMidiCallback, void *refCon = 0); + static MidiParser *createParser_QT(); static void timerCallback(void *data) { ((MidiParser *) data)->onTimer(); } }; diff --git a/audio/midiparser_qt.cpp b/audio/midiparser_qt.cpp new file mode 100644 index 0000000000..6214d28f95 --- /dev/null +++ b/audio/midiparser_qt.cpp @@ -0,0 +1,496 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "audio/midiparser_qt.h" +#include "common/debug.h" +#include "common/memstream.h" + +bool MidiParser_QT::loadMusic(byte *data, uint32 size) { + if (size < 8) + return false; + + Common::SeekableReadStream *stream = new Common::MemoryReadStream(data, size, DisposeAfterUse::NO); + + // Attempt to detect what format we have + bool result; + if (READ_BE_UINT32(data + 4) == MKTAG('m', 'u', 's', 'i')) + result = loadFromTune(stream); + else + result = loadFromContainerStream(stream); + + if (!result) { + delete stream; + return false; + } + + return true; +} + +void MidiParser_QT::unloadMusic() { + MidiParser::unloadMusic(); + close(); + + // Unlike those lesser formats, we *do* hold track data + for (uint i = 0; i < _trackInfo.size(); i++) + free(_trackInfo[i].data); + + _trackInfo.clear(); +} + +bool MidiParser_QT::loadFromTune(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) { + unloadMusic(); + + // a tune starts off with a sample description + stream->readUint32BE(); // header size + + if (stream->readUint32BE() != MKTAG('m', 'u', 's', 'i')) + return false; + + stream->readUint32BE(); // reserved + stream->readUint16BE(); // reserved + stream->readUint16BE(); // index + + stream->readUint32BE(); // flags, ignore + + MIDITrackInfo trackInfo; + trackInfo.size = stream->size() - stream->pos(); + assert(trackInfo.size > 0); + + trackInfo.data = (byte *)malloc(trackInfo.size); + stream->read(trackInfo.data, trackInfo.size); + + trackInfo.timeScale = 600; // the default + _trackInfo.push_back(trackInfo); + + initCommon(); + return true; +} + +bool MidiParser_QT::loadFromContainerStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) { + unloadMusic(); + + if (!parseStream(stream, disposeAfterUse)) + return false; + + initFromContainerTracks(); + return true; +} + +bool MidiParser_QT::loadFromContainerFile(const Common::String &fileName) { + unloadMusic(); + + if (!parseFile(fileName)) + return false; + + initFromContainerTracks(); + return true; +} + +void MidiParser_QT::parseNextEvent(EventInfo &info) { + uint32 delta = 0; + + while (_queuedEvents.empty()) + delta += readNextEvent(); + + info = _queuedEvents.pop(); + info.delta = delta; +} + +uint32 MidiParser_QT::readNextEvent() { + if (_position._playPos >= _trackInfo[_activeTrack].data + _trackInfo[_activeTrack].size) { + // Manually insert end of track when we reach the end + EventInfo info; + info.event = 0xFF; + info.ext.type = 0x2F; + _queuedEvents.push(info); + return 0; + } + + uint32 control = readUint32(); + + switch (control >> 28) { + case 0x0: + case 0x1: + // Rest + // We handle this by recursively adding up all the rests into the + // next event's delta + return readNextEvent() + (control & 0xFFFFFF); + case 0x2: + case 0x3: + // Note event + handleNoteEvent((control >> 24) & 0x1F, ((control >> 18) & 0x3F) + 32, (control >> 11) & 0x7F, control & 0x7FF); + break; + case 0x4: + case 0x5: + // Controller + handleControllerEvent((control >> 16) & 0xFF, (control >> 24) & 0x1F, (control >> 8) & 0xFF, control & 0xFF); + break; + case 0x6: + case 0x7: + // Marker + // Used for editing only, so we don't need to care about this + break; + case 0x9: { + // Extended note event + uint32 extra = readUint32(); + handleNoteEvent((control >> 16) & 0xFFF, (control >> 8) & 0xFF, (extra >> 22) & 0x7F, extra & 0x3FFFFF); + break; + } + case 0xA: { + // Extended controller + uint32 extra = readUint32(); + handleControllerEvent((extra >> 16) & 0x3FFF, (control >> 16) & 0xFFF, (extra >> 8) & 0xFF, extra & 0xFF); + break; + } + case 0xB: + // Knob + error("Encountered knob event in QuickTime MIDI"); + break; + case 0x8: + case 0xC: + case 0xD: + case 0xE: + // Reserved + readUint32(); + break; + case 0xF: + // General + handleGeneralEvent(control); + break; + } + + return 0; +} + +void MidiParser_QT::handleNoteEvent(uint32 part, byte pitch, byte velocity, uint32 length) { + byte channel = getChannel(part); + + EventInfo info; + info.event = 0x90 | channel; + info.basic.param1 = pitch; + info.basic.param2 = velocity; + info.length = (velocity == 0) ? 0 : length; + _queuedEvents.push(info); +} + +void MidiParser_QT::handleControllerEvent(uint32 control, uint32 part, byte intPart, byte fracPart) { + byte channel = getChannel(part); + EventInfo info; + + if (control == 0) { + // "Bank select" + // QuickTime docs don't list this, but IHNM Mac calls this anyway + // We have to ignore this. + return; + } else if (control == 32) { + // Pitch bend + info.event = 0xE0 | channel; + + // Actually an 8.8 fixed point number + int16 value = (int16)((intPart << 8) | fracPart); + + if (value < -0x200 || value > 0x1FF) { + warning("QuickTime MIDI pitch bend value (%d) out of range, clipping", value); + value = CLIP<int16>(value, -0x200, 0x1FF); + } + + // Now convert the value to 'normal' MIDI values + value += 0x200; + value *= 16; + + // param1 holds the low 7 bits, param2 holds the high 7 bits + info.basic.param1 = value & 0x7F; + info.basic.param2 = value >> 7; + + _partMap[part].pitchBend = value; + } else { + // Regular controller + info.event = 0xB0 | channel; + info.basic.param1 = control; + info.basic.param2 = intPart; + + // TODO: Parse more controls to hold their status + switch (control) { + case 7: + _partMap[part].volume = intPart; + break; + case 10: + _partMap[part].pan = intPart; + break; + } + } + + _queuedEvents.push(info); +} + +void MidiParser_QT::handleGeneralEvent(uint32 control) { + uint32 part = (control >> 16) & 0xFFF; + uint32 dataSize = ((control & 0xFFFF) - 2) * 4; + byte subType = READ_BE_UINT16(_position._playPos + dataSize) & 0x3FFF; + + switch (subType) { + case 1: + // Note Request + // Currently we're only using the GM number from the request + assert(dataSize == 84); + + // We have to remap channels because GM needs percussion to be on the + // percussion channel but QuickTime can have that anywhere. + definePart(part, READ_BE_UINT32(_position._playPos + 80)); + break; + case 5: // Tune Difference + case 8: // MIDI Channel + case 10: // No-op + case 11: // Used Notes + // Should be safe to skip these + break; + default: + warning("Unhandled general event %d", subType); + } + + _position._playPos += dataSize + 4; +} + +void MidiParser_QT::definePart(uint32 part, uint32 instrument) { + if (_partMap.contains(part)) + warning("QuickTime MIDI part %d being redefined", part); + + PartStatus partStatus; + partStatus.instrument = instrument; + partStatus.volume = 127; + partStatus.pan = 64; + partStatus.pitchBend = 0x2000; + _partMap[part] = partStatus; +} + +byte MidiParser_QT::getChannel(uint32 part) { + // If we already mapped it, just go with it + if (!_channelMap.contains(part)) { + byte newChannel = findFreeChannel(part); + _channelMap[part] = newChannel; + setupPart(part); + } + + return _channelMap[part]; +} + +byte MidiParser_QT::findFreeChannel(uint32 part) { + if (_partMap[part].instrument != 0x4001) { + // Normal Instrument -> First Free Channel + if (allChannelsAllocated()) + deallocateFreeChannel(); + + for (int i = 0; i < 16; i++) + if (i != 9 && !isChannelAllocated(i)) // 9 is reserved for Percussion + return i; + + // Can't actually get here + } + + // Drum Kit -> Percussion Channel + deallocateChannel(9); + return 9; +} + +void MidiParser_QT::deallocateFreeChannel() { + for (int i = 0; i < 16; i++) { + if (i != 9 && !_activeNotes[i]) { + // TODO: Improve this by looking for the channel with the longest + // time since the last note. + deallocateChannel(i); + return; + } + } + + error("Exceeded QuickTime MIDI channel polyphony"); +} + +void MidiParser_QT::deallocateChannel(byte channel) { + for (ChannelMap::iterator it = _channelMap.begin(); it != _channelMap.end(); it++) { + if (it->_value == channel) { + _channelMap.erase(it); + return; + } + } +} + +bool MidiParser_QT::isChannelAllocated(byte channel) const { + for (ChannelMap::const_iterator it = _channelMap.begin(); it != _channelMap.end(); it++) + if (it->_value == channel) + return true; + + return false; +} + +bool MidiParser_QT::allChannelsAllocated() const { + // Less than 15? We definitely have room + if (_channelMap.size() < 15) + return false; + + // 15? One of the allocated channels might be the percussion one + if (_channelMap.size() == 15) + for (ChannelMap::const_iterator it = _channelMap.begin(); it != _channelMap.end(); it++) + if (it->_value == 9) + return false; + + // 16 -> definitely all allocated + return true; +} + +void MidiParser_QT::setupPart(uint32 part) { + PartStatus &status = _partMap[part]; + byte channel = _channelMap[part]; + EventInfo info; + + // First, the program change + if (channel != 9) { + // 9 is always percussion + info.event = 0xC0 | channel; + info.basic.param1 = status.instrument; + _queuedEvents.push(info); + } + + // Volume + info.event = 0xB0 | channel; + info.basic.param1 = 7; + info.basic.param2 = status.volume; + _queuedEvents.push(info); + + // Pan + info.event = 0xB0 | channel; + info.basic.param1 = 10; + info.basic.param2 = status.pan; + _queuedEvents.push(info); + + // Pitch Bend + info.event = 0xE0 | channel; + info.basic.param1 = status.pitchBend & 0x7F; + info.basic.param2 = status.pitchBend >> 7; + _queuedEvents.push(info); +} + +void MidiParser_QT::resetTracking() { + MidiParser::resetTracking(); + _channelMap.clear(); + _queuedEvents.clear(); + _partMap.clear(); +} + +Common::QuickTimeParser::SampleDesc *MidiParser_QT::readSampleDesc(Track *track, uint32 format, uint32 descSize) { + if (track->codecType == CODEC_TYPE_MIDI) { + debug(0, "MIDI Codec FourCC '%s'", tag2str(format)); + + _fd->readUint32BE(); // flags, ignore + descSize -= 4; + + MIDISampleDesc *entry = new MIDISampleDesc(track, format); + entry->_requestSize = descSize; + entry->_requestData = (byte *)malloc(descSize); + _fd->read(entry->_requestData, descSize); + return entry; + } + + return 0; +} + +MidiParser_QT::MIDISampleDesc::MIDISampleDesc(Common::QuickTimeParser::Track *parentTrack, uint32 codecTag) : + Common::QuickTimeParser::SampleDesc(parentTrack, codecTag) { +} + +void MidiParser_QT::initFromContainerTracks() { + const Common::Array<Common::QuickTimeParser::Track *> &tracks = Common::QuickTimeParser::_tracks; + + for (uint32 i = 0; i < tracks.size(); i++) { + if (tracks[i]->codecType == CODEC_TYPE_MIDI) { + assert(tracks[i]->sampleDescs.size() == 1); + + if (tracks[i]->editCount != 1) + warning("Unhandled QuickTime MIDI edit lists, things may go awry"); + + MIDITrackInfo trackInfo; + trackInfo.data = readWholeTrack(tracks[i], trackInfo.size); + trackInfo.timeScale = tracks[i]->timeScale; + _trackInfo.push_back(trackInfo); + } + } + + initCommon(); +} + +void MidiParser_QT::initCommon() { + // Now we have all our info needed in _trackInfo from whatever container + // form, we can fill in the MidiParser tracks. + + _numTracks = _trackInfo.size(); + assert(_numTracks > 0); + + for (uint32 i = 0; i < _trackInfo.size(); i++) + MidiParser::_tracks[i] = _trackInfo[i].data; + + _ppqn = _trackInfo[0].timeScale; + resetTracking(); + setTempo(1000000); + setTrack(0); +} + +byte *MidiParser_QT::readWholeTrack(Common::QuickTimeParser::Track *track, uint32 &trackSize) { + // This just goes through all chunks and appends them together + + Common::MemoryWriteStreamDynamic output; + uint32 curSample = 0; + + // Read in the note request data first + MIDISampleDesc *entry = (MIDISampleDesc *)track->sampleDescs[0]; + output.write(entry->_requestData, entry->_requestSize); + + for (uint i = 0; i < track->chunkCount; i++) { + _fd->seek(track->chunkOffsets[i]); + + uint32 sampleCount = 0; + + for (uint32 j = 0; j < track->sampleToChunkCount; j++) + if (i >= track->sampleToChunk[j].first) + sampleCount = track->sampleToChunk[j].count; + + for (uint32 j = 0; j < sampleCount; j++, curSample++) { + uint32 size = (track->sampleSize != 0) ? track->sampleSize : track->sampleSizes[curSample]; + + byte *data = new byte[size]; + _fd->read(data, size); + output.write(data, size); + delete[] data; + } + } + + trackSize = output.size(); + return output.getData(); +} + +uint32 MidiParser_QT::readUint32() { + uint32 value = READ_BE_UINT32(_position._playPos); + _position._playPos += 4; + return value; +} + +MidiParser *MidiParser::createParser_QT() { + return new MidiParser_QT(); +} diff --git a/audio/midiparser_qt.h b/audio/midiparser_qt.h new file mode 100644 index 0000000000..d6d0f40a48 --- /dev/null +++ b/audio/midiparser_qt.h @@ -0,0 +1,134 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef AUDIO_MIDIPARSER_QT_H +#define AUDIO_MIDIPARSER_QT_H + +#include "audio/midiparser.h" +#include "common/array.h" +#include "common/hashmap.h" +#include "common/queue.h" +#include "common/quicktime.h" + +/** + * The QuickTime Music version of MidiParser. + * + * QuickTime Music is actually a superset of MIDI. It has its own custom + * instruments and supports more than 15 non-percussion channels. It also + * has custom control changes and a more advanced pitch bend (which we + * convert to GM pitch bend as best as possible). We then use the fallback + * GM instrument that each QuickTime instrument definition has to provide. + * + * Furthermore, Apple's documentation on this is terrible. You know + * documentation is bad when it contradicts itself three times on the same + * subject (like about setting the GM instrument field to percussion). + * + * This is as close to a proper QuickTime Music parser as we can currently + * implement using our MidiParser interface. + */ +class MidiParser_QT : public MidiParser, public Common::QuickTimeParser { +public: + MidiParser_QT() {} + ~MidiParser_QT() {} + + // MidiParser + bool loadMusic(byte *data, uint32 size); + void unloadMusic(); + + /** + * Load the MIDI from a 'Tune' resource + */ + bool loadFromTune(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES); + + /** + * Load the MIDI from a QuickTime stream + */ + bool loadFromContainerStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES); + + /** + * Load the MIDI from a QuickTime file + */ + bool loadFromContainerFile(const Common::String &fileName); + +protected: + // MidiParser + void parseNextEvent(EventInfo &info); + void resetTracking(); + + // QuickTimeParser + SampleDesc *readSampleDesc(Track *track, uint32 format, uint32 descSize); + +private: + struct MIDITrackInfo { + byte *data; + uint32 size; + uint32 timeScale; + }; + + struct PartStatus { + uint32 instrument; + byte volume; + byte pan; + uint16 pitchBend; + }; + + class MIDISampleDesc : public SampleDesc { + public: + MIDISampleDesc(Common::QuickTimeParser::Track *parentTrack, uint32 codecTag); + ~MIDISampleDesc() {} + + byte *_requestData; + uint32 _requestSize; + }; + + uint32 readNextEvent(); + void handleGeneralEvent(uint32 control); + void handleControllerEvent(uint32 control, uint32 part, byte intPart, byte fracPart); + void handleNoteEvent(uint32 part, byte pitch, byte velocity, uint32 length); + + void definePart(uint32 part, uint32 instrument); + void setupPart(uint32 part); + + byte getChannel(uint32 part); + bool isChannelAllocated(byte channel) const; + byte findFreeChannel(uint32 part); + void deallocateFreeChannel(); + void deallocateChannel(byte channel); + bool allChannelsAllocated() const; + + byte *readWholeTrack(Common::QuickTimeParser::Track *track, uint32 &trackSize); + + Common::Array<MIDITrackInfo> _trackInfo; + Common::Queue<EventInfo> _queuedEvents; + + typedef Common::HashMap<uint, PartStatus> PartMap; + PartMap _partMap; + + typedef Common::HashMap<uint, byte> ChannelMap; + ChannelMap _channelMap; + + void initFromContainerTracks(); + void initCommon(); + uint32 readUint32(); +}; + +#endif diff --git a/audio/module.mk b/audio/module.mk index e3aa0aaa81..4e1c031c83 100644 --- a/audio/module.mk +++ b/audio/module.mk @@ -4,6 +4,7 @@ MODULE_OBJS := \ audiostream.o \ fmopl.o \ mididrv.o \ + midiparser_qt.o \ midiparser_smf.o \ midiparser_xmidi.o \ midiparser.o \ diff --git a/common/forbidden.h b/common/forbidden.h index eec80bba59..9050114442 100644 --- a/common/forbidden.h +++ b/common/forbidden.h @@ -333,11 +333,21 @@ #define isalpha(a) FORBIDDEN_SYMBOL_REPLACEMENT #endif + #ifndef FORBIDDEN_SYMBOL_EXCEPTION_iscntrl + #undef iscntrl + #define iscntrl(a) FORBIDDEN_SYMBOL_REPLACEMENT + #endif + #ifndef FORBIDDEN_SYMBOL_EXCEPTION_isdigit #undef isdigit #define isdigit(a) FORBIDDEN_SYMBOL_REPLACEMENT #endif + #ifndef FORBIDDEN_SYMBOL_EXCEPTION_isgraph + #undef isgraph + #define isgraph(a) FORBIDDEN_SYMBOL_REPLACEMENT + #endif + #ifndef FORBIDDEN_SYMBOL_EXCEPTION_isnumber #undef isnumber #define isnumber(a) FORBIDDEN_SYMBOL_REPLACEMENT @@ -348,6 +358,16 @@ #define islower(a) FORBIDDEN_SYMBOL_REPLACEMENT #endif + #ifndef FORBIDDEN_SYMBOL_EXCEPTION_isprint + #undef isprint + #define isprint(a) FORBIDDEN_SYMBOL_REPLACEMENT + #endif + + #ifndef FORBIDDEN_SYMBOL_EXCEPTION_ispunct + #undef ispunct + #define ispunct(a) FORBIDDEN_SYMBOL_REPLACEMENT + #endif + #ifndef FORBIDDEN_SYMBOL_EXCEPTION_isspace #undef isspace #define isspace(a) FORBIDDEN_SYMBOL_REPLACEMENT @@ -358,6 +378,11 @@ #define isupper(a) FORBIDDEN_SYMBOL_REPLACEMENT #endif + #ifndef FORBIDDEN_SYMBOL_EXCEPTION_isxdigit + #undef isxdigit + #define isxdigit(a) FORBIDDEN_SYMBOL_REPLACEMENT + #endif + #endif // FORBIDDEN_SYMBOL_EXCEPTION_ctype_h #ifndef FORBIDDEN_SYMBOL_EXCEPTION_mkdir diff --git a/common/macresman.cpp b/common/macresman.cpp index f2f020c6de..00562f746a 100644 --- a/common/macresman.cpp +++ b/common/macresman.cpp @@ -360,8 +360,8 @@ bool MacResManager::load(SeekableReadStream &stream) { _mapLength = stream.readUint32BE(); // do sanity check - if (_dataOffset >= (uint32)stream.size() || _mapOffset >= (uint32)stream.size() || - _dataLength + _mapLength > (uint32)stream.size()) { + if (stream.eos() || _dataOffset >= (uint32)stream.size() || _mapOffset >= (uint32)stream.size() || + _dataLength + _mapLength > (uint32)stream.size()) { _resForkOffset = -1; _mode = kResForkNone; return false; diff --git a/common/quicktime.cpp b/common/quicktime.cpp index 173d3c6a97..5b3659b0d5 100644 --- a/common/quicktime.cpp +++ b/common/quicktime.cpp @@ -165,6 +165,8 @@ void QuickTimeParser::initParseTable() { { &QuickTimeParser::readWAVE, MKTAG('w', 'a', 'v', 'e') }, { &QuickTimeParser::readESDS, MKTAG('e', 's', 'd', 's') }, { &QuickTimeParser::readSMI, MKTAG('S', 'M', 'I', ' ') }, + { &QuickTimeParser::readDefault, MKTAG('g', 'm', 'h', 'd') }, + { &QuickTimeParser::readLeaf, MKTAG('g', 'm', 'i', 'n') }, { 0, 0 } }; @@ -477,6 +479,8 @@ int QuickTimeParser::readHDLR(Atom atom) { track->codecType = CODEC_TYPE_VIDEO; else if (type == MKTAG('s', 'o', 'u', 'n')) track->codecType = CODEC_TYPE_AUDIO; + else if (type == MKTAG('m', 'u', 's', 'i')) + track->codecType = CODEC_TYPE_MIDI; _fd->readUint32BE(); // component manufacture _fd->readUint32BE(); // component flags @@ -540,7 +544,7 @@ int QuickTimeParser::readSTSD(Atom atom) { _fd->readUint16BE(); // reserved _fd->readUint16BE(); // index - track->sampleDescs[i] = readSampleDesc(track, format); + track->sampleDescs[i] = readSampleDesc(track, format, size - 16); debug(0, "size=%d 4CC= %s codec_type=%d", size, tag2str(format), track->codecType); diff --git a/common/quicktime.h b/common/quicktime.h index 641718e13a..caa92578b1 100644 --- a/common/quicktime.h +++ b/common/quicktime.h @@ -120,7 +120,8 @@ protected: enum CodecType { CODEC_TYPE_MOV_OTHER, CODEC_TYPE_VIDEO, - CODEC_TYPE_AUDIO + CODEC_TYPE_AUDIO, + CODEC_TYPE_MIDI }; struct Track { @@ -161,7 +162,7 @@ protected: byte objectTypeMP4; }; - virtual SampleDesc *readSampleDesc(Track *track, uint32 format) = 0; + virtual SampleDesc *readSampleDesc(Track *track, uint32 format, uint32 descSize) = 0; uint32 _timeScale; uint32 _duration; diff --git a/common/str.cpp b/common/str.cpp index 84805082ac..8210ca6bb8 100644 --- a/common/str.cpp +++ b/common/str.cpp @@ -764,7 +764,7 @@ String tag2string(uint32 tag) { str[4] = '\0'; // Replace non-printable chars by dot for (int i = 0; i < 4; ++i) { - if (!isprint((unsigned char)str[i])) + if (!Common::isPrint(str[i])) str[i] = '.'; } return String(str); diff --git a/common/util.cpp b/common/util.cpp index 4d9ff11c5c..3d40fffff5 100644 --- a/common/util.cpp +++ b/common/util.cpp @@ -26,6 +26,7 @@ #define FORBIDDEN_SYMBOL_EXCEPTION_islower #define FORBIDDEN_SYMBOL_EXCEPTION_isspace #define FORBIDDEN_SYMBOL_EXCEPTION_isupper +#define FORBIDDEN_SYMBOL_EXCEPTION_isprint #include "common/util.h" @@ -144,4 +145,8 @@ bool isUpper(int c) { return isupper((byte)c); } +bool isPrint(int c) { + ENSURE_ASCII_CHAR(c); + return isprint((byte)c); +} } // End of namespace Common diff --git a/common/util.h b/common/util.h index 78340980d5..4ca1c42929 100644 --- a/common/util.h +++ b/common/util.h @@ -165,6 +165,17 @@ bool isSpace(int c); */ bool isUpper(int c); -} // End of namespace Common +/** + * Test whether the given character is printable. This includes the space + * character (' '). + * + * If the parameter is outside the range of a signed or unsigned char, then + * false is returned. + * + * @param c the character to test + * @return true if the character is printable, false otherwise. + */ +bool isPrint(int c); +} // End of namespace Common #endif diff --git a/devtools/credits.pl b/devtools/credits.pl index f5fc9d64eb..3c97e6ab0b 100755 --- a/devtools/credits.pl +++ b/devtools/credits.pl @@ -940,6 +940,9 @@ begin_credits("Credits"); begin_section("Basque"); add_person("Mikel Iturbe Urretxa", "", ""); end_section(); + begin_section("Belarusian"); + add_person("Ivan Lukyanov", "", ""); + end_section(); begin_section("Catalan"); add_person("Jordi Vilalta Prat", "jvprat", ""); end_section(); 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; diff --git a/gui/credits.h b/gui/credits.h index 8a90f88429..f63f54a6fb 100644 --- a/gui/credits.h +++ b/gui/credits.h @@ -475,6 +475,9 @@ static const char *credits[] = { "C1""Basque", "C0""Mikel Iturbe Urretxa", "", +"C1""Belarusian", +"C0""Ivan Lukyanov", +"", "C1""Catalan", "C0""Jordi Vilalta Prat", "", diff --git a/gui/themes/translations.dat b/gui/themes/translations.dat Binary files differindex 23cf2b8fe4..704e9caca0 100644 --- a/gui/themes/translations.dat +++ b/gui/themes/translations.dat diff --git a/gui/widgets/list.cpp b/gui/widgets/list.cpp index 13784ddf7f..95d39c4f24 100644 --- a/gui/widgets/list.cpp +++ b/gui/widgets/list.cpp @@ -284,7 +284,7 @@ bool ListWidget::handleKeyDown(Common::KeyState state) { bool dirty = false; int oldSelectedItem = _selectedItem; - if (!_editMode && state.keycode <= Common::KEYCODE_z && isprint((unsigned char)state.ascii)) { + if (!_editMode && state.keycode <= Common::KEYCODE_z && Common::isPrint(state.ascii)) { // Quick selection mode: Go to first list item starting with this key // (or a substring accumulated from the last couple key presses). // Only works in a useful fashion if the list entries are sorted. diff --git a/po/be_BY.po b/po/be_BY.po new file mode 100644 index 0000000000..9539588ba6 --- /dev/null +++ b/po/be_BY.po @@ -0,0 +1,3140 @@ +# Belarusian translation for ScummVM. +# Copyright (C) 2010-2012 ScummVM Team +# This file is distributed under the same license as the ScummVM package. +# Ivan Lukyanov <greencis@mail.ru>, 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: ScummVM 1.6.0git\n" +"Report-Msgid-Bugs-To: scummvm-devel@lists.sf.net\n" +"POT-Creation-Date: 2012-08-12 14:57+0200\n" +"PO-Revision-Date: 2012-12-12 22:02+0300\n" +"Last-Translator: Ivan Lukyanov <greencis@mail.ru>\n" +"Language-Team: Ivan Lukyanov <greencis@mail.ru>\n" +"Language: Belarusian\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=iso-8859-5\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n" +"%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"X-Generator: Poedit 1.5.4\n" + +#: gui/about.cpp:91 +#, c-format +msgid "(built on %s)" +msgstr "(сабраны %s)" + +#: gui/about.cpp:98 +msgid "Features compiled in:" +msgstr "Уключаныя ў білд опцыі:" + +#: gui/about.cpp:107 +msgid "Available engines:" +msgstr "Даступныя рухавічкі:" + +#: gui/browser.cpp:66 +msgid "Go up" +msgstr "Уверх" + +#: gui/browser.cpp:66 gui/browser.cpp:68 +msgid "Go to previous directory level" +msgstr "Перайсці на дырэкторыю ўзроўнем вышэй" + +#: gui/browser.cpp:68 +msgctxt "lowres" +msgid "Go up" +msgstr "Уверх" + +#: gui/browser.cpp:69 gui/chooser.cpp:45 gui/KeysDialog.cpp:43 +#: gui/launcher.cpp:345 gui/massadd.cpp:94 gui/options.cpp:1228 +#: gui/saveload-dialog.cpp:207 gui/saveload-dialog.cpp:267 +#: gui/saveload-dialog.cpp:516 gui/saveload-dialog.cpp:843 +#: gui/themebrowser.cpp:54 engines/engine.cpp:442 +#: engines/scumm/dialogs.cpp:190 engines/sword1/control.cpp:865 +#: engines/parallaction/saveload.cpp:274 backends/platform/wii/options.cpp:48 +#: backends/events/default/default-events.cpp:191 +#: backends/events/default/default-events.cpp:213 +msgid "Cancel" +msgstr "Адмена" + +#: gui/browser.cpp:70 gui/chooser.cpp:46 gui/themebrowser.cpp:55 +msgid "Choose" +msgstr "Абраць" + +#: gui/gui-manager.cpp:115 engines/scumm/help.cpp:125 +#: engines/scumm/help.cpp:140 engines/scumm/help.cpp:165 +#: engines/scumm/help.cpp:191 engines/scumm/help.cpp:209 +#: backends/keymapper/remap-dialog.cpp:52 +msgid "Close" +msgstr "Закрыць" + +#: gui/gui-manager.cpp:118 +msgid "Mouse click" +msgstr "Клік мышшу" + +#: gui/gui-manager.cpp:122 base/main.cpp:300 +msgid "Display keyboard" +msgstr "Паказаць клавіятуру" + +#: gui/gui-manager.cpp:126 base/main.cpp:304 +msgid "Remap keys" +msgstr "Перапрызначыць клавішы" + +#: gui/gui-manager.cpp:129 base/main.cpp:307 +msgid "Toggle FullScreen" +msgstr "Пераключэнне на ўвесь экран" + +#: gui/KeysDialog.h:36 gui/KeysDialog.cpp:145 +msgid "Choose an action to map" +msgstr "Абярыце дзеянне для прызначэння" + +#: gui/KeysDialog.cpp:41 +msgid "Map" +msgstr "Прызначыць" + +#: gui/KeysDialog.cpp:42 gui/launcher.cpp:346 gui/launcher.cpp:1001 +#: gui/launcher.cpp:1005 gui/massadd.cpp:91 gui/options.cpp:1229 +#: gui/saveload-dialog.cpp:844 engines/engine.cpp:361 engines/engine.cpp:372 +#: engines/scumm/dialogs.cpp:192 engines/scumm/scumm.cpp:1775 +#: engines/agos/animation.cpp:561 engines/groovie/script.cpp:420 +#: engines/sky/compact.cpp:131 engines/sky/compact.cpp:141 +#: engines/sword1/animation.cpp:539 engines/sword1/animation.cpp:560 +#: engines/sword1/animation.cpp:570 engines/sword1/animation.cpp:577 +#: engines/sword1/control.cpp:865 engines/sword1/logic.cpp:1633 +#: engines/sword2/animation.cpp:435 engines/sword2/animation.cpp:455 +#: engines/sword2/animation.cpp:465 engines/sword2/animation.cpp:474 +#: engines/parallaction/saveload.cpp:274 backends/platform/wii/options.cpp:47 +#: backends/platform/wince/CELauncherDialog.cpp:54 +msgid "OK" +msgstr "OK" + +#: gui/KeysDialog.cpp:49 +msgid "Select an action and click 'Map'" +msgstr "Абярыце дзеянне і клікніце 'Прызначыць'" + +#: gui/KeysDialog.cpp:80 gui/KeysDialog.cpp:102 gui/KeysDialog.cpp:141 +#, c-format +msgid "Associated key : %s" +msgstr "Прызначаная клавіша : %s" + +#: gui/KeysDialog.cpp:82 gui/KeysDialog.cpp:104 gui/KeysDialog.cpp:143 +#, c-format +msgid "Associated key : none" +msgstr "Прызначаная клавіша : няма" + +#: gui/KeysDialog.cpp:90 +msgid "Please select an action" +msgstr "Калі ласка, абярыце дзеянне" + +#: gui/KeysDialog.cpp:106 +msgid "Press the key to associate" +msgstr "Націсніце клавішу для прызначэння" + +#: gui/launcher.cpp:187 +msgid "Game" +msgstr "Гульня" + +#: gui/launcher.cpp:191 +msgid "ID:" +msgstr "ID:" + +#: gui/launcher.cpp:191 gui/launcher.cpp:193 gui/launcher.cpp:194 +msgid "" +"Short game identifier used for referring to savegames and running the game " +"from the command line" +msgstr "" +"Кароткі ідэнтыфікатар, выкарыстоўваны для імёнаў захаванняў гульняў і для " +"запуску з каманднага радка" + +#: gui/launcher.cpp:193 +msgctxt "lowres" +msgid "ID:" +msgstr "ID:" + +#: gui/launcher.cpp:198 +msgid "Name:" +msgstr "Назва:" + +#: gui/launcher.cpp:198 gui/launcher.cpp:200 gui/launcher.cpp:201 +msgid "Full title of the game" +msgstr "Поўная назва гульні" + +#: gui/launcher.cpp:200 +msgctxt "lowres" +msgid "Name:" +msgstr "Назв:" + +#: gui/launcher.cpp:204 +msgid "Language:" +msgstr "Мова:" + +#: gui/launcher.cpp:204 gui/launcher.cpp:205 +msgid "" +"Language of the game. This will not turn your Spanish game version into " +"English" +msgstr "" +"Мова гульні. Змена гэтай налады не ператворыць ангельскую гульню ў рускую" + +#: gui/launcher.cpp:206 gui/launcher.cpp:220 gui/options.cpp:80 +#: gui/options.cpp:730 gui/options.cpp:743 gui/options.cpp:1199 +#: audio/null.cpp:40 +msgid "<default>" +msgstr "<па змаўчанні>" + +#: gui/launcher.cpp:216 +msgid "Platform:" +msgstr "Платформа:" + +#: gui/launcher.cpp:216 gui/launcher.cpp:218 gui/launcher.cpp:219 +msgid "Platform the game was originally designed for" +msgstr "Платформа, для якой гульня была першапачаткова распрацавана" + +#: gui/launcher.cpp:218 +msgctxt "lowres" +msgid "Platform:" +msgstr "Платформа:" + +#: gui/launcher.cpp:231 +msgid "Engine" +msgstr "Рухавічок" + +#: gui/launcher.cpp:239 gui/options.cpp:1062 gui/options.cpp:1079 +msgid "Graphics" +msgstr "Графіка" + +#: gui/launcher.cpp:239 gui/options.cpp:1062 gui/options.cpp:1079 +msgid "GFX" +msgstr "Грф" + +#: gui/launcher.cpp:242 +msgid "Override global graphic settings" +msgstr "Перакрыць глабальныя налады графікі" + +#: gui/launcher.cpp:244 +msgctxt "lowres" +msgid "Override global graphic settings" +msgstr "Перакрыць глабальныя налады графікі" + +#: gui/launcher.cpp:251 gui/options.cpp:1085 +msgid "Audio" +msgstr "Аўдыё" + +#: gui/launcher.cpp:254 +msgid "Override global audio settings" +msgstr "Перакрыць глабальныя налады аўдыё" + +#: gui/launcher.cpp:256 +msgctxt "lowres" +msgid "Override global audio settings" +msgstr "Перакрыць глабальныя налады аўдыё" + +#: gui/launcher.cpp:265 gui/options.cpp:1090 +msgid "Volume" +msgstr "Гучнасць" + +#: gui/launcher.cpp:267 gui/options.cpp:1092 +msgctxt "lowres" +msgid "Volume" +msgstr "Гучн" + +#: gui/launcher.cpp:270 +msgid "Override global volume settings" +msgstr "Перакрыць глабальныя налады гучнасці" + +#: gui/launcher.cpp:272 +msgctxt "lowres" +msgid "Override global volume settings" +msgstr "Перакрыць глабальныя налады гучнасці" + +#: gui/launcher.cpp:280 gui/options.cpp:1100 +msgid "MIDI" +msgstr "MIDI" + +#: gui/launcher.cpp:283 +msgid "Override global MIDI settings" +msgstr "Перакрыць глабальныя налады MIDI" + +#: gui/launcher.cpp:285 +msgctxt "lowres" +msgid "Override global MIDI settings" +msgstr "Перакрыць глабальныя налады MIDI" + +#: gui/launcher.cpp:294 gui/options.cpp:1106 +msgid "MT-32" +msgstr "MT-32" + +#: gui/launcher.cpp:297 +msgid "Override global MT-32 settings" +msgstr "Перакрыць глабальныя налады MT-32" + +#: gui/launcher.cpp:299 +msgctxt "lowres" +msgid "Override global MT-32 settings" +msgstr "Перакрыць глабальныя налады MT-32" + +#: gui/launcher.cpp:308 gui/options.cpp:1113 +msgid "Paths" +msgstr "Шляхі" + +#: gui/launcher.cpp:310 gui/options.cpp:1115 +msgctxt "lowres" +msgid "Paths" +msgstr "Шляхі" + +#: gui/launcher.cpp:317 +msgid "Game Path:" +msgstr "Шлях да гульні:" + +#: gui/launcher.cpp:319 +msgctxt "lowres" +msgid "Game Path:" +msgstr "Дзе гульня:" + +#: gui/launcher.cpp:324 gui/options.cpp:1139 +msgid "Extra Path:" +msgstr "Дад. шлях:" + +#: gui/launcher.cpp:324 gui/launcher.cpp:326 gui/launcher.cpp:327 +msgid "Specifies path to additional data used the game" +msgstr "Паказвае шлях да дадатковых файлаў, дадзеных для гульні" + +#: gui/launcher.cpp:326 gui/options.cpp:1141 +msgctxt "lowres" +msgid "Extra Path:" +msgstr "Дад. шлях:" + +#: gui/launcher.cpp:333 gui/options.cpp:1123 +msgid "Save Path:" +msgstr "Захаванні гульняў:" + +#: gui/launcher.cpp:333 gui/launcher.cpp:335 gui/launcher.cpp:336 +#: gui/options.cpp:1123 gui/options.cpp:1125 gui/options.cpp:1126 +msgid "Specifies where your savegames are put" +msgstr "Паказвае шлях да захаванняў гульні" + +#: gui/launcher.cpp:335 gui/options.cpp:1125 +msgctxt "lowres" +msgid "Save Path:" +msgstr "Шлях зах:" + +#: gui/launcher.cpp:354 gui/launcher.cpp:453 gui/launcher.cpp:511 +#: gui/launcher.cpp:565 gui/options.cpp:1134 gui/options.cpp:1142 +#: gui/options.cpp:1151 gui/options.cpp:1258 gui/options.cpp:1264 +#: gui/options.cpp:1272 gui/options.cpp:1302 gui/options.cpp:1308 +#: gui/options.cpp:1315 gui/options.cpp:1408 gui/options.cpp:1411 +#: gui/options.cpp:1423 +msgctxt "path" +msgid "None" +msgstr "Не зададзены" + +#: gui/launcher.cpp:359 gui/launcher.cpp:459 gui/launcher.cpp:569 +#: gui/options.cpp:1252 gui/options.cpp:1296 gui/options.cpp:1414 +#: backends/platform/wii/options.cpp:56 +msgid "Default" +msgstr "Па змаўчанні" + +#: gui/launcher.cpp:504 gui/options.cpp:1417 +msgid "Select SoundFont" +msgstr "Абярыце SoundFont" + +#: gui/launcher.cpp:523 gui/launcher.cpp:677 +msgid "Select directory with game data" +msgstr "Абярыце дырэкторыю з файламі гульні" + +#: gui/launcher.cpp:541 +msgid "Select additional game directory" +msgstr "Абярыце дадатковую дырэкторыю гульні" + +#: gui/launcher.cpp:553 +msgid "Select directory for saved games" +msgstr "Абярыце дырэкторыю для захаванняў" + +#: gui/launcher.cpp:580 +msgid "This game ID is already taken. Please choose another one." +msgstr "Гэты ID гульні ўжо выкарыстоўваецца. Калі ласка, абярыце іншы." + +#: gui/launcher.cpp:621 engines/dialogs.cpp:110 +msgid "~Q~uit" +msgstr "~В~ыхад" + +#: gui/launcher.cpp:621 backends/platform/sdl/macosx/appmenu_osx.mm:96 +msgid "Quit ScummVM" +msgstr "Завяршыць ScummVM" + +#: gui/launcher.cpp:622 +msgid "A~b~out..." +msgstr "Пра п~р~аграму..." + +#: gui/launcher.cpp:622 backends/platform/sdl/macosx/appmenu_osx.mm:70 +msgid "About ScummVM" +msgstr "Пра праграму ScummVM" + +#: gui/launcher.cpp:623 +msgid "~O~ptions..." +msgstr "~Н~алады..." + +#: gui/launcher.cpp:623 +msgid "Change global ScummVM options" +msgstr "Змяніць глабальныя налады ScummVM" + +#: gui/launcher.cpp:625 +msgid "~S~tart" +msgstr "П~у~ск" + +#: gui/launcher.cpp:625 +msgid "Start selected game" +msgstr "Запусціць абраную гульню" + +#: gui/launcher.cpp:628 +msgid "~L~oad..." +msgstr "~З~агрузіць..." + +#: gui/launcher.cpp:628 +msgid "Load savegame for selected game" +msgstr "Загрузіць захаванне для абранай гульні" + +#: gui/launcher.cpp:633 gui/launcher.cpp:1120 +msgid "~A~dd Game..." +msgstr "~Д~адаць гульню..." + +#: gui/launcher.cpp:633 gui/launcher.cpp:640 +msgid "Hold Shift for Mass Add" +msgstr "Утрымлівайце клавішу Shift для таго, каб дадаць некалькі гульняў" + +#: gui/launcher.cpp:635 +msgid "~E~dit Game..." +msgstr "Н~а~лады гульні..." + +#: gui/launcher.cpp:635 gui/launcher.cpp:642 +msgid "Change game options" +msgstr "Змяніць налады гульні" + +#: gui/launcher.cpp:637 +msgid "~R~emove Game" +msgstr "В~ы~даліць гульню" + +#: gui/launcher.cpp:637 gui/launcher.cpp:644 +msgid "Remove game from the list. The game data files stay intact" +msgstr "Выдаліць гульню са спісу. Не выдаляе гульню з жорсткага дыска" + +#: gui/launcher.cpp:640 gui/launcher.cpp:1120 +msgctxt "lowres" +msgid "~A~dd Game..." +msgstr "~Д~ад. гульню..." + +#: gui/launcher.cpp:642 +msgctxt "lowres" +msgid "~E~dit Game..." +msgstr "Н~а~л. гульні..." + +#: gui/launcher.cpp:644 +msgctxt "lowres" +msgid "~R~emove Game" +msgstr "В~ы~даліць гульню" + +#: gui/launcher.cpp:652 +msgid "Search in game list" +msgstr "Пошук у спісе гульняў" + +#: gui/launcher.cpp:656 gui/launcher.cpp:1167 +msgid "Search:" +msgstr "Пошук:" + +#: gui/launcher.cpp:680 engines/dialogs.cpp:114 engines/mohawk/myst.cpp:245 +#: engines/mohawk/riven.cpp:716 engines/cruise/menu.cpp:214 +msgid "Load game:" +msgstr "Загрузіць гульню:" + +#: gui/launcher.cpp:680 engines/dialogs.cpp:114 engines/scumm/dialogs.cpp:188 +#: engines/mohawk/myst.cpp:245 engines/mohawk/riven.cpp:716 +#: engines/cruise/menu.cpp:214 backends/platform/wince/CEActionsPocket.cpp:267 +#: backends/platform/wince/CEActionsSmartphone.cpp:231 +msgid "Load" +msgstr "Загрузіць" + +#: gui/launcher.cpp:788 +msgid "" +"Do you really want to run the mass game detector? This could potentially add " +"a huge number of games." +msgstr "" +"Вы сапраўды жадаеце запусціць дэтэктар усіх гульняў? Гэта патэнцыяльна можа " +"дадаць вялікую колькасць гульняў." + +#: gui/launcher.cpp:789 gui/launcher.cpp:937 +#: backends/events/symbiansdl/symbiansdl-events.cpp:184 +#: backends/platform/wince/CEActionsPocket.cpp:326 +#: backends/platform/wince/CEActionsSmartphone.cpp:287 +#: backends/platform/wince/CELauncherDialog.cpp:83 +msgid "Yes" +msgstr "Так" + +#: gui/launcher.cpp:789 gui/launcher.cpp:937 +#: backends/events/symbiansdl/symbiansdl-events.cpp:184 +#: backends/platform/wince/CEActionsPocket.cpp:326 +#: backends/platform/wince/CEActionsSmartphone.cpp:287 +#: backends/platform/wince/CELauncherDialog.cpp:83 +msgid "No" +msgstr "Не" + +#: gui/launcher.cpp:837 +msgid "ScummVM couldn't open the specified directory!" +msgstr "ScummVM не можа адкрыць азначаную дырэкторыю!" + +#: gui/launcher.cpp:849 +msgid "ScummVM could not find any game in the specified directory!" +msgstr "ScummVM не можа знайсці гульню ў азначанай дырэкторыі!" + +#: gui/launcher.cpp:863 +msgid "Pick the game:" +msgstr "Абярыце гульню:" + +#: gui/launcher.cpp:937 +msgid "Do you really want to remove this game configuration?" +msgstr "Вы сапраўды жадаеце выдаліць налады для гэтай гульні?" + +#: gui/launcher.cpp:1001 +msgid "This game does not support loading games from the launcher." +msgstr "Гэтая гульня не падтрымлівае загрузку захаванняў праз галоўнае меню." + +#: gui/launcher.cpp:1005 +msgid "ScummVM could not find any engine capable of running the selected game!" +msgstr "ScummVM не змог знайсці рухавічок для запуску абранай гульні!" + +#: gui/launcher.cpp:1119 +msgctxt "lowres" +msgid "Mass Add..." +msgstr "Шмат гульняў..." + +#: gui/launcher.cpp:1119 +msgid "Mass Add..." +msgstr "Шмат гульняў..." + +#: gui/massadd.cpp:78 gui/massadd.cpp:81 +msgid "... progress ..." +msgstr "... шукаю ..." + +#: gui/massadd.cpp:258 +msgid "Scan complete!" +msgstr "Пошук скончаны!" + +#: gui/massadd.cpp:261 +#, c-format +msgid "Discovered %d new games, ignored %d previously added games." +msgstr "Знойдзена %d новых гульняў, прапушчана %d раней дададзеных гульняў." + +#: gui/massadd.cpp:265 +#, c-format +msgid "Scanned %d directories ..." +msgstr "Прагледжана %d дырэкторый ..." + +#: gui/massadd.cpp:268 +#, c-format +msgid "Discovered %d new games, ignored %d previously added games ..." +msgstr "Знойдзена %d новых гульняў, прапушчана %d раней дададзеных гульняў ..." + +#: gui/options.cpp:78 +msgid "Never" +msgstr "Ніколі" + +#: gui/options.cpp:78 +msgid "every 5 mins" +msgstr "кожныя 5 хвілін" + +#: gui/options.cpp:78 +msgid "every 10 mins" +msgstr "кожныя 10 хвілін" + +#: gui/options.cpp:78 +msgid "every 15 mins" +msgstr "кожныя 15 хвілін" + +#: gui/options.cpp:78 +msgid "every 30 mins" +msgstr "кожныя 30 хвілін" + +#: gui/options.cpp:80 +msgid "8 kHz" +msgstr "8 кГц" + +#: gui/options.cpp:80 +msgid "11kHz" +msgstr "11 кГц" + +#: gui/options.cpp:80 +msgid "22 kHz" +msgstr "22 кГц" + +#: gui/options.cpp:80 +msgid "44 kHz" +msgstr "44 кГц" + +#: gui/options.cpp:80 +msgid "48 kHz" +msgstr "48 кГц" + +#: gui/options.cpp:248 gui/options.cpp:474 gui/options.cpp:575 +#: gui/options.cpp:644 gui/options.cpp:852 +msgctxt "soundfont" +msgid "None" +msgstr "Не зададзены" + +#: gui/options.cpp:382 +msgid "Failed to apply some of the graphic options changes:" +msgstr "Не атрымалася ўжыць змены некаторых графічных налад:" + +#: gui/options.cpp:394 +msgid "the video mode could not be changed." +msgstr "відэарэжым не можа быць зменены." + +#: gui/options.cpp:400 +msgid "the fullscreen setting could not be changed" +msgstr "поўнаэкранны рэжым не можа быць зменены" + +#: gui/options.cpp:406 +msgid "the aspect ratio setting could not be changed" +msgstr "рэжым карэктыроўкі суадносін бакоў не можа быць зменены" + +#: gui/options.cpp:727 +msgid "Graphics mode:" +msgstr "Граф. рэжым:" + +#: gui/options.cpp:741 +msgid "Render mode:" +msgstr "Рэжым растру:" + +#: gui/options.cpp:741 gui/options.cpp:742 +msgid "Special dithering modes supported by some games" +msgstr "Спецыяльныя рэжымы рэндэрынгу, падтрымоўваныя некаторымі гульнямі" + +#: gui/options.cpp:753 +#: backends/graphics/surfacesdl/surfacesdl-graphics.cpp:2236 +#: backends/graphics/openglsdl/openglsdl-graphics.cpp:472 +msgid "Fullscreen mode" +msgstr "Поўнаэкранны рэжым" + +#: gui/options.cpp:756 +msgid "Aspect ratio correction" +msgstr "Карэкцыя суадносін бакоў" + +#: gui/options.cpp:756 +msgid "Correct aspect ratio for 320x200 games" +msgstr "Карэктаваць суадносіны бакоў для гульняў з рэзалюцыяй 320x200" + +#: gui/options.cpp:764 +msgid "Preferred Device:" +msgstr "Упадабанае:" + +#: gui/options.cpp:764 +msgid "Music Device:" +msgstr "Гукавая прылада:" + +#: gui/options.cpp:764 gui/options.cpp:766 +msgid "Specifies preferred sound device or sound card emulator" +msgstr "Зазначае ўпадабаную гукавую прыладу ці эмулятар гукавой карты" + +#: gui/options.cpp:764 gui/options.cpp:766 gui/options.cpp:767 +msgid "Specifies output sound device or sound card emulator" +msgstr "Зазначае выходную гукавую прыладу ці эмулятар гукавой карты" + +#: gui/options.cpp:766 +msgctxt "lowres" +msgid "Preferred Dev.:" +msgstr "Упадабанае:" + +#: gui/options.cpp:766 +msgctxt "lowres" +msgid "Music Device:" +msgstr "Гук. прылада:" + +#: gui/options.cpp:793 +msgid "AdLib emulator:" +msgstr "Эмулятар AdLib:" + +#: gui/options.cpp:793 gui/options.cpp:794 +msgid "AdLib is used for music in many games" +msgstr "Гукавая карта AdLib выкарыстоўваецца многімі гульнямі" + +#: gui/options.cpp:804 +msgid "Output rate:" +msgstr "Чашчыня гуку:" + +#: gui/options.cpp:804 gui/options.cpp:805 +msgid "" +"Higher value specifies better sound quality but may be not supported by your " +"soundcard" +msgstr "" +"Большыя значэнні задаюць лепшую якасць гуку, аднак яны могуць не " +"падтрымлівацца вашай гукавой картай" + +#: gui/options.cpp:815 +msgid "GM Device:" +msgstr "Прылада GM:" + +#: gui/options.cpp:815 +msgid "Specifies default sound device for General MIDI output" +msgstr "Зазначае выходную гукавую прыладу для MIDI" + +#: gui/options.cpp:826 +msgid "Don't use General MIDI music" +msgstr "Не выкарыстоўваць музыку для General MIDI" + +#: gui/options.cpp:837 gui/options.cpp:899 +msgid "Use first available device" +msgstr "Выкарыстоўваць першую даступную прыладу" + +#: gui/options.cpp:849 +msgid "SoundFont:" +msgstr "SoundFont:" + +#: gui/options.cpp:849 gui/options.cpp:851 gui/options.cpp:852 +msgid "SoundFont is supported by some audio cards, Fluidsynth and Timidity" +msgstr "" +"SoundFont'ы падтрымліваюцца некаторымі гукавымі картамі, Fluidsynth ды " +"Timidity" + +#: gui/options.cpp:851 +msgctxt "lowres" +msgid "SoundFont:" +msgstr "SoundFont:" + +#: gui/options.cpp:857 +msgid "Mixed AdLib/MIDI mode" +msgstr "Змешаны рэжым AdLib/MIDI" + +#: gui/options.cpp:857 +msgid "Use both MIDI and AdLib sound generation" +msgstr "Выкарыстоўваць і MIDI, і AdLib для генерацыі гуку" + +#: gui/options.cpp:860 +msgid "MIDI gain:" +msgstr "Узмацненне MIDI:" + +#: gui/options.cpp:870 +msgid "MT-32 Device:" +msgstr "Нал. MT-32:" + +#: gui/options.cpp:870 +msgid "Specifies default sound device for Roland MT-32/LAPC1/CM32l/CM64 output" +msgstr "" +"Паказвае гукавую прыладу па змаўчанні для вываду на Roland MT-32/LAPC1/CM32l/" +"CM64" + +#: gui/options.cpp:875 +msgid "True Roland MT-32 (disable GM emulation)" +msgstr "Сапраўдны Roland MT-32 (забараніць эмуляцыю GM)" + +#: gui/options.cpp:875 gui/options.cpp:877 +msgid "" +"Check if you want to use your real hardware Roland-compatible sound device " +"connected to your computer" +msgstr "" +"Адзначце, калі ў вас падключана Roland-сумяшчальная гукавая прылада і вы " +"жадаеце яе выкарыстоўваць" + +#: gui/options.cpp:877 +msgctxt "lowres" +msgid "True Roland MT-32 (no GM emulation)" +msgstr "Сапраўдны Roland MT-32 (забараніць GM)" + +#: gui/options.cpp:880 +msgid "Enable Roland GS Mode" +msgstr "Уключыць рэжым Roland GS" + +#: gui/options.cpp:880 +msgid "Turns off General MIDI mapping for games with Roland MT-32 soundtrack" +msgstr "" +"Выключае супастаўленне General MIDI для гульняў з гукавой дарожкай для " +"Roland MT-32" + +#: gui/options.cpp:889 +msgid "Don't use Roland MT-32 music" +msgstr "Не выкарыстоўваць музыку для MT-32" + +#: gui/options.cpp:916 +msgid "Text and Speech:" +msgstr "Тэкст і агучка:" + +#: gui/options.cpp:920 gui/options.cpp:930 +msgid "Speech" +msgstr "Агучка" + +#: gui/options.cpp:921 gui/options.cpp:931 +msgid "Subtitles" +msgstr "Субтытры" + +#: gui/options.cpp:922 +msgid "Both" +msgstr "Абое" + +#: gui/options.cpp:924 +msgid "Subtitle speed:" +msgstr "Хуткасць тытраў:" + +#: gui/options.cpp:926 +msgctxt "lowres" +msgid "Text and Speech:" +msgstr "Тэкст і агучка:" + +#: gui/options.cpp:930 +msgid "Spch" +msgstr "Агуч" + +#: gui/options.cpp:931 +msgid "Subs" +msgstr "Суб" + +#: gui/options.cpp:932 +msgctxt "lowres" +msgid "Both" +msgstr "Абое" + +#: gui/options.cpp:932 +msgid "Show subtitles and play speech" +msgstr "Паказваць субтытры і прайграваць гаворку" + +#: gui/options.cpp:934 +msgctxt "lowres" +msgid "Subtitle speed:" +msgstr "Хуткасць тытраў:" + +#: gui/options.cpp:950 +msgid "Music volume:" +msgstr "Гучн. музыкі:" + +#: gui/options.cpp:952 +msgctxt "lowres" +msgid "Music volume:" +msgstr "Гучн. музыкі:" + +#: gui/options.cpp:959 +msgid "Mute All" +msgstr "Выкл. усё" + +#: gui/options.cpp:962 +msgid "SFX volume:" +msgstr "Гучнасць SFX:" + +#: gui/options.cpp:962 gui/options.cpp:964 gui/options.cpp:965 +msgid "Special sound effects volume" +msgstr "Гучнасць спецыяльных гукавых эфектаў" + +#: gui/options.cpp:964 +msgctxt "lowres" +msgid "SFX volume:" +msgstr "Гучн. SFX:" + +#: gui/options.cpp:972 +msgid "Speech volume:" +msgstr "Гучн. агучкі:" + +#: gui/options.cpp:974 +msgctxt "lowres" +msgid "Speech volume:" +msgstr "Гучн. агучкі:" + +#: gui/options.cpp:1131 +msgid "Theme Path:" +msgstr "Шлях да тэм:" + +#: gui/options.cpp:1133 +msgctxt "lowres" +msgid "Theme Path:" +msgstr "Дзе тэмы:" + +#: gui/options.cpp:1139 gui/options.cpp:1141 gui/options.cpp:1142 +msgid "Specifies path to additional data used by all games or ScummVM" +msgstr "" +"Паказвае шлях да дадатковых файлаў дадзеных, выкарыстоўваных усімі гульнямі, " +"або ScummVM" + +#: gui/options.cpp:1148 +msgid "Plugins Path:" +msgstr "Шлях да плагінаў:" + +#: gui/options.cpp:1150 +msgctxt "lowres" +msgid "Plugins Path:" +msgstr "Шлях да плагінаў:" + +#: gui/options.cpp:1159 +msgid "Misc" +msgstr "Рознае" + +#: gui/options.cpp:1161 +msgctxt "lowres" +msgid "Misc" +msgstr "Рознае" + +#: gui/options.cpp:1163 +msgid "Theme:" +msgstr "Тэма" + +#: gui/options.cpp:1167 +msgid "GUI Renderer:" +msgstr "Малявалка GUI:" + +#: gui/options.cpp:1179 +msgid "Autosave:" +msgstr "Аўтазахаванне:" + +#: gui/options.cpp:1181 +msgctxt "lowres" +msgid "Autosave:" +msgstr "Аўтазах.:" + +#: gui/options.cpp:1189 +msgid "Keys" +msgstr "Клавішы" + +#: gui/options.cpp:1196 +msgid "GUI Language:" +msgstr "Мова GUI:" + +#: gui/options.cpp:1196 +msgid "Language of ScummVM GUI" +msgstr "Мова графічнага інтэрфейсу ScummVM" + +#: gui/options.cpp:1347 +msgid "You have to restart ScummVM before your changes will take effect." +msgstr "Вы павінны перазапусціць ScummVM, каб ужыць змены." + +#: gui/options.cpp:1360 +msgid "Select directory for savegames" +msgstr "Абярыце дырэкторыю для захаванняў" + +#: gui/options.cpp:1367 +msgid "The chosen directory cannot be written to. Please select another one." +msgstr "Не магу пісаць у абраную дырэкторыю. Калі ласка, азначце іншую." + +#: gui/options.cpp:1376 +msgid "Select directory for GUI themes" +msgstr "Абярыце дырэкторыю для тэм GUI" + +#: gui/options.cpp:1386 +msgid "Select directory for extra files" +msgstr "Абярыце дырэкторыю з дадатковымі файламі" + +#: gui/options.cpp:1397 +msgid "Select directory for plugins" +msgstr "Абярыце дырэкторыю з плагінамі" + +#: gui/options.cpp:1450 +msgid "" +"The theme you selected does not support your current language. If you want " +"to use this theme you need to switch to another language first." +msgstr "" +"Тэма, абраная вамі, не падтрымлівае бягучую мову. Калі вы жадаеце " +"выкарыстоўваць гэтую тэму, вам неабходна спачатку пераключыцца на іншую мову." + +#: gui/saveload-dialog.cpp:158 +msgid "List view" +msgstr "Выгляд спісу" + +#: gui/saveload-dialog.cpp:159 +msgid "Grid view" +msgstr "Выгляд сеткі" + +#: gui/saveload-dialog.cpp:202 gui/saveload-dialog.cpp:350 +msgid "No date saved" +msgstr "Дата не запісана" + +#: gui/saveload-dialog.cpp:203 gui/saveload-dialog.cpp:351 +msgid "No time saved" +msgstr "Час не запісаны" + +#: gui/saveload-dialog.cpp:204 gui/saveload-dialog.cpp:352 +msgid "No playtime saved" +msgstr "Час гульні не запісаны" + +#: gui/saveload-dialog.cpp:211 gui/saveload-dialog.cpp:267 +msgid "Delete" +msgstr "Выдаліць" + +#: gui/saveload-dialog.cpp:266 +msgid "Do you really want to delete this savegame?" +msgstr "Вы сапраўды жадаеце выдаліць гэта захаванне?" + +#: gui/saveload-dialog.cpp:375 gui/saveload-dialog.cpp:796 +msgid "Date: " +msgstr "Дата: " + +#: gui/saveload-dialog.cpp:379 gui/saveload-dialog.cpp:802 +msgid "Time: " +msgstr "Час: " + +#: gui/saveload-dialog.cpp:385 gui/saveload-dialog.cpp:810 +msgid "Playtime: " +msgstr "Час гульні: " + +#: gui/saveload-dialog.cpp:398 gui/saveload-dialog.cpp:465 +msgid "Untitled savestate" +msgstr "Захаванне без імя" + +#: gui/saveload-dialog.cpp:517 +msgid "Next" +msgstr "Наступны" + +#: gui/saveload-dialog.cpp:520 +msgid "Prev" +msgstr "Папярэдні" + +#: gui/saveload-dialog.cpp:684 +msgid "New Save" +msgstr "Новае захаванне" + +#: gui/saveload-dialog.cpp:684 +msgid "Create a new save game" +msgstr "Стварыць новы запіс гульні" + +#: gui/saveload-dialog.cpp:789 +msgid "Name: " +msgstr "Назва: " + +#: gui/saveload-dialog.cpp:861 +#, c-format +msgid "Enter a description for slot %d:" +msgstr "Увядзіце апісанне слота %d:" + +#: gui/themebrowser.cpp:44 +msgid "Select a Theme" +msgstr "Абярыце тэму" + +#: gui/ThemeEngine.cpp:337 +msgid "Disabled GFX" +msgstr "Без графікі" + +#: gui/ThemeEngine.cpp:337 +msgctxt "lowres" +msgid "Disabled GFX" +msgstr "Без графікі" + +#: gui/ThemeEngine.cpp:338 +msgid "Standard Renderer (16bpp)" +msgstr "Стандартны растарызатар (16bpp)" + +#: gui/ThemeEngine.cpp:338 +msgid "Standard (16bpp)" +msgstr "Стандартны растарызатар (16bpp)" + +#: gui/ThemeEngine.cpp:340 +msgid "Antialiased Renderer (16bpp)" +msgstr "Растарызатар са згладжваннем (16bpp)" + +#: gui/ThemeEngine.cpp:340 +msgid "Antialiased (16bpp)" +msgstr "Растарызатар са згладжваннем (16bpp)" + +#: gui/widget.cpp:322 gui/widget.cpp:324 gui/widget.cpp:330 gui/widget.cpp:332 +msgid "Clear value" +msgstr "Ачысціць значэнне" + +#: base/main.cpp:209 +#, c-format +msgid "Engine does not support debug level '%s'" +msgstr "Рухавічок не падтрымлівае ўзровень адладкі '%s'" + +#: base/main.cpp:287 +msgid "Menu" +msgstr "Меню" + +#: base/main.cpp:290 backends/platform/symbian/src/SymbianActions.cpp:45 +#: backends/platform/wince/CEActionsPocket.cpp:45 +#: backends/platform/wince/CEActionsSmartphone.cpp:46 +msgid "Skip" +msgstr "Прапусціць" + +#: base/main.cpp:293 backends/platform/symbian/src/SymbianActions.cpp:50 +#: backends/platform/wince/CEActionsPocket.cpp:42 +msgid "Pause" +msgstr "Паўза" + +#: base/main.cpp:296 +msgid "Skip line" +msgstr "Прапусціць радок" + +#: base/main.cpp:467 +msgid "Error running game:" +msgstr "Памылка запуску гульні:" + +#: base/main.cpp:491 +msgid "Could not find any engine capable of running the selected game" +msgstr "Не магу знайсці рухавічок для запуску абранай гульні" + +#: common/error.cpp:38 +msgid "No error" +msgstr "Няма памылкі" + +#: common/error.cpp:40 +msgid "Game data not found" +msgstr "Няма файлаў гульні" + +#: common/error.cpp:42 +msgid "Game id not supported" +msgstr "Game id не падтрымліваецца" + +#: common/error.cpp:44 +msgid "Unsupported color mode" +msgstr "Непадтрымоўваны рэжым колеру" + +#: common/error.cpp:47 +msgid "Read permission denied" +msgstr "Недастаткова правоў для чытання" + +#: common/error.cpp:49 +msgid "Write permission denied" +msgstr "Недастаткова правоў для запісу" + +#: common/error.cpp:52 +msgid "Path does not exist" +msgstr "Шлях не знойдзены" + +#: common/error.cpp:54 +msgid "Path not a directory" +msgstr "Шлях не з'яўляецца дырэкторыяй" + +#: common/error.cpp:56 +msgid "Path not a file" +msgstr "Шлях не з'яўляецца файлам" + +#: common/error.cpp:59 +msgid "Cannot create file" +msgstr "Не магу стварыць файл" + +#: common/error.cpp:61 +msgid "Reading data failed" +msgstr "Памылка чытання дадзеных" + +#: common/error.cpp:63 +msgid "Writing data failed" +msgstr "Памылка запісу дадзеных" + +#: common/error.cpp:66 +msgid "Could not find suitable engine plugin" +msgstr "Не магу знайсці падыходны плагін для рухавічка" + +#: common/error.cpp:68 +msgid "Engine plugin does not support save states" +msgstr "Рухавічок не падтрымлівае захаванні" + +#: common/error.cpp:71 +msgid "User canceled" +msgstr "Перапынена карыстачом" + +#: common/error.cpp:75 +msgid "Unknown error" +msgstr "Невядомая памылка" + +#: engines/advancedDetector.cpp:316 +#, c-format +msgid "The game in '%s' seems to be unknown." +msgstr "Здаецца, што гульня '%s' яшчэ невядома." + +#: engines/advancedDetector.cpp:317 +msgid "Please, report the following data to the ScummVM team along with name" +msgstr "" +"Калі ласка, перадайце наступныя дадзеныя камандзе ScummVM разам з назвай" + +#: engines/advancedDetector.cpp:319 +msgid "of the game you tried to add and its version/language/etc.:" +msgstr "гульні, якую вы спрабуеце дадаць, і азначце яе версію, мову і г.д." + +#: engines/dialogs.cpp:84 +msgid "~R~esume" +msgstr "Працяг~н~уць" + +#: engines/dialogs.cpp:86 +msgid "~L~oad" +msgstr "~З~агрузіць" + +#: engines/dialogs.cpp:90 +msgid "~S~ave" +msgstr "~З~апісаць" + +#: engines/dialogs.cpp:94 +msgid "~O~ptions" +msgstr "~О~пцыі" + +#: engines/dialogs.cpp:99 +msgid "~H~elp" +msgstr "~Д~апамога" + +#: engines/dialogs.cpp:101 +msgid "~A~bout" +msgstr "Пра пра~г~раму" + +#: engines/dialogs.cpp:104 engines/dialogs.cpp:180 +msgid "~R~eturn to Launcher" +msgstr "~В~ыйсці ў галоўнае меню" + +#: engines/dialogs.cpp:106 engines/dialogs.cpp:182 +msgctxt "lowres" +msgid "~R~eturn to Launcher" +msgstr "~У~ галоўнае меню" + +#: engines/dialogs.cpp:115 engines/agi/saveload.cpp:803 +#: engines/cruise/menu.cpp:212 engines/sci/engine/kfile.cpp:742 +msgid "Save game:" +msgstr "Захаваць гульню:" + +#: engines/dialogs.cpp:115 engines/agi/saveload.cpp:803 +#: engines/scumm/dialogs.cpp:187 engines/cruise/menu.cpp:212 +#: engines/sci/engine/kfile.cpp:742 +#: backends/platform/symbian/src/SymbianActions.cpp:44 +#: backends/platform/wince/CEActionsPocket.cpp:43 +#: backends/platform/wince/CEActionsPocket.cpp:267 +#: backends/platform/wince/CEActionsSmartphone.cpp:45 +#: backends/platform/wince/CEActionsSmartphone.cpp:231 +msgid "Save" +msgstr "Захаваць" + +#: engines/dialogs.cpp:144 +msgid "" +"Sorry, this engine does not currently provide in-game help. Please consult " +"the README for basic information, and for instructions on how to obtain " +"further assistance." +msgstr "" +"Прабачце, але гэты рухавічок пакуль не падае дапамогі ў гульні. Калі ласка, " +"звярніцеся ў файл README за базавай інфармацыяй, а таксама інструкцыямі пра " +"тое, як атрымаць далейшую дапамогу." + +#: engines/dialogs.cpp:228 +#, c-format +msgid "" +"Gamestate save failed (%s)! Please consult the README for basic information, " +"and for instructions on how to obtain further assistance." +msgstr "" +"Не атрымалася захаваць гульню (%s)! Калі ласка, звярніцеся ў файл README за " +"базавай інфармацыяй, а таксама інструкцыямі пра тое, як атрымаць далейшую " +"дапамогу." + +#: engines/dialogs.cpp:301 engines/mohawk/dialogs.cpp:109 +#: engines/mohawk/dialogs.cpp:174 +msgid "~O~K" +msgstr "~О~К" + +#: engines/dialogs.cpp:302 engines/mohawk/dialogs.cpp:110 +#: engines/mohawk/dialogs.cpp:175 +msgid "~C~ancel" +msgstr "~А~дмена" + +#: engines/dialogs.cpp:305 +msgid "~K~eys" +msgstr "~К~лавішы" + +#: engines/engine.cpp:235 +msgid "Could not initialize color format." +msgstr "Не магу ініцыялізаваць фармат колеру." + +#: engines/engine.cpp:243 +msgid "Could not switch to video mode: '" +msgstr "Не атрымалася пераключыць відэарэжым: '" + +#: engines/engine.cpp:252 +msgid "Could not apply aspect ratio setting." +msgstr "Не атрымалася выкарыстаць карэкцыю суадносін бакоў." + +#: engines/engine.cpp:257 +msgid "Could not apply fullscreen setting." +msgstr "Не магу ўжыць поўнаэкранны рэжым." + +#: engines/engine.cpp:357 +msgid "" +"You appear to be playing this game directly\n" +"from the CD. This is known to cause problems,\n" +"and it is therefore recommended that you copy\n" +"the data files to your hard disk instead.\n" +"See the README file for details." +msgstr "" +"Здаецца, вы спрабуеце запусціць гэту гульню прама\n" +"з CD. Гэта звычайна выклікае праблемы, і таму\n" +"мы рэкамендуем скапіяваць файлы дадзеных гульні\n" +"на жорсткі дыск. Падрабязнасці можна знайсці ў\n" +"файле README." + +#: engines/engine.cpp:368 +msgid "" +"This game has audio tracks in its disk. These\n" +"tracks need to be ripped from the disk using\n" +"an appropriate CD audio extracting tool in\n" +"order to listen to the game's music.\n" +"See the README file for details." +msgstr "" +"Дыск гэтай гульні ўтрымоўвае гукавыя дарожкі. Іх\n" +"неабходна перапісаць з дыска з дапамогай\n" +"адпаведнай праграмы для капіявання\n" +"аўдыёдыскаў, і толькі пасля гэтага ў гульні\n" +"з'явіцца музыка. Падрабязнасці можна знайсці ў\n" +"файле README." + +#: engines/engine.cpp:426 +#, c-format +msgid "" +"Gamestate load failed (%s)! Please consult the README for basic information, " +"and for instructions on how to obtain further assistance." +msgstr "" +"Не атрымалася прачытаць захаванне гульні (%s)! Калі ласка, звярніцеся ў файл " +"README за базавай інфармацыяй, а таксама інструкцыямі пра тое, як атрымаць " +"далейшую дапамогу." + +#: engines/engine.cpp:439 +msgid "" +"WARNING: The game you are about to start is not yet fully supported by " +"ScummVM. As such, it is likely to be unstable, and any saves you make might " +"not work in future versions of ScummVM." +msgstr "" +"ПАПЯРЭДЖАННЕ: Гульня, якую вы збіраецеся запусціць, яшчэ не падтрымліваецца " +"ScummVM цалкам. Яна, хутчэй за ўсё, не будзе працаваць стабільна, і " +"захаванні гульняў могуць не працаваць у будучых версіях ScummVM." + +#: engines/engine.cpp:442 +msgid "Start anyway" +msgstr "Усё адно запусціць" + +#: engines/agi/detection.cpp:145 engines/dreamweb/detection.cpp:47 +#: engines/sci/detection.cpp:390 +msgid "Use original save/load screens" +msgstr "Выкарыстоўваць арыгінальныя экраны запісу/чытанні гульні" + +#: engines/agi/detection.cpp:146 engines/dreamweb/detection.cpp:48 +#: engines/sci/detection.cpp:391 +msgid "Use the original save/load screens, instead of the ScummVM ones" +msgstr "" +"Выкарыстоўваць арыгінальныя экраны запісу і захаванні гульні замест " +"зробленых у ScummVM" + +#: engines/agi/saveload.cpp:816 engines/sci/engine/kfile.cpp:838 +msgid "Restore game:" +msgstr "Узнавіць гульню:" + +#: engines/agi/saveload.cpp:816 engines/sci/engine/kfile.cpp:838 +msgid "Restore" +msgstr "Узнавіць" + +#: engines/dreamweb/detection.cpp:57 +msgid "Use bright palette mode" +msgstr "Выкарыстоўваць рэжым яркай палітры" + +#: engines/dreamweb/detection.cpp:58 +msgid "Display graphics using the game's bright palette" +msgstr "Малюе графіку з выкарыстаннем яркай палітры гульні" + +#: engines/sci/detection.cpp:370 +msgid "EGA undithering" +msgstr "EGA без растру" + +#: engines/sci/detection.cpp:371 +msgid "Enable undithering in EGA games" +msgstr "Уключае рэжым без растравання ў EGA гульнях" + +#: engines/sci/detection.cpp:380 +msgid "Prefer digital sound effects" +msgstr "Аддаваць перавагу лічбавым гукавым эфектам" + +#: engines/sci/detection.cpp:381 +msgid "Prefer digital sound effects instead of synthesized ones" +msgstr "Аддаваць перавагу лічбавым гукавым эфектам замест сінтэзаваных" + +#: engines/sci/detection.cpp:400 +msgid "Use IMF/Yamaha FB-01 for MIDI output" +msgstr "Выкарыстоўваць IMF/Yamaha FB-01 для вываду MIDI" + +#: engines/sci/detection.cpp:401 +msgid "" +"Use an IBM Music Feature card or a Yamaha FB-01 FM synth module for MIDI " +"output" +msgstr "" +"Выкарыстоўваць гукавую карту IBM Music Feature ці модуль сінтэзу Yamaha " +"FB-01 FM для MIDI" + +#: engines/sci/detection.cpp:411 +msgid "Use CD audio" +msgstr "Выкарыстоўваць CD аўдыё" + +#: engines/sci/detection.cpp:412 +msgid "Use CD audio instead of in-game audio, if available" +msgstr "" +"Выкарыстоўваць гукавыя дарожкі з CD замест музыкі з файлаў гульні (калі " +"даступна)" + +#: engines/sci/detection.cpp:422 +msgid "Use Windows cursors" +msgstr "Выкарыстоўваць курсоры Windows" + +#: engines/sci/detection.cpp:423 +msgid "" +"Use the Windows cursors (smaller and monochrome) instead of the DOS ones" +msgstr "" +"Выкарыстоўваць курсоры Windows (меншыя па памеры і аднакаляровыя) замест " +"курсораў DOS" + +#: engines/sci/detection.cpp:433 +msgid "Use silver cursors" +msgstr "Выкарыстоўваць срэбныя курсоры" + +#: engines/sci/detection.cpp:434 +msgid "" +"Use the alternate set of silver cursors, instead of the normal golden ones" +msgstr "" +"Выкарыстоўваць альтэрнатыўны набор срэбных курсораў замест звычайных залатых" + +#: engines/scumm/dialogs.cpp:175 +#, c-format +msgid "Insert Disk %c and Press Button to Continue." +msgstr "Устаўце дыск %c і націсніце клавішу, каб працягнуць." + +#: engines/scumm/dialogs.cpp:176 +#, c-format +msgid "Unable to Find %s, (%c%d) Press Button." +msgstr "Не атрымалася знайсці %s, (%c%d) Націсніце клавішу." + +#: engines/scumm/dialogs.cpp:177 +#, c-format +msgid "Error reading disk %c, (%c%d) Press Button." +msgstr "Памылка чытання дыска %c, (%c%d) Націсніце клавішу." + +#: engines/scumm/dialogs.cpp:178 +msgid "Game Paused. Press SPACE to Continue." +msgstr "Гульня спынена. Націсніце прабел, каб працягнуць." + +#. I18N: You may specify 'Yes' symbol at the end of the line, like this: +#. "Moechten Sie wirklich neu starten? (J/N)J" +#. Will react to J as 'Yes' +#: engines/scumm/dialogs.cpp:182 +msgid "Are you sure you want to restart? (Y/N)" +msgstr "Вы ўпэўнены, што жадаеце пачаць ізноў? (Y/N)" + +#. I18N: you may specify 'Yes' symbol at the end of the line. See previous comment +#: engines/scumm/dialogs.cpp:184 +msgid "Are you sure you want to quit? (Y/N)" +msgstr "Вы ўпэўнены, што жадаеце выйсці? (Y/N)" + +#: engines/scumm/dialogs.cpp:189 +msgid "Play" +msgstr "Гуляць" + +#: engines/scumm/dialogs.cpp:191 engines/scumm/help.cpp:82 +#: engines/scumm/help.cpp:84 +#: backends/platform/symbian/src/SymbianActions.cpp:52 +#: backends/platform/wince/CEActionsPocket.cpp:44 +#: backends/platform/wince/CEActionsSmartphone.cpp:52 +#: backends/events/default/default-events.cpp:213 +msgid "Quit" +msgstr "Выхад" + +#: engines/scumm/dialogs.cpp:193 +msgid "Insert save/load game disk" +msgstr "Устаўце дыск з захаваннямі" + +#: engines/scumm/dialogs.cpp:194 +msgid "You must enter a name" +msgstr "Вы павінны ўвесці імя" + +#: engines/scumm/dialogs.cpp:195 +msgid "The game was NOT saved (disk full?)" +msgstr "Гульня НЕ БЫЛА запісана (дыск поўны?)" + +#: engines/scumm/dialogs.cpp:196 +msgid "The game was NOT loaded" +msgstr "Гульня НЕ БЫЛА загружана" + +#: engines/scumm/dialogs.cpp:197 +#, c-format +msgid "Saving '%s'" +msgstr "Захоўваю '%s'" + +#: engines/scumm/dialogs.cpp:198 +#, c-format +msgid "Loading '%s'" +msgstr "Загружаю '%s'" + +#: engines/scumm/dialogs.cpp:199 +msgid "Name your SAVE game" +msgstr "Назавіце захаванне гульні" + +#: engines/scumm/dialogs.cpp:200 +msgid "Select a game to LOAD" +msgstr "Абярыце гульню для загрузкі" + +#: engines/scumm/dialogs.cpp:201 +msgid "Game title)" +msgstr "Назва гульні)" + +#. I18N: Previous page button +#: engines/scumm/dialogs.cpp:287 +msgid "~P~revious" +msgstr "~П~апяр" + +#. I18N: Next page button +#: engines/scumm/dialogs.cpp:289 +msgid "~N~ext" +msgstr "~Н~аст" + +#: engines/scumm/dialogs.cpp:290 +#: backends/platform/ds/arm9/source/dsoptions.cpp:56 +msgid "~C~lose" +msgstr "~З~акрыць" + +#: engines/scumm/dialogs.cpp:597 +msgid "Speech Only" +msgstr "Толькі агучка" + +#: engines/scumm/dialogs.cpp:598 +msgid "Speech and Subtitles" +msgstr "Агучка і субтытры" + +#: engines/scumm/dialogs.cpp:599 +msgid "Subtitles Only" +msgstr "Толькі субтытры" + +#: engines/scumm/dialogs.cpp:607 +msgctxt "lowres" +msgid "Speech & Subs" +msgstr "Агучка і тэкст" + +#: engines/scumm/dialogs.cpp:653 +msgid "Select a Proficiency Level." +msgstr "Абярыце ўзровень складанасці." + +#: engines/scumm/dialogs.cpp:655 +msgid "Refer to your Loom(TM) manual for help." +msgstr "За дапамогай звярніцеся да інструкцыі Loom(TM)." + +#: engines/scumm/dialogs.cpp:658 +msgid "Standard" +msgstr "Стандартны" + +#: engines/scumm/dialogs.cpp:659 +msgid "Practice" +msgstr "Практыкант" + +#: engines/scumm/dialogs.cpp:660 +msgid "Expert" +msgstr "Эксперт" + +#: engines/scumm/help.cpp:73 +msgid "Common keyboard commands:" +msgstr "Агульныя клавіятурныя каманды:" + +#: engines/scumm/help.cpp:74 +msgid "Save / Load dialog" +msgstr "Дыялог запісу / чытання" + +#: engines/scumm/help.cpp:76 +msgid "Skip line of text" +msgstr "Прапусціць радок" + +#: engines/scumm/help.cpp:77 +msgid "Esc" +msgstr "Esc" + +#: engines/scumm/help.cpp:77 +msgid "Skip cutscene" +msgstr "Прапусціць застаўку" + +#: engines/scumm/help.cpp:78 +msgid "Space" +msgstr "Прабел" + +#: engines/scumm/help.cpp:78 +msgid "Pause game" +msgstr "Паўза гульні" + +#: engines/scumm/help.cpp:79 engines/scumm/help.cpp:84 +#: engines/scumm/help.cpp:95 engines/scumm/help.cpp:96 +#: engines/scumm/help.cpp:97 engines/scumm/help.cpp:98 +#: engines/scumm/help.cpp:99 engines/scumm/help.cpp:100 +#: engines/scumm/help.cpp:101 engines/scumm/help.cpp:102 +msgid "Ctrl" +msgstr "Ctrl" + +#: engines/scumm/help.cpp:79 +msgid "Load game state 1-10" +msgstr "Загрузіць гульню 1-10" + +#: engines/scumm/help.cpp:80 engines/scumm/help.cpp:84 +#: engines/scumm/help.cpp:86 engines/scumm/help.cpp:100 +#: engines/scumm/help.cpp:101 engines/scumm/help.cpp:102 +msgid "Alt" +msgstr "Alt" + +#: engines/scumm/help.cpp:80 +msgid "Save game state 1-10" +msgstr "Захаваць гульню 1-10" + +#: engines/scumm/help.cpp:86 engines/scumm/help.cpp:89 +msgid "Enter" +msgstr "Увод" + +#: engines/scumm/help.cpp:86 +msgid "Toggle fullscreen" +msgstr "Пераключыць на ўвесь экран" + +#: engines/scumm/help.cpp:87 +msgid "Music volume up / down" +msgstr "Гучнасць музыкі павялічыць / паменшыць" + +#: engines/scumm/help.cpp:88 +msgid "Text speed slower / faster" +msgstr "Хуткасць тэксту хутчэй / павольней" + +#: engines/scumm/help.cpp:89 +msgid "Simulate left mouse button" +msgstr "Эмуляцыя левай клавішы мышы" + +#: engines/scumm/help.cpp:90 +msgid "Tab" +msgstr "Tab" + +#: engines/scumm/help.cpp:90 +msgid "Simulate right mouse button" +msgstr "Эмуляцыя правай клавішы мышы" + +#: engines/scumm/help.cpp:93 +msgid "Special keyboard commands:" +msgstr "Спецыяльныя клавіятурныя каманды:" + +#: engines/scumm/help.cpp:94 +msgid "Show / Hide console" +msgstr "Паказаць / Прыбраць кансоль" + +#: engines/scumm/help.cpp:95 +msgid "Start the debugger" +msgstr "Запуск адладчыка" + +#: engines/scumm/help.cpp:96 +msgid "Show memory consumption" +msgstr "Паказаць спажыванне памяці" + +#: engines/scumm/help.cpp:97 +msgid "Run in fast mode (*)" +msgstr "Запусціць хуткі рэжым (*)" + +#: engines/scumm/help.cpp:98 +msgid "Run in really fast mode (*)" +msgstr "Запусціць вельмі хуткі рэжым (*)" + +#: engines/scumm/help.cpp:99 +msgid "Toggle mouse capture" +msgstr "Пераключэнне перахопу мышы" + +#: engines/scumm/help.cpp:100 +msgid "Switch between graphics filters" +msgstr "Пераключэнне паміж графічнымі фільтрамі" + +#: engines/scumm/help.cpp:101 +msgid "Increase / Decrease scale factor" +msgstr "Павялічыць/паменшыць маштаб" + +#: engines/scumm/help.cpp:102 +msgid "Toggle aspect-ratio correction" +msgstr "Пераключэнне карэкцыі суадносін бакоў" + +#: engines/scumm/help.cpp:107 +msgid "* Note that using ctrl-f and" +msgstr "* Выкарыстанне ctrl-f і" + +#: engines/scumm/help.cpp:108 +msgid " ctrl-g are not recommended" +msgstr " ctrl-g не рэкамендуецца," + +#: engines/scumm/help.cpp:109 +msgid " since they may cause crashes" +msgstr " бо яны могуць прывесці да" + +#: engines/scumm/help.cpp:110 +msgid " or incorrect game behavior." +msgstr " няправільнай работы гульні." + +#: engines/scumm/help.cpp:114 +msgid "Spinning drafts on the keyboard:" +msgstr "Змяняныя чарнавікі на клавіятуры:" + +#: engines/scumm/help.cpp:116 +msgid "Main game controls:" +msgstr "Асноўнае кіраванне гульнёй:" + +#: engines/scumm/help.cpp:121 engines/scumm/help.cpp:136 +#: engines/scumm/help.cpp:161 +msgid "Push" +msgstr "Пхаць" + +#: engines/scumm/help.cpp:122 engines/scumm/help.cpp:137 +#: engines/scumm/help.cpp:162 +msgid "Pull" +msgstr "Цягнуць" + +#: engines/scumm/help.cpp:123 engines/scumm/help.cpp:138 +#: engines/scumm/help.cpp:163 engines/scumm/help.cpp:197 +#: engines/scumm/help.cpp:207 +msgid "Give" +msgstr "Даць" + +#: engines/scumm/help.cpp:124 engines/scumm/help.cpp:139 +#: engines/scumm/help.cpp:164 engines/scumm/help.cpp:190 +#: engines/scumm/help.cpp:208 +msgid "Open" +msgstr "Адкрыць" + +#: engines/scumm/help.cpp:126 +msgid "Go to" +msgstr "Ісці" + +#: engines/scumm/help.cpp:127 +msgid "Get" +msgstr "Узяць" + +#: engines/scumm/help.cpp:128 engines/scumm/help.cpp:152 +#: engines/scumm/help.cpp:170 engines/scumm/help.cpp:198 +#: engines/scumm/help.cpp:213 engines/scumm/help.cpp:224 +#: engines/scumm/help.cpp:250 +msgid "Use" +msgstr "Выкарыстаць" + +#: engines/scumm/help.cpp:129 engines/scumm/help.cpp:141 +msgid "Read" +msgstr "Чытаць" + +#: engines/scumm/help.cpp:130 engines/scumm/help.cpp:147 +msgid "New kid" +msgstr "Новы перс" + +#: engines/scumm/help.cpp:131 engines/scumm/help.cpp:153 +#: engines/scumm/help.cpp:171 +msgid "Turn on" +msgstr "Уключыць" + +#: engines/scumm/help.cpp:132 engines/scumm/help.cpp:154 +#: engines/scumm/help.cpp:172 +msgid "Turn off" +msgstr "Выключыць" + +#: engines/scumm/help.cpp:142 engines/scumm/help.cpp:167 +#: engines/scumm/help.cpp:194 +msgid "Walk to" +msgstr "Ісці да" + +#: engines/scumm/help.cpp:143 engines/scumm/help.cpp:168 +#: engines/scumm/help.cpp:195 engines/scumm/help.cpp:210 +#: engines/scumm/help.cpp:227 +msgid "Pick up" +msgstr "Падняць" + +#: engines/scumm/help.cpp:144 engines/scumm/help.cpp:169 +msgid "What is" +msgstr "Што такое" + +#: engines/scumm/help.cpp:146 +msgid "Unlock" +msgstr "Адкрыць" + +#: engines/scumm/help.cpp:149 +msgid "Put on" +msgstr "Пакласці" + +#: engines/scumm/help.cpp:150 +msgid "Take off" +msgstr "Падняць" + +#: engines/scumm/help.cpp:156 +msgid "Fix" +msgstr "Выправіць" + +#: engines/scumm/help.cpp:158 +msgid "Switch" +msgstr "Пераключыць" + +#: engines/scumm/help.cpp:166 engines/scumm/help.cpp:228 +msgid "Look" +msgstr "Глядзець" + +#: engines/scumm/help.cpp:173 engines/scumm/help.cpp:223 +msgid "Talk" +msgstr "Гаварыць" + +#: engines/scumm/help.cpp:174 +msgid "Travel" +msgstr "Падарожнічаць" + +#: engines/scumm/help.cpp:175 +msgid "To Henry / To Indy" +msgstr "Генры/Інды" + +#. I18N: These are different musical notes +#: engines/scumm/help.cpp:179 +msgid "play C minor on distaff" +msgstr "іграць до мінор на калаўроце" + +#: engines/scumm/help.cpp:180 +msgid "play D on distaff" +msgstr "іграць рэ на калаўроце" + +#: engines/scumm/help.cpp:181 +msgid "play E on distaff" +msgstr "іграць мі на калаўроце" + +#: engines/scumm/help.cpp:182 +msgid "play F on distaff" +msgstr "іграць фа на калаўроце" + +#: engines/scumm/help.cpp:183 +msgid "play G on distaff" +msgstr "іграць соль на калаўроце" + +#: engines/scumm/help.cpp:184 +msgid "play A on distaff" +msgstr "іграць ля на калаўроце" + +#: engines/scumm/help.cpp:185 +msgid "play B on distaff" +msgstr "іграць сі на калаўроце" + +#: engines/scumm/help.cpp:186 +msgid "play C major on distaff" +msgstr "іграць до мажор на калаўроце" + +#: engines/scumm/help.cpp:192 engines/scumm/help.cpp:214 +msgid "puSh" +msgstr "пхаць" + +#: engines/scumm/help.cpp:193 engines/scumm/help.cpp:215 +msgid "pull (Yank)" +msgstr "цягнуць (чапляць)" + +#: engines/scumm/help.cpp:196 engines/scumm/help.cpp:212 +#: engines/scumm/help.cpp:248 +msgid "Talk to" +msgstr "Гаварыць з" + +#: engines/scumm/help.cpp:199 engines/scumm/help.cpp:211 +msgid "Look at" +msgstr "Глядзець на" + +#: engines/scumm/help.cpp:200 +msgid "turn oN" +msgstr "уключыць" + +#: engines/scumm/help.cpp:201 +msgid "turn oFf" +msgstr "выключыць" + +#: engines/scumm/help.cpp:217 +msgid "KeyUp" +msgstr "Уверх" + +#: engines/scumm/help.cpp:217 +msgid "Highlight prev dialogue" +msgstr "Падсвятліць папярэдні дыялог" + +#: engines/scumm/help.cpp:218 +msgid "KeyDown" +msgstr "Уніз" + +#: engines/scumm/help.cpp:218 +msgid "Highlight next dialogue" +msgstr "Падсвятліць наступны дыялог" + +#: engines/scumm/help.cpp:222 +msgid "Walk" +msgstr "Ісці" + +#: engines/scumm/help.cpp:225 engines/scumm/help.cpp:234 +#: engines/scumm/help.cpp:241 engines/scumm/help.cpp:249 +msgid "Inventory" +msgstr "Інвентар" + +#: engines/scumm/help.cpp:226 +msgid "Object" +msgstr "Аб'ект" + +#: engines/scumm/help.cpp:229 +msgid "Black and White / Color" +msgstr "Чорна-белы / Каляровы" + +#: engines/scumm/help.cpp:232 +msgid "Eyes" +msgstr "Вочы" + +#: engines/scumm/help.cpp:233 +msgid "Tongue" +msgstr "Язык" + +#: engines/scumm/help.cpp:235 +msgid "Punch" +msgstr "Удар" + +#: engines/scumm/help.cpp:236 +msgid "Kick" +msgstr "Нагой" + +#: engines/scumm/help.cpp:239 engines/scumm/help.cpp:247 +msgid "Examine" +msgstr "Праверыць" + +#: engines/scumm/help.cpp:240 +msgid "Regular cursor" +msgstr "Звычайны курсор" + +#. I18N: Comm is a communication device +#: engines/scumm/help.cpp:243 +msgid "Comm" +msgstr "Кам" + +#: engines/scumm/help.cpp:246 +msgid "Save / Load / Options" +msgstr "Загрузіць / Захаваць / Налады" + +#: engines/scumm/help.cpp:255 +msgid "Other game controls:" +msgstr "Астатняе кіраванне гульнёй:" + +#: engines/scumm/help.cpp:257 engines/scumm/help.cpp:267 +msgid "Inventory:" +msgstr "Інвентар:" + +#: engines/scumm/help.cpp:258 engines/scumm/help.cpp:274 +msgid "Scroll list up" +msgstr "Пракруціць спіс уверх" + +#: engines/scumm/help.cpp:259 engines/scumm/help.cpp:275 +msgid "Scroll list down" +msgstr "Пракруціць спіс уніз" + +#: engines/scumm/help.cpp:260 engines/scumm/help.cpp:268 +msgid "Upper left item" +msgstr "Верхні левы прадмет" + +#: engines/scumm/help.cpp:261 engines/scumm/help.cpp:270 +msgid "Lower left item" +msgstr "Ніжні левы прадмет" + +#: engines/scumm/help.cpp:262 engines/scumm/help.cpp:271 +msgid "Upper right item" +msgstr "Верхні правы прадмет" + +#: engines/scumm/help.cpp:263 engines/scumm/help.cpp:273 +msgid "Lower right item" +msgstr "Ніжні правы прадмет" + +#: engines/scumm/help.cpp:269 +msgid "Middle left item" +msgstr "Сярэдні левы прадмет" + +#: engines/scumm/help.cpp:272 +msgid "Middle right item" +msgstr "Сярэдні правы прадмет" + +#: engines/scumm/help.cpp:279 engines/scumm/help.cpp:284 +msgid "Switching characters:" +msgstr "Змена героя:" + +#: engines/scumm/help.cpp:281 +msgid "Second kid" +msgstr "Другі герой" + +#: engines/scumm/help.cpp:282 +msgid "Third kid" +msgstr "Трэці герой" + +#: engines/scumm/help.cpp:294 +msgid "Fighting controls (numpad):" +msgstr "Кіраванне боем (лічбавыя клавішы)" + +#: engines/scumm/help.cpp:295 engines/scumm/help.cpp:296 +#: engines/scumm/help.cpp:297 +msgid "Step back" +msgstr "Крок назад" + +#: engines/scumm/help.cpp:298 +msgid "Block high" +msgstr "Абарона зверху" + +#: engines/scumm/help.cpp:299 +msgid "Block middle" +msgstr "Абарона пасярэдзіне" + +#: engines/scumm/help.cpp:300 +msgid "Block low" +msgstr "Абарона знізу" + +#: engines/scumm/help.cpp:301 +msgid "Punch high" +msgstr "Удар зверху" + +#: engines/scumm/help.cpp:302 +msgid "Punch middle" +msgstr "Удар пасярэдзіне" + +#: engines/scumm/help.cpp:303 +msgid "Punch low" +msgstr "Удар знізу" + +#: engines/scumm/help.cpp:306 +msgid "These are for Indy on left." +msgstr "Гэта калі Інды злева." + +#: engines/scumm/help.cpp:307 +msgid "When Indy is on the right," +msgstr "Калі Інды справа," + +#: engines/scumm/help.cpp:308 +msgid "7, 4, and 1 are switched with" +msgstr "7, 4 і 1 змяняюцца з" + +#: engines/scumm/help.cpp:309 +msgid "9, 6, and 3, respectively." +msgstr "9, 6 і 3 адпаведна." + +#: engines/scumm/help.cpp:316 +msgid "Biplane controls (numpad):" +msgstr "Кіраванне самалётам (лічбавыя клавішы)" + +#: engines/scumm/help.cpp:317 +msgid "Fly to upper left" +msgstr "Ляцець налева-ўверх" + +#: engines/scumm/help.cpp:318 +msgid "Fly to left" +msgstr "Ляцець налева" + +#: engines/scumm/help.cpp:319 +msgid "Fly to lower left" +msgstr "Ляцець налева-ўніз" + +#: engines/scumm/help.cpp:320 +msgid "Fly upwards" +msgstr "Ляцець уверх" + +#: engines/scumm/help.cpp:321 +msgid "Fly straight" +msgstr "Ляцець прама" + +#: engines/scumm/help.cpp:322 +msgid "Fly down" +msgstr "Ляцець уніз" + +#: engines/scumm/help.cpp:323 +msgid "Fly to upper right" +msgstr "Ляцець направа-ўверх" + +#: engines/scumm/help.cpp:324 +msgid "Fly to right" +msgstr "Ляцець направа" + +#: engines/scumm/help.cpp:325 +msgid "Fly to lower right" +msgstr "Ляцець направа-ўніз" + +#: engines/scumm/scumm.cpp:1773 +#, c-format +msgid "" +"Native MIDI support requires the Roland Upgrade from LucasArts,\n" +"but %s is missing. Using AdLib instead." +msgstr "" +"Рэжым \"роднага\" MIDI патрабуе абнаўленне Roland Upgrade ад\n" +"LucasArts, але не хапае %s. Пераключаюся на AdLib." + +#: engines/scumm/scumm.cpp:2278 engines/agos/saveload.cpp:220 +#, c-format +msgid "" +"Failed to save game state to file:\n" +"\n" +"%s" +msgstr "" +"Не атрымалася запісаць гульню ў файл:\n" +"\n" +"%s" + +#: engines/scumm/scumm.cpp:2285 engines/agos/saveload.cpp:185 +#, c-format +msgid "" +"Failed to load game state from file:\n" +"\n" +"%s" +msgstr "" +"Не атрымалася загрузіць гульню з файла:\n" +"\n" +"%s" + +#: engines/scumm/scumm.cpp:2297 engines/agos/saveload.cpp:228 +#, c-format +msgid "" +"Successfully saved game state in file:\n" +"\n" +"%s" +msgstr "" +"Гульня паспяхова захавана ў файл:\n" +"\n" +"%s" + +#: engines/scumm/scumm.cpp:2512 +msgid "" +"Usually, Maniac Mansion would start now. But ScummVM doesn't do that yet. To " +"play it, go to 'Add Game' in the ScummVM start menu and select the 'Maniac' " +"directory inside the Tentacle game directory." +msgstr "" +"Зараз павінна запусціцца гульня Maniac Mansion. Але ScummVM пакуль гэтага не " +"ўмее. Каб згуляць, націсніце 'Новая гульня' у стартавым меню ScummVM, а " +"затым абярыце дырэкторыю Maniac у дырэкторыі з гульнёй Tentacle." + +#. I18N: Option for fast scene switching +#: engines/mohawk/dialogs.cpp:92 engines/mohawk/dialogs.cpp:171 +msgid "~Z~ip Mode Activated" +msgstr "Рэжым хуткага пераходу актываваны" + +#: engines/mohawk/dialogs.cpp:93 +msgid "~T~ransitions Enabled" +msgstr "Пераходы актываваны" + +#. I18N: Drop book page +#: engines/mohawk/dialogs.cpp:95 +msgid "~D~rop Page" +msgstr "Выкінуць старонку" + +#: engines/mohawk/dialogs.cpp:99 +msgid "~S~how Map" +msgstr "Показать карту" + +#: engines/mohawk/dialogs.cpp:105 +msgid "~M~ain Menu" +msgstr "Галоўнае меню" + +#: engines/mohawk/dialogs.cpp:172 +msgid "~W~ater Effect Enabled" +msgstr "Эфекты вады ўключаны" + +#: engines/agos/animation.cpp:560 +#, c-format +msgid "Cutscene file '%s' not found!" +msgstr "Файл застаўкі '%s' не знойдзены!" + +#: engines/gob/inter_playtoons.cpp:256 engines/gob/inter_v2.cpp:1287 +#: engines/tinsel/saveload.cpp:532 +msgid "Failed to load game state from file." +msgstr "Не атрымалася загрузіць захаваную гульню з файла." + +#: engines/gob/inter_v2.cpp:1357 engines/tinsel/saveload.cpp:545 +msgid "Failed to save game state to file." +msgstr "Не атрымалася захаваць гульню ў файл." + +#: engines/gob/inter_v5.cpp:107 +msgid "Failed to delete file." +msgstr "Не атрымалася выдаліць файл." + +#: engines/groovie/script.cpp:420 +msgid "Failed to save game" +msgstr "Не атрымалася захаваць гульню" + +#. I18N: Studio audience adds an applause and cheering sounds whenever +#. Malcolm makes a joke. +#: engines/kyra/detection.cpp:62 +msgid "Studio audience" +msgstr "Студыйная аўдыторыя" + +#: engines/kyra/detection.cpp:63 +msgid "Enable studio audience" +msgstr "Уключыць гукі аўдыторыі ў студыі" + +#. I18N: This option allows the user to skip text and cutscenes. +#: engines/kyra/detection.cpp:73 +msgid "Skip support" +msgstr "Падтрымка пропускаў" + +#: engines/kyra/detection.cpp:74 +msgid "Allow text and cutscenes to be skipped" +msgstr "Уключае магчымасць прапускаць тэксты і застаўкі" + +#. I18N: Helium mode makes people sound like they've inhaled Helium. +#: engines/kyra/detection.cpp:84 +msgid "Helium mode" +msgstr "Рэжым гелія" + +#: engines/kyra/detection.cpp:85 +msgid "Enable helium mode" +msgstr "Уключыць рэжым гелія" + +#. I18N: When enabled, this option makes scrolling smoother when +#. changing from one screen to another. +#: engines/kyra/detection.cpp:99 +msgid "Smooth scrolling" +msgstr "Плыўная прагортка" + +#: engines/kyra/detection.cpp:100 +msgid "Enable smooth scrolling when walking" +msgstr "Уключыць плыўную прагортку падчас хады" + +#. I18N: When enabled, this option changes the cursor when it floats to the +#. edge of the screen to a directional arrow. The player can then click to +#. walk towards that direction. +#: engines/kyra/detection.cpp:112 +msgid "Floating cursors" +msgstr "Плывучыя курсоры" + +#: engines/kyra/detection.cpp:113 +msgid "Enable floating cursors" +msgstr "Уключыць плывучыя курсоры" + +#. I18N: HP stands for Hit Points +#: engines/kyra/detection.cpp:127 +msgid "HP bar graphs" +msgstr "Палоскі здароўя" + +#: engines/kyra/detection.cpp:128 +msgid "Enable hit point bar graphs" +msgstr "Уключыць адлюстраванне палосак здароўя" + +#: engines/kyra/lol.cpp:478 +msgid "Attack 1" +msgstr "Атака 1" + +#: engines/kyra/lol.cpp:479 +msgid "Attack 2" +msgstr "Атака 2" + +#: engines/kyra/lol.cpp:480 +msgid "Attack 3" +msgstr "Атака 3" + +#: engines/kyra/lol.cpp:481 +msgid "Move Forward" +msgstr "Ісці наперад" + +#: engines/kyra/lol.cpp:482 +msgid "Move Back" +msgstr "Ісці назад" + +#: engines/kyra/lol.cpp:483 +msgid "Slide Left" +msgstr "Слізгаць налева" + +#: engines/kyra/lol.cpp:484 +msgid "Slide Right" +msgstr "Слізгаць направа" + +#: engines/kyra/lol.cpp:485 +msgid "Turn Left" +msgstr "Паварот налева" + +#: engines/kyra/lol.cpp:486 +msgid "Turn Right" +msgstr "Паварот направа" + +#: engines/kyra/lol.cpp:487 +msgid "Rest" +msgstr "Адпачыць" + +#: engines/kyra/lol.cpp:488 +msgid "Options" +msgstr "Опцыі" + +#: engines/kyra/lol.cpp:489 +msgid "Choose Spell" +msgstr "Абраць загавор" + +#: engines/kyra/sound_midi.cpp:475 +msgid "" +"You appear to be using a General MIDI device,\n" +"but your game only supports Roland MT32 MIDI.\n" +"We try to map the Roland MT32 instruments to\n" +"General MIDI ones. It is still possible that\n" +"some tracks sound incorrect." +msgstr "" +"Здаецца, вы спрабуеце выкарыстоўваць прыладу\n" +"General MIDI, але гэтая гульня падтрымлівае толькі\n" +"Roland MT32 MIDI. Мы паспрабуем падабраць General\n" +"MIDI прылады, падобныя на Roland MT32, але\n" +"можа так атрымацца, што некаторыя трэкі будуць\n" +"сыграны няправільна." + +#: engines/queen/queen.cpp:59 +msgid "Alternative intro" +msgstr "Альтэрнатыўны ўступ" + +#: engines/queen/queen.cpp:60 +msgid "Use an alternative game intro (CD version only)" +msgstr "Выкарыстоўваць альтэрнатыўны ўступ (толькі для CD версіі гульні)" + +#: engines/sky/compact.cpp:130 +msgid "" +"Unable to find \"sky.cpt\" file!\n" +"Please download it from www.scummvm.org" +msgstr "" +"Адсутнічае файл sky.cpt!\n" +"Калі ласка, запампуйце яго з www.scummvm.org" + +#: engines/sky/compact.cpp:141 +msgid "" +"The \"sky.cpt\" file has an incorrect size.\n" +"Please (re)download it from www.scummvm.org" +msgstr "" +"Файл sky.cpt мае няправільны памер.\n" +"Калі ласка, запампуйце яго нанова з www.scummvm.org" + +#: engines/sky/detection.cpp:44 +msgid "Floppy intro" +msgstr "Уступ з дыскет" + +#: engines/sky/detection.cpp:45 +msgid "Use the floppy version's intro (CD version only)" +msgstr "Выкарыстоўваць уступ з гнуткіх дыскаў (толькі для CD версіі гульні)" + +#: engines/sword1/animation.cpp:539 +#, c-format +msgid "PSX stream cutscene '%s' cannot be played in paletted mode" +msgstr "Застаўка PSX '%s' не можа быць прайграна ў рэжыме з палітрай" + +#: engines/sword1/animation.cpp:560 engines/sword2/animation.cpp:455 +msgid "DXA cutscenes found but ScummVM has been built without zlib support" +msgstr "" +"Знойдзены застаўкі ў фармаце DXA, але ScummVM быў сабраны без падтрымкі zlib" + +#: engines/sword1/animation.cpp:570 engines/sword2/animation.cpp:465 +msgid "MPEG2 cutscenes are no longer supported" +msgstr "Застаўкі ў фармаце MPEG2 больш не падтрымліваюцца" + +#: engines/sword1/animation.cpp:576 engines/sword2/animation.cpp:473 +#, c-format +msgid "Cutscene '%s' not found" +msgstr "Застаўка '%s' не знойдзена" + +#: engines/sword1/control.cpp:863 +msgid "" +"ScummVM found that you have old savefiles for Broken Sword 1 that should be " +"converted.\n" +"The old save game format is no longer supported, so you will not be able to " +"load your games if you don't convert them.\n" +"\n" +"Press OK to convert them now, otherwise you will be asked again the next " +"time you start the game.\n" +msgstr "" +"ScummVM выявіў у вас захаванні гульні Broken Sword 1 у старым фармаце.\n" +"Стары фармат больш не падтрымліваецца, і, каб загрузіць захаванні, яны " +"павінны быць пераведзены ў новы фармат.\n" +"\n" +"Націсніце ОК, каб перавесці іх у новы фармат зараз, у адваротным выпадку " +"гэта паведамленне з'явіцца зноў пры наступным запуску гульні.\n" + +#: engines/sword1/control.cpp:1232 +#, c-format +msgid "" +"Target new save game already exists!\n" +"Would you like to keep the old save game (%s) or the new one (%s)?\n" +msgstr "" +"Захаванне гульні з такім імем ужо існуе!\n" +"Вы жадаеце пакінуць старую назву (%s) ці зрабіць новую (%s)?\n" + +#: engines/sword1/control.cpp:1235 +msgid "Keep the old one" +msgstr "Пакінуць старое" + +#: engines/sword1/control.cpp:1235 +msgid "Keep the new one" +msgstr "Зрабіць новае" + +#: engines/sword1/logic.cpp:1633 +msgid "This is the end of the Broken Sword 1 Demo" +msgstr "Гэта завяршэнне дэма Broken Sword 1" + +#: engines/sword2/animation.cpp:435 +msgid "" +"PSX cutscenes found but ScummVM has been built without RGB color support" +msgstr "" +"Знойдзены застаўкі ў фармаце PSX, але ScummVM быў сабраны без падтрымкі RGB " +"колераў" + +#: engines/sword2/sword2.cpp:79 +msgid "Show object labels" +msgstr "Паказваць назвы аб'ектаў" + +#: engines/sword2/sword2.cpp:80 +msgid "Show labels for objects on mouse hover" +msgstr "Паказвае назвы аб'ектаў пры навядзенні курсора мышы" + +#: engines/teenagent/resources.cpp:68 +msgid "" +"You're missing the 'teenagent.dat' file. Get it from the ScummVM website" +msgstr "" +"У вас адсутнічае файл 'teenagent.dat'. Запампуйце яго з вэб-сайта ScummVM" + +#: engines/teenagent/resources.cpp:89 +msgid "" +"The teenagent.dat file is compressed and zlib hasn't been included in this " +"executable. Please decompress it" +msgstr "" +"Файл teenagent.dat зжаты, але zlib не ўключана ў гэту праграму. Калі ласка, " +"распакуйце яго" + +#: engines/parallaction/saveload.cpp:133 +#, c-format +msgid "" +"Can't save game in slot %i\n" +"\n" +msgstr "" +"Не магу захаваць гульню ў пазіцыю %i\n" +"\n" + +#: engines/parallaction/saveload.cpp:204 +msgid "Loading game..." +msgstr "Загружаю гульню..." + +#: engines/parallaction/saveload.cpp:219 +msgid "Saving game..." +msgstr "Захоўваю гульню..." + +#: engines/parallaction/saveload.cpp:272 +msgid "" +"ScummVM found that you have old savefiles for Nippon Safes that should be " +"renamed.\n" +"The old names are no longer supported, so you will not be able to load your " +"games if you don't convert them.\n" +"\n" +"Press OK to convert them now, otherwise you will be asked next time.\n" +msgstr "" +"ScummVM знайшоў у вас старыя захаванні гульні Nippon Safes, якія неабходна " +"пераназваць. Старыя назвы больш не падтрымліваюцца, і таму вы не зможаце " +"загрузіць захаванні, калі не пераназавеце іх.\n" +"\n" +"Націсніце ОК, каб пераназваць іх зараз, у адваротным выпадку гэта ж " +"паведамленне з'явіцца пры наступным запуску гульні.\n" + +#: engines/parallaction/saveload.cpp:319 +msgid "ScummVM successfully converted all your savefiles." +msgstr "ScummVM паспяхова пераўтварыў усе вашы захаванні гульняў." + +#: engines/parallaction/saveload.cpp:321 +msgid "" +"ScummVM printed some warnings in your console window and can't guarantee all " +"your files have been converted.\n" +"\n" +"Please report to the team." +msgstr "" +"ScummVM напісаў некалькі папярэджанняў у акно кансолі і не змог пераўтварыць " +"усе файлы.\n" +"\n" +"Калі ласка, паведаміце пра гэта камандзе ScummVM." + +#: audio/fmopl.cpp:49 +msgid "MAME OPL emulator" +msgstr "Эмулятар MAME OPL" + +#: audio/fmopl.cpp:51 +msgid "DOSBox OPL emulator" +msgstr "Эмулятар DOSBox OPL" + +#: audio/mididrv.cpp:209 +#, c-format +msgid "" +"The selected audio device '%s' was not found (e.g. might be turned off or " +"disconnected)." +msgstr "" +"Абраная гукавая прылада '%s' не была знойдзена (магчыма, яна выключана ці не " +"падключана)." + +#: audio/mididrv.cpp:209 audio/mididrv.cpp:221 audio/mididrv.cpp:257 +#: audio/mididrv.cpp:272 +msgid "Attempting to fall back to the next available device..." +msgstr "Спрабую выкарыстаць іншую даступную прыладу..." + +#: audio/mididrv.cpp:221 +#, c-format +msgid "" +"The selected audio device '%s' cannot be used. See log file for more " +"information." +msgstr "" +"Абраная гукавая прылада '%s' не можа быць скарыстана. Глядзіце файл " +"пратаколу для больш падрабязнай інфармацыі." + +#: audio/mididrv.cpp:257 +#, c-format +msgid "" +"The preferred audio device '%s' was not found (e.g. might be turned off or " +"disconnected)." +msgstr "" +"Пераважная гукавая прылада '%s' не была знойдзена (магчыма, яна выключана ці " +"не падключана)." + +#: audio/mididrv.cpp:272 +#, c-format +msgid "" +"The preferred audio device '%s' cannot be used. See log file for more " +"information." +msgstr "" +"Пераважная гукавая прылада '%s' не можа быць скарыстана. Глядзіце файл " +"пратаколу для больш падрабязнай інфармацыі." + +#: audio/null.h:43 +msgid "No music" +msgstr "Без музыкі" + +#: audio/mods/paula.cpp:189 +msgid "Amiga Audio Emulator" +msgstr "Эмулятар гуку Amiga" + +#: audio/softsynth/adlib.cpp:1593 +msgid "AdLib Emulator" +msgstr "Эмулятар AdLib" + +#: audio/softsynth/appleiigs.cpp:33 +msgid "Apple II GS Emulator (NOT IMPLEMENTED)" +msgstr "Эмулятар Apple II GS (адсутнічае)" + +#: audio/softsynth/sid.cpp:1430 +msgid "C64 Audio Emulator" +msgstr "Эмулятар гуку C64" + +#: audio/softsynth/mt32.cpp:293 +msgid "Initializing MT-32 Emulator" +msgstr "Наладжваю эмулятар MT-32" + +#: audio/softsynth/mt32.cpp:512 +msgid "MT-32 Emulator" +msgstr "Эмулятар MT-32" + +#: audio/softsynth/pcspk.cpp:139 +msgid "PC Speaker Emulator" +msgstr "Эмулятар PC спікера" + +#: audio/softsynth/pcspk.cpp:158 +msgid "IBM PCjr Emulator" +msgstr "Эмулятар IBM PCjr" + +#: backends/keymapper/remap-dialog.cpp:47 +msgid "Keymap:" +msgstr "Табліца клавіш:" + +#: backends/keymapper/remap-dialog.cpp:66 +msgid " (Effective)" +msgstr " (Дзейсная)" + +#: backends/keymapper/remap-dialog.cpp:106 +msgid " (Active)" +msgstr " (Актыўная)" + +#: backends/keymapper/remap-dialog.cpp:106 +msgid " (Blocked)" +msgstr " (Заблакавана)" + +#: backends/keymapper/remap-dialog.cpp:119 +msgid " (Global)" +msgstr " (Глабальная)" + +#: backends/keymapper/remap-dialog.cpp:127 +msgid " (Game)" +msgstr " (Гульні)" + +#: backends/midi/windows.cpp:164 +msgid "Windows MIDI" +msgstr "Windows MIDI" + +#: backends/platform/ds/arm9/source/dsoptions.cpp:57 +msgid "ScummVM Main Menu" +msgstr "Галоўнае меню ScummVM" + +#: backends/platform/ds/arm9/source/dsoptions.cpp:63 +msgid "~L~eft handed mode" +msgstr "Леварукі рэжым" + +#: backends/platform/ds/arm9/source/dsoptions.cpp:64 +msgid "~I~ndy fight controls" +msgstr "Кіраванне баямі ў Indy" + +#: backends/platform/ds/arm9/source/dsoptions.cpp:65 +msgid "Show mouse cursor" +msgstr "Паказваць курсор мышы" + +#: backends/platform/ds/arm9/source/dsoptions.cpp:66 +msgid "Snap to edges" +msgstr "Прымацаваць да меж" + +#: backends/platform/ds/arm9/source/dsoptions.cpp:68 +msgid "Touch X Offset" +msgstr "Зрушэнне дотыкаў па восі X" + +#: backends/platform/ds/arm9/source/dsoptions.cpp:75 +msgid "Touch Y Offset" +msgstr "Зрушэнне дотыкаў па восі Y" + +#: backends/platform/ds/arm9/source/dsoptions.cpp:87 +msgid "Use laptop trackpad-style cursor control" +msgstr "Выкарыстоўваць кіраванне курсорам як на трэкпадзе лэптопаў" + +#: backends/platform/ds/arm9/source/dsoptions.cpp:88 +msgid "Tap for left click, double tap right click" +msgstr "Тап для левай пстрычкі, падвойны тап для правай пстрычкі" + +#: backends/platform/ds/arm9/source/dsoptions.cpp:90 +msgid "Sensitivity" +msgstr "Адчувальнасць" + +#: backends/platform/ds/arm9/source/dsoptions.cpp:99 +msgid "Initial top screen scale:" +msgstr "Пачатковы маштаб верхняга экрана:" + +#: backends/platform/ds/arm9/source/dsoptions.cpp:105 +msgid "Main screen scaling:" +msgstr "Маштаб галоўнага экрана:" + +#: backends/platform/ds/arm9/source/dsoptions.cpp:107 +msgid "Hardware scale (fast, but low quality)" +msgstr "Хардварнае маштабаванне (хутка, але нізкай якасці)" + +#: backends/platform/ds/arm9/source/dsoptions.cpp:108 +msgid "Software scale (good quality, but slower)" +msgstr "Праграмнае маштабаванне (добрая якасць, але марудней)" + +#: backends/platform/ds/arm9/source/dsoptions.cpp:109 +msgid "Unscaled (you must scroll left and right)" +msgstr "Без маштабавання (трэба будзе пракручваць налева і направа)" + +#: backends/platform/ds/arm9/source/dsoptions.cpp:111 +msgid "Brightness:" +msgstr "Яркасць:" + +#: backends/platform/ds/arm9/source/dsoptions.cpp:121 +msgid "High quality audio (slower) (reboot)" +msgstr "Высокая якасць гуку (марудней) (рэбут)" + +#: backends/platform/ds/arm9/source/dsoptions.cpp:122 +msgid "Disable power off" +msgstr "Забараніць выключэнне" + +#: backends/platform/iphone/osys_events.cpp:300 +msgid "Mouse-click-and-drag mode enabled." +msgstr "Рэжым мышы націснуць-і-цягнуць уключаны." + +#: backends/platform/iphone/osys_events.cpp:302 +msgid "Mouse-click-and-drag mode disabled." +msgstr "Рэжым мышы націснуць-і-цягнуць выключаны." + +#: backends/platform/iphone/osys_events.cpp:313 +msgid "Touchpad mode enabled." +msgstr "Рэжым тачпада ўключаны." + +#: backends/platform/iphone/osys_events.cpp:315 +msgid "Touchpad mode disabled." +msgstr "Рэжым тачпада выключаны." + +#: backends/platform/maemo/maemo.cpp:209 +msgid "Click Mode" +msgstr "Рэжым пстрычкі" + +#: backends/platform/maemo/maemo.cpp:215 +#: backends/platform/symbian/src/SymbianActions.cpp:42 +#: backends/platform/wince/CEActionsPocket.cpp:60 +#: backends/platform/wince/CEActionsSmartphone.cpp:43 +#: backends/platform/bada/form.cpp:281 +msgid "Left Click" +msgstr "Левая пстрычка" + +#: backends/platform/maemo/maemo.cpp:218 +msgid "Middle Click" +msgstr "Сярэдняя пстрычка" + +#: backends/platform/maemo/maemo.cpp:221 +#: backends/platform/symbian/src/SymbianActions.cpp:43 +#: backends/platform/wince/CEActionsSmartphone.cpp:44 +#: backends/platform/bada/form.cpp:273 +msgid "Right Click" +msgstr "Правая пстрычка" + +#: backends/platform/sdl/macosx/appmenu_osx.mm:78 +msgid "Hide ScummVM" +msgstr "Схаваць ScummVM" + +#: backends/platform/sdl/macosx/appmenu_osx.mm:83 +msgid "Hide Others" +msgstr "Схаваць астатнія" + +#: backends/platform/sdl/macosx/appmenu_osx.mm:88 +msgid "Show All" +msgstr "Паказаць усё" + +#: backends/platform/sdl/macosx/appmenu_osx.mm:110 +#: backends/platform/sdl/macosx/appmenu_osx.mm:121 +msgid "Window" +msgstr "Акно" + +#: backends/platform/sdl/macosx/appmenu_osx.mm:115 +msgid "Minimize" +msgstr "Прыбраць у Dock" + +#: backends/graphics/surfacesdl/surfacesdl-graphics.cpp:45 +msgid "Normal (no scaling)" +msgstr "Без павелічэння" + +#: backends/graphics/surfacesdl/surfacesdl-graphics.cpp:64 +msgctxt "lowres" +msgid "Normal (no scaling)" +msgstr "Без павелічэння" + +#: backends/graphics/surfacesdl/surfacesdl-graphics.cpp:2135 +#: backends/graphics/openglsdl/openglsdl-graphics.cpp:533 +msgid "Enabled aspect ratio correction" +msgstr "Карэкцыя суадносін бакоў уключана" + +#: backends/graphics/surfacesdl/surfacesdl-graphics.cpp:2141 +#: backends/graphics/openglsdl/openglsdl-graphics.cpp:538 +msgid "Disabled aspect ratio correction" +msgstr "Карэкцыя суадносін бакоў выключана" + +#: backends/graphics/surfacesdl/surfacesdl-graphics.cpp:2196 +msgid "Active graphics filter:" +msgstr "Актыўны графічны фільтр:" + +#: backends/graphics/surfacesdl/surfacesdl-graphics.cpp:2238 +#: backends/graphics/openglsdl/openglsdl-graphics.cpp:477 +msgid "Windowed mode" +msgstr "Аконны рэжым" + +#: backends/graphics/opengl/opengl-graphics.cpp:135 +msgid "OpenGL Normal" +msgstr "OpenGL без павелічэння" + +#: backends/graphics/opengl/opengl-graphics.cpp:136 +msgid "OpenGL Conserve" +msgstr "OpenGL з захаваннем" + +#: backends/graphics/opengl/opengl-graphics.cpp:137 +msgid "OpenGL Original" +msgstr "OpenGL першапачатковы" + +#: backends/graphics/openglsdl/openglsdl-graphics.cpp:415 +msgid "Current display mode" +msgstr "Бягучы відэарэжым" + +#: backends/graphics/openglsdl/openglsdl-graphics.cpp:428 +msgid "Current scale" +msgstr "Бягучы маштаб" + +#: backends/graphics/openglsdl/openglsdl-graphics.cpp:558 +msgid "Active filter mode: Linear" +msgstr "Актыўны рэжым фільтра: Лінейны" + +#: backends/graphics/openglsdl/openglsdl-graphics.cpp:560 +msgid "Active filter mode: Nearest" +msgstr "Актыўны рэжым фільтра: Найблізкі" + +#: backends/platform/symbian/src/SymbianActions.cpp:38 +#: backends/platform/wince/CEActionsSmartphone.cpp:39 +msgid "Up" +msgstr "Уверх" + +#: backends/platform/symbian/src/SymbianActions.cpp:39 +#: backends/platform/wince/CEActionsSmartphone.cpp:40 +msgid "Down" +msgstr "Уніз" + +#: backends/platform/symbian/src/SymbianActions.cpp:40 +#: backends/platform/wince/CEActionsSmartphone.cpp:41 +msgid "Left" +msgstr "Налева" + +#: backends/platform/symbian/src/SymbianActions.cpp:41 +#: backends/platform/wince/CEActionsSmartphone.cpp:42 +msgid "Right" +msgstr "Направа" + +#: backends/platform/symbian/src/SymbianActions.cpp:46 +#: backends/platform/wince/CEActionsSmartphone.cpp:47 +msgid "Zone" +msgstr "Зона" + +#: backends/platform/symbian/src/SymbianActions.cpp:47 +#: backends/platform/wince/CEActionsPocket.cpp:54 +#: backends/platform/wince/CEActionsSmartphone.cpp:48 +msgid "Multi Function" +msgstr "Мультыфункцыя" + +#: backends/platform/symbian/src/SymbianActions.cpp:48 +msgid "Swap character" +msgstr "Змяніць героя" + +#: backends/platform/symbian/src/SymbianActions.cpp:49 +msgid "Skip text" +msgstr "Прапусціць тэкст" + +#: backends/platform/symbian/src/SymbianActions.cpp:51 +msgid "Fast mode" +msgstr "Хуткі рэжым" + +#: backends/platform/symbian/src/SymbianActions.cpp:53 +msgid "Debugger" +msgstr "Адладчык" + +#: backends/platform/symbian/src/SymbianActions.cpp:54 +msgid "Global menu" +msgstr "Глабальнае меню" + +#: backends/platform/symbian/src/SymbianActions.cpp:55 +msgid "Virtual keyboard" +msgstr "Віртуальная клавіятура" + +#: backends/platform/symbian/src/SymbianActions.cpp:56 +msgid "Key mapper" +msgstr "Прызначэнне клавіш" + +#: backends/events/symbiansdl/symbiansdl-events.cpp:184 +msgid "Do you want to quit ?" +msgstr "Вы сапраўды жадаеце выйсці?" + +#: backends/platform/wii/options.cpp:51 +msgid "Video" +msgstr "Відэа" + +#: backends/platform/wii/options.cpp:54 +msgid "Current video mode:" +msgstr "Бягучы відэарэжым:" + +#: backends/platform/wii/options.cpp:56 +msgid "Double-strike" +msgstr "Двайны ўдар" + +#: backends/platform/wii/options.cpp:60 +msgid "Horizontal underscan:" +msgstr "Гарызантальны underscan:" + +#: backends/platform/wii/options.cpp:66 +msgid "Vertical underscan:" +msgstr "Вертыкальны underscan:" + +#: backends/platform/wii/options.cpp:71 +msgid "Input" +msgstr "Увод" + +#: backends/platform/wii/options.cpp:74 +msgid "GC Pad sensitivity:" +msgstr "Адчувальнасць GC пада:" + +#: backends/platform/wii/options.cpp:80 +msgid "GC Pad acceleration:" +msgstr "Паскарэнне GC пада:" + +#: backends/platform/wii/options.cpp:86 +msgid "DVD" +msgstr "DVD" + +#: backends/platform/wii/options.cpp:89 backends/platform/wii/options.cpp:101 +msgid "Status:" +msgstr "Стан:" + +#: backends/platform/wii/options.cpp:90 backends/platform/wii/options.cpp:102 +msgid "Unknown" +msgstr "Невядома" + +#: backends/platform/wii/options.cpp:93 +msgid "Mount DVD" +msgstr "Падключыць DVD" + +#: backends/platform/wii/options.cpp:94 +msgid "Unmount DVD" +msgstr "Адключыць DVD" + +#: backends/platform/wii/options.cpp:98 +msgid "SMB" +msgstr "SMB" + +#: backends/platform/wii/options.cpp:106 +msgid "Server:" +msgstr "Сервер:" + +#: backends/platform/wii/options.cpp:110 +msgid "Share:" +msgstr "Сеткавая тэчка:" + +#: backends/platform/wii/options.cpp:114 +msgid "Username:" +msgstr "Карыстач:" + +#: backends/platform/wii/options.cpp:118 +msgid "Password:" +msgstr "Пароль:" + +#: backends/platform/wii/options.cpp:121 +msgid "Init network" +msgstr "Ініцыялізацыя сеткі" + +#: backends/platform/wii/options.cpp:123 +msgid "Mount SMB" +msgstr "Падключыць SMB" + +#: backends/platform/wii/options.cpp:124 +msgid "Unmount SMB" +msgstr "Адключыць SMB" + +#: backends/platform/wii/options.cpp:143 +msgid "DVD Mounted successfully" +msgstr "DVD падключаны паспяхова" + +#: backends/platform/wii/options.cpp:146 +msgid "Error while mounting the DVD" +msgstr "Памылка пры падключэнні DVD" + +#: backends/platform/wii/options.cpp:148 +msgid "DVD not mounted" +msgstr "DVD не падключаны" + +#: backends/platform/wii/options.cpp:161 +msgid "Network up, share mounted" +msgstr "Сетка працуе, тэчка падключана" + +#: backends/platform/wii/options.cpp:163 +msgid "Network up" +msgstr "Сетка працуе" + +#: backends/platform/wii/options.cpp:166 +msgid ", error while mounting the share" +msgstr ", памылка пры падключэнні тэчкі" + +#: backends/platform/wii/options.cpp:168 +msgid ", share not mounted" +msgstr ", тэчка не падключана" + +#: backends/platform/wii/options.cpp:174 +msgid "Network down" +msgstr "Сетка выключана" + +#: backends/platform/wii/options.cpp:178 +msgid "Initializing network" +msgstr "Наладжваю сетку" + +#: backends/platform/wii/options.cpp:182 +msgid "Timeout while initializing network" +msgstr "Час падключэння да сеткі мінуў" + +#: backends/platform/wii/options.cpp:186 +#, c-format +msgid "Network not initialized (%d)" +msgstr "Сетка не наладзілася (%d)" + +#: backends/platform/wince/CEActionsPocket.cpp:46 +msgid "Hide Toolbar" +msgstr "Схаваць панэль інструментаў" + +#: backends/platform/wince/CEActionsPocket.cpp:47 +msgid "Show Keyboard" +msgstr "Паказаць клавіятуру" + +#: backends/platform/wince/CEActionsPocket.cpp:48 +msgid "Sound on/off" +msgstr "Гук укл/выкл" + +#: backends/platform/wince/CEActionsPocket.cpp:49 +msgid "Right click" +msgstr "Правая пстрычка" + +#: backends/platform/wince/CEActionsPocket.cpp:50 +msgid "Show/Hide Cursor" +msgstr "Паказаць/Прыбраць курсор" + +#: backends/platform/wince/CEActionsPocket.cpp:51 +msgid "Free look" +msgstr "Вольны агляд" + +#: backends/platform/wince/CEActionsPocket.cpp:52 +msgid "Zoom up" +msgstr "Павял. маштаб" + +#: backends/platform/wince/CEActionsPocket.cpp:53 +msgid "Zoom down" +msgstr "Паменш. маштаб" + +#: backends/platform/wince/CEActionsPocket.cpp:55 +#: backends/platform/wince/CEActionsSmartphone.cpp:49 +msgid "Bind Keys" +msgstr "Прызначыць клавішы" + +#: backends/platform/wince/CEActionsPocket.cpp:56 +msgid "Cursor Up" +msgstr "Курсор уверх" + +#: backends/platform/wince/CEActionsPocket.cpp:57 +msgid "Cursor Down" +msgstr "Курсор уніз" + +#: backends/platform/wince/CEActionsPocket.cpp:58 +msgid "Cursor Left" +msgstr "Курсор налева" + +#: backends/platform/wince/CEActionsPocket.cpp:59 +msgid "Cursor Right" +msgstr "Курсор направа" + +#: backends/platform/wince/CEActionsPocket.cpp:267 +#: backends/platform/wince/CEActionsSmartphone.cpp:231 +msgid "Do you want to load or save the game?" +msgstr "Вы жадаеце загрузіць або захаваць гульню?" + +#: backends/platform/wince/CEActionsPocket.cpp:326 +#: backends/platform/wince/CEActionsSmartphone.cpp:287 +msgid " Are you sure you want to quit ? " +msgstr " Вы ўпэўнены, што жадаеце выйсці? " + +#: backends/platform/wince/CEActionsSmartphone.cpp:50 +msgid "Keyboard" +msgstr "Клавіятура" + +#: backends/platform/wince/CEActionsSmartphone.cpp:51 +msgid "Rotate" +msgstr "Павярнуць" + +#: backends/platform/wince/CELauncherDialog.cpp:56 +msgid "Using SDL driver " +msgstr "Выкарыстоўваю драйвер SDL " + +#: backends/platform/wince/CELauncherDialog.cpp:60 +msgid "Display " +msgstr "Паказаць " + +#: backends/platform/wince/CELauncherDialog.cpp:83 +msgid "Do you want to perform an automatic scan ?" +msgstr "Вы жадаеце зрабіць аўтаматычны пошук?" + +#: backends/platform/wince/wince-sdl.cpp:515 +msgid "Map right click action" +msgstr "Прызначыць дзеянне па правай пстрычцы" + +#: backends/platform/wince/wince-sdl.cpp:519 +msgid "You must map a key to the 'Right Click' action to play this game" +msgstr "" +"Вы павінны прызначыць клавішу на дзеянне 'Right Click' для гэтай гульні" + +#: backends/platform/wince/wince-sdl.cpp:528 +msgid "Map hide toolbar action" +msgstr "Прызначыць дзеянне 'схаваць панэль інструментаў'" + +#: backends/platform/wince/wince-sdl.cpp:532 +msgid "You must map a key to the 'Hide toolbar' action to play this game" +msgstr "" +"Вы павінны прызначыць клавішу на дзеянне 'Hide toolbar' для гэтай гульні" + +#: backends/platform/wince/wince-sdl.cpp:541 +msgid "Map Zoom Up action (optional)" +msgstr "Прызначыць дзеянне Павялічыць Маштаб (неабавязкова)" + +#: backends/platform/wince/wince-sdl.cpp:544 +msgid "Map Zoom Down action (optional)" +msgstr "Прызначыць дзеянне Паменшыць Маштаб (неабавязкова)" + +#: backends/platform/wince/wince-sdl.cpp:552 +msgid "" +"Don't forget to map a key to 'Hide Toolbar' action to see the whole inventory" +msgstr "" +"Не забудзьцеся прызначыць клавішу для дзеяння 'Hide Toolbar', каб убачыць " +"увесь інвентар у гульні" + +#: backends/events/default/default-events.cpp:191 +msgid "Do you really want to return to the Launcher?" +msgstr "Вы сапраўды жадаеце вярнуцца ў галоўнае меню?" + +#: backends/events/default/default-events.cpp:191 +msgid "Launcher" +msgstr "Галоўнае меню" + +#: backends/events/default/default-events.cpp:213 +msgid "Do you really want to quit?" +msgstr "Вы сапраўды жадаеце выйсці?" + +#: backends/events/gph/gph-events.cpp:386 +#: backends/events/gph/gph-events.cpp:429 +#: backends/events/openpandora/op-events.cpp:139 +msgid "Touchscreen 'Tap Mode' - Left Click" +msgstr "Рэжым 'дотыкаў' тачскрына - Левы клік" + +#: backends/events/gph/gph-events.cpp:388 +#: backends/events/gph/gph-events.cpp:431 +#: backends/events/openpandora/op-events.cpp:141 +msgid "Touchscreen 'Tap Mode' - Right Click" +msgstr "Рэжым 'дотыкаў' тачскрына - Правы клік" + +#: backends/events/gph/gph-events.cpp:390 +#: backends/events/gph/gph-events.cpp:433 +#: backends/events/openpandora/op-events.cpp:143 +msgid "Touchscreen 'Tap Mode' - Hover (No Click)" +msgstr "Рэжым 'дотыкаў' тачскрына - Пралёт (без кліку)" + +#: backends/events/gph/gph-events.cpp:410 +msgid "Maximum Volume" +msgstr "Максімальная гучнасць" + +#: backends/events/gph/gph-events.cpp:412 +msgid "Increasing Volume" +msgstr "Павелічэнне гучнасці" + +#: backends/events/gph/gph-events.cpp:418 +msgid "Minimal Volume" +msgstr "Мінімальная гучнасць" + +#: backends/events/gph/gph-events.cpp:420 +msgid "Decreasing Volume" +msgstr "Памяншэнне гучнасці" + +#: backends/updates/macosx/macosx-updates.mm:65 +msgid "Check for Updates..." +msgstr "Правяраю абнаўленні..." + +#: backends/platform/bada/form.cpp:269 +msgid "Right Click Once" +msgstr "Адна правая пстрычка" + +#: backends/platform/bada/form.cpp:277 +msgid "Move Only" +msgstr "Толькі перамясціць" + +#: backends/platform/bada/form.cpp:291 +msgid "Escape Key" +msgstr "Клавіша ESC" + +#: backends/platform/bada/form.cpp:296 +msgid "Game Menu" +msgstr "Меню гульні" + +#: backends/platform/bada/form.cpp:301 +msgid "Show Keypad" +msgstr "Паказаць клавіятуру" + +#: backends/platform/bada/form.cpp:309 +msgid "Control Mouse" +msgstr "Кіраванне мышшу" + +#: backends/events/maemosdl/maemosdl-events.cpp:192 +msgid "Clicking Enabled" +msgstr "Пстрычкі ўключаны" + +#: backends/events/maemosdl/maemosdl-events.cpp:192 +msgid "Clicking Disabled" +msgstr "Пстрычкі выключаны" + +#~ msgid "Hercules Green" +#~ msgstr "Hercules Зелёный" + +#~ msgid "Hercules Amber" +#~ msgstr "Hercules Янтарный" + +#~ msgctxt "lowres" +#~ msgid "Hercules Green" +#~ msgstr "Hercules Зелёный" + +#~ msgctxt "lowres" +#~ msgid "Hercules Amber" +#~ msgstr "Hercules Янтарный" + +#~ msgid "Save game failed!" +#~ msgstr "Не удалось сохранить игру!" + +#~ msgctxt "lowres" +#~ msgid "Add Game..." +#~ msgstr "Доб. игру" + +#~ msgid "Add Game..." +#~ msgstr "Добавить игру..." + +#~ msgid "Discovered %d new games." +#~ msgstr "Найдено %d новых игр." + +#~ msgid "Command line argument not processed" +#~ msgstr "Параметры командной строки не обработаны" + +#~ msgid "FM Towns Emulator" +#~ msgstr "Эмулятор FM Towns" + +#~ msgid "Invalid Path" +#~ msgstr "Неверный путь" diff --git a/test/common/bitstream.h b/test/common/bitstream.h new file mode 100644 index 0000000000..3f6a15fcd8 --- /dev/null +++ b/test/common/bitstream.h @@ -0,0 +1,152 @@ +#include <cxxtest/TestSuite.h> + +#include "common/bitstream.h" +#include "common/memstream.h" + +class BitStreamTestSuite : public CxxTest::TestSuite +{ + public: + void test_get_bit() { + byte contents[] = { 'a' }; + + Common::MemoryReadStream ms(contents, sizeof(contents)); + + Common::BitStream8MSB bs(ms); + TS_ASSERT_EQUALS(bs.pos(), 0u); + TS_ASSERT_EQUALS(bs.getBit(), 0u); + TS_ASSERT_EQUALS(bs.getBit(), 1u); + TS_ASSERT_EQUALS(bs.getBit(), 1u); + TS_ASSERT_EQUALS(bs.pos(), 3u); + TS_ASSERT(!bs.eos()); + } + + void test_get_bits() { + byte contents[] = { 'a', 'b' }; + + Common::MemoryReadStream ms(contents, sizeof(contents)); + + Common::BitStream8MSB bs(ms); + TS_ASSERT_EQUALS(bs.pos(), 0u); + TS_ASSERT_EQUALS(bs.getBits(3), 3u); + TS_ASSERT_EQUALS(bs.pos(), 3u); + TS_ASSERT_EQUALS(bs.getBits(8), 11u); + TS_ASSERT_EQUALS(bs.pos(), 11u); + TS_ASSERT(!bs.eos()); + } + + void test_skip() { + byte contents[] = { 'a', 'b' }; + + Common::MemoryReadStream ms(contents, sizeof(contents)); + + Common::BitStream8MSB bs(ms); + TS_ASSERT_EQUALS(bs.pos(), 0u); + bs.skip(5); + TS_ASSERT_EQUALS(bs.pos(), 5u); + bs.skip(4); + TS_ASSERT_EQUALS(bs.pos(), 9u); + TS_ASSERT_EQUALS(bs.getBits(3), 6u); + TS_ASSERT(!bs.eos()); + } + + void test_rewind() { + byte contents[] = { 'a' }; + + Common::MemoryReadStream ms(contents, sizeof(contents)); + + Common::BitStream8MSB bs(ms); + TS_ASSERT_EQUALS(bs.pos(), 0u); + bs.skip(5); + TS_ASSERT_EQUALS(bs.pos(), 5u); + bs.rewind(); + TS_ASSERT_EQUALS(bs.pos(), 0u); + TS_ASSERT_EQUALS(bs.getBits(3), 3u); + TS_ASSERT(!bs.eos()); + + TS_ASSERT_EQUALS(bs.size(), 8u); + } + + void test_peek_bit() { + byte contents[] = { 'a' }; + + Common::MemoryReadStream ms(contents, sizeof(contents)); + + Common::BitStream8MSB bs(ms); + TS_ASSERT_EQUALS(bs.pos(), 0u); + TS_ASSERT_EQUALS(bs.peekBit(), 0u); + TS_ASSERT_EQUALS(bs.pos(), 0u); + TS_ASSERT_EQUALS(bs.getBit(), 0u); + TS_ASSERT_EQUALS(bs.pos(), 1u); + TS_ASSERT_EQUALS(bs.peekBit(), 1u); + TS_ASSERT_EQUALS(bs.pos(), 1u); + TS_ASSERT(!bs.eos()); + } + + void test_peek_bits() { + byte contents[] = { 'a', 'b' }; + + Common::MemoryReadStream ms(contents, sizeof(contents)); + + Common::BitStream8MSB bs(ms); + TS_ASSERT_EQUALS(bs.pos(), 0u); + TS_ASSERT_EQUALS(bs.peekBits(3), 3u); + TS_ASSERT_EQUALS(bs.pos(), 0u); + bs.skip(3); + TS_ASSERT_EQUALS(bs.pos(), 3u); + TS_ASSERT_EQUALS(bs.peekBits(8), 11u); + TS_ASSERT_EQUALS(bs.pos(), 3u); + bs.skip(8); + TS_ASSERT_EQUALS(bs.pos(), 11u); + TS_ASSERT_EQUALS(bs.peekBits(5), 2u); + TS_ASSERT(!bs.eos()); + } + + void test_eos() { + byte contents[] = { 'a', 'b' }; + + Common::MemoryReadStream ms(contents, sizeof(contents)); + + Common::BitStream8MSB bs(ms); + bs.skip(11); + TS_ASSERT_EQUALS(bs.pos(), 11u); + TS_ASSERT_EQUALS(bs.getBits(5), 2u); + TS_ASSERT(bs.eos()); + + bs.rewind(); + TS_ASSERT_EQUALS(bs.pos(), 0u); + TS_ASSERT(!bs.eos()); + } + + void test_get_bits_lsb() { + byte contents[] = { 'a', 'b' }; + + Common::MemoryReadStream ms(contents, sizeof(contents)); + + Common::BitStream8LSB bs(ms); + TS_ASSERT_EQUALS(bs.pos(), 0u); + TS_ASSERT_EQUALS(bs.getBits(3), 1u); + TS_ASSERT_EQUALS(bs.pos(), 3u); + TS_ASSERT_EQUALS(bs.getBits(8), 76u); + TS_ASSERT_EQUALS(bs.pos(), 11u); + TS_ASSERT(!bs.eos()); + } + + void test_peek_bits_lsb() { + byte contents[] = { 'a', 'b' }; + + Common::MemoryReadStream ms(contents, sizeof(contents)); + + Common::BitStream8LSB bs(ms); + TS_ASSERT_EQUALS(bs.pos(), 0u); + TS_ASSERT_EQUALS(bs.peekBits(3), 1u); + TS_ASSERT_EQUALS(bs.pos(), 0u); + bs.skip(3); + TS_ASSERT_EQUALS(bs.pos(), 3u); + TS_ASSERT_EQUALS(bs.peekBits(8), 76u); + TS_ASSERT_EQUALS(bs.pos(), 3u); + bs.skip(8); + TS_ASSERT_EQUALS(bs.pos(), 11u); + TS_ASSERT_EQUALS(bs.peekBits(5), 12u); + TS_ASSERT(!bs.eos()); + } +}; diff --git a/video/qt_decoder.cpp b/video/qt_decoder.cpp index c5fcab4b49..9e26e96c7b 100644 --- a/video/qt_decoder.cpp +++ b/video/qt_decoder.cpp @@ -110,7 +110,7 @@ const Graphics::Surface *QuickTimeDecoder::decodeNextFrame() { return frame; } -Common::QuickTimeParser::SampleDesc *QuickTimeDecoder::readSampleDesc(Common::QuickTimeParser::Track *track, uint32 format) { +Common::QuickTimeParser::SampleDesc *QuickTimeDecoder::readSampleDesc(Common::QuickTimeParser::Track *track, uint32 format, uint32 descSize) { if (track->codecType == CODEC_TYPE_VIDEO) { debug(0, "Video Codec FourCC: \'%s\'", tag2str(format)); @@ -205,7 +205,7 @@ Common::QuickTimeParser::SampleDesc *QuickTimeDecoder::readSampleDesc(Common::Qu } // Pass it on up - return Audio::QuickTimeAudioDecoder::readSampleDesc(track, format); + return Audio::QuickTimeAudioDecoder::readSampleDesc(track, format, descSize); } void QuickTimeDecoder::init() { @@ -333,6 +333,7 @@ QuickTimeDecoder::VideoTrackHandler::VideoTrackHandler(QuickTimeDecoder *decoder _scaledSurface = 0; _curPalette = 0; _dirtyPalette = false; + _reversed = false; } QuickTimeDecoder::VideoTrackHandler::~VideoTrackHandler() { @@ -344,26 +345,26 @@ QuickTimeDecoder::VideoTrackHandler::~VideoTrackHandler() { bool QuickTimeDecoder::VideoTrackHandler::endOfTrack() const { // A track is over when we've finished going through all edits - return _curEdit == _parent->editCount; + return _reversed ? (_curEdit == 0 && _curFrame < 0) : atLastEdit(); } bool QuickTimeDecoder::VideoTrackHandler::seek(const Audio::Timestamp &requestedTime) { uint32 convertedFrames = requestedTime.convertToFramerate(_decoder->_timeScale).totalNumberOfFrames(); - for (_curEdit = 0; !endOfTrack(); _curEdit++) + for (_curEdit = 0; !atLastEdit(); _curEdit++) if (convertedFrames >= _parent->editList[_curEdit].timeOffset && convertedFrames < _parent->editList[_curEdit].timeOffset + _parent->editList[_curEdit].trackDuration) break; // If we did reach the end of the track, break out - if (endOfTrack()) + if (atLastEdit()) return true; // If this track is in an empty edit, position us at the next non-empty // edit. There's nothing else to do after this. if (_parent->editList[_curEdit].mediaTime == -1) { - while (!endOfTrack() && _parent->editList[_curEdit].mediaTime == -1) + while (!atLastEdit() && _parent->editList[_curEdit].mediaTime == -1) _curEdit++; - if (!endOfTrack()) + if (!atLastEdit()) enterNewEditList(true); return true; @@ -372,7 +373,7 @@ bool QuickTimeDecoder::VideoTrackHandler::seek(const Audio::Timestamp &requested enterNewEditList(false); // One extra check for the end of a track - if (endOfTrack()) + if (atLastEdit()) return true; // Now we're in the edit and need to figure out what frame we need @@ -391,17 +392,24 @@ bool QuickTimeDecoder::VideoTrackHandler::seek(const Audio::Timestamp &requested // Compare the starting point for the frame to where we need to be _holdNextFrameStartTime = getRateAdjustedFrameTime() != (uint32)time.totalNumberOfFrames(); - // If we went past the time, go back a frame + // If we went past the time, go back a frame. _curFrame before this point is at the frame + // that should be displayed. This adjustment ensures it is on the frame before the one that + // should be displayed. if (_holdNextFrameStartTime) _curFrame--; - // Handle the keyframe here - int32 destinationFrame = _curFrame + 1; + if (_reversed) { + // Call setReverse again to update + setReverse(true); + } else { + // Handle the keyframe here + int32 destinationFrame = _curFrame + 1; - assert(destinationFrame < (int32)_parent->frameCount); - _curFrame = findKeyFrame(destinationFrame) - 1; - while (_curFrame < destinationFrame - 1) - bufferNextFrame(); + assert(destinationFrame < (int32)_parent->frameCount); + _curFrame = findKeyFrame(destinationFrame) - 1; + while (_curFrame < destinationFrame - 1) + bufferNextFrame(); + } return true; } @@ -438,27 +446,54 @@ const Graphics::Surface *QuickTimeDecoder::VideoTrackHandler::decodeNextFrame() if (endOfTrack()) return 0; + if (_reversed) { + // Subtract one to place us on the frame before the current displayed frame. + _curFrame--; + + // We have one "dummy" frame at the end to so the last frame is displayed + // for the right amount of time. + if (_curFrame < 0) + return 0; + + // Decode from the last key frame to the frame before the one we need. + // TODO: Probably would be wise to do some caching + int targetFrame = _curFrame; + _curFrame = findKeyFrame(targetFrame) - 1; + while (_curFrame != targetFrame - 1) + bufferNextFrame(); + } + const Graphics::Surface *frame = bufferNextFrame(); - if (_holdNextFrameStartTime) { - // Don't set the next frame start time here; we just did a seek - _holdNextFrameStartTime = false; - } else if (_durationOverride >= 0) { - // Use our own duration from the edit list calculation - _nextFrameStartTime += _durationOverride; - _durationOverride = -1; + if (_reversed) { + if (_holdNextFrameStartTime) { + // Don't set the next frame start time here; we just did a seek + _holdNextFrameStartTime = false; + } else { + // Just need to subtract the time + _nextFrameStartTime -= getFrameDuration(); + } } else { - _nextFrameStartTime += getFrameDuration(); - } + if (_holdNextFrameStartTime) { + // Don't set the next frame start time here; we just did a seek + _holdNextFrameStartTime = false; + } else if (_durationOverride >= 0) { + // Use our own duration from the edit list calculation + _nextFrameStartTime += _durationOverride; + _durationOverride = -1; + } else { + _nextFrameStartTime += getFrameDuration(); + } - // Update the edit list, if applicable - // HACK: We're also accepting the time minus one because edit lists - // aren't as accurate as one would hope. - if (!endOfTrack() && getRateAdjustedFrameTime() >= getCurEditTimeOffset() + getCurEditTrackDuration() - 1) { - _curEdit++; + // Update the edit list, if applicable + // HACK: We're also accepting the time minus one because edit lists + // aren't as accurate as one would hope. + if (!atLastEdit() && getRateAdjustedFrameTime() >= getCurEditTimeOffset() + getCurEditTrackDuration() - 1) { + _curEdit++; - if (!endOfTrack()) - enterNewEditList(true); + if (!atLastEdit()) + enterNewEditList(true); + } } if (frame && (_parent->scaleFactorX != 1 || _parent->scaleFactorY != 1)) { @@ -474,6 +509,68 @@ const Graphics::Surface *QuickTimeDecoder::VideoTrackHandler::decodeNextFrame() return frame; } +bool QuickTimeDecoder::VideoTrackHandler::setReverse(bool reverse) { + _reversed = reverse; + + if (_reversed) { + if (_parent->editCount != 1) { + // TODO: Myst's holo.mov needs this :( + warning("Can only set reverse without edits"); + return false; + } + + if (atLastEdit()) { + // If we're at the end of the video, go to the penultimate edit. + // The current frame is set to one beyond the last frame here; + // one "past" the currently displayed frame. + _curEdit = _parent->editCount - 1; + _curFrame = _parent->frameCount; + _nextFrameStartTime = _parent->editList[_curEdit].trackDuration + _parent->editList[_curEdit].timeOffset; + } else if (_holdNextFrameStartTime) { + // We just seeked, so "pivot" around the frame that should be displayed + _curFrame++; + _nextFrameStartTime -= getFrameDuration(); + _curFrame++; + } else { + // We need to put _curFrame to be the one after the one that should be displayed. + // Since we're on the frame that should be displaying right now, add one. + _curFrame++; + } + } else { + // Update the edit list, if applicable + // HACK: We're also accepting the time minus one because edit lists + // aren't as accurate as one would hope. + if (!atLastEdit() && getRateAdjustedFrameTime() >= getCurEditTimeOffset() + getCurEditTrackDuration() - 1) { + _curEdit++; + + if (atLastEdit()) + return true; + } + + if (_holdNextFrameStartTime) { + // We just seeked, so "pivot" around the frame that should be displayed + _curFrame--; + _nextFrameStartTime += getFrameDuration(); + } + + // We need to put _curFrame to be the one before the one that should be displayed. + // Since we're on the frame that should be displaying right now, subtract one. + // (As long as the current frame isn't -1, of course) + if (_curFrame > 0) { + // We then need to handle the keyframe situation + int targetFrame = _curFrame - 1; + _curFrame = findKeyFrame(targetFrame) - 1; + while (_curFrame < targetFrame) + bufferNextFrame(); + } else if (_curFrame == 0) { + // Make us start at the first frame (no keyframe needed) + _curFrame--; + } + } + + return true; +} + Common::Rational QuickTimeDecoder::VideoTrackHandler::getScaledWidth() const { return Common::Rational(_parent->width) / _parent->scaleFactorX; } @@ -555,10 +652,10 @@ uint32 QuickTimeDecoder::VideoTrackHandler::findKeyFrame(uint32 frame) const { void QuickTimeDecoder::VideoTrackHandler::enterNewEditList(bool bufferFrames) { // Bypass all empty edit lists first - while (!endOfTrack() && _parent->editList[_curEdit].mediaTime == -1) + while (!atLastEdit() && _parent->editList[_curEdit].mediaTime == -1) _curEdit++; - if (endOfTrack()) + if (atLastEdit()) return; uint32 frameNum = 0; @@ -675,4 +772,8 @@ uint32 QuickTimeDecoder::VideoTrackHandler::getCurEditTrackDuration() const { return _parent->editList[_curEdit].trackDuration * _parent->timeScale / _decoder->_timeScale; } +bool QuickTimeDecoder::VideoTrackHandler::atLastEdit() const { + return _curEdit == _parent->editCount; +} + } // End of namespace Video diff --git a/video/qt_decoder.h b/video/qt_decoder.h index 45ab155c2c..28314f2e63 100644 --- a/video/qt_decoder.h +++ b/video/qt_decoder.h @@ -70,7 +70,7 @@ public: Audio::Timestamp getDuration() const { return Audio::Timestamp(0, _duration, _timeScale); } protected: - Common::QuickTimeParser::SampleDesc *readSampleDesc(Common::QuickTimeParser::Track *track, uint32 format); + Common::QuickTimeParser::SampleDesc *readSampleDesc(Common::QuickTimeParser::Track *track, uint32 format, uint32 descSize); private: void init(); @@ -136,6 +136,8 @@ private: const Graphics::Surface *decodeNextFrame(); const byte *getPalette() const { _dirtyPalette = false; return _curPalette; } bool hasDirtyPalette() const { return _curPalette; } + bool setReverse(bool reverse); + bool isReversed() const { return _reversed; } Common::Rational getScaledWidth() const; Common::Rational getScaledHeight() const; @@ -151,6 +153,7 @@ private: int32 _durationOverride; const byte *_curPalette; mutable bool _dirtyPalette; + bool _reversed; Common::SeekableReadStream *getNextFramePacket(uint32 &descId); uint32 getFrameDuration(); @@ -160,6 +163,7 @@ private: uint32 getRateAdjustedFrameTime() const; uint32 getCurEditTimeOffset() const; uint32 getCurEditTrackDuration() const; + bool atLastEdit() const; }; }; diff --git a/video/video_decoder.cpp b/video/video_decoder.cpp index 4d9f19cfd9..ebe15c5fc1 100644 --- a/video/video_decoder.cpp +++ b/video/video_decoder.cpp @@ -188,6 +188,25 @@ const Graphics::Surface *VideoDecoder::decodeNextFrame() { return frame; } +bool VideoDecoder::setReverse(bool reverse) { + // Can only reverse video-only videos + if (reverse && hasAudio()) + return false; + + // Attempt to make sure all the tracks are in the requested direction + for (TrackList::iterator it = _tracks.begin(); it != _tracks.end(); it++) { + if ((*it)->getTrackType() == Track::kTrackTypeVideo && ((VideoTrack *)*it)->isReversed() != reverse) { + if (!((VideoTrack *)*it)->setReverse(reverse)) + return false; + + _needsUpdate = true; // force an update + } + } + + findNextVideoTrack(); + return true; +} + const byte *VideoDecoder::getPalette() { _dirtyPalette = false; return _palette; @@ -218,7 +237,7 @@ uint32 VideoDecoder::getTime() const { return _lastTimeChange.msecs(); if (isPaused()) - return (_playbackRate * (_pauseStartTime - _startTime)).toInt(); + return MAX<int>((_playbackRate * (_pauseStartTime - _startTime)).toInt(), 0); if (useAudioSync()) { for (TrackList::const_iterator it = _tracks.begin(); it != _tracks.end(); it++) { @@ -231,20 +250,29 @@ uint32 VideoDecoder::getTime() const { } } - return (_playbackRate * (g_system->getMillis() - _startTime)).toInt(); + return MAX<int>((_playbackRate * (g_system->getMillis() - _startTime)).toInt(), 0); } uint32 VideoDecoder::getTimeToNextFrame() const { if (endOfVideo() || _needsUpdate || !_nextVideoTrack) return 0; - uint32 elapsedTime = getTime(); + uint32 currentTime = getTime(); uint32 nextFrameStartTime = _nextVideoTrack->getNextFrameStartTime(); - if (nextFrameStartTime <= elapsedTime) + if (_nextVideoTrack->isReversed()) { + // For reversed videos, we need to handle the time difference the opposite way. + if (nextFrameStartTime >= currentTime) + return 0; + + return currentTime - nextFrameStartTime; + } + + // Otherwise, handle it normally. + if (nextFrameStartTime <= currentTime) return 0; - return nextFrameStartTime - elapsedTime; + return nextFrameStartTime - currentTime; } bool VideoDecoder::endOfVideo() const { @@ -318,7 +346,7 @@ bool VideoDecoder::seek(const Audio::Timestamp &time) { // Also reset our start time if (isPlaying()) { startAudio(); - _startTime = g_system->getMillis() - time.msecs(); + _startTime = g_system->getMillis() - (time.msecs() / _playbackRate).toInt(); } resetPauseStartTime(); @@ -402,9 +430,11 @@ void VideoDecoder::setRate(const Common::Rational &rate) { Common::Rational targetRate = rate; - if (rate < 0) { - // TODO: Implement support for this + // Attempt to set the reverse + if (!setReverse(rate < 0)) { + assert(rate < 0); // We shouldn't fail for forward. warning("Cannot set custom rate to backwards"); + setReverse(false); targetRate = 1; if (_playbackRate == targetRate) diff --git a/video/video_decoder.h b/video/video_decoder.h index 5fec52cf42..d0a6e08005 100644 --- a/video/video_decoder.h +++ b/video/video_decoder.h @@ -353,6 +353,17 @@ public: */ void setDefaultHighColorFormat(const Graphics::PixelFormat &format) { _defaultHighColorFormat = format; } + /** + * Set the video to decode frames in reverse. + * + * By default, VideoDecoder will decode forward. + * + * @note This is used by setRate() + * @note This will not work if an audio track is present + * @param reverse true for reverse, false for forward + * @return true on success, false otherwise + */ + bool setReverse(bool reverse); ///////////////////////////////////////// // Audio Control @@ -551,6 +562,21 @@ protected: * should only be used by VideoDecoder::seekToFrame(). */ virtual Audio::Timestamp getFrameTime(uint frame) const; + + /** + * Set the video track to play in reverse or forward. + * + * By default, a VideoTrack must decode forward. + * + * @param reverse true for reverse, false for forward + * @return true for success, false for failure + */ + virtual bool setReverse(bool reverse) { return !reverse; } + + /** + * Is the video track set to play in reverse? + */ + virtual bool isReversed() const { return false; } }; /** |