diff options
Diffstat (limited to 'engines/zvision')
109 files changed, 13553 insertions, 4725 deletions
diff --git a/engines/zvision/animation/rlf_animation.cpp b/engines/zvision/animation/rlf_animation.cpp deleted file mode 100644 index 945461e7b2..0000000000 --- a/engines/zvision/animation/rlf_animation.cpp +++ /dev/null @@ -1,331 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#include "common/scummsys.h" - -#include "zvision/animation/rlf_animation.h" - -#include "common/str.h" -#include "common/file.h" -#include "common/textconsole.h" -#include "common/debug.h" -#include "common/endian.h" - -#include "graphics/colormasks.h" - - -namespace ZVision { - -RlfAnimation::RlfAnimation(const Common::String &fileName, bool stream) - : _stream(stream), - _lastFrameRead(0), - _frameCount(0), - _width(0), - _height(0), - _frameTime(0), - _frames(0), - _currentFrame(-1), - _frameBufferByteSize(0) { - if (!_file.open(fileName)) { - warning("RLF animation file %s could not be opened", fileName.c_str()); - return; - } - - if (!readHeader()) { - warning("%s is not a RLF animation file. Wrong magic number", fileName.c_str()); - return; - } - - _currentFrameBuffer.create(_width, _height, Graphics::createPixelFormat<565>()); - _frameBufferByteSize = _width * _height * sizeof(uint16); - - if (!stream) { - _frames = new Frame[_frameCount]; - - // Read in each frame - for (uint i = 0; i < _frameCount; ++i) { - _frames[i] = readNextFrame(); - } - } -} - -RlfAnimation::~RlfAnimation() { - for (uint i = 0; i < _frameCount; ++i) { - delete[] _frames[i].encodedData; - } - delete[] _frames; - _currentFrameBuffer.free(); -} - -bool RlfAnimation::readHeader() { - if (_file.readUint32BE() != MKTAG('F', 'E', 'L', 'R')) { - return false; - } - - // Read the header - _file.readUint32LE(); // Size1 - _file.readUint32LE(); // Unknown1 - _file.readUint32LE(); // Unknown2 - _frameCount = _file.readUint32LE(); // Frame count - - // Since we don't need any of the data, we can just seek right to the - // entries we need rather than read in all the individual entries. - _file.seek(136, SEEK_CUR); - - //// Read CIN header - //_file.readUint32BE(); // Magic number FNIC - //_file.readUint32LE(); // Size2 - //_file.readUint32LE(); // Unknown3 - //_file.readUint32LE(); // Unknown4 - //_file.readUint32LE(); // Unknown5 - //_file.seek(0x18, SEEK_CUR); // VRLE - //_file.readUint32LE(); // LRVD - //_file.readUint32LE(); // Unknown6 - //_file.seek(0x18, SEEK_CUR); // HRLE - //_file.readUint32LE(); // ELHD - //_file.readUint32LE(); // Unknown7 - //_file.seek(0x18, SEEK_CUR); // HKEY - //_file.readUint32LE(); // ELRH - - //// Read MIN info header - //_file.readUint32BE(); // Magic number FNIM - //_file.readUint32LE(); // Size3 - //_file.readUint32LE(); // OEDV - //_file.readUint32LE(); // Unknown8 - //_file.readUint32LE(); // Unknown9 - //_file.readUint32LE(); // Unknown10 - _width = _file.readUint32LE(); // Width - _height = _file.readUint32LE(); // Height - - // Read time header - _file.readUint32BE(); // Magic number EMIT - _file.readUint32LE(); // Size4 - _file.readUint32LE(); // Unknown11 - _frameTime = _file.readUint32LE() / 10; // Frame time in microseconds - - return true; -} - -RlfAnimation::Frame RlfAnimation::readNextFrame() { - RlfAnimation::Frame frame; - - _file.readUint32BE(); // Magic number MARF - uint32 size = _file.readUint32LE(); // Size - _file.readUint32LE(); // Unknown1 - _file.readUint32LE(); // Unknown2 - uint32 type = _file.readUint32BE(); // Either ELHD or ELRH - uint32 headerSize = _file.readUint32LE(); // Offset from the beginning of this frame to the frame data. Should always be 28 - _file.readUint32LE(); // Unknown3 - - frame.encodedSize = size - headerSize; - frame.encodedData = new int8[frame.encodedSize]; - _file.read(frame.encodedData, frame.encodedSize); - - if (type == MKTAG('E', 'L', 'H', 'D')) { - frame.type = Masked; - } else if (type == MKTAG('E', 'L', 'R', 'H')) { - frame.type = Simple; - _completeFrames.push_back(_lastFrameRead); - } else { - warning("Frame %u doesn't have type that can be decoded", _lastFrameRead); - } - - _lastFrameRead++; - return frame; -} - -void RlfAnimation::seekToFrame(int frameNumber) { - assert(!_stream); - assert(frameNumber < (int)_frameCount || frameNumber >= -1); - - if (frameNumber == -1) { - _currentFrame = -1; - return; - } - - int closestFrame = _currentFrame; - int distance = (int)frameNumber - _currentFrame; - for (uint i = 0; i < _completeFrames.size(); ++i) { - int newDistance = (int)frameNumber - (int)(_completeFrames[i]); - if (newDistance > 0 && (closestFrame == -1 || newDistance < distance)) { - closestFrame = _completeFrames[i]; - distance = newDistance; - } - } - - for (int i = closestFrame; i <= frameNumber; ++i) { - applyFrameToCurrent(i); - } - - _currentFrame = frameNumber; -} - -const Graphics::Surface *RlfAnimation::getFrameData(uint frameNumber) { - assert(!_stream); - assert(frameNumber < _frameCount); - - // Since this method is so expensive, first check to see if we can use - // getNextFrame() it's cheap. - if ((int)frameNumber == _currentFrame) { - return &_currentFrameBuffer; - } else if (_currentFrame + 1 == (int)frameNumber) { - return getNextFrame(); - } - - seekToFrame(frameNumber); - return &_currentFrameBuffer; -} - -const Graphics::Surface *RlfAnimation::getNextFrame() { - assert(_currentFrame + 1 < (int)_frameCount); - - if (_stream) { - applyFrameToCurrent(readNextFrame()); - } else { - applyFrameToCurrent(_currentFrame + 1); - } - - _currentFrame++; - return &_currentFrameBuffer; -} - -void RlfAnimation::applyFrameToCurrent(uint frameNumber) { - if (_frames[frameNumber].type == Masked) { - decodeMaskedRunLengthEncoding(_frames[frameNumber].encodedData, (int8 *)_currentFrameBuffer.getPixels(), _frames[frameNumber].encodedSize, _frameBufferByteSize); - } else if (_frames[frameNumber].type == Simple) { - decodeSimpleRunLengthEncoding(_frames[frameNumber].encodedData, (int8 *)_currentFrameBuffer.getPixels(), _frames[frameNumber].encodedSize, _frameBufferByteSize); - } -} - -void RlfAnimation::applyFrameToCurrent(const RlfAnimation::Frame &frame) { - if (frame.type == Masked) { - decodeMaskedRunLengthEncoding(frame.encodedData, (int8 *)_currentFrameBuffer.getPixels(), frame.encodedSize, _frameBufferByteSize); - } else if (frame.type == Simple) { - decodeSimpleRunLengthEncoding(frame.encodedData, (int8 *)_currentFrameBuffer.getPixels(), frame.encodedSize, _frameBufferByteSize); - } -} - -void RlfAnimation::decodeMaskedRunLengthEncoding(int8 *source, int8 *dest, uint32 sourceSize, uint32 destSize) const { - uint32 sourceOffset = 0; - uint32 destOffset = 0; - - while (sourceOffset < sourceSize) { - int8 numberOfSamples = source[sourceOffset]; - sourceOffset++; - - // If numberOfSamples is negative, the next abs(numberOfSamples) samples should - // be copied directly from source to dest - if (numberOfSamples < 0) { - numberOfSamples = ABS(numberOfSamples); - - while (numberOfSamples > 0) { - if (sourceOffset + 1 >= sourceSize) { - return; - } else if (destOffset + 1 >= destSize) { - debug(2, "Frame decoding overflow\n\tsourceOffset=%u\tsourceSize=%u\n\tdestOffset=%u\tdestSize=%u", sourceOffset, sourceSize, destOffset, destSize); - return; - } - - byte r, g, b; - Graphics::colorToRGB<Graphics::ColorMasks<555> >(READ_LE_UINT16(source + sourceOffset), r, g, b); - uint16 destColor = Graphics::RGBToColor<Graphics::ColorMasks<565> >(r, g, b); - WRITE_UINT16(dest + destOffset, destColor); - - sourceOffset += 2; - destOffset += 2; - numberOfSamples--; - } - - // If numberOfSamples is >= 0, move destOffset forward ((numberOfSamples * 2) + 2) - // This function assumes the dest buffer has been memset with 0's. - } else { - if (sourceOffset + 1 >= sourceSize) { - return; - } else if (destOffset + 1 >= destSize) { - debug(2, "Frame decoding overflow\n\tsourceOffset=%u\tsourceSize=%u\n\tdestOffset=%u\tdestSize=%u", sourceOffset, sourceSize, destOffset, destSize); - return; - } - - destOffset += (numberOfSamples * 2) + 2; - } - } -} - -void RlfAnimation::decodeSimpleRunLengthEncoding(int8 *source, int8 *dest, uint32 sourceSize, uint32 destSize) const { - uint32 sourceOffset = 0; - uint32 destOffset = 0; - - while (sourceOffset < sourceSize) { - int8 numberOfSamples = source[sourceOffset]; - sourceOffset++; - - // If numberOfSamples is negative, the next abs(numberOfSamples) samples should - // be copied directly from source to dest - if (numberOfSamples < 0) { - numberOfSamples = ABS(numberOfSamples); - - while (numberOfSamples > 0) { - if (sourceOffset + 1 >= sourceSize) { - return; - } else if (destOffset + 1 >= destSize) { - debug(2, "Frame decoding overflow\n\tsourceOffset=%u\tsourceSize=%u\n\tdestOffset=%u\tdestSize=%u", sourceOffset, sourceSize, destOffset, destSize); - return; - } - - byte r, g, b; - Graphics::colorToRGB<Graphics::ColorMasks<555> >(READ_LE_UINT16(source + sourceOffset), r, g, b); - uint16 destColor = Graphics::RGBToColor<Graphics::ColorMasks<565> >(r, g, b); - WRITE_UINT16(dest + destOffset, destColor); - - sourceOffset += 2; - destOffset += 2; - numberOfSamples--; - } - - // If numberOfSamples is >= 0, copy one sample from source to the - // next (numberOfSamples + 2) dest spots - } else { - if (sourceOffset + 1 >= sourceSize) { - return; - } - - byte r, g, b; - Graphics::colorToRGB<Graphics::ColorMasks<555> >(READ_LE_UINT16(source + sourceOffset), r, g, b); - uint16 sampleColor = Graphics::RGBToColor<Graphics::ColorMasks<565> >(r, g, b); - sourceOffset += 2; - - numberOfSamples += 2; - while (numberOfSamples > 0) { - if (destOffset + 1 >= destSize) { - debug(2, "Frame decoding overflow\n\tsourceOffset=%u\tsourceSize=%u\n\tdestOffset=%u\tdestSize=%u", sourceOffset, sourceSize, destOffset, destSize); - return; - } - - WRITE_UINT16(dest + destOffset, sampleColor); - destOffset += 2; - numberOfSamples--; - } - } - } -} - -} // End of namespace ZVision diff --git a/engines/zvision/animation/rlf_animation.h b/engines/zvision/animation/rlf_animation.h deleted file mode 100644 index 4bb779301b..0000000000 --- a/engines/zvision/animation/rlf_animation.h +++ /dev/null @@ -1,163 +0,0 @@ -/* 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 ZVISION_RLF_ANIMATION_H -#define ZVISION_RLF_ANIMATION_H - -#include "common/file.h" - -#include "graphics/surface.h" - - -namespace Common { -class String; -} - -namespace ZVision { - -class RlfAnimation { -public: - RlfAnimation(const Common::String &fileName, bool stream = true); - ~RlfAnimation(); - -private: - enum EncodingType { - Masked, - Simple - }; - - struct Frame { - EncodingType type; - int8 *encodedData; - uint32 encodedSize; - }; - -private: - Common::File _file; - bool _stream; - uint _lastFrameRead; - - uint _frameCount; - uint _width; - uint _height; - uint32 _frameTime; // In milliseconds - Frame *_frames; - Common::Array<uint> _completeFrames; - - int _currentFrame; - Graphics::Surface _currentFrameBuffer; - uint32 _frameBufferByteSize; - -public: - uint frameCount() { return _frameCount; } - uint width() { return _width; } - uint height() { return _height; } - uint32 frameTime() { return _frameTime; } - - /** - * Seeks to the frameNumber and updates the internal Surface with - * the new frame data. If frameNumber == -1, it only sets _currentFrame, - * the internal Surface is unchanged. This function requires _stream = false - * - * @param frameNumber The frame number to seek to - */ - void seekToFrame(int frameNumber); - - /** - * Returns the pixel data of the frame specified. It will try to use - * getNextFrame() if possible. If not, it uses seekToFrame() to - * update the internal Surface and then returns a pointer to it. - * This function requires _stream = false - * - * @param frameNumber The frame number to get data for - * @return A pointer to the pixel data. Do NOT delete this. - */ - const Graphics::Surface *getFrameData(uint frameNumber); - /** - * Returns the pixel data of the next frame. It is up to the user to - * check if the next frame is valid before calling this. - * IE. Use endOfAnimation() - * - * @return A pointer to the pixel data. Do NOT delete this. - */ - const Graphics::Surface *getNextFrame(); - - /** - * @return Is the currentFrame is the last frame in the animation? - */ - bool endOfAnimation() { return _currentFrame == (int)_frameCount - 1; } - -private: - /** - * Reads in the header of the RLF file - * - * @return Will return false if the header magic number is wrong - */ - bool readHeader(); - /** - * Reads the next frame from the RLF file, stores the data in - * a Frame object, then returns the object - * - * @return A Frame object representing the frame data - */ - Frame readNextFrame(); - - /** - * Applies the frame corresponding to frameNumber on top of _currentFrameBuffer. - * This function requires _stream = false so it can look up the Frame object - * referenced by frameNumber. - * - * @param frameNumber The frame number to apply to _currentFrameBuffer - */ - void applyFrameToCurrent(uint frameNumber); - /** - * Applies the data from a Frame object on top of a _currentFrameBuffer. - * - * @param frame A Frame object to apply to _currentFrameBuffer - */ - void applyFrameToCurrent(const RlfAnimation::Frame &frame); - - /** - * Decode frame data that uses masked run length encoding. This is the encoding - * used by P-frames. - * - * @param source The source pixel data - * @param dest The destination buffer - * @param sourceSize The size of the source pixel data - * @param destSize The size of the destination buffer - */ - void decodeMaskedRunLengthEncoding(int8 *source, int8 *dest, uint32 sourceSize, uint32 destSize) const; - /** - * Decode frame data that uses simple run length encoding. This is the encoding - * used by I-frames. - * - * @param source The source pixel data - * @param dest The destination buffer - * @param sourceSize The size of the source pixel data - * @param destSize The size of the destination buffer - */ - void decodeSimpleRunLengthEncoding(int8 *source, int8 *dest, uint32 sourceSize, uint32 destSize) const; -}; - -} // End of namespace ZVision - -#endif diff --git a/engines/zvision/configure.engine b/engines/zvision/configure.engine index 02e31943af..38a5959995 100644 --- a/engines/zvision/configure.engine +++ b/engines/zvision/configure.engine @@ -1,3 +1,3 @@ # This file is included from the main "configure" script # add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] -add_engine zvision "ZVision" no "" "" "freetype2 16bit" +add_engine zvision "ZVision" yes "" "" "freetype2 16bit" diff --git a/engines/zvision/utility/clock.cpp b/engines/zvision/core/clock.cpp index 45ab23ab65..1425d550b7 100644 --- a/engines/zvision/utility/clock.cpp +++ b/engines/zvision/core/clock.cpp @@ -22,11 +22,10 @@ #include "common/scummsys.h" -#include "zvision/utility/clock.h" +#include "zvision/core/clock.h" #include "common/system.h" - namespace ZVision { Clock::Clock(OSystem *system) diff --git a/engines/zvision/utility/clock.h b/engines/zvision/core/clock.h index 6ae0f86161..cbf52be560 100644 --- a/engines/zvision/utility/clock.h +++ b/engines/zvision/core/clock.h @@ -47,24 +47,31 @@ public: * when the last update() was called. */ void update(); + /** * Get the delta time since the last frame. (The time between update() calls) * * @return Delta time since the last frame (in milliseconds) */ - uint32 getDeltaTime() const { return _deltaTime; } + uint32 getDeltaTime() const { + return _deltaTime; + } + /** * Get the time from the program starting to the last update() call * * @return Time from program start to last update() call (in milliseconds) */ - uint32 getLastMeasuredTime() { return _lastTime; } + uint32 getLastMeasuredTime() { + return _lastTime; + } /** * Pause the clock. Any future delta times will take this pause into account. * Has no effect if the clock is already paused. */ void start(); + /** * Un-pause the clock. * Has no effect if the clock is already un-paused. diff --git a/engines/zvision/core/console.cpp b/engines/zvision/core/console.cpp index e610f34474..f5cacb582c 100644 --- a/engines/zvision/core/console.cpp +++ b/engines/zvision/core/console.cpp @@ -1,24 +1,24 @@ /* 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. - * - */ +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* of the License, or (at your option) any later version. + +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. + +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +*/ #include "common/scummsys.h" @@ -27,11 +27,10 @@ #include "zvision/zvision.h" #include "zvision/scripting/script_manager.h" #include "zvision/graphics/render_manager.h" -#include "zvision/strings/string_manager.h" +#include "zvision/text/string_manager.h" #include "zvision/video/zork_avi_decoder.h" #include "zvision/sound/zork_raw.h" -#include "zvision/utility/utility.h" -#include "zvision/cursors/cursor.h" +#include "zvision/graphics/cursors/cursor.h" #include "common/system.h" #include "common/file.h" @@ -41,11 +40,9 @@ #include "audio/mixer.h" - namespace ZVision { Console::Console(ZVision *engine) : GUI::Debugger(), _engine(engine) { - registerCmd("loadimage", WRAP_METHOD(Console, cmdLoadImage)); registerCmd("loadvideo", WRAP_METHOD(Console, cmdLoadVideo)); registerCmd("loadsound", WRAP_METHOD(Console, cmdLoadSound)); registerCmd("raw2wav", WRAP_METHOD(Console, cmdRawToWav)); @@ -53,26 +50,17 @@ Console::Console(ZVision *engine) : GUI::Debugger(), _engine(engine) { registerCmd("generaterendertable", WRAP_METHOD(Console, cmdGenerateRenderTable)); registerCmd("setpanoramafov", WRAP_METHOD(Console, cmdSetPanoramaFoV)); registerCmd("setpanoramascale", WRAP_METHOD(Console, cmdSetPanoramaScale)); - registerCmd("changelocation", WRAP_METHOD(Console, cmdChangeLocation)); + registerCmd("location", WRAP_METHOD(Console, cmdLocation)); registerCmd("dumpfile", WRAP_METHOD(Console, cmdDumpFile)); - registerCmd("parseallscrfiles", WRAP_METHOD(Console, cmdParseAllScrFiles)); - registerCmd("rendertext", WRAP_METHOD(Console, cmdRenderText)); -} - -bool Console::cmdLoadImage(int argc, const char **argv) { - if (argc == 4) - _engine->getRenderManager()->renderImageToScreen(argv[1], atoi(argv[2]), atoi(argv[3])); - else { - debugPrintf("Use loadimage <fileName> <destinationX> <destinationY> to load an image to the screen\n"); - return true; - } - - return true; + registerCmd("dumpfiles", WRAP_METHOD(Console, cmdDumpFiles)); + registerCmd("dumpimage", WRAP_METHOD(Console, cmdDumpImage)); + registerCmd("statevalue", WRAP_METHOD(Console, cmdStateValue)); + registerCmd("stateflag", WRAP_METHOD(Console, cmdStateFlag)); } bool Console::cmdLoadVideo(int argc, const char **argv) { if (argc != 2) { - debugPrintf("Use loadvideo <fileName> to load a video to the screen\n"); + debugPrintf("Use %s <fileName> to load a video to the screen\n", argv[0]); return true; } @@ -94,18 +82,20 @@ bool Console::cmdLoadSound(int argc, const char **argv) { Audio::AudioStream *soundStream = makeRawZorkStream(argv[1], _engine); Audio::SoundHandle handle; _engine->_mixer->playStream(Audio::Mixer::kPlainSoundType, &handle, soundStream, -1, 100, 0, DisposeAfterUse::YES, false, false); - } else if (argc == 4) { int isStereo = atoi(argv[3]); Common::File *file = new Common::File(); - file->open(argv[1]); + if (!_engine->getSearchManager()->openFile(*file, argv[1])) { + warning("File not found: %s", argv[1]); + return true; + } Audio::AudioStream *soundStream = makeRawZorkStream(file, atoi(argv[2]), isStereo == 0 ? false : true); Audio::SoundHandle handle; _engine->_mixer->playStream(Audio::Mixer::kPlainSoundType, &handle, soundStream, -1, 100, 0, DisposeAfterUse::YES, false, false); } else { - debugPrintf("Use loadsound <fileName> [<rate> <isStereo: 1 or 0>] to load a sound\n"); + debugPrintf("Use %s <fileName> [<rate> <isStereo: 1 or 0>] to load a sound\n", argv[0]); return true; } @@ -114,17 +104,58 @@ bool Console::cmdLoadSound(int argc, const char **argv) { bool Console::cmdRawToWav(int argc, const char **argv) { if (argc != 3) { - debugPrintf("Use raw2wav <rawFilePath> <wavFileName> to dump a .RAW file to .WAV\n"); + debugPrintf("Use %s <rawFilePath> <wavFileName> to dump a .RAW file to .WAV\n", argv[0]); return true; } - convertRawToWav(argv[1], _engine, argv[2]); + Common::File file; + if (!_engine->getSearchManager()->openFile(file, argv[1])) { + warning("File not found: %s", argv[1]); + return true; + } + + Audio::AudioStream *audioStream = makeRawZorkStream(argv[1], _engine); + + Common::DumpFile output; + output.open(argv[2]); + + output.writeUint32BE(MKTAG('R', 'I', 'F', 'F')); + output.writeUint32LE(file.size() * 2 + 36); + output.writeUint32BE(MKTAG('W', 'A', 'V', 'E')); + output.writeUint32BE(MKTAG('f', 'm', 't', ' ')); + output.writeUint32LE(16); + output.writeUint16LE(1); + uint16 numChannels; + if (audioStream->isStereo()) { + numChannels = 2; + output.writeUint16LE(2); + } else { + numChannels = 1; + output.writeUint16LE(1); + } + output.writeUint32LE(audioStream->getRate()); + output.writeUint32LE(audioStream->getRate() * numChannels * 2); + output.writeUint16LE(numChannels * 2); + output.writeUint16LE(16); + output.writeUint32BE(MKTAG('d', 'a', 't', 'a')); + output.writeUint32LE(file.size() * 2); + int16 *buffer = new int16[file.size()]; + audioStream->readBuffer(buffer, file.size()); +#ifndef SCUMM_LITTLE_ENDIAN + for (int i = 0; i < file.size(); ++i) + buffer[i] = TO_LE_16(buffer[i]); +#endif + output.write(buffer, file.size() * 2); + + delete[] buffer; + + return true; } bool Console::cmdSetRenderState(int argc, const char **argv) { if (argc != 2) { - debugPrintf("Use setrenderstate <RenderState: panorama, tilt, flat> to change the current render state\n"); + debugPrintf("Use %s <RenderState: panorama, tilt, flat> to change the current render state\n", argv[0]); return true; } @@ -137,7 +168,7 @@ bool Console::cmdSetRenderState(int argc, const char **argv) { else if (renderState.matchString("flat", true)) _engine->getRenderManager()->getRenderTable()->setRenderState(RenderTable::FLAT); else - debugPrintf("Use setrenderstate <RenderState: panorama, tilt, flat> to change the current render state\n"); + debugPrintf("Use %s <RenderState: panorama, tilt, flat> to change the current render state\n", argv[0]); return true; } @@ -150,7 +181,7 @@ bool Console::cmdGenerateRenderTable(int argc, const char **argv) { bool Console::cmdSetPanoramaFoV(int argc, const char **argv) { if (argc != 2) { - debugPrintf("Use setpanoramafov <fieldOfView> to change the current panorama field of view\n"); + debugPrintf("Use %s <fieldOfView> to change the current panorama field of view\n", argv[0]); return true; } @@ -161,7 +192,7 @@ bool Console::cmdSetPanoramaFoV(int argc, const char **argv) { bool Console::cmdSetPanoramaScale(int argc, const char **argv) { if (argc != 2) { - debugPrintf("Use setpanoramascale <scale> to change the current panorama scale\n"); + debugPrintf("Use %s <scale> to change the current panorama scale\n", argv[0]); return true; } @@ -170,9 +201,14 @@ bool Console::cmdSetPanoramaScale(int argc, const char **argv) { return true; } -bool Console::cmdChangeLocation(int argc, const char **argv) { +bool Console::cmdLocation(int argc, const char **argv) { + Location curLocation = _engine->getScriptManager()->getCurrentLocation(); + Common::String scrFile = Common::String::format("%c%c%c%c.scr", curLocation.world, curLocation.room, curLocation.node, curLocation.view); + debugPrintf("Current location: world '%c', room '%c', node '%c', view '%c', offset %d, script %s\n", + curLocation.world, curLocation.room, curLocation.node, curLocation.view, curLocation.offset, scrFile.c_str()); + if (argc != 6) { - debugPrintf("Use changelocation <char: world> <char: room> <char:node> <char:view> <int: x position> to change your location\n"); + debugPrintf("Use %s <char: world> <char: room> <char:node> <char:view> <int: x offset> to change your location\n", argv[0]); return true; } @@ -181,36 +217,152 @@ bool Console::cmdChangeLocation(int argc, const char **argv) { return true; } +void dumpFile(Common::SeekableReadStream *s, const char *outName) { + byte *buffer = new byte[s->size()]; + s->read(buffer, s->size()); + + Common::DumpFile dumpFile; + dumpFile.open(outName); + + dumpFile.write(buffer, s->size()); + dumpFile.flush(); + dumpFile.close(); + + delete[] buffer; +} + bool Console::cmdDumpFile(int argc, const char **argv) { if (argc != 2) { - debugPrintf("Use dumpfile <fileName> to dump a file\n"); + debugPrintf("Use %s <fileName> to dump a file\n", argv[0]); return true; } - writeFileContentsToFile(argv[1], argv[1]); + Common::File f; + if (!_engine->getSearchManager()->openFile(f, argv[1])) { + warning("File not found: %s", argv[1]); + return true; + } + + dumpFile(&f, argv[1]); return true; } -bool Console::cmdParseAllScrFiles(int argc, const char **argv) { - Common::ArchiveMemberList list; - SearchMan.listMatchingMembers(list, "*.scr"); +bool Console::cmdDumpFiles(int argc, const char **argv) { + Common::String fileName; + Common::SeekableReadStream *in; + + if (argc != 2) { + debugPrintf("Use %s <file extension> to dump all files with a specific extension\n", argv[0]); + return true; + } + + SearchManager::MatchList fileList; + _engine->getSearchManager()->listMembersWithExtension(fileList, argv[1]); + + for (SearchManager::MatchList::iterator iter = fileList.begin(); iter != fileList.end(); ++iter) { + fileName = iter->_value.name; + debugPrintf("Dumping %s\n", fileName.c_str()); - for (Common::ArchiveMemberList::iterator iter = list.begin(); iter != list.end(); ++iter) { - _engine->getScriptManager()->parseScrFile((*iter)->getName()); + in = iter->_value.arch->createReadStreamForMember(iter->_value.name); + if (in) + dumpFile(in, fileName.c_str()); + delete in; } return true; } -bool Console::cmdRenderText(int argc, const char **argv) { - if (argc != 7) { - debugPrintf("Use rendertext <text> <fontNumber> <destX> <destY> <maxWidth> <1 or 0: wrap> to render text\n"); +bool Console::cmdDumpImage(int argc, const char **argv) { + if (argc != 2) { + debugPrintf("Use %s <TGA/TGZ name> to dump a ZVision TGA/TGZ image into a regular BMP image\n", argv[0]); + return true; + } + + Common::String fileName = argv[1]; + if (!fileName.hasSuffix(".tga")) { + debugPrintf("%s is not an image file", argv[1]); + } + + Common::File f; + if (!_engine->getSearchManager()->openFile(f, argv[1])) { + warning("File not found: %s", argv[1]); return true; } - StringManager::TextStyle style = _engine->getStringManager()->getTextStyle(atoi(argv[2])); - _engine->getRenderManager()->renderTextToWorkingWindow(333, Common::String(argv[1]), style.font, atoi(argv[3]), atoi(argv[4]), style.color, atoi(argv[5]), -1, Graphics::kTextAlignLeft, atoi(argv[6]) == 0 ? false : true); + Graphics::Surface surface; + _engine->getRenderManager()->readImageToSurface(argv[1], surface, false); + + // Open file + Common::DumpFile out; + + fileName.setChar('b', fileName.size() - 3); + fileName.setChar('m', fileName.size() - 2); + fileName.setChar('p', fileName.size() - 1); + + out.open(fileName); + + // Write BMP header + out.writeByte('B'); + out.writeByte('M'); + out.writeUint32LE(surface.h * surface.pitch + 54); + out.writeUint32LE(0); + out.writeUint32LE(54); + out.writeUint32LE(40); + out.writeUint32LE(surface.w); + out.writeUint32LE(surface.h); + out.writeUint16LE(1); + out.writeUint16LE(16); + out.writeUint32LE(0); + out.writeUint32LE(0); + out.writeUint32LE(0); + out.writeUint32LE(0); + out.writeUint32LE(0); + out.writeUint32LE(0); + + // Write pixel data to BMP + out.write(surface.getPixels(), surface.pitch * surface.h); + + out.flush(); + out.close(); + + surface.free(); + + return true; +} + +bool Console::cmdStateValue(int argc, const char **argv) { + if (argc < 2) { + debugPrintf("Use %s <valuenum> to show the value of a state variable\n", argv[0]); + debugPrintf("Use %s <valuenum> <newvalue> to set the value of a state variable\n", argv[0]); + return true; + } + + int valueNum = atoi(argv[1]); + int newValue = (argc > 2) ? atoi(argv[2]) : -1; + + if (argc == 2) + debugPrintf("[%d] = %d\n", valueNum, _engine->getScriptManager()->getStateValue(valueNum)); + else if (argc == 3) + _engine->getScriptManager()->setStateValue(valueNum, newValue); + + return true; +} + +bool Console::cmdStateFlag(int argc, const char **argv) { + if (argc < 2) { + debugPrintf("Use %s <flagnum> to show the value of a state flag\n", argv[0]); + debugPrintf("Use %s <flagnum> <newvalue> to set the value of a state flag\n", argv[0]); + return true; + } + + int valueNum = atoi(argv[1]); + int newValue = (argc > 2) ? atoi(argv[2]) : -1; + + if (argc == 2) + debugPrintf("[%d] = %d\n", valueNum, _engine->getScriptManager()->getStateFlag(valueNum)); + else if (argc == 3) + _engine->getScriptManager()->setStateFlag(valueNum, newValue); return true; } diff --git a/engines/zvision/core/console.h b/engines/zvision/core/console.h index 29523c57ee..ac834185a0 100644 --- a/engines/zvision/core/console.h +++ b/engines/zvision/core/console.h @@ -37,7 +37,6 @@ public: private: ZVision *_engine; - bool cmdLoadImage(int argc, const char **argv); bool cmdLoadVideo(int argc, const char **argv); bool cmdLoadSound(int argc, const char **argv); bool cmdRawToWav(int argc, const char **argv); @@ -45,10 +44,12 @@ private: bool cmdGenerateRenderTable(int argc, const char **argv); bool cmdSetPanoramaFoV(int argc, const char **argv); bool cmdSetPanoramaScale(int argc, const char **argv); - bool cmdChangeLocation(int argc, const char **argv); + bool cmdLocation(int argc, const char **argv); bool cmdDumpFile(int argc, const char **argv); - bool cmdParseAllScrFiles(int argc, const char **argv); - bool cmdRenderText(int argc, const char **argv); + bool cmdDumpFiles(int argc, const char **argv); + bool cmdDumpImage(int argc, const char **argv); + bool cmdStateValue(int argc, const char **argv); + bool cmdStateFlag(int argc, const char **argv); }; } // End of namespace ZVision diff --git a/engines/zvision/core/events.cpp b/engines/zvision/core/events.cpp index 83d6c17dec..cc1c00b6d0 100644 --- a/engines/zvision/core/events.cpp +++ b/engines/zvision/core/events.cpp @@ -8,12 +8,12 @@ * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. - * + * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. @@ -25,10 +25,12 @@ #include "zvision/zvision.h" #include "zvision/core/console.h" -#include "zvision/cursors/cursor_manager.h" +#include "zvision/graphics/cursors/cursor_manager.h" #include "zvision/graphics/render_manager.h" #include "zvision/scripting/script_manager.h" -#include "zvision/animation/rlf_animation.h" +#include "zvision/scripting/menu.h" +#include "zvision/sound/zork_raw.h" +#include "zvision/text/string_manager.h" #include "common/events.h" #include "common/system.h" @@ -36,29 +38,190 @@ #include "engines/util.h" - namespace ZVision { +void ZVision::pushKeyToCheatBuf(uint8 key) { + for (int i = 0; i < KEYBUF_SIZE - 1; i++) + _cheatBuffer[i] = _cheatBuffer[i + 1]; + + _cheatBuffer[KEYBUF_SIZE - 1] = key; +} + +bool ZVision::checkCode(const char *code) { + int codeLen = strlen(code); + + if (codeLen > KEYBUF_SIZE) + return false; + + for (int i = 0; i < codeLen; i++) + if (code[i] != _cheatBuffer[KEYBUF_SIZE - codeLen + i] && code[i] != '?') + return false; + + return true; +} + +uint8 ZVision::getBufferedKey(uint8 pos) { + if (pos >= KEYBUF_SIZE) + return 0; + else + return _cheatBuffer[KEYBUF_SIZE - pos - 1]; +} + +void ZVision::shortKeys(Common::Event event) { + if (event.kbd.hasFlags(Common::KBD_CTRL)) { + switch (event.kbd.keycode) { + case Common::KEYCODE_s: + if (_menu->getEnable() & kMenubarSave) + _scriptManager->changeLocation('g', 'j', 's', 'e', 0); + break; + case Common::KEYCODE_r: + if (_menu->getEnable() & kMenubarRestore) + _scriptManager->changeLocation('g', 'j', 'r', 'e', 0); + break; + case Common::KEYCODE_p: + if (_menu->getEnable() & kMenubarSettings) + _scriptManager->changeLocation('g', 'j', 'p', 'e', 0); + break; + case Common::KEYCODE_q: + if (_menu->getEnable() & kMenubarExit) + ifQuit(); + break; + default: + break; + } + } +} + +void ZVision::cheatCodes(uint8 key) { + Location loc = _scriptManager->getCurrentLocation(); + // Do not process cheat codes while in the game menus + if (loc.world == 'g' && loc.room == 'j') + return; + + pushKeyToCheatBuf(key); + + if (getGameId() == GID_GRANDINQUISITOR) { + if (checkCode("IMNOTDEAF")) { + // Unknown cheat + _renderManager->showDebugMsg(Common::String::format("IMNOTDEAF cheat or debug, not implemented")); + } + + if (checkCode("3100OPB")) { + _renderManager->showDebugMsg(Common::String::format("Current location: %c%c%c%c", + _scriptManager->getStateValue(StateKey_World), + _scriptManager->getStateValue(StateKey_Room), + _scriptManager->getStateValue(StateKey_Node), + _scriptManager->getStateValue(StateKey_View))); + } + + if (checkCode("KILLMENOW")) { + _scriptManager->changeLocation('g', 'j', 'd', 'e', 0); + _scriptManager->setStateValue(2201, 35); + } + + if (checkCode("MIKESPANTS")) { + _scriptManager->changeLocation('g', 'j', 't', 'm', 0); + } + + // There are 3 more cheats in script files: + // - "WHOAMI": gjcr.scr + // - "HUISOK": hp1e.scr + // - "EAT ME": uh1f.scr + } else if (getGameId() == GID_NEMESIS) { + if (checkCode("CHLOE")) { + _scriptManager->changeLocation('t', 'm', '2', 'g', 0); + _scriptManager->setStateValue(224, 1); + } + + if (checkCode("77MASSAVE")) { + _renderManager->showDebugMsg(Common::String::format("Current location: %c%c%c%c", + _scriptManager->getStateValue(StateKey_World), + _scriptManager->getStateValue(StateKey_Room), + _scriptManager->getStateValue(StateKey_Node), + _scriptManager->getStateValue(StateKey_View))); + } + + if (checkCode("IDKFA")) { + _scriptManager->changeLocation('t', 'w', '3', 'f', 0); + _scriptManager->setStateValue(249, 1); + } + + if (checkCode("309NEWDORMA")) { + _scriptManager->changeLocation('g', 'j', 'g', 'j', 0); + } + + if (checkCode("HELLOSAILOR")) { + Audio::AudioStream *soundStream; + if (loc == "vb10") { + soundStream = makeRawZorkStream("v000hpta.raw", this); + } else { + soundStream = makeRawZorkStream("v000hnta.raw", this); + } + Audio::SoundHandle handle; + _mixer->playStream(Audio::Mixer::kPlainSoundType, &handle, soundStream); + } + } + + if (checkCode("FRAME")) { + Common::String fpsStr = Common::String::format("FPS: %d", getFPS()); + _renderManager->showDebugMsg(fpsStr); + } + + if (checkCode("COMPUTERARCH")) + _renderManager->showDebugMsg("COMPUTERARCH: var-viewer not implemented"); + + // This cheat essentially toggles the GOxxxx cheat below + if (checkCode("XYZZY")) + _scriptManager->setStateValue(StateKey_DebugCheats, 1 - _scriptManager->getStateValue(StateKey_DebugCheats)); + + if (_scriptManager->getStateValue(StateKey_DebugCheats) == 1) + if (checkCode("GO????")) + _scriptManager->changeLocation(getBufferedKey(3), + getBufferedKey(2), + getBufferedKey(1), + getBufferedKey(0), 0); + + // Show the Venus screen when "?" or "/" is pressed while inside the temple world + if (_scriptManager->getStateValue(StateKey_VenusEnable) == 1) + if (getBufferedKey(0) == 0xBF && _scriptManager->getStateValue(StateKey_World) == 't') + _scriptManager->changeLocation('g', 'j', 'h', 'e', 0); +} + void ZVision::processEvents() { while (_eventMan->pollEvent(_event)) { switch (_event.type) { case Common::EVENT_LBUTTONDOWN: - onMouseDown(_event.mouse); + _cursorManager->cursorDown(true); + _scriptManager->setStateValue(StateKey_LMouse, 1); + _menu->onMouseDown(_event.mouse); + _scriptManager->addEvent(_event); break; case Common::EVENT_LBUTTONUP: - onMouseUp(_event.mouse); + _cursorManager->cursorDown(false); + _scriptManager->setStateValue(StateKey_LMouse, 0); + _menu->onMouseUp(_event.mouse); + _scriptManager->addEvent(_event); break; case Common::EVENT_RBUTTONDOWN: - // TODO: Inventory logic + _cursorManager->cursorDown(true); + _scriptManager->setStateValue(StateKey_RMouse, 1); + + if (getGameId() == GID_NEMESIS) + _scriptManager->inventoryCycle(); + break; + + case Common::EVENT_RBUTTONUP: + _cursorManager->cursorDown(false); + _scriptManager->setStateValue(StateKey_RMouse, 0); break; case Common::EVENT_MOUSEMOVE: onMouseMove(_event.mouse); break; - case Common::EVENT_KEYDOWN: + case Common::EVENT_KEYDOWN: { switch (_event.kbd.keycode) { case Common::KEYCODE_d: if (_event.kbd.hasFlags(Common::KBD_CTRL)) { @@ -67,18 +230,57 @@ void ZVision::processEvents() { _console->onFrame(); } break; - case Common::KEYCODE_q: - if (_event.kbd.hasFlags(Common::KBD_CTRL)) - quitGame(); + + case Common::KEYCODE_LEFT: + case Common::KEYCODE_RIGHT: + if (_renderManager->getRenderTable()->getRenderState() == RenderTable::PANORAMA) + _keyboardVelocity = (_event.kbd.keycode == Common::KEYCODE_LEFT ? + -_scriptManager->getStateValue(StateKey_KbdRotateSpeed) : + _scriptManager->getStateValue(StateKey_KbdRotateSpeed)) * 2; + break; + + case Common::KEYCODE_UP: + case Common::KEYCODE_DOWN: + if (_renderManager->getRenderTable()->getRenderState() == RenderTable::TILT) + _keyboardVelocity = (_event.kbd.keycode == Common::KEYCODE_UP ? + -_scriptManager->getStateValue(StateKey_KbdRotateSpeed) : + _scriptManager->getStateValue(StateKey_KbdRotateSpeed)) * 2; + break; + + case Common::KEYCODE_F10: { + Common::String fpsStr = Common::String::format("FPS: %d", getFPS()); + _renderManager->showDebugMsg(fpsStr); + } break; default: break; } - _scriptManager->onKeyDown(_event.kbd); - break; + uint8 vkKey = getZvisionKey(_event.kbd.keycode); + + _scriptManager->setStateValue(StateKey_KeyPress, vkKey); + + _scriptManager->addEvent(_event); + shortKeys(_event); + cheatCodes(vkKey); + } + break; case Common::EVENT_KEYUP: - _scriptManager->onKeyUp(_event.kbd); + _scriptManager->addEvent(_event); + switch (_event.kbd.keycode) { + case Common::KEYCODE_LEFT: + case Common::KEYCODE_RIGHT: + if (_renderManager->getRenderTable()->getRenderState() == RenderTable::PANORAMA) + _keyboardVelocity = 0; + break; + case Common::KEYCODE_UP: + case Common::KEYCODE_DOWN: + if (_renderManager->getRenderTable()->getRenderState() == RenderTable::TILT) + _keyboardVelocity = 0; + break; + default: + break; + } break; default: break; @@ -86,24 +288,11 @@ void ZVision::processEvents() { } } -void ZVision::onMouseDown(const Common::Point &pos) { - _cursorManager->cursorDown(true); - - Common::Point imageCoord(_renderManager->screenSpaceToImageSpace(pos)); - _scriptManager->onMouseDown(pos, imageCoord); -} - -void ZVision::onMouseUp(const Common::Point &pos) { - _cursorManager->cursorDown(false); - - Common::Point imageCoord(_renderManager->screenSpaceToImageSpace(pos)); - _scriptManager->onMouseUp(pos, imageCoord); -} - void ZVision::onMouseMove(const Common::Point &pos) { + _menu->onMouseMove(pos); Common::Point imageCoord(_renderManager->screenSpaceToImageSpace(pos)); - bool cursorWasChanged = _scriptManager->onMouseMove(pos, imageCoord); + bool cursorWasChanged = false; // Graph of the function governing rotation velocity: // @@ -135,52 +324,183 @@ void ZVision::onMouseMove(const Common::Point &pos) { // | // ^ - if (_workingWindow.contains(pos)) { + // Clip the horizontal mouse position to the working window + Common::Point clippedPos = pos; + clippedPos.x = CLIP<int16>(pos.x, _workingWindow.left + 1, _workingWindow.right - 1); + + if (_workingWindow.contains(clippedPos)) { + cursorWasChanged = _scriptManager->onMouseMove(clippedPos, imageCoord); + RenderTable::RenderState renderState = _renderManager->getRenderTable()->getRenderState(); if (renderState == RenderTable::PANORAMA) { - if (pos.x >= _workingWindow.left && pos.x < _workingWindow.left + ROTATION_SCREEN_EDGE_OFFSET) { - // Linear function of distance to the left edge (y = -mx + b) - // We use fixed point math to get better accuracy - Common::Rational velocity = (Common::Rational(MAX_ROTATION_SPEED, ROTATION_SCREEN_EDGE_OFFSET) * (pos.x - _workingWindow.left)) - MAX_ROTATION_SPEED; - _renderManager->setBackgroundVelocity(velocity.toInt()); - _cursorManager->setLeftCursor(); + if (clippedPos.x >= _workingWindow.left && clippedPos.x < _workingWindow.left + ROTATION_SCREEN_EDGE_OFFSET) { + + int16 mspeed = _scriptManager->getStateValue(StateKey_RotateSpeed) >> 4; + if (mspeed <= 0) { + mspeed = 25; + } + _mouseVelocity = MIN(((Common::Rational(mspeed, ROTATION_SCREEN_EDGE_OFFSET) * (clippedPos.x - _workingWindow.left)) - mspeed).toInt(), -1); + + + _cursorManager->changeCursor(CursorIndex_Left); cursorWasChanged = true; - } else if (pos.x <= _workingWindow.right && pos.x > _workingWindow.right - ROTATION_SCREEN_EDGE_OFFSET) { - // Linear function of distance to the right edge (y = mx) - // We use fixed point math to get better accuracy - Common::Rational velocity = Common::Rational(MAX_ROTATION_SPEED, ROTATION_SCREEN_EDGE_OFFSET) * (pos.x - _workingWindow.right + ROTATION_SCREEN_EDGE_OFFSET); - _renderManager->setBackgroundVelocity(velocity.toInt()); - _cursorManager->setRightCursor(); + } else if (clippedPos.x <= _workingWindow.right && clippedPos.x > _workingWindow.right - ROTATION_SCREEN_EDGE_OFFSET) { + + int16 mspeed = _scriptManager->getStateValue(StateKey_RotateSpeed) >> 4; + if (mspeed <= 0) { + mspeed = 25; + } + _mouseVelocity = MAX((Common::Rational(mspeed, ROTATION_SCREEN_EDGE_OFFSET) * (clippedPos.x - _workingWindow.right + ROTATION_SCREEN_EDGE_OFFSET)).toInt(), 1); + + _cursorManager->changeCursor(CursorIndex_Right); cursorWasChanged = true; } else { - _renderManager->setBackgroundVelocity(0); + _mouseVelocity = 0; } } else if (renderState == RenderTable::TILT) { - if (pos.y >= _workingWindow.top && pos.y < _workingWindow.top + ROTATION_SCREEN_EDGE_OFFSET) { - // Linear function of distance to top edge - // We use fixed point math to get better accuracy - Common::Rational velocity = (Common::Rational(MAX_ROTATION_SPEED, ROTATION_SCREEN_EDGE_OFFSET) * (pos.y - _workingWindow.top)) - MAX_ROTATION_SPEED; - _renderManager->setBackgroundVelocity(velocity.toInt()); - _cursorManager->setUpCursor(); + if (clippedPos.y >= _workingWindow.top && clippedPos.y < _workingWindow.top + ROTATION_SCREEN_EDGE_OFFSET) { + + int16 mspeed = _scriptManager->getStateValue(StateKey_RotateSpeed) >> 4; + if (mspeed <= 0) { + mspeed = 25; + } + _mouseVelocity = MIN(((Common::Rational(mspeed, ROTATION_SCREEN_EDGE_OFFSET) * (pos.y - _workingWindow.top)) - mspeed).toInt(), -1); + + _cursorManager->changeCursor(CursorIndex_UpArr); cursorWasChanged = true; - } else if (pos.y <= _workingWindow.bottom && pos.y > _workingWindow.bottom - ROTATION_SCREEN_EDGE_OFFSET) { - // Linear function of distance to the bottom edge (y = mx) - // We use fixed point math to get better accuracy - Common::Rational velocity = Common::Rational(MAX_ROTATION_SPEED, ROTATION_SCREEN_EDGE_OFFSET) * (pos.y - _workingWindow.bottom + ROTATION_SCREEN_EDGE_OFFSET); - _renderManager->setBackgroundVelocity(velocity.toInt()); - _cursorManager->setDownCursor(); + } else if (clippedPos.y <= _workingWindow.bottom && clippedPos.y > _workingWindow.bottom - ROTATION_SCREEN_EDGE_OFFSET) { + + int16 mspeed = _scriptManager->getStateValue(StateKey_RotateSpeed) >> 4; + if (mspeed <= 0) { + mspeed = 25; + } + _mouseVelocity = MAX((Common::Rational(MAX_ROTATION_SPEED, ROTATION_SCREEN_EDGE_OFFSET) * (pos.y - _workingWindow.bottom + ROTATION_SCREEN_EDGE_OFFSET)).toInt(), 1); + + _cursorManager->changeCursor(CursorIndex_DownArr); cursorWasChanged = true; } else { - _renderManager->setBackgroundVelocity(0); + _mouseVelocity = 0; } + } else { + _mouseVelocity = 0; } } else { - _renderManager->setBackgroundVelocity(0); + _mouseVelocity = 0; } if (!cursorWasChanged) { - _cursorManager->revertToIdle(); + _cursorManager->changeCursor(CursorIndex_Idle); + } +} + +uint8 ZVision::getZvisionKey(Common::KeyCode scummKeyCode) { + if (scummKeyCode >= Common::KEYCODE_a && scummKeyCode <= Common::KEYCODE_z) + return 0x41 + scummKeyCode - Common::KEYCODE_a; + if (scummKeyCode >= Common::KEYCODE_0 && scummKeyCode <= Common::KEYCODE_9) + return 0x30 + scummKeyCode - Common::KEYCODE_0; + if (scummKeyCode >= Common::KEYCODE_F1 && scummKeyCode <= Common::KEYCODE_F15) + return 0x70 + scummKeyCode - Common::KEYCODE_F1; + if (scummKeyCode >= Common::KEYCODE_KP0 && scummKeyCode <= Common::KEYCODE_KP9) + return 0x60 + scummKeyCode - Common::KEYCODE_KP0; + + switch (scummKeyCode) { + case Common::KEYCODE_BACKSPACE: + return 0x8; + case Common::KEYCODE_TAB: + return 0x9; + case Common::KEYCODE_CLEAR: + return 0xC; + case Common::KEYCODE_RETURN: + return 0xD; + case Common::KEYCODE_CAPSLOCK: + return 0x14; + case Common::KEYCODE_ESCAPE: + return 0x1B; + case Common::KEYCODE_SPACE: + return 0x20; + case Common::KEYCODE_PAGEUP: + return 0x21; + case Common::KEYCODE_PAGEDOWN: + return 0x22; + case Common::KEYCODE_END: + return 0x23; + case Common::KEYCODE_HOME: + return 0x24; + case Common::KEYCODE_LEFT: + return 0x25; + case Common::KEYCODE_UP: + return 0x26; + case Common::KEYCODE_RIGHT: + return 0x27; + case Common::KEYCODE_DOWN: + return 0x28; + case Common::KEYCODE_PRINT: + return 0x2A; + case Common::KEYCODE_INSERT: + return 0x2D; + case Common::KEYCODE_DELETE: + return 0x2E; + case Common::KEYCODE_HELP: + return 0x2F; + case Common::KEYCODE_KP_MULTIPLY: + return 0x6A; + case Common::KEYCODE_KP_PLUS: + return 0x6B; + case Common::KEYCODE_KP_MINUS: + return 0x6D; + case Common::KEYCODE_KP_PERIOD: + return 0x6E; + case Common::KEYCODE_KP_DIVIDE: + return 0x6F; + case Common::KEYCODE_NUMLOCK: + return 0x90; + case Common::KEYCODE_SCROLLOCK: + return 0x91; + case Common::KEYCODE_LSHIFT: + return 0xA0; + case Common::KEYCODE_RSHIFT: + return 0xA1; + case Common::KEYCODE_LCTRL: + return 0xA2; + case Common::KEYCODE_RCTRL: + return 0xA3; + case Common::KEYCODE_MENU: + return 0xA5; + case Common::KEYCODE_LEFTBRACKET: + return 0xDB; + case Common::KEYCODE_RIGHTBRACKET: + return 0xDD; + case Common::KEYCODE_SEMICOLON: + return 0xBA; + case Common::KEYCODE_BACKSLASH: + return 0xDC; + case Common::KEYCODE_QUOTE: + return 0xDE; + case Common::KEYCODE_SLASH: + return 0xBF; + case Common::KEYCODE_TILDE: + return 0xC0; + case Common::KEYCODE_COMMA: + return 0xBC; + case Common::KEYCODE_PERIOD: + return 0xBE; + case Common::KEYCODE_MINUS: + return 0xBD; + case Common::KEYCODE_PLUS: + return 0xBB; + default: + return 0; + } + + return 0; +} + +bool ZVision::ifQuit() { + if (_renderManager->askQuestion(_stringManager->getTextLine(StringManager::ZVISION_STR_EXITPROMT))) { + quitGame(); + return true; } + return false; } } // End of namespace ZVision diff --git a/engines/zvision/core/save_manager.cpp b/engines/zvision/core/save_manager.cpp deleted file mode 100644 index 07fb7637e7..0000000000 --- a/engines/zvision/core/save_manager.cpp +++ /dev/null @@ -1,206 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#include "common/scummsys.h" - -#include "zvision/core/save_manager.h" - -#include "zvision/zvision.h" -#include "zvision/scripting/script_manager.h" -#include "zvision/graphics/render_manager.h" - -#include "common/system.h" - -#include "graphics/surface.h" -#include "graphics/thumbnail.h" - -#include "gui/message.h" - - -namespace ZVision { - -const uint32 SaveManager::SAVEGAME_ID = MKTAG('Z', 'E', 'N', 'G'); - -void SaveManager::saveGame(uint slot, const Common::String &saveName) { - // The games only support 20 slots - assert(slot <= 1 && slot <= 20); - - Common::SaveFileManager *saveFileManager = g_system->getSavefileManager(); - Common::OutSaveFile *file = saveFileManager->openForSaving(_engine->generateSaveFileName(slot)); - - // Write out the savegame header - file->writeUint32BE(SAVEGAME_ID); - - // Write version - file->writeByte(SAVE_VERSION); - - // Write savegame name - file->writeString(saveName); - file->writeByte(0); - - // We can't call writeGameSaveData because the save menu is actually - // a room, so writeGameSaveData would save us in the save menu. - // However, an auto save is performed before each room change, so we - // can copy the data from there. We can guarantee that an auto save file will - // exist before this is called because the save menu can only be accessed - // after the first room (the main menu) has loaded. - Common::InSaveFile *autoSaveFile = saveFileManager->openForLoading(_engine->generateAutoSaveFileName()); - - // Skip over the header info - autoSaveFile->readSint32BE(); // SAVEGAME_ID - autoSaveFile->readByte(); // Version - autoSaveFile->seek(5, SEEK_CUR); // The string "auto" with terminating NULL - - // Read the rest to a buffer - uint32 size = autoSaveFile->size() - autoSaveFile->pos(); - byte *buffer = new byte[size]; - autoSaveFile->read(buffer, size); - - // Then write the buffer to the new file - file->write(buffer, size); - - // Cleanup - delete[] buffer; - file->finalize(); - delete file; -} - -void SaveManager::autoSave() { - Common::OutSaveFile *file = g_system->getSavefileManager()->openForSaving(_engine->generateAutoSaveFileName()); - - // Write out the savegame header - file->writeUint32BE(SAVEGAME_ID); - - // Version - file->writeByte(SAVE_VERSION); - - file->writeString("auto"); - file->writeByte(0); - - writeSaveGameData(file); - - // Cleanup - file->finalize(); - delete file; -} - -void SaveManager::writeSaveGameData(Common::OutSaveFile *file) { - // Create a thumbnail and save it - Graphics::saveThumbnail(*file); - - // Write out the save date/time - TimeDate td; - g_system->getTimeAndDate(td); - file->writeSint16LE(td.tm_year + 1900); - file->writeSint16LE(td.tm_mon + 1); - file->writeSint16LE(td.tm_mday); - file->writeSint16LE(td.tm_hour); - file->writeSint16LE(td.tm_min); - - ScriptManager *scriptManager = _engine->getScriptManager(); - // Write out the current location - Location currentLocation = scriptManager->getCurrentLocation(); - file->writeByte(currentLocation.world); - file->writeByte(currentLocation.room); - file->writeByte(currentLocation.node); - file->writeByte(currentLocation.view); - file->writeUint32LE(currentLocation.offset); - - // Write out the current state table values - scriptManager->serializeStateTable(file); - - // Write out any controls needing to save state - scriptManager->serializeControls(file); -} - -Common::Error SaveManager::loadGame(uint slot) { - // The games only support 20 slots - assert(slot <= 1 && slot <= 20); - - Common::InSaveFile *saveFile = g_system->getSavefileManager()->openForLoading(_engine->generateSaveFileName(slot)); - if (saveFile == 0) { - return Common::kPathDoesNotExist; - } - - // Read the header - SaveGameHeader header; - if (!readSaveGameHeader(saveFile, header)) { - return Common::kUnknownError; - } - - char world = (char)saveFile->readByte(); - char room = (char)saveFile->readByte(); - char node = (char)saveFile->readByte(); - char view = (char)saveFile->readByte(); - uint32 offset = (char)saveFile->readUint32LE(); - - ScriptManager *scriptManager = _engine->getScriptManager(); - // Update the state table values - scriptManager->deserializeStateTable(saveFile); - - // Load the room - scriptManager->changeLocation(world, room, node, view, offset); - - // Update the controls - scriptManager->deserializeControls(saveFile); - - return Common::kNoError; -} - -bool SaveManager::readSaveGameHeader(Common::InSaveFile *in, SaveGameHeader &header) { - if (in->readUint32BE() != SAVEGAME_ID) { - warning("File is not a ZVision save file. Aborting load"); - return false; - } - - // Read in the version - header.version = in->readByte(); - - // Check that the save version isn't newer than this binary - if (header.version > SAVE_VERSION) { - uint tempVersion = header.version; - GUI::MessageDialog dialog(Common::String::format("This save file uses version %u, but this engine only supports up to version %d. You will need an updated version of the engine to use this save file.", tempVersion, SAVE_VERSION), "OK"); - dialog.runModal(); - } - - // Read in the save name - header.saveName.clear(); - char ch; - while ((ch = (char)in->readByte()) != '\0') - header.saveName += ch; - - // Get the thumbnail - header.thumbnail = Graphics::loadThumbnail(*in); - if (!header.thumbnail) - return false; - - // Read in save date/time - header.saveYear = in->readSint16LE(); - header.saveMonth = in->readSint16LE(); - header.saveDay = in->readSint16LE(); - header.saveHour = in->readSint16LE(); - header.saveMinutes = in->readSint16LE(); - - return true; -} - -} // End of namespace ZVision diff --git a/engines/zvision/cursors/cursor.cpp b/engines/zvision/cursors/cursor.cpp deleted file mode 100644 index 9b9b9a3f71..0000000000 --- a/engines/zvision/cursors/cursor.cpp +++ /dev/null @@ -1,94 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#include "common/scummsys.h" - -#include "zvision/cursors/cursor.h" - -#include "common/str.h" -#include "common/file.h" - - -namespace ZVision { - -ZorkCursor::ZorkCursor() - : _width(0), - _height(0), - _hotspotX(0), - _hotspotY(0) { -} - -ZorkCursor::ZorkCursor(const Common::String &fileName) - : _width(0), - _height(0), - _hotspotX(0), - _hotspotY(0) { - Common::File file; - if (!file.open(fileName)) - return; - - uint32 magic = file.readUint32BE(); - if (magic != MKTAG('Z', 'C', 'R', '1')) { - warning("%s is not a Zork Cursor file", fileName.c_str()); - return; - } - - _hotspotX = file.readUint16LE(); - _hotspotY = file.readUint16LE(); - _width = file.readUint16LE(); - _height = file.readUint16LE(); - - uint dataSize = _width * _height * sizeof(uint16); - _surface.create(_width, _height, Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0)); - uint32 bytesRead = file.read(_surface.getPixels(), dataSize); - assert(bytesRead == dataSize); - - // Convert to RGB 565 - _surface.convertToInPlace(Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0)); -} - -ZorkCursor::ZorkCursor(const ZorkCursor &other) { - _width = other._width; - _height = other._height; - _hotspotX = other._hotspotX; - _hotspotY = other._hotspotY; - - _surface.copyFrom(other._surface); -} - -ZorkCursor &ZorkCursor::operator=(const ZorkCursor &other) { - _width = other._width; - _height = other._height; - _hotspotX = other._hotspotX; - _hotspotY = other._hotspotY; - - _surface.free(); - _surface.copyFrom(other._surface); - - return *this; -} - -ZorkCursor::~ZorkCursor() { - _surface.free(); -} - -} // End of namespace ZVision diff --git a/engines/zvision/cursors/cursor_manager.cpp b/engines/zvision/cursors/cursor_manager.cpp deleted file mode 100644 index 7f70c8b4e3..0000000000 --- a/engines/zvision/cursors/cursor_manager.cpp +++ /dev/null @@ -1,152 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#include "common/scummsys.h" - -#include "zvision/cursors/cursor_manager.h" - -#include "zvision/zvision.h" - -#include "common/system.h" - -#include "graphics/pixelformat.h" -#include "graphics/cursorman.h" - - -namespace ZVision { - -const char *CursorManager::_cursorNames[NUM_CURSORS] = { "active", "arrow", "backward", "downarrow", "forward", "handpt", "handpu", "hdown", "hleft", - "hright", "hup", "idle", "leftarrow", "rightarrow", "suggest_surround", "suggest_tilt", "turnaround", "zuparrow" }; - -const char *CursorManager::_zgiCursorFileNames[NUM_CURSORS] = { "g0gbc011.zcr", "g0gac001.zcr", "g0gac021.zcr", "g0gac031.zcr", "g0gac041.zcr", "g0gac051.zcr", "g0gac061.zcr", "g0gac071.zcr", "g0gac081.zcr", - "g0gac091.zcr", "g0gac101.zcr", "g0gac011.zcr", "g0gac111.zcr", "g0gac121.zcr", "g0gac131.zcr", "g0gac141.zcr", "g0gac151.zcr", "g0gac161.zcr" }; - -const char *CursorManager::_zNemCursorFileNames[NUM_CURSORS] = { "00act", "arrow", "back", "down", "forw", "handpt", "handpu", "hdown", "hleft", - "hright", "hup", "00idle", "left", "right", "ssurr", "stilt", "turn", "up" }; - - -CursorManager::CursorManager(ZVision *engine, const Graphics::PixelFormat *pixelFormat) - : _engine(engine), - _pixelFormat(pixelFormat), - _cursorIsPushed(false) { - // WARNING: The index IDLE_CURSOR_INDEX is hardcoded. If you change the order of _cursorNames/_zgiCursorFileNames/_zNemCursorFileNames, you HAVE to change the index accordingly - if (_engine->getGameId() == GID_NEMESIS) { - Common::String name(Common::String::format("%sa.zcr", _zNemCursorFileNames[IDLE_CURSOR_INDEX])); - _idleCursor = ZorkCursor(name); - } else if (_engine->getGameId() == GID_GRANDINQUISITOR) { - _idleCursor = ZorkCursor(_zgiCursorFileNames[IDLE_CURSOR_INDEX]); - } -} - -void CursorManager::initialize() { - revertToIdle(); - CursorMan.showMouse(true); -} - -void CursorManager::changeCursor(const Common::String &cursorName) { - changeCursor(cursorName, _cursorIsPushed); -} - -void CursorManager::changeCursor(const Common::String &cursorName, bool pushed) { - if (_currentCursor.equals(cursorName) && _cursorIsPushed == pushed) - return; - - if (_cursorIsPushed != pushed) - _cursorIsPushed = pushed; - - if (cursorName == "idle" && !pushed) { - CursorMan.replaceCursor(_idleCursor.getSurface(), _idleCursor.getWidth(), _idleCursor.getHeight(), _idleCursor.getHotspotX(), _idleCursor.getHotspotY(), _idleCursor.getKeyColor(), false, _pixelFormat); - return; - } - - for (int i = 0; i < NUM_CURSORS; ++i) { - if (_engine->getGameId() == GID_NEMESIS) { - if (cursorName.equals(_cursorNames[i])) { - _currentCursor = cursorName; - - // ZNem uses a/b at the end of the file to signify not pushed/pushed respectively - Common::String pushedFlag = pushed ? "b" : "a"; - Common::String name = Common::String::format("%s%s.zcr", _zNemCursorFileNames[i], pushedFlag.c_str()); - - changeCursor(ZorkCursor(name)); - return; - } - } else if (_engine->getGameId() == GID_GRANDINQUISITOR) { - if (cursorName.equals(_cursorNames[i])) { - _currentCursor = cursorName; - - if (!pushed) { - changeCursor(ZorkCursor(_zgiCursorFileNames[i])); - } else { - // ZGI flips not pushed/pushed between a/c and b/d - // It flips the 4th character of the name - char buffer[25]; - strcpy(buffer, _zgiCursorFileNames[i]); - buffer[3] += 2; - changeCursor(ZorkCursor(buffer)); - } - return; - } - } - } - - // If we get here, something went wrong - warning("No cursor found for identifier %s", cursorName.c_str()); -} - -void CursorManager::changeCursor(const ZorkCursor &cursor) { - CursorMan.replaceCursor(cursor.getSurface(), cursor.getWidth(), cursor.getHeight(), cursor.getHotspotX(), cursor.getHotspotY(), cursor.getKeyColor(), false, _pixelFormat); -} - -void CursorManager::cursorDown(bool pushed) { - if (_cursorIsPushed == pushed) - return; - - _cursorIsPushed = pushed; - changeCursor(_currentCursor, pushed); -} - -void CursorManager::setLeftCursor() { - changeCursor("leftarrow"); -} - -void CursorManager::setRightCursor() { - changeCursor("rightarrow"); -} - -void CursorManager::setUpCursor() { - changeCursor("zuparrow"); -} - -void CursorManager::setDownCursor() { - changeCursor("downarrow"); -} - -void CursorManager::revertToIdle() { - _currentCursor = "idle"; - if (!_cursorIsPushed) - CursorMan.replaceCursor(_idleCursor.getSurface(), _idleCursor.getWidth(), _idleCursor.getHeight(), _idleCursor.getHotspotX(), _idleCursor.getHotspotY(), _idleCursor.getKeyColor(), false, _pixelFormat); - else - changeCursor(_currentCursor, _cursorIsPushed); -} - -} // End of namespace ZVision diff --git a/engines/zvision/detection.cpp b/engines/zvision/detection.cpp index 9961db1215..c817cbf3e9 100644 --- a/engines/zvision/detection.cpp +++ b/engines/zvision/detection.cpp @@ -26,13 +26,14 @@ #include "zvision/zvision.h" #include "zvision/detection.h" +#include "zvision/file/save_manager.h" +#include "zvision/scripting/script_manager.h" #include "common/translation.h" #include "common/savefile.h" #include "common/str-array.h" #include "common/system.h" - namespace ZVision { uint32 ZVision::getFeatures() const { @@ -45,7 +46,6 @@ Common::Language ZVision::getLanguage() const { } // End of namespace ZVision - static const PlainGameDescriptor zVisionGames[] = { {"zvision", "ZVision Game"}, {"znemesis", "Zork Nemesis: The Forbidden Lands"}, @@ -53,9 +53,14 @@ static const PlainGameDescriptor zVisionGames[] = { {0, 0} }; - namespace ZVision { +#define GAMEOPTION_ORIGINAL_SAVELOAD GUIO_GAMEOPTIONS1 +#define GAMEOPTION_DOUBLE_FPS GUIO_GAMEOPTIONS2 +#define GAMEOPTION_ENABLE_VENUS GUIO_GAMEOPTIONS3 +#define GAMEOPTION_DISABLE_ANIM_WHILE_TURNING GUIO_GAMEOPTIONS4 +#define GAMEOPTION_USE_HIRES_MPEG_MOVIES GUIO_GAMEOPTIONS5 + static const ZVisionGameDescription gameDescriptions[] = { { @@ -67,21 +72,156 @@ static const ZVisionGameDescription gameDescriptions[] = { Common::EN_ANY, Common::kPlatformDOS, ADGF_NO_FLAGS, - GUIO1(GUIO_NONE) + GUIO4(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_ENABLE_VENUS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING) }, GID_NEMESIS }, { - // Zork Grand Inquisitor English version + // Zork Nemesis French version { - "zgi", + "znemesis", + 0, + {{"CSCR.ZFS", 0, "f04113357b4748c13efcb58b4629887c", 2577873}, + {"NEMESIS.STR", 0, "333bcb17bbb7f57cae742fbbe44f56f3", 9219}, + AD_LISTEND + }, + Common::FR_FRA, + Common::kPlatformDOS, + ADGF_NO_FLAGS, + GUIO4(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_ENABLE_VENUS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING) + }, + GID_NEMESIS + }, + + { + // Zork Nemesis German version + { + "znemesis", + 0, + {{"CSCR.ZFS", 0, "f04113357b4748c13efcb58b4629887c", 2577873}, + {"NEMESIS.STR", 0, "3d1a12b907751653866cffc6d4dfb331", 9505}, + AD_LISTEND + }, + Common::DE_DEU, + Common::kPlatformDOS, + ADGF_NO_FLAGS, + GUIO4(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_ENABLE_VENUS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING) + }, + GID_NEMESIS + }, + + { + // Zork Nemesis Italian version + { + "znemesis", 0, + {{"CSCR.ZFS", 0, "f04113357b4748c13efcb58b4629887c", 2577873}, + {"NEMESIS.STR", 0, "7c568feca8d9f9ae855c47183612c305", 9061}, + AD_LISTEND + }, + Common::IT_ITA, + Common::kPlatformDOS, + ADGF_NO_FLAGS, + GUIO4(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_ENABLE_VENUS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING) + }, + GID_NEMESIS + }, + + { + // Zork Nemesis English demo version + { + "znemesis", + "Demo", + AD_ENTRY1s("SCRIPTS.ZFS", "64f1e881394e9462305104f99513c833", 380539), + Common::EN_ANY, + Common::kPlatformWindows, + ADGF_DEMO, + GUIO4(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_ENABLE_VENUS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING) + }, + GID_NEMESIS + }, + + { + // Zork Grand Inquisitor English CD version + { + "zgi", + "CD", AD_ENTRY1s("SCRIPTS.ZFS", "81efd40ecc3d22531e211368b779f17f", 8336944), Common::EN_ANY, Common::kPlatformWindows, ADGF_NO_FLAGS, - GUIO1(GUIO_NONE) + GUIO3(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING) + }, + GID_GRANDINQUISITOR + }, + + { + // Zork Grand Inquisitor French CD version, reported by ulrichh on IRC + { + "zgi", + "CD", + AD_ENTRY1s("SCRIPTS.ZFS", "4d1ec4ade7ecc9ee9ec591d43ca3d213", 8338133), + Common::FR_FRA, + Common::kPlatformWindows, + ADGF_NO_FLAGS, + GUIO3(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING) + }, + GID_GRANDINQUISITOR + }, + + { + // Zork Grand Inquisitor German CD version, reported by breit in bug #6760 + { + "zgi", + "CD", + AD_ENTRY1s("SCRIPTS.ZFS", "b7ac7e331b9b7f884590b0b325b560c8", 8338133), + Common::DE_DEU, + Common::kPlatformWindows, + ADGF_NO_FLAGS, + GUIO3(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING) + }, + GID_GRANDINQUISITOR + }, + + { + // Zork Grand Inquisitor Spanish CD version, reported by dianiu in bug #6764 + { + "zgi", + "CD", + AD_ENTRY1s("SCRIPTS.ZFS", "5cdc4b99c1134053af135aae71326fd1", 8338141), + Common::ES_ESP, + Common::kPlatformWindows, + ADGF_NO_FLAGS, + GUIO3(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING) + }, + GID_GRANDINQUISITOR + }, + + { + // Zork Grand Inquisitor English DVD version + { + "zgi", + "DVD", + AD_ENTRY1s("SCRIPTS.ZFS", "03157a3399513bfaaf8dc6d5ab798b36", 8433326), + Common::EN_ANY, + Common::kPlatformWindows, + ADGF_NO_FLAGS, + GUIO4(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING, GAMEOPTION_USE_HIRES_MPEG_MOVIES) + }, + GID_GRANDINQUISITOR + }, + + { + // Zork Grand Inquisitor English demo version + { + "zgi", + "Demo", + AD_ENTRY1s("SCRIPTS.ZFS", "71a2494fd2fb999347deb13401e9b998", 304239), + Common::EN_ANY, + Common::kPlatformWindows, + ADGF_DEMO, + GUIO3(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING) }, GID_GRANDINQUISITOR }, @@ -99,16 +239,63 @@ static const char *directoryGlobs[] = { 0 }; -static const ExtraGuiOption ZVisionExtraGuiOption = { - _s("Use original save/load screens"), - _s("Use the original save/load screens, instead of the ScummVM ones"), - "originalsaveload", - false +static const ADExtraGuiOptionsMap optionsList[] = { + { + GAMEOPTION_ORIGINAL_SAVELOAD, + { + _s("Use original save/load screens"), + _s("Use the original save/load screens, instead of the ScummVM ones"), + "originalsaveload", + false + } + }, + + { + GAMEOPTION_DOUBLE_FPS, + { + _s("Double FPS"), + _s("Increase game FPS from 30 to 60"), + "doublefps", + false + } + }, + + { + GAMEOPTION_ENABLE_VENUS, + { + _s("Enable Venus"), + _s("Enable the Venus help system"), + "venusenabled", + true + } + }, + + { + GAMEOPTION_DISABLE_ANIM_WHILE_TURNING, + { + _s("Disable animation while turning"), + _s("Disable animation while turning in panoramic mode"), + "noanimwhileturning", + false + } + }, + + { + GAMEOPTION_USE_HIRES_MPEG_MOVIES, + { + _s("Use the hires MPEG movies"), + _s("Use the hires MPEG movies of the DVD version, instead of the lowres AVI ones"), + "mpegmovies", + true + } + }, + + AD_EXTRA_GUI_OPTIONS_TERMINATOR }; class ZVisionMetaEngine : public AdvancedMetaEngine { public: - ZVisionMetaEngine() : AdvancedMetaEngine(ZVision::gameDescriptions, sizeof(ZVision::ZVisionGameDescription), zVisionGames) { + ZVisionMetaEngine() : AdvancedMetaEngine(ZVision::gameDescriptions, sizeof(ZVision::ZVisionGameDescription), zVisionGames, optionsList) { _maxScanDepth = 2; _directoryGlobs = directoryGlobs; _singleid = "zvision"; @@ -124,7 +311,6 @@ public: virtual bool hasFeature(MetaEngineFeature f) const; virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const; - virtual const ExtraGuiOptions getExtraGuiOptions(const Common::String &target) const; SaveStateList listSaves(const char *target) const; virtual int getMaximumSaveSlot() const; void removeSaveState(const char *target, int slot) const; @@ -132,24 +318,40 @@ public: }; bool ZVisionMetaEngine::hasFeature(MetaEngineFeature f) const { - return false; - /* - (f == kSupportsListSaves) || - (f == kSupportsLoadingDuringStartup) || - (f == kSupportsDeleteSave) || - (f == kSavesSupportMetaInfo) || - (f == kSavesSupportThumbnail) || - (f == kSavesSupportCreationDate) || - (f == kSavesSupportPlayTime); - */ + return + (f == kSupportsListSaves) || + (f == kSupportsLoadingDuringStartup) || + (f == kSupportsDeleteSave) || + (f == kSavesSupportMetaInfo) || + (f == kSavesSupportThumbnail) || + (f == kSavesSupportCreationDate); + //(f == kSavesSupportPlayTime); } -/*bool ZVision::ZVision::hasFeature(EngineFeature f) const { - return - (f == kSupportsRTL) || - (f == kSupportsLoadingDuringRuntime) || - (f == kSupportsSavingDuringRuntime); -}*/ +bool ZVision::ZVision::hasFeature(EngineFeature f) const { + return + (f == kSupportsRTL) || + (f == kSupportsLoadingDuringRuntime) || + (f == kSupportsSavingDuringRuntime); +} + +Common::Error ZVision::ZVision::loadGameState(int slot) { + return _saveManager->loadGame(slot); +} + +Common::Error ZVision::ZVision::saveGameState(int slot, const Common::String &desc) { + _saveManager->saveGame(slot, desc, false); + return Common::kNoError; +} + +bool ZVision::ZVision::canLoadGameStateCurrently() { + return !_videoIsPlaying; +} + +bool ZVision::ZVision::canSaveGameStateCurrently() { + Location currentLocation = _scriptManager->getCurrentLocation(); + return !_videoIsPlaying && currentLocation.world != 'g' && !(currentLocation.room == 'j' || currentLocation.room == 'a'); +} bool ZVisionMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const { const ZVision::ZVisionGameDescription *gd = (const ZVision::ZVisionGameDescription *)desc; @@ -159,37 +361,36 @@ bool ZVisionMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADG return gd != 0; } -const ExtraGuiOptions ZVisionMetaEngine::getExtraGuiOptions(const Common::String &target) const { - ExtraGuiOptions options; - options.push_back(ZVisionExtraGuiOption); - return options; -} - SaveStateList ZVisionMetaEngine::listSaves(const char *target) const { - //Common::SaveFileManager *saveFileMan = g_system->getSavefileManager(); - /*ZVision::ZVision::SaveHeader header; + Common::SaveFileManager *saveFileMan = g_system->getSavefileManager(); + ZVision::SaveGameHeader header; Common::String pattern = target; pattern += ".???"; Common::StringArray filenames; filenames = saveFileMan->listSavefiles(pattern.c_str()); - Common::sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..)*/ + Common::sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..)*/ SaveStateList saveList; -/* for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); file++) { - // Obtain the last 3 digits of the filename, since they correspond to the save slot - int slotNum = atoi(file->c_str() + file->size() - 3); - - if (slotNum >= 0 && slotNum <= 999) { - Common::InSaveFile *in = saveFileMan->openForLoading(file->c_str()); - if (in) { - if (ZVision::ZVision::readSaveHeader(in, false, header) == ZVision::ZVision::kRSHENoError) { - saveList.push_back(SaveStateDescriptor(slotNum, header.description)); - } - delete in; - } - } - }*/ + // We only use readSaveGameHeader() here, which doesn't need an engine callback + ZVision::SaveManager *zvisionSaveMan = new ZVision::SaveManager(NULL); + + for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); file++) { + // Obtain the last 3 digits of the filename, since they correspond to the save slot + int slotNum = atoi(file->c_str() + file->size() - 3); + + if (slotNum >= 0 && slotNum <= 999) { + Common::InSaveFile *in = saveFileMan->openForLoading(file->c_str()); + if (in) { + if (zvisionSaveMan->readSaveGameHeader(in, header)) { + saveList.push_back(SaveStateDescriptor(slotNum, header.saveName)); + } + delete in; + } + } + } + + delete zvisionSaveMan; return saveList; } @@ -199,9 +400,8 @@ int ZVisionMetaEngine::getMaximumSaveSlot() const { } void ZVisionMetaEngine::removeSaveState(const char *target, int slot) const { - /* Common::SaveFileManager *saveFileMan = g_system->getSavefileManager(); - Common::String filename = ZVision::ZVision::getSavegameFilename(target, slot); + Common::String filename = Common::String::format("%s.%03u", target, slot); saveFileMan->removeSavefile(filename.c_str()); @@ -209,63 +409,67 @@ void ZVisionMetaEngine::removeSaveState(const char *target, int slot) const { Common::String pattern = target; pattern += ".???"; filenames = saveFileMan->listSavefiles(pattern.c_str()); - Common::sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) + Common::sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) { - // Obtain the last 3 digits of the filename, since they correspond to the save slot - int slotNum = atoi(file->c_str() + file->size() - 3); - - // Rename every slot greater than the deleted slot, - if (slotNum > slot) { - saveFileMan->renameSavefile(file->c_str(), filename.c_str()); - filename = ZVision::ZVision::getSavegameFilename(target, ++slot); - } + // Obtain the last 3 digits of the filename, since they correspond to the save slot + int slotNum = atoi(file->c_str() + file->size() - 3); + + // Rename every slot greater than the deleted slot, + if (slotNum > slot) { + saveFileMan->renameSavefile(file->c_str(), filename.c_str()); + filename = Common::String::format("%s.%03u", target, ++slot); + } } - */ } SaveStateDescriptor ZVisionMetaEngine::querySaveMetaInfos(const char *target, int slot) const { - /* - Common::String filename = ZVision::ZVision::getSavegameFilename(target, slot); + Common::String filename = Common::String::format("%s.%03u", target, slot); Common::InSaveFile *in = g_system->getSavefileManager()->openForLoading(filename.c_str()); if (in) { - ZVision::ZVision::SaveHeader header; - ZVision::ZVision::kReadSaveHeaderError error; + ZVision::SaveGameHeader header; - error = ZVision::ZVision::readSaveHeader(in, true, header); - delete in; + // We only use readSaveGameHeader() here, which doesn't need an engine callback + ZVision::SaveManager *zvisionSaveMan = new ZVision::SaveManager(NULL); + bool successfulRead = zvisionSaveMan->readSaveGameHeader(in, header); + delete zvisionSaveMan; + delete in; - if (error == ZVision::ZVision::kRSHENoError) { - SaveStateDescriptor desc(slot, header.description); + if (successfulRead) { + SaveStateDescriptor desc(slot, header.saveName); - desc.setThumbnail(header.thumbnail); + // Do not allow save slot 0 (used for auto-saving) to be deleted or + // overwritten. + desc.setDeletableFlag(slot != 0); + desc.setWriteProtectedFlag(slot == 0); - if (header.version > 0) { - int day = (header.saveDate >> 24) & 0xFF; - int month = (header.saveDate >> 16) & 0xFF; - int year = header.saveDate & 0xFFFF; + desc.setThumbnail(header.thumbnail); - desc.setSaveDate(year, month, day); + if (header.version > 0) { + int day = header.saveDay; + int month = header.saveMonth; + int year = header.saveYear; - int hour = (header.saveTime >> 16) & 0xFF; - int minutes = (header.saveTime >> 8) & 0xFF; + desc.setSaveDate(year, month, day); - desc.setSaveTime(hour, minutes); + int hour = header.saveHour; + int minutes = header.saveMinutes; - desc.setPlayTime(header.playTime * 1000); - } + desc.setSaveTime(hour, minutes); - return desc; - } + //desc.setPlayTime(header.playTime * 1000); + } + + return desc; + } } - */ return SaveStateDescriptor(); } #if PLUGIN_ENABLED_DYNAMIC(ZVISION) - REGISTER_PLUGIN_DYNAMIC(ZVISION, PLUGIN_TYPE_ENGINE, ZVisionMetaEngine); +REGISTER_PLUGIN_DYNAMIC(ZVISION, PLUGIN_TYPE_ENGINE, ZVisionMetaEngine); #else - REGISTER_PLUGIN_STATIC(ZVISION, PLUGIN_TYPE_ENGINE, ZVisionMetaEngine); +REGISTER_PLUGIN_STATIC(ZVISION, PLUGIN_TYPE_ENGINE, ZVisionMetaEngine); #endif diff --git a/engines/zvision/detection.h b/engines/zvision/detection.h index a788e710b7..f80cac79ec 100644 --- a/engines/zvision/detection.h +++ b/engines/zvision/detection.h @@ -25,7 +25,6 @@ #include "engines/advancedDetector.h" - namespace ZVision { enum ZVisionGameId { diff --git a/engines/zvision/utility/lzss_read_stream.cpp b/engines/zvision/file/lzss_read_stream.cpp index e094188ef6..ca10af7d72 100644 --- a/engines/zvision/utility/lzss_read_stream.cpp +++ b/engines/zvision/file/lzss_read_stream.cpp @@ -22,18 +22,18 @@ #include "common/scummsys.h" -#include "zvision/utility/lzss_read_stream.h" - +#include "zvision/file/lzss_read_stream.h" namespace ZVision { LzssReadStream::LzssReadStream(Common::SeekableReadStream *source) - : _source(source), - // It's convention to set the starting cursor position to blockSize - 16 - _windowCursor(0x0FEE), - _eosFlag(false) { - // Clear the window to null - memset(_window, 0, BLOCK_SIZE); + : _source(source), + // It's convention to set the starting cursor position to blockSize - 16 + _windowCursor(0x0FEE), + _eosFlag(false) { + // All values up to _windowCursor inits by 0x20 + memset(_window, 0x20, _windowCursor); + memset(_window + _windowCursor, 0, BLOCK_SIZE - _windowCursor); } uint32 LzssReadStream::decompressBytes(byte *destination, uint32 numberOfBytes) { @@ -69,9 +69,9 @@ uint32 LzssReadStream::decompressBytes(byte *destination, uint32 numberOfBytes) } uint16 length = (high & 0xF) + 2; - uint16 offset = low | ((high & 0xF0)<<4); + uint16 offset = low | ((high & 0xF0) << 4); - for(int j = 0; j <= length; ++j) { + for (int j = 0; j <= length; ++j) { byte temp = _window[(offset + j) & 0xFFF]; _window[_windowCursor] = temp; destination[destinationCursor++] = temp; diff --git a/engines/zvision/utility/lzss_read_stream.h b/engines/zvision/file/lzss_read_stream.h index b51cf3905f..1420621f13 100644 --- a/engines/zvision/utility/lzss_read_stream.h +++ b/engines/zvision/file/lzss_read_stream.h @@ -26,7 +26,6 @@ #include "common/stream.h" #include "common/array.h" - namespace Common { class SeekableReadStream; } @@ -64,7 +63,7 @@ private: * * @param numberOfBytes How many bytes to decompress. This is a count of source bytes, not destination bytes */ - uint32 decompressBytes(byte* destination, uint32 numberOfBytes); + uint32 decompressBytes(byte *destination, uint32 numberOfBytes); }; } diff --git a/engines/zvision/file/save_manager.cpp b/engines/zvision/file/save_manager.cpp new file mode 100644 index 0000000000..63b54269de --- /dev/null +++ b/engines/zvision/file/save_manager.cpp @@ -0,0 +1,292 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/scummsys.h" + +#include "zvision/zvision.h" +#include "zvision/file/save_manager.h" +#include "zvision/scripting/script_manager.h" +#include "zvision/graphics/render_manager.h" + +#include "common/system.h" +#include "common/translation.h" + +#include "graphics/surface.h" +#include "graphics/thumbnail.h" + +#include "gui/message.h" +#include "gui/saveload.h" + +namespace ZVision { + +const uint32 SaveManager::SAVEGAME_ID = MKTAG('Z', 'E', 'N', 'G'); + +bool SaveManager::scummVMSaveLoadDialog(bool isSave) { + GUI::SaveLoadChooser *dialog; + Common::String desc; + int slot; + + if (isSave) { + dialog = new GUI::SaveLoadChooser(_("Save game:"), _("Save"), true); + + slot = dialog->runModalWithCurrentTarget(); + desc = dialog->getResultString(); + + if (desc.empty()) { + // create our own description for the saved game, the user didnt enter it + desc = dialog->createDefaultSaveDescription(slot); + } + + if (desc.size() > 28) + desc = Common::String(desc.c_str(), 28); + } else { + dialog = new GUI::SaveLoadChooser(_("Restore game:"), _("Restore"), false); + slot = dialog->runModalWithCurrentTarget(); + } + + delete dialog; + + if (slot < 0) + return false; + + if (isSave) { + saveGame(slot, desc, false); + return true; + } else { + Common::ErrorCode result = loadGame(slot).getCode(); + return (result == Common::kNoError); + } +} + +void SaveManager::saveGame(uint slot, const Common::String &saveName, bool useSaveBuffer) { + if (!_tempSave && useSaveBuffer) + return; + + Common::SaveFileManager *saveFileManager = g_system->getSavefileManager(); + Common::OutSaveFile *file = saveFileManager->openForSaving(_engine->generateSaveFileName(slot)); + + writeSaveGameHeader(file, saveName, useSaveBuffer); + + if (useSaveBuffer) + file->write(_tempSave->getData(), _tempSave->size()); + else + _engine->getScriptManager()->serialize(file); + + file->finalize(); + delete file; + + if (useSaveBuffer) + flushSaveBuffer(); + + _lastSaveTime = g_system->getMillis(); +} + +void SaveManager::autoSave() { + saveGame(0, "Auto save", false); +} + +void SaveManager::writeSaveGameHeader(Common::OutSaveFile *file, const Common::String &saveName, bool useSaveBuffer) { + file->writeUint32BE(SAVEGAME_ID); + + // Write version + file->writeByte(SAVE_VERSION); + + // Write savegame name + file->writeString(saveName); + file->writeByte(0); + + // Save the game thumbnail + if (useSaveBuffer) + file->write(_tempThumbnail->getData(), _tempThumbnail->size()); + else + Graphics::saveThumbnail(*file); + + // Write out the save date/time + TimeDate td; + g_system->getTimeAndDate(td); + file->writeSint16LE(td.tm_year + 1900); + file->writeSint16LE(td.tm_mon + 1); + file->writeSint16LE(td.tm_mday); + file->writeSint16LE(td.tm_hour); + file->writeSint16LE(td.tm_min); +} + +Common::Error SaveManager::loadGame(int slot) { + Common::SeekableReadStream *saveFile = NULL; + + if (slot >= 0) { + saveFile = getSlotFile(slot); + } else { + saveFile = _engine->getSearchManager()->openFile("r.svr"); + if (!saveFile) { + Common::File *restoreFile = new Common::File(); + if (!restoreFile->open("r.svr")) { + delete restoreFile; + return Common::kPathDoesNotExist; + } + + saveFile = restoreFile; + } + } + + if (!saveFile) + return Common::kPathDoesNotExist; + + // Read the header + SaveGameHeader header; + if (!readSaveGameHeader(saveFile, header)) { + return Common::kUnknownError; + } + + ScriptManager *scriptManager = _engine->getScriptManager(); + // Update the state table values + scriptManager->deserialize(saveFile); + + delete saveFile; + if (header.thumbnail) + delete header.thumbnail; + + if (_engine->getGameId() == GID_NEMESIS && scriptManager->getCurrentLocation() == "tv2f") { + // WORKAROUND for script bug #6793: location tv2f (stairs) has two states: + // one at the top of the stairs, and one at the bottom. When the player + // goes to the bottom of the stairs, the screen changes, and hotspot + // 4652 (exit opposite the stairs) is enabled. However, the variable that + // controls the state (2408) is reset when the player goes down the stairs. + // Furthermore, the room's initialization script disables the stair exit + // control (4652). This leads to an impossible situation, where all the + // exit controls are disabled, and the player can't more anywhere. Thus, + // when loading a game in that room, we check for that impossible + // situation, which only occurs after the player has moved down the stairs, + // and fix it here by setting the correct background, and enabling the + // stair exit hotspot. + if ((scriptManager->getStateFlag(2411) & Puzzle::DISABLED) && + (scriptManager->getStateFlag(2408) & Puzzle::DISABLED) && + (scriptManager->getStateFlag(4652) & Puzzle::DISABLED)) { + _engine->getRenderManager()->setBackgroundImage("tv2fb21c.tga"); + scriptManager->unsetStateFlag(4652, Puzzle::DISABLED); + } + } + + return Common::kNoError; +} + +bool SaveManager::readSaveGameHeader(Common::InSaveFile *in, SaveGameHeader &header) { + uint32 tag = in->readUint32BE(); + // Check if it's original savegame than fill header structure + if (tag == MKTAG('Z', 'N', 'S', 'G')) { + header.saveYear = 0; + header.saveMonth = 0; + header.saveDay = 0; + header.saveHour = 0; + header.saveMinutes = 0; + header.saveName = "Original Save"; + header.thumbnail = NULL; + header.version = SAVE_ORIGINAL; + in->seek(-4, SEEK_CUR); + return true; + } + if (tag != SAVEGAME_ID) { + warning("File is not a ZVision save file. Aborting load"); + return false; + } + + // Read in the version + header.version = in->readByte(); + + // Check that the save version isn't newer than this binary + if (header.version > SAVE_VERSION) { + uint tempVersion = header.version; + GUI::MessageDialog dialog( + Common::String::format( + "This save file uses version %u, but this engine only " + "supports up to version %d. You will need an updated version " + "of the engine to use this save file.", tempVersion, SAVE_VERSION + ), + "OK"); + dialog.runModal(); + } + + // Read in the save name + header.saveName.clear(); + char ch; + while ((ch = (char)in->readByte()) != '\0') + header.saveName += ch; + + // Get the thumbnail + header.thumbnail = Graphics::loadThumbnail(*in); + if (!header.thumbnail) + return false; + + // Read in save date/time + header.saveYear = in->readSint16LE(); + header.saveMonth = in->readSint16LE(); + header.saveDay = in->readSint16LE(); + header.saveHour = in->readSint16LE(); + header.saveMinutes = in->readSint16LE(); + + return true; +} + +Common::SeekableReadStream *SaveManager::getSlotFile(uint slot) { + Common::SeekableReadStream *saveFile = g_system->getSavefileManager()->openForLoading(_engine->generateSaveFileName(slot)); + if (saveFile == NULL) { + // Try to load standard save file + Common::String filename; + if (_engine->getGameId() == GID_GRANDINQUISITOR) + filename = Common::String::format("inqsav%u.sav", slot); + else if (_engine->getGameId() == GID_NEMESIS) + filename = Common::String::format("nemsav%u.sav", slot); + + saveFile = _engine->getSearchManager()->openFile(filename); + if (saveFile == NULL) { + Common::File *tmpFile = new Common::File; + if (!tmpFile->open(filename)) { + delete tmpFile; + } else { + saveFile = tmpFile; + } + } + + } + + return saveFile; +} + +void SaveManager::prepareSaveBuffer() { + delete _tempThumbnail; + _tempThumbnail = new Common::MemoryWriteStreamDynamic; + Graphics::saveThumbnail(*_tempThumbnail); + + delete _tempSave; + _tempSave = new Common::MemoryWriteStreamDynamic; + _engine->getScriptManager()->serialize(_tempSave); +} + +void SaveManager::flushSaveBuffer() { + delete _tempThumbnail; + _tempThumbnail = NULL; + + delete _tempSave; + _tempSave = NULL; +} + +} // End of namespace ZVision diff --git a/engines/zvision/core/save_manager.h b/engines/zvision/file/save_manager.h index 43fb0c0faf..9e816373ea 100644 --- a/engines/zvision/core/save_manager.h +++ b/engines/zvision/file/save_manager.h @@ -24,6 +24,7 @@ #define ZVISION_SAVE_MANAGER_H #include "common/savefile.h" +#include "common/memstream.h" namespace Common { class String; @@ -47,21 +48,32 @@ struct SaveGameHeader { class SaveManager { public: - SaveManager(ZVision *engine) : _engine(engine) {} + SaveManager(ZVision *engine) : _engine(engine), _tempSave(NULL), _tempThumbnail(NULL), _lastSaveTime(0) {} + ~SaveManager() { + flushSaveBuffer(); + } + + uint32 getLastSaveTime() const { + return _lastSaveTime; + } private: ZVision *_engine; + uint32 _lastSaveTime; static const uint32 SAVEGAME_ID; enum { + SAVE_ORIGINAL = 0, SAVE_VERSION = 1 }; + Common::MemoryWriteStreamDynamic *_tempThumbnail; + Common::MemoryWriteStreamDynamic *_tempSave; + public: /** * Called every room change. Saves the state of the room just before - * we switched rooms. Uses ZVision::generateAutoSaveFileName() to - * create the save file name. + * the room changes. */ void autoSave(); /** @@ -72,18 +84,23 @@ public: * @param slot The save slot this save pertains to. Must be [1, 20] * @param saveName The internal name for this save. This is NOT the name of the actual save file. */ - void saveGame(uint slot, const Common::String &saveName); + void saveGame(uint slot, const Common::String &saveName, bool useSaveBuffer); /** * Loads the state data from the save file that slot references. Uses * ZVision::generateSaveFileName(slot) to get the save file name. * * @param slot The save slot to load. Must be [1, 20] */ - Common::Error loadGame(uint slot); + Common::Error loadGame(int slot); + + Common::SeekableReadStream *getSlotFile(uint slot); + bool readSaveGameHeader(Common::SeekableReadStream *in, SaveGameHeader &header); + void prepareSaveBuffer(); + void flushSaveBuffer(); + bool scummVMSaveLoadDialog(bool isSave); private: - void writeSaveGameData(Common::OutSaveFile *file); - bool readSaveGameHeader(Common::InSaveFile *in, SaveGameHeader &header); + void writeSaveGameHeader(Common::OutSaveFile *file, const Common::String &saveName, bool useSaveBuffer); }; } // End of namespace ZVision diff --git a/engines/zvision/file/search_manager.cpp b/engines/zvision/file/search_manager.cpp new file mode 100644 index 0000000000..821b85b053 --- /dev/null +++ b/engines/zvision/file/search_manager.cpp @@ -0,0 +1,285 @@ +/* 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/debug.h" +#include "common/fs.h" +#include "common/stream.h" + +#include "zvision/file/search_manager.h" +#include "zvision/file/zfs_archive.h" + +namespace ZVision { + +SearchManager::SearchManager(const Common::String &rootPath, int depth) { + _root = rootPath; + if (_root[_root.size() - 1] == '\\' || _root[_root.size() - 1] == '/') + _root.deleteLastChar(); + + Common::FSNode fsNode(_root); + + listDirRecursive(_dirList, fsNode, depth); + + for (Common::List<Common::String>::iterator it = _dirList.begin(); it != _dirList.end();) { + if ((*it).hasSuffix("\\") || (*it).hasSuffix("/")) + (*it).deleteLastChar(); + + if (it->size() == _root.size()) + it = _dirList.erase(it); + else if (it->size() > _root.size()) { + *it = Common::String(it->c_str() + _root.size() + 1); + it++; + } else + it++; + } +} + +SearchManager::~SearchManager() { + Common::List<Common::Archive *>::iterator it = _archList.begin(); + while (it != _archList.end()) { + delete *it; + it++; + } + + _archList.clear(); +} + +void SearchManager::addFile(const Common::String &name, Common::Archive *arch) { + bool addArch = true; + Common::List<Common::Archive *>::iterator it = _archList.begin(); + while (it != _archList.end()) { + if (*it == arch) { + addArch = false; + break; + } + it++; + } + if (addArch) + _archList.push_back(arch); + + Common::String lowerCaseName = name; + lowerCaseName.toLowercase(); + + SearchManager::Node nod; + nod.name = lowerCaseName; + nod.arch = arch; + + SearchManager::MatchList::iterator fit = _files.find(lowerCaseName); + + if (fit == _files.end()) { + _files[lowerCaseName] = nod; + } else { + Common::SeekableReadStream *stream = fit->_value.arch->createReadStreamForMember(fit->_value.name); + if (stream) { + if (stream->size() < 10) + fit->_value.arch = arch; + delete stream; + } else { + _files[lowerCaseName] = nod; + } + } +} + +Common::File *SearchManager::openFile(const Common::String &name) { + Common::String lowerCaseName = name; + lowerCaseName.toLowercase(); + + SearchManager::MatchList::iterator fit = _files.find(lowerCaseName); + + if (fit != _files.end()) { + Common::File *tmp = new Common::File(); + tmp->open(fit->_value.name, *fit->_value.arch); + return tmp; + } + return NULL; +} + +bool SearchManager::openFile(Common::File &file, const Common::String &name) { + Common::String lowerCaseName = name; + lowerCaseName.toLowercase(); + + SearchManager::MatchList::iterator fit = _files.find(lowerCaseName); + + if (fit != _files.end()) + return file.open(fit->_value.name, *fit->_value.arch); + return false; +} + +bool SearchManager::hasFile(const Common::String &name) { + Common::String lowerCaseName = name; + lowerCaseName.toLowercase(); + + SearchManager::MatchList::iterator fit = _files.find(lowerCaseName); + + if (fit != _files.end()) + return true; + return false; +} + +bool SearchManager::loadZix(const Common::String &name) { + Common::File file; + if (!file.open(name)) + return false; + + Common::String line; + + while (!file.eos()) { + line = file.readLine(); + if (line.matchString("----------*", true)) + break; + } + + if (file.eos()) + error("Corrupt ZIX file: %s", name.c_str()); + + Common::Array<Common::Archive *> archives; + + while (!file.eos()) { + line = file.readLine(); + line.trim(); + if (line.matchString("----------*", true)) + break; + else if (line.matchString("DIR:*", true) || line.matchString("CD0:*", true) || line.matchString("CD1:*", true) || line.matchString("CD2:*", true)) { + Common::Archive *arc; + + Common::String path(line.c_str() + 5); + for (uint i = 0; i < path.size(); i++) + if (path[i] == '\\') + path.setChar('/', i); + + // Check if NEMESIS.ZIX/MEDIUM.ZIX refers to the znemesis folder, and + // check the game root folder instead + if (path.hasPrefix("znemesis/")) + path = Common::String(path.c_str() + 9); + + // Check if INQUIS.ZIX refers to the ZGI folder, and check the game + // root folder instead + if (path.hasPrefix("zgi/")) + path = Common::String(path.c_str() + 4); + if (path.hasPrefix("zgi_e/")) + path = Common::String(path.c_str() + 6); + + if (path.size() && path[0] == '.') + path.deleteChar(0); + if (path.size() && path[0] == '/') + path.deleteChar(0); + if (path.size() && path.hasSuffix("/")) + path.deleteLastChar(); + + // Handle paths in case-sensitive file systems (bug #6775) + if (path.size()) { + for (Common::List<Common::String>::iterator it = _dirList.begin(); it != _dirList.end(); ++it) { + if (path.equalsIgnoreCase(*it)) { + path = *it; + break; + } + } + } + + if (path.matchString("*.zfs", true)) { + arc = new ZfsArchive(path); + } else { + path = Common::String::format("%s/%s", _root.c_str(), path.c_str()); + arc = new Common::FSDirectory(path); + } + archives.push_back(arc); + } + } + + if (file.eos()) + error("Corrupt ZIX file: %s", name.c_str()); + + while (!file.eos()) { + line = file.readLine(); + line.trim(); + uint dr = 0; + char buf[32]; + if (sscanf(line.c_str(), "%u %s", &dr, buf) == 2) { + if (dr <= archives.size() && dr > 0) { + addFile(Common::String(buf), archives[dr - 1]); + } + } + } + + return true; +} + +void SearchManager::addDir(const Common::String &name) { + Common::String path; + for (Common::List<Common::String>::iterator it = _dirList.begin(); it != _dirList.end(); ++it) + if (name.equalsIgnoreCase(*it)) { + path = *it; + break; + } + + if (path.size() == 0) + return; + + path = Common::String::format("%s/%s", _root.c_str(), path.c_str()); + + Common::FSDirectory *dir = new Common::FSDirectory(path); + + Common::ArchiveMemberList list; + dir->listMatchingMembers(list, "*.zfs"); + + for (Common::ArchiveMemberList::iterator iter = list.begin(); iter != list.end(); ++iter) { + Common::String flname = (*iter)->getName(); + + ZfsArchive *zfs = new ZfsArchive(Common::String::format("%s/%s", name.c_str(), flname.c_str())); + + Common::ArchiveMemberList zfslist; + zfs->listMembers(zfslist); + + for (Common::ArchiveMemberList::iterator ziter = zfslist.begin(); ziter != zfslist.end(); ++ziter) { + Common::String zfsFileName = (*ziter)->getName(); + addFile(zfsFileName, zfs); + } + } + + list.clear(); + dir->listMembers(list); + + for (Common::ArchiveMemberList::iterator iter = list.begin(); iter != list.end(); ++iter) { + Common::String flname = (*iter)->getName(); + addFile(flname, dir); + } +} + +void SearchManager::listDirRecursive(Common::List<Common::String> &_list, const Common::FSNode &fsNode, int depth) { + Common::FSList fsList; + if (fsNode.getChildren(fsList)) { + + _list.push_back(fsNode.getPath()); + + if (depth > 1) + for (Common::FSList::const_iterator it = fsList.begin(); it != fsList.end(); ++it) + listDirRecursive(_list, *it, depth - 1); + } +} + +void SearchManager::listMembersWithExtension(MatchList &fileList, Common::String extension) { + for (SearchManager::MatchList::iterator it = _files.begin(); it != _files.end(); ++it) { + if (it->_key.hasSuffix(extension)) + fileList[it->_key] = it->_value; + } +} + +} // End of namespace ZVision diff --git a/engines/zvision/file/search_manager.h b/engines/zvision/file/search_manager.h new file mode 100644 index 0000000000..0d0ab14d31 --- /dev/null +++ b/engines/zvision/file/search_manager.h @@ -0,0 +1,70 @@ +/* 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 ZVISION_SEARCH_MANAGER_H +#define ZVISION_SEARCH_MANAGER_H + +#include "common/str.h" +#include "common/hash-str.h" +#include "common/hashmap.h" +#include "common/archive.h" +#include "common/file.h" +#include "common/list.h" + +namespace ZVision { + +class SearchManager { +public: + SearchManager(const Common::String &rootPath, int depth); + ~SearchManager(); + + void addFile(const Common::String &name, Common::Archive *arch); + void addDir(const Common::String &name); + + Common::File *openFile(const Common::String &name); + bool openFile(Common::File &file, const Common::String &name); + bool hasFile(const Common::String &name); + + bool loadZix(const Common::String &name); + + struct Node { + Common::String name; + Common::Archive *arch; + }; + + typedef Common::HashMap<Common::String, Node, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> MatchList; + + void listMembersWithExtension(MatchList &fileList, Common::String extension); + +private: + + void listDirRecursive(Common::List<Common::String> &dirList, const Common::FSNode &fsNode, int depth); + + Common::List<Common::String> _dirList; + Common::List<Common::Archive *> _archList; + Common::String _root; + MatchList _files; +}; + +} + +#endif // ZVISION_SEARCH_MANAGER_H diff --git a/engines/zvision/archives/zfs_archive.cpp b/engines/zvision/file/zfs_archive.cpp index f5fa6fc9bf..3a385cd8fd 100644 --- a/engines/zvision/archives/zfs_archive.cpp +++ b/engines/zvision/file/zfs_archive.cpp @@ -21,17 +21,17 @@ */ #include "common/scummsys.h" - -#include "zvision/archives/zfs_archive.h" - #include "common/memstream.h" #include "common/debug.h" #include "common/file.h" +#include "zvision/file/zfs_archive.h" + namespace ZVision { ZfsArchive::ZfsArchive(const Common::String &fileName) : _fileName(fileName) { Common::File zfsFile; + memset(&_header, 0, sizeof(_header)); if (!zfsFile.open(_fileName)) { warning("ZFSArchive::ZFSArchive(): Could not find the archive file"); @@ -52,7 +52,7 @@ ZfsArchive::ZfsArchive(const Common::String &fileName, Common::SeekableReadStrea ZfsArchive::~ZfsArchive() { debug(1, "ZfsArchive Destructor Called"); ZfsEntryHeaderMap::iterator it = _entryHeaders.begin(); - for ( ; it != _entryHeaders.end(); ++it) { + for (; it != _entryHeaders.end(); ++it) { delete it->_value; } } @@ -79,7 +79,7 @@ void ZfsArchive::readHeaders(Common::SeekableReadStream *stream) { // Read in each entry header for (uint32 i = 0; i < _header.filesPerBlock; ++i) { ZfsEntryHeader entryHeader; - + entryHeader.name = readEntryName(stream); entryHeader.offset = stream->readUint32LE(); entryHeader.id = stream->readUint32LE(); @@ -138,10 +138,10 @@ Common::SeekableReadStream *ZfsArchive::createReadStreamForMember(const Common:: zfsArchive.seek(entryHeader->offset); // This *HAS* to be malloc (not new[]) because MemoryReadStream uses free() to free the memory - byte* buffer = (byte *)malloc(entryHeader->size); + byte *buffer = (byte *)malloc(entryHeader->size); zfsArchive.read(buffer, entryHeader->size); // Decrypt the data in place - if (_header.xorKey != 0) + if (_header.xorKey[0] + _header.xorKey[1] + _header.xorKey[2] + _header.xorKey[3] != 0) unXor(buffer, entryHeader->size, _header.xorKey); return new Common::MemoryReadStream(buffer, entryHeader->size, DisposeAfterUse::YES); @@ -153,5 +153,3 @@ void ZfsArchive::unXor(byte *buffer, uint32 length, const byte *xorKey) const { } } // End of namespace ZVision - - diff --git a/engines/zvision/archives/zfs_archive.h b/engines/zvision/file/zfs_archive.h index 3509cfee26..fe0221416d 100644 --- a/engines/zvision/archives/zfs_archive.h +++ b/engines/zvision/file/zfs_archive.h @@ -27,7 +27,6 @@ #include "common/hashmap.h" #include "common/hash-str.h" - namespace Common { class String; } @@ -40,7 +39,7 @@ struct ZfsHeader { uint32 maxNameLength; uint32 filesPerBlock; uint32 fileCount; - byte xorKey[4]; + uint8 xorKey[4]; uint32 fileSectionOffset; }; @@ -53,7 +52,7 @@ struct ZfsEntryHeader { uint32 unknown; }; -typedef Common::HashMap<Common::String, ZfsEntryHeader*, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> ZfsEntryHeaderMap; +typedef Common::HashMap<Common::String, ZfsEntryHeader *, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> ZfsEntryHeaderMap; class ZfsArchive : public Common::Archive { public: diff --git a/engines/zvision/fonts/truetype_font.cpp b/engines/zvision/fonts/truetype_font.cpp deleted file mode 100644 index ba4d72bde8..0000000000 --- a/engines/zvision/fonts/truetype_font.cpp +++ /dev/null @@ -1,113 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#include "common/scummsys.h" - -#include "zvision/fonts/truetype_font.h" - -#include "zvision/zvision.h" -#include "zvision/graphics/render_manager.h" - -#include "common/debug.h" -#include "common/file.h" -#include "common/system.h" - -#include "graphics/font.h" -#include "graphics/fonts/ttf.h" -#include "graphics/surface.h" - - -namespace ZVision { - -TruetypeFont::TruetypeFont(ZVision *engine, int32 fontHeight) - : _fontHeight(fontHeight), - _font(0), - _lineHeight(0) { -} - -TruetypeFont::~TruetypeFont(void) { - delete _font; -} - -bool TruetypeFont::loadFile(const Common::String &filename) { - Common::File file; - - bool fileOpened = false; - if (!Common::File::exists(filename)) { - debug("TTF font file %s was not found. Reverting to arial.ttf", filename.c_str()); - fileOpened = file.open("arial.ttf"); - } else { - fileOpened = file.open(filename); - } - - if (!fileOpened) { - debug("TTF file could not be opened"); - return false; - } - - _font = Graphics::loadTTFFont(file, _fontHeight); - _lineHeight = _font->getFontHeight(); - - return true; -} - -Graphics::Surface *TruetypeFont::drawTextToSurface(const Common::String &text, uint16 textColor, int maxWidth, int maxHeight, Graphics::TextAlign align, bool wrap) { - if (text.equals("")) { - return nullptr; - } - - Graphics::Surface *surface = new Graphics::Surface(); - - if (!wrap) { - int width = MIN(_font->getStringWidth(text), maxWidth); - surface->create(width, _lineHeight, Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0)); - // TODO: Add better alpha support by getting the pixels from the backbuffer. - // However doing that requires some kind of caching system so future text doesn't try to use this text as it's alpha background. - surface->fillRect(Common::Rect(0, 0, surface->w, surface->h), 0); - - _font->drawString(surface, text, 0, 0, maxWidth, textColor, align); - return surface; - } - - Common::Array<Common::String> lines; - _font->wordWrapText(text, maxWidth, lines); - - while (maxHeight > 0 && (int)lines.size() * _lineHeight > maxHeight) { - lines.pop_back(); - } - if (lines.size() == 0) { - return nullptr; - } - - surface->create(maxWidth, lines.size() * _lineHeight, Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0)); - surface->fillRect(Common::Rect(0, 0, surface->w, surface->h), 0); - - int heightOffset = 0; - for (Common::Array<Common::String>::iterator it = lines.begin(); it != lines.end(); it++) { - _font->drawString(surface, *it, 0, 0 + heightOffset, maxWidth, textColor, align); - heightOffset += _lineHeight; - } - - return surface; -} - -} // End of namespace ZVision diff --git a/engines/zvision/graphics/cursors/cursor.cpp b/engines/zvision/graphics/cursors/cursor.cpp new file mode 100644 index 0000000000..2c011668ac --- /dev/null +++ b/engines/zvision/graphics/cursors/cursor.cpp @@ -0,0 +1,96 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* of the License, or (at your option) any later version. + +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. + +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +*/ + +#include "common/scummsys.h" + +#include "zvision/graphics/cursors/cursor.h" + +#include "common/str.h" +#include "common/file.h" + +namespace ZVision { + +ZorkCursor::ZorkCursor() + : _width(0), + _height(0), + _hotspotX(0), + _hotspotY(0) { +} + +ZorkCursor::ZorkCursor(ZVision *engine, const Common::String &fileName) + : _width(0), + _height(0), + _hotspotX(0), + _hotspotY(0) { + Common::File file; + if (!engine->getSearchManager()->openFile(file, fileName)) + error("Cursor file %s does not exist", fileName.c_str()); + + uint32 magic = file.readUint32BE(); + if (magic != MKTAG('Z', 'C', 'R', '1')) { + warning("%s is not a Zork Cursor file", fileName.c_str()); + return; + } + + _hotspotX = file.readUint16LE(); + _hotspotY = file.readUint16LE(); + _width = file.readUint16LE(); + _height = file.readUint16LE(); + + uint dataSize = _width * _height * sizeof(uint16); + _surface.create(_width, _height, engine->_resourcePixelFormat); + uint32 bytesRead = file.read(_surface.getPixels(), dataSize); + assert(bytesRead == dataSize); + +#ifndef SCUMM_LITTLE_ENDIAN + int16 *buffer = (int16 *)_surface.getPixels(); + for (uint32 i = 0; i < dataSize / 2; ++i) + buffer[i] = FROM_LE_16(buffer[i]); +#endif +} + +ZorkCursor::ZorkCursor(const ZorkCursor &other) { + _width = other._width; + _height = other._height; + _hotspotX = other._hotspotX; + _hotspotY = other._hotspotY; + + _surface.copyFrom(other._surface); +} + +ZorkCursor &ZorkCursor::operator=(const ZorkCursor &other) { + _width = other._width; + _height = other._height; + _hotspotX = other._hotspotX; + _hotspotY = other._hotspotY; + + _surface.free(); + _surface.copyFrom(other._surface); + + return *this; +} + +ZorkCursor::~ZorkCursor() { + _surface.free(); +} + +} // End of namespace ZVision diff --git a/engines/zvision/cursors/cursor.h b/engines/zvision/graphics/cursors/cursor.h index be9fae64da..6e0083520a 100644 --- a/engines/zvision/cursors/cursor.h +++ b/engines/zvision/graphics/cursors/cursor.h @@ -24,7 +24,7 @@ #define ZVISION_CURSOR_H #include "graphics/surface.h" - +#include "zvision/zvision.h" namespace Common { class String; @@ -39,7 +39,7 @@ namespace ZVision { class ZorkCursor { public: ZorkCursor(); - ZorkCursor(const Common::String &fileName); + ZorkCursor(ZVision *engine, const Common::String &fileName); ZorkCursor(const ZorkCursor &other); ~ZorkCursor(); @@ -53,12 +53,24 @@ private: public: ZorkCursor &operator=(const ZorkCursor &other); - uint16 getWidth() const { return _width; } - uint16 getHeight() const { return _height; } - uint16 getHotspotX() const { return _hotspotX; } - uint16 getHotspotY() const { return _hotspotY; } - byte getKeyColor() const { return 0; } - const byte *getSurface() const { return (const byte *)_surface.getPixels(); } + uint16 getWidth() const { + return _width; + } + uint16 getHeight() const { + return _height; + } + uint16 getHotspotX() const { + return _hotspotX; + } + uint16 getHotspotY() const { + return _hotspotY; + } + byte getKeyColor() const { + return 0; + } + const byte *getSurface() const { + return (const byte *)_surface.getPixels(); + } }; } // End of namespace ZVision diff --git a/engines/zvision/graphics/cursors/cursor_manager.cpp b/engines/zvision/graphics/cursors/cursor_manager.cpp new file mode 100644 index 0000000000..eeab18f4ba --- /dev/null +++ b/engines/zvision/graphics/cursors/cursor_manager.cpp @@ -0,0 +1,158 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* of the License, or (at your option) any later version. + +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. + +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +*/ + +#include "common/scummsys.h" + +#include "zvision/graphics/cursors/cursor_manager.h" + +#include "zvision/zvision.h" + +#include "common/system.h" + +#include "graphics/pixelformat.h" +#include "graphics/cursorman.h" + +namespace ZVision { + +const char *CursorManager::_cursorNames[NUM_CURSORS] = { "active", "arrow", "backward", "downarrow", "forward", "handpt", "handpu", "hdown", "hleft", + "hright", "hup", "idle", "leftarrow", "rightarrow", "suggest_surround", "suggest_tilt", "turnaround", "zuparrow" + }; + +const char *CursorManager::_zgiCursorFileNames[NUM_CURSORS] = { "g0gbc011.zcr", "g0gac011.zcr", "g0gac021.zcr", "g0gac031.zcr", "g0gac041.zcr", "g0gac051.zcr", "g0gac061.zcr", "g0gac071.zcr", "g0gac081.zcr", + "g0gac091.zcr", "g0gac101.zcr", "g0gac011.zcr", "g0gac111.zcr", "g0gac121.zcr", "g0gac131.zcr", "g0gac141.zcr", "g0gac151.zcr", "g0gac161.zcr" + }; + +const char *CursorManager::_zNemCursorFileNames[NUM_CURSORS] = { "00act", "arrow", "back", "down", "forw", "handpt", "handpu", "hdown", "hleft", + "hright", "hup", "00idle", "left", "right", "ssurr", "stilt", "turn", "up" + }; + +CursorManager::CursorManager(ZVision *engine, const Graphics::PixelFormat pixelFormat) + : _engine(engine), + _pixelFormat(pixelFormat), + _cursorIsPushed(false), + _item(0), + _lastitem(0), + _currentCursor(CursorIndex_Idle) { + for (int i = 0; i < NUM_CURSORS; i++) { + if (_engine->getGameId() == GID_NEMESIS) { + Common::String name; + if (i == 1) { + // Cursors "arrowa.zcr" and "arrowb.zcr" are missing + _cursors[i][0] = _cursors[i][1] = ZorkCursor(); + continue; + } + name = Common::String::format("%sa.zcr", _zNemCursorFileNames[i]); + _cursors[i][0] = ZorkCursor(_engine, name); // Up cursor + name = Common::String::format("%sb.zcr", _zNemCursorFileNames[i]); + _cursors[i][1] = ZorkCursor(_engine, name); // Down cursor + } else if (_engine->getGameId() == GID_GRANDINQUISITOR) { + _cursors[i][0] = ZorkCursor(_engine, _zgiCursorFileNames[i]); // Up cursor + char buffer[25]; + memset(buffer, 0, 25); + strncpy(buffer, _zgiCursorFileNames[i], 24); + buffer[3] += 2; + _cursors[i][1] = ZorkCursor(_engine, buffer); // Down cursor + } + } +} + +void CursorManager::setItemID(int id) { + if (id != _item) { + if (id) { + Common::String file; + if (_engine->getGameId() == GID_NEMESIS) { + file = Common::String::format("%2.2d%s%c.zcr", id, "idle", 'a'); + _cursors[NUM_CURSORS][0] = ZorkCursor(_engine, file); + file = Common::String::format("%2.2d%s%c.zcr", id, "idle", 'b'); + _cursors[NUM_CURSORS][1] = ZorkCursor(_engine, file); + file = Common::String::format("%2.2d%s%c.zcr", id, "act", 'a'); + _cursors[NUM_CURSORS + 1][0] = ZorkCursor(_engine, file); + file = Common::String::format("%2.2d%s%c.zcr", id, "act", 'b'); + _cursors[NUM_CURSORS + 1][0] = ZorkCursor(_engine, file); + } else if (_engine->getGameId() == GID_GRANDINQUISITOR) { + file = Common::String::format("g0b%cc%2.2x1.zcr", 'a' , id); + _cursors[NUM_CURSORS][0] = ZorkCursor(_engine, file); + file = Common::String::format("g0b%cc%2.2x1.zcr", 'c' , id); + _cursors[NUM_CURSORS][1] = ZorkCursor(_engine, file); + file = Common::String::format("g0b%cc%2.2x1.zcr", 'b' , id); + _cursors[NUM_CURSORS + 1][0] = ZorkCursor(_engine, file); + file = Common::String::format("g0b%cc%2.2x1.zcr", 'd' , id); + _cursors[NUM_CURSORS + 1][1] = ZorkCursor(_engine, file); + } else + return; + } + _item = id; + changeCursor(CursorIndex_Idle); + } +} + +void CursorManager::initialize() { + changeCursor(_cursors[CursorIndex_Idle][_cursorIsPushed]); + showMouse(true); +} + +void CursorManager::changeCursor(const ZorkCursor &cursor) { + CursorMan.replaceCursor(cursor.getSurface(), cursor.getWidth(), cursor.getHeight(), cursor.getHotspotX(), cursor.getHotspotY(), cursor.getKeyColor(), false, &_pixelFormat); +} + +void CursorManager::cursorDown(bool pushed) { + if (_cursorIsPushed == pushed) + return; + + _cursorIsPushed = pushed; + + changeCursor(_cursors[_currentCursor][_cursorIsPushed]); +} + +void CursorManager::changeCursor(int id) { + if (_item && (id == CursorIndex_Active || + id == CursorIndex_Idle || + id == CursorIndex_HandPu)) { + if (id == CursorIndex_Idle) { + id = CursorIndex_ItemIdle; + } else { + id = CursorIndex_ItemAct; + } + } + + if (_currentCursor != id || ((id == CursorIndex_ItemAct || id == CursorIndex_ItemIdle) && _lastitem != _item)) { + _currentCursor = id; + _lastitem = _item; + changeCursor(_cursors[_currentCursor][_cursorIsPushed]); + } +} + +int CursorManager::getCursorId(const Common::String &name) { + for (int i = 0; i < NUM_CURSORS; i++) { + if (name.equals(_cursorNames[i])) { + return i; + } + } + + return CursorIndex_Idle; +} + +void CursorManager::showMouse(bool vis) { + CursorMan.showMouse(vis); +} + +} // End of namespace ZVision diff --git a/engines/zvision/cursors/cursor_manager.h b/engines/zvision/graphics/cursors/cursor_manager.h index 0576517f58..35c605baf8 100644 --- a/engines/zvision/cursors/cursor_manager.h +++ b/engines/zvision/graphics/cursors/cursor_manager.h @@ -8,12 +8,12 @@ * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. - * + * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. @@ -23,11 +23,10 @@ #ifndef ZVISION_CURSOR_MANAGER_H #define ZVISION_CURSOR_MANAGER_H -#include "zvision/cursors/cursor.h" +#include "zvision/graphics/cursors/cursor.h" #include "common/str.h" - namespace Graphics { struct PixelFormat; } @@ -37,6 +36,21 @@ namespace ZVision { class ZVision; /** + * Mostly usable cursors + */ +enum CursorIndex { + CursorIndex_Active = 0, + CursorIndex_DownArr = 3, + CursorIndex_HandPu = 6, + CursorIndex_Idle = 11, + CursorIndex_Left = 12, + CursorIndex_Right = 13, + CursorIndex_UpArr = 17, + CursorIndex_ItemIdle = 18, + CursorIndex_ItemAct = 19 +}; + +/** * Class to manage cursor changes. The actual changes have to be done * through CursorMan. Otherwise the cursor will disappear after GMM * or debug console. @@ -44,20 +58,20 @@ class ZVision; */ class CursorManager { public: - CursorManager(ZVision *engine, const Graphics::PixelFormat *pixelFormat); + CursorManager(ZVision *engine, const Graphics::PixelFormat pixelFormat); private: - enum { - NUM_CURSORS = 18, - // WARNING: The index 11 is hardcoded. If you change the order of _cursorNames/_zgiCursorFileNames/_zNemCursorFileNames, you HAVE to change the index accordingly - IDLE_CURSOR_INDEX = 11 - }; + static const int NUM_CURSORS = 18; + + // 18 default cursors in up/down states, +2 for items idle/act cursors + ZorkCursor _cursors[NUM_CURSORS + 2][2]; ZVision *_engine; - const Graphics::PixelFormat *_pixelFormat; - ZorkCursor _idleCursor; - Common::String _currentCursor; + const Graphics::PixelFormat _pixelFormat; bool _cursorIsPushed; + int _item; + int _lastitem; + int _currentCursor; static const char *_cursorNames[]; static const char *_zgiCursorFileNames[]; @@ -68,19 +82,30 @@ public: void initialize(); /** - * Parses a cursor name into a cursor file then creates and shows that cursor. - * It will use the current _isCursorPushed state to choose the correct cursor + * Change cursor to specified cursor ID. If item setted to not 0 and cursor id idle/acrive/handpu change cursor to item. + * + * @param id Wanted cursor id. + */ + + void changeCursor(int id); + + /** + * Return founded id for string contains cursor name * - * @param cursorName The name of a cursor. This *HAS* to correspond to one of the entries in _cursorNames[] + * @param name Cursor name + * @return Id of cursor or idle cursor id if not found */ - void changeCursor(const Common::String &cursorName); + + int getCursorId(const Common::String &name); + /** - * Parses a cursor name into a cursor file then creates and shows that cursor. + * Load cursor for item by id, and try to change cursor to item cursor if it's not 0 * - * @param cursorName The name of a cursor. This *HAS* to correspond to one of the entries in _cursorNames[] - * @param pushed Should the cursor be pushed (true) or not pushed (false) (Another way to say it: down or up) + * @param id Item id or 0 for no item cursor */ - void changeCursor(const Common::String &cursorName, bool pushed); + + void setItemID(int id); + /** * Change the cursor to a certain push state. If the cursor is already in the specified push state, nothing will happen. * @@ -88,17 +113,12 @@ public: */ void cursorDown(bool pushed); - /** Set the cursor to 'Left Arrow'. It will retain the current _isCursorPushed state */ - void setLeftCursor(); - /** Set the cursor to 'Right Arrow'. It will retain the current _isCursorPushed state */ - void setRightCursor(); - /** Set the cursor to 'Up Arrow'. It will retain the current _isCursorPushed state */ - void setUpCursor(); - /** Set the cursor to 'Down Arrow'. It will retain the current _isCursorPushed state */ - void setDownCursor(); - - /** Set the cursor to 'Idle'. It will retain the current _isCursorPushed state */ - void revertToIdle(); + /** + * Show or hide mouse cursor. + * + * @param vis Should the cursor be showed (true) or hide (false) + */ + void showMouse(bool vis); private: /** diff --git a/engines/zvision/graphics/effects/fog.cpp b/engines/zvision/graphics/effects/fog.cpp new file mode 100644 index 0000000000..7b65f60f24 --- /dev/null +++ b/engines/zvision/graphics/effects/fog.cpp @@ -0,0 +1,173 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/scummsys.h" + +#include "zvision/graphics/effects/fog.h" + +#include "zvision/zvision.h" +#include "zvision/graphics/render_manager.h" +#include "zvision/scripting/script_manager.h" + +namespace ZVision { + +FogFx::FogFx(ZVision *engine, uint32 key, Common::Rect region, bool ported, EffectMap *Map, const Common::String &clouds): + GraphicsEffect(engine, key, region, ported) { + + _map = Map; + + _r = 0; + _g = 0; + _b = 0; + + _pos = 0; + + if (_engine->getSearchManager()->hasFile(clouds)) + _engine->getRenderManager()->readImageToSurface(clouds, _fog); + else + _engine->getRenderManager()->readImageToSurface("cloud.tga", _fog); + + _mp.resize(_fog.h); + for (int16 i = 0; i < _fog.h; i++) { + _mp[i].resize(_fog.w); + for (int16 j = 0; j < _fog.w; j++) + _mp[i][j] = true; + } + + for (uint8 i = 0; i < 32; i++) + _colorMap[i] = 0; +} + +FogFx::~FogFx() { + if (_map) + delete _map; + + for (uint16 i = 0; i < _mp.size(); i++) + _mp[i].clear(); + _mp.clear(); +} + +const Graphics::Surface *FogFx::draw(const Graphics::Surface &srcSubRect) { + _surface.copyFrom(srcSubRect); + EffectMap::iterator it = _map->begin(); + + uint32 cnt = 0; + + for (uint16 j = 0; j < _surface.h; j++) { + uint16 *lineBuf = (uint16 *)_surface.getBasePtr(0, j); + + for (uint16 i = 0; i < _surface.w; i++) { + if (it->inEffect) { + // Not 100% equivalent, but looks nice and not buggy + uint8 sr, sg, sb; + _engine->_resourcePixelFormat.colorToRGB(lineBuf[i], sr, sg, sb); + uint16 fogColor = *(uint16 *)_fog.getBasePtr((i + _pos) % _fog.w, j); + uint8 dr, dg, db; + _engine->_resourcePixelFormat.colorToRGB(_colorMap[fogColor & 0x1F], dr, dg, db); + uint16 fr = dr + sr; + if (fr > 255) + fr = 255; + uint16 fg = dg + sg; + if (fg > 255) + fg = 255; + uint16 fb = db + sb; + if (fb > 255) + fb = 255; + lineBuf[i] = _engine->_resourcePixelFormat.RGBToColor(fr, fg, fb); + } + cnt++; + if (cnt >= it->count) { + it++; + cnt = 0; + } + if (it == _map->end()) + break; + } + if (it == _map->end()) + break; + } + + return &_surface; +} + +void FogFx::update() { + _pos += _engine->getScriptManager()->getStateValue(StateKey_EF9_Speed); + _pos %= _fog.w; + + uint8 dr = _engine->getScriptManager()->getStateValue(StateKey_EF9_R); + uint8 dg = _engine->getScriptManager()->getStateValue(StateKey_EF9_G); + uint8 db = _engine->getScriptManager()->getStateValue(StateKey_EF9_B); + dr = CLIP((int)dr, 0, 31); + dg = CLIP((int)dg, 0, 31); + db = CLIP((int)db, 0, 31); + + if (dr != _r || dg != _g || db != _b) { + if (_r > dr) + _r--; + else if (_r < dr) + _r++; + + if (_g > dg) + _g--; + else if (_g < dg) + _g++; + + if (_b > db) + _b--; + else if (_b < db) + _b++; + + // Not 100% equivalent, but looks nice and not buggy + + _colorMap[31] = _engine->_resourcePixelFormat.RGBToColor(_r << 3, _g << 3, _b << 3); + + for (uint8 i = 0; i < 31; i++) { + float perc = (float)i / 31.0; + uint8 cr = (uint8)((float)_r * perc); + uint8 cg = (uint8)((float)_g * perc); + uint8 cb = (uint8)((float)_b * perc); + _colorMap[i] = _engine->_resourcePixelFormat.RGBToColor(cr << 3, cg << 3, cb << 3); + } + } + + for (uint16 j = 0; j < _fog.h; j++) { + uint16 *pix = (uint16 *)_fog.getBasePtr(0, j); + + for (uint16 i = 0; i < _fog.w; i++) { + if (_mp[j][i]) { + if ((pix[i] & 0x1F) == 0x1F) { + pix[i]--; + _mp[j][i] = false; + } else + pix[i]++; + } else { + if ((pix[i] & 0x1F) == 0) { + pix[i]++; + _mp[j][i] = true; + } else + pix[i]--; + } + } + } +} + +} // End of namespace ZVision diff --git a/engines/zvision/graphics/effects/fog.h b/engines/zvision/graphics/effects/fog.h new file mode 100644 index 0000000000..498347609e --- /dev/null +++ b/engines/zvision/graphics/effects/fog.h @@ -0,0 +1,53 @@ +/* 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 ZVISION_FOG_H +#define ZVISION_FOG_H + +#include "zvision/graphics/graphics_effect.h" + +namespace ZVision { + +class ZVision; + +// Used by Zork: Nemesis for the mixing chamber gas effect in the gas puzzle (location tt5e, when the blinds are down) +class FogFx : public GraphicsEffect { +public: + + FogFx(ZVision *engine, uint32 key, Common::Rect region, bool ported, EffectMap *Map, const Common::String &clouds); + ~FogFx(); + + const Graphics::Surface *draw(const Graphics::Surface &srcSubRect); + + void update(); + +private: + EffectMap *_map; + Graphics::Surface _fog; + uint8 _r, _g, _b; + int32 _pos; + Common::Array< Common::Array< bool > > _mp; + uint16 _colorMap[32]; +}; +} // End of namespace ZVision + +#endif // ZVISION_FOG_H diff --git a/engines/zvision/graphics/effects/light.cpp b/engines/zvision/graphics/effects/light.cpp new file mode 100644 index 0000000000..39341687f8 --- /dev/null +++ b/engines/zvision/graphics/effects/light.cpp @@ -0,0 +1,109 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/scummsys.h" + +#include "zvision/graphics/effects/light.h" + +#include "zvision/zvision.h" +#include "zvision/graphics/render_manager.h" + +namespace ZVision { + +LightFx::LightFx(ZVision *engine, uint32 key, Common::Rect region, bool ported, EffectMap *Map, int8 delta, int8 minD, int8 maxD): + GraphicsEffect(engine, key, region, ported) { + _map = Map; + _delta = delta; + _up = true; + _pos = 0; + + _minD = minD; + if (_minD < -delta) + _minD = -delta; + + _maxD = maxD; + if (_maxD > delta) + _maxD = delta; +} + +LightFx::~LightFx() { + if (_map) + delete _map; +} + +const Graphics::Surface *LightFx::draw(const Graphics::Surface &srcSubRect) { + _surface.copyFrom(srcSubRect); + EffectMap::iterator it = _map->begin(); + uint32 cnt = 0; + + uint32 dcolor = 0; + + if (_pos < 0) { + uint8 cc = ((-_pos) & 0x1F) << 3; + dcolor = _engine->_resourcePixelFormat.RGBToColor(cc, cc, cc); + } else { + uint8 cc = (_pos & 0x1F) << 3; + dcolor = _engine->_resourcePixelFormat.RGBToColor(cc, cc, cc); + } + + for (uint16 j = 0; j < _surface.h; j++) { + uint16 *lineBuf = (uint16 *)_surface.getBasePtr(0, j); + + for (uint16 i = 0; i < _surface.w; i++) { + if (it->inEffect) { + if (_pos < 0) { + lineBuf[i] -= dcolor; + } else { + lineBuf[i] += dcolor; + } + } + cnt++; + if (cnt >= it->count) { + it++; + cnt = 0; + } + if (it == _map->end()) + break; + } + if (it == _map->end()) + break; + } + + return &_surface; +} + +void LightFx::update() { + if (_up) + _pos++; + else + _pos--; + + if (_pos <= _minD) { + _up = !_up; + _pos = _minD; + } else if (_pos >= _maxD) { + _up = !_up; + _pos = _maxD; + } +} + +} // End of namespace ZVision diff --git a/engines/zvision/graphics/effects/light.h b/engines/zvision/graphics/effects/light.h new file mode 100644 index 0000000000..cd73a585ec --- /dev/null +++ b/engines/zvision/graphics/effects/light.h @@ -0,0 +1,53 @@ +/* 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 LIGHTFX_H_INCLUDED +#define LIGHTFX_H_INCLUDED + +#include "zvision/graphics/graphics_effect.h" + +namespace ZVision { + +class ZVision; + +class LightFx : public GraphicsEffect { +public: + + LightFx(ZVision *engine, uint32 key, Common::Rect region, bool ported, EffectMap *Map, int8 delta, int8 minD = -127, int8 maxD = 127); + ~LightFx(); + + const Graphics::Surface *draw(const Graphics::Surface &srcSubRect); + + void update(); + +private: + EffectMap *_map; + int32 _delta; + bool _up; + int32 _pos; + + int8 _minD; + int8 _maxD; +}; +} // End of namespace ZVision + +#endif // LIGHTFX_H_INCLUDED diff --git a/engines/zvision/graphics/effects/wave.cpp b/engines/zvision/graphics/effects/wave.cpp new file mode 100644 index 0000000000..d2887b3112 --- /dev/null +++ b/engines/zvision/graphics/effects/wave.cpp @@ -0,0 +1,145 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/scummsys.h" + +#include "zvision/graphics/effects/wave.h" + +#include "zvision/zvision.h" +#include "zvision/graphics/render_manager.h" + +namespace ZVision { + +WaveFx::WaveFx(ZVision *engine, uint32 key, Common::Rect region, bool ported, int16 frames, int16 centerX, int16 centerY, float ampl, float waveln, float spd): + GraphicsEffect(engine, key, region, ported) { + + _frame = 0; + _frameCount = frames; + + _ampls.resize(_frameCount); + _halfWidth = _region.width() / 2; + _halfHeight = _region.height() / 2; + + int32 frmsize = _halfWidth * _halfHeight; + + float phase = 0; + + int16 quarterWidth = _halfWidth / 2; + int16 quarterHeight = _halfHeight / 2; + + for (int16 i = 0; i < _frameCount; i++) { + _ampls[i].resize(frmsize); + + for (int16 y = 0; y < _halfHeight; y++) + for (int16 x = 0; x < _halfWidth; x++) { + int16 dx = (x - quarterWidth); + int16 dy = (y - quarterHeight); + + _ampls[i][x + y * _halfWidth] = (int8)(ampl * sin(sqrt(dx * dx / (float)centerX + dy * dy / (float)centerY) / (-waveln * 3.1415926) + phase)); + } + phase += spd; + } +} + +WaveFx::~WaveFx() { + for (uint16 i = 0; i < _ampls.size(); i++) + _ampls[i].clear(); + _ampls.clear(); +} + +const Graphics::Surface *WaveFx::draw(const Graphics::Surface &srcSubRect) { + for (int16 y = 0; y < _halfHeight; y++) { + uint16 *abc = (uint16 *)_surface.getBasePtr(0, y); + uint16 *abc2 = (uint16 *)_surface.getBasePtr(0, _halfHeight + y); + uint16 *abc3 = (uint16 *)_surface.getBasePtr(_halfWidth, y); + uint16 *abc4 = (uint16 *)_surface.getBasePtr(_halfWidth, _halfHeight + y); + + for (int16 x = 0; x < _halfWidth; x++) { + int8 amnt = _ampls[_frame][x + _halfWidth * y]; + + int16 nX = x + amnt; + int16 nY = y + amnt; + + if (nX < 0) + nX = 0; + if (nX >= _region.width()) + nX = _region.width() - 1; + if (nY < 0) + nY = 0; + if (nY >= _region.height()) + nY = _region.height() - 1; + *abc = *(const uint16 *)srcSubRect.getBasePtr(nX, nY); + + nX = x + amnt + _halfWidth; + nY = y + amnt; + + if (nX < 0) + nX = 0; + if (nX >= _region.width()) + nX = _region.width() - 1; + if (nY < 0) + nY = 0; + if (nY >= _region.height()) + nY = _region.height() - 1; + *abc3 = *(const uint16 *)srcSubRect.getBasePtr(nX, nY); + + nX = x + amnt; + nY = y + amnt + _halfHeight; + + if (nX < 0) + nX = 0; + if (nX >= _region.width()) + nX = _region.width() - 1; + if (nY < 0) + nY = 0; + if (nY >= _region.height()) + nY = _region.height() - 1; + *abc2 = *(const uint16 *)srcSubRect.getBasePtr(nX, nY); + + nX = x + amnt + _halfWidth; + nY = y + amnt + _halfHeight; + + if (nX < 0) + nX = 0; + if (nX >= _region.width()) + nX = _region.width() - 1; + if (nY < 0) + nY = 0; + if (nY >= _region.height()) + nY = _region.height() - 1; + *abc4 = *(const uint16 *)srcSubRect.getBasePtr(nX, nY); + + abc++; + abc2++; + abc3++; + abc4++; + } + } + + return &_surface; +} + +void WaveFx::update() { + _frame = (_frame + 1) % _frameCount; +} + +} // End of namespace ZVision diff --git a/engines/zvision/graphics/effects/wave.h b/engines/zvision/graphics/effects/wave.h new file mode 100644 index 0000000000..8e912372d7 --- /dev/null +++ b/engines/zvision/graphics/effects/wave.h @@ -0,0 +1,51 @@ +/* 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 WAVEFX_H_INCLUDED +#define WAVEFX_H_INCLUDED + +#include "common/array.h" +#include "zvision/graphics/graphics_effect.h" + +namespace ZVision { + +class ZVision; + +class WaveFx : public GraphicsEffect { +public: + + WaveFx(ZVision *engine, uint32 key, Common::Rect region, bool ported, int16 frames, int16 centerX, int16 centerY, float ampl, float waveln, float spd); + ~WaveFx(); + + const Graphics::Surface *draw(const Graphics::Surface &srcSubRect); + + void update(); + +private: + int16 _frame; + int16 _frameCount; + int16 _halfWidth, _halfHeight; + Common::Array< Common::Array< int8 > > _ampls; +}; +} // End of namespace ZVision + +#endif // WAVEFX_H_INCLUDED diff --git a/engines/zvision/graphics/graphics_effect.h b/engines/zvision/graphics/graphics_effect.h new file mode 100644 index 0000000000..bfa266b11d --- /dev/null +++ b/engines/zvision/graphics/graphics_effect.h @@ -0,0 +1,83 @@ +/* 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 GRAPHICS_EFFECT_H_INCLUDED +#define GRAPHICS_EFFECT_H_INCLUDED + +#include "common/rect.h" +#include "common/list.h" +#include "graphics/surface.h" + +#include "zvision/zvision.h" + +namespace ZVision { + +class ZVision; + +class GraphicsEffect { +public: + + GraphicsEffect(ZVision *engine, uint32 key, Common::Rect region, bool ported) : _engine(engine), _key(key), _region(region), _ported(ported) { + _surface.create(_region.width(), _region.height(), _engine->_resourcePixelFormat); + } + virtual ~GraphicsEffect() {} + + uint32 getKey() { + return _key; + } + + Common::Rect getRegion() { + return _region; + } + + bool isPort() { + return _ported; + } + + virtual const Graphics::Surface *draw(const Graphics::Surface &srcSubRect) { + return &_surface; + } + + virtual void update() {} + +protected: + ZVision *_engine; + uint32 _key; + Common::Rect _region; + bool _ported; + Graphics::Surface _surface; + +// Static member functions +public: + +}; + +struct EffectMapUnit { + uint32 count; + bool inEffect; +}; + +typedef Common::List<EffectMapUnit> EffectMap; + +} // End of namespace ZVision + +#endif // GRAPHICS_EFFECT_H_INCLUDED diff --git a/engines/zvision/graphics/render_manager.cpp b/engines/zvision/graphics/render_manager.cpp index aed30ea12c..ce0a02a1ad 100644 --- a/engines/zvision/graphics/render_manager.cpp +++ b/engines/zvision/graphics/render_manager.cpp @@ -1,30 +1,33 @@ /* 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. - * - */ +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* of the License, or (at your option) any later version. + +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. + +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +*/ #include "common/scummsys.h" +#include "zvision/zvision.h" #include "zvision/graphics/render_manager.h" +#include "zvision/scripting/script_manager.h" +#include "zvision/text/text.h" -#include "zvision/utility/lzss_read_stream.h" +#include "zvision/file/lzss_read_stream.h" #include "common/file.h" #include "common/system.h" @@ -34,200 +37,153 @@ #include "image/tga.h" - namespace ZVision { -RenderManager::RenderManager(OSystem *system, uint32 windowWidth, uint32 windowHeight, const Common::Rect workingWindow, const Graphics::PixelFormat pixelFormat) - : _system(system), - _workingWidth(workingWindow.width()), - _workingHeight(workingWindow.height()), - _screenCenterX(_workingWidth / 2), - _screenCenterY(_workingHeight / 2), - _workingWindow(workingWindow), - _pixelFormat(pixelFormat), - _backgroundWidth(0), - _backgroundHeight(0), - _backgroundInverseVelocity(0), - _backgroundOffset(0, 0), - _accumulatedVelocityMilliseconds(0), - _renderTable(_workingWidth, _workingHeight) { - - _workingWindowBuffer.create(_workingWidth, _workingHeight, _pixelFormat); - _backBuffer.create(windowWidth, windowHeight, pixelFormat); +RenderManager::RenderManager(ZVision *engine, uint32 windowWidth, uint32 windowHeight, const Common::Rect workingWindow, const Graphics::PixelFormat pixelFormat, bool doubleFPS) + : _engine(engine), + _system(engine->_system), + _screenCenterX(_workingWindow.width() / 2), + _screenCenterY(_workingWindow.height() / 2), + _workingWindow(workingWindow), + _pixelFormat(pixelFormat), + _backgroundWidth(0), + _backgroundHeight(0), + _backgroundOffset(0), + _renderTable(_workingWindow.width(), _workingWindow.height()), + _doubleFPS(doubleFPS), + _subid(0) { + + _backgroundSurface.create(_workingWindow.width(), _workingWindow.height(), _pixelFormat); + _effectSurface.create(_workingWindow.width(), _workingWindow.height(), _pixelFormat); + _warpedSceneSurface.create(_workingWindow.width(), _workingWindow.height(), _pixelFormat); + _menuSurface.create(windowWidth, workingWindow.top, _pixelFormat); + + _menuArea = Common::Rect(0, 0, windowWidth, workingWindow.top); + + initSubArea(windowWidth, windowHeight, workingWindow); } RenderManager::~RenderManager() { - _workingWindowBuffer.free(); - _currentBackground.free(); - _backBuffer.free(); - - for (AlphaEntryMap::iterator iter = _alphaDataEntries.begin(); iter != _alphaDataEntries.end(); ++iter) { - iter->_value.data->free(); - delete iter->_value.data; - } -} - -void RenderManager::update(uint deltaTimeInMillis) { - // An inverse velocity of 0 would be infinitely fast, so we'll let 0 mean no velocity. - if (_backgroundInverseVelocity != 0) { - _accumulatedVelocityMilliseconds += deltaTimeInMillis; - - uint absVelocity = uint(abs(_backgroundInverseVelocity)); - - int numberOfSteps = 0; - while (_accumulatedVelocityMilliseconds >= absVelocity) { - _accumulatedVelocityMilliseconds -= absVelocity; - numberOfSteps++; - } - - // Choose the direction of movement using the sign of the velocity - moveBackground(_backgroundInverseVelocity < 0 ? -numberOfSteps : numberOfSteps); - } + _currentBackgroundImage.free(); + _backgroundSurface.free(); + _effectSurface.free(); + _warpedSceneSurface.free(); + _menuSurface.free(); + _subtitleSurface.free(); } -void RenderManager::renderBackbufferToScreen() { - if (!_workingWindowDirtyRect.isEmpty()) { - RenderTable::RenderState state = _renderTable.getRenderState(); - if (state == RenderTable::PANORAMA || state == RenderTable::TILT) { - _renderTable.mutateImage((uint16 *)_workingWindowBuffer.getPixels(), (uint16 *)_backBuffer.getBasePtr(_workingWindow.left + _workingWindowDirtyRect.left, _workingWindow.top + _workingWindowDirtyRect.top), _backBuffer.w, _workingWindowDirtyRect); - } else { - _backBuffer.copyRectToSurface(_workingWindowBuffer.getBasePtr(_workingWindowDirtyRect.left, _workingWindowDirtyRect.top), _workingWindowBuffer.pitch, _workingWindow.left + _workingWindowDirtyRect.left, _workingWindow.top + _workingWindowDirtyRect.top, _workingWindowDirtyRect.width(), _workingWindowDirtyRect.height()); - } - - // Translate the working window dirty rect to screen coords - _workingWindowDirtyRect.translate(_workingWindow.left, _workingWindow.top); - // Then extend the backbuffer dirty rect to contain it - if (_backBufferDirtyRect.isEmpty()) { - _backBufferDirtyRect = _workingWindowDirtyRect; - } else { - _backBufferDirtyRect.extend(_workingWindowDirtyRect); - } - - // Clear the dirty rect - _workingWindowDirtyRect = Common::Rect(); - } - - // TODO: Add menu rendering +void RenderManager::renderSceneToScreen() { + Graphics::Surface *out = &_warpedSceneSurface; + Graphics::Surface *in = &_backgroundSurface; + Common::Rect outWndDirtyRect; - // Render alpha entries - processAlphaEntries(); + // If we have graphical effects, we apply them using a temporary buffer + if (!_effects.empty()) { + bool copied = false; + Common::Rect windowRect(_workingWindow.width(), _workingWindow.height()); - if (!_backBufferDirtyRect.isEmpty()) { - _system->copyRectToScreen(_backBuffer.getBasePtr(_backBufferDirtyRect.left, _backBufferDirtyRect.top), _backBuffer.pitch, _backBufferDirtyRect.left, _backBufferDirtyRect.top, _backBufferDirtyRect.width(), _backBufferDirtyRect.height()); - _backBufferDirtyRect = Common::Rect(); - } -} + for (EffectsList::iterator it = _effects.begin(); it != _effects.end(); it++) { + Common::Rect rect = (*it)->getRegion(); + Common::Rect screenSpaceLocation = rect; -void RenderManager::processAlphaEntries() { - // TODO: Add dirty rectangling support. AKA only draw an entry if the _backbufferDirtyRect intersects/contains the entry Rect - - for (AlphaEntryMap::iterator iter = _alphaDataEntries.begin(); iter != _alphaDataEntries.end(); ++iter) { - uint32 destOffset = 0; - uint32 sourceOffset = 0; - uint16 *backbufferPtr = (uint16 *)_backBuffer.getBasePtr(iter->_value.destX + _workingWindow.left, iter->_value.destY + _workingWindow.top); - uint16 *entryPtr = (uint16 *)iter->_value.data->getPixels(); + if ((*it)->isPort()) { + screenSpaceLocation = transformBackgroundSpaceRectToScreenSpace(screenSpaceLocation); + } - for (int32 y = 0; y < iter->_value.height; ++y) { - for (int32 x = 0; x < iter->_value.width; ++x) { - uint16 color = entryPtr[sourceOffset + x]; - if (color != iter->_value.alphaColor) { - backbufferPtr[destOffset + x] = color; + if (windowRect.intersects(screenSpaceLocation)) { + if (!copied) { + copied = true; + _effectSurface.copyFrom(_backgroundSurface); + in = &_effectSurface; + } + const Graphics::Surface *post; + if ((*it)->isPort()) + post = (*it)->draw(_currentBackgroundImage.getSubArea(rect)); + else + post = (*it)->draw(_effectSurface.getSubArea(rect)); + Common::Rect empty; + blitSurfaceToSurface(*post, empty, _effectSurface, screenSpaceLocation.left, screenSpaceLocation.top); + screenSpaceLocation.clip(windowRect); + if (_backgroundSurfaceDirtyRect .isEmpty()) { + _backgroundSurfaceDirtyRect = screenSpaceLocation; + } else { + _backgroundSurfaceDirtyRect.extend(screenSpaceLocation); } } - - destOffset += _backBuffer.w; - sourceOffset += iter->_value.width; } + } - if (_backBufferDirtyRect.isEmpty()) { - _backBufferDirtyRect = Common::Rect(iter->_value.destX + _workingWindow.left, iter->_value.destY + _workingWindow.top, iter->_value.destX + _workingWindow.left + iter->_value.width, iter->_value.destY + _workingWindow.top + iter->_value.height); - } else { - _backBufferDirtyRect.extend(Common::Rect(iter->_value.destX + _workingWindow.left, iter->_value.destY + _workingWindow.top, iter->_value.destX + _workingWindow.left + iter->_value.width, iter->_value.destY + _workingWindow.top + iter->_value.height)); + RenderTable::RenderState state = _renderTable.getRenderState(); + if (state == RenderTable::PANORAMA || state == RenderTable::TILT) { + if (!_backgroundSurfaceDirtyRect.isEmpty()) { + _renderTable.mutateImage(&_warpedSceneSurface, in); + out = &_warpedSceneSurface; + outWndDirtyRect = Common::Rect(_workingWindow.width(), _workingWindow.height()); } + } else { + out = in; + outWndDirtyRect = _backgroundSurfaceDirtyRect; } -} -void RenderManager::clearWorkingWindowTo555Color(uint16 color) { - uint32 workingWindowSize = _workingWidth * _workingHeight; - byte r, g, b; - Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0).colorToRGB(color, r, g, b); - uint16 colorIn565 = _pixelFormat.RGBToColor(r, g, b); - uint16 *bufferPtr = (uint16 *)_workingWindowBuffer.getPixels(); - - for (uint32 i = 0; i < workingWindowSize; ++i) { - bufferPtr[i] = colorIn565; + if (!outWndDirtyRect.isEmpty()) { + Common::Rect rect( + outWndDirtyRect.left + _workingWindow.left, + outWndDirtyRect.top + _workingWindow.top, + outWndDirtyRect.left + _workingWindow.left + outWndDirtyRect.width(), + outWndDirtyRect.top + _workingWindow.top + outWndDirtyRect.height() + ); + copyToScreen(*out, rect, outWndDirtyRect.left, outWndDirtyRect.top); } } -void RenderManager::renderSubRectToScreen(Graphics::Surface &surface, int16 destinationX, int16 destinationY, bool wrap) { - int16 subRectX = 0; - int16 subRectY = 0; - - // Take care of negative destinations - if (destinationX < 0) { - subRectX = -destinationX; - destinationX = 0; - } else if (destinationX >= surface.w) { - // Take care of extreme positive destinations - destinationX -= surface.w; - } - - // Take care of negative destinations - if (destinationY < 0) { - subRectY = -destinationY; - destinationY = 0; - } else if (destinationY >= surface.h) { - // Take care of extreme positive destinations - destinationY -= surface.h; - } - - if (wrap) { - _backgroundWidth = surface.w; - _backgroundHeight = surface.h; - - if (destinationX > 0) { - // Move destinationX to 0 - subRectX = surface.w - destinationX; - destinationX = 0; - } - - if (destinationY > 0) { - // Move destinationY to 0 - subRectY = surface.h - destinationY; - destinationY = 0; - } - } +void RenderManager::copyToScreen(const Graphics::Surface &surface, Common::Rect &rect, int16 srcLeft, int16 srcTop) { + // Convert the surface to RGB565, if needed + Graphics::Surface *outSurface = surface.convertTo(_engine->_screenPixelFormat); + _system->copyRectToScreen(outSurface->getBasePtr(srcLeft, srcTop), + outSurface->pitch, + rect.left, + rect.top, + rect.width(), + rect.height()); + outSurface->free(); + delete outSurface; +} - // Clip subRect to working window bounds - Common::Rect subRect(subRectX, subRectY, subRectX + _workingWidth, subRectY + _workingHeight); +void RenderManager::renderImageToBackground(const Common::String &fileName, int16 destX, int16 destY) { + Graphics::Surface surface; + readImageToSurface(fileName, surface); - if (!wrap) { - // Clip to image bounds - subRect.clip(surface.w, surface.h); - } + blitSurfaceToBkg(surface, destX, destY); + surface.free(); +} - // Check destRect for validity - if (!subRect.isValidRect() || subRect.isEmpty()) - return; +void RenderManager::renderImageToBackground(const Common::String &fileName, int16 destX, int16 destY, uint32 keycolor) { + Graphics::Surface surface; + readImageToSurface(fileName, surface); - copyRectToWorkingWindow((const uint16 *)surface.getBasePtr(subRect.left, subRect.top), destinationX, destinationY, surface.w, subRect.width(), subRect.height()); + blitSurfaceToBkg(surface, destX, destY, keycolor); + surface.free(); } -void RenderManager::renderImageToScreen(const Common::String &fileName, int16 destinationX, int16 destinationY, bool wrap) { +void RenderManager::renderImageToBackground(const Common::String &fileName, int16 destX, int16 destY, int16 keyX, int16 keyY) { Graphics::Surface surface; readImageToSurface(fileName, surface); - renderSubRectToScreen(surface, destinationX, destinationY, wrap); -} + uint16 keycolor = *(uint16 *)surface.getBasePtr(keyX, keyY); -void RenderManager::renderImageToScreen(Graphics::Surface &surface, int16 destinationX, int16 destinationY, bool wrap) { - renderSubRectToScreen(surface, destinationX, destinationY, wrap); + blitSurfaceToBkg(surface, destX, destY, keycolor); + surface.free(); } void RenderManager::readImageToSurface(const Common::String &fileName, Graphics::Surface &destination) { + bool isTransposed = _renderTable.getRenderState() == RenderTable::PANORAMA; + readImageToSurface(fileName, destination, isTransposed); +} + +void RenderManager::readImageToSurface(const Common::String &fileName, Graphics::Surface &destination, bool transposed) { Common::File file; - if (!file.open(fileName)) { + if (!_engine->getSearchManager()->openFile(file, fileName)) { warning("Could not open file %s", fileName.c_str()); return; } @@ -240,10 +196,8 @@ void RenderManager::readImageToSurface(const Common::String &fileName, Graphics: uint32 imageHeight; Image::TGADecoder tga; uint16 *buffer; - bool isTransposed = _renderTable.getRenderState() == RenderTable::PANORAMA; // All ZVision images are in RGB 555 - Graphics::PixelFormat pixelFormat555 = Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0); - destination.format = pixelFormat555; + destination.format = _engine->_resourcePixelFormat; bool isTGZ; @@ -252,13 +206,17 @@ void RenderManager::readImageToSurface(const Common::String &fileName, Graphics: isTGZ = true; // TGZ files have a header and then Bitmap data that is compressed with LZSS - uint32 decompressedSize = file.readSint32LE(); + uint32 decompressedSize = file.readSint32LE() / 2; imageWidth = file.readSint32LE(); imageHeight = file.readSint32LE(); LzssReadStream lzssStream(&file); buffer = (uint16 *)(new uint16[decompressedSize]); - lzssStream.read(buffer, decompressedSize); + lzssStream.read(buffer, 2 * decompressedSize); +#ifndef SCUMM_LITTLE_ENDIAN + for (uint32 i = 0; i < decompressedSize; ++i) + buffer[i] = FROM_LE_16(buffer[i]); +#endif } else { isTGZ = false; @@ -279,7 +237,7 @@ void RenderManager::readImageToSurface(const Common::String &fileName, Graphics: } // Flip the width and height if transposed - if (isTransposed) { + if (transposed) { uint16 temp = imageHeight; imageHeight = imageWidth; imageWidth = temp; @@ -288,12 +246,12 @@ void RenderManager::readImageToSurface(const Common::String &fileName, Graphics: // If the destination internal buffer is the same size as what we're copying into it, // there is no need to free() and re-create if (imageWidth != destination.w || imageHeight != destination.h) { - destination.create(imageWidth, imageHeight, pixelFormat555); + destination.create(imageWidth, imageHeight, _engine->_resourcePixelFormat); } // If transposed, 'un-transpose' the data while copying it to the destination // Otherwise, just do a simple copy - if (isTransposed) { + if (transposed) { uint16 *dest = (uint16 *)destination.getPixels(); for (uint32 y = 0; y < imageHeight; ++y) { @@ -304,7 +262,7 @@ void RenderManager::readImageToSurface(const Common::String &fileName, Graphics: } } } else { - memcpy(destination.getPixels(), buffer, imageWidth * imageHeight * _pixelFormat.bytesPerPixel); + memcpy(destination.getPixels(), buffer, imageWidth * imageHeight * destination.format.bytesPerPixel); } // Cleanup @@ -313,214 +271,947 @@ void RenderManager::readImageToSurface(const Common::String &fileName, Graphics: } else { tga.destroy(); } - - // Convert in place to RGB 565 from RGB 555 - destination.convertToInPlace(_pixelFormat); } -void RenderManager::copyRectToWorkingWindow(const uint16 *buffer, int32 destX, int32 destY, int32 imageWidth, int32 width, int32 height) { - uint32 destOffset = 0; - uint32 sourceOffset = 0; - uint16 *workingWindowBufferPtr = (uint16 *)_workingWindowBuffer.getBasePtr(destX, destY); +const Common::Point RenderManager::screenSpaceToImageSpace(const Common::Point &point) { + if (_workingWindow.contains(point)) { + // Convert from screen space to working window space + Common::Point newPoint(point - Common::Point(_workingWindow.left, _workingWindow.top)); + + RenderTable::RenderState state = _renderTable.getRenderState(); + if (state == RenderTable::PANORAMA || state == RenderTable::TILT) { + newPoint = _renderTable.convertWarpedCoordToFlatCoord(newPoint); + } - for (int32 y = 0; y < height; ++y) { - for (int32 x = 0; x < width; ++x) { - workingWindowBufferPtr[destOffset + x] = buffer[sourceOffset + x]; + if (state == RenderTable::PANORAMA) { + newPoint += (Common::Point(_backgroundOffset - _screenCenterX, 0)); + } else if (state == RenderTable::TILT) { + newPoint += (Common::Point(0, _backgroundOffset - _screenCenterY)); } - destOffset += _workingWidth; - sourceOffset += imageWidth; + if (_backgroundWidth) + newPoint.x %= _backgroundWidth; + if (_backgroundHeight) + newPoint.y %= _backgroundHeight; + + if (newPoint.x < 0) + newPoint.x += _backgroundWidth; + if (newPoint.y < 0) + newPoint.y += _backgroundHeight; + + return newPoint; + } else { + return Common::Point(0, 0); } +} + +RenderTable *RenderManager::getRenderTable() { + return &_renderTable; +} + +void RenderManager::setBackgroundImage(const Common::String &fileName) { + readImageToSurface(fileName, _currentBackgroundImage); + _backgroundWidth = _currentBackgroundImage.w; + _backgroundHeight = _currentBackgroundImage.h; + _backgroundDirtyRect = Common::Rect(_backgroundWidth, _backgroundHeight); +} + +void RenderManager::setBackgroundPosition(int offset) { + RenderTable::RenderState state = _renderTable.getRenderState(); + if (state == RenderTable::TILT || state == RenderTable::PANORAMA) + if (_backgroundOffset != offset) + _backgroundDirtyRect = Common::Rect(_backgroundWidth, _backgroundHeight); + _backgroundOffset = offset; - if (_workingWindowDirtyRect.isEmpty()) { - _workingWindowDirtyRect = Common::Rect(destX, destY, destX + width, destY + height); + _engine->getScriptManager()->setStateValue(StateKey_ViewPos, offset); +} + +uint32 RenderManager::getCurrentBackgroundOffset() { + RenderTable::RenderState state = _renderTable.getRenderState(); + + if (state == RenderTable::PANORAMA) { + return _backgroundOffset; + } else if (state == RenderTable::TILT) { + return _backgroundOffset; } else { - _workingWindowDirtyRect.extend(Common::Rect(destX, destY, destX + width, destY + height)); + return 0; + } +} + +Graphics::Surface *RenderManager::tranposeSurface(const Graphics::Surface *surface) { + Graphics::Surface *tranposedSurface = new Graphics::Surface(); + tranposedSurface->create(surface->h, surface->w, surface->format); + + const uint16 *source = (const uint16 *)surface->getPixels(); + uint16 *dest = (uint16 *)tranposedSurface->getPixels(); + + for (uint32 y = 0; y < tranposedSurface->h; ++y) { + uint32 columnIndex = y * tranposedSurface->w; + + for (uint32 x = 0; x < tranposedSurface->w; ++x) { + dest[columnIndex + x] = source[x * surface->w + y]; + } } - // TODO: Remove this from release. It's here to make sure code that uses this function clips their destinations correctly - assert(_workingWindowDirtyRect.width() <= _workingWidth && _workingWindowDirtyRect.height() <= _workingHeight); + return tranposedSurface; } -void RenderManager::copyRectToWorkingWindow(const uint16 *buffer, int32 destX, int32 destY, int32 imageWidth, int32 width, int32 height, int16 alphaColor, uint32 idNumber) { - AlphaDataEntry entry; - entry.alphaColor = alphaColor; - entry.data = new Graphics::Surface(); - entry.data->create(width, height, _pixelFormat); - entry.destX = destX; - entry.destY = destY; - entry.width = width; - entry.height = height; +void RenderManager::scaleBuffer(const void *src, void *dst, uint32 srcWidth, uint32 srcHeight, byte bytesPerPixel, uint32 dstWidth, uint32 dstHeight) { + assert(bytesPerPixel == 1 || bytesPerPixel == 2); - uint32 sourceOffset = 0; - uint32 destOffset = 0; - uint16 *surfacePtr = (uint16 *)entry.data->getPixels(); + const float xscale = (float)srcWidth / (float)dstWidth; + const float yscale = (float)srcHeight / (float)dstHeight; - for (int32 y = 0; y < height; ++y) { - for (int32 x = 0; x < width; ++x) { - surfacePtr[destOffset + x] = buffer[sourceOffset + x]; + if (bytesPerPixel == 1) { + const byte *srcPtr = (const byte *)src; + byte *dstPtr = (byte *)dst; + for (uint32 y = 0; y < dstHeight; ++y) { + for (uint32 x = 0; x < dstWidth; ++x) { + *dstPtr = srcPtr[(int)(x * xscale) + (int)(y * yscale) * srcWidth]; + dstPtr++; + } } + } else if (bytesPerPixel == 2) { + const uint16 *srcPtr = (const uint16 *)src; + uint16 *dstPtr = (uint16 *)dst; + for (uint32 y = 0; y < dstHeight; ++y) { + for (uint32 x = 0; x < dstWidth; ++x) { + *dstPtr = srcPtr[(int)(x * xscale) + (int)(y * yscale) * srcWidth]; + dstPtr++; + } + } + } +} + +void RenderManager::blitSurfaceToSurface(const Graphics::Surface &src, const Common::Rect &_srcRect , Graphics::Surface &dst, int _x, int _y) { + Common::Rect srcRect = _srcRect; + if (srcRect.isEmpty()) + srcRect = Common::Rect(src.w, src.h); + srcRect.clip(src.w, src.h); + Common::Rect dstRect = Common::Rect(-_x + srcRect.left , -_y + srcRect.top, -_x + srcRect.left + dst.w, -_y + srcRect.top + dst.h); + srcRect.clip(dstRect); + + if (srcRect.isEmpty() || !srcRect.isValidRect()) + return; + + Graphics::Surface *srcAdapted = src.convertTo(dst.format); + + // Copy srcRect from src surface to dst surface + const byte *srcBuffer = (const byte *)srcAdapted->getBasePtr(srcRect.left, srcRect.top); + + int xx = _x; + int yy = _y; + + if (xx < 0) + xx = 0; + if (yy < 0) + yy = 0; + + if (_x >= dst.w || _y >= dst.h) { + srcAdapted->free(); + delete srcAdapted; + return; + } + + byte *dstBuffer = (byte *)dst.getBasePtr(xx, yy); - destOffset += width; - sourceOffset += imageWidth; + int32 w = srcRect.width(); + int32 h = srcRect.height(); + + for (int32 y = 0; y < h; y++) { + memcpy(dstBuffer, srcBuffer, w * srcAdapted->format.bytesPerPixel); + srcBuffer += srcAdapted->pitch; + dstBuffer += dst.pitch; } - _alphaDataEntries[idNumber] = entry; + srcAdapted->free(); + delete srcAdapted; } -Common::Rect RenderManager::renderTextToWorkingWindow(uint32 idNumber, const Common::String &text, TruetypeFont *font, int destX, int destY, uint16 textColor, int maxWidth, int maxHeight, Graphics::TextAlign align, bool wrap) { - AlphaDataEntry entry; - entry.alphaColor = 0; - entry.destX = destX; - entry.destY = destY; +void RenderManager::blitSurfaceToSurface(const Graphics::Surface &src, const Common::Rect &_srcRect , Graphics::Surface &dst, int _x, int _y, uint32 colorkey) { + Common::Rect srcRect = _srcRect; + if (srcRect.isEmpty()) + srcRect = Common::Rect(src.w, src.h); + srcRect.clip(src.w, src.h); + Common::Rect dstRect = Common::Rect(-_x + srcRect.left , -_y + srcRect.top, -_x + srcRect.left + dst.w, -_y + srcRect.top + dst.h); + srcRect.clip(dstRect); + + if (srcRect.isEmpty() || !srcRect.isValidRect()) + return; - // Draw the text to the working window - entry.data = font->drawTextToSurface(text, textColor, maxWidth, maxHeight, align, wrap); - entry.width = entry.data->w; - entry.height = entry.data->h; + Graphics::Surface *srcAdapted = src.convertTo(dst.format); + uint32 keycolor = colorkey & ((1 << (src.format.bytesPerPixel << 3)) - 1); - _alphaDataEntries[idNumber] = entry; + // Copy srcRect from src surface to dst surface + const byte *srcBuffer = (const byte *)srcAdapted->getBasePtr(srcRect.left, srcRect.top); - return Common::Rect(destX, destY, destX + entry.width, destY + entry.height); -} + int xx = _x; + int yy = _y; -const Common::Point RenderManager::screenSpaceToImageSpace(const Common::Point &point) { - // Convert from screen space to working window space - Common::Point newPoint(point - Common::Point(_workingWindow.left, _workingWindow.top)); + if (xx < 0) + xx = 0; + if (yy < 0) + yy = 0; - RenderTable::RenderState state = _renderTable.getRenderState(); - if (state == RenderTable::PANORAMA || state == RenderTable::TILT) { - newPoint = _renderTable.convertWarpedCoordToFlatCoord(newPoint); + if (_x >= dst.w || _y >= dst.h) { + srcAdapted->free(); + delete srcAdapted; + return; } - if (state == RenderTable::PANORAMA) { - newPoint -= (Common::Point(_screenCenterX, 0) - _backgroundOffset); - } else if (state == RenderTable::TILT) { - newPoint -= (Common::Point(0, _screenCenterY) - _backgroundOffset); + byte *dstBuffer = (byte *)dst.getBasePtr(xx, yy); + + int32 w = srcRect.width(); + int32 h = srcRect.height(); + + for (int32 y = 0; y < h; y++) { + switch (srcAdapted->format.bytesPerPixel) { + case 1: { + const uint *srcTemp = (const uint *)srcBuffer; + uint *dstTemp = (uint *)dstBuffer; + for (int32 x = 0; x < w; x++) { + if (*srcTemp != keycolor) + *dstTemp = *srcTemp; + srcTemp++; + dstTemp++; + } + } + break; + + case 2: { + const uint16 *srcTemp = (const uint16 *)srcBuffer; + uint16 *dstTemp = (uint16 *)dstBuffer; + for (int32 x = 0; x < w; x++) { + if (*srcTemp != keycolor) + *dstTemp = *srcTemp; + srcTemp++; + dstTemp++; + } + } + break; + + case 4: { + const uint32 *srcTemp = (const uint32 *)srcBuffer; + uint32 *dstTemp = (uint32 *)dstBuffer; + for (int32 x = 0; x < w; x++) { + if (*srcTemp != keycolor) + *dstTemp = *srcTemp; + srcTemp++; + dstTemp++; + } + } + break; + + default: + break; + } + srcBuffer += srcAdapted->pitch; + dstBuffer += dst.pitch; } - if (newPoint.x < 0) - newPoint.x += _backgroundWidth; - else if (newPoint.x >= _backgroundWidth) - newPoint.x -= _backgroundWidth; - if (newPoint.y < 0) - newPoint.y += _backgroundHeight; - else if (newPoint.y >= _backgroundHeight) - newPoint.y -= _backgroundHeight; + srcAdapted->free(); + delete srcAdapted; +} + +void RenderManager::blitSurfaceToBkg(const Graphics::Surface &src, int x, int y, int32 colorkey) { + Common::Rect empt; + if (colorkey >= 0) + blitSurfaceToSurface(src, empt, _currentBackgroundImage, x, y, colorkey); + else + blitSurfaceToSurface(src, empt, _currentBackgroundImage, x, y); + Common::Rect dirty(src.w, src.h); + dirty.translate(x, y); + if (_backgroundDirtyRect.isEmpty()) + _backgroundDirtyRect = dirty; + else + _backgroundDirtyRect.extend(dirty); +} + +void RenderManager::blitSurfaceToBkgScaled(const Graphics::Surface &src, const Common::Rect &_dstRect, int32 colorkey) { + if (src.w == _dstRect.width() && src.h == _dstRect.height()) { + blitSurfaceToBkg(src, _dstRect.left, _dstRect.top, colorkey); + } else { + Graphics::Surface *tmp = new Graphics::Surface; + tmp->create(_dstRect.width(), _dstRect.height(), src.format); + scaleBuffer(src.getPixels(), tmp->getPixels(), src.w, src.h, src.format.bytesPerPixel, _dstRect.width(), _dstRect.height()); + blitSurfaceToBkg(*tmp, _dstRect.left, _dstRect.top, colorkey); + tmp->free(); + delete tmp; + } +} - return newPoint; +void RenderManager::blitSurfaceToMenu(const Graphics::Surface &src, int x, int y, int32 colorkey) { + Common::Rect empt; + if (colorkey >= 0) + blitSurfaceToSurface(src, empt, _menuSurface, x, y, colorkey); + else + blitSurfaceToSurface(src, empt, _menuSurface, x, y); + Common::Rect dirty(src.w, src.h); + dirty.translate(x, y); + if (_menuSurfaceDirtyRect.isEmpty()) + _menuSurfaceDirtyRect = dirty; + else + _menuSurfaceDirtyRect.extend(dirty); } -const Common::Point RenderManager::imageSpaceToWorkingWindowSpace(const Common::Point &point) { - Common::Point newPoint(point); +Graphics::Surface *RenderManager::getBkgRect(Common::Rect &rect) { + Common::Rect dst = rect; + dst.clip(_backgroundWidth, _backgroundHeight); + + if (dst.isEmpty() || !dst.isValidRect()) + return NULL; + Graphics::Surface *srf = new Graphics::Surface; + srf->create(dst.width(), dst.height(), _currentBackgroundImage.format); + + srf->copyRectToSurface(_currentBackgroundImage, 0, 0, Common::Rect(dst)); + + return srf; +} + +Graphics::Surface *RenderManager::loadImage(Common::String file) { + Graphics::Surface *tmp = new Graphics::Surface; + readImageToSurface(file, *tmp); + return tmp; +} + +Graphics::Surface *RenderManager::loadImage(Common::String file, bool transposed) { + Graphics::Surface *tmp = new Graphics::Surface; + readImageToSurface(file, *tmp, transposed); + return tmp; +} + +void RenderManager::prepareBackground() { + _backgroundDirtyRect.clip(_backgroundWidth, _backgroundHeight); RenderTable::RenderState state = _renderTable.getRenderState(); + if (state == RenderTable::PANORAMA) { - newPoint += (Common::Point(_screenCenterX, 0) - _backgroundOffset); + // Calculate the visible portion of the background + Common::Rect viewPort(_workingWindow.width(), _workingWindow.height()); + viewPort.translate(-(_screenCenterX - _backgroundOffset), 0); + Common::Rect drawRect = _backgroundDirtyRect; + drawRect.clip(viewPort); + + // Render the visible portion + if (!drawRect.isEmpty()) { + blitSurfaceToSurface(_currentBackgroundImage, drawRect, _backgroundSurface, _screenCenterX - _backgroundOffset + drawRect.left, drawRect.top); + } + + // Mark the dirty portion of the surface + _backgroundSurfaceDirtyRect = _backgroundDirtyRect; + _backgroundSurfaceDirtyRect.translate(_screenCenterX - _backgroundOffset, 0); + + // Panorama mode allows the user to spin in circles. Therefore, we need to render + // the portion of the image that wrapped to the other side of the screen + if (_backgroundOffset < _screenCenterX) { + viewPort.moveTo(-(_screenCenterX - (_backgroundOffset + _backgroundWidth)), 0); + drawRect = _backgroundDirtyRect; + drawRect.clip(viewPort); + + if (!drawRect.isEmpty()) + blitSurfaceToSurface(_currentBackgroundImage, drawRect, _backgroundSurface, _screenCenterX - (_backgroundOffset + _backgroundWidth) + drawRect.left, drawRect.top); + + Common::Rect tmp = _backgroundDirtyRect; + tmp.translate(_screenCenterX - (_backgroundOffset + _backgroundWidth), 0); + if (!tmp.isEmpty()) + _backgroundSurfaceDirtyRect.extend(tmp); + + } else if (_backgroundWidth - _backgroundOffset < _screenCenterX) { + viewPort.moveTo(-(_screenCenterX + _backgroundWidth - _backgroundOffset), 0); + drawRect = _backgroundDirtyRect; + drawRect.clip(viewPort); + + if (!drawRect.isEmpty()) + blitSurfaceToSurface(_currentBackgroundImage, drawRect, _backgroundSurface, _screenCenterX + _backgroundWidth - _backgroundOffset + drawRect.left, drawRect.top); + + Common::Rect tmp = _backgroundDirtyRect; + tmp.translate(_screenCenterX + _backgroundWidth - _backgroundOffset, 0); + if (!tmp.isEmpty()) + _backgroundSurfaceDirtyRect.extend(tmp); + + } } else if (state == RenderTable::TILT) { - newPoint += (Common::Point(0, _screenCenterY) - _backgroundOffset); + // Tilt doesn't allow wrapping, so we just do a simple clip + Common::Rect viewPort(_workingWindow.width(), _workingWindow.height()); + viewPort.translate(0, -(_screenCenterY - _backgroundOffset)); + Common::Rect drawRect = _backgroundDirtyRect; + drawRect.clip(viewPort); + if (!drawRect.isEmpty()) + blitSurfaceToSurface(_currentBackgroundImage, drawRect, _backgroundSurface, drawRect.left, _screenCenterY - _backgroundOffset + drawRect.top); + + // Mark the dirty portion of the surface + _backgroundSurfaceDirtyRect = _backgroundDirtyRect; + _backgroundSurfaceDirtyRect.translate(0, _screenCenterY - _backgroundOffset); + + } else { + if (!_backgroundDirtyRect.isEmpty()) + blitSurfaceToSurface(_currentBackgroundImage, _backgroundDirtyRect, _backgroundSurface, _backgroundDirtyRect.left, _backgroundDirtyRect.top); + _backgroundSurfaceDirtyRect = _backgroundDirtyRect; } - return newPoint; + // Clear the dirty rect since everything is clean now + _backgroundDirtyRect = Common::Rect(); + + _backgroundSurfaceDirtyRect.clip(_workingWindow.width(), _workingWindow.height()); +} + +void RenderManager::clearMenuSurface() { + _menuSurfaceDirtyRect = Common::Rect(0, 0, _menuSurface.w, _menuSurface.h); + _menuSurface.fillRect(_menuSurfaceDirtyRect, 0); } -bool RenderManager::clipRectToWorkingWindow(Common::Rect &rect) { - if (!_workingWindow.contains(rect)) { - return false; +void RenderManager::clearMenuSurface(const Common::Rect &r) { + if (_menuSurfaceDirtyRect.isEmpty()) + _menuSurfaceDirtyRect = r; + else + _menuSurfaceDirtyRect.extend(r); + _menuSurface.fillRect(r, 0); +} + +void RenderManager::renderMenuToScreen() { + if (!_menuSurfaceDirtyRect.isEmpty()) { + _menuSurfaceDirtyRect.clip(Common::Rect(_menuSurface.w, _menuSurface.h)); + if (!_menuSurfaceDirtyRect.isEmpty()) { + Common::Rect rect( + _menuSurfaceDirtyRect.left + _menuArea.left, + _menuSurfaceDirtyRect.top + _menuArea.top, + _menuSurfaceDirtyRect.left + _menuArea.left + _menuSurfaceDirtyRect.width(), + _menuSurfaceDirtyRect.top + _menuArea.top + _menuSurfaceDirtyRect.height() + ); + copyToScreen(_menuSurface, rect, _menuSurfaceDirtyRect.left, _menuSurfaceDirtyRect.top); + } + _menuSurfaceDirtyRect = Common::Rect(); } +} - // We can't clip against the actual working window rect because it's in screen space - // But rect is in working window space - rect.clip(_workingWidth, _workingHeight); - return true; +void RenderManager::initSubArea(uint32 windowWidth, uint32 windowHeight, const Common::Rect workingWindow) { + _workingWindow = workingWindow; + + _subtitleSurface.free(); + + _subtitleSurface.create(windowWidth, windowHeight - workingWindow.bottom, _pixelFormat); + _subtitleArea = Common::Rect(0, workingWindow.bottom, windowWidth, windowHeight); } -RenderTable *RenderManager::getRenderTable() { - return &_renderTable; +uint16 RenderManager::createSubArea(const Common::Rect &area) { + _subid++; + + OneSubtitle sub; + sub.redraw = false; + sub.timer = -1; + sub.todelete = false; + sub.r = area; + + _subsList[_subid] = sub; + + return _subid; } -void RenderManager::setBackgroundImage(const Common::String &fileName) { - readImageToSurface(fileName, _currentBackground); +uint16 RenderManager::createSubArea() { + Common::Rect r(_subtitleArea.left, _subtitleArea.top, _subtitleArea.right, _subtitleArea.bottom); + r.translate(-_workingWindow.left, -_workingWindow.top); + return createSubArea(r); +} - moveBackground(0); +void RenderManager::deleteSubArea(uint16 id) { + if (_subsList.contains(id)) + _subsList[id].todelete = true; } -void RenderManager::setBackgroundPosition(int offset) { - RenderTable::RenderState state = _renderTable.getRenderState(); - if (state == RenderTable::TILT) { - _backgroundOffset.x = 0; - _backgroundOffset.y = offset; - } else if (state == RenderTable::PANORAMA) { - _backgroundOffset.x = offset; - _backgroundOffset.y = 0; - } else { - _backgroundOffset.x = 0; - _backgroundOffset.y = 0; +void RenderManager::deleteSubArea(uint16 id, int16 delay) { + if (_subsList.contains(id)) + _subsList[id].timer = delay; +} + +void RenderManager::updateSubArea(uint16 id, const Common::String &txt) { + if (_subsList.contains(id)) { + OneSubtitle *sub = &_subsList[id]; + sub->txt = txt; + sub->redraw = true; } } -void RenderManager::setBackgroundVelocity(int velocity) { - // setBackgroundVelocity(0) will be called quite often, so make sure - // _backgroundInverseVelocity isn't already 0 to prevent an extraneous assignment - if (velocity == 0) { - if (_backgroundInverseVelocity != 0) { - _backgroundInverseVelocity = 0; +void RenderManager::processSubs(uint16 deltatime) { + bool redraw = false; + for (SubtitleMap::iterator it = _subsList.begin(); it != _subsList.end(); it++) { + if (it->_value.timer != -1) { + it->_value.timer -= deltatime; + if (it->_value.timer <= 0) + it->_value.todelete = true; + } + if (it->_value.todelete) { + _subsList.erase(it); + redraw = true; + } else if (it->_value.redraw) { + redraw = true; } - } else { - _backgroundInverseVelocity = 1000 / velocity; } -} -void RenderManager::moveBackground(int offset) { - RenderTable::RenderState state = _renderTable.getRenderState(); - if (state == RenderTable::TILT) { - _backgroundOffset += Common::Point(0, offset); + if (redraw) { + _subtitleSurface.fillRect(Common::Rect(_subtitleSurface.w, _subtitleSurface.h), 0); + + for (SubtitleMap::iterator it = _subsList.begin(); it != _subsList.end(); it++) { + OneSubtitle *sub = &it->_value; + if (sub->txt.size()) { + Graphics::Surface subtitleSurface; + subtitleSurface.create(sub->r.width(), sub->r.height(), _engine->_resourcePixelFormat); + _engine->getTextRenderer()->drawTextWithWordWrapping(sub->txt, subtitleSurface); + Common::Rect empty; + blitSurfaceToSurface(subtitleSurface, empty, _subtitleSurface, sub->r.left - _subtitleArea.left + _workingWindow.left, sub->r.top - _subtitleArea.top + _workingWindow.top); + subtitleSurface.free(); + } + sub->redraw = false; + } - _backgroundOffset.y = CLIP<int16>(_backgroundOffset.y, _screenCenterY, (int16)_backgroundHeight - _screenCenterY); + Common::Rect rect( + _subtitleArea.left, + _subtitleArea.top, + _subtitleArea.left + _subtitleSurface.w, + _subtitleArea.top + _subtitleSurface.h + ); + copyToScreen(_subtitleSurface, rect, 0, 0); + } +} - renderImageToScreen(_currentBackground, 0, _screenCenterY - _backgroundOffset.y, true); - } else if (state == RenderTable::PANORAMA) { - _backgroundOffset += Common::Point(offset, 0); +Common::Point RenderManager::getBkgSize() { + return Common::Point(_backgroundWidth, _backgroundHeight); +} - if (_backgroundOffset.x <= -_backgroundWidth) - _backgroundOffset.x += _backgroundWidth; - else if (_backgroundOffset.x >= _backgroundWidth) - _backgroundOffset.x -= _backgroundWidth; +void RenderManager::addEffect(GraphicsEffect *_effect) { + _effects.push_back(_effect); +} - renderImageToScreen(_currentBackground, _screenCenterX - _backgroundOffset.x, 0, true); - } else { - renderImageToScreen(_currentBackground, 0, 0); +void RenderManager::deleteEffect(uint32 ID) { + for (EffectsList::iterator it = _effects.begin(); it != _effects.end(); it++) { + if ((*it)->getKey() == ID) { + delete *it; + it = _effects.erase(it); + } } } -uint32 RenderManager::getCurrentBackgroundOffset() { +Common::Rect RenderManager::transformBackgroundSpaceRectToScreenSpace(const Common::Rect &src) { + Common::Rect tmp = src; RenderTable::RenderState state = _renderTable.getRenderState(); if (state == RenderTable::PANORAMA) { - return _backgroundOffset.x; + if (_backgroundOffset < _screenCenterX) { + Common::Rect rScreen(_screenCenterX + _backgroundOffset, _workingWindow.height()); + Common::Rect lScreen(_workingWindow.width() - rScreen.width(), _workingWindow.height()); + lScreen.translate(_backgroundWidth - lScreen.width(), 0); + lScreen.clip(src); + rScreen.clip(src); + if (rScreen.width() < lScreen.width()) { + tmp.translate(_screenCenterX - _backgroundOffset - _backgroundWidth, 0); + } else { + tmp.translate(_screenCenterX - _backgroundOffset, 0); + } + } else if (_backgroundWidth - _backgroundOffset < _screenCenterX) { + Common::Rect rScreen(_screenCenterX - (_backgroundWidth - _backgroundOffset), _workingWindow.height()); + Common::Rect lScreen(_workingWindow.width() - rScreen.width(), _workingWindow.height()); + lScreen.translate(_backgroundWidth - lScreen.width(), 0); + lScreen.clip(src); + rScreen.clip(src); + if (lScreen.width() < rScreen.width()) { + tmp.translate(_screenCenterX + (_backgroundWidth - _backgroundOffset), 0); + } else { + tmp.translate(_screenCenterX - _backgroundOffset, 0); + } + } else { + tmp.translate(_screenCenterX - _backgroundOffset, 0); + } } else if (state == RenderTable::TILT) { - return _backgroundOffset.y; - } else { - return 0; + tmp.translate(0, (_screenCenterY - _backgroundOffset)); } + + return tmp; } -Graphics::Surface *RenderManager::tranposeSurface(const Graphics::Surface *surface) { - Graphics::Surface *tranposedSurface = new Graphics::Surface(); - tranposedSurface->create(surface->h, surface->w, surface->format); +EffectMap *RenderManager::makeEffectMap(const Common::Point &xy, int16 depth, const Common::Rect &rect, int8 *_minComp, int8 *_maxComp) { + Common::Rect bkgRect(_backgroundWidth, _backgroundHeight); + if (!bkgRect.contains(xy)) + return NULL; + + if (!bkgRect.intersects(rect)) + return NULL; + + uint16 color = *(uint16 *)_currentBackgroundImage.getBasePtr(xy.x, xy.y); + uint8 stC1, stC2, stC3; + _currentBackgroundImage.format.colorToRGB(color, stC1, stC2, stC3); + EffectMap *newMap = new EffectMap; + + EffectMapUnit unit; + unit.count = 0; + unit.inEffect = false; + + int16 w = rect.width(); + int16 h = rect.height(); + + bool first = true; + + uint8 minComp = MIN(MIN(stC1, stC2), stC3); + uint8 maxComp = MAX(MAX(stC1, stC2), stC3); + + uint8 depth8 = depth << 3; + + for (int16 j = 0; j < h; j++) { + uint16 *pix = (uint16 *)_currentBackgroundImage.getBasePtr(rect.left, rect.top + j); + for (int16 i = 0; i < w; i++) { + uint16 curClr = pix[i]; + uint8 cC1, cC2, cC3; + _currentBackgroundImage.format.colorToRGB(curClr, cC1, cC2, cC3); + + bool use = false; + + if (curClr == color) + use = true; + else if (curClr > color) { + if ((cC1 - stC1 < depth8) && + (cC2 - stC2 < depth8) && + (cC3 - stC3 < depth8)) + use = true; + } else { /* if (curClr < color) */ + if ((stC1 - cC1 < depth8) && + (stC2 - cC2 < depth8) && + (stC3 - cC3 < depth8)) + use = true; + } - const uint16 *source = (const uint16 *)surface->getPixels(); - uint16 *dest = (uint16 *)tranposedSurface->getPixels(); + if (first) { + unit.inEffect = use; + first = false; + } - for (uint32 y = 0; y < tranposedSurface->h; ++y) { - uint32 columnIndex = y * tranposedSurface->w; + if (use) { + uint8 cMinComp = MIN(MIN(cC1, cC2), cC3); + uint8 cMaxComp = MAX(MAX(cC1, cC2), cC3); + if (cMinComp < minComp) + minComp = cMinComp; + if (cMaxComp > maxComp) + maxComp = cMaxComp; + } - for (uint32 x = 0; x < tranposedSurface->w; ++x) { - dest[columnIndex + x] = source[x * surface->w + y]; + if (unit.inEffect == use) + unit.count++; + else { + newMap->push_back(unit); + unit.count = 1; + unit.inEffect = use; + } } } + newMap->push_back(unit); - return tranposedSurface; + if (_minComp) { + if (minComp - depth8 < 0) + *_minComp = -(minComp >> 3); + else + *_minComp = -depth; + } + if (_maxComp) { + if ((int16)maxComp + (int16)depth8 > 255) + *_maxComp = (255 - maxComp) >> 3; + else + *_maxComp = depth; + } + + return newMap; +} + +EffectMap *RenderManager::makeEffectMap(const Graphics::Surface &surf, uint16 transp) { + EffectMapUnit unit; + unit.count = 0; + unit.inEffect = false; + + int16 w = surf.w; + int16 h = surf.h; + + EffectMap *newMap = new EffectMap; + + bool first = true; + + for (int16 j = 0; j < h; j++) { + const uint16 *pix = (const uint16 *)surf.getBasePtr(0, j); + for (int16 i = 0; i < w; i++) { + bool use = false; + if (pix[i] != transp) + use = true; + + if (first) { + unit.inEffect = use; + first = false; + } + + if (unit.inEffect == use) + unit.count++; + else { + newMap->push_back(unit); + unit.count = 1; + unit.inEffect = use; + } + } + } + newMap->push_back(unit); + + return newMap; +} + +void RenderManager::markDirty() { + _backgroundDirtyRect = Common::Rect(_backgroundWidth, _backgroundHeight); +} + +#if 0 +void RenderManager::bkgFill(uint8 r, uint8 g, uint8 b) { + _currentBackgroundImage.fillRect(Common::Rect(_currentBackgroundImage.w, _currentBackgroundImage.h), _currentBackgroundImage.format.RGBToColor(r, g, b)); + markDirty(); +} +#endif + +void RenderManager::timedMessage(const Common::String &str, uint16 milsecs) { + uint16 msgid = createSubArea(); + updateSubArea(msgid, str); + deleteSubArea(msgid, milsecs); +} + +bool RenderManager::askQuestion(const Common::String &str) { + Graphics::Surface textSurface; + textSurface.create(_subtitleArea.width(), _subtitleArea.height(), _engine->_resourcePixelFormat); + _engine->getTextRenderer()->drawTextWithWordWrapping(str, textSurface); + copyToScreen(textSurface, _subtitleArea, 0, 0); + + _engine->stopClock(); + + int result = 0; + + while (result == 0) { + Common::Event evnt; + while (_engine->getEventManager()->pollEvent(evnt)) { + if (evnt.type == Common::EVENT_KEYDOWN) { + // English: yes/no + // German: ja/nein + // Spanish: si/no + // French Nemesis: F4/any other key + // French ZGI: oui/non + switch (evnt.kbd.keycode) { + case Common::KEYCODE_y: + if (_engine->getLanguage() == Common::EN_ANY) + result = 2; + break; + case Common::KEYCODE_j: + if (_engine->getLanguage() == Common::DE_DEU) + result = 2; + break; + case Common::KEYCODE_s: + if (_engine->getLanguage() == Common::ES_ESP) + result = 2; + break; + case Common::KEYCODE_o: + if (_engine->getLanguage() == Common::FR_FRA && _engine->getGameId() == GID_GRANDINQUISITOR) + result = 2; + break; + case Common::KEYCODE_F4: + if (_engine->getLanguage() == Common::FR_FRA && _engine->getGameId() == GID_NEMESIS) + result = 2; + break; + case Common::KEYCODE_n: + result = 1; + break; + default: + if (_engine->getLanguage() == Common::FR_FRA && _engine->getGameId() == GID_NEMESIS) + result = 1; + break; + } + } + } + _system->updateScreen(); + if (_doubleFPS) + _system->delayMillis(33); + else + _system->delayMillis(66); + } + + // Draw over the text in order to clear it + textSurface.fillRect(Common::Rect(_subtitleArea.width(), _subtitleArea.height()), 0); + copyToScreen(textSurface, _subtitleArea, 0, 0); + + // Free the surface + textSurface.free(); + + _engine->startClock(); + return result == 2; +} + +void RenderManager::delayedMessage(const Common::String &str, uint16 milsecs) { + uint16 msgid = createSubArea(); + updateSubArea(msgid, str); + processSubs(0); + renderSceneToScreen(); + _engine->stopClock(); + + uint32 stopTime = _system->getMillis() + milsecs; + while (_system->getMillis() < stopTime) { + Common::Event evnt; + while (_engine->getEventManager()->pollEvent(evnt)) { + if (evnt.type == Common::EVENT_KEYDOWN && + (evnt.kbd.keycode == Common::KEYCODE_SPACE || + evnt.kbd.keycode == Common::KEYCODE_RETURN || + evnt.kbd.keycode == Common::KEYCODE_ESCAPE)) + break; + } + _system->updateScreen(); + if (_doubleFPS) + _system->delayMillis(33); + else + _system->delayMillis(66); + } + deleteSubArea(msgid); + _engine->startClock(); +} + +void RenderManager::showDebugMsg(const Common::String &msg, int16 delay) { + uint16 msgid = createSubArea(); + updateSubArea(msgid, msg); + deleteSubArea(msgid, delay); +} + +void RenderManager::updateRotation() { + int16 _velocity = _engine->getMouseVelocity() + _engine->getKeyboardVelocity(); + ScriptManager *scriptManager = _engine->getScriptManager(); + + if (_doubleFPS) + _velocity /= 2; + + if (_velocity) { + RenderTable::RenderState renderState = _renderTable.getRenderState(); + if (renderState == RenderTable::PANORAMA) { + int16 startPosition = scriptManager->getStateValue(StateKey_ViewPos); + + int16 newPosition = startPosition + (_renderTable.getPanoramaReverse() ? -_velocity : _velocity); + + int16 zeroPoint = _renderTable.getPanoramaZeroPoint(); + if (startPosition >= zeroPoint && newPosition < zeroPoint) + scriptManager->setStateValue(StateKey_Rounds, scriptManager->getStateValue(StateKey_Rounds) - 1); + if (startPosition <= zeroPoint && newPosition > zeroPoint) + scriptManager->setStateValue(StateKey_Rounds, scriptManager->getStateValue(StateKey_Rounds) + 1); + + int16 screenWidth = getBkgSize().x; + if (screenWidth) + newPosition %= screenWidth; + + if (newPosition < 0) + newPosition += screenWidth; + + setBackgroundPosition(newPosition); + } else if (renderState == RenderTable::TILT) { + int16 startPosition = scriptManager->getStateValue(StateKey_ViewPos); + + int16 newPosition = startPosition + _velocity; + + int16 screenHeight = getBkgSize().y; + int16 tiltGap = (int16)_renderTable.getTiltGap(); + + if (newPosition >= (screenHeight - tiltGap)) + newPosition = screenHeight - tiltGap; + if (newPosition <= tiltGap) + newPosition = tiltGap; + + setBackgroundPosition(newPosition); + } + } +} + +void RenderManager::checkBorders() { + RenderTable::RenderState renderState = _renderTable.getRenderState(); + if (renderState == RenderTable::PANORAMA) { + int16 startPosition = _engine->getScriptManager()->getStateValue(StateKey_ViewPos); + + int16 newPosition = startPosition; + + int16 screenWidth = getBkgSize().x; + + if (screenWidth) + newPosition %= screenWidth; + + if (newPosition < 0) + newPosition += screenWidth; + + if (startPosition != newPosition) + setBackgroundPosition(newPosition); + } else if (renderState == RenderTable::TILT) { + int16 startPosition = _engine->getScriptManager()->getStateValue(StateKey_ViewPos); + + int16 newPosition = startPosition; + + int16 screenHeight = getBkgSize().y; + int16 tiltGap = (int16)_renderTable.getTiltGap(); + + if (newPosition >= (screenHeight - tiltGap)) + newPosition = screenHeight - tiltGap; + if (newPosition <= tiltGap) + newPosition = tiltGap; + + if (startPosition != newPosition) + setBackgroundPosition(newPosition); + } +} + +void RenderManager::rotateTo(int16 _toPos, int16 _time) { + if (_renderTable.getRenderState() != RenderTable::PANORAMA) + return; + + if (_time == 0) + _time = 1; + + int32 maxX = getBkgSize().x; + int32 curX = getCurrentBackgroundOffset(); + int32 dx = 0; + + if (curX == _toPos) + return; + + if (curX > _toPos) { + if (curX - _toPos > maxX / 2) + dx = (_toPos + (maxX - curX)) / _time; + else + dx = -(curX - _toPos) / _time; + } else { + if (_toPos - curX > maxX / 2) + dx = -((maxX - _toPos) + curX) / _time; + else + dx = (_toPos - curX) / _time; + } + + _engine->stopClock(); + + for (int16 i = 0; i <= _time; i++) { + if (i == _time) + curX = _toPos; + else + curX += dx; + + if (curX < 0) + curX = maxX - curX; + else if (curX >= maxX) + curX %= maxX; + + setBackgroundPosition(curX); + + prepareBackground(); + renderSceneToScreen(); + + _system->updateScreen(); + + _system->delayMillis(500 / _time); + } + + _engine->startClock(); +} + +void RenderManager::upscaleRect(Common::Rect &rect) { + rect.top = rect.top * HIRES_WINDOW_HEIGHT / WINDOW_HEIGHT; + rect.left = rect.left * HIRES_WINDOW_WIDTH / WINDOW_WIDTH; + rect.bottom = rect.bottom * HIRES_WINDOW_HEIGHT / WINDOW_HEIGHT; + rect.right = rect.right * HIRES_WINDOW_WIDTH / WINDOW_WIDTH; } } // End of namespace ZVision diff --git a/engines/zvision/graphics/render_manager.h b/engines/zvision/graphics/render_manager.h index 9feff4c030..33d8a88e78 100644 --- a/engines/zvision/graphics/render_manager.h +++ b/engines/zvision/graphics/render_manager.h @@ -8,12 +8,12 @@ * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. - * + * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. @@ -24,13 +24,14 @@ #define ZVISION_RENDER_MANAGER_H #include "zvision/graphics/render_table.h" -#include "zvision/fonts/truetype_font.h" +#include "zvision/text/truetype_font.h" #include "common/rect.h" #include "common/hashmap.h" #include "graphics/surface.h" +#include "graphics_effect.h" class OSystem; @@ -47,171 +48,131 @@ namespace ZVision { class RenderManager { public: - RenderManager(OSystem *system, uint32 windowWidth, uint32 windowHeight, const Common::Rect workingWindow, const Graphics::PixelFormat pixelFormat); + RenderManager(ZVision *engine, uint32 windowWidth, uint32 windowHeight, const Common::Rect workingWindow, const Graphics::PixelFormat pixelFormat, bool doubleFPS); ~RenderManager(); private: - struct AlphaDataEntry { - Graphics::Surface *data; - uint16 alphaColor; - uint16 destX; - uint16 destY; - uint16 width; - uint16 height; + struct OneSubtitle { + Common::Rect r; + Common::String txt; + int16 timer; + bool todelete; + bool redraw; }; - typedef Common::HashMap<uint32, AlphaDataEntry> AlphaEntryMap; + typedef Common::HashMap<uint16, OneSubtitle> SubtitleMap; + typedef Common::List<GraphicsEffect *> EffectsList; private: + ZVision *_engine; OSystem *_system; const Graphics::PixelFormat _pixelFormat; - // A buffer the exact same size as the workingWindow - // This buffer stores everything un-warped, then does a warp at the end of the frame - Graphics::Surface _workingWindowBuffer; - // A buffer representing the entire screen. Any graphical updates are first done with this buffer - // before actually being blitted to the screen - Graphics::Surface _backBuffer; - // A list of Alpha Entries that need to be blitted to the backbuffer - AlphaEntryMap _alphaDataEntries; - - // A rectangle representing the portion of the working window where the pixels have been changed since last frame - Common::Rect _workingWindowDirtyRect; - // A rectangle representing the portion of the backbuffer where the pixels have been changed since last frame - Common::Rect _backBufferDirtyRect; - - /** Width of the working window. Saved to prevent extraneous calls to _workingWindow.width() */ - const int _workingWidth; - /** Height of the working window. Saved to prevent extraneous calls to _workingWindow.height() */ - const int _workingHeight; - /** Center of the screen in the x direction */ - const int _screenCenterX; - /** Center of the screen in the y direction */ - const int _screenCenterY; - /** * A Rectangle centered inside the actual window. All in-game coordinates * are given in this coordinate space. Also, all images are clipped to the * edges of this Rectangle */ - const Common::Rect _workingWindow; - /** Used to warp the background image */ - RenderTable _renderTable; + Common::Rect _workingWindow; - Graphics::Surface _currentBackground; - /** The (x1,y1) coordinates of the subRectangle of the background that is currently displayed on the screen */ - Common::Point _backgroundOffset; + // Center of the screen in the x direction + const int _screenCenterX; + // Center of the screen in the y direction + const int _screenCenterY; + + /** A buffer for background image that's being used to create the background */ + Graphics::Surface _currentBackgroundImage; + Common::Rect _backgroundDirtyRect; + + /** + * The x1 or y1 offset of the subRectangle of the background that is currently displayed on the screen + * It will be x1 if PANORAMA, or y1 if TILT + */ + int16 _backgroundOffset; /** The width of the current background image */ uint16 _backgroundWidth; /** The height of the current background image */ uint16 _backgroundHeight; - /** - * The "velocity" at which the background image is panning. We actually store the inverse of velocity (ms/pixel instead of pixels/ms) - * because it allows you to accumulate whole pixels 'steps' instead of rounding pixels every frame - */ - int _backgroundInverseVelocity; - /** Holds any 'leftover' milliseconds between frames */ - uint _accumulatedVelocityMilliseconds; + // A buffer that holds the portion of the background that is used to render the final image + // If it's a normal scene, the pixels will be blitted directly to the screen + // If it's a panorma / tilt scene, the pixels will be first warped to _warpedSceneSurface + Graphics::Surface _backgroundSurface; + Common::Rect _backgroundSurfaceDirtyRect; -public: - void initialize(); - /** - * Rotates the background image in accordance to the current _backgroundInverseVelocity - * - * @param deltaTimeInMillis The amount of time that has passed since the last frame - */ - void update(uint deltaTimeInMillis); + // A buffer for subtitles + Graphics::Surface _subtitleSurface; - /** - * Renders the current state of the backbuffer to the screen - */ - void renderBackbufferToScreen(); + // Rectangle for subtitles area + Common::Rect _subtitleArea; - /** - * Renders all AlphaEntries to the backbuffer - */ - void processAlphaEntries(); - /** - * Clears the AlphaEntry list - */ - void clearAlphaEntries() { _alphaDataEntries.clear(); } - /** - * Removes a specific AlphaEntry from the list - * - * @param idNumber The id number identifing the AlphaEntry - */ - void removeAlphaEntry(uint32 idNumber) { _alphaDataEntries.erase(idNumber); } + // A buffer for menu drawing + Graphics::Surface _menuSurface; + Common::Rect _menuSurfaceDirtyRect; - /** - * Copies a sub-rectangle of a buffer to the working window - * - * @param buffer The pixel data to copy to the working window - * @param destX The X destination in the working window where the subRect of data should be put - * @param destY The Y destination in the working window where the subRect of data should be put - * @param imageWidth The width of the source image - * @param width The width of the sub rectangle - * @param height The height of the sub rectangle - */ - void copyRectToWorkingWindow(const uint16 *buffer, int32 destX, int32 destY, int32 imageWidth, int32 width, int32 height); - /** - * Copies a sub-rectangle of a buffer to the working window with binary alpha support. - * - * @param buffer The pixel data to copy to the working window - * @param destX The X destination in the working window where the subRect of data should be put - * @param destY The Y destination in the working window where the subRect of data should be put - * @param imageWidth The width of the source image - * @param width The width of the sub rectangle - * @param height The height of the sub rectangle - * @param alphaColor The color to interpret as meaning 'transparent' - * @param idNumber A unique identifier for the data being copied over. - */ - void copyRectToWorkingWindow(const uint16 *buffer, int32 destX, int32 destY, int32 imageWidth, int32 width, int32 height, int16 alphaColor, uint32 idNumber); + // Rectangle for menu area + Common::Rect _menuArea; + + // A buffer used for apply graphics effects + Graphics::Surface _effectSurface; + + // A buffer to store the result of the panorama / tilt warps + Graphics::Surface _warpedSceneSurface; - /** - * Renders the supplied text to the working window - * - * @param idNumber A unique identifier for the text - * @param text The text to be rendered - * @param font The font to use to render the text - * @param destX The X destination in the working window where the text should be rendered - * @param destY The Y destination in the working window where the text should be rendered - * @param textColor The color to render the text with (in RBG 565) - * @param maxWidth The max width the text should take up. - * @param maxHeight The max height the text should take up. - * @param align The alignment of the text within the bounds of maxWidth - * @param wrap If true, any words extending past maxWidth will wrap to a new line. If false, ellipses will be rendered to show that the text didn't fit - * @return A rectangle representing where the text was drawn in the working window - */ - Common::Rect renderTextToWorkingWindow(uint32 idNumber, const Common::String &text, TruetypeFont *font, int destX, int destY, uint16 textColor, int maxWidth, int maxHeight = -1, Graphics::TextAlign align = Graphics::kTextAlignLeft, bool wrap = true); + + /** Used to warp the background image */ + RenderTable _renderTable; + + // Internal subtitles counter + uint16 _subid; + + // Subtitle list + SubtitleMap _subsList; + + // Visual effects list + EffectsList _effects; + + bool _doubleFPS; + +public: + void initialize(); /** - * Fills the entire workingWindow with the specified color. Internally, the color - * will be converted to RGB 565 and then blitted. - * - * @param color The color to fill the working window with. (In RGB 555) + * Renders the scene to the screen */ - void clearWorkingWindowTo555Color(uint16 color); + void renderSceneToScreen(); + + void copyToScreen(const Graphics::Surface &surface, Common::Rect &rect, int16 srcLeft, int16 srcTop); /** - * Blits the image or a portion of the image to the backbuffer. Actual screen updates won't happen until the end of the frame. - * The image will be clipped to fit inside the working window. Coords are in working window space, not screen space! + * Blits the image or a portion of the image to the background. * * @param fileName Name of the image file * @param destinationX X position where the image should be put. Coords are in working window space, not screen space! * @param destinationY Y position where the image should be put. Coords are in working window space, not screen space! */ - void renderImageToScreen(const Common::String &fileName, int16 destinationX, int16 destinationY, bool wrap = false); + void renderImageToBackground(const Common::String &fileName, int16 destinationX, int16 destinationY); /** - * Blits the image or a portion of the image to the backbuffer. Actual screen updates won't happen until the end of the frame. - * The image will be clipped to fit inside the working window. Coords are in working window space, not screen space! + * Blits the image or a portion of the image to the background. * - * @param stream Surface to read the image data from - * @param destinationX X position where the image should be put. Coords are in working window space, not screen space! - * @param destinationY Y position where the image should be put. Coords are in working window space, not screen space! + * @param fileName Name of the image file + * @param destX X position where the image should be put. Coords are in working window space, not screen space! + * @param destY Y position where the image should be put. Coords are in working window space, not screen space! + * @param colorkey Transparent color */ - void renderImageToScreen(Graphics::Surface &surface, int16 destinationX, int16 destinationY, bool wrap = false); + void renderImageToBackground(const Common::String &fileName, int16 destX, int16 destY, uint32 colorkey); + + /** + * Blits the image or a portion of the image to the background. + * + * @param fileName Name of the image file + * @param destX X position where the image should be put. Coords are in working window space, not screen space! + * @param destY Y position where the image should be put. Coords are in working window space, not screen space! + * @param keyX X position of transparent color + * @param keyY Y position of transparent color + */ + void renderImageToBackground(const Common::String &fileName, int16 destX, int16 destY, int16 keyX, int16 keyY); /** * Sets the current background image to be used by the RenderManager and immediately @@ -234,41 +195,18 @@ public: void setBackgroundPosition(int offset); /** - * Set the background scroll velocity. Negative velocities correspond to left / up scrolling and - * positive velocities correspond to right / down scrolling - * - * @param velocity Velocity - */ - void setBackgroundVelocity(int velocity); - - /** * Converts a point in screen coordinate space to image coordinate space * * @param point Point in screen coordinate space * @return Point in image coordinate space */ const Common::Point screenSpaceToImageSpace(const Common::Point &point); - /** - * Converts a point in image coordinate space to ***PRE-WARP*** - * working window coordinate space - * - * @param point Point in image coordinate space - * @return Point in PRE-WARP working window coordinate space - */ - const Common::Point imageSpaceToWorkingWindowSpace(const Common::Point &point); - - /** - * Clip a rectangle to the working window. If it returns false, the original rect - * is not inside the working window. - * - * @param rect The rectangle to clip against the working window - * @return Is rect at least partially inside the working window (true) or completely outside (false) - */ - bool clipRectToWorkingWindow(Common::Rect &rect); + // Return pointer of RenderTable object RenderTable *getRenderTable(); + + // Return current background offset uint32 getCurrentBackgroundOffset(); - const Graphics::Surface *getBackBuffer() { return &_backBuffer; } /** * Creates a copy of surface and transposes the data. @@ -281,25 +219,62 @@ public: */ static Graphics::Surface *tranposeSurface(const Graphics::Surface *surface); -private: - /** - * Renders a subRectangle of an image to the backbuffer. The destinationRect and SubRect - * will be clipped to image bound and to working window bounds - * - * @param buffer Pointer to (0, 0) of the image data - * @param imageWidth The width of the original image (not of the subRectangle) - * @param imageHeight The width of the original image (not of the subRectangle) - * @param horizontalPitch The horizontal pitch of the original image - * @param destinationX The x coordinate (in working window space) of where to put the final image - * @param destinationY The y coordinate (in working window space) of where to put the final image - * @param subRectangle A rectangle representing the part of the image that should be rendered - * @param wrap Should the image wrap (tile) if it doesn't completely fill the screen? - */ - void renderSubRectToScreen(Graphics::Surface &surface, int16 destinationX, int16 destinationY, bool wrap); + // Scale buffer (nearest) + void scaleBuffer(const void *src, void *dst, uint32 srcWidth, uint32 srcHeight, byte bytesPerPixel, uint32 dstWidth, uint32 dstHeight); + + // Blitting surface-to-surface methods + void blitSurfaceToSurface(const Graphics::Surface &src, const Common::Rect &_srcRect , Graphics::Surface &dst, int x, int y); + void blitSurfaceToSurface(const Graphics::Surface &src, const Common::Rect &_srcRect , Graphics::Surface &dst, int _x, int _y, uint32 colorkey); + + // Blitting surface-to-background methods + void blitSurfaceToBkg(const Graphics::Surface &src, int x, int y, int32 colorkey = -1); + + // Blitting surface-to-background methods with scale + void blitSurfaceToBkgScaled(const Graphics::Surface &src, const Common::Rect &_dstRect, int32 colorkey = -1); + + // Blitting surface-to-menu methods + void blitSurfaceToMenu(const Graphics::Surface &src, int x, int y, int32 colorkey = -1); + + // Subtitles methods + + void initSubArea(uint32 windowWidth, uint32 windowHeight, const Common::Rect workingWindow); + + // Create subtitle area and return ID + uint16 createSubArea(const Common::Rect &area); + uint16 createSubArea(); + + // Delete subtitle by ID + void deleteSubArea(uint16 id); + void deleteSubArea(uint16 id, int16 delay); + + // Update subtitle area + void updateSubArea(uint16 id, const Common::String &txt); + + // Processing subtitles + void processSubs(uint16 deltatime); + + // Return background size + Common::Point getBkgSize(); + + // Return portion of background as new surface + Graphics::Surface *getBkgRect(Common::Rect &rect); + + // Load image into new surface + Graphics::Surface *loadImage(Common::String file); + Graphics::Surface *loadImage(Common::String file, bool transposed); + + // Clear whole/area of menu surface + void clearMenuSurface(); + void clearMenuSurface(const Common::Rect &r); + + // Copy menu buffer to screen + void renderMenuToScreen(); + + // Copy needed portion of background surface to workingWindow surface + void prepareBackground(); /** - * Reads an image file pixel data into a Surface buffer. In the process - * it converts the pixel data from RGB 555 to RGB 565. Also, if the image + * Reads an image file pixel data into a Surface buffer. Also, if the image * is transposed, it will un-transpose the pixel data. The function will * call destination::create() if the dimensions of destination do not match * up with the dimensions of the image. @@ -310,17 +285,55 @@ private: void readImageToSurface(const Common::String &fileName, Graphics::Surface &destination); /** - * Move the background image by an offset. If we are currently in Panorama mode, - * the offset will correspond to a horizontal motion. If we are currently in Tilt mode, - * the offset will correspond to a vertical motion. This function should not be called - * if we are in Flat mode. - * - * The RenderManager will take care of wrapping the image. - * Ex: If the image has width 1400px, it is legal to offset 1500px. + * Reads an image file pixel data into a Surface buffer. Also, if the image + * is transposed, it will un-transpose the pixel data. The function will + * call destination::create() if the dimensions of destination do not match + * up with the dimensions of the image. * - * @param offset The amount to move the background + * @param fileName The name of a .tga file + * @param destination A reference to the Surface to store the pixel data in + * @param transposed Transpose flag */ - void moveBackground(int offset); + void readImageToSurface(const Common::String &fileName, Graphics::Surface &destination, bool transposed); + + // Add visual effect to effects list + void addEffect(GraphicsEffect *_effect); + + // Delete effect(s) by ID (ID equal to slot of action:region that create this effect) + void deleteEffect(uint32 ID); + + // Create "mask" for effects - (color +/- depth) will be selected as not transparent. Like color selection + // xy - base color + // depth - +/- of base color + // rect - rectangle where select pixels + // minD - if not NULL will recieve real bottom border of depth + // maxD - if not NULL will recieve real top border of depth + EffectMap *makeEffectMap(const Common::Point &xy, int16 depth, const Common::Rect &rect, int8 *minD, int8 *maxD); + + // Create "mask" for effects by simple transparent color + EffectMap *makeEffectMap(const Graphics::Surface &surf, uint16 transp); + + // Return background rectangle in screen coordinates + Common::Rect transformBackgroundSpaceRectToScreenSpace(const Common::Rect &src); + + // Mark whole background surface as dirty + void markDirty(); + +#if 0 + // Fill background surface by color + void bkgFill(uint8 r, uint8 g, uint8 b); +#endif + + bool askQuestion(const Common::String &str); + void delayedMessage(const Common::String &str, uint16 milsecs); + void timedMessage(const Common::String &str, uint16 milsecs); + void showDebugMsg(const Common::String &msg, int16 delay = 3000); + + void checkBorders(); + void rotateTo(int16 to, int16 time); + void updateRotation(); + + void upscaleRect(Common::Rect &rect); }; } // End of namespace ZVision diff --git a/engines/zvision/graphics/render_table.cpp b/engines/zvision/graphics/render_table.cpp index 49b934dc37..df73247344 100644 --- a/engines/zvision/graphics/render_table.cpp +++ b/engines/zvision/graphics/render_table.cpp @@ -21,23 +21,22 @@ */ #include "common/scummsys.h" - #include "zvision/graphics/render_table.h" - #include "common/rect.h" - #include "graphics/colormasks.h" - namespace ZVision { RenderTable::RenderTable(uint numColumns, uint numRows) - : _numRows(numRows), - _numColumns(numColumns), - _renderState(FLAT) { + : _numRows(numRows), + _numColumns(numColumns), + _renderState(FLAT) { assert(numRows != 0 && numColumns != 0); _internalBuffer = new Common::Point[numRows * numColumns]; + + memset(&_panoramaOptions, 0, sizeof(_panoramaOptions)); + memset(&_tiltOptions, 0, sizeof(_tiltOptions)); } RenderTable::~RenderTable() { @@ -52,10 +51,11 @@ void RenderTable::setRenderState(RenderState newState) { _panoramaOptions.fieldOfView = 27.0f; _panoramaOptions.linearScale = 0.55f; _panoramaOptions.reverse = false; + _panoramaOptions.zeroPoint = 0; break; case TILT: _tiltOptions.fieldOfView = 27.0f; - _tiltOptions.linearScale = 0.55f; + _tiltOptions.linearScale = 0.65f; _tiltOptions.reverse = false; break; case FLAT: @@ -81,28 +81,7 @@ const Common::Point RenderTable::convertWarpedCoordToFlatCoord(const Common::Poi return newPoint; } -uint16 mixTwoRGB(uint16 colorOne, uint16 colorTwo, float percentColorOne) { - assert(percentColorOne < 1.0f); - - float rOne = float((colorOne & Graphics::ColorMasks<555>::kRedMask) >> Graphics::ColorMasks<555>::kRedShift); - float rTwo = float((colorTwo & Graphics::ColorMasks<555>::kRedMask) >> Graphics::ColorMasks<555>::kRedShift); - float gOne = float((colorOne & Graphics::ColorMasks<555>::kGreenMask) >> Graphics::ColorMasks<555>::kGreenShift); - float gTwo = float((colorTwo & Graphics::ColorMasks<555>::kGreenMask) >> Graphics::ColorMasks<555>::kGreenShift); - float bOne = float((colorOne & Graphics::ColorMasks<555>::kBlueMask) >> Graphics::ColorMasks<555>::kBlueShift); - float bTwo = float((colorTwo & Graphics::ColorMasks<555>::kBlueMask) >> Graphics::ColorMasks<555>::kBlueShift); - - float rFinal = rOne * percentColorOne + rTwo * (1.0f - percentColorOne); - float gFinal = gOne * percentColorOne + gTwo * (1.0f - percentColorOne); - float bFinal = bOne * percentColorOne + bTwo * (1.0f - percentColorOne); - - uint16 returnColor = (byte(rFinal + 0.5f) << Graphics::ColorMasks<555>::kRedShift) | - (byte(gFinal + 0.5f) << Graphics::ColorMasks<555>::kGreenShift) | - (byte(bFinal + 0.5f) << Graphics::ColorMasks<555>::kBlueShift); - - return returnColor; -} - -void RenderTable::mutateImage(uint16 *sourceBuffer, uint16* destBuffer, uint32 destWidth, const Common::Rect &subRect) { +void RenderTable::mutateImage(uint16 *sourceBuffer, uint16 *destBuffer, uint32 destWidth, const Common::Rect &subRect) { uint32 destOffset = 0; for (int16 y = subRect.top; y < subRect.bottom; ++y) { @@ -123,6 +102,28 @@ void RenderTable::mutateImage(uint16 *sourceBuffer, uint16* destBuffer, uint32 d } } +void RenderTable::mutateImage(Graphics::Surface *dstBuf, Graphics::Surface *srcBuf) { + uint32 destOffset = 0; + + uint16 *sourceBuffer = (uint16 *)srcBuf->getPixels(); + uint16 *destBuffer = (uint16 *)dstBuf->getPixels(); + + for (int16 y = 0; y < srcBuf->h; ++y) { + uint32 sourceOffset = y * _numColumns; + + for (int16 x = 0; x < srcBuf->w; ++x) { + uint32 index = sourceOffset + x; + + // RenderTable only stores offsets from the original coordinates + uint32 sourceYIndex = y + _internalBuffer[index].y; + uint32 sourceXIndex = x + _internalBuffer[index].x; + + destBuffer[destOffset] = sourceBuffer[sourceYIndex * _numColumns + sourceXIndex]; + destOffset++; + } + } +} + void RenderTable::generateRenderTable() { switch (_renderState) { case ZVision::RenderTable::PANORAMA: @@ -177,6 +178,7 @@ void RenderTable::generateTiltLookupTable() { float fovInRadians = (_tiltOptions.fieldOfView * M_PI / 180.0f); float cylinderRadius = halfWidth / tan(fovInRadians); + _tiltOptions.gap = cylinderRadius * atan2((float)(halfHeight / cylinderRadius), 1.0f) * _tiltOptions.linearScale; for (uint y = 0; y < _numRows; ++y) { @@ -221,6 +223,18 @@ void RenderTable::setPanoramaReverse(bool reverse) { _panoramaOptions.reverse = reverse; } +bool RenderTable::getPanoramaReverse() { + return _panoramaOptions.reverse; +} + +void RenderTable::setPanoramaZeroPoint(uint16 point) { + _panoramaOptions.zeroPoint = point; +} + +uint16 RenderTable::getPanoramaZeroPoint() { + return _panoramaOptions.zeroPoint; +} + void RenderTable::setTiltFoV(float fov) { assert(fov > 0.0f); @@ -237,4 +251,26 @@ void RenderTable::setTiltReverse(bool reverse) { _tiltOptions.reverse = reverse; } +float RenderTable::getTiltGap() { + return _tiltOptions.gap; +} + +float RenderTable::getAngle() { + if (_renderState == TILT) + return _tiltOptions.fieldOfView; + else if (_renderState == PANORAMA) + return _panoramaOptions.fieldOfView; + else + return 1.0; +} + +float RenderTable::getLinscale() { + if (_renderState == TILT) + return _tiltOptions.linearScale; + else if (_renderState == PANORAMA) + return _panoramaOptions.linearScale; + else + return 1.0; +} + } // End of namespace ZVision diff --git a/engines/zvision/graphics/render_table.h b/engines/zvision/graphics/render_table.h index f066187ad1..7455d9ba39 100644 --- a/engines/zvision/graphics/render_table.h +++ b/engines/zvision/graphics/render_table.h @@ -24,7 +24,7 @@ #define ZVISION_RENDER_TABLE_H #include "common/rect.h" - +#include "graphics/surface.h" namespace ZVision { @@ -49,6 +49,7 @@ private: float fieldOfView; float linearScale; bool reverse; + uint16 zeroPoint; } _panoramaOptions; // TODO: See if tilt and panorama need to have separate options @@ -56,25 +57,36 @@ private: float fieldOfView; float linearScale; bool reverse; + float gap; } _tiltOptions; public: - RenderState getRenderState() { return _renderState; } + RenderState getRenderState() { + return _renderState; + } void setRenderState(RenderState newState); const Common::Point convertWarpedCoordToFlatCoord(const Common::Point &point); - void mutateImage(uint16 *sourceBuffer, uint16* destBuffer, uint32 destWidth, const Common::Rect &subRect); + void mutateImage(uint16 *sourceBuffer, uint16 *destBuffer, uint32 destWidth, const Common::Rect &subRect); + void mutateImage(Graphics::Surface *dstBuf, Graphics::Surface *srcBuf); void generateRenderTable(); void setPanoramaFoV(float fov); void setPanoramaScale(float scale); void setPanoramaReverse(bool reverse); + void setPanoramaZeroPoint(uint16 point); + uint16 getPanoramaZeroPoint(); + bool getPanoramaReverse(); void setTiltFoV(float fov); void setTiltScale(float scale); void setTiltReverse(bool reverse); + float getTiltGap(); + float getAngle(); + float getLinscale(); + private: void generatePanoramaLookupTable(); void generateTiltLookupTable(); diff --git a/engines/zvision/module.mk b/engines/zvision/module.mk index 2e298f24c6..93fba2879d 100644 --- a/engines/zvision/module.mk +++ b/engines/zvision/module.mk @@ -1,32 +1,51 @@ MODULE := engines/zvision MODULE_OBJS := \ - animation/rlf_animation.o \ - archives/zfs_archive.o \ core/console.o \ + core/clock.o \ core/events.o \ - core/save_manager.o \ - cursors/cursor.o \ - cursors/cursor_manager.o \ detection.o \ - fonts/truetype_font.o \ + file/lzss_read_stream.o \ + file/save_manager.o \ + file/search_manager.o \ + file/zfs_archive.o \ + graphics/cursors/cursor_manager.o \ + graphics/cursors/cursor.o \ + graphics/effects/fog.o \ + graphics/effects/light.o \ + graphics/effects/wave.o \ graphics/render_manager.o \ graphics/render_table.o \ scripting/actions.o \ scripting/control.o \ - scripting/controls/animation_control.o \ + scripting/controls/fist_control.o \ + scripting/controls/hotmov_control.o \ scripting/controls/input_control.o \ scripting/controls/lever_control.o \ + scripting/controls/paint_control.o \ scripting/controls/push_toggle_control.o \ - scripting/controls/timer_node.o \ + scripting/controls/safe_control.o \ + scripting/controls/save_control.o \ + scripting/controls/slot_control.o \ + scripting/controls/titler_control.o \ + scripting/inventory.o \ + scripting/menu.o \ scripting/scr_file_handling.o \ scripting/script_manager.o \ + scripting/effects/animation_effect.o \ + scripting/effects/distort_effect.o \ + scripting/effects/music_effect.o \ + scripting/effects/region_effect.o \ + scripting/effects/syncsound_effect.o \ + scripting/effects/timer_effect.o \ + scripting/effects/ttytext_effect.o \ + sound/midi.o \ sound/zork_raw.o \ - strings/string_manager.o \ - utility/clock.o \ - utility/lzss_read_stream.o \ - utility/single_value_container.o \ - utility/utility.o \ + text/string_manager.o \ + text/subtitles.o \ + text/text.o \ + text/truetype_font.o \ + video/rlf_decoder.o \ video/video.o \ video/zork_avi_decoder.o \ zvision.o diff --git a/engines/zvision/scripting/actions.cpp b/engines/zvision/scripting/actions.cpp index 517278e155..9a8b734e0c 100644 --- a/engines/zvision/scripting/actions.cpp +++ b/engines/zvision/scripting/actions.cpp @@ -8,12 +8,12 @@ * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. - * + * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. @@ -21,380 +21,1075 @@ */ #include "common/scummsys.h" +#include "video/video_decoder.h" #include "zvision/scripting/actions.h" #include "zvision/zvision.h" #include "zvision/scripting/script_manager.h" #include "zvision/graphics/render_manager.h" -#include "zvision/sound/zork_raw.h" -#include "zvision/video/zork_avi_decoder.h" -#include "zvision/scripting/controls/timer_node.h" -#include "zvision/scripting/controls/animation_control.h" - -#include "common/file.h" - -#include "audio/decoders/wave.h" - +#include "zvision/file/save_manager.h" +#include "zvision/scripting/menu.h" +#include "zvision/scripting/effects/timer_effect.h" +#include "zvision/scripting/effects/music_effect.h" +#include "zvision/scripting/effects/syncsound_effect.h" +#include "zvision/scripting/effects/animation_effect.h" +#include "zvision/scripting/effects/distort_effect.h" +#include "zvision/scripting/effects/ttytext_effect.h" +#include "zvision/scripting/effects/region_effect.h" +#include "zvision/scripting/controls/titler_control.h" +#include "zvision/graphics/render_table.h" +#include "zvision/graphics/graphics_effect.h" +#include "zvision/graphics/effects/fog.h" +#include "zvision/graphics/effects/light.h" +#include "zvision/graphics/effects/wave.h" +#include "zvision/graphics/cursors/cursor_manager.h" namespace ZVision { +ResultAction::ResultAction(ZVision *engine, int32 slotkey) : _engine(engine), _slotKey(slotkey), _scriptManager(engine->getScriptManager()) { +} + ////////////////////////////////////////////////////////////////////////////// // ActionAdd ////////////////////////////////////////////////////////////////////////////// -ActionAdd::ActionAdd(const Common::String &line) { - sscanf(line.c_str(), "%*[^(](%u,%u)", &_key, &_value); +ActionAdd::ActionAdd(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + _key = 0; + _value = 0; + + sscanf(line.c_str(), "%u,%d", &_key, &_value); } -bool ActionAdd::execute(ZVision *engine) { - engine->getScriptManager()->addToStateValue(_key, _value); +bool ActionAdd::execute() { + _scriptManager->setStateValue(_key, _scriptManager->getStateValue(_key) + _value); return true; } - ////////////////////////////////////////////////////////////////////////////// // ActionAssign ////////////////////////////////////////////////////////////////////////////// -ActionAssign::ActionAssign(const Common::String &line) { - sscanf(line.c_str(), "%*[^(](%u, %u)", &_key, &_value); +ActionAssign::ActionAssign(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + _key = 0; + + char buf[64]; + memset(buf, 0, 64); + sscanf(line.c_str(), "%u, %s", &_key, buf); + _value = new ValueSlot(_scriptManager, buf); } -bool ActionAssign::execute(ZVision *engine) { - engine->getScriptManager()->setStateValue(_key, _value); - return true; +ActionAssign::~ActionAssign() { + delete _value; } +bool ActionAssign::execute() { + _scriptManager->setStateValue(_key, _value->getValue()); + return true; +} ////////////////////////////////////////////////////////////////////////////// // ActionAttenuate ////////////////////////////////////////////////////////////////////////////// -ActionAttenuate::ActionAttenuate(const Common::String &line) { - sscanf(line.c_str(), "%*[^(](%u, %d)", &_key, &_attenuation); +ActionAttenuate::ActionAttenuate(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + _key = 0; + _attenuation = 0; + + sscanf(line.c_str(), "%u, %d", &_key, &_attenuation); } -bool ActionAttenuate::execute(ZVision *engine) { - // TODO: Implement +bool ActionAttenuate::execute() { + ScriptingEffect *fx = _scriptManager->getSideFX(_key); + if (fx && fx->getType() == ScriptingEffect::SCRIPTING_EFFECT_AUDIO) { + MusicNodeBASE *mus = (MusicNodeBASE *)fx; + mus->setVolume(255 * (10000 - abs(_attenuation)) / 10000 ); + } return true; } - ////////////////////////////////////////////////////////////////////////////// // ActionChangeLocation ////////////////////////////////////////////////////////////////////////////// -ActionChangeLocation::ActionChangeLocation(const Common::String &line) { - sscanf(line.c_str(), "%*[^(](%c,%c,%c%c,%u)", &_world, &_room, &_node, &_view, &_offset); +ActionChangeLocation::ActionChangeLocation(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + _world = 'g'; + _room = 'a'; + _node = 'r'; + _view = 'y'; + _offset = 0; + + sscanf(line.c_str(), "%c, %c, %c%c, %u", &_world, &_room, &_node, &_view, &_offset); } -bool ActionChangeLocation::execute(ZVision *engine) { +bool ActionChangeLocation::execute() { // We can't directly call ScriptManager::ChangeLocationIntern() because doing so clears all the Puzzles, and thus would corrupt the current puzzle checking - engine->getScriptManager()->changeLocation(_world, _room, _node, _view, _offset); + _scriptManager->changeLocation(_world, _room, _node, _view, _offset); // Tell the puzzle system to stop checking any more puzzles return false; } - ////////////////////////////////////////////////////////////////////////////// // ActionCrossfade ////////////////////////////////////////////////////////////////////////////// -ActionCrossfade::ActionCrossfade(const Common::String &line) { +ActionCrossfade::ActionCrossfade(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + _keyOne = 0; + _keyTwo = 0; + _oneStartVolume = 0; + _twoStartVolume = 0; + _oneEndVolume = 0; + _twoEndVolume = 0; + _timeInMillis = 0; + sscanf(line.c_str(), - "%*[^(](%u %u %u %u %u %u %u)", - &_keyOne, &_keyTwo, &_oneStartVolume, &_twoStartVolume, &_oneEndVolume, &_twoEndVolume, &_timeInMillis); + "%u %u %d %d %d %d %d", + &_keyOne, &_keyTwo, &_oneStartVolume, &_twoStartVolume, &_oneEndVolume, &_twoEndVolume, &_timeInMillis); +} + +bool ActionCrossfade::execute() { + if (_keyOne) { + ScriptingEffect *fx = _scriptManager->getSideFX(_keyOne); + if (fx && fx->getType() == ScriptingEffect::SCRIPTING_EFFECT_AUDIO) { + MusicNodeBASE *mus = (MusicNodeBASE *)fx; + if (_oneStartVolume >= 0) + mus->setVolume((_oneStartVolume * 255) / 100); + + mus->setFade(_timeInMillis, (_oneEndVolume * 255) / 100); + } + } + + if (_keyTwo) { + ScriptingEffect *fx = _scriptManager->getSideFX(_keyTwo); + if (fx && fx->getType() == ScriptingEffect::SCRIPTING_EFFECT_AUDIO) { + MusicNodeBASE *mus = (MusicNodeBASE *)fx; + if (_twoStartVolume >= 0) + mus->setVolume((_twoStartVolume * 255) / 100); + + mus->setFade(_timeInMillis, (_twoEndVolume * 255) / 100); + } + } + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionCursor +////////////////////////////////////////////////////////////////////////////// + +ActionCursor::ActionCursor(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + Common::String up = line; + up.toUppercase(); + _action = 0; + + if (up[0] == 'B') + _action = 2; + else if (up[0] == 'I') + _action = 3; + else if (up[0] == 'U') + _action = 0; + else if (up[0] == 'H') + _action = 1; } -bool ActionCrossfade::execute(ZVision *engine) { - // TODO: Implement +bool ActionCursor::execute() { + switch (_action) { + case 1: + _engine->getCursorManager()->showMouse(false); + break; + default: + _engine->getCursorManager()->showMouse(true); + break; + } return true; } +////////////////////////////////////////////////////////////////////////////// +// ActionDelayRender +////////////////////////////////////////////////////////////////////////////// + +ActionDelayRender::ActionDelayRender(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + _framesToDelay = 0; + sscanf(line.c_str(), "%u", &_framesToDelay); + // Limit to 10 frames maximum. This fixes the script bug in ZGI scene px10 + // (outside Frobozz Electric building), where this is set to 100 (bug #6791). + _framesToDelay = MIN<uint32>(_framesToDelay, 10); +} + +bool ActionDelayRender::execute() { + _engine->setRenderDelay(_framesToDelay); + return true; +} ////////////////////////////////////////////////////////////////////////////// // ActionDisableControl ////////////////////////////////////////////////////////////////////////////// -ActionDisableControl::ActionDisableControl(const Common::String &line) { - sscanf(line.c_str(), "%*[^(](%u)", &_key); +ActionDisableControl::ActionDisableControl(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + _key = 0; + + sscanf(line.c_str(), "%u", &_key); +} + +bool ActionDisableControl::execute() { + _scriptManager->setStateFlag(_key, Puzzle::DISABLED); + return true; } -bool ActionDisableControl::execute(ZVision *engine) { - debug("Disabling control %u", _key); - - ScriptManager *scriptManager = engine->getScriptManager(); - scriptManager->setStateFlags(_key, scriptManager->getStateFlags(_key) | ScriptManager::DISABLED); +////////////////////////////////////////////////////////////////////////////// +// ActionDisplayMessage +////////////////////////////////////////////////////////////////////////////// +ActionDisplayMessage::ActionDisplayMessage(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + _control = 0; + _msgid = 0; + + sscanf(line.c_str(), "%hd %hd", &_control, &_msgid); +} + +bool ActionDisplayMessage::execute() { + Control *ctrl = _scriptManager->getControl(_control); + if (ctrl && ctrl->getType() == Control::CONTROL_TITLER) { + TitlerControl *titler = (TitlerControl *)ctrl; + titler->setString(_msgid); + } return true; } +////////////////////////////////////////////////////////////////////////////// +// ActionDissolve +////////////////////////////////////////////////////////////////////////////// + +ActionDissolve::ActionDissolve(ZVision *engine) : + ResultAction(engine, 0) { +} + +bool ActionDissolve::execute() { + // Cause black screen flick + // _engine->getRenderManager()->bkgFill(0, 0, 0); + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionDistort - only used by Zork: Nemesis for the "treatment" puzzle in the Sanitarium (aj30) +////////////////////////////////////////////////////////////////////////////// + +ActionDistort::ActionDistort(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + _distSlot = 0; + _speed = 0; + _startAngle = 60.0; + _endAngle = 60.0; + _startLineScale = 1.0; + _endLineScale = 1.0; + + sscanf(line.c_str(), "%hd %hd %f %f %f %f", &_distSlot, &_speed, &_startAngle, &_endAngle, &_startLineScale, &_endLineScale); +} + +ActionDistort::~ActionDistort() { + _scriptManager->killSideFx(_distSlot); +} + +bool ActionDistort::execute() { + if (_scriptManager->getSideFX(_distSlot)) + return true; + + _scriptManager->addSideFX(new DistortNode(_engine, _distSlot, _speed, _startAngle, _endAngle, _startLineScale, _endLineScale)); + + return true; +} ////////////////////////////////////////////////////////////////////////////// // ActionEnableControl ////////////////////////////////////////////////////////////////////////////// -ActionEnableControl::ActionEnableControl(const Common::String &line) { - sscanf(line.c_str(), "%*[^(](%u)", &_key); +ActionEnableControl::ActionEnableControl(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + _key = 0; + + sscanf(line.c_str(), "%u", &_key); +} + +bool ActionEnableControl::execute() { + _scriptManager->unsetStateFlag(_key, Puzzle::DISABLED); + return true; } -bool ActionEnableControl::execute(ZVision *engine) { - debug("Enabling control %u", _key); +////////////////////////////////////////////////////////////////////////////// +// ActionFlushMouseEvents +////////////////////////////////////////////////////////////////////////////// - ScriptManager *scriptManager = engine->getScriptManager(); - scriptManager->setStateFlags(_key, scriptManager->getStateFlags(_key) & ~ScriptManager::DISABLED); +ActionFlushMouseEvents::ActionFlushMouseEvents(ZVision *engine, int32 slotkey) : + ResultAction(engine, slotkey) { +} +bool ActionFlushMouseEvents::execute() { + _scriptManager->flushEvent(Common::EVENT_LBUTTONUP); + _scriptManager->flushEvent(Common::EVENT_LBUTTONDOWN); return true; } +////////////////////////////////////////////////////////////////////////////// +// ActionInventory +////////////////////////////////////////////////////////////////////////////// + +ActionInventory::ActionInventory(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + _type = -1; + _key = 0; + + char buf[25]; + sscanf(line.c_str(), "%24s %d", buf, &_key); + + if (strcmp(buf, "add") == 0) { + _type = 0; + } else if (strcmp(buf, "addi") == 0) { + _type = 1; + } else if (strcmp(buf, "drop") == 0) { + _type = 2; + } else if (strcmp(buf, "dropi") == 0) { + _type = 3; + } else if (strcmp(buf, "cycle") == 0) { + _type = 4; + } + +} + +bool ActionInventory::execute() { + switch (_type) { + case 0: // add + _scriptManager->inventoryAdd(_key); + break; + case 1: // addi + _scriptManager->inventoryAdd(_scriptManager->getStateValue(_key)); + break; + case 2: // drop + if (_key >= 0) + _scriptManager->inventoryDrop(_key); + else + _scriptManager->inventoryDrop(_scriptManager->getStateValue(StateKey_InventoryItem)); + break; + case 3: // dropi + _scriptManager->inventoryDrop(_scriptManager->getStateValue(_key)); + break; + case 4: // cycle + _scriptManager->inventoryCycle(); + break; + default: + break; + } + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionKill - only used by ZGI +////////////////////////////////////////////////////////////////////////////// + +ActionKill::ActionKill(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + _key = 0; + _type = 0; + char keytype[25]; + sscanf(line.c_str(), "%24s", keytype); + if (keytype[0] == '"') { + if (!scumm_stricmp(keytype, "\"ANIM\"")) + _type = ScriptingEffect::SCRIPTING_EFFECT_ANIM; + else if (!scumm_stricmp(keytype, "\"AUDIO\"")) + _type = ScriptingEffect::SCRIPTING_EFFECT_AUDIO; + else if (!scumm_stricmp(keytype, "\"DISTORT\"")) + _type = ScriptingEffect::SCRIPTING_EFFECT_DISTORT; + else if (!scumm_stricmp(keytype, "\"PANTRACK\"")) + _type = ScriptingEffect::SCRIPTING_EFFECT_PANTRACK; + else if (!scumm_stricmp(keytype, "\"REGION\"")) + _type = ScriptingEffect::SCRIPTING_EFFECT_REGION; + else if (!scumm_stricmp(keytype, "\"TIMER\"")) + _type = ScriptingEffect::SCRIPTING_EFFECT_TIMER; + else if (!scumm_stricmp(keytype, "\"TTYTEXT\"")) + _type = ScriptingEffect::SCRIPTING_EFFECT_TTYTXT; + else if (!scumm_stricmp(keytype, "\"ALL\"")) + _type = ScriptingEffect::SCRIPTING_EFFECT_ALL; + } else + _key = atoi(keytype); +} + +bool ActionKill::execute() { + if (_type) + _scriptManager->killSideFxType((ScriptingEffect::ScriptingEffectType)_type); + else + _scriptManager->killSideFx(_key); + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionMenuBarEnable +////////////////////////////////////////////////////////////////////////////// + +ActionMenuBarEnable::ActionMenuBarEnable(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + _menus = 0xFFFF; + + sscanf(line.c_str(), "%hu", &_menus); +} + +bool ActionMenuBarEnable::execute() { + _engine->getMenuHandler()->setEnable(_menus); + return true; +} ////////////////////////////////////////////////////////////////////////////// // ActionMusic ////////////////////////////////////////////////////////////////////////////// -ActionMusic::ActionMusic(const Common::String &line) : _volume(255) { - uint type; - char fileNameBuffer[26]; - uint loop; - uint volume = 255; +ActionMusic::ActionMusic(ZVision *engine, int32 slotkey, const Common::String &line, bool global) : + ResultAction(engine, slotkey), + _note(0), + _prog(0), + _universe(global) { + uint type = 0; + char fileNameBuffer[25]; + uint loop = 0; + char volumeBuffer[15]; + + // Volume is optional. If it doesn't appear, assume full volume + strcpy(volumeBuffer, "100"); - sscanf(line.c_str(), "%*[^:]:%*[^:]:%u(%u %25s %u %u)", &_key, &type, fileNameBuffer, &loop, &volume); + sscanf(line.c_str(), "%u %24s %u %14s", &type, fileNameBuffer, &loop, volumeBuffer); - // type 4 are midi sound effect files + // Type 4 actions are MIDI commands, not files. These are only used by + // Zork: Nemesis, for the flute and piano puzzles (tj4e and ve6f, as well + // as vr) if (type == 4) { - _soundType = Audio::Mixer::kSFXSoundType; - _fileName = Common::String::format("midi/%s/%u.wav", fileNameBuffer, loop); - _loop = false; + _midi = true; + int note; + int prog; + sscanf(line.c_str(), "%u %d %d %14s", &type, &prog, ¬e, volumeBuffer); + _volume = new ValueSlot(_scriptManager, volumeBuffer); + _note = note; + _prog = prog; } else { - // TODO: See what the other types are so we can specify the correct Mixer::SoundType. In the meantime use kPlainSoundType - _soundType = Audio::Mixer::kPlainSoundType; + _midi = false; _fileName = Common::String(fileNameBuffer); _loop = loop == 1 ? true : false; + if (volumeBuffer[0] != '[' && atoi(volumeBuffer) > 100) { + // I thought I saw a case like this in Zork Nemesis, so + // let's guard against it. + warning("ActionMusic: Adjusting volume for %s from %s to 100", _fileName.c_str(), volumeBuffer); + strcpy(volumeBuffer, "100"); + } + _volume = new ValueSlot(_scriptManager, volumeBuffer); } - // Volume is optional. If it doesn't appear, assume full volume - if (volume != 255) { - // Volume in the script files is mapped to [0, 100], but the ScummVM mixer uses [0, 255] - _volume = volume * 255 / 100; - } + // WORKAROUND for a script bug in Zork Nemesis, rooms mq70/mq80. + // Fixes an edge case where the player goes to the dark room with the grue + // without holding a torch, and then quickly runs away before the grue's + // sound effect finishes. Fixes script bug #6794. + if (engine->getGameId() == GID_NEMESIS && _slotKey == 14822 && _scriptManager->getStateValue(_slotKey) == 2) + _scriptManager->setStateValue(_slotKey, 0); + } -bool ActionMusic::execute(ZVision *engine) { - Audio::RewindableAudioStream *audioStream; +ActionMusic::~ActionMusic() { + if (!_universe) + _scriptManager->killSideFx(_slotKey); + delete _volume; +} - if (_fileName.contains(".wav")) { - Common::File *file = new Common::File(); - if (file->open(_fileName)) { - audioStream = Audio::makeWAVStream(file, DisposeAfterUse::YES); - } else { - warning("Unable to open %s", _fileName.c_str()); - return false; - } - } else { - audioStream = makeRawZorkStream(_fileName, engine); +bool ActionMusic::execute() { + if (_scriptManager->getSideFX(_slotKey)) { + _scriptManager->killSideFx(_slotKey); + _scriptManager->setStateValue(_slotKey, 2); } - - if (_loop) { - Audio::LoopingAudioStream *loopingAudioStream = new Audio::LoopingAudioStream(audioStream, 0, DisposeAfterUse::YES); - engine->_mixer->playStream(_soundType, 0, loopingAudioStream, -1, _volume); + + uint volume = _volume->getValue(); + + if (_midi) { + _scriptManager->addSideFX(new MusicMidiNode(_engine, _slotKey, _prog, _note, volume)); } else { - engine->_mixer->playStream(_soundType, 0, audioStream, -1, _volume); + if (!_engine->getSearchManager()->hasFile(_fileName)) + return true; + + // Volume in the script files is mapped to [0, 100], but the ScummVM mixer uses [0, 255] + _scriptManager->addSideFX(new MusicNode(_engine, _slotKey, _fileName, _loop, volume * 255 / 100)); } return true; } +////////////////////////////////////////////////////////////////////////////// +// ActionPanTrack +////////////////////////////////////////////////////////////////////////////// + +ActionPanTrack::ActionPanTrack(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey), + _pos(0), + _musicSlot(0) { + + sscanf(line.c_str(), "%u %d", &_musicSlot, &_pos); +} + +ActionPanTrack::~ActionPanTrack() { + _scriptManager->killSideFx(_slotKey); +} + +bool ActionPanTrack::execute() { + if (_scriptManager->getSideFX(_slotKey)) + return true; + + _scriptManager->addSideFX(new PanTrackNode(_engine, _slotKey, _musicSlot, _pos)); + + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionPreferences +////////////////////////////////////////////////////////////////////////////// + +ActionPreferences::ActionPreferences(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + if (line.compareToIgnoreCase("save") == 0) + _save = true; + else + _save = false; +} + +bool ActionPreferences::execute() { + if (_save) + _engine->saveSettings(); + else + _engine->loadSettings(); + + return true; +} ////////////////////////////////////////////////////////////////////////////// // ActionPreloadAnimation ////////////////////////////////////////////////////////////////////////////// -ActionPreloadAnimation::ActionPreloadAnimation(const Common::String &line) { - char fileName[26]; +ActionPreloadAnimation::ActionPreloadAnimation(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + _mask = 0; + _framerate = 0; - // The two %*u are always 0 and dont seem to have a use - sscanf(line.c_str(), "%*[^:]:%*[^:]:%u(%25s %*u %*u %u %u)", &_key, fileName, &_mask, &_framerate); + char fileName[25]; + + // The two %*u are usually 0 and dont seem to have a use + sscanf(line.c_str(), "%24s %*u %*u %d %d", fileName, &_mask, &_framerate); + + // Mask 0 means "no transparency" in this case. Since we use a common blitting + // code for images and animations, we set it to -1 to avoid confusion with + // color 0, which is used as a mask in some images + if (_mask == 0) + _mask = -1; _fileName = Common::String(fileName); } -bool ActionPreloadAnimation::execute(ZVision *engine) { - // TODO: We ignore the mask and framerate atm. Mask refers to a key color used for binary alpha. We assume the framerate is the default framerate embedded in the videos - - // TODO: Check if the Control already exists +ActionPreloadAnimation::~ActionPreloadAnimation() { + _scriptManager->deleteSideFx(_slotKey); +} - // Create the control, but disable it until PlayPreload is called - ScriptManager *scriptManager = engine->getScriptManager(); - scriptManager->addControl(new AnimationControl(engine, _key, _fileName)); - scriptManager->setStateFlags(_key, scriptManager->getStateFlags(_key) | ScriptManager::DISABLED); +bool ActionPreloadAnimation::execute() { + AnimationEffect *nod = (AnimationEffect *)_scriptManager->getSideFX(_slotKey); + if (!nod) { + nod = new AnimationEffect(_engine, _slotKey, _fileName, _mask, _framerate, false); + _scriptManager->addSideFX(nod); + } else + nod->stop(); + _scriptManager->setStateValue(_slotKey, 2); return true; } +////////////////////////////////////////////////////////////////////////////// +// ActionUnloadAnimation +////////////////////////////////////////////////////////////////////////////// + +ActionUnloadAnimation::ActionUnloadAnimation(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + _key = 0; + + sscanf(line.c_str(), "%u", &_key); +} + +bool ActionUnloadAnimation::execute() { + AnimationEffect *nod = (AnimationEffect *)_scriptManager->getSideFX(_key); + + if (nod && nod->getType() == ScriptingEffect::SCRIPTING_EFFECT_ANIM) + _scriptManager->deleteSideFx(_key); + + return true; +} ////////////////////////////////////////////////////////////////////////////// // ActionPlayAnimation ////////////////////////////////////////////////////////////////////////////// -ActionPlayAnimation::ActionPlayAnimation(const Common::String &line) { - char fileName[26]; +ActionPlayAnimation::ActionPlayAnimation(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + _x = 0; + _y = 0; + _x2 = 0; + _y2 = 0; + _start = 0; + _end = 0; + _loopCount = 0; + _mask = 0; + _framerate = 0; + + char fileName[25]; // The two %*u are always 0 and dont seem to have a use sscanf(line.c_str(), - "%*[^:]:%*[^:]:%u(%25s %u %u %u %u %u %u %u %*u %*u %u %u)", - &_key, fileName, &_x, &_y, &_width, &_height, &_start, &_end, &_loopCount, &_mask, &_framerate); + "%24s %u %u %u %u %u %u %d %*u %*u %d %d", + fileName, &_x, &_y, &_x2, &_y2, &_start, &_end, &_loopCount, &_mask, &_framerate); + + // Mask 0 means "no transparency" in this case. Since we use a common blitting + // code for images and animations, we set it to -1 to avoid confusion with + // color 0, which is used as a mask in some images + if (_mask == 0) + _mask = -1; _fileName = Common::String(fileName); + + // WORKAROUND for bug #6769, location me1g.scr (the "Alchemical debacle" + // video in ZGI). We only scale up by 2x, in AnimationEffect::process(), + // but the dimensions of the target frame are off by 2 pixels. We fix that + // here, so that the video can be scaled. + if (_fileName == "me1ga011.avi" && _y2 == 213) + _y2 = 215; } -bool ActionPlayAnimation::execute(ZVision *engine) { - // TODO: Implement - return true; +ActionPlayAnimation::~ActionPlayAnimation() { + _scriptManager->deleteSideFx(_slotKey); } +bool ActionPlayAnimation::execute() { + AnimationEffect *nod = (AnimationEffect *)_scriptManager->getSideFX(_slotKey); + + if (!nod) { + nod = new AnimationEffect(_engine, _slotKey, _fileName, _mask, _framerate); + _scriptManager->addSideFX(nod); + } else + nod->stop(); + + if (nod) + nod->addPlayNode(_slotKey, _x, _y, _x2, _y2, _start, _end, _loopCount); + + return true; +} ////////////////////////////////////////////////////////////////////////////// // ActionPlayPreloadAnimation ////////////////////////////////////////////////////////////////////////////// -ActionPlayPreloadAnimation::ActionPlayPreloadAnimation(const Common::String &line) { +ActionPlayPreloadAnimation::ActionPlayPreloadAnimation(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + _controlKey = 0; + _x1 = 0; + _y1 = 0; + _x2 = 0; + _y2 = 0; + _startFrame = 0; + _endFrame = 0; + _loopCount = 0; + sscanf(line.c_str(), - "%*[^:]:%*[^:]:%u(%u %u %u %u %u %u %u %u)", - &_animationKey, &_controlKey, &_x1, &_y1, &_x2, &_y2, &_startFrame, &_endFrame, &_loopCount); + "%u %u %u %u %u %u %u %u", + &_controlKey, &_x1, &_y1, &_x2, &_y2, &_startFrame, &_endFrame, &_loopCount); } -bool ActionPlayPreloadAnimation::execute(ZVision *engine) { - // Find the control - AnimationControl *control = (AnimationControl *)engine->getScriptManager()->getControl(_controlKey); +bool ActionPlayPreloadAnimation::execute() { + AnimationEffect *nod = (AnimationEffect *)_scriptManager->getSideFX(_controlKey); - // Set the needed values within the control - control->setAnimationKey(_animationKey); - control->setLoopCount(_loopCount); - control->setXPos(_x1); - control->setYPost(_y1); - - // Enable the control. ScriptManager will take care of the rest - control->enable(); + if (nod) + nod->addPlayNode(_slotKey, _x1, _y1, _x2, _y2, _startFrame, _endFrame, _loopCount); return true; } - ////////////////////////////////////////////////////////////////////////////// // ActionQuit ////////////////////////////////////////////////////////////////////////////// -bool ActionQuit::execute(ZVision *engine) { - engine->quitGame(); +bool ActionQuit::execute() { + _engine->quitGame(); return true; } +////////////////////////////////////////////////////////////////////////////// +// ActionRegion - only used by Zork: Nemesis +////////////////////////////////////////////////////////////////////////////// + +ActionRegion::ActionRegion(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + _delay = 0; + _type = 0; + _unk1 = 0; + _unk2 = 0; + + char art[64]; + char custom[64]; + + int32 x1 = 0, x2 = 0, y1 = 0, y2 = 0; + + sscanf(line.c_str(), "%s %d %d %d %d %hu %hu %hu %hu %s", art, &x1, &y1, &x2, &y2, &_delay, &_type, &_unk1, &_unk2, custom); + _art = Common::String(art); + _custom = Common::String(custom); + _rect = Common::Rect(x1, y1, x2 + 1, y2 + 1); +} + +ActionRegion::~ActionRegion() { + _scriptManager->killSideFx(_slotKey); +} + +bool ActionRegion::execute() { + if (_scriptManager->getSideFX(_slotKey)) + return true; + + GraphicsEffect *effect = NULL; + switch (_type) { + case 0: { + uint16 centerX, centerY, frames; + double amplitude, waveln, speed; + sscanf(_custom.c_str(), "%hu,%hu,%hu,%lf,%lf,%lf,", ¢erX, ¢erY, &frames, &litude, &waveln, &speed); + effect = new WaveFx(_engine, _slotKey, _rect, _unk1, frames, centerX, centerY, amplitude, waveln, speed); + } + break; + case 1: { + uint16 aX, aY, aD; + if (_engine->getRenderManager()->getRenderTable()->getRenderState() == RenderTable::PANORAMA) + sscanf(_art.c_str(), "useart[%hu,%hu,%hu]", &aY, &aX, &aD); + else + sscanf(_art.c_str(), "useart[%hu,%hu,%hu]", &aX, &aY, &aD); + int8 minD; + int8 maxD; + EffectMap *_map = _engine->getRenderManager()->makeEffectMap(Common::Point(aX, aY), aD, _rect, &minD, &maxD); + effect = new LightFx(_engine, _slotKey, _rect, _unk1, _map, atoi(_custom.c_str()), minD, maxD); + } + break; + case 9: { + int16 dum1; + int32 dum2; + char buf[64]; + sscanf(_custom.c_str(), "%hd,%d,%s", &dum1, &dum2, buf); + Graphics::Surface tempMask; + _engine->getRenderManager()->readImageToSurface(_art, tempMask); + if (_rect.width() != tempMask.w) + _rect.setWidth(tempMask.w); + if (_rect.height() != tempMask.h) + _rect.setHeight(tempMask.h); + + EffectMap *_map = _engine->getRenderManager()->makeEffectMap(tempMask, 0); + effect = new FogFx(_engine, _slotKey, _rect, _unk1, _map, Common::String(buf)); + } + break; + default: + break; + } + + if (effect) { + _scriptManager->addSideFX(new RegionNode(_engine, _slotKey, effect, _delay)); + _engine->getRenderManager()->addEffect(effect); + } + + return true; +} ////////////////////////////////////////////////////////////////////////////// // ActionRandom ////////////////////////////////////////////////////////////////////////////// -ActionRandom::ActionRandom(const Common::String &line) { - sscanf(line.c_str(), "%*[^:]:%*[^:]:%u, %u)", &_key, &_max); +ActionRandom::ActionRandom(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + char maxBuffer[64]; + memset(maxBuffer, 0, 64); + sscanf(line.c_str(), "%s", maxBuffer); + _max = new ValueSlot(_scriptManager, maxBuffer); +} + +ActionRandom::~ActionRandom() { + delete _max; } -bool ActionRandom::execute(ZVision *engine) { - uint randNumber = engine->getRandomSource()->getRandomNumber(_max); - engine->getScriptManager()->setStateValue(_key, randNumber); +bool ActionRandom::execute() { + uint randNumber = _engine->getRandomSource()->getRandomNumber(_max->getValue()); + _scriptManager->setStateValue(_slotKey, randNumber); return true; } +////////////////////////////////////////////////////////////////////////////// +// ActionRestoreGame +////////////////////////////////////////////////////////////////////////////// + +ActionRestoreGame::ActionRestoreGame(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + char buf[128]; + sscanf(line.c_str(), "%s", buf); + _fileName = Common::String(buf); +} + +bool ActionRestoreGame::execute() { + _engine->getSaveManager()->loadGame(-1); + return false; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionRotateTo +////////////////////////////////////////////////////////////////////////////// + +ActionRotateTo::ActionRotateTo(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + _time = 0; + _toPos = 0; + + sscanf(line.c_str(), "%d, %d", &_toPos, &_time); +} + +bool ActionRotateTo::execute() { + _engine->getRenderManager()->rotateTo(_toPos, _time); + + return true; +} ////////////////////////////////////////////////////////////////////////////// // ActionSetPartialScreen ////////////////////////////////////////////////////////////////////////////// -ActionSetPartialScreen::ActionSetPartialScreen(const Common::String &line) { - char fileName[26]; - uint color; +ActionSetPartialScreen::ActionSetPartialScreen(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + _x = 0; + _y = 0; + + char fileName[25]; - sscanf(line.c_str(), "%*[^(](%u %u %25s %*u %u)", &_x, &_y, fileName, &color); + sscanf(line.c_str(), "%u %u %24s %*u %d", &_x, &_y, fileName, &_backgroundColor); _fileName = Common::String(fileName); - if (color > 0xFFFF) { + if (_backgroundColor > 65535) { warning("Background color for ActionSetPartialScreen is bigger than a uint16"); } - _backgroundColor = color; } -bool ActionSetPartialScreen::execute(ZVision *engine) { - RenderManager *renderManager = engine->getRenderManager(); - - if (_backgroundColor > 0) { - renderManager->clearWorkingWindowTo555Color(_backgroundColor); +bool ActionSetPartialScreen::execute() { + RenderManager *renderManager = _engine->getRenderManager(); + + if (_engine->getGameId() == GID_NEMESIS) { + if (_backgroundColor) + renderManager->renderImageToBackground(_fileName, _x, _y, 0, 0); + else + renderManager->renderImageToBackground(_fileName, _x, _y); + } else { + if (_backgroundColor >= 0) + renderManager->renderImageToBackground(_fileName, _x, _y, _backgroundColor); + else if (_backgroundColor == -2) + renderManager->renderImageToBackground(_fileName, _x, _y, 0, 0); + else + renderManager->renderImageToBackground(_fileName, _x, _y); } - renderManager->renderImageToScreen(_fileName, _x, _y); return true; } - ////////////////////////////////////////////////////////////////////////////// // ActionSetScreen ////////////////////////////////////////////////////////////////////////////// -ActionSetScreen::ActionSetScreen(const Common::String &line) { - char fileName[26]; - sscanf(line.c_str(), "%*[^(](%25[^)])", fileName); +ActionSetScreen::ActionSetScreen(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + char fileName[25]; + sscanf(line.c_str(), "%24s", fileName); _fileName = Common::String(fileName); } -bool ActionSetScreen::execute(ZVision *engine) { - engine->getRenderManager()->setBackgroundImage(_fileName); +bool ActionSetScreen::execute() { + _engine->getRenderManager()->setBackgroundImage(_fileName); return true; } +////////////////////////////////////////////////////////////////////////////// +// ActionStop +////////////////////////////////////////////////////////////////////////////// + +ActionStop::ActionStop(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + _key = 0; + sscanf(line.c_str(), "%u", &_key); +} + +bool ActionStop::execute() { + _scriptManager->stopSideFx(_key); + return true; +} ////////////////////////////////////////////////////////////////////////////// // ActionStreamVideo ////////////////////////////////////////////////////////////////////////////// -ActionStreamVideo::ActionStreamVideo(const Common::String &line) { - char fileName[26]; - uint skippable; +ActionStreamVideo::ActionStreamVideo(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + _x1 = 0; + _x2 = 0; + _y1 = 0; + _y2 = 0; + _flags = 0; - sscanf(line.c_str(), "%*[^(](%25s %u %u %u %u %u %u)", fileName, &_x1, &_y1, &_x2, &_y2, &_flags, &skippable); + char fileName[25]; + uint skipline = 0; //skipline - render video with skip every second line, not skippable. + + sscanf(line.c_str(), "%24s %u %u %u %u %u %u", fileName, &_x1, &_y1, &_x2, &_y2, &_flags, &skipline); _fileName = Common::String(fileName); - _skippable = (skippable == 0) ? false : true; + _skippable = true; } -bool ActionStreamVideo::execute(ZVision *engine) { - ZorkAVIDecoder decoder; - if (!decoder.loadFile(_fileName)) { +bool ActionStreamVideo::execute() { + Video::VideoDecoder *decoder; + Common::Rect destRect = Common::Rect(_x1, _y1, _x2 + 1, _y2 + 1); + Common::String subname = _fileName; + subname.setChar('s', subname.size() - 3); + subname.setChar('u', subname.size() - 2); + subname.setChar('b', subname.size() - 1); + bool subtitleExists = _engine->getSearchManager()->hasFile(subname); + bool switchToHires = false; + +// NOTE: We only show the hires MPEG2 videos when libmpeg2 is compiled in, +// otherwise we fall back to the lowres ones +#ifdef USE_MPEG2 + Common::String hiresFileName = _fileName; + hiresFileName.setChar('d', hiresFileName.size() - 8); + hiresFileName.setChar('v', hiresFileName.size() - 3); + hiresFileName.setChar('o', hiresFileName.size() - 2); + hiresFileName.setChar('b', hiresFileName.size() - 1); + + if (_scriptManager->getStateValue(StateKey_MPEGMovies) == 1 &&_engine->getSearchManager()->hasFile(hiresFileName)) { + // TODO: Enable once AC3 support is implemented + if (!_engine->getSearchManager()->hasFile(_fileName)) // Check for the regular video + return true; + warning("The hires videos of the DVD version of ZGI aren't supported yet, using lowres"); + //_fileName = hiresFileName; + //switchToHires = true; + } else if (!_engine->getSearchManager()->hasFile(_fileName)) + return true; +#else + if (!_engine->getSearchManager()->hasFile(_fileName)) return true; +#endif + + decoder = _engine->loadAnimation(_fileName); + Subtitle *sub = (subtitleExists) ? new Subtitle(_engine, subname, switchToHires) : NULL; + + _engine->getCursorManager()->showMouse(false); + + if (switchToHires) { + _engine->initHiresScreen(); + destRect = Common::Rect(40, -40, 760, 440); + Common::Rect workingWindow = _engine->_workingWindow; + workingWindow.translate(0, -40); + _engine->getRenderManager()->initSubArea(HIRES_WINDOW_WIDTH, HIRES_WINDOW_HEIGHT, workingWindow); } - Common::Rect destRect; - if ((_flags & DIFFERENT_DIMENSIONS) == DIFFERENT_DIMENSIONS) { - destRect = Common::Rect(_x1, _y1, _x2, _y2); + _engine->playVideo(*decoder, destRect, _skippable, sub); + + if (switchToHires) { + _engine->initScreen(); + _engine->getRenderManager()->initSubArea(WINDOW_WIDTH, WINDOW_HEIGHT, _engine->_workingWindow); } - engine->playVideo(decoder, destRect, _skippable); + _engine->getCursorManager()->showMouse(true); + + delete decoder; + delete sub; + return true; } +////////////////////////////////////////////////////////////////////////////// +// ActionSyncSound +////////////////////////////////////////////////////////////////////////////// + +ActionSyncSound::ActionSyncSound(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + _syncto = 0; + + char fileName[25]; + int notUsed = 0; + + sscanf(line.c_str(), "%d %d %24s", &_syncto, ¬Used, fileName); + + _fileName = Common::String(fileName); +} + +bool ActionSyncSound::execute() { + ScriptingEffect *fx = _scriptManager->getSideFX(_syncto); + if (!fx) + return true; + + if (!(fx->getType() & ScriptingEffect::SCRIPTING_EFFECT_ANIM)) + return true; + + _scriptManager->addSideFX(new SyncSoundNode(_engine, _slotKey, _fileName, _syncto)); + return true; +} ////////////////////////////////////////////////////////////////////////////// // ActionTimer ////////////////////////////////////////////////////////////////////////////// -ActionTimer::ActionTimer(const Common::String &line) { - sscanf(line.c_str(), "%*[^:]:%*[^:]:%u(%u)", &_key, &_time); +ActionTimer::ActionTimer(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + char timeBuffer[64]; + memset(timeBuffer, 0, 64); + sscanf(line.c_str(), "%s", timeBuffer); + _time = new ValueSlot(_scriptManager, timeBuffer); +} + +ActionTimer::~ActionTimer() { + delete _time; + _scriptManager->killSideFx(_slotKey); +} + +bool ActionTimer::execute() { + if (_scriptManager->getSideFX(_slotKey)) + return true; + _scriptManager->addSideFX(new TimerNode(_engine, _slotKey, _time->getValue())); + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionTtyText +////////////////////////////////////////////////////////////////////////////// + +ActionTtyText::ActionTtyText(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + _delay = 0; + + char filename[64]; + int32 x1 = 0, y1 = 0, x2 = 0, y2 = 0; + sscanf(line.c_str(), "%d %d %d %d %63s %u", &x1, &y1, &x2, &y2, filename, &_delay); + _r = Common::Rect(x1, y1, x2, y2); + _filename = Common::String(filename); +} + +ActionTtyText::~ActionTtyText() { + _scriptManager->killSideFx(_slotKey); } -bool ActionTimer::execute(ZVision *engine) { - engine->getScriptManager()->addControl(new TimerNode(engine, _key, _time)); +bool ActionTtyText::execute() { + if (_scriptManager->getSideFX(_slotKey)) + return true; + _scriptManager->addSideFX(new ttyTextNode(_engine, _slotKey, _filename, _r, _delay)); return true; } diff --git a/engines/zvision/scripting/actions.h b/engines/zvision/scripting/actions.h index 01457d21cc..ff19fc54fc 100644 --- a/engines/zvision/scripting/actions.h +++ b/engines/zvision/scripting/actions.h @@ -24,14 +24,16 @@ #define ZVISION_ACTIONS_H #include "common/str.h" +#include "common/rect.h" #include "audio/mixer.h" - namespace ZVision { // Forward declaration of ZVision. This file is included before ZVision is declared class ZVision; +class ScriptManager; +class ValueSlot; /** * The base class that represents any action that a Puzzle can take. @@ -39,6 +41,7 @@ class ZVision; */ class ResultAction { public: + ResultAction(ZVision *engine, int32 slotkey); virtual ~ResultAction() {} /** * This is called by the script system whenever a Puzzle's criteria are found to be true. @@ -48,75 +51,48 @@ public: * @param engine A pointer to the base engine so the ResultAction can access all the necessary methods * @return Should the script system continue to test any remaining puzzles (true) or immediately break and go on to the next frame (false) */ - virtual bool execute(ZVision *engine) = 0; -}; - - -// The different types of actions -// DEBUG, -// DISABLE_CONTROL, -// DISABLE_VENUS, -// DISPLAY_MESSAGE, -// DISSOLVE, -// DISTORT, -// ENABLE_CONTROL, -// FLUSH_MOUSE_EVENTS, -// INVENTORY, -// KILL, -// MENU_BAR_ENABLE, -// MUSIC, -// PAN_TRACK, -// PLAY_PRELOAD, -// PREFERENCES, -// QUIT, -// RANDOM, -// REGION, -// RESTORE_GAME, -// ROTATE_TO, -// SAVE_GAME, -// SET_PARTIAL_SCREEN, -// SET_SCREEN, -// SET_VENUS, -// STOP, -// STREAM_VIDEO, -// SYNC_SOUND, -// TTY_TEXT, -// UNIVERSE_MUSIC, + virtual bool execute() = 0; +protected: + ZVision *_engine; + ScriptManager *_scriptManager; + int32 _slotKey; +}; class ActionAdd : public ResultAction { public: - ActionAdd(const Common::String &line); - bool execute(ZVision *engine); + ActionAdd(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); private: uint32 _key; - uint _value; + int _value; }; class ActionAssign : public ResultAction { public: - ActionAssign(const Common::String &line); - bool execute(ZVision *engine); + ActionAssign(ZVision *engine, int32 slotkey, const Common::String &line); + ~ActionAssign(); + bool execute(); private: uint32 _key; - uint _value; + ValueSlot *_value; }; class ActionAttenuate : public ResultAction { public: - ActionAttenuate(const Common::String &line); - bool execute(ZVision *engine); + ActionAttenuate(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); private: uint32 _key; - int _attenuation; + int32 _attenuation; }; class ActionChangeLocation : public ResultAction { public: - ActionChangeLocation(const Common::String &line); - bool execute(ZVision *engine); + ActionChangeLocation(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); private: char _world; @@ -128,121 +104,169 @@ private: class ActionCrossfade : public ResultAction { public: - ActionCrossfade(const Common::String &line); - bool execute(ZVision *engine); + ActionCrossfade(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); private: uint32 _keyOne; uint32 _keyTwo; - uint _oneStartVolume; - uint _twoStartVolume; - uint _oneEndVolume; - uint _twoEndVolume; - uint _timeInMillis; + int32 _oneStartVolume; + int32 _twoStartVolume; + int32 _oneEndVolume; + int32 _twoEndVolume; + int32 _timeInMillis; }; -class ActionDebug : public ResultAction { +class ActionCursor : public ResultAction { public: - ActionDebug(const Common::String &line); - bool execute(ZVision *engine); + ActionCursor(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); private: + uint8 _action; }; class ActionDelayRender : public ResultAction { public: - ActionDelayRender(const Common::String &line); - bool execute(ZVision *engine); + ActionDelayRender(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); private: - // TODO: Check if this should actually be frames or if it should be milliseconds/seconds - uint32 framesToDelay; + uint32 _framesToDelay; }; class ActionDisableControl : public ResultAction { public: - ActionDisableControl(const Common::String &line); - bool execute(ZVision *engine); + ActionDisableControl(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); private: uint32 _key; }; -class ActionDisableVenus : public ResultAction { +class ActionDisplayMessage : public ResultAction { public: - ActionDisableVenus(const Common::String &line); - bool execute(ZVision *engine); + ActionDisplayMessage(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); private: + int16 _control; + int16 _msgid; }; -class ActionDisplayMessage : public ResultAction { +class ActionDissolve : public ResultAction { public: - ActionDisplayMessage(const Common::String &line); - bool execute(ZVision *engine); + ActionDissolve(ZVision *engine); + bool execute(); +}; + +class ActionDistort : public ResultAction { +public: + ActionDistort(ZVision *engine, int32 slotkey, const Common::String &line); + ~ActionDistort(); + bool execute(); private: + int16 _distSlot; + int16 _speed; + float _startAngle; + float _endAngle; + float _startLineScale; + float _endLineScale; }; -class ActionDissolve : public ResultAction { +class ActionEnableControl : public ResultAction { public: - ActionDissolve(); - bool execute(ZVision *engine); + ActionEnableControl(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); + +private: + uint32 _key; }; -class ActionDistort : public ResultAction { +class ActionFlushMouseEvents : public ResultAction { public: - ActionDistort(const Common::String &line); - bool execute(ZVision *engine); + ActionFlushMouseEvents(ZVision *engine, int32 slotkey); + bool execute(); +}; +class ActionInventory : public ResultAction { +public: + ActionInventory(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); private: + int8 _type; + int32 _key; }; -class ActionEnableControl : public ResultAction { +class ActionKill : public ResultAction { public: - ActionEnableControl(const Common::String &line); - bool execute(ZVision *engine); + ActionKill(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); private: uint32 _key; + uint32 _type; +}; + +class ActionMenuBarEnable : public ResultAction { +public: + ActionMenuBarEnable(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); +private: + uint16 _menus; }; class ActionMusic : public ResultAction { public: - ActionMusic(const Common::String &line); - bool execute(ZVision *engine); + ActionMusic(ZVision *engine, int32 slotkey, const Common::String &line, bool global); + ~ActionMusic(); + bool execute(); private: - uint32 _key; - Audio::Mixer::SoundType _soundType; Common::String _fileName; bool _loop; - byte _volume; + ValueSlot *_volume; + bool _universe; + bool _midi; + int8 _note; + int8 _prog; +}; + +class ActionPanTrack : public ResultAction { +public: + ActionPanTrack(ZVision *engine, int32 slotkey, const Common::String &line); + ~ActionPanTrack(); + bool execute(); + +private: + int32 _pos; + uint32 _musicSlot; }; class ActionPlayAnimation : public ResultAction { public: - ActionPlayAnimation(const Common::String &line); - bool execute(ZVision *engine); + ActionPlayAnimation(ZVision *engine, int32 slotkey, const Common::String &line); + ~ActionPlayAnimation(); + bool execute(); private: - uint32 _key; Common::String _fileName; uint32 _x; uint32 _y; - uint32 _width; - uint32 _height; + uint32 _x2; + uint32 _y2; uint32 _start; uint32 _end; - uint _mask; - uint _framerate; - uint _loopCount; + int32 _mask; + int32 _framerate; + int32 _loopCount; }; class ActionPlayPreloadAnimation : public ResultAction { public: - ActionPlayPreloadAnimation(const Common::String &line); - bool execute(ZVision *engine); + ActionPlayPreloadAnimation(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); private: uint32 _animationKey; @@ -258,64 +282,119 @@ private: class ActionPreloadAnimation : public ResultAction { public: - ActionPreloadAnimation(const Common::String &line); - bool execute(ZVision *engine); + ActionPreloadAnimation(ZVision *engine, int32 slotkey, const Common::String &line); + ~ActionPreloadAnimation(); + bool execute(); private: - uint32 _key; Common::String _fileName; - uint _mask; - uint _framerate; + int32 _mask; + int32 _framerate; +}; + +class ActionPreferences : public ResultAction { +public: + ActionPreferences(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); + +private: + bool _save; }; class ActionQuit : public ResultAction { public: - ActionQuit() {} - bool execute(ZVision *engine); + ActionQuit(ZVision *engine, int32 slotkey) : ResultAction(engine, slotkey) {} + bool execute(); }; -// TODO: See if this exists in ZGI. It doesn't in ZNem +class ActionRegion : public ResultAction { +public: + ActionRegion(ZVision *engine, int32 slotkey, const Common::String &line); + ~ActionRegion(); + bool execute(); + +private: + Common::String _art; + Common::String _custom; + Common::Rect _rect; + uint16 _delay; + uint16 _type; + uint16 _unk1; + uint16 _unk2; +}; + +// Only used by ZGI (locations cd6e, cd6k, dg2f, dg4e, dv1j) class ActionUnloadAnimation : public ResultAction { public: - ActionUnloadAnimation(const Common::String &line); - bool execute(ZVision *engine); + ActionUnloadAnimation(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); +private: + uint32 _key; }; class ActionRandom : public ResultAction { public: - ActionRandom(const Common::String &line); - bool execute(ZVision *engine); + ActionRandom(ZVision *engine, int32 slotkey, const Common::String &line); + ~ActionRandom(); + bool execute(); private: - uint32 _key; - uint _max; + ValueSlot *_max; +}; + +class ActionRestoreGame : public ResultAction { +public: + ActionRestoreGame(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); + +private: + Common::String _fileName; +}; + +class ActionRotateTo : public ResultAction { +public: + ActionRotateTo(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); + +private: + int32 _toPos; + int32 _time; }; class ActionSetPartialScreen : public ResultAction { public: - ActionSetPartialScreen(const Common::String &line); - bool execute(ZVision *engine); + ActionSetPartialScreen(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); private: uint _x; uint _y; Common::String _fileName; - uint16 _backgroundColor; + int32 _backgroundColor; }; class ActionSetScreen : public ResultAction { public: - ActionSetScreen(const Common::String &line); - bool execute(ZVision *engine); + ActionSetScreen(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); private: Common::String _fileName; }; +class ActionStop : public ResultAction { +public: + ActionStop(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); + +private: + uint32 _key; +}; + class ActionStreamVideo : public ResultAction { public: - ActionStreamVideo(const Common::String &line); - bool execute(ZVision *engine); + ActionStreamVideo(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); private: enum { @@ -331,16 +410,36 @@ private: bool _skippable; }; -class ActionTimer : public ResultAction { +class ActionSyncSound : public ResultAction { public: - ActionTimer(const Common::String &line); - bool execute(ZVision *engine); + ActionSyncSound(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); private: - uint32 _key; - uint _time; + int _syncto; + Common::String _fileName; }; +class ActionTimer : public ResultAction { +public: + ActionTimer(ZVision *engine, int32 slotkey, const Common::String &line); + ~ActionTimer(); + bool execute(); +private: + ValueSlot *_time; +}; + +class ActionTtyText : public ResultAction { +public: + ActionTtyText(ZVision *engine, int32 slotkey, const Common::String &line); + ~ActionTtyText(); + bool execute(); + +private: + Common::String _filename; + uint32 _delay; + Common::Rect _r; +}; } // End of namespace ZVision #endif diff --git a/engines/zvision/scripting/control.cpp b/engines/zvision/scripting/control.cpp index 2343c83c56..81123eb99b 100644 --- a/engines/zvision/scripting/control.cpp +++ b/engines/zvision/scripting/control.cpp @@ -23,34 +23,15 @@ #include "common/scummsys.h" #include "zvision/scripting/control.h" +#include "zvision/scripting/script_manager.h" #include "zvision/zvision.h" #include "zvision/graphics/render_manager.h" -#include "zvision/utility/utility.h" #include "common/stream.h" - namespace ZVision { -void Control::enable() { - if (!_enabled) { - _enabled = true; - return; - } - - debug("Control %u is already enabled", _key); -} - -void Control::disable() { - if (_enabled) { - _enabled = false; - return; - } - - debug("Control %u is already disabled", _key); -} - void Control::parseFlatControl(ZVision *engine) { engine->getRenderManager()->getRenderTable()->setRenderState(RenderTable::FLAT); } @@ -61,7 +42,7 @@ void Control::parsePanoramaControl(ZVision *engine, Common::SeekableReadStream & // Loop until we find the closing brace Common::String line = stream.readLine(); - trimCommentsAndWhiteSpace(&line); + engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); while (!stream.eos() && !line.contains('}')) { if (line.matchString("angle*", true)) { @@ -79,23 +60,26 @@ void Control::parsePanoramaControl(ZVision *engine, Common::SeekableReadStream & renderTable->setPanoramaReverse(true); } } else if (line.matchString("zeropoint*", true)) { - // TODO: Implement + uint point; + sscanf(line.c_str(), "zeropoint(%u)", &point); + renderTable->setPanoramaZeroPoint(point); } line = stream.readLine(); - trimCommentsAndWhiteSpace(&line); + engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); } renderTable->generateRenderTable(); } +// Only used in Zork Nemesis, handles tilt controls (ZGI doesn't have a tilt view) void Control::parseTiltControl(ZVision *engine, Common::SeekableReadStream &stream) { RenderTable *renderTable = engine->getRenderManager()->getRenderTable(); renderTable->setRenderState(RenderTable::TILT); // Loop until we find the closing brace Common::String line = stream.readLine(); - trimCommentsAndWhiteSpace(&line); + engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); while (!stream.eos() && !line.contains('}')) { if (line.matchString("angle*", true)) { @@ -115,10 +99,40 @@ void Control::parseTiltControl(ZVision *engine, Common::SeekableReadStream &stre } line = stream.readLine(); - trimCommentsAndWhiteSpace(&line); + engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); } renderTable->generateRenderTable(); } +void Control::getParams(const Common::String &inputStr, Common::String ¶meter, Common::String &values) { + const char *chrs = inputStr.c_str(); + uint lbr; + + for (lbr = 0; lbr < inputStr.size(); lbr++) + if (chrs[lbr] == '(') + break; + + if (lbr >= inputStr.size()) + return; + + uint rbr; + + for (rbr = lbr + 1; rbr < inputStr.size(); rbr++) + if (chrs[rbr] == ')') + break; + + if (rbr >= inputStr.size()) + return; + + parameter = Common::String(chrs, chrs + lbr); + values = Common::String(chrs + lbr + 1, chrs + rbr); +} + +void Control::setVenus() { + if (_venusId >= 0) + if (_engine->getScriptManager()->getStateValue(_venusId) > 0) + _engine->getScriptManager()->setStateValue(StateKey_Venus, _venusId); +} + } // End of namespace ZVision diff --git a/engines/zvision/scripting/control.h b/engines/zvision/scripting/control.h index ffeacb273d..108b83fd00 100644 --- a/engines/zvision/scripting/control.h +++ b/engines/zvision/scripting/control.h @@ -8,12 +8,12 @@ * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. - * + * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. @@ -24,7 +24,7 @@ #define ZVISION_CONTROL_H #include "common/keyboard.h" - +#include "common/str.h" namespace Common { class SeekableReadStream; @@ -36,16 +36,39 @@ namespace ZVision { class ZVision; +/** + * The base class for all Controls. + * + * Controls are the things that the user interacts with. Ex: A lever on the door + */ class Control { public: - Control() : _engine(0), _key(0), _enabled(false) {} - Control(ZVision *engine, uint32 key) : _engine(engine), _key(key), _enabled(false) {} + + enum ControlType { + CONTROL_UNKNOW, + CONTROL_INPUT, + CONTROL_PUSHTGL, + CONTROL_SLOT, + CONTROL_LEVER, + CONTROL_SAVE, + CONTROL_SAFE, + CONTROL_FIST, + CONTROL_TITLER, + CONTROL_HOTMOV, + CONTROL_PAINT + }; + + Control(ZVision *engine, uint32 key, ControlType type) : _engine(engine), _key(key), _type(type), _venusId(-1) {} virtual ~Control() {} - uint32 getKey() { return _key; } + uint32 getKey() { + return _key; + } + + ControlType getType() { + return _type; + } - virtual void enable(); - virtual void disable(); virtual void focus() {} virtual void unfocus() {} /** @@ -54,14 +77,18 @@ public: * @param screenSpacePos The position of the mouse in screen space * @param backgroundImageSpacePos The position of the mouse in background image space */ - virtual void onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {} + virtual bool onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + return false; + } /** * Called when LeftMouse is lifted. Default is NOP. * * @param screenSpacePos The position of the mouse in screen space * @param backgroundImageSpacePos The position of the mouse in background image space */ - virtual void onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {} + virtual bool onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + return false; + } /** * Called on every MouseMove. Default is NOP. * @@ -69,78 +96,52 @@ public: * @param backgroundImageSpacePos The position of the mouse in background image space * @return Was the cursor changed? */ - virtual bool onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { return false; } + virtual bool onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + return false; + } /** * Called when a key is pressed. Default is NOP. * * @param keycode The key that was pressed */ - virtual void onKeyDown(Common::KeyState keyState) {} + virtual bool onKeyDown(Common::KeyState keyState) { + return false; + } /** * Called when a key is released. Default is NOP. * * @param keycode The key that was pressed */ - virtual void onKeyUp(Common::KeyState keyState) {} + virtual bool onKeyUp(Common::KeyState keyState) { + return false; + } /** * Processes the node given the deltaTime since last frame. Default is NOP. * * @param deltaTimeInMillis The number of milliseconds that have passed since last frame * @return If true, the node can be deleted after process() finishes */ - virtual bool process(uint32 deltaTimeInMillis) { return false; } - /** - * Serialize a Control for save game use. This should only be used if a Control needs - * to save values that would be different from initialization. AKA a TimerNode needs to - * store the amount of time left on the timer. Any Controls overriding this *MUST* write - * their key as the first data outputted. The default implementation is NOP. - * - * NOTE: If this method is overridden, you MUST also override deserialize() - * and needsSerialization() - * - * @param stream Stream to write any needed data to - */ - virtual void serialize(Common::WriteStream *stream) {} - /** - * De-serialize data from a save game stream. This should only be implemented if the - * Control also implements serialize(). The calling method assumes the size of the - * data read from the stream exactly equals that written in serialize(). The default - * implementation is NOP. - * - * NOTE: If this method is overridden, you MUST also override serialize() - * and needsSerialization() - * - * @param stream Save game file stream - */ - virtual void deserialize(Common::SeekableReadStream *stream) {} - /** - * If a Control overrides serialize() and deserialize(), this should return true - * - * @return Does the Control need save game serialization? - */ - virtual inline bool needsSerialization() { return false; } + virtual bool process(uint32 deltaTimeInMillis) { + return false; + } + + void setVenus(); protected: - ZVision * _engine; + ZVision *_engine; uint32 _key; - bool _enabled; + int32 _venusId; + void getParams(const Common::String &inputStr, Common::String ¶meter, Common::String &values); // Static member functions public: static void parseFlatControl(ZVision *engine); static void parsePanoramaControl(ZVision *engine, Common::SeekableReadStream &stream); static void parseTiltControl(ZVision *engine, Common::SeekableReadStream &stream); +private: + ControlType _type; }; -// TODO: Implement InputControl -// TODO: Implement SaveControl -// TODO: Implement SlotControl -// TODO: Implement SafeControl -// TODO: Implement FistControl -// TODO: Implement HotMovieControl -// TODO: Implement PaintControl -// TODO: Implement TilterControl - } // End of namespace ZVision #endif diff --git a/engines/zvision/scripting/controls/animation_control.cpp b/engines/zvision/scripting/controls/animation_control.cpp deleted file mode 100644 index e351e81d25..0000000000 --- a/engines/zvision/scripting/controls/animation_control.cpp +++ /dev/null @@ -1,263 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#include "common/scummsys.h" - -#include "zvision/scripting/controls/animation_control.h" - -#include "zvision/zvision.h" -#include "zvision/graphics/render_manager.h" -#include "zvision/scripting/script_manager.h" -#include "zvision/animation/rlf_animation.h" -#include "zvision/video/zork_avi_decoder.h" - -#include "video/video_decoder.h" - -#include "graphics/surface.h" - - -namespace ZVision { - -AnimationControl::AnimationControl(ZVision *engine, uint32 controlKey, const Common::String &fileName) - : Control(engine, controlKey), - _fileType(RLF), - _loopCount(1), - _currentLoop(0), - _accumulatedTime(0), - _cachedFrame(0), - _cachedFrameNeedsDeletion(false) { - if (fileName.hasSuffix(".rlf")) { - _fileType = RLF; - _animation.rlf = new RlfAnimation(fileName, false); - } else if (fileName.hasSuffix(".avi")) { - _fileType = AVI; - _animation.avi = new ZorkAVIDecoder(); - _animation.avi->loadFile(fileName); - } else { - warning("Unrecognized animation file type: %s", fileName.c_str()); - } - - _cachedFrame = new Graphics::Surface(); -} - -AnimationControl::~AnimationControl() { - if (_fileType == RLF) { - delete _animation.rlf; - } else if (_fileType == AVI) { - delete _animation.avi; - } - - _cachedFrame->free(); - delete _cachedFrame; -} - -bool AnimationControl::process(uint32 deltaTimeInMillis) { - if (!_enabled) { - return false; - } - - bool finished = false; - - if (_fileType == RLF) { - _accumulatedTime += deltaTimeInMillis; - - uint32 frameTime = _animation.rlf->frameTime(); - if (_accumulatedTime >= frameTime) { - while (_accumulatedTime >= frameTime) { - _accumulatedTime -= frameTime; - - // Make sure the frame is inside the working window - // If it's not, then just return - - RenderManager *renderManager = _engine->getRenderManager(); - Common::Point workingWindowPoint = renderManager->imageSpaceToWorkingWindowSpace(Common::Point(_x, _y)); - Common::Rect subRect(workingWindowPoint.x, workingWindowPoint.y, workingWindowPoint.x + _animation.rlf->width(), workingWindowPoint.y + _animation.rlf->height()); - - // If the clip returns false, it means the animation is outside the working window - if (!renderManager->clipRectToWorkingWindow(subRect)) { - return false; - } - - const Graphics::Surface *frame = _animation.rlf->getNextFrame(); - - // Animation frames for PANORAMAs are transposed, so un-transpose them - RenderTable::RenderState state = renderManager->getRenderTable()->getRenderState(); - if (state == RenderTable::PANORAMA) { - Graphics::Surface *tranposedFrame = RenderManager::tranposeSurface(frame); - - renderManager->copyRectToWorkingWindow((uint16 *)tranposedFrame->getBasePtr(tranposedFrame->w - subRect.width(), tranposedFrame->h - subRect.height()), subRect.left, subRect.top, _animation.rlf->width(), subRect.width(), subRect.height()); - - // If the background can move, we need to cache the last frame so it can be rendered during background movement - if (state == RenderTable::PANORAMA || state == RenderTable::TILT) { - if (_cachedFrameNeedsDeletion) { - _cachedFrame->free(); - delete _cachedFrame; - _cachedFrameNeedsDeletion = false; - } - _cachedFrame = tranposedFrame; - _cachedFrameNeedsDeletion = true; - } else { - // Cleanup - tranposedFrame->free(); - delete tranposedFrame; - } - } else { - renderManager->copyRectToWorkingWindow((const uint16 *)frame->getBasePtr(frame->w - subRect.width(), frame->h - subRect.height()), subRect.left, subRect.top, _animation.rlf->width(), subRect.width(), subRect.height()); - - // If the background can move, we need to cache the last frame so it can be rendered during background movement - if (state == RenderTable::PANORAMA || state == RenderTable::TILT) { - if (_cachedFrameNeedsDeletion) { - _cachedFrame->free(); - delete _cachedFrame; - _cachedFrameNeedsDeletion = false; - } - _cachedFrame->copyFrom(*frame); - } - } - - // Check if we should continue looping - if (_animation.rlf->endOfAnimation()) { - _animation.rlf->seekToFrame(-1); - if (_loopCount > 0) { - _currentLoop++; - if (_currentLoop >= _loopCount) { - finished = true; - } - } - } - } - } else { - // If the background can move, we have to keep rendering animation frames, otherwise the animation flickers during background movement - RenderManager *renderManager = _engine->getRenderManager(); - RenderTable::RenderState state = renderManager->getRenderTable()->getRenderState(); - - if (state == RenderTable::PANORAMA || state == RenderTable::TILT) { - Common::Point workingWindowPoint = renderManager->imageSpaceToWorkingWindowSpace(Common::Point(_x, _y)); - Common::Rect subRect(workingWindowPoint.x, workingWindowPoint.y, workingWindowPoint.x + _cachedFrame->w, workingWindowPoint.y + _cachedFrame->h); - - // If the clip returns false, it means the animation is outside the working window - if (!renderManager->clipRectToWorkingWindow(subRect)) { - return false; - } - - renderManager->copyRectToWorkingWindow((uint16 *)_cachedFrame->getBasePtr(_cachedFrame->w - subRect.width(), _cachedFrame->h - subRect.height()), subRect.left, subRect.top, _cachedFrame->w, subRect.width(), subRect.height()); - } - } - } else if (_fileType == AVI) { - if (!_animation.avi->isPlaying()) { - _animation.avi->start(); - } - - if (_animation.avi->needsUpdate()) { - const Graphics::Surface *frame = _animation.avi->decodeNextFrame(); - - if (frame) { - // Make sure the frame is inside the working window - // If it's not, then just return - - RenderManager *renderManager = _engine->getRenderManager(); - Common::Point workingWindowPoint = renderManager->imageSpaceToWorkingWindowSpace(Common::Point(_x, _y)); - Common::Rect subRect(workingWindowPoint.x, workingWindowPoint.y, workingWindowPoint.x + frame->w, workingWindowPoint.y + frame->h); - - // If the clip returns false, it means the animation is outside the working window - if (!renderManager->clipRectToWorkingWindow(subRect)) { - return false; - } - - // Animation frames for PANORAMAs are transposed, so un-transpose them - RenderTable::RenderState state = renderManager->getRenderTable()->getRenderState(); - if (state == RenderTable::PANORAMA) { - Graphics::Surface *tranposedFrame = RenderManager::tranposeSurface(frame); - - renderManager->copyRectToWorkingWindow((uint16 *)tranposedFrame->getBasePtr(tranposedFrame->w - subRect.width(), tranposedFrame->h - subRect.height()), subRect.left, subRect.top, frame->w, subRect.width(), subRect.height()); - - // If the background can move, we need to cache the last frame so it can be rendered during background movement - if (state == RenderTable::PANORAMA || state == RenderTable::TILT) { - if (_cachedFrameNeedsDeletion) { - _cachedFrame->free(); - delete _cachedFrame; - _cachedFrameNeedsDeletion = false; - } - _cachedFrame = tranposedFrame; - _cachedFrameNeedsDeletion = true; - } else { - // Cleanup - tranposedFrame->free(); - delete tranposedFrame; - } - } else { - renderManager->copyRectToWorkingWindow((const uint16 *)frame->getBasePtr(frame->w - subRect.width(), frame->h - subRect.height()), subRect.left, subRect.top, frame->w, subRect.width(), subRect.height()); - - // If the background can move, we need to cache the last frame so it can be rendered during background movement - if (state == RenderTable::PANORAMA || state == RenderTable::TILT) { - if (_cachedFrameNeedsDeletion) { - _cachedFrame->free(); - delete _cachedFrame; - _cachedFrameNeedsDeletion = false; - } - _cachedFrame->copyFrom(*frame); - } - } - } else { - // If the background can move, we have to keep rendering animation frames, otherwise the animation flickers during background movement - RenderManager *renderManager = _engine->getRenderManager(); - RenderTable::RenderState state = renderManager->getRenderTable()->getRenderState(); - - if (state == RenderTable::PANORAMA || state == RenderTable::TILT) { - Common::Point workingWindowPoint = renderManager->imageSpaceToWorkingWindowSpace(Common::Point(_x, _y)); - Common::Rect subRect(workingWindowPoint.x, workingWindowPoint.y, workingWindowPoint.x + _cachedFrame->w, workingWindowPoint.y + _cachedFrame->h); - - // If the clip returns false, it means the animation is outside the working window - if (!renderManager->clipRectToWorkingWindow(subRect)) { - return false; - } - - renderManager->copyRectToWorkingWindow((uint16 *)_cachedFrame->getBasePtr(_cachedFrame->w - subRect.width(), _cachedFrame->h - subRect.height()), subRect.left, subRect.top, _cachedFrame->w, subRect.width(), subRect.height()); - } - } - } - - // Check if we should continue looping - if (_animation.avi->endOfVideo()) { - _animation.avi->rewind(); - if (_loopCount > 0) { - _currentLoop++; - if (_currentLoop >= _loopCount) { - _animation.avi->stop(); - finished = true; - } - } - } - } - - // If we're done, set _animation key = 2 (Why 2? I don't know. It's just the value that they used) - // Then disable the control. DON'T delete it. It can be re-used - if (finished) { - _engine->getScriptManager()->setStateValue(_animationKey, 2); - disable(); - _currentLoop = 0; - } - - return false; -} - -} // End of namespace ZVision diff --git a/engines/zvision/scripting/controls/fist_control.cpp b/engines/zvision/scripting/controls/fist_control.cpp new file mode 100644 index 0000000000..f79c82dc79 --- /dev/null +++ b/engines/zvision/scripting/controls/fist_control.cpp @@ -0,0 +1,304 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/scummsys.h" + +#include "zvision/zvision.h" +#include "zvision/scripting/script_manager.h" +#include "zvision/scripting/controls/fist_control.h" +#include "zvision/graphics/render_manager.h" +#include "zvision/graphics/cursors/cursor_manager.h" +#include "zvision/video/rlf_decoder.h" + +#include "common/stream.h" +#include "common/file.h" +#include "common/system.h" +#include "graphics/surface.h" +#include "video/video_decoder.h" + +namespace ZVision { + +FistControl::FistControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream) + : Control(engine, key, CONTROL_FIST) { + _cursor = CursorIndex_Idle; + _animation = NULL; + _soundKey = 0; + _fiststatus = 0; + _order = 0; + _fistnum = 0; + + _animationId = 0; + + clearFistArray(_fistsUp); + clearFistArray(_fistsDwn); + + _numEntries = 0; + _entries.clear(); + + _anmRect = Common::Rect(); + + // Loop until we find the closing brace + Common::String line = stream.readLine(); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + Common::String param; + Common::String values; + getParams(line, param, values); + + while (!stream.eos() && !line.contains('}')) { + if (param.matchString("sound_key", true)) { + _soundKey = atoi(values.c_str()); + } else if (param.matchString("cursor", true)) { + _cursor = _engine->getCursorManager()->getCursorId(values); + } else if (param.matchString("descfile", true)) { + readDescFile(values); + } else if (param.matchString("animation_id", true)) { + _animationId = atoi(values.c_str()); + } else if (param.matchString("venus_id", true)) { + _venusId = atoi(values.c_str()); + } + + line = stream.readLine(); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + getParams(line, param, values); + } +} + +FistControl::~FistControl() { + if (_animation) + delete _animation; + + clearFistArray(_fistsUp); + clearFistArray(_fistsDwn); + _entries.clear(); +} + +bool FistControl::process(uint32 deltaTimeInMillis) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + if (_animation && _animation->isPlaying()) { + if (_animation->endOfVideo()) { + _animation->stop(); + _engine->getScriptManager()->setStateValue(_animationId, 2); + return false; + } + + if (_animation->needsUpdate()) { + const Graphics::Surface *frameData = _animation->decodeNextFrame(); + if (frameData) + // WORKAROUND: Ignore the target frame dimensions for the finger animations. + // The target dimensions specify an area smaller than expected, thus if we + // scale the finger videos to fit these dimensions, they are not aligned + // correctly. Not scaling these videos yields a result identical to the + // original. Fixes bug #6784. + _engine->getRenderManager()->blitSurfaceToBkg(*frameData, _anmRect.left, _anmRect.top); + } + } + + return false; +} + +bool FistControl::onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + if (mouseIn(screenSpacePos, backgroundImageSpacePos) >= 0) { + _engine->getCursorManager()->changeCursor(_cursor); + return true; + } + + return false; +} + +bool FistControl::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + int fistNumber = mouseIn(screenSpacePos, backgroundImageSpacePos); + + if (fistNumber >= 0) { + setVenus(); + + uint32 oldStatus = _fiststatus; + _fiststatus ^= (1 << fistNumber); + + for (int i = 0; i < _numEntries; i++) + if (_entries[i]._bitsStrt == oldStatus && _entries[i]._bitsEnd == _fiststatus) { + if (_animation) { + _animation->stop(); + _animation->seekToFrame(_entries[i]._anmStrt); + _animation->setEndFrame(_entries[i]._anmEnd); + _animation->start(); + } + + _engine->getScriptManager()->setStateValue(_animationId, 1); + _engine->getScriptManager()->setStateValue(_soundKey, _entries[i]._sound); + break; + } + + _engine->getScriptManager()->setStateValue(_key, _fiststatus); + } + + return false; +} + +void FistControl::readDescFile(const Common::String &fileName) { + Common::File file; + if (!_engine->getSearchManager()->openFile(file, fileName)) { + warning("Desc file %s could could be opened", fileName.c_str()); + return; + } + + Common::String line; + Common::String param; + Common::String values; + + while (!file.eos()) { + line = file.readLine(); + getFistParams(line, param, values); + + if (param.matchString("animation_id", true)) { + // Not used + } else if (param.matchString("animation", true)) { + _animation = _engine->loadAnimation(values); + } else if (param.matchString("anim_rect", true)) { + int left, top, right, bottom; + sscanf(values.c_str(), "%d %d %d %d", &left, &top, &right, &bottom); + _anmRect = Common::Rect(left, top, right, bottom); + } else if (param.matchString("num_fingers", true)) { + sscanf(values.c_str(), "%d", &_fistnum); + _fistsUp.resize(_fistnum); + _fistsDwn.resize(_fistnum); + } else if (param.matchString("entries", true)) { + sscanf(values.c_str(), "%d", &_numEntries); + _entries.resize(_numEntries); + } else if (param.matchString("eval_order_ascending", true)) { + sscanf(values.c_str(), "%d", &_order); + } else if (param.matchString("up_hs_num_*", true)) { + int fist, num; + num = atoi(values.c_str()); + + sscanf(param.c_str(), "up_hs_num_%d", &fist); + _fistsUp[fist].resize(num); + } else if (param.matchString("up_hs_*", true)) { + int16 fist, box, x1, y1, x2, y2; + sscanf(param.c_str(), "up_hs_%hd_%hd", &fist, &box); + sscanf(values.c_str(), "%hd %hd %hd %hd", &x1, &y1, &x2, &y2); + (_fistsUp[fist])[box] = Common::Rect(x1, y1, x2, y2); + } else if (param.matchString("down_hs_num_*", true)) { + int fist, num; + num = atoi(values.c_str()); + + sscanf(param.c_str(), "down_hs_num_%d", &fist); + _fistsDwn[fist].resize(num); + } else if (param.matchString("down_hs_*", true)) { + int16 fist, box, x1, y1, x2, y2; + sscanf(param.c_str(), "down_hs_%hd_%hd", &fist, &box); + sscanf(values.c_str(), "%hd %hd %hd %hd", &x1, &y1, &x2, &y2); + (_fistsDwn[fist])[box] = Common::Rect(x1, y1, x2, y2); + } else { + int entry, start, end, sound; + char bitsStart[33]; + char bitsEnd[33]; + entry = atoi(param.c_str()); + if (sscanf(values.c_str(), "%s %s %d %d (%d)", bitsStart, bitsEnd, &start, &end, &sound) == 5) { + _entries[entry]._bitsStrt = readBits(bitsStart); + _entries[entry]._bitsEnd = readBits(bitsEnd); + _entries[entry]._anmStrt = start; + _entries[entry]._anmEnd = end; + _entries[entry]._sound = sound; + } + } + } + file.close(); +} + +void FistControl::clearFistArray(Common::Array< Common::Array<Common::Rect> > &arr) { + for (uint i = 0; i < arr.size(); i++) + arr[i].clear(); + + arr.clear(); +} + +uint32 FistControl::readBits(const char *str) { + uint32 bfield = 0; + int len = strlen(str); + for (int i = 0; i < len; i++) + if (str[i] != '0') + bfield |= (1 << i); + return bfield; +} + +int FistControl::mouseIn(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (_order) { + for (int i = 0; i < _fistnum; i++) { + if (((_fiststatus >> i) & 1) == 1) { + for (uint j = 0; j < _fistsDwn[i].size(); j++) + if ((_fistsDwn[i])[j].contains(backgroundImageSpacePos)) + return i; + } else { + for (uint j = 0; j < _fistsUp[i].size(); j++) + if ((_fistsUp[i])[j].contains(backgroundImageSpacePos)) + return i; + } + } + } else { + for (int i = _fistnum - 1; i >= 0; i--) { + if (((_fiststatus >> i) & 1) == 1) { + for (uint j = 0; j < _fistsDwn[i].size(); j++) + if ((_fistsDwn[i])[j].contains(backgroundImageSpacePos)) + return i; + } else { + for (uint j = 0; j < _fistsUp[i].size(); j++) + if ((_fistsUp[i])[j].contains(backgroundImageSpacePos)) + return i; + } + } + } + return -1; +} + +void FistControl::getFistParams(const Common::String &inputStr, Common::String ¶meter, Common::String &values) { + const char *chrs = inputStr.c_str(); + uint lbr; + + for (lbr = 0; lbr < inputStr.size(); lbr++) + if (chrs[lbr] == ':') + break; + + if (lbr >= inputStr.size()) + return; + + uint rbr; + + for (rbr = lbr + 1; rbr < inputStr.size(); rbr++) + if (chrs[rbr] == '~') + break; + + if (rbr >= inputStr.size()) + return; + + parameter = Common::String(chrs, chrs + lbr); + values = Common::String(chrs + lbr + 1, chrs + rbr); +} + +} // End of namespace ZVision diff --git a/engines/zvision/scripting/controls/fist_control.h b/engines/zvision/scripting/controls/fist_control.h new file mode 100644 index 0000000000..d7cbcb1f71 --- /dev/null +++ b/engines/zvision/scripting/controls/fist_control.h @@ -0,0 +1,84 @@ +/* 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 ZVISION_FIST_CONTROL_H +#define ZVISION_FIST_CONTROL_H + +#include "zvision/scripting/control.h" + +#include "common/array.h" +#include "common/rect.h" + +namespace Video { + class VideoDecoder; +} + +namespace ZVision { + +// Only used in Zork Nemesis, handles the door lock puzzle with the skeletal fingers (td9e) +class FistControl : public Control { +public: + FistControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream); + ~FistControl(); + +private: + uint32 _fiststatus; + int _fistnum; + int16 _cursor; + int _order; + + Common::Array< Common::Array<Common::Rect> > _fistsUp; + Common::Array< Common::Array<Common::Rect> > _fistsDwn; + + int32 _numEntries; + + struct entries { + uint32 _bitsStrt; + uint32 _bitsEnd; + int32 _anmStrt; + int32 _anmEnd; + int32 _sound; + }; + + Common::Array<entries> _entries; + + Video::VideoDecoder *_animation; + Common::Rect _anmRect; + int32 _soundKey; + int32 _animationId; + +public: + bool onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + bool onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + bool process(uint32 deltaTimeInMillis); + +private: + void readDescFile(const Common::String &fileName); + void clearFistArray(Common::Array< Common::Array<Common::Rect> > &arr); + uint32 readBits(const char *str); + int mouseIn(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + void getFistParams(const Common::String &inputStr, Common::String ¶meter, Common::String &values); +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/scripting/controls/hotmov_control.cpp b/engines/zvision/scripting/controls/hotmov_control.cpp new file mode 100644 index 0000000000..182447a990 --- /dev/null +++ b/engines/zvision/scripting/controls/hotmov_control.cpp @@ -0,0 +1,188 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/scummsys.h" + +#include "zvision/scripting/controls/hotmov_control.h" + +#include "zvision/zvision.h" +#include "zvision/scripting/script_manager.h" +#include "zvision/graphics/render_manager.h" +#include "zvision/graphics/cursors/cursor_manager.h" + +#include "common/stream.h" +#include "common/file.h" +#include "common/system.h" +#include "graphics/surface.h" +#include "video/video_decoder.h" + +namespace ZVision { + +HotMovControl::HotMovControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream) + : Control(engine, key, CONTROL_HOTMOV) { + _animation = NULL; + _cycle = 0; + _frames.clear(); + _cyclesCount = 0; + _framesCount = 0; + + _engine->getScriptManager()->setStateValue(_key, 0); + + // Loop until we find the closing brace + Common::String line = stream.readLine(); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + Common::String param; + Common::String values; + getParams(line, param, values); + + while (!stream.eos() && !line.contains('}')) { + if (param.matchString("hs_frame_list", true)) { + readHsFile(values); + } else if (param.matchString("rectangle", true)) { + int x; + int y; + int width; + int height; + + sscanf(values.c_str(), "%d %d %d %d", &x, &y, &width, &height); + + _rectangle = Common::Rect(x, y, width, height); + } else if (param.matchString("num_frames", true)) { + _framesCount = atoi(values.c_str()); + } else if (param.matchString("num_cycles", true)) { + _cyclesCount = atoi(values.c_str()); + } else if (param.matchString("animation", true)) { + char filename[64]; + sscanf(values.c_str(), "%s", filename); + values = Common::String(filename); + _animation = _engine->loadAnimation(values); + _animation->start(); + } else if (param.matchString("venus_id", true)) { + _venusId = atoi(values.c_str()); + } + + line = stream.readLine(); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + getParams(line, param, values); + } +} + +HotMovControl::~HotMovControl() { + if (_animation) + delete _animation; + + _frames.clear(); +} + +bool HotMovControl::process(uint32 deltaTimeInMillis) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + if (_cycle < _cyclesCount) { + if (_animation && _animation->endOfVideo()) { + _cycle++; + + if (_cycle == _cyclesCount) { + _engine->getScriptManager()->setStateValue(_key, 2); + return false; + } + + _animation->rewind(); + } + + if (_animation && _animation->needsUpdate()) { + const Graphics::Surface *frameData = _animation->decodeNextFrame(); + if (frameData) + _engine->getRenderManager()->blitSurfaceToBkgScaled(*frameData, _rectangle); + } + } + + return false; +} + +bool HotMovControl::onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + if (!_animation) + return false; + + if (_cycle < _cyclesCount) { + if (_frames[_animation->getCurFrame()].contains(backgroundImageSpacePos)) { + _engine->getCursorManager()->changeCursor(CursorIndex_Active); + return true; + } + } + + return false; +} + +bool HotMovControl::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + if (!_animation) + return false; + + if (_cycle < _cyclesCount) { + if (_frames[_animation->getCurFrame()].contains(backgroundImageSpacePos)) { + setVenus(); + _engine->getScriptManager()->setStateValue(_key, 1); + return true; + } + } + + return false; +} + +void HotMovControl::readHsFile(const Common::String &fileName) { + if (_framesCount == 0) + return; + + Common::File file; + if (!_engine->getSearchManager()->openFile(file, fileName)) { + warning("HS file %s could could be opened", fileName.c_str()); + return; + } + + Common::String line; + + _frames.resize(_framesCount); + + while (!file.eos()) { + line = file.readLine(); + + int frame; + int x; + int y; + int width; + int height; + + sscanf(line.c_str(), "%d:%d %d %d %d~", &frame, &x, &y, &width, &height); + + if (frame >= 0 && frame < _framesCount) + _frames[frame] = Common::Rect(x, y, width, height); + } + file.close(); +} + +} // End of namespace ZVision diff --git a/engines/zvision/scripting/controls/hotmov_control.h b/engines/zvision/scripting/controls/hotmov_control.h new file mode 100644 index 0000000000..99d1fd0979 --- /dev/null +++ b/engines/zvision/scripting/controls/hotmov_control.h @@ -0,0 +1,61 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ZVISION_HOTMOV_CONTROL_H +#define ZVISION_HOTMOV_CONTROL_H + +#include "zvision/scripting/control.h" + +#include "common/array.h" +#include "common/rect.h" + +namespace Video { + class VideoDecoder; +} + +namespace ZVision { + +// Only used in Zork Nemesis, handles movies where the player needs to click on something (mj7g, vw3g) +class HotMovControl : public Control { +public: + HotMovControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream); + ~HotMovControl(); + +private: + int32 _framesCount; + int32 _cycle; + int32 _cyclesCount; + Video::VideoDecoder *_animation; + Common::Rect _rectangle; + Common::Array<Common::Rect> _frames; +public: + bool onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + bool onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + bool process(uint32 deltaTimeInMillis); + +private: + void readHsFile(const Common::String &fileName); +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/scripting/controls/input_control.cpp b/engines/zvision/scripting/controls/input_control.cpp index a35548d02e..df0c77ba96 100644 --- a/engines/zvision/scripting/controls/input_control.cpp +++ b/engines/zvision/scripting/controls/input_control.cpp @@ -8,12 +8,12 @@ * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. - * + * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. @@ -23,120 +23,249 @@ #include "common/scummsys.h" #include "zvision/scripting/controls/input_control.h" +#include "zvision/graphics/cursors/cursor_manager.h" #include "zvision/zvision.h" #include "zvision/scripting/script_manager.h" -#include "zvision/strings/string_manager.h" +#include "zvision/text/string_manager.h" #include "zvision/graphics/render_manager.h" -#include "zvision/utility/utility.h" #include "common/str.h" #include "common/stream.h" #include "common/rect.h" - +#include "video/video_decoder.h" namespace ZVision { InputControl::InputControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream) - : Control(engine, key), - _nextTabstop(0), - _focused(false), - _textChanged(false), - _cursorOffset(0) { + : Control(engine, key, CONTROL_INPUT), + _background(0), + _nextTabstop(0), + _focused(false), + _textChanged(false), + _cursorOffset(0), + _enterPressed(false), + _readOnly(false), + _txtWidth(0), + _animation(NULL) { // Loop until we find the closing brace Common::String line = stream.readLine(); - trimCommentsAndWhiteSpace(&line); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + Common::String param; + Common::String values; + getParams(line, param, values); while (!stream.eos() && !line.contains('}')) { - if (line.matchString("*rectangle*", true)) { + if (param.matchString("rectangle", true)) { int x1; int y1; int x2; int y2; - sscanf(line.c_str(), "%*[^(](%d %d %d %d)", &x1, &y1, &x2, &y2); + sscanf(values.c_str(), "%d %d %d %d", &x1, &y1, &x2, &y2); _textRectangle = Common::Rect(x1, y1, x2, y2); - } else if (line.matchString("*aux_hotspot*", true)) { + } else if (param.matchString("aux_hotspot", true)) { int x1; int y1; int x2; int y2; - sscanf(line.c_str(), "%*[^(](%d %d %d %d)", &x1, &y1, &x2, &y2); + sscanf(values.c_str(), "%d %d %d %d", &x1, &y1, &x2, &y2); _headerRectangle = Common::Rect(x1, y1, x2, y2); - } else if (line.matchString("*string_init*", true)) { + } else if (param.matchString("string_init", true)) { uint fontFormatNumber; - sscanf(line.c_str(), "%*[^(](%u)", &fontFormatNumber); + sscanf(values.c_str(), "%u", &fontFormatNumber); - _textStyle = _engine->getStringManager()->getTextStyle(fontFormatNumber); - } else if (line.matchString("*next_tabstop*", true)) { - sscanf(line.c_str(), "%*[^(](%u)", &_nextTabstop); - } else if (line.matchString("*cursor_animation*", true)) { - char fileName[26]; + _stringInit.readAllStyles(_engine->getStringManager()->getTextLine(fontFormatNumber)); + } else if (param.matchString("chooser_init_string", true)) { + uint fontFormatNumber; - sscanf(line.c_str(), "%*[^(](%25s %*u)", fileName); + sscanf(values.c_str(), "%u", &fontFormatNumber); - _cursorAnimationFileName = Common::String(fileName); - } else if (line.matchString("*cursor_dimensions*", true)) { + _stringChooserInit.readAllStyles(_engine->getStringManager()->getTextLine(fontFormatNumber)); + } else if (param.matchString("next_tabstop", true)) { + sscanf(values.c_str(), "%u", &_nextTabstop); + } else if (param.matchString("cursor_dimensions", true)) { // Ignore, use the dimensions in the animation file - } else if (line.matchString("*cursor_animation_frames*", true)) { + } else if (param.matchString("cursor_animation_frames", true)) { // Ignore, use the frame count in the animation file - } else if (line.matchString("*focus*", true)) { + } else if (param.matchString("cursor_animation", true)) { + char fileName[25]; + + sscanf(values.c_str(), "%24s %*u", fileName); + + _animation = _engine->loadAnimation(fileName); + _animation->start(); + } else if (param.matchString("focus", true)) { _focused = true; + _engine->getScriptManager()->setFocusControlKey(_key); + } else if (param.matchString("venus_id", true)) { + _venusId = atoi(values.c_str()); } line = stream.readLine(); - trimCommentsAndWhiteSpace(&line); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + getParams(line, param, values); + } + + _maxTxtWidth = _textRectangle.width(); + if (_animation) + _maxTxtWidth -= _animation->getWidth(); +} + +InputControl::~InputControl() { + _background->free(); + delete _background; +} + +bool InputControl::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + if (_textRectangle.contains(backgroundImageSpacePos)) { + if (!_readOnly) { + // Save + _engine->getScriptManager()->focusControl(_key); + setVenus(); + } else { + // Restore + if (_currentInputText.size()) { + setVenus(); + _enterPressed = true; + } + } } + return false; } -void InputControl::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { - _engine->getScriptManager()->focusControl(_key); +bool InputControl::onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + if (_textRectangle.contains(backgroundImageSpacePos)) { + if (!_readOnly) { + // Save + _engine->getCursorManager()->changeCursor(CursorIndex_Active); + return true; + } else { + // Restore + if (_currentInputText.size()) { + _engine->getCursorManager()->changeCursor(CursorIndex_Active); + _engine->getScriptManager()->focusControl(_key); + return true; + } + } + } + return false; } -void InputControl::onKeyDown(Common::KeyState keyState) { +bool InputControl::onKeyDown(Common::KeyState keyState) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + if (!_focused) { - return; + return false; } if (keyState.keycode == Common::KEYCODE_BACKSPACE) { - _currentInputText.deleteLastChar(); + if (!_readOnly) { + _currentInputText.deleteLastChar(); + _textChanged = true; + } + } else if (keyState.keycode == Common::KEYCODE_RETURN) { + _enterPressed = true; } else if (keyState.keycode == Common::KEYCODE_TAB) { - _focused = false; + unfocus(); // Focus the next input control _engine->getScriptManager()->focusControl(_nextTabstop); + // Don't process this event for other controls + return true; } else { - // Otherwise, append the new character to the end of the current text - - uint16 asciiValue = keyState.ascii; - // We only care about text values - if (asciiValue >= 32 && asciiValue <= 126) { - _currentInputText += (char)asciiValue; - _textChanged = true; + if (!_readOnly) { + // Otherwise, append the new character to the end of the current text + uint16 asciiValue = keyState.ascii; + // We only care about text values + if (asciiValue >= 32 && asciiValue <= 126) { + _currentInputText += (char)asciiValue; + _textChanged = true; + } } } + return false; } bool InputControl::process(uint32 deltaTimeInMillis) { - if (!_focused) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) return false; + + if (!_background) { + _background = _engine->getRenderManager()->getBkgRect(_textRectangle); } // First see if we need to render the text if (_textChanged) { // Blit the text using the RenderManager - Common::Rect destRect = _engine->getRenderManager()->renderTextToWorkingWindow(_key, _currentInputText, _textStyle.font, _textRectangle.left, _textRectangle.top, _textStyle.color, _textRectangle.width()); - _cursorOffset = destRect.left - _textRectangle.left; + Graphics::Surface txt; + txt.copyFrom(*_background); + + int32 oldTxtWidth = _txtWidth; + + if (!_readOnly || !_focused) + _txtWidth = _engine->getTextRenderer()->drawText(_currentInputText, _stringInit, txt); + else + _txtWidth = _engine->getTextRenderer()->drawText(_currentInputText, _stringChooserInit, txt); + + if (_readOnly || _txtWidth <= _maxTxtWidth) + _engine->getRenderManager()->blitSurfaceToBkg(txt, _textRectangle.left, _textRectangle.top); + else { + // Assume the last character caused the overflow. + _currentInputText.deleteLastChar(); + _txtWidth = oldTxtWidth; + } + + txt.free(); } - // Render the next frame of the animation - // TODO: Implement animation handling + if (_animation && !_readOnly && _focused) { + if (_animation->endOfVideo()) + _animation->rewind(); + if (_animation->needsUpdate()) { + const Graphics::Surface *srf = _animation->decodeNextFrame(); + int16 xx = _textRectangle.left + _txtWidth; + if (xx >= _textRectangle.left + (_textRectangle.width() - (int16)_animation->getWidth())) + xx = _textRectangle.left + _textRectangle.width() - (int16)_animation->getWidth(); + _engine->getRenderManager()->blitSurfaceToBkg(*srf, xx, _textRectangle.top); + } + } + + _textChanged = false; + return false; +} + +bool InputControl::enterPress() { + if (_enterPressed) { + _enterPressed = false; + return true; + } return false; } +void InputControl::setText(const Common::String &_str) { + _currentInputText = _str; + _textChanged = true; +} + +const Common::String InputControl::getText() { + return _currentInputText; +} + +void InputControl::setReadOnly(bool readonly) { + _readOnly = readonly; +} + } // End of namespace ZVision diff --git a/engines/zvision/scripting/controls/input_control.h b/engines/zvision/scripting/controls/input_control.h index 32432438bb..9b48514e16 100644 --- a/engines/zvision/scripting/controls/input_control.h +++ b/engines/zvision/scripting/controls/input_control.h @@ -24,35 +24,60 @@ #define ZVISION_INPUT_CONTROL_H #include "zvision/scripting/control.h" -#include "zvision/strings/string_manager.h" +#include "zvision/text/text.h" +#include "zvision/text/string_manager.h" #include "common/rect.h" +namespace Video { + class VideoDecoder; +} namespace ZVision { class InputControl : public Control { public: InputControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream); + ~InputControl(); private: + Graphics::Surface *_background; Common::Rect _textRectangle; Common::Rect _headerRectangle; - StringManager::TextStyle _textStyle; + TextStyleState _stringInit; + TextStyleState _stringChooserInit; uint32 _nextTabstop; - Common::String _cursorAnimationFileName; bool _focused; Common::String _currentInputText; bool _textChanged; uint _cursorOffset; + bool _enterPressed; + bool _readOnly; + + int16 _txtWidth; + int16 _maxTxtWidth; + Video::VideoDecoder *_animation; + int32 _frameDelay; + int16 _frame; public: - void focus() { _focused = true; } - void unfocus() { _focused = false; } - void onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); - void onKeyDown(Common::KeyState keyState); + void focus() { + _focused = true; + _textChanged = true; + } + void unfocus() { + _focused = false; + _textChanged = true; + } + bool onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + bool onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + bool onKeyDown(Common::KeyState keyState); bool process(uint32 deltaTimeInMillis); + void setText(const Common::String &_str); + const Common::String getText(); + bool enterPress(); + void setReadOnly(bool); }; } // End of namespace ZVision diff --git a/engines/zvision/scripting/controls/lever_control.cpp b/engines/zvision/scripting/controls/lever_control.cpp index 9724e661b7..0f105b424c 100644 --- a/engines/zvision/scripting/controls/lever_control.cpp +++ b/engines/zvision/scripting/controls/lever_control.cpp @@ -8,12 +8,12 @@ * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. - * + * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. @@ -27,130 +27,128 @@ #include "zvision/zvision.h" #include "zvision/scripting/script_manager.h" #include "zvision/graphics/render_manager.h" -#include "zvision/cursors/cursor_manager.h" -#include "zvision/animation/rlf_animation.h" -#include "zvision/video/zork_avi_decoder.h" -#include "zvision/utility/utility.h" +#include "zvision/graphics/cursors/cursor_manager.h" #include "common/stream.h" #include "common/file.h" #include "common/tokenizer.h" #include "common/system.h" - #include "graphics/surface.h" - +#include "video/video_decoder.h" namespace ZVision { LeverControl::LeverControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream) - : Control(engine, key), - _frameInfo(0), - _frameCount(0), - _startFrame(0), - _currentFrame(0), - _lastRenderedFrame(0), - _mouseIsCaptured(false), - _isReturning(false), - _accumulatedTime(0), - _returnRoutesCurrentFrame(0) { + : Control(engine, key, CONTROL_LEVER), + _frameInfo(0), + _frameCount(0), + _startFrame(0), + _currentFrame(0), + _lastRenderedFrame(0), + _mouseIsCaptured(false), + _isReturning(false), + _accumulatedTime(0), + _returnRoutesCurrentFrame(0), + _animation(NULL), + _cursor(CursorIndex_Active), + _mirrored(false) { // Loop until we find the closing brace Common::String line = stream.readLine(); - trimCommentsAndWhiteSpace(&line); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + + Common::String param; + Common::String values; + getParams(line, param, values); while (!stream.eos() && !line.contains('}')) { - if (line.matchString("*descfile*", true)) { + if (param.matchString("descfile", true)) { char levFileName[25]; - sscanf(line.c_str(), "%*[^(](%25[^)])", levFileName); + sscanf(values.c_str(), "%24s", levFileName); parseLevFile(levFileName); - } else if (line.matchString("*cursor*", true)) { + } else if (param.matchString("cursor", true)) { char cursorName[25]; - sscanf(line.c_str(), "%*[^(](%25[^)])", cursorName); + sscanf(values.c_str(), "%24s", cursorName); - _cursorName = Common::String(cursorName); + _cursor = _engine->getCursorManager()->getCursorId(Common::String(cursorName)); } line = stream.readLine(); - trimCommentsAndWhiteSpace(&line); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + getParams(line, param, values); } renderFrame(_currentFrame); } LeverControl::~LeverControl() { - if (_fileType == AVI) { - delete _animation.avi; - } else if (_fileType == RLF) { - delete _animation.rlf; - } - + if (_animation) + delete _animation; + delete[] _frameInfo; } void LeverControl::parseLevFile(const Common::String &fileName) { Common::File file; - if (!file.open(fileName)) { + if (!_engine->getSearchManager()->openFile(file, fileName)) { warning("LEV file %s could could be opened", fileName.c_str()); return; } - Common::String line = file.readLine(); + Common::String line; + Common::String param; + Common::String values; while (!file.eos()) { - if (line.matchString("*animation_id*", true)) { + line = file.readLine(); + getLevParams(line, param, values); + + if (param.matchString("animation_id", true)) { // Not used - } else if (line.matchString("*filename*", true)) { - char fileNameBuffer[25]; - sscanf(line.c_str(), "%*[^:]:%25[^~]~", fileNameBuffer); - - Common::String animationFileName(fileNameBuffer); - - if (animationFileName.hasSuffix(".avi")) { - _animation.avi = new ZorkAVIDecoder(); - _animation.avi->loadFile(animationFileName); - _fileType = AVI; - } else if (animationFileName.hasSuffix(".rlf")) { - _animation.rlf = new RlfAnimation(animationFileName, false); - _fileType = RLF; - } - } else if (line.matchString("*skipcolor*", true)) { + } else if (param.matchString("filename", true)) { + _animation = _engine->loadAnimation(values); + } else if (param.matchString("skipcolor", true)) { // Not used - } else if (line.matchString("*anim_coords*", true)) { + } else if (param.matchString("anim_coords", true)) { int left, top, right, bottom; - sscanf(line.c_str(), "%*[^:]:%d %d %d %d~", &left, &top, &right, &bottom); + sscanf(values.c_str(), "%d %d %d %d", &left, &top, &right, &bottom); _animationCoords.left = left; _animationCoords.top = top; _animationCoords.right = right; _animationCoords.bottom = bottom; - } else if (line.matchString("*mirrored*", true)) { + } else if (param.matchString("mirrored", true)) { uint mirrored; - sscanf(line.c_str(), "%*[^:]:%u~", &mirrored); + sscanf(values.c_str(), "%u", &mirrored); _mirrored = mirrored == 0 ? false : true; - } else if (line.matchString("*frames*", true)) { - sscanf(line.c_str(), "%*[^:]:%u~", &_frameCount); + } else if (param.matchString("frames", true)) { + sscanf(values.c_str(), "%u", &_frameCount); _frameInfo = new FrameInfo[_frameCount]; - } else if (line.matchString("*elsewhere*", true)) { + } else if (param.matchString("elsewhere", true)) { // Not used - } else if (line.matchString("*out_of_control*", true)) { + } else if (param.matchString("out_of_control", true)) { // Not used - } else if (line.matchString("*start_pos*", true)) { - sscanf(line.c_str(), "%*[^:]:%u~", &_startFrame); + } else if (param.matchString("start_pos", true)) { + sscanf(values.c_str(), "%u", &_startFrame); _currentFrame = _startFrame; - } else if (line.matchString("*hotspot_deltas*", true)) { + } else if (param.matchString("hotspot_deltas", true)) { uint x; uint y; - sscanf(line.c_str(), "%*[^:]:%u %u~", &x, &y); + sscanf(values.c_str(), "%u %u", &x, &y); _hotspotDelta.x = x; _hotspotDelta.y = y; + } else if (param.matchString("venus_id", true)) { + _venusId = atoi(values.c_str()); } else { uint frameNumber; uint x, y; + line.toLowercase(); + if (sscanf(line.c_str(), "%u:%u %u", &frameNumber, &x, &y) == 3) { _frameInfo[frameNumber].hotspot.left = x; _frameInfo[frameNumber].hotspot.top = y; @@ -158,13 +156,13 @@ void LeverControl::parseLevFile(const Common::String &fileName) { _frameInfo[frameNumber].hotspot.bottom = y + _hotspotDelta.y; } - Common::StringTokenizer tokenizer(line, " ^=()"); + Common::StringTokenizer tokenizer(line, " ^=()~"); tokenizer.nextToken(); tokenizer.nextToken(); Common::String token = tokenizer.nextToken(); while (!tokenizer.empty()) { - if (token == "D") { + if (token == "d") { token = tokenizer.nextToken(); uint angle; @@ -172,7 +170,7 @@ void LeverControl::parseLevFile(const Common::String &fileName) { sscanf(token.c_str(), "%u,%u", &toFrame, &angle); _frameInfo[frameNumber].directions.push_back(Direction(angle, toFrame)); - } else if (token.hasPrefix("P")) { + } else if (token.hasPrefix("p")) { // Format: P(<from> to <to>) tokenizer.nextToken(); tokenizer.nextToken(); @@ -186,26 +184,26 @@ void LeverControl::parseLevFile(const Common::String &fileName) { } } - line = file.readLine(); + // Don't read lines in this place because last will not be parsed. } } -void LeverControl::onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { - if (!_enabled) { - return; - } - +bool LeverControl::onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + if (_frameInfo[_currentFrame].hotspot.contains(backgroundImageSpacePos)) { + setVenus(); _mouseIsCaptured = true; _lastMousePos = backgroundImageSpacePos; } + return false; } -void LeverControl::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { - if (!_enabled) { - return; - } - +bool LeverControl::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + if (_mouseIsCaptured) { _mouseIsCaptured = false; _engine->getScriptManager()->setStateValue(_key, _currentFrame); @@ -214,20 +212,19 @@ void LeverControl::onMouseUp(const Common::Point &screenSpacePos, const Common:: _returnRoutesCurrentProgress = _frameInfo[_currentFrame].returnRoute.begin(); _returnRoutesCurrentFrame = _currentFrame; } + return false; } bool LeverControl::onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { - if (!_enabled) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) return false; - } - + bool cursorWasChanged = false; if (_mouseIsCaptured) { - // Make sure the square distance between the last point and the current point is greater than 64 + // Make sure the square distance between the last point and the current point is greater than 16 // This is a heuristic. This determines how responsive the lever is to mouse movement. - // TODO: Fiddle with the heuristic to get a good lever responsiveness 'feel' - if (_lastMousePos.sqrDist(backgroundImageSpacePos) >= 64) { + if (_lastMousePos.sqrDist(backgroundImageSpacePos) >= 16) { int angle = calculateVectorAngle(_lastMousePos, backgroundImageSpacePos); _lastMousePos = backgroundImageSpacePos; @@ -235,12 +232,15 @@ bool LeverControl::onMouseMove(const Common::Point &screenSpacePos, const Common if (angle >= (int)iter->angle - ANGLE_DELTA && angle <= (int)iter->angle + ANGLE_DELTA) { _currentFrame = iter->toFrame; renderFrame(_currentFrame); + _engine->getScriptManager()->setStateValue(_key, _currentFrame); break; } } } + _engine->getCursorManager()->changeCursor(_cursor); + cursorWasChanged = true; } else if (_frameInfo[_currentFrame].hotspot.contains(backgroundImageSpacePos)) { - _engine->getCursorManager()->changeCursor(_cursorName); + _engine->getCursorManager()->changeCursor(_cursor); cursorWasChanged = true; } @@ -248,9 +248,8 @@ bool LeverControl::onMouseMove(const Common::Point &screenSpacePos, const Common } bool LeverControl::process(uint32 deltaTimeInMillis) { - if (!_enabled) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) return false; - } if (_isReturning) { _accumulatedTime += deltaTimeInMillis; @@ -276,7 +275,7 @@ bool LeverControl::process(uint32 deltaTimeInMillis) { renderFrame(_returnRoutesCurrentFrame); } } - + return false; } @@ -301,7 +300,7 @@ int LeverControl::calculateVectorAngle(const Common::Point &pointOne, const Comm // Calculate the angle using arctan // Then convert to degrees. (180 / 3.14159 = 57.2958) - int angle = int(atan((float)yDist / (float)xDist) * 57); + int angle = int(atan((float)yDist / (float)abs(xDist)) * 57); // Calculate what quadrant pointTwo is in uint quadrant = ((yDist > 0 ? 1 : 0) << 1) | (xDist < 0 ? 1 : 0); @@ -350,16 +349,16 @@ int LeverControl::calculateVectorAngle(const Common::Point &pointOne, const Comm // Convert the local angles to unit circle angles switch (quadrant) { case 0: - angle = 180 + angle; + angle = -angle; break; case 1: - // Do nothing + angle = angle + 180; break; case 2: - angle = 180 + angle; + angle = 360 - angle; break; case 3: - angle = 360 + angle; + angle = 180 + angle; break; } @@ -377,26 +376,36 @@ void LeverControl::renderFrame(uint frameNumber) { _lastRenderedFrame = frameNumber; } - const uint16 *frameData = 0; - int x = _animationCoords.left; - int y = _animationCoords.top; - int width = 0; - int height = 0; - - if (_fileType == RLF) { - // getFrameData() will automatically optimize to getNextFrame() / getPreviousFrame() if it can - frameData = (const uint16 *)_animation.rlf->getFrameData(frameNumber)->getPixels(); - width = _animation.rlf->width(); // Use the animation width instead of _animationCoords.width() - height = _animation.rlf->height(); // Use the animation height instead of _animationCoords.height() - } else if (_fileType == AVI) { - _animation.avi->seekToFrame(frameNumber); - const Graphics::Surface *surface = _animation.avi->decodeNextFrame(); - frameData = (const uint16 *)surface->getPixels(); - width = surface->w; - height = surface->h; - } + const Graphics::Surface *frameData; + + _animation->seekToFrame(frameNumber); + frameData = _animation->decodeNextFrame(); + if (frameData) + _engine->getRenderManager()->blitSurfaceToBkgScaled(*frameData, _animationCoords); +} + +void LeverControl::getLevParams(const Common::String &inputStr, Common::String ¶meter, Common::String &values) { + const char *chrs = inputStr.c_str(); + uint lbr; + + for (lbr = 0; lbr < inputStr.size(); lbr++) + if (chrs[lbr] == ':') + break; + + if (lbr >= inputStr.size()) + return; + + uint rbr; + + for (rbr = lbr + 1; rbr < inputStr.size(); rbr++) + if (chrs[rbr] == '~') + break; + + if (rbr >= inputStr.size()) + return; - _engine->getRenderManager()->copyRectToWorkingWindow(frameData, x, y, width, width, height); + parameter = Common::String(chrs, chrs + lbr); + values = Common::String(chrs + lbr + 1, chrs + rbr); } } // End of namespace ZVision diff --git a/engines/zvision/scripting/controls/lever_control.h b/engines/zvision/scripting/controls/lever_control.h index 49e4fd3806..8787234c51 100644 --- a/engines/zvision/scripting/controls/lever_control.h +++ b/engines/zvision/scripting/controls/lever_control.h @@ -28,22 +28,19 @@ #include "common/list.h" #include "common/rect.h" +namespace Video { + class VideoDecoder; +} namespace ZVision { -class ZorkAVIDecoder; -class RlfAnimation; - +// Only used in Zork Nemesis, handles draggable levers (te2e, tm7e, tp2e, tt2e, tz2e) class LeverControl : public Control { public: LeverControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream); ~LeverControl(); private: - enum FileType { - RLF = 1, - AVI = 2 - }; struct Direction { Direction(uint a, uint t) : angle(a), toFrame(t) {} @@ -64,13 +61,9 @@ private: }; private: - union { - RlfAnimation *rlf; - ZorkAVIDecoder *avi; - } _animation; - FileType _fileType; + Video::VideoDecoder *_animation; - Common::String _cursorName; + int _cursor; Common::Rect _animationCoords; bool _mirrored; uint _frameCount; @@ -88,8 +81,8 @@ private: uint32 _accumulatedTime; public: - void onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); - void onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + bool onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + bool onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); bool onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); bool process(uint32 deltaTimeInMillis); @@ -120,6 +113,7 @@ private: */ static int calculateVectorAngle(const Common::Point &pointOne, const Common::Point &pointTwo); void renderFrame(uint frameNumber); + void getLevParams(const Common::String &inputStr, Common::String ¶meter, Common::String &values); }; } // End of namespace ZVision diff --git a/engines/zvision/scripting/controls/paint_control.cpp b/engines/zvision/scripting/controls/paint_control.cpp new file mode 100644 index 0000000000..62dde3d170 --- /dev/null +++ b/engines/zvision/scripting/controls/paint_control.cpp @@ -0,0 +1,219 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/scummsys.h" + +#include "zvision/scripting/controls/paint_control.h" + +#include "zvision/zvision.h" +#include "zvision/scripting/script_manager.h" +#include "zvision/graphics/cursors/cursor_manager.h" +#include "zvision/graphics/render_manager.h" + +namespace ZVision { + +PaintControl::PaintControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream) + : Control(engine, key, CONTROL_PAINT) { + + _cursor = CursorIndex_Active; + _paint = NULL; + _bkg = NULL; + _brush = NULL; + _colorKey = 0; + _mouseDown = false; + + // Loop until we find the closing brace + Common::String line = stream.readLine(); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + Common::String param; + Common::String values; + getParams(line, param, values); + + while (!stream.eos() && !line.contains('}')) { + if (param.matchString("rectangle", true)) { + int x; + int y; + int width; + int height; + + sscanf(values.c_str(), "%d %d %d %d", &x, &y, &width, &height); + + _rectangle = Common::Rect(x, y, width + x, height + y); + } else if (param.matchString("cursor", true)) { + _cursor = _engine->getCursorManager()->getCursorId(values); + } else if (param.matchString("brush_file", true)) { + _brush = _engine->getRenderManager()->loadImage(values, false); + } else if (param.matchString("venus_id", true)) { + _venusId = atoi(values.c_str()); + } else if (param.matchString("paint_file", true)) { + _paint = _engine->getRenderManager()->loadImage(values, false); + } else if (param.matchString("eligible_objects", true)) { + char buf[256]; + memset(buf, 0, 256); + strncpy(buf, values.c_str(), 255); + + char *curpos = buf; + char *strend = buf + strlen(buf); + while (true) { + char *st = curpos; + + if (st >= strend) + break; + + while (*curpos != ' ' && curpos < strend) + curpos++; + + *curpos = 0; + curpos++; + + int obj = atoi(st); + + _eligibleObjects.push_back(obj); + } + } + + line = stream.readLine(); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + getParams(line, param, values); + } + + if (_paint) { + _colorKey = _paint->format.RGBToColor(255, 0, 255); + _bkg = new Graphics::Surface; + _bkg->create(_rectangle.width(), _rectangle.height(), _paint->format); + _bkg->fillRect(Common::Rect(_rectangle.width(), _rectangle.height()), _colorKey); + + Graphics::Surface *tmp = new Graphics::Surface; + tmp->create(_rectangle.width(), _rectangle.height(), _paint->format); + _engine->getRenderManager()->blitSurfaceToSurface(*_paint, _rectangle, *tmp, 0, 0); + _paint->free(); + delete _paint; + _paint = tmp; + } +} + +PaintControl::~PaintControl() { + // Clear the state value back to 0 + //_engine->getScriptManager()->setStateValue(_key, 0); + if (_paint) { + _paint->free(); + delete _paint; + } + if (_brush) { + _brush->free(); + delete _brush; + } + if (_bkg) { + _bkg->free(); + delete _bkg; + } +} + +bool PaintControl::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + _mouseDown = false; + + return false; +} + +bool PaintControl::onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + if (_rectangle.contains(backgroundImageSpacePos)) { + int mouseItem = _engine->getScriptManager()->getStateValue(StateKey_InventoryItem); + + if (eligeblity(mouseItem)) { + setVenus(); + _mouseDown = true; + } + } + + return false; +} + +bool PaintControl::onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + if (_rectangle.contains(backgroundImageSpacePos)) { + int mouseItem = _engine->getScriptManager()->getStateValue(StateKey_InventoryItem); + + if (eligeblity(mouseItem)) { + _engine->getCursorManager()->changeCursor(_cursor); + + if (_mouseDown) { + Common::Rect bkgRect = paint(backgroundImageSpacePos); + if (!bkgRect.isEmpty()) { + Common::Rect imgRect = bkgRect; + imgRect.translate(-_rectangle.left, -_rectangle.top); + + Graphics::Surface imgUpdate = _bkg->getSubArea(imgRect); + + _engine->getRenderManager()->blitSurfaceToBkg(imgUpdate, bkgRect.left, bkgRect.top, _colorKey); + } + } + return true; + } + } + + return false; +} + +bool PaintControl::eligeblity(int itemId) { + for (Common::List<int>::iterator it = _eligibleObjects.begin(); it != _eligibleObjects.end(); it++) + if (*it == itemId) + return true; + return false; +} + +Common::Rect PaintControl::paint(const Common::Point &point) { + Common::Rect paintRect = Common::Rect(_brush->w, _brush->h); + paintRect.moveTo(point); + paintRect.clip(_rectangle); + + if (!paintRect.isEmpty()) { + Common::Rect brushRect = paintRect; + brushRect.translate(-point.x, -point.y); + + Common::Rect bkgRect = paintRect; + bkgRect.translate(-_rectangle.left, -_rectangle.top); + + for (int yy = 0; yy < brushRect.height(); yy++) { + uint16 *mask = (uint16 *)_brush->getBasePtr(brushRect.left, brushRect.top + yy); + uint16 *from = (uint16 *)_paint->getBasePtr(bkgRect.left, bkgRect.top + yy); + uint16 *to = (uint16 *)_bkg->getBasePtr(bkgRect.left, bkgRect.top + yy); + for (int xx = 0; xx < brushRect.width(); xx++) { + if (*mask != 0) + *(to + xx) = *(from + xx); + + mask++; + } + } + + } + return paintRect; +} + +} // End of namespace ZVision diff --git a/engines/zvision/scripting/controls/paint_control.h b/engines/zvision/scripting/controls/paint_control.h new file mode 100644 index 0000000000..8c01f0e68a --- /dev/null +++ b/engines/zvision/scripting/controls/paint_control.h @@ -0,0 +1,92 @@ +/* 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 ZVISION_PAINT_CONTROL_H +#define ZVISION_PAINT_CONTROL_H + +#include "zvision/scripting/control.h" + +#include "graphics/surface.h" + +#include "common/rect.h" +#include "common/list.h" + +namespace ZVision { + +// Only used in Zork Nemesis, handles the painting puzzle screen in Lucien's room in Irondune (ch4g) +class PaintControl : public Control { +public: + PaintControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream); + ~PaintControl(); + + /** + * @param screenSpacePos The position of the mouse in screen space + * @param backgroundImageSpacePos The position of the mouse in background image space + */ + bool onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + + /** + * @param screenSpacePos The position of the mouse in screen space + * @param backgroundImageSpacePos The position of the mouse in background image space + */ + bool onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + /** + * Called on every MouseMove. Tests if the mouse is inside _hotspot, and if so, sets the cursor. + * + * @param engine The base engine + * @param screenSpacePos The position of the mouse in screen space + * @param backgroundImageSpacePos The position of the mouse in background image space + * @return Was the cursor changed? + */ + bool onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + + bool process(uint32 deltaTimeInMillis) { + return false; + }; + +private: + /** + * The area that will trigger the event + * This is in image space coordinates, NOT screen space + */ + + uint32 _colorKey; + + Graphics::Surface *_paint; + Graphics::Surface *_bkg; + Graphics::Surface *_brush; + + Common::List<int> _eligibleObjects; + + int _cursor; + Common::Rect _rectangle; + + bool _mouseDown; + + bool eligeblity(int itemId); + Common::Rect paint(const Common::Point &point); + +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/scripting/controls/push_toggle_control.cpp b/engines/zvision/scripting/controls/push_toggle_control.cpp index 82736b7576..f51a28d644 100644 --- a/engines/zvision/scripting/controls/push_toggle_control.cpp +++ b/engines/zvision/scripting/controls/push_toggle_control.cpp @@ -26,73 +26,122 @@ #include "zvision/zvision.h" #include "zvision/scripting/script_manager.h" -#include "zvision/cursors/cursor_manager.h" -#include "zvision/utility/utility.h" +#include "zvision/graphics/cursors/cursor_manager.h" #include "common/stream.h" - namespace ZVision { PushToggleControl::PushToggleControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream) - : Control(engine, key) { + : Control(engine, key, CONTROL_PUSHTGL), + _countTo(2), + _cursor(CursorIndex_Active), + _event(Common::EVENT_LBUTTONUP) { + + _hotspots.clear(); + // Loop until we find the closing brace Common::String line = stream.readLine(); - trimCommentsAndWhiteSpace(&line); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + Common::String param; + Common::String values; + getParams(line, param, values); while (!stream.eos() && !line.contains('}')) { - if (line.matchString("*_hotspot*", true)) { + if (param.matchString("*_hotspot", true)) { uint x; uint y; uint width; uint height; - sscanf(line.c_str(), "%*[^(](%u,%u,%u,%u)", &x, &y, &width, &height); - - _hotspot = Common::Rect(x, y, x + width, y + height); - } else if (line.matchString("cursor*", true)) { - char nameBuffer[25]; - - sscanf(line.c_str(), "%*[^(](%25[^)])", nameBuffer); - - _hoverCursor = Common::String(nameBuffer); + sscanf(values.c_str(), "%u,%u,%u,%u", &x, &y, &width, &height); + + _hotspots.push_back(Common::Rect(x, y, x + width + 1, y + height + 1)); + } else if (param.matchString("cursor", true)) { + _cursor = _engine->getCursorManager()->getCursorId(values); + } else if (param.matchString("animation", true)) { + // Not used + } else if (param.matchString("sound", true)) { + // Not used + } else if (param.matchString("count_to", true)) { + sscanf(values.c_str(), "%u", &_countTo); + } else if (param.matchString("mouse_event", true)) { + if (values.equalsIgnoreCase("up")) { + _event = Common::EVENT_LBUTTONUP; + } else if (values.equalsIgnoreCase("down")) { + _event = Common::EVENT_LBUTTONDOWN; + } else if (values.equalsIgnoreCase("double")) { + // Not used + } + } else if (param.matchString("venus_id", true)) { + _venusId = atoi(values.c_str()); } line = stream.readLine(); - trimCommentsAndWhiteSpace(&line); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + getParams(line, param, values); } - if (_hotspot.isEmpty() || _hoverCursor.empty()) { - warning("Push_toggle cursor %u was parsed incorrectly", key); + if (_hotspots.size() == 0) { + warning("Push_toggle %u was parsed incorrectly", key); } } PushToggleControl::~PushToggleControl() { - // Clear the state value back to 0 - _engine->getScriptManager()->setStateValue(_key, 0); + _hotspots.clear(); } -void PushToggleControl::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { - if (!_enabled) { - return; +bool PushToggleControl::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + if (_event != Common::EVENT_LBUTTONUP) + return false; + + if (contain(backgroundImageSpacePos)) { + setVenus(); + int32 val = _engine->getScriptManager()->getStateValue(_key); + val = (val + 1) % _countTo; + _engine->getScriptManager()->setStateValue(_key, val); + return true; } - - if (_hotspot.contains(backgroundImageSpacePos)) { - _engine->getScriptManager()->setStateValue(_key, 1); + return false; +} + +bool PushToggleControl::onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + if (_event != Common::EVENT_LBUTTONDOWN) + return false; + + if (contain(backgroundImageSpacePos)) { + setVenus(); + int32 val = _engine->getScriptManager()->getStateValue(_key); + val = (val + 1) % _countTo; + _engine->getScriptManager()->setStateValue(_key, val); + return true; } + return false; } bool PushToggleControl::onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { - if (!_enabled) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) return false; - } - - if (_hotspot.contains(backgroundImageSpacePos)) { - _engine->getCursorManager()->changeCursor(_hoverCursor); + + if (contain(backgroundImageSpacePos)) { + _engine->getCursorManager()->changeCursor(_cursor); return true; } return false; } +bool PushToggleControl::contain(const Common::Point &point) { + for (uint i = 0; i < _hotspots.size(); i++) + if (_hotspots[i].contains(point)) + return true; + return false; +} + } // End of namespace ZVision diff --git a/engines/zvision/scripting/controls/push_toggle_control.h b/engines/zvision/scripting/controls/push_toggle_control.h index 3854fc2005..9444c77cb6 100644 --- a/engines/zvision/scripting/controls/push_toggle_control.h +++ b/engines/zvision/scripting/controls/push_toggle_control.h @@ -26,7 +26,8 @@ #include "zvision/scripting/control.h" #include "common/rect.h" - +#include "common/events.h" +#include "common/array.h" namespace ZVision { @@ -36,12 +37,19 @@ public: ~PushToggleControl(); /** + * Called when LeftMouse is pushed. Default is NOP. + * + * @param screenSpacePos The position of the mouse in screen space + * @param backgroundImageSpacePos The position of the mouse in background image space + */ + bool onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + /** * Called when LeftMouse is lifted. Calls ScriptManager::setStateValue(_key, 1); * * @param screenSpacePos The position of the mouse in screen space * @param backgroundImageSpacePos The position of the mouse in background image space */ - void onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + bool onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); /** * Called on every MouseMove. Tests if the mouse is inside _hotspot, and if so, sets the cursor. * @@ -57,9 +65,15 @@ private: * The area that will trigger the event * This is in image space coordinates, NOT screen space */ - Common::Rect _hotspot; + Common::Array<Common::Rect> _hotspots; /** The cursor to use when hovering over _hotspot */ - Common::String _hoverCursor; + int _cursor; + /** Button maximal values count */ + uint _countTo; + + Common::EventType _event; + + bool contain(const Common::Point &point); }; } // End of namespace ZVision diff --git a/engines/zvision/scripting/controls/safe_control.cpp b/engines/zvision/scripting/controls/safe_control.cpp new file mode 100644 index 0000000000..4d2a91a1ad --- /dev/null +++ b/engines/zvision/scripting/controls/safe_control.cpp @@ -0,0 +1,180 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/scummsys.h" + +#include "zvision/scripting/controls/safe_control.h" + +#include "zvision/zvision.h" +#include "zvision/scripting/script_manager.h" +#include "zvision/graphics/render_manager.h" +#include "zvision/graphics/cursors/cursor_manager.h" + +#include "common/stream.h" +#include "common/file.h" +#include "common/tokenizer.h" +#include "common/system.h" +#include "graphics/surface.h" +#include "video/video_decoder.h" + +namespace ZVision { + +SafeControl::SafeControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream) + : Control(engine, key, CONTROL_SAFE) { + _statesCount = 0; + _curState = 0; + _animation = NULL; + _innerRaduis = 0; + _innerRadiusSqr = 0; + _outerRadius = 0; + _outerRadiusSqr = 0; + _zeroPointer = 0; + _startPointer = 0; + _targetFrame = 0; + + // Loop until we find the closing brace + Common::String line = stream.readLine(); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + Common::String param; + Common::String values; + getParams(line, param, values); + + while (!stream.eos() && !line.contains('}')) { + if (param.matchString("animation", true)) { + _animation = _engine->loadAnimation(values); + _animation->start(); + } else if (param.matchString("rectangle", true)) { + int x; + int y; + int width; + int height; + + sscanf(values.c_str(), "%d %d %d %d", &x, &y, &width, &height); + + _rectangle = Common::Rect(x, y, width, height); + } else if (param.matchString("num_states", true)) { + _statesCount = atoi(values.c_str()); + } else if (param.matchString("center", true)) { + int x; + int y; + + sscanf(values.c_str(), "%d %d", &x, &y); + _center = Common::Point(x, y); + } else if (param.matchString("dial_inner_radius", true)) { + _innerRaduis = atoi(values.c_str()); + _innerRadiusSqr = _innerRaduis * _innerRaduis; + } else if (param.matchString("radius", true)) { + _outerRadius = atoi(values.c_str()); + _outerRadiusSqr = _outerRadius * _outerRadius; + } else if (param.matchString("zero_radians_offset", true)) { + _zeroPointer = atoi(values.c_str()); + } else if (param.matchString("pointer_offset", true)) { + _startPointer = atoi(values.c_str()); + _curState = _startPointer; + } else if (param.matchString("cursor", true)) { + // Not used + } else if (param.matchString("mirrored", true)) { + // Not used + } else if (param.matchString("venus_id", true)) { + _venusId = atoi(values.c_str()); + } + + line = stream.readLine(); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + getParams(line, param, values); + } + + if (_animation) + _animation->seekToFrame(_curState); +} + +SafeControl::~SafeControl() { + if (_animation) + delete _animation; + +} + +bool SafeControl::process(uint32 deltaTimeInMillis) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + if (_animation && _animation->getCurFrame() != _targetFrame && _animation->needsUpdate()) { + // If we're past the target frame, move back one + if (_animation->getCurFrame() > _targetFrame) + _animation->seekToFrame(_animation->getCurFrame() - 1); + + const Graphics::Surface *frameData = _animation->decodeNextFrame(); + if (_animation->getCurFrame() == _targetFrame) + _engine->getScriptManager()->setStateValue(_key, _curState); + if (frameData) + _engine->getRenderManager()->blitSurfaceToBkg(*frameData, _rectangle.left, _rectangle.top); + } + + return false; +} + +bool SafeControl::onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + if (_rectangle.contains(backgroundImageSpacePos)) { + int32 mR = backgroundImageSpacePos.sqrDist(_center); + if (mR <= _outerRadiusSqr && mR >= _innerRadiusSqr) { + _engine->getCursorManager()->changeCursor(CursorIndex_Active); + return true; + } + } + return false; +} + +bool SafeControl::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + if (_rectangle.contains(backgroundImageSpacePos)) { + int32 mR = backgroundImageSpacePos.sqrDist(_center); + if (mR <= _outerRadiusSqr && mR >= _innerRadiusSqr) { + setVenus(); + + Common::Point tmp = backgroundImageSpacePos - _center; + + float dd = atan2((float)tmp.x, (float)tmp.y) * 57.29578; + + int16 dp_state = 360 / _statesCount; + + int16 m_state = (_statesCount - ((((int16)dd + 540) % 360) / dp_state)) % _statesCount; + + int16 tmp2 = (m_state + _curState - _zeroPointer + _statesCount - 1) % _statesCount; + + if (_animation) + _animation->seekToFrame((_curState + _statesCount - _startPointer) % _statesCount); + + _curState = (_statesCount * 2 + tmp2) % _statesCount; + + _targetFrame = (_curState + _statesCount - _startPointer) % _statesCount; + return true; + } + } + return false; +} + +} // End of namespace ZVision diff --git a/engines/zvision/scripting/controls/safe_control.h b/engines/zvision/scripting/controls/safe_control.h new file mode 100644 index 0000000000..3e8c17635c --- /dev/null +++ b/engines/zvision/scripting/controls/safe_control.h @@ -0,0 +1,65 @@ +/* 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 ZVISION_SAFE_CONTROL_H +#define ZVISION_SAFE_CONTROL_H + +#include "zvision/scripting/control.h" + +#include "common/list.h" +#include "common/rect.h" + +namespace Video { + class VideoDecoder; +} + +namespace ZVision { + +// Only used in Zork Nemesis, handles the safe in the Asylum (ac4g) +class SafeControl : public Control { +public: + SafeControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream); + ~SafeControl(); + +private: + int16 _statesCount; + int16 _curState; + Video::VideoDecoder *_animation; + Common::Point _center; + Common::Rect _rectangle; + int16 _innerRaduis; + int32 _innerRadiusSqr; + int16 _outerRadius; + int32 _outerRadiusSqr; + int16 _zeroPointer; + int16 _startPointer; + int16 _targetFrame; + +public: + bool onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + bool onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + bool process(uint32 deltaTimeInMillis); +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/scripting/controls/save_control.cpp b/engines/zvision/scripting/controls/save_control.cpp new file mode 100644 index 0000000000..2ac77c4776 --- /dev/null +++ b/engines/zvision/scripting/controls/save_control.cpp @@ -0,0 +1,123 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/scummsys.h" + +#include "zvision/scripting/controls/input_control.h" +#include "zvision/scripting/controls/save_control.h" + +#include "zvision/zvision.h" +#include "zvision/scripting/script_manager.h" +#include "zvision/text/string_manager.h" + +#include "zvision/file/save_manager.h" +#include "zvision/graphics/render_manager.h" + +#include "common/str.h" +#include "common/stream.h" + +namespace ZVision { + +SaveControl::SaveControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream) + : Control(engine, key, CONTROL_SAVE), + _saveControl(false) { + // Loop until we find the closing brace + Common::String line = stream.readLine(); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + Common::String param; + Common::String values; + getParams(line, param, values); + + while (!stream.eos() && !line.contains('}')) { + if (param.matchString("savebox", true)) { + int saveId; + int inputId; + + sscanf(values.c_str(), "%d %d", &saveId, &inputId); + saveElement elmnt; + elmnt.inputKey = inputId; + elmnt.saveId = saveId; + elmnt.exist = false; + _inputs.push_back(elmnt); + } else if (param.matchString("control_type", true)) { + if (values.contains("save")) + _saveControl = true; + else + _saveControl = false; + } + + line = stream.readLine(); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + getParams(line, param, values); + } + + for (saveElmntList::iterator iter = _inputs.begin(); iter != _inputs.end(); ++iter) { + Control *ctrl = _engine->getScriptManager()->getControl(iter->inputKey); + if (ctrl && ctrl->getType() == Control::CONTROL_INPUT) { + InputControl *inp = (InputControl *)ctrl; + inp->setReadOnly(!_saveControl); + Common::SeekableReadStream *save = _engine->getSaveManager()->getSlotFile(iter->saveId); + if (save) { + SaveGameHeader header; + if (_engine->getSaveManager()->readSaveGameHeader(save, header)) { + inp->setText(header.saveName); + iter->exist = true; + } + delete save; + } + } + } +} + +bool SaveControl::process(uint32 deltaTimeInMillis) { + for (saveElmntList::iterator iter = _inputs.begin(); iter != _inputs.end(); ++iter) { + Control *ctrl = _engine->getScriptManager()->getControl(iter->inputKey); + if (ctrl && ctrl->getType() == Control::CONTROL_INPUT) { + InputControl *inp = (InputControl *)ctrl; + if (inp->enterPress()) { + if (_saveControl) { + if (inp->getText().size() > 0) { + bool toSave = true; + if (iter->exist) + if (!_engine->getRenderManager()->askQuestion(_engine->getStringManager()->getTextLine(StringManager::ZVISION_STR_SAVEEXIST))) + toSave = false; + + if (toSave) { + _engine->getSaveManager()->saveGame(iter->saveId, inp->getText(), true); + _engine->getRenderManager()->delayedMessage(_engine->getStringManager()->getTextLine(StringManager::ZVISION_STR_SAVED), 2000); + _engine->getScriptManager()->changeLocation(_engine->getScriptManager()->getLastMenuLocation()); + } + } else { + _engine->getRenderManager()->timedMessage(_engine->getStringManager()->getTextLine(StringManager::ZVISION_STR_SAVEEMPTY), 2000); + } + } else { + _engine->getSaveManager()->loadGame(iter->saveId); + return true; + } + break; + } + } + } + return false; +} + +} // End of namespace ZVision diff --git a/engines/zvision/core/menu.h b/engines/zvision/scripting/controls/save_control.h index 3ab6d4c2ec..cdc50eb54d 100644 --- a/engines/zvision/core/menu.h +++ b/engines/zvision/scripting/controls/save_control.h @@ -20,9 +20,36 @@ * */ -#ifndef ZVISION_MENU_H -#define ZVISION_MENU_H +#ifndef ZVISION_SAVE_CONTROL_H +#define ZVISION_SAVE_CONTROL_H -// TODO: Implement MenuHandler +#include "zvision/scripting/control.h" + +#include "common/list.h" + +namespace ZVision { + +class SaveControl : public Control { +public: + SaveControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream); + +private: + struct saveElement { + int saveId; + int inputKey; + bool exist; + }; + typedef Common::List<saveElement> saveElmntList; + saveElmntList _inputs; + + bool _saveControl; + +public: + + bool process(uint32 deltaTimeInMillis); + +}; + +} // End of namespace ZVision #endif diff --git a/engines/zvision/scripting/controls/slot_control.cpp b/engines/zvision/scripting/controls/slot_control.cpp new file mode 100644 index 0000000000..42b54a9ab5 --- /dev/null +++ b/engines/zvision/scripting/controls/slot_control.cpp @@ -0,0 +1,219 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/scummsys.h" + +#include "zvision/scripting/controls/slot_control.h" + +#include "zvision/zvision.h" +#include "zvision/scripting/script_manager.h" +#include "zvision/graphics/cursors/cursor_manager.h" +#include "zvision/graphics/render_manager.h" + +#include "common/stream.h" + +namespace ZVision { + +SlotControl::SlotControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream) + : Control(engine, key, CONTROL_SLOT), + _cursor(CursorIndex_Active), + _distanceId('0') { + + _renderedItem = 0; + _bkg = NULL; + + // Loop until we find the closing brace + Common::String line = stream.readLine(); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + Common::String param; + Common::String values; + getParams(line, param, values); + + while (!stream.eos() && !line.contains('}')) { + if (param.matchString("hotspot", true)) { + int x; + int y; + int width; + int height; + + sscanf(values.c_str(), "%d %d %d %d", &x, &y, &width, &height); + + _hotspot = Common::Rect(x, y, width, height); + } else if (param.matchString("rectangle", true)) { + int x; + int y; + int width; + int height; + + sscanf(values.c_str(), "%d %d %d %d", &x, &y, &width, &height); + + _rectangle = Common::Rect(x, y, width, height); + } else if (param.matchString("cursor", true)) { + _cursor = _engine->getCursorManager()->getCursorId(values); + } else if (param.matchString("distance_id", true)) { + sscanf(values.c_str(), "%c", &_distanceId); + } else if (param.matchString("venus_id", true)) { + _venusId = atoi(values.c_str()); + } else if (param.matchString("eligible_objects", true)) { + char buf[256]; + memset(buf, 0, 256); + strncpy(buf, values.c_str(), 255); + + char *curpos = buf; + char *strend = buf + strlen(buf); + while (true) { + char *st = curpos; + + if (st >= strend) + break; + + while (*curpos != ' ' && curpos < strend) + curpos++; + + *curpos = 0; + curpos++; + + int obj = atoi(st); + + _eligibleObjects.push_back(obj); + } + } + + line = stream.readLine(); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + getParams(line, param, values); + } + + if (_hotspot.isEmpty() || _rectangle.isEmpty()) { + warning("Slot %u was parsed incorrectly", key); + } +} + +SlotControl::~SlotControl() { + // Clear the state value back to 0 + //_engine->getScriptManager()->setStateValue(_key, 0); + + if (_bkg) + delete _bkg; +} + +bool SlotControl::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + if (_hotspot.contains(backgroundImageSpacePos)) { + setVenus(); + + int item = _engine->getScriptManager()->getStateValue(_key); + int mouseItem = _engine->getScriptManager()->getStateValue(StateKey_InventoryItem); + if (item != 0) { + if (mouseItem != 0) { + if (eligeblity(mouseItem)) { + _engine->getScriptManager()->inventoryDrop(mouseItem); + _engine->getScriptManager()->inventoryAdd(item); + _engine->getScriptManager()->setStateValue(_key, mouseItem); + } + } else { + _engine->getScriptManager()->inventoryAdd(item); + _engine->getScriptManager()->setStateValue(_key, 0); + } + } else if (mouseItem == 0) { + if (eligeblity(0)) { + _engine->getScriptManager()->inventoryDrop(0); + _engine->getScriptManager()->setStateValue(_key, 0); + } + } else if (eligeblity(mouseItem)) { + _engine->getScriptManager()->setStateValue(_key, mouseItem); + _engine->getScriptManager()->inventoryDrop(mouseItem); + } + } + return false; +} + +bool SlotControl::onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + if (_hotspot.contains(backgroundImageSpacePos)) { + _engine->getCursorManager()->changeCursor(_cursor); + return true; + } + + return false; +} + +bool SlotControl::process(uint32 deltaTimeInMillis) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + if (_engine->canRender()) { + int curItem = _engine->getScriptManager()->getStateValue(_key); + if (curItem != _renderedItem) { + if (_renderedItem != 0 && curItem == 0) { + _engine->getRenderManager()->blitSurfaceToBkg(*_bkg, _rectangle.left, _rectangle.top); + _renderedItem = curItem; + } else { + if (_renderedItem == 0) { + if (_bkg) + delete _bkg; + + _bkg = _engine->getRenderManager()->getBkgRect(_rectangle); + } else { + _engine->getRenderManager()->blitSurfaceToBkg(*_bkg, _rectangle.left, _rectangle.top); + } + + char buf[16]; + if (_engine->getGameId() == GID_NEMESIS) + sprintf(buf, "%d%cobj.tga", curItem, _distanceId); + else + sprintf(buf, "g0z%cu%2.2x1.tga", _distanceId, curItem); + + Graphics::Surface *srf = _engine->getRenderManager()->loadImage(buf); + + int16 drawx = _rectangle.left; + int16 drawy = _rectangle.top; + + if (_rectangle.width() > srf->w) + drawx = _rectangle.left + (_rectangle.width() - srf->w) / 2; + + if (_rectangle.height() > srf->h) + drawy = _rectangle.top + (_rectangle.height() - srf->h) / 2; + + _engine->getRenderManager()->blitSurfaceToBkg(*srf, drawx, drawy, 0); + + delete srf; + + _renderedItem = curItem; + } + } + } + return false; +} + +bool SlotControl::eligeblity(int itemId) { + for (Common::List<int>::iterator it = _eligibleObjects.begin(); it != _eligibleObjects.end(); it++) + if (*it == itemId) + return true; + return false; +} + +} // End of namespace ZVision diff --git a/engines/zvision/scripting/controls/slot_control.h b/engines/zvision/scripting/controls/slot_control.h new file mode 100644 index 0000000000..e776d99311 --- /dev/null +++ b/engines/zvision/scripting/controls/slot_control.h @@ -0,0 +1,84 @@ +/* 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 ZVISION_SLOT_CONTROL_H +#define ZVISION_SLOT_CONTROL_H + +#include "zvision/scripting/control.h" + +#include "graphics/surface.h" + +#include "common/rect.h" +#include "common/list.h" + +namespace ZVision { + +class SlotControl : public Control { +public: + SlotControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream); + ~SlotControl(); + + /** + * Called when LeftMouse is lifted. Calls ScriptManager::setStateValue(_key, 1); + * + * @param screenSpacePos The position of the mouse in screen space + * @param backgroundImageSpacePos The position of the mouse in background image space + */ + bool onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + /** + * Called on every MouseMove. Tests if the mouse is inside _hotspot, and if so, sets the cursor. + * + * @param engine The base engine + * @param screenSpacePos The position of the mouse in screen space + * @param backgroundImageSpacePos The position of the mouse in background image space + * @return Was the cursor changed? + */ + bool onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + + bool process(uint32 deltaTimeInMillis); + +private: + /** + * The area that will trigger the event + * This is in image space coordinates, NOT screen space + */ + Common::Rect _rectangle; + Common::Rect _hotspot; + + int _cursor; + char _distanceId; + + int _renderedItem; + + Common::List<int> _eligibleObjects; + + bool eligeblity(int itemId); + + Graphics::Surface *_bkg; + + /** The cursor to use when hovering over _hotspot */ + Common::String _hoverCursor; +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/scripting/controls/titler_control.cpp b/engines/zvision/scripting/controls/titler_control.cpp new file mode 100644 index 0000000000..683d6660af --- /dev/null +++ b/engines/zvision/scripting/controls/titler_control.cpp @@ -0,0 +1,108 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/scummsys.h" + +#include "zvision/scripting/controls/titler_control.h" + +#include "zvision/zvision.h" +#include "zvision/text/text.h" +#include "zvision/scripting/script_manager.h" +#include "zvision/graphics/render_manager.h" + +#include "common/stream.h" + +namespace ZVision { + +TitlerControl::TitlerControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream) + : Control(engine, key, CONTROL_TITLER) { + + _surface = NULL; + _curString = -1; + + // Loop until we find the closing brace + Common::String line = stream.readLine(); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + Common::String param; + Common::String values; + getParams(line, param, values); + + while (!stream.eos() && !line.contains('}')) { + if (param.matchString("string_resource_file", true)) { + readStringsFile(values); + } else if (param.matchString("rectangle", true)) { + int x; + int y; + int x2; + int y2; + + sscanf(values.c_str(), "%d %d %d %d", &x, &y, &x2, &y2); + + _rectangle = Common::Rect(x, y, x2, y2); + } + + line = stream.readLine(); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + getParams(line, param, values); + } + + if (!_rectangle.isEmpty()) { + _surface = new Graphics::Surface; + _surface->create(_rectangle.width(), _rectangle.height(), _engine->_resourcePixelFormat); + _surface->fillRect(Common::Rect(_surface->w, _surface->h), 0); + } +} + +TitlerControl::~TitlerControl() { + if (_surface) { + _surface->free(); + delete _surface; + } +} + +void TitlerControl::setString(int strLine) { + if (strLine != _curString && strLine >= 0 && strLine < (int)_strings.size()) { + _surface->fillRect(Common::Rect(_surface->w, _surface->h), 0); + _engine->getTextRenderer()->drawTextWithWordWrapping(_strings[strLine], *_surface); + _engine->getRenderManager()->blitSurfaceToBkg(*_surface, _rectangle.left, _rectangle.top); + _curString = strLine; + } +} + +void TitlerControl::readStringsFile(const Common::String &fileName) { + Common::File file; + if (!_engine->getSearchManager()->openFile(file, fileName)) { + warning("String_resource_file %s could could be opened", fileName.c_str()); + return; + } + + _strings.clear(); + + while (!file.eos()) { + + Common::String line = readWideLine(file); + _strings.push_back(line); + } + file.close(); +} + +} // End of namespace ZVision diff --git a/engines/zvision/scripting/controls/titler_control.h b/engines/zvision/scripting/controls/titler_control.h new file mode 100644 index 0000000000..dd96e4a846 --- /dev/null +++ b/engines/zvision/scripting/controls/titler_control.h @@ -0,0 +1,55 @@ +/* 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 ZVISION_TITLER_CONTROL_H +#define ZVISION_TITLER_CONTROL_H + +#include "zvision/scripting/control.h" + +#include "graphics/surface.h" + +#include "common/rect.h" +#include "common/array.h" + +namespace ZVision { + +// Only used in Zork Nemesis, handles the death screen with the Restore/Exit buttons +class TitlerControl : public Control { +public: + TitlerControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream); + ~TitlerControl(); + + void setString(int strLine); + +private: + + Common::Array< Common::String > _strings; + Common::Rect _rectangle; + int16 _curString; + Graphics::Surface *_surface; + + void readStringsFile(const Common::String &fileName); +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/scripting/effects/animation_effect.cpp b/engines/zvision/scripting/effects/animation_effect.cpp new file mode 100644 index 0000000000..511a0db353 --- /dev/null +++ b/engines/zvision/scripting/effects/animation_effect.cpp @@ -0,0 +1,217 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/scummsys.h" + +#include "zvision/scripting/effects/animation_effect.h" + +#include "zvision/zvision.h" +#include "zvision/graphics/render_manager.h" +#include "zvision/scripting/script_manager.h" + +#include "graphics/surface.h" +#include "video/video_decoder.h" + +namespace ZVision { + +AnimationEffect::AnimationEffect(ZVision *engine, uint32 controlKey, const Common::String &fileName, int32 mask, int32 frate, bool disposeAfterUse) + : ScriptingEffect(engine, controlKey, SCRIPTING_EFFECT_ANIM), + _disposeAfterUse(disposeAfterUse), + _mask(mask), + _animation(NULL) { + + _animation = engine->loadAnimation(fileName); + + if (frate > 0) { + _frmDelayOverride = (int32)(1000.0 / frate); + + // WORKAROUND: We do not allow the engine to delay more than 66 msec + // per frame (15fps max) + if (_frmDelayOverride > 66) + _frmDelayOverride = 66; + } else { + _frmDelayOverride = 0; + } +} + +AnimationEffect::~AnimationEffect() { + if (_animation) + delete _animation; + + _engine->getScriptManager()->setStateValue(_key, 2); + + PlayNodes::iterator it = _playList.begin(); + if (it != _playList.end()) { + _engine->getScriptManager()->setStateValue((*it).slot, 2); + + if ((*it)._scaled) { + (*it)._scaled->free(); + delete(*it)._scaled; + } + } + + _playList.clear(); +} + +bool AnimationEffect::process(uint32 deltaTimeInMillis) { + ScriptManager *scriptManager = _engine->getScriptManager(); + RenderManager *renderManager = _engine->getRenderManager(); + RenderTable::RenderState renderState = renderManager->getRenderTable()->getRenderState(); + bool isPanorama = (renderState == RenderTable::PANORAMA); + int16 velocity = _engine->getMouseVelocity() + _engine->getKeyboardVelocity(); + + // Do not update animation nodes in panoramic mode while turning, if the user + // has set this option + if (scriptManager->getStateValue(StateKey_NoTurnAnim) == 1 && isPanorama && velocity) + return false; + + PlayNodes::iterator it = _playList.begin(); + if (it != _playList.end()) { + playnode *nod = &(*it); + + if (nod->_curFrame == -1) { + // The node is just beginning playback + nod->_curFrame = nod->start; + + _animation->start(); + _animation->seekToFrame(nod->start); + _animation->setEndFrame(nod->stop); + + nod->_delay = deltaTimeInMillis; // Force the frame to draw + if (nod->slot) + scriptManager->setStateValue(nod->slot, 1); + } else if (_animation->endOfVideo()) { + // The node has reached the end; check if we need to loop + nod->loop--; + + if (nod->loop == 0) { + if (nod->slot >= 0) + scriptManager->setStateValue(nod->slot, 2); + if (nod->_scaled) { + nod->_scaled->free(); + delete nod->_scaled; + } + _playList.erase(it); + return _disposeAfterUse; + } + + nod->_curFrame = nod->start; + _animation->seekToFrame(nod->start); + } + + // Check if we need to draw a frame + bool needsUpdate = false; + if (_frmDelayOverride == 0) { + // If not overridden, use the VideoDecoder's check + needsUpdate = _animation->needsUpdate(); + } else { + // Otherwise, implement our own timing + nod->_delay -= deltaTimeInMillis; + + if (nod->_delay <= 0) { + nod->_delay += _frmDelayOverride; + needsUpdate = true; + } + } + + if (needsUpdate) { + const Graphics::Surface *frame = _animation->decodeNextFrame(); + + if (frame) { + uint32 dstw; + uint32 dsth; + if (isPanorama) { + dstw = nod->pos.height(); + dsth = nod->pos.width(); + } else { + dstw = nod->pos.width(); + dsth = nod->pos.height(); + } + + // We only scale down the animation to fit its frame, not up, otherwise we + // end up with distorted animations - e.g. the armor visor in location cz1e + // in Nemesis (one of the armors inside Irondune), or the planet in location + // aa10 in Nemesis (Juperon, outside the asylum). We do allow scaling up only + // when a simple 2x filter is requested (e.g. the alchemists and cup sequence + // in Nemesis) + if (frame->w > dstw || frame->h > dsth || (frame->w == dstw / 2 && frame->h == dsth / 2)) { + if (nod->_scaled) + if (nod->_scaled->w != dstw || nod->_scaled->h != dsth) { + nod->_scaled->free(); + delete nod->_scaled; + nod->_scaled = NULL; + } + + if (!nod->_scaled) { + nod->_scaled = new Graphics::Surface; + nod->_scaled->create(dstw, dsth, frame->format); + } + + renderManager->scaleBuffer(frame->getPixels(), nod->_scaled->getPixels(), frame->w, frame->h, frame->format.bytesPerPixel, dstw, dsth); + frame = nod->_scaled; + } + + if (isPanorama) { + Graphics::Surface *transposed = RenderManager::tranposeSurface(frame); + renderManager->blitSurfaceToBkg(*transposed, nod->pos.left, nod->pos.top, _mask); + transposed->free(); + delete transposed; + } else { + renderManager->blitSurfaceToBkg(*frame, nod->pos.left, nod->pos.top, _mask); + } + } + } + } + + return false; +} + +void AnimationEffect::addPlayNode(int32 slot, int x, int y, int x2, int y2, int startFrame, int endFrame, int loops) { + playnode nod; + nod.loop = loops; + nod.pos = Common::Rect(x, y, x2 + 1, y2 + 1); + nod.start = startFrame; + nod.stop = CLIP<int>(endFrame, 0, _animation->getFrameCount() - 1); + nod.slot = slot; + nod._curFrame = -1; + nod._delay = 0; + nod._scaled = NULL; + _playList.push_back(nod); +} + +bool AnimationEffect::stop() { + PlayNodes::iterator it = _playList.begin(); + if (it != _playList.end()) { + _engine->getScriptManager()->setStateValue((*it).slot, 2); + if ((*it)._scaled) { + (*it)._scaled->free(); + delete(*it)._scaled; + } + } + + _playList.clear(); + + // We don't need to delete, it's may be reused + return false; +} + +} // End of namespace ZVision diff --git a/engines/zvision/scripting/controls/animation_control.h b/engines/zvision/scripting/effects/animation_effect.h index 6c4d6dfcf7..fd6e24ab8b 100644 --- a/engines/zvision/scripting/controls/animation_control.h +++ b/engines/zvision/scripting/effects/animation_effect.h @@ -8,78 +8,70 @@ * 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 ZVISION_ANIMATION_CONTROL_H -#define ZVISION_ANIMATION_CONTROL_H +#ifndef ZVISION_ANIMATION_NODE_H +#define ZVISION_ANIMATION_NODE_H -#include "zvision/scripting/control.h" +#include "zvision/scripting/scripting_effect.h" +#include "common/rect.h" +#include "common/list.h" - -namespace Common { -class String; +namespace Graphics { +struct Surface; } namespace Video { -class VideoDecoder; -} - -namespace Graphics { -struct Surface; + class VideoDecoder; } namespace ZVision { class ZVision; -class RlfAnimation; -class AnimationControl : public Control { +class AnimationEffect : public ScriptingEffect { public: - AnimationControl(ZVision *engine, uint32 controlKey, const Common::String &fileName); - ~AnimationControl(); - -private: - enum FileType { - RLF = 1, - AVI = 2 + AnimationEffect(ZVision *engine, uint32 controlKey, const Common::String &fileName, int32 mask, int32 frate, bool disposeAfterUse = true); + ~AnimationEffect(); + + struct playnode { + Common::Rect pos; + int32 slot; + int32 start; + int32 stop; + int32 loop; + int32 _curFrame; + int32 _delay; + Graphics::Surface *_scaled; }; private: - uint32 _animationKey; - - union { - RlfAnimation *rlf; - Video::VideoDecoder *avi; - } _animation; + typedef Common::List<playnode> PlayNodes; - FileType _fileType; - uint _loopCount; - int32 _x; - int32 _y; + PlayNodes _playList; - uint _accumulatedTime; - uint _currentLoop; + int32 _mask; + bool _disposeAfterUse; - Graphics::Surface *_cachedFrame; - bool _cachedFrameNeedsDeletion; + Video::VideoDecoder *_animation; + int32 _frmDelayOverride; public: bool process(uint32 deltaTimeInMillis); - void setAnimationKey(uint32 animationKey) { _animationKey = animationKey; } - void setLoopCount(uint loopCount) { _loopCount = loopCount; } - void setXPos(int32 x) { _x = x; } - void setYPost(int32 y) { _y = y; } + void addPlayNode(int32 slot, int x, int y, int x2, int y2, int startFrame, int endFrame, int loops = 1); + + bool stop(); }; } // End of namespace ZVision diff --git a/engines/zvision/scripting/effects/distort_effect.cpp b/engines/zvision/scripting/effects/distort_effect.cpp new file mode 100644 index 0000000000..113b5d048d --- /dev/null +++ b/engines/zvision/scripting/effects/distort_effect.cpp @@ -0,0 +1,104 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/scummsys.h" + +#include "zvision/scripting/effects/distort_effect.h" + +#include "zvision/zvision.h" +#include "zvision/scripting/script_manager.h" +#include "zvision/graphics/render_manager.h" +#include "zvision/graphics/render_table.h" + +#include "common/stream.h" + +namespace ZVision { + +DistortNode::DistortNode(ZVision *engine, uint32 key, int16 speed, float startAngle, float endAngle, float startLineScale, float endLineScale) + : ScriptingEffect(engine, key, SCRIPTING_EFFECT_DISTORT) { + + _angle = _engine->getRenderManager()->getRenderTable()->getAngle(); + _linScale = _engine->getRenderManager()->getRenderTable()->getLinscale(); + + _speed = speed; + _incr = true; + _startAngle = startAngle; + _endAngle = endAngle; + _startLineScale = startLineScale; + _endLineScale = endLineScale; + + _curFrame = 1.0; + + _diffAngle = endAngle - startAngle; + _diffLinScale = endLineScale - startLineScale; + + _frmSpeed = (float)speed / 15.0; + _frames = (int)ceil((5.0 - _frmSpeed * 2.0) / _frmSpeed); + if (_frames <= 0) + _frames = 1; + + if (_key != StateKey_NotSet) + _engine->getScriptManager()->setStateValue(_key, 1); +} + +DistortNode::~DistortNode() { + setParams(_angle, _linScale); +} + +bool DistortNode::process(uint32 deltaTimeInMillis) { + float updTime = deltaTimeInMillis / (1000.0 / 60.0); + + if (_incr) + _curFrame += updTime; + else + _curFrame -= updTime; + + if (_curFrame < 1.0) { + _curFrame = 1.0; + _incr = true; + } else if (_curFrame > _frames) { + _curFrame = _frames; + _incr = false; + } + + float diff = (1.0 / (5.0 - (_curFrame * _frmSpeed))) / (5.0 - _frmSpeed); + setParams(_startAngle + diff * _diffAngle, _startLineScale + diff * _diffLinScale); + + return false; +} + +void DistortNode::setParams(float angl, float linScale) { + RenderTable *table = _engine->getRenderManager()->getRenderTable(); + if (table->getRenderState() == RenderTable::PANORAMA) { + table->setPanoramaFoV(angl); + table->setPanoramaScale(linScale); + table->generateRenderTable(); + _engine->getRenderManager()->markDirty(); + } else if (table->getRenderState() == RenderTable::TILT) { + table->setTiltFoV(angl); + table->setTiltScale(linScale); + table->generateRenderTable(); + _engine->getRenderManager()->markDirty(); + } +} + +} // End of namespace ZVision diff --git a/engines/zvision/scripting/effects/distort_effect.h b/engines/zvision/scripting/effects/distort_effect.h new file mode 100644 index 0000000000..c64f10e6ff --- /dev/null +++ b/engines/zvision/scripting/effects/distort_effect.h @@ -0,0 +1,63 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ZVISION_DISTORT_NODE_H +#define ZVISION_DISTORT_NODE_H + +#include "zvision/scripting/scripting_effect.h" + +namespace ZVision { + +class ZVision; + +class DistortNode : public ScriptingEffect { +public: + DistortNode(ZVision *engine, uint32 key, int16 speed, float startAngle, float endAngle, float startLineScale, float endLineScale); + ~DistortNode(); + + bool process(uint32 deltaTimeInMillis); + +private: + int16 _speed; + float _startAngle; + float _endAngle; + float _startLineScale; + float _endLineScale; + + float _frmSpeed; + float _diffAngle; + float _diffLinScale; + bool _incr; + int16 _frames; + + float _curFrame; + + float _angle; + float _linScale; + +private: + void setParams(float angl, float linScale); +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/scripting/effects/music_effect.cpp b/engines/zvision/scripting/effects/music_effect.cpp new file mode 100644 index 0000000000..e3fdc96dba --- /dev/null +++ b/engines/zvision/scripting/effects/music_effect.cpp @@ -0,0 +1,296 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/scummsys.h" + +#include "zvision/scripting/effects/music_effect.h" + +#include "zvision/zvision.h" +#include "zvision/scripting/script_manager.h" +#include "zvision/graphics/render_manager.h" +#include "zvision/sound/midi.h" +#include "zvision/sound/zork_raw.h" + +#include "common/stream.h" +#include "common/file.h" +#include "audio/decoders/wave.h" + +namespace ZVision { + +static const uint8 dbMapLinear[256] = +{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, +2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, +4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, +8, 8, 8, 9, 9, 9, 10, 10, 11, 11, 11, 12, 12, 13, 13, 14, +14, 15, 15, 16, 16, 17, 18, 18, 19, 20, 21, 21, 22, 23, 24, 25, +26, 27, 28, 29, 30, 31, 32, 33, 34, 36, 37, 38, 40, 41, 43, 45, +46, 48, 50, 52, 53, 55, 57, 60, 62, 64, 67, 69, 72, 74, 77, 80, +83, 86, 89, 92, 96, 99, 103, 107, 111, 115, 119, 123, 128, 133, 137, 143, +148, 153, 159, 165, 171, 177, 184, 191, 198, 205, 212, 220, 228, 237, 245, 255}; + +MusicNode::MusicNode(ZVision *engine, uint32 key, Common::String &filename, bool loop, uint8 volume) + : MusicNodeBASE(engine, key, SCRIPTING_EFFECT_AUDIO) { + _loop = loop; + _volume = volume; + _deltaVolume = 0; + _balance = 0; + _crossfade = false; + _crossfadeTarget = 0; + _crossfadeTime = 0; + _sub = NULL; + _stereo = false; + _loaded = false; + + Audio::RewindableAudioStream *audioStream = NULL; + + if (filename.contains(".wav")) { + Common::File *file = new Common::File(); + if (_engine->getSearchManager()->openFile(*file, filename)) { + audioStream = Audio::makeWAVStream(file, DisposeAfterUse::YES); + } + } else { + audioStream = makeRawZorkStream(filename, _engine); + } + + if (audioStream) { + _stereo = audioStream->isStereo(); + + if (_loop) { + Audio::LoopingAudioStream *loopingAudioStream = new Audio::LoopingAudioStream(audioStream, 0, DisposeAfterUse::YES); + _engine->_mixer->playStream(Audio::Mixer::kPlainSoundType, &_handle, loopingAudioStream, -1, dbMapLinear[_volume]); + } else { + _engine->_mixer->playStream(Audio::Mixer::kPlainSoundType, &_handle, audioStream, -1, dbMapLinear[_volume]); + } + + if (_key != StateKey_NotSet) + _engine->getScriptManager()->setStateValue(_key, 1); + + // Change filename.raw into filename.sub + Common::String subname = filename; + subname.setChar('s', subname.size() - 3); + subname.setChar('u', subname.size() - 2); + subname.setChar('b', subname.size() - 1); + + if (_engine->getSearchManager()->hasFile(subname)) + _sub = new Subtitle(_engine, subname); + + _loaded = true; + } +} + +MusicNode::~MusicNode() { + if (_loaded) + _engine->_mixer->stopHandle(_handle); + if (_key != StateKey_NotSet) + _engine->getScriptManager()->setStateValue(_key, 2); + if (_sub) + delete _sub; + debug(1, "MusicNode: %d destroyed", _key); +} + +void MusicNode::setDeltaVolume(uint8 volume) { + _deltaVolume = volume; + setVolume(_volume); +} + +void MusicNode::setBalance(int8 balance) { + _balance = balance; + _engine->_mixer->setChannelBalance(_handle, _balance); +} + +void MusicNode::setFade(int32 time, uint8 target) { + _crossfadeTarget = target; + _crossfadeTime = time; + _crossfade = true; +} + +bool MusicNode::process(uint32 deltaTimeInMillis) { + if (!_loaded || ! _engine->_mixer->isSoundHandleActive(_handle)) + return stop(); + else { + uint8 _newvol = _volume; + + if (_crossfade) { + if (_crossfadeTime > 0) { + if ((int32)deltaTimeInMillis > _crossfadeTime) + deltaTimeInMillis = _crossfadeTime; + _newvol += (int)(floor(((float)(_crossfadeTarget - _newvol) / (float)_crossfadeTime)) * (float)deltaTimeInMillis); + _crossfadeTime -= deltaTimeInMillis; + } else { + _crossfade = false; + _newvol = _crossfadeTarget; + } + } + + if (_volume != _newvol) + setVolume(_newvol); + + if (_sub && _engine->getScriptManager()->getStateValue(StateKey_Subtitles) == 1) + _sub->process(_engine->_mixer->getSoundElapsedTime(_handle) / 100); + } + return false; +} + +void MusicNode::setVolume(uint8 newVolume) { + if (!_loaded) + return; + + _volume = newVolume; + + if (_deltaVolume >= _volume) + _engine->_mixer->setChannelVolume(_handle, 0); + else + _engine->_mixer->setChannelVolume(_handle, dbMapLinear[_volume - _deltaVolume]); +} + +uint8 MusicNode::getVolume() { + return _volume; +} + +PanTrackNode::PanTrackNode(ZVision *engine, uint32 key, uint32 slot, int16 pos) + : ScriptingEffect(engine, key, SCRIPTING_EFFECT_PANTRACK) { + _slot = slot; + _position = pos; + + // Try to set pan value for music node immediately + process(0); +} + +PanTrackNode::~PanTrackNode() { +} + +bool PanTrackNode::process(uint32 deltaTimeInMillis) { + ScriptManager * scriptManager = _engine->getScriptManager(); + ScriptingEffect *fx = scriptManager->getSideFX(_slot); + if (fx && fx->getType() == SCRIPTING_EFFECT_AUDIO) { + MusicNodeBASE *mus = (MusicNodeBASE *)fx; + + int curPos = scriptManager->getStateValue(StateKey_ViewPos); + int16 _width = _engine->getRenderManager()->getBkgSize().x; + int16 _halfWidth = _width / 2; + int16 _quarterWidth = _width / 4; + + int tmp = 0; + if (curPos <= _position) + tmp = _position - curPos; + else + tmp = _position - curPos + _width; + + int balance = 0; + + if (tmp > _halfWidth) + tmp -= _width; + + if (tmp > _quarterWidth) { + balance = 1; + tmp = _halfWidth - tmp; + } else if (tmp < -_quarterWidth) { + balance = -1; + tmp = -_halfWidth - tmp; + } + + // Originally it's value -90...90 but we use -127...127 and therefore 360 replaced by 508 + mus->setBalance( (508 * tmp) / _width ); + + tmp = (360 * tmp) / _width; + + int deltaVol = balance; + + // This value sets how fast volume goes off than sound source back of you + // By this value we can hack some "bugs" have place in originall game engine like beat sound in ZGI-dc10 + int volumeCorrection = 2; + + if (_engine->getGameId() == GID_GRANDINQUISITOR) { + if (scriptManager->getCurrentLocation() == "dc10") + volumeCorrection = 5; + } + + if (deltaVol != 0) + deltaVol = (mus->getVolume() * volumeCorrection) * (90 - tmp * balance) / 90; + if (deltaVol > 255) + deltaVol = 255; + + mus->setDeltaVolume(deltaVol); + } + return false; +} + +MusicMidiNode::MusicMidiNode(ZVision *engine, uint32 key, int8 program, int8 note, int8 volume) + : MusicNodeBASE(engine, key, SCRIPTING_EFFECT_AUDIO) { + _volume = volume; + _prog = program; + _noteNumber = note; + _pan = 0; + + _chan = _engine->getMidiManager()->getFreeChannel(); + + if (_chan >= 0) { + _engine->getMidiManager()->setVolume(_chan, _volume); + _engine->getMidiManager()->setPan(_chan, _pan); + _engine->getMidiManager()->setProgram(_chan, _prog); + _engine->getMidiManager()->noteOn(_chan, _noteNumber, _volume); + } + + if (_key != StateKey_NotSet) + _engine->getScriptManager()->setStateValue(_key, 1); +} + +MusicMidiNode::~MusicMidiNode() { + if (_chan >= 0) { + _engine->getMidiManager()->noteOff(_chan); + } + if (_key != StateKey_NotSet) + _engine->getScriptManager()->setStateValue(_key, 2); +} + +void MusicMidiNode::setDeltaVolume(uint8 volume) { +} + +void MusicMidiNode::setBalance(int8 balance) { +} + +void MusicMidiNode::setFade(int32 time, uint8 target) { +} + +bool MusicMidiNode::process(uint32 deltaTimeInMillis) { + return false; +} + +void MusicMidiNode::setVolume(uint8 newVolume) { + if (_chan >= 0) { + _engine->getMidiManager()->setVolume(_chan, newVolume); + } + _volume = newVolume; +} + +uint8 MusicMidiNode::getVolume() { + return _volume; +} + +} // End of namespace ZVision diff --git a/engines/zvision/scripting/effects/music_effect.h b/engines/zvision/scripting/effects/music_effect.h new file mode 100644 index 0000000000..7657be8e09 --- /dev/null +++ b/engines/zvision/scripting/effects/music_effect.h @@ -0,0 +1,137 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ZVISION_MUSIC_NODE_H +#define ZVISION_MUSIC_NODE_H + +#include "audio/mixer.h" +#include "zvision/scripting/scripting_effect.h" +#include "zvision/text/subtitles.h" + +namespace Common { +class String; +} + +namespace ZVision { + +class MusicNodeBASE : public ScriptingEffect { +public: + MusicNodeBASE(ZVision *engine, uint32 key, ScriptingEffectType type) : ScriptingEffect(engine, key, type) {} + ~MusicNodeBASE() {} + + /** + * Decrement the timer by the delta time. If the timer is finished, set the status + * in _globalState and let this node be deleted + * + * @param deltaTimeInMillis The number of milliseconds that have passed since last frame + * @return If true, the node can be deleted after process() finishes + */ + virtual bool process(uint32 deltaTimeInMillis) = 0; + + virtual void setVolume(uint8 volume) = 0; + virtual uint8 getVolume() = 0; + virtual void setDeltaVolume(uint8 volume) = 0; + virtual void setBalance(int8 balance) = 0; + + virtual void setFade(int32 time, uint8 target) = 0; +}; + +class MusicNode : public MusicNodeBASE { +public: + MusicNode(ZVision *engine, uint32 key, Common::String &file, bool loop, uint8 volume); + ~MusicNode(); + + /** + * Decrement the timer by the delta time. If the timer is finished, set the status + * in _globalState and let this node be deleted + * + * @param deltaTimeInMillis The number of milliseconds that have passed since last frame + * @return If true, the node can be deleted after process() finishes + */ + bool process(uint32 deltaTimeInMillis); + + void setVolume(uint8 volume); + uint8 getVolume(); + void setDeltaVolume(uint8 volume); + void setBalance(int8 balance); + + void setFade(int32 time, uint8 target); + +private: + uint8 _volume; + uint8 _deltaVolume; + int8 _balance; + bool _loop; + bool _crossfade; + uint8 _crossfadeTarget; + int32 _crossfadeTime; + bool _stereo; + Audio::SoundHandle _handle; + Subtitle *_sub; + bool _loaded; +}; + +// Only used by Zork: Nemesis, for the flute and piano puzzles (tj4e and ve6f, as well as vr) +class MusicMidiNode : public MusicNodeBASE { +public: + MusicMidiNode(ZVision *engine, uint32 key, int8 program, int8 note, int8 volume); + ~MusicMidiNode(); + + /** + * Decrement the timer by the delta time. If the timer is finished, set the status + * in _globalState and let this node be deleted + * + * @param deltaTimeInMillis The number of milliseconds that have passed since last frame + * @return If true, the node can be deleted after process() finishes + */ + bool process(uint32 deltaTimeInMillis); + + void setVolume(uint8 volume); + uint8 getVolume(); + void setDeltaVolume(uint8 volume); + void setBalance(int8 balance); + + void setFade(int32 time, uint8 target); + +private: + int8 _chan; + int8 _noteNumber; + int8 _pan; + int8 _volume; + int8 _prog; +}; + +class PanTrackNode : public ScriptingEffect { +public: + PanTrackNode(ZVision *engine, uint32 key, uint32 slot, int16 pos); + ~PanTrackNode(); + + bool process(uint32 deltaTimeInMillis); + +private: + uint32 _slot; + int16 _position; +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/scripting/effects/region_effect.cpp b/engines/zvision/scripting/effects/region_effect.cpp new file mode 100644 index 0000000000..78061cf4de --- /dev/null +++ b/engines/zvision/scripting/effects/region_effect.cpp @@ -0,0 +1,56 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/scummsys.h" + +#include "zvision/scripting/effects/region_effect.h" + +#include "zvision/zvision.h" +#include "zvision/scripting/script_manager.h" +#include "zvision/graphics/render_manager.h" + +namespace ZVision { + +RegionNode::RegionNode(ZVision *engine, uint32 key, GraphicsEffect *effect, uint32 delay) + : ScriptingEffect(engine, key, SCRIPTING_EFFECT_REGION) { + _effect = effect; + _delay = delay; + _timeLeft = 0; +} + +RegionNode::~RegionNode() { + _engine->getRenderManager()->deleteEffect(_key); +} + +bool RegionNode::process(uint32 deltaTimeInMillis) { + _timeLeft -= deltaTimeInMillis; + + if (_timeLeft <= 0) { + _timeLeft = _delay; + if (_effect) + _effect->update(); + } + + return false; +} + +} // End of namespace ZVision diff --git a/engines/zvision/scripting/effects/region_effect.h b/engines/zvision/scripting/effects/region_effect.h new file mode 100644 index 0000000000..4fc16224ff --- /dev/null +++ b/engines/zvision/scripting/effects/region_effect.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 ZVISION_REGION_NODE_H +#define ZVISION_REGION_NODE_H + +#include "graphics/surface.h" + +#include "zvision/scripting/scripting_effect.h" +#include "zvision/graphics/graphics_effect.h" + +namespace ZVision { + +class ZVision; + +class RegionNode : public ScriptingEffect { +public: + RegionNode(ZVision *engine, uint32 key, GraphicsEffect *effect, uint32 delay); + ~RegionNode(); + + /** + * Decrement the timer by the delta time. If the timer is finished, set the status + * in _globalState and let this node be deleted + * + * @param deltaTimeInMillis The number of milliseconds that have passed since last frame + * @return If true, the node can be deleted after process() finishes + */ + bool process(uint32 deltaTimeInMillis); + +private: + int32 _timeLeft; + uint32 _delay; + GraphicsEffect *_effect; +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/scripting/effects/syncsound_effect.cpp b/engines/zvision/scripting/effects/syncsound_effect.cpp new file mode 100644 index 0000000000..70ba97deb8 --- /dev/null +++ b/engines/zvision/scripting/effects/syncsound_effect.cpp @@ -0,0 +1,85 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/scummsys.h" + +#include "zvision/scripting/effects/syncsound_effect.h" + +#include "zvision/zvision.h" +#include "zvision/scripting/script_manager.h" +#include "zvision/graphics/render_manager.h" +#include "zvision/sound/zork_raw.h" + +#include "common/stream.h" +#include "common/file.h" +#include "audio/decoders/wave.h" + +namespace ZVision { + +SyncSoundNode::SyncSoundNode(ZVision *engine, uint32 key, Common::String &filename, int32 syncto) + : ScriptingEffect(engine, key, SCRIPTING_EFFECT_AUDIO) { + _syncto = syncto; + _sub = NULL; + + Audio::RewindableAudioStream *audioStream = NULL; + + if (filename.contains(".wav")) { + Common::File *file = new Common::File(); + if (_engine->getSearchManager()->openFile(*file, filename)) { + audioStream = Audio::makeWAVStream(file, DisposeAfterUse::YES); + } + } else { + audioStream = makeRawZorkStream(filename, _engine); + } + + _engine->_mixer->playStream(Audio::Mixer::kPlainSoundType, &_handle, audioStream); + + Common::String subname = filename; + subname.setChar('s', subname.size() - 3); + subname.setChar('u', subname.size() - 2); + subname.setChar('b', subname.size() - 1); + + if (_engine->getSearchManager()->hasFile(subname)) + _sub = new Subtitle(_engine, subname); +} + +SyncSoundNode::~SyncSoundNode() { + _engine->_mixer->stopHandle(_handle); + if (_sub) + delete _sub; +} + +bool SyncSoundNode::process(uint32 deltaTimeInMillis) { + if (! _engine->_mixer->isSoundHandleActive(_handle)) + return stop(); + else { + + if (_engine->getScriptManager()->getSideFX(_syncto) == NULL) + return stop(); + + if (_sub && _engine->getScriptManager()->getStateValue(StateKey_Subtitles) == 1) + _sub->process(_engine->_mixer->getSoundElapsedTime(_handle) / 100); + } + return false; +} + +} // End of namespace ZVision diff --git a/engines/zvision/scripting/effects/syncsound_effect.h b/engines/zvision/scripting/effects/syncsound_effect.h new file mode 100644 index 0000000000..0eabff77a3 --- /dev/null +++ b/engines/zvision/scripting/effects/syncsound_effect.h @@ -0,0 +1,56 @@ +/* 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 ZVISION_SYNCSOUND_NODE_H +#define ZVISION_SYNCSOUND_NODE_H + +#include "audio/mixer.h" +#include "zvision/scripting/scripting_effect.h" +#include "zvision/text/subtitles.h" + +namespace Common { +class String; +} + +namespace ZVision { +class SyncSoundNode : public ScriptingEffect { +public: + SyncSoundNode(ZVision *engine, uint32 key, Common::String &file, int32 syncto); + ~SyncSoundNode(); + + /** + * Decrement the timer by the delta time. If the timer is finished, set the status + * in _globalState and let this node be deleted + * + * @param deltaTimeInMillis The number of milliseconds that have passed since last frame + * @return If true, the node can be deleted after process() finishes + */ + bool process(uint32 deltaTimeInMillis); +private: + int32 _syncto; + Audio::SoundHandle _handle; + Subtitle *_sub; +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/scripting/controls/timer_node.cpp b/engines/zvision/scripting/effects/timer_effect.cpp index c8c8a85d34..778f9dec6c 100644 --- a/engines/zvision/scripting/controls/timer_node.cpp +++ b/engines/zvision/scripting/effects/timer_effect.cpp @@ -8,12 +8,12 @@ * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. - * + * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. @@ -22,46 +22,54 @@ #include "common/scummsys.h" -#include "zvision/scripting/controls/timer_node.h" +#include "zvision/scripting/effects/timer_effect.h" #include "zvision/zvision.h" #include "zvision/scripting/script_manager.h" #include "common/stream.h" - namespace ZVision { - + TimerNode::TimerNode(ZVision *engine, uint32 key, uint timeInSeconds) - : Control(engine, key) { - if (_engine->getGameId() == GID_NEMESIS) { + : ScriptingEffect(engine, key, SCRIPTING_EFFECT_TIMER) { + _timeLeft = 0; + + if (_engine->getGameId() == GID_NEMESIS) _timeLeft = timeInSeconds * 1000; - } else if (_engine->getGameId() == GID_GRANDINQUISITOR) { + else if (_engine->getGameId() == GID_GRANDINQUISITOR) _timeLeft = timeInSeconds * 100; - } - _engine->getScriptManager()->setStateValue(_key, 1); + if (_key != StateKey_NotSet) + _engine->getScriptManager()->setStateValue(_key, 1); } TimerNode::~TimerNode() { - if (_timeLeft <= 0) + if (_key != StateKey_NotSet) _engine->getScriptManager()->setStateValue(_key, 2); - else - _engine->getScriptManager()->setStateValue(_key, _timeLeft); // If timer was stopped by stop or kill + int32 timeLeft = _timeLeft / (_engine->getGameId() == GID_NEMESIS ? 1000 : 100); + if (timeLeft > 0) + _engine->getScriptManager()->setStateValue(_key, timeLeft); // If timer was stopped by stop or kill } bool TimerNode::process(uint32 deltaTimeInMillis) { _timeLeft -= deltaTimeInMillis; - if (_timeLeft <= 0) { - // Let the destructor reset the state value - return true; - } + if (_timeLeft <= 0) + return stop(); return false; } +bool TimerNode::stop() { + if (_key != StateKey_NotSet) + _engine->getScriptManager()->setStateValue(_key, 2); + return true; +} + void TimerNode::serialize(Common::WriteStream *stream) { + stream->writeUint32BE(MKTAG('T', 'I', 'M', 'R')); + stream->writeUint32LE(8); // size stream->writeUint32LE(_key); stream->writeUint32LE(_timeLeft); } diff --git a/engines/zvision/scripting/controls/timer_node.h b/engines/zvision/scripting/effects/timer_effect.h index 48b5fad1e9..5e45d54d7d 100644 --- a/engines/zvision/scripting/controls/timer_node.h +++ b/engines/zvision/scripting/effects/timer_effect.h @@ -8,12 +8,12 @@ * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. - * + * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. @@ -23,13 +23,13 @@ #ifndef ZVISION_TIMER_NODE_H #define ZVISION_TIMER_NODE_H -#include "zvision/scripting/control.h" +#include "zvision/scripting/scripting_effect.h" namespace ZVision { class ZVision; -class TimerNode : public Control { +class TimerNode : public ScriptingEffect { public: TimerNode(ZVision *engine, uint32 key, uint timeInSeconds); ~TimerNode(); @@ -44,7 +44,11 @@ public: bool process(uint32 deltaTimeInMillis); void serialize(Common::WriteStream *stream); void deserialize(Common::SeekableReadStream *stream); - inline bool needsSerialization() { return true; } + inline bool needsSerialization() { + return true; + } + + bool stop(); private: int32 _timeLeft; diff --git a/engines/zvision/scripting/effects/ttytext_effect.cpp b/engines/zvision/scripting/effects/ttytext_effect.cpp new file mode 100644 index 0000000000..8d340dae39 --- /dev/null +++ b/engines/zvision/scripting/effects/ttytext_effect.cpp @@ -0,0 +1,172 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/scummsys.h" + +#include "zvision/scripting/effects/ttytext_effect.h" + +#include "zvision/zvision.h" +#include "zvision/scripting/script_manager.h" +#include "zvision/graphics/render_manager.h" +#include "zvision/text/text.h" + +#include "common/stream.h" +#include "common/file.h" + +namespace ZVision { + +ttyTextNode::ttyTextNode(ZVision *engine, uint32 key, const Common::String &file, const Common::Rect &r, int32 delay) : + ScriptingEffect(engine, key, SCRIPTING_EFFECT_TTYTXT), + _fnt(engine) { + _delay = delay; + _r = r; + _txtpos = 0; + _nexttime = 0; + _dx = 0; + _dy = 0; + + Common::File *infile = _engine->getSearchManager()->openFile(file); + if (infile) { + while (!infile->eos()) { + Common::String asciiLine = readWideLine(*infile); + if (asciiLine.empty()) { + continue; + } + _txtbuf += asciiLine; + } + + delete infile; + } + _img.create(_r.width(), _r.height(), _engine->_resourcePixelFormat); + _state._sharp = true; + _state.readAllStyles(_txtbuf); + _state.updateFontWithTextState(_fnt); + _engine->getScriptManager()->setStateValue(_key, 1); +} + +ttyTextNode::~ttyTextNode() { + _engine->getScriptManager()->setStateValue(_key, 2); + _img.free(); +} + +bool ttyTextNode::process(uint32 deltaTimeInMillis) { + _nexttime -= deltaTimeInMillis; + + if (_nexttime < 0) { + if (_txtpos < _txtbuf.size()) { + if (_txtbuf[_txtpos] == '<') { + int32 start = _txtpos; + int32 end = 0; + int16 ret = 0; + while (_txtbuf[_txtpos] != '>' && _txtpos < _txtbuf.size()) + _txtpos++; + end = _txtpos; + if (start != -1) { + if ((end - start - 1) > 0) { + ret = _state.parseStyle(_txtbuf.c_str() + start + 1, end - start - 1); + } + } + + if (ret & (TEXT_CHANGE_FONT_TYPE | TEXT_CHANGE_FONT_STYLE)) { + _state.updateFontWithTextState(_fnt); + } else if (ret & TEXT_CHANGE_NEWLINE) { + newline(); + } + + if (ret & TEXT_CHANGE_HAS_STATE_BOX) { + Common::String buf; + buf = Common::String::format("%d", _engine->getScriptManager()->getStateValue(_state._statebox)); + + for (uint8 j = 0; j < buf.size(); j++) + outchar(buf[j]); + } + + _txtpos++; + } else { + int8 charsz = getUtf8CharSize(_txtbuf[_txtpos]); + + uint16 chr = readUtf8Char(_txtbuf.c_str() + _txtpos); + + if (chr == ' ') { + uint32 i = _txtpos + charsz; + uint16 width = _fnt.getCharWidth(chr); + + while (i < _txtbuf.size() && _txtbuf[i] != ' ' && _txtbuf[i] != '<') { + + int8 chsz = getUtf8CharSize(_txtbuf[i]); + uint16 uchr = readUtf8Char(_txtbuf.c_str() + _txtpos); + + width += _fnt.getCharWidth(uchr); + + i += chsz; + } + + if (_dx + width > _r.width()) + newline(); + else + outchar(chr); + } else + outchar(chr); + + _txtpos += charsz; + } + _nexttime = _delay; + _engine->getRenderManager()->blitSurfaceToBkg(_img, _r.left, _r.top); + } else + return stop(); + } + + return false; +} + +void ttyTextNode::scroll() { + int32 scrl = 0; + while (_dy - scrl > _r.height() - _fnt.getFontHeight()) + scrl += _fnt.getFontHeight(); + int8 *pixels = (int8 *)_img.getPixels(); + for (uint16 h = scrl; h < _img.h; h++) + memcpy(pixels + _img.pitch * (h - scrl), pixels + _img.pitch * h, _img.pitch); + + _img.fillRect(Common::Rect(0, _img.h - scrl, _img.w, _img.h), 0); + _dy -= scrl; +} + +void ttyTextNode::newline() { + _dy += _fnt.getFontHeight(); + _dx = 0; +} + +void ttyTextNode::outchar(uint16 chr) { + uint32 clr = _engine->_resourcePixelFormat.RGBToColor(_state._red, _state._green, _state._blue); + + if (_dx + _fnt.getCharWidth(chr) > _r.width()) + newline(); + + if (_dy + _fnt.getFontHeight() >= _r.height()) + scroll(); + + _fnt.drawChar(&_img, chr, _dx, _dy, clr); + + _dx += _fnt.getCharWidth(chr); +} + +} // End of namespace ZVision diff --git a/engines/zvision/scripting/effects/ttytext_effect.h b/engines/zvision/scripting/effects/ttytext_effect.h new file mode 100644 index 0000000000..18cbbbaee3 --- /dev/null +++ b/engines/zvision/scripting/effects/ttytext_effect.h @@ -0,0 +1,73 @@ +/* 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 ZVISION_TTYTEXT_NODE_H +#define ZVISION_TTYTEXT_NODE_H + +#include "common/rect.h" +#include "graphics/surface.h" + +#include "zvision/scripting/scripting_effect.h" +#include "zvision/text/text.h" +#include "zvision/text/truetype_font.h" + +namespace Common { +class String; +} + +namespace ZVision { +class ttyTextNode : public ScriptingEffect { +public: + ttyTextNode(ZVision *engine, uint32 key, const Common::String &file, const Common::Rect &r, int32 delay); + ~ttyTextNode(); + + /** + * Decrement the timer by the delta time. If the timer is finished, set the status + * in _globalState and let this node be deleted + * + * @param deltaTimeInMillis The number of milliseconds that have passed since last frame + * @return If true, the node can be deleted after process() finishes + */ + bool process(uint32 deltaTimeInMillis); +private: + Common::Rect _r; + + TextStyleState _state; + StyledTTFont _fnt; + Common::String _txtbuf; + uint32 _txtpos; + + int32 _delay; + int32 _nexttime; + Graphics::Surface _img; + int16 _dx; + int16 _dy; +private: + + void newline(); + void scroll(); + void outchar(uint16 chr); +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/scripting/inventory.cpp b/engines/zvision/scripting/inventory.cpp new file mode 100644 index 0000000000..76d43b200b --- /dev/null +++ b/engines/zvision/scripting/inventory.cpp @@ -0,0 +1,122 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* of the License, or (at your option) any later version. + +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. + +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +*/ + +#include "common/scummsys.h" + +#include "zvision/scripting/script_manager.h" + +namespace ZVision { + +int8 ScriptManager::inventoryGetCount() { + return getStateValue(StateKey_Inv_Cnt_Slot); +} + +void ScriptManager::inventorySetCount(int8 cnt) { + setStateValue(StateKey_Inv_Cnt_Slot, cnt); +} + +int16 ScriptManager::inventoryGetItem(int8 id) { + if (id < 49 && id >= 0) + return getStateValue(StateKey_Inv_1_Slot + id); + return -1; +} + +void ScriptManager::inventorySetItem(int8 id, int16 item) { + if (id < 49 && id >= 0) + setStateValue(StateKey_Inv_1_Slot + id, item); +} + +void ScriptManager::inventoryAdd(int16 item) { + int8 cnt = inventoryGetCount(); + + if (cnt < 49) { + bool notExist = true; + + if (cnt == 0) { + inventorySetItem(0, 0); + inventorySetCount(1); // we needed empty item for cycle code + cnt = 1; + } + + for (int8 cur = 0; cur < cnt; cur++) + if (inventoryGetItem(cur) == item) { + notExist = false; + break; + } + + if (notExist) { + for (int8 i = cnt; i > 0; i--) + inventorySetItem(i, inventoryGetItem(i - 1)); + + inventorySetItem(0, item); + + setStateValue(StateKey_InventoryItem, item); + + inventorySetCount(cnt + 1); + } + } +} + +void ScriptManager::inventoryDrop(int16 item) { + int8 itemCount = inventoryGetCount(); + + // if items in inventory > 0 + if (itemCount != 0) { + int8 index = 0; + + // finding needed item + while (index < itemCount) { + if (inventoryGetItem(index) == item) + break; + + index++; + } + + // if item in the inventory + if (itemCount != index) { + // shift all items left with rewrite founded item + for (int8 v = index; v < itemCount - 1 ; v++) + inventorySetItem(v, inventoryGetItem(v + 1)); + + // del last item + inventorySetItem(itemCount - 1, 0); + inventorySetCount(inventoryGetCount() - 1); + + setStateValue(StateKey_InventoryItem, inventoryGetItem(0)); + } + } +} +void ScriptManager::inventoryCycle() { + int8 itemCount = inventoryGetCount(); + int8 curItem = inventoryGetItem(0); + if (itemCount > 1) { + for (int8 i = 0; i < itemCount - 1; i++) + inventorySetItem(i, inventoryGetItem(i + 1)); + + inventorySetItem(itemCount - 1, curItem); + + setStateValue(StateKey_InventoryItem, inventoryGetItem(0)); + + } +} + +} // End of namespace ZVision diff --git a/engines/zvision/scripting/menu.cpp b/engines/zvision/scripting/menu.cpp new file mode 100644 index 0000000000..e7775cbe3f --- /dev/null +++ b/engines/zvision/scripting/menu.cpp @@ -0,0 +1,761 @@ +/* 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 "zvision/graphics/render_manager.h" +#include "zvision/scripting/menu.h" + +namespace ZVision { + +enum { + kMainMenuSave = 0, + kMainMenuLoad = 1, + kMainMenuPrefs = 2, + kMainMenuExit = 3 +}; + +enum { + kMenuItem = 0, + kMenuMagic = 1, + kMenuMain = 2 +}; + +MenuHandler::MenuHandler(ZVision *engine) { + _engine = engine; + menuBarFlag = 0xFFFF; +} + +MenuZGI::MenuZGI(ZVision *engine) : + MenuHandler(engine) { + menuMouseFocus = -1; + inmenu = false; + scrolled[0] = false; + scrolled[1] = false; + scrolled[2] = false; + scrollPos[0] = 0; + scrollPos[1] = 0; + scrollPos[2] = 0; + mouseOnItem = -1; + redraw = false; + clean = false; + + char buf[24]; + for (int i = 1; i < 4; i++) { + sprintf(buf, "gmzau%2.2x1.tga", i); + _engine->getRenderManager()->readImageToSurface(buf, menuback[i - 1][0], false); + sprintf(buf, "gmzau%2.2x1.tga", i + 0x10); + _engine->getRenderManager()->readImageToSurface(buf, menuback[i - 1][1], false); + } + for (int i = 0; i < 4; i++) { + sprintf(buf, "gmzmu%2.2x1.tga", i); + _engine->getRenderManager()->readImageToSurface(buf, menubar[i][0], false); + sprintf(buf, "gmznu%2.2x1.tga", i); + _engine->getRenderManager()->readImageToSurface(buf, menubar[i][1], false); + } + + for (int i = 0; i < 50; i++) { + items[i][0] = NULL; + items[i][1] = NULL; + itemId[i] = 0; + } + + for (int i = 0; i < 12; i++) { + magic[i][0] = NULL; + magic[i][1] = NULL; + magicId[i] = 0; + } +} + +MenuZGI::~MenuZGI() { + for (int i = 0; i < 3; i++) { + menuback[i][0].free(); + menuback[i][1].free(); + } + for (int i = 0; i < 4; i++) { + menubar[i][0].free(); + menubar[i][1].free(); + } + for (int i = 0; i < 50; i++) { + if (items[i][0]) { + items[i][0]->free(); + delete items[i][0]; + } + if (items[i][1]) { + items[i][1]->free(); + delete items[i][1]; + } + } + for (int i = 0; i < 12; i++) { + if (magic[i][0]) { + magic[i][0]->free(); + delete magic[i][0]; + } + if (magic[i][1]) { + magic[i][1]->free(); + delete magic[i][1]; + } + } +} + +void MenuZGI::onMouseUp(const Common::Point &Pos) { + if (Pos.y < 40) { + switch (menuMouseFocus) { + case kMenuItem: + if (menuBarFlag & kMenubarItems) { + int itemCount = _engine->getScriptManager()->getStateValue(StateKey_Inv_TotalSlots); + if (itemCount == 0) + itemCount = 20; + + for (int i = 0; i < itemCount; i++) { + int itemspace = (600 - 28) / itemCount; + + if (Common::Rect(scrollPos[kMenuItem] + itemspace * i, 0, + scrollPos[kMenuItem] + itemspace * i + 28, 32).contains(Pos)) { + int32 mouseItem = _engine->getScriptManager()->getStateValue(StateKey_InventoryItem); + if (mouseItem >= 0 && mouseItem < 0xE0) { + _engine->getScriptManager()->inventoryDrop(mouseItem); + _engine->getScriptManager()->inventoryAdd(_engine->getScriptManager()->getStateValue(StateKey_Inv_StartSlot + i)); + _engine->getScriptManager()->setStateValue(StateKey_Inv_StartSlot + i, mouseItem); + + redraw = true; + } + } + } + } + break; + + case kMenuMagic: + if (menuBarFlag & kMenubarMagic) { + for (int i = 0; i < 12; i++) { + + uint itemnum = _engine->getScriptManager()->getStateValue(StateKey_Spell_1 + i); + if (itemnum != 0) { + if (_engine->getScriptManager()->getStateValue(StateKey_Reversed_Spellbooc) == 1) + itemnum = 0xEE + i; + else + itemnum = 0xE0 + i; + } + if (itemnum) + if (_engine->getScriptManager()->getStateValue(StateKey_InventoryItem) == 0 || _engine->getScriptManager()->getStateValue(StateKey_InventoryItem) >= 0xE0) + if (Common::Rect(668 + 47 * i - scrollPos[kMenuMagic], 0, + 668 + 47 * i - scrollPos[kMenuMagic] + 28, 32).contains(Pos)) + _engine->getScriptManager()->setStateValue(StateKey_Active_Spell, itemnum); + } + + } + break; + + case kMenuMain: + + // Exit + if (menuBarFlag & kMenubarExit) + if (Common::Rect(320 + 135, + scrollPos[kMenuMain], + 320 + 135 + 135, + scrollPos[kMenuMain] + 32).contains(Pos)) { + _engine->ifQuit(); + } + + // Settings + if (menuBarFlag & kMenubarSettings) + if (Common::Rect(320 , + scrollPos[kMenuMain], + 320 + 135, + scrollPos[kMenuMain] + 32).contains(Pos)) { + _engine->getScriptManager()->changeLocation('g', 'j', 'p', 'e', 0); + } + + // Load + if (menuBarFlag & kMenubarRestore) + if (Common::Rect(320 - 135, + scrollPos[kMenuMain], + 320, + scrollPos[kMenuMain] + 32).contains(Pos)) { + _engine->getScriptManager()->changeLocation('g', 'j', 'r', 'e', 0); + } + + // Save + if (menuBarFlag & kMenubarSave) + if (Common::Rect(320 - 135 * 2, + scrollPos[kMenuMain], + 320 - 135, + scrollPos[kMenuMain] + 32).contains(Pos)) { + _engine->getScriptManager()->changeLocation('g', 'j', 's', 'e', 0); + } + break; + } + } +} + +void MenuZGI::onMouseMove(const Common::Point &Pos) { + if (Pos.y < 40) { + + if (!inmenu) + redraw = true; + inmenu = true; + switch (menuMouseFocus) { + case kMenuItem: + if (menuBarFlag & kMenubarItems) { + int itemCount = _engine->getScriptManager()->getStateValue(StateKey_Inv_TotalSlots); + if (itemCount == 0) + itemCount = 20; + else if (itemCount > 50) + itemCount = 50; + + int lastItem = mouseOnItem; + + mouseOnItem = -1; + + for (int i = 0; i < itemCount; i++) { + int itemspace = (600 - 28) / itemCount; + + if (Common::Rect(scrollPos[kMenuItem] + itemspace * i, 0, + scrollPos[kMenuItem] + itemspace * i + 28, 32).contains(Pos)) { + mouseOnItem = i; + break; + } + } + + if (lastItem != mouseOnItem) + if (_engine->getScriptManager()->getStateValue(StateKey_Inv_StartSlot + mouseOnItem) || + _engine->getScriptManager()->getStateValue(StateKey_Inv_StartSlot + lastItem)) + redraw = true; + } + break; + + case kMenuMagic: + if (menuBarFlag & kMenubarMagic) { + int lastItem = mouseOnItem; + mouseOnItem = -1; + for (int i = 0; i < 12; i++) { + if (Common::Rect(668 + 47 * i - scrollPos[kMenuMagic], 0, + 668 + 47 * i - scrollPos[kMenuMagic] + 28, 32).contains(Pos)) { + mouseOnItem = i; + break; + } + } + + if (lastItem != mouseOnItem) + if (_engine->getScriptManager()->getStateValue(StateKey_Spell_1 + mouseOnItem) || + _engine->getScriptManager()->getStateValue(StateKey_Spell_1 + lastItem)) + redraw = true; + + } + break; + + case kMenuMain: { + int lastItem = mouseOnItem; + mouseOnItem = -1; + + // Exit + if (menuBarFlag & kMenubarExit) + if (Common::Rect(320 + 135, + scrollPos[kMenuMain], + 320 + 135 + 135, + scrollPos[kMenuMain] + 32).contains(Pos)) { + mouseOnItem = kMainMenuExit; + } + + // Settings + if (menuBarFlag & kMenubarSettings) + if (Common::Rect(320 , + scrollPos[kMenuMain], + 320 + 135, + scrollPos[kMenuMain] + 32).contains(Pos)) { + mouseOnItem = kMainMenuPrefs; + } + + // Load + if (menuBarFlag & kMenubarRestore) + if (Common::Rect(320 - 135, + scrollPos[kMenuMain], + 320, + scrollPos[kMenuMain] + 32).contains(Pos)) { + mouseOnItem = kMainMenuLoad; + } + + // Save + if (menuBarFlag & kMenubarSave) + if (Common::Rect(320 - 135 * 2, + scrollPos[kMenuMain], + 320 - 135, + scrollPos[kMenuMain] + 32).contains(Pos)) { + mouseOnItem = kMainMenuSave; + } + + if (lastItem != mouseOnItem) + redraw = true; + } + break; + + default: + int cur_menu = menuMouseFocus; + if (Common::Rect(64, 0, 64 + 512, 8).contains(Pos)) { // Main + menuMouseFocus = kMenuMain; + scrolled[kMenuMain] = false; + scrollPos[kMenuMain] = menuback[kMenuMain][1].h - menuback[kMenuMain][0].h; + _engine->getScriptManager()->setStateValue(StateKey_MenuState, 2); + } + + if (menuBarFlag & kMenubarMagic) + if (Common::Rect(640 - 28, 0, 640, 32).contains(Pos)) { // Magic + menuMouseFocus = kMenuMagic; + scrolled[kMenuMagic] = false; + scrollPos[kMenuMagic] = 28; + _engine->getScriptManager()->setStateValue(StateKey_MenuState, 3); + } + + if (menuBarFlag & kMenubarItems) + if (Common::Rect(0, 0, 28, 32).contains(Pos)) { // Items + menuMouseFocus = kMenuItem; + scrolled[kMenuItem] = false; + scrollPos[kMenuItem] = 28 - 600; + _engine->getScriptManager()->setStateValue(StateKey_MenuState, 1); + } + + if (cur_menu != menuMouseFocus) + clean = true; + + break; + } + } else { + if (inmenu) + clean = true; + inmenu = false; + if (_engine->getScriptManager()->getStateValue(StateKey_MenuState) != 0) + _engine->getScriptManager()->setStateValue(StateKey_MenuState, 0); + menuMouseFocus = -1; + } +} + +void MenuZGI::process(uint32 deltatime) { + if (clean) { + _engine->getRenderManager()->clearMenuSurface(); + clean = false; + } + switch (menuMouseFocus) { + case kMenuItem: + if (menuBarFlag & kMenubarItems) + if (!scrolled[kMenuItem]) { + redraw = true; + float scrl = 600.0 * (deltatime / 1000.0); + + if (scrl == 0) + scrl = 1.0; + + scrollPos[kMenuItem] += (int)scrl; + + if (scrollPos[kMenuItem] >= 0) { + scrolled[kMenuItem] = true; + scrollPos[kMenuItem] = 0; + } + } + if (redraw) { + _engine->getRenderManager()->blitSurfaceToMenu(menuback[kMenuItem][0], scrollPos[kMenuItem], 0); + + int itemCount = _engine->getScriptManager()->getStateValue(StateKey_Inv_TotalSlots); + if (itemCount == 0) + itemCount = 20; + else if (itemCount > 50) + itemCount = 50; + + for (int i = 0; i < itemCount; i++) { + int itemspace = (600 - 28) / itemCount; + + bool inrect = false; + + if (mouseOnItem == i) + inrect = true; + + uint curItemId = _engine->getScriptManager()->getStateValue(StateKey_Inv_StartSlot + i); + + if (curItemId != 0) { + if (itemId[i] != curItemId) { + char buf[16]; + sprintf(buf, "gmzwu%2.2x1.tga", curItemId); + items[i][0] = _engine->getRenderManager()->loadImage(buf, false); + sprintf(buf, "gmzxu%2.2x1.tga", curItemId); + items[i][1] = _engine->getRenderManager()->loadImage(buf, false); + itemId[i] = curItemId; + } + + if (inrect) + _engine->getRenderManager()->blitSurfaceToMenu(*items[i][1], scrollPos[kMenuItem] + itemspace * i, 0, 0); + else + _engine->getRenderManager()->blitSurfaceToMenu(*items[i][0], scrollPos[kMenuItem] + itemspace * i, 0, 0); + + } else { + if (items[i][0]) { + items[i][0]->free(); + delete items[i][0]; + items[i][0] = NULL; + } + if (items[i][1]) { + items[i][1]->free(); + delete items[i][1]; + items[i][1] = NULL; + } + itemId[i] = 0; + } + } + + redraw = false; + } + break; + + case kMenuMagic: + if (menuBarFlag & kMenubarMagic) + if (!scrolled[kMenuMagic]) { + redraw = true; + float scrl = 600.0 * (deltatime / 1000.0); + + if (scrl == 0) + scrl = 1.0; + + scrollPos[kMenuMagic] += (int)scrl; + + if (scrollPos[kMenuMagic] >= 600) { + scrolled[kMenuMagic] = true; + scrollPos[kMenuMagic] = 600; + } + } + if (redraw) { + _engine->getRenderManager()->blitSurfaceToMenu(menuback[kMenuMagic][0], 640 - scrollPos[kMenuMagic], 0); + + for (int i = 0; i < 12; i++) { + bool inrect = false; + + if (mouseOnItem == i) + inrect = true; + + uint curItemId = _engine->getScriptManager()->getStateValue(StateKey_Spell_1 + i); + if (curItemId) { + if (_engine->getScriptManager()->getStateValue(StateKey_Reversed_Spellbooc) == 1) + curItemId = 0xEE + i; + else + curItemId = 0xE0 + i; + } + + if (curItemId != 0) { + if (itemId[i] != curItemId) { + char buf[16]; + sprintf(buf, "gmzwu%2.2x1.tga", curItemId); + magic[i][0] = _engine->getRenderManager()->loadImage(buf, false); + sprintf(buf, "gmzxu%2.2x1.tga", curItemId); + magic[i][1] = _engine->getRenderManager()->loadImage(buf, false); + magicId[i] = curItemId; + } + + if (inrect) + _engine->getRenderManager()->blitSurfaceToMenu(*magic[i][1], 668 + 47 * i - scrollPos[kMenuMagic], 0, 0); + else + _engine->getRenderManager()->blitSurfaceToMenu(*magic[i][0], 668 + 47 * i - scrollPos[kMenuMagic], 0, 0); + + } else { + if (magic[i][0]) { + magic[i][0]->free(); + delete magic[i][0]; + magic[i][0] = NULL; + } + if (magic[i][1]) { + magic[i][1]->free(); + delete magic[i][1]; + magic[i][1] = NULL; + } + magicId[i] = 0; + } + } + redraw = false; + } + break; + + case kMenuMain: + if (!scrolled[kMenuMain]) { + redraw = true; + float scrl = 32.0 * 2.0 * (deltatime / 1000.0); + + if (scrl == 0) + scrl = 1.0; + + scrollPos[kMenuMain] += (int)scrl; + + if (scrollPos[kMenuMain] >= 0) { + scrolled[kMenuMain] = true; + scrollPos[kMenuMain] = 0; + } + } + if (redraw) { + _engine->getRenderManager()->blitSurfaceToMenu(menuback[kMenuMain][0], 30, scrollPos[kMenuMain]); + + if (menuBarFlag & kMenubarExit) { + if (mouseOnItem == kMainMenuExit) + _engine->getRenderManager()->blitSurfaceToMenu(menubar[kMainMenuExit][1], 320 + 135, scrollPos[kMenuMain]); + else + _engine->getRenderManager()->blitSurfaceToMenu(menubar[kMainMenuExit][0], 320 + 135, scrollPos[kMenuMain]); + } + if (menuBarFlag & kMenubarSettings) { + if (mouseOnItem == kMainMenuPrefs) + _engine->getRenderManager()->blitSurfaceToMenu(menubar[kMainMenuPrefs][1], 320, scrollPos[kMenuMain]); + else + _engine->getRenderManager()->blitSurfaceToMenu(menubar[kMainMenuPrefs][0], 320, scrollPos[kMenuMain]); + } + if (menuBarFlag & kMenubarRestore) { + if (mouseOnItem == kMainMenuLoad) + _engine->getRenderManager()->blitSurfaceToMenu(menubar[kMainMenuLoad][1], 320 - 135, scrollPos[kMenuMain]); + else + _engine->getRenderManager()->blitSurfaceToMenu(menubar[kMainMenuLoad][0], 320 - 135, scrollPos[kMenuMain]); + } + if (menuBarFlag & kMenubarSave) { + if (mouseOnItem == kMainMenuSave) + _engine->getRenderManager()->blitSurfaceToMenu(menubar[kMainMenuSave][1], 320 - 135 * 2, scrollPos[kMenuMain]); + else + _engine->getRenderManager()->blitSurfaceToMenu(menubar[kMainMenuSave][0], 320 - 135 * 2, scrollPos[kMenuMain]); + } + redraw = false; + } + break; + default: + if (redraw) { + if (inmenu) { + _engine->getRenderManager()->blitSurfaceToMenu(menuback[kMenuMain][1], 30, 0); + + if (menuBarFlag & kMenubarItems) + _engine->getRenderManager()->blitSurfaceToMenu(menuback[kMenuItem][1], 0, 0); + + if (menuBarFlag & kMenubarMagic) + _engine->getRenderManager()->blitSurfaceToMenu(menuback[kMenuMagic][1], 640 - 28, 0); + } + redraw = false; + } + break; + } +} + +MenuNemesis::MenuNemesis(ZVision *engine) : + MenuHandler(engine) { + inmenu = false; + scrolled = false; + scrollPos = 0; + mouseOnItem = -1; + redraw = false; + delay = 0; + + char buf[24]; + for (int i = 0; i < 4; i++) + for (int j = 0; j < 6; j++) { + sprintf(buf, "butfrm%d%d.tga", i + 1, j); + _engine->getRenderManager()->readImageToSurface(buf, but[i][j], false); + } + + _engine->getRenderManager()->readImageToSurface("bar.tga", menubar, false); + + frm = 0; +} + +MenuNemesis::~MenuNemesis() { + for (int i = 0; i < 4; i++) + for (int j = 0; j < 6; j++) + but[i][j].free(); + + menubar.free(); +} + +static const int16 buts[4][2] = { {120 , 64}, {144, 184}, {128, 328}, {120, 456} }; + +void MenuNemesis::onMouseUp(const Common::Point &Pos) { + if (Pos.y < 40) { + // Exit + if (menuBarFlag & kMenubarExit) + if (Common::Rect(buts[3][1], + scrollPos, + buts[3][0] + buts[3][1], + scrollPos + 32).contains(Pos)) { + _engine->ifQuit(); + frm = 5; + redraw = true; + } + + // Settings + if (menuBarFlag & kMenubarSettings) + if (Common::Rect(buts[2][1], + scrollPos, + buts[2][0] + buts[2][1], + scrollPos + 32).contains(Pos)) { + _engine->getScriptManager()->changeLocation('g', 'j', 'p', 'e', 0); + frm = 5; + redraw = true; + } + + // Load + if (menuBarFlag & kMenubarRestore) + if (Common::Rect(buts[1][1], + scrollPos, + buts[1][0] + buts[1][1], + scrollPos + 32).contains(Pos)) { + _engine->getScriptManager()->changeLocation('g', 'j', 'r', 'e', 0); + frm = 5; + redraw = true; + } + + // Save + if (menuBarFlag & kMenubarSave) + if (Common::Rect(buts[0][1], + scrollPos, + buts[0][0] + buts[0][1], + scrollPos + 32).contains(Pos)) { + _engine->getScriptManager()->changeLocation('g', 'j', 's', 'e', 0); + frm = 5; + redraw = true; + } + } +} + +void MenuNemesis::onMouseMove(const Common::Point &Pos) { + if (Pos.y < 40) { + + inmenu = true; + + if (_engine->getScriptManager()->getStateValue(StateKey_MenuState) != 2) + _engine->getScriptManager()->setStateValue(StateKey_MenuState, 2); + + int lastItem = mouseOnItem; + mouseOnItem = -1; + + // Exit + if (menuBarFlag & kMenubarExit) + if (Common::Rect(buts[3][1], + scrollPos, + buts[3][0] + buts[3][1], + scrollPos + 32).contains(Pos)) { + mouseOnItem = kMainMenuExit; + } + + // Settings + if (menuBarFlag & kMenubarSettings) + if (Common::Rect(buts[2][1], + scrollPos, + buts[2][0] + buts[2][1], + scrollPos + 32).contains(Pos)) { + mouseOnItem = kMainMenuPrefs; + } + + // Load + if (menuBarFlag & kMenubarRestore) + if (Common::Rect(buts[1][1], + scrollPos, + buts[1][0] + buts[1][1], + scrollPos + 32).contains(Pos)) { + mouseOnItem = kMainMenuLoad; + } + + // Save + if (menuBarFlag & kMenubarSave) + if (Common::Rect(buts[0][1], + scrollPos, + buts[0][0] + buts[0][1], + scrollPos + 32).contains(Pos)) { + mouseOnItem = kMainMenuSave; + } + + if (lastItem != mouseOnItem) { + redraw = true; + frm = 0; + delay = 200; + } + } else { + inmenu = false; + if (_engine->getScriptManager()->getStateValue(StateKey_MenuState) != 0) + _engine->getScriptManager()->setStateValue(StateKey_MenuState, 0); + mouseOnItem = -1; + } +} + +void MenuNemesis::process(uint32 deltatime) { + if (inmenu) { + if (!scrolled) { + float scrl = 32.0 * 2.0 * (deltatime / 1000.0); + + if (scrl == 0) + scrl = 1.0; + + scrollPos += (int)scrl; + redraw = true; + } + + if (scrollPos >= 0) { + scrolled = true; + scrollPos = 0; + } + + if (mouseOnItem != -1) { + delay -= deltatime; + if (delay <= 0 && frm < 4) { + delay = 200; + frm++; + redraw = true; + } + } + + if (redraw) { + _engine->getRenderManager()->blitSurfaceToMenu(menubar, 64, scrollPos); + + if (menuBarFlag & kMenubarExit) + if (mouseOnItem == kMainMenuExit) + _engine->getRenderManager()->blitSurfaceToMenu(but[3][frm], buts[3][1], scrollPos); + + if (menuBarFlag & kMenubarSettings) + if (mouseOnItem == kMainMenuPrefs) + _engine->getRenderManager()->blitSurfaceToMenu(but[2][frm], buts[2][1], scrollPos); + + if (menuBarFlag & kMenubarRestore) + if (mouseOnItem == kMainMenuLoad) + _engine->getRenderManager()->blitSurfaceToMenu(but[1][frm], buts[1][1], scrollPos); + + if (menuBarFlag & kMenubarSave) + if (mouseOnItem == kMainMenuSave) + _engine->getRenderManager()->blitSurfaceToMenu(but[0][frm], buts[0][1], scrollPos); + + redraw = false; + } + } else { + scrolled = false; + if (scrollPos > -32) { + float scrl = 32.0 * 2.0 * (deltatime / 1000.0); + + if (scrl == 0) + scrl = 1.0; + + Common::Rect cl(64, (int16)(32 + scrollPos - scrl), 64 + 512, 32 + scrollPos + 1); + _engine->getRenderManager()->clearMenuSurface(cl); + + scrollPos -= (int)scrl; + redraw = true; + } else + scrollPos = -32; + + if (redraw) { + _engine->getRenderManager()->blitSurfaceToMenu(menubar, 64, scrollPos); + redraw = false; + } + } +} + +} // End of namespace ZVision diff --git a/engines/zvision/scripting/menu.h b/engines/zvision/scripting/menu.h new file mode 100644 index 0000000000..a88587966f --- /dev/null +++ b/engines/zvision/scripting/menu.h @@ -0,0 +1,119 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ZVISION_MENU_H +#define ZVISION_MENU_H + +#include "graphics/surface.h" +#include "common/rect.h" + +#include "zvision/zvision.h" +#include "zvision/scripting/script_manager.h" + +namespace ZVision { + +enum menuBar { + kMenubarExit = 0x1, + kMenubarSettings = 0x2, + kMenubarRestore = 0x4, + kMenubarSave = 0x8, + kMenubarItems = 0x100, + kMenubarMagic = 0x200 +}; + +class MenuHandler { +public: + MenuHandler(ZVision *engine); + virtual ~MenuHandler() {}; + virtual void onMouseMove(const Common::Point &Pos) {}; + virtual void onMouseDown(const Common::Point &Pos) {}; + virtual void onMouseUp(const Common::Point &Pos) {}; + virtual void process(uint32 deltaTimeInMillis) {}; + + void setEnable(uint16 flags) { + menuBarFlag = flags; + } + uint16 getEnable() { + return menuBarFlag; + } +protected: + uint16 menuBarFlag; + ZVision *_engine; +}; + +class MenuZGI: public MenuHandler { +public: + MenuZGI(ZVision *engine); + ~MenuZGI(); + void onMouseMove(const Common::Point &Pos); + void onMouseUp(const Common::Point &Pos); + void process(uint32 deltaTimeInMillis); +private: + Graphics::Surface menuback[3][2]; + Graphics::Surface menubar[4][2]; + Graphics::Surface *items[50][2]; + uint itemId[50]; + + Graphics::Surface *magic[12][2]; + uint magicId[12]; + + int menuMouseFocus; + bool inmenu; + + int mouseOnItem; + + bool scrolled[3]; + int16 scrollPos[3]; + + bool clean; + bool redraw; + +}; + +class MenuNemesis: public MenuHandler { +public: + MenuNemesis(ZVision *engine); + ~MenuNemesis(); + void onMouseMove(const Common::Point &Pos); + void onMouseUp(const Common::Point &Pos); + void process(uint32 deltaTimeInMillis); +private: + Graphics::Surface but[4][6]; + Graphics::Surface menubar; + + bool inmenu; + + int mouseOnItem; + + bool scrolled; + int16 scrollPos; + + bool redraw; + + int frm; + int16 delay; + +}; + +} + +#endif diff --git a/engines/zvision/scripting/puzzle.h b/engines/zvision/scripting/puzzle.h index ee9ce521c9..7d64357b0a 100644 --- a/engines/zvision/scripting/puzzle.h +++ b/engines/zvision/scripting/puzzle.h @@ -8,12 +8,12 @@ * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. - * + * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. @@ -28,11 +28,10 @@ #include "common/list.h" #include "common/ptr.h" - namespace ZVision { struct Puzzle { - Puzzle() : key(0) {} + Puzzle() : key(0), addedBySetState(false) {} ~Puzzle() { for (Common::List<ResultAction *>::iterator iter = resultActions.begin(); iter != resultActions.end(); ++iter) { @@ -63,10 +62,17 @@ struct Puzzle { bool argumentIsAKey; }; + enum StateFlags { + ONCE_PER_INST = 0x01, + DISABLED = 0x02, + DO_ME_NOW = 0x04 + }; + uint32 key; Common::List<Common::List <CriteriaEntry> > criteriaList; // This has to be list of pointers because ResultAction is abstract Common::List<ResultAction *> resultActions; + bool addedBySetState; }; } // End of namespace ZVision diff --git a/engines/zvision/scripting/scr_file_handling.cpp b/engines/zvision/scripting/scr_file_handling.cpp index 753ce4ac6a..edc1b8622c 100644 --- a/engines/zvision/scripting/scr_file_handling.cpp +++ b/engines/zvision/scripting/scr_file_handling.cpp @@ -8,12 +8,12 @@ * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. - * + * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. @@ -22,33 +22,38 @@ #include "common/scummsys.h" +#include "zvision/zvision.h" #include "zvision/scripting/script_manager.h" -#include "zvision/utility/utility.h" #include "zvision/scripting/puzzle.h" #include "zvision/scripting/actions.h" #include "zvision/scripting/controls/push_toggle_control.h" #include "zvision/scripting/controls/lever_control.h" +#include "zvision/scripting/controls/slot_control.h" +#include "zvision/scripting/controls/save_control.h" +#include "zvision/scripting/controls/input_control.h" +#include "zvision/scripting/controls/safe_control.h" +#include "zvision/scripting/controls/hotmov_control.h" +#include "zvision/scripting/controls/fist_control.h" +#include "zvision/scripting/controls/paint_control.h" +#include "zvision/scripting/controls/titler_control.h" #include "common/textconsole.h" #include "common/file.h" #include "common/tokenizer.h" - namespace ZVision { -void ScriptManager::parseScrFile(const Common::String &fileName, bool isGlobal) { +void ScriptManager::parseScrFile(const Common::String &fileName, ScriptScope &scope) { Common::File file; - if (!file.open(fileName)) { - warning("Script file not found: %s", fileName.c_str()); - return; + if (!_engine->getSearchManager()->openFile(file, fileName)) { + error("Script file not found: %s", fileName.c_str()); } - while(!file.eos()) { + while (!file.eos()) { Common::String line = file.readLine(); if (file.err()) { - warning("Error parsing scr file: %s", fileName.c_str()); - return; + error("Error parsing scr file: %s", fileName.c_str()); } trimCommentsAndWhiteSpace(&line); @@ -57,18 +62,19 @@ void ScriptManager::parseScrFile(const Common::String &fileName, bool isGlobal) if (line.matchString("puzzle:*", true)) { Puzzle *puzzle = new Puzzle(); - sscanf(line.c_str(),"puzzle:%u",&(puzzle->key)); - + sscanf(line.c_str(), "puzzle:%u", &(puzzle->key)); + if (getStateFlag(puzzle->key) & Puzzle::ONCE_PER_INST) + setStateValue(puzzle->key, 0); parsePuzzle(puzzle, file); - if (isGlobal) { - _globalPuzzles.push_back(puzzle); - } else { - _activePuzzles.push_back(puzzle); - } + scope.puzzles.push_back(puzzle); + } else if (line.matchString("control:*", true)) { - parseControl(line, file); + Control *ctrl = parseControl(line, file); + if (ctrl) + scope.controls.push_back(ctrl); } } + scope.procCount = 0; } void ScriptManager::parsePuzzle(Puzzle *puzzle, Common::SeekableReadStream &stream) { @@ -77,23 +83,41 @@ void ScriptManager::parsePuzzle(Puzzle *puzzle, Common::SeekableReadStream &stre while (!stream.eos() && !line.contains('}')) { if (line.matchString("criteria {", true)) { - parseCriteria(stream, puzzle->criteriaList); + parseCriteria(stream, puzzle->criteriaList, puzzle->key); } else if (line.matchString("results {", true)) { parseResults(stream, puzzle->resultActions); + + // WORKAROUND for a script bug in Zork Nemesis, room ve5e (tuning + // fork box closeup). If the player leaves the screen while the + // box is open, puzzle 19398 shows the animation where the box + // closes, but the box state (state variable 19397) is not updated. + // We insert the missing assignment for the box state here. + // Fixes bug #6803. + if (_engine->getGameId() == GID_NEMESIS && puzzle->key == 19398) + puzzle->resultActions.push_back(new ActionAssign(_engine, 11, "19397, 0")); } else if (line.matchString("flags {", true)) { - setStateFlags(puzzle->key, parseFlags(stream)); + setStateFlag(puzzle->key, parseFlags(stream)); } line = stream.readLine(); trimCommentsAndWhiteSpace(&line); } + + puzzle->addedBySetState = false; } -bool ScriptManager::parseCriteria(Common::SeekableReadStream &stream, Common::List<Common::List<Puzzle::CriteriaEntry> > &criteriaList) const { +bool ScriptManager::parseCriteria(Common::SeekableReadStream &stream, Common::List<Common::List<Puzzle::CriteriaEntry> > &criteriaList, uint32 key) const { // Loop until we find the closing brace Common::String line = stream.readLine(); trimCommentsAndWhiteSpace(&line); + // Skip any commented out criteria. If all the criteria are commented out, + // we might end up with an invalid criteria list (bug #6776). + while (line.empty()) { + line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + } + // Criteria can be empty if (line.contains('}')) { return false; @@ -102,6 +126,21 @@ bool ScriptManager::parseCriteria(Common::SeekableReadStream &stream, Common::Li // Create a new List to hold the CriteriaEntries criteriaList.push_back(Common::List<Puzzle::CriteriaEntry>()); + // WORKAROUND for a script bug in Zork: Nemesis, room td9e (fist puzzle) + // Since we patch the script that triggers when manipulating the left fist + // (below), we add an additional check for the left fist sound, so that it + // doesn't get killed immediately when the left fist animation starts. + // Together with the workaround below, it fixes bug #6783. + if (_engine->getGameId() == GID_NEMESIS && key == 3594) { + Puzzle::CriteriaEntry entry; + entry.key = 567; + entry.criteriaOperator = Puzzle::NOT_EQUAL_TO; + entry.argumentIsAKey = false; + entry.argument = 1; + + criteriaList.back().push_back(entry); + } + while (!stream.eos() && !line.contains('}')) { Puzzle::CriteriaEntry entry; @@ -113,6 +152,13 @@ bool ScriptManager::parseCriteria(Common::SeekableReadStream &stream, Common::Li token = tokenizer.nextToken(); sscanf(token.c_str(), "[%u]", &(entry.key)); + // WORKAROUND for a script bug in Zork: Nemesis, room td9e (fist puzzle) + // Check for the state of animation 567 (left fist) when manipulating + // the fingers of the left fist (puzzle slots 3582, 3583). + // Together with the workaround above, it fixes bug #6783. + if (_engine->getGameId() == GID_NEMESIS && (key == 3582 || key == 3583) && entry.key == 568) + entry.key = 567; + // Parse the operator out of the second token token = tokenizer.nextToken(); if (token.c_str()[0] == '=') @@ -124,9 +170,17 @@ bool ScriptManager::parseCriteria(Common::SeekableReadStream &stream, Common::Li else if (token.c_str()[0] == '<') entry.criteriaOperator = Puzzle::LESS_THAN; + // There are supposed to be three tokens, but there is no + // guarantee that there will be a space between the second and + // the third one (bug #6774) + if (token.size() == 1) { + token = tokenizer.nextToken(); + } else { + token.deleteChar(0); + } + // First determine if the last token is an id or a value // Then parse it into 'argument' - token = tokenizer.nextToken(); if (token.contains('[')) { sscanf(token.c_str(), "[%u]", &(entry.argument)); entry.argumentIsAKey = true; @@ -148,103 +202,154 @@ void ScriptManager::parseResults(Common::SeekableReadStream &stream, Common::Lis // Loop until we find the closing brace Common::String line = stream.readLine(); trimCommentsAndWhiteSpace(&line); + line.toLowercase(); // TODO: Re-order the if-then statements in order of highest occurrence while (!stream.eos() && !line.contains('}')) { if (line.empty()) { line = stream.readLine(); trimCommentsAndWhiteSpace(&line); - + line.toLowercase(); continue; } - // Parse for the action type - if (line.matchString("*:add*", true)) { - actionList.push_back(new ActionAdd(line)); - } else if (line.matchString("*:animplay*", true)) { - actionList.push_back(new ActionPlayAnimation(line)); - } else if (line.matchString("*:animpreload*", true)) { - actionList.push_back(new ActionPreloadAnimation(line)); - } else if (line.matchString("*:animunload*", true)) { - //actionList.push_back(new ActionUnloadAnimation(line)); - } else if (line.matchString("*:attenuate*", true)) { - // TODO: Implement ActionAttenuate - } else if (line.matchString("*:assign*", true)) { - actionList.push_back(new ActionAssign(line)); - } else if (line.matchString("*:change_location*", true)) { - actionList.push_back(new ActionChangeLocation(line)); - } else if (line.matchString("*:crossfade*", true)) { - // TODO: Implement ActionCrossfade - } else if (line.matchString("*:debug*", true)) { - // TODO: Implement ActionDebug - } else if (line.matchString("*:delay_render*", true)) { - // TODO: Implement ActionDelayRender - } else if (line.matchString("*:disable_control*", true)) { - actionList.push_back(new ActionDisableControl(line)); - } else if (line.matchString("*:disable_venus*", true)) { - // TODO: Implement ActionDisableVenus - } else if (line.matchString("*:display_message*", true)) { - // TODO: Implement ActionDisplayMessage - } else if (line.matchString("*:dissolve*", true)) { - // TODO: Implement ActionDissolve - } else if (line.matchString("*:distort*", true)) { - // TODO: Implement ActionDistort - } else if (line.matchString("*:enable_control*", true)) { - actionList.push_back(new ActionEnableControl(line)); - } else if (line.matchString("*:flush_mouse_events*", true)) { - // TODO: Implement ActionFlushMouseEvents - } else if (line.matchString("*:inventory*", true)) { - // TODO: Implement ActionInventory - } else if (line.matchString("*:kill*", true)) { - // TODO: Implement ActionKill - } else if (line.matchString("*:menu_bar_enable*", true)) { - // TODO: Implement ActionMenuBarEnable - } else if (line.matchString("*:music*", true)) { - actionList.push_back(new ActionMusic(line)); - } else if (line.matchString("*:pan_track*", true)) { - // TODO: Implement ActionPanTrack - } else if (line.matchString("*:playpreload*", true)) { - actionList.push_back(new ActionPlayPreloadAnimation(line)); - } else if (line.matchString("*:preferences*", true)) { - // TODO: Implement ActionPreferences - } else if (line.matchString("*:quit*", true)) { - actionList.push_back(new ActionQuit()); - } else if (line.matchString("*:random*", true)) { - actionList.push_back(new ActionRandom(line)); - } else if (line.matchString("*:region*", true)) { - // TODO: Implement ActionRegion - } else if (line.matchString("*:restore_game*", true)) { - // TODO: Implement ActionRestoreGame - } else if (line.matchString("*:rotate_to*", true)) { - // TODO: Implement ActionRotateTo - } else if (line.matchString("*:save_game*", true)) { - // TODO: Implement ActionSaveGame - } else if (line.matchString("*:set_partial_screen*", true)) { - actionList.push_back(new ActionSetPartialScreen(line)); - } else if (line.matchString("*:set_screen*", true)) { - actionList.push_back(new ActionSetScreen(line)); - } else if (line.matchString("*:set_venus*", true)) { - // TODO: Implement ActionSetVenus - } else if (line.matchString("*:stop*", true)) { - // TODO: Implement ActionStop - } else if (line.matchString("*:streamvideo*", true)) { - actionList.push_back(new ActionStreamVideo(line)); - } else if (line.matchString("*:syncsound*", true)) { - // TODO: Implement ActionSyncSound - } else if (line.matchString("*:timer*", true)) { - actionList.push_back(new ActionTimer(line)); - } else if (line.matchString("*:ttytext*", true)) { - // TODO: Implement ActionTTYText - } else if (line.matchString("*:universe_music*", true)) { - // TODO: Implement ActionUniverseMusic - } else if (line.matchString("*:copy_file*", true)) { - // Not used. Purposely left empty - } else { - warning("Unhandled result action type: %s", line.c_str()); + const char *chrs = line.c_str(); + uint pos; + for (pos = 0; pos < line.size(); pos++) + if (chrs[pos] == ':') + break; + + if (pos < line.size()) { + + uint startpos = pos + 1; + + for (pos = startpos; pos < line.size(); pos++) + if (chrs[pos] == ':' || chrs[pos] == '(') + break; + + if (pos < line.size()) { + int32 slot = 11; + Common::String args = ""; + Common::String act(chrs + startpos, chrs + pos); + + startpos = pos + 1; + + if (chrs[pos] == ':') { + for (pos = startpos; pos < line.size(); pos++) + if (chrs[pos] == '(') + break; + Common::String strSlot(chrs + startpos, chrs + pos); + slot = atoi(strSlot.c_str()); + + startpos = pos + 1; + } + + if (pos < line.size()) { + for (pos = startpos; pos < line.size(); pos++) + if (chrs[pos] == ')') + break; + + args = Common::String(chrs + startpos, chrs + pos); + } + + // Parse for the action type + if (act.matchString("add", true)) { + actionList.push_back(new ActionAdd(_engine, slot, args)); + } else if (act.matchString("animplay", true)) { + actionList.push_back(new ActionPlayAnimation(_engine, slot, args)); + } else if (act.matchString("animpreload", true)) { + actionList.push_back(new ActionPreloadAnimation(_engine, slot, args)); + } else if (act.matchString("animunload", true)) { + // Only used by ZGI (locations cd6e, cd6k, dg2f, dg4e, dv1j) + actionList.push_back(new ActionUnloadAnimation(_engine, slot, args)); + } else if (act.matchString("attenuate", true)) { + actionList.push_back(new ActionAttenuate(_engine, slot, args)); + } else if (act.matchString("assign", true)) { + actionList.push_back(new ActionAssign(_engine, slot, args)); + } else if (act.matchString("change_location", true)) { + actionList.push_back(new ActionChangeLocation(_engine, slot, args)); + } else if (act.matchString("crossfade", true)) { + actionList.push_back(new ActionCrossfade(_engine, slot, args)); + } else if (act.matchString("cursor", true)) { + actionList.push_back(new ActionCursor(_engine, slot, args)); + } else if (act.matchString("debug", true)) { + // Not used. Purposely left empty + } else if (act.matchString("delay_render", true)) { + actionList.push_back(new ActionDelayRender(_engine, slot, args)); + } else if (act.matchString("disable_control", true)) { + actionList.push_back(new ActionDisableControl(_engine, slot, args)); + } else if (act.matchString("disable_venus", true)) { + // Not used. Purposely left empty + } else if (act.matchString("display_message", true)) { + actionList.push_back(new ActionDisplayMessage(_engine, slot, args)); + } else if (act.matchString("dissolve", true)) { + actionList.push_back(new ActionDissolve(_engine)); + } else if (act.matchString("distort", true)) { + // Only used by Zork: Nemesis for the "treatment" puzzle in the Sanitarium (aj30) + actionList.push_back(new ActionDistort(_engine, slot, args)); + } else if (act.matchString("enable_control", true)) { + actionList.push_back(new ActionEnableControl(_engine, slot, args)); + } else if (act.matchString("flush_mouse_events", true)) { + actionList.push_back(new ActionFlushMouseEvents(_engine, slot)); + } else if (act.matchString("inventory", true)) { + actionList.push_back(new ActionInventory(_engine, slot, args)); + } else if (act.matchString("kill", true)) { + // Only used by ZGI + actionList.push_back(new ActionKill(_engine, slot, args)); + } else if (act.matchString("menu_bar_enable", true)) { + actionList.push_back(new ActionMenuBarEnable(_engine, slot, args)); + } else if (act.matchString("music", true)) { + actionList.push_back(new ActionMusic(_engine, slot, args, false)); + } else if (act.matchString("pan_track", true)) { + actionList.push_back(new ActionPanTrack(_engine, slot, args)); + } else if (act.matchString("playpreload", true)) { + actionList.push_back(new ActionPlayPreloadAnimation(_engine, slot, args)); + } else if (act.matchString("preferences", true)) { + actionList.push_back(new ActionPreferences(_engine, slot, args)); + } else if (act.matchString("quit", true)) { + actionList.push_back(new ActionQuit(_engine, slot)); + } else if (act.matchString("random", true)) { + actionList.push_back(new ActionRandom(_engine, slot, args)); + } else if (act.matchString("region", true)) { + // Only used by Zork: Nemesis + actionList.push_back(new ActionRegion(_engine, slot, args)); + } else if (act.matchString("restore_game", true)) { + // Only used by ZGI to load the restart game slot, r.svr. + // Used by the credits screen. + actionList.push_back(new ActionRestoreGame(_engine, slot, args)); + } else if (act.matchString("rotate_to", true)) { + actionList.push_back(new ActionRotateTo(_engine, slot, args)); + } else if (act.matchString("save_game", true)) { + // Not used. Purposely left empty + } else if (act.matchString("set_partial_screen", true)) { + actionList.push_back(new ActionSetPartialScreen(_engine, slot, args)); + } else if (act.matchString("set_screen", true)) { + actionList.push_back(new ActionSetScreen(_engine, slot, args)); + } else if (act.matchString("set_venus", true)) { + // Not used. Purposely left empty + } else if (act.matchString("stop", true)) { + actionList.push_back(new ActionStop(_engine, slot, args)); + } else if (act.matchString("streamvideo", true)) { + actionList.push_back(new ActionStreamVideo(_engine, slot, args)); + } else if (act.matchString("syncsound", true)) { + actionList.push_back(new ActionSyncSound(_engine, slot, args)); + } else if (act.matchString("timer", true)) { + actionList.push_back(new ActionTimer(_engine, slot, args)); + } else if (act.matchString("ttytext", true)) { + actionList.push_back(new ActionTtyText(_engine, slot, args)); + } else if (act.matchString("universe_music", true)) { + actionList.push_back(new ActionMusic(_engine, slot, args, true)); + } else if (act.matchString("copy_file", true)) { + // Not used. Purposely left empty + } else { + warning("Unhandled result action type: %s", line.c_str()); + } + } } line = stream.readLine(); trimCommentsAndWhiteSpace(&line); + line.toLowercase(); } return; @@ -259,11 +364,11 @@ uint ScriptManager::parseFlags(Common::SeekableReadStream &stream) const { while (!stream.eos() && !line.contains('}')) { if (line.matchString("ONCE_PER_INST", true)) { - flags |= ONCE_PER_INST; + flags |= Puzzle::ONCE_PER_INST; } else if (line.matchString("DO_ME_NOW", true)) { - flags |= DO_ME_NOW; + flags |= Puzzle::DO_ME_NOW; } else if (line.matchString("DISABLED", true)) { - flags |= DISABLED; + flags |= Puzzle::DISABLED; } line = stream.readLine(); @@ -273,7 +378,7 @@ uint ScriptManager::parseFlags(Common::SeekableReadStream &stream) const { return flags; } -void ScriptManager::parseControl(Common::String &line, Common::SeekableReadStream &stream) { +Control *ScriptManager::parseControl(Common::String &line, Common::SeekableReadStream &stream) { uint32 key; char controlTypeBuffer[20]; @@ -282,21 +387,53 @@ void ScriptManager::parseControl(Common::String &line, Common::SeekableReadStrea Common::String controlType(controlTypeBuffer); if (controlType.equalsIgnoreCase("push_toggle")) { - _activeControls.push_back(new PushToggleControl(_engine, key, stream)); - return; + // WORKAROUND for a script bug in ZGI: There is an invalid hotspot + // at scene em1h (bottom of tower), which points to a missing + // script em1n. This is a hotspot at the right of the screen. + // In the original, this hotspot doesn't lead anywhere anyway, + // so instead of moving to a missing scene, we just remove the + // hotspot altogether. The alternative would be to just process + // and ignore invalid scenes, but I don't think it's worth the + // effort. Fixes bug #6780. + if (_engine->getGameId() == GID_GRANDINQUISITOR && key == 5653) + return NULL; + return new PushToggleControl(_engine, key, stream); } else if (controlType.equalsIgnoreCase("flat")) { Control::parseFlatControl(_engine); - return; + return NULL; } else if (controlType.equalsIgnoreCase("pana")) { Control::parsePanoramaControl(_engine, stream); - return; + return NULL; } else if (controlType.equalsIgnoreCase("tilt")) { + // Only used in Zork Nemesis, handles tilt controls (ZGI doesn't have a tilt view) Control::parseTiltControl(_engine, stream); - return; + return NULL; + } else if (controlType.equalsIgnoreCase("slot")) { + return new SlotControl(_engine, key, stream); + } else if (controlType.equalsIgnoreCase("input")) { + return new InputControl(_engine, key, stream); + } else if (controlType.equalsIgnoreCase("save")) { + return new SaveControl(_engine, key, stream); } else if (controlType.equalsIgnoreCase("lever")) { - _activeControls.push_back(new LeverControl(_engine, key, stream)); - return; + // Only used in Zork Nemesis, handles draggable levers (te2e, tm7e, tp2e, tt2e, tz2e) + return new LeverControl(_engine, key, stream); + } else if (controlType.equalsIgnoreCase("safe")) { + // Only used in Zork Nemesis, handles the safe in the Asylum (ac4g) + return new SafeControl(_engine, key, stream); + } else if (controlType.equalsIgnoreCase("hotmovie")) { + // Only used in Zork Nemesis, handles movies where the player needs to click on something (mj7g, vw3g) + return new HotMovControl(_engine, key, stream); + } else if (controlType.equalsIgnoreCase("fist")) { + // Only used in Zork Nemesis, handles the door lock puzzle with the skeletal fingers (td9e) + return new FistControl(_engine, key, stream); + } else if (controlType.equalsIgnoreCase("paint")) { + // Only used in Zork Nemesis, handles the painting puzzle screen in Lucien's room in Irondune (ch4g) + return new PaintControl(_engine, key, stream); + } else if (controlType.equalsIgnoreCase("titler")) { + // Only used in Zork Nemesis, handles the death screen with the Restore/Exit buttons (cjde) + return new TitlerControl(_engine, key, stream); } + return NULL; } } // End of namespace ZVision diff --git a/engines/zvision/scripting/script_manager.cpp b/engines/zvision/scripting/script_manager.cpp index 41b835e550..70eaab2a0a 100644 --- a/engines/zvision/scripting/script_manager.cpp +++ b/engines/zvision/scripting/script_manager.cpp @@ -1,24 +1,24 @@ /* 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. - * - */ +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* of the License, or (at your option) any later version. + +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. + +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +*/ #include "common/scummsys.h" @@ -26,172 +26,254 @@ #include "zvision/zvision.h" #include "zvision/graphics/render_manager.h" -#include "zvision/cursors/cursor_manager.h" -#include "zvision/core/save_manager.h" +#include "zvision/graphics/cursors/cursor_manager.h" +#include "zvision/file/save_manager.h" #include "zvision/scripting/actions.h" -#include "zvision/utility/utility.h" +#include "zvision/scripting/menu.h" +#include "zvision/scripting/effects/timer_effect.h" #include "common/algorithm.h" #include "common/hashmap.h" #include "common/debug.h" #include "common/stream.h" - +#include "common/config-manager.h" namespace ZVision { ScriptManager::ScriptManager(ZVision *engine) : _engine(engine), - _currentlyFocusedControl(0) { + _currentlyFocusedControl(0), + _activeControls(NULL) { } ScriptManager::~ScriptManager() { - for (PuzzleList::iterator iter = _activePuzzles.begin(); iter != _activePuzzles.end(); ++iter) { - delete (*iter); - } - for (PuzzleList::iterator iter = _globalPuzzles.begin(); iter != _globalPuzzles.end(); ++iter) { - delete (*iter); - } - for (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end(); ++iter) { - delete (*iter); - } + cleanScriptScope(universe); + cleanScriptScope(world); + cleanScriptScope(room); + cleanScriptScope(nodeview); + _controlEvents.clear(); } void ScriptManager::initialize() { - parseScrFile("universe.scr", true); + cleanScriptScope(universe); + cleanScriptScope(world); + cleanScriptScope(room); + cleanScriptScope(nodeview); + + _currentLocation.node = 0; + _currentLocation.world = 0; + _currentLocation.room = 0; + _currentLocation.view = 0; + + parseScrFile("universe.scr", universe); changeLocation('g', 'a', 'r', 'y', 0); + + _controlEvents.clear(); } void ScriptManager::update(uint deltaTimeMillis) { + if (_currentLocation != _nextLocation) { + ChangeLocationReal(false); + } + updateNodes(deltaTimeMillis); - checkPuzzleCriteria(); + if (!execScope(nodeview)) { + return; + } + if (!execScope(room)) { + return; + } + if (!execScope(world)) { + return; + } + if (!execScope(universe)) { + return; + } + updateControls(deltaTimeMillis); } -void ScriptManager::createReferenceTable() { - // Iterate through each local Puzzle - for (PuzzleList::iterator activePuzzleIter = _activePuzzles.begin(); activePuzzleIter != _activePuzzles.end(); ++activePuzzleIter) { - Puzzle *puzzlePtr = (*activePuzzleIter); +bool ScriptManager::execScope(ScriptScope &scope) { + // Swap queues + PuzzleList *tmp = scope.execQueue; + scope.execQueue = scope.scopeQueue; + scope.scopeQueue = tmp; + scope.scopeQueue->clear(); - // Iterate through each CriteriaEntry and add a reference from the criteria key to the Puzzle - for (Common::List<Common::List<Puzzle::CriteriaEntry> >::iterator criteriaIter = (*activePuzzleIter)->criteriaList.begin(); criteriaIter != (*activePuzzleIter)->criteriaList.end(); ++criteriaIter) { - for (Common::List<Puzzle::CriteriaEntry>::iterator entryIter = criteriaIter->begin(); entryIter != criteriaIter->end(); ++entryIter) { - _referenceTable[entryIter->key].push_back(puzzlePtr); + for (PuzzleList::iterator PuzzleIter = scope.puzzles.begin(); PuzzleIter != scope.puzzles.end(); ++PuzzleIter) { + (*PuzzleIter)->addedBySetState = false; + } - // If the argument is a key, add a reference to it as well - if (entryIter->argumentIsAKey) { - _referenceTable[entryIter->argument].push_back(puzzlePtr); - } + if (scope.procCount < 2 || getStateValue(StateKey_ExecScopeStyle)) { + for (PuzzleList::iterator PuzzleIter = scope.puzzles.begin(); PuzzleIter != scope.puzzles.end(); ++PuzzleIter) { + if (!checkPuzzleCriteria(*PuzzleIter, scope.procCount)) { + return false; + } + } + } else { + for (PuzzleList::iterator PuzzleIter = scope.execQueue->begin(); PuzzleIter != scope.execQueue->end(); ++PuzzleIter) { + if (!checkPuzzleCriteria(*PuzzleIter, scope.procCount)) { + return false; } } } - // Iterate through each global Puzzle - for (PuzzleList::iterator globalPuzzleIter = _globalPuzzles.begin(); globalPuzzleIter != _globalPuzzles.end(); ++globalPuzzleIter) { - Puzzle *puzzlePtr = (*globalPuzzleIter); - - // Iterate through each CriteriaEntry and add a reference from the criteria key to the Puzzle - for (Common::List<Common::List<Puzzle::CriteriaEntry> >::iterator criteriaIter = (*globalPuzzleIter)->criteriaList.begin(); criteriaIter != (*globalPuzzleIter)->criteriaList.end(); ++criteriaIter) { - for (Common::List<Puzzle::CriteriaEntry>::iterator entryIter = criteriaIter->begin(); entryIter != criteriaIter->end(); ++entryIter) { - _referenceTable[entryIter->key].push_back(puzzlePtr); + if (scope.procCount < 2) { + scope.procCount++; + } + return true; +} - // If the argument is a key, add a reference to it as well - if (entryIter->argumentIsAKey) { - _referenceTable[entryIter->argument].push_back(puzzlePtr); - } +void ScriptManager::referenceTableAddPuzzle(uint32 key, PuzzleRef ref) { + if (_referenceTable.contains(key)) { + Common::Array<PuzzleRef> *arr = &_referenceTable[key]; + for (uint32 i = 0; i < arr->size(); i++) { + if ((*arr)[i].puz == ref.puz) { + return; } } } - // Remove duplicate entries - for (PuzzleMap::iterator referenceTableIter = _referenceTable.begin(); referenceTableIter != _referenceTable.end(); ++referenceTableIter) { - removeDuplicateEntries(referenceTableIter->_value); + _referenceTable[key].push_back(ref); +} + +void ScriptManager::addPuzzlesToReferenceTable(ScriptScope &scope) { + // Iterate through each local Puzzle + for (PuzzleList::iterator PuzzleIter = scope.puzzles.begin(); PuzzleIter != scope.puzzles.end(); ++PuzzleIter) { + Puzzle *puzzlePtr = (*PuzzleIter); + + PuzzleRef ref; + ref.scope = &scope; + ref.puz = puzzlePtr; + + referenceTableAddPuzzle(puzzlePtr->key, ref); + + // Iterate through each CriteriaEntry and add a reference from the criteria key to the Puzzle + for (Common::List<Common::List<Puzzle::CriteriaEntry> >::iterator criteriaIter = (*PuzzleIter)->criteriaList.begin(); criteriaIter != (*PuzzleIter)->criteriaList.end(); ++criteriaIter) { + for (Common::List<Puzzle::CriteriaEntry>::iterator entryIter = criteriaIter->begin(); entryIter != criteriaIter->end(); ++entryIter) { + referenceTableAddPuzzle(entryIter->key, ref); + } + } } } void ScriptManager::updateNodes(uint deltaTimeMillis) { // If process() returns true, it means the node can be deleted - for (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end();) { + for (SideFXList::iterator iter = _activeSideFx.begin(); iter != _activeSideFx.end();) { if ((*iter)->process(deltaTimeMillis)) { - delete (*iter); + delete(*iter); // Remove the node - iter = _activeControls.erase(iter); + iter = _activeSideFx.erase(iter); } else { ++iter; } } } -void ScriptManager::checkPuzzleCriteria() { - while (!_puzzlesToCheck.empty()) { - Puzzle *puzzle = _puzzlesToCheck.pop(); +void ScriptManager::updateControls(uint deltaTimeMillis) { + if (!_activeControls) { + return; + } - // Check if the puzzle is already finished - // Also check that the puzzle isn't disabled - if (getStateValue(puzzle->key) == 1 && (getStateFlags(puzzle->key) & DISABLED) == 0) { - continue; + // Process only one event + if (!_controlEvents.empty()) { + Common::Event _event = _controlEvents.front(); + Common::Point imageCoord; + switch (_event.type) { + case Common::EVENT_LBUTTONDOWN: + imageCoord = _engine->getRenderManager()->screenSpaceToImageSpace(_event.mouse); + onMouseDown(_event.mouse, imageCoord); + break; + case Common::EVENT_LBUTTONUP: + imageCoord = _engine->getRenderManager()->screenSpaceToImageSpace(_event.mouse); + onMouseUp(_event.mouse, imageCoord); + break; + case Common::EVENT_KEYDOWN: + onKeyDown(_event.kbd); + break; + case Common::EVENT_KEYUP: + onKeyUp(_event.kbd); + break; + default: + break; } + _controlEvents.pop_front(); + } + + for (ControlList::iterator iter = _activeControls->begin(); iter != _activeControls->end(); iter++) { + if ((*iter)->process(deltaTimeMillis)) { + break; + } + } +} - // Check each Criteria +bool ScriptManager::checkPuzzleCriteria(Puzzle *puzzle, uint counter) { + // Check if the puzzle is already finished + // Also check that the puzzle isn't disabled + if (getStateValue(puzzle->key) == 1 || (getStateFlag(puzzle->key) & Puzzle::DISABLED)) { + return true; + } - bool criteriaMet = false; - for (Common::List<Common::List<Puzzle::CriteriaEntry> >::iterator criteriaIter = puzzle->criteriaList.begin(); criteriaIter != puzzle->criteriaList.end(); ++criteriaIter) { - criteriaMet = false; + // Check each Criteria + if (counter == 0 && (getStateFlag(puzzle->key) & Puzzle::DO_ME_NOW) == 0) { + return true; + } - for (Common::List<Puzzle::CriteriaEntry>::iterator entryIter = criteriaIter->begin(); entryIter != criteriaIter->end(); ++entryIter) { - // Get the value to compare against - uint argumentValue; - if (entryIter->argumentIsAKey) - argumentValue = getStateValue(entryIter->argument); - else - argumentValue = entryIter->argument; - - // Do the comparison - switch (entryIter->criteriaOperator) { - case Puzzle::EQUAL_TO: - criteriaMet = getStateValue(entryIter->key) == argumentValue; - break; - case Puzzle::NOT_EQUAL_TO: - criteriaMet = getStateValue(entryIter->key) != argumentValue; - break; - case Puzzle::GREATER_THAN: - criteriaMet = getStateValue(entryIter->key) > argumentValue; - break; - case Puzzle::LESS_THAN: - criteriaMet = getStateValue(entryIter->key) < argumentValue; - break; - } - - // If one check returns false, don't keep checking - if (!criteriaMet) { - break; - } + bool criteriaMet = false; + for (Common::List<Common::List<Puzzle::CriteriaEntry> >::iterator criteriaIter = puzzle->criteriaList.begin(); criteriaIter != puzzle->criteriaList.end(); ++criteriaIter) { + criteriaMet = false; + + for (Common::List<Puzzle::CriteriaEntry>::iterator entryIter = criteriaIter->begin(); entryIter != criteriaIter->end(); ++entryIter) { + // Get the value to compare against + int argumentValue; + if (entryIter->argumentIsAKey) { + argumentValue = getStateValue(entryIter->argument); + } else { + argumentValue = entryIter->argument; + } + + // Do the comparison + switch (entryIter->criteriaOperator) { + case Puzzle::EQUAL_TO: + criteriaMet = getStateValue(entryIter->key) == argumentValue; + break; + case Puzzle::NOT_EQUAL_TO: + criteriaMet = getStateValue(entryIter->key) != argumentValue; + break; + case Puzzle::GREATER_THAN: + criteriaMet = getStateValue(entryIter->key) > argumentValue; + break; + case Puzzle::LESS_THAN: + criteriaMet = getStateValue(entryIter->key) < argumentValue; + break; } - // If any of the Criteria are *fully* met, then execute the results - if (criteriaMet) { + // If one check returns false, don't keep checking + if (!criteriaMet) { break; } } - // criteriaList can be empty. Aka, the puzzle should be executed immediately - if (puzzle->criteriaList.empty() || criteriaMet) { - debug(1, "Puzzle %u criteria passed. Executing its ResultActions", puzzle->key); + // If any of the Criteria are *fully* met, then execute the results + if (criteriaMet) { + break; + } + } - // Set the puzzle as completed - setStateValue(puzzle->key, 1); + // criteriaList can be empty. Aka, the puzzle should be executed immediately + if (puzzle->criteriaList.empty() || criteriaMet) { + debug(1, "Puzzle %u criteria passed. Executing its ResultActions", puzzle->key); - bool shouldContinue = true; - for (Common::List<ResultAction *>::iterator resultIter = puzzle->resultActions.begin(); resultIter != puzzle->resultActions.end(); ++resultIter) { - shouldContinue = shouldContinue && (*resultIter)->execute(_engine); - if (!shouldContinue) { - break; - } - } + // Set the puzzle as completed + setStateValue(puzzle->key, 1); - if (!shouldContinue) { - break; + for (Common::List<ResultAction *>::iterator resultIter = puzzle->resultActions.begin(); resultIter != puzzle->resultActions.end(); ++resultIter) { + if (!(*resultIter)->execute()) { + return false; } } } + + return true; } void ScriptManager::cleanStateTable() { @@ -205,52 +287,102 @@ void ScriptManager::cleanStateTable() { } } -uint ScriptManager::getStateValue(uint32 key) { - if (_globalState.contains(key)) +void ScriptManager::cleanScriptScope(ScriptScope &scope) { + scope.privQueueOne.clear(); + scope.privQueueTwo.clear(); + scope.scopeQueue = &scope.privQueueOne; + scope.execQueue = &scope.privQueueTwo; + for (PuzzleList::iterator iter = scope.puzzles.begin(); iter != scope.puzzles.end(); ++iter) { + delete(*iter); + } + + scope.puzzles.clear(); + + for (ControlList::iterator iter = scope.controls.begin(); iter != scope.controls.end(); ++iter) { + delete(*iter); + } + + scope.controls.clear(); + + scope.procCount = 0; +} + +int ScriptManager::getStateValue(uint32 key) { + if (_globalState.contains(key)) { return _globalState[key]; - else + } else { return 0; + } } -void ScriptManager::setStateValue(uint32 key, uint value) { - _globalState[key] = value; - +void ScriptManager::queuePuzzles(uint32 key) { if (_referenceTable.contains(key)) { - for (Common::Array<Puzzle *>::iterator iter = _referenceTable[key].begin(); iter != _referenceTable[key].end(); ++iter) { - _puzzlesToCheck.push((*iter)); + Common::Array<PuzzleRef> *arr = &_referenceTable[key]; + for (int32 i = arr->size() - 1; i >= 0; i--) { + if (!(*arr)[i].puz->addedBySetState) { + (*arr)[i].scope->scopeQueue->push_back((*arr)[i].puz); + (*arr)[i].puz->addedBySetState = true; + } } } } -uint ScriptManager::getStateFlags(uint32 key) { - if (_globalStateFlags.contains(key)) +void ScriptManager::setStateValue(uint32 key, int value) { + if (value == 0) { + _globalState.erase(key); + } else { + _globalState[key] = value; + } + + queuePuzzles(key); +} + +void ScriptManager::setStateValueSilent(uint32 key, int value) { + if (value == 0) { + _globalState.erase(key); + } else { + _globalState[key] = value; + } +} + +uint ScriptManager::getStateFlag(uint32 key) { + if (_globalStateFlags.contains(key)) { return _globalStateFlags[key]; - else + } else { return 0; + } } -void ScriptManager::setStateFlags(uint32 key, uint flags) { - _globalStateFlags[key] = flags; +void ScriptManager::setStateFlag(uint32 key, uint value) { + queuePuzzles(key); - if (_referenceTable.contains(key)) { - for (Common::Array<Puzzle *>::iterator iter = _referenceTable[key].begin(); iter != _referenceTable[key].end(); ++iter) { - _puzzlesToCheck.push((*iter)); - } - } + _globalStateFlags[key] |= value; } -void ScriptManager::addToStateValue(uint32 key, uint valueToAdd) { - _globalState[key] += valueToAdd; +void ScriptManager::setStateFlagSilent(uint32 key, uint value) { + if (value == 0) { + _globalStateFlags.erase(key); + } else { + _globalStateFlags[key] = value; + } } -void ScriptManager::addControl(Control *control) { - _activeControls.push_back(control); +void ScriptManager::unsetStateFlag(uint32 key, uint value) { + queuePuzzles(key); + + if (_globalStateFlags.contains(key)) { + _globalStateFlags[key] &= ~value; + + if (_globalStateFlags[key] == 0) { + _globalStateFlags.erase(key); + } + } } Control *ScriptManager::getControl(uint32 key) { - for (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end(); ++iter) { + for (ControlList::iterator iter = _activeControls->begin(); iter != _activeControls->end(); ++iter) { if ((*iter)->getKey() == key) { - return (*iter); + return *iter; } } @@ -258,9 +390,15 @@ Control *ScriptManager::getControl(uint32 key) { } void ScriptManager::focusControl(uint32 key) { - for (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end(); ++iter) { + if (!_activeControls) { + return; + } + if (_currentlyFocusedControl == key) { + return; + } + for (ControlList::iterator iter = _activeControls->begin(); iter != _activeControls->end(); ++iter) { uint32 controlKey = (*iter)->getKey(); - + if (controlKey == key) { (*iter)->focus(); } else if (controlKey == _currentlyFocusedControl) { @@ -271,167 +409,415 @@ void ScriptManager::focusControl(uint32 key) { _currentlyFocusedControl = key; } +void ScriptManager::setFocusControlKey(uint32 key) { + _currentlyFocusedControl = key; +} + +void ScriptManager::addSideFX(ScriptingEffect *fx) { + _activeSideFx.push_back(fx); +} + +ScriptingEffect *ScriptManager::getSideFX(uint32 key) { + for (SideFXList::iterator iter = _activeSideFx.begin(); iter != _activeSideFx.end(); ++iter) { + if ((*iter)->getKey() == key) { + return (*iter); + } + } + + return nullptr; +} + +void ScriptManager::deleteSideFx(uint32 key) { + for (SideFXList::iterator iter = _activeSideFx.begin(); iter != _activeSideFx.end(); ++iter) { + if ((*iter)->getKey() == key) { + delete(*iter); + _activeSideFx.erase(iter); + break; + } + } +} + +void ScriptManager::stopSideFx(uint32 key) { + for (SideFXList::iterator iter = _activeSideFx.begin(); iter != _activeSideFx.end(); ++iter) { + if ((*iter)->getKey() == key) { + bool ret = (*iter)->stop(); + if (ret) { + delete(*iter); + _activeSideFx.erase(iter); + } + break; + } + } +} + +void ScriptManager::killSideFx(uint32 key) { + for (SideFXList::iterator iter = _activeSideFx.begin(); iter != _activeSideFx.end(); ++iter) { + if ((*iter)->getKey() == key) { + (*iter)->kill(); + delete(*iter); + _activeSideFx.erase(iter); + break; + } + } +} + +void ScriptManager::killSideFxType(ScriptingEffect::ScriptingEffectType type) { + for (SideFXList::iterator iter = _activeSideFx.begin(); iter != _activeSideFx.end();) { + if ((*iter)->getType() & type) { + (*iter)->kill(); + delete(*iter); + iter = _activeSideFx.erase(iter); + } else { + ++iter; + } + } +} + void ScriptManager::onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { - for (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end(); ++iter) { - (*iter)->onMouseDown(screenSpacePos, backgroundImageSpacePos); + if (!_activeControls) { + return; + } + for (ControlList::iterator iter = _activeControls->reverse_begin(); iter != _activeControls->end(); iter--) { + if ((*iter)->onMouseDown(screenSpacePos, backgroundImageSpacePos)) { + return; + } } } void ScriptManager::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { - for (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end(); ++iter) { - (*iter)->onMouseUp(screenSpacePos, backgroundImageSpacePos); + if (!_activeControls) { + return; + } + for (ControlList::iterator iter = _activeControls->reverse_begin(); iter != _activeControls->end(); iter--) { + if ((*iter)->onMouseUp(screenSpacePos, backgroundImageSpacePos)) { + return; + } } } bool ScriptManager::onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { - bool cursorWasChanged = false; - for (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end(); ++iter) { - cursorWasChanged = cursorWasChanged || (*iter)->onMouseMove(screenSpacePos, backgroundImageSpacePos); + if (!_activeControls) { + return false; } - return cursorWasChanged; + for (ControlList::iterator iter = _activeControls->reverse_begin(); iter != _activeControls->end(); iter--) { + if ((*iter)->onMouseMove(screenSpacePos, backgroundImageSpacePos)) { + return true; + } + } + + return false; } void ScriptManager::onKeyDown(Common::KeyState keyState) { - for (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end(); ++iter) { - (*iter)->onKeyDown(keyState); + if (!_activeControls) { + return; + } + for (ControlList::iterator iter = _activeControls->begin(); iter != _activeControls->end(); ++iter) { + if ((*iter)->onKeyDown(keyState)) { + return; + } } } void ScriptManager::onKeyUp(Common::KeyState keyState) { - for (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end(); ++iter) { - (*iter)->onKeyUp(keyState); + if (!_activeControls) { + return; + } + for (ControlList::iterator iter = _activeControls->begin(); iter != _activeControls->end(); ++iter) { + if ((*iter)->onKeyUp(keyState)) { + return; + } } } -void ScriptManager::changeLocation(char world, char room, char node, char view, uint32 offset) { - assert(world != 0); - debug(1, "Changing location to: %c %c %c %c %u", world, room, node, view, offset); +void ScriptManager::changeLocation(const Location &_newLocation) { + changeLocation(_newLocation.world, _newLocation.room, _newLocation.node, _newLocation.view, _newLocation.offset); +} - // Auto save - _engine->getSaveManager()->autoSave(); +void ScriptManager::changeLocation(char _world, char _room, char _node, char _view, uint32 offset) { + _nextLocation.world = _world; + _nextLocation.room = _room; + _nextLocation.node = _node; + _nextLocation.view = _view; + _nextLocation.offset = offset; + // If next location is 0000, return to the previous location. + if (_nextLocation == "0000") { + if (getStateValue(StateKey_World) != 'g' || getStateValue(StateKey_Room) != 'j') { + _nextLocation.world = getStateValue(StateKey_LastWorld); + _nextLocation.room = getStateValue(StateKey_LastRoom); + _nextLocation.node = getStateValue(StateKey_LastNode); + _nextLocation.view = getStateValue(StateKey_LastView); + _nextLocation.offset = getStateValue(StateKey_LastViewPos); + } else { + _nextLocation.world = getStateValue(StateKey_Menu_LastWorld); + _nextLocation.room = getStateValue(StateKey_Menu_LastRoom); + _nextLocation.node = getStateValue(StateKey_Menu_LastNode); + _nextLocation.view = getStateValue(StateKey_Menu_LastView); + _nextLocation.offset = getStateValue(StateKey_Menu_LastViewPos); + } + } +} - // Clear all the containers - _referenceTable.clear(); - _puzzlesToCheck.clear(); - for (PuzzleList::iterator iter = _activePuzzles.begin(); iter != _activePuzzles.end(); ++iter) { - delete (*iter); +void ScriptManager::ChangeLocationReal(bool isLoading) { + assert(_nextLocation.world != 0); + debug(1, "Changing location to: %c %c %c %c %u", _nextLocation.world, _nextLocation.room, _nextLocation.node, _nextLocation.view, _nextLocation.offset); + + const bool enteringMenu = (_nextLocation.world == 'g' && _nextLocation.room == 'j'); + const bool leavingMenu = (_currentLocation.world == 'g' && _currentLocation.room == 'j'); + const bool isSaveScreen = (enteringMenu && _nextLocation.node == 's' && _nextLocation.view == 'e'); + const bool isRestoreScreen = (enteringMenu && _nextLocation.node == 'r' && _nextLocation.view == 'e'); + + if (enteringMenu && !ConfMan.getBool("originalsaveload")) { + if (isSaveScreen || isRestoreScreen) { + // Hook up the ScummVM save/restore dialog + bool gameSavedOrLoaded = _engine->getSaveManager()->scummVMSaveLoadDialog(isSaveScreen); + if (!gameSavedOrLoaded || isSaveScreen) { + // Reload the current room + _nextLocation.world = _currentLocation.world; + _nextLocation.room = _currentLocation.room; + _nextLocation.node = _currentLocation.node; + _nextLocation.view = _currentLocation.view; + _nextLocation.offset = _currentLocation.offset; + + return; + } else { + _currentLocation.world = 'g'; + _currentLocation.room = '0'; + _currentLocation.node = '0'; + _currentLocation.view = '0'; + _currentLocation.offset = 0; + } + } } - _activePuzzles.clear(); - for (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end(); ++iter) { - delete (*iter); + + _engine->setRenderDelay(2); + + if (!leavingMenu) { + if (!isLoading && !enteringMenu) { + setStateValue(StateKey_LastWorld, getStateValue(StateKey_World)); + setStateValue(StateKey_LastRoom, getStateValue(StateKey_Room)); + setStateValue(StateKey_LastNode, getStateValue(StateKey_Node)); + setStateValue(StateKey_LastView, getStateValue(StateKey_View)); + setStateValue(StateKey_LastViewPos, getStateValue(StateKey_ViewPos)); + } else { + setStateValue(StateKey_Menu_LastWorld, getStateValue(StateKey_World)); + setStateValue(StateKey_Menu_LastRoom, getStateValue(StateKey_Room)); + setStateValue(StateKey_Menu_LastNode, getStateValue(StateKey_Node)); + setStateValue(StateKey_Menu_LastView, getStateValue(StateKey_View)); + setStateValue(StateKey_Menu_LastViewPos, getStateValue(StateKey_ViewPos)); + } } - _activeControls.clear(); - // Revert to the idle cursor - _engine->getCursorManager()->revertToIdle(); + if (enteringMenu) { + if (isSaveScreen && !leavingMenu) { + _engine->getSaveManager()->prepareSaveBuffer(); + } + } else { + if (leavingMenu) { + _engine->getSaveManager()->flushSaveBuffer(); + } + } - // Reset the background velocity - _engine->getRenderManager()->setBackgroundVelocity(0); + setStateValue(StateKey_World, _nextLocation.world); + setStateValue(StateKey_Room, _nextLocation.room); + setStateValue(StateKey_Node, _nextLocation.node); + setStateValue(StateKey_View, _nextLocation.view); + setStateValue(StateKey_ViewPos, _nextLocation.offset); - // Remove any alphaEntries - _engine->getRenderManager()->clearAlphaEntries(); + _referenceTable.clear(); + addPuzzlesToReferenceTable(universe); - // Clean the global state table - cleanStateTable(); + _engine->getMenuHandler()->setEnable(0xFFFF); - // Parse into puzzles and controls - Common::String fileName = Common::String::format("%c%c%c%c.scr", world, room, node, view); - parseScrFile(fileName); + if (_nextLocation.world != _currentLocation.world) { + cleanScriptScope(nodeview); + cleanScriptScope(room); + cleanScriptScope(world); - // Change the background position - _engine->getRenderManager()->setBackgroundPosition(offset); + Common::String fileName = Common::String::format("%c%c%c%c.scr", _nextLocation.world, _nextLocation.room, _nextLocation.node, _nextLocation.view); + parseScrFile(fileName, nodeview); + addPuzzlesToReferenceTable(nodeview); - // Enable all the controls - for (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end(); ++iter) { - (*iter)->enable(); + fileName = Common::String::format("%c%c.scr", _nextLocation.world, _nextLocation.room); + parseScrFile(fileName, room); + addPuzzlesToReferenceTable(room); + + fileName = Common::String::format("%c.scr", _nextLocation.world); + parseScrFile(fileName, world); + addPuzzlesToReferenceTable(world); + } else if (_nextLocation.room != _currentLocation.room) { + cleanScriptScope(nodeview); + cleanScriptScope(room); + + addPuzzlesToReferenceTable(world); + + Common::String fileName = Common::String::format("%c%c%c%c.scr", _nextLocation.world, _nextLocation.room, _nextLocation.node, _nextLocation.view); + parseScrFile(fileName, nodeview); + addPuzzlesToReferenceTable(nodeview); + + fileName = Common::String::format("%c%c.scr", _nextLocation.world, _nextLocation.room); + parseScrFile(fileName, room); + addPuzzlesToReferenceTable(room); + + } else if (_nextLocation.node != _currentLocation.node || _nextLocation.view != _currentLocation.view) { + cleanScriptScope(nodeview); + + addPuzzlesToReferenceTable(room); + addPuzzlesToReferenceTable(world); + + Common::String fileName = Common::String::format("%c%c%c%c.scr", _nextLocation.world, _nextLocation.room, _nextLocation.node, _nextLocation.view); + parseScrFile(fileName, nodeview); + addPuzzlesToReferenceTable(nodeview); } - // Add all the local puzzles to the queue to be checked - for (PuzzleList::iterator iter = _activePuzzles.begin(); iter != _activePuzzles.end(); ++iter) { - // Reset any Puzzles that have the flag ONCE_PER_INST - if ((getStateFlags((*iter)->key) & ONCE_PER_INST) == ONCE_PER_INST) { - setStateValue((*iter)->key, 0); - } + _activeControls = &nodeview.controls; + + // Revert to the idle cursor + _engine->getCursorManager()->changeCursor(CursorIndex_Idle); - _puzzlesToCheck.push((*iter)); + // Change the background position + _engine->getRenderManager()->setBackgroundPosition(_nextLocation.offset); + + if (_currentLocation == "0000") { + _currentLocation = _nextLocation; + execScope(world); + execScope(room); + execScope(nodeview); + } else if (_nextLocation.world != _currentLocation.world) { + _currentLocation = _nextLocation; + execScope(room); + execScope(nodeview); + } else if (_nextLocation.room != _currentLocation.room) { + _currentLocation = _nextLocation; + execScope(room); + execScope(nodeview); + } else if (_nextLocation.node != _currentLocation.node || _nextLocation.view != _currentLocation.view) { + _currentLocation = _nextLocation; + execScope(nodeview); } - // Add all the global puzzles to the queue to be checked - for (PuzzleList::iterator iter = _globalPuzzles.begin(); iter != _globalPuzzles.end(); ++iter) { - // Reset any Puzzles that have the flag ONCE_PER_INST - if ((getStateFlags((*iter)->key) & ONCE_PER_INST) == ONCE_PER_INST) { - setStateValue((*iter)->key, 0); - } + _engine->getRenderManager()->checkBorders(); +} - _puzzlesToCheck.push((*iter)); +void ScriptManager::serialize(Common::WriteStream *stream) { + stream->writeUint32BE(MKTAG('Z', 'N', 'S', 'G')); + stream->writeUint32LE(4); + stream->writeUint32LE(0); + stream->writeUint32BE(MKTAG('L', 'O', 'C', ' ')); + stream->writeUint32LE(8); + stream->writeByte(getStateValue(StateKey_World)); + stream->writeByte(getStateValue(StateKey_Room)); + stream->writeByte(getStateValue(StateKey_Node)); + stream->writeByte(getStateValue(StateKey_View)); + stream->writeUint32LE(getStateValue(StateKey_ViewPos)); + + for (SideFXList::iterator iter = _activeSideFx.begin(); iter != _activeSideFx.end(); ++iter) { + (*iter)->serialize(stream); } - // Create the puzzle reference table - createReferenceTable(); + stream->writeUint32BE(MKTAG('F', 'L', 'A', 'G')); - // Update _currentLocation - _currentLocation.world = world; - _currentLocation.room = room; - _currentLocation.node = node; - _currentLocation.view = view; - _currentLocation.offset = offset; -} + int32 slots = 20000; + if (_engine->getGameId() == GID_NEMESIS) { + slots = 30000; + } -void ScriptManager::serializeStateTable(Common::WriteStream *stream) { - // Write the number of state value entries - stream->writeUint32LE(_globalState.size()); + stream->writeUint32LE(slots * 2); - for (StateMap::iterator iter = _globalState.begin(); iter != _globalState.end(); ++iter) { - // Write out the key/value pair - stream->writeUint32LE(iter->_key); - stream->writeUint32LE(iter->_value); + for (int32 i = 0; i < slots; i++) { + stream->writeUint16LE(getStateFlag(i)); + } + + stream->writeUint32BE(MKTAG('P', 'U', 'Z', 'Z')); + + stream->writeUint32LE(slots * 2); + + for (int32 i = 0; i < slots; i++) { + stream->writeSint16LE(getStateValue(i)); } } -void ScriptManager::deserializeStateTable(Common::SeekableReadStream *stream) { +void ScriptManager::deserialize(Common::SeekableReadStream *stream) { // Clear out the current table values _globalState.clear(); + _globalStateFlags.clear(); - // Read the number of key/value pairs - uint32 numberOfPairs = stream->readUint32LE(); + cleanScriptScope(nodeview); + cleanScriptScope(room); + cleanScriptScope(world); - for (uint32 i = 0; i < numberOfPairs; ++i) { - uint32 key = stream->readUint32LE(); - uint32 value = stream->readUint32LE(); - // Directly access the state table so we don't trigger Puzzle checks - _globalState[key] = value; - } -} + _currentLocation.node = 0; + _currentLocation.world = 0; + _currentLocation.room = 0; + _currentLocation.view = 0; -void ScriptManager::serializeControls(Common::WriteStream *stream) { - // Count how many controls need to save their data - // Because WriteStream isn't seekable - uint32 numberOfControlsNeedingSerialization = 0; - for (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end(); ++iter) { - if ((*iter)->needsSerialization()) { - numberOfControlsNeedingSerialization++; - } + for (SideFXList::iterator iter = _activeSideFx.begin(); iter != _activeSideFx.end(); iter++) { + delete(*iter); } - stream->writeUint32LE(numberOfControlsNeedingSerialization); - for (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end(); ++iter) { - (*iter)->serialize(stream); + _activeSideFx.clear(); + + _referenceTable.clear(); + + if (stream->readUint32BE() != MKTAG('Z', 'N', 'S', 'G') || stream->readUint32LE() != 4) { + changeLocation('g', 'a', 'r', 'y', 0); + return; } -} -void ScriptManager::deserializeControls(Common::SeekableReadStream *stream) { - uint32 numberOfControls = stream->readUint32LE(); + stream->seek(4, SEEK_CUR); - for (uint32 i = 0; i < numberOfControls; ++i) { - uint32 key = stream->readUint32LE(); - for (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end(); ++iter) { - if ((*iter)->getKey() == key) { - (*iter)->deserialize(stream); - break; + if (stream->readUint32BE() != MKTAG('L', 'O', 'C', ' ') || stream->readUint32LE() != 8) { + changeLocation('g', 'a', 'r', 'y', 0); + return; + } + + Location nextLocation; + + nextLocation.world = stream->readByte(); + nextLocation.room = stream->readByte(); + nextLocation.node = stream->readByte(); + nextLocation.view = stream->readByte(); + nextLocation.offset = stream->readUint32LE() & 0x0000FFFF; + + while (stream->pos() < stream->size()) { + uint32 tag = stream->readUint32BE(); + uint32 tagSize = stream->readUint32LE(); + switch (tag) { + case MKTAG('T', 'I', 'M', 'R'): { + uint32 key = stream->readUint32LE(); + uint32 time = stream->readUint32LE(); + if (_engine->getGameId() == GID_GRANDINQUISITOR) { + time /= 100; + } else if (_engine->getGameId() == GID_NEMESIS) { + time /= 1000; + } + addSideFX(new TimerNode(_engine, key, time)); + } + break; + case MKTAG('F', 'L', 'A', 'G'): + for (uint32 i = 0; i < tagSize / 2; i++) { + setStateFlagSilent(i, stream->readUint16LE()); + } + break; + case MKTAG('P', 'U', 'Z', 'Z'): + for (uint32 i = 0; i < tagSize / 2; i++) { + setStateValueSilent(i, stream->readUint16LE()); } + break; + default: + stream->seek(tagSize, SEEK_CUR); } } + + _nextLocation = nextLocation; + + ChangeLocationReal(true); + + _engine->setRenderDelay(10); + setStateValue(StateKey_RestoreFlag, 1); + + _engine->loadSettings(); } Location ScriptManager::getCurrentLocation() const { @@ -441,4 +827,78 @@ Location ScriptManager::getCurrentLocation() const { return location; } +Location ScriptManager::getLastLocation() { + Location location; + location.world = getStateValue(StateKey_LastWorld); + location.room = getStateValue(StateKey_LastRoom); + location.node = getStateValue(StateKey_LastNode); + location.view = getStateValue(StateKey_LastView); + location.offset = getStateValue(StateKey_LastViewPos); + + return location; +} + +Location ScriptManager::getLastMenuLocation() { + Location location; + location.world = getStateValue(StateKey_Menu_LastWorld); + location.room = getStateValue(StateKey_Menu_LastRoom); + location.node = getStateValue(StateKey_Menu_LastNode); + location.view = getStateValue(StateKey_Menu_LastView); + location.offset = getStateValue(StateKey_Menu_LastViewPos); + + return location; +} + +void ScriptManager::addEvent(Common::Event event) { + _controlEvents.push_back(event); +} + +void ScriptManager::flushEvent(Common::EventType type) { + EventList::iterator it = _controlEvents.begin(); + while (it != _controlEvents.end()) { + + if ((*it).type == type) { + it = _controlEvents.erase(it); + } else { + it++; + } + } +} + +void ScriptManager::trimCommentsAndWhiteSpace(Common::String *string) const { + for (int i = string->size() - 1; i >= 0; i--) { + if ((*string)[i] == '#') { + string->erase(i); + } + } + + string->trim(); +} + +ValueSlot::ValueSlot(ScriptManager *scriptManager, const char *slotValue): + _scriptManager(scriptManager) { + value = 0; + slot = false; + const char *isSlot = strstr(slotValue, "["); + if (isSlot) { + slot = true; + value = atoi(isSlot + 1); + } else { + slot = false; + value = atoi(slotValue); + } +} +int16 ValueSlot::getValue() { + if (slot) { + if (value >= 0) { + return _scriptManager->getStateValue(value); + } + else { + return 0; + } + } else { + return value; + } +} + } // End of namespace ZVision diff --git a/engines/zvision/scripting/script_manager.h b/engines/zvision/scripting/script_manager.h index 4d1b1f359b..7c276bf917 100644 --- a/engines/zvision/scripting/script_manager.h +++ b/engines/zvision/scripting/script_manager.h @@ -8,12 +8,12 @@ * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. - * + * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. @@ -25,10 +25,11 @@ #include "zvision/scripting/puzzle.h" #include "zvision/scripting/control.h" +#include "zvision/scripting/scripting_effect.h" #include "common/hashmap.h" #include "common/queue.h" - +#include "common/events.h" namespace Common { class String; @@ -39,6 +40,69 @@ namespace ZVision { class ZVision; +enum StateKey { + StateKey_World = 3, + StateKey_Room = 4, + StateKey_Node = 5, + StateKey_View = 6, + StateKey_ViewPos = 7, + StateKey_KeyPress = 8, + StateKey_InventoryItem = 9, + StateKey_LMouse = 10, + StateKey_NotSet = 11, // This key doesn't set + StateKey_Rounds = 12, + StateKey_Venus = 13, + StateKey_RMouse = 18, + StateKey_MenuState = 19, + StateKey_RestoreFlag = 20, + StateKey_Quitting = 39, + StateKey_LastWorld = 40, + StateKey_LastRoom = 41, + StateKey_LastNode = 42, + StateKey_LastView = 43, + StateKey_LastViewPos = 44, + StateKey_Menu_LastWorld = 45, + StateKey_Menu_LastRoom = 46, + StateKey_Menu_LastNode = 47, + StateKey_Menu_LastView = 48, + StateKey_Menu_LastViewPos = 49, + StateKey_KbdRotateSpeed = 50, + StateKey_Subtitles = 51, + StateKey_StreamSkipKey = 52, + StateKey_RotateSpeed = 53, + StateKey_Volume = 56, + StateKey_Qsound = 57, + StateKey_VenusEnable = 58, + StateKey_HighQuality = 59, + StateKey_VideoLineSkip = 65, + StateKey_Platform = 66, + StateKey_InstallLevel = 67, + StateKey_CountryCode = 68, + StateKey_CPU = 69, + StateKey_MovieCursor = 70, + StateKey_NoTurnAnim = 71, + StateKey_WIN958 = 72, + StateKey_ShowErrorDlg = 73, + StateKey_DebugCheats = 74, + StateKey_JapanFonts = 75, + StateKey_ExecScopeStyle = 76, + StateKey_Brightness = 77, + StateKey_MPEGMovies = 78, + StateKey_EF9_R = 91, + StateKey_EF9_G = 92, + StateKey_EF9_B = 93, + StateKey_EF9_Speed = 94, + StateKey_Inv_Cnt_Slot = 100, + StateKey_Inv_1_Slot = 101, + StateKey_Inv_49_Slot = 149, + // ZGI only + StateKey_Inv_TotalSlots = 150, + StateKey_Inv_StartSlot = 151, + StateKey_Spell_1 = 191, + StateKey_Active_Spell = 205, + StateKey_Reversed_Spellbooc = 206 +}; + struct Location { Location() : world('g'), room('a'), node('r'), view('y'), offset(0) {} @@ -49,68 +113,121 @@ struct Location { uint32 offset; }; -typedef Common::HashMap<uint32, Common::Array<Puzzle *> > PuzzleMap; +inline bool operator==(const Location& lhs, const Location& rhs) { + return ( + lhs.world == rhs.world && + lhs.room == rhs.room && + lhs.node == rhs.node && + lhs.view == rhs.view + ); +} + +inline bool operator==(const Location& lhs, const char* rhs) { + Common::String lhsStr = Common::String::format("%c%c%c%c", lhs.world, lhs.room, lhs.node, lhs.view); + return lhsStr == rhs; +} + +inline bool operator!=(const Location& lhs, const Location& rhs) { + return !(lhs == rhs); +} + +inline bool operator!=(const Location& lhs, const char* rhs) { + return !(lhs == rhs); +} + typedef Common::List<Puzzle *> PuzzleList; typedef Common::Queue<Puzzle *> PuzzleQueue; typedef Common::List<Control *> ControlList; -typedef Common::HashMap<uint32, uint32> StateMap; -typedef Common::HashMap<uint32, uint> StateFlagMap; +typedef Common::HashMap<uint32, int32> StateMap; +typedef Common::List<ScriptingEffect *> SideFXList; +typedef Common::List<Common::Event> EventList; class ScriptManager { public: ScriptManager(ZVision *engine); ~ScriptManager(); -public: - enum StateFlags { - ONCE_PER_INST = 0x01, - DO_ME_NOW = 0x02, // Somewhat useless flag since anything that needs to be done immediately has no criteria - DISABLED = 0x04 - }; - private: ZVision *_engine; + + struct ScriptScope { + uint32 procCount; + + PuzzleList *scopeQueue; // For adding puzzles to queue + PuzzleList *execQueue; // Switch to it when execute + PuzzleList privQueueOne; + PuzzleList privQueueTwo; + + PuzzleList puzzles; + ControlList controls; + }; + + struct PuzzleRef { + Puzzle *puz; + ScriptScope *scope; + }; + + typedef Common::HashMap<uint32, Common::Array<PuzzleRef> > PuzzleMap; + /** * Holds the global state variable. Do NOT directly modify this. Use the accessors and * mutators getStateValue() and setStateValue(). This ensures that Puzzles that reference a * particular state key are checked after the key is modified. */ StateMap _globalState; - /** - * Holds the flags for the global states. This is used to enable/disable puzzles and/or - * controls as well as which puzzles should are allowed to be re-executed - */ - StateFlagMap _globalStateFlags; + /** Holds execute flags */ + StateMap _globalStateFlags; /** References _globalState keys to Puzzles */ PuzzleMap _referenceTable; - /** Holds the Puzzles that should be checked this frame */ - PuzzleQueue _puzzlesToCheck; - /** Holds the currently active puzzles */ - PuzzleList _activePuzzles; - /** Holds the global puzzles */ - PuzzleList _globalPuzzles; /** Holds the currently active controls */ - ControlList _activeControls; + ControlList *_activeControls; + + EventList _controlEvents; + + ScriptScope universe; + ScriptScope world; + ScriptScope room; + ScriptScope nodeview; + + /** Holds the currently active timers, musics, other */ + SideFXList _activeSideFx; Location _currentLocation; + Location _nextLocation; uint32 _currentlyFocusedControl; public: void initialize(); void update(uint deltaTimeMillis); + void queuePuzzles(uint32 key); - uint getStateValue(uint32 key); - void setStateValue(uint32 key, uint value); - void addToStateValue(uint32 key, uint valueToAdd); + int getStateValue(uint32 key); + void setStateValue(uint32 key, int value); - uint getStateFlags(uint32 key); - void setStateFlags(uint32 key, uint flags); + uint getStateFlag(uint32 key); + void setStateFlag(uint32 key, uint value); + void unsetStateFlag(uint32 key, uint value); void addControl(Control *control); Control *getControl(uint32 key); + void enableControl(uint32 key); + void disableControl(uint32 key); + void focusControl(uint32 key); + // Only change focus control without call focus/unfocus. + void setFocusControlKey(uint32 key); + + void addSideFX(ScriptingEffect *fx); + ScriptingEffect *getSideFX(uint32 key); + void deleteSideFx(uint32 key); + void stopSideFx(uint32 key); + void killSideFx(uint32 key); + void killSideFxType(ScriptingEffect::ScriptingEffectType type); + + void addEvent(Common::Event); + void flushEvent(Common::EventType type); /** * Called when LeftMouse is pushed. @@ -147,32 +264,61 @@ public: */ void onKeyUp(Common::KeyState keyState); + /** Mark next location */ void changeLocation(char world, char room, char node, char view, uint32 offset); + void changeLocation(const Location &_newLocation); - void serializeStateTable(Common::WriteStream *stream); - void deserializeStateTable(Common::SeekableReadStream *stream); - void serializeControls(Common::WriteStream *stream); - void deserializeControls(Common::SeekableReadStream *stream); + void serialize(Common::WriteStream *stream); + void deserialize(Common::SeekableReadStream *stream); Location getCurrentLocation() const; + Location getLastLocation(); + Location getLastMenuLocation(); + + /** + * Removes any line comments using '#' as a sequence start. + * Then removes any trailing and leading 'whitespace' using String::trim() + * Note: String::trim uses isspace() to determine what is whitespace and what is not. + * + * @param string The string to modify. It is modified in place + */ + void trimCommentsAndWhiteSpace(Common::String *string) const; private: - void createReferenceTable(); + void referenceTableAddPuzzle(uint32 key, PuzzleRef ref); + void addPuzzlesToReferenceTable(ScriptScope &scope); void updateNodes(uint deltaTimeMillis); - void checkPuzzleCriteria(); + void updateControls(uint deltaTimeMillis); + bool checkPuzzleCriteria(Puzzle *puzzle, uint counter); void cleanStateTable(); + void cleanScriptScope(ScriptScope &scope); + bool execScope(ScriptScope &scope); + + /** Perform change location */ + void ChangeLocationReal(bool isLoading); + + int8 inventoryGetCount(); + void inventorySetCount(int8 cnt); + int16 inventoryGetItem(int8 id); + void inventorySetItem(int8 id, int16 item); + + void setStateFlagSilent(uint32 key, uint value); + void setStateValueSilent(uint32 key, int value); -// TODO: Make this private. It was only made public so Console::cmdParseAllScrFiles() could use it public: + void inventoryAdd(int16 item); + void inventoryDrop(int16 item); + void inventoryCycle(); + +private: /** * Parses a script file into triggers and events * * @param fileName Name of the .scr file * @param isGlobal Are the puzzles included in the file global (true). AKA, the won't be purged during location changes */ - void parseScrFile(const Common::String &fileName, bool isGlobal = false); + void parseScrFile(const Common::String &fileName, ScriptScope &scope); -private: /** * Parses the stream into a Puzzle object * Helper method for parseScrFile. @@ -188,9 +334,10 @@ private: * * @param criteria Pointer to the Criteria object to fill * @param stream Scr file stream + * @param key Puzzle key (for workarounds) * @return Whether any criteria were read */ - bool parseCriteria(Common::SeekableReadStream &stream, Common::List<Common::List<Puzzle::CriteriaEntry> > &criteriaList) const; + bool parseCriteria(Common::SeekableReadStream &stream, Common::List<Common::List<Puzzle::CriteriaEntry> > &criteriaList, uint32 key) const; /** * Parses the stream into a ResultAction objects @@ -216,9 +363,18 @@ private: * @param line The line initially read * @param stream Scr file stream */ - void parseControl(Common::String &line, Common::SeekableReadStream &stream); + Control *parseControl(Common::String &line, Common::SeekableReadStream &stream); }; +class ValueSlot { +public: + ValueSlot(ScriptManager *scriptManager, const char *slotValue); + int16 getValue(); +private: + int16 value; + bool slot; + ScriptManager *_scriptManager; +}; } // End of namespace ZVision diff --git a/engines/zvision/scripting/scripting_effect.h b/engines/zvision/scripting/scripting_effect.h new file mode 100644 index 0000000000..2a2153204f --- /dev/null +++ b/engines/zvision/scripting/scripting_effect.h @@ -0,0 +1,124 @@ +/* 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 SCRIPTING_EFFECT_H_INCLUDED +#define SCRIPTING_EFFECT_H_INCLUDED + +namespace Common { +class SeekableReadStream; +struct Point; +class WriteStream; +} + +namespace ZVision { + +class ZVision; + +/** + * The base class that represents effects created from Actions. + * This class is virtual. + * + * Detailed Description: + * A scene has Controls. By interacting with the controls, the user + * causes Actions to execute. Certain Actions create 'effects', for + * example, a sound or an animation. This is the base class for + * those effects. + */ +class ScriptingEffect { +public: + + enum ScriptingEffectType { + SCRIPTING_EFFECT_ANIM = 1, + SCRIPTING_EFFECT_AUDIO = 2, + SCRIPTING_EFFECT_DISTORT = 4, + SCRIPTING_EFFECT_PANTRACK = 8, + SCRIPTING_EFFECT_REGION = 16, + SCRIPTING_EFFECT_TIMER = 32, + SCRIPTING_EFFECT_TTYTXT = 64, + SCRIPTING_EFFECT_UNKNOWN = 128, + SCRIPTING_EFFECT_ALL = 255 + }; + + ScriptingEffect() : _engine(0), _key(0), _type(SCRIPTING_EFFECT_UNKNOWN) {} + ScriptingEffect(ZVision *engine, uint32 key, ScriptingEffectType type) : _engine(engine), _key(key), _type(type) {} + virtual ~ScriptingEffect() {} + + uint32 getKey() { + return _key; + } + ScriptingEffectType getType() { + return _type; + } + + virtual bool process(uint32 deltaTimeInMillis) { + return false; + } + /** + * Serialize a SideFX for save game use. This should only be used if a SideFX needs + * to save values that would be different from initialization. AKA a TimerNode needs to + * store the amount of time left on the timer. Any Controls overriding this *MUST* write + * their key as the first data outputted. The default implementation is NOP. + * + * NOTE: If this method is overridden, you MUST also override deserialize() + * and needsSerialization() + * + * @param stream Stream to write any needed data to + */ + virtual void serialize(Common::WriteStream *stream) {} + /** + * De-serialize data from a save game stream. This should only be implemented if the + * SideFX also implements serialize(). The calling method assumes the size of the + * data read from the stream exactly equals that written in serialize(). The default + * implementation is NOP. + * + * NOTE: If this method is overridden, you MUST also override serialize() + * and needsSerialization() + * + * @param stream Save game file stream + */ + virtual void deserialize(Common::SeekableReadStream *stream) {} + /** + * If a SideFX overrides serialize() and deserialize(), this should return true + * + * @return Does the SideFX need save game serialization? + */ + virtual inline bool needsSerialization() { + return false; + } + + virtual bool stop() { + return true; + } + virtual void kill() {} + +protected: + ZVision *_engine; + uint32 _key; + ScriptingEffectType _type; + +// Static member functions +public: + +}; +} // End of namespace ZVision + +#endif // SCRIPTING_EFFECT_H_INCLUDED diff --git a/engines/zvision/sound/midi.cpp b/engines/zvision/sound/midi.cpp new file mode 100644 index 0000000000..3dd66ff2d4 --- /dev/null +++ b/engines/zvision/sound/midi.cpp @@ -0,0 +1,91 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/scummsys.h" +#include "common/textconsole.h" + +#include "zvision/sound/midi.h" + +namespace ZVision { + +MidiManager::MidiManager() { + MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB); + _driver = MidiDriver::createMidi(dev); + if (_driver->open()) + warning("Can't open MIDI, no MIDI output!"); +} + +MidiManager::~MidiManager() { + stop(); + _driver->close(); + delete _driver; +} + +void MidiManager::stop() { + for (int8 i = 0; i < 16; i++) + if (_playChannels[i].playing) + noteOff(i); +} + +void MidiManager::noteOn(int8 channel, int8 note, int8 velocity) { + assert(channel <= 15); + + _playChannels[channel].playing = true; + _playChannels[channel].note = note; + _driver->send(channel | (velocity << 16) | (note << 8) | 0x90); +} + +void MidiManager::noteOff(int8 channel) { + assert(channel <= 15); + + if (_playChannels[channel].playing) { + _playChannels[channel].playing = false; + _driver->send(channel | (_playChannels[channel].note << 8) | 0x80); + } +} + +int8 MidiManager::getFreeChannel() { + for (int8 i = 0; i < 16; i++) + if (!_playChannels[i].playing) + return i; + return -1; +} + +void MidiManager::setPan(int8 channel, int8 pan) { + assert(channel <= 15); + + _driver->send(channel | (pan << 16) | 0xAB0); +} + +void MidiManager::setVolume(int8 channel, int8 volume) { + assert(channel <= 15); + + _driver->send(channel | (volume << 16) | 0x7B0); +} + +void MidiManager::setProgram(int8 channel, int8 prog) { + assert(channel <= 15); + + _driver->send(channel | (prog << 8) | 0xC0); +} + +} // End of namespace ZVision diff --git a/engines/zvision/inventory/inventory_manager.h b/engines/zvision/sound/midi.h index f9d2ff294a..a3bac19636 100644 --- a/engines/zvision/inventory/inventory_manager.h +++ b/engines/zvision/sound/midi.h @@ -20,9 +20,40 @@ * */ -#ifndef ZVISION_INVENTORY_MANAGER_H -#define ZVISION_INVENTORY_MANAGER_H +#ifndef ZVISION_MIDI_H +#define ZVISION_MIDI_H -// TODO: Implement InventoryManager +#include "audio/mididrv.h" + +namespace ZVision { + +class MidiManager { +public: + MidiManager(); + ~MidiManager(); + + void stop(); + void noteOn(int8 channel, int8 noteNumber, int8 velocity); + void noteOff(int8 channel); + void setPan(int8 channel, int8 pan); + void setVolume(int8 channel, int8 volume); + void setProgram(int8 channel, int8 prog); + + int8 getFreeChannel(); + +protected: + + struct chan { + bool playing; + int8 note; + + chan() : playing(false), note(0) {}; + }; + + MidiDriver *_driver; + chan _playChannels[16]; +}; + +} #endif diff --git a/engines/zvision/sound/zork_raw.cpp b/engines/zvision/sound/zork_raw.cpp index edee1fd16e..7bdd4875fc 100644 --- a/engines/zvision/sound/zork_raw.cpp +++ b/engines/zvision/sound/zork_raw.cpp @@ -8,12 +8,12 @@ * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. - * + * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. @@ -21,85 +21,78 @@ */ #include "common/scummsys.h" - -#include "zvision/sound/zork_raw.h" - -#include "zvision/zvision.h" -#include "zvision/detection.h" -#include "zvision/utility/utility.h" - #include "common/file.h" #include "common/str.h" #include "common/stream.h" #include "common/memstream.h" #include "common/bufferedstream.h" #include "common/util.h" - +#include "common/tokenizer.h" #include "audio/audiostream.h" #include "audio/decoders/raw.h" +#include "zvision/sound/zork_raw.h" +#include "zvision/zvision.h" +#include "zvision/detection.h" namespace ZVision { -const int16 RawZorkStream::_stepAdjustmentTable[8] = {-1, -1, -1, 1, 4, 7, 10, 12}; - -const int32 RawZorkStream::_amplitudeLookupTable[89] = {0x0007, 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, - 0x0010, 0x0011, 0x0013, 0x0015, 0x0017, 0x0019, 0x001C, 0x001F, - 0x0022, 0x0025, 0x0029, 0x002D, 0x0032, 0x0037, 0x003C, 0x0042, - 0x0049, 0x0050, 0x0058, 0x0061, 0x006B, 0x0076, 0x0082, 0x008F, - 0x009D, 0x00AD, 0x00BE, 0x00D1, 0x00E6, 0x00FD, 0x0117, 0x0133, - 0x0151, 0x0173, 0x0198, 0x01C1, 0x01EE, 0x0220, 0x0256, 0x0292, - 0x02D4, 0x031C, 0x036C, 0x03C3, 0x0424, 0x048E, 0x0502, 0x0583, - 0x0610, 0x06AB, 0x0756, 0x0812, 0x08E0, 0x09C3, 0x0ABD, 0x0BD0, - 0x0CFF, 0x0E4C, 0x0FBA, 0x114C, 0x1307, 0x14EE, 0x1706, 0x1954, - 0x1BDC, 0x1EA5, 0x21B6, 0x2515, 0x28CA, 0x2CDF, 0x315B, 0x364B, - 0x3BB9, 0x41B2, 0x4844, 0x4F7E, 0x5771, 0x602F, 0x69CE, 0x7462, 0x7FFF}; - -const SoundParams RawZorkStream::_zNemSoundParamLookupTable[6] = {{'6', 0x2B11, false, false}, - {'a', 0x5622, false, true}, - {'b', 0x5622, true, true}, - {'n', 0x2B11, false, true}, - {'s', 0x5622, false, true}, - {'t', 0x5622, true, true} -}; - -const SoundParams RawZorkStream::_zgiSoundParamLookupTable[5] = {{'a',0x5622, false, false}, - {'k',0x2B11, true, true}, - {'p',0x5622, false, true}, - {'q',0x5622, true, true}, - {'u',0xAC44, true, true} -}; - -RawZorkStream::RawZorkStream(uint32 rate, bool stereo, DisposeAfterUse::Flag disposeStream, Common::SeekableReadStream *stream) - : _rate(rate), - _stereo(0), - _stream(stream, disposeStream), - _endOfData(false) { +const int16 RawChunkStream::_stepAdjustmentTable[8] = { -1, -1, -1, 1, 4, 7, 10, 12}; + +const int32 RawChunkStream::_amplitudeLookupTable[89] = {0x0007, 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, + 0x0010, 0x0011, 0x0013, 0x0015, 0x0017, 0x0019, 0x001C, 0x001F, + 0x0022, 0x0025, 0x0029, 0x002D, 0x0032, 0x0037, 0x003C, 0x0042, + 0x0049, 0x0050, 0x0058, 0x0061, 0x006B, 0x0076, 0x0082, 0x008F, + 0x009D, 0x00AD, 0x00BE, 0x00D1, 0x00E6, 0x00FD, 0x0117, 0x0133, + 0x0151, 0x0173, 0x0198, 0x01C1, 0x01EE, 0x0220, 0x0256, 0x0292, + 0x02D4, 0x031C, 0x036C, 0x03C3, 0x0424, 0x048E, 0x0502, 0x0583, + 0x0610, 0x06AB, 0x0756, 0x0812, 0x08E0, 0x09C3, 0x0ABD, 0x0BD0, + 0x0CFF, 0x0E4C, 0x0FBA, 0x114C, 0x1307, 0x14EE, 0x1706, 0x1954, + 0x1BDC, 0x1EA5, 0x21B6, 0x2515, 0x28CA, 0x2CDF, 0x315B, 0x364B, + 0x3BB9, 0x41B2, 0x4844, 0x4F7E, 0x5771, 0x602F, 0x69CE, 0x7462, 0x7FFF + }; + +RawChunkStream::RawChunkStream(bool stereo) { if (stereo) _stereo = 1; + else + _stereo = 0; + init(); +} + +void RawChunkStream::init() { _lastSample[0].index = 0; _lastSample[0].sample = 0; _lastSample[1].index = 0; _lastSample[1].sample = 0; +} - // Calculate the total playtime of the stream - if (stereo) - _playtime = Audio::Timestamp(0, _stream->size() / 2, rate); - else - _playtime = Audio::Timestamp(0, _stream->size(), rate); +RawChunkStream::RawChunk RawChunkStream::readNextChunk(Common::SeekableReadStream *stream) { + RawChunk tmp; + tmp.size = 0; + tmp.data = NULL; + + if (!stream || stream->size() == 0 || stream->eos()) + return tmp; + + tmp.size = (stream->size() - stream->pos()) * 2; + tmp.data = (int16 *)calloc(tmp.size, 1); + + readBuffer(tmp.data, stream, stream->size() - stream->pos()); + + return tmp; } -int RawZorkStream::readBuffer(int16 *buffer, const int numSamples) { - int bytesRead = 0; +int RawChunkStream::readBuffer(int16 *buffer, Common::SeekableReadStream *stream, const int numSamples) { + int32 bytesRead = 0; // 0: Left, 1: Right uint channel = 0; while (bytesRead < numSamples) { - byte encodedSample = _stream->readByte(); - if (_stream->eos()) { - _endOfData = true; + byte encodedSample = stream->readByte(); + if (stream->eos()) { return bytesRead; } bytesRead++; @@ -140,6 +133,90 @@ int RawZorkStream::readBuffer(int16 *buffer, const int numSamples) { // Increment and wrap the channel channel = (channel + 1) & _stereo; } + return bytesRead; +} + +const SoundParams RawZorkStream::_zNemSoundParamLookupTable[32] = {{'0', 0x1F40, false, false, false}, + {'1', 0x1F40, true, false, false}, + {'2', 0x1F40, false, false, true}, + {'3', 0x1F40, true, false, true}, + {'4', 0x2B11, false, false, false}, + {'5', 0x2B11, true, false, false}, + {'6', 0x2B11, false, false, true}, + {'7', 0x2B11, true, false, true}, + {'8', 0x5622, false, false, false}, + {'9', 0x5622, true, false, false}, + {'a', 0x5622, false, false, true}, + {'b', 0x5622, true, false, true}, + {'c', 0xAC44, false, false, false}, + {'d', 0xAC44, true, false, false}, + {'e', 0xAC44, false, false, true}, + {'f', 0xAC44, true, false, true}, + {'g', 0x1F40, false, true, false}, + {'h', 0x1F40, true, true, false}, + {'j', 0x1F40, false, true, true}, + {'k', 0x1F40, true, true, true}, + {'l', 0x2B11, false, true, false}, + {'m', 0x2B11, true, true, false}, + {'n', 0x2B11, false, true, true}, + {'p', 0x2B11, true, true, true}, + {'q', 0x5622, false, true, false}, + {'r', 0x5622, true, true, false}, + {'s', 0x5622, false, true, true}, + {'t', 0x5622, true, true, true}, + {'u', 0xAC44, false, true, false}, + {'v', 0xAC44, true, true, false}, + {'w', 0xAC44, false, true, true}, + {'x', 0xAC44, true, true, true} +}; + +const SoundParams RawZorkStream::_zgiSoundParamLookupTable[24] = {{'4', 0x2B11, false, false, false}, + {'5', 0x2B11, true, false, false}, + {'6', 0x2B11, false, false, true}, + {'7', 0x2B11, true, false, true}, + {'8', 0x5622, false, false, false}, + {'9', 0x5622, true, false, false}, + {'a', 0x5622, false, false, true}, + {'b', 0x5622, true, false, true}, + {'c', 0xAC44, false, false, false}, + {'d', 0xAC44, true, false, false}, + {'e', 0xAC44, false, false, true}, + {'f', 0xAC44, true, false, true}, + {'g', 0x2B11, false, true, false}, + {'h', 0x2B11, true, true, false}, + {'j', 0x2B11, false, true, true}, + {'k', 0x2B11, true, true, true}, + {'m', 0x5622, false, true, false}, + {'n', 0x5622, true, true, false}, + {'p', 0x5622, false, true, true}, + {'q', 0x5622, true, true, true}, + {'r', 0xAC44, false, true, false}, + {'s', 0xAC44, true, true, false}, + {'t', 0xAC44, false, true, true}, + {'u', 0xAC44, true, true, true} +}; + +RawZorkStream::RawZorkStream(uint32 rate, bool stereo, DisposeAfterUse::Flag disposeStream, Common::SeekableReadStream *stream) + : _rate(rate), + _stereo(0), + _stream(stream, disposeStream), + _endOfData(false), + _streamReader(stereo) { + if (stereo) + _stereo = 1; + + // Calculate the total playtime of the stream + if (stereo) + _playtime = Audio::Timestamp(0, _stream->size() / 2, rate); + else + _playtime = Audio::Timestamp(0, _stream->size(), rate); +} + +int RawZorkStream::readBuffer(int16 *buffer, const int numSamples) { + int32 bytesRead = _streamReader.readBuffer(buffer, _stream.get(), numSamples); + + if (_stream->eos()) + _endOfData = true; return bytesRead; } @@ -148,69 +225,78 @@ bool RawZorkStream::rewind() { _stream->seek(0, 0); _stream->clearErr(); _endOfData = false; - _lastSample[0].index = 0; - _lastSample[0].sample = 0; - _lastSample[1].index = 0; - _lastSample[1].sample = 0; + _streamReader.init(); return true; } Audio::RewindableAudioStream *makeRawZorkStream(Common::SeekableReadStream *stream, - int rate, - bool stereo, - DisposeAfterUse::Flag disposeAfterUse) { + int rate, + bool stereo, + DisposeAfterUse::Flag disposeAfterUse) { if (stereo) assert(stream->size() % 2 == 0); return new RawZorkStream(rate, stereo, disposeAfterUse, stream); } -Audio::RewindableAudioStream *makeRawZorkStream(const byte *buffer, uint32 size, - int rate, - bool stereo, - DisposeAfterUse::Flag disposeAfterUse) { - return makeRawZorkStream(new Common::MemoryReadStream(buffer, size, disposeAfterUse), rate, stereo, DisposeAfterUse::YES); -} - Audio::RewindableAudioStream *makeRawZorkStream(const Common::String &filePath, ZVision *engine) { Common::File *file = new Common::File(); - assert(file->open(filePath)); + Common::String actualName = filePath; + bool found = engine->getSearchManager()->openFile(*file, actualName); + bool isRaw = actualName.hasSuffix(".raw"); + + if ((!found && isRaw) || (found && isRaw && file->size() < 10)) { + if (found) + file->close(); + + // Check for an audio patch (.src) + actualName.setChar('s', actualName.size() - 3); + actualName.setChar('r', actualName.size() - 2); + actualName.setChar('c', actualName.size() - 1); + + if (!engine->getSearchManager()->openFile(*file, actualName)) + return NULL; + } else if (!found && !isRaw) { + return NULL; + } + + // Get the file name + Common::StringTokenizer tokenizer(actualName, "/\\"); + Common::String fileName; + while (!tokenizer.empty()) { + fileName = tokenizer.nextToken(); + } - Common::String fileName = getFileName(filePath); fileName.toLowercase(); - SoundParams soundParams = { ' ', 0, false, false }; - bool foundParams = false; - char fileIdentifier = (engine->getGameId() == GID_NEMESIS) ? fileName[6] : fileName[7]; + const SoundParams *soundParams = NULL; if (engine->getGameId() == GID_NEMESIS) { - for (uint i = 0; i < ARRAYSIZE(RawZorkStream::_zNemSoundParamLookupTable); ++i) { - if (RawZorkStream::_zNemSoundParamLookupTable[i].identifier == fileIdentifier) { - soundParams = RawZorkStream::_zNemSoundParamLookupTable[i]; - foundParams = true; - } + for (int i = 0; i < 32; ++i) { + if (RawZorkStream::_zNemSoundParamLookupTable[i].identifier == (fileName[6])) + soundParams = &RawZorkStream::_zNemSoundParamLookupTable[i]; } } else if (engine->getGameId() == GID_GRANDINQUISITOR) { - for (uint i = 0; i < ARRAYSIZE(RawZorkStream::_zgiSoundParamLookupTable); ++i) { - if (RawZorkStream::_zgiSoundParamLookupTable[i].identifier == fileIdentifier) { - soundParams = RawZorkStream::_zgiSoundParamLookupTable[i]; - foundParams = true; - } + for (int i = 0; i < 24; ++i) { + if (RawZorkStream::_zgiSoundParamLookupTable[i].identifier == (fileName[7])) + soundParams = &RawZorkStream::_zgiSoundParamLookupTable[i]; } } - if (!foundParams) - error("Unable to find sound params for file '%s'. File identifier is '%c'", filePath.c_str(), fileIdentifier); + if (soundParams == NULL) + return NULL; - if (soundParams.packed) { - return makeRawZorkStream(wrapBufferedSeekableReadStream(file, 2048, DisposeAfterUse::YES), soundParams.rate, soundParams.stereo, DisposeAfterUse::YES); + if (soundParams->packed) { + return makeRawZorkStream(wrapBufferedSeekableReadStream(file, 2048, DisposeAfterUse::YES), soundParams->rate, soundParams->stereo, DisposeAfterUse::YES); } else { byte flags = 0; - if (soundParams.stereo) + if (soundParams->bits16) + flags |= Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN; + if (soundParams->stereo) flags |= Audio::FLAG_STEREO; - return Audio::makeRawStream(file, soundParams.rate, flags, DisposeAfterUse::YES); + return Audio::makeRawStream(file, soundParams->rate, flags, DisposeAfterUse::YES); } } diff --git a/engines/zvision/sound/zork_raw.h b/engines/zvision/sound/zork_raw.h index ef98e3e1ef..892bad4d5f 100644 --- a/engines/zvision/sound/zork_raw.h +++ b/engines/zvision/sound/zork_raw.h @@ -8,12 +8,12 @@ * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. - * + * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. @@ -25,7 +25,6 @@ #include "audio/audiostream.h" - namespace Common { class SeekableReadStream; } @@ -39,27 +38,19 @@ struct SoundParams { uint32 rate; bool stereo; bool packed; + bool bits16; }; /** - * This is a stream, which allows for playing raw ADPCM data from a stream. + * This is a ADPCM stream-reader, this class holds context for multi-chunk reading and no buffers. */ -class RawZorkStream : public Audio::RewindableAudioStream { +class RawChunkStream { public: - RawZorkStream(uint32 rate, bool stereo, DisposeAfterUse::Flag disposeStream, Common::SeekableReadStream *stream); + RawChunkStream(bool stereo); - ~RawZorkStream() { + ~RawChunkStream() { } - -public: - static const SoundParams _zNemSoundParamLookupTable[6]; - static const SoundParams _zgiSoundParamLookupTable[5]; - private: - const int _rate; // Sample rate of stream - Audio::Timestamp _playtime; // Calculated total play time - Common::DisposablePtr<Common::SeekableReadStream> _stream; // Stream to read data from - bool _endOfData; // Whether the stream end has been reached uint _stereo; /** @@ -75,30 +66,61 @@ private: static const int32 _amplitudeLookupTable[89]; public: - int readBuffer(int16 *buffer, const int numSamples); - bool isStereo() const { return true; } - bool endOfData() const { return _endOfData; } + struct RawChunk { + int16 *data; + uint32 size; + }; - int getRate() const { return _rate; } - Audio::Timestamp getLength() const { return _playtime; } - - bool rewind(); + void init(); + //Read next audio portion in new stream (needed for avi), return structure with buffer + RawChunk readNextChunk(Common::SeekableReadStream *stream); + //Read numSamples from stream to buffer + int readBuffer(int16 *buffer, Common::SeekableReadStream *stream, const int numSamples); }; /** - * Creates an audio stream, which plays from the given buffer. - * - * @param buffer Buffer to play from. - * @param size Size of the buffer in bytes. - * @param rate Rate of the sound data. - * @param dispose AfterUse Whether to free the buffer after use (with free!). - * @return The new SeekableAudioStream (or 0 on failure). + * This is a stream, which allows for playing raw ADPCM data from a stream. */ -Audio::RewindableAudioStream *makeRawZorkStream(const byte *buffer, uint32 size, - int rate, - bool stereo, - DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES); +class RawZorkStream : public Audio::RewindableAudioStream { +public: + RawZorkStream(uint32 rate, bool stereo, DisposeAfterUse::Flag disposeStream, Common::SeekableReadStream *stream); + + ~RawZorkStream() { + } + +public: + static const SoundParams _zNemSoundParamLookupTable[32]; + static const SoundParams _zgiSoundParamLookupTable[24]; + +private: + const int _rate; // Sample rate of stream + Audio::Timestamp _playtime; // Calculated total play time + Common::DisposablePtr<Common::SeekableReadStream> _stream; // Stream to read data from + bool _endOfData; // Whether the stream end has been reached + uint _stereo; + + RawChunkStream _streamReader; + +public: + int readBuffer(int16 *buffer, const int numSamples); + + bool isStereo() const { + return _stereo; + } + bool endOfData() const { + return _endOfData; + } + + int getRate() const { + return _rate; + } + Audio::Timestamp getLength() const { + return _playtime; + } + + bool rewind(); +}; /** * Creates an audio stream, which plays from the given stream. @@ -109,9 +131,9 @@ Audio::RewindableAudioStream *makeRawZorkStream(const byte *buffer, uint32 size, * @return The new SeekableAudioStream (or 0 on failure). */ Audio::RewindableAudioStream *makeRawZorkStream(Common::SeekableReadStream *stream, - int rate, - bool stereo, - DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES); + int rate, + bool stereo, + DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES); Audio::RewindableAudioStream *makeRawZorkStream(const Common::String &filePath, ZVision *engine); diff --git a/engines/zvision/strings/string_manager.cpp b/engines/zvision/strings/string_manager.cpp deleted file mode 100644 index 22331d8a24..0000000000 --- a/engines/zvision/strings/string_manager.cpp +++ /dev/null @@ -1,255 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#include "common/scummsys.h" - -#include "zvision/strings/string_manager.h" - -#include "zvision/fonts/truetype_font.h" - -#include "common/file.h" -#include "common/tokenizer.h" -#include "common/debug.h" - -#include "graphics/fontman.h" -#include "graphics/colormasks.h" - - -namespace ZVision { - -StringManager::StringManager(ZVision *engine) - : _engine(engine) { -} - -StringManager::~StringManager() { - for (Common::HashMap<Common::String, TruetypeFont *>::iterator iter = _fonts.begin(); iter != _fonts.end(); ++iter) { - delete iter->_value; - } -} - -void StringManager::initialize(ZVisionGameId gameId) { - if (gameId == GID_NEMESIS) { - // TODO: Check this hardcoded filename against all versions of Nemesis - parseStrFile("nemesis.str"); - } else if (gameId == GID_GRANDINQUISITOR) { - // TODO: Check this hardcoded filename against all versions of Grand Inquisitor - parseStrFile("inquis.str"); - } -} - -void StringManager::parseStrFile(const Common::String &fileName) { - Common::File file; - if (!file.open(fileName)) { - warning("%s does not exist. String parsing failed", fileName.c_str()); - return; - } - - uint lineNumber = 0; - while (!file.eos()) { - _lastStyle.align = Graphics::kTextAlignLeft; - _lastStyle.color = 0; - _lastStyle.font = nullptr; - - Common::String asciiLine = readWideLine(file); - if (asciiLine.empty()) { - continue; - } - - char tagString[150]; - uint tagStringCursor = 0; - char textString[150]; - uint textStringCursor = 0; - bool inTag = false; - - for (uint i = 0; i < asciiLine.size(); ++i) { - switch (asciiLine[i]) { - case '<': - inTag = true; - if (!_inGameText[lineNumber].fragments.empty()) { - _inGameText[lineNumber].fragments.back().text = Common::String(textString, textStringCursor); - textStringCursor = 0; - } - break; - case '>': - inTag = false; - parseTag(Common::String(tagString, tagStringCursor), lineNumber); - tagStringCursor = 0; - break; - default: - if (inTag) { - tagString[tagStringCursor] = asciiLine[i]; - tagStringCursor++; - } else { - textString[textStringCursor] = asciiLine[i]; - textStringCursor++; - } - break; - } - } - - if (textStringCursor > 0) { - _inGameText[lineNumber].fragments.back().text = Common::String(textString, textStringCursor); - } - - lineNumber++; - assert(lineNumber <= NUM_TEXT_LINES); - } -} - -void StringManager::parseTag(const Common::String &tagString, uint lineNumber) { - Common::StringTokenizer tokenizer(tagString); - - Common::String token = tokenizer.nextToken(); - - Common::String fontName; - bool bold = false; - Graphics::TextAlign align = _lastStyle.align; - int point = _lastStyle.font != nullptr ? _lastStyle.font->_fontHeight : 12; - int red = 0; - int green = 0; - int blue = 0; - - while (!token.empty()) { - if (token.matchString("font", true)) { - fontName = tokenizer.nextToken(); - } else if (token.matchString("bold", true)) { - token = tokenizer.nextToken(); - if (token.matchString("on", false)) { - bold = true; - } - } else if (token.matchString("justify", true)) { - token = tokenizer.nextToken(); - if (token.matchString("center", false)) { - align = Graphics::kTextAlignCenter; - } else if (token.matchString("right", false)) { - align = Graphics::kTextAlignRight; - } - } else if (token.matchString("point", true)) { - point = atoi(tokenizer.nextToken().c_str()); - } else if (token.matchString("red", true)) { - red = atoi(tokenizer.nextToken().c_str()); - } else if (token.matchString("green", true)) { - green = atoi(tokenizer.nextToken().c_str()); - } else if (token.matchString("blue", true)) { - blue = atoi(tokenizer.nextToken().c_str()); - } - - token = tokenizer.nextToken(); - } - - TextFragment fragment; - - if (fontName.empty()) { - fragment.style.font = _lastStyle.font; - } else { - Common::String newFontName; - if (fontName.matchString("*times new roman*", true)) { - if (bold) { - newFontName = "timesbd.ttf"; - } else { - newFontName = "times.ttf"; - } - } else if (fontName.matchString("*courier new*", true)) { - if (bold) { - newFontName = "courbd.ttf"; - } else { - newFontName = "cour.ttf"; - } - } else if (fontName.matchString("*century schoolbook*", true)) { - if (bold) { - newFontName = "censcbkbd.ttf"; - } else { - newFontName = "censcbk.ttf"; - } - } else if (fontName.matchString("*garamond*", true)) { - if (bold) { - newFontName = "garabd.ttf"; - } else { - newFontName = "gara.ttf"; - } - } else { - debug("Could not identify font: %s. Reverting to Arial", fontName.c_str()); - if (bold) { - newFontName = "zorknorm.ttf"; - } else { - newFontName = "arial.ttf"; - } - } - - Common::String fontKey = Common::String::format("%s-%d", newFontName.c_str(), point); - if (_fonts.contains(fontKey)) { - fragment.style.font = _fonts[fontKey]; - } else { - fragment.style.font = new TruetypeFont(_engine, point); - fragment.style.font->loadFile(newFontName); - _fonts[fontKey] = fragment.style.font; - } - } - - fragment.style.align = align; - fragment.style.color = Graphics::ARGBToColor<Graphics::ColorMasks<565> >(0, red, green, blue); - _inGameText[lineNumber].fragments.push_back(fragment); - - _lastStyle = fragment.style; -} - -Common::String StringManager::readWideLine(Common::SeekableReadStream &stream) { - Common::String asciiString; - - // Don't spam the user with warnings about UTF-16 support. - // Just do one warning per String - bool charOverflowWarning = false; - - uint16 value = stream.readUint16LE(); - while (!stream.eos()) { - // Check for CRLF - if (value == 0x0A0D) { - // Read in the extra NULL char - stream.readByte(); // \0 - // End of the line. Break - break; - } - - // Crush each octet pair to a single octet with a simple cast - if (value > 255) { - charOverflowWarning = true; - value = '?'; - } - char charValue = (char)value; - - asciiString += charValue; - - value = stream.readUint16LE(); - } - - if (charOverflowWarning) { - warning("UTF-16 is not supported. Characters greater than 255 are replaced with '?'"); - } - - return asciiString; -} - -StringManager::TextStyle StringManager::getTextStyle(uint stringNumber) { - return _inGameText[stringNumber].fragments.front().style; -} - -} // End of namespace ZVision diff --git a/engines/zvision/text/string_manager.cpp b/engines/zvision/text/string_manager.cpp new file mode 100644 index 0000000000..c62e18f4b0 --- /dev/null +++ b/engines/zvision/text/string_manager.cpp @@ -0,0 +1,70 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/scummsys.h" +#include "common/file.h" +#include "common/tokenizer.h" +#include "common/debug.h" +#include "graphics/fontman.h" +#include "graphics/colormasks.h" + +#include "zvision/zvision.h" +#include "zvision/file/search_manager.h" +#include "zvision/text/string_manager.h" +#include "zvision/text/text.h" + +namespace ZVision { + +StringManager::StringManager(ZVision *engine) + : _engine(engine) { +} + +StringManager::~StringManager() { + +} + +void StringManager::initialize(ZVisionGameId gameId) { + if (gameId == GID_NEMESIS) + loadStrFile("nemesis.str"); + else if (gameId == GID_GRANDINQUISITOR) + loadStrFile("inquis.str"); +} + +void StringManager::loadStrFile(const Common::String &fileName) { + Common::File file; + if (!_engine->getSearchManager()->openFile(file, fileName)) + error("%s does not exist. String parsing failed", fileName.c_str()); + + uint lineNumber = 0; + while (!file.eos()) { + _lines[lineNumber] = readWideLine(file); + + lineNumber++; + assert(lineNumber <= NUM_TEXT_LINES); + } +} + +const Common::String StringManager::getTextLine(uint stringNumber) { + return _lines[stringNumber]; +} + +} // End of namespace ZVision diff --git a/engines/zvision/strings/string_manager.h b/engines/zvision/text/string_manager.h index af8324b890..f4564ee1ec 100644 --- a/engines/zvision/strings/string_manager.h +++ b/engines/zvision/text/string_manager.h @@ -24,8 +24,7 @@ #define ZVISION_STRING_MANAGER_H #include "zvision/detection.h" -#include "zvision/fonts/truetype_font.h" - +#include "zvision/text/truetype_font.h" namespace Graphics { class FontManager; @@ -41,42 +40,28 @@ public: ~StringManager(); public: - struct TextStyle { - TruetypeFont *font; - uint16 color; // In RBG 565 - Graphics::TextAlign align; - }; - - struct TextFragment { - TextStyle style; - Common::String text; + enum { + ZVISION_STR_SAVEEXIST = 23, + ZVISION_STR_SAVED = 4, + ZVISION_STR_SAVEEMPTY = 21, + ZVISION_STR_EXITPROMT = 6 }; private: - struct InGameText { - Common::List<TextFragment> fragments; - }; - enum { NUM_TEXT_LINES = 56 // Max number of lines in a .str file. We hardcode this number because we know ZNem uses 42 strings and ZGI uses 56 }; private: ZVision *_engine; - InGameText _inGameText[NUM_TEXT_LINES]; - Common::HashMap<Common::String, TruetypeFont *> _fonts; - - TextStyle _lastStyle; + Common::String _lines[NUM_TEXT_LINES]; public: void initialize(ZVisionGameId gameId); - StringManager::TextStyle getTextStyle(uint stringNumber); + const Common::String getTextLine(uint stringNumber); private: - void parseStrFile(const Common::String &fileName); - void parseTag(const Common::String &tagString, uint lineNumber); - - static Common::String readWideLine(Common::SeekableReadStream &stream); + void loadStrFile(const Common::String &fileName); }; } // End of namespace ZVision diff --git a/engines/zvision/text/subtitles.cpp b/engines/zvision/text/subtitles.cpp new file mode 100644 index 0000000000..ffc9e2b808 --- /dev/null +++ b/engines/zvision/text/subtitles.cpp @@ -0,0 +1,115 @@ +/* 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 "zvision/graphics/render_manager.h" +#include "zvision/text/subtitles.h" +#include "zvision/file/search_manager.h" +#include "zvision/text/text.h" + +namespace ZVision { + +Subtitle::Subtitle(ZVision *engine, const Common::String &subname, bool upscaleToHires) : + _engine(engine), + _areaId(-1), + _subId(-1) { + Common::File file; + if (_engine->getSearchManager()->openFile(file, subname)) { + while (!file.eos()) { + Common::String str = file.readLine(); + if (str.lastChar() == '~') + str.deleteLastChar(); + + if (str.matchString("*Initialization*", true)) { + // Not used + } else if (str.matchString("*Rectangle*", true)) { + int32 x1, y1, x2, y2; + sscanf(str.c_str(), "%*[^:]:%d %d %d %d", &x1, &y1, &x2, &y2); + Common::Rect rct = Common::Rect(x1, y1, x2, y2); + if (upscaleToHires) + _engine->getRenderManager()->upscaleRect(rct); + _areaId = _engine->getRenderManager()->createSubArea(rct); + } else if (str.matchString("*TextFile*", true)) { + char filename[64]; + sscanf(str.c_str(), "%*[^:]:%s", filename); + Common::File txt; + if (_engine->getSearchManager()->openFile(txt, filename)) { + while (!txt.eos()) { + Common::String txtline = readWideLine(txt); + sub curSubtitle; + curSubtitle.start = -1; + curSubtitle.stop = -1; + curSubtitle.subStr = txtline; + + _subs.push_back(curSubtitle); + } + txt.close(); + } + } else { + int32 st; + int32 en; + int32 sb; + if (sscanf(str.c_str(), "%*[^:]:(%d,%d)=%d", &st, &en, &sb) == 3) { + if (sb <= (int32)_subs.size()) { + if (upscaleToHires) { + // Convert from 15FPS (AVI) to 29.97FPS (VOB) + st = st * 2997 / 1500; + en = en * 2997 / 1500; + } + _subs[sb].start = st; + _subs[sb].stop = en; + } + } + } + } + } +} + +Subtitle::~Subtitle() { + if (_areaId != -1) + _engine->getRenderManager()->deleteSubArea(_areaId); + + _subs.clear(); +} + +void Subtitle::process(int32 time) { + int16 j = -1; + for (uint16 i = 0; i < _subs.size(); i++) + if (time >= _subs[i].start && time <= _subs[i].stop) { + j = i; + break; + } + + if (j == -1 && _subId != -1) { + if (_areaId != -1) + _engine->getRenderManager()->updateSubArea(_areaId, ""); + _subId = -1; + } + + if (j != -1 && j != _subId) { + if (_subs[j].subStr.size()) + if (_areaId != -1) + _engine->getRenderManager()->updateSubArea(_areaId, _subs[j].subStr); + _subId = j; + } +} + +} // End of namespace ZVision diff --git a/engines/zvision/subtitles/subtitles.h b/engines/zvision/text/subtitles.h index 776ddd3a97..329339be55 100644 --- a/engines/zvision/subtitles/subtitles.h +++ b/engines/zvision/text/subtitles.h @@ -23,6 +23,32 @@ #ifndef ZVISION_SUBTITLES_H #define ZVISION_SUBTITLES_H -// TODO: Implement Subtitles +#include "zvision/zvision.h" + +namespace ZVision { + +class ZVision; + +class Subtitle { +public: + Subtitle(ZVision *engine, const Common::String &subname, bool upscaleToHires = false); + ~Subtitle(); + + void process(int32 time); +private: + ZVision *_engine; + int32 _areaId; + int16 _subId; + + struct sub { + int start; + int stop; + Common::String subStr; + }; + + Common::Array<sub> _subs; +}; + +} #endif diff --git a/engines/zvision/text/text.cpp b/engines/zvision/text/text.cpp new file mode 100644 index 0000000000..868ee4f1ae --- /dev/null +++ b/engines/zvision/text/text.cpp @@ -0,0 +1,578 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/scummsys.h" +#include "common/file.h" +#include "common/tokenizer.h" +#include "common/debug.h" +#include "common/rect.h" +#include "graphics/fontman.h" +#include "graphics/colormasks.h" +#include "graphics/surface.h" +#include "graphics/font.h" +#include "graphics/fonts/ttf.h" + +#include "zvision/text/text.h" +#include "zvision/graphics/render_manager.h" +#include "zvision/text/truetype_font.h" +#include "zvision/scripting/script_manager.h" + +namespace ZVision { + +TextStyleState::TextStyleState() { + _fontname = "Arial"; + _blue = 255; + _green = 255; + _red = 255; + _bold = false; +#if 0 + _newline = false; + _escapement = 0; +#endif + _italic = false; + _justification = TEXT_JUSTIFY_LEFT; + _size = 12; +#if 0 + _skipcolor = false; +#endif + _strikeout = false; + _underline = false; + _statebox = 0; + _sharp = false; +} + +TextChange TextStyleState::parseStyle(const Common::String &str, int16 len) { + Common::String buf = Common::String(str.c_str(), len); + + uint retval = TEXT_CHANGE_NONE; + + Common::StringTokenizer tokenizer(buf, " "); + Common::String token; + + while (!tokenizer.empty()) { + token = tokenizer.nextToken(); + + if (token.matchString("font", true)) { + token = tokenizer.nextToken(); + if (token[0] == '"') { + Common::String _tmp = Common::String(token.c_str() + 1); + + while (token.lastChar() != '"' && !tokenizer.empty()) { + token = tokenizer.nextToken(); + _tmp += " " + token; + } + + if (_tmp.lastChar() == '"') + _tmp.deleteLastChar(); + + _fontname = _tmp; + } else { + if (!tokenizer.empty()) + _fontname = token; + } + retval |= TEXT_CHANGE_FONT_TYPE; + + } else if (token.matchString("blue", true)) { + if (!tokenizer.empty()) { + token = tokenizer.nextToken(); + int32 tmp = atoi(token.c_str()); + if (_blue != tmp) { + _blue = tmp; + retval |= TEXT_CHANGE_FONT_STYLE; + } + } + } else if (token.matchString("red", true)) { + if (!tokenizer.empty()) { + token = tokenizer.nextToken(); + int32 tmp = atoi(token.c_str()); + if (_red != tmp) { + _red = tmp; + retval |= TEXT_CHANGE_FONT_STYLE; + } + } + } else if (token.matchString("green", true)) { + if (!tokenizer.empty()) { + token = tokenizer.nextToken(); + int32 tmp = atoi(token.c_str()); + if (_green != tmp) { + _green = tmp; + retval |= TEXT_CHANGE_FONT_STYLE; + } + } + } else if (token.matchString("newline", true)) { +#if 0 + if ((retval & TXT_RET_NEWLN) == 0) + _newline = 0; + + _newline++; +#endif + retval |= TEXT_CHANGE_NEWLINE; + } else if (token.matchString("point", true)) { + if (!tokenizer.empty()) { + token = tokenizer.nextToken(); + int32 tmp = atoi(token.c_str()); + if (_size != tmp) { + _size = tmp; + retval |= TEXT_CHANGE_FONT_TYPE; + } + } + } else if (token.matchString("escapement", true)) { + if (!tokenizer.empty()) { + token = tokenizer.nextToken(); +#if 0 + int32 tmp = atoi(token.c_str()); + _escapement = tmp; +#endif + } + } else if (token.matchString("italic", true)) { + if (!tokenizer.empty()) { + token = tokenizer.nextToken(); + if (token.matchString("on", true)) { + if (_italic != true) { + _italic = true; + retval |= TEXT_CHANGE_FONT_STYLE; + } + } else if (token.matchString("off", true)) { + if (_italic != false) { + _italic = false; + retval |= TEXT_CHANGE_FONT_STYLE; + } + } + } + } else if (token.matchString("underline", true)) { + if (!tokenizer.empty()) { + token = tokenizer.nextToken(); + if (token.matchString("on", true)) { + if (_underline != true) { + _underline = true; + retval |= TEXT_CHANGE_FONT_STYLE; + } + } else if (token.matchString("off", true)) { + if (_underline != false) { + _underline = false; + retval |= TEXT_CHANGE_FONT_STYLE; + } + } + } + } else if (token.matchString("strikeout", true)) { + if (!tokenizer.empty()) { + token = tokenizer.nextToken(); + if (token.matchString("on", true)) { + if (_strikeout != true) { + _strikeout = true; + retval |= TEXT_CHANGE_FONT_STYLE; + } + } else if (token.matchString("off", true)) { + if (_strikeout != false) { + _strikeout = false; + retval |= TEXT_CHANGE_FONT_STYLE; + } + } + } + } else if (token.matchString("bold", true)) { + if (!tokenizer.empty()) { + token = tokenizer.nextToken(); + if (token.matchString("on", true)) { + if (_bold != true) { + _bold = true; + retval |= TEXT_CHANGE_FONT_STYLE; + } + } else if (token.matchString("off", true)) { + if (_bold != false) { + _bold = false; + retval |= TEXT_CHANGE_FONT_STYLE; + } + } + } + } else if (token.matchString("skipcolor", true)) { + if (!tokenizer.empty()) { + token = tokenizer.nextToken(); +#if 0 + if (token.matchString("on", true)) { + _skipcolor = true; + } else if (token.matchString("off", true)) { + _skipcolor = false; + } +#endif + } + } else if (token.matchString("image", true)) { + // Not used + } else if (token.matchString("statebox", true)) { + if (!tokenizer.empty()) { + token = tokenizer.nextToken(); + _statebox = atoi(token.c_str()); + retval |= TEXT_CHANGE_HAS_STATE_BOX; + } + } else if (token.matchString("justify", true)) { + if (!tokenizer.empty()) { + token = tokenizer.nextToken(); + if (token.matchString("center", true)) + _justification = TEXT_JUSTIFY_CENTER; + else if (token.matchString("left", true)) + _justification = TEXT_JUSTIFY_LEFT; + else if (token.matchString("right", true)) + _justification = TEXT_JUSTIFY_RIGHT; + } + } + } + return (TextChange)retval; +} + +void TextStyleState::readAllStyles(const Common::String &txt) { + int16 startTextPosition = -1; + int16 endTextPosition = -1; + + for (uint16 i = 0; i < txt.size(); i++) { + if (txt[i] == '<') + startTextPosition = i; + else if (txt[i] == '>') { + endTextPosition = i; + if (startTextPosition != -1) { + if ((endTextPosition - startTextPosition - 1) > 0) { + parseStyle(Common::String(txt.c_str() + startTextPosition + 1), endTextPosition - startTextPosition - 1); + } + } + } + + } +} + +void TextStyleState::updateFontWithTextState(StyledTTFont &font) { + uint tempStyle = 0; + + if (_bold) { + tempStyle |= StyledTTFont::TTF_STYLE_BOLD; + } + if (_italic) { + tempStyle |= StyledTTFont::TTF_STYLE_ITALIC; + } + if (_underline) { + tempStyle |= StyledTTFont::TTF_STYLE_UNDERLINE; + } + if (_strikeout) { + tempStyle |= StyledTTFont::TTF_STYLE_STRIKETHROUGH; + } + if (_sharp) { + tempStyle |= StyledTTFont::TTF_STYLE_SHARP; + } + + font.loadFont(_fontname, _size, tempStyle); +} + +void TextRenderer::drawTextWithJustification(const Common::String &text, StyledTTFont &font, uint32 color, Graphics::Surface &dest, int lineY, TextJustification justify) { + if (justify == TEXT_JUSTIFY_LEFT) + font.drawString(&dest, text, 0, lineY, dest.w, color, Graphics::kTextAlignLeft); + else if (justify == TEXT_JUSTIFY_CENTER) + font.drawString(&dest, text, 0, lineY, dest.w, color, Graphics::kTextAlignCenter); + else if (justify == TEXT_JUSTIFY_RIGHT) + font.drawString(&dest, text, 0, lineY, dest.w, color, Graphics::kTextAlignRight); +} + +int32 TextRenderer::drawText(const Common::String &text, TextStyleState &state, Graphics::Surface &dest) { + StyledTTFont font(_engine); + state.updateFontWithTextState(font); + + uint32 color = _engine->_resourcePixelFormat.RGBToColor(state._red, state._green, state._blue); + drawTextWithJustification(text, font, color, dest, 0, state._justification); + + return font.getStringWidth(text); +} + +struct TextSurface { + TextSurface(Graphics::Surface *surface, Common::Point surfaceOffset, uint lineNumber) + : _surface(surface), + _surfaceOffset(surfaceOffset), + _lineNumber(lineNumber) { + } + + Graphics::Surface *_surface; + Common::Point _surfaceOffset; + uint _lineNumber; +}; + +void TextRenderer::drawTextWithWordWrapping(const Common::String &text, Graphics::Surface &dest) { + Common::Array<TextSurface> textSurfaces; + Common::Array<uint> lineWidths; + Common::Array<TextJustification> lineJustifications; + + // Create the initial text state + TextStyleState currentState; + + // Create an empty font and bind it to the state + StyledTTFont font(_engine); + currentState.updateFontWithTextState(font); + + Common::String currentSentence; // Not a true 'grammatical' sentence. Rather, it's just a collection of words + Common::String currentWord; + int sentenceWidth = 0; + int wordWidth = 0; + int lineWidth = 0; + int lineHeight = font.getFontHeight(); + + uint currentLineNumber = 0u; + + uint numSpaces = 0u; + int spaceWidth = 0; + + // The pixel offset to the currentSentence + Common::Point sentencePixelOffset; + + uint i = 0u; + uint stringlen = text.size(); + + while (i < stringlen) { + if (text[i] == '<') { + // Flush the currentWord to the currentSentence + currentSentence += currentWord; + sentenceWidth += wordWidth; + + // Reset the word variables + currentWord.clear(); + wordWidth = 0; + + // Parse the style tag + uint startTextPosition = i; + while (i < stringlen && text[i] != '>') { + ++i; + } + uint endTextPosition = i; + + uint32 textColor = currentState.getTextColor(_engine); + + uint stateChanges = 0u; + if ((endTextPosition - startTextPosition - 1) > 0) { + stateChanges = currentState.parseStyle(Common::String(text.c_str() + startTextPosition + 1), endTextPosition - startTextPosition - 1); + } + + if (stateChanges & (TEXT_CHANGE_FONT_TYPE | TEXT_CHANGE_FONT_STYLE)) { + // Use the last state to render out the current sentence + // Styles apply to the text 'after' them + if (!currentSentence.empty()) { + textSurfaces.push_back(TextSurface(font.renderSolidText(currentSentence, textColor), sentencePixelOffset, currentLineNumber)); + + lineWidth += sentenceWidth; + sentencePixelOffset.x += sentenceWidth; + + // Reset the sentence variables + currentSentence.clear(); + sentenceWidth = 0; + } + + // Update the current state with the style information + currentState.updateFontWithTextState(font); + + lineHeight = MAX(lineHeight, font.getFontHeight()); + spaceWidth = font.getCharWidth(' '); + } + if (stateChanges & TEXT_CHANGE_NEWLINE) { + // If the current sentence has content, render it out + if (!currentSentence.empty()) { + textSurfaces.push_back(TextSurface(font.renderSolidText(currentSentence, textColor), sentencePixelOffset, currentLineNumber)); + } + + // Set line width + lineWidths.push_back(lineWidth + sentenceWidth - (numSpaces * spaceWidth)); + + currentSentence.clear(); + sentenceWidth = 0; + + // Update the offsets + sentencePixelOffset.x = 0u; + sentencePixelOffset.y += lineHeight; + + // Reset the line variables + lineHeight = font.getFontHeight(); + lineWidth = 0; + ++currentLineNumber; + lineJustifications.push_back(currentState._justification); + } + if (stateChanges & TEXT_CHANGE_HAS_STATE_BOX) { + Common::String temp = Common::String::format("%d", _engine->getScriptManager()->getStateValue(currentState._statebox)); + wordWidth += font.getStringWidth(temp); + + // If the word causes the line to overflow, render the sentence and start a new line + if (lineWidth + sentenceWidth + wordWidth > dest.w) { + textSurfaces.push_back(TextSurface(font.renderSolidText(currentSentence, textColor), sentencePixelOffset, currentLineNumber)); + + // Set line width + lineWidths.push_back(lineWidth + sentenceWidth - (numSpaces * spaceWidth)); + + currentSentence.clear(); + sentenceWidth = 0; + + // Update the offsets + sentencePixelOffset.x = 0u; + sentencePixelOffset.y += lineHeight; + + // Reset the line variables + lineHeight = font.getFontHeight(); + lineWidth = 0; + ++currentLineNumber; + lineJustifications.push_back(currentState._justification); + } + } + } else { + currentWord += text[i]; + wordWidth += font.getCharWidth(text[i]); + + if (text[i] == ' ') { + // When we hit the first space, flush the current word to the sentence + if (!currentWord.empty()) { + currentSentence += currentWord; + sentenceWidth += wordWidth; + + currentWord.clear(); + wordWidth = 0; + } + + // We track the number of spaces so we can disregard their width in lineWidth calculations + ++numSpaces; + } else { + // If the word causes the line to overflow, render the sentence and start a new line + if (lineWidth + sentenceWidth + wordWidth > dest.w) { + // Only render out content + if (!currentSentence.empty()) { + textSurfaces.push_back(TextSurface(font.renderSolidText(currentSentence, currentState.getTextColor(_engine)), sentencePixelOffset, currentLineNumber)); + } + + // Set line width + lineWidths.push_back(lineWidth + sentenceWidth - (numSpaces * spaceWidth)); + + currentSentence.clear(); + sentenceWidth = 0; + + // Update the offsets + sentencePixelOffset.x = 0u; + sentencePixelOffset.y += lineHeight; + + // Reset the line variables + lineHeight = font.getFontHeight(); + lineWidth = 0; + ++currentLineNumber; + lineJustifications.push_back(currentState._justification); + } + + numSpaces = 0u; + } + } + + i++; + } + + // Render out any remaining words/sentences + if (!currentWord.empty() || !currentSentence.empty()) { + currentSentence += currentWord; + sentenceWidth += wordWidth; + + textSurfaces.push_back(TextSurface(font.renderSolidText(currentSentence, currentState.getTextColor(_engine)), sentencePixelOffset, currentLineNumber)); + } + + lineWidths.push_back(lineWidth + sentenceWidth); + lineJustifications.push_back(currentState._justification); + + for (Common::Array<TextSurface>::iterator iter = textSurfaces.begin(); iter != textSurfaces.end(); ++iter) { + Common::Rect empty; + + if (lineJustifications[iter->_lineNumber] == TEXT_JUSTIFY_LEFT) { + _engine->getRenderManager()->blitSurfaceToSurface(*iter->_surface, empty, dest, iter->_surfaceOffset.x, iter->_surfaceOffset.y, 0); + } else if (lineJustifications[iter->_lineNumber] == TEXT_JUSTIFY_CENTER) { + _engine->getRenderManager()->blitSurfaceToSurface(*iter->_surface, empty, dest, ((dest.w - lineWidths[iter->_lineNumber]) / 2) + iter->_surfaceOffset.x, iter->_surfaceOffset.y, 0); + } else if (lineJustifications[iter->_lineNumber] == TEXT_JUSTIFY_RIGHT) { + _engine->getRenderManager()->blitSurfaceToSurface(*iter->_surface, empty, dest, dest.w - lineWidths[iter->_lineNumber] + iter->_surfaceOffset.x, iter->_surfaceOffset.y, 0); + } + + // Release memory + iter->_surface->free(); + delete iter->_surface; + } +} + +Common::String readWideLine(Common::SeekableReadStream &stream) { + Common::String asciiString; + + while (true) { + uint32 value = stream.readUint16LE(); + if (stream.eos()) + break; + // Check for CRLF + if (value == 0x0A0D) { + // Read in the extra NULL char + stream.readByte(); // \0 + // End of the line. Break + break; + } + + // Crush each octet pair to a UTF-8 sequence + if (value < 0x80) { + asciiString += (char)(value & 0x7F); + } else if (value >= 0x80 && value < 0x800) { + asciiString += (char)(0xC0 | ((value >> 6) & 0x1F)); + asciiString += (char)(0x80 | (value & 0x3F)); + } else if (value >= 0x800 && value < 0x10000 && value != 0xCCCC) { + asciiString += (char)(0xE0 | ((value >> 12) & 0xF)); + asciiString += (char)(0x80 | ((value >> 6) & 0x3F)); + asciiString += (char)(0x80 | (value & 0x3F)); + } else if (value == 0xCCCC) { + // Ignore, this character is used as newline sometimes + } else if (value >= 0x10000 && value < 0x200000) { + asciiString += (char)(0xF0); + asciiString += (char)(0x80 | ((value >> 12) & 0x3F)); + asciiString += (char)(0x80 | ((value >> 6) & 0x3F)); + asciiString += (char)(0x80 | (value & 0x3F)); + } + } + + return asciiString; +} + +int8 getUtf8CharSize(char chr) { + if ((chr & 0x80) == 0) + return 1; + else if ((chr & 0xE0) == 0xC0) + return 2; + else if ((chr & 0xF0) == 0xE0) + return 3; + else if ((chr & 0xF8) == 0xF0) + return 4; + else if ((chr & 0xFC) == 0xF8) + return 5; + else if ((chr & 0xFE) == 0xFC) + return 6; + + return 1; +} + +uint16 readUtf8Char(const char *chr) { + uint16 result = 0; + if ((chr[0] & 0x80) == 0) + result = chr[0]; + else if ((chr[0] & 0xE0) == 0xC0) + result = ((chr[0] & 0x1F) << 6) | (chr[1] & 0x3F); + else if ((chr[0] & 0xF0) == 0xE0) + result = ((chr[0] & 0x0F) << 12) | ((chr[1] & 0x3F) << 6) | (chr[2] & 0x3F); + else + result = chr[0]; + + return result; +} + +} // End of namespace ZVision diff --git a/engines/zvision/text/text.h b/engines/zvision/text/text.h new file mode 100644 index 0000000000..d35b90499d --- /dev/null +++ b/engines/zvision/text/text.h @@ -0,0 +1,93 @@ +/* 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 ZVISION_TEXT_H +#define ZVISION_TEXT_H + +#include "zvision/detection.h" +#include "zvision/text/truetype_font.h" +#include "zvision/zvision.h" + +namespace ZVision { + +class ZVision; + +enum TextJustification { + TEXT_JUSTIFY_CENTER = 0, + TEXT_JUSTIFY_LEFT = 1, + TEXT_JUSTIFY_RIGHT = 2 +}; + +enum TextChange { + TEXT_CHANGE_NONE = 0x0, + TEXT_CHANGE_FONT_TYPE = 0x1, + TEXT_CHANGE_FONT_STYLE = 0x2, + TEXT_CHANGE_NEWLINE = 0x4, + TEXT_CHANGE_HAS_STATE_BOX = 0x8 +}; + +class TextStyleState { +public: + TextStyleState(); + TextChange parseStyle(const Common::String &str, int16 len); + void readAllStyles(const Common::String &txt); + void updateFontWithTextState(StyledTTFont &font); + + uint32 getTextColor(ZVision *engine) { + return engine->_resourcePixelFormat.RGBToColor(_red, _green, _blue); + } + +public: + Common::String _fontname; + TextJustification _justification; // 0 - center, 1-left, 2-right + int16 _size; + uint8 _red; // 0-255 + uint8 _green; // 0-255 + uint8 _blue; // 0-255 + bool _italic; + bool _bold; + bool _underline; + bool _strikeout; + int32 _statebox; + bool _sharp; +}; + +class TextRenderer { +public: + TextRenderer(ZVision *engine): _engine(engine) {}; + + void drawTextWithJustification(const Common::String &text, StyledTTFont &font, uint32 color, Graphics::Surface &dest, int lineY, TextJustification jusification); + int32 drawText(const Common::String &text, TextStyleState &state, Graphics::Surface &dest); + void drawTextWithWordWrapping(const Common::String &text, Graphics::Surface &dest); + +private: + ZVision *_engine; +}; + +Common::String readWideLine(Common::SeekableReadStream &stream); +int8 getUtf8CharSize(char chr); +uint16 readUtf8Char(const char *chr); + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/text/truetype_font.cpp b/engines/zvision/text/truetype_font.cpp new file mode 100644 index 0000000000..acb053ea8d --- /dev/null +++ b/engines/zvision/text/truetype_font.cpp @@ -0,0 +1,265 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/scummsys.h" +#include "common/config-manager.h" +#include "common/debug.h" +#include "common/file.h" +#include "common/system.h" +#include "common/unzip.h" +#include "common/ustr.h" +#include "graphics/font.h" +#include "graphics/fonts/ttf.h" +#include "graphics/surface.h" + +#include "zvision/zvision.h" +#include "zvision/graphics/render_manager.h" +#include "zvision/text/truetype_font.h" + +namespace ZVision { + +const FontStyle systemFonts[] = { + { "*times new roman*", "times", "FreeSerif", "Italic", "LiberationSerif" }, + { "*times*", "times", "FreeSerif", "Italic", "LiberationSerif" }, + { "*century schoolbook*", "censcbk", "FreeSerif", "Italic", "LiberationSerif" }, + { "*garamond*", "gara", "FreeSerif", "Italic", "LiberationSerif" }, + { "*courier new*", "cour", "FreeMono", "Oblique", "LiberationMono" }, + { "*courier*", "cour", "FreeMono", "Oblique", "LiberationMono" }, + { "*ZorkDeath*", "cour", "FreeMono", "Oblique", "LiberationMono" }, + { "*arial*", "arial", "FreeSans", "Oblique", "LiberationSans" }, + { "*ZorkNormal*", "arial", "FreeSans", "Oblique", "LiberationSans" } +}; + +const FontStyle getSystemFont(int fontIndex) { + return systemFonts[fontIndex]; +} + +StyledTTFont::StyledTTFont(ZVision *engine) { + _engine = engine; + _style = 0; + _font = nullptr; + _lineHeight = 0; +} + +StyledTTFont::~StyledTTFont() { + delete _font; +} + +bool StyledTTFont::loadFont(const Common::String &fontName, int32 point, uint style) { + // Don't re-load the font if we've already loaded it + // We have to check for empty so we can default to Arial + if (!fontName.empty() && _fontName.equalsIgnoreCase(fontName) && _lineHeight == point && _style == style) { + return true; + } + + _style = style; + + Common::String newFontName; + Common::String freeFontName; + Common::String liberationFontName; + + for (int i = 0; i < FONT_COUNT; i++) { + FontStyle curFont = getSystemFont(i); + if (fontName.matchString(curFont.zorkFont, true)) { + newFontName = curFont.fontBase; + freeFontName = curFont.freeFontBase; + liberationFontName = curFont.liberationFontBase; + + if ((_style & TTF_STYLE_BOLD) && (_style & TTF_STYLE_ITALIC)) { + newFontName += "bi"; + freeFontName += "Bold"; + freeFontName += curFont.freeFontItalicName; + liberationFontName += "-BoldItalic"; + } else if (_style & TTF_STYLE_BOLD) { + newFontName += "bd"; + freeFontName += "Bold"; + liberationFontName += "-Bold"; + } else if (_style & TTF_STYLE_ITALIC) { + newFontName += "i"; + freeFontName += curFont.freeFontItalicName; + liberationFontName += "-Italic"; + } else { + liberationFontName += "-Regular"; + } + + newFontName += ".ttf"; + freeFontName += ".ttf"; + liberationFontName += ".ttf"; + break; + } + } + + if (newFontName.empty()) { + debug("Could not identify font: %s. Reverting to Arial", fontName.c_str()); + newFontName = "arial.ttf"; + freeFontName = "FreeSans.ttf"; + liberationFontName = "LiberationSans-Regular.ttf"; + } + + bool sharp = (_style & TTF_STYLE_SHARP) == TTF_STYLE_SHARP; + + Common::File file; + if (!file.open(newFontName) && !_engine->getSearchManager()->openFile(file, newFontName) && + !file.open(liberationFontName) && !_engine->getSearchManager()->openFile(file, liberationFontName) && + !file.open(freeFontName) && !_engine->getSearchManager()->openFile(file, freeFontName)) + error("Unable to open font file %s (Liberation Font alternative: %s, FreeFont alternative: %s)", newFontName.c_str(), liberationFontName.c_str(), freeFontName.c_str()); + + Graphics::Font *newFont = Graphics::loadTTFFont(file, point, 60, (sharp ? Graphics::kTTFRenderModeMonochrome : Graphics::kTTFRenderModeNormal)); // 66 dpi for 640 x 480 on 14" display + if (newFont == nullptr) { + return false; + } + + delete _font; + _font = newFont; + + _fontName = fontName; + _lineHeight = point; + + return true; +} + +int StyledTTFont::getFontHeight() { + if (_font) + return _font->getFontHeight(); + + return 0; +} + +int StyledTTFont::getMaxCharWidth() { + if (_font) + return _font->getMaxCharWidth(); + + return 0; +} + +int StyledTTFont::getCharWidth(byte chr) { + if (_font) + return _font->getCharWidth(chr); + + return 0; +} + +int StyledTTFont::getKerningOffset(byte left, byte right) { + if (_font) + return _font->getKerningOffset(left, right); + + return 0; +} + +Common::U32String StyledTTFont::convertUtf8ToUtf32(const Common::String &str) { + // The String class, and therefore the Font class as well, assume one + // character is one byte, but in this case it's actually an UTF-8 + // string with up to 4 bytes per character. To work around this, + // convert it to an U32String before drawing it, because our Font class + // can handle that. + Common::U32String u32str; + uint i = 0; + while (i < str.size()) { + uint32 chr = 0; + if ((str[i] & 0xF8) == 0xF0) { + chr |= (str[i++] & 0x07) << 18; + chr |= (str[i++] & 0x3F) << 12; + chr |= (str[i++] & 0x3F) << 6; + chr |= (str[i++] & 0x3F); + } else if ((str[i] & 0xF0) == 0xE0) { + chr |= (str[i++] & 0x0F) << 12; + chr |= (str[i++] & 0x3F) << 6; + chr |= (str[i++] & 0x3F); + } else if ((str[i] & 0xE0) == 0xC0) { + chr |= (str[i++] & 0x1F) << 6; + chr |= (str[i++] & 0x3F); + } else { + chr = (str[i++] & 0x7F); + } + u32str += chr; + } + return u32str; +} + +void StyledTTFont::drawChar(Graphics::Surface *dst, byte chr, int x, int y, uint32 color) { + if (_font) { + _font->drawChar(dst, chr, x, y, color); + if (_style & TTF_STYLE_UNDERLINE) { + int16 pos = (int16)floor(_font->getFontHeight() * 0.87); + int thk = MAX((int)(_font->getFontHeight() * 0.05), 1); + dst->fillRect(Common::Rect(x, y + pos, x + _font->getCharWidth(chr), y + pos + thk), color); + } + if (_style & TTF_STYLE_STRIKETHROUGH) { + int16 pos = (int16)floor(_font->getFontHeight() * 0.60); + int thk = MAX((int)(_font->getFontHeight() * 0.05), 1); + dst->fillRect(Common::Rect(x, y + pos, x + _font->getCharWidth(chr), y + pos + thk), color); + } + } +} + +void StyledTTFont::drawString(Graphics::Surface *dst, const Common::String &str, int x, int y, int w, uint32 color, Graphics::TextAlign align) { + if (_font) { + Common::U32String u32str = convertUtf8ToUtf32(str); + _font->drawString(dst, u32str, x, y, w, color, align); + if (_style & TTF_STYLE_UNDERLINE) { + int16 pos = (int16)floor(_font->getFontHeight() * 0.87); + int16 wd = MIN(_font->getStringWidth(u32str), w); + int16 stX = x; + if (align == Graphics::kTextAlignCenter) + stX += (w - wd) / 2; + else if (align == Graphics::kTextAlignRight) + stX += (w - wd); + + int thk = MAX((int)(_font->getFontHeight() * 0.05), 1); + + dst->fillRect(Common::Rect(stX, y + pos, stX + wd, y + pos + thk), color); + } + if (_style & TTF_STYLE_STRIKETHROUGH) { + int16 pos = (int16)floor(_font->getFontHeight() * 0.60); + int16 wd = MIN(_font->getStringWidth(u32str), w); + int16 stX = x; + if (align == Graphics::kTextAlignCenter) + stX += (w - wd) / 2; + else if (align == Graphics::kTextAlignRight) + stX += (w - wd); + + int thk = MAX((int)(_font->getFontHeight() * 0.05), 1); + + dst->fillRect(Common::Rect(stX, y + pos, stX + wd, y + pos + thk), color); + } + } +} + +int StyledTTFont::getStringWidth(const Common::String &str) { + if (_font) + return _font->getStringWidth(str); + return 0; +} + +Graphics::Surface *StyledTTFont::renderSolidText(const Common::String &str, uint32 color) { + Graphics::Surface *tmp = new Graphics::Surface; + if (_font) { + int16 w = _font->getStringWidth(str); + if (w && w < 1024) { + tmp->create(w, _font->getFontHeight(), _engine->_resourcePixelFormat); + drawString(tmp, str, 0, 0, w, color); + } + } + return tmp; +} + +} // End of namespace ZVision diff --git a/engines/zvision/fonts/truetype_font.h b/engines/zvision/text/truetype_font.h index 64f53a2c3b..6abe05cda6 100644 --- a/engines/zvision/fonts/truetype_font.h +++ b/engines/zvision/text/truetype_font.h @@ -28,52 +28,65 @@ #include "graphics/font.h" #include "graphics/pixelformat.h" - namespace Graphics { struct Surface; } namespace ZVision { +struct FontStyle { + const char *zorkFont; + const char *fontBase; + const char *freeFontBase; + const char *freeFontItalicName; + const char *liberationFontBase; +}; + +#define FONT_COUNT 9 + class ZVision; -class TruetypeFont { +// Styled TTF +class StyledTTFont { public: - TruetypeFont(ZVision *engine, int32 fontHeight); - ~TruetypeFont(); + StyledTTFont(ZVision *engine); + ~StyledTTFont(); + + enum { + TTF_STYLE_BOLD = 0x01, + TTF_STYLE_ITALIC = 0x02, + TTF_STYLE_UNDERLINE = 0x04, + TTF_STYLE_STRIKETHROUGH = 0x08, + TTF_STYLE_SHARP = 0x10 + }; private: -// ZVision *_engine; + ZVision *_engine; Graphics::Font *_font; int _lineHeight; - -// size_t _maxCharWidth; -// size_t _maxCharHeight; + uint _style; + Common::String _fontName; public: - int32 _fontHeight; + bool loadFont(const Common::String &fontName, int32 point, uint style); -public: - /** - * Loads a .ttf file into memory. This must be called - * before any calls to drawTextToSurface - * - * @param filename The file name of the .ttf file to load - */ - bool loadFile(const Common::String &filename); - /** - * Renders the supplied text to a Surface using 0x0 as the - * background color. - * - * @param text The to render - * @param textColor The color to render the text with - * @param maxWidth The max width the text should take up. - * @param maxHeight The max height the text should take up. - * @param align The alignment of the text within the bounds of maxWidth - * @param wrap If true, any words extending past maxWidth will wrap to a new line. If false, ellipses will be rendered to show that the text didn't fit - * @return A Surface containing the rendered text - */ - Graphics::Surface *drawTextToSurface(const Common::String &text, uint16 textColor, int maxWidth, int maxHeight, Graphics::TextAlign align, bool wrap); + int getFontHeight(); + int getMaxCharWidth(); + int getCharWidth(byte chr); + int getKerningOffset(byte left, byte right); + + Common::U32String convertUtf8ToUtf32(const Common::String &str); + + void drawChar(Graphics::Surface *dst, byte chr, int x, int y, uint32 color); + + void drawString(Graphics::Surface *dst, const Common::String &str, int x, int y, int w, uint32 color, Graphics::TextAlign align = Graphics::kTextAlignLeft); + int getStringWidth(const Common::String &str); + + Graphics::Surface *renderSolidText(const Common::String &str, uint32 color); + + bool isLoaded() { + return _font != NULL; + }; }; } // End of namespace ZVision diff --git a/engines/zvision/utility/single_value_container.cpp b/engines/zvision/utility/single_value_container.cpp deleted file mode 100644 index e609474285..0000000000 --- a/engines/zvision/utility/single_value_container.cpp +++ /dev/null @@ -1,348 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#include "common/scummsys.h" - -#include "zvision/utility/single_value_container.h" - -#include "common/textconsole.h" -#include "common/str.h" - - -namespace ZVision { - -SingleValueContainer::SingleValueContainer(ValueType type) : _objectType(type) { } - -SingleValueContainer::SingleValueContainer(bool value) : _objectType(BOOL) { - _value.boolVal = value; -} - -SingleValueContainer::SingleValueContainer(byte value) : _objectType(BYTE) { - _value.byteVal = value; -} - -SingleValueContainer::SingleValueContainer(int16 value) : _objectType(INT16) { - _value.int16Val = value; -} - -SingleValueContainer::SingleValueContainer(uint16 value) : _objectType(UINT16) { - _value.uint16Val = value; -} - -SingleValueContainer::SingleValueContainer(int32 value) : _objectType(INT32) { - _value.int32Val = value; -} - -SingleValueContainer::SingleValueContainer(uint32 value) : _objectType(UINT32) { - _value.uint32Val = value; -} - -SingleValueContainer::SingleValueContainer(float value) : _objectType(FLOAT) { - _value.floatVal = value; -} - -SingleValueContainer::SingleValueContainer(double value) : _objectType(DOUBLE) { - _value.doubleVal = value; -} - -SingleValueContainer::SingleValueContainer(Common::String value) : _objectType(BYTE) { - _value.stringVal = new char[value.size() + 1]; - memcpy(_value.stringVal, value.c_str(), value.size() + 1); -} - -SingleValueContainer::SingleValueContainer(const SingleValueContainer &other) { - _objectType = other._objectType; - - switch (_objectType) { - case BOOL: - _value.boolVal = other._value.boolVal; - break; - case BYTE: - _value.byteVal = other._value.byteVal; - break; - case INT16: - _value.int16Val = other._value.int16Val; - break; - case UINT16: - _value.uint16Val = other._value.uint16Val; - break; - case INT32: - _value.int32Val = other._value.int32Val; - break; - case UINT32: - _value.uint32Val = other._value.uint32Val; - break; - case FLOAT: - _value.floatVal = other._value.floatVal; - break; - case DOUBLE: - _value.doubleVal = other._value.doubleVal; - break; - case STRING: - uint32 length = strlen(other._value.stringVal); - _value.stringVal = new char[length + 1]; - memcpy(_value.stringVal, other._value.stringVal, length + 1); - break; - } -} - -SingleValueContainer::~SingleValueContainer() { - deleteCharPointer(); -} - -void SingleValueContainer::deleteCharPointer() { - if (_objectType == STRING) - delete[] _value.stringVal; -} - - -SingleValueContainer &SingleValueContainer::operator=(const bool &rhs) { - if (_objectType == BOOL) { - _value.boolVal = rhs; - return *this; - } - - deleteCharPointer(); - _objectType = BOOL; - _value.boolVal = rhs; - - return *this; -} - -SingleValueContainer &SingleValueContainer::operator=(const byte &rhs) { - if (_objectType == BYTE) { - _value.byteVal = rhs; - return *this; - } - - deleteCharPointer(); - _objectType = BYTE; - _value.byteVal = rhs; - - return *this; -} - -SingleValueContainer &SingleValueContainer::operator=(const int16 &rhs) { - if (_objectType == INT16) { - _value.int16Val = rhs; - return *this; - } - - deleteCharPointer(); - _objectType = INT16; - _value.int16Val = rhs; - - return *this; -} - -SingleValueContainer &SingleValueContainer::operator=(const uint16 &rhs) { - if (_objectType == UINT16) { - _value.uint16Val = rhs; - return *this; - } - - deleteCharPointer(); - _objectType = UINT16; - _value.uint16Val = rhs; - - return *this; -} - -SingleValueContainer &SingleValueContainer::operator=(const int32 &rhs) { - if (_objectType == INT32) { - _value.int32Val = rhs; - return *this; - } - - deleteCharPointer(); - _objectType = INT32; - _value.int32Val = rhs; - - return *this; -} - -SingleValueContainer &SingleValueContainer::operator=(const uint32 &rhs) { - if (_objectType == UINT32) { - _value.uint32Val = rhs; - return *this; - } - - deleteCharPointer(); - _objectType = UINT32; - _value.uint32Val = rhs; - - return *this; -} - -SingleValueContainer &SingleValueContainer::operator=(const float &rhs) { - if (_objectType == FLOAT) { - _value.floatVal = rhs; - return *this; - } - - deleteCharPointer(); - _objectType = FLOAT; - _value.floatVal = rhs; - - return *this; -} - -SingleValueContainer &SingleValueContainer::operator=(const double &rhs) { - if (_objectType == DOUBLE) { - _value.doubleVal = rhs; - return *this; - } - - deleteCharPointer(); - _objectType = DOUBLE; - _value.doubleVal = rhs; - - return *this; -} - -SingleValueContainer &SingleValueContainer::operator=(const Common::String &rhs) { - if (_objectType != STRING) { - _objectType = STRING; - _value.stringVal = new char[rhs.size() + 1]; - memcpy(_value.stringVal, rhs.c_str(), rhs.size() + 1); - - return *this; - } - - uint32 length = strlen(_value.stringVal); - if (length <= rhs.size() + 1) { - memcpy(_value.stringVal, rhs.c_str(), rhs.size() + 1); - } else { - delete[] _value.stringVal; - _value.stringVal = new char[rhs.size() + 1]; - memcpy(_value.stringVal, rhs.c_str(), rhs.size() + 1); - } - - return *this; -} - -SingleValueContainer &SingleValueContainer::operator=(const SingleValueContainer &rhs) { - switch (_objectType) { - case BOOL: - return operator=(rhs._value.boolVal); - case BYTE: - return operator=(rhs._value.byteVal); - case INT16: - return operator=(rhs._value.int16Val); - case UINT16: - return operator=(rhs._value.uint16Val); - case INT32: - return operator=(rhs._value.int32Val); - case UINT32: - return operator=(rhs._value.uint32Val); - case FLOAT: - return operator=(rhs._value.floatVal); - case DOUBLE: - return operator=(rhs._value.doubleVal); - case STRING: - uint32 length = strlen(rhs._value.stringVal); - - _value.stringVal = new char[length + 1]; - memcpy(_value.stringVal, rhs._value.stringVal, length + 1); - - return *this; - } - - return *this; -} - - -bool SingleValueContainer::getBoolValue(bool *returnValue) const { - if (_objectType != BOOL) { - warning("'Object' is not storing a bool."); - return false; - } - - *returnValue = _value.boolVal; - return true; -} - -bool SingleValueContainer::getByteValue(byte *returnValue) const { - if (_objectType != BYTE) - warning("'Object' is not storing a byte."); - - *returnValue = _value.byteVal; - return true; -} - -bool SingleValueContainer::getInt16Value(int16 *returnValue) const { - if (_objectType != INT16) - warning("'Object' is not storing an int16."); - - *returnValue = _value.int16Val; - return true; -} - -bool SingleValueContainer::getUInt16Value(uint16 *returnValue) const { - if (_objectType != UINT16) - warning("'Object' is not storing a uint16."); - - *returnValue = _value.uint16Val; - return true; -} - -bool SingleValueContainer::getInt32Value(int32 *returnValue) const { - if (_objectType != INT32) - warning("'Object' is not storing an int32."); - - *returnValue = _value.int32Val; - return true; -} - -bool SingleValueContainer::getUInt32Value(uint32 *returnValue) const { - if (_objectType != UINT32) - warning("'Object' is not storing a uint32."); - - *returnValue = _value.uint32Val; - return true; -} - -bool SingleValueContainer::getFloatValue(float *returnValue) const { - if (_objectType != FLOAT) - warning("'Object' is not storing a float."); - - *returnValue = _value.floatVal; - return true; -} - -bool SingleValueContainer::getDoubleValue(double *returnValue) const { - if (_objectType != DOUBLE) - warning("'Object' is not storing a double."); - - *returnValue = _value.doubleVal; - return true; -} - -bool SingleValueContainer::getStringValue(Common::String *returnValue) const { - if (_objectType != STRING) - warning("'Object' is not storing a Common::String."); - - *returnValue = _value.stringVal; - return true; -} - -} // End of namespace ZVision diff --git a/engines/zvision/utility/single_value_container.h b/engines/zvision/utility/single_value_container.h deleted file mode 100644 index 951383661a..0000000000 --- a/engines/zvision/utility/single_value_container.h +++ /dev/null @@ -1,183 +0,0 @@ -/* 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 ZVISION_SINGLE_VALUE_CONTAINER_H -#define ZVISION_SINGLE_VALUE_CONTAINER_H - -namespace Common { -class String; -} - -namespace ZVision { - -/** - * A generic single value storage class. It is useful for storing different - * value types in a single List, Hashmap, etc. - */ -class SingleValueContainer { -public: - enum ValueType { - BOOL, - BYTE, - INT16, - UINT16, - INT32, - UINT32, - FLOAT, - DOUBLE, - STRING - }; - - // Constructors - explicit SingleValueContainer(ValueType type); - explicit SingleValueContainer(bool value); - explicit SingleValueContainer(byte value); - explicit SingleValueContainer(int16 value); - explicit SingleValueContainer(uint16 value); - explicit SingleValueContainer(int32 value); - explicit SingleValueContainer(uint32 value); - explicit SingleValueContainer(float value); - explicit SingleValueContainer(double value); - explicit SingleValueContainer(Common::String value); - - // Copy constructor - explicit SingleValueContainer(const SingleValueContainer& other); - - // Destructor - ~SingleValueContainer(); - -private: - ValueType _objectType; - - union { - bool boolVal; - byte byteVal; - int16 int16Val; - uint16 uint16Val; - int32 int32Val; - uint32 uint32Val; - float floatVal; - double doubleVal; - char *stringVal; - } _value; - -public: - SingleValueContainer &operator=(const bool &rhs); - SingleValueContainer &operator=(const byte &rhs); - SingleValueContainer &operator=(const int16 &rhs); - SingleValueContainer &operator=(const uint16 &rhs); - SingleValueContainer &operator=(const int32 &rhs); - SingleValueContainer &operator=(const uint32 &rhs); - SingleValueContainer &operator=(const float &rhs); - SingleValueContainer &operator=(const double &rhs); - SingleValueContainer &operator=(const Common::String &rhs); - - SingleValueContainer& operator=(const SingleValueContainer &rhs); - - /** - * Retrieve a bool from the container. If the container is not storing a - * bool, this will return false and display a warning(). - * - * @param returnValue Pointer to where you want the value stored - * @return Value indicating whether the value assignment was successful - */ - bool getBoolValue(bool *returnValue) const; - /** - * Retrieve a byte from the container. If the container is not storing a - * byte, this will return false and display a warning(). - * - * @param returnValue Pointer to where you want the value stored - * @return Value indicating whether the value assignment was successful - */ - bool getByteValue(byte *returnValue) const; - /** - * Retrieve an int16 from the container. If the container is not storing an - * int16, this will return false and display a warning(). - * - * @param returnValue Pointer to where you want the value stored - * @return Value indicating whether the value assignment was successful - */ - bool getInt16Value(int16 *returnValue) const; - /** - * Retrieve a uint16 from the container. If the container is not storing a - * uint16, this will return false and display a warning(). - * - * @param returnValue Pointer to where you want the value stored - * @return Value indicating whether the value assignment was successful - */ - bool getUInt16Value(uint16 *returnValue) const; - /** - * Retrieve an int32 from the container. If the container is not storing an - * int32, this will return false and display a warning(). - * - * @param returnValue Pointer to where you want the value stored - * @return Value indicating whether the value assignment was successful - */ - bool getInt32Value(int32 *returnValue) const; - /** - * Retrieve a uint32 from the container. If the container is not storing a - * uint32, this will return false and display a warning(). - * - * @param returnValue Pointer to where you want the value stored - * @return Value indicating whether the value assignment was successful - */ - bool getUInt32Value(uint32 *returnValue) const; - /** - * Retrieve a float from the container. If the container is not storing a - * float, this will return false and display a warning(). - * - * @param returnValue Pointer to where you want the value stored - * @return Value indicating whether the value assignment was successful - */ - bool getFloatValue(float *returnValue) const; - /** - * Retrieve a double from the container. If the container is not storing a - * double, this will return false and display a warning(). - * - * @param returnValue Pointer to where you want the value stored - * @return Value indicating whether the value assignment was successful - */ - bool getDoubleValue(double *returnValue) const; - /** - * Retrieve a String from the container. If the container is not storing a - * string, this will return false and display a warning(). - * - * Caution: Strings are internally stored as char[]. getStringValue uses - * Common::String::operator=(char *) to do the assigment, which uses both - * strlen() AND memmove(). - * - * @param returnValue Pointer to where you want the value stored - * @return Value indicating whether the value assignment was successful - */ - bool getStringValue(Common::String *returnValue) const; - -private: - /** - * Helper method for destruction and assignment. It checks to see - * if the char pointer is being used, and if so calls delete on it - */ - void deleteCharPointer(); -}; - -} // End of namespace ZVision - -#endif diff --git a/engines/zvision/utility/utility.cpp b/engines/zvision/utility/utility.cpp deleted file mode 100644 index 905bc4513a..0000000000 --- a/engines/zvision/utility/utility.cpp +++ /dev/null @@ -1,237 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#include "common/scummsys.h" - -#include "zvision/utility/utility.h" - -#include "zvision/zvision.h" -#include "zvision/sound/zork_raw.h" - -#include "common/tokenizer.h" -#include "common/file.h" - - -namespace ZVision { - -void writeFileContentsToFile(const Common::String &sourceFile, const Common::String &destFile) { - Common::File f; - if (!f.open(sourceFile)) { - return; - } - - byte* buffer = new byte[f.size()]; - f.read(buffer, f.size()); - - Common::DumpFile dumpFile; - dumpFile.open(destFile); - - dumpFile.write(buffer, f.size()); - dumpFile.flush(); - dumpFile.close(); - - delete[] buffer; -} - -void trimCommentsAndWhiteSpace(Common::String *string) { - for (int i = string->size() - 1; i >= 0; i--) { - if ((*string)[i] == '#') { - string->erase(i); - } - } - - string->trim(); -} - -void tryToDumpLine(const Common::String &key, - Common::String &line, - Common::HashMap<Common::String, byte> *count, - Common::HashMap<Common::String, bool> *fileAlreadyUsed, - Common::DumpFile &output) { - const byte numberOfExamplesPerType = 8; - - if ((*count)[key] < numberOfExamplesPerType && !(*fileAlreadyUsed)[key]) { - output.writeString(line); - output.writeByte('\n'); - (*count)[key]++; - (*fileAlreadyUsed)[key] = true; - } -} - -void dumpEveryResultAction(const Common::String &destFile) { - Common::HashMap<Common::String, byte> count; - Common::HashMap<Common::String, bool> fileAlreadyUsed; - - Common::DumpFile output; - output.open(destFile); - - // Find scr files - Common::ArchiveMemberList list; - SearchMan.listMatchingMembers(list, "*.scr"); - - for (Common::ArchiveMemberList::iterator iter = list.begin(); iter != list.end(); ++iter) { - Common::SeekableReadStream *stream = (*iter)->createReadStream(); - - Common::String line = stream->readLine(); - trimCommentsAndWhiteSpace(&line); - - while (!stream->eos()) { - if (line.matchString("*:add*", true)) { - tryToDumpLine("add", line, &count, &fileAlreadyUsed, output); - } else if (line.matchString("*:animplay*", true)) { - tryToDumpLine("animplay", line, &count, &fileAlreadyUsed, output); - } else if (line.matchString("*:animpreload*", true)) { - tryToDumpLine("animpreload", line, &count, &fileAlreadyUsed, output); - } else if (line.matchString("*:animunload*", true)) { - tryToDumpLine("animunload", line, &count, &fileAlreadyUsed, output); - } else if (line.matchString("*:attenuate*", true)) { - tryToDumpLine("attenuate", line, &count, &fileAlreadyUsed, output); - } else if (line.matchString("*:assign*", true)) { - tryToDumpLine("assign", line, &count, &fileAlreadyUsed, output); - } else if (line.matchString("*:change_location*", true)) { - tryToDumpLine("change_location", line, &count, &fileAlreadyUsed, output); - } else if (line.matchString("*:crossfade*", true) && !fileAlreadyUsed["add"]) { - tryToDumpLine("crossfade", line, &count, &fileAlreadyUsed, output); - } else if (line.matchString("*:debug*", true)) { - tryToDumpLine("debug", line, &count, &fileAlreadyUsed, output); - } else if (line.matchString("*:delay_render*", true)) { - tryToDumpLine("delay_render", line, &count, &fileAlreadyUsed, output); - } else if (line.matchString("*:disable_control*", true)) { - tryToDumpLine("disable_control", line, &count, &fileAlreadyUsed, output); - } else if (line.matchString("*:disable_venus*", true)) { - tryToDumpLine("disable_venus", line, &count, &fileAlreadyUsed, output); - } else if (line.matchString("*:display_message*", true)) { - tryToDumpLine("display_message", line, &count, &fileAlreadyUsed, output); - } else if (line.matchString("*:dissolve*", true)) { - tryToDumpLine("dissolve", line, &count, &fileAlreadyUsed, output); - } else if (line.matchString("*:distort*", true)) { - tryToDumpLine("distort", line, &count, &fileAlreadyUsed, output); - } else if (line.matchString("*:enable_control*", true)) { - tryToDumpLine("enable_control", line, &count, &fileAlreadyUsed, output); - } else if (line.matchString("*:flush_mouse_events*", true)) { - tryToDumpLine("flush_mouse_events", line, &count, &fileAlreadyUsed, output); - } else if (line.matchString("*:inventory*", true)) { - tryToDumpLine("inventory", line, &count, &fileAlreadyUsed, output); - } else if (line.matchString("*:kill*", true)) { - tryToDumpLine("kill", line, &count, &fileAlreadyUsed, output); - } else if (line.matchString("*:menu_bar_enable*", true)) { - tryToDumpLine("menu_bar_enable", line, &count, &fileAlreadyUsed, output); - } else if (line.matchString("*:music*", true)) { - tryToDumpLine("music", line, &count, &fileAlreadyUsed, output); - } else if (line.matchString("*:pan_track*", true)) { - tryToDumpLine("pan_track", line, &count, &fileAlreadyUsed, output); - } else if (line.matchString("*:playpreload*", true)) { - tryToDumpLine("playpreload", line, &count, &fileAlreadyUsed, output); - } else if (line.matchString("*:preferences*", true)) { - tryToDumpLine("preferences", line, &count, &fileAlreadyUsed, output); - } else if (line.matchString("*:quit*", true)) { - tryToDumpLine("quit", line, &count, &fileAlreadyUsed, output); - } else if (line.matchString("*:random*", true)) { - tryToDumpLine("random", line, &count, &fileAlreadyUsed, output); - } else if (line.matchString("*:region*", true)) { - tryToDumpLine("region", line, &count, &fileAlreadyUsed, output); - } else if (line.matchString("*:restore_game*", true)) { - tryToDumpLine("restore_game", line, &count, &fileAlreadyUsed, output); - } else if (line.matchString("*:rotate_to*", true)) { - tryToDumpLine("rotate_to", line, &count, &fileAlreadyUsed, output); - } else if (line.matchString("*:save_game*", true)) { - tryToDumpLine("save_game", line, &count, &fileAlreadyUsed, output); - } else if (line.matchString("*:set_partial_screen*", true)) { - tryToDumpLine("set_partial_screen", line, &count, &fileAlreadyUsed, output); - } else if (line.matchString("*:set_screen*", true)) { - tryToDumpLine("set_screen", line, &count, &fileAlreadyUsed, output); - } else if (line.matchString("*:set_venus*", true)) { - tryToDumpLine("set_venus", line, &count, &fileAlreadyUsed, output); - } else if (line.matchString("*:stop*", true)) { - tryToDumpLine("stop", line, &count, &fileAlreadyUsed, output); - } else if (line.matchString("*:streamvideo*", true)) { - tryToDumpLine("streamvideo", line, &count, &fileAlreadyUsed, output); - } else if (line.matchString("*:syncsound*", true)) { - tryToDumpLine("syncsound", line, &count, &fileAlreadyUsed, output); - } else if (line.matchString("*:timer*", true)) { - tryToDumpLine("timer", line, &count, &fileAlreadyUsed, output); - } else if (line.matchString("*:ttytext*", true)) { - tryToDumpLine("ttytext", line, &count, &fileAlreadyUsed, output); - } else if (line.matchString("*:universe_music*", true)) { - tryToDumpLine("universe_music", line, &count, &fileAlreadyUsed, output); - } - - line = stream->readLine(); - trimCommentsAndWhiteSpace(&line); - } - - for (Common::HashMap<Common::String, bool>::iterator fileUsedIter = fileAlreadyUsed.begin(); fileUsedIter != fileAlreadyUsed.end(); ++fileUsedIter) { - fileUsedIter->_value = false; - } - } - - output.close(); -} - -Common::String getFileName(const Common::String &fullPath) { - Common::StringTokenizer tokenizer(fullPath, "/\\"); - Common::String token; - while (!tokenizer.empty()) { - token = tokenizer.nextToken(); - } - - return token; -} - -void convertRawToWav(const Common::String &inputFile, ZVision *engine, const Common::String &outputFile) { - Common::File file; - if (!file.open(inputFile)) - return; - - Audio::AudioStream *audioStream = makeRawZorkStream(inputFile, engine); - - Common::DumpFile output; - output.open(outputFile); - - output.writeUint32BE(MKTAG('R', 'I', 'F', 'F')); - output.writeUint32LE(file.size() * 2 + 36); - output.writeUint32BE(MKTAG('W', 'A', 'V', 'E')); - output.writeUint32BE(MKTAG('f', 'm', 't', ' ')); - output.writeUint32LE(16); - output.writeUint16LE(1); - uint16 numChannels; - if (audioStream->isStereo()) { - numChannels = 2; - output.writeUint16LE(2); - } else { - numChannels = 1; - output.writeUint16LE(1); - } - output.writeUint32LE(audioStream->getRate()); - output.writeUint32LE(audioStream->getRate() * numChannels * 2); - output.writeUint16LE(numChannels * 2); - output.writeUint16LE(16); - output.writeUint32BE(MKTAG('d', 'a', 't', 'a')); - output.writeUint32LE(file.size() * 2); - int16 *buffer = new int16[file.size()]; - audioStream->readBuffer(buffer, file.size()); - output.write(buffer, file.size() * 2); - - delete[] buffer; -} - -} // End of namespace ZVision diff --git a/engines/zvision/utility/utility.h b/engines/zvision/utility/utility.h deleted file mode 100644 index 063d4c0663..0000000000 --- a/engines/zvision/utility/utility.h +++ /dev/null @@ -1,114 +0,0 @@ -/* 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 ZVISION_UTILITY_H -#define ZVISION_UTILITY_H - -#include "common/array.h" - - -namespace Common { -class String; -} - -namespace ZVision { - -class ZVision; - -/** - * Opens the sourceFile utilizing Common::File (aka SearchMan) and writes the - * contents to destFile. destFile is created in the working directory - * - * @param sourceFile The 'file' you want the contents of - * @param destFile The name of the file where the content will be written to - */ -void writeFileContentsToFile(const Common::String &sourceFile, const Common::String &destFile); - -/** - * Removes any line comments using '#' as a sequence start. - * Then removes any trailing and leading 'whitespace' using String::trim() - * Note: String::trim uses isspace() to determine what is whitespace and what is not. - * - * @param string The string to modify. It is modified in place - */ -void trimCommentsAndWhiteSpace(Common::String *string); - -/** - * Searches through all the .scr files and dumps 'numberOfExamplesPerType' examples of each type of ResultAction - * ZVision::initialize() must have been called before this function can be used. - * - * @param destFile Where to write the examples - */ -void dumpEveryResultAction(const Common::String &destFile); - -/** - * Removes all duplicate entries from container. Relative order will be preserved. - * - * @param container The Array to remove duplicate entries from - */ -template<class T> -void removeDuplicateEntries(Common::Array<T> &container) { - // Length of modified array - uint newLength = 1; - uint j; - - for(uint i = 1; i < container.size(); i++) { - for(j = 0; j < newLength; j++) { - if (container[i] == container[j]) { - break; - } - } - - // If none of the values in index[0..j] of container are the same as array[i], - // then copy the current value to corresponding new position in array - if (j == newLength) { - container[newLength++] = container[i]; - } - } - - // Actually remove the unneeded space - while (container.size() < newLength) { - container.pop_back(); - } -} - -/** - * Gets the name of the file (including extension). Forward or back slashes - * are interpreted as directory changes - * - * @param fullPath A full or partial path to the file. Ex: folderOne/folderTwo/file.txt - * @return The name of the file without any preceding directories. Ex: file.txt - */ -Common::String getFileName(const Common::String &fullPath); - -/** - * Converts a ZVision .RAW file to a .WAV - * The .WAV will be created in the working directory and will overwrite any existing file - * - * @param inputFile The path to the input .RAW file - * @param outputFile The name of the output .WAV file - */ -void convertRawToWav(const Common::String &inputFile, ZVision *engine, const Common::String &outputFile); - -} // End of namespace ZVision - -#endif diff --git a/engines/zvision/video/rlf_decoder.cpp b/engines/zvision/video/rlf_decoder.cpp new file mode 100644 index 0000000000..3bbf22edff --- /dev/null +++ b/engines/zvision/video/rlf_decoder.cpp @@ -0,0 +1,313 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* of the License, or (at your option) any later version. + +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. + +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +*/ + +#include "common/scummsys.h" + +#include "zvision/video/rlf_decoder.h" + +#include "common/str.h" +#include "common/file.h" +#include "common/textconsole.h" +#include "common/debug.h" +#include "common/endian.h" + +namespace ZVision { + +RLFDecoder::~RLFDecoder() { + close(); +} + +bool RLFDecoder::loadStream(Common::SeekableReadStream *stream) { + close(); + + // Check if the stream is valid + if (stream && !stream->err() && stream->readUint32BE() == MKTAG('F', 'E', 'L', 'R')) { + addTrack(new RLFVideoTrack(stream)); + return true; + } else { + return false; + } +} + +RLFDecoder::RLFVideoTrack::RLFVideoTrack(Common::SeekableReadStream *stream) + : _readStream(stream), + _lastFrameRead(0), + _frameCount(0), + _width(0), + _height(0), + _frameTime(0), + _frames(0), + _displayedFrame(-1), + _frameBufferByteSize(0) { + + if (!readHeader()) { + warning("Not a RLF animation file. Wrong magic number"); + return; + } + + _currentFrameBuffer.create(_width, _height, getPixelFormat()); + _frameBufferByteSize = _width * _height * sizeof(uint16); + + _frames = new Frame[_frameCount]; + + // Read in each frame + for (uint i = 0; i < _frameCount; ++i) { + _frames[i] = readNextFrame(); + } +} + +RLFDecoder::RLFVideoTrack::~RLFVideoTrack() { + for (uint i = 0; i < _frameCount; ++i) { + delete[] _frames[i].encodedData; + } + delete[] _frames; + delete _readStream; + _currentFrameBuffer.free(); +} + +bool RLFDecoder::RLFVideoTrack::readHeader() { + // Read the header + _readStream->readUint32LE(); // Size1 + _readStream->readUint32LE(); // Unknown1 + _readStream->readUint32LE(); // Unknown2 + _frameCount = _readStream->readUint32LE(); // Frame count + + // Since we don't need any of the data, we can just seek right to the + // entries we need rather than read in all the individual entries. + _readStream->seek(136, SEEK_CUR); + + //// Read CIN header + //_readStream->readUint32BE(); // Magic number FNIC + //_readStream->readUint32LE(); // Size2 + //_readStream->readUint32LE(); // Unknown3 + //_readStream->readUint32LE(); // Unknown4 + //_readStream->readUint32LE(); // Unknown5 + //_readStream->seek(0x18, SEEK_CUR); // VRLE + //_readStream->readUint32LE(); // LRVD + //_readStream->readUint32LE(); // Unknown6 + //_readStream->seek(0x18, SEEK_CUR); // HRLE + //_readStream->readUint32LE(); // ELHD + //_readStream->readUint32LE(); // Unknown7 + //_readStream->seek(0x18, SEEK_CUR); // HKEY + //_readStream->readUint32LE(); // ELRH + + //// Read MIN info header + //_readStream->readUint32BE(); // Magic number FNIM + //_readStream->readUint32LE(); // Size3 + //_readStream->readUint32LE(); // OEDV + //_readStream->readUint32LE(); // Unknown8 + //_readStream->readUint32LE(); // Unknown9 + //_readStream->readUint32LE(); // Unknown10 + _width = _readStream->readUint32LE(); // Width + _height = _readStream->readUint32LE(); // Height + + // Read time header + _readStream->readUint32BE(); // Magic number EMIT + _readStream->readUint32LE(); // Size4 + _readStream->readUint32LE(); // Unknown11 + _frameTime = _readStream->readUint32LE() / 10; // Frame time in microseconds + + return true; +} + +RLFDecoder::RLFVideoTrack::Frame RLFDecoder::RLFVideoTrack::readNextFrame() { + RLFDecoder::RLFVideoTrack::Frame frame; + + _readStream->readUint32BE(); // Magic number MARF + uint32 size = _readStream->readUint32LE(); // Size + _readStream->readUint32LE(); // Unknown1 + _readStream->readUint32LE(); // Unknown2 + uint32 type = _readStream->readUint32BE(); // Either ELHD or ELRH + uint32 headerSize = _readStream->readUint32LE(); // Offset from the beginning of this frame to the frame data. Should always be 28 + _readStream->readUint32LE(); // Unknown3 + + frame.encodedSize = size - headerSize; + frame.encodedData = new int8[frame.encodedSize]; + _readStream->read(frame.encodedData, frame.encodedSize); + + if (type == MKTAG('E', 'L', 'H', 'D')) { + frame.type = Masked; + } else if (type == MKTAG('E', 'L', 'R', 'H')) { + frame.type = Simple; + _completeFrames.push_back(_lastFrameRead); + } else { + warning("Frame %u doesn't have type that can be decoded", _lastFrameRead); + } + + _lastFrameRead++; + return frame; +} + +bool RLFDecoder::RLFVideoTrack::seek(const Audio::Timestamp &time) { + uint frame = getFrameAtTime(time); + assert(frame < _frameCount); + + if ((uint)_displayedFrame == frame) + return true; + + int closestFrame = _displayedFrame; + int distance = (int)frame - closestFrame; + + if (distance < 0) { + for (uint i = 0; i < _completeFrames.size(); ++i) { + if (_completeFrames[i] > frame) + break; + closestFrame = _completeFrames[i]; + } + } else { + for (uint i = 0; i < _completeFrames.size(); ++i) { + int newDistance = (int)frame - (int)(_completeFrames[i]); + if (newDistance < 0) + break; + if (newDistance < distance) { + closestFrame = _completeFrames[i]; + distance = newDistance; + } + } + } + + for (uint i = closestFrame; i < frame; ++i) { + applyFrameToCurrent(i); + } + + _displayedFrame = frame - 1; + + return true; +} + +const Graphics::Surface *RLFDecoder::RLFVideoTrack::decodeNextFrame() { + if (_displayedFrame >= (int)_frameCount) + return NULL; + + _displayedFrame++; + applyFrameToCurrent(_displayedFrame); + + return &_currentFrameBuffer; +} + +void RLFDecoder::RLFVideoTrack::applyFrameToCurrent(uint frameNumber) { + if (_frames[frameNumber].type == Masked) { + decodeMaskedRunLengthEncoding(_frames[frameNumber].encodedData, (int8 *)_currentFrameBuffer.getPixels(), _frames[frameNumber].encodedSize, _frameBufferByteSize); + } else if (_frames[frameNumber].type == Simple) { + decodeSimpleRunLengthEncoding(_frames[frameNumber].encodedData, (int8 *)_currentFrameBuffer.getPixels(), _frames[frameNumber].encodedSize, _frameBufferByteSize); + } +} + +void RLFDecoder::RLFVideoTrack::decodeMaskedRunLengthEncoding(int8 *source, int8 *dest, uint32 sourceSize, uint32 destSize) const { + uint32 sourceOffset = 0; + uint32 destOffset = 0; + int16 numberOfCopy = 0; + + while (sourceOffset < sourceSize) { + int8 numberOfSamples = source[sourceOffset]; + sourceOffset++; + + // If numberOfSamples is negative, the next abs(numberOfSamples) samples should + // be copied directly from source to dest + if (numberOfSamples < 0) { + numberOfCopy = -numberOfSamples; + + while (numberOfCopy > 0) { + if (sourceOffset + 1 >= sourceSize) { + return; + } else if (destOffset + 1 >= destSize) { + debug(2, "Frame decoding overflow\n\tsourceOffset=%u\tsourceSize=%u\n\tdestOffset=%u\tdestSize=%u", sourceOffset, sourceSize, destOffset, destSize); + return; + } + + WRITE_UINT16(dest + destOffset, READ_LE_UINT16(source + sourceOffset)); + + sourceOffset += 2; + destOffset += 2; + numberOfCopy--; + } + + // If numberOfSamples is >= 0, move destOffset forward ((numberOfSamples * 2) + 2) + // This function assumes the dest buffer has been memset with 0's. + } else { + if (sourceOffset + 1 >= sourceSize) { + return; + } else if (destOffset + 1 >= destSize) { + debug(2, "Frame decoding overflow\n\tsourceOffset=%u\tsourceSize=%u\n\tdestOffset=%u\tdestSize=%u", sourceOffset, sourceSize, destOffset, destSize); + return; + } + + destOffset += (numberOfSamples * 2) + 2; + } + } +} + +void RLFDecoder::RLFVideoTrack::decodeSimpleRunLengthEncoding(int8 *source, int8 *dest, uint32 sourceSize, uint32 destSize) const { + uint32 sourceOffset = 0; + uint32 destOffset = 0; + int16 numberOfCopy = 0; + + while (sourceOffset < sourceSize) { + int8 numberOfSamples = source[sourceOffset]; + sourceOffset++; + + // If numberOfSamples is negative, the next abs(numberOfSamples) samples should + // be copied directly from source to dest + if (numberOfSamples < 0) { + numberOfCopy = -numberOfSamples; + + while (numberOfCopy > 0) { + if (sourceOffset + 1 >= sourceSize) { + return; + } else if (destOffset + 1 >= destSize) { + debug(2, "Frame decoding overflow\n\tsourceOffset=%u\tsourceSize=%u\n\tdestOffset=%u\tdestSize=%u", sourceOffset, sourceSize, destOffset, destSize); + return; + } + + WRITE_UINT16(dest + destOffset, READ_LE_UINT16(source + sourceOffset)); + + sourceOffset += 2; + destOffset += 2; + numberOfCopy--; + } + + // If numberOfSamples is >= 0, copy one sample from source to the + // next (numberOfSamples + 2) dest spots + } else { + if (sourceOffset + 1 >= sourceSize) { + return; + } + + uint16 sampleColor = READ_LE_UINT16(source + sourceOffset); + sourceOffset += 2; + + numberOfCopy = numberOfSamples + 2; + while (numberOfCopy > 0) { + if (destOffset + 1 >= destSize) { + debug(2, "Frame decoding overflow\n\tsourceOffset=%u\tsourceSize=%u\n\tdestOffset=%u\tdestSize=%u", sourceOffset, sourceSize, destOffset, destSize); + return; + } + + WRITE_UINT16(dest + destOffset, sampleColor); + destOffset += 2; + numberOfCopy--; + } + } + } +} + +} // End of namespace ZVision diff --git a/engines/zvision/video/rlf_decoder.h b/engines/zvision/video/rlf_decoder.h new file mode 100644 index 0000000000..8b8cbaecd5 --- /dev/null +++ b/engines/zvision/video/rlf_decoder.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 ZVISION_RLF_DECODER_H +#define ZVISION_RLF_DECODER_H + +#include "common/file.h" +#include "video/video_decoder.h" + +#include "graphics/surface.h" + +namespace ZVision { + +class RLFDecoder : public Video::VideoDecoder { +public: + RLFDecoder() {} + ~RLFDecoder(); + + bool loadStream(Common::SeekableReadStream *stream); + +private: + class RLFVideoTrack : public FixedRateVideoTrack { + public: + RLFVideoTrack(Common::SeekableReadStream *stream); + ~RLFVideoTrack(); + + uint16 getWidth() const { return _width; } + uint16 getHeight() const { return _height; } + Graphics::PixelFormat getPixelFormat() const { return Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0); /* RGB 555 */ } + int getCurFrame() const { return _displayedFrame; } + int getFrameCount() const { return _frameCount; } + const Graphics::Surface *decodeNextFrame(); + bool isSeekable() const { return true; } + bool seek(const Audio::Timestamp &time); + + protected: + Common::Rational getFrameRate() const { return Common::Rational(1000, _frameTime); } + + private: + enum EncodingType { + Masked, + Simple + }; + + struct Frame { + EncodingType type; + int8 *encodedData; + uint32 encodedSize; + }; + + /** + * Reads in the header of the RLF file + * + * @return Will return false if the header magic number is wrong + */ + bool readHeader(); + + /** + * Reads the next frame from the RLF file, stores the data in + * a Frame object, then returns the object + * + * @return A Frame object representing the frame data + */ + Frame readNextFrame(); + + /** + * Applies the frame corresponding to frameNumber on top of _currentFrameBuffer. + * This function requires _stream = false so it can look up the Frame object + * referenced by frameNumber. + * + * @param frameNumber The frame number to apply to _currentFrameBuffer + */ + void applyFrameToCurrent(uint frameNumber); + + /** + * Decode frame data that uses masked run length encoding. This is the encoding + * used by P-frames. + * + * @param source The source pixel data + * @param dest The destination buffer + * @param sourceSize The size of the source pixel data + * @param destSize The size of the destination buffer + */ + void decodeMaskedRunLengthEncoding(int8 *source, int8 *dest, uint32 sourceSize, uint32 destSize) const; + /** + * Decode frame data that uses simple run length encoding. This is the encoding + * used by I-frames. + * + * @param source The source pixel data + * @param dest The destination buffer + * @param sourceSize The size of the source pixel data + * @param destSize The size of the destination buffer + */ + void decodeSimpleRunLengthEncoding(int8 *source, int8 *dest, uint32 sourceSize, uint32 destSize) const; + + uint _lastFrameRead; + + uint _frameCount; + uint _width; + uint _height; + uint32 _frameTime; // In milliseconds + Frame *_frames; + Common::Array<uint> _completeFrames; + + int _displayedFrame; + Graphics::Surface _currentFrameBuffer; + uint32 _frameBufferByteSize; + + Common::SeekableReadStream *_readStream; + }; // RLFVideoTrack +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/video/video.cpp b/engines/zvision/video/video.cpp index d1fff30408..1cfd0f4197 100644 --- a/engines/zvision/video/video.cpp +++ b/engines/zvision/video/video.cpp @@ -8,12 +8,12 @@ * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. - * + * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. @@ -21,107 +21,78 @@ */ #include "common/scummsys.h" - -#include "zvision/zvision.h" - -#include "zvision/utility/clock.h" -#include "zvision/graphics/render_manager.h" - #include "common/system.h" - #include "video/video_decoder.h" - +#ifdef USE_MPEG2 +#include "video/mpegps_decoder.h" +#endif #include "engines/util.h" - #include "graphics/surface.h" +#include "zvision/zvision.h" +#include "zvision/core/clock.h" +#include "zvision/graphics/render_manager.h" +#include "zvision/scripting/script_manager.h" +#include "zvision/text/subtitles.h" +#include "zvision/video/rlf_decoder.h" +#include "zvision/video/zork_avi_decoder.h" namespace ZVision { -// Taken/modified from SCI -void scaleBuffer(const byte *src, byte *dst, uint32 srcWidth, uint32 srcHeight, byte bytesPerPixel, uint scaleAmount) { - assert(bytesPerPixel == 1 || bytesPerPixel == 2); - - const uint32 newWidth = srcWidth * scaleAmount; - const uint32 pitch = newWidth * bytesPerPixel; - const byte *srcPtr = src; - - if (bytesPerPixel == 1) { - for (uint32 y = 0; y < srcHeight; ++y) { - for (uint32 x = 0; x < srcWidth; ++x) { - const byte color = *srcPtr++; - - for (uint i = 0; i < scaleAmount; ++i) { - dst[i] = color; - dst[pitch + i] = color; - } - dst += scaleAmount; - } - dst += pitch; - } - } else if (bytesPerPixel == 2) { - for (uint32 y = 0; y < srcHeight; ++y) { - for (uint32 x = 0; x < srcWidth; ++x) { - const byte color = *srcPtr++; - const byte color2 = *srcPtr++; - - for (uint i = 0; i < scaleAmount; ++i) { - uint index = i *2; - - dst[index] = color; - dst[index + 1] = color2; - dst[pitch + index] = color; - dst[pitch + index + 1] = color2; - } - dst += 2 * scaleAmount; - } - dst += pitch; - } - } +Video::VideoDecoder *ZVision::loadAnimation(const Common::String &fileName) { + Common::String tmpFileName = fileName; + tmpFileName.toLowercase(); + Video::VideoDecoder *animation = NULL; + + if (tmpFileName.hasSuffix(".rlf")) + animation = new RLFDecoder(); + else if (tmpFileName.hasSuffix(".avi")) + animation = new ZorkAVIDecoder(); +#ifdef USE_MPEG2 + else if (tmpFileName.hasSuffix(".vob")) + animation = new Video::MPEGPSDecoder(); +#endif + else + error("Unknown suffix for animation %s", fileName.c_str()); + + Common::File *_file = getSearchManager()->openFile(tmpFileName); + if (!_file) + error("Error opening %s", tmpFileName.c_str()); + + bool loaded = animation->loadStream(_file); + if (!loaded) + error("Error loading animation %s", tmpFileName.c_str()); + + return animation; } -void ZVision::playVideo(Video::VideoDecoder &videoDecoder, const Common::Rect &destRect, bool skippable) { - byte bytesPerPixel = videoDecoder.getPixelFormat().bytesPerPixel; - - uint16 origWidth = videoDecoder.getWidth(); - uint16 origHeight = videoDecoder.getHeight(); - - uint scale = 1; +void ZVision::playVideo(Video::VideoDecoder &vid, const Common::Rect &destRect, bool skippable, Subtitle *sub) { + Common::Rect dst = destRect; // If destRect is empty, no specific scaling was requested. However, we may choose to do scaling anyway - if (destRect.isEmpty()) { - // Most videos are very small. Therefore we do a simple 2x scale - if (origWidth * 2 <= 640 && origHeight * 2 <= 480) { - scale = 2; - } - } else { - // Assume bilinear scaling. AKA calculate the scale from just the width. - // Also assume that the scaling is in integral intervals. AKA no 1.5x scaling - // TODO: Test ^these^ assumptions - scale = destRect.width() / origWidth; - - // TODO: Test if we need to support downscale. - } - - uint16 pitch = origWidth * bytesPerPixel; + if (dst.isEmpty()) + dst = Common::Rect(vid.getWidth(), vid.getHeight()); - uint16 finalWidth = origWidth * scale; - uint16 finalHeight = origHeight * scale; + Graphics::Surface *scaled = NULL; - byte *scaledVideoFrameBuffer = 0; - if (scale != 1) { - scaledVideoFrameBuffer = new byte[finalWidth * finalHeight * bytesPerPixel]; + if (vid.getWidth() != dst.width() || vid.getHeight() != dst.height()) { + scaled = new Graphics::Surface; + scaled->create(dst.width(), dst.height(), vid.getPixelFormat()); } - uint16 x = ((WINDOW_WIDTH - finalWidth) / 2) + destRect.left; - uint16 y = ((WINDOW_HEIGHT - finalHeight) / 2) + destRect.top; + uint16 x = _workingWindow.left + dst.left; + uint16 y = _workingWindow.top + dst.top; + uint16 finalWidth = dst.width() < _workingWindow.width() ? dst.width() : _workingWindow.width(); + uint16 finalHeight = dst.height() < _workingWindow.height() ? dst.height() : _workingWindow.height(); + bool showSubs = (_scriptManager->getStateValue(StateKey_Subtitles) == 1); _clock.stop(); - videoDecoder.start(); + vid.start(); + _videoIsPlaying = true; // Only continue while the video is still playing - while (!shouldQuit() && !videoDecoder.endOfVideo() && videoDecoder.isPlaying()) { + while (!shouldQuit() && !vid.endOfVideo() && vid.isPlaying()) { // Check for engine quit and video stop key presses - while (!videoDecoder.endOfVideo() && videoDecoder.isPlaying() && _eventMan->pollEvent(_event)) { + while (_eventMan->pollEvent(_event)) { switch (_event.type) { case Common::EVENT_KEYDOWN: switch (_event.kbd.keycode) { @@ -131,7 +102,7 @@ void ZVision::playVideo(Video::VideoDecoder &videoDecoder, const Common::Rect &d break; case Common::KEYCODE_SPACE: if (skippable) { - videoDecoder.stop(); + vid.stop(); } break; default: @@ -142,29 +113,34 @@ void ZVision::playVideo(Video::VideoDecoder &videoDecoder, const Common::Rect &d } } - if (videoDecoder.needsUpdate()) { - const Graphics::Surface *frame = videoDecoder.decodeNextFrame(); + if (vid.needsUpdate()) { + const Graphics::Surface *frame = vid.decodeNextFrame(); + if (sub && showSubs) + sub->process(vid.getCurFrame()); if (frame) { - if (scale != 1) { - scaleBuffer((const byte *)frame->getPixels(), scaledVideoFrameBuffer, origWidth, origHeight, bytesPerPixel, scale); - _system->copyRectToScreen(scaledVideoFrameBuffer, pitch * 2, x, y, finalWidth, finalHeight); - } else { - _system->copyRectToScreen((const byte *)frame->getPixels(), pitch, x, y, finalWidth, finalHeight); + if (scaled) { + _renderManager->scaleBuffer(frame->getPixels(), scaled->getPixels(), frame->w, frame->h, frame->format.bytesPerPixel, scaled->w, scaled->h); + frame = scaled; } + Common::Rect rect = Common::Rect(x, y, x + finalWidth, y + finalHeight); + _renderManager->copyToScreen(*frame, rect, 0, 0); + _renderManager->processSubs(0); } } // Always update the screen so the mouse continues to render _system->updateScreen(); - _system->delayMillis(videoDecoder.getTimeToNextFrame()); + _system->delayMillis(vid.getTimeToNextFrame() / 2); } + _videoIsPlaying = false; _clock.start(); - if (scale != 1) { - delete[] scaledVideoFrameBuffer; + if (scaled) { + scaled->free(); + delete scaled; } } diff --git a/engines/zvision/video/zork_avi_decoder.cpp b/engines/zvision/video/zork_avi_decoder.cpp index f22a4203ab..cf8505ec82 100644 --- a/engines/zvision/video/zork_avi_decoder.cpp +++ b/engines/zvision/video/zork_avi_decoder.cpp @@ -29,7 +29,7 @@ #include "common/stream.h" #include "audio/audiostream.h" - +#include "audio/decoders/raw.h" namespace ZVision { @@ -39,14 +39,39 @@ Video::AVIDecoder::AVIAudioTrack *ZorkAVIDecoder::createAudioTrack(Video::AVIDec } void ZorkAVIDecoder::ZorkAVIAudioTrack::queueSound(Common::SeekableReadStream *stream) { + bool updateCurChunk = true; if (_audStream) { if (_wvInfo.tag == kWaveFormatZorkPCM) { assert(_wvInfo.size == 8); - _audStream->queueAudioStream(makeRawZorkStream(stream, _wvInfo.samplesPerSec, _audStream->isStereo(), DisposeAfterUse::YES), DisposeAfterUse::YES); + RawChunkStream::RawChunk chunk = decoder->readNextChunk(stream); + delete stream; + + if (chunk.data) { + byte flags = Audio::FLAG_16BITS | Audio::FLAG_STEREO; +#ifdef SCUMM_LITTLE_ENDIAN + // RawChunkStream produces native endianness int16 + flags |= Audio::FLAG_LITTLE_ENDIAN; +#endif + _audStream->queueBuffer((byte *)chunk.data, chunk.size, DisposeAfterUse::YES, flags); + } + } else { + updateCurChunk = false; + AVIAudioTrack::queueSound(stream); } } else { delete stream; } + + // The superclass always updates _curChunk, whether or not audio has + // been queued, so we should do that too. Unless the superclass already + // has done it for us. + if (updateCurChunk) { + _curChunk++; + } +} + +void ZorkAVIDecoder::ZorkAVIAudioTrack::resetStream() { + decoder->init(); } } // End of namespace ZVision diff --git a/engines/zvision/video/zork_avi_decoder.h b/engines/zvision/video/zork_avi_decoder.h index c47f007f9b..89c0d1e4b9 100644 --- a/engines/zvision/video/zork_avi_decoder.h +++ b/engines/zvision/video/zork_avi_decoder.h @@ -8,41 +8,53 @@ * 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 ZORK_AVI_DECODER_H #define ZORK_AVI_DECODER_H #include "video/avi_decoder.h" - +#include "zvision/sound/zork_raw.h" namespace ZVision { class ZorkAVIDecoder : public Video::AVIDecoder { public: ZorkAVIDecoder(Audio::Mixer::SoundType soundType = Audio::Mixer::kPlainSoundType) : - Video::AVIDecoder(soundType) {} + Video::AVIDecoder(soundType) {} - virtual ~ZorkAVIDecoder() {} + virtual ~ZorkAVIDecoder() {} private: class ZorkAVIAudioTrack : public Video::AVIDecoder::AVIAudioTrack { public: ZorkAVIAudioTrack(const AVIStreamHeader &streamHeader, const PCMWaveFormat &waveFormat, Audio::Mixer::SoundType soundType) : - Video::AVIDecoder::AVIAudioTrack(streamHeader, waveFormat, soundType) {} - virtual ~ZorkAVIAudioTrack() {} + Video::AVIDecoder::AVIAudioTrack(streamHeader, waveFormat, soundType), + decoder(NULL) { + if (_audStream) { + decoder = new RawChunkStream(_audStream->isStereo()); + } + } + virtual ~ZorkAVIAudioTrack() { + if (decoder) + delete decoder; + } void queueSound(Common::SeekableReadStream *stream); + void resetStream(); + private: + RawChunkStream *decoder; }; Video::AVIDecoder::AVIAudioTrack *createAudioTrack(Video::AVIDecoder::AVIStreamHeader sHeader, Video::AVIDecoder::PCMWaveFormat wvInfo); @@ -50,7 +62,7 @@ private: private: // Audio Codecs enum { - kWaveFormatZorkPCM = 17 // special Zork PCM audio format (clashes with MS IMA ADPCM) + kWaveFormatZorkPCM = 17 // special Zork PCM audio format (clashes with MS IMA ADPCM) }; }; diff --git a/engines/zvision/zvision.cpp b/engines/zvision/zvision.cpp index b464f6ee81..da80ff9d02 100644 --- a/engines/zvision/zvision.cpp +++ b/engines/zvision/zvision.cpp @@ -8,12 +8,12 @@ * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. - * + * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. @@ -23,17 +23,22 @@ #include "common/scummsys.h" #include "zvision/zvision.h" - #include "zvision/core/console.h" #include "zvision/scripting/script_manager.h" #include "zvision/graphics/render_manager.h" -#include "zvision/cursors/cursor_manager.h" -#include "zvision/core/save_manager.h" -#include "zvision/strings/string_manager.h" -#include "zvision/archives/zfs_archive.h" +#include "zvision/graphics/cursors/cursor_manager.h" +#include "zvision/file/save_manager.h" +#include "zvision/text/string_manager.h" #include "zvision/detection.h" +#include "zvision/scripting/menu.h" +#include "zvision/file/search_manager.h" +#include "zvision/text/text.h" +#include "zvision/text/truetype_font.h" +#include "zvision/sound/midi.h" +#include "zvision/file/zfs_archive.h" #include "common/config-manager.h" +#include "common/str.h" #include "common/debug.h" #include "common/debug-channels.h" #include "common/textconsole.h" @@ -41,28 +46,67 @@ #include "common/system.h" #include "common/file.h" +#include "gui/message.h" #include "engines/util.h" - #include "audio/mixer.h" - namespace ZVision { +#define ZVISION_SETTINGS_KEYS_COUNT 12 + +struct zvisionIniSettings { + const char *name; + int16 slot; + int16 defaultValue; // -1: use the bool value + bool defaultBoolValue; + bool allowEditing; +} settingsKeys[ZVISION_SETTINGS_KEYS_COUNT] = { + // Hardcoded settings + {"countrycode", StateKey_CountryCode, 0, false, false}, // always 0 = US, subtitles are shown for codes 0 - 4, unused + {"lineskipvideo", StateKey_VideoLineSkip, 0, false, false}, // video line skip, 0 = default, 1 = always, 2 = pixel double when possible, unused + {"installlevel", StateKey_InstallLevel, 0, false, false}, // 0 = full, checked by universe.scr + {"highquality", StateKey_HighQuality, -1, true, false}, // high panorama quality, unused + {"qsoundenabled", StateKey_Qsound, -1, true, false}, // 1 = enable QSound - TODO: not supported yet + {"debugcheats", StateKey_DebugCheats, -1, true, false}, // always start with the GOxxxx cheat enabled + // Editable settings + {"keyboardturnspeed", StateKey_KbdRotateSpeed, 5, false, true}, + {"panarotatespeed", StateKey_RotateSpeed, 540, false, true}, // checked by universe.scr + {"noanimwhileturning", StateKey_NoTurnAnim, -1, false, true}, // toggle playing animations during pana rotation + {"venusenabled", StateKey_VenusEnable, -1, true, true}, + {"subtitles", StateKey_Subtitles, -1, true, true}, + {"mpegmovies", StateKey_MPEGMovies, -1, true, true} // Zork: Grand Inquisitor DVD hi-res MPEG movies (0 = normal, 1 = hires, 2 = disable option) +}; + ZVision::ZVision(OSystem *syst, const ZVisionGameDescription *gameDesc) - : Engine(syst), - _gameDescription(gameDesc), - _workingWindow(gameDesc->gameId == GID_NEMESIS ? Common::Rect((WINDOW_WIDTH - ZNEM_WORKING_WINDOW_WIDTH) / 2, (WINDOW_HEIGHT - ZNEM_WORKING_WINDOW_HEIGHT) / 2, ((WINDOW_WIDTH - ZNEM_WORKING_WINDOW_WIDTH) / 2) + ZNEM_WORKING_WINDOW_WIDTH, ((WINDOW_HEIGHT - ZNEM_WORKING_WINDOW_HEIGHT) / 2) + ZNEM_WORKING_WINDOW_HEIGHT) : - Common::Rect((WINDOW_WIDTH - ZGI_WORKING_WINDOW_WIDTH) / 2, (WINDOW_HEIGHT - ZGI_WORKING_WINDOW_HEIGHT) / 2, ((WINDOW_WIDTH - ZGI_WORKING_WINDOW_WIDTH) / 2) + ZGI_WORKING_WINDOW_WIDTH, ((WINDOW_HEIGHT - ZGI_WORKING_WINDOW_HEIGHT) / 2) + ZGI_WORKING_WINDOW_HEIGHT)), - _pixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0), /*RGB 565*/ - _desiredFrameTime(33), /* ~30 fps */ - _clock(_system), - _scriptManager(nullptr), - _renderManager(nullptr), - _saveManager(nullptr), - _stringManager(nullptr), - _cursorManager(nullptr) { + : Engine(syst), + _gameDescription(gameDesc), + _resourcePixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0), /* RGB 555 */ + _screenPixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0), /* RGB 565 */ + _desiredFrameTime(33), /* ~30 fps */ + _clock(_system), + _scriptManager(nullptr), + _renderManager(nullptr), + _saveManager(nullptr), + _stringManager(nullptr), + _cursorManager(nullptr), + _midiManager(nullptr), + _rnd(nullptr), + _console(nullptr), + _menu(nullptr), + _searchManager(nullptr), + _textRenderer(nullptr), + _doubleFPS(false), + _audioId(0), + _frameRenderDelay(2), + _keyboardVelocity(0), + _mouseVelocity(0), + _videoIsPlaying(false), + _renderedFrameCount(0), + _fps(0) { debug(1, "ZVision::ZVision"); + + memset(_cheatBuffer, 0, sizeof(_cheatBuffer)); } ZVision::~ZVision() { @@ -73,90 +117,237 @@ ZVision::~ZVision() { delete _cursorManager; delete _stringManager; delete _saveManager; - delete _renderManager; delete _scriptManager; + delete _renderManager; // should be deleted after the script manager delete _rnd; + delete _midiManager; + + getTimerManager()->removeTimerProc(&fpsTimerCallback); // Remove all of our debug levels DebugMan.clearAllDebugChannels(); } +void ZVision::registerDefaultSettings() { + for (int i = 0; i < ZVISION_SETTINGS_KEYS_COUNT; i++) { + if (settingsKeys[i].allowEditing) { + if (settingsKeys[i].defaultValue >= 0) + ConfMan.registerDefault(settingsKeys[i].name, settingsKeys[i].defaultValue); + else + ConfMan.registerDefault(settingsKeys[i].name, settingsKeys[i].defaultBoolValue); + } + } + + ConfMan.registerDefault("originalsaveload", false); + ConfMan.registerDefault("doublefps", false); +} + +void ZVision::loadSettings() { + int16 value = 0; + bool boolValue = false; + + for (int i = 0; i < ZVISION_SETTINGS_KEYS_COUNT; i++) { + if (settingsKeys[i].defaultValue >= 0) { + value = (settingsKeys[i].allowEditing) ? ConfMan.getInt(settingsKeys[i].name) : settingsKeys[i].defaultValue; + } else { + boolValue = (settingsKeys[i].allowEditing) ? ConfMan.getBool(settingsKeys[i].name) : settingsKeys[i].defaultBoolValue; + value = (boolValue) ? 1 : 0; + } + + _scriptManager->setStateValue(settingsKeys[i].slot, value); + } + + if (getGameId() == GID_NEMESIS) + _scriptManager->setStateValue(StateKey_ExecScopeStyle, 1); + else + _scriptManager->setStateValue(StateKey_ExecScopeStyle, 0); +} + +void ZVision::saveSettings() { + for (int i = 0; i < ZVISION_SETTINGS_KEYS_COUNT; i++) { + if (settingsKeys[i].allowEditing) { + if (settingsKeys[i].defaultValue >= 0) + ConfMan.setInt(settingsKeys[i].name, _scriptManager->getStateValue(settingsKeys[i].slot)); + else + ConfMan.setBool(settingsKeys[i].name, (_scriptManager->getStateValue(settingsKeys[i].slot) == 1)); + } + } + + ConfMan.flushToDisk(); +} + void ZVision::initialize() { const Common::FSNode gameDataDir(ConfMan.get("path")); - // TODO: There are 10 file clashes when we flatten the directories. - // From a quick look, the files are exactly the same, so it shouldn't matter. - // But I'm noting it here just in-case it does become a problem. - SearchMan.addSubDirectoryMatching(gameDataDir, "data1", 0, 4, true); - SearchMan.addSubDirectoryMatching(gameDataDir, "data2", 0, 4, true); - SearchMan.addSubDirectoryMatching(gameDataDir, "data3", 0, 4, true); - SearchMan.addSubDirectoryMatching(gameDataDir, "zassets1", 0, 2, true); - SearchMan.addSubDirectoryMatching(gameDataDir, "zassets2", 0, 2, true); - SearchMan.addSubDirectoryMatching(gameDataDir, "znemmx", 0, 1, true); - SearchMan.addSubDirectoryMatching(gameDataDir, "zgi", 0, 4, true); - SearchMan.addSubDirectoryMatching(gameDataDir, "fonts", 0, 1, true); - - // Find zfs archive files - Common::ArchiveMemberList list; - SearchMan.listMatchingMembers(list, "*.zfs"); - - // Register the file entries within the zfs archives with the SearchMan - for (Common::ArchiveMemberList::iterator iter = list.begin(); iter != list.end(); ++iter) { - Common::String name = (*iter)->getName(); - Common::SeekableReadStream *stream = (*iter)->createReadStream(); - ZfsArchive *archive = new ZfsArchive(name, stream); - - delete stream; - - SearchMan.add(name, archive); + + _searchManager = new SearchManager(ConfMan.get("path"), 6); + + _searchManager->addDir("FONTS"); + _searchManager->addDir("addon"); + + if (_gameDescription->gameId == GID_GRANDINQUISITOR) { + if (!_searchManager->loadZix("INQUIS.ZIX")) + error("Unable to load file INQUIS.ZIX"); + } else if (_gameDescription->gameId == GID_NEMESIS) { + if (!_searchManager->loadZix("NEMESIS.ZIX")) { + // The game might not be installed, try MEDIUM.ZIX instead + if (!_searchManager->loadZix("ZNEMSCR/MEDIUM.ZIX")) + error("Unable to load the file ZNEMSCR/MEDIUM.ZIX"); + } } - initGraphics(WINDOW_WIDTH, WINDOW_HEIGHT, true, &_pixelFormat); + initScreen(); // Register random source _rnd = new Common::RandomSource("zvision"); // Create managers _scriptManager = new ScriptManager(this); - _renderManager = new RenderManager(_system, WINDOW_WIDTH, WINDOW_HEIGHT, _workingWindow, _pixelFormat); + _renderManager = new RenderManager(this, WINDOW_WIDTH, WINDOW_HEIGHT, _workingWindow, _resourcePixelFormat, _doubleFPS); _saveManager = new SaveManager(this); _stringManager = new StringManager(this); - _cursorManager = new CursorManager(this, &_pixelFormat); + _cursorManager = new CursorManager(this, _resourcePixelFormat); + _textRenderer = new TextRenderer(this); + _midiManager = new MidiManager(); + + if (_gameDescription->gameId == GID_GRANDINQUISITOR) + _menu = new MenuZGI(this); + else + _menu = new MenuNemesis(this); // Initialize the managers _cursorManager->initialize(); _scriptManager->initialize(); _stringManager->initialize(_gameDescription->gameId); + registerDefaultSettings(); + + loadSettings(); + +#ifndef USE_MPEG2 + // libmpeg2 not loaded, disable the MPEG2 movies option + _scriptManager->setStateValue(StateKey_MPEGMovies, 2); +#endif + // Create debugger console. It requires GFX to be initialized _console = new Console(this); + _doubleFPS = ConfMan.getBool("doublefps"); + + // Initialize FPS timer callback + getTimerManager()->installTimerProc(&fpsTimerCallback, 1000000, this, "zvisionFPS"); } +extern const FontStyle getSystemFont(int fontIndex); + Common::Error ZVision::run() { initialize(); + // Check if a saved game is to be loaded from the launcher + if (ConfMan.hasKey("save_slot")) + _saveManager->loadGame(ConfMan.getInt("save_slot")); + + bool foundAllFonts = true; + + // Before starting, make absolutely sure that the user has copied the needed fonts + for (int i = 0; i < FONT_COUNT; i++) { + FontStyle curFont = getSystemFont(i); + Common::String freeFontBoldItalic = Common::String("Bold") + curFont.freeFontItalicName; + + const char *fontSuffixes[4] = { "", "bd", "i", "bi" }; + const char *freeFontSuffixes[4] = { "", "Bold", curFont.freeFontItalicName, freeFontBoldItalic.c_str() }; + const char *liberationFontSuffixes[4] = { "-Regular", "-Bold", "-Italic", "-BoldItalic" }; + + for (int j = 0; j < 4; j++) { + Common::String fontName = curFont.fontBase; + if (fontName == "censcbk" && j > 0) + fontName = "schlbk"; + fontName += fontSuffixes[j]; + fontName += ".ttf"; + + if (fontName == "schlbkbd.ttf") + fontName = "schlbkb.ttf"; + if (fontName == "garabi.ttf") + continue; + if (fontName == "garai.ttf") + fontName = "garait.ttf"; + + Common::String freeFontName = curFont.freeFontBase; + freeFontName += freeFontSuffixes[j]; + freeFontName += ".ttf"; + + Common::String liberationFontName = curFont.liberationFontBase; + liberationFontName += liberationFontSuffixes[j]; + liberationFontName += ".ttf"; + + if (!Common::File::exists(fontName) && !_searchManager->hasFile(fontName) && + !Common::File::exists(liberationFontName) && !_searchManager->hasFile(liberationFontName) && + !Common::File::exists(freeFontName) && !_searchManager->hasFile(freeFontName)) { + foundAllFonts = false; + break; + } + } + + if (!foundAllFonts) + break; + } + + if (!foundAllFonts) { + GUI::MessageDialog dialog( + "Before playing this game, you'll need to copy the required " + "fonts into ScummVM's extras directory, or into the game directory. " + "On Windows, you'll need the following font files from the Windows " + "font directory: Times New Roman, Century Schoolbook, Garamond, " + "Courier New and Arial. Alternatively, you can download the " + "Liberation Fonts or the GNU FreeFont package. You'll need all the " + "fonts from the font package you choose, i.e., LiberationMono, " + "LiberationSans and LiberationSerif, or FreeMono, FreeSans and " + "FreeSerif respectively." + ); + dialog.runModal(); + quitGame(); + return Common::kUnknownError; + } + // Main loop while (!shouldQuit()) { _clock.update(); uint32 currentTime = _clock.getLastMeasuredTime(); uint32 deltaTime = _clock.getDeltaTime(); + _cursorManager->setItemID(_scriptManager->getStateValue(StateKey_InventoryItem)); + processEvents(); + _renderManager->updateRotation(); - // Call _renderManager->update() first so the background renders - // before anything that puzzles/controls will render - _renderManager->update(deltaTime); _scriptManager->update(deltaTime); + _menu->process(deltaTime); // Render the backBuffer to the screen - _renderManager->renderBackbufferToScreen(); + _renderManager->prepareBackground(); + _renderManager->renderMenuToScreen(); + _renderManager->processSubs(deltaTime); + _renderManager->renderSceneToScreen(); // Update the screen - _system->updateScreen(); + if (canRender()) { + _system->updateScreen(); + _renderedFrameCount++; + } else { + _frameRenderDelay--; + } // Calculate the frame delay based off a desired frame time int delay = _desiredFrameTime - int32(_system->getMillis() - currentTime); // Ensure non-negative delay = delay < 0 ? 0 : delay; + + if (_doubleFPS) { + delay >>= 1; + } + + if (canSaveGameStateCurrently() && shouldPerformAutoSave(_saveManager->getLastSaveTime())) { + _saveManager->autoSave(); + } + _system->delayMillis(delay); } @@ -174,11 +365,53 @@ void ZVision::pauseEngineIntern(bool pause) { } Common::String ZVision::generateSaveFileName(uint slot) { - return Common::String::format("%s.%02u", _targetName.c_str(), slot); + return Common::String::format("%s.%03u", _targetName.c_str(), slot); +} + +void ZVision::setRenderDelay(uint delay) { + _frameRenderDelay = delay; } -Common::String ZVision::generateAutoSaveFileName() { - return Common::String::format("%s.auto", _targetName.c_str()); +bool ZVision::canRender() { + return _frameRenderDelay <= 0; +} + +GUI::Debugger *ZVision::getDebugger() { + return _console; +} + +void ZVision::syncSoundSettings() { + Engine::syncSoundSettings(); + + _scriptManager->setStateValue(StateKey_Subtitles, ConfMan.getBool("subtitles") ? 1 : 0); +} + +void ZVision::fpsTimerCallback(void *refCon) { + ((ZVision *)refCon)->fpsTimer(); +} + +void ZVision::fpsTimer() { + _fps = _renderedFrameCount; + _renderedFrameCount = 0; +} + +void ZVision::initScreen() { + uint16 workingWindowWidth = (_gameDescription->gameId == GID_NEMESIS) ? ZNM_WORKING_WINDOW_WIDTH : ZGI_WORKING_WINDOW_WIDTH; + uint16 workingWindowHeight = (_gameDescription->gameId == GID_NEMESIS) ? ZNM_WORKING_WINDOW_HEIGHT : ZGI_WORKING_WINDOW_HEIGHT; + _workingWindow = Common::Rect( + (WINDOW_WIDTH - workingWindowWidth) / 2, + (WINDOW_HEIGHT - workingWindowHeight) / 2, + ((WINDOW_WIDTH - workingWindowWidth) / 2) + workingWindowWidth, + ((WINDOW_HEIGHT - workingWindowHeight) / 2) + workingWindowHeight + ); + + initGraphics(WINDOW_WIDTH, WINDOW_HEIGHT, true, &_screenPixelFormat); +} + +void ZVision::initHiresScreen() { + _renderManager->upscaleRect(_workingWindow); + + initGraphics(HIRES_WINDOW_WIDTH, HIRES_WINDOW_HEIGHT, true, &_screenPixelFormat); } } // End of namespace ZVision diff --git a/engines/zvision/zvision.h b/engines/zvision/zvision.h index 0b5a86f370..854cd77bb8 100644 --- a/engines/zvision/zvision.h +++ b/engines/zvision/zvision.h @@ -8,24 +8,25 @@ * 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 ZVISION_ZVISION_H #define ZVISION_ZVISION_H -#include "zvision/core/console.h" #include "zvision/detection.h" -#include "zvision/utility/clock.h" +#include "zvision/core/clock.h" +#include "zvision/file/search_manager.h" #include "common/random.h" #include "common/events.h" @@ -36,20 +37,56 @@ #include "gui/debugger.h" - namespace Video { class VideoDecoder; } +/** + * This is the namespace of the ZVision engine. + * + * Status of this engine: complete + * + * Games using this engine: + * - Zork Nemesis: The Forbidden Lands + * - Zork: Grand Inquisitor + * + */ + namespace ZVision { struct ZVisionGameDescription; +class Console; class ScriptManager; class RenderManager; class CursorManager; class StringManager; class SaveManager; -class RlfAnimation; +class RLFDecoder; +class MenuHandler; +class TextRenderer; +class Subtitle; +class MidiManager; + +enum { + WINDOW_WIDTH = 640, + WINDOW_HEIGHT = 480, + + HIRES_WINDOW_WIDTH = 800, + HIRES_WINDOW_HEIGHT = 600, + + // Zork nemesis working window sizes + ZNM_WORKING_WINDOW_WIDTH = 512, + ZNM_WORKING_WINDOW_HEIGHT = 320, + + // ZGI working window sizes + ZGI_WORKING_WINDOW_WIDTH = 640, + ZGI_WORKING_WINDOW_HEIGHT = 344, + + ROTATION_SCREEN_EDGE_OFFSET = 60, + MAX_ROTATION_SPEED = 400, // Pixels per second + + KEYBUF_SIZE = 20 +}; class ZVision : public Engine { public: @@ -62,26 +99,11 @@ public: * are given in this coordinate space. Also, all images are clipped to the * edges of this Rectangle */ - const Common::Rect _workingWindow; - const Graphics::PixelFormat _pixelFormat; + Common::Rect _workingWindow; + const Graphics::PixelFormat _resourcePixelFormat; + const Graphics::PixelFormat _screenPixelFormat; private: - enum { - WINDOW_WIDTH = 640, - WINDOW_HEIGHT = 480, - - //Zork nemesis working window sizes - ZNEM_WORKING_WINDOW_WIDTH = 512, - ZNEM_WORKING_WINDOW_HEIGHT = 320, - - //ZGI(and default) working window sizes - ZGI_WORKING_WINDOW_WIDTH = 640, - ZGI_WORKING_WINDOW_HEIGHT = 344, - - ROTATION_SCREEN_EDGE_OFFSET = 60, - MAX_ROTATION_SPEED = 400 // Pixels per second - }; - Console *_console; const ZVisionGameDescription *_gameDescription; @@ -96,27 +118,87 @@ private: CursorManager *_cursorManager; SaveManager *_saveManager; StringManager *_stringManager; + MenuHandler *_menu; + SearchManager *_searchManager; + TextRenderer *_textRenderer; + MidiManager *_midiManager; // Clock Clock _clock; + // Audio ID + int _audioId; + // To prevent allocation every time we process events Common::Event _event; + int _frameRenderDelay; + int _renderedFrameCount; + int _fps; + int16 _mouseVelocity; + int16 _keyboardVelocity; + bool _doubleFPS; + bool _videoIsPlaying; + + uint8 _cheatBuffer[KEYBUF_SIZE]; public: uint32 getFeatures() const; Common::Language getLanguage() const; Common::Error run(); void pauseEngineIntern(bool pause); - ScriptManager *getScriptManager() const { return _scriptManager; } - RenderManager *getRenderManager() const { return _renderManager; } - CursorManager *getCursorManager() const { return _cursorManager; } - SaveManager *getSaveManager() const { return _saveManager; } - StringManager *getStringManager() const { return _stringManager; } - Common::RandomSource *getRandomSource() const { return _rnd; } - ZVisionGameId getGameId() const { return _gameDescription->gameId; } - GUI::Debugger *getDebugger() { return _console; } + ScriptManager *getScriptManager() const { + return _scriptManager; + } + RenderManager *getRenderManager() const { + return _renderManager; + } + CursorManager *getCursorManager() const { + return _cursorManager; + } + SaveManager *getSaveManager() const { + return _saveManager; + } + StringManager *getStringManager() const { + return _stringManager; + } + SearchManager *getSearchManager() const { + return _searchManager; + } + TextRenderer *getTextRenderer() const { + return _textRenderer; + } + MidiManager *getMidiManager() const { + return _midiManager; + } + MenuHandler *getMenuHandler() const { + return _menu; + } + Common::RandomSource *getRandomSource() const { + return _rnd; + } + ZVisionGameId getGameId() const { + return _gameDescription->gameId; + } + int16 getKeyboardVelocity() const { + return _keyboardVelocity; + } + int16 getMouseVelocity() const { + return _mouseVelocity; + } + + uint8 getZvisionKey(Common::KeyCode scummKeyCode); + + void startClock() { + _clock.start(); + } + + void stopClock() { + _clock.stop(); + } + + void initScreen(); + void initHiresScreen(); /** * Play a video until it is finished. This is a blocking call. It will call @@ -127,11 +209,33 @@ public: * @param destRect Where to put the video. (In working window coords) * @param skippable If true, the video can be skipped at any time using [Spacebar] */ - void playVideo(Video::VideoDecoder &videoDecoder, const Common::Rect &destRect = Common::Rect(0, 0, 0, 0), bool skippable = true); + void playVideo(Video::VideoDecoder &videoDecoder, const Common::Rect &destRect = Common::Rect(0, 0, 0, 0), bool skippable = true, Subtitle *sub = NULL); + Video::VideoDecoder *loadAnimation(const Common::String &fileName); Common::String generateSaveFileName(uint slot); - Common::String generateAutoSaveFileName(); + void setRenderDelay(uint); + bool canRender(); + static void fpsTimerCallback(void *refCon); + void fpsTimer(); + int getFPS() const { + return _fps; + } + + GUI::Debugger *getDebugger(); + void syncSoundSettings(); + + void loadSettings(); + void saveSettings(); + + bool ifQuit(); + + // Engine features + bool hasFeature(EngineFeature f) const; + bool canLoadGameStateCurrently(); + bool canSaveGameStateCurrently(); + Common::Error loadGameState(int slot); + Common::Error saveGameState(int slot, const Common::String &desc); private: void initialize(); void initFonts(); @@ -141,9 +245,15 @@ private: /** Called every frame from ZVision::run() to process any events from EventMan */ void processEvents(); - void onMouseDown(const Common::Point &pos); - void onMouseUp(const Common::Point &pos); void onMouseMove(const Common::Point &pos); + + void registerDefaultSettings(); + void shortKeys(Common::Event); + + void cheatCodes(uint8 key); + void pushKeyToCheatBuf(uint8 key); + bool checkCode(const char *code); + uint8 getBufferedKey(uint8 pos); }; } // End of namespace ZVision |