aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AUTHORS5
-rwxr-xr-xdevtools/credits.pl5
-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.cpp394
-rw-r--r--engines/zvision/actions.h346
-rw-r--r--engines/zvision/animation_control.cpp263
-rw-r--r--engines/zvision/animation_control.h87
-rw-r--r--engines/zvision/clock.cpp70
-rw-r--r--engines/zvision/clock.h78
-rw-r--r--engines/zvision/console.cpp218
-rw-r--r--engines/zvision/console.h55
-rw-r--r--engines/zvision/control.cpp124
-rw-r--r--engines/zvision/control.h146
-rw-r--r--engines/zvision/cursor.cpp94
-rw-r--r--engines/zvision/cursor.h66
-rw-r--r--engines/zvision/cursor_manager.cpp151
-rw-r--r--engines/zvision/cursor_manager.h114
-rw-r--r--engines/zvision/detection.cpp272
-rw-r--r--engines/zvision/detection.h44
-rw-r--r--engines/zvision/events.cpp186
-rw-r--r--engines/zvision/input_control.cpp142
-rw-r--r--engines/zvision/input_control.h60
-rw-r--r--engines/zvision/inventory_manager.h28
-rw-r--r--engines/zvision/lever_control.cpp402
-rw-r--r--engines/zvision/lever_control.h127
-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.mk43
-rw-r--r--engines/zvision/push_toggle_control.cpp98
-rw-r--r--engines/zvision/push_toggle_control.h67
-rw-r--r--engines/zvision/puzzle.h81
-rw-r--r--engines/zvision/render_manager.cpp526
-rw-r--r--engines/zvision/render_manager.h328
-rw-r--r--engines/zvision/render_table.cpp240
-rw-r--r--engines/zvision/render_table.h85
-rw-r--r--engines/zvision/rlf_animation.cpp331
-rw-r--r--engines/zvision/rlf_animation.h163
-rw-r--r--engines/zvision/save_manager.cpp206
-rw-r--r--engines/zvision/save_manager.h91
-rw-r--r--engines/zvision/scr_file_handling.cpp302
-rw-r--r--engines/zvision/script_manager.cpp446
-rw-r--r--engines/zvision/script_manager.h211
-rw-r--r--engines/zvision/single_value_container.cpp348
-rw-r--r--engines/zvision/single_value_container.h183
-rw-r--r--engines/zvision/string_manager.cpp255
-rw-r--r--engines/zvision/string_manager.h85
-rw-r--r--engines/zvision/subtitles.h29
-rw-r--r--engines/zvision/timer_node.cpp59
-rw-r--r--engines/zvision/timer_node.h54
-rw-r--r--engines/zvision/truetype_font.cpp116
-rw-r--r--engines/zvision/truetype_font.h81
-rw-r--r--engines/zvision/utility.cpp237
-rw-r--r--engines/zvision/utility.h115
-rw-r--r--engines/zvision/video.cpp171
-rw-r--r--engines/zvision/zfs_archive.cpp157
-rw-r--r--engines/zvision/zfs_archive.h126
-rw-r--r--engines/zvision/zork_avi_decoder.cpp53
-rw-r--r--engines/zvision/zork_avi_decoder.h60
-rw-r--r--engines/zvision/zork_raw.cpp209
-rw-r--r--engines/zvision/zork_raw.h120
-rw-r--r--engines/zvision/zvision.cpp183
-rw-r--r--engines/zvision/zvision.h145
-rw-r--r--graphics/surface.cpp24
-rw-r--r--graphics/surface.h24
-rw-r--r--gui/credits.h5
-rw-r--r--video/avi_decoder.cpp6
-rw-r--r--video/avi_decoder.h7
-rw-r--r--video/codecs/truemotion1.h4
71 files changed, 9757 insertions, 6 deletions
diff --git a/AUTHORS b/AUTHORS
index 35a8a9ad96..d22c69e7e8 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -244,6 +244,9 @@ ScummVM Team
Wintermute:
Einar Johan T. Somaaen
+ ZVision:
+ Adrian Astley
+
Backend Teams
-------------
Android:
@@ -614,6 +617,8 @@ Special thanks to
Jimmi Thogersen - For ScummRev, and much obscure code/documentation
Tristan - For additional work on the original MT-32 emulator
James Woodcock - Soundtrack enhancements
+ Anton Yartsev - For the original re-implementation of the ZVision
+ engine
Tony Warriner and everyone at Revolution Software Ltd. for sharing with us
the source of some of their brilliant games, allowing us to release
diff --git a/devtools/credits.pl b/devtools/credits.pl
index 7ccc282d0e..fda6f4782e 100755
--- a/devtools/credits.pl
+++ b/devtools/credits.pl
@@ -759,6 +759,10 @@ begin_credits("Credits");
begin_section("Wintermute");
add_person("Einar Johan T. Sømåen", "somaen", "");
end_section();
+
+ begin_section("ZVision");
+ add_person("Adrian Astley", "RichieSams", "");
+ end_section();
end_section();
@@ -1160,6 +1164,7 @@ begin_credits("Credits");
add_person("Jimmi Thøgersen", "", "For ScummRev, and much obscure code/documentation");
add_person("", "Tristan", "For additional work on the original MT-32 emulator");
add_person("James Woodcock", "", "Soundtrack enhancements");
+ add_person("Anton Yartsev", "Zidane", "For the original re-implementation of the ZVision engine");
end_persons();
add_paragraph(
diff --git a/engines/configure.engines b/engines/configure.engines
index 8ae2c07a33..be1a91922b 100644
--- a/engines/configure.engines
+++ b/engines/configure.engines
@@ -54,3 +54,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 "" "" "jpeg png zlib vorbis 16bit"
+add_engine zvision "ZVision" no
diff --git a/engines/engines.mk b/engines/engines.mk
index c710fb143c..1d9d8fd89b 100644
--- a/engines/engines.mk
+++ b/engines/engines.mk
@@ -256,3 +256,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 ca2fe3fa5b..38cd43ac74 100644
--- a/engines/plugins_table.h
+++ b/engines/plugins_table.h
@@ -125,3 +125,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..eae4ec2ed5
--- /dev/null
+++ b/engines/zvision/actions.cpp
@@ -0,0 +1,394 @@
+/* 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/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"
+
+#include "common/file.h"
+
+#include "audio/decoders/wave.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 = new Common::File();
+ if (file->open(_fileName)) {
+ audioStream = Audio::makeWAVStream(file, DisposeAfterUse::YES);
+ }
+ } 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;
+ if ((_flags & DIFFERENT_DIMENSIONS) == DIFFERENT_DIMENSIONS) {
+ 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..afa3e3a2ac
--- /dev/null
+++ b/engines/zvision/actions.h
@@ -0,0 +1,346 @@
+/* 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/str.h"
+
+#include "audio/mixer.h"
+
+
+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:
+ enum {
+ DIFFERENT_DIMENSIONS = 0x1 // 0x1 flags that the destRect dimensions are different from the original video dimensions
+ };
+
+ 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_control.cpp b/engines/zvision/animation_control.cpp
new file mode 100644
index 0000000000..1143380501
--- /dev/null
+++ b/engines/zvision/animation_control.cpp
@@ -0,0 +1,263 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/scummsys.h"
+
+#include "zvision/animation_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"
+
+#include "video/video_decoder.h"
+
+#include "graphics/surface.h"
+
+
+namespace ZVision {
+
+AnimationControl::AnimationControl(ZVision *engine, uint32 controlKey, const Common::String &fileName)
+ : Control(engine, controlKey),
+ _fileType(RLF),
+ _loopCount(1),
+ _currentLoop(0),
+ _accumulatedTime(0),
+ _cachedFrame(0),
+ _cachedFrameNeedsDeletion(false) {
+ if (fileName.hasSuffix(".rlf")) {
+ _fileType = RLF;
+ _animation.rlf = new RlfAnimation(fileName, false);
+ } else if (fileName.hasSuffix(".avi")) {
+ _fileType = AVI;
+ _animation.avi = new ZorkAVIDecoder();
+ _animation.avi->loadFile(fileName);
+ } else {
+ warning("Unrecognized animation file type: %s", fileName.c_str());
+ }
+
+ _cachedFrame = new Graphics::Surface();
+}
+
+AnimationControl::~AnimationControl() {
+ if (_fileType == RLF) {
+ delete _animation.rlf;
+ } else if (_fileType == AVI) {
+ delete _animation.avi;
+ }
+
+ _cachedFrame->free();
+ delete _cachedFrame;
+}
+
+bool AnimationControl::process(uint32 deltaTimeInMillis) {
+ if (!_enabled) {
+ return false;
+ }
+
+ bool finished = false;
+
+ if (_fileType == RLF) {
+ _accumulatedTime += deltaTimeInMillis;
+
+ uint32 frameTime = _animation.rlf->frameTime();
+ if (_accumulatedTime >= frameTime) {
+ while (_accumulatedTime >= frameTime) {
+ _accumulatedTime -= frameTime;
+
+ // Make sure the frame is inside the working window
+ // If it's not, then just return
+
+ RenderManager *renderManager = _engine->getRenderManager();
+ Common::Point workingWindowPoint = renderManager->imageSpaceToWorkingWindowSpace(Common::Point(_x, _y));
+ Common::Rect subRect(workingWindowPoint.x, workingWindowPoint.y, workingWindowPoint.x + _animation.rlf->width(), workingWindowPoint.y + _animation.rlf->height());
+
+ // If the clip returns false, it means the animation is outside the working window
+ if (!renderManager->clipRectToWorkingWindow(subRect)) {
+ return false;
+ }
+
+ const Graphics::Surface *frame = _animation.rlf->getNextFrame();
+
+ // Animation frames for PANORAMAs are transposed, so un-transpose them
+ RenderTable::RenderState state = renderManager->getRenderTable()->getRenderState();
+ if (state == RenderTable::PANORAMA) {
+ Graphics::Surface *tranposedFrame = RenderManager::tranposeSurface(frame);
+
+ renderManager->copyRectToWorkingWindow((uint16 *)tranposedFrame->getBasePtr(tranposedFrame->w - subRect.width(), tranposedFrame->h - subRect.height()), subRect.left, subRect.top, _animation.rlf->width(), subRect.width(), subRect.height());
+
+ // If the background can move, we need to cache the last frame so it can be rendered during background movement
+ if (state == RenderTable::PANORAMA || state == RenderTable::TILT) {
+ if (_cachedFrameNeedsDeletion) {
+ _cachedFrame->free();
+ delete _cachedFrame;
+ _cachedFrameNeedsDeletion = false;
+ }
+ _cachedFrame = tranposedFrame;
+ _cachedFrameNeedsDeletion = true;
+ } else {
+ // Cleanup
+ tranposedFrame->free();
+ delete tranposedFrame;
+ }
+ } else {
+ renderManager->copyRectToWorkingWindow((const uint16 *)frame->getBasePtr(frame->w - subRect.width(), frame->h - subRect.height()), subRect.left, subRect.top, _animation.rlf->width(), subRect.width(), subRect.height());
+
+ // If the background can move, we need to cache the last frame so it can be rendered during background movement
+ if (state == RenderTable::PANORAMA || state == RenderTable::TILT) {
+ if (_cachedFrameNeedsDeletion) {
+ _cachedFrame->free();
+ delete _cachedFrame;
+ _cachedFrameNeedsDeletion = false;
+ }
+ _cachedFrame->copyFrom(*frame);
+ }
+ }
+
+ // Check if we should continue looping
+ if (_animation.rlf->endOfAnimation()) {
+ _animation.rlf->seekToFrame(-1);
+ if (_loopCount > 0) {
+ _currentLoop++;
+ if (_currentLoop >= _loopCount) {
+ finished = true;
+ }
+ }
+ }
+ }
+ } else {
+ // If the background can move, we have to keep rendering animation frames, otherwise the animation flickers during background movement
+ RenderManager *renderManager = _engine->getRenderManager();
+ RenderTable::RenderState state = renderManager->getRenderTable()->getRenderState();
+
+ if (state == RenderTable::PANORAMA || state == RenderTable::TILT) {
+ Common::Point workingWindowPoint = renderManager->imageSpaceToWorkingWindowSpace(Common::Point(_x, _y));
+ Common::Rect subRect(workingWindowPoint.x, workingWindowPoint.y, workingWindowPoint.x + _cachedFrame->w, workingWindowPoint.y + _cachedFrame->h);
+
+ // If the clip returns false, it means the animation is outside the working window
+ if (!renderManager->clipRectToWorkingWindow(subRect)) {
+ return false;
+ }
+
+ renderManager->copyRectToWorkingWindow((uint16 *)_cachedFrame->getBasePtr(_cachedFrame->w - subRect.width(), _cachedFrame->h - subRect.height()), subRect.left, subRect.top, _cachedFrame->w, subRect.width(), subRect.height());
+ }
+ }
+ } else if (_fileType == AVI) {
+ if (!_animation.avi->isPlaying()) {
+ _animation.avi->start();
+ }
+
+ if (_animation.avi->needsUpdate()) {
+ const Graphics::Surface *frame = _animation.avi->decodeNextFrame();
+
+ if (frame) {
+ // Make sure the frame is inside the working window
+ // If it's not, then just return
+
+ RenderManager *renderManager = _engine->getRenderManager();
+ Common::Point workingWindowPoint = renderManager->imageSpaceToWorkingWindowSpace(Common::Point(_x, _y));
+ Common::Rect subRect(workingWindowPoint.x, workingWindowPoint.y, workingWindowPoint.x + frame->w, workingWindowPoint.y + frame->h);
+
+ // If the clip returns false, it means the animation is outside the working window
+ if (!renderManager->clipRectToWorkingWindow(subRect)) {
+ return false;
+ }
+
+ // Animation frames for PANORAMAs are transposed, so un-transpose them
+ RenderTable::RenderState state = renderManager->getRenderTable()->getRenderState();
+ if (state == RenderTable::PANORAMA) {
+ Graphics::Surface *tranposedFrame = RenderManager::tranposeSurface(frame);
+
+ renderManager->copyRectToWorkingWindow((uint16 *)tranposedFrame->getBasePtr(tranposedFrame->w - subRect.width(), tranposedFrame->h - subRect.height()), subRect.left, subRect.top, frame->w, subRect.width(), subRect.height());
+
+ // If the background can move, we need to cache the last frame so it can be rendered during background movement
+ if (state == RenderTable::PANORAMA || state == RenderTable::TILT) {
+ if (_cachedFrameNeedsDeletion) {
+ _cachedFrame->free();
+ delete _cachedFrame;
+ _cachedFrameNeedsDeletion = false;
+ }
+ _cachedFrame = tranposedFrame;
+ _cachedFrameNeedsDeletion = true;
+ } else {
+ // Cleanup
+ tranposedFrame->free();
+ delete tranposedFrame;
+ }
+ } else {
+ renderManager->copyRectToWorkingWindow((const uint16 *)frame->getBasePtr(frame->w - subRect.width(), frame->h - subRect.height()), subRect.left, subRect.top, frame->w, subRect.width(), subRect.height());
+
+ // If the background can move, we need to cache the last frame so it can be rendered during background movement
+ if (state == RenderTable::PANORAMA || state == RenderTable::TILT) {
+ if (_cachedFrameNeedsDeletion) {
+ _cachedFrame->free();
+ delete _cachedFrame;
+ _cachedFrameNeedsDeletion = false;
+ }
+ _cachedFrame->copyFrom(*frame);
+ }
+ }
+ } else {
+ // If the background can move, we have to keep rendering animation frames, otherwise the animation flickers during background movement
+ RenderManager *renderManager = _engine->getRenderManager();
+ RenderTable::RenderState state = renderManager->getRenderTable()->getRenderState();
+
+ if (state == RenderTable::PANORAMA || state == RenderTable::TILT) {
+ Common::Point workingWindowPoint = renderManager->imageSpaceToWorkingWindowSpace(Common::Point(_x, _y));
+ Common::Rect subRect(workingWindowPoint.x, workingWindowPoint.y, workingWindowPoint.x + _cachedFrame->w, workingWindowPoint.y + _cachedFrame->h);
+
+ // If the clip returns false, it means the animation is outside the working window
+ if (!renderManager->clipRectToWorkingWindow(subRect)) {
+ return false;
+ }
+
+ renderManager->copyRectToWorkingWindow((uint16 *)_cachedFrame->getBasePtr(_cachedFrame->w - subRect.width(), _cachedFrame->h - subRect.height()), subRect.left, subRect.top, _cachedFrame->w, subRect.width(), subRect.height());
+ }
+ }
+ }
+
+ // Check if we should continue looping
+ if (_animation.avi->endOfVideo()) {
+ _animation.avi->rewind();
+ if (_loopCount > 0) {
+ _currentLoop++;
+ if (_currentLoop >= _loopCount) {
+ _animation.avi->stop();
+ finished = true;
+ }
+ }
+ }
+ }
+
+ // If we're done, set _animation key = 2 (Why 2? I don't know. It's just the value that they used)
+ // Then disable the control. DON'T delete it. It can be re-used
+ if (finished) {
+ _engine->getScriptManager()->setStateValue(_animationKey, 2);
+ disable();
+ _currentLoop = 0;
+ }
+
+ return false;
+}
+
+} // End of namespace ZVision
diff --git a/engines/zvision/animation_control.h b/engines/zvision/animation_control.h
new file mode 100644
index 0000000000..2ac3691483
--- /dev/null
+++ b/engines/zvision/animation_control.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_ANIMATION_CONTROL_H
+#define ZVISION_ANIMATION_CONTROL_H
+
+#include "zvision/control.h"
+
+
+namespace Common {
+class String;
+}
+
+namespace Video {
+class VideoDecoder;
+}
+
+namespace Graphics {
+struct Surface;
+}
+
+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..c8ee717a33
--- /dev/null
+++ b/engines/zvision/clock.cpp
@@ -0,0 +1,70 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ *
+ */
+
+#include "common/scummsys.h"
+
+#include "zvision/clock.h"
+
+#include "common/system.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..a095d3fa6a
--- /dev/null
+++ b/engines/zvision/console.cpp
@@ -0,0 +1,218 @@
+/* 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/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"
+
+#include "common/system.h"
+#include "common/file.h"
+#include "common/bufferedstream.h"
+
+#include "gui/debugger.h"
+
+#include "audio/mixer.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..bcbdabc143
--- /dev/null
+++ b/engines/zvision/control.cpp
@@ -0,0 +1,124 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/scummsys.h"
+
+#include "zvision/control.h"
+
+#include "zvision/zvision.h"
+#include "zvision/render_manager.h"
+#include "zvision/utility.h"
+
+#include "common/stream.h"
+
+
+namespace ZVision {
+
+void Control::enable() {
+ if (!_enabled) {
+ _enabled = true;
+ return;
+ }
+
+ debug("Control %u is already enabled", _key);
+}
+
+void Control::disable() {
+ if (_enabled) {
+ _enabled = false;
+ return;
+ }
+
+ debug("Control %u is already disabled", _key);
+}
+
+void Control::parseFlatControl(ZVision *engine) {
+ engine->getRenderManager()->getRenderTable()->setRenderState(RenderTable::FLAT);
+}
+
+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..770c540a12
--- /dev/null
+++ b/engines/zvision/control.h
@@ -0,0 +1,146 @@
+/* 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/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..9023d97e0d
--- /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 "zvision/cursor.h"
+
+#include "common/str.h"
+#include "common/file.h"
+
+
+namespace ZVision {
+
+ZorkCursor::ZorkCursor()
+ : _width(0),
+ _height(0),
+ _hotspotX(0),
+ _hotspotY(0) {
+}
+
+ZorkCursor::ZorkCursor(const Common::String &fileName)
+ : _width(0),
+ _height(0),
+ _hotspotX(0),
+ _hotspotY(0) {
+ Common::File file;
+ if (!file.open(fileName))
+ return;
+
+ uint32 magic = file.readUint32BE();
+ if (magic != MKTAG('Z', 'C', 'R', '1')) {
+ warning("%s is not a Zork Cursor file", fileName.c_str());
+ return;
+ }
+
+ _hotspotX = file.readUint16LE();
+ _hotspotY = file.readUint16LE();
+ _width = file.readUint16LE();
+ _height = file.readUint16LE();
+
+ uint dataSize = _width * _height * sizeof(uint16);
+ _surface.create(_width, _height, Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0));
+ uint32 bytesRead = file.read(_surface.getPixels(), dataSize);
+ assert(bytesRead == dataSize);
+
+ // Convert to RGB 565
+ _surface.convertToInPlace(Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0));
+}
+
+ZorkCursor::ZorkCursor(const ZorkCursor &other) {
+ _width = other._width;
+ _height = other._height;
+ _hotspotX = other._hotspotX;
+ _hotspotY = other._hotspotY;
+
+ _surface.copyFrom(other._surface);
+}
+
+ZorkCursor &ZorkCursor::operator=(const ZorkCursor &other) {
+ _width = other._width;
+ _height = other._height;
+ _hotspotX = other._hotspotX;
+ _hotspotY = other._hotspotY;
+
+ _surface.free();
+ _surface.copyFrom(other._surface);
+
+ return *this;
+}
+
+ZorkCursor::~ZorkCursor() {
+ _surface.free();
+}
+
+} // End of namespace ZVision
diff --git a/engines/zvision/cursor.h b/engines/zvision/cursor.h
new file mode 100644
index 0000000000..18ac28ce8b
--- /dev/null
+++ b/engines/zvision/cursor.h
@@ -0,0 +1,66 @@
+/* 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 "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..595e7946dd
--- /dev/null
+++ b/engines/zvision/cursor_manager.cpp
@@ -0,0 +1,151 @@
+/* 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/cursor_manager.h"
+
+#include "zvision/zvision.h"
+
+#include "common/system.h"
+
+#include "graphics/pixelformat.h"
+#include "graphics/cursorman.h"
+
+
+namespace ZVision {
+
+const char *CursorManager::_cursorNames[NUM_CURSORS] = { "active", "arrow", "backward", "downarrow", "forward", "handpt", "handpu", "hdown", "hleft",
+ "hright", "hup", "idle", "leftarrow", "rightarrow", "suggest_surround", "suggest_tilt", "turnaround", "zuparrow" };
+
+const char *CursorManager::_zgiCursorFileNames[NUM_CURSORS] = { "g0gbc011.zcr", "g0gac001.zcr", "g0gac021.zcr", "g0gac031.zcr", "g0gac041.zcr", "g0gac051.zcr", "g0gac061.zcr", "g0gac071.zcr", "g0gac081.zcr",
+ "g0gac091.zcr", "g0gac101.zcr", "g0gac011.zcr", "g0gac111.zcr", "g0gac121.zcr", "g0gac131.zcr", "g0gac141.zcr", "g0gac151.zcr", "g0gac161.zcr" };
+
+const char *CursorManager::_zNemCursorFileNames[NUM_CURSORS] = { "00act", "arrow", "back", "down", "forw", "handpt", "handpu", "hdown", "hleft",
+ "hright", "hup", "00idle", "left", "right", "ssurr", "stilt", "turn", "up" };
+
+
+CursorManager::CursorManager(ZVision *engine, const Graphics::PixelFormat *pixelFormat)
+ : _engine(engine),
+ _pixelFormat(pixelFormat),
+ _cursorIsPushed(false) {
+ // WARNING: The index IDLE_CURSOR_INDEX is hardcoded. If you change the order of _cursorNames/_zgiCursorFileNames/_zNemCursorFileNames, you HAVE to change the index accordingly
+ if (_engine->getGameId() == GID_NEMESIS) {
+ Common::String name(Common::String::format("%sa.zcr", _zNemCursorFileNames[IDLE_CURSOR_INDEX]));
+ _idleCursor = ZorkCursor(name);
+ } else if (_engine->getGameId() == GID_GRANDINQUISITOR) {
+ _idleCursor = ZorkCursor(_zgiCursorFileNames[IDLE_CURSOR_INDEX]);
+ }
+}
+
+void CursorManager::initialize() {
+ revertToIdle();
+ CursorMan.showMouse(true);
+}
+
+void CursorManager::changeCursor(const Common::String &cursorName) {
+ changeCursor(cursorName, _cursorIsPushed);
+}
+
+void CursorManager::changeCursor(const Common::String &cursorName, bool pushed) {
+ if (_currentCursor.equals(cursorName) && _cursorIsPushed == pushed)
+ return;
+
+ if (_cursorIsPushed != pushed)
+ _cursorIsPushed = pushed;
+
+ if (cursorName == "idle" && !pushed) {
+ CursorMan.replaceCursor(_idleCursor.getSurface(), _idleCursor.getWidth(), _idleCursor.getHeight(), _idleCursor.getHotspotX(), _idleCursor.getHotspotY(), _idleCursor.getKeyColor(), false, _pixelFormat);
+ return;
+ }
+
+ for (int i = 0; i < NUM_CURSORS; ++i) {
+ if (_engine->getGameId() == GID_NEMESIS) {
+ if (cursorName.equals(_cursorNames[i])) {
+ _currentCursor = cursorName;
+
+ // ZNem uses a/b at the end of the file to signify not pushed/pushed respectively
+ Common::String pushedFlag = pushed ? "b" : "a";
+ Common::String name = Common::String::format("%s%s.zcr", _zNemCursorFileNames[i], pushedFlag.c_str());
+
+ changeCursor(ZorkCursor(name));
+ return;
+ }
+ } else if (_engine->getGameId() == GID_GRANDINQUISITOR) {
+ if (cursorName.equals(_cursorNames[i])) {
+ _currentCursor = cursorName;
+
+ if (!pushed) {
+ changeCursor(ZorkCursor(_zgiCursorFileNames[i]));
+ } else {
+ // ZGI flips not pushed/pushed between a/c and b/d
+ // It flips the 4th character of the name
+ char buffer[25];
+ strcpy(buffer, _zgiCursorFileNames[i]);
+ buffer[3] += 2;
+ }
+ 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..0a369aaf9e
--- /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 "zvision/cursor.h"
+
+#include "common/str.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..06e921dfa8
--- /dev/null
+++ b/engines/zvision/detection.cpp
@@ -0,0 +1,272 @@
+/* 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 "base/plugins.h"
+
+#include "zvision/zvision.h"
+#include "zvision/detection.h"
+
+#include "common/translation.h"
+#include "common/savefile.h"
+#include "common/str-array.h"
+#include "common/system.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)
+ },
+ GID_NEMESIS
+ },
+
+ {
+ // Zork Grand Inquisitor English version
+ {
+ "zgi",
+ 0,
+ AD_ENTRY1s("SCRIPTS.ZFS", "81efd40ecc3d22531e211368b779f17f", 8336944),
+ Common::EN_ANY,
+ Common::kPlatformDOS,
+ ADGF_NO_FLAGS,
+ GUIO1(GUIO_NONE)
+ },
+ GID_GRANDINQUISITOR
+ },
+
+ {
+ AD_TABLE_END_MARKER,
+ GID_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..34417601e8
--- /dev/null
+++ b/engines/zvision/detection.h
@@ -0,0 +1,44 @@
+/* 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 {
+ GID_NONE = 0,
+ GID_NEMESIS = 1,
+ GID_GRANDINQUISITOR = 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..1103dc3000
--- /dev/null
+++ b/engines/zvision/events.cpp
@@ -0,0 +1,186 @@
+/* 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 "zvision/cursor_manager.h"
+#include "zvision/render_manager.h"
+#include "zvision/script_manager.h"
+#include "zvision/rlf_animation.h"
+
+#include "common/events.h"
+#include "common/system.h"
+#include "common/rational.h"
+
+#include "engines/util.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..a445e1aae5
--- /dev/null
+++ b/engines/zvision/input_control.cpp
@@ -0,0 +1,142 @@
+/* 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/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"
+
+#include "common/str.h"
+#include "common/stream.h"
+#include "common/rect.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..aab2c991dc
--- /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 "zvision/control.h"
+#include "zvision/string_manager.h"
+
+#include "common/rect.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..79049b8fcb
--- /dev/null
+++ b/engines/zvision/lever_control.cpp
@@ -0,0 +1,402 @@
+/* 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/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"
+
+#include "common/stream.h"
+#include "common/file.h"
+#include "common/tokenizer.h"
+#include "common/system.h"
+
+#include "graphics/surface.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;
+ return false;
+ }
+
+ uint toFrame = *_returnRoutesCurrentProgress;
+ if (_returnRoutesCurrentFrame < toFrame) {
+ _returnRoutesCurrentFrame++;
+ } else if (_returnRoutesCurrentFrame > toFrame) {
+ _returnRoutesCurrentFrame--;
+ }
+
+ _engine->getScriptManager()->setStateValue(_key, _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..8ef8f06fec
--- /dev/null
+++ b/engines/zvision/lever_control.h
@@ -0,0 +1,127 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef ZVISION_LEVER_CONTROL_H
+#define ZVISION_LEVER_CONTROL_H
+
+#include "zvision/control.h"
+
+#include "common/list.h"
+#include "common/rect.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 a, uint t) : angle(a), toFrame(t) {}
+
+ 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..bbbda6f526
--- /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..f6b1eb1a65
--- /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/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..261168f133
--- /dev/null
+++ b/engines/zvision/module.mk
@@ -0,0 +1,43 @@
+MODULE := engines/zvision
+
+MODULE_OBJS := \
+ actions.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 \
+ 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
diff --git a/engines/zvision/push_toggle_control.cpp b/engines/zvision/push_toggle_control.cpp
new file mode 100644
index 0000000000..689311ba5b
--- /dev/null
+++ b/engines/zvision/push_toggle_control.cpp
@@ -0,0 +1,98 @@
+/* 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/push_toggle_control.h"
+
+#include "zvision/zvision.h"
+#include "zvision/script_manager.h"
+#include "zvision/cursor_manager.h"
+#include "zvision/utility.h"
+
+#include "common/stream.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..2a407cada9
--- /dev/null
+++ b/engines/zvision/push_toggle_control.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_PUSH_TOGGLE_CONTROL_H
+#define ZVISION_PUSH_TOGGLE_CONTROL_H
+
+#include "zvision/control.h"
+
+#include "common/rect.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..1e730365dc
--- /dev/null
+++ b/engines/zvision/puzzle.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_PUZZLE_H
+#define ZVISION_PUZZLE_H
+
+#include "zvision/actions.h"
+
+#include "common/list.h"
+#include "common/ptr.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;
+};
+
+} // 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..af8ca7fd64
--- /dev/null
+++ b/engines/zvision/render_manager.cpp
@@ -0,0 +1,526 @@
+/* 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/render_manager.h"
+
+#include "zvision/lzss_read_stream.h"
+
+#include "common/file.h"
+#include "common/system.h"
+#include "common/stream.h"
+
+#include "engines/util.h"
+
+#include "graphics/decoders/tga.h"
+
+
+namespace ZVision {
+
+RenderManager::RenderManager(OSystem *system, uint32 windowWidth, uint32 windowHeight, const Common::Rect workingWindow, const Graphics::PixelFormat pixelFormat)
+ : _system(system),
+ _workingWidth(workingWindow.width()),
+ _workingHeight(workingWindow.height()),
+ _screenCenterX(_workingWidth / 2),
+ _screenCenterY(_workingHeight / 2),
+ _workingWindow(workingWindow),
+ _pixelFormat(pixelFormat),
+ _backgroundWidth(0),
+ _backgroundHeight(0),
+ _backgroundInverseVelocity(0),
+ _backgroundOffset(0, 0),
+ _accumulatedVelocityMilliseconds(0),
+ _renderTable(_workingWidth, _workingHeight) {
+
+ _workingWindowBuffer.create(_workingWidth, _workingHeight, _pixelFormat);
+ _backBuffer.create(windowWidth, windowHeight, pixelFormat);
+}
+
+RenderManager::~RenderManager() {
+ _workingWindowBuffer.free();
+ _currentBackground.free();
+ _backBuffer.free();
+
+ for (AlphaEntryMap::iterator iter = _alphaDataEntries.begin(); iter != _alphaDataEntries.end(); ++iter) {
+ iter->_value.data->free();
+ delete iter->_value.data;
+ }
+}
+
+void RenderManager::update(uint deltaTimeInMillis) {
+ // An inverse velocity of 0 would be infinitely fast, so we'll let 0 mean no velocity.
+ if (_backgroundInverseVelocity != 0) {
+ _accumulatedVelocityMilliseconds += deltaTimeInMillis;
+
+ uint absVelocity = uint(abs(_backgroundInverseVelocity));
+
+ int numberOfSteps = 0;
+ while (_accumulatedVelocityMilliseconds >= absVelocity) {
+ _accumulatedVelocityMilliseconds -= absVelocity;
+ numberOfSteps++;
+ }
+
+ // Choose the direction of movement using the sign of the velocity
+ moveBackground(_backgroundInverseVelocity < 0 ? -numberOfSteps : numberOfSteps);
+ }
+}
+
+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 (AlphaEntryMap::iterator iter = _alphaDataEntries.begin(); iter != _alphaDataEntries.end(); ++iter) {
+ uint32 destOffset = 0;
+ uint32 sourceOffset = 0;
+ uint16 *backbufferPtr = (uint16 *)_backBuffer.getBasePtr(iter->_value.destX + _workingWindow.left, iter->_value.destY + _workingWindow.top);
+ uint16 *entryPtr = (uint16 *)iter->_value.data->getPixels();
+
+ 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, 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);
+
+ const uint16 *source = (const uint16 *)surface->getPixels();
+ uint16 *dest = (uint16 *)tranposedSurface->getPixels();
+
+ for (uint32 y = 0; y < tranposedSurface->h; ++y) {
+ uint32 columnIndex = y * tranposedSurface->w;
+
+ for (uint32 x = 0; x < tranposedSurface->w; ++x) {
+ dest[columnIndex + x] = source[x * surface->w + y];
+ }
+ }
+
+ 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..111bf6276c
--- /dev/null
+++ b/engines/zvision/render_manager.h
@@ -0,0 +1,328 @@
+/* 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 "zvision/render_table.h"
+#include "zvision/truetype_font.h"
+
+#include "common/rect.h"
+#include "common/hashmap.h"
+
+#include "graphics/surface.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;
+ };
+
+ typedef Common::HashMap<uint32, AlphaDataEntry> AlphaEntryMap;
+
+private:
+ OSystem *_system;
+ const Graphics::PixelFormat _pixelFormat;
+
+ // A buffer the exact same size as the workingWindow
+ // This buffer stores everything un-warped, then does a warp at the end of the frame
+ Graphics::Surface _workingWindowBuffer;
+ // A buffer representing the entire screen. Any graphical updates are first done with this buffer
+ // before actually being blitted to the screen
+ Graphics::Surface _backBuffer;
+ // A list of Alpha Entries that need to be blitted to the backbuffer
+ AlphaEntryMap _alphaDataEntries;
+
+ // A rectangle representing the portion of the working window where the pixels have been changed since last frame
+ Common::Rect _workingWindowDirtyRect;
+ // A rectangle representing the portion of the backbuffer where the pixels have been changed since last frame
+ Common::Rect _backBufferDirtyRect;
+
+ /** Width of the working window. Saved to prevent extraneous calls to _workingWindow.width() */
+ const int _workingWidth;
+ /** Height of the working window. Saved to prevent extraneous calls to _workingWindow.height() */
+ const int _workingHeight;
+ /** Center of the screen in the x direction */
+ const int _screenCenterX;
+ /** Center of the screen in the y direction */
+ const int _screenCenterY;
+
+ /**
+ * A Rectangle centered inside the actual window. All in-game coordinates
+ * are given in this coordinate space. Also, all images are clipped to the
+ * edges of this Rectangle
+ */
+ const Common::Rect _workingWindow;
+ /** Used to warp the background image */
+ RenderTable _renderTable;
+
+ 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();
+
+ /**
+ * Renders all AlphaEntries to the backbuffer
+ */
+ void processAlphaEntries();
+ /**
+ * Clears the AlphaEntry list
+ */
+ void clearAlphaEntries() { _alphaDataEntries.clear(); }
+ /**
+ * Removes a specific AlphaEntry from the list
+ *
+ * @param idNumber The id number identifing the AlphaEntry
+ */
+ void removeAlphaEntry(uint32 idNumber) { _alphaDataEntries.erase(idNumber); }
+
+ /**
+ * Copies a sub-rectangle of a buffer to the working window
+ *
+ * @param buffer The pixel data to copy to the working window
+ * @param destX The X destination in the working window where the subRect of data should be put
+ * @param destY The Y destination in the working window where the subRect of data should be put
+ * @param imageWidth The width of the source image
+ * @param width The width of the sub rectangle
+ * @param height The height of the sub rectangle
+ */
+ void copyRectToWorkingWindow(const uint16 *buffer, int32 destX, int32 destY, int32 imageWidth, int32 width, int32 height);
+ /**
+ * Copies a sub-rectangle of a buffer to the working window with binary alpha support.
+ *
+ * @param buffer The pixel data to copy to the working window
+ * @param destX The X destination in the working window where the subRect of data should be put
+ * @param destY The Y destination in the working window where the subRect of data should be put
+ * @param imageWidth The width of the source image
+ * @param width The width of the sub rectangle
+ * @param height The height of the sub rectangle
+ * @param alphaColor The color to interpret as meaning 'transparent'
+ * @param idNumber A unique identifier for the data being copied over.
+ */
+ void copyRectToWorkingWindow(const uint16 *buffer, int32 destX, int32 destY, int32 imageWidth, int32 width, int32 height, int16 alphaColor, uint32 idNumber);
+
+ /**
+ * Renders the supplied text to the working window
+ *
+ * @param idNumber A unique identifier for the text
+ * @param text The text to be rendered
+ * @param font The font to use to render the text
+ * @param destX The X destination in the working window where the text should be rendered
+ * @param destY The Y destination in the working window where the text should be rendered
+ * @param textColor The color to render the text with (in RBG 565)
+ * @param maxWidth The max width the text should take up.
+ * @param maxHeight The max height the text should take up.
+ * @param align The alignment of the text within the bounds of maxWidth
+ * @param wrap If true, any words extending past maxWidth will wrap to a new line. If false, ellipses will be rendered to show that the text didn't fit
+ * @return A rectangle representing where the text was drawn in the working window
+ */
+ Common::Rect renderTextToWorkingWindow(uint32 idNumber, const Common::String &text, TruetypeFont *font, int destX, int destY, uint16 textColor, int maxWidth, int maxHeight = -1, Graphics::TextAlign align = Graphics::kTextAlignLeft, bool wrap = true);
+
+ /**
+ * 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);
+
+ /**
+ * Reads an image file pixel data into a Surface buffer. In the process
+ * it converts the pixel data from RGB 555 to RGB 565. Also, if the image
+ * is transposed, it will un-transpose the pixel data. The function will
+ * call destination::create() if the dimensions of destination do not match
+ * up with the dimensions of the image.
+ *
+ * @param fileName The name of a .tga file
+ * @param destination A reference to the Surface to store the pixel data in
+ */
+ void readImageToSurface(const Common::String &fileName, Graphics::Surface &destination);
+
+ /**
+ * Move the background image by an offset. If we are currently in Panorama mode,
+ * the offset will correspond to a horizontal motion. If we are currently in Tilt mode,
+ * the offset will correspond to a vertical motion. This function should not be called
+ * if we are in Flat mode.
+ *
+ * The RenderManager will take care of wrapping the image.
+ * Ex: If the image has width 1400px, it is legal to offset 1500px.
+ *
+ * @param offset The amount to move the background
+ */
+ 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..b6a6a3d2bb
--- /dev/null
+++ b/engines/zvision/render_table.cpp
@@ -0,0 +1,240 @@
+/* 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/render_table.h"
+
+#include "common/rect.h"
+
+#include "graphics/colormasks.h"
+
+
+namespace ZVision {
+
+RenderTable::RenderTable(uint numColumns, uint numRows)
+ : _numRows(numRows),
+ _numColumns(numColumns),
+ _renderState(FLAT) {
+ assert(numRows != 0 && numColumns != 0);
+
+ _internalBuffer = new Common::Point[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 (int16 y = subRect.top; y < subRect.bottom; ++y) {
+ uint32 sourceOffset = y * _numColumns;
+
+ for (int16 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;
+
+ 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..898091193a
--- /dev/null
+++ b/engines/zvision/render_table.h
@@ -0,0 +1,85 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef ZVISION_RENDER_TABLE_H
+#define ZVISION_RENDER_TABLE_H
+
+#include "common/rect.h"
+
+
+namespace ZVision {
+
+class RenderTable {
+public:
+ RenderTable(uint numRows, uint numColumns);
+ ~RenderTable();
+
+public:
+ enum RenderState {
+ PANORAMA,
+ TILT,
+ FLAT
+ };
+
+private:
+ uint _numColumns, _numRows;
+ Common::Point *_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..5f1d41daae
--- /dev/null
+++ b/engines/zvision/rlf_animation.cpp
@@ -0,0 +1,331 @@
+/* ScummVM - Graphic Adventure Engine
+*
+* ScummVM is the legal property of its developers, whose names
+* are too numerous to list here. Please refer to the COPYRIGHT
+* file distributed with this source distribution.
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 2
+* of the License, or (at your option) any later version.
+
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+#include "common/scummsys.h"
+
+#include "zvision/rlf_animation.h"
+
+#include "common/str.h"
+#include "common/file.h"
+#include "common/textconsole.h"
+#include "common/debug.h"
+#include "common/endian.h"
+
+#include "graphics/colormasks.h"
+
+
+namespace ZVision {
+
+RlfAnimation::RlfAnimation(const Common::String &fileName, bool stream)
+ : _stream(stream),
+ _lastFrameRead(0),
+ _frameCount(0),
+ _width(0),
+ _height(0),
+ _frameTime(0),
+ _frames(0),
+ _currentFrame(-1),
+ _frameBufferByteSize(0) {
+ if (!_file.open(fileName)) {
+ warning("RLF animation file %s could not be opened", fileName.c_str());
+ return;
+ }
+
+ if (!readHeader()) {
+ warning("%s is not a RLF animation file. Wrong magic number", fileName.c_str());
+ return;
+ }
+
+ _currentFrameBuffer.create(_width, _height, Graphics::createPixelFormat<565>());
+ _frameBufferByteSize = _width * _height * sizeof(uint16);
+
+ if (!stream) {
+ _frames = new Frame[_frameCount];
+
+ // Read in each frame
+ for (uint i = 0; i < _frameCount; ++i) {
+ _frames[i] = readNextFrame();
+ }
+ }
+}
+
+RlfAnimation::~RlfAnimation() {
+ for (uint i = 0; i < _frameCount; ++i) {
+ delete[] _frames[i].encodedData;
+ }
+ delete[] _frames;
+ _currentFrameBuffer.free();
+}
+
+bool RlfAnimation::readHeader() {
+ if (_file.readUint32BE() != MKTAG('F', 'E', 'L', 'R')) {
+ return false;
+ }
+
+ // Read the header
+ _file.readUint32LE(); // Size1
+ _file.readUint32LE(); // Unknown1
+ _file.readUint32LE(); // Unknown2
+ _frameCount = _file.readUint32LE(); // Frame count
+
+ // Since we don't need any of the data, we can just seek right to the
+ // entries we need rather than read in all the individual entries.
+ _file.seek(136, SEEK_CUR);
+
+ //// Read CIN header
+ //_file.readUint32BE(); // Magic number FNIC
+ //_file.readUint32LE(); // Size2
+ //_file.readUint32LE(); // Unknown3
+ //_file.readUint32LE(); // Unknown4
+ //_file.readUint32LE(); // Unknown5
+ //_file.seek(0x18, SEEK_CUR); // VRLE
+ //_file.readUint32LE(); // LRVD
+ //_file.readUint32LE(); // Unknown6
+ //_file.seek(0x18, SEEK_CUR); // HRLE
+ //_file.readUint32LE(); // ELHD
+ //_file.readUint32LE(); // Unknown7
+ //_file.seek(0x18, SEEK_CUR); // HKEY
+ //_file.readUint32LE(); // ELRH
+
+ //// Read MIN info header
+ //_file.readUint32BE(); // Magic number FNIM
+ //_file.readUint32LE(); // Size3
+ //_file.readUint32LE(); // OEDV
+ //_file.readUint32LE(); // Unknown8
+ //_file.readUint32LE(); // Unknown9
+ //_file.readUint32LE(); // Unknown10
+ _width = _file.readUint32LE(); // Width
+ _height = _file.readUint32LE(); // Height
+
+ // Read time header
+ _file.readUint32BE(); // Magic number EMIT
+ _file.readUint32LE(); // Size4
+ _file.readUint32LE(); // Unknown11
+ _frameTime = _file.readUint32LE() / 10; // Frame time in microseconds
+
+ return true;
+}
+
+RlfAnimation::Frame RlfAnimation::readNextFrame() {
+ RlfAnimation::Frame frame;
+
+ _file.readUint32BE(); // Magic number MARF
+ uint32 size = _file.readUint32LE(); // Size
+ _file.readUint32LE(); // Unknown1
+ _file.readUint32LE(); // Unknown2
+ uint32 type = _file.readUint32BE(); // Either ELHD or ELRH
+ uint32 headerSize = _file.readUint32LE(); // Offset from the beginning of this frame to the frame data. Should always be 28
+ _file.readUint32LE(); // Unknown3
+
+ frame.encodedSize = size - headerSize;
+ frame.encodedData = new int8[frame.encodedSize];
+ _file.read(frame.encodedData, frame.encodedSize);
+
+ if (type == MKTAG('E', 'L', 'H', 'D')) {
+ frame.type = Masked;
+ } else if (type == MKTAG('E', 'L', 'R', 'H')) {
+ frame.type = Simple;
+ _completeFrames.push_back(_lastFrameRead);
+ } else {
+ warning("Frame %u doesn't have type that can be decoded", _lastFrameRead);
+ }
+
+ _lastFrameRead++;
+ return frame;
+}
+
+void RlfAnimation::seekToFrame(int frameNumber) {
+ assert(!_stream);
+ assert(frameNumber < (int)_frameCount || frameNumber >= -1);
+
+ if (frameNumber == -1) {
+ _currentFrame = -1;
+ return;
+ }
+
+ int closestFrame = _currentFrame;
+ int distance = (int)frameNumber - _currentFrame;
+ for (uint i = 0; i < _completeFrames.size(); ++i) {
+ int newDistance = (int)frameNumber - (int)(_completeFrames[i]);
+ if (newDistance > 0 && (closestFrame == -1 || newDistance < distance)) {
+ closestFrame = _completeFrames[i];
+ distance = newDistance;
+ }
+ }
+
+ for (int i = closestFrame; i <= frameNumber; ++i) {
+ applyFrameToCurrent(i);
+ }
+
+ _currentFrame = frameNumber;
+}
+
+const Graphics::Surface *RlfAnimation::getFrameData(uint frameNumber) {
+ assert(!_stream);
+ assert(frameNumber < _frameCount);
+
+ // Since this method is so expensive, first check to see if we can use
+ // getNextFrame() it's cheap.
+ if ((int)frameNumber == _currentFrame) {
+ return &_currentFrameBuffer;
+ } else if (_currentFrame + 1 == (int)frameNumber) {
+ return getNextFrame();
+ }
+
+ seekToFrame(frameNumber);
+ return &_currentFrameBuffer;
+}
+
+const Graphics::Surface *RlfAnimation::getNextFrame() {
+ assert(_currentFrame + 1 < (int)_frameCount);
+
+ if (_stream) {
+ applyFrameToCurrent(readNextFrame());
+ } else {
+ applyFrameToCurrent(_currentFrame + 1);
+ }
+
+ _currentFrame++;
+ return &_currentFrameBuffer;
+}
+
+void RlfAnimation::applyFrameToCurrent(uint frameNumber) {
+ if (_frames[frameNumber].type == Masked) {
+ decodeMaskedRunLengthEncoding(_frames[frameNumber].encodedData, (int8 *)_currentFrameBuffer.getPixels(), _frames[frameNumber].encodedSize, _frameBufferByteSize);
+ } else if (_frames[frameNumber].type == Simple) {
+ decodeSimpleRunLengthEncoding(_frames[frameNumber].encodedData, (int8 *)_currentFrameBuffer.getPixels(), _frames[frameNumber].encodedSize, _frameBufferByteSize);
+ }
+}
+
+void RlfAnimation::applyFrameToCurrent(const RlfAnimation::Frame &frame) {
+ if (frame.type == Masked) {
+ decodeMaskedRunLengthEncoding(frame.encodedData, (int8 *)_currentFrameBuffer.getPixels(), frame.encodedSize, _frameBufferByteSize);
+ } else if (frame.type == Simple) {
+ decodeSimpleRunLengthEncoding(frame.encodedData, (int8 *)_currentFrameBuffer.getPixels(), frame.encodedSize, _frameBufferByteSize);
+ }
+}
+
+void RlfAnimation::decodeMaskedRunLengthEncoding(int8 *source, int8 *dest, uint32 sourceSize, uint32 destSize) const {
+ uint32 sourceOffset = 0;
+ uint32 destOffset = 0;
+
+ while (sourceOffset < sourceSize) {
+ int8 numberOfSamples = source[sourceOffset];
+ sourceOffset++;
+
+ // If numberOfSamples is negative, the next abs(numberOfSamples) samples should
+ // be copied directly from source to dest
+ if (numberOfSamples < 0) {
+ numberOfSamples = ABS(numberOfSamples);
+
+ while (numberOfSamples > 0) {
+ if (sourceOffset + 1 >= sourceSize) {
+ return;
+ } else if (destOffset + 1 >= destSize) {
+ debug(2, "Frame decoding overflow\n\tsourceOffset=%u\tsourceSize=%u\n\tdestOffset=%u\tdestSize=%u", sourceOffset, sourceSize, destOffset, destSize);
+ return;
+ }
+
+ byte r, g, b;
+ Graphics::colorToRGB<Graphics::ColorMasks<555> >(READ_LE_UINT16(source + sourceOffset), r, g, b);
+ uint16 destColor = Graphics::RGBToColor<Graphics::ColorMasks<565> >(r, g, b);
+ WRITE_UINT16(dest + destOffset, destColor);
+
+ sourceOffset += 2;
+ destOffset += 2;
+ numberOfSamples--;
+ }
+
+ // If numberOfSamples is >= 0, move destOffset forward ((numberOfSamples * 2) + 2)
+ // This function assumes the dest buffer has been memset with 0's.
+ } else {
+ if (sourceOffset + 1 >= sourceSize) {
+ return;
+ } else if (destOffset + 1 >= destSize) {
+ debug(2, "Frame decoding overflow\n\tsourceOffset=%u\tsourceSize=%u\n\tdestOffset=%u\tdestSize=%u", sourceOffset, sourceSize, destOffset, destSize);
+ return;
+ }
+
+ destOffset += (numberOfSamples * 2) + 2;
+ }
+ }
+}
+
+void RlfAnimation::decodeSimpleRunLengthEncoding(int8 *source, int8 *dest, uint32 sourceSize, uint32 destSize) const {
+ uint32 sourceOffset = 0;
+ uint32 destOffset = 0;
+
+ while (sourceOffset < sourceSize) {
+ int8 numberOfSamples = source[sourceOffset];
+ sourceOffset++;
+
+ // If numberOfSamples is negative, the next abs(numberOfSamples) samples should
+ // be copied directly from source to dest
+ if (numberOfSamples < 0) {
+ numberOfSamples = ABS(numberOfSamples);
+
+ while (numberOfSamples > 0) {
+ if (sourceOffset + 1 >= sourceSize) {
+ return;
+ } else if (destOffset + 1 >= destSize) {
+ debug(2, "Frame decoding overflow\n\tsourceOffset=%u\tsourceSize=%u\n\tdestOffset=%u\tdestSize=%u", sourceOffset, sourceSize, destOffset, destSize);
+ return;
+ }
+
+ byte r, g, b;
+ Graphics::colorToRGB<Graphics::ColorMasks<555> >(READ_LE_UINT16(source + sourceOffset), r, g, b);
+ uint16 destColor = Graphics::RGBToColor<Graphics::ColorMasks<565> >(r, g, b);
+ WRITE_UINT16(dest + destOffset, destColor);
+
+ sourceOffset += 2;
+ destOffset += 2;
+ numberOfSamples--;
+ }
+
+ // If numberOfSamples is >= 0, copy one sample from source to the
+ // next (numberOfSamples + 2) dest spots
+ } else {
+ if (sourceOffset + 1 >= sourceSize) {
+ return;
+ }
+
+ byte r, g, b;
+ Graphics::colorToRGB<Graphics::ColorMasks<555> >(READ_LE_UINT16(source + sourceOffset), r, g, b);
+ uint16 sampleColor = Graphics::RGBToColor<Graphics::ColorMasks<565> >(r, g, b);
+ sourceOffset += 2;
+
+ numberOfSamples += 2;
+ while (numberOfSamples > 0) {
+ if (destOffset + 1 >= destSize) {
+ debug(2, "Frame decoding overflow\n\tsourceOffset=%u\tsourceSize=%u\n\tdestOffset=%u\tdestSize=%u", sourceOffset, sourceSize, destOffset, destSize);
+ return;
+ }
+
+ WRITE_UINT16(dest + destOffset, sampleColor);
+ destOffset += 2;
+ numberOfSamples--;
+ }
+ }
+ }
+}
+
+} // End of namespace ZVision
diff --git a/engines/zvision/rlf_animation.h b/engines/zvision/rlf_animation.h
new file mode 100644
index 0000000000..fe5b0d68b4
--- /dev/null
+++ b/engines/zvision/rlf_animation.h
@@ -0,0 +1,163 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef ZVISION_RLF_ANIMATION_H
+#define ZVISION_RLF_ANIMATION_H
+
+#include "common/file.h"
+
+#include "graphics/surface.h"
+
+
+namespace Common {
+class String;
+}
+
+namespace ZVision {
+
+class RlfAnimation {
+public:
+ RlfAnimation(const Common::String &fileName, bool stream = true);
+ ~RlfAnimation();
+
+private:
+ enum EncodingType {
+ Masked,
+ Simple
+ };
+
+ struct Frame {
+ EncodingType type;
+ int8 *encodedData;
+ uint32 encodedSize;
+ };
+
+private:
+ Common::File _file;
+ bool _stream;
+ uint _lastFrameRead;
+
+ uint _frameCount;
+ uint _width;
+ uint _height;
+ uint32 _frameTime; // In milliseconds
+ Frame *_frames;
+ Common::Array<uint> _completeFrames;
+
+ int _currentFrame;
+ Graphics::Surface _currentFrameBuffer;
+ uint32 _frameBufferByteSize;
+
+public:
+ uint frameCount() { return _frameCount; }
+ uint width() { return _width; }
+ uint height() { return _height; }
+ uint32 frameTime() { return _frameTime; }
+
+ /**
+ * Seeks to the frameNumber and updates the internal Surface with
+ * the new frame data. If frameNumber == -1, it only sets _currentFrame,
+ * the internal Surface is unchanged. This function requires _stream = false
+ *
+ * @param frameNumber The frame number to seek to
+ */
+ void seekToFrame(int frameNumber);
+
+ /**
+ * Returns the pixel data of the frame specified. It will try to use
+ * getNextFrame() if possible. If not, it uses seekToFrame() to
+ * update the internal Surface and then returns a pointer to it.
+ * This function requires _stream = false
+ *
+ * @param frameNumber The frame number to get data for
+ * @return A pointer to the pixel data. Do NOT delete this.
+ */
+ const Graphics::Surface *getFrameData(uint frameNumber);
+ /**
+ * Returns the pixel data of the next frame. It is up to the user to
+ * check if the next frame is valid before calling this.
+ * IE. Use endOfAnimation()
+ *
+ * @return A pointer to the pixel data. Do NOT delete this.
+ */
+ const Graphics::Surface *getNextFrame();
+
+ /**
+ * @return Is the currentFrame is the last frame in the animation?
+ */
+ bool endOfAnimation() { return _currentFrame == (int)_frameCount - 1; }
+
+private:
+ /**
+ * Reads in the header of the RLF file
+ *
+ * @return Will return false if the header magic number is wrong
+ */
+ bool readHeader();
+ /**
+ * Reads the next frame from the RLF file, stores the data in
+ * a Frame object, then returns the object
+ *
+ * @return A Frame object representing the frame data
+ */
+ Frame readNextFrame();
+
+ /**
+ * Applies the frame corresponding to frameNumber on top of _currentFrameBuffer.
+ * This function requires _stream = false so it can look up the Frame object
+ * referenced by frameNumber.
+ *
+ * @param frameNumber The frame number to apply to _currentFrameBuffer
+ */
+ void applyFrameToCurrent(uint frameNumber);
+ /**
+ * Applies the data from a Frame object on top of a _currentFrameBuffer.
+ *
+ * @param frame A Frame object to apply to _currentFrameBuffer
+ */
+ void applyFrameToCurrent(const RlfAnimation::Frame &frame);
+
+ /**
+ * Decode frame data that uses masked run length encoding. This is the encoding
+ * used by P-frames.
+ *
+ * @param source The source pixel data
+ * @param dest The destination buffer
+ * @param sourceSize The size of the source pixel data
+ * @param destSize The size of the destination buffer
+ */
+ void decodeMaskedRunLengthEncoding(int8 *source, int8 *dest, uint32 sourceSize, uint32 destSize) const;
+ /**
+ * Decode frame data that uses simple run length encoding. This is the encoding
+ * used by I-frames.
+ *
+ * @param source The source pixel data
+ * @param dest The destination buffer
+ * @param sourceSize The size of the source pixel data
+ * @param destSize The size of the destination buffer
+ */
+ void decodeSimpleRunLengthEncoding(int8 *source, int8 *dest, uint32 sourceSize, uint32 destSize) const;
+};
+
+} // End of namespace ZVision
+
+#endif
diff --git a/engines/zvision/save_manager.cpp b/engines/zvision/save_manager.cpp
new file mode 100644
index 0000000000..c3deadd703
--- /dev/null
+++ b/engines/zvision/save_manager.cpp
@@ -0,0 +1,206 @@
+/* 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/save_manager.h"
+
+#include "zvision/zvision.h"
+#include "zvision/script_manager.h"
+#include "zvision/render_manager.h"
+
+#include "common/system.h"
+
+#include "graphics/surface.h"
+#include "graphics/thumbnail.h"
+
+#include "gui/message.h"
+
+
+namespace ZVision {
+
+const uint32 SaveManager::SAVEGAME_ID = MKTAG('Z', 'E', 'N', 'G');
+
+void SaveManager::saveGame(uint slot, const Common::String &saveName) {
+ // The games only support 20 slots
+ assert(slot <= 1 && slot <= 20);
+
+ Common::SaveFileManager *saveFileManager = g_system->getSavefileManager();
+ Common::OutSaveFile *file = saveFileManager->openForSaving(_engine->generateSaveFileName(slot));
+
+ // Write out the savegame header
+ file->writeUint32BE(SAVEGAME_ID);
+
+ // Write version
+ file->writeByte(SAVE_VERSION);
+
+ // Write savegame name
+ file->writeString(saveName);
+ file->writeByte(0);
+
+ // We can't call writeGameSaveData because the save menu is actually
+ // a room, so writeGameSaveData would save us in the save menu.
+ // However, an auto save is performed before each room change, so we
+ // can copy the data from there. We can guarantee that an auto save file will
+ // exist before this is called because the save menu can only be accessed
+ // after the first room (the main menu) has loaded.
+ Common::InSaveFile *autoSaveFile = saveFileManager->openForLoading(_engine->generateAutoSaveFileName());
+
+ // Skip over the header info
+ autoSaveFile->readSint32BE(); // SAVEGAME_ID
+ autoSaveFile->readByte(); // Version
+ autoSaveFile->seek(5, SEEK_CUR); // The string "auto" with terminating NULL
+
+ // Read the rest to a buffer
+ uint32 size = autoSaveFile->size() - autoSaveFile->pos();
+ byte *buffer = new byte[size];
+ autoSaveFile->read(buffer, size);
+
+ // Then write the buffer to the new file
+ file->write(buffer, size);
+
+ // Cleanup
+ delete[] buffer;
+ file->finalize();
+ delete file;
+}
+
+void SaveManager::autoSave() {
+ Common::OutSaveFile *file = g_system->getSavefileManager()->openForSaving(_engine->generateAutoSaveFileName());
+
+ // Write out the savegame header
+ file->writeUint32BE(SAVEGAME_ID);
+
+ // Version
+ file->writeByte(SAVE_VERSION);
+
+ file->writeString("auto");
+ file->writeByte(0);
+
+ writeSaveGameData(file);
+
+ // Cleanup
+ file->finalize();
+ delete file;
+}
+
+void SaveManager::writeSaveGameData(Common::OutSaveFile *file) {
+ // Create a thumbnail and save it
+ Graphics::saveThumbnail(*file);
+
+ // Write out the save date/time
+ TimeDate td;
+ g_system->getTimeAndDate(td);
+ file->writeSint16LE(td.tm_year + 1900);
+ file->writeSint16LE(td.tm_mon + 1);
+ file->writeSint16LE(td.tm_mday);
+ file->writeSint16LE(td.tm_hour);
+ file->writeSint16LE(td.tm_min);
+
+ ScriptManager *scriptManager = _engine->getScriptManager();
+ // Write out the current location
+ Location currentLocation = scriptManager->getCurrentLocation();
+ file->writeByte(currentLocation.world);
+ file->writeByte(currentLocation.room);
+ file->writeByte(currentLocation.node);
+ file->writeByte(currentLocation.view);
+ file->writeUint32LE(currentLocation.offset);
+
+ // Write out the current state table values
+ scriptManager->serializeStateTable(file);
+
+ // Write out any controls needing to save state
+ scriptManager->serializeControls(file);
+}
+
+Common::Error SaveManager::loadGame(uint slot) {
+ // The games only support 20 slots
+ assert(slot <= 1 && slot <= 20);
+
+ Common::InSaveFile *saveFile = g_system->getSavefileManager()->openForLoading(_engine->generateSaveFileName(slot));
+ if (saveFile == 0) {
+ return Common::kPathDoesNotExist;
+ }
+
+ // Read the header
+ SaveGameHeader header;
+ if (!readSaveGameHeader(saveFile, header)) {
+ return Common::kUnknownError;
+ }
+
+ char world = (char)saveFile->readByte();
+ char room = (char)saveFile->readByte();
+ char node = (char)saveFile->readByte();
+ char view = (char)saveFile->readByte();
+ uint32 offset = (char)saveFile->readUint32LE();
+
+ ScriptManager *scriptManager = _engine->getScriptManager();
+ // Update the state table values
+ scriptManager->deserializeStateTable(saveFile);
+
+ // Load the room
+ scriptManager->changeLocation(world, room, node, view, offset);
+
+ // Update the controls
+ scriptManager->deserializeControls(saveFile);
+
+ return Common::kNoError;
+}
+
+bool SaveManager::readSaveGameHeader(Common::InSaveFile *in, SaveGameHeader &header) {
+ if (in->readUint32BE() != SAVEGAME_ID) {
+ warning("File is not a ZVision save file. Aborting load");
+ return false;
+ }
+
+ // Read in the version
+ header.version = in->readByte();
+
+ // Check that the save version isn't newer than this binary
+ if (header.version > SAVE_VERSION) {
+ uint tempVersion = header.version;
+ GUI::MessageDialog dialog(Common::String::format("This save file uses version %u, but this engine only supports up to version %d. You will need an updated version of the engine to use this save file.", tempVersion, SAVE_VERSION), "OK");
+ dialog.runModal();
+ }
+
+ // Read in the save name
+ header.saveName.clear();
+ char ch;
+ while ((ch = (char)in->readByte()) != '\0')
+ header.saveName += ch;
+
+ // Get the thumbnail
+ header.thumbnail = Graphics::loadThumbnail(*in);
+ if (!header.thumbnail)
+ return false;
+
+ // Read in save date/time
+ header.saveYear = in->readSint16LE();
+ header.saveMonth = in->readSint16LE();
+ header.saveDay = in->readSint16LE();
+ header.saveHour = in->readSint16LE();
+ header.saveMinutes = in->readSint16LE();
+
+ return true;
+}
+
+} // End of namespace ZVision
diff --git a/engines/zvision/save_manager.h b/engines/zvision/save_manager.h
new file mode 100644
index 0000000000..b4770e68b2
--- /dev/null
+++ b/engines/zvision/save_manager.h
@@ -0,0 +1,91 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef ZVISION_SAVE_MANAGER_H
+#define ZVISION_SAVE_MANAGER_H
+
+#include "common/savefile.h"
+
+namespace Common {
+class String;
+}
+
+namespace Graphics {
+struct Surface;
+}
+
+namespace ZVision {
+
+class ZVision;
+
+struct SaveGameHeader {
+ byte version;
+ 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;
+
+ enum {
+ SAVE_VERSION = 1
+ };
+
+public:
+ /**
+ * Called every room change. Saves the state of the room just before
+ * we switched rooms. Uses ZVision::generateAutoSaveFileName() to
+ * create the save file name.
+ */
+ void autoSave();
+ /**
+ * Copies the data from the last auto-save into a new save file. We
+ * can't use the current state data because the save menu *IS* a room.
+ * The file is named using ZVision::generateSaveFileName(slot)
+ *
+ * @param slot The save slot this save pertains to. Must be [1, 20]
+ * @param saveName The internal name for this save. This is NOT the name of the actual save file.
+ */
+ void saveGame(uint slot, const Common::String &saveName);
+ /**
+ * Loads the state data from the save file that slot references. Uses
+ * ZVision::generateSaveFileName(slot) to get the save file name.
+ *
+ * @param slot The save slot to load. Must be [1, 20]
+ */
+ Common::Error loadGame(uint slot);
+
+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..e90408cf0d
--- /dev/null
+++ b/engines/zvision/scr_file_handling.cpp
@@ -0,0 +1,302 @@
+/* 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..b33ca4c515
--- /dev/null
+++ b/engines/zvision/script_manager.cpp
@@ -0,0 +1,446 @@
+/* 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/zvision.h"
+#include "zvision/render_manager.h"
+#include "zvision/cursor_manager.h"
+#include "zvision/save_manager.h"
+#include "zvision/actions.h"
+#include "zvision/utility.h"
+
+#include "common/algorithm.h"
+#include "common/hashmap.h"
+#include "common/debug.h"
+#include "common/stream.h"
+
+
+namespace ZVision {
+
+ScriptManager::ScriptManager(ZVision *engine)
+ : _engine(engine),
+ _currentlyFocusedControl(0) {
+}
+
+ScriptManager::~ScriptManager() {
+ for (PuzzleList::iterator iter = _activePuzzles.begin(); iter != _activePuzzles.end(); ++iter) {
+ delete (*iter);
+ }
+ for (PuzzleList::iterator iter = _globalPuzzles.begin(); iter != _globalPuzzles.end(); ++iter) {
+ delete (*iter);
+ }
+ for (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end(); ++iter) {
+ delete (*iter);
+ }
+}
+
+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 (PuzzleList::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 (PuzzleList::iterator globalPuzzleIter = _globalPuzzles.begin(); globalPuzzleIter != _globalPuzzles.end(); ++globalPuzzleIter) {
+ Puzzle *puzzlePtr = (*globalPuzzleIter);
+
+ // Iterate through each CriteriaEntry and add a reference from the criteria key to the Puzzle
+ for (Common::List<Common::List<Puzzle::CriteriaEntry> >::iterator criteriaIter = (*globalPuzzleIter)->criteriaList.begin(); criteriaIter != (*globalPuzzleIter)->criteriaList.end(); ++criteriaIter) {
+ for (Common::List<Puzzle::CriteriaEntry>::iterator entryIter = criteriaIter->begin(); entryIter != criteriaIter->end(); ++entryIter) {
+ _referenceTable[entryIter->key].push_back(puzzlePtr);
+
+ // If 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 (PuzzleMap::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 (ControlList::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(1, "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 (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end(); ++iter) {
+ if ((*iter)->getKey() == key) {
+ return (*iter);
+ }
+ }
+
+ return nullptr;
+}
+
+void ScriptManager::enableControl(uint32 key) {
+ for (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end(); ++iter) {
+ if ((*iter)->getKey() == key) {
+ (*iter)->enable();
+ break;
+ }
+ }
+}
+
+void ScriptManager::disableControl(uint32 key) {
+ for (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end(); ++iter) {
+ if ((*iter)->getKey() == key) {
+ (*iter)->disable();
+ break;
+ }
+ }
+}
+
+void ScriptManager::focusControl(uint32 key) {
+ for (ControlList::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 (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end(); ++iter) {
+ (*iter)->onMouseDown(screenSpacePos, backgroundImageSpacePos);
+ }
+}
+
+void ScriptManager::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
+ for (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end(); ++iter) {
+ (*iter)->onMouseUp(screenSpacePos, backgroundImageSpacePos);
+ }
+}
+
+bool ScriptManager::onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
+ bool cursorWasChanged = false;
+ for (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end(); ++iter) {
+ cursorWasChanged = cursorWasChanged || (*iter)->onMouseMove(screenSpacePos, backgroundImageSpacePos);
+ }
+
+ return cursorWasChanged;
+}
+
+void ScriptManager::onKeyDown(Common::KeyState keyState) {
+ for (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end(); ++iter) {
+ (*iter)->onKeyDown(keyState);
+ }
+}
+
+void ScriptManager::onKeyUp(Common::KeyState keyState) {
+ for (ControlList::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(1, "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 (PuzzleList::iterator iter = _activePuzzles.begin(); iter != _activePuzzles.end(); ++iter) {
+ delete (*iter);
+ }
+ _activePuzzles.clear();
+ for (ControlList::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 (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end(); ++iter) {
+ (*iter)->enable();
+ }
+
+ // Add all the local puzzles to the queue to be checked
+ for (PuzzleList::iterator iter = _activePuzzles.begin(); iter != _activePuzzles.end(); ++iter) {
+ // Reset any Puzzles that have the flag ONCE_PER_INST
+ if (((*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 (PuzzleList::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) {
+ // Clear out the current table values
+ _globalState.clear();
+
+ // 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();
+ // Directly access the state table so we don't trigger Puzzle checks
+ _globalState[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 (ControlList::iterator iter = _activeControls.begin(); iter != _activeControls.end(); ++iter) {
+ if ((*iter)->needsSerialization()) {
+ numberOfControlsNeedingSerialization++;
+ }
+ }
+ stream->writeUint32LE(numberOfControlsNeedingSerialization);
+
+ for (ControlList::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 (ControlList::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..c44c836171
--- /dev/null
+++ b/engines/zvision/script_manager.h
@@ -0,0 +1,211 @@
+/* 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 "zvision/puzzle.h"
+#include "zvision/control.h"
+
+#include "common/hashmap.h"
+#include "common/queue.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;
+};
+
+typedef Common::HashMap<uint32, Common::Array<Puzzle *> > PuzzleMap;
+typedef Common::List<Puzzle *> PuzzleList;
+typedef Common::Queue<Puzzle *> PuzzleQueue;
+typedef Common::List<Control *> ControlList;
+
+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 */
+ PuzzleMap _referenceTable;
+ /** Holds the Puzzles that should be checked this frame */
+ PuzzleQueue _puzzlesToCheck;
+ /** Holds the currently active puzzles */
+ PuzzleList _activePuzzles;
+ /** Holds the global puzzles */
+ PuzzleList _globalPuzzles;
+ /** Holds the currently active controls */
+ ControlList _activeControls;
+
+ 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/single_value_container.cpp b/engines/zvision/single_value_container.cpp
new file mode 100644
index 0000000000..837bd8d7fc
--- /dev/null
+++ b/engines/zvision/single_value_container.cpp
@@ -0,0 +1,348 @@
+/* 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/single_value_container.h"
+
+#include "common/textconsole.h"
+#include "common/str.h"
+
+
+namespace ZVision {
+
+SingleValueContainer::SingleValueContainer(ValueType type) : _objectType(type) { }
+
+SingleValueContainer::SingleValueContainer(bool value) : _objectType(BOOL) {
+ _value.boolVal = value;
+}
+
+SingleValueContainer::SingleValueContainer(byte value) : _objectType(BYTE) {
+ _value.byteVal = value;
+}
+
+SingleValueContainer::SingleValueContainer(int16 value) : _objectType(INT16) {
+ _value.int16Val = value;
+}
+
+SingleValueContainer::SingleValueContainer(uint16 value) : _objectType(UINT16) {
+ _value.uint16Val = value;
+}
+
+SingleValueContainer::SingleValueContainer(int32 value) : _objectType(INT32) {
+ _value.int32Val = value;
+}
+
+SingleValueContainer::SingleValueContainer(uint32 value) : _objectType(UINT32) {
+ _value.uint32Val = value;
+}
+
+SingleValueContainer::SingleValueContainer(float value) : _objectType(FLOAT) {
+ _value.floatVal = value;
+}
+
+SingleValueContainer::SingleValueContainer(double value) : _objectType(DOUBLE) {
+ _value.doubleVal = value;
+}
+
+SingleValueContainer::SingleValueContainer(Common::String value) : _objectType(BYTE) {
+ _value.stringVal = new char[value.size() + 1];
+ memcpy(_value.stringVal, value.c_str(), value.size() + 1);
+}
+
+SingleValueContainer::SingleValueContainer(const SingleValueContainer &other) {
+ _objectType = other._objectType;
+
+ switch (_objectType) {
+ case BOOL:
+ _value.boolVal = other._value.boolVal;
+ break;
+ case BYTE:
+ _value.byteVal = other._value.byteVal;
+ break;
+ case INT16:
+ _value.int16Val = other._value.int16Val;
+ break;
+ case UINT16:
+ _value.uint16Val = other._value.uint16Val;
+ break;
+ case INT32:
+ _value.int32Val = other._value.int32Val;
+ break;
+ case UINT32:
+ _value.uint32Val = other._value.uint32Val;
+ break;
+ case FLOAT:
+ _value.floatVal = other._value.floatVal;
+ break;
+ case DOUBLE:
+ _value.doubleVal = other._value.doubleVal;
+ break;
+ case STRING:
+ uint32 length = strlen(other._value.stringVal);
+ _value.stringVal = new char[length + 1];
+ memcpy(_value.stringVal, other._value.stringVal, length + 1);
+ break;
+ }
+}
+
+SingleValueContainer::~SingleValueContainer() {
+ deleteCharPointer();
+}
+
+void SingleValueContainer::deleteCharPointer() {
+ if (_objectType == STRING)
+ delete[] _value.stringVal;
+}
+
+
+SingleValueContainer &SingleValueContainer::operator=(const bool &rhs) {
+ if (_objectType == BOOL) {
+ _value.boolVal = rhs;
+ return *this;
+ }
+
+ deleteCharPointer();
+ _objectType = BOOL;
+ _value.boolVal = rhs;
+
+ return *this;
+}
+
+SingleValueContainer &SingleValueContainer::operator=(const byte &rhs) {
+ if (_objectType == BYTE) {
+ _value.byteVal = rhs;
+ return *this;
+ }
+
+ deleteCharPointer();
+ _objectType = BYTE;
+ _value.byteVal = rhs;
+
+ return *this;
+}
+
+SingleValueContainer &SingleValueContainer::operator=(const int16 &rhs) {
+ if (_objectType == INT16) {
+ _value.int16Val = rhs;
+ return *this;
+ }
+
+ deleteCharPointer();
+ _objectType = INT16;
+ _value.int16Val = rhs;
+
+ return *this;
+}
+
+SingleValueContainer &SingleValueContainer::operator=(const uint16 &rhs) {
+ if (_objectType == UINT16) {
+ _value.uint16Val = rhs;
+ return *this;
+ }
+
+ deleteCharPointer();
+ _objectType = UINT16;
+ _value.uint16Val = rhs;
+
+ return *this;
+}
+
+SingleValueContainer &SingleValueContainer::operator=(const int32 &rhs) {
+ if (_objectType == INT32) {
+ _value.int32Val = rhs;
+ return *this;
+ }
+
+ deleteCharPointer();
+ _objectType = INT32;
+ _value.int32Val = rhs;
+
+ return *this;
+}
+
+SingleValueContainer &SingleValueContainer::operator=(const uint32 &rhs) {
+ if (_objectType == UINT32) {
+ _value.uint32Val = rhs;
+ return *this;
+ }
+
+ deleteCharPointer();
+ _objectType = UINT32;
+ _value.uint32Val = rhs;
+
+ return *this;
+}
+
+SingleValueContainer &SingleValueContainer::operator=(const float &rhs) {
+ if (_objectType == FLOAT) {
+ _value.floatVal = rhs;
+ return *this;
+ }
+
+ deleteCharPointer();
+ _objectType = FLOAT;
+ _value.floatVal = rhs;
+
+ return *this;
+}
+
+SingleValueContainer &SingleValueContainer::operator=(const double &rhs) {
+ if (_objectType == DOUBLE) {
+ _value.doubleVal = rhs;
+ return *this;
+ }
+
+ deleteCharPointer();
+ _objectType = DOUBLE;
+ _value.doubleVal = rhs;
+
+ return *this;
+}
+
+SingleValueContainer &SingleValueContainer::operator=(const Common::String &rhs) {
+ if (_objectType != STRING) {
+ _objectType = STRING;
+ _value.stringVal = new char[rhs.size() + 1];
+ memcpy(_value.stringVal, rhs.c_str(), rhs.size() + 1);
+
+ return *this;
+ }
+
+ uint32 length = strlen(_value.stringVal);
+ if (length <= rhs.size() + 1) {
+ memcpy(_value.stringVal, rhs.c_str(), rhs.size() + 1);
+ } else {
+ delete[] _value.stringVal;
+ _value.stringVal = new char[rhs.size() + 1];
+ memcpy(_value.stringVal, rhs.c_str(), rhs.size() + 1);
+ }
+
+ return *this;
+}
+
+SingleValueContainer &SingleValueContainer::operator=(const SingleValueContainer &rhs) {
+ switch (_objectType) {
+ case BOOL:
+ return operator=(rhs._value.boolVal);
+ case BYTE:
+ return operator=(rhs._value.byteVal);
+ case INT16:
+ return operator=(rhs._value.int16Val);
+ case UINT16:
+ return operator=(rhs._value.uint16Val);
+ case INT32:
+ return operator=(rhs._value.int32Val);
+ case UINT32:
+ return operator=(rhs._value.uint32Val);
+ case FLOAT:
+ return operator=(rhs._value.floatVal);
+ case DOUBLE:
+ return operator=(rhs._value.doubleVal);
+ case STRING:
+ uint32 length = strlen(rhs._value.stringVal);
+
+ _value.stringVal = new char[length + 1];
+ memcpy(_value.stringVal, rhs._value.stringVal, length + 1);
+
+ return *this;
+ }
+
+ return *this;
+}
+
+
+bool SingleValueContainer::getBoolValue(bool *returnValue) const {
+ if (_objectType != BOOL) {
+ warning("'Object' is not storing a bool.");
+ return false;
+ }
+
+ *returnValue = _value.boolVal;
+ return true;
+}
+
+bool SingleValueContainer::getByteValue(byte *returnValue) const {
+ if (_objectType != BYTE)
+ warning("'Object' is not storing a byte.");
+
+ *returnValue = _value.byteVal;
+ return true;
+}
+
+bool SingleValueContainer::getInt16Value(int16 *returnValue) const {
+ if (_objectType != INT16)
+ warning("'Object' is not storing an int16.");
+
+ *returnValue = _value.int16Val;
+ return true;
+}
+
+bool SingleValueContainer::getUInt16Value(uint16 *returnValue) const {
+ if (_objectType != UINT16)
+ warning("'Object' is not storing a uint16.");
+
+ *returnValue = _value.uint16Val;
+ return true;
+}
+
+bool SingleValueContainer::getInt32Value(int32 *returnValue) const {
+ if (_objectType != INT32)
+ warning("'Object' is not storing an int32.");
+
+ *returnValue = _value.int32Val;
+ return true;
+}
+
+bool SingleValueContainer::getUInt32Value(uint32 *returnValue) const {
+ if (_objectType != UINT32)
+ warning("'Object' is not storing a uint32.");
+
+ *returnValue = _value.uint32Val;
+ return true;
+}
+
+bool SingleValueContainer::getFloatValue(float *returnValue) const {
+ if (_objectType != FLOAT)
+ warning("'Object' is not storing a float.");
+
+ *returnValue = _value.floatVal;
+ return true;
+}
+
+bool SingleValueContainer::getDoubleValue(double *returnValue) const {
+ if (_objectType != DOUBLE)
+ warning("'Object' is not storing a double.");
+
+ *returnValue = _value.doubleVal;
+ return true;
+}
+
+bool SingleValueContainer::getStringValue(Common::String *returnValue) const {
+ if (_objectType != STRING)
+ warning("'Object' is not storing a Common::String.");
+
+ *returnValue = _value.stringVal;
+ return true;
+}
+
+} // End of namespace ZVision
diff --git a/engines/zvision/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..ab42f3d3e0
--- /dev/null
+++ b/engines/zvision/string_manager.cpp
@@ -0,0 +1,255 @@
+/* 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/string_manager.h"
+
+#include "zvision/truetype_font.h"
+
+#include "common/file.h"
+#include "common/tokenizer.h"
+#include "common/debug.h"
+
+#include "graphics/fontman.h"
+#include "graphics/colormasks.h"
+
+
+namespace ZVision {
+
+StringManager::StringManager(ZVision *engine)
+ : _engine(engine) {
+}
+
+StringManager::~StringManager() {
+ for (Common::HashMap<Common::String, TruetypeFont *>::iterator iter = _fonts.begin(); iter != _fonts.end(); ++iter) {
+ delete iter->_value;
+ }
+}
+
+void StringManager::initialize(ZVisionGameId gameId) {
+ if (gameId == GID_NEMESIS) {
+ // TODO: Check this hardcoded filename against all versions of Nemesis
+ parseStrFile("nemesis.str");
+ } else if (gameId == GID_GRANDINQUISITOR) {
+ // TODO: Check this hardcoded filename against all versions of Grand Inquisitor
+ parseStrFile("inquis.str");
+ }
+}
+
+void StringManager::parseStrFile(const Common::String &fileName) {
+ Common::File file;
+ if (!file.open(fileName)) {
+ warning("%s does not exist. String parsing failed", fileName.c_str());
+ return;
+ }
+
+ uint lineNumber = 0;
+ while (!file.eos()) {
+ _lastStyle.align = Graphics::kTextAlignLeft;
+ _lastStyle.color = 0;
+ _lastStyle.font = nullptr;
+
+ Common::String asciiLine = readWideLine(file);
+ if (asciiLine.empty()) {
+ continue;
+ }
+
+ char tagString[150];
+ uint tagStringCursor = 0;
+ char textString[150];
+ uint textStringCursor = 0;
+ bool inTag = false;
+
+ for (uint i = 0; i < asciiLine.size(); ++i) {
+ switch (asciiLine[i]) {
+ case '<':
+ inTag = true;
+ if (!_inGameText[lineNumber].fragments.empty()) {
+ _inGameText[lineNumber].fragments.back().text = Common::String(textString, textStringCursor);
+ textStringCursor = 0;
+ }
+ break;
+ case '>':
+ inTag = false;
+ parseTag(Common::String(tagString, tagStringCursor), lineNumber);
+ tagStringCursor = 0;
+ break;
+ default:
+ if (inTag) {
+ tagString[tagStringCursor] = asciiLine[i];
+ tagStringCursor++;
+ } else {
+ textString[textStringCursor] = asciiLine[i];
+ textStringCursor++;
+ }
+ break;
+ }
+ }
+
+ if (textStringCursor > 0) {
+ _inGameText[lineNumber].fragments.back().text = Common::String(textString, textStringCursor);
+ }
+
+ lineNumber++;
+ assert(lineNumber <= NUM_TEXT_LINES);
+ }
+}
+
+void StringManager::parseTag(const Common::String &tagString, uint lineNumber) {
+ Common::StringTokenizer tokenizer(tagString);
+
+ Common::String token = tokenizer.nextToken();
+
+ Common::String fontName;
+ bool bold = false;
+ Graphics::TextAlign align = _lastStyle.align;
+ int point = _lastStyle.font != nullptr ? _lastStyle.font->_fontHeight : 12;
+ int red = 0;
+ int green = 0;
+ int blue = 0;
+
+ while (!token.empty()) {
+ if (token.matchString("font", true)) {
+ fontName = tokenizer.nextToken();
+ } else if (token.matchString("bold", true)) {
+ token = tokenizer.nextToken();
+ if (token.matchString("on", false)) {
+ bold = true;
+ }
+ } else if (token.matchString("justify", true)) {
+ token = tokenizer.nextToken();
+ if (token.matchString("center", false)) {
+ align = Graphics::kTextAlignCenter;
+ } else if (token.matchString("right", false)) {
+ align = Graphics::kTextAlignRight;
+ }
+ } else if (token.matchString("point", true)) {
+ point = atoi(tokenizer.nextToken().c_str());
+ } else if (token.matchString("red", true)) {
+ red = atoi(tokenizer.nextToken().c_str());
+ } else if (token.matchString("green", true)) {
+ green = atoi(tokenizer.nextToken().c_str());
+ } else if (token.matchString("blue", true)) {
+ blue = atoi(tokenizer.nextToken().c_str());
+ }
+
+ token = tokenizer.nextToken();
+ }
+
+ TextFragment fragment;
+
+ if (fontName.empty()) {
+ fragment.style.font = _lastStyle.font;
+ } else {
+ Common::String newFontName;
+ if (fontName.matchString("*times new roman*", true)) {
+ if (bold) {
+ newFontName = "timesbd.ttf";
+ } else {
+ newFontName = "times.ttf";
+ }
+ } else if (fontName.matchString("*courier new*", true)) {
+ if (bold) {
+ newFontName = "courbd.ttf";
+ } else {
+ newFontName = "cour.ttf";
+ }
+ } else if (fontName.matchString("*century schoolbook*", true)) {
+ if (bold) {
+ newFontName = "censcbkbd.ttf";
+ } else {
+ newFontName = "censcbk.ttf";
+ }
+ } else if (fontName.matchString("*garamond*", true)) {
+ if (bold) {
+ newFontName = "garabd.ttf";
+ } else {
+ newFontName = "gara.ttf";
+ }
+ } else {
+ debug("Could not identify font: %s. Reverting to Arial", fontName.c_str());
+ if (bold) {
+ newFontName = "zorknorm.ttf";
+ } else {
+ newFontName = "arial.ttf";
+ }
+ }
+
+ Common::String fontKey = Common::String::format("%s-%d", newFontName.c_str(), point);
+ if (_fonts.contains(fontKey)) {
+ fragment.style.font = _fonts[fontKey];
+ } else {
+ fragment.style.font = new TruetypeFont(_engine, point);
+ fragment.style.font->loadFile(newFontName);
+ _fonts[fontKey] = fragment.style.font;
+ }
+ }
+
+ fragment.style.align = align;
+ fragment.style.color = Graphics::ARGBToColor<Graphics::ColorMasks<565> >(0, red, green, blue);
+ _inGameText[lineNumber].fragments.push_back(fragment);
+
+ _lastStyle = fragment.style;
+}
+
+Common::String StringManager::readWideLine(Common::SeekableReadStream &stream) {
+ Common::String asciiString;
+
+ // Don't spam the user with warnings about UTF-16 support.
+ // Just do one warning per String
+ bool charOverflowWarning = false;
+
+ uint16 value = stream.readUint16LE();
+ while (!stream.eos()) {
+ // Check for CRLF
+ if (value == 0x0A0D) {
+ // Read in the extra NULL char
+ stream.readByte(); // \0
+ // End of the line. Break
+ break;
+ }
+
+ // Crush each octet pair to a single octet with a simple cast
+ if (value > 255) {
+ charOverflowWarning = true;
+ value = '?';
+ }
+ char charValue = (char)value;
+
+ asciiString += charValue;
+
+ value = stream.readUint16LE();
+ }
+
+ if (charOverflowWarning) {
+ warning("UTF-16 is not supported. Characters greater than 255 are replaced with '?'");
+ }
+
+ return asciiString;
+}
+
+StringManager::TextStyle StringManager::getTextStyle(uint stringNumber) {
+ return _inGameText[stringNumber].fragments.front().style;
+}
+
+} // End of namespace ZVision
diff --git a/engines/zvision/string_manager.h b/engines/zvision/string_manager.h
new file mode 100644
index 0000000000..9cfed5261b
--- /dev/null
+++ b/engines/zvision/string_manager.h
@@ -0,0 +1,85 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ *
+ */
+
+#ifndef ZVISION_STRING_MANAGER_H
+#define ZVISION_STRING_MANAGER_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;
+ };
+
+ enum {
+ NUM_TEXT_LINES = 56 // Max number of lines in a .str file. We hardcode this number because we know ZNem uses 42 strings and ZGI uses 56
+ };
+
+private:
+ ZVision *_engine;
+ InGameText _inGameText[NUM_TEXT_LINES];
+ Common::HashMap<Common::String, TruetypeFont *> _fonts;
+
+ TextStyle _lastStyle;
+
+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..13426e03e4
--- /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
diff --git a/engines/zvision/timer_node.cpp b/engines/zvision/timer_node.cpp
new file mode 100644
index 0000000000..55dfa51dfe
--- /dev/null
+++ b/engines/zvision/timer_node.cpp
@@ -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.
+ *
+ */
+
+#include "common/scummsys.h"
+
+#include "zvision/timer_node.h"
+
+#include "zvision/zvision.h"
+#include "zvision/script_manager.h"
+
+#include "common/stream.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..32dca71548
--- /dev/null
+++ b/engines/zvision/timer_node.h
@@ -0,0 +1,54 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef ZVISION_TIMER_NODE_H
+#define ZVISION_TIMER_NODE_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:
+ int32 _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..8d6aaca51e
--- /dev/null
+++ b/engines/zvision/truetype_font.cpp
@@ -0,0 +1,116 @@
+/* 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/truetype_font.h"
+
+#include "zvision/zvision.h"
+#include "zvision/render_manager.h"
+
+#include "common/debug.h"
+#include "common/file.h"
+#include "common/system.h"
+
+#include "graphics/font.h"
+#include "graphics/fonts/ttf.h"
+#include "graphics/surface.h"
+
+
+namespace ZVision {
+
+TruetypeFont::TruetypeFont(ZVision *engine, int32 fontHeight)
+ : _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, 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..4f2ff864a8
--- /dev/null
+++ b/engines/zvision/truetype_font.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.
+ *
+ */
+
+// 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 "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:
+ /**
+ * Loads a .ttf file into memory. This must be called
+ * before any calls to drawTextToSurface
+ *
+ * @param filename The file name of the .ttf file to load
+ */
+ bool loadFile(const Common::String &filename);
+ /**
+ * Renders the supplied text to a Surface using 0x0 as the
+ * background color.
+ *
+ * @param text The to render
+ * @param textColor The color to render the text with
+ * @param maxWidth The max width the text should take up.
+ * @param maxHeight The max height the text should take up.
+ * @param align The alignment of the text within the bounds of maxWidth
+ * @param wrap If true, any words extending past maxWidth will wrap to a new line. If false, ellipses will be rendered to show that the text didn't fit
+ * @return A Surface containing the rendered text
+ */
+ Graphics::Surface *drawTextToSurface(const Common::String &text, uint16 textColor, int maxWidth, int maxHeight, Graphics::TextAlign align, bool wrap);
+};
+
+} // End of namespace ZVision
+
+#endif
diff --git a/engines/zvision/utility.cpp b/engines/zvision/utility.cpp
new file mode 100644
index 0000000000..d973cb2f4c
--- /dev/null
+++ b/engines/zvision/utility.cpp
@@ -0,0 +1,237 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/scummsys.h"
+
+#include "zvision/utility.h"
+
+#include "zvision/zvision.h"
+#include "zvision/zork_raw.h"
+
+#include "common/tokenizer.h"
+#include "common/file.h"
+
+
+namespace ZVision {
+
+void writeFileContentsToFile(const Common::String &sourceFile, const Common::String &destFile) {
+ Common::File f;
+ if (!f.open(sourceFile)) {
+ return;
+ }
+
+ byte* buffer = new byte[f.size()];
+ f.read(buffer, f.size());
+
+ Common::DumpFile dumpFile;
+ dumpFile.open(destFile);
+
+ dumpFile.write(buffer, f.size());
+ dumpFile.flush();
+ dumpFile.close();
+
+ delete[] buffer;
+}
+
+void trimCommentsAndWhiteSpace(Common::String *string) {
+ for (int i = string->size() - 1; i >= 0; i--) {
+ if ((*string)[i] == '#') {
+ string->erase(i);
+ }
+ }
+
+ string->trim();
+}
+
+void tryToDumpLine(const Common::String &key,
+ Common::String &line,
+ Common::HashMap<Common::String, byte> *count,
+ Common::HashMap<Common::String, bool> *fileAlreadyUsed,
+ Common::DumpFile &output) {
+ const byte numberOfExamplesPerType = 8;
+
+ if ((*count)[key] < numberOfExamplesPerType && !(*fileAlreadyUsed)[key]) {
+ output.writeString(line);
+ output.writeByte('\n');
+ (*count)[key]++;
+ (*fileAlreadyUsed)[key] = true;
+ }
+}
+
+void dumpEveryResultAction(const Common::String &destFile) {
+ Common::HashMap<Common::String, byte> count;
+ Common::HashMap<Common::String, bool> fileAlreadyUsed;
+
+ Common::DumpFile output;
+ output.open(destFile);
+
+ // Find scr files
+ Common::ArchiveMemberList list;
+ SearchMan.listMatchingMembers(list, "*.scr");
+
+ for (Common::ArchiveMemberList::iterator iter = list.begin(); iter != list.end(); ++iter) {
+ Common::SeekableReadStream *stream = (*iter)->createReadStream();
+
+ Common::String line = stream->readLine();
+ trimCommentsAndWhiteSpace(&line);
+
+ while (!stream->eos()) {
+ if (line.matchString("*:add*", true)) {
+ tryToDumpLine("add", line, &count, &fileAlreadyUsed, output);
+ } else if (line.matchString("*:animplay*", true)) {
+ tryToDumpLine("animplay", line, &count, &fileAlreadyUsed, output);
+ } else if (line.matchString("*:animpreload*", true)) {
+ tryToDumpLine("animpreload", line, &count, &fileAlreadyUsed, output);
+ } else if (line.matchString("*:animunload*", true)) {
+ tryToDumpLine("animunload", line, &count, &fileAlreadyUsed, output);
+ } else if (line.matchString("*:attenuate*", true)) {
+ tryToDumpLine("attenuate", line, &count, &fileAlreadyUsed, output);
+ } else if (line.matchString("*:assign*", true)) {
+ tryToDumpLine("assign", line, &count, &fileAlreadyUsed, output);
+ } else if (line.matchString("*:change_location*", true)) {
+ tryToDumpLine("change_location", line, &count, &fileAlreadyUsed, output);
+ } else if (line.matchString("*:crossfade*", true) && !fileAlreadyUsed["add"]) {
+ tryToDumpLine("crossfade", line, &count, &fileAlreadyUsed, output);
+ } else if (line.matchString("*:debug*", true)) {
+ tryToDumpLine("debug", line, &count, &fileAlreadyUsed, output);
+ } else if (line.matchString("*:delay_render*", true)) {
+ tryToDumpLine("delay_render", line, &count, &fileAlreadyUsed, output);
+ } else if (line.matchString("*:disable_control*", true)) {
+ tryToDumpLine("disable_control", line, &count, &fileAlreadyUsed, output);
+ } else if (line.matchString("*:disable_venus*", true)) {
+ tryToDumpLine("disable_venus", line, &count, &fileAlreadyUsed, output);
+ } else if (line.matchString("*:display_message*", true)) {
+ tryToDumpLine("display_message", line, &count, &fileAlreadyUsed, output);
+ } else if (line.matchString("*:dissolve*", true)) {
+ tryToDumpLine("dissolve", line, &count, &fileAlreadyUsed, output);
+ } else if (line.matchString("*:distort*", true)) {
+ tryToDumpLine("distort", line, &count, &fileAlreadyUsed, output);
+ } else if (line.matchString("*:enable_control*", true)) {
+ tryToDumpLine("enable_control", line, &count, &fileAlreadyUsed, output);
+ } else if (line.matchString("*:flush_mouse_events*", true)) {
+ tryToDumpLine("flush_mouse_events", line, &count, &fileAlreadyUsed, output);
+ } else if (line.matchString("*:inventory*", true)) {
+ tryToDumpLine("inventory", line, &count, &fileAlreadyUsed, output);
+ } else if (line.matchString("*:kill*", true)) {
+ tryToDumpLine("kill", line, &count, &fileAlreadyUsed, output);
+ } else if (line.matchString("*:menu_bar_enable*", true)) {
+ tryToDumpLine("menu_bar_enable", line, &count, &fileAlreadyUsed, output);
+ } else if (line.matchString("*:music*", true)) {
+ tryToDumpLine("music", line, &count, &fileAlreadyUsed, output);
+ } else if (line.matchString("*:pan_track*", true)) {
+ tryToDumpLine("pan_track", line, &count, &fileAlreadyUsed, output);
+ } else if (line.matchString("*:playpreload*", true)) {
+ tryToDumpLine("playpreload", line, &count, &fileAlreadyUsed, output);
+ } else if (line.matchString("*:preferences*", true)) {
+ tryToDumpLine("preferences", line, &count, &fileAlreadyUsed, output);
+ } else if (line.matchString("*:quit*", true)) {
+ tryToDumpLine("quit", line, &count, &fileAlreadyUsed, output);
+ } else if (line.matchString("*:random*", true)) {
+ tryToDumpLine("random", line, &count, &fileAlreadyUsed, output);
+ } else if (line.matchString("*:region*", true)) {
+ tryToDumpLine("region", line, &count, &fileAlreadyUsed, output);
+ } else if (line.matchString("*:restore_game*", true)) {
+ tryToDumpLine("restore_game", line, &count, &fileAlreadyUsed, output);
+ } else if (line.matchString("*:rotate_to*", true)) {
+ tryToDumpLine("rotate_to", line, &count, &fileAlreadyUsed, output);
+ } else if (line.matchString("*:save_game*", true)) {
+ tryToDumpLine("save_game", line, &count, &fileAlreadyUsed, output);
+ } else if (line.matchString("*:set_partial_screen*", true)) {
+ tryToDumpLine("set_partial_screen", line, &count, &fileAlreadyUsed, output);
+ } else if (line.matchString("*:set_screen*", true)) {
+ tryToDumpLine("set_screen", line, &count, &fileAlreadyUsed, output);
+ } else if (line.matchString("*:set_venus*", true)) {
+ tryToDumpLine("set_venus", line, &count, &fileAlreadyUsed, output);
+ } else if (line.matchString("*:stop*", true)) {
+ tryToDumpLine("stop", line, &count, &fileAlreadyUsed, output);
+ } else if (line.matchString("*:streamvideo*", true)) {
+ tryToDumpLine("streamvideo", line, &count, &fileAlreadyUsed, output);
+ } else if (line.matchString("*:syncsound*", true)) {
+ tryToDumpLine("syncsound", line, &count, &fileAlreadyUsed, output);
+ } else if (line.matchString("*:timer*", true)) {
+ tryToDumpLine("timer", line, &count, &fileAlreadyUsed, output);
+ } else if (line.matchString("*:ttytext*", true)) {
+ tryToDumpLine("ttytext", line, &count, &fileAlreadyUsed, output);
+ } else if (line.matchString("*:universe_music*", true)) {
+ tryToDumpLine("universe_music", line, &count, &fileAlreadyUsed, output);
+ }
+
+ line = stream->readLine();
+ trimCommentsAndWhiteSpace(&line);
+ }
+
+ for (Common::HashMap<Common::String, bool>::iterator fileUsedIter = fileAlreadyUsed.begin(); fileUsedIter != fileAlreadyUsed.end(); ++fileUsedIter) {
+ fileUsedIter->_value = false;
+ }
+ }
+
+ output.close();
+}
+
+Common::String getFileName(const Common::String &fullPath) {
+ Common::StringTokenizer tokenizer(fullPath, "/\\");
+ Common::String token;
+ while (!tokenizer.empty()) {
+ token = tokenizer.nextToken();
+ }
+
+ return token;
+}
+
+void convertRawToWav(const Common::String &inputFile, ZVision *engine, const Common::String &outputFile) {
+ Common::File file;
+ if (!file.open(inputFile))
+ return;
+
+ Audio::AudioStream *audioStream = makeRawZorkStream(inputFile, engine);
+
+ Common::DumpFile output;
+ output.open(outputFile);
+
+ output.writeUint32BE(MKTAG('R', 'I', 'F', 'F'));
+ output.writeUint32LE(file.size() * 2 + 36);
+ output.writeUint32BE(MKTAG('W', 'A', 'V', 'E'));
+ output.writeUint32BE(MKTAG('f', 'm', 't', ' '));
+ output.writeUint32LE(16);
+ output.writeUint16LE(1);
+ uint16 numChannels;
+ if (audioStream->isStereo()) {
+ numChannels = 2;
+ output.writeUint16LE(2);
+ } else {
+ numChannels = 1;
+ output.writeUint16LE(1);
+ }
+ output.writeUint32LE(audioStream->getRate());
+ output.writeUint32LE(audioStream->getRate() * numChannels * 2);
+ output.writeUint16LE(numChannels * 2);
+ output.writeUint16LE(16);
+ output.writeUint32BE(MKTAG('d', 'a', 't', 'a'));
+ output.writeUint32LE(file.size() * 2);
+ int16 *buffer = new int16[file.size()];
+ audioStream->readBuffer(buffer, file.size());
+ output.write(buffer, file.size() * 2);
+
+ delete[] buffer;
+}
+
+} // End of namespace ZVision
diff --git a/engines/zvision/utility.h b/engines/zvision/utility.h
new file mode 100644
index 0000000000..fb571f3fe8
--- /dev/null
+++ b/engines/zvision/utility.h
@@ -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.
+ *
+ *
+ */
+
+#ifndef ZVISION_UTILITY_H
+#define ZVISION_UTILITY_H
+
+#include "common/array.h"
+
+
+namespace Common {
+class String;
+}
+
+namespace ZVision {
+
+class ZVision;
+
+/**
+ * Opens the sourceFile utilizing Common::File (aka SearchMan) and writes the
+ * contents to destFile. destFile is created in the working directory
+ *
+ * @param sourceFile The 'file' you want the contents of
+ * @param destFile The name of the file where the content will be written to
+ */
+void writeFileContentsToFile(const Common::String &sourceFile, const Common::String &destFile);
+
+/**
+ * Removes any line comments using '#' as a sequence start.
+ * Then removes any trailing and leading 'whitespace' using String::trim()
+ * Note: String::trim uses isspace() to determine what is whitespace and what is not.
+ *
+ * @param string The string to modify. It is modified in place
+ */
+void trimCommentsAndWhiteSpace(Common::String *string);
+
+/**
+ * Searches through all the .scr files and dumps 'numberOfExamplesPerType' examples of each type of ResultAction
+ * ZVision::initialize() must have been called before this function can be used.
+ *
+ * @param destFile Where to write the examples
+ */
+void dumpEveryResultAction(const Common::String &destFile);
+
+/**
+ * Removes all duplicate entries from container. Relative order will be preserved.
+ *
+ * @param container The Array to remove duplicate entries from
+ */
+template<class T>
+void removeDuplicateEntries(Common::Array<T> &container) {
+ // Length of modified array
+ uint newLength = 1;
+ uint j;
+
+ for(uint i = 1; i < container.size(); i++) {
+ for(j = 0; j < newLength; j++) {
+ if (container[i] == container[j]) {
+ break;
+ }
+ }
+
+ // If none of the values in index[0..j] of container are the same as array[i],
+ // then copy the current value to corresponding new position in array
+ if (j == newLength) {
+ container[newLength++] = container[i];
+ }
+ }
+
+ // Actually remove the unneeded space
+ while (container.size() < newLength) {
+ container.pop_back();
+ }
+}
+
+/**
+ * Gets the name of the file (including extension). Forward or back slashes
+ * are interpreted as directory changes
+ *
+ * @param fullPath A full or partial path to the file. Ex: folderOne/folderTwo/file.txt
+ * @return The name of the file without any preceding directories. Ex: file.txt
+ */
+Common::String getFileName(const Common::String &fullPath);
+
+/**
+ * Converts a ZVision .RAW file to a .WAV
+ * The .WAV will be created in the working directory and will overwrite any existing file
+ *
+ * @param inputFile The path to the input .RAW file
+ * @param outputFile The name of the output .WAV file
+ */
+void convertRawToWav(const Common::String &inputFile, ZVision *engine, const Common::String &outputFile);
+
+} // End of namespace ZVision
+
+#endif
diff --git a/engines/zvision/video.cpp b/engines/zvision/video.cpp
new file mode 100644
index 0000000000..cd11618e67
--- /dev/null
+++ b/engines/zvision/video.cpp
@@ -0,0 +1,171 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/scummsys.h"
+
+#include "zvision/zvision.h"
+
+#include "zvision/clock.h"
+#include "zvision/render_manager.h"
+
+#include "common/system.h"
+
+#include "video/video_decoder.h"
+
+#include "engines/util.h"
+
+#include "graphics/surface.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..24cff27fc4
--- /dev/null
+++ b/engines/zvision/zfs_archive.cpp
@@ -0,0 +1,157 @@
+/* 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/zfs_archive.h"
+
+#include "common/memstream.h"
+#include "common/debug.h"
+#include "common/file.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(1, "ZfsArchive::ZfsArchive(%s): Located %d files", _fileName.c_str(), _entryHeaders.size());
+}
+
+ZfsArchive::ZfsArchive(const Common::String &fileName, Common::SeekableReadStream *stream) : _fileName(fileName) {
+ readHeaders(stream);
+
+ debug(1, "ZfsArchive::ZfsArchive(%s): Located %d files", _fileName.c_str(), _entryHeaders.size());
+}
+
+ZfsArchive::~ZfsArchive() {
+ debug(1, "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..d7b45e4b47
--- /dev/null
+++ b/engines/zvision/zfs_archive.h
@@ -0,0 +1,126 @@
+/* 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"
+#include "common/hash-str.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..a614f77bb7
--- /dev/null
+++ b/engines/zvision/zork_avi_decoder.cpp
@@ -0,0 +1,53 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ *
+ */
+
+#include "common/scummsys.h"
+
+#include "zvision/zork_avi_decoder.h"
+
+#include "zvision/zork_raw.h"
+
+#include "common/stream.h"
+
+#include "audio/audiostream.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..ec2be1bb13
--- /dev/null
+++ b/engines/zvision/zork_avi_decoder.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 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..55493da9b3
--- /dev/null
+++ b/engines/zvision/zork_raw.cpp
@@ -0,0 +1,209 @@
+/* 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/zork_raw.h"
+
+#include "zvision/zvision.h"
+#include "zvision/detection.h"
+#include "zvision/utility.h"
+
+#include "common/file.h"
+#include "common/str.h"
+#include "common/stream.h"
+#include "common/memstream.h"
+#include "common/bufferedstream.h"
+#include "common/util.h"
+
+#include "audio/audiostream.h"
+#include "audio/decoders/raw.h"
+
+
+namespace ZVision {
+
+const int16 RawZorkStream::_stepAdjustmentTable[8] = {-1, -1, -1, 1, 4, 7, 10, 12};
+
+const int32 RawZorkStream::_amplitudeLookupTable[89] = {0x0007, 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E,
+ 0x0010, 0x0011, 0x0013, 0x0015, 0x0017, 0x0019, 0x001C, 0x001F,
+ 0x0022, 0x0025, 0x0029, 0x002D, 0x0032, 0x0037, 0x003C, 0x0042,
+ 0x0049, 0x0050, 0x0058, 0x0061, 0x006B, 0x0076, 0x0082, 0x008F,
+ 0x009D, 0x00AD, 0x00BE, 0x00D1, 0x00E6, 0x00FD, 0x0117, 0x0133,
+ 0x0151, 0x0173, 0x0198, 0x01C1, 0x01EE, 0x0220, 0x0256, 0x0292,
+ 0x02D4, 0x031C, 0x036C, 0x03C3, 0x0424, 0x048E, 0x0502, 0x0583,
+ 0x0610, 0x06AB, 0x0756, 0x0812, 0x08E0, 0x09C3, 0x0ABD, 0x0BD0,
+ 0x0CFF, 0x0E4C, 0x0FBA, 0x114C, 0x1307, 0x14EE, 0x1706, 0x1954,
+ 0x1BDC, 0x1EA5, 0x21B6, 0x2515, 0x28CA, 0x2CDF, 0x315B, 0x364B,
+ 0x3BB9, 0x41B2, 0x4844, 0x4F7E, 0x5771, 0x602F, 0x69CE, 0x7462, 0x7FFF};
+
+const SoundParams RawZorkStream::_zNemSoundParamLookupTable[6] = {{'6', 0x2B11, false, false},
+ {'a', 0x5622, false, true},
+ {'b', 0x5622, true, true},
+ {'n', 0x2B11, false, true},
+ {'s', 0x5622, false, true},
+ {'t', 0x5622, true, true}
+};
+
+const SoundParams RawZorkStream::_zgiSoundParamLookupTable[5] = {{'a',0x5622, false, false},
+ {'k',0x2B11, true, true},
+ {'p',0x5622, false, true},
+ {'q',0x5622, true, true},
+ {'u',0xAC44, true, true}
+};
+
+RawZorkStream::RawZorkStream(uint32 rate, bool stereo, DisposeAfterUse::Flag disposeStream, Common::SeekableReadStream *stream)
+ : _rate(rate),
+ _stereo(0),
+ _stream(stream, disposeStream),
+ _endOfData(false) {
+ 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() == GID_NEMESIS) {
+ for (int i = 0; i < 6; ++i) {
+ if (RawZorkStream::_zNemSoundParamLookupTable[i].identifier == (fileName[6]))
+ soundParams = RawZorkStream::_zNemSoundParamLookupTable[i];
+ }
+ }
+ else if (engine->getGameId() == GID_GRANDINQUISITOR) {
+ for (int i = 0; i < 6; ++i) {
+ if (RawZorkStream::_zgiSoundParamLookupTable[i].identifier == (fileName[7]))
+ soundParams = RawZorkStream::_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..481ea79f2d
--- /dev/null
+++ b/engines/zvision/zork_raw.h
@@ -0,0 +1,120 @@
+/* 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 "audio/audiostream.h"
+
+
+namespace Common {
+class SeekableReadStream;
+}
+
+namespace ZVision {
+
+class ZVision;
+
+struct SoundParams {
+ char identifier;
+ uint32 rate;
+ bool stereo;
+ bool packed;
+};
+
+/**
+ * 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:
+ static const SoundParams _zNemSoundParamLookupTable[6];
+ static const SoundParams _zgiSoundParamLookupTable[5];
+
+private:
+ const int _rate; // Sample rate of stream
+ Audio::Timestamp _playtime; // Calculated total play time
+ Common::DisposablePtr<Common::SeekableReadStream> _stream; // Stream to read data from
+ bool _endOfData; // Whether the stream end has been reached
+ uint _stereo;
+
+ /**
+ * 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 dispose AfterUse 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 dispose AfterUse 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..6d8ae6d5a7
--- /dev/null
+++ b/engines/zvision/zvision.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 "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 "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"
+
+
+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),
+ _scriptManager(nullptr),
+ _renderManager(nullptr),
+ _saveManager(nullptr),
+ _stringManager(nullptr),
+ _cursorManager(nullptr) {
+
+ debug(1, "ZVision::ZVision");
+}
+
+ZVision::~ZVision() {
+ debug(1, "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);
+
+ // 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);
+ _cursorManager = new CursorManager(this, &_pixelFormat);
+
+ // Initialize the managers
+ _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..84784d9a89
--- /dev/null
+++ b/engines/zvision/zvision.h
@@ -0,0 +1,145 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ *
+ */
+
+#ifndef ZVISION_ZVISION_H
+#define ZVISION_ZVISION_H
+
+#include "zvision/detection.h"
+#include "zvision/clock.h"
+
+#include "common/random.h"
+#include "common/events.h"
+
+#include "engines/engine.h"
+
+#include "graphics/pixelformat.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;
+
+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);
+
+ 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
diff --git a/graphics/surface.cpp b/graphics/surface.cpp
index 929157203e..777c1058fb 100644
--- a/graphics/surface.cpp
+++ b/graphics/surface.cpp
@@ -133,6 +133,30 @@ const Surface Surface::getSubArea(const Common::Rect &area) const {
return subSurface;
}
+void Surface::copyRectToSurface(const void *buffer, int srcPitch, int destX, int destY, int width, int height) {
+ assert(buffer);
+
+ assert(destX >= 0 && destX < w);
+ assert(destY >= 0 && destY < h);
+ assert(height > 0 && destY + height <= h);
+ assert(width > 0 && destX + width <= w);
+
+ // Copy buffer data to internal buffer
+ const byte *src = (const byte *)buffer;
+ byte *dst = (byte *)getBasePtr(destX, destY);
+ for (int i = 0; i < height; i++) {
+ memcpy(dst, src, width * format.bytesPerPixel);
+ src += srcPitch;
+ dst += pitch;
+ }
+}
+
+void Surface::copyRectToSurface(const Graphics::Surface &srcSurface, int destX, int destY, const Common::Rect subRect) {
+ assert(srcSurface.format == format);
+
+ copyRectToSurface(srcSurface.getBasePtr(subRect.left, subRect.top), srcSurface.pitch, destX, destY, subRect.width(), subRect.height());
+}
+
void Surface::hLine(int x, int y, int x2, uint32 color) {
// Clipping
if (y < 0 || y >= h)
diff --git a/graphics/surface.h b/graphics/surface.h
index b08d4a5cb7..07e289b0bb 100644
--- a/graphics/surface.h
+++ b/graphics/surface.h
@@ -209,6 +209,30 @@ public:
const Surface getSubArea(const Common::Rect &area) const;
/**
+ * Copies a bitmap to the Surface internal buffer. The pixel format
+ * of buffer must match the pixel format of the Surface.
+ *
+ * @param buffer The buffer containing the graphics data source
+ * @param pitch The pitch of the buffer (number of bytes in a scanline)
+ * @param destX The x coordinate of the destination rectangle
+ * @param destY The y coordinate of the destination rectangle
+ * @param width The width of the destination rectangle
+ * @param height The height of the destination rectangle
+ */
+ void copyRectToSurface(const void *buffer, int srcPitch, int destX, int destY, int width, int height);
+ /**
+ * Copies a bitmap to the Surface internal buffer. The pixel format
+ * of buffer must match the pixel format of the Surface.
+ *
+ * @param srcSurface The source of the bitmap data
+ * @param destX The x coordinate of the destination rectangle
+ * @param destY The y coordinate of the destination rectangle
+ * @param subRect The subRect of surface to be blitted
+ * @return
+ */
+ void copyRectToSurface(const Graphics::Surface &srcSurface, int destX, int destY, const Common::Rect subRect);
+
+ /**
* Convert the data to another pixel format.
*
* This works in-place. This means it will not create an additional buffer
diff --git a/gui/credits.h b/gui/credits.h
index 0797dd458c..aca3745631 100644
--- a/gui/credits.h
+++ b/gui/credits.h
@@ -303,6 +303,9 @@ static const char *credits[] = {
"A0""Einar Johan T. Somaaen",
"C0""Einar Johan T. S\370m\345en",
"",
+"C1""ZVision",
+"C0""Adrian Astley",
+"",
"",
"C1""Backend Teams",
"C1""Android",
@@ -777,6 +780,8 @@ static const char *credits[] = {
"C2""For additional work on the original MT-32 emulator",
"C0""James Woodcock",
"C2""Soundtrack enhancements",
+"C0""Anton Yartsev",
+"C2""For the original re-implementation of the ZVision engine",
"C0""Tony Warriner and everyone at Revolution Software Ltd. for sharing with us the source of some of their brilliant games, allowing us to release Beneath a Steel Sky as freeware... and generally being supportive above and beyond the call of duty.",
"C0""",
"C0""John Passfield and Steve Stamatiadis for sharing the source of their classic title, Flight of the Amazon Queen and also being incredibly supportive.",
diff --git a/video/avi_decoder.cpp b/video/avi_decoder.cpp
index 5e4b91d1bd..aee2d88988 100644
--- a/video/avi_decoder.cpp
+++ b/video/avi_decoder.cpp
@@ -101,6 +101,10 @@ AVIDecoder::~AVIDecoder() {
close();
}
+AVIDecoder::AVIAudioTrack *AVIDecoder::createAudioTrack(AVIStreamHeader sHeader, PCMWaveFormat wvInfo) {
+ return new AVIAudioTrack(sHeader, wvInfo, _soundType);
+}
+
void AVIDecoder::initCommon() {
_decodedHeader = false;
_foundMovieList = false;
@@ -293,7 +297,7 @@ void AVIDecoder::handleStreamHeader(uint32 size) {
if (wvInfo.channels == 2)
sHeader.sampleSize /= 2;
- addTrack(new AVIAudioTrack(sHeader, wvInfo, _soundType));
+ addTrack(createAudioTrack(sHeader, wvInfo));
}
// Ensure that we're at the end of the chunk
diff --git a/video/avi_decoder.h b/video/avi_decoder.h
index fffcbfbe16..80c11b1e09 100644
--- a/video/avi_decoder.h
+++ b/video/avi_decoder.h
@@ -74,7 +74,6 @@ protected:
void readNextPacket();
bool seekIntern(const Audio::Timestamp &time);
-private:
struct BitmapInfoHeader {
uint32 size;
uint32 width;
@@ -203,7 +202,7 @@ private:
AVIAudioTrack(const AVIStreamHeader &streamHeader, const PCMWaveFormat &waveFormat, Audio::Mixer::SoundType soundType);
~AVIAudioTrack();
- void queueSound(Common::SeekableReadStream *stream);
+ virtual void queueSound(Common::SeekableReadStream *stream);
Audio::Mixer::SoundType getSoundType() const { return _soundType; }
void skipAudio(const Audio::Timestamp &time, const Audio::Timestamp &frameTime);
void resetStream();
@@ -214,7 +213,6 @@ private:
protected:
Audio::AudioStream *getAudioStream() const;
- private:
// Audio Codecs
enum {
kWaveFormatNone = 0,
@@ -249,6 +247,9 @@ private:
void handleStreamHeader(uint32 size);
uint16 getStreamType(uint32 tag) const { return tag & 0xFFFF; }
byte getStreamIndex(uint32 tag) const;
+
+public:
+ virtual AVIAudioTrack *createAudioTrack(AVIStreamHeader sHeader, PCMWaveFormat wvInfo);
};
} // End of namespace Video
diff --git a/video/codecs/truemotion1.h b/video/codecs/truemotion1.h
index b2a35cf873..6ac09af24b 100644
--- a/video/codecs/truemotion1.h
+++ b/video/codecs/truemotion1.h
@@ -22,8 +22,8 @@
// Based on the TrueMotion 1 decoder by Alex Beregszaszi & Mike Melanson in FFmpeg
-// Only compile if SCI32 is enabled or if we're building dynamic modules
-#if defined(ENABLE_SCI32) || defined(DYNAMIC_MODULES)
+// Only compile if SCI32 is enabled, ZVISION is enabled, or if we're building dynamic modules
+#if defined(ENABLE_SCI32) || defined(ENABLE_ZVISION) || defined(DYNAMIC_MODULES)
#ifndef VIDEO_CODECS_TRUEMOTION1_H
#define VIDEO_CODECS_TRUEMOTION1_H