aboutsummaryrefslogtreecommitdiff
path: root/engines
diff options
context:
space:
mode:
Diffstat (limited to 'engines')
-rw-r--r--engines/configure.engines1
-rw-r--r--engines/engines.mk5
-rw-r--r--engines/plugins_table.h3
-rw-r--r--engines/zvision/actions.cpp393
-rw-r--r--engines/zvision/actions.h345
-rw-r--r--engines/zvision/animation.cpp131
-rw-r--r--engines/zvision/animation_control.cpp260
-rw-r--r--engines/zvision/animation_control.h81
-rw-r--r--engines/zvision/clock.cpp69
-rw-r--r--engines/zvision/clock.h78
-rw-r--r--engines/zvision/console.cpp215
-rw-r--r--engines/zvision/console.h55
-rw-r--r--engines/zvision/control.cpp123
-rw-r--r--engines/zvision/control.h148
-rw-r--r--engines/zvision/cursor.cpp94
-rw-r--r--engines/zvision/cursor.h67
-rw-r--r--engines/zvision/cursor_manager.cpp149
-rw-r--r--engines/zvision/cursor_manager.h114
-rw-r--r--engines/zvision/detection.cpp270
-rw-r--r--engines/zvision/detection.h43
-rw-r--r--engines/zvision/events.cpp183
-rw-r--r--engines/zvision/input_control.cpp141
-rw-r--r--engines/zvision/input_control.h60
-rw-r--r--engines/zvision/inventory_manager.h28
-rw-r--r--engines/zvision/lever_control.cpp400
-rw-r--r--engines/zvision/lever_control.h129
-rw-r--r--engines/zvision/lzss_read_stream.cpp103
-rw-r--r--engines/zvision/lzss_read_stream.h72
-rw-r--r--engines/zvision/menu.h28
-rw-r--r--engines/zvision/module.mk45
-rw-r--r--engines/zvision/push_toggle_control.cpp96
-rw-r--r--engines/zvision/push_toggle_control.h69
-rw-r--r--engines/zvision/puzzle.h88
-rw-r--r--engines/zvision/render_manager.cpp518
-rw-r--r--engines/zvision/render_manager.h252
-rw-r--r--engines/zvision/render_table.cpp244
-rw-r--r--engines/zvision/render_table.h87
-rw-r--r--engines/zvision/rlf_animation.cpp336
-rw-r--r--engines/zvision/rlf_animation.h100
-rw-r--r--engines/zvision/save_manager.cpp177
-rw-r--r--engines/zvision/save_manager.h65
-rw-r--r--engines/zvision/scr_file_handling.cpp300
-rw-r--r--engines/zvision/script_manager.cpp440
-rw-r--r--engines/zvision/script_manager.h205
-rw-r--r--engines/zvision/scripts.cpp31
-rw-r--r--engines/zvision/single_value_container.cpp347
-rw-r--r--engines/zvision/single_value_container.h183
-rw-r--r--engines/zvision/string_manager.cpp257
-rw-r--r--engines/zvision/string_manager.h86
-rw-r--r--engines/zvision/subtitles.h29
-rw-r--r--engines/zvision/timer_node.cpp57
-rw-r--r--engines/zvision/timer_node.h56
-rw-r--r--engines/zvision/truetype_font.cpp115
-rw-r--r--engines/zvision/truetype_font.h64
-rw-r--r--engines/zvision/utility.cpp235
-rw-r--r--engines/zvision/utility.h100
-rw-r--r--engines/zvision/vector2.h79
-rw-r--r--engines/zvision/video.cpp167
-rw-r--r--engines/zvision/zfs_archive.cpp158
-rw-r--r--engines/zvision/zfs_archive.h125
-rw-r--r--engines/zvision/zork_avi_decoder.cpp50
-rw-r--r--engines/zvision/zork_avi_decoder.h59
-rw-r--r--engines/zvision/zork_raw.cpp191
-rw-r--r--engines/zvision/zork_raw.h133
-rw-r--r--engines/zvision/zvision.cpp185
-rw-r--r--engines/zvision/zvision.h156
66 files changed, 9673 insertions, 0 deletions
diff --git a/engines/configure.engines b/engines/configure.engines
index 195cdda6c7..8ef4e66dc3 100644
--- a/engines/configure.engines
+++ b/engines/configure.engines
@@ -53,3 +53,4 @@ add_engine tony "Tony Tough and the Night of Roasted Moths" yes "" "" "16bit"
add_engine tsage "TsAGE" yes
add_engine tucker "Bud Tucker in Double Trouble" yes
add_engine wintermute "Wintermute" no "" "" "png zlib vorbis 16bit"
+add_engine zvision "ZVision" no
diff --git a/engines/engines.mk b/engines/engines.mk
index 5b3eeea61c..a3270007dc 100644
--- a/engines/engines.mk
+++ b/engines/engines.mk
@@ -251,3 +251,8 @@ ifdef ENABLE_WINTERMUTE
DEFINES += -DENABLE_WINTERMUTE=$(ENABLE_WINTERMUTE)
MODULES += engines/wintermute
endif
+
+ifdef ENABLE_ZVISION
+DEFINES += -DENABLE_ZVISION=$(ENABLE_ZVISION)
+MODULES += engines/zvision
+endif
diff --git a/engines/plugins_table.h b/engines/plugins_table.h
index ee7713bb76..04971a6764 100644
--- a/engines/plugins_table.h
+++ b/engines/plugins_table.h
@@ -122,3 +122,6 @@ LINK_PLUGIN(TUCKER)
#if PLUGIN_ENABLED_STATIC(WINTERMUTE)
LINK_PLUGIN(WINTERMUTE)
#endif
+#if PLUGIN_ENABLED_STATIC(ZVISION)
+LINK_PLUGIN(ZVISION)
+#endif
diff --git a/engines/zvision/actions.cpp b/engines/zvision/actions.cpp
new file mode 100644
index 0000000000..3d25fa9e99
--- /dev/null
+++ b/engines/zvision/actions.cpp
@@ -0,0 +1,393 @@
+/* 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 "audio/decoders/wave.h"
+
+#include "zvision/actions.h"
+#include "zvision/zvision.h"
+#include "zvision/script_manager.h"
+#include "zvision/render_manager.h"
+#include "zvision/zork_raw.h"
+#include "zvision/zork_avi_decoder.h"
+#include "zvision/timer_node.h"
+#include "zvision/animation_control.h"
+
+namespace ZVision {
+
+//////////////////////////////////////////////////////////////////////////////
+// ActionAdd
+//////////////////////////////////////////////////////////////////////////////
+
+ActionAdd::ActionAdd(const Common::String &line) {
+ sscanf(line.c_str(), "%*[^(](%u,%u)", &_key, &_value);
+}
+
+bool ActionAdd::execute(ZVision *engine) {
+ engine->getScriptManager()->addToStateValue(_key, _value);
+ return true;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+// ActionAssign
+//////////////////////////////////////////////////////////////////////////////
+
+ActionAssign::ActionAssign(const Common::String &line) {
+ sscanf(line.c_str(), "%*[^(](%u, %u)", &_key, &_value);
+}
+
+bool ActionAssign::execute(ZVision *engine) {
+ engine->getScriptManager()->setStateValue(_key, _value);
+ return true;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+// ActionAttenuate
+//////////////////////////////////////////////////////////////////////////////
+
+ActionAttenuate::ActionAttenuate(const Common::String &line) {
+ sscanf(line.c_str(), "%*[^(](%u, %d)", &_key, &_attenuation);
+}
+
+bool ActionAttenuate::execute(ZVision *engine) {
+ // TODO: Implement
+ return true;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+// ActionChangeLocation
+//////////////////////////////////////////////////////////////////////////////
+
+ActionChangeLocation::ActionChangeLocation(const Common::String &line) {
+ sscanf(line.c_str(), "%*[^(](%c,%c,%c%c,%u)", &_world, &_room, &_node, &_view, &_offset);
+}
+
+bool ActionChangeLocation::execute(ZVision *engine) {
+ // 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);
+ // Tell the puzzle system to stop checking any more puzzles
+ return false;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+// ActionCrossfade
+//////////////////////////////////////////////////////////////////////////////
+
+ActionCrossfade::ActionCrossfade(const Common::String &line) {
+ sscanf(line.c_str(),
+ "%*[^(](%u %u %u %u %u %u %u)",
+ &_keyOne, &_keyTwo, &_oneStartVolume, &_twoStartVolume, &_oneEndVolume, &_twoEndVolume, &_timeInMillis);
+}
+
+bool ActionCrossfade::execute(ZVision *engine) {
+ // TODO: Implement
+ return true;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+// ActionDisableControl
+//////////////////////////////////////////////////////////////////////////////
+
+ActionDisableControl::ActionDisableControl(const Common::String &line) {
+ sscanf(line.c_str(), "%*[^(](%u)", &_key);
+}
+
+bool ActionDisableControl::execute(ZVision *engine) {
+ debug("Disabling control %u", _key);
+
+ engine->getScriptManager()->disableControl(_key);
+
+ return true;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+// ActionEnableControl
+//////////////////////////////////////////////////////////////////////////////
+
+ActionEnableControl::ActionEnableControl(const Common::String &line) {
+ sscanf(line.c_str(), "%*[^(](%u)", &_key);
+}
+
+bool ActionEnableControl::execute(ZVision *engine) {
+ debug("Enabling control %u", _key);
+
+ engine->getScriptManager()->enableControl(_key);
+
+ return true;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+// ActionMusic
+//////////////////////////////////////////////////////////////////////////////
+
+ActionMusic::ActionMusic(const Common::String &line) : _volume(255) {
+ uint type;
+ char fileNameBuffer[25];
+ uint loop;
+ uint volume = 255;
+
+ sscanf(line.c_str(), "%*[^:]:%*[^:]:%u(%u %25s %u %u)", &_key, &type, fileNameBuffer, &loop, &volume);
+
+ // type 4 are midi sound effect files
+ if (type == 4) {
+ _soundType = Audio::Mixer::kSFXSoundType;
+ _fileName = Common::String::format("midi/%s/%u.wav", fileNameBuffer, loop);
+ _loop = false;
+ } 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;
+ _fileName = Common::String(fileNameBuffer);
+ _loop = loop == 1 ? true : false;
+ }
+
+ // 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;
+ }
+}
+
+bool ActionMusic::execute(ZVision *engine) {
+ Audio::RewindableAudioStream *audioStream;
+
+ if (_fileName.contains(".wav")) {
+ Common::File file;
+ if (file.open(_fileName)) {
+ audioStream = Audio::makeWAVStream(&file, DisposeAfterUse::NO);
+ }
+ } else {
+ audioStream = makeRawZorkStream(_fileName, engine);
+ }
+
+ if (_loop) {
+ Audio::LoopingAudioStream *loopingAudioStream = new Audio::LoopingAudioStream(audioStream, 0, DisposeAfterUse::YES);
+ engine->_mixer->playStream(_soundType, 0, loopingAudioStream, -1, _volume);
+ } else {
+ engine->_mixer->playStream(_soundType, 0, audioStream, -1, _volume);
+ }
+
+ return true;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+// ActionPreloadAnimation
+//////////////////////////////////////////////////////////////////////////////
+
+ActionPreloadAnimation::ActionPreloadAnimation(const Common::String &line) {
+ 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)", &_key, fileName, &_mask, &_framerate);
+
+ _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
+
+ // Create the control, but disable it until PlayPreload is called
+ engine->getScriptManager()->addControl(new AnimationControl(engine, _key, _fileName));
+ engine->getScriptManager()->disableControl(_key);
+ return true;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+// ActionPlayAnimation
+//////////////////////////////////////////////////////////////////////////////
+
+ActionPlayAnimation::ActionPlayAnimation(const Common::String &line) {
+ 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);
+
+ _fileName = Common::String(fileName);
+}
+
+bool ActionPlayAnimation::execute(ZVision *engine) {
+ // TODO: Implement
+ return true;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+// ActionPlayPreloadAnimation
+//////////////////////////////////////////////////////////////////////////////
+
+ActionPlayPreloadAnimation::ActionPlayPreloadAnimation(const Common::String &line) {
+ sscanf(line.c_str(),
+ "%*[^:]:%*[^:]:%u(%u %u %u %u %u %u %u %u)",
+ &_animationKey, &_controlKey, &_x1, &_y1, &_x2, &_y2, &_startFrame, &_endFrame, &_loopCount);
+}
+
+bool ActionPlayPreloadAnimation::execute(ZVision *engine) {
+ // Find the control
+ AnimationControl *control = (AnimationControl *)engine->getScriptManager()->getControl(_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();
+
+ return true;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+// ActionQuit
+//////////////////////////////////////////////////////////////////////////////
+
+bool ActionQuit::execute(ZVision *engine) {
+ engine->quitGame();
+
+ return true;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+// ActionRandom
+//////////////////////////////////////////////////////////////////////////////
+
+ActionRandom::ActionRandom(const Common::String &line) {
+ sscanf(line.c_str(), "%*[^:]:%*[^:]:%u, %u)", &_key, &_max);
+}
+
+bool ActionRandom::execute(ZVision *engine) {
+ uint randNumber = engine->getRandomSource()->getRandomNumber(_max);
+ engine->getScriptManager()->setStateValue(_key, randNumber);
+ return true;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+// ActionSetPartialScreen
+//////////////////////////////////////////////////////////////////////////////
+
+ActionSetPartialScreen::ActionSetPartialScreen(const Common::String &line) {
+ char fileName[25];
+ uint color;
+
+ sscanf(line.c_str(), "%*[^(](%u %u %25s %*u %u)", &_x, &_y, fileName, &color);
+
+ _fileName = Common::String(fileName);
+
+ if (color > 0xFFFF) {
+ 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);
+ }
+ renderManager->renderImageToScreen(_fileName, _x, _y);
+
+ return true;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+// ActionSetScreen
+//////////////////////////////////////////////////////////////////////////////
+
+ActionSetScreen::ActionSetScreen(const Common::String &line) {
+ char fileName[25];
+ sscanf(line.c_str(), "%*[^(](%25[^)])", fileName);
+
+ _fileName = Common::String(fileName);
+}
+
+bool ActionSetScreen::execute(ZVision *engine) {
+ engine->getRenderManager()->setBackgroundImage(_fileName);
+
+ return true;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+// ActionStreamVideo
+//////////////////////////////////////////////////////////////////////////////
+
+ActionStreamVideo::ActionStreamVideo(const Common::String &line) {
+ char fileName[25];
+ uint skippable;
+
+ sscanf(line.c_str(), "%*[^(](%25s %u %u %u %u %u %u)", fileName, &_x1, &_y1, &_x2, &_y2, &_flags, &skippable);
+
+ _fileName = Common::String(fileName);
+ _skippable = (skippable == 0) ? false : true;
+}
+
+bool ActionStreamVideo::execute(ZVision *engine) {
+ ZorkAVIDecoder decoder;
+ if (!decoder.loadFile(_fileName)) {
+ return true;
+ }
+
+ Common::Rect destRect;
+ // 0x1 flags that the destRect dimensions are different from the original video dimensions
+ if ((_flags & 0x1) == 0x1) {
+ destRect = Common::Rect(_x1, _y1, _x2, _y2);
+ }
+
+ engine->playVideo(decoder, destRect, _skippable);
+ return true;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+// ActionTimer
+//////////////////////////////////////////////////////////////////////////////
+
+ActionTimer::ActionTimer(const Common::String &line) {
+ sscanf(line.c_str(), "%*[^:]:%*[^:]:%u(%u)", &_key, &_time);
+}
+
+bool ActionTimer::execute(ZVision *engine) {
+ engine->getScriptManager()->addControl(new TimerNode(engine, _key, _time));
+ return true;
+}
+
+} // End of namespace ZVision
diff --git a/engines/zvision/actions.h b/engines/zvision/actions.h
new file mode 100644
index 0000000000..5d7cdad68f
--- /dev/null
+++ b/engines/zvision/actions.h
@@ -0,0 +1,345 @@
+/* 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_ACTIONS_H
+#define ZVISION_ACTIONS_H
+
+#include "common/scummsys.h"
+
+#include "audio/mixer.h"
+
+namespace Common {
+class String;
+}
+
+namespace ZVision {
+
+// Forward declaration of ZVision. This file is included before ZVision is declared
+class ZVision;
+
+/**
+ * The base class that represents any action that a Puzzle can take.
+ * This class is purely virtual.
+ */
+class ResultAction {
+public:
+ virtual ~ResultAction() {}
+ /**
+ * This is called by the script system whenever a Puzzle's criteria are found to be true.
+ * It should execute any necessary actions and return a value indicating whether the script
+ * system should continue to test puzzles. In 99% of cases this will be 'true'.
+ *
+ * @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,
+
+class ActionAdd : public ResultAction {
+public:
+ ActionAdd(const Common::String &line);
+ bool execute(ZVision *engine);
+
+private:
+ uint32 _key;
+ uint _value;
+};
+
+class ActionAssign : public ResultAction {
+public:
+ ActionAssign(const Common::String &line);
+ bool execute(ZVision *engine);
+
+private:
+ uint32 _key;
+ uint _value;
+};
+
+class ActionAttenuate : public ResultAction {
+public:
+ ActionAttenuate(const Common::String &line);
+ bool execute(ZVision *engine);
+
+private:
+ uint32 _key;
+ int _attenuation;
+};
+
+class ActionChangeLocation : public ResultAction {
+public:
+ ActionChangeLocation(const Common::String &line);
+ bool execute(ZVision *engine);
+
+private:
+ char _world;
+ char _room;
+ char _node;
+ char _view;
+ uint32 _offset;
+};
+
+class ActionCrossfade : public ResultAction {
+public:
+ ActionCrossfade(const Common::String &line);
+ bool execute(ZVision *engine);
+
+private:
+ uint32 _keyOne;
+ uint32 _keyTwo;
+ uint _oneStartVolume;
+ uint _twoStartVolume;
+ uint _oneEndVolume;
+ uint _twoEndVolume;
+ uint _timeInMillis;
+};
+
+class ActionDebug : public ResultAction {
+public:
+ ActionDebug(const Common::String &line);
+ bool execute(ZVision *engine);
+
+private:
+};
+
+class ActionDelayRender : public ResultAction {
+public:
+ ActionDelayRender(const Common::String &line);
+ bool execute(ZVision *engine);
+
+private:
+ // TODO: Check if this should actually be frames or if it should be milliseconds/seconds
+ uint32 framesToDelay;
+};
+
+class ActionDisableControl : public ResultAction {
+public:
+ ActionDisableControl(const Common::String &line);
+ bool execute(ZVision *engine);
+
+private:
+ uint32 _key;
+};
+
+class ActionDisableVenus : public ResultAction {
+public:
+ ActionDisableVenus(const Common::String &line);
+ bool execute(ZVision *engine);
+
+private:
+};
+
+class ActionDisplayMessage : public ResultAction {
+public:
+ ActionDisplayMessage(const Common::String &line);
+ bool execute(ZVision *engine);
+
+private:
+};
+
+class ActionDissolve : public ResultAction {
+public:
+ ActionDissolve();
+ bool execute(ZVision *engine);
+};
+
+class ActionDistort : public ResultAction {
+public:
+ ActionDistort(const Common::String &line);
+ bool execute(ZVision *engine);
+
+private:
+};
+
+class ActionEnableControl : public ResultAction {
+public:
+ ActionEnableControl(const Common::String &line);
+ bool execute(ZVision *engine);
+
+private:
+ uint32 _key;
+};
+
+class ActionMusic : public ResultAction {
+public:
+ ActionMusic(const Common::String &line);
+ bool execute(ZVision *engine);
+
+private:
+ uint32 _key;
+ Audio::Mixer::SoundType _soundType;
+ Common::String _fileName;
+ bool _loop;
+ byte _volume;
+};
+
+class ActionPlayAnimation : public ResultAction {
+public:
+ ActionPlayAnimation(const Common::String &line);
+ bool execute(ZVision *engine);
+
+private:
+ uint32 _key;
+ Common::String _fileName;
+ uint32 _x;
+ uint32 _y;
+ uint32 _width;
+ uint32 _height;
+ uint32 _start;
+ uint32 _end;
+ uint _mask;
+ uint _framerate;
+ uint _loopCount;
+};
+
+class ActionPlayPreloadAnimation : public ResultAction {
+public:
+ ActionPlayPreloadAnimation(const Common::String &line);
+ bool execute(ZVision *engine);
+
+private:
+ uint32 _animationKey;
+ uint32 _controlKey;
+ uint32 _x1;
+ uint32 _y1;
+ uint32 _x2;
+ uint32 _y2;
+ uint _startFrame;
+ uint _endFrame;
+ uint _loopCount;
+};
+
+class ActionPreloadAnimation : public ResultAction {
+public:
+ ActionPreloadAnimation(const Common::String &line);
+ bool execute(ZVision *engine);
+
+private:
+ uint32 _key;
+ Common::String _fileName;
+ uint _mask;
+ uint _framerate;
+};
+
+class ActionQuit : public ResultAction {
+public:
+ ActionQuit() {}
+ bool execute(ZVision *engine);
+};
+
+// TODO: See if this exists in ZGI. It doesn't in ZNem
+class ActionUnloadAnimation : public ResultAction {
+public:
+ ActionUnloadAnimation(const Common::String &line);
+ bool execute(ZVision *engine);
+};
+
+class ActionRandom : public ResultAction {
+public:
+ ActionRandom(const Common::String &line);
+ bool execute(ZVision *engine);
+
+private:
+ uint32 _key;
+ uint _max;
+};
+
+class ActionSetPartialScreen : public ResultAction {
+public:
+ ActionSetPartialScreen(const Common::String &line);
+ bool execute(ZVision *engine);
+
+private:
+ uint _x;
+ uint _y;
+ Common::String _fileName;
+ uint16 _backgroundColor;
+};
+
+class ActionSetScreen : public ResultAction {
+public:
+ ActionSetScreen(const Common::String &line);
+ bool execute(ZVision *engine);
+
+private:
+ Common::String _fileName;
+};
+
+class ActionStreamVideo : public ResultAction {
+public:
+ ActionStreamVideo(const Common::String &line);
+ bool execute(ZVision *engine);
+
+private:
+ Common::String _fileName;
+ uint _x1;
+ uint _y1;
+ uint _x2;
+ uint _y2;
+ uint _flags;
+ bool _skippable;
+};
+
+class ActionTimer : public ResultAction {
+public:
+ ActionTimer(const Common::String &line);
+ bool execute(ZVision *engine);
+
+private:
+ uint32 _key;
+ uint _time;
+};
+
+} // End of namespace ZVision
+
+#endif
diff --git a/engines/zvision/animation.cpp b/engines/zvision/animation.cpp
new file mode 100644
index 0000000000..f43e854f73
--- /dev/null
+++ b/engines/zvision/animation.cpp
@@ -0,0 +1,131 @@
+/* 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/system.h"
+#include "video/video_decoder.h"
+
+#include "zvision/zvision.h"
+#include "zvision/rlf_animation.h"
+
+
+namespace ZVision {
+
+void ZVision::playAnimation(RlfAnimation *animation, uint16 x, uint16 y, DisposeAfterUse::Flag disposeAfterUse) {
+ bool skip = false;
+ uint32 frameTime = animation->frameTime();
+ uint width = animation->width();
+ uint height = animation->height();
+
+ uint16 newX = x + _workingWindow.left;
+ uint16 newY = y + _workingWindow.top;
+
+ uint32 accumulatedTime = 0;
+
+ // Only continue while the video is still playing
+ while (!shouldQuit() && !skip && !animation->endOfAnimation()) {
+ _clock.update();
+ uint32 currentTime = _clock.getLastMeasuredTime();
+ accumulatedTime += _clock.getDeltaTime();
+
+ // Check for engine quit and video stop key presses
+ while (_eventMan->pollEvent(_event)) {
+ switch (_event.type) {
+ case Common::EVENT_KEYDOWN:
+ switch (_event.kbd.keycode) {
+ case Common::KEYCODE_q:
+ if (_event.kbd.hasFlags(Common::KBD_CTRL))
+ quitGame();
+ break;
+ case Common::KEYCODE_SPACE:
+ skip = true;
+ break;
+ default:
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ while (accumulatedTime >= frameTime && !animation->endOfAnimation()) {
+ accumulatedTime -= frameTime;
+
+ _system->copyRectToScreen(animation->getNextFrame(), width * sizeof(uint16), newX, newY, width, height);
+ }
+
+ // Always update the screen so the mouse continues to render
+ _system->updateScreen();
+
+ // 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;
+ _system->delayMillis(delay);
+ }
+}
+
+void ZVision::playAnimation(Video::VideoDecoder *animation, uint16 x, uint16 y, DisposeAfterUse::Flag disposeAfterUse) {
+ _clock.stop();
+ animation->start();
+
+ // Only continue while the video is still playing
+ while (!shouldQuit() && !animation->endOfVideo() && animation->isPlaying()) {
+ // Check for engine quit and video stop key presses
+ while (!animation->endOfVideo() && animation->isPlaying() && _eventMan->pollEvent(_event)) {
+ switch (_event.type) {
+ case Common::EVENT_KEYDOWN:
+ switch (_event.kbd.keycode) {
+ case Common::KEYCODE_q:
+ if (_event.kbd.hasFlags(Common::KBD_CTRL))
+ quitGame();
+ break;
+ case Common::KEYCODE_SPACE:
+ animation->stop();
+ break;
+ default:
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ if (animation->needsUpdate()) {
+ const Graphics::Surface *frame = animation->decodeNextFrame();
+
+ if (frame) {
+ _system->copyRectToScreen((const byte *)frame->getPixels(), frame->pitch, x, y, frame->w, frame->h);
+ }
+ }
+
+ // Always update the screen so the mouse continues to render
+ _system->updateScreen();
+
+ _system->delayMillis(animation->getTimeToNextFrame());
+ }
+
+ _clock.start();
+}
+
+} // End of namespace ZVision
diff --git a/engines/zvision/animation_control.cpp b/engines/zvision/animation_control.cpp
new file mode 100644
index 0000000000..f80faea0f9
--- /dev/null
+++ b/engines/zvision/animation_control.cpp
@@ -0,0 +1,260 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/scummsys.h"
+
+#include "video/video_decoder.h"
+
+#include "zvision/animation_control.h"
+#include "zvision/zvision.h"
+#include "zvision/render_manager.h"
+#include "zvision/script_manager.h"
+#include "zvision/rlf_animation.h"
+#include "zvision/zork_avi_decoder.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((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((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/animation_control.h b/engines/zvision/animation_control.h
new file mode 100644
index 0000000000..935f4abb4b
--- /dev/null
+++ b/engines/zvision/animation_control.h
@@ -0,0 +1,81 @@
+/* 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_ANIMATION_CONTROL_H
+#define ZVISION_ANIMATION_CONTROL_H
+
+#include "common/types.h"
+
+#include "zvision/control.h"
+
+
+namespace Video {
+class VideoDecoder;
+}
+
+namespace ZVision {
+
+class ZVision;
+class RlfAnimation;
+
+class AnimationControl : public Control {
+public:
+ AnimationControl(ZVision *engine, uint32 controlKey, const Common::String &fileName);
+ ~AnimationControl();
+
+private:
+ enum FileType {
+ RLF = 1,
+ AVI = 2
+ };
+
+private:
+ uint32 _animationKey;
+
+ union {
+ RlfAnimation *rlf;
+ Video::VideoDecoder *avi;
+ } _animation;
+
+ FileType _fileType;
+ uint _loopCount;
+ int32 _x;
+ int32 _y;
+
+ uint _accumulatedTime;
+ uint _currentLoop;
+
+ Graphics::Surface *_cachedFrame;
+ bool _cachedFrameNeedsDeletion;
+
+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; }
+};
+
+} // End of namespace ZVision
+
+#endif
diff --git a/engines/zvision/clock.cpp b/engines/zvision/clock.cpp
new file mode 100644
index 0000000000..fe322525f8
--- /dev/null
+++ b/engines/zvision/clock.cpp
@@ -0,0 +1,69 @@
+/* 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/system.h"
+
+#include "zvision/clock.h"
+
+namespace ZVision {
+
+Clock::Clock(OSystem *system)
+ : _system(system),
+ _lastTime(0),
+ _deltaTime(0),
+ _pausedTime(0),
+ _paused(false) {
+}
+
+void Clock::update() {
+ uint32 currentTime = _system->getMillis();
+
+ _deltaTime = (currentTime - _lastTime);
+ if (_paused) {
+ _deltaTime -= (currentTime - _pausedTime);
+ }
+
+ if (_deltaTime < 0) {
+ _deltaTime = 0;
+ }
+
+ _lastTime = currentTime;
+}
+
+void Clock::start() {
+ if (_paused) {
+ _lastTime = _system->getMillis();
+ _paused = false;
+ }
+}
+
+void Clock::stop() {
+ if (!_paused) {
+ _pausedTime = _system->getMillis();
+ _paused = true;
+ }
+}
+
+} // End of namespace ZVision
diff --git a/engines/zvision/clock.h b/engines/zvision/clock.h
new file mode 100644
index 0000000000..3939ba1612
--- /dev/null
+++ b/engines/zvision/clock.h
@@ -0,0 +1,78 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ *
+ */
+
+#ifndef ZVISION_CLOCK_H
+#define ZVISION_CLOCK_H
+
+#include "common/types.h"
+
+class OSystem;
+
+namespace ZVision {
+
+/* Class for handling frame to frame deltaTime while keeping track of time pauses/un-pauses */
+class Clock {
+public:
+ Clock(OSystem *system);
+
+private:
+ OSystem *_system;
+ uint32 _lastTime;
+ int32 _deltaTime;
+ uint32 _pausedTime;
+ bool _paused;
+
+public:
+ /**
+ * Updates _deltaTime with the difference between the current time and
+ * 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; }
+ /**
+ * 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; }
+
+ /**
+ * 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.
+ */
+ void stop();
+};
+
+} // End of namespace ZVision
+
+#endif
diff --git a/engines/zvision/console.cpp b/engines/zvision/console.cpp
new file mode 100644
index 0000000000..0d290a661a
--- /dev/null
+++ b/engines/zvision/console.cpp
@@ -0,0 +1,215 @@
+/* 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/system.h"
+#include "gui/debugger.h"
+#include "common/file.h"
+#include "common/bufferedstream.h"
+#include "audio/mixer.h"
+
+#include "zvision/console.h"
+#include "zvision/zvision.h"
+#include "zvision/script_manager.h"
+#include "zvision/render_manager.h"
+#include "zvision/string_manager.h"
+#include "zvision/zork_avi_decoder.h"
+#include "zvision/zork_raw.h"
+#include "zvision/utility.h"
+#include "zvision/cursor.h"
+
+
+namespace ZVision {
+
+Console::Console(ZVision *engine) : GUI::Debugger(), _engine(engine) {
+ DCmd_Register("loadimage", WRAP_METHOD(Console, cmdLoadImage));
+ DCmd_Register("loadvideo", WRAP_METHOD(Console, cmdLoadVideo));
+ DCmd_Register("loadsound", WRAP_METHOD(Console, cmdLoadSound));
+ DCmd_Register("raw2wav", WRAP_METHOD(Console, cmdRawToWav));
+ DCmd_Register("setrenderstate", WRAP_METHOD(Console, cmdSetRenderState));
+ DCmd_Register("generaterendertable", WRAP_METHOD(Console, cmdGenerateRenderTable));
+ DCmd_Register("setpanoramafov", WRAP_METHOD(Console, cmdSetPanoramaFoV));
+ DCmd_Register("setpanoramascale", WRAP_METHOD(Console, cmdSetPanoramaScale));
+ DCmd_Register("changelocation", WRAP_METHOD(Console, cmdChangeLocation));
+ DCmd_Register("dumpfile", WRAP_METHOD(Console, cmdDumpFile));
+ DCmd_Register("parseallscrfiles", WRAP_METHOD(Console, cmdParseAllScrFiles));
+ DCmd_Register("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;
+}
+
+bool Console::cmdLoadVideo(int argc, const char **argv) {
+ if (argc != 2) {
+ DebugPrintf("Use loadvideo <fileName> to load a video to the screen\n");
+ return true;
+ }
+
+ ZorkAVIDecoder videoDecoder;
+ if (videoDecoder.loadFile(argv[1])) {
+ _engine->playVideo(videoDecoder);
+ }
+
+ return true;
+}
+
+bool Console::cmdLoadSound(int argc, const char **argv) {
+ if (!Common::File::exists(argv[1])) {
+ DebugPrintf("File does not exist\n");
+ return true;
+ }
+
+ if (argc == 2) {
+ 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]);
+
+ 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");
+ return true;
+ }
+
+ return true;
+}
+
+bool Console::cmdRawToWav(int argc, const char **argv) {
+ if (argc != 3) {
+ DebugPrintf("Use raw2wav <rawFilePath> <wavFileName> to dump a .RAW file to .WAV\n");
+ return true;
+ }
+
+ convertRawToWav(argv[1], _engine, argv[2]);
+ 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");
+ return true;
+ }
+
+ Common::String renderState(argv[1]);
+
+ if (renderState.matchString("panorama", true))
+ _engine->getRenderManager()->getRenderTable()->setRenderState(RenderTable::PANORAMA);
+ else if (renderState.matchString("tilt", true))
+ _engine->getRenderManager()->getRenderTable()->setRenderState(RenderTable::TILT);
+ 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");
+
+ return true;
+}
+
+bool Console::cmdGenerateRenderTable(int argc, const char **argv) {
+ _engine->getRenderManager()->getRenderTable()->generateRenderTable();
+
+ return true;
+}
+
+bool Console::cmdSetPanoramaFoV(int argc, const char **argv) {
+ if (argc != 2) {
+ DebugPrintf("Use setpanoramafov <fieldOfView> to change the current panorama field of view\n");
+ return true;
+ }
+
+ _engine->getRenderManager()->getRenderTable()->setPanoramaFoV(atof(argv[1]));
+
+ return true;
+}
+
+bool Console::cmdSetPanoramaScale(int argc, const char **argv) {
+ if (argc != 2) {
+ DebugPrintf("Use setpanoramascale <scale> to change the current panorama scale\n");
+ return true;
+ }
+
+ _engine->getRenderManager()->getRenderTable()->setPanoramaScale(atof(argv[1]));
+
+ return true;
+}
+
+bool Console::cmdChangeLocation(int argc, const char **argv) {
+ if (argc != 6) {
+ DebugPrintf("Use changelocation <char: world> <char: room> <char:node> <char:view> <int: x position> to change your location\n");
+ return true;
+ }
+
+ _engine->getScriptManager()->changeLocation(*(argv[1]), *(argv[2]), *(argv[3]), *(argv[4]), atoi(argv[5]));
+
+ return true;
+}
+
+bool Console::cmdDumpFile(int argc, const char **argv) {
+ if (argc != 2) {
+ DebugPrintf("Use dumpfile <fileName> to dump a file\n");
+ return true;
+ }
+
+ writeFileContentsToFile(argv[1], argv[1]);
+
+ return true;
+}
+
+bool Console::cmdParseAllScrFiles(int argc, const char **argv) {
+ Common::ArchiveMemberList list;
+ SearchMan.listMatchingMembers(list, "*.scr");
+
+ for (Common::ArchiveMemberList::iterator iter = list.begin(); iter != list.end(); ++iter) {
+ _engine->getScriptManager()->parseScrFile((*iter)->getName());
+ }
+
+ 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");
+ 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);
+
+ return true;
+}
+
+} // End of namespace ZVision
diff --git a/engines/zvision/console.h b/engines/zvision/console.h
new file mode 100644
index 0000000000..0ca1b8cc70
--- /dev/null
+++ b/engines/zvision/console.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_CONSOLE_H
+#define ZVISION_CONSOLE_H
+
+#include "gui/debugger.h"
+
+namespace ZVision {
+
+class ZVision;
+
+class Console : public GUI::Debugger {
+public:
+ Console(ZVision *engine);
+ virtual ~Console() {}
+
+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);
+ bool cmdSetRenderState(int argc, const char **argv);
+ 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 cmdDumpFile(int argc, const char **argv);
+ bool cmdParseAllScrFiles(int argc, const char **argv);
+ bool cmdRenderText(int argc, const char **argv);
+};
+
+} // End of namespace ZVision
+#endif
diff --git a/engines/zvision/control.cpp b/engines/zvision/control.cpp
new file mode 100644
index 0000000000..ad0a4c5013
--- /dev/null
+++ b/engines/zvision/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 "common/stream.h"
+
+#include "zvision/control.h"
+#include "zvision/zvision.h"
+#include "zvision/render_manager.h"
+#include "zvision/utility.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);
+}
+
+void Control::parsePanoramaControl(ZVision *engine, Common::SeekableReadStream &stream) {
+ RenderTable *renderTable = engine->getRenderManager()->getRenderTable();
+ renderTable->setRenderState(RenderTable::PANORAMA);
+
+ // Loop until we find the closing brace
+ Common::String line = stream.readLine();
+ trimCommentsAndWhiteSpace(&line);
+
+ while (!stream.eos() && !line.contains('}')) {
+ if (line.matchString("angle*", true)) {
+ float fov;
+ sscanf(line.c_str(), "angle(%f)", &fov);
+ renderTable->setPanoramaFoV(fov);
+ } else if (line.matchString("linscale*", true)) {
+ float scale;
+ sscanf(line.c_str(), "linscale(%f)", &scale);
+ renderTable->setPanoramaScale(scale);
+ } else if (line.matchString("reversepana*", true)) {
+ uint reverse;
+ sscanf(line.c_str(), "reversepana(%u)", &reverse);
+ if (reverse == 1) {
+ renderTable->setPanoramaReverse(true);
+ }
+ } else if (line.matchString("zeropoint*", true)) {
+ // TODO: Implement
+ }
+
+ line = stream.readLine();
+ trimCommentsAndWhiteSpace(&line);
+ }
+
+ renderTable->generateRenderTable();
+}
+
+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);
+
+ while (!stream.eos() && !line.contains('}')) {
+ if (line.matchString("angle*", true)) {
+ float fov;
+ sscanf(line.c_str(), "angle(%f)", &fov);
+ renderTable->setTiltFoV(fov);
+ } else if (line.matchString("linscale*", true)) {
+ float scale;
+ sscanf(line.c_str(), "linscale(%f)", &scale);
+ renderTable->setTiltScale(scale);
+ } else if (line.matchString("reversepana*", true)) {
+ uint reverse;
+ sscanf(line.c_str(), "reversepana(%u)", &reverse);
+ if (reverse == 1) {
+ renderTable->setTiltReverse(true);
+ }
+ }
+
+ line = stream.readLine();
+ trimCommentsAndWhiteSpace(&line);
+ }
+
+ renderTable->generateRenderTable();
+}
+
+} // End of namespace ZVision
diff --git a/engines/zvision/control.h b/engines/zvision/control.h
new file mode 100644
index 0000000000..a279381298
--- /dev/null
+++ b/engines/zvision/control.h
@@ -0,0 +1,148 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef ZVISION_CONTROL_H
+#define ZVISION_CONTROL_H
+
+#include "common/types.h"
+
+#include "common/keyboard.h"
+
+
+namespace Common {
+class SeekableReadStream;
+struct Point;
+class WriteStream;
+}
+
+namespace ZVision {
+
+class ZVision;
+
+class Control {
+public:
+ Control() : _engine(0), _key(0), _enabled(false) {}
+ Control(ZVision *engine, uint32 key) : _engine(engine), _key(key), _enabled(false) {}
+ virtual ~Control() {}
+
+ uint32 getKey() { return _key; }
+
+ virtual void enable();
+ virtual void disable();
+ virtual void focus() {}
+ virtual void unfocus() {}
+ /**
+ * 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
+ */
+ virtual void onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {}
+ /**
+ * 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) {}
+ /**
+ * Called on every MouseMove. Default is NOP.
+ *
+ * @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?
+ */
+ 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) {}
+ /**
+ * Called when a key is released. Default is NOP.
+ *
+ * @param keycode The key that was pressed
+ */
+ virtual void onKeyUp(Common::KeyState keyState) {}
+ /**
+ * 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; }
+
+protected:
+ ZVision * _engine;
+ uint32 _key;
+ bool _enabled;
+
+// 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);
+};
+
+// 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/cursor.cpp b/engines/zvision/cursor.cpp
new file mode 100644
index 0000000000..bb0700cebe
--- /dev/null
+++ b/engines/zvision/cursor.cpp
@@ -0,0 +1,94 @@
+/* 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/str.h"
+#include "common/file.h"
+
+#include "zvision/cursor.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/cursor.h b/engines/zvision/cursor.h
new file mode 100644
index 0000000000..805a8e7982
--- /dev/null
+++ b/engines/zvision/cursor.h
@@ -0,0 +1,67 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef ZVISION_CURSOR_H
+#define ZVISION_CURSOR_H
+
+#include "common/types.h"
+
+#include "graphics/surface.h"
+
+namespace Common {
+class String;
+}
+
+namespace ZVision {
+
+/**
+ * Utility class to parse and hold cursor data
+ * Modeled off Graphics::Cursor
+ */
+class ZorkCursor {
+public:
+ ZorkCursor();
+ ZorkCursor(const Common::String &fileName);
+ ZorkCursor(const ZorkCursor &other);
+ ~ZorkCursor();
+
+private:
+ uint16 _width;
+ uint16 _height;
+ uint16 _hotspotX;
+ uint16 _hotspotY;
+ Graphics::Surface _surface;
+
+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(); }
+};
+
+} // End of namespace ZVision
+
+#endif
diff --git a/engines/zvision/cursor_manager.cpp b/engines/zvision/cursor_manager.cpp
new file mode 100644
index 0000000000..50fa5031b2
--- /dev/null
+++ b/engines/zvision/cursor_manager.cpp
@@ -0,0 +1,149 @@
+/* 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/system.h"
+#include "graphics/pixelformat.h"
+#include "graphics/cursorman.h"
+
+#include "zvision/zvision.h"
+#include "zvision/cursor_manager.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() == ZorkNemesis) {
+ Common::String name(Common::String::format("%sa.zcr", _zNemCursorFileNames[IDLE_CURSOR_INDEX]));
+ _idleCursor = ZorkCursor(name);
+ } else if (_engine->getGameId() == ZorkGrandInquisitor) {
+ _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() == ZorkNemesis) {
+ 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() == ZorkGrandInquisitor) {
+ 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;
+ }
+ 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/cursor_manager.h b/engines/zvision/cursor_manager.h
new file mode 100644
index 0000000000..1c09620b7e
--- /dev/null
+++ b/engines/zvision/cursor_manager.h
@@ -0,0 +1,114 @@
+/* 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_CURSOR_MANAGER_H
+#define ZVISION_CURSOR_MANAGER_H
+
+#include "common/types.h"
+
+#include "zvision/cursor.h"
+
+
+namespace Graphics {
+struct PixelFormat;
+}
+
+namespace ZVision {
+
+class ZVision;
+
+/**
+ * Class to manage cursor changes. The actual changes have to be done
+ * through CursorMan. Otherwise the cursor will disappear after GMM
+ * or debug console.
+ * TODO: Figure out a way to get rid of the extraneous data copying due to having to use CursorMan
+ */
+class CursorManager {
+public:
+ 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
+ };
+
+ ZVision *_engine;
+ const Graphics::PixelFormat *_pixelFormat;
+ ZorkCursor _idleCursor;
+ Common::String _currentCursor;
+ bool _cursorIsPushed;
+
+ static const char *_cursorNames[];
+ static const char *_zgiCursorFileNames[];
+ static const char *_zNemCursorFileNames[];
+
+public:
+ /** Creates the idle cursor and shows it */
+ 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
+ *
+ * @param cursorName The name of a cursor. This *HAS* to correspond to one of the entries in _cursorNames[]
+ */
+ void changeCursor(const Common::String &cursorName);
+ /**
+ * Parses a cursor name into a cursor file then creates and shows that cursor.
+ *
+ * @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)
+ */
+ void changeCursor(const Common::String &cursorName, bool pushed);
+ /**
+ * Change the cursor to a certain push state. If the cursor is already in the specified push state, nothing will happen.
+ *
+ * @param pushed Should the cursor be pushed (true) or not pushed (false) (Another way to say it: down or up)
+ */
+ 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();
+
+private:
+ /**
+ * Calls CursorMan.replaceCursor() using the data in cursor
+ *
+ * @param cursor The cursor to show
+ */
+ void changeCursor(const ZorkCursor &cursor);
+};
+
+} // End of namespace ZVision
+
+#endif
diff --git a/engines/zvision/detection.cpp b/engines/zvision/detection.cpp
new file mode 100644
index 0000000000..d9bb692f07
--- /dev/null
+++ b/engines/zvision/detection.cpp
@@ -0,0 +1,270 @@
+/* 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 "base/plugins.h"
+
+#include "common/translation.h"
+#include "common/savefile.h"
+#include "common/str-array.h"
+#include "common/system.h"
+
+#include "zvision/zvision.h"
+#include "zvision/detection.h"
+
+
+namespace ZVision {
+
+uint32 ZVision::getFeatures() const {
+ return _gameDescription->desc.flags;
+}
+
+Common::Language ZVision::getLanguage() const {
+ return _gameDescription->desc.language;
+}
+
+} // End of namespace ZVision
+
+
+static const PlainGameDescriptor zVisionGames[] = {
+ {"zvision", "ZVision Game"},
+ {"znemesis", "Zork Nemesis: The Forbidden Lands"},
+ {"zgi", "Zork: Grand Inquisitor"},
+ {0, 0}
+};
+
+
+namespace ZVision {
+
+static const ZVisionGameDescription gameDescriptions[] = {
+
+ {
+ // Zork Nemesis English version
+ {
+ "znemesis",
+ 0,
+ AD_ENTRY1s("CSCR.ZFS", "88226e51a205d2e50c67a5237f3bd5f2", 2397741),
+ Common::EN_ANY,
+ Common::kPlatformDOS,
+ ADGF_NO_FLAGS,
+ GUIO1(GUIO_NONE)
+ },
+ ZorkNemesis
+ },
+
+ {
+ // Zork Grand Inquisitor English version
+ {
+ "zgi",
+ 0,
+ AD_ENTRY1s("SCRIPTS.ZFS", "81efd40ecc3d22531e211368b779f17f", 8336944),
+ Common::EN_ANY,
+ Common::kPlatformDOS,
+ ADGF_NO_FLAGS,
+ GUIO1(GUIO_NONE)
+ },
+ ZorkGrandInquisitor
+ },
+
+ {
+ AD_TABLE_END_MARKER,
+ None
+ }
+};
+
+} // End of namespace ZVision
+
+static const char *directoryGlobs[] = {
+ "znemscr",
+ 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
+};
+
+class ZVisionMetaEngine : public AdvancedMetaEngine {
+public:
+ ZVisionMetaEngine() : AdvancedMetaEngine(ZVision::gameDescriptions, sizeof(ZVision::ZVisionGameDescription), zVisionGames) {
+ _maxScanDepth = 2;
+ _directoryGlobs = directoryGlobs;
+ _singleid = "zvision";
+ }
+
+ virtual const char *getName() const {
+ return "ZVision";
+ }
+
+ virtual const char *getOriginalCopyright() const {
+ return "ZVision Activision (C) 1996";
+ }
+
+ 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;
+ SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const;
+};
+
+bool ZVisionMetaEngine::hasFeature(MetaEngineFeature f) const {
+ return false;
+ /*
+ (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 ZVisionMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const {
+ const ZVision::ZVisionGameDescription *gd = (const ZVision::ZVisionGameDescription *)desc;
+ if (gd) {
+ *engine = new ZVision::ZVision(syst, gd);
+ }
+ 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::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..)*/
+
+ 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;
+ }
+ }
+ }*/
+
+ return saveList;
+}
+
+int ZVisionMetaEngine::getMaximumSaveSlot() const {
+ return 999;
+}
+
+void ZVisionMetaEngine::removeSaveState(const char *target, int slot) const {
+ /*
+ Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
+ Common::String filename = ZVision::ZVision::getSavegameFilename(target, slot);
+
+ saveFileMan->removeSavefile(filename.c_str());
+
+ Common::StringArray filenames;
+ Common::String pattern = target;
+ pattern += ".???";
+ filenames = saveFileMan->listSavefiles(pattern.c_str());
+ 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);
+ }
+ }
+ */
+}
+
+SaveStateDescriptor ZVisionMetaEngine::querySaveMetaInfos(const char *target, int slot) const {
+ /*
+ Common::String filename = ZVision::ZVision::getSavegameFilename(target, slot);
+ Common::InSaveFile *in = g_system->getSavefileManager()->openForLoading(filename.c_str());
+
+ if (in) {
+ ZVision::ZVision::SaveHeader header;
+ ZVision::ZVision::kReadSaveHeaderError error;
+
+ error = ZVision::ZVision::readSaveHeader(in, true, header);
+ delete in;
+
+ if (error == ZVision::ZVision::kRSHENoError) {
+ SaveStateDescriptor desc(slot, header.description);
+
+ desc.setThumbnail(header.thumbnail);
+
+ if (header.version > 0) {
+ int day = (header.saveDate >> 24) & 0xFF;
+ int month = (header.saveDate >> 16) & 0xFF;
+ int year = header.saveDate & 0xFFFF;
+
+ desc.setSaveDate(year, month, day);
+
+ int hour = (header.saveTime >> 16) & 0xFF;
+ int minutes = (header.saveTime >> 8) & 0xFF;
+
+ desc.setSaveTime(hour, minutes);
+
+ desc.setPlayTime(header.playTime * 1000);
+ }
+
+ return desc;
+ }
+ }
+ */
+
+ return SaveStateDescriptor();
+}
+
+#if PLUGIN_ENABLED_DYNAMIC(ZVISION)
+ REGISTER_PLUGIN_DYNAMIC(ZVISION, PLUGIN_TYPE_ENGINE, ZVisionMetaEngine);
+#else
+ REGISTER_PLUGIN_STATIC(ZVISION, PLUGIN_TYPE_ENGINE, ZVisionMetaEngine);
+#endif
diff --git a/engines/zvision/detection.h b/engines/zvision/detection.h
new file mode 100644
index 0000000000..f08cfd2fe1
--- /dev/null
+++ b/engines/zvision/detection.h
@@ -0,0 +1,43 @@
+/* ScummVM - Graphic Adventure Engine
+*
+* ScummVM is the legal property of its developers, whose names
+* are too numerous to list here. Please refer to the COPYRIGHT
+* file distributed with this source distribution.
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 2
+* of the License, or (at your option) any later version.
+
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+#ifndef ZVISION_DETECTION_H
+#define ZVISION_DETECTION_H
+
+#include "engines/advancedDetector.h"
+
+namespace ZVision {
+
+enum ZVisionGameId {
+ None = 0,
+ ZorkNemesis = 1,
+ ZorkGrandInquisitor = 2
+};
+
+struct ZVisionGameDescription {
+ ADGameDescription desc;
+ ZVisionGameId gameId;
+};
+
+}
+
+#endif
diff --git a/engines/zvision/events.cpp b/engines/zvision/events.cpp
new file mode 100644
index 0000000000..c1b894c290
--- /dev/null
+++ b/engines/zvision/events.cpp
@@ -0,0 +1,183 @@
+/* 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/console.h"
+#include "common/events.h"
+#include "engines/util.h"
+#include "common/system.h"
+#include "common/rational.h"
+
+#include "zvision/cursor_manager.h"
+#include "zvision/render_manager.h"
+#include "zvision/script_manager.h"
+#include "zvision/rlf_animation.h"
+
+namespace ZVision {
+
+void ZVision::processEvents() {
+ while (_eventMan->pollEvent(_event)) {
+ switch (_event.type) {
+ case Common::EVENT_LBUTTONDOWN:
+ onMouseDown(_event.mouse);
+ break;
+
+ case Common::EVENT_LBUTTONUP:
+ onMouseUp(_event.mouse);
+ break;
+
+ case Common::EVENT_RBUTTONDOWN:
+ // TODO: Inventory logic
+ break;
+
+ case Common::EVENT_MOUSEMOVE:
+ onMouseMove(_event.mouse);
+ break;
+
+ case Common::EVENT_KEYDOWN:
+ switch (_event.kbd.keycode) {
+ case Common::KEYCODE_d:
+ if (_event.kbd.hasFlags(Common::KBD_CTRL)) {
+ // Start the debugger
+ _console->attach();
+ _console->onFrame();
+ }
+ break;
+ case Common::KEYCODE_q:
+ if (_event.kbd.hasFlags(Common::KBD_CTRL))
+ quitGame();
+ break;
+ default:
+ break;
+ }
+
+ _scriptManager->onKeyDown(_event.kbd);
+ break;
+ case Common::EVENT_KEYUP:
+ _scriptManager->onKeyUp(_event.kbd);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+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) {
+ Common::Point imageCoord(_renderManager->screenSpaceToImageSpace(pos));
+
+ bool cursorWasChanged = _scriptManager->onMouseMove(pos, imageCoord);
+
+ // Graph of the function governing rotation velocity:
+ //
+ // |---------------- working window ------------------|
+ // ^ |---------|
+ // | |
+ // +Max velocity | rotation screen edge offset
+ // | /|
+ // | / |
+ // | / |
+ // | / |
+ // | / |
+ // | / |
+ // | / |
+ // | / |
+ // | / |
+ // Zero velocity |______________________________ ______________________________/_________|__________________________>
+ // | Position -> | /
+ // | | /
+ // | | /
+ // | | /
+ // | | /
+ // | | /
+ // | | /
+ // | | /
+ // | | /
+ // -Max velocity | |/
+ // |
+ // |
+ // ^
+
+ if (_workingWindow.contains(pos)) {
+ 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();
+ 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();
+ cursorWasChanged = true;
+ } else {
+ _renderManager->setBackgroundVelocity(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();
+ 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();
+ cursorWasChanged = true;
+ } else {
+ _renderManager->setBackgroundVelocity(0);
+ }
+ }
+ } else {
+ _renderManager->setBackgroundVelocity(0);
+ }
+
+ if (!cursorWasChanged) {
+ _cursorManager->revertToIdle();
+ }
+}
+
+} // End of namespace ZVision
diff --git a/engines/zvision/input_control.cpp b/engines/zvision/input_control.cpp
new file mode 100644
index 0000000000..ab4cc6bd62
--- /dev/null
+++ b/engines/zvision/input_control.cpp
@@ -0,0 +1,141 @@
+/* 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/str.h"
+#include "common/stream.h"
+#include "common/rect.h"
+
+#include "zvision/input_control.h"
+#include "zvision/zvision.h"
+#include "zvision/script_manager.h"
+#include "zvision/string_manager.h"
+#include "zvision/render_manager.h"
+#include "zvision/utility.h"
+
+
+namespace ZVision {
+
+InputControl::InputControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream)
+ : Control(engine, key),
+ _nextTabstop(0),
+ _focused(false),
+ _textChanged(false),
+ _cursorOffset(0) {
+ // Loop until we find the closing brace
+ Common::String line = stream.readLine();
+ trimCommentsAndWhiteSpace(&line);
+
+ while (!stream.eos() && !line.contains('}')) {
+ if (line.matchString("*rectangle*", true)) {
+ int x1;
+ int y1;
+ int x2;
+ int y2;
+
+ sscanf(line.c_str(), "%*[^(](%d %d %d %d)", &x1, &y1, &x2, &y2);
+
+ _textRectangle = Common::Rect(x1, y1, x2, y2);
+ } else if (line.matchString("*aux_hotspot*", true)) {
+ int x1;
+ int y1;
+ int x2;
+ int y2;
+
+ sscanf(line.c_str(), "%*[^(](%d %d %d %d)", &x1, &y1, &x2, &y2);
+
+ _headerRectangle = Common::Rect(x1, y1, x2, y2);
+ } else if (line.matchString("*string_init*", true)) {
+ uint fontFormatNumber;
+
+ sscanf(line.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[25];
+
+ sscanf(line.c_str(), "%*[^(](%25s %*u)", fileName);
+
+ _cursorAnimationFileName = Common::String(fileName);
+ } else if (line.matchString("*cursor_dimensions*", true)) {
+ // Ignore, use the dimensions in the animation file
+ } else if (line.matchString("*cursor_animation_frames*", true)) {
+ // Ignore, use the frame count in the animation file
+ } else if (line.matchString("*focus*", true)) {
+ _focused = true;
+ }
+
+ line = stream.readLine();
+ trimCommentsAndWhiteSpace(&line);
+ }
+}
+
+void InputControl::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
+ _engine->getScriptManager()->focusControl(_key);
+}
+
+void InputControl::onKeyDown(Common::KeyState keyState) {
+ if (!_focused) {
+ return;
+ }
+
+ if (keyState.keycode == Common::KEYCODE_BACKSPACE) {
+ _currentInputText.deleteLastChar();
+ } else if (keyState.keycode == Common::KEYCODE_TAB) {
+ _focused = false;
+ // Focus the next input control
+ _engine->getScriptManager()->focusControl(_nextTabstop);
+ } 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;
+ }
+ }
+}
+
+bool InputControl::process(uint32 deltaTimeInMillis) {
+ if (!_focused) {
+ return false;
+ }
+
+ // 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;
+ }
+
+ // Render the next frame of the animation
+ // TODO: Implement animation handling
+
+ return false;
+}
+
+} // End of namespace ZVision
diff --git a/engines/zvision/input_control.h b/engines/zvision/input_control.h
new file mode 100644
index 0000000000..33a1b34313
--- /dev/null
+++ b/engines/zvision/input_control.h
@@ -0,0 +1,60 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef ZVISION_INPUT_CONTROL_H
+#define ZVISION_INPUT_CONTROL_H
+
+#include "common/types.h"
+
+#include "zvision/control.h"
+#include "zvision/string_manager.h"
+
+
+namespace ZVision {
+
+class InputControl : public Control {
+public:
+ InputControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream);
+
+private:
+ Common::Rect _textRectangle;
+ Common::Rect _headerRectangle;
+ StringManager::TextStyle _textStyle;
+ uint32 _nextTabstop;
+ Common::String _cursorAnimationFileName;
+ bool _focused;
+
+ Common::String _currentInputText;
+ bool _textChanged;
+ uint _cursorOffset;
+
+public:
+ void focus() { _focused = true; }
+ void unfocus() { _focused = false; }
+ void onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos);
+ void onKeyDown(Common::KeyState keyState);
+ bool process(uint32 deltaTimeInMillis);
+};
+
+} // End of namespace ZVision
+
+#endif
diff --git a/engines/zvision/inventory_manager.h b/engines/zvision/inventory_manager.h
new file mode 100644
index 0000000000..ae6d116b18
--- /dev/null
+++ b/engines/zvision/inventory_manager.h
@@ -0,0 +1,28 @@
+/* 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_INVENTORY_MANAGER_H
+#define ZVISION_INVENTORY_MANAGER_H
+
+// TODO: Implement InventoryManager
+
+#endif
diff --git a/engines/zvision/lever_control.cpp b/engines/zvision/lever_control.cpp
new file mode 100644
index 0000000000..93a6054850
--- /dev/null
+++ b/engines/zvision/lever_control.cpp
@@ -0,0 +1,400 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/scummsys.h"
+
+#include "common/stream.h"
+#include "common/file.h"
+#include "common/tokenizer.h"
+#include "common/system.h"
+
+#include "graphics/surface.h"
+
+#include "zvision/lever_control.h"
+#include "zvision/zvision.h"
+#include "zvision/script_manager.h"
+#include "zvision/render_manager.h"
+#include "zvision/cursor_manager.h"
+#include "zvision/rlf_animation.h"
+#include "zvision/zork_avi_decoder.h"
+#include "zvision/utility.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) {
+
+ // Loop until we find the closing brace
+ Common::String line = stream.readLine();
+ trimCommentsAndWhiteSpace(&line);
+
+ while (!stream.eos() && !line.contains('}')) {
+ if (line.matchString("*descfile*", true)) {
+ char levFileName[25];
+ sscanf(line.c_str(), "%*[^(](%25[^)])", levFileName);
+
+ parseLevFile(levFileName);
+ } else if (line.matchString("*cursor*", true)) {
+ char cursorName[25];
+ sscanf(line.c_str(), "%*[^(](%25[^)])", cursorName);
+
+ _cursorName = Common::String(cursorName);
+ }
+
+ line = stream.readLine();
+ trimCommentsAndWhiteSpace(&line);
+ }
+
+ renderFrame(_currentFrame);
+}
+
+LeverControl::~LeverControl() {
+ if (_fileType == AVI) {
+ delete _animation.avi;
+ } else if (_fileType == RLF) {
+ delete _animation.rlf;
+ }
+
+ delete[] _frameInfo;
+}
+
+void LeverControl::parseLevFile(const Common::String &fileName) {
+ Common::File file;
+ if (!file.open(fileName)) {
+ warning("LEV file %s could could be opened", fileName.c_str());
+ return;
+ }
+
+ Common::String line = file.readLine();
+
+ while (!file.eos()) {
+ if (line.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)) {
+ // Not used
+ } else if (line.matchString("*anim_coords*", true)) {
+ int left, top, right, bottom;
+ sscanf(line.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)) {
+ uint mirrored;
+ sscanf(line.c_str(), "%*[^:]:%u~", &mirrored);
+
+ _mirrored = mirrored == 0 ? false : true;
+ } else if (line.matchString("*frames*", true)) {
+ sscanf(line.c_str(), "%*[^:]:%u~", &_frameCount);
+
+ _frameInfo = new FrameInfo[_frameCount];
+ } else if (line.matchString("*elsewhere*", true)) {
+ // Not used
+ } else if (line.matchString("*out_of_control*", true)) {
+ // Not used
+ } else if (line.matchString("*start_pos*", true)) {
+ sscanf(line.c_str(), "%*[^:]:%u~", &_startFrame);
+ _currentFrame = _startFrame;
+ } else if (line.matchString("*hotspot_deltas*", true)) {
+ uint x;
+ uint y;
+ sscanf(line.c_str(), "%*[^:]:%u %u~", &x, &y);
+
+ _hotspotDelta.x = x;
+ _hotspotDelta.y = y;
+ } else {
+ uint frameNumber;
+ uint x, y;
+
+ if (sscanf(line.c_str(), "%u:%u %u", &frameNumber, &x, &y) == 3) {
+ _frameInfo[frameNumber].hotspot.left = x;
+ _frameInfo[frameNumber].hotspot.top = y;
+ _frameInfo[frameNumber].hotspot.right = x + _hotspotDelta.x;
+ _frameInfo[frameNumber].hotspot.bottom = y + _hotspotDelta.y;
+ }
+
+ Common::StringTokenizer tokenizer(line, " ^=()");
+ tokenizer.nextToken();
+ tokenizer.nextToken();
+
+ Common::String token = tokenizer.nextToken();
+ while (!tokenizer.empty()) {
+ if (token == "D") {
+ token = tokenizer.nextToken();
+
+ uint angle;
+ uint toFrame;
+ sscanf(token.c_str(), "%u,%u", &toFrame, &angle);
+
+ _frameInfo[frameNumber].directions.push_back(Direction(angle, toFrame));
+ } else if (token.hasPrefix("P")) {
+ // Format: P(<from> to <to>)
+ tokenizer.nextToken();
+ tokenizer.nextToken();
+ token = tokenizer.nextToken();
+ uint to = atoi(token.c_str());
+
+ _frameInfo[frameNumber].returnRoute.push_back(to);
+ }
+
+ token = tokenizer.nextToken();
+ }
+ }
+
+ line = file.readLine();
+ }
+}
+
+void LeverControl::onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
+ if (!_enabled) {
+ return;
+ }
+
+ if (_frameInfo[_currentFrame].hotspot.contains(backgroundImageSpacePos)) {
+ _mouseIsCaptured = true;
+ _lastMousePos = backgroundImageSpacePos;
+ }
+}
+
+void LeverControl::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
+ if (!_enabled) {
+ return;
+ }
+
+ if (_mouseIsCaptured) {
+ _mouseIsCaptured = false;
+ _engine->getScriptManager()->setStateValue(_key, _currentFrame);
+
+ _isReturning = true;
+ _returnRoutesCurrentProgress = _frameInfo[_currentFrame].returnRoute.begin();
+ _returnRoutesCurrentFrame = _currentFrame;
+ }
+}
+
+bool LeverControl::onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
+ if (!_enabled) {
+ return false;
+ }
+
+ bool cursorWasChanged = false;
+
+ if (_mouseIsCaptured) {
+ // Make sure the square distance between the last point and the current point is greater than 64
+ // 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) {
+ int angle = calculateVectorAngle(_lastMousePos, backgroundImageSpacePos);
+ _lastMousePos = backgroundImageSpacePos;
+
+ for (Common::List<Direction>::iterator iter = _frameInfo[_currentFrame].directions.begin(); iter != _frameInfo[_currentFrame].directions.end(); iter++) {
+ if (angle >= (int)(*iter).angle - ANGLE_DELTA && angle <= (int)(*iter).angle + ANGLE_DELTA) {
+ _currentFrame = (*iter).toFrame;
+ renderFrame(_currentFrame);
+ break;
+ }
+ }
+ }
+ } else if (_frameInfo[_currentFrame].hotspot.contains(backgroundImageSpacePos)) {
+ _engine->getCursorManager()->changeCursor(_cursorName);
+ cursorWasChanged = true;
+ }
+
+ return cursorWasChanged;
+}
+
+bool LeverControl::process(uint32 deltaTimeInMillis) {
+ if (!_enabled) {
+ return false;
+ }
+
+ if (_isReturning) {
+ _accumulatedTime += deltaTimeInMillis;
+ while (_accumulatedTime >= ANIMATION_FRAME_TIME) {
+ _accumulatedTime -= ANIMATION_FRAME_TIME;
+ if (_returnRoutesCurrentFrame == *_returnRoutesCurrentProgress) {
+ _returnRoutesCurrentProgress++;
+ }
+ if (_returnRoutesCurrentProgress == _frameInfo[_currentFrame].returnRoute.end()) {
+ _isReturning = false;
+ _currentFrame = _returnRoutesCurrentFrame;
+ _engine->getScriptManager()->setStateValue(_key, _currentFrame);
+ return false;
+ }
+
+ uint toFrame = *_returnRoutesCurrentProgress;
+ if (_returnRoutesCurrentFrame < toFrame) {
+ _returnRoutesCurrentFrame++;
+ } else if (_returnRoutesCurrentFrame > toFrame) {
+ _returnRoutesCurrentFrame--;
+ }
+
+ renderFrame(_returnRoutesCurrentFrame);
+ }
+ }
+
+ return false;
+}
+
+int LeverControl::calculateVectorAngle(const Common::Point &pointOne, const Common::Point &pointTwo) {
+ // Check for the easy angles first
+ if (pointOne.x == pointTwo.x && pointOne.y == pointTwo.y)
+ return -1; // This should never happen
+ else if (pointOne.x == pointTwo.x) {
+ if (pointTwo.y < pointOne.y)
+ return 90;
+ else
+ return 270;
+ } else if (pointOne.y == pointTwo.y) {
+ if (pointTwo.x > pointOne.x)
+ return 0;
+ else
+ return 180;
+ } else {
+ // Calculate the angle with trig
+ int16 xDist = pointTwo.x - pointOne.x;
+ int16 yDist = pointTwo.y - pointOne.y;
+
+ // Calculate the angle using arctan
+ // Then convert to degrees. (180 / 3.14159 = 57.2958)
+ int angle = int(atan((float)yDist / (float)xDist) * 57);
+
+ // Calculate what quadrant pointTwo is in
+ uint quadrant = ((yDist > 0 ? 1 : 0) << 1) | (xDist < 0 ? 1 : 0);
+
+ // Explanation of quadrants:
+ //
+ // yDist > 0 | xDist < 0 | Quadrant number
+ // 0 | 0 | 0
+ // 0 | 1 | 1
+ // 1 | 0 | 2
+ // 1 | 1 | 3
+ //
+ // Note: I know this doesn't line up with traditional mathematical quadrants
+ // but doing it this way allows you can use a switch and is a bit cleaner IMO.
+ //
+ // The graph below shows the 4 quadrants pointTwo can end up in as well
+ // as what the angle as calculated above refers to.
+ // Note: The calculated angle in quadrants 0 and 3 is negative
+ // due to arctan(-x) = -theta
+ //
+ // Origin => (pointOne.x, pointOne.y)
+ // * => (pointTwo.x, pointTwo.y)
+ //
+ // 90
+ // ^
+ // * | *
+ // \ | /
+ // \ | /
+ // \ | /
+ // Quadrant 1 \ | / Quadrant 0
+ // \ | /
+ // \ | /
+ // angle ( \|/ ) -angle
+ // 180 <----------------------------------------> 0
+ // -angle ( /|\ ) angle
+ // / | \
+ // / | \
+ // Quadrant 3 / | \ Quadrant 2
+ // / | \
+ // / | \
+ // / | \
+ // * | *
+ // ^
+ // 270
+
+ // Convert the local angles to unit circle angles
+ switch (quadrant) {
+ case 0:
+ angle = 180 + angle;
+ break;
+ case 1:
+ // Do nothing
+ break;
+ case 2:
+ angle = 180 + angle;
+ break;
+ case 3:
+ angle = 360 + angle;
+ break;
+ }
+
+ return angle;
+ }
+}
+
+void LeverControl::renderFrame(uint frameNumber) {
+ if (frameNumber == 0) {
+ _lastRenderedFrame = frameNumber;
+ } else if (frameNumber < _lastRenderedFrame && _mirrored) {
+ _lastRenderedFrame = frameNumber;
+ frameNumber = (_frameCount * 2) - frameNumber - 1;
+ } else {
+ _lastRenderedFrame = frameNumber;
+ }
+
+ const uint16 *frameData;
+ int x = _animationCoords.left;
+ int y = _animationCoords.top;
+ int width;
+ int height;
+
+ 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;
+ }
+
+ _engine->getRenderManager()->copyRectToWorkingWindow(frameData, x, y, width, width, height);
+}
+
+} // End of namespace ZVision
diff --git a/engines/zvision/lever_control.h b/engines/zvision/lever_control.h
new file mode 100644
index 0000000000..b5677a269e
--- /dev/null
+++ b/engines/zvision/lever_control.h
@@ -0,0 +1,129 @@
+/* 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_LEVER_CONTROL_H
+#define ZVISION_LEVER_CONTROL_H
+
+#include "common/types.h"
+
+#include "common/list.h"
+#include "common/rect.h"
+
+#include "zvision/control.h"
+
+
+namespace ZVision {
+
+class ZorkAVIDecoder;
+class RlfAnimation;
+
+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 angle, uint toFrame) : angle(angle), toFrame(toFrame) {}
+
+ uint angle;
+ uint toFrame;
+ };
+
+ struct FrameInfo {
+ Common::Rect hotspot;
+ Common::List<Direction> directions;
+ Common::List<uint> returnRoute;
+ };
+
+ enum {
+ ANGLE_DELTA = 30, // How far off a mouse angle can be and still be considered valid. This is in both directions, so the total buffer zone is (2 * ANGLE_DELTA)
+ ANIMATION_FRAME_TIME = 30 // In millis
+ };
+
+private:
+ union {
+ RlfAnimation *rlf;
+ ZorkAVIDecoder *avi;
+ } _animation;
+ FileType _fileType;
+
+ Common::String _cursorName;
+ Common::Rect _animationCoords;
+ bool _mirrored;
+ uint _frameCount;
+ uint _startFrame;
+ Common::Point _hotspotDelta;
+ FrameInfo *_frameInfo;
+
+ uint _currentFrame;
+ uint _lastRenderedFrame;
+ bool _mouseIsCaptured;
+ bool _isReturning;
+ Common::Point _lastMousePos;
+ Common::List<uint>::iterator _returnRoutesCurrentProgress;
+ uint _returnRoutesCurrentFrame;
+ uint32 _accumulatedTime;
+
+public:
+ void onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos);
+ void 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 parseLevFile(const Common::String &fileName);
+ /**
+ * Calculates the angle a vector makes with the negative y-axis
+ *
+ * 90
+ * pointTwo * ^
+ * \ |
+ * \ |
+ * \ |
+ * \ |
+ * angle ( \|pointOne
+ * 180 <-----------*-----------> 0
+ * |
+ * |
+ * |
+ * |
+ * |
+ * ^
+ * 270
+ *
+ * @param pointOne The origin of the vector
+ * @param pointTwo The end of the vector
+ * @return The angle the vector makes with the negative y-axis
+ */
+ static int calculateVectorAngle(const Common::Point &pointOne, const Common::Point &pointTwo);
+ void renderFrame(uint frameNumber);
+};
+
+} // End of namespace ZVision
+
+#endif
diff --git a/engines/zvision/lzss_read_stream.cpp b/engines/zvision/lzss_read_stream.cpp
new file mode 100644
index 0000000000..03ab12d8be
--- /dev/null
+++ b/engines/zvision/lzss_read_stream.cpp
@@ -0,0 +1,103 @@
+/* ScummVM - Graphic Adventure Engine
+*
+* ScummVM is the legal property of its developers, whose names
+* are too numerous to list here. Please refer to the COPYRIGHT
+* file distributed with this source distribution.
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 2
+* of the License, or (at your option) any later version.
+
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+#include "common/scummsys.h"
+
+#include "zvision/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);
+}
+
+uint32 LzssReadStream::decompressBytes(byte *destination, uint32 numberOfBytes) {
+ uint32 destinationCursor = 0;
+
+ while (destinationCursor < numberOfBytes) {
+ byte flagbyte = _source->readByte();
+ if (_source->eos())
+ break;
+ uint mask = 1;
+
+ for (int i = 0; i < 8; i++) {
+ if ((flagbyte & mask) == mask)
+ {
+ byte data = _source->readByte();
+ if (_source->eos())
+ return destinationCursor;
+
+ _window[_windowCursor] = data;
+ destination[destinationCursor++] = data;
+
+ // Increment and wrap the window cursor
+ _windowCursor = (_windowCursor + 1) & 0xFFF;
+ }
+ else
+ {
+ byte low = _source->readByte();
+ if (_source->eos())
+ return destinationCursor;
+
+ byte high = _source->readByte();
+ if (_source->eos())
+ return destinationCursor;
+
+ uint16 length = (high & 0xF) + 2;
+ uint16 offset = low | ((high & 0xF0)<<4);
+
+ for(int j = 0; j <= length; j++)
+ {
+ byte temp = _window[(offset + j) & 0xFFF];
+ _window[_windowCursor] = temp;
+ destination[destinationCursor++] = temp;
+ _windowCursor = (_windowCursor + 1) & 0xFFF;
+ }
+ };
+
+ mask = mask << 1;
+ }
+ }
+
+ return destinationCursor;
+}
+
+bool LzssReadStream::eos() const {
+ return _eosFlag;
+}
+
+uint32 LzssReadStream::read(void *dataPtr, uint32 dataSize) {
+ uint32 bytesRead = decompressBytes(static_cast<byte *>(dataPtr), dataSize);
+ if (bytesRead < dataSize) {
+ // Flag that we're at EOS
+ _eosFlag = true;
+ }
+
+ return dataSize;
+}
+
+} // End of namespace ZVision
diff --git a/engines/zvision/lzss_read_stream.h b/engines/zvision/lzss_read_stream.h
new file mode 100644
index 0000000000..25a8b67222
--- /dev/null
+++ b/engines/zvision/lzss_read_stream.h
@@ -0,0 +1,72 @@
+/* ScummVM - Graphic Adventure Engine
+*
+* ScummVM is the legal property of its developers, whose names
+* are too numerous to list here. Please refer to the COPYRIGHT
+* file distributed with this source distribution.
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 2
+* of the License, or (at your option) any later version.
+
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+#ifndef ZVISION_LZSS_STREAM_H
+#define ZVISION_LZSS_STREAM_H
+
+#include "common/types.h"
+#include "common/stream.h"
+#include "common/array.h"
+
+namespace Common {
+class SeekableReadStream;
+}
+
+namespace ZVision {
+
+class LzssReadStream : public Common::ReadStream {
+public:
+ /**
+ * A class that decompresses LZSS data and implements ReadStream for easy access
+ * to the decompiled data.
+ *
+ * @param source The source data
+ */
+ LzssReadStream(Common::SeekableReadStream *source);
+
+private:
+ enum {
+ BLOCK_SIZE = 0x1000
+ };
+
+private:
+ Common::SeekableReadStream *_source;
+ byte _window[BLOCK_SIZE];
+ uint _windowCursor;
+ bool _eosFlag;
+
+public:
+ bool eos() const;
+ uint32 read(void *dataPtr, uint32 dataSize);
+
+private:
+ /**
+ * Decompress the next <numberOfBytes> from the source stream. Or until EOS
+ *
+ * @param numberOfBytes How many bytes to decompress. This is a count of source bytes, not destination bytes
+ */
+ uint32 decompressBytes(byte* destination, uint32 numberOfBytes);
+};
+
+}
+
+#endif
diff --git a/engines/zvision/menu.h b/engines/zvision/menu.h
new file mode 100644
index 0000000000..affc69abd5
--- /dev/null
+++ b/engines/zvision/menu.h
@@ -0,0 +1,28 @@
+/* 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
+
+// TODO: Implement MenuHandler
+
+#endif
diff --git a/engines/zvision/module.mk b/engines/zvision/module.mk
new file mode 100644
index 0000000000..2f2929db67
--- /dev/null
+++ b/engines/zvision/module.mk
@@ -0,0 +1,45 @@
+MODULE := engines/zvision
+
+MODULE_OBJS := \
+ actions.o \
+ animation.o \
+ animation_control.o \
+ clock.o \
+ console.o \
+ control.o \
+ cursor.o \
+ cursor_manager.o \
+ detection.o \
+ events.o \
+ input_control.o \
+ lever_control.o \
+ lzss_read_stream.o \
+ push_toggle_control.o \
+ render_manager.o \
+ render_table.o \
+ rlf_animation.o \
+ save_manager.o \
+ scr_file_handling.o \
+ script_manager.o \
+ scripts.o \
+ single_value_container.o \
+ string_manager.o \
+ timer_node.o \
+ truetype_font.o \
+ utility.o \
+ video.o \
+ zvision.o \
+ zfs_archive.o \
+ zork_avi_decoder.o \
+ zork_raw.o
+
+MODULE_DIRS += \
+ engines/zvision
+
+# This module can be built as a plugin
+ifeq ($(ENABLE_ZVISION), DYNAMIC_PLUGIN)
+PLUGIN := 1
+endif
+
+# Include common rules
+include $(srcdir)/rules.mk \ No newline at end of file
diff --git a/engines/zvision/push_toggle_control.cpp b/engines/zvision/push_toggle_control.cpp
new file mode 100644
index 0000000000..f4c0bc987e
--- /dev/null
+++ b/engines/zvision/push_toggle_control.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 "common/stream.h"
+
+#include "zvision/push_toggle_control.h"
+#include "zvision/zvision.h"
+#include "zvision/script_manager.h"
+#include "zvision/cursor_manager.h"
+#include "zvision/utility.h"
+
+namespace ZVision {
+
+PushToggleControl::PushToggleControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream)
+ : Control(engine, key) {
+ // Loop until we find the closing brace
+ Common::String line = stream.readLine();
+ trimCommentsAndWhiteSpace(&line);
+
+ while (!stream.eos() && !line.contains('}')) {
+ if (line.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);
+ }
+
+ line = stream.readLine();
+ trimCommentsAndWhiteSpace(&line);
+ }
+
+ if (_hotspot.isEmpty() || _hoverCursor.empty()) {
+ warning("Push_toggle cursor %u was parsed incorrectly", key);
+ }
+}
+
+PushToggleControl::~PushToggleControl() {
+ // Clear the state value back to 0
+ _engine->getScriptManager()->setStateValue(_key, 0);
+}
+
+void PushToggleControl::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
+ if (!_enabled) {
+ return;
+ }
+
+ if (_hotspot.contains(backgroundImageSpacePos)) {
+ _engine->getScriptManager()->setStateValue(_key, 1);
+ }
+}
+
+bool PushToggleControl::onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
+ if (!_enabled) {
+ return false;
+ }
+
+ if (_hotspot.contains(backgroundImageSpacePos)) {
+ _engine->getCursorManager()->changeCursor(_hoverCursor);
+ return true;
+ }
+
+ return false;
+}
+
+} // End of namespace ZVision
diff --git a/engines/zvision/push_toggle_control.h b/engines/zvision/push_toggle_control.h
new file mode 100644
index 0000000000..8ab6f619bb
--- /dev/null
+++ b/engines/zvision/push_toggle_control.h
@@ -0,0 +1,69 @@
+/* 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_PUSH_TOGGLE_CONTROL_H
+#define ZVISION_PUSH_TOGGLE_CONTROL_H
+
+#include "common/types.h"
+
+#include "common/rect.h"
+
+#include "zvision/control.h"
+
+
+namespace ZVision {
+
+class PushToggleControl : public Control {
+public:
+ PushToggleControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream);
+ ~PushToggleControl();
+
+ /**
+ * 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);
+ /**
+ * 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);
+
+private:
+ /**
+ * The area that will trigger the event
+ * This is in image space coordinates, NOT screen space
+ */
+ Common::Rect _hotspot;
+ /** The cursor to use when hovering over _hotspot */
+ Common::String _hoverCursor;
+};
+
+} // End of namespace ZVision
+
+#endif
diff --git a/engines/zvision/puzzle.h b/engines/zvision/puzzle.h
new file mode 100644
index 0000000000..512a9636b9
--- /dev/null
+++ b/engines/zvision/puzzle.h
@@ -0,0 +1,88 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef ZVISION_PUZZLE_H
+#define ZVISION_PUZZLE_H
+
+
+#include "common/list.h"
+#include "common/ptr.h"
+
+#include "zvision/actions.h"
+
+namespace ZVision {
+
+struct Puzzle {
+ Puzzle() : key(0), flags(0) {}
+
+ ~Puzzle() {
+ for (Common::List<ResultAction *>::iterator iter = resultActions.begin(); iter != resultActions.end(); iter++) {
+ delete (*iter);
+ }
+ }
+
+ /** How criteria should be decided */
+ enum CriteriaOperator {
+ EQUAL_TO,
+ NOT_EQUAL_TO,
+ GREATER_THAN,
+ LESS_THAN
+ };
+
+ /** Criteria for a Puzzle result to be fired */
+ struct CriteriaEntry {
+ /** The key of a global state */
+ uint32 key;
+ /**
+ * What we're comparing the value of the global state against
+ * This can either be a pure value or it can be the key of another global state
+ */
+ uint32 argument;
+ /** How to do the comparison */
+ CriteriaOperator criteriaOperator;
+ /** Whether 'argument' is the key of a global state (true) or a pure value (false) */
+ bool argumentIsAKey;
+ };
+
+ 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
+ };
+
+ uint32 key;
+ Common::List<Common::List <CriteriaEntry> > criteriaList;
+ // This has to be list of pointers because ResultAction is abstract
+ Common::List<ResultAction *> resultActions;
+ uint flags;
+
+ // Used by the ScriptManager to allow unique-ification of _referenceTable
+ // The unique-ification is done by sorting, then iterating and removing duplicates
+ // The sort uses operator<
+ inline bool operator<(const Puzzle &other) const {
+ return key < other.key;
+ }
+};
+
+} // End of namespace ZVision
+
+#endif
diff --git a/engines/zvision/render_manager.cpp b/engines/zvision/render_manager.cpp
new file mode 100644
index 0000000000..7f10535bb2
--- /dev/null
+++ b/engines/zvision/render_manager.cpp
@@ -0,0 +1,518 @@
+/* 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/system.h"
+#include "common/stream.h"
+
+#include "engines/util.h"
+#include "graphics/decoders/tga.h"
+
+#include "zvision/render_manager.h"
+#include "zvision/lzss_read_stream.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() {
+ _workingWindowBuffer.free();
+ _currentBackground.free();
+ _backBuffer.free();
+}
+
+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);
+ }
+}
+
+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
+
+ // Render alpha entries
+ processAlphaEntries();
+
+ if (!_backBufferDirtyRect.isEmpty()) {
+ _system->copyRectToScreen(_backBuffer.getBasePtr(_backBufferDirtyRect.left, _backBufferDirtyRect.top), _backBuffer.pitch, _backBufferDirtyRect.left, _backBufferDirtyRect.top, _backBufferDirtyRect.width(), _backBufferDirtyRect.height());
+ _backBufferDirtyRect = Common::Rect();
+ }
+}
+
+void RenderManager::processAlphaEntries() {
+ // TODO: Add dirty rectangling support. AKA only draw an entry if the _backbufferDirtyRect intersects/contains the entry Rect
+
+ for (Common::HashMap<uint32, AlphaDataEntry>::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();
+
+ 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;
+ }
+ }
+
+ 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));
+ }
+ }
+}
+
+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;
+ }
+}
+
+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;
+ }
+ }
+
+ // Clip subRect to working window bounds
+ Common::Rect subRect(subRectX, subRectY, subRectX + _workingWidth, subRectY + _workingHeight);
+
+ if (!wrap) {
+ // Clip to image bounds
+ subRect.clip(surface.w, surface.h);
+ }
+
+ // Check destRect for validity
+ if (!subRect.isValidRect() || subRect.isEmpty())
+ return;
+
+ copyRectToWorkingWindow((const uint16 *)surface.getBasePtr(subRect.left, subRect.top), destinationX, destinationY, surface.w, subRect.width(), subRect.height());
+}
+
+void RenderManager::renderImageToScreen(const Common::String &fileName, int16 destinationX, int16 destinationY, bool wrap) {
+ Graphics::Surface surface;
+ readImageToSurface(fileName, surface);
+
+ renderSubRectToScreen(surface, destinationX, destinationY, wrap);
+}
+
+void RenderManager::renderImageToScreen(Graphics::Surface &surface, int16 destinationX, int16 destinationY, bool wrap) {
+ renderSubRectToScreen(surface, destinationX, destinationY, wrap);
+}
+
+void RenderManager::readImageToSurface(const Common::String &fileName, Graphics::Surface &destination) {
+ Common::File file;
+
+ if (!file.open(fileName)) {
+ warning("Could not open file %s", fileName.c_str());
+ return;
+ }
+
+ // Read the magic number
+ // Some files are true TGA, while others are TGZ
+ uint32 fileType = file.readUint32BE();
+
+ uint32 imageWidth;
+ uint32 imageHeight;
+ Graphics::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;
+
+ bool isTGZ;
+
+ // Check for TGZ files
+ if (fileType == MKTAG('T', 'G', 'Z', '\0')) {
+ isTGZ = true;
+
+ // TGZ files have a header and then Bitmap data that is compressed with LZSS
+ uint32 decompressedSize = file.readSint32LE();
+ imageWidth = file.readSint32LE();
+ imageHeight = file.readSint32LE();
+
+ LzssReadStream lzssStream(&file);
+ buffer = (uint16 *)(new uint16[decompressedSize]);
+ lzssStream.read(buffer, decompressedSize);
+ } else {
+ isTGZ = false;
+
+ // Reset the cursor
+ file.seek(0);
+
+ // Decode
+ if (!tga.loadStream(file)) {
+ warning("Error while reading TGA image");
+ return;
+ }
+
+ Graphics::Surface tgaSurface = *(tga.getSurface());
+ imageWidth = tgaSurface.w;
+ imageHeight = tgaSurface.h;
+
+ buffer = (uint16 *)tgaSurface.getPixels();
+ }
+
+ // Flip the width and height if transposed
+ if (isTransposed) {
+ uint16 temp = imageHeight;
+ imageHeight = imageWidth;
+ imageWidth = temp;
+ }
+
+ // 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);
+ }
+
+ // If transposed, 'un-transpose' the data while copying it to the destination
+ // Otherwise, just do a simple copy
+ if (isTransposed) {
+ uint16 *dest = (uint16 *)destination.getPixels();
+
+ for (uint32 y = 0; y < imageHeight; y++) {
+ uint32 columnIndex = y * imageWidth;
+
+ for (uint32 x = 0; x < imageWidth; x++) {
+ dest[columnIndex + x] = buffer[x * imageHeight + y];
+ }
+ }
+ } else {
+ memcpy(destination.getPixels(), buffer, imageWidth * imageHeight * _pixelFormat.bytesPerPixel);
+ }
+
+ // Cleanup
+ if (isTGZ) {
+ delete[] buffer;
+ } 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);
+
+ for (int32 y = 0; y < height; y++) {
+ for (int32 x = 0; x < width; x++) {
+ workingWindowBufferPtr[destOffset + x] = buffer[sourceOffset + x];
+ }
+
+ destOffset += _workingWidth;
+ sourceOffset += imageWidth;
+ }
+
+ if (_workingWindowDirtyRect.isEmpty()) {
+ _workingWindowDirtyRect = Common::Rect(destX, destY, destX + width, destY + height);
+ } else {
+ _workingWindowDirtyRect.extend(Common::Rect(destX, destY, destX + width, destY + height));
+ }
+
+ // 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);
+}
+
+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;
+
+ uint32 sourceOffset = 0;
+ uint32 destOffset = 0;
+ uint16 *surfacePtr = (uint16 *)entry.data->getPixels();
+
+ for (int32 y = 0; y < height; y++) {
+ for (int32 x = 0; x < width; x++) {
+ surfacePtr[destOffset + x] = buffer[sourceOffset + x];
+ }
+
+ destOffset += width;
+ sourceOffset += imageWidth;
+ }
+
+ _alphaDataEntries[idNumber] = entry;
+}
+
+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;
+
+ // Draw the text to the working window
+ entry.data = font->drawTextToSurface(text, destX, destY, textColor, maxWidth, maxHeight, align, wrap);
+ entry.width = entry.data->w;
+ entry.height = entry.data->h;
+
+ _alphaDataEntries[idNumber] = entry;
+
+ return Common::Rect(destX, destY, destX + entry.width, destY + entry.height);
+}
+
+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));
+
+ RenderTable::RenderState state = _renderTable.getRenderState();
+ if (state == RenderTable::PANORAMA || state == RenderTable::TILT) {
+ newPoint = _renderTable.convertWarpedCoordToFlatCoord(newPoint);
+ }
+
+ if (state == RenderTable::PANORAMA) {
+ newPoint -= (Common::Point(_screenCenterX, 0) - _backgroundOffset);
+ } else if (state == RenderTable::TILT) {
+ newPoint -= (Common::Point(0, _screenCenterY) - _backgroundOffset);
+ }
+
+ 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;
+
+ return newPoint;
+}
+
+const Common::Point RenderManager::imageSpaceToWorkingWindowSpace(const Common::Point &point) {
+ Common::Point newPoint(point);
+
+ RenderTable::RenderState state = _renderTable.getRenderState();
+ if (state == RenderTable::PANORAMA) {
+ newPoint += (Common::Point(_screenCenterX, 0) - _backgroundOffset);
+ } else if (state == RenderTable::TILT) {
+ newPoint += (Common::Point(0, _screenCenterY) - _backgroundOffset);
+ }
+
+ return newPoint;
+}
+
+bool RenderManager::clipRectToWorkingWindow(Common::Rect &rect) {
+ if (!_workingWindow.contains(rect)) {
+ return false;
+ }
+
+ // 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;
+}
+
+RenderTable *RenderManager::getRenderTable() {
+ return &_renderTable;
+}
+
+void RenderManager::setBackgroundImage(const Common::String &fileName) {
+ readImageToSurface(fileName, _currentBackground);
+
+ moveBackground(0);
+}
+
+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::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;
+ }
+ } else {
+ _backgroundInverseVelocity = 1000 / velocity;
+ }
+}
+
+void RenderManager::moveBackground(int offset) {
+ RenderTable::RenderState state = _renderTable.getRenderState();
+ if (state == RenderTable::TILT) {
+ _backgroundOffset += Common::Point(0, offset);
+
+ _backgroundOffset.y = CLIP<int16>(_backgroundOffset.y, _screenCenterY, (int16)_backgroundHeight - _screenCenterY);
+
+ renderImageToScreen(_currentBackground, 0, _screenCenterY - _backgroundOffset.y, true);
+ } else if (state == RenderTable::PANORAMA) {
+ _backgroundOffset += Common::Point(offset, 0);
+
+ if (_backgroundOffset.x <= -_backgroundWidth)
+ _backgroundOffset.x += _backgroundWidth;
+ else if (_backgroundOffset.x >= _backgroundWidth)
+ _backgroundOffset.x -= _backgroundWidth;
+
+ renderImageToScreen(_currentBackground, _screenCenterX - _backgroundOffset.x, 0, true);
+ } else {
+ renderImageToScreen(_currentBackground, 0, 0);
+ }
+}
+
+uint32 RenderManager::getCurrentBackgroundOffset() {
+ RenderTable::RenderState state = _renderTable.getRenderState();
+
+ if (state == RenderTable::PANORAMA) {
+ return _backgroundOffset.x;
+ } else if (state == RenderTable::TILT) {
+ return _backgroundOffset.y;
+ } else {
+ return 0;
+ }
+}
+
+Graphics::Surface *RenderManager::tranposeSurface(const Graphics::Surface *surface) {
+ Graphics::Surface *tranposedSurface = new Graphics::Surface();
+ tranposedSurface->create(surface->h, surface->w, surface->format);
+
+ uint16 *source = (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];
+ }
+ }
+
+ return tranposedSurface;
+}
+
+} // End of namespace ZVision
diff --git a/engines/zvision/render_manager.h b/engines/zvision/render_manager.h
new file mode 100644
index 0000000000..b0a42d9ba7
--- /dev/null
+++ b/engines/zvision/render_manager.h
@@ -0,0 +1,252 @@
+/* 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_RENDER_MANAGER_H
+#define ZVISION_RENDER_MANAGER_H
+
+#include "common/types.h"
+#include "common/rect.h"
+#include "common/hashmap.h"
+
+#include "graphics/surface.h"
+
+#include "zvision/render_table.h"
+#include "zvision/truetype_font.h"
+
+class OSystem;
+
+namespace Common {
+class String;
+class SeekableReadStream;
+}
+
+namespace Video {
+class VideoDecoder;
+}
+
+namespace ZVision {
+
+class RenderManager {
+public:
+ RenderManager(OSystem *system, uint32 windowWidth, uint32 windowHeight, const Common::Rect workingWindow, const Graphics::PixelFormat pixelFormat);
+ ~RenderManager();
+
+private:
+ struct AlphaDataEntry {
+ Graphics::Surface *data;
+ uint16 alphaColor;
+ uint16 destX;
+ uint16 destY;
+ uint16 width;
+ uint16 height;
+ };
+
+private:
+ OSystem *_system;
+ const Graphics::PixelFormat _pixelFormat;
+
+ // A buffer the exact same size as the workingWindow
+ // It's used for panorama/tilt warping and for clearing the workingWindow to a single color
+ Graphics::Surface _workingWindowBuffer;
+ Graphics::Surface _backBuffer;
+ Common::HashMap<uint32, AlphaDataEntry> _alphaDataEntries;
+
+ Common::Rect _workingWindowDirtyRect;
+ 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;
+
+ Graphics::Surface _currentBackground;
+ /** The (x1,y1) coordinates of the subRectangle of the background that is currently displayed on the screen */
+ Common::Point _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;
+
+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);
+
+ /**
+ * Renders the current state of the backbuffer to the screen
+ */
+ void renderBackbufferToScreen();
+
+ void processAlphaEntries();
+ void clearAlphaEntries() { _alphaDataEntries.clear(); }
+ void removeAlphaEntry(uint32 idNumber) { _alphaDataEntries.erase(idNumber); }
+
+ void copyRectToWorkingWindow(const uint16 *buffer, int32 destX, int32 destY, int32 imageWidth, int32 width, int32 height);
+ void copyRectToWorkingWindow(const uint16 *buffer, int32 destX, int32 destY, int32 imageWidth, int32 width, int32 height, int16 alphaColor, uint32 idNumber);
+
+ 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);
+
+ /**
+ * 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)
+ */
+ void clearWorkingWindowTo555Color(uint16 color);
+
+ /**
+ * 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!
+ *
+ * @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);
+
+ /**
+ * 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!
+ *
+ * @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!
+ */
+ void renderImageToScreen(Graphics::Surface &surface, int16 destinationX, int16 destinationY, bool wrap = false);
+
+ /**
+ * Sets the current background image to be used by the RenderManager and immediately
+ * blits it to the screen. (It won't show up until the end of the frame)
+ *
+ * @param fileName The name of the image file
+ */
+ void setBackgroundImage(const Common::String &fileName);
+
+ /**
+ * Set the background position (_backgroundOffset). If the current RenderState is PANORAMA, the offset
+ * will be in the horizontal direction. If the current RenderState is TILT, the offset will be in the
+ * vertical direction.
+ *
+ * This method will not render anything on the screen. So if nothing else is called that renders the
+ * background, the change won't be seen until next frame.
+ *
+ * @param offset The amount to offset the background
+ */
+ 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);
+
+ RenderTable *getRenderTable();
+ uint32 getCurrentBackgroundOffset();
+ const Graphics::Surface *getBackBuffer() { return &_backBuffer; }
+
+ /**
+ * Creates a copy of surface and transposes the data.
+ *
+ * Note: The user is responsible for calling free() on the returned surface
+ * and then deleting it
+ *
+ * @param surface The data to be transposed
+ * @return A copy of the surface with the data transposed
+ */
+ 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);
+
+ void readImageToSurface(const Common::String &fileName, Graphics::Surface &destination);
+
+ void moveBackground(int offset);
+};
+
+} // End of namespace ZVision
+
+#endif
diff --git a/engines/zvision/render_table.cpp b/engines/zvision/render_table.cpp
new file mode 100644
index 0000000000..5eb208eb8c
--- /dev/null
+++ b/engines/zvision/render_table.cpp
@@ -0,0 +1,244 @@
+/* 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 "graphics/colormasks.h"
+
+#include "zvision/render_table.h"
+#include "zvision/vector2.h"
+
+namespace ZVision {
+
+RenderTable::RenderTable(uint numColumns, uint numRows)
+ : _numRows(numRows),
+ _numColumns(numColumns),
+ _renderState(FLAT) {
+ assert(numRows != 0 && numColumns != 0);
+
+ _internalBuffer = new Vector2[numRows * numColumns];
+}
+
+RenderTable::~RenderTable() {
+ delete[] _internalBuffer;
+}
+
+void RenderTable::setRenderState(RenderState newState) {
+ _renderState = newState;
+
+ switch (newState) {
+ case PANORAMA:
+ _panoramaOptions.fieldOfView = 27.0f;
+ _panoramaOptions.linearScale = 0.55f;
+ _panoramaOptions.reverse = false;
+ break;
+ case TILT:
+ _tiltOptions.fieldOfView = 27.0f;
+ _tiltOptions.linearScale = 0.55f;
+ _tiltOptions.reverse = false;
+ break;
+ case FLAT:
+ // Intentionally left empty
+ break;
+ }
+}
+
+const Common::Point RenderTable::convertWarpedCoordToFlatCoord(const Common::Point &point) {
+ // If we're outside the range of the RenderTable, no warping is happening. Return the maximum image coords
+ if (point.x >= (int16)_numColumns || point.y >= (int16)_numRows || point.x < 0 || point.y < 0) {
+ int16 x = CLIP<int16>(point.x, 0, (int16)_numColumns);
+ int16 y = CLIP<int16>(point.y, 0, (int16)_numRows);
+ return Common::Point(x, y);
+ }
+
+ uint32 index = point.y * _numColumns + point.x;
+
+ Common::Point newPoint(point);
+ newPoint.x += _internalBuffer[index].x;
+ newPoint.y += _internalBuffer[index].y;
+
+ 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) {
+ uint32 destOffset = 0;
+
+ for (uint32 y = subRect.top; y < subRect.bottom; y++) {
+ uint32 sourceOffset = y * _numColumns;
+
+ for (uint32 x = subRect.left; x < subRect.right; x++) {
+ uint32 normalizedX = x - subRect.left;
+ uint32 index = sourceOffset + x;
+
+ // RenderTable only stores offsets from the original coordinates
+ uint32 sourceYIndex = y + _internalBuffer[index].y;
+ uint32 sourceXIndex = x + _internalBuffer[index].x;
+
+ // Clamp the yIndex to the size of the image
+ sourceYIndex = CLIP<uint32>(sourceYIndex, 0, _numRows - 1);
+
+ // Clamp the xIndex to the size of the image
+ sourceXIndex = CLIP<uint32>(sourceXIndex, 0, _numColumns - 1);
+
+ destBuffer[destOffset + normalizedX] = sourceBuffer[sourceYIndex * _numColumns + sourceXIndex];
+ }
+
+ destOffset += destWidth;
+ }
+}
+
+void RenderTable::generateRenderTable() {
+ switch (_renderState) {
+ case ZVision::RenderTable::PANORAMA:
+ generatePanoramaLookupTable();
+ break;
+ case ZVision::RenderTable::TILT:
+ generateTiltLookupTable();
+ break;
+ case ZVision::RenderTable::FLAT:
+ // Intentionally left empty
+ break;
+ }
+}
+
+void RenderTable::generatePanoramaLookupTable() {
+ memset(_internalBuffer, 0, _numRows * _numColumns * sizeof(uint16));
+
+ float halfWidth = (float)_numColumns / 2.0f;
+ float halfHeight = (float)_numRows / 2.0f;
+
+ float fovInRadians = (_panoramaOptions.fieldOfView * M_PI / 180.0f);
+ float cylinderRadius = halfHeight / tan(fovInRadians);
+
+ for (uint x = 0; x < _numColumns; x++) {
+ // Add an offset of 0.01 to overcome zero tan/atan issue (vertical line on half of screen)
+ // Alpha represents the horizontal angle between the viewer at the center of a cylinder and x
+ float alpha = atan(((float)x - halfWidth + 0.01f) / cylinderRadius);
+
+ // To get x in cylinder coordinates, we just need to calculate the arc length
+ // We also scale it by _panoramaOptions.linearScale
+ int32 xInCylinderCoords = int32(floor((cylinderRadius * _panoramaOptions.linearScale * alpha) + halfWidth));
+
+ float cosAlpha = cos(alpha);
+
+ for (uint y = 0; y < _numRows; y++) {
+ // To calculate y in cylinder coordinates, we can do similar triangles comparison,
+ // comparing the triangle from the center to the screen and from the center to the edge of the cylinder
+ int32 yInCylinderCoords = int32(floor(halfHeight + ((float)y - halfHeight) * cosAlpha));
+
+ uint32 index = y * _numColumns + x;
+
+ // Only store the (x,y) offsets instead of the absolute positions
+ _internalBuffer[index].x = xInCylinderCoords - x;
+ _internalBuffer[index].y = yInCylinderCoords - y;
+ }
+ }
+}
+
+void RenderTable::generateTiltLookupTable() {
+ float halfWidth = (float)_numColumns / 2.0f;
+ float halfHeight = (float)_numRows / 2.0f;
+
+ float fovInRadians = (_tiltOptions.fieldOfView * M_PI / 180.0f);
+ float cylinderRadius = halfWidth / tan(fovInRadians);
+
+ for (uint y = 0; y < _numRows; y++) {
+
+ // Add an offset of 0.01 to overcome zero tan/atan issue (horizontal line on half of screen)
+ // Alpha represents the vertical angle between the viewer at the center of a cylinder and y
+ float alpha = atan(((float)y - halfHeight + 0.01f) / cylinderRadius);
+
+ // To get y in cylinder coordinates, we just need to calculate the arc length
+ // We also scale it by _tiltOptions.linearScale
+ int32 yInCylinderCoords = int32(floor((cylinderRadius * _tiltOptions.linearScale * alpha) + halfHeight));
+
+ float cosAlpha = cos(alpha);
+ uint32 columnIndex = y * _numColumns;
+
+ for (uint x = 0; x < _numColumns; x++) {
+ // To calculate x in cylinder coordinates, we can do similar triangles comparison,
+ // comparing the triangle from the center to the screen and from the center to the edge of the cylinder
+ int32 xInCylinderCoords = int32(floor(halfWidth + ((float)x - halfWidth) * cosAlpha));
+
+ uint32 index = columnIndex + x;
+
+ // Only store the (x,y) offsets instead of the absolute positions
+ _internalBuffer[index].x = xInCylinderCoords - x;
+ _internalBuffer[index].y = yInCylinderCoords - y;
+ }
+ }
+}
+
+void RenderTable::setPanoramaFoV(float fov) {
+ assert(fov > 0.0f);
+
+ _panoramaOptions.fieldOfView = fov;
+}
+
+void RenderTable::setPanoramaScale(float scale) {
+ assert(scale > 0.0f);
+
+ _panoramaOptions.linearScale = scale;
+}
+
+void RenderTable::setPanoramaReverse(bool reverse) {
+ _panoramaOptions.reverse = reverse;
+}
+
+void RenderTable::setTiltFoV(float fov) {
+ assert(fov > 0.0f);
+
+ _tiltOptions.fieldOfView = fov;
+}
+
+void RenderTable::setTiltScale(float scale) {
+ assert(scale > 0.0f);
+
+ _tiltOptions.linearScale = scale;
+}
+
+void RenderTable::setTiltReverse(bool reverse) {
+ _tiltOptions.reverse = reverse;
+}
+
+} // End of namespace ZVision
diff --git a/engines/zvision/render_table.h b/engines/zvision/render_table.h
new file mode 100644
index 0000000000..7c8e7d6a2d
--- /dev/null
+++ b/engines/zvision/render_table.h
@@ -0,0 +1,87 @@
+/* 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_RENDER_TABLE_H
+#define ZVISION_RENDER_TABLE_H
+
+#include "common/types.h"
+#include "common/rect.h"
+
+#include "zvision/vector2.h"
+
+namespace ZVision {
+
+class RenderTable {
+public:
+ RenderTable(uint numRows, uint numColumns);
+ ~RenderTable();
+
+public:
+ enum RenderState {
+ PANORAMA,
+ TILT,
+ FLAT
+ };
+
+private:
+ uint _numColumns, _numRows;
+ Vector2 *_internalBuffer;
+ RenderState _renderState;
+
+ struct {
+ float fieldOfView;
+ float linearScale;
+ bool reverse;
+ } _panoramaOptions;
+
+ // TODO: See if tilt and panorama need to have separate options
+ struct {
+ float fieldOfView;
+ float linearScale;
+ bool reverse;
+ } _tiltOptions;
+
+public:
+ 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 generateRenderTable();
+
+ void setPanoramaFoV(float fov);
+ void setPanoramaScale(float scale);
+ void setPanoramaReverse(bool reverse);
+
+ void setTiltFoV(float fov);
+ void setTiltScale(float scale);
+ void setTiltReverse(bool reverse);
+
+private:
+ void generatePanoramaLookupTable();
+ void generateTiltLookupTable();
+};
+
+} // End of namespace ZVision
+
+#endif
diff --git a/engines/zvision/rlf_animation.cpp b/engines/zvision/rlf_animation.cpp
new file mode 100644
index 0000000000..79638511ad
--- /dev/null
+++ b/engines/zvision/rlf_animation.cpp
@@ -0,0 +1,336 @@
+/* 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/str.h"
+#include "common/file.h"
+#include "common/textconsole.h"
+#include "common/debug.h"
+#include "common/endian.h"
+
+#include "zvision/rlf_animation.h"
+
+
+namespace ZVision {
+
+const Graphics::PixelFormat RlfAnimation::_pixelFormat555 = Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0);
+const Graphics::PixelFormat RlfAnimation::_pixelFormat565 = Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0);
+
+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, _pixelFormat565);
+ _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 (Common::List<uint>::const_iterator iter = _completeFrames.begin(); iter != _completeFrames.end(); iter++) {
+ int newDistance = (int)frameNumber - (int)(*iter);
+ if (newDistance > 0 && (closestFrame == -1 || newDistance < distance)) {
+ closestFrame = (*iter);
+ 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) {
+ // TODO: Make this warning silent or in a high debug level. It happens for almost all frames.
+ //warning("Frame decoding overflow\n\tsourceOffset=%u\tsourceSize=%u\n\tdestOffset=%u\tdestSize=%u", sourceOffset, sourceSize, destOffset, destSize);
+ return;
+ }
+
+ byte r, g, b;
+ _pixelFormat555.colorToRGB(READ_LE_UINT16(source + sourceOffset), r, g, b);
+ uint16 destColor = _pixelFormat565.RGBToColor(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) {
+ // TODO: Make this warning silent or in a high debug level. It happens for almost all frames.
+ //warning("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) {
+ // TODO: Make this warning silent or in a high debug level. It happens for almost all frames.
+ //warning("Frame decoding overflow\n\tsourceOffset=%u\tsourceSize=%u\n\tdestOffset=%u\tdestSize=%u", sourceOffset, sourceSize, destOffset, destSize);
+ return;
+ }
+
+ byte r, g, b;
+ _pixelFormat555.colorToRGB(READ_LE_UINT16(source + sourceOffset), r, g, b);
+ uint16 destColor = _pixelFormat565.RGBToColor(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;
+ _pixelFormat555.colorToRGB(READ_LE_UINT16(source + sourceOffset), r, g, b);
+ uint16 sampleColor = _pixelFormat565.RGBToColor(r, g, b);
+ sourceOffset += 2;
+
+ numberOfSamples += 2;
+ while (numberOfSamples > 0) {
+ if (destOffset + 1 >= destSize) {
+ // TODO: Make this warning silent or in a high debug level. It happens for almost all frames.
+ //warning("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/rlf_animation.h b/engines/zvision/rlf_animation.h
new file mode 100644
index 0000000000..133bf3913e
--- /dev/null
+++ b/engines/zvision/rlf_animation.h
@@ -0,0 +1,100 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef ZVISION_RLF_ANIMATION_H
+#define ZVISION_RLF_ANIMATION_H
+
+#include "common/types.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;
+ };
+
+ const static Graphics::PixelFormat _pixelFormat555;
+ const static Graphics::PixelFormat _pixelFormat565;
+
+private:
+ Common::File _file;
+ bool _stream;
+ uint _lastFrameRead;
+
+ uint _frameCount;
+ uint _width;
+ uint _height;
+ uint32 _frameTime; // In milliseconds
+ Frame *_frames;
+ Common::List<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; }
+
+ void seekToFrame(int frameNumber);
+
+ const Graphics::Surface *getFrameData(uint frameNumber);
+ const Graphics::Surface *getNextFrame();
+
+ bool endOfAnimation() { return _currentFrame == (int)_frameCount - 1; }
+
+private:
+ bool readHeader();
+ Frame readNextFrame();
+
+ void applyFrameToCurrent(uint frameNumber);
+ void applyFrameToCurrent(const RlfAnimation::Frame &frame);
+
+ void decodeMaskedRunLengthEncoding(int8 *source, int8 *dest, uint32 sourceSize, uint32 destSize) const;
+ void decodeSimpleRunLengthEncoding(int8 *source, int8 *dest, uint32 sourceSize, uint32 destSize) const;
+};
+
+} // End of namespace ZVision
+
+#endif
diff --git a/engines/zvision/save_manager.cpp b/engines/zvision/save_manager.cpp
new file mode 100644
index 0000000000..4f8bfeb836
--- /dev/null
+++ b/engines/zvision/save_manager.cpp
@@ -0,0 +1,177 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/scummsys.h"
+
+#include "common/system.h"
+
+#include "graphics/surface.h"
+#include "graphics/thumbnail.h"
+
+#include "zvision/save_manager.h"
+#include "zvision/zvision.h"
+#include "zvision/script_manager.h"
+#include "zvision/render_manager.h"
+
+
+namespace ZVision {
+
+const uint32 SaveManager::SAVEGAME_ID = MKTAG('Z', 'E', 'N', 'G');
+
+void SaveManager::saveGame(uint slot, const Common::String &saveName) {
+ Common::SaveFileManager *saveFileManager = g_system->getSavefileManager();
+ Common::OutSaveFile *file = saveFileManager->openForSaving(_engine->generateSaveFileName(slot));
+
+ // Write out the savegame header
+ file->writeUint32BE(SAVEGAME_ID);
+
+ // 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->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();
+}
+
+void SaveManager::autoSave() {
+ Common::OutSaveFile *file = g_system->getSavefileManager()->openForSaving(_engine->generateAutoSaveFileName());
+
+ // Write out the savegame header
+ file->writeUint32BE(SAVEGAME_ID);
+
+ file->writeString("auto");
+ file->writeByte(0);
+
+ writeSaveGameData(file);
+
+ file->finalize();
+}
+
+void SaveManager::writeSaveGameData(Common::OutSaveFile *file) {
+ // Create a thumbnail and save it
+ Graphics::saveThumbnail(*file, *_engine->getRenderManager()->getBackBuffer());
+
+ // 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) {
+ 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();
+ // Load the room
+ scriptManager->changeLocation(world, room, node, view, offset);
+
+ // Update the state table values
+ scriptManager->deserializeStateTable(saveFile);
+
+ // 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 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/save_manager.h b/engines/zvision/save_manager.h
new file mode 100644
index 0000000000..219a8e7715
--- /dev/null
+++ b/engines/zvision/save_manager.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_SAVE_MANAGER_H
+#define ZVISION_SAVE_MANAGER_H
+
+#include "common/types.h"
+
+#include "common/savefile.h"
+
+namespace Common {
+class String;
+}
+
+namespace ZVision {
+
+class ZVision;
+
+struct SaveGameHeader {
+ Common::String saveName;
+ Graphics::Surface *thumbnail;
+ int saveYear, saveMonth, saveDay;
+ int saveHour, saveMinutes;
+};
+
+class SaveManager {
+public:
+ SaveManager(ZVision *engine) : _engine(engine) {}
+
+private:
+ ZVision *_engine;
+ static const uint32 SAVEGAME_ID;
+
+public:
+ void autoSave();
+ void saveGame(uint slot, const Common::String &saveName);
+ Common::Error loadGame(uint slot);
+
+private:
+ void writeSaveGameData(Common::OutSaveFile *file);
+ bool readSaveGameHeader(Common::InSaveFile *in, SaveGameHeader &header);
+};
+
+} // End of namespace ZVision
+
+#endif
diff --git a/engines/zvision/scr_file_handling.cpp b/engines/zvision/scr_file_handling.cpp
new file mode 100644
index 0000000000..fa89f756e4
--- /dev/null
+++ b/engines/zvision/scr_file_handling.cpp
@@ -0,0 +1,300 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/scummsys.h"
+
+#include "zvision/script_manager.h"
+#include "zvision/utility.h"
+#include "zvision/puzzle.h"
+#include "zvision/actions.h"
+#include "zvision/push_toggle_control.h"
+#include "zvision/lever_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) {
+ Common::File file;
+ if (!file.open(fileName)) {
+ warning("Script file not found: %s", fileName.c_str());
+ return;
+ }
+
+ while(!file.eos()) {
+ Common::String line = file.readLine();
+ if (file.err()) {
+ warning("Error parsing scr file: %s", fileName.c_str());
+ return;
+ }
+
+ trimCommentsAndWhiteSpace(&line);
+ if (line.empty())
+ continue;
+
+ if (line.matchString("puzzle:*", true)) {
+ Puzzle *puzzle = new Puzzle();
+ sscanf(line.c_str(),"puzzle:%u",&(puzzle->key));
+
+ parsePuzzle(puzzle, file);
+ if (isGlobal) {
+ _globalPuzzles.push_back(puzzle);
+ } else {
+ _activePuzzles.push_back(puzzle);
+ }
+ } else if (line.matchString("control:*", true)) {
+ parseControl(line, file);
+ }
+ }
+}
+
+void ScriptManager::parsePuzzle(Puzzle *puzzle, Common::SeekableReadStream &stream) {
+ Common::String line = stream.readLine();
+ trimCommentsAndWhiteSpace(&line);
+
+ while (!stream.eos() && !line.contains('}')) {
+ if (line.matchString("criteria {", true)) {
+ parseCriteria(stream, puzzle->criteriaList);
+ } else if (line.matchString("results {", true)) {
+ parseResults(stream, puzzle->resultActions);
+ } else if (line.matchString("flags {", true)) {
+ puzzle->flags = parseFlags(stream);
+ }
+
+ line = stream.readLine();
+ trimCommentsAndWhiteSpace(&line);
+ }
+}
+
+bool ScriptManager::parseCriteria(Common::SeekableReadStream &stream, Common::List<Common::List<Puzzle::CriteriaEntry> > &criteriaList) const {
+ // Loop until we find the closing brace
+ Common::String line = stream.readLine();
+ trimCommentsAndWhiteSpace(&line);
+
+ // Criteria can be empty
+ if (line.contains('}')) {
+ return false;
+ }
+
+ // Create a new List to hold the CriteriaEntries
+ criteriaList.push_back(Common::List<Puzzle::CriteriaEntry>());
+
+ while (!stream.eos() && !line.contains('}')) {
+ Puzzle::CriteriaEntry entry;
+
+ // Split the string into tokens using ' ' as a delimiter
+ Common::StringTokenizer tokenizer(line);
+ Common::String token;
+
+ // Parse the id out of the first token
+ token = tokenizer.nextToken();
+ sscanf(token.c_str(), "[%u]", &(entry.key));
+
+ // Parse the operator out of the second token
+ token = tokenizer.nextToken();
+ if (token.c_str()[0] == '=')
+ entry.criteriaOperator = Puzzle::EQUAL_TO;
+ else if (token.c_str()[0] == '!')
+ entry.criteriaOperator = Puzzle::NOT_EQUAL_TO;
+ else if (token.c_str()[0] == '>')
+ entry.criteriaOperator = Puzzle::GREATER_THAN;
+ else if (token.c_str()[0] == '<')
+ entry.criteriaOperator = Puzzle::LESS_THAN;
+
+ // 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;
+ } else {
+ sscanf(token.c_str(), "%u", &(entry.argument));
+ entry.argumentIsAKey = false;
+ }
+
+ criteriaList.back().push_back(entry);
+
+ line = stream.readLine();
+ trimCommentsAndWhiteSpace(&line);
+ }
+
+ return true;
+}
+
+void ScriptManager::parseResults(Common::SeekableReadStream &stream, Common::List<ResultAction *> &actionList) const {
+ // Loop until we find the closing brace
+ Common::String line = stream.readLine();
+ trimCommentsAndWhiteSpace(&line);
+
+ // 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);
+
+ 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());
+ }
+
+ line = stream.readLine();
+ trimCommentsAndWhiteSpace(&line);
+ }
+
+ return;
+}
+
+uint ScriptManager::parseFlags(Common::SeekableReadStream &stream) const {
+ uint flags = 0;
+
+ // Loop until we find the closing brace
+ Common::String line = stream.readLine();
+ trimCommentsAndWhiteSpace(&line);
+
+ while (!stream.eos() && !line.contains('}')) {
+ if (line.matchString("ONCE_PER_INST", true)) {
+ flags |= Puzzle::ONCE_PER_INST;
+ } else if (line.matchString("DO_ME_NOW", true)) {
+ flags |= Puzzle::DO_ME_NOW;
+ } else if (line.matchString("DISABLED", true)) {
+ flags |= Puzzle::DISABLED;
+ }
+
+ line = stream.readLine();
+ trimCommentsAndWhiteSpace(&line);
+ }
+
+ return flags;
+}
+
+void ScriptManager::parseControl(Common::String &line, Common::SeekableReadStream &stream) {
+ uint32 key;
+ char controlTypeBuffer[20];
+
+ sscanf(line.c_str(), "control:%u %s {", &key, controlTypeBuffer);
+
+ Common::String controlType(controlTypeBuffer);
+
+ if (controlType.equalsIgnoreCase("push_toggle")) {
+ _activeControls.push_back(new PushToggleControl(_engine, key, stream));
+ return;
+ } else if (controlType.equalsIgnoreCase("flat")) {
+ Control::parseFlatControl(_engine);
+ return;
+ } else if (controlType.equalsIgnoreCase("pana")) {
+ Control::parsePanoramaControl(_engine, stream);
+ return;
+ } else if (controlType.equalsIgnoreCase("tilt")) {
+ Control::parseTiltControl(_engine, stream);
+ return;
+ } else if (controlType.equalsIgnoreCase("lever")) {
+ _activeControls.push_back(new LeverControl(_engine, key, stream));
+ return;
+ }
+}
+
+} // End of namespace ZVision
diff --git a/engines/zvision/script_manager.cpp b/engines/zvision/script_manager.cpp
new file mode 100644
index 0000000000..4b4e0f74fe
--- /dev/null
+++ b/engines/zvision/script_manager.cpp
@@ -0,0 +1,440 @@
+/* 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/algorithm.h"
+#include "common/hashmap.h"
+#include "common/debug.h"
+#include "common/stream.h"
+
+#include "zvision/zvision.h"
+#include "zvision/script_manager.h"
+#include "zvision/render_manager.h"
+#include "zvision/cursor_manager.h"
+#include "zvision/save_manager.h"
+#include "zvision/actions.h"
+#include "zvision/utility.h"
+
+namespace ZVision {
+
+ScriptManager::ScriptManager(ZVision *engine)
+ : _engine(engine),
+ _currentlyFocusedControl(0) {
+}
+
+ScriptManager::~ScriptManager() {
+ for (Common::List<Puzzle *>::iterator iter = _activePuzzles.begin(); iter != _activePuzzles.end(); iter++) {
+ delete (*iter);
+ }
+ for (Common::List<Puzzle *>::iterator iter = _globalPuzzles.begin(); iter != _globalPuzzles.end(); iter++) {
+ delete (*iter);
+ }
+ for (Common::List<Control *>::iterator iter = _activeControls.begin(); iter != _activeControls.end(); iter++) {
+ delete (*iter);
+ }
+}
+
+void ScriptManager::initialize() {
+ parseScrFile("universe.scr", true);
+ changeLocation('g', 'a', 'r', 'y', 0);
+}
+
+void ScriptManager::update(uint deltaTimeMillis) {
+ updateNodes(deltaTimeMillis);
+ checkPuzzleCriteria();
+}
+
+void ScriptManager::createReferenceTable() {
+ // Iterate through each local Puzzle
+ for (Common::List<Puzzle *>::iterator activePuzzleIter = _activePuzzles.begin(); activePuzzleIter != _activePuzzles.end(); activePuzzleIter++) {
+ Puzzle *puzzlePtr = (*activePuzzleIter);
+
+ // 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);
+
+ // If the argument is a key, add a reference to it as well
+ if (entryIter->argumentIsAKey) {
+ _referenceTable[entryIter->argument].push_back(puzzlePtr);
+ }
+ }
+ }
+ }
+
+ // Iterate through each global Puzzle
+ for (Common::List<Puzzle *>::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 the argument is a key, add a reference to it as well
+ if (entryIter->argumentIsAKey) {
+ _referenceTable[entryIter->argument].push_back(puzzlePtr);
+ }
+ }
+ }
+ }
+
+ // Remove duplicate entries
+ for (Common::HashMap<uint32, Common::Array<Puzzle *> >::iterator referenceTableIter = _referenceTable.begin(); referenceTableIter != _referenceTable.end(); referenceTableIter++) {
+ removeDuplicateEntries(referenceTableIter->_value);
+ }
+}
+
+void ScriptManager::updateNodes(uint deltaTimeMillis) {
+ // If process() returns true, it means the node can be deleted
+ for (Common::List<Control *>::iterator iter = _activeControls.begin(); iter != _activeControls.end();) {
+ if ((*iter)->process(deltaTimeMillis)) {
+ delete (*iter);
+ // Remove the node
+ iter = _activeControls.erase(iter);
+ } else {
+ iter++;
+ }
+ }
+}
+
+void ScriptManager::checkPuzzleCriteria() {
+ while (!_puzzlesToCheck.empty()) {
+ Puzzle *puzzle = _puzzlesToCheck.pop();
+
+ // Check if the puzzle is already finished
+ // Also check that the puzzle isn't disabled
+ if (getStateValue(puzzle->key) == 1 &&
+ (puzzle->flags & Puzzle::DISABLED) == 0) {
+ continue;
+ }
+
+ // Check each Criteria
+
+ 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
+ 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;
+ }
+ }
+
+ // If any of the Criteria are *fully* met, then execute the results
+ if (criteriaMet) {
+ break;
+ }
+ }
+
+ // criteriaList can be empty. Aka, the puzzle should be executed immediately
+ if (puzzle->criteriaList.empty() || criteriaMet) {
+ debug("Puzzle %u criteria passed. Executing its ResultActions", puzzle->key);
+
+ // Set the puzzle as completed
+ setStateValue(puzzle->key, 1);
+
+ 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;
+ }
+ }
+
+ if (!shouldContinue) {
+ break;
+ }
+ }
+ }
+}
+
+void ScriptManager::cleanStateTable() {
+ for (Common::HashMap<uint32, uint32>::iterator iter = _globalState.begin(); iter != _globalState.end(); iter++) {
+ // If the value is equal to zero, we can purge it since getStateValue()
+ // will return zero if _globalState doesn't contain a key
+ if ((*iter)._value == 0) {
+ // Remove the node
+ _globalState.erase(iter);
+ }
+ }
+}
+
+uint ScriptManager::getStateValue(uint32 key) {
+ if (_globalState.contains(key))
+ return _globalState[key];
+ else
+ return 0;
+}
+
+void ScriptManager::setStateValue(uint32 key, uint value) {
+ _globalState[key] = value;
+
+ if (_referenceTable.contains(key)) {
+ for (Common::Array<Puzzle *>::iterator iter = _referenceTable[key].begin(); iter != _referenceTable[key].end(); iter++) {
+ _puzzlesToCheck.push((*iter));
+ }
+ }
+}
+
+void ScriptManager::addToStateValue(uint32 key, uint valueToAdd) {
+ _globalState[key] += valueToAdd;
+}
+
+void ScriptManager::addControl(Control *control) {
+ _activeControls.push_back(control);
+}
+
+Control *ScriptManager::getControl(uint32 key) {
+ for (Common::List<Control *>::iterator iter = _activeControls.begin(); iter != _activeControls.end(); iter++) {
+ if ((*iter)->getKey() == key) {
+ return (*iter);
+ }
+ }
+
+ return nullptr;
+}
+
+void ScriptManager::enableControl(uint32 key) {
+ for (Common::List<Control *>::iterator iter = _activeControls.begin(); iter != _activeControls.end(); iter++) {
+ if ((*iter)->getKey() == key) {
+ (*iter)->enable();
+ break;
+ }
+ }
+}
+
+void ScriptManager::disableControl(uint32 key) {
+ for (Common::List<Control *>::iterator iter = _activeControls.begin(); iter != _activeControls.end(); iter++) {
+ if ((*iter)->getKey() == key) {
+ (*iter)->disable();
+ break;
+ }
+ }
+}
+
+void ScriptManager::focusControl(uint32 key) {
+ for (Common::List<Control *>::iterator iter = _activeControls.begin(); iter != _activeControls.end(); iter++) {
+ uint32 controlKey = (*iter)->getKey();
+
+ if (controlKey == key) {
+ (*iter)->focus();
+ } else if (controlKey == _currentlyFocusedControl) {
+ (*iter)->unfocus();
+ }
+ }
+
+ _currentlyFocusedControl = key;
+}
+
+void ScriptManager::onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
+ for (Common::List<Control *>::iterator iter = _activeControls.begin(); iter != _activeControls.end(); iter++) {
+ (*iter)->onMouseDown(screenSpacePos, backgroundImageSpacePos);
+ }
+}
+
+void ScriptManager::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
+ for (Common::List<Control *>::iterator iter = _activeControls.begin(); iter != _activeControls.end(); iter++) {
+ (*iter)->onMouseUp(screenSpacePos, backgroundImageSpacePos);
+ }
+}
+
+bool ScriptManager::onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
+ bool cursorWasChanged = false;
+ for (Common::List<Control *>::iterator iter = _activeControls.begin(); iter != _activeControls.end(); iter++) {
+ cursorWasChanged = cursorWasChanged || (*iter)->onMouseMove(screenSpacePos, backgroundImageSpacePos);
+ }
+
+ return cursorWasChanged;
+}
+
+void ScriptManager::onKeyDown(Common::KeyState keyState) {
+ for (Common::List<Control *>::iterator iter = _activeControls.begin(); iter != _activeControls.end(); iter++) {
+ (*iter)->onKeyDown(keyState);
+ }
+}
+
+void ScriptManager::onKeyUp(Common::KeyState keyState) {
+ for (Common::List<Control *>::iterator iter = _activeControls.begin(); iter != _activeControls.end(); iter++) {
+ (*iter)->onKeyUp(keyState);
+ }
+}
+
+void ScriptManager::changeLocation(char world, char room, char node, char view, uint32 offset) {
+ assert(world != 0);
+ debug("Changing location to: %c %c %c %c %u", world, room, node, view, offset);
+
+ // Auto save
+ _engine->getSaveManager()->autoSave();
+
+ // Clear all the containers
+ _referenceTable.clear();
+ _puzzlesToCheck.clear();
+ for (Common::List<Puzzle *>::iterator iter = _activePuzzles.begin(); iter != _activePuzzles.end(); iter++) {
+ delete (*iter);
+ }
+ _activePuzzles.clear();
+ for (Common::List<Control *>::iterator iter = _activeControls.begin(); iter != _activeControls.end(); iter++) {
+ delete (*iter);
+ }
+ _activeControls.clear();
+
+ // Revert to the idle cursor
+ _engine->getCursorManager()->revertToIdle();
+
+ // Reset the background velocity
+ _engine->getRenderManager()->setBackgroundVelocity(0);
+
+ // Remove any alphaEntries
+ _engine->getRenderManager()->clearAlphaEntries();
+
+ // Clean the global state table
+ cleanStateTable();
+
+ // Parse into puzzles and controls
+ Common::String fileName = Common::String::format("%c%c%c%c.scr", world, room, node, view);
+ parseScrFile(fileName);
+
+ // Change the background position
+ _engine->getRenderManager()->setBackgroundPosition(offset);
+
+ // Enable all the controls
+ for (Common::List<Control *>::iterator iter = _activeControls.begin(); iter != _activeControls.end(); iter++) {
+ (*iter)->enable();
+ }
+
+ // Add all the local puzzles to the queue to be checked
+ for (Common::List<Puzzle *>::iterator iter = _activePuzzles.begin(); iter != _activePuzzles.end(); iter++) {
+ // Reset any Puzzles that have the flag ONCE_PER_INST
+ if (((*iter)->flags & Puzzle::ONCE_PER_INST) == Puzzle::ONCE_PER_INST) {
+ setStateValue((*iter)->key, 0);
+ }
+
+ _puzzlesToCheck.push((*iter));
+ }
+
+ // Add all the global puzzles to the queue to be checked
+ for (Common::List<Puzzle *>::iterator iter = _globalPuzzles.begin(); iter != _globalPuzzles.end(); iter++) {
+ // Reset any Puzzles that have the flag ONCE_PER_INST
+ if (((*iter)->flags & Puzzle::ONCE_PER_INST) == Puzzle::ONCE_PER_INST) {
+ setStateValue((*iter)->key, 0);
+ }
+
+ _puzzlesToCheck.push((*iter));
+ }
+
+ // Create the puzzle reference table
+ createReferenceTable();
+
+ // Update _currentLocation
+ _currentLocation.world = world;
+ _currentLocation.room = room;
+ _currentLocation.node = node;
+ _currentLocation.view = view;
+ _currentLocation.offset = offset;
+}
+
+void ScriptManager::serializeStateTable(Common::WriteStream *stream) {
+ // Write the number of state value entries
+ stream->writeUint32LE(_globalState.size());
+
+ for (Common::HashMap<uint32, uint32>::iterator iter = _globalState.begin(); iter != _globalState.end(); iter++) {
+ // Write out the key/value pair
+ stream->writeUint32LE((*iter)._key);
+ stream->writeUint32LE((*iter)._value);
+ }
+}
+
+void ScriptManager::deserializeStateTable(Common::SeekableReadStream *stream) {
+ // Read the number of key/value pairs
+ uint32 numberOfPairs = stream->readUint32LE();
+
+ for (uint32 i = 0; i < numberOfPairs; i++) {
+ uint32 key = stream->readUint32LE();
+ uint32 value = stream->readUint32LE();
+ setStateValue(key, value);
+ }
+}
+
+void ScriptManager::serializeControls(Common::WriteStream *stream) {
+ // Count how many controls need to save their data
+ // Because WriteStream isn't seekable
+ uint32 numberOfControlsNeedingSerialization = 0;
+ for (Common::List<Control *>::iterator iter = _activeControls.begin(); iter != _activeControls.end(); iter++) {
+ if ((*iter)->needsSerialization()) {
+ numberOfControlsNeedingSerialization++;
+ }
+ }
+ stream->writeUint32LE(numberOfControlsNeedingSerialization);
+
+ for (Common::List<Control *>::iterator iter = _activeControls.begin(); iter != _activeControls.end(); iter++) {
+ (*iter)->serialize(stream);
+ }
+}
+
+void ScriptManager::deserializeControls(Common::SeekableReadStream *stream) {
+ uint32 numberOfControls = stream->readUint32LE();
+
+ for (uint32 i = 0; i < numberOfControls; i++) {
+ uint32 key = stream->readUint32LE();
+ for (Common::List<Control *>::iterator iter = _activeControls.begin(); iter != _activeControls.end(); iter++) {
+ if ((*iter)->getKey() == key) {
+ (*iter)->deserialize(stream);
+ break;
+ }
+ }
+ }
+}
+
+Location ScriptManager::getCurrentLocation() const {
+ Location location = _currentLocation;
+ location.offset = _engine->getRenderManager()->getCurrentBackgroundOffset();
+
+ return location;
+}
+
+} // End of namespace ZVision
diff --git a/engines/zvision/script_manager.h b/engines/zvision/script_manager.h
new file mode 100644
index 0000000000..349f259198
--- /dev/null
+++ b/engines/zvision/script_manager.h
@@ -0,0 +1,205 @@
+/* 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_SCRIPT_MANAGER_H
+#define ZVISION_SCRIPT_MANAGER_H
+
+#include "common/hashmap.h"
+#include "common/queue.h"
+
+#include "zvision/puzzle.h"
+#include "zvision/control.h"
+
+namespace Common {
+class String;
+class SeekableReadStream;
+}
+
+namespace ZVision {
+
+class ZVision;
+
+struct Location {
+ Location() : world('g'), room('a'), node('r'), view('y'), offset(0) {}
+
+ char world;
+ char room;
+ char node;
+ char view;
+ uint32 offset;
+};
+
+class ScriptManager {
+public:
+ ScriptManager(ZVision *engine);
+ ~ScriptManager();
+
+private:
+ ZVision *_engine;
+ /**
+ * 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.
+ */
+ Common::HashMap<uint32, uint> _globalState;
+ /** References _globalState keys to Puzzles */
+ Common::HashMap<uint32, Common::Array<Puzzle *> > _referenceTable;
+ /** Holds the Puzzles that should be checked this frame */
+ Common::Queue<Puzzle *> _puzzlesToCheck;
+ /** Holds the currently active puzzles */
+ Common::List<Puzzle *> _activePuzzles;
+ /** Holds the global puzzles */
+ Common::List<Puzzle *>_globalPuzzles;
+ /** Holds the currently active controls */
+ Common::List<Control *> _activeControls;
+
+ Location _currentLocation;
+
+ uint32 _currentlyFocusedControl;
+
+public:
+ void initialize();
+ void update(uint deltaTimeMillis);
+
+ uint getStateValue(uint32 key);
+ void setStateValue(uint32 key, uint value);
+ void addToStateValue(uint32 key, uint valueToAdd);
+
+ void addControl(Control *control);
+ Control *getControl(uint32 key);
+
+ void enableControl(uint32 key);
+ void disableControl(uint32 key);
+
+ void focusControl(uint32 key);
+
+ /**
+ * Called when LeftMouse is pushed.
+ *
+ * @param screenSpacePos The position of the mouse in screen space
+ * @param backgroundImageSpacePos The position of the mouse in background image space
+ */
+ void onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos);
+ /**
+ * Called when LeftMouse is lifted.
+ *
+ * @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);
+ /**
+ * Called on every MouseMove.
+ *
+ * @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);
+ /**
+ * Called when a key is pressed.
+ *
+ * @param keycode The key that was pressed
+ */
+ void onKeyDown(Common::KeyState keyState);
+ /**
+ * Called when a key is released.
+ *
+ * @param keycode The key that was pressed
+ */
+ void onKeyUp(Common::KeyState keyState);
+
+ void changeLocation(char world, char room, char node, char view, uint32 offset);
+
+ void serializeStateTable(Common::WriteStream *stream);
+ void deserializeStateTable(Common::SeekableReadStream *stream);
+ void serializeControls(Common::WriteStream *stream);
+ void deserializeControls(Common::SeekableReadStream *stream);
+
+ Location getCurrentLocation() const;
+
+private:
+ void createReferenceTable();
+ void updateNodes(uint deltaTimeMillis);
+ void checkPuzzleCriteria();
+ void cleanStateTable();
+
+// TODO: Make this private. It was only made public so Console::cmdParseAllScrFiles() could use it
+public:
+ /**
+ * 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);
+
+private:
+ /**
+ * Parses the stream into a Puzzle object
+ * Helper method for parseScrFile.
+ *
+ * @param puzzle The object to store what is parsed
+ * @param stream Scr file stream
+ */
+ void parsePuzzle(Puzzle *puzzle, Common::SeekableReadStream &stream);
+
+ /**
+ * Parses the stream into a Criteria object
+ * Helper method for parsePuzzle.
+ *
+ * @param criteria Pointer to the Criteria object to fill
+ * @param stream Scr file stream
+ * @return Whether any criteria were read
+ */
+ bool parseCriteria(Common::SeekableReadStream &stream, Common::List<Common::List<Puzzle::CriteriaEntry> > &criteriaList) const;
+
+ /**
+ * Parses the stream into a ResultAction objects
+ * Helper method for parsePuzzle.
+ *
+ * @param stream Scr file stream
+ * @param actionList The list where the results will be added
+ * @return Created Results object
+ */
+ void parseResults(Common::SeekableReadStream &stream, Common::List<ResultAction *> &actionList) const;
+
+ /**
+ * Helper method for parsePuzzle. Parses the stream into a bitwise or of the StateFlags enum
+ *
+ * @param stream Scr file stream
+ * @return Bitwise OR of all the flags set within the puzzle
+ */
+ uint parseFlags(Common::SeekableReadStream &stream) const;
+
+ /**
+ * Helper method for parseScrFile. Parses the stream into a Control object
+ *
+ * @param line The line initially read
+ * @param stream Scr file stream
+ */
+ void parseControl(Common::String &line, Common::SeekableReadStream &stream);
+};
+
+
+} // End of namespace ZVision
+
+#endif
diff --git a/engines/zvision/scripts.cpp b/engines/zvision/scripts.cpp
new file mode 100644
index 0000000000..b6deb1b510
--- /dev/null
+++ b/engines/zvision/scripts.cpp
@@ -0,0 +1,31 @@
+/* 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"
+
+namespace ZVision {
+
+
+
+} // End of namespace ZVision
diff --git a/engines/zvision/single_value_container.cpp b/engines/zvision/single_value_container.cpp
new file mode 100644
index 0000000000..0fd758a85e
--- /dev/null
+++ b/engines/zvision/single_value_container.cpp
@@ -0,0 +1,347 @@
+/* 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 "common/str.h"
+
+#include "zvision/single_value_container.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/single_value_container.h b/engines/zvision/single_value_container.h
new file mode 100644
index 0000000000..45b5a89e95
--- /dev/null
+++ b/engines/zvision/single_value_container.h
@@ -0,0 +1,183 @@
+/* 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/string_manager.cpp b/engines/zvision/string_manager.cpp
new file mode 100644
index 0000000000..4c4fc7b168
--- /dev/null
+++ b/engines/zvision/string_manager.cpp
@@ -0,0 +1,257 @@
+/* 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 "zvision/string_manager.h"
+#include "zvision/truetype_font.h"
+
+
+namespace ZVision {
+
+const Graphics::PixelFormat StringManager::_pixelFormat565 = Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0);
+
+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 == ZorkNemesis) {
+ // TODO: Check this hardcoded filename against all versions of Nemesis
+ parseStrFile("nemesis.str");
+ } else if (gameId == ZorkGrandInquisitor) {
+ // 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++;
+ }
+}
+
+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 = _inGameText->fragments.back();
+
+ 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 = _pixelFormat565.ARGBToColor(0, red, green, blue);
+ _inGameText[lineNumber].fragments.push_back(fragment);
+
+ _lastStyle = fragment.style;
+}
+
+Common::String StringManager::readWideLine(Common::SeekableReadStream &stream) {
+ // NOTE: Hardcoded size. All strings I've checked are less than 290 chars
+ char asciiString[300];
+
+ // Don't spam the user with warnings about UTF-16 support.
+ // Just do one warning per String
+ bool charOverflowWarning = false;
+
+ uint16 value = stream.readUint16LE();
+ uint i = 0;
+ 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 = 255;
+ }
+ char charValue = (char)value;
+
+ asciiString[i] = charValue;
+ i++;
+
+ value = stream.readUint16LE();
+ }
+
+ if (charOverflowWarning) {
+ warning("UTF-16 is not supported. Characters greater than 255 are clamped to 255");
+ }
+
+ return Common::String(asciiString, i);
+}
+
+StringManager::TextStyle StringManager::getTextStyle(uint stringNumber) {
+ return _inGameText[stringNumber].fragments.front().style;
+}
+
+} // End of namespace ZVision
diff --git a/engines/zvision/string_manager.h b/engines/zvision/string_manager.h
new file mode 100644
index 0000000000..e3539a732c
--- /dev/null
+++ b/engines/zvision/string_manager.h
@@ -0,0 +1,86 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ *
+ */
+
+#ifndef ZVISION_STRING_MANAGER_H
+#define ZVISION_STRING_MANAGER_H
+
+#include "common/types.h"
+
+#include "zvision/detection.h"
+#include "zvision/truetype_font.h"
+
+
+namespace Graphics {
+class FontManager;
+}
+
+namespace ZVision {
+
+class ZVision;
+
+class StringManager {
+public:
+ StringManager(ZVision *engine);
+ ~StringManager();
+
+public:
+ struct TextStyle {
+ TruetypeFont *font;
+ uint16 color; // In RBG 565
+ Graphics::TextAlign align;
+ };
+
+ struct TextFragment {
+ TextStyle style;
+ Common::String text;
+ };
+
+private:
+ struct InGameText {
+ Common::List<TextFragment> fragments;
+ };
+
+private:
+ ZVision *_engine;
+ // NOTE: We hardcode this number because we know ZNem uses 42 strings and ZGI uses 56
+ InGameText _inGameText[56];
+ Common::HashMap<Common::String, TruetypeFont *> _fonts;
+
+ static const Graphics::PixelFormat _pixelFormat565;
+
+ TextStyle _lastStyle;
+
+public:
+ void initialize(ZVisionGameId gameId);
+ StringManager::TextStyle getTextStyle(uint stringNumber);
+
+private:
+ void parseStrFile(const Common::String &fileName);
+ void parseTag(const Common::String &tagString, uint lineNumber);
+
+ static Common::String readWideLine(Common::SeekableReadStream &stream);
+};
+
+} // End of namespace ZVision
+
+#endif
diff --git a/engines/zvision/subtitles.h b/engines/zvision/subtitles.h
new file mode 100644
index 0000000000..e88108327f
--- /dev/null
+++ b/engines/zvision/subtitles.h
@@ -0,0 +1,29 @@
+/* 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_SUBTITLES_H
+#define ZVISION_SUBTITLES_H
+
+// TODO: Implement Subtitles
+
+#endif \ No newline at end of file
diff --git a/engines/zvision/timer_node.cpp b/engines/zvision/timer_node.cpp
new file mode 100644
index 0000000000..81468dbb0b
--- /dev/null
+++ b/engines/zvision/timer_node.cpp
@@ -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.
+ *
+ */
+
+#include "common/scummsys.h"
+
+#include "common/stream.h"
+
+#include "zvision/timer_node.h"
+#include "zvision/zvision.h"
+#include "zvision/script_manager.h"
+
+namespace ZVision {
+
+TimerNode::TimerNode(ZVision *engine, uint32 key, uint timeInSeconds)
+ : Control(engine, key), _timeLeft(timeInSeconds * 1000) {
+}
+
+bool TimerNode::process(uint32 deltaTimeInMillis) {
+ _timeLeft -= deltaTimeInMillis;
+
+ if (_timeLeft <= 0) {
+ _engine->getScriptManager()->setStateValue(_key, 0);
+ return true;
+ }
+
+ return false;
+}
+
+void TimerNode::serialize(Common::WriteStream *stream) {
+ stream->writeUint32LE(_key);
+ stream->writeUint32LE(_timeLeft);
+}
+
+void TimerNode::deserialize(Common::SeekableReadStream *stream) {
+ _timeLeft = stream->readUint32LE();
+}
+
+} // End of namespace ZVision
diff --git a/engines/zvision/timer_node.h b/engines/zvision/timer_node.h
new file mode 100644
index 0000000000..21c344bb15
--- /dev/null
+++ b/engines/zvision/timer_node.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_TIMER_NODE_H
+#define ZVISION_TIMER_NODE_H
+
+#include "common/types.h"
+
+#include "zvision/control.h"
+
+namespace ZVision {
+
+class ZVision;
+
+class TimerNode : public Control {
+public:
+ TimerNode(ZVision *engine, uint32 key, uint timeInSeconds);
+
+ /**
+ * 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 serialize(Common::WriteStream *stream);
+ void deserialize(Common::SeekableReadStream *stream);
+ inline bool needsSerialization() { return true; }
+
+private:
+ uint32 _timeLeft;
+};
+
+} // End of namespace ZVision
+
+#endif
diff --git a/engines/zvision/truetype_font.cpp b/engines/zvision/truetype_font.cpp
new file mode 100644
index 0000000000..8fd331df48
--- /dev/null
+++ b/engines/zvision/truetype_font.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 "common/scummsys.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"
+
+#include "zvision/truetype_font.h"
+#include "zvision/zvision.h"
+#include "zvision/render_manager.h"
+
+
+namespace ZVision {
+
+TruetypeFont::TruetypeFont(ZVision *engine, int32 fontHeight)
+ : _engine(engine),
+ _fontHeight(fontHeight),
+ _font(0),
+ _lineHeight(0),
+ _maxCharWidth(0),
+ _maxCharHeight(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, int destX, int destY, 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 && 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 += (int)_lineHeight;
+ }
+
+ return surface;
+}
+
+} // End of namespace ZVision
diff --git a/engines/zvision/truetype_font.h b/engines/zvision/truetype_font.h
new file mode 100644
index 0000000000..ae7732cba4
--- /dev/null
+++ b/engines/zvision/truetype_font.h
@@ -0,0 +1,64 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+// This file is based on engines/wintermute/base/fonts/base_font_truetype.h/.cpp
+
+#ifndef ZVISION_TRUETYPE_FONT_H
+#define ZVISION_TRUETYPE_FONT_H
+
+#include "common/types.h"
+
+#include "graphics/font.h"
+#include "graphics/pixelformat.h"
+
+namespace Graphics {
+struct Surface;
+}
+
+namespace ZVision {
+
+class ZVision;
+
+class TruetypeFont {
+public:
+ TruetypeFont(ZVision *engine, int32 fontHeight);
+ ~TruetypeFont();
+
+private:
+ ZVision *_engine;
+ Graphics::Font *_font;
+ float _lineHeight;
+
+ size_t _maxCharWidth;
+ size_t _maxCharHeight;
+
+public:
+ int32 _fontHeight;
+
+public:
+ bool loadFile(const Common::String &filename);
+ Graphics::Surface *drawTextToSurface(const Common::String &text, int destX, int destY, uint16 textColor, int maxWidth, int maxHeight, Graphics::TextAlign align, bool wrap);
+};
+
+} // End of namespace ZVision
+
+#endif
diff --git a/engines/zvision/utility.cpp b/engines/zvision/utility.cpp
new file mode 100644
index 0000000000..fb4fe22ff8
--- /dev/null
+++ b/engines/zvision/utility.cpp
@@ -0,0 +1,235 @@
+/* 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/tokenizer.h"
+#include "common/file.h"
+
+#include "zvision/utility.h"
+#include "zvision/zvision.h"
+#include "zvision/zork_raw.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.h b/engines/zvision/utility.h
new file mode 100644
index 0000000000..3348a43fe3
--- /dev/null
+++ b/engines/zvision/utility.h
@@ -0,0 +1,100 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ *
+ */
+
+#ifndef 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.
+ *
+ * @param container
+ * @return
+ */
+template<class T>
+void removeDuplicateEntries(Common::Array<T> &container) {
+ Common::sort(container.begin(), container.end());
+
+ for (uint i = 0; i + 1 < container.size(); i++) {
+ while (i + 1 < container.size() && container[i] == container[i + 1]) {
+ container.remove_at(i + 1);
+ }
+ }
+}
+
+/**
+ * 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/vector2.h b/engines/zvision/vector2.h
new file mode 100644
index 0000000000..575915c87d
--- /dev/null
+++ b/engines/zvision/vector2.h
@@ -0,0 +1,79 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef ZVISION_VECTOR2_H
+#define ZVISION_VECTOR2_H
+
+#include "common/scummsys.h"
+
+namespace ZVision {
+
+/**
+ * Simple class for handling both 2D position and size.
+ */
+class Vector2 {
+public:
+ int16 x; ///< The horizontal part of the point
+ int16 y; ///< The vertical part of the point
+
+public:
+ Vector2() : x(0), y(0) {}
+ Vector2(int16 x1, int16 y1) : x(x1), y(y1) {}
+
+public:
+ bool operator==(const Vector2 &p) const { return x == p.x && y == p.y; }
+ bool operator!=(const Vector2 &p) const { return x != p.x || y != p.y; }
+ Vector2 operator+(const Vector2 &delta) const { return Vector2(x + delta.x, y + delta.y); }
+ Vector2 operator-(const Vector2 &delta) const { return Vector2(x - delta.x, y - delta.y); }
+
+ void operator+=(const Vector2 &delta) {
+ x += delta.x;
+ y += delta.y;
+ }
+
+ void operator-=(const Vector2 &delta) {
+ x -= delta.x;
+ y -= delta.y;
+ }
+
+ /**
+ * Return the square of the distance between this point and the point p.
+ *
+ * @param p the other point
+ * @return the distance between this and p
+ */
+ uint sqrDist(const Vector2 &p) const {
+ int diffx = ABS(p.x - x);
+ if (diffx >= 0x1000)
+ return 0xFFFFFF;
+
+ int diffy = ABS(p.y - y);
+ if (diffy >= 0x1000)
+ return 0xFFFFFF;
+
+ return uint(diffx * diffx + diffy * diffy);
+ }
+};
+
+} // End of namespace ZVision
+
+#endif
diff --git a/engines/zvision/video.cpp b/engines/zvision/video.cpp
new file mode 100644
index 0000000000..5230653ca3
--- /dev/null
+++ b/engines/zvision/video.cpp
@@ -0,0 +1,167 @@
+/* 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/system.h"
+#include "video/video_decoder.h"
+#include "engines/util.h"
+#include "graphics/surface.h"
+
+#include "zvision/clock.h"
+#include "zvision/render_manager.h"
+#include "zvision/zvision.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;
+ }
+ }
+}
+
+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;
+ // 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;
+
+ uint16 finalWidth = origWidth * scale;
+ uint16 finalHeight = origHeight * scale;
+
+ byte *scaledVideoFrameBuffer;
+ if (scale != 1) {
+ scaledVideoFrameBuffer = new byte[finalWidth * finalHeight * bytesPerPixel];
+ }
+
+ uint16 x = ((WINDOW_WIDTH - finalWidth) / 2) + destRect.left;
+ uint16 y = ((WINDOW_HEIGHT - finalHeight) / 2) + destRect.top;
+
+ _clock.stop();
+ videoDecoder.start();
+
+ // Only continue while the video is still playing
+ while (!shouldQuit() && !videoDecoder.endOfVideo() && videoDecoder.isPlaying()) {
+ // Check for engine quit and video stop key presses
+ while (!videoDecoder.endOfVideo() && videoDecoder.isPlaying() && _eventMan->pollEvent(_event)) {
+ switch (_event.type) {
+ case Common::EVENT_KEYDOWN:
+ switch (_event.kbd.keycode) {
+ case Common::KEYCODE_q:
+ if (_event.kbd.hasFlags(Common::KBD_CTRL))
+ quitGame();
+ break;
+ case Common::KEYCODE_SPACE:
+ if (skippable) {
+ videoDecoder.stop();
+ }
+ break;
+ default:
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ if (videoDecoder.needsUpdate()) {
+ const Graphics::Surface *frame = videoDecoder.decodeNextFrame();
+
+ 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);
+ }
+ }
+ }
+
+ // Always update the screen so the mouse continues to render
+ _system->updateScreen();
+
+ _system->delayMillis(videoDecoder.getTimeToNextFrame());
+ }
+
+ _clock.start();
+
+ if (scale != 1) {
+ delete[] scaledVideoFrameBuffer;
+ }
+}
+
+} // End of namespace ZVision
diff --git a/engines/zvision/zfs_archive.cpp b/engines/zvision/zfs_archive.cpp
new file mode 100644
index 0000000000..3577ce3d14
--- /dev/null
+++ b/engines/zvision/zfs_archive.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 "common/hashmap.h"
+#include "common/memstream.h"
+#include "common/debug.h"
+#include "common/file.h"
+
+#include "zvision/zfs_archive.h"
+
+namespace ZVision {
+
+ZfsArchive::ZfsArchive(const Common::String &fileName) : _fileName(fileName) {
+ Common::File zfsFile;
+
+ if (!zfsFile.open(_fileName)) {
+ warning("ZFSArchive::ZFSArchive(): Could not find the archive file");
+ return;
+ }
+
+ readHeaders(&zfsFile);
+
+ debug(0, "ArjArchive::ArjArchive(%s): Located %d files", _fileName.c_str(), _entryHeaders.size());
+}
+
+ZfsArchive::ZfsArchive(const Common::String &fileName, Common::SeekableReadStream *stream) : _fileName(fileName) {
+ readHeaders(stream);
+
+ debug(0, "ArjArchive::ArjArchive(%s): Located %d files", _fileName.c_str(), _entryHeaders.size());
+}
+
+ZfsArchive::~ZfsArchive() {
+ debug(0, "ZfsArchive Destructor Called");
+ ZfsEntryHeaderMap::iterator it = _entryHeaders.begin();
+ for ( ; it != _entryHeaders.end(); ++it) {
+ delete it->_value;
+ }
+}
+
+void ZfsArchive::readHeaders(Common::SeekableReadStream *stream) {
+ // Don't do a straight struct cast since we can't guarantee endianness
+ _header.magic = stream->readUint32LE();
+ _header.unknown1 = stream->readUint32LE();
+ _header.maxNameLength = stream->readUint32LE();
+ _header.filesPerBlock = stream->readUint32LE();
+ _header.fileCount = stream->readUint32LE();
+ _header.xorKey[0] = stream->readByte();
+ _header.xorKey[1] = stream->readByte();
+ _header.xorKey[2] = stream->readByte();
+ _header.xorKey[3] = stream->readByte();
+ _header.fileSectionOffset = stream->readUint32LE();
+
+ uint32 nextOffset;
+
+ do {
+ // Read the offset to the next block
+ nextOffset = stream->readUint32LE();
+
+ // 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();
+ entryHeader.size = stream->readUint32LE();
+ entryHeader.time = stream->readUint32LE();
+ entryHeader.unknown = stream->readUint32LE();
+
+ if (entryHeader.size != 0)
+ _entryHeaders[entryHeader.name] = new ZfsEntryHeader(entryHeader);
+ }
+
+ // Seek to the next block of headers
+ stream->seek(nextOffset);
+ } while (nextOffset != 0);
+}
+
+Common::String ZfsArchive::readEntryName(Common::SeekableReadStream *stream) const {
+ // Entry Names are at most 16 bytes and are null padded
+ char buffer[16];
+ stream->read(buffer, 16);
+
+ return Common::String(buffer);
+}
+
+bool ZfsArchive::hasFile(const Common::String &name) const {
+ return _entryHeaders.contains(name);
+}
+
+int ZfsArchive::listMembers(Common::ArchiveMemberList &list) const {
+ int matches = 0;
+
+ for (ZfsEntryHeaderMap::const_iterator it = _entryHeaders.begin(); it != _entryHeaders.end(); ++it) {
+ list.push_back(Common::ArchiveMemberList::value_type(new Common::GenericArchiveMember(it->_value->name, this)));
+ matches++;
+ }
+
+ return matches;
+}
+
+const Common::ArchiveMemberPtr ZfsArchive::getMember(const Common::String &name) const {
+ if (!_entryHeaders.contains(name))
+ return Common::ArchiveMemberPtr();
+
+ return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(name, this));
+}
+
+Common::SeekableReadStream *ZfsArchive::createReadStreamForMember(const Common::String &name) const {
+ if (!_entryHeaders.contains(name)) {
+ return 0;
+ }
+
+ ZfsEntryHeader *entryHeader = _entryHeaders[name];
+
+ Common::File zfsArchive;
+ zfsArchive.open(_fileName);
+ 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);
+ zfsArchive.read(buffer, entryHeader->size);
+ // Decrypt the data in place
+ if (_header.xorKey != 0)
+ unXor(buffer, entryHeader->size, _header.xorKey);
+
+ return new Common::MemoryReadStream(buffer, entryHeader->size, DisposeAfterUse::YES);
+}
+
+void ZfsArchive::unXor(byte *buffer, uint32 length, const byte *xorKey) const {
+ for (uint32 i = 0; i < length; i++)
+ buffer[i] ^= xorKey[i % 4];
+}
+
+} // End of namespace ZVision
+
+
diff --git a/engines/zvision/zfs_archive.h b/engines/zvision/zfs_archive.h
new file mode 100644
index 0000000000..3bf492578c
--- /dev/null
+++ b/engines/zvision/zfs_archive.h
@@ -0,0 +1,125 @@
+/* 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_ZFS_ARCHIVE_H
+#define ZVISION_ZFS_ARCHIVE_H
+
+#include "common/archive.h"
+#include "common/hashmap.h"
+
+
+namespace Common {
+class String;
+}
+
+namespace ZVision {
+
+struct ZfsHeader {
+ uint32 magic;
+ uint32 unknown1;
+ uint32 maxNameLength;
+ uint32 filesPerBlock;
+ uint32 fileCount;
+ byte xorKey[4];
+ uint32 fileSectionOffset;
+};
+
+struct ZfsEntryHeader {
+ Common::String name;
+ uint32 offset;
+ uint32 id;
+ uint32 size;
+ uint32 time;
+ uint32 unknown;
+};
+
+typedef Common::HashMap<Common::String, ZfsEntryHeader*, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> ZfsEntryHeaderMap;
+
+class ZfsArchive : public Common::Archive {
+public:
+ ZfsArchive(const Common::String &fileName);
+ ZfsArchive(const Common::String &fileName, Common::SeekableReadStream *stream);
+ ~ZfsArchive();
+
+ /**
+ * Check if a member with the given name is present in the Archive.
+ * Patterns are not allowed, as this is meant to be a quick File::exists()
+ * replacement.
+ */
+ bool hasFile(const Common::String &fileName) const;
+
+ /**
+ * Add all members of the Archive to list.
+ * Must only append to list, and not remove elements from it.
+ *
+ * @return The number of names added to list
+ */
+ int listMembers(Common::ArchiveMemberList &list) const;
+
+ /**
+ * Returns a ArchiveMember representation of the given file.
+ */
+ const Common::ArchiveMemberPtr getMember(const Common::String &name) const;
+
+ /**
+ * Create a stream bound to a member with the specified name in the
+ * archive. If no member with this name exists, 0 is returned.
+ *
+ * @return The newly created input stream
+ */
+ Common::SeekableReadStream *createReadStreamForMember(const Common::String &name) const;
+
+private:
+ const Common::String _fileName;
+ ZfsHeader _header;
+ ZfsEntryHeaderMap _entryHeaders;
+
+ /**
+ * Parses the zfs file into file entry headers that can be used later
+ * to get the entry data.
+ *
+ * @param stream The contents of the zfs file
+ */
+ void readHeaders(Common::SeekableReadStream *stream);
+
+ /**
+ * Entry names are contained within a 16 byte block. This reads the block
+ * and converts it the name to a Common::String
+ *
+ * @param stream The zfs file stream
+ * @return The entry file name
+ */
+ Common::String readEntryName(Common::SeekableReadStream *stream) const;
+
+ /**
+ * ZFS file entries can be encrypted using XOR encoding. This method
+ * decodes the buffer in place using the supplied xorKey.
+ *
+ * @param buffer The data to decode
+ * @param length Length of buffer
+ */
+ void unXor(byte *buffer, uint32 length, const byte *xorKey) const;
+};
+
+} // End of namespace ZVision
+
+#endif
diff --git a/engines/zvision/zork_avi_decoder.cpp b/engines/zvision/zork_avi_decoder.cpp
new file mode 100644
index 0000000000..d9b9511175
--- /dev/null
+++ b/engines/zvision/zork_avi_decoder.cpp
@@ -0,0 +1,50 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ *
+ */
+
+#include "common/scummsys.h"
+
+#include "common/stream.h"
+#include "audio/audiostream.h"
+
+#include "zvision/zork_avi_decoder.h"
+#include "zvision/zork_raw.h"
+
+namespace ZVision {
+
+Video::AVIDecoder::AVIAudioTrack *ZorkAVIDecoder::createAudioTrack(Video::AVIDecoder::AVIStreamHeader sHeader, Video::AVIDecoder::PCMWaveFormat wvInfo) {
+ ZorkAVIDecoder::ZorkAVIAudioTrack *audioTrack = new ZorkAVIDecoder::ZorkAVIAudioTrack(sHeader, wvInfo, _soundType);
+ return (Video::AVIDecoder::AVIAudioTrack *)audioTrack;
+}
+
+void ZorkAVIDecoder::ZorkAVIAudioTrack::queueSound(Common::SeekableReadStream *stream) {
+ if (_audStream) {
+ if (_wvInfo.tag == kWaveFormatZorkPCM) {
+ assert(_wvInfo.size == 8);
+ _audStream->queueAudioStream(makeRawZorkStream(stream, _wvInfo.samplesPerSec, _audStream->isStereo(), DisposeAfterUse::YES), DisposeAfterUse::YES);
+ }
+ } else {
+ delete stream;
+ }
+}
+
+} // End of namespace ZVision
diff --git a/engines/zvision/zork_avi_decoder.h b/engines/zvision/zork_avi_decoder.h
new file mode 100644
index 0000000000..1ce2508828
--- /dev/null
+++ b/engines/zvision/zork_avi_decoder.h
@@ -0,0 +1,59 @@
+/* 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 ZORK_AVI_DECODER_H
+#define ZORK_AVI_DECODER_H
+
+#include "video/avi_decoder.h"
+
+namespace ZVision {
+
+class ZorkAVIDecoder : public Video::AVIDecoder {
+public:
+ ZorkAVIDecoder(Audio::Mixer::SoundType soundType = Audio::Mixer::kPlainSoundType) :
+ Video::AVIDecoder(soundType) {}
+
+ 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() {}
+
+ void queueSound(Common::SeekableReadStream *stream);
+ };
+
+ Video::AVIDecoder::AVIAudioTrack *createAudioTrack(Video::AVIDecoder::AVIStreamHeader sHeader, Video::AVIDecoder::PCMWaveFormat wvInfo);
+
+private:
+ // Audio Codecs
+ enum {
+ kWaveFormatZorkPCM = 17 // special Zork PCM audio format (clashes with MS IMA ADPCM)
+ };
+};
+
+} // End of namespace ZVision
+
+#endif
diff --git a/engines/zvision/zork_raw.cpp b/engines/zvision/zork_raw.cpp
new file mode 100644
index 0000000000..92f659203b
--- /dev/null
+++ b/engines/zvision/zork_raw.cpp
@@ -0,0 +1,191 @@
+/* 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/str.h"
+#include "common/stream.h"
+#include "common/memstream.h"
+#include "common/bufferedstream.h"
+#include "common/util.h"
+#include "audio/audiostream.h"
+#include "audio/decoders/raw.h"
+
+#include "zvision/zork_raw.h"
+#include "zvision/zvision.h"
+#include "zvision/detection.h"
+#include "zvision/utility.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};
+
+RawZorkStream::RawZorkStream(uint32 rate, bool stereo, DisposeAfterUse::Flag disposeStream, Common::SeekableReadStream *stream)
+ : _rate(rate),
+ _stereo(0),
+ _stream(stream, disposeStream),
+ _endOfData(false) {
+ if (stereo)
+ _stereo = 1;
+
+ _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);
+}
+
+int RawZorkStream::readBuffer(int16 *buffer, const int numSamples) {
+ int bytesRead = 0;
+
+ // 0: Left, 1: Right
+ uint channel = 0;
+
+ while (bytesRead < numSamples) {
+ byte encodedSample = _stream->readByte();
+ if (_stream->eos()) {
+ _endOfData = true;
+ return bytesRead;
+ }
+ bytesRead++;
+
+ int16 index = _lastSample[channel].index;
+ uint32 lookUpSample = _amplitudeLookupTable[index];
+
+ int32 sample = 0;
+
+ if (encodedSample & 0x40)
+ sample += lookUpSample;
+ if (encodedSample & 0x20)
+ sample += lookUpSample >> 1;
+ if (encodedSample & 0x10)
+ sample += lookUpSample >> 2;
+ if (encodedSample & 8)
+ sample += lookUpSample >> 3;
+ if (encodedSample & 4)
+ sample += lookUpSample >> 4;
+ if (encodedSample & 2)
+ sample += lookUpSample >> 5;
+ if (encodedSample & 1)
+ sample += lookUpSample >> 6;
+ if (encodedSample & 0x80)
+ sample = -sample;
+
+ sample += _lastSample[channel].sample;
+ sample = CLIP(sample, -32768, 32767);
+
+ buffer[bytesRead - 1] = (int16)sample;
+
+ index += _stepAdjustmentTable[(encodedSample >> 4) & 7];
+ index = CLIP<int16>(index, 0, 88);
+
+ _lastSample[channel].sample = sample;
+ _lastSample[channel].index = index;
+
+ // Increment and wrap the channel
+ channel = (channel + 1) & _stereo;
+ }
+
+ return bytesRead;
+}
+
+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;
+
+ return true;
+}
+
+Audio::RewindableAudioStream *makeRawZorkStream(Common::SeekableReadStream *stream,
+ 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 fileName = getFileName(filePath);
+ fileName.toLowercase();
+
+ SoundParams soundParams;
+
+ if (engine->getGameId() == ZorkNemesis) {
+ for (int i = 0; i < 6; i++) {
+ if (zNemSoundParamLookupTable[i].identifier == (fileName[6]))
+ soundParams = zNemSoundParamLookupTable[i];
+ }
+ }
+ else if (engine->getGameId() == ZorkGrandInquisitor) {
+ for (int i = 0; i < 6; i++) {
+ if (zgiSoundParamLookupTable[i].identifier == (fileName[7]))
+ soundParams = zgiSoundParamLookupTable[i];
+ }
+ }
+
+ if (soundParams.packed) {
+ return makeRawZorkStream(wrapBufferedSeekableReadStream(file, 2048, DisposeAfterUse::YES), soundParams.rate, soundParams.stereo, DisposeAfterUse::YES);
+ } else {
+ byte flags = 0;
+ if (soundParams.stereo)
+ flags |= Audio::FLAG_STEREO;
+
+ return Audio::makeRawStream(file, soundParams.rate, flags, DisposeAfterUse::YES);
+ }
+}
+
+} // End of namespace ZVision
diff --git a/engines/zvision/zork_raw.h b/engines/zvision/zork_raw.h
new file mode 100644
index 0000000000..f19ffc1661
--- /dev/null
+++ b/engines/zvision/zork_raw.h
@@ -0,0 +1,133 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef ZVISION_ZORK_RAW_H
+#define ZVISION_ZORK_RAW_H
+
+#include "common/types.h"
+#include "audio/audiostream.h"
+
+namespace Common {
+class SeekableReadStream;
+}
+
+namespace ZVision {
+
+class ZVision;
+
+struct SoundParams {
+ char identifier;
+ uint32 rate;
+ bool stereo;
+ bool packed;
+};
+
+const SoundParams 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 zgiSoundParamLookupTable[5] = {{'a',0x5622, false, false},
+ {'k',0x2B11, true, true},
+ {'p',0x5622, false, true},
+ {'q',0x5622, true, true},
+ {'u',0xAC44, true, true}
+};
+
+/**
+ * This is a stream, which allows for playing raw ADPCM data from a stream.
+ */
+class RawZorkStream : public Audio::RewindableAudioStream {
+public:
+ RawZorkStream(uint32 rate, bool stereo, DisposeAfterUse::Flag disposeStream, Common::SeekableReadStream *stream);
+
+ ~RawZorkStream() {
+ }
+
+public:
+
+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;
+
+ /**
+ * Holds the frequency and index from the last sample
+ * 0 holds the left channel, 1 holds the right channel
+ */
+ struct {
+ int32 sample;
+ int16 index;
+ } _lastSample[2];
+
+ static const int16 _stepAdjustmentTable[8];
+ static const int32 _amplitudeLookupTable[89];
+
+public:
+ int readBuffer(int16 *buffer, const int numSamples);
+
+ bool isStereo() const { return true; }
+ 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 buffer.
+ *
+ * @param buffer Buffer to play from.
+ * @param size Size of the buffer in bytes.
+ * @param rate Rate of the sound data.
+ * @param disposeAfterUse Whether to free the buffer after use (with free!).
+ * @return The new SeekableAudioStream (or 0 on failure).
+ */
+Audio::RewindableAudioStream *makeRawZorkStream(const byte *buffer, uint32 size,
+ int rate,
+ bool stereo,
+ DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES);
+
+/**
+ * Creates an audio stream, which plays from the given stream.
+ *
+ * @param stream Stream object to play from.
+ * @param rate Rate of the sound data.
+ * @param disposeAfterUse Whether to delete the stream after use.
+ * @return The new SeekableAudioStream (or 0 on failure).
+ */
+Audio::RewindableAudioStream *makeRawZorkStream(Common::SeekableReadStream *stream,
+ int rate,
+ bool stereo,
+ DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES);
+
+Audio::RewindableAudioStream *makeRawZorkStream(const Common::String &filePath, ZVision *engine);
+
+} // End of namespace ZVision
+
+#endif
diff --git a/engines/zvision/zvision.cpp b/engines/zvision/zvision.cpp
new file mode 100644
index 0000000000..b071908698
--- /dev/null
+++ b/engines/zvision/zvision.cpp
@@ -0,0 +1,185 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/scummsys.h"
+
+#include "common/config-manager.h"
+#include "common/debug.h"
+#include "common/debug-channels.h"
+#include "common/textconsole.h"
+#include "common/error.h"
+#include "common/system.h"
+#include "common/file.h"
+
+#include "engines/util.h"
+
+#include "audio/mixer.h"
+
+#include "zvision/zvision.h"
+#include "zvision/console.h"
+#include "zvision/script_manager.h"
+#include "zvision/render_manager.h"
+#include "zvision/cursor_manager.h"
+#include "zvision/save_manager.h"
+#include "zvision/string_manager.h"
+#include "zvision/zfs_archive.h"
+#include "zvision/detection.h"
+
+#include "zvision/utility.h"
+
+namespace ZVision {
+
+ZVision::ZVision(OSystem *syst, const ZVisionGameDescription *gameDesc)
+ : Engine(syst),
+ _gameDescription(gameDesc),
+ _workingWindow((WINDOW_WIDTH - WORKING_WINDOW_WIDTH) / 2, (WINDOW_HEIGHT - WORKING_WINDOW_HEIGHT) / 2, ((WINDOW_WIDTH - WORKING_WINDOW_WIDTH) / 2) + WORKING_WINDOW_WIDTH, ((WINDOW_HEIGHT - WORKING_WINDOW_HEIGHT) / 2) + WORKING_WINDOW_HEIGHT),
+ _pixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0), /*RGB 565*/
+ _desiredFrameTime(33), /* ~30 fps */
+ _clock(_system) {
+
+ // Put your engine in a sane state, but do nothing big yet;
+ // in particular, do not load data from files; rather, if you
+ // need to do such things, do them from run().
+
+ // Do not initialize graphics here
+
+ // Here is the right place to set up the engine specific debug channels
+ //DebugMan.addDebugChannel(kZVisionDebugExample, "example", "this is just an example for a engine specific debug channel");
+ //DebugMan.addDebugChannel(kZVisionDebugExample2, "example2", "also an example");
+
+ // Register random source
+ _rnd = new Common::RandomSource("zvision");
+
+ // Create managers
+ _scriptManager = new ScriptManager(this);
+ _renderManager = new RenderManager(_system, WINDOW_WIDTH, WINDOW_HEIGHT, _workingWindow, _pixelFormat);
+ _saveManager = new SaveManager(this);
+ _stringManager = new StringManager(this);
+ debug("ZVision::ZVision");
+}
+
+ZVision::~ZVision() {
+ debug("ZVision::~ZVision");
+
+ // Dispose of resources
+ delete _console;
+ delete _cursorManager;
+ delete _stringManager;
+ delete _saveManager;
+ delete _renderManager;
+ delete _scriptManager;
+ delete _rnd;
+
+ // Remove all of our debug levels
+ DebugMan.clearAllDebugChannels();
+}
+
+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);
+ }
+
+ initGraphics(WINDOW_WIDTH, WINDOW_HEIGHT, true, &_pixelFormat);
+
+ // CursorManager must be created after all the directories have been added
+ _cursorManager = new CursorManager(this, &_pixelFormat);
+ _cursorManager->initialize();
+ _scriptManager->initialize();
+ _stringManager->initialize(_gameDescription->gameId);
+
+ // Create debugger console. It requires GFX to be initialized
+ _console = new Console(this);
+}
+
+Common::Error ZVision::run() {
+ initialize();
+
+ // Main loop
+ while (!shouldQuit()) {
+ _clock.update();
+ uint32 currentTime = _clock.getLastMeasuredTime();
+ uint32 deltaTime = _clock.getDeltaTime();
+
+ processEvents();
+
+ // Call _renderManager->update() first so the background renders
+ // before anything that puzzles/controls will render
+ _renderManager->update(deltaTime);
+ _scriptManager->update(deltaTime);
+
+ // Render the backBuffer to the screen
+ _renderManager->renderBackbufferToScreen();
+
+ // Update the screen
+ _system->updateScreen();
+
+ // 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;
+ _system->delayMillis(delay);
+ }
+
+ return Common::kNoError;
+}
+
+void ZVision::pauseEngineIntern(bool pause) {
+ _mixer->pauseAll(pause);
+
+ if (pause) {
+ _clock.stop();
+ } else {
+ _clock.start();
+ }
+}
+
+Common::String ZVision::generateSaveFileName(uint slot) {
+ return Common::String::format("%s.%02u", _targetName.c_str(), slot);
+}
+
+Common::String ZVision::generateAutoSaveFileName() {
+ return Common::String::format("%s.auto", _targetName.c_str());
+}
+
+} // End of namespace ZVision
diff --git a/engines/zvision/zvision.h b/engines/zvision/zvision.h
new file mode 100644
index 0000000000..a481aba28f
--- /dev/null
+++ b/engines/zvision/zvision.h
@@ -0,0 +1,156 @@
+/* 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_H
+#define ZVISION_H
+
+#include "common/random.h"
+#include "common/events.h"
+
+#include "engines/engine.h"
+
+#include "graphics/pixelformat.h"
+
+#include "zvision/detection.h"
+#include "zvision/clock.h"
+
+#include "gui/debugger.h"
+
+
+namespace Video {
+class VideoDecoder;
+}
+
+namespace ZVision {
+
+struct ZVisionGameDescription;
+class Console;
+class ScriptManager;
+class RenderManager;
+class CursorManager;
+class StringManager;
+class SaveManager;
+class RlfAnimation;
+
+// our engine debug channels
+enum {
+ kZDebugExample = 1 << 0,
+ kZDebugExample2 = 1 << 1
+ // next new channel must be 1 << 2 (4)
+ // the current limitation is 32 debug channels (1 << 31 is the last one)
+};
+
+class ZVision : public Engine {
+public:
+ ZVision(OSystem *syst, const ZVisionGameDescription *gameDesc);
+ ~ZVision();
+
+public:
+ /**
+ * 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;
+ const Graphics::PixelFormat _pixelFormat;
+
+private:
+ enum {
+ WINDOW_WIDTH = 640,
+ WINDOW_HEIGHT = 480,
+ WORKING_WINDOW_WIDTH = 512,
+ WORKING_WINDOW_HEIGHT = 320,
+
+ ROTATION_SCREEN_EDGE_OFFSET = 60,
+ MAX_ROTATION_SPEED = 400 // Pixels per second
+ };
+
+ Console *_console;
+ const ZVisionGameDescription *_gameDescription;
+
+ const int _desiredFrameTime;
+
+ // We need random numbers
+ Common::RandomSource *_rnd;
+
+ // Managers
+ ScriptManager *_scriptManager;
+ RenderManager *_renderManager;
+ CursorManager *_cursorManager;
+ SaveManager *_saveManager;
+ StringManager *_stringManager;
+
+ // Clock
+ Clock _clock;
+
+ // To prevent allocation every time we process events
+ Common::Event _event;
+
+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; }
+
+ /**
+ * Play a video until it is finished. This is a blocking call. It will call
+ * _clock.stop() when the video starts and _clock.start() when the video finishes.
+ * It will also consume all events during video playback.
+ *
+ * @param videoDecoder The video to play
+ * @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 playAnimation(RlfAnimation *animation, uint16 x, uint16 y, DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES);
+ void playAnimation(Video::VideoDecoder *animation, uint16 x, uint16 y, DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES);
+
+ Common::String generateSaveFileName(uint slot);
+ Common::String generateAutoSaveFileName();
+
+private:
+ void initialize();
+ void initFonts();
+
+ void parseStrFile(const Common::String fileName);
+
+ /** 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);
+};
+
+} // End of namespace ZVision
+
+#endif