aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--engines/cryomni3d/configure.engine4
-rw-r--r--engines/cryomni3d/cryomni3d.cpp411
-rw-r--r--engines/cryomni3d/cryomni3d.h164
-rw-r--r--engines/cryomni3d/detection.cpp255
-rw-r--r--engines/cryomni3d/detection_tables.h114
-rw-r--r--engines/cryomni3d/dialogs_manager.cpp555
-rw-r--r--engines/cryomni3d/dialogs_manager.h132
-rw-r--r--engines/cryomni3d/fixed_image.cpp308
-rw-r--r--engines/cryomni3d/fixed_image.h128
-rw-r--r--engines/cryomni3d/font_manager.cpp340
-rw-r--r--engines/cryomni3d/font_manager.h120
-rw-r--r--engines/cryomni3d/image/codecs/hlz.cpp140
-rw-r--r--engines/cryomni3d/image/codecs/hlz.h53
-rw-r--r--engines/cryomni3d/image/hlz.cpp67
-rw-r--r--engines/cryomni3d/image/hlz.h69
-rw-r--r--engines/cryomni3d/module.mk38
-rw-r--r--engines/cryomni3d/mouse_boxes.cpp95
-rw-r--r--engines/cryomni3d/mouse_boxes.h73
-rw-r--r--engines/cryomni3d/objects.cpp107
-rw-r--r--engines/cryomni3d/objects.h102
-rw-r--r--engines/cryomni3d/omni3d.cpp275
-rw-r--r--engines/cryomni3d/omni3d.h83
-rw-r--r--engines/cryomni3d/sprites.cpp218
-rw-r--r--engines/cryomni3d/sprites.h102
-rw-r--r--engines/cryomni3d/versailles/data.cpp1018
-rw-r--r--engines/cryomni3d/versailles/dialogs.cpp318
-rw-r--r--engines/cryomni3d/versailles/dialogs_manager.cpp379
-rw-r--r--engines/cryomni3d/versailles/dialogs_manager.h68
-rw-r--r--engines/cryomni3d/versailles/documentation.cpp2070
-rw-r--r--engines/cryomni3d/versailles/documentation.h143
-rw-r--r--engines/cryomni3d/versailles/engine.cpp1443
-rw-r--r--engines/cryomni3d/versailles/engine.h453
-rw-r--r--engines/cryomni3d/versailles/logic.cpp1036
-rw-r--r--engines/cryomni3d/versailles/menus.cpp1066
-rw-r--r--engines/cryomni3d/versailles/music.cpp281
-rw-r--r--engines/cryomni3d/versailles/saveload.cpp315
-rw-r--r--engines/cryomni3d/versailles/toolbar.cpp599
-rw-r--r--engines/cryomni3d/versailles/toolbar.h117
-rw-r--r--engines/cryomni3d/video/hnm_decoder.cpp392
-rw-r--r--engines/cryomni3d/video/hnm_decoder.h129
-rw-r--r--engines/cryomni3d/wam_parser.cpp216
-rw-r--r--engines/cryomni3d/wam_parser.h81
42 files changed, 14077 insertions, 0 deletions
diff --git a/engines/cryomni3d/configure.engine b/engines/cryomni3d/configure.engine
new file mode 100644
index 0000000000..eac62b6e8b
--- /dev/null
+++ b/engines/cryomni3d/configure.engine
@@ -0,0 +1,4 @@
+# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps]
+add_engine cryomni3d "Cryo Omni3D games" no "versailles" "" "highres"
+add_engine versailles "Versailles 1685" no
+
diff --git a/engines/cryomni3d/cryomni3d.cpp b/engines/cryomni3d/cryomni3d.cpp
new file mode 100644
index 0000000000..49347a2cd9
--- /dev/null
+++ b/engines/cryomni3d/cryomni3d.cpp
@@ -0,0 +1,411 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/scummsys.h"
+#include "common/error.h"
+#include "common/system.h"
+#include "common/textconsole.h"
+#include "common/translation.h"
+#include "common/debug-channels.h"
+
+#include "common/events.h"
+#include "common/file.h"
+
+#include "audio/mixer.h"
+#include "graphics/palette.h"
+
+#include "cryomni3d/cryomni3d.h"
+
+#include "cryomni3d/image/hlz.h"
+#include "cryomni3d/video/hnm_decoder.h"
+
+#include <stdarg.h> // For va_list etc.
+
+namespace CryOmni3D {
+
+CryOmni3DEngine::CryOmni3DEngine(OSystem *syst,
+ const CryOmni3DGameDescription *gamedesc) : Engine(syst), _gameDescription(gamedesc),
+ _fontManager(), _sprites(), _dragStatus(kDragStatus_NoDrag), _autoRepeatNextEvent(-1u) {
+ if (!_mixer->isReady()) {
+ error("Sound initialization failed");
+ }
+
+ // Setup mixer
+ syncSoundSettings();
+
+ unlockPalette();
+
+ DebugMan.addDebugChannel(kDebugFile, "File", "Track File Accesses");
+ DebugMan.addDebugChannel(kDebugVariable, "Variable", "Track Variable Accesses");
+ DebugMan.addDebugChannel(kDebugSaveLoad, "SaveLoad", "Track Save/Load Function");
+}
+
+CryOmni3DEngine::~CryOmni3DEngine() {
+ DebugMan.clearAllDebugChannels();
+}
+
+Common::Error CryOmni3DEngine::run() {
+ return Common::kNoError;
+}
+
+void CryOmni3DEngine::pauseEngineIntern(bool pause) {
+ Engine::pauseEngineIntern(pause);
+
+ /*
+ if (pause) {
+ _video->pauseVideos();
+ } else {
+ _video->resumeVideos();
+ _system->updateScreen();
+ }
+ */
+}
+
+void CryOmni3DEngine::playHNM(const Common::String &filename, Audio::Mixer::SoundType soundType) {
+ Common::String fname(filename);
+
+ Video::VideoDecoder *videoDecoder = new Video::HNMDecoder();
+ videoDecoder->setSoundType(soundType);
+
+ int lastDotPos = fname.size() - 1;
+ for (; lastDotPos >= 0; --lastDotPos) {
+ if (fname[lastDotPos] == '.') {
+ break;
+ }
+ }
+ if (lastDotPos > -1) {
+ fname.erase(lastDotPos);
+ } else {
+ lastDotPos = fname.size();
+ }
+ fname += ".hnm";
+
+ if (!Common::File::exists(fname)) {
+ fname.erase(lastDotPos);
+ fname += ".hns";
+ }
+
+ if (!videoDecoder->loadFile(fname)) {
+ warning("Failed to open movie file %s/%s", filename.c_str(), fname.c_str());
+ delete videoDecoder;
+ return;
+ }
+
+ videoDecoder->start();
+
+ uint16 width = videoDecoder->getWidth();
+ uint16 height = videoDecoder->getHeight();
+
+ bool skipVideo = false;
+ while (!g_engine->shouldQuit() && !videoDecoder->endOfVideo() && !skipVideo) {
+ if (videoDecoder->needsUpdate()) {
+ const Graphics::Surface *frame = videoDecoder->decodeNextFrame();
+
+ if (frame) {
+ if (videoDecoder->hasDirtyPalette()) {
+ const byte *palette = videoDecoder->getPalette();
+ setPalette(palette, 0, 256);
+ }
+
+ // TODO: beforeDraw
+ g_system->copyRectToScreen(frame->getPixels(), frame->pitch, 0, 0, width, height);
+ // TODO: afterDraw
+
+ }
+ }
+ g_system->updateScreen();
+
+ if (pollEvents() && checkKeysPressed()) {
+ skipVideo = true;
+ }
+ }
+
+ delete videoDecoder;
+}
+
+Image::ImageDecoder *CryOmni3DEngine::loadHLZ(const Common::String &filename) {
+ Common::String fname(filename);
+
+ Image::ImageDecoder *imageDecoder = new Image::HLZFileDecoder();
+
+ Common::File file;
+
+ int lastDotPos = fname.size() - 1;
+ for (; lastDotPos >= 0; --lastDotPos) {
+ if (fname[lastDotPos] == '.') {
+ break;
+ }
+ }
+ if (lastDotPos > -1) {
+ fname.erase(lastDotPos);
+ }
+ fname += ".hlz";
+
+ if (!file.open(fname)) {
+ warning("Failed to open hlz file %s/%s", filename.c_str(), fname.c_str());
+ return nullptr;
+ }
+
+ if (!imageDecoder->loadStream(file)) {
+ warning("Failed to open hlz file %s", fname.c_str());
+ delete imageDecoder;
+ imageDecoder = 0;
+ return nullptr;
+ }
+
+ return imageDecoder;
+}
+
+void CryOmni3DEngine::displayHLZ(const Common::String &filename) {
+ Image::ImageDecoder *imageDecoder = loadHLZ(filename);
+
+ if (!imageDecoder) {
+ return;
+ }
+
+ if (imageDecoder->hasPalette()) {
+ const byte *palette = imageDecoder->getPalette();
+ setPalette(palette, imageDecoder->getPaletteStartIndex(), imageDecoder->getPaletteColorCount());
+ }
+
+ const Graphics::Surface *frame = imageDecoder->getSurface();
+ g_system->copyRectToScreen(frame->getPixels(), frame->pitch, 0, 0, frame->w, frame->h);
+ g_system->updateScreen();
+
+ bool exitImg = false;
+ while (!g_engine->shouldQuit() && !exitImg) {
+ if (pollEvents()) {
+ if (checkKeysPressed(1, Common::KEYCODE_ESCAPE) || getCurrentMouseButton() == 1) {
+ exitImg = true;
+ }
+ }
+ g_system->updateScreen();
+ }
+
+ delete imageDecoder;
+}
+
+void CryOmni3DEngine::setCursor(const Graphics::Cursor &cursor) const {
+ g_system->setMouseCursor(cursor.getSurface(), cursor.getWidth(), cursor.getHeight(),
+ cursor.getHotspotX(), cursor.getHotspotY(), cursor.getKeyColor());
+}
+
+void CryOmni3DEngine::setCursor(unsigned int cursorId) const {
+ const Graphics::Cursor &cursor = _sprites.getCursor(cursorId);
+ g_system->setMouseCursor(cursor.getSurface(), cursor.getWidth(), cursor.getHeight(),
+ cursor.getHotspotX(), cursor.getHotspotY(), cursor.getKeyColor());
+}
+
+bool CryOmni3DEngine::pollEvents() {
+ Common::Event event;
+ bool hasEvents = false;
+
+ unsigned int oldMouseButton = getCurrentMouseButton();
+
+ while (g_system->getEventManager()->pollEvent(event)) {
+ if (event.type == Common::EVENT_KEYDOWN) {
+ _keysPressed.push(event.kbd);
+ }
+ hasEvents = true;
+ }
+ g_system->delayMillis(10);
+
+ _dragStatus = kDragStatus_NoDrag;
+ unsigned int currentMouseButton = getCurrentMouseButton();
+ if (!oldMouseButton && currentMouseButton == 1) {
+ // Starting the drag
+ _dragStatus = kDragStatus_Pressed;
+ _dragStart = getMousePos();
+ } else if (oldMouseButton == 1) {
+ // We were already pressing
+ if (currentMouseButton == 1) {
+ // We are still pressing
+ Common::Point delta = _dragStart - getMousePos();
+ if (ABS(delta.x) > 2 || ABS(delta.y) > 2) {
+ // We moved from the start point
+ _dragStatus = kDragStatus_Dragging;
+ } else if (_autoRepeatNextEvent != -1u) {
+ // Check for auto repeat duration
+ if (_autoRepeatNextEvent < g_system->getMillis()) {
+ _dragStatus = kDragStatus_Pressed;
+ }
+ }
+ } else {
+ // We just finished dragging
+ _dragStatus = kDragStatus_Finished;
+ // Cancel auto repeat
+ _autoRepeatNextEvent = -1;
+ }
+ }
+ // Else we weren't dragging and still aren't
+
+ return hasEvents;
+}
+
+void CryOmni3DEngine::setAutoRepeatClick(unsigned int millis) {
+ _autoRepeatNextEvent = g_system->getMillis() + millis;
+}
+
+unsigned int CryOmni3DEngine::getCurrentMouseButton() {
+ int mask = g_system->getEventManager()->getButtonState();
+ if (mask & 0x1) {
+ return 1;
+ } else if (mask & 0x2) {
+ return 2;
+ } else {
+ return 0;
+ }
+}
+
+void CryOmni3DEngine::waitMouseRelease() {
+ while (g_system->getEventManager()->getButtonState() != 0 && !g_engine->shouldQuit()) {
+ pollEvents();
+ }
+}
+
+void CryOmni3DEngine::setMousePos(const Common::Point &point) {
+ g_system->warpMouse(point.x, point.y);
+ // Ensure to update mouse position in event manager
+ pollEvents();
+}
+
+Common::Point CryOmni3DEngine::getMousePos() {
+ return g_system->getEventManager()->getMousePos();
+}
+
+Common::KeyState CryOmni3DEngine::getNextKey() {
+ if (_keysPressed.empty()) {
+ return Common::KeyState();
+ } else {
+ return _keysPressed.pop();
+ }
+}
+
+bool CryOmni3DEngine::checkKeysPressed() {
+ Common::KeyCode kc = getNextKey().keycode;
+ if (kc != Common::KEYCODE_INVALID) {
+ clearKeys();
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool CryOmni3DEngine::checkKeysPressed(unsigned int numKeys, ...) {
+ bool found = false;
+ Common::KeyCode kc = getNextKey().keycode;
+ while (!found && kc != Common::KEYCODE_INVALID) {
+ va_list va;
+ va_start(va, numKeys);
+ for (unsigned int i = 0; i < numKeys; i++) {
+ // Compiler says that KeyCode is promoted to int, so we need this ugly cast
+ Common::KeyCode match = (Common::KeyCode) va_arg(va, int);
+ if (match == kc) {
+ found = true;
+ break;
+ }
+ }
+ va_end(va);
+ kc = getNextKey().keycode;
+ }
+ clearKeys();
+ return found;
+}
+
+void CryOmni3DEngine::copySubPalette(byte *dst, const byte *src, uint start, uint num) {
+ memcpy(&dst[3 * start], &src[3 * start], 3 * num * sizeof(*dst));
+}
+
+void CryOmni3DEngine::setPalette(const byte *colors, uint start, uint num) {
+ if (start < _lockPaletteStartRW) {
+ colors = colors + 3 * (_lockPaletteStartRW - start);
+ start = _lockPaletteStartRW;
+ }
+ uint end = start + num - 1;
+ if (end > _lockPaletteEndRW) {
+ num = num - (end - _lockPaletteEndRW);
+ end = _lockPaletteEndRW;
+ }
+ g_system->getPaletteManager()->setPalette(colors, start, num);
+ // Don't update screen there: palette will be updated with next updateScreen call
+}
+
+void CryOmni3DEngine::fadeOutPalette() {
+ byte palOut[256 * 3];
+ uint16 palWork[256 * 3];
+ uint16 delta[256 * 3];
+
+ g_system->getPaletteManager()->grabPalette(palOut, 0, 256);
+ for (unsigned int i = 0; i < 256 * 3; i++) {
+ palWork[i] = palOut[i] << 8;
+ delta[i] = palWork[i] / 25;
+ }
+
+ for (unsigned int step = 0; step < 25 && !g_engine->shouldQuit(); step++) {
+ for (unsigned int i = 0; i < 256 * 3; i++) {
+ palWork[i] -= delta[i];
+ palOut[i] = palWork[i] >> 8;
+ }
+ setPalette(palOut, 0, 256);
+ g_system->updateScreen();
+ g_system->delayMillis(50);
+ }
+ setBlackPalette();
+}
+
+void CryOmni3DEngine::fadeInPalette(const byte *palette) {
+ byte palOut[256 * 3];
+ uint16 palWork[256 * 3];
+ uint16 delta[256 * 3];
+
+ memset(palOut, 0, sizeof(palOut));
+ memset(palWork, 0, sizeof(palWork));
+ for (unsigned int i = 0; i < 256 * 3; i++) {
+ delta[i] = (palette[i] << 8) / 25;
+ }
+
+ setBlackPalette();
+ for (unsigned int step = 0; step < 25 && !g_engine->shouldQuit(); step++) {
+ for (unsigned int i = 0; i < 256 * 3; i++) {
+ palWork[i] += delta[i];
+ palOut[i] = palWork[i] >> 8;
+ }
+ setPalette(palOut, 0, 256);
+ g_system->updateScreen();
+ g_system->delayMillis(50);
+ }
+ setPalette(palette, 0, 256);
+ g_system->updateScreen();
+}
+
+void CryOmni3DEngine::setBlackPalette() {
+ byte pal[256 * 3];
+ memset(pal, 0, 256 * 3);
+ g_system->getPaletteManager()->setPalette(pal, 0, 256);
+ g_system->updateScreen();
+}
+
+void CryOmni3DEngine::fillSurface(byte color) {
+ g_system->fillScreen(color);
+ g_system->updateScreen();
+}
+} // End of namespace CryOmni3D
diff --git a/engines/cryomni3d/cryomni3d.h b/engines/cryomni3d/cryomni3d.h
new file mode 100644
index 0000000000..0220001eae
--- /dev/null
+++ b/engines/cryomni3d/cryomni3d.h
@@ -0,0 +1,164 @@
+/* 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 CRYOMNI3D_CRYOMNI3D_H
+#define CRYOMNI3D_CRYOMNI3D_H
+
+#include "audio/mixer.h"
+
+#include "common/array.h"
+#include "common/keyboard.h"
+#include "common/queue.h"
+#include "common/rect.h"
+#include "common/scummsys.h"
+
+#include "engines/engine.h"
+
+#include "font_manager.h"
+#include "objects.h"
+#include "sprites.h"
+
+class OSystem;
+
+namespace Common {
+class Point;
+class SeekableReadStream;
+}
+
+namespace Image {
+class ImageDecoder;
+}
+
+/**
+ * This is the namespace of the Cryo Omni3D engine.
+ *
+ * Status of this engine: ???
+ *
+ * Games using this engine:
+ * - Versailles
+ * - ...
+ */
+namespace CryOmni3D {
+
+enum CryOmni3DGameType {
+ GType_VERSAILLES
+};
+
+struct CryOmni3DGameDescription;
+
+// Engine Debug Flags
+enum {
+ kDebugFile = (1 << 0),
+ kDebugVariable = (1 << 1),
+ kDebugSaveLoad = (1 << 2)
+};
+
+enum DragStatus {
+ kDragStatus_NoDrag = 0,
+ kDragStatus_Pressed,
+ kDragStatus_Finished,
+ kDragStatus_Dragging
+};
+
+class CryOmni3DEngine : public ::Engine {
+protected:
+ virtual Common::Error run();
+
+public:
+ CryOmni3DEngine(OSystem *syst, const CryOmni3DGameDescription *gamedesc);
+ virtual ~CryOmni3DEngine();
+
+ // Detection related functions
+ const CryOmni3DGameDescription *_gameDescription;
+ const char *getGameId() const;
+ uint32 getFeatures() const;
+ const char *getAppName() const;
+ uint16 getVersion() const;
+ Common::Platform getPlatform() const;
+ uint8 getGameType() const;
+ Common::Language getLanguage() const;
+
+ bool hasFeature(EngineFeature f) const;
+
+private:
+ void pauseEngineIntern(bool);
+
+public:
+ Image::ImageDecoder *loadHLZ(const Common::String &filename);
+
+ void fillSurface(byte color);
+ void setCursor(const Graphics::Cursor &cursor) const;
+ void setCursor(unsigned int cursorId) const;
+ void playHNM(const Common::String &filename,
+ Audio::Mixer::SoundType soundType = Audio::Mixer::kPlainSoundType);
+ void displayHLZ(const Common::String &filename);
+
+ bool pollEvents();
+ Common::Point getMousePos();
+ void setMousePos(const Common::Point &point);
+ unsigned int getCurrentMouseButton();
+ Common::KeyState getNextKey();
+ bool checkKeysPressed();
+ bool checkKeysPressed(unsigned int numKeys, ...);
+ void clearKeys() { _keysPressed.clear(); }
+ void waitMouseRelease();
+ void setAutoRepeatClick(unsigned int millis);
+ DragStatus getDragStatus() { return _dragStatus; }
+
+ virtual bool displayToolbar(const Graphics::Surface *original) = 0;
+ virtual bool hasPlaceDocumentation() = 0;
+ virtual bool displayPlaceDocumentation() = 0;
+ virtual unsigned int displayOptions() = 0;
+ virtual bool shouldAbort() { return g_engine->shouldQuit(); }
+
+ virtual void makeTranslucent(Graphics::Surface &dst, const Graphics::Surface &src) const = 0;
+ virtual void setupPalette(const byte *colors, uint start, uint num) = 0;
+
+protected:
+ void copySubPalette(byte *dst, const byte *src, uint start, uint num);
+ void setPalette(const byte *colors, uint start, uint num);
+ void lockPalette(uint startRW, uint endRW) { _lockPaletteStartRW = startRW; _lockPaletteEndRW = endRW; }
+ void unlockPalette() { _lockPaletteStartRW = 0; _lockPaletteEndRW = 255; }
+ void fadeOutPalette();
+ void fadeInPalette(const byte *colors);
+ void setBlackPalette();
+
+protected:
+ FontManager _fontManager;
+ Sprites _sprites;
+ Objects _objects;
+ Inventory _inventory;
+
+ Common::Queue<Common::KeyState> _keysPressed;
+
+ DragStatus _dragStatus;
+ Common::Point _dragStart;
+ unsigned int _autoRepeatNextEvent;
+
+private:
+ unsigned int _lockPaletteStartRW;
+ unsigned int _lockPaletteEndRW;
+};
+
+} // End of namespace CryOmni3D
+
+#endif
diff --git a/engines/cryomni3d/detection.cpp b/engines/cryomni3d/detection.cpp
new file mode 100644
index 0000000000..62bdebd072
--- /dev/null
+++ b/engines/cryomni3d/detection.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 "base/plugins.h"
+
+#include "engines/advancedDetector.h"
+#include "common/savefile.h"
+#include "common/system.h"
+#include "common/textconsole.h"
+#include "common/translation.h"
+
+#include "cryomni3d/cryomni3d.h"
+
+#ifdef ENABLE_VERSAILLES
+ #include "cryomni3d/versailles/engine.h"
+#endif
+
+namespace CryOmni3D {
+
+struct CryOmni3DGameDescription {
+ ADGameDescription desc;
+
+ uint8 gameType;
+ uint32 features;
+ const char *appName;
+};
+
+const char *CryOmni3DEngine::getGameId() const {
+ return _gameDescription->desc.gameId;
+}
+
+uint32 CryOmni3DEngine::getFeatures() const {
+ return _gameDescription->features;
+}
+
+Common::Platform CryOmni3DEngine::getPlatform() const {
+ return _gameDescription->desc.platform;
+}
+
+const char *CryOmni3DEngine::getAppName() const {
+ return _gameDescription->appName;
+}
+
+uint8 CryOmni3DEngine::getGameType() const {
+ return _gameDescription->gameType;
+}
+
+Common::Language CryOmni3DEngine::getLanguage() const {
+ return _gameDescription->desc.language;
+}
+
+bool CryOmni3DEngine::hasFeature(EngineFeature f) const {
+ return false;
+// (f == kSupportsRTL);
+}
+
+/*
+#ifdef ENABLE_MYST
+
+bool MohawkEngine_Myst::hasFeature(EngineFeature f) const {
+ return
+ MohawkEngine::hasFeature(f)
+ || (f == kSupportsLoadingDuringRuntime)
+ || (f == kSupportsSavingDuringRuntime);
+}
+
+#endif
+*/
+
+} // End of Namespace CryOmni3D
+
+static const PlainGameDescriptor cryomni3DGames[] = {
+ {"versailles", "Versailles 1685"},
+ {0, 0}
+};
+
+#include "cryomni3d/detection_tables.h"
+
+/*
+static const char *directoryGlobs[] = {
+ "all",
+ "assets1",
+ "data",
+ "program",
+ "95instal",
+ "Rugrats Adventure Game",
+ 0
+};
+*/
+
+static const ADExtraGuiOptionsMap optionsList[] = {
+ /*{
+ GAMEOPTION_PLAY_MYST_FLYBY,
+ {
+ _s("Play the Myst fly by movie"),
+ _s("The Myst fly by movie was not played by the original engine."),
+ "playmystflyby",
+ false
+ }
+ },*/
+
+ AD_EXTRA_GUI_OPTIONS_TERMINATOR
+};
+
+class CryOmni3DMetaEngine : public AdvancedMetaEngine {
+public:
+ CryOmni3DMetaEngine() : AdvancedMetaEngine(CryOmni3D::gameDescriptions,
+ sizeof(CryOmni3D::CryOmni3DGameDescription), cryomni3DGames, optionsList) {
+ //_singleId = "cryomni3d";
+ _maxScanDepth = 2;
+ //_directoryGlobs = directoryGlobs;
+ }
+
+ ADDetectedGame fallbackDetect(const FileMap &allFiles,
+ const Common::FSList &fslist) const override {
+ return detectGameFilebased(allFiles, fslist, CryOmni3D::fileBased);
+ }
+
+ virtual const char *getName() const {
+ return "Cryo Omni3D";
+ }
+
+ virtual const char *getOriginalCopyright() const {
+ return "Cryo game Engine (C) 1997-2002 Cryo Interactive";
+ }
+
+ virtual bool hasFeature(MetaEngineFeature f) const;
+ virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const;
+ virtual SaveStateList listSaves(const char *target) const;
+ SaveStateList listSavesForPrefix(const char *prefix, const char *extension) const;
+ virtual int getMaximumSaveSlot() const { return 999; }
+ virtual void removeSaveState(const char *target, int slot) const;
+ virtual SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const;
+};
+
+bool CryOmni3DMetaEngine::hasFeature(MetaEngineFeature f) const {
+ return
+ (f == kSupportsListSaves)
+ || (f == kSupportsLoadingDuringStartup)
+ || (f == kSupportsDeleteSave)
+ || (f == kSavesSupportMetaInfo)
+ || (f == kSavesSupportThumbnail)
+ || (f == kSavesSupportCreationDate)
+ || (f == kSavesSupportPlayTime);
+}
+
+SaveStateList CryOmni3DMetaEngine::listSavesForPrefix(const char *prefix,
+ const char *extension) const {
+ Common::String pattern = Common::String::format("%s-###.%s", prefix, extension);
+ Common::StringArray filenames = g_system->getSavefileManager()->listSavefiles(pattern);
+ size_t prefixLen = strlen(prefix);
+
+ SaveStateList saveList;
+ for (Common::StringArray::const_iterator filename = filenames.begin(); filename != filenames.end();
+ ++filename) {
+ // Extract the slot number from the filename
+ char slot[4];
+ slot[0] = (*filename)[prefixLen + 1];
+ slot[1] = (*filename)[prefixLen + 2];
+ slot[2] = (*filename)[prefixLen + 3];
+ slot[3] = '\0';
+
+ int slotNum = atoi(slot);
+
+ saveList.push_back(SaveStateDescriptor(slotNum, ""));
+ }
+
+ Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator());
+
+ return saveList;
+}
+
+SaveStateList CryOmni3DMetaEngine::listSaves(const char *target) const {
+ SaveStateList saveList;
+
+ /*
+ // Loading games is only supported in Myst/Riven currently.
+ saveList = listSavesForPrefix("myst", "mys");
+
+ for (SaveStateList::iterator save = saveList.begin(); save != saveList.end(); ++save) {
+ // Read the description from the save
+ int slot = save->getSaveSlot();
+ Common::String description = Mohawk::MystGameState::querySaveDescription(slot);
+ save->setDescription(description);
+ }
+ */
+
+ return saveList;
+}
+
+void CryOmni3DMetaEngine::removeSaveState(const char *target, int slot) const {
+
+ /*
+ // Removing saved games is only supported in Myst/Riven currently.
+ if (strstr(target, "myst")) {
+ Mohawk::MystGameState::deleteSave(slot);
+ }
+ */
+}
+
+SaveStateDescriptor CryOmni3DMetaEngine::querySaveMetaInfos(const char *target, int slot) const {
+ /*
+ if (strstr(target, "myst")) {
+ return Mohawk::MystGameState::querySaveMetaInfos(slot);
+ }
+ */
+ return SaveStateDescriptor();
+}
+
+bool CryOmni3DMetaEngine::createInstance(OSystem *syst, Engine **engine,
+ const ADGameDescription *desc) const {
+ const CryOmni3D::CryOmni3DGameDescription *gd = (const CryOmni3D::CryOmni3DGameDescription *)desc;
+
+ if (gd) {
+ switch (gd->gameType) {
+ case CryOmni3D::GType_VERSAILLES:
+#ifdef ENABLE_VERSAILLES
+ *engine = new CryOmni3D::Versailles::CryOmni3DEngine_Versailles(syst, gd);
+ break;
+#else
+ warning("Versailles support not compiled in");
+ return false;
+#endif
+ default:
+ error("Unknown Cryo Omni3D Engine");
+ }
+ }
+
+ return (gd != 0);
+}
+
+#if PLUGIN_ENABLED_DYNAMIC(CRYOMNI3D)
+ REGISTER_PLUGIN_DYNAMIC(CRYOMNI3D, PLUGIN_TYPE_ENGINE, CryOmni3DMetaEngine);
+#else
+ REGISTER_PLUGIN_STATIC(CRYOMNI3D, PLUGIN_TYPE_ENGINE, CryOmni3DMetaEngine);
+#endif
diff --git a/engines/cryomni3d/detection_tables.h b/engines/cryomni3d/detection_tables.h
new file mode 100644
index 0000000000..49a7cb00da
--- /dev/null
+++ b/engines/cryomni3d/detection_tables.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.
+ *
+ */
+
+namespace CryOmni3D {
+
+//#define GAMEOPTION_PLAY_MYST_FLYBY GUIO_GAMEOPTIONS1
+
+//#define GUI_OPTIONS_MYST GUIO3(GUIO_NOASPECT, GUIO_NOSUBTITLES, GUIO_NOMIDI)
+
+static const CryOmni3DGameDescription gameDescriptions[] = {
+ // Versailles 1685
+ // French Windows 95
+ // From lePhilousophe
+ {
+ {
+ "versailles",
+ "",
+ AD_ENTRY1s("VERSAILL.EXE", "3775004b96f056716ce615b458b1f394", 372736),
+ Common::FR_FRA,
+ Common::kPlatformWindows,
+ ADGF_UNSTABLE,
+ GUIO1(GUIO_NOASPECT)
+ },
+ GType_VERSAILLES,
+ 0,
+ 0,
+ },
+
+ // Versailles 1685
+ // French Windows 95 compressed
+ // From lePhilousophe
+ {
+ {
+ "versailles",
+ "",
+ AD_ENTRY1s("PROGRAM.Z", "a07b5d86af5f3a8883ba97db2bade87d", 293223),
+ Common::FR_FRA,
+ Common::kPlatformWindows,
+ ADGF_UNSTABLE,
+ GUIO1(GUIO_NOASPECT)
+ },
+ GType_VERSAILLES,
+ 0,
+ 0,
+ },
+
+ // Versailles 1685
+ // French DOS
+ // From lePhilousophe
+ {
+ {
+ "versailles",
+ "",
+ AD_ENTRY1s("VERSAILL.PGM", "1c992f034f43418a5da2e8ebd0b92620", 630431),
+ Common::FR_FRA,
+ Common::kPlatformDOS,
+ ADGF_UNSTABLE,
+ GUIO1(GUIO_NOASPECT)
+ },
+ GType_VERSAILLES,
+ 0,
+ 0,
+ },
+
+ { AD_TABLE_END_MARKER, 0, 0, 0 }
+};
+
+//////////////////////////////
+//Fallback detection
+//////////////////////////////
+
+static const CryOmni3DGameDescription fallbackDescs[] = {
+ {
+ {
+ "versailles",
+ "unknown",
+ AD_ENTRY1(0, 0),
+ Common::UNK_LANG,
+ Common::kPlatformWindows,
+ ADGF_UNSTABLE,
+ GUIO1(GUIO_NOASPECT)
+ },
+ GType_VERSAILLES,
+ 0,
+ 0
+ },
+};
+
+static const ADFileBasedFallback fileBased[] = {
+ { &fallbackDescs[0].desc, { "VERSAILL.EXE", 0 } },
+ { &fallbackDescs[0].desc, { "VERSAILL.PGM", 0 } },
+ { 0, { 0 } }
+};
+
+} // End of Namespace CryOmni3D
diff --git a/engines/cryomni3d/dialogs_manager.cpp b/engines/cryomni3d/dialogs_manager.cpp
new file mode 100644
index 0000000000..6c1c2ac5ad
--- /dev/null
+++ b/engines/cryomni3d/dialogs_manager.cpp
@@ -0,0 +1,555 @@
+/* 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 "cryomni3d/dialogs_manager.h"
+
+#include "common/debug.h"
+#include "common/file.h"
+
+namespace CryOmni3D {
+
+DialogsManager::~DialogsManager() {
+ delete[] _gtoBuffer;
+}
+
+void DialogsManager::loadGTO(const Common::String &gtoFileName) {
+ Common::File gtoFile;
+ if (!gtoFile.open(gtoFileName)) {
+ error("Can't open GTO file '%s'", gtoFileName.c_str());
+ }
+
+ _labels.clear();
+ _gtoEnd = nullptr;
+ delete[] _gtoBuffer;
+ _gtoBuffer = nullptr;
+
+ unsigned int gtoSize = gtoFile.size();
+ _gtoBuffer = new char[gtoSize];
+ gtoFile.read(_gtoBuffer, gtoSize);
+ gtoFile.close();
+
+ _gtoEnd = _gtoBuffer + gtoSize;
+
+ populateLabels();
+}
+
+void DialogsManager::populateLabels() {
+ /* Get labels count and populate the labels array */
+ unsigned int numLabels;
+ const char *labelsP = strstr(_gtoBuffer, "LABELS=");
+ if (labelsP) {
+ labelsP += sizeof("LABELS=") - 1;
+ for (; *labelsP == ' '; labelsP++) { }
+ numLabels = atoi(labelsP);
+ } else {
+ numLabels = 0;
+ }
+
+ for (const char *labelP = _gtoBuffer; labelP != nullptr; labelP = nextLine(labelP)) {
+ if (*labelP == ':') {
+ /* Line starting with ':', it's a label */
+ _labels.push_back(nextChar(labelP));
+ }
+ }
+
+ if (_labels.size() != numLabels) {
+ error("Bad labels number in GTO");
+ }
+}
+
+const char *DialogsManager::findLabel(const char *label, const char **realLabel) const {
+ unsigned int labelLen = 0;
+ /* Truncate input label */
+ for (const char *labelP = label;
+ *labelP != '\0' &&
+ *labelP != ' ' &&
+ *labelP != '.' &&
+ *labelP != '\r'; labelP++, labelLen++) { }
+
+ Common::Array<const char *>::const_iterator labelsIt;
+ for (labelsIt = _labels.begin(); labelsIt != _labels.end(); labelsIt++) {
+ if (!strncmp(*labelsIt, label, labelLen)) {
+ break;
+ }
+ }
+
+ if (labelsIt == _labels.end()) {
+ error("Label not found");
+ }
+
+ if (realLabel) {
+ *realLabel = *labelsIt;
+ }
+ return nextLine(*labelsIt);
+}
+
+Common::String DialogsManager::getLabelSound(const char *label) const {
+ /* Remove starting : if any */
+ if (*label == ':') {
+ label++;
+ }
+
+ const char *labelEnd;
+ for (labelEnd = label; *labelEnd >= '0' && *labelEnd <= 'Z'; labelEnd++) { }
+
+ return Common::String(label, labelEnd);
+}
+
+const char *DialogsManager::findSequence(const char *sequence) const {
+ unsigned int sequenceLen = strlen(sequence);
+
+ const char *lineP;
+ for (lineP = _gtoBuffer; lineP != nullptr; lineP = nextLine(lineP)) {
+ if (!strncmp(lineP, sequence, sequenceLen)) {
+ /* Line starting with the sequence name */
+ break;
+ }
+ }
+
+ if (!lineP) {
+ return nullptr;
+ }
+
+ /* Find next label */
+ for (; lineP != nullptr && *lineP != ':'; lineP = nextLine(lineP)) { }
+
+ /* Return the label name without it's ':' */
+ return nextChar(lineP);
+}
+
+Common::String DialogsManager::findVideo(const char *data) const {
+ data = previousMatch(data, ".FLC");
+ if (data == nullptr) {
+ return nullptr;
+ }
+
+ // Video name is without the extension
+ const char *end = data;
+
+ for (; data >= _gtoBuffer && *data != '\r'; data--) { }
+ data++;
+
+ if (data < _gtoBuffer || *data == '.') {
+ return Common::String();
+ }
+
+ return Common::String(data, end);
+}
+
+Common::String DialogsManager::getText(const char *text) const {
+ /* Skip '<' */
+ text = nextChar(text);
+
+ if (text == nullptr) {
+ return Common::String();
+ }
+
+ const char *end;
+ for (end = text; end < _gtoEnd && *end != '>'; end++) { }
+
+ if (end == _gtoEnd) {
+ return Common::String();
+ }
+
+ return Common::String(text, end);
+}
+
+void DialogsManager::reinitVariables() {
+ for (Common::Array<DialogVariable>::iterator it = _dialogsVariables.begin();
+ it != _dialogsVariables.end(); it++) {
+ it->value = 'N';
+ }
+}
+
+const DialogsManager::DialogVariable &DialogsManager::find(const Common::String &name) const {
+ for (Common::Array<DialogVariable>::const_iterator it = _dialogsVariables.begin();
+ it != _dialogsVariables.end(); it++) {
+ if (it->name == name) {
+ return *it;
+ }
+ }
+ error("Can't find dialog variable %s", name.c_str());
+}
+
+DialogsManager::DialogVariable &DialogsManager::find(const Common::String &name) {
+ for (Common::Array<DialogVariable>::iterator it = _dialogsVariables.begin();
+ it != _dialogsVariables.end(); it++) {
+ if (it->name == name) {
+ return *it;
+ }
+ }
+ error("Can't find dialog variable %s", name.c_str());
+}
+
+const char *DialogsManager::nextLine(const char *currentPtr) const {
+ for (; currentPtr < _gtoEnd && *currentPtr != '\r'; currentPtr++) { }
+
+ /* Go after the \r */
+ return nextChar(currentPtr);
+}
+
+const char *DialogsManager::nextChar(const char *currentPtr) const {
+ if (currentPtr == nullptr || currentPtr < _gtoBuffer || currentPtr >= _gtoEnd) {
+ return nullptr;
+ }
+
+ currentPtr++;
+
+ if (currentPtr >= _gtoEnd) {
+ return nullptr;
+ } else {
+ return currentPtr;
+ }
+}
+
+const char *DialogsManager::previousMatch(const char *currentPtr, const char *str) const {
+ if (currentPtr == nullptr || currentPtr >= _gtoEnd || currentPtr < _gtoBuffer) {
+ return nullptr;
+ }
+
+ unsigned int matchLen = strlen(str);
+ for (; currentPtr >= _gtoBuffer; currentPtr--) {
+ if (*currentPtr == str[0]) {
+ if (!strncmp(currentPtr, str, matchLen)) {
+ break;
+ }
+ }
+ }
+
+ if (currentPtr < _gtoBuffer) {
+ return nullptr;
+ } else {
+ return currentPtr;
+ }
+}
+
+bool DialogsManager::play(const Common::String &sequence, bool &slowStop) {
+ const char *label = findSequence(sequence.c_str());
+
+ if (!label) {
+ error("Can't find sequence '%s' in GTO", sequence.c_str());
+ }
+
+ Common::String video = sequence;
+
+ const char *text = findLabel(label);
+
+ slowStop = false;
+ bool playerLabel = !strncmp(label, "JOU", 3);
+ bool didSomething = false;
+ bool finished = false;
+ while (!finished) {
+ const char *actions;
+ if (playerLabel) {
+ /* If sequence begins with a player label go to action directly */
+ playerLabel = false;
+ actions = text;
+ // Maybe a bug in original game, we should go to next line
+ } else if (!strncmp(text, "<#>", 3)) {
+ /* Text is empty: go to action directly */
+ actions = nextLine(text);
+ } else {
+ /* Real text, play video */
+ video = findVideo(text);
+ Common::String properText = getText(text);
+ Common::String sound = getLabelSound(label);
+ Common::HashMap<Common::String, SubtitlesSettings>::const_iterator settingsIt =
+ _subtitlesSettings.find(video);
+ if (settingsIt == _subtitlesSettings.end()) {
+ settingsIt = _subtitlesSettings.find("default");
+ }
+ if (settingsIt == _subtitlesSettings.end()) {
+ error("No video settings for %s", video.c_str());
+ }
+ playDialog(video, sound, properText, settingsIt->_value);
+ didSomething = true;
+ actions = nextLine(text);
+ }
+ Common::Array<DialogsManager::Goto> gotoList = executeAfterPlayAndBuildGotoList(actions);
+ Common::StringArray questions;
+ bool endOfConversationFound = false;;
+ if (_ignoreNoEndOfConversation) {
+ // Don't check if there is an end, so, there is one
+ endOfConversationFound = true;
+ }
+ for (Common::Array<DialogsManager::Goto>::iterator it = gotoList.begin(); it != gotoList.end();
+ it++) {
+ if (!endOfConversationFound && it->label.hasPrefix("JOU")) {
+ // No need to get the real label here, we just need to know if the question ends up
+ if (!executePlayerQuestion(it->text, true)) {
+ endOfConversationFound = true;
+ }
+ }
+ assert(it->text);
+ const char *questionStart = it->text + 1;
+ const char *questionEnd = questionStart;
+ for (; *questionEnd != '>'; questionEnd++) { }
+ questions.push_back(Common::String(questionStart, questionEnd));
+ }
+ unsigned int eocInserted = -1;
+ if (!endOfConversationFound && questions.size() > 0) {
+ eocInserted = questions.size();
+ questions.push_back(_endOfConversationText);
+ }
+ if (questions.size() == 0) {
+ // There are no choices, just quit with a pause to avoid abrupt ending
+ slowStop = true;
+ break;
+ }
+
+ if (gotoList[0].label.hasPrefix("JOU")) {
+ // We must give a subject
+ unsigned int playerChoice = askPlayerQuestions(video, questions);
+ didSomething = true;
+ // -1 when shouldQuit
+ if (playerChoice == -1u || playerChoice == eocInserted) {
+ break;
+ }
+
+ text = executePlayerQuestion(gotoList[playerChoice].text, false, &label);
+ if (!text) {
+ break;
+ }
+ } else if (gotoList[0].label.hasPrefix("MES")) {
+ // Display a simple message
+ const char *messageStart = gotoList[0].text + 1;
+ const char *messageEnd = messageStart;
+ for (; *messageEnd != '>'; messageEnd++) { }
+ displayMessage(Common::String(messageStart, messageEnd));
+ break;
+ } else {
+ // Unattended conversation: two NPC speak
+ label = gotoList[0].label.c_str();
+ text = gotoList[0].text;
+ }
+ }
+ return didSomething;
+}
+
+Common::Array<DialogsManager::Goto> DialogsManager::executeAfterPlayAndBuildGotoList(
+ const char *actions) {
+ Common::Array<DialogsManager::Goto> gotos;
+
+ for (; actions && *actions != ':'; actions = nextLine(actions)) {
+ if (!strncmp(actions, "GOTO ", 5)) {
+ buildGotoGoto(actions, gotos);
+ break;
+ } else if (!strncmp(actions, "IF ", 3)) {
+ if (buildGotoIf(actions, gotos)) {
+ break;
+ }
+ } else if (!strncmp(actions, "LET ", 4)) {
+ executeLet(actions);
+ } else if (!strncmp(actions, "SHOW ", 5)) {
+ executeShow(actions);
+ }
+ }
+ return gotos;
+}
+
+void DialogsManager::buildGotoGoto(const char *gotoLine, Common::Array<Goto> &gotos) {
+ Common::String label;
+ gotoLine = gotoLine + 5;
+ while (true) {
+ const char *labelEnd = gotoLine;
+ for (labelEnd = gotoLine; *labelEnd >= '0' && *labelEnd <= 'Z'; labelEnd++) { }
+ label = Common::String(gotoLine, labelEnd);
+
+ if (label == "REM") {
+ break;
+ }
+
+ // To build goto list, no need to get back the real label position
+ const char *text = findLabel(label.c_str());
+ gotos.push_back(Goto(label, text));
+
+ if (*labelEnd == '.') {
+ if (!strncmp(labelEnd, ".WAV", 4)) {
+ labelEnd += 4;
+ } else {
+ debug("Problem with GOTO.WAV: '%s'", gotoLine);
+ }
+ }
+ for (; *labelEnd == ' ' || *labelEnd == ','; labelEnd++) { }
+
+ if (*labelEnd == '\r') {
+ break;
+ }
+
+ // Next goto tag
+ gotoLine = labelEnd;
+ }
+}
+
+bool DialogsManager::buildGotoIf(const char *ifLine, Common::Array<Goto> &gotos) {
+ ifLine += 3;
+
+ bool finishedConditions = false;
+ while (!finishedConditions) {
+ const char *endVar = ifLine;
+ const char *equalPos;
+ // Find next '='
+ for (; *endVar != '='; endVar++) { }
+ equalPos = endVar;
+ // Strip spaces at the end
+ endVar--;
+ for (; *endVar == ' '; endVar--) { }
+ endVar++;
+ Common::String variable(ifLine, endVar);
+
+ const char *testValue = equalPos + 1;
+ for (; *testValue == ' ' || *testValue == '\t'; testValue++) { }
+
+ byte value = (*this)[variable];
+
+ if (value != *testValue) {
+ // IF is not taken, go to next line
+ return false;
+ }
+
+ ifLine = testValue + 1;
+ for (; *ifLine == ' ' || *ifLine == '\t'; ifLine++) { }
+
+ if (!strncmp(ifLine, "AND IF ", 7)) {
+ ifLine += 7;
+ } else {
+ finishedConditions = true;
+ }
+ }
+
+ /* We are in the (implicit) THEN part of the IF
+ * ifLine points to the instruction */
+ if (!strncmp(ifLine, "GOTO", 4)) {
+ buildGotoGoto(ifLine, gotos);
+ } else if (!strncmp(ifLine, "LET", 3)) {
+ executeLet(ifLine);
+ } else if (!strncmp(ifLine, "SHOW", 4)) {
+ executeShow(ifLine);
+ } else {
+ debug("Invalid IF line: %s", ifLine);
+ return false;
+ }
+
+ return true;
+}
+
+void DialogsManager::executeLet(const char *letLine) {
+ letLine = letLine + 4;
+
+ const char *endVar = letLine;
+ const char *equalPos;
+ // Find next '='
+ for (; *endVar != '='; endVar++) { }
+ equalPos = endVar;
+ // Strip spaces at the end
+ endVar--;
+ for (; *endVar == ' '; endVar--) { }
+ endVar++;
+ Common::String variable(letLine, endVar);
+
+ (*this)[variable] = equalPos[1];
+}
+
+void DialogsManager::executeShow(const char *showLine) {
+ showLine = showLine + 5;
+
+ const char *endShow = showLine;
+ // Find next ')' and include it
+ for (; *endShow != ')'; endShow++) { }
+ endShow++;
+
+ Common::String show(showLine, endShow);
+
+ executeShow(show);
+}
+
+const char *DialogsManager::executePlayerQuestion(const char *text, bool dryRun,
+ const char **realLabel) {
+ // Go after the text
+ const char *actions = nextLine(text);
+
+ while (actions && *actions != ':') {
+ if (!strncmp(actions, "IF ", 3)) {
+ actions = parseIf(actions);
+ } else if (!strncmp(actions, "LET ", 4)) {
+ if (!dryRun) {
+ executeLet(actions);
+ }
+ actions = nextLine(actions);
+ } else if (!strncmp(actions, "GOTO ", 5)) {
+ return findLabel(actions + 5, realLabel);
+ } else {
+ actions = nextLine(actions);
+ }
+ }
+
+ // There were no GOTO, so it's the end of the conversation
+ return nullptr;
+}
+
+const char *DialogsManager::parseIf(const char *ifLine) {
+ ifLine += 3;
+
+ bool finishedConditions = false;
+ while (!finishedConditions) {
+ const char *endVar = ifLine;
+ const char *equalPos;
+ // Find next '='
+ for (; *endVar != '='; endVar++) { }
+ equalPos = endVar;
+ // Strip spaces at the end
+ endVar--;
+ for (; *endVar == ' '; endVar--) { }
+ endVar++;
+ Common::String variable(ifLine, endVar);
+
+ const char *testValue = equalPos + 1;
+ for (; *testValue == ' ' || *testValue == '\t'; testValue++) { }
+
+ byte value = (*this)[variable];
+
+ if (value != *testValue) {
+ // IF is not taken, go to next line
+ return nextLine(ifLine);
+ }
+
+ ifLine = testValue + 1;
+ for (; *ifLine == ' ' || *ifLine == '\t'; ifLine++) { }
+
+ if (!strncmp(ifLine, "AND IF ", 7)) {
+ ifLine += 7;
+ } else {
+ finishedConditions = true;
+ }
+ }
+
+ /* We are in the (implicit) THEN part of the IF
+ * ifLine points to the instruction */
+ return ifLine;
+}
+
+void DialogsManager::registerSubtitlesSettings(const Common::String &videoName,
+ const SubtitlesSettings &settings) {
+ _subtitlesSettings[videoName] = settings;
+}
+
+} // End of namespace CryOmni3D
diff --git a/engines/cryomni3d/dialogs_manager.h b/engines/cryomni3d/dialogs_manager.h
new file mode 100644
index 0000000000..ce84dcd378
--- /dev/null
+++ b/engines/cryomni3d/dialogs_manager.h
@@ -0,0 +1,132 @@
+/* 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 CRYOMNI3D_DIALOGS_MANAGER_H
+#define CRYOMNI3D_DIALOGS_MANAGER_H
+
+#include "common/array.h"
+#include "common/hash-str.h"
+#include "common/hashmap.h"
+#include "common/rect.h"
+#include "common/str.h"
+#include "common/str-array.h"
+
+namespace CryOmni3D {
+
+class DialogsManager {
+public:
+ struct SubtitlesSettings {
+ SubtitlesSettings() { }
+ SubtitlesSettings(int16 textLeft, int16 textTop, int16 textRight, int16 textBottom,
+ int16 drawLeft, int16 drawTop, int16 drawRight, int16 drawBottom) :
+ textRect(textLeft, textTop, textRight, textBottom), drawRect(drawLeft, drawTop, drawRight,
+ drawBottom) { }
+ Common::Rect textRect;
+ Common::Rect drawRect;
+ };
+
+ DialogsManager() : _gtoBuffer(nullptr), _gtoEnd(nullptr),
+ _ignoreNoEndOfConversation(false) { }
+ virtual ~DialogsManager();
+
+ void init(unsigned int size, const Common::String &endOfConversationText) { _dialogsVariables.resize(size); _endOfConversationText = endOfConversationText; }
+ void loadGTO(const Common::String &gtoFile);
+
+ void setupVariable(unsigned int id, const Common::String &variable) { _dialogsVariables[id] = DialogVariable(variable, 'N'); }
+ void reinitVariables();
+ unsigned int size() const { return _dialogsVariables.size(); }
+ byte &operator[](unsigned int idx) { return _dialogsVariables[idx].value; }
+ const byte &operator[](unsigned int idx) const { return _dialogsVariables[idx].value; }
+ byte &operator[](const Common::String &name) { return find(name).value; }
+ const byte &operator[](const Common::String &name) const { return find(name).value; }
+
+ void registerSubtitlesSettings(const Common::String &videoName, const SubtitlesSettings &settings);
+
+ bool play(const Common::String &sequence, bool &slowStop);
+
+protected:
+ virtual void executeShow(const Common::String &show) = 0;
+ virtual void playDialog(const Common::String &video, const Common::String &sound,
+ const Common::String &text, const SubtitlesSettings &settings) = 0;
+ virtual void displayMessage(const Common::String &text) = 0;
+ virtual unsigned int askPlayerQuestions(const Common::String &video,
+ const Common::StringArray &questions) = 0;
+
+private:
+ struct Goto {
+ Goto() : label(), text(nullptr) {
+ }
+ Goto(const Common::String &label_, const char *text_) : label(label_), text(text_) {
+ }
+
+ Common::String label;
+ const char *text;
+ };
+
+ struct DialogVariable {
+ DialogVariable() : name(), value(0) {
+ }
+ DialogVariable(const Common::String &name_, byte value_) : name(name_), value(value_) {
+ }
+
+ Common::String name;
+ byte value;
+ };
+
+ const DialogVariable &find(const Common::String &name) const;
+ DialogVariable &find(const Common::String &name);
+ Common::Array<DialogVariable> _dialogsVariables;
+
+ void populateLabels();
+ const char *findLabel(const char *label, const char **realLabel = nullptr) const;
+ Common::String getLabelSound(const char *label) const;
+
+ const char *findSequence(const char *sequence) const;
+ Common::String findVideo(const char *data) const;
+ Common::String getText(const char *text) const;
+
+ Common::Array<Goto> executeAfterPlayAndBuildGotoList(const char *actions);
+ void buildGotoGoto(const char *gotoLine, Common::Array<Goto> &gotos);
+ bool buildGotoIf(const char *ifLine, Common::Array<Goto> &gotos);
+ void executeLet(const char *letLine);
+ void executeShow(const char *showLine);
+
+ const char *executePlayerQuestion(const char *text, bool dryRun, const char **realLabel = nullptr);
+ const char *parseIf(const char *ifLine);
+
+ const char *nextLine(const char *currentPtr) const;
+ const char *nextChar(const char *currentPtr) const;
+ const char *previousMatch(const char *currentPtr, const char *str) const;
+
+ char *_gtoBuffer;
+ const char *_gtoEnd;
+ Common::Array<const char *> _labels;
+
+ Common::String _endOfConversationText;
+ bool _ignoreNoEndOfConversation;
+
+ Common::HashMap<Common::String, SubtitlesSettings> _subtitlesSettings;
+};
+
+} // End of namespace CryOmni3D
+
+#endif
diff --git a/engines/cryomni3d/fixed_image.cpp b/engines/cryomni3d/fixed_image.cpp
new file mode 100644
index 0000000000..fa2b4a2002
--- /dev/null
+++ b/engines/cryomni3d/fixed_image.cpp
@@ -0,0 +1,308 @@
+/* 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 "engines/cryomni3d/fixed_image.h"
+
+#include "common/file.h"
+#include "common/system.h"
+#include "graphics/surface.h"
+#include "image/image_decoder.h"
+
+namespace CryOmni3D {
+
+ZonFixedImage::ZonFixedImage(CryOmni3DEngine &engine,
+ Inventory &inventory,
+ const Sprites &sprites,
+ const FixedImageConfiguration *configuration) :
+ _engine(engine), _inventory(inventory), _sprites(sprites),
+ _configuration(configuration),
+ _callback(nullptr), _imageDecoder(nullptr), _imageSurface(nullptr) {
+}
+
+ZonFixedImage::~ZonFixedImage() {
+ delete _imageDecoder;
+}
+
+void ZonFixedImage::run(const Common::Functor1<ZonFixedImage *, void> *callback) {
+ _exit = false;
+ _zonesMode = kZonesMode_None;
+
+ _callback = callback;
+
+ g_system->showMouse(true);
+ while (!_exit) {
+ (*_callback)(this);
+ }
+ _engine.waitMouseRelease();
+ g_system->showMouse(false);
+
+ // Deselect object
+ _inventory.setSelectedObject(nullptr);
+
+ delete _callback;
+ _callback = nullptr;
+}
+
+void ZonFixedImage::load(const Common::String &image) {
+ _imageSurface = nullptr;
+ delete _imageDecoder;
+ _imageDecoder = nullptr;
+
+ _imageDecoder = _engine.loadHLZ(image);
+ if (!_imageDecoder) {
+ error("Can't display fixed image");
+ }
+ _imageSurface = _imageDecoder->getSurface();
+
+ loadZones(image);
+#if 0
+ // This is not correct but to debug zones I think it's OK
+ Graphics::Surface *tmpSurf = (Graphics::Surface *) _imageSurface;
+ for (Common::Array<Zone>::const_iterator it = _zones.begin(); it != _zones.end(); it++) {
+ Common::Rect tmp = it->rect;
+ tmpSurf->frameRect(tmp, 244);
+ }
+#endif
+
+ _zonesMode = kZonesMode_Standard;
+ _refreshCursor = true;
+
+ display();
+
+ // WORKAROUND: Wait for release after displaying the fixed image to avoid handling events due to mouse being pressed
+ // There is this bug in game
+ // Don't display cursor to prevent displaying an invalid cursor
+ g_system->showMouse(false);
+ g_system->updateScreen();
+ _engine.waitMouseRelease();
+ g_system->showMouse(true);
+}
+
+void ZonFixedImage::display() const {
+ _engine.setupPalette(_imageDecoder->getPalette(), _imageDecoder->getPaletteStartIndex(),
+ _imageDecoder->getPaletteColorCount());
+
+ g_system->copyRectToScreen(_imageSurface->getPixels(), _imageSurface->pitch, 0, 0,
+ _imageSurface->w, _imageSurface->h);
+ g_system->updateScreen();
+}
+
+void ZonFixedImage::loadZones(const Common::String &image) {
+ _zones.clear();
+
+ Common::String fname(image);
+
+ int lastDotPos = fname.size() - 1;
+ for (; lastDotPos >= 0; --lastDotPos) {
+ if (fname[lastDotPos] == '.') {
+ break;
+ }
+ }
+ if (lastDotPos > -1) {
+ fname.erase(lastDotPos);
+ fname += ".zon";
+ }
+
+ Common::File zonFile;
+ if (!zonFile.open(fname)) {
+ error("Can't open ZON file '%s'", fname.c_str());
+ }
+
+ int32 zonesNumber = zonFile.size() / 26;
+ _zones.reserve(zonesNumber);
+
+ _highLeftId = -1;
+ _highRightId = -1;
+
+ int leftSeen = 0x7fffffff; // MAX_INT
+ int rightSeen = 0;
+ Common::Array<Zone>::size_type index = 0;
+
+ while (zonesNumber > 0) {
+ Zone zone;
+ zone.rect.left = zonFile.readSint16BE();
+ zone.rect.top = zonFile.readSint16BE();
+ zone.rect.right = zonFile.readSint16BE();
+ zone.rect.bottom = zonFile.readSint16BE();
+ zone.spriteId = zonFile.readSint16BE();
+ zone.cursorId = _sprites.revMapSpriteId(zone.spriteId);
+ zone.valid = true;
+ zonFile.skip(16);
+
+ _zones.push_back(zone);
+
+ if (zone.cursorId == _configuration->spriteHigh) {
+ if (leftSeen > zone.rect.right) {
+ // The right side is at the leftest seen
+ leftSeen = zone.rect.right;
+ _highLeftId = index;
+ }
+ if (rightSeen < zone.rect.left) {
+ // The left side is at the rightest seen
+ rightSeen = zone.rect.left;
+ _highRightId = index;
+ }
+ }
+
+ zonesNumber--;
+ index++;
+ }
+}
+
+Common::Point ZonFixedImage::getZoneCenter(unsigned int zoneId) const {
+ if (zoneId >= _zones.size()) {
+ error("Invalid zoneId %u/%u", zoneId, _zones.size());
+ }
+ const Common::Rect &rect = _zones[zoneId].rect;
+
+ return Common::Point((rect.left + rect.right) / 2, (rect.top + rect.bottom) / 2);
+}
+
+void ZonFixedImage::manage() {
+ _currentZone = -1;
+ _zoneLow = false;
+ _zoneHigh = false;
+ _zoneHighLeft = false;
+ _zoneHighRight = false;
+ _zoneLeft = false;
+ _zoneRight = false;
+ _zoneQuestion = false;
+ _zoneListen = false;
+ _zoneSee = false;
+ _zoneUse = false;
+ _zoneSpeak = false;
+ _usedObject = nullptr;
+
+ // Force poll events even when we must refresh the cursor
+ if (!_engine.pollEvents() && !_refreshCursor) {
+ g_system->updateScreen();
+ // TODO: countdown even when no events
+ return;
+ }
+ _refreshCursor = false;
+
+ // Feed the key for the caller
+ _key = _engine.getNextKey();
+ Common::Point mousePos = _engine.getMousePos();
+
+ if (_key == Common::KEYCODE_ESCAPE) {
+ _exit = true;
+ return;
+ } else if (_engine.shouldAbort()) {
+ _exit = true;
+ return;
+ }
+
+ if (_key == Common::KEYCODE_SPACE ||
+ _engine.getCurrentMouseButton() == 2 ||
+ mousePos.y > _configuration->toolbarTriggerY) {
+ bool mustRedraw = _engine.displayToolbar(_imageSurface);
+ // We just came back from toolbar: check if an object is selected and go into object mode
+ if (_inventory.selectedObject()) {
+ _zonesMode = kZonesMode_Object;
+ }
+ if (mustRedraw) {
+ display();
+ }
+ // Return without any event to redo the loop and force refresh
+ _refreshCursor = true;
+ return;
+ }
+
+ Common::Array<Zone>::iterator zoneIt;
+ for (zoneIt = _zones.begin(); zoneIt != _zones.end(); zoneIt++) {
+ if (zoneIt->valid && zoneIt->rect.contains(mousePos)) {
+ break;
+ }
+ }
+
+ if (zoneIt != _zones.end()) {
+ _currentZone = zoneIt - _zones.begin();
+ } else {
+ _currentZone = -1;
+ }
+
+ if (_zonesMode == kZonesMode_Standard) {
+ if (zoneIt != _zones.end()) {
+ _engine.setCursor(zoneIt->cursorId);
+ if (_engine.getCurrentMouseButton() == 1) {
+ handleMouseZones(zoneIt);
+ }
+ } else {
+ _engine.setCursor(_configuration->spriteNothing);
+ }
+ } else if (_zonesMode == kZonesMode_Object) {
+ Object *selectedObj = _inventory.selectedObject();
+ if (!selectedObj) {
+ // Normally useless but we never know
+ _engine.setCursor(_configuration->spriteNothing);
+ } else if (zoneIt != _zones.end()) {
+ _engine.setCursor(selectedObj->idSA());
+ if (_engine.getDragStatus() == kDragStatus_Finished) {
+ // Just clicked, store the event and go back to standard mode
+ _usedObject = selectedObj;
+ _zonesMode = kZonesMode_Standard;
+ // We changed mode: need to refresh
+ _refreshCursor = true;
+ }
+ } else {
+ _engine.setCursor(selectedObj->idSl());
+ }
+
+ }
+
+ // TODO: handle countdown
+ g_system->updateScreen();
+}
+
+void ZonFixedImage::handleMouseZones(const Common::Array<Zone>::const_iterator &currentZone) {
+ if (currentZone->cursorId == _configuration->spriteLow) {
+ _zoneLow = true;
+ } else if (currentZone->cursorId == _configuration->spriteHigh) {
+ Common::Array<Zone>::size_type id = currentZone - _zones.begin();
+ if (id == _highLeftId) {
+ _zoneHighLeft = true;
+ } else if (id == _highRightId) {
+ _zoneHighRight = true;
+ } else {
+ _zoneHigh = true;
+ }
+ } else if (currentZone->cursorId == _configuration->spriteLeft) {
+ _zoneLeft = true;
+ } else if (currentZone->cursorId == _configuration->spriteRight) {
+ _zoneRight = true;
+ } else if (currentZone->cursorId == _configuration->spriteQuestion) {
+ _zoneQuestion = true;
+ } else if (currentZone->cursorId == _configuration->spriteListen) {
+ _zoneListen = true;
+ } else if (currentZone->cursorId == _configuration->spriteSee) {
+ _zoneSee = true;
+ } else if (currentZone->cursorId == _configuration->spriteUse) {
+ _zoneUse = true;
+ } else if (currentZone->cursorId == _configuration->spriteSpeak) {
+ _zoneSpeak = true;
+ } else {
+ error("Invalid cursor ID: %d in ImgFix", currentZone->cursorId);
+ }
+}
+
+} // End of namespace CryOmni3D
diff --git a/engines/cryomni3d/fixed_image.h b/engines/cryomni3d/fixed_image.h
new file mode 100644
index 0000000000..5690788463
--- /dev/null
+++ b/engines/cryomni3d/fixed_image.h
@@ -0,0 +1,128 @@
+/* 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 CRYOMNI3D_FIXED_IMAGE_H
+#define CRYOMNI3D_FIXED_IMAGE_H
+
+#include "common/func.h"
+
+#include "engines/cryomni3d/cryomni3d.h"
+#include "engines/cryomni3d/objects.h"
+
+namespace Graphics {
+class Surface;
+}
+
+namespace CryOmni3D {
+
+struct FixedImageConfiguration {
+ unsigned int spriteNothing;
+ unsigned int spriteLow;
+ unsigned int spriteHigh;
+ unsigned int spriteLeft;
+ unsigned int spriteRight;
+ unsigned int spriteQuestion;
+ unsigned int spriteListen;
+ unsigned int spriteSee;
+ unsigned int spriteUse;
+ unsigned int spriteSpeak;
+
+ int16 toolbarTriggerY;
+};
+
+class ZonFixedImage {
+public:
+ typedef Common::Functor1<ZonFixedImage *, void> CallbackFunctor;
+ enum ZonesMode {
+ kZonesMode_None = 0,
+ kZonesMode_Standard,
+ kZonesMode_Object
+ };
+
+ /* These functions are used in main engine code */
+ ZonFixedImage(CryOmni3DEngine &engine, Inventory &inventory, const Sprites &sprites,
+ const FixedImageConfiguration *configuration);
+ ~ZonFixedImage();
+
+ void run(const CallbackFunctor *callback);
+
+ /* THis function is used to refresh image after various events */
+ void display() const;
+
+ /* These functions and attributes are used in image handler */
+ void load(const Common::String &image);
+ void manage();
+ const Graphics::Surface *surface() const { return _imageSurface; }
+ void changeCallback(CallbackFunctor *callback) { delete _callback; _callback = callback; }
+ Common::Point getZoneCenter(unsigned int zoneId) const;
+
+ ZonesMode _zonesMode;
+
+ /* These attributes are read by the image handler to check what action player did */
+ unsigned int _currentZone;
+ bool _exit;
+ bool _zoneLow;
+ bool _zoneHigh;
+ bool _zoneHighLeft;
+ bool _zoneHighRight;
+ bool _zoneLeft;
+ bool _zoneRight;
+ bool _zoneQuestion;
+ bool _zoneListen;
+ bool _zoneSee;
+ bool _zoneUse;
+ bool _zoneSpeak;
+ Object *_usedObject;
+ Common::KeyState _key;
+
+protected:
+ const Common::Functor1<ZonFixedImage *, void> *_callback;
+ CryOmni3DEngine &_engine;
+ Inventory &_inventory;
+ const Sprites &_sprites;
+
+ struct Zone {
+ Common::Rect rect;
+ /* ZON file stores the index in the sprite */
+ uint16 spriteId;
+ uint16 cursorId;
+ bool valid;
+ };
+
+ void loadZones(const Common::String &image);
+ void handleMouseZones(const Common::Array<Zone>::const_iterator &currentZone);
+
+ Image::ImageDecoder *_imageDecoder;
+ const Graphics::Surface *_imageSurface;
+
+ Common::Array<Zone> _zones;
+ Common::Array<Zone>::size_type _highLeftId;
+ Common::Array<Zone>::size_type _highRightId;
+
+ const FixedImageConfiguration *_configuration;
+
+ bool _refreshCursor;
+};
+
+} // End of namespace CryOmni3D
+
+#endif
diff --git a/engines/cryomni3d/font_manager.cpp b/engines/cryomni3d/font_manager.cpp
new file mode 100644
index 0000000000..0b3b1a53fe
--- /dev/null
+++ b/engines/cryomni3d/font_manager.cpp
@@ -0,0 +1,340 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/debug.h"
+#include "common/file.h"
+#include "graphics/managed_surface.h"
+
+#include "engines/cryomni3d/font_manager.h"
+
+namespace CryOmni3D {
+
+FontManager::FontManager() : _currentFont(nullptr), _transparentBackground(false),
+ _spaceWidth(0), _charSpacing(0), _lineHeight(30), _foreColor(0), _blockTextRemaining(nullptr) {
+}
+
+FontManager::~FontManager() {
+ for (Common::Array<Font *>::iterator it = _fonts.begin(); it != _fonts.end(); it++) {
+ delete *it;
+ }
+}
+
+void FontManager::loadFonts(const Common::Array<Common::String> &fontFiles) {
+ _fonts.reserve(_fonts.size() + fontFiles.size());
+
+ for (Common::Array<Common::String>::const_iterator it = fontFiles.begin(); it != fontFiles.end();
+ it++) {
+ Common::File font_fl;
+ //debug("Open font file %s", it->c_str());
+ if (!font_fl.open(*it)) {
+ error("Can't open file %s", it->c_str());
+ }
+ loadFont(font_fl);
+ }
+}
+
+void FontManager::loadFont(Common::ReadStream &font_fl) {
+ byte magic[8];
+
+ font_fl.read(magic, sizeof(magic));
+ if (memcmp(magic, "CRYOFONT", 8)) {
+ error("Invalid font magic");
+ }
+
+ // 3 unknown uint16
+ font_fl.readUint16BE();
+ font_fl.readUint16BE();
+ font_fl.readUint16BE();
+
+ Font *font = new Font();
+
+ font->maxHeight = font_fl.readSint16BE();
+ //debug("Max char height %d", font.maxHeight);
+
+ font_fl.read(font->comment, sizeof(font->comment));
+ //debug("Comment %s", font.comment);
+
+ for (unsigned int i = 0; i < Font::kCharactersCount; i++) {
+ uint16 h = font_fl.readUint16BE();
+ uint16 w = font_fl.readUint16BE();
+ unsigned int sz = font->chars[i].setup(w, h);
+ //debug("Char %d sz %dx%d %d", i, w, h, sz);
+ font->chars[i].offX = font_fl.readSint16BE();
+ font->chars[i].offY = font_fl.readSint16BE();
+ font->chars[i].printedWidth = font_fl.readUint16BE();
+ //debug("Char %d offX %d offY %d PW %d", i, font.chars[i].offX, font.chars[i].offY, font.chars[i].printedWidth);
+
+ font_fl.read(font->chars[i].data, sz);
+ //debug("Char %d read %d", i, v);
+ }
+
+ _fonts.push_back(font);
+}
+
+void FontManager::setCurrentFont(int currentFont) {
+ if (currentFont == -1) {
+ currentFont = 0;
+ }
+ _currentFontId = currentFont;
+ _currentFont = _fonts[currentFont];
+
+ setSpaceWidth(0);
+}
+
+void FontManager::setSpaceWidth(unsigned int additionalSpace) {
+ if (_currentFont) {
+ _spaceWidth = additionalSpace + _currentFont->chars[0].printedWidth;
+ } else {
+ _spaceWidth = 0;
+ }
+}
+
+unsigned int FontManager::displayStr_(unsigned int x, unsigned int y,
+ const Common::String &text) const {
+ unsigned int offset = 0;
+ for (Common::String::const_iterator it = text.begin(); it != text.end(); it++) {
+ offset += displayChar(x + offset, y, *it);
+ }
+ return offset;
+}
+
+unsigned int FontManager::displayChar(unsigned int x, unsigned int y, unsigned char c) const {
+ if (!_currentFont) {
+ error("There is no current font");
+ }
+ if (!_currentSurface) {
+ error("There is no current surface");
+ }
+
+ if (c < ' ' || c >= 255) {
+ c = '?';
+ }
+ c -= 32;
+
+ const Character &char_ = _currentFont->chars[c];
+ int realX = x + char_.offX;
+ int realY = y + char_.offY + _currentFont->maxHeight - 2;
+
+ if (!_transparentBackground) {
+ _currentSurface->fillRect(Common::Rect(realX, realY, realX + char_.w, realY + char_.h), 0xff);
+ }
+ Graphics::Surface src;
+ src.init(char_.w, char_.h, char_.w, char_.data, Graphics::PixelFormat::createFormatCLUT8());
+ _currentSurface->transBlitFrom(src, Common::Point(realX, realY), 0, false, _foreColor);
+
+ // WORKAROUND: in Versailles game the space width is calculated differently in this function and in the getStrWidth one, let's try to be consistent
+#define KEEP_SPACE_BUG
+#ifndef KEEP_SPACE_BUG
+ if (c == 0) {
+ return _spaceWidth;
+ } else {
+ return _charSpacing + char_.printedWidth;
+ }
+#else
+ return _charSpacing + char_.printedWidth;
+#endif
+}
+
+unsigned int FontManager::getStrWidth(const Common::String &text) const {
+ unsigned int width = 0;
+ for (Common::String::const_iterator it = text.begin(); it != text.end(); it++) {
+ unsigned char c = *it;
+ if (c == ' ') {
+ width += _spaceWidth;
+ } else {
+ if (c < ' ' || c >= 255) {
+ c = '?';
+ }
+ c -= 32;
+ width += _charSpacing;
+ width += _currentFont->chars[c].printedWidth;
+ }
+ }
+ return width;
+}
+
+bool FontManager::displayBlockText(const Common::String &text,
+ Common::String::const_iterator begin) {
+ bool notEnoughSpace = false;
+ Common::String::const_iterator ptr = begin;
+ Common::Array<Common::String> words;
+
+ if (begin != text.end()) {
+ _blockTextRemaining = nullptr;
+ while (ptr != text.end() && !notEnoughSpace) {
+ unsigned int finalPos;
+ bool has_cr;
+ calculateWordWrap(text, &ptr, &finalPos, &has_cr, words);
+ unsigned int spacesWidth = (words.size() - 1) * _spaceWidth;
+ unsigned int remainingSpace = (_blockRect.right - finalPos);
+ unsigned int spaceConsumed = 0;
+ double spaceWidthPerWord;
+ if (words.size() == 1) {
+ spaceWidthPerWord = _spaceWidth;
+ } else {
+ spaceWidthPerWord = (double)spacesWidth / (double)words.size();
+ }
+ Common::Array<Common::String>::const_iterator word;
+ unsigned int word_i;
+ for (word = words.begin(), word_i = 0; word != words.end(); word++, word_i++) {
+ _blockPos.x += displayStr_(_blockPos.x, _blockPos.y, *word);
+ if (!_justifyText || has_cr) {
+ _blockPos.x += _spaceWidth;
+ } else {
+ double sp = (word_i + 1) * spaceWidthPerWord - spaceConsumed;
+ _blockPos.x += sp;
+ spaceConsumed += sp;
+ remainingSpace -= sp;
+ }
+ }
+ if (_blockPos.y + _lineHeight + getFontMaxHeight() >= _blockRect.bottom) {
+ notEnoughSpace = true;
+ _blockTextRemaining = ptr;
+ } else {
+ _blockPos.x = _blockRect.left;
+ _blockPos.y += _lineHeight;
+ }
+ }
+ }
+ return notEnoughSpace;
+}
+
+unsigned int FontManager::getLinesCount(const Common::String &text, unsigned int width) {
+ if (text.size() == 0) {
+ // One line even if it's empty
+ return 1;
+ }
+ if (text.size() > 1024) {
+ // Too long text, be lazy
+ return getStrWidth(text) / width + 3;
+ }
+
+ unsigned int lineCount = 0;
+ Common::String::const_iterator textP = text.begin();
+ unsigned int len = text.size();
+
+ while (len > 0) {
+ Common::String buffer;
+ unsigned int lineWidth = 0;
+ lineCount++;
+ while (lineWidth < width && len > 0 && *textP != '\r') {
+ buffer += *(textP++);
+ len--;
+ lineWidth = getStrWidth(buffer);
+ }
+
+ if (lineWidth >= width) {
+ // We overrun the line, get backwards
+ while (buffer.size()) {
+ if (buffer[buffer.size() - 1] == ' ') {
+ break;
+ }
+ buffer.deleteLastChar();
+ textP--;
+ len++;
+ }
+ if (!buffer.size()) {
+ // Word was too long: fail
+ return 0;
+ }
+ if (*textP == ' ') {
+ textP++;
+ }
+ // Continue with next line
+ continue;
+ }
+
+ if (len == 0) {
+ // Job is finished
+ break;
+ }
+ if (*textP == '\r') {
+ // Next line
+ len--;
+ textP++;
+ }
+ }
+ return lineCount;
+}
+
+void FontManager::calculateWordWrap(const Common::String &text,
+ Common::String::const_iterator *position, unsigned int *finalPos, bool *hasCr,
+ Common::Array<Common::String> &words) const {
+ *hasCr = false;
+ unsigned int offset = 0;
+ bool wordWrap = false;
+ unsigned int lineWidth = _blockRect.right - _blockRect.left;
+ Common::String::const_iterator ptr = *position;
+
+ words.clear();
+
+ if (ptr == text.end() || *ptr == '\r') {
+ ptr++;
+ *hasCr = true;
+ *position = ptr;
+ *finalPos = offset;
+ return;
+ }
+
+ while (!wordWrap) {
+ Common::String::const_iterator begin = ptr;
+ for (; ptr != text.end() && *ptr != '\r' && *ptr != ' '; ptr++) { }
+ Common::String word(begin, ptr);
+ unsigned int width = getStrWidth(word);
+ if (width + offset >= lineWidth) {
+ wordWrap = true;
+ // word is too long: just put pointer back at begining
+ ptr = begin;
+ } else {
+ words.push_back(word);
+ offset += width + _spaceWidth;
+ for (; ptr != text.end() && *ptr == ' '; ptr++) { }
+ for (; ptr != text.end() && *ptr == '\r'; ptr++) {
+ wordWrap = true;
+ *hasCr = true;
+ }
+ }
+ }
+
+ if (words.size() > 0) {
+ offset -= _spaceWidth;
+ }
+ *finalPos = offset;
+ *position = ptr;
+}
+
+FontManager::Character::Character() : h(0), w(0), offX(0), offY(0), printedWidth(0), data(0) {
+}
+
+FontManager::Character::~Character() {
+ delete[] data;
+}
+
+unsigned int FontManager::Character::setup(uint16 width, uint16 height) {
+ w = width;
+ h = height;
+ unsigned int sz = w * h;
+ data = new byte[sz];
+ return sz;
+}
+
+} // End of namespace CryOmni3D
diff --git a/engines/cryomni3d/font_manager.h b/engines/cryomni3d/font_manager.h
new file mode 100644
index 0000000000..24b45f5560
--- /dev/null
+++ b/engines/cryomni3d/font_manager.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 CRYOMNI3D_FONT_MANAGER_H
+#define CRYOMNI3D_FONT_MANAGER_H
+
+#include "common/array.h"
+#include "common/str.h"
+#include "common/rect.h"
+
+namespace Common {
+class ReadStream;
+}
+
+namespace Graphics {
+class ManagedSurface;
+}
+
+namespace CryOmni3D {
+
+class FontManager {
+public:
+ FontManager();
+ virtual ~FontManager();
+
+ void loadFonts(const Common::Array<Common::String> &fontFiles);
+ void setCurrentFont(int currentFont);
+ unsigned int getCurrentFont() { return _currentFontId; }
+ void setTransparentBackground(bool transparent) { _transparentBackground = transparent; }
+ void setSpaceWidth(unsigned int additionalSpace);
+ void setForeColor(byte color) { _foreColor = color; }
+ void setLineHeight(int h) { _lineHeight = h; }
+ int lineHeight() { return _lineHeight; }
+ void setCharSpacing(unsigned int w) { _charSpacing = w; }
+ void setSurface(Graphics::ManagedSurface *surface) { _currentSurface = surface; }
+
+ int getFontMaxHeight() { return _currentFont->maxHeight; }
+
+ void displayInt(unsigned int x, unsigned int y, int value) const { displayStr_(x, y, Common::String::format("%d", value)); }
+ void displayStr(unsigned int x, unsigned int y, const Common::String &text) const { displayStr_(x, y, text); }
+ unsigned int getStrWidth(const Common::String &text) const;
+
+ unsigned int getLinesCount(const Common::String &text, unsigned int width);
+
+ void setupBlock(const Common::Rect &block, bool justifyText = false) { _blockRect = block; _blockPos.x = block.left; _blockPos.y = block.top; _justifyText = justifyText; }
+ bool displayBlockText(const Common::String &text) { return displayBlockText(text, text.begin()); }
+ bool displayBlockText(const Common::String &text, Common::String::const_iterator begin);
+ Common::String::const_iterator blockTextRemaining() { return _blockTextRemaining; }
+ Common::Point blockTextLastPos() { return _blockPos; }
+
+private:
+ void loadFont(Common::ReadStream &font_fl);
+ unsigned int displayStr_(unsigned int x, unsigned int y, const Common::String &text) const;
+ unsigned int displayChar(unsigned int x, unsigned int y, unsigned char c) const;
+ void calculateWordWrap(const Common::String &text, Common::String::const_iterator *position,
+ unsigned int *finalPos, bool *has_br, Common::Array<Common::String> &words) const;
+
+ struct Character {
+ uint16 h;
+ uint16 w;
+ int16 offX;
+ int16 offY;
+ uint16 printedWidth;
+
+ byte *data;
+
+ Character();
+ ~Character();
+
+ unsigned int setup(uint16 width, uint16 height);
+ };
+
+ struct Font {
+ static const int kCharactersCount = 223;
+
+ uint16 maxHeight;
+ byte comment[32];
+ Character chars[kCharactersCount];
+ };
+
+ Common::Array<Font *> _fonts;
+ const Font *_currentFont;
+ unsigned int _currentFontId;
+ bool _transparentBackground;
+ unsigned int _spaceWidth;
+ unsigned int _charSpacing;
+
+ byte _foreColor;
+
+ Graphics::ManagedSurface *_currentSurface;
+
+ Common::Rect _blockRect;
+ Common::Point _blockPos;
+ int _lineHeight;
+ bool _justifyText;
+ Common::String::const_iterator _blockTextRemaining;
+};
+
+} // End of namespace CryOmni3D
+
+#endif
diff --git a/engines/cryomni3d/image/codecs/hlz.cpp b/engines/cryomni3d/image/codecs/hlz.cpp
new file mode 100644
index 0000000000..146336efdc
--- /dev/null
+++ b/engines/cryomni3d/image/codecs/hlz.cpp
@@ -0,0 +1,140 @@
+/* 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 "cryomni3d/image/codecs/hlz.h"
+
+#include "common/stream.h"
+#include "common/textconsole.h"
+#include "graphics/surface.h"
+
+namespace Image {
+
+HLZDecoder::HLZDecoder(int width, int height) : Codec(),
+ _width(width), _height(height), _surface(nullptr) {
+}
+
+HLZDecoder::~HLZDecoder() {
+ if (_surface) {
+ _surface->free();
+ delete _surface;
+ }
+}
+
+const Graphics::Surface *HLZDecoder::decodeFrame(Common::SeekableReadStream &stream) {
+ if (!_surface) {
+ _surface = new Graphics::Surface();
+ }
+
+ _surface->create(_width, _height, Graphics::PixelFormat::createFormatCLUT8());
+
+ byte *dst = (byte *)_surface->getPixels();
+ decodeFrameInPlace(stream, -1, dst);
+
+ return _surface;
+}
+
+Graphics::PixelFormat HLZDecoder::getPixelFormat() const {
+ return Graphics::PixelFormat::createFormatCLUT8();
+}
+
+static inline bool getReg(Common::SeekableReadStream &stream, uint32 *size, uint32 *reg,
+ int *regBits) {
+ if (*regBits == 0) {
+ if (*size < 4) {
+ error("Can't feed register: not enough data");
+ }
+ *reg = stream.readUint32LE();
+ *size -= 4;
+ *regBits = 32;
+ }
+ bool ret = (*reg >> 31) != 0;
+ *reg <<= 1;
+ (*regBits)--;
+ return ret;
+}
+
+void HLZDecoder::decodeFrameInPlace(Common::SeekableReadStream &stream, uint32 size, byte *dst) {
+ bool eof = false;
+ bool checkSize = (size != (uint32) - 1);
+ byte *orig = dst;
+ uint32 reg;
+ int regBits = 0;
+#define GETREG() getReg(stream, &size, &reg, &regBits)
+
+ while (!eof) {
+ if (GETREG()) {
+ if (size < 1) {
+ error("Can't read pixel byte");
+ }
+ byte c = stream.readByte();
+ *(dst++) = c;
+ size--;
+ } else {
+ int offset, repeat_count;
+ if (GETREG()) {
+ // Long repeat
+ if (size < 2) {
+ error("Can't read repeat count/offset");
+ }
+ uint16 tmp = stream.readUint16LE();
+ size -= 2;
+ repeat_count = tmp & 0x7;
+ offset = (tmp >> 3) - 0x2000;
+ if (repeat_count == 0) {
+ if (size < 1) {
+ error("Can't read long repeat count");
+ }
+ repeat_count = stream.readByte();
+ size--;
+ if (repeat_count == 0) {
+ eof = true;
+ continue;
+ }
+ }
+ } else {
+ // Short repeat
+ repeat_count = GETREG() << 1;
+ repeat_count |= GETREG();
+ if (size < 1) {
+ error("Can't read offset byte");
+ }
+ offset = stream.readByte() - 0x100;
+ size--;
+ }
+ repeat_count += 2;
+ if (dst + offset < orig) {
+ error("Invalid offset %d, dst is %d", offset, (int)(dst - orig));
+ }
+ for (; repeat_count > 0; repeat_count--) {
+ // offset is always < 0
+ *dst = *(dst + offset);
+ dst++;
+ }
+ }
+ }
+ if (checkSize && size != 0) {
+ stream.skip(size);
+ }
+#undef GETREG
+}
+
+} // End of namespace Image
diff --git a/engines/cryomni3d/image/codecs/hlz.h b/engines/cryomni3d/image/codecs/hlz.h
new file mode 100644
index 0000000000..4ec8d3e0f1
--- /dev/null
+++ b/engines/cryomni3d/image/codecs/hlz.h
@@ -0,0 +1,53 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef CRYOMNI3D_IMAGE_CODECS_HLZ_H
+#define CRYOMNI3D_IMAGE_CODECS_HLZ_H
+
+#include "image/codecs/codec.h"
+
+namespace Image {
+
+/**
+ * HLZ image decoder.
+ *
+ * Used by HLZ image format and HNM video format.
+ */
+class HLZDecoder : public Codec {
+public:
+ HLZDecoder(int width, int height);
+ ~HLZDecoder();
+
+ const Graphics::Surface *decodeFrame(Common::SeekableReadStream &stream);
+ Graphics::PixelFormat getPixelFormat() const;
+
+ static void decodeFrameInPlace(Common::SeekableReadStream &stream, uint32 size, byte *dst);
+
+private:
+ Graphics::Surface *_surface;
+ int _width, _height;
+ int _bitsPerPixel;
+};
+
+} // End of namespace Image
+
+#endif
diff --git a/engines/cryomni3d/image/hlz.cpp b/engines/cryomni3d/image/hlz.cpp
new file mode 100644
index 0000000000..dd545c5056
--- /dev/null
+++ b/engines/cryomni3d/image/hlz.cpp
@@ -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.
+ *
+ */
+
+#include "cryomni3d/image/hlz.h"
+
+#include "common/stream.h"
+#include "common/substream.h"
+#include "common/textconsole.h"
+#include "graphics/pixelformat.h"
+#include "graphics/surface.h"
+#include "image/codecs/codec.h"
+
+#include "cryomni3d/image/codecs/hlz.h"
+
+namespace Image {
+
+HLZFileDecoder::HLZFileDecoder() {
+ _surface = 0;
+ _codec = 0;
+}
+
+HLZFileDecoder::~HLZFileDecoder() {
+ destroy();
+}
+
+void HLZFileDecoder::destroy() {
+ delete _codec;
+ _codec = 0;
+ _surface = 0;
+}
+
+bool HLZFileDecoder::loadStream(Common::SeekableReadStream &stream) {
+ destroy();
+
+ stream.read(_palette, sizeof(_palette));
+ uint16 width = stream.readUint16LE();
+ uint16 height = stream.readUint16LE();
+
+ if (width == 0 || height == 0) {
+ return false;
+ }
+
+ _codec = new HLZDecoder(width, height);
+ _surface = _codec->decodeFrame(stream);
+ return true;
+}
+
+} // End of namespace Image
diff --git a/engines/cryomni3d/image/hlz.h b/engines/cryomni3d/image/hlz.h
new file mode 100644
index 0000000000..831632f59c
--- /dev/null
+++ b/engines/cryomni3d/image/hlz.h
@@ -0,0 +1,69 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+/**
+ * @file
+ * Image decoder used in engines:
+ * - hugo
+ * - mohawk
+ * - wintermute
+ */
+
+#ifndef CRYOMNI3D_IMAGE_HLZ_H
+#define CRYOMNI3D_IMAGE_HLZ_H
+
+#include "common/scummsys.h"
+#include "common/str.h"
+#include "image/image_decoder.h"
+
+namespace Common {
+class SeekableReadStream;
+}
+
+namespace Graphics {
+struct Surface;
+}
+
+namespace Image {
+class HLZDecoder;
+
+class HLZFileDecoder : public ImageDecoder {
+public:
+ HLZFileDecoder();
+ virtual ~HLZFileDecoder();
+
+ // ImageDecoder API
+ void destroy();
+ virtual bool loadStream(Common::SeekableReadStream &stream);
+ virtual const Graphics::Surface *getSurface() const { return _surface; }
+ const byte *getPalette() const { return _palette; }
+ uint16 getPaletteColorCount() const { return 256; }
+
+private:
+ HLZDecoder *_codec;
+ const Graphics::Surface *_surface;
+ byte _palette[256 * 3];
+};
+
+} // End of namespace Image
+
+#endif
diff --git a/engines/cryomni3d/module.mk b/engines/cryomni3d/module.mk
new file mode 100644
index 0000000000..637cf34518
--- /dev/null
+++ b/engines/cryomni3d/module.mk
@@ -0,0 +1,38 @@
+MODULE := engines/cryomni3d
+
+MODULE_OBJS = \
+ cryomni3d.o \
+ omni3d.o \
+ detection.o \
+ mouse_boxes.o \
+ dialogs_manager.o \
+ fixed_image.o \
+ font_manager.o \
+ objects.o \
+ sprites.o \
+ wam_parser.o \
+ video/hnm_decoder.o \
+ image/hlz.o \
+ image/codecs/hlz.o
+
+ifdef ENABLE_VERSAILLES
+MODULE_OBJS += \
+ versailles/data.o \
+ versailles/dialogs_manager.o \
+ versailles/dialogs.o \
+ versailles/documentation.o \
+ versailles/engine.o \
+ versailles/logic.o \
+ versailles/menus.o \
+ versailles/music.o \
+ versailles/saveload.o \
+ versailles/toolbar.o
+endif
+
+# This module can be built as a plugin
+ifeq ($(ENABLE_CRYOMNI3D), DYNAMIC_PLUGIN)
+PLUGIN := 1
+endif
+
+# Include common rules
+include $(srcdir)/rules.mk
diff --git a/engines/cryomni3d/mouse_boxes.cpp b/engines/cryomni3d/mouse_boxes.cpp
new file mode 100644
index 0000000000..6e8887d0dd
--- /dev/null
+++ b/engines/cryomni3d/mouse_boxes.cpp
@@ -0,0 +1,95 @@
+/* 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 "cryomni3d/mouse_boxes.h"
+
+#include "common/rect.h"
+
+#include "cryomni3d/font_manager.h"
+
+namespace CryOmni3D {
+
+MouseBoxes::MouseBoxes(unsigned int size) {
+ _boxes.resize(size);
+}
+
+MouseBoxes::~MouseBoxes() {
+}
+
+void MouseBoxes::reset() {
+ unsigned int sz = _boxes.size();
+ _boxes.clear();
+ _boxes.resize(sz);
+}
+
+void MouseBoxes::setupBox(int box_id, int left, int top, int right, int bottom,
+ const Common::String *text) {
+ MouseBox &box = _boxes[box_id];
+ box.left = left;
+ box.top = top;
+ box.right = right;
+ box.bottom = bottom;
+ box.isChar = false;
+ box.string = text;
+}
+
+void MouseBoxes::setupBox(int box_id, int left, int top, int right, int bottom, const char *text) {
+ MouseBox &box = _boxes[box_id];
+ box.left = left;
+ box.top = top;
+ box.right = right;
+ box.bottom = bottom;
+ box.isChar = true;
+ box.charp = text;
+}
+
+Common::Rect MouseBoxes::getBoxRect(int box_id) const {
+ const MouseBox &box = _boxes[box_id];
+ return Common::Rect(box.left, box.top, box.right, box.bottom);
+}
+
+Common::Point MouseBoxes::getBoxOrigin(int box_id) const {
+ const MouseBox &box = _boxes[box_id];
+ return Common::Point(box.left, box.top);
+}
+
+bool MouseBoxes::hitTest(int box_id, const Common::Point &pt) {
+ const MouseBox &box = _boxes[box_id];
+
+ return (box.left != -1) &&
+ (pt.x > box.left && pt.x < box.right &&
+ pt.y > box.top && pt.y < box.bottom);
+}
+
+void MouseBoxes::display(int box_id, const FontManager &font_manager) {
+ const MouseBox &box = _boxes[box_id];
+
+ if (box.string) {
+ if (box.isChar) {
+ font_manager.displayStr(box.left, box.top, box.charp);
+ } else {
+ font_manager.displayStr(box.left, box.top, *box.string);
+ }
+ }
+}
+
+}
diff --git a/engines/cryomni3d/mouse_boxes.h b/engines/cryomni3d/mouse_boxes.h
new file mode 100644
index 0000000000..5e4d49671a
--- /dev/null
+++ b/engines/cryomni3d/mouse_boxes.h
@@ -0,0 +1,73 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef CRYOMNI3D_MOUSE_BOXES_H
+#define CRYOMNI3D_MOUSE_BOXES_H
+
+#include "common/array.h"
+#include "common/str.h"
+
+namespace Common {
+class Point;
+class Rect;
+}
+
+namespace CryOmni3D {
+
+class FontManager;
+
+class MouseBoxes {
+public:
+ MouseBoxes(unsigned int size);
+ virtual ~MouseBoxes();
+
+ void reset();
+ void setupBox(int box_id, int left, int top, int right, int bottom,
+ const Common::String *text = nullptr);
+ void setupBox(int box_id, int left, int top, int right, int bottom, const char *text);
+ Common::Rect getBoxRect(int box_id) const;
+ Common::Point getBoxOrigin(int box_id) const;
+ bool hitTest(int box_id, const Common::Point &pt);
+ void display(int box_id, const FontManager &font_manager);
+
+private:
+ struct MouseBox {
+ MouseBox() : left(-1), top(-1), right(-1), bottom(-1), string(nullptr), isChar(false) {}
+
+ int left;
+ int top;
+ int right;
+ int bottom;
+ // Can be nullptr
+ bool isChar;
+ union {
+ const Common::String *string;
+ const char *charp;
+ };
+ };
+
+ Common::Array<MouseBox> _boxes;
+};
+
+} // End of namespace CryOmni3D
+
+#endif
diff --git a/engines/cryomni3d/objects.cpp b/engines/cryomni3d/objects.cpp
new file mode 100644
index 0000000000..5963a1dd0e
--- /dev/null
+++ b/engines/cryomni3d/objects.cpp
@@ -0,0 +1,107 @@
+/* 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 "engines/cryomni3d/objects.h"
+
+namespace CryOmni3D {
+
+Object *Objects::findObjectByNameID(unsigned int nameID) {
+ for (iterator it = begin(); it != end(); it++) {
+ if (it->valid() && it->idOBJ() == nameID) {
+ return it;
+ }
+ }
+ // TODO: check if 111 and 112 are called and should be filtered out or not
+ error("nameID not found %u", nameID);
+}
+
+Object *Objects::findObjectByIconID(unsigned int iconID) {
+ for (iterator it = begin(); it != end(); it++) {
+ if (it->valid() && it->idCA() == iconID) {
+ return it;
+ }
+ }
+ error("iconID not found %u", iconID);
+}
+
+void Inventory::clear() {
+ for (iterator it = begin(); it != end(); it++) {
+ *it = nullptr;
+ }
+}
+
+void Inventory::add(Object *obj) {
+ for (iterator it = begin(); it != end(); it++) {
+ if (*it == nullptr) {
+ *it = obj;
+ (*_changeCallback)(it - begin());
+ return;
+ }
+ }
+ error("No more room in inventory");
+}
+
+void Inventory::remove(unsigned int position) {
+ (*this)[position] = nullptr;
+ (*_changeCallback)(-1u);
+}
+
+void Inventory::removeByCursorId(unsigned int cursorId) {
+ for (iterator it = begin(); it != end(); it++) {
+ if ((*it) && (*it)->idCA() == cursorId) {
+ remove(it - begin());
+ return;
+ }
+ }
+ // Don't bail out
+}
+
+void Inventory::removeByNameId(unsigned int nameId) {
+ for (iterator it = begin(); it != end(); it++) {
+ if ((*it) && (*it)->idOBJ() == nameId) {
+ remove(it - begin());
+ return;
+ }
+ }
+ // Don't bail out
+}
+
+bool Inventory::inInventoryByCursorId(unsigned int cursorId) const {
+ for (const_iterator it = begin(); it != end(); it++) {
+ if ((*it) && (*it)->idCA() == cursorId) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool Inventory::inInventoryByNameId(unsigned int nameId) const {
+ for (const_iterator it = begin(); it != end(); it++) {
+ if ((*it) && (*it)->idOBJ() == nameId) {
+ return true;
+ }
+ }
+ return false;
+}
+
+} // End of namespace CryOmni3D
diff --git a/engines/cryomni3d/objects.h b/engines/cryomni3d/objects.h
new file mode 100644
index 0000000000..917233c02f
--- /dev/null
+++ b/engines/cryomni3d/objects.h
@@ -0,0 +1,102 @@
+/* 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 CRYOMNI3D_OBJECTS_H
+#define CRYOMNI3D_OBJECTS_H
+
+#include "common/array.h"
+#include "common/func.h"
+#include "common/str.h"
+
+#include "cryomni3d/sprites.h"
+
+namespace CryOmni3D {
+
+class Object {
+public:
+ typedef Common::Functor0<void> *ViewCallback;
+
+ Object() : _valid(false), _idCA(-1), _idCl(-1), _idSA(-1), _idSl(-1), _idOBJ(-1),
+ _viewCallback(nullptr) {}
+
+ Object(const Sprites &sprites, unsigned int idCA, unsigned int idOBJ) : _idCA(idCA),
+ _idCl(sprites.calculateSpriteId(idCA, 1)), _idSA(sprites.calculateSpriteId(idCA, 2)),
+ _idSl(sprites.calculateSpriteId(idCA, 3)),
+ _valid(true), _idOBJ(idOBJ), _viewCallback(nullptr) {}
+
+ ~Object() { delete _viewCallback; }
+
+ unsigned int valid() const { return _valid; }
+ unsigned int idCA() const { return _idCA; }
+ unsigned int idCl() const { return _idCl; }
+ unsigned int idSA() const { return _idSA; }
+ unsigned int idSl() const { return _idSl; }
+ unsigned int idOBJ() const { return _idOBJ; }
+ ViewCallback viewCallback() const { return _viewCallback; }
+ // Takes ownership of the pointer
+ void setViewCallback(ViewCallback callback) { _viewCallback = callback; }
+
+ void rename(unsigned int newIdOBJ) { _idOBJ = newIdOBJ; }
+
+private:
+ unsigned int _idOBJ;
+ unsigned int _idCA;
+ unsigned int _idCl;
+ unsigned int _idSA;
+ unsigned int _idSl;
+ bool _valid;
+ ViewCallback _viewCallback;
+};
+
+class Objects : public Common::Array<Object> {
+public:
+ Object *findObjectByNameID(unsigned int nameID);
+ Object *findObjectByIconID(unsigned int iconID);
+private:
+};
+
+class Inventory : public Common::Array<Object *> {
+public:
+ Inventory() : _selectedObject(nullptr), _changeCallback(nullptr) { }
+ ~Inventory() { delete _changeCallback; }
+ void init(unsigned int count, Common::Functor1<unsigned int, void> *changeCallback) { _changeCallback = changeCallback; resize(count); }
+
+ void clear();
+ void add(Object *);
+ void remove(unsigned int position);
+ void removeByCursorId(unsigned int cursorId);
+ void removeByNameId(unsigned int nameId);
+ bool inInventoryByCursorId(unsigned int cursorId) const;
+ bool inInventoryByNameId(unsigned int nameId) const;
+
+ Object *selectedObject() const { return _selectedObject; }
+ void setSelectedObject(Object *obj) { _selectedObject = obj; }
+
+private:
+ Object *_selectedObject;
+ Common::Functor1<unsigned int, void> *_changeCallback;
+};
+
+} // End of namespace CryOmni3D
+
+#endif
+
diff --git a/engines/cryomni3d/omni3d.cpp b/engines/cryomni3d/omni3d.cpp
new file mode 100644
index 0000000000..5c6d6db21c
--- /dev/null
+++ b/engines/cryomni3d/omni3d.cpp
@@ -0,0 +1,275 @@
+/* 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 "engines/cryomni3d/omni3d.h"
+
+#include "common/rect.h"
+
+namespace CryOmni3D {
+
+void Omni3DManager::init(double hfov) {
+ _alpha = 0.;
+ _beta = 0.;
+ _xSpeed = 0.;
+ _ySpeed = 0.;
+
+ double oppositeSide = tan(hfov / 2.) / (4. / 3.);
+ double vf = atan2(oppositeSide, 1.);
+ _vfov = (M_PI_2 - vf - (13. / 180.*M_PI)) * 10. / 9.;
+
+ double warpVfov = 155. / 180. * M_PI;
+ double hypV = 768. / 2. / sin(warpVfov / 2.);
+ double oppHTot = tan(hfov / 2.) * 16. / 320.;
+ _helperValue = 2048 * 65536 / (2. * M_PI);
+
+ for (int i = 0; i < 31; i++) {
+ double oppH = (i - 15) * oppHTot;
+ double angle = atan2(oppH, 1.);
+
+ _anglesH[i] = angle;
+ _hypothenusesH[i] = sqrt(oppH * oppH + 1);
+
+ double oppVTot = hypV * _hypothenusesH[i];
+ for (int j = 0; j < 21; j++) {
+ double oppV = (j - 20) * oppHTot;
+
+ _oppositeV[j] = oppV;
+
+ double coord = sqrt(oppV * oppV + _hypothenusesH[i] * _hypothenusesH[i]);
+ coord = oppVTot / coord;
+ coord = coord * 65536;
+
+ _squaresCoords[i][j] = coord;
+ }
+ }
+
+ _surface.create(640, 480, Graphics::PixelFormat::createFormatCLUT8());
+ clearConstraints();
+}
+
+Omni3DManager::~Omni3DManager() {
+ _surface.free();
+}
+
+void Omni3DManager::updateCoords(int xDelta, int yDelta, bool useOldSpeed) {
+ double xDelta1 = xDelta * 0.00025;
+ double yDelta1 = yDelta * 0.0002;
+
+ if (useOldSpeed) {
+ _xSpeed += xDelta1;
+ _ySpeed += yDelta1;
+ } else {
+ _xSpeed = xDelta1;
+ _ySpeed = yDelta1;
+ }
+ _alpha += _xSpeed;
+ _beta += _ySpeed;
+
+ //debug("alpha = %lf beta = %lf xSpeed = %lf ySpeed = %lf", _alpha, _beta, _xSpeed, _ySpeed);
+
+ _xSpeed *= 0.4;
+ _ySpeed *= 0.6;
+
+ if (useOldSpeed) {
+ if (abs(_xSpeed) < 0.001) {
+ _xSpeed = 0.;
+ }
+ if (abs(_ySpeed) < 0.001) {
+ _ySpeed = 0.;
+ }
+ }
+
+ if (_alpha < _alphaMin) {
+ _alpha = _alphaMin;
+ _xSpeed = 0.;
+ } else if (_alpha > _alphaMax) {
+ _alpha = _alphaMax;
+ _xSpeed = 0.;
+ }
+ if (_beta < _betaMin) {
+ _beta = _betaMin;
+ _ySpeed = 0.;
+ } else if (_beta > _betaMax) {
+ _beta = _betaMax;
+ _ySpeed = 0.;
+ }
+
+ if (_alpha >= 2. * M_PI) {
+ _alpha -= 2. * M_PI;
+ } else if (_alpha < 0.) {
+ _alpha += 2. * M_PI;
+ }
+
+ _dirtyCoords = true;
+
+ updateImageCoords();
+}
+
+void Omni3DManager::updateImageCoords() {
+ if (!_dirtyCoords) {
+ return;
+ }
+
+ if (_alpha >= 2.*M_PI) {
+ _alpha -= 2.*M_PI;
+ } else if (_alpha < 0) {
+ _alpha += 2.*M_PI;
+ }
+ if (_beta > 0.9 * _vfov) {
+ _beta = 0.9 * _vfov;
+ } else if (_beta < -0.9 * _vfov) {
+ _beta = -0.9 * _vfov;
+ }
+
+ double tmp = (2048 * 65536) - 2048 * 65536 / (2. * M_PI) * _alpha;
+
+ unsigned int k = 0;
+ for (unsigned int i = 0; i < 31; i++) {
+ double v11 = _anglesH[i] + _beta;
+ double v26 = sin(v11);
+ double v25 = cos(v11) * _hypothenusesH[i];
+
+ unsigned int offset = 80;
+ unsigned int j;
+ for (j = 0; j < 20; j++) {
+ double v16 = atan2(_oppositeV[j], v25);
+ double v17 = v16 * _helperValue;
+ double v18 = (384 * 65536) - _squaresCoords[i][j] * v26;
+
+ k += 2;
+ _imageCoords[k + 0] = (int)(tmp + v17);
+ _imageCoords[k + offset + 0] = (int)(tmp - v17);
+ _imageCoords[k + 1] = (int) v18;
+ _imageCoords[k + offset + 1] = (int) v18;
+
+ offset -= 4;
+ }
+
+ double v19 = atan2(_oppositeV[j], v25);
+
+ k += 2;
+ _imageCoords[k + 0] = (int)((2048.*65536.) - (_alpha - v19) * _helperValue);
+ _imageCoords[k + 1] = (int)((384.*65536.) - _squaresCoords[i][j] * v26);
+
+ k += 40;
+ }
+
+ _dirtyCoords = false;
+ _dirty = true;
+}
+
+const Graphics::Surface *Omni3DManager::getSurface() {
+ if (!_sourceSurface) {
+ return nullptr;
+ }
+
+ if (_dirtyCoords) {
+ updateImageCoords();
+ }
+
+ if (_dirty) {
+ unsigned int off = 2;
+ byte *dst = (byte *)_surface.getBasePtr(0, 0);
+ const byte *src = (const byte *)_sourceSurface->getBasePtr(0, 0);
+
+ for (unsigned int i = 0; i < 30; i++) {
+ for (unsigned int j = 0; j < 40; j++) {
+ int x1 = (_imageCoords[off + 2] - _imageCoords[off + 0]) >> 4;
+ int y1 = (_imageCoords[off + 3] - _imageCoords[off + 1]) >> 4;
+ int x1_ = (_imageCoords[off + 82 + 2] - _imageCoords[off + 82 + 0]) >> 4;
+ int y1_ = (_imageCoords[off + 82 + 3] - _imageCoords[off + 82 + 1]) >> 4;
+
+ int dx1 = (x1_ - x1) >> 10;
+ int dy1 = (y1_ - y1) >> 15;
+
+ y1 >>= 5;
+
+ int dx2 = (_imageCoords[off + 82 + 0] - _imageCoords[off + 0]) >> 4;
+ int dy2 = (_imageCoords[off + 82 + 1] - _imageCoords[off + 1]) >> 9;
+ int x2 = (((_imageCoords[off + 0] >> 0) * 2) + dx2) >> 1;
+ int y2 = (((_imageCoords[off + 1] >> 5) * 2) + dy2) >> 1;
+
+ for (unsigned int y = 0; y < 16; y++) {
+ unsigned int px = (x2 * 2 + x1) * 16;
+ unsigned int py = (y2 * 2 + y1) / 2;
+ unsigned int deltaX = x1 * 32;
+ unsigned int deltaY = y1;
+
+ for (unsigned int x = 0; x < 16; x++) {
+ unsigned int srcOff = (py & 0x1ff800) | (px >> 21);
+ dst[x] = src[srcOff];
+ px += deltaX;
+ py += deltaY;
+ }
+ dst += 640;
+
+ x1 += dx1;
+ y1 += dy1;
+ x2 += dx2;
+ y2 += dy2;
+ }
+ dst -= 16 * 640 - 16;
+ off += 2;
+ }
+ dst += 15 * 640;
+ off += 2;
+ }
+
+ _dirty = false;
+ }
+
+ return &_surface;
+}
+
+void Omni3DManager::clearConstraints() {
+ _alphaMin = -HUGE_VAL;
+ _alphaMax = HUGE_VAL;
+ _betaMin = -HUGE_VAL;
+ _betaMax = HUGE_VAL;
+}
+
+Common::Point Omni3DManager::mapMouseCoords(const Common::Point &mouse) {
+ Common::Point pt;
+
+ if (_dirtyCoords) {
+ updateImageCoords();
+ }
+
+ int smallX = mouse.x & 0xf, squareX = mouse.x >> 4;
+ int smallY = mouse.y & 0xf, squareY = mouse.y >> 4;
+
+ unsigned int off = 82 * squareY + 2 * squareX;
+
+ pt.x = ((_imageCoords[off + 2] +
+ smallY * ((_imageCoords[off + 84] - _imageCoords[off + 2]) >> 4) +
+ (smallX * smallY) * ((_imageCoords[off + 86] - _imageCoords[off + 84]) >> 8) +
+ (smallX * (16 - smallY)) * ((_imageCoords[off + 4] - _imageCoords[off + 2]) >> 8))
+ & 0x07ff0000) >> 16;
+ pt.y = (_imageCoords[off + 3] +
+ smallY * ((_imageCoords[off + 85] - _imageCoords[off + 3]) >> 4) +
+ (smallX * smallY) * ((_imageCoords[off + 87] - _imageCoords[off + 85]) >> 8) +
+ (smallX * (16 - smallY)) * ((_imageCoords[off + 5] - _imageCoords[off + 3]) >> 8)) >> 16;
+
+ return pt;
+}
+
+} // End of namespace CryOmni3D
diff --git a/engines/cryomni3d/omni3d.h b/engines/cryomni3d/omni3d.h
new file mode 100644
index 0000000000..e7d9e08a89
--- /dev/null
+++ b/engines/cryomni3d/omni3d.h
@@ -0,0 +1,83 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef CRYOMNI3D_OMNI3D_H
+#define CRYOMNI3D_OMNI3D_H
+
+#include "graphics/surface.h"
+
+namespace CryOmni3D {
+
+class Omni3DManager {
+public:
+ Omni3DManager() {}
+ virtual ~Omni3DManager();
+
+ void init(double hfov);
+
+ void setSourceSurface(const Graphics::Surface *surface) { _sourceSurface = surface; _dirty = true; }
+
+ void clearConstraints();
+ void setAlphaConstraints(double alphaMin, double alphaMax) { _alphaMin = alphaMin; _alphaMax = alphaMax; }
+ void setBetaMinConstraint(double betaMin) { _betaMin = betaMin; }
+ void setBetaMaxConstraint(double betaMax) { _betaMax = betaMax; }
+
+ void setAlpha(double alpha) { _alpha = alpha; _dirtyCoords = true; }
+ void setBeta(double beta) { _beta = beta; _dirtyCoords = true; }
+ void updateCoords(int xDelta, int yDelta, bool useOldSpeed);
+
+ double getAlpha() const { return _alpha; }
+ double getBeta() const { return _beta; }
+
+ Common::Point mapMouseCoords(const Common::Point &mouse);
+
+ bool hasSpeed() { return _xSpeed != 0. || _ySpeed != 0.; }
+ bool needsUpdate() { return _dirty || _dirtyCoords; }
+ const Graphics::Surface *getSurface();
+
+private:
+ void updateImageCoords();
+
+ double _vfov;
+
+ double _alpha, _beta;
+ double _xSpeed, _ySpeed;
+
+ double _alphaMin, _alphaMax;
+ double _betaMin, _betaMax;
+
+ int _imageCoords[2544];
+ double _squaresCoords[31][21];
+ double _hypothenusesH[31];
+ double _anglesH[31];
+ double _oppositeV[21];
+ double _helperValue;
+
+ bool _dirty;
+ bool _dirtyCoords;
+ const Graphics::Surface *_sourceSurface;
+ Graphics::Surface _surface;
+};
+
+} // End of namespace CryOmni3D
+
+#endif
diff --git a/engines/cryomni3d/sprites.cpp b/engines/cryomni3d/sprites.cpp
new file mode 100644
index 0000000000..b1c5f95b0e
--- /dev/null
+++ b/engines/cryomni3d/sprites.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/debug.h"
+#include "common/file.h"
+#include "graphics/surface.h"
+
+#include "engines/cryomni3d/sprites.h"
+
+// #define SPRTIES_DEBUG
+
+namespace CryOmni3D {
+
+#define MAP_ID(id) \
+ do { \
+ if (_map) { \
+ id = (*_map)[id]; \
+ } \
+ } while(false)
+
+Sprites::Sprites() : _map(nullptr) {
+ _surface = new Graphics::Surface();
+}
+
+Sprites::~Sprites() {
+ for (Common::Array<CryoCursor *>::iterator it = _cursors.begin(); it != _cursors.end(); it++) {
+ if ((*it)->refCnt > 1) {
+ (*it)->refCnt--;
+ } else {
+ delete *it;
+ }
+ }
+ delete _map;
+ delete _surface;
+}
+
+void Sprites::loadSprites(Common::ReadStream &spr_fl) {
+ byte magic[4];
+
+ while (true) {
+ if (spr_fl.read(magic, sizeof(magic)) == 0) {
+ break;
+ }
+ if (memcmp(magic, "SPRI", sizeof(magic))) {
+ error("Invalid sprite magic");
+ }
+
+ // 2 unknown uint32
+ spr_fl.readUint32BE();
+ spr_fl.readUint32BE();
+
+ CryoCursor *cursor = new CryoCursor();
+
+ uint16 w = spr_fl.readUint16BE();
+ uint16 h = spr_fl.readUint16BE();
+ unsigned int sz = cursor->setup(w, h);
+ cursor->_offX = spr_fl.readUint32BE();
+ cursor->_offY = spr_fl.readUint32BE();
+
+ spr_fl.read(cursor->_data, sz);
+ _cursors.push_back(cursor);
+ }
+}
+
+void Sprites::setupMapTable(const unsigned int *table, unsigned int size) {
+ delete _map;
+ _map = nullptr;
+ // Reset the reverse mapping
+ for (Common::Array<CryoCursor *>::iterator it = _cursors.begin(); it != _cursors.end(); it++) {
+ (*it)->_constantId = -1;
+ }
+ if (table) {
+ _map = new Common::Array<unsigned int>(table, size);
+
+ // Sweep all the mapping and set its reverse values
+ unsigned int i = 0;
+ for (Common::Array<unsigned int>::const_iterator it = _map->begin(); it != _map->end(); it++, i++) {
+ _cursors[*it]->_constantId = i;
+ }
+
+#ifdef SPRITES_DEBUG
+ // Normally we don't have any unreachable sprties from constants,
+ // as it could be time consuming, this should be fixed in the static map
+ // Count unswept values
+ unsigned int unswept = 0;
+ for (Common::Array<CryoCursor *>::iterator it = _cursors.begin(); it != _cursors.end(); it++) {
+ if ((*it)->_constantId == -1u) {
+ unswept++;
+ }
+ }
+
+ if (unswept) {
+ warning("We got %d unreachable sprites from map table. This should not happen."
+ " Fixing it for now", unswept);
+ // Enlarge the map to hold new indexes
+ _map->reserve(_map->size() + unswept);
+
+ // Set new indexes to unswept sprites
+ i = 0;
+ for (Common::Array<CryoCursor *>::iterator it = _cursors.begin(); it != _cursors.end(); it++, i++) {
+ if ((*it)->_constantId == -1u) {
+ warning("Fixing sprite the %d sprite", i);
+ (*it)->_constantId = _map->size();
+ _map->push_back(i);
+ }
+ }
+ }
+#endif
+ }
+}
+
+void Sprites::setSpriteHotspot(unsigned int spriteId, unsigned int x, unsigned int y) {
+ MAP_ID(spriteId);
+ _cursors[spriteId]->_offX = x;
+ _cursors[spriteId]->_offY = y;
+}
+
+void Sprites::replaceSprite(unsigned int oldSpriteId, unsigned int newSpriteId) {
+ MAP_ID(oldSpriteId);
+ MAP_ID(newSpriteId);
+ if (_cursors[oldSpriteId]->refCnt > 1) {
+ _cursors[oldSpriteId]->refCnt--;
+ } else {
+ delete _cursors[oldSpriteId];
+ }
+ _cursors[oldSpriteId] = _cursors[newSpriteId];
+ _cursors[oldSpriteId]->refCnt++;
+}
+
+unsigned int Sprites::getSpritesCount() const {
+ if (_map) {
+ return _map->size();
+ } else {
+ return _cursors.size();
+ }
+}
+
+unsigned int Sprites::revMapSpriteId(unsigned int id) const {
+ if (_map) {
+ if (id >= _cursors.size()) {
+ error("revMapSpriteId is out of bounds: %d/%d", id, _cursors.size());
+ }
+ id = _cursors[id]->_constantId;
+ }
+
+ return id;
+}
+
+unsigned int Sprites::calculateSpriteId(unsigned int baseId, unsigned int offset) const {
+ if (_map) {
+ MAP_ID(baseId);
+ baseId += offset;
+ if (baseId >= _cursors.size()) {
+ error("Calculate sprite is out of bounds: %d/%d", baseId, _cursors.size());
+ }
+ unsigned int spriteId = _cursors[baseId]->_constantId;
+ if (spriteId == -1u) {
+ error("Sprite %d is unreachable", baseId);
+ }
+ return spriteId;
+ } else {
+ return baseId + offset;
+ }
+}
+
+const Graphics::Surface &Sprites::getSurface(unsigned int spriteId) const {
+ MAP_ID(spriteId);
+
+ CryoCursor *cursor = _cursors[spriteId];
+
+ _surface->init(cursor->_width, cursor->_height, cursor->_width, cursor->_data,
+ Graphics::PixelFormat::createFormatCLUT8());
+ return *_surface;
+}
+
+const Graphics::Cursor &Sprites::getCursor(unsigned int spriteId) const {
+ MAP_ID(spriteId);
+
+ return *_cursors[spriteId];
+}
+
+Sprites::CryoCursor::CryoCursor() : _width(0), _height(0), _offX(0), _offY(0), _data(nullptr),
+ refCnt(1) {
+}
+
+Sprites::CryoCursor::~CryoCursor() {
+ assert(refCnt == 1);
+ delete[] _data;
+}
+
+unsigned int Sprites::CryoCursor::setup(uint16 width, uint16 height) {
+ _width = width;
+ _height = height;
+ unsigned int sz = _width * _height;
+ _data = new byte[sz];
+ return sz;
+}
+
+} // End of namespace CryOmni3D
diff --git a/engines/cryomni3d/sprites.h b/engines/cryomni3d/sprites.h
new file mode 100644
index 0000000000..eac4be3489
--- /dev/null
+++ b/engines/cryomni3d/sprites.h
@@ -0,0 +1,102 @@
+/* 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 CRYOMNI3D_SPRITES_H
+#define CRYOMNI3D_SPRITES_H
+
+#include "common/array.h"
+#include "common/str.h"
+
+#include "graphics/cursor.h"
+
+namespace Common {
+class ReadStream;
+}
+
+namespace Graphics {
+class Surface;
+}
+
+namespace CryOmni3D {
+
+class Sprites {
+public:
+ Sprites();
+ virtual ~Sprites();
+
+ void loadSprites(Common::ReadStream &spr_fl);
+ void setupMapTable(const unsigned int *table, unsigned int size);
+
+ void setSpriteHotspot(unsigned int spriteId, unsigned int x, unsigned int y);
+
+ void replaceSprite(unsigned int oldSpriteId, unsigned int newSpriteId);
+
+ unsigned int getSpritesCount() const;
+
+ const Graphics::Surface &getSurface(unsigned int spriteId) const;
+ const Graphics::Cursor &getCursor(unsigned int spriteId) const;
+
+ unsigned int revMapSpriteId(unsigned int id) const;
+ unsigned int calculateSpriteId(unsigned int baseId, unsigned int offset) const;
+
+ byte getKeyColor(unsigned int spriteId) const { return 0; }
+
+private:
+ class CryoCursor : public Graphics::Cursor {
+ public:
+ virtual uint16 getWidth() const override { return _width; }
+ virtual uint16 getHeight() const override { return _height; }
+ virtual uint16 getHotspotX() const override { return _offX; }
+ virtual uint16 getHotspotY() const override { return _offY; }
+ virtual byte getKeyColor() const override { return 0; }
+
+ virtual const byte *getSurface() const override { return _data; }
+
+ virtual const byte *getPalette() const override { return nullptr; }
+ virtual byte getPaletteStartIndex() const override { return 0; }
+ virtual uint16 getPaletteCount() const override { return 0; }
+
+ unsigned int setup(uint16 width, uint16 height);
+
+ uint16 _width;
+ uint16 _height;
+ int16 _offX;
+ int16 _offY;
+ unsigned int _constantId;
+
+ byte *_data;
+
+ unsigned int refCnt;
+
+ CryoCursor();
+ virtual ~CryoCursor();
+ };
+
+ // Pointer to avoid to mutate Sprites when asking for a cursor
+ Graphics::Surface *_surface;
+ Common::Array<CryoCursor *> _cursors;
+ Common::Array<unsigned int> *_map;
+};
+
+} // End of namespace CryOmni3D
+
+#endif
diff --git a/engines/cryomni3d/versailles/data.cpp b/engines/cryomni3d/versailles/data.cpp
new file mode 100644
index 0000000000..7439f4dae0
--- /dev/null
+++ b/engines/cryomni3d/versailles/data.cpp
@@ -0,0 +1,1018 @@
+/* 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 "cryomni3d/versailles/engine.h"
+
+namespace CryOmni3D {
+namespace Versailles {
+
+const unsigned int CryOmni3DEngine_Versailles::kSpritesMapTable[] = {
+ /* 0 */ 242, 240, 243, 241, 256, 93, 97, 94, 160, 98, 178, 161, 179, 196, 197, 244,
+ /* 16 */ 142, 245, 143, 254, 95, 99, 113, 96, 100, 180, 114, 181, 73, 144, 74, 250,
+ /* 32 */ 202, 145, 170, 251, 203, 130, 206, 171, 49, 131, 207, 115, 116, 222, 75, 85,
+ /* 48 */ 76, 252, 204, 236, 86, 172, 253, 205, 237, 132, 81, 208, 173, 133, 82, 209,
+ /* 64 */ 24, 101, 25, 102, 87, 198, 88, 83, 258, 199, 84, 259, 257, 260, 26, 103,
+ /* 80 */ 28, 44, 27, 104, 29, 45, 200, 105, 201, 106, 162, 163, 32, 30, 46, 126,
+ /* 96 */ 33, 31, 47, 5, 127, 122, 219, 227, 123, 220, 107, 69, 108, 70, 164, 165,
+ /* 112 */ 89, 4, 90, 36, 34, 58, 128, 109, 37, 35, 255, 129, 110, 124, 125, 71,
+ /* 128 */ 40, 72, 41, 91, 92, 59, 228, 38, 7, 60, 111, 229, 39, 149, 121, 138,
+ /* 144 */ 112, 6, 139, 148, 42, 43, 232, 230, 233, 231, 140, 141, 134, 150, 135, 234,
+ /* 160 */ 151, 20, 226, 261, 235, 21, 262, 166, 246, 167, 136, 50, 247, 215, 152, 137,
+ /* 176 */ 51, 216, 153, 22, 117, 48, 23, 225, 118, 223, 182, 168, 248, 183, 169, 54,
+ /* 192 */ 52, 249, 217, 55, 53, 218, 8, 214, 119, 120, 186, 184, 154, 61, 187, 185,
+ /* 208 */ 155, 62, 56, 57, 188, 156, 65, 63, 210, 189, 157, 66, 64, 211, 19, 3,
+ /* 224 */ 80, 221, 1, 263, 78, 67, 174, 212, 68, 175, 213, 190, 191, 238, 0, 239,
+ /* 240 */ 224, 77, 146, 2, 147, 79, 158, 176, 159, 177, 194, 192, 195, 193, /*-1u, -1u*/
+};
+const unsigned int CryOmni3DEngine_Versailles::kSpritesMapTableSize = ARRAYSIZE(kSpritesMapTable);
+
+const LevelInitialState CryOmni3DEngine_Versailles::kLevelInitialStates[] = {
+ { 1, M_PI, 0. }, // Level 1
+ { 9, M_PI, 0. }, // Level 2
+ { 10, M_PI_2, 0. }, // Level 3
+ { 10, 0., 0. }, // Level 4
+ { 14, M_PI, 0. }, // Level 5
+ { 8, 0., 0. }, // Level 6
+ { 1, M_PI, 0. }, // Level 7
+ { 4, M_PI, 0. } // Level 8
+};
+
+const FakeTransitionActionPlace CryOmni3DEngine_Versailles::kFakeTransitions[] = {
+ {31141, 15},
+ {31142, 16},
+ {31143, 17},
+ {32201, 18},
+ {32202, 19},
+ {32203, 20},
+ {32204, 21},
+ {35330, 36},
+ {34172, 18},
+ {0, 0} // Must be the last one
+};
+
+void CryOmni3DEngine_Versailles::setupMessages() {
+ _messages.resize(146);
+#define SET_MESSAGE(id, str) _messages[id] = str
+ SET_MESSAGE(0, "Il est interdit d'ouvrir cette porte pour l'instant.");
+ SET_MESSAGE(1, "Cette porte est ferm" "\x8e" "e " "\x88" " clef.");
+ SET_MESSAGE(2, "Cette porte est ferm" "\x8e" "e.");
+ SET_MESSAGE(3, "Ce tiroir est vide.");
+ SET_MESSAGE(4, "Vous ne pouvez pas atteindre la b" "\x89" "che.");
+ SET_MESSAGE(5, "Il n'y a rien dans cet oranger");
+ SET_MESSAGE(6, "Ceci n'est pas un oranger!");
+ SET_MESSAGE(7, "Il fait trop sombre. ");
+ SET_MESSAGE(8, "Le coffre est ferm" "\x8e" ". ");
+ SET_MESSAGE(9, "Vous pouvez ouvrir la porte");
+ SET_MESSAGE(10, "Il faudrait quelque chose pour atteindre la bombe.");
+ SET_MESSAGE(11, "Ce vase est vide.");
+ SET_MESSAGE(12, "Maintenant, vous pouvez y aller.");
+ SET_MESSAGE(13, "Vous niavez plus le temps de vous renseigner sur la Cour!");
+ SET_MESSAGE(14, "Il est trop tard pour regarder les tableaux!");
+ SET_MESSAGE(16, "Vous ne pouvez pas atteindre le papier.");
+ SET_MESSAGE(15, "Attendez ! Transmettez donc vos indices " "\x88" " l'huissier.");
+ SET_MESSAGE(17, "Vers l'apothicairerie");
+ SET_MESSAGE(
+ 18,
+ "Attention : Vous allez pouvoir terminer ce niveau, mais vous n'avez pas effectu" "\x8e"
+ " toutes les actions necessaires pour la suite. "
+ "Il est conseill" "\x8e" " de SAUVEGARDER votre partie maintenant.");
+ SET_MESSAGE(
+ 19,
+ "Attention : Vous allez pouvoir terminer ce niveau, mais vous n'avez peut-" "\x89" "tre"
+ " pas effectu" "\x8e" " toutes les actions necessaires pour la suite. "
+ "Il est conseill" "\x8e" " de SAUVEGARDER votre partie maintenant.");
+ SET_MESSAGE(20, "Vous ne pouvez pas vous d" "\x8e" "placer en portant une " "\x8e" "chelle!");
+ SET_MESSAGE(21, "Il n'y a plus rien ici");
+ SET_MESSAGE(22, "Au revoir ...");
+ SET_MESSAGE(23, "VERSAILLES,");
+ SET_MESSAGE(24, "Complot " "\x88" " la Cour du Roi Soleil");
+ SET_MESSAGE(27, " Commencer une nouvelle partie");
+ SET_MESSAGE(26, " Reprendre la partie en cours");
+ SET_MESSAGE(44, " Reprendre la visite en cours");
+ SET_MESSAGE(28, " Charger une partie");
+ SET_MESSAGE(46, " Charger une visite");
+ SET_MESSAGE(29, " Sauver la partie");
+ SET_MESSAGE(45, " Sauver la visite");
+ SET_MESSAGE(25, "Consulter l'espace documentaire");
+ SET_MESSAGE(42, "Visiter le ch" "\x89" "teau");
+ SET_MESSAGE(48, " Omni3D : normal");
+ SET_MESSAGE(51, " Omni3D : rapide");
+ SET_MESSAGE(52, " Omni3D : tr" "\x8f" "s rapide");
+ SET_MESSAGE(49, " Omni3D : lent");
+ SET_MESSAGE(50, " Omni3D : tr" "\x8f" "s lent");
+ SET_MESSAGE(30, " Afficher les sous-titres : OUI");
+ SET_MESSAGE(31, " Afficher les sous-titres : NON");
+ SET_MESSAGE(32, " Musique : OUI");
+ SET_MESSAGE(33, " Musique : NON");
+ SET_MESSAGE(35, " Toutes les musiques sur disque dur (92 Mo)");
+ SET_MESSAGE(34, " Une seule musique sur disque dur (20 Mo)");
+ SET_MESSAGE(36, " Aucune musique sur disque dur (lecture CD)");
+ SET_MESSAGE(43, "Cr" "\x8e" "dits");
+ SET_MESSAGE(39, "Volume");
+ SET_MESSAGE(41, "");
+ SET_MESSAGE(40, "Quitter le jeu");
+ SET_MESSAGE(53, "Confirmer");
+ SET_MESSAGE(54, "Annuler");
+ SET_MESSAGE(55, "libre");
+ SET_MESSAGE(56, "sans nom");
+ SET_MESSAGE(57, "Attention : la partie en cours va " "\x89" "tre abandonn" "\x8e" "e.");
+ SET_MESSAGE(58, "Retour");
+ SET_MESSAGE(59, "Le chateau");
+ SET_MESSAGE(60, "Retour Menu Principal");
+ SET_MESSAGE(61, "Sommaire Espace documentaire");
+ SET_MESSAGE(62, "Plan du ch" "\x89" "teau et des jardins");
+ SET_MESSAGE(63, "Plan des int" "\x8e" "rieurs du ch" "\x89" "teau");
+ SET_MESSAGE(64, "Probl" "\x8f" "me d'" "\x8e" "criture sur dique dur : disque plein ");
+ SET_MESSAGE(66, "Veuillez ins" "\x8e" "rer le CD ");
+ SET_MESSAGE(67, "Veuillez ins" "\x8e" "rer le CD %d et presser une touche");
+ SET_MESSAGE(68, "Les arts");
+ SET_MESSAGE(69, "Le r" "\x8f" "gne");
+ SET_MESSAGE(70, "La Cour");
+ SET_MESSAGE(71, "Vie de Ch" "\x89" "teau");
+ SET_MESSAGE(72, "Le ch" "\x89" "teau et les jardins");
+ SET_MESSAGE(73, "Chronologie");
+ SET_MESSAGE(74, "Bassin d'Apollon");
+ SET_MESSAGE(75, "Le Ch" "\x89" "teau");
+ SET_MESSAGE(76, "Colonnade");
+ SET_MESSAGE(77, "Labyrinthe");
+ SET_MESSAGE(78, "Latone");
+ SET_MESSAGE(79, "Orangerie");
+ SET_MESSAGE(80, "Parterre d'eau");
+ SET_MESSAGE(81, "Tapis vert");
+ SET_MESSAGE(86, "Grand Canal");
+ SET_MESSAGE(87, "Parterre du Midi");
+ SET_MESSAGE(88, "Parterre du nord");
+ SET_MESSAGE(89, "Potager du Roi");
+ SET_MESSAGE(90, "Salle de bal");
+ SET_MESSAGE(91, "Bassin de Neptune");
+ SET_MESSAGE(92, "Pi" "\x8f" "ce d'eau des suisses");
+ SET_MESSAGE(82, "Grandes Ecuries");
+ SET_MESSAGE(83, "Petites Ecuries");
+ SET_MESSAGE(84, "Les jardins");
+ SET_MESSAGE(85, "Avant cour");
+ SET_MESSAGE(93, "Aiguilles (Inutile!)");
+ SET_MESSAGE(94, "Ciseaux");
+ SET_MESSAGE(95, "Papier");
+ SET_MESSAGE(96, "Pamphlet sur les arts");
+ SET_MESSAGE(97, "Petite clef 1");
+ SET_MESSAGE(98, "Papier r" "\x8e" "v" "\x8e" "l" "\x8e" "");
+ SET_MESSAGE(99, "Papier t" "\x89" "ch" "\x8e" "");
+ SET_MESSAGE(100, "Papier du coffre");
+ SET_MESSAGE(101, "Pamphlet sur la lign" "\x8e" "e royale");
+ SET_MESSAGE(102, "Bougie allum" "\x8e" "e");
+ SET_MESSAGE(103, "Bougie");
+ SET_MESSAGE(104, "Clef ");
+ SET_MESSAGE(105, "Carton " "\x88" " dessin");
+ SET_MESSAGE(106, "Carton " "\x88" " dessin");
+ SET_MESSAGE(107, "Fausse esquisse");
+ SET_MESSAGE(108, "Echelle");
+ SET_MESSAGE(109, "Esquisse d" "\x8e" "truite");
+ SET_MESSAGE(110, "pinceau");
+ SET_MESSAGE(111, "pinceau Or");
+ SET_MESSAGE(112, "pinceau Rouge");
+ SET_MESSAGE(113, "Fusain");
+ SET_MESSAGE(114, "Papier");
+ SET_MESSAGE(115, "Pamphlet sur liarchitecture");
+ SET_MESSAGE(116, "Petite clef 2");
+ SET_MESSAGE(117, "Archer(inutile!)");
+ SET_MESSAGE(118, "Partition");
+ SET_MESSAGE(119, "Queue de billard");
+ SET_MESSAGE(120, "Autorisation");
+ SET_MESSAGE(121, "Reproduction des m" "\x8e" "dailles");
+ SET_MESSAGE(122, "Tiroir " "\x88" " m" "\x8e" "dailles");
+ SET_MESSAGE(123, "Clef de la petite porte diApollon");
+ SET_MESSAGE(124, "Nourriture");
+ SET_MESSAGE(125, "Pamphlet sur la religion");
+ SET_MESSAGE(126, "Epigraphe");
+ SET_MESSAGE(127, "Pamphlet sur le gouvernement");
+ SET_MESSAGE(128, "Plume");
+ SET_MESSAGE(129, "Pense-b" "\x89" "te");
+ SET_MESSAGE(130, "Lunette");
+ SET_MESSAGE(131, "Plan Vauban");
+ SET_MESSAGE(132, "Plan Vauban");
+ SET_MESSAGE(133, "Cordon");
+ SET_MESSAGE(134, "Gravure");
+ SET_MESSAGE(135, "Petite clef 3");
+ SET_MESSAGE(136, "Petite clef 4");
+ SET_MESSAGE(137, "M" "\x8e" "morandum");
+ SET_MESSAGE(138, "Plans du chateau");
+ SET_MESSAGE(139, "Plans du chateau");
+ SET_MESSAGE(140, "Clef des combles");
+ SET_MESSAGE(141, "Fables");
+ SET_MESSAGE(142, "Plan du Labyrinthe");
+ SET_MESSAGE(143, "Outil");
+ SET_MESSAGE(144, "M" "\x8e" "dicament");
+ SET_MESSAGE(145, "Eteignoir");
+#undef SET_MESSAGE
+}
+
+void CryOmni3DEngine_Versailles::setupPaintingsTitles() {
+ _paintingsTitles.reserve(48);
+#define SET_PAINTING_TITLE(str) _paintingsTitles.push_back(str)
+ SET_PAINTING_TITLE("\"Entr" "\x8e" "e des animaux dans l'arche\"\rGerolamo Bassano"); // 0: 41201
+ SET_PAINTING_TITLE("\"Le repas d'Emma" "\x9f" "s\"\rJacopo Bassano"); // 1: 41202
+ SET_PAINTING_TITLE("\"La Madeleine aux pieds de J\x8esus Christ\"\rSustris"); // 2: 41203
+ SET_PAINTING_TITLE("\"La sortie de l'arche\"\rGerolamo Bassano"); // 3: 41204
+ SET_PAINTING_TITLE("\"Le frappement du rocher\"\rJacopo Bassano"); // 4: 41205
+ SET_PAINTING_TITLE("\"La Bataille d'Arbelles\"\rJoseph Parrocel"); // 5: 41301
+ SET_PAINTING_TITLE("\"Alexandre Le Grand vainqueur de Darius " "\x88"
+ " la bataille d'Arbelles\"\rLe Bourguignon"); // 6: 41302
+ SET_PAINTING_TITLE("\"Le Combat de Leuze\"\rJoseph Parrocel"); // 7: 42401
+ SET_PAINTING_TITLE("\"Sainte C" "\x8e"
+ "cile avec un ange tenant une partition musicale\"\rDominiquin"); // 8: 42901
+ SET_PAINTING_TITLE("\"Don Francisco du Moncada \"\rVan Dyck"); // 9: 42902
+ SET_PAINTING_TITLE("\"Le Petit Saint Jean Baptiste\"\rLe Carrache"); // 10: 42903
+ SET_PAINTING_TITLE("\"Saint Mathieu\"\rValentin"); // 11: 42904
+ SET_PAINTING_TITLE("\"Le Denier de C" "\x8e" "sar \"\rValentin"); // 12: 42905
+ SET_PAINTING_TITLE("\"Saint Luc\"\rValentin"); // 13: 42906
+ SET_PAINTING_TITLE("\"Le mariage mystique de Sainte Catherine\"\r Alessandro Turchi"); // 14: 42907
+ SET_PAINTING_TITLE("\"R" "\x8e" "union de buveurs\"\rNicolas Tournier"); // 15: 42908
+ SET_PAINTING_TITLE("\"La diseuse de Bonne aventure \"\rValentin"); // 16: 42909
+ SET_PAINTING_TITLE("\"le roi David jouant de la harpe \"\rDominiquin"); // 17: 42910
+ SET_PAINTING_TITLE("\"Sainte Madeleine\"\rDominiquin"); // 18: 42911
+ SET_PAINTING_TITLE("\"Autoportrait \"\rVan Dyck"); // 19: 42912
+ SET_PAINTING_TITLE("\"Saint Jean l'" "\x8e" "vang" "\x8e" "liste\"\r Valentin"); // 20: 42913
+ SET_PAINTING_TITLE("\"Agar secouru par un ange \"\rGiovanni Lanfranco"); // 21: 42914
+ SET_PAINTING_TITLE("\"Saint Marc \"\rValentin"); // 22: 42915
+ SET_PAINTING_TITLE("\"M" "\x8e" "l" "\x8e" "agre ayant " "\x88"
+ " ses pieds la hure du sanglier de Calydon\"\r Jacques Rousseau"); // 23: 43090
+ SET_PAINTING_TITLE("\"Le Roi en costume romain\"\rJean Warin"); // 24: 43091
+ SET_PAINTING_TITLE("\"attalante\"\rJacques Rousseau"); // 25: 43092
+ SET_PAINTING_TITLE("\"En" "\x8e" "e portant Anchise\"\rSpada"); // 26: 43100
+ SET_PAINTING_TITLE("\"David et Bethsab" "\x8e" "e\"\rV" "\x8e" "ron" "\x8f" "se"); // 27: 43101
+ SET_PAINTING_TITLE("\"La fuite en Egypte\"\rGuido R" "\x8e" "ni "); // 28: 43102
+ SET_PAINTING_TITLE("\"Louis XIV " "\x88" " cheval\"\rPierre Mignard"); // 29: 43103
+ SET_PAINTING_TITLE("\"La magnificience royale & le progr" "\x8f"
+ "s des beaux arts\"\rHouasse"); // 30: 43104
+ SET_PAINTING_TITLE("\"Le Sacrifice d'Iphig" "\x8e" "nie\"\rCharles de la Fosse"); // 31: 43130
+ SET_PAINTING_TITLE("\"Buste de Louis XIV\"\rsculpt" "\x8e"
+ " par le Chevalier Bernin "); // 32: 43131
+ SET_PAINTING_TITLE("\"Diane d" "\x8e" "couvrant son berger Endymion endormi dans les bras de Morph"
+ "\x8e" "e\"\rGabriel Blanchard"); // 33: 43132
+ SET_PAINTING_TITLE("\"La vierge & Saint Pierre\"\rGuerchin"); // 34: 43140
+ SET_PAINTING_TITLE("\"Les P" "\x8e" "lerins d'Emma" "\x9f" "s\"\rV" "\x8e" "ron" "\x8f"
+ "se"); // 35: 43141
+ SET_PAINTING_TITLE("\"La sainte Famille\"\rV" "\x8e" "ron" "\x8f" "se"); // 36: 43142
+ SET_PAINTING_TITLE("\"La famille de Darius aux pieds d'Alexandre\"\rCharles LeBrun"); // 37: 43143
+ SET_PAINTING_TITLE("\"Saint Jean-Baptiste\"\rRapha" "\x91" "l"); // 38: 43144
+ SET_PAINTING_TITLE("\"Marie de m" "\x8e" "dicis\"\rVan Dyck"); // 39: 43150
+ SET_PAINTING_TITLE("\"Hercule luttant contre Achelous\"\rGuido R" "\x8e" "ni"); // 40: 43151
+ SET_PAINTING_TITLE("\"Le Centaure Nessus porte Dejanire\"\rGuido R" "\x8e" "ni"); // 41: 43152
+ SET_PAINTING_TITLE("\"Saint Franìois d'Assise r" "\x8e" "confort" "\x8e" " apr" "\x8f"
+ "s sa stigmatisation\"\rSeghers"); // 42: 43153
+ SET_PAINTING_TITLE("\"Thomiris faisant tremper la t" "\x90"
+ "te de Cyrus dans le sang\"\rRubens"); // 43: 43154
+ SET_PAINTING_TITLE("\"Hercule tuant l'Hydre\"\rGuido R" "\x8e" "ni"); // 44: 43155
+ SET_PAINTING_TITLE("\"Hercule sur le b" "\x9e" "cher\"\rGuido R" "\x8e" "ni"); // 45: 43156
+ SET_PAINTING_TITLE("\"Portrait du Prince Palatin & de son fr" "\x8f"
+ "re le Prince Robert\"\rVan Dyck"); // 46: 43157
+ SET_PAINTING_TITLE("\"La descente de Croix \"\rCharles Lebrun"); // 47: 45260
+#undef SET_PAINTING_TITLE
+}
+
+struct VideoSubSetting {
+ const char *videoName;
+ int16 textLeft;
+ int16 textTop;
+ int16 textRight;
+ int16 textBottom;
+ int16 drawLeft;
+ int16 drawTop;
+ int16 drawRight;
+ int16 drawBottom;
+};
+
+static const VideoSubSetting videoSubSettings[] = {
+ {"11D_LEB", 15, 11, 190, 479, 208, 129, 562, 479},
+ {"11E_HUI", 330, 9, 620, 479, 111, 109, 321, 341},
+ {"11E_MAN", 403, 12, 630, 479, 134, 89, 390, 405},
+ {"11E_RAC", 10, 9, 241, 479, 271, 147, 628, 479},
+ {"12E_HUI", 361, 16, 618, 479, 84, 107, 330, 479},
+ {"13F_HUI", 373, 12, 633, 479, 96, 88, 341, 479},
+ {"21B1_HUI", 355, 13, 625, 479, 96, 104, 337, 479},
+ {"21F_BON", 324, 11, 628, 479, 84, 74, 307, 479},
+ {"21F_BON2", 11, 13, 298, 479, 321, 99, 536, 424},
+ {"21G_CON", 12, 13, 255, 479, 273, 156, 539, 479},
+ {"21G_DAU", 358, 11, 631, 479, 82, 151, 346, 479},
+ {"21G_HUI", 309, 17, 626, 479, 77, 85, 304, 479},
+ {"21I_LEB", 343, 10, 628, 479, 38, 125, 330, 479},
+ {"21Z_ALI", 380, 13, 627, 479, 184, 106, 369, 479},
+ {"21Z_BOU", 365, 13, 629, 479, 95, 65, 341, 321},
+ {"21Z_MON", 12, 11, 309, 479, 336, 101, 561, 406},
+ {"21Z_PR", 10, 16, 352, 471, 375, 104, 567, 400},
+ {"22G_DAU", 339, 13, 629, 479, 114, 152, 326, 479},
+ {"23I_LEB", 341, 15, 627, 479, 67, 140, 325, 410},
+ {"24Z_BON", 253, 23, 620, 479, 58, 166, 228, 439},
+ {"31J_SUI", 9, 9, 183, 475, 195, 159, 428, 479},
+ {"31L1_LUL", 367, 16, 628, 477, 136, 164, 359, 472},
+ {"31M_SUI", 19, 16, 212, 479, 231, 193, 395, 479},
+ {"31O_SUIA", 11, 12, 175, 479, 186, 118, 490, 479},
+ {"31O_SUIP", 12, 9, 277, 466, 296, 183, 380, 349},
+ {"31Q_SUI", 334, 15, 626, 479, 158, 169, 313, 308},
+ {"31X_BO", 332, 11, 615, 479, 89, 78, 313, 296},
+ {"31X_BON", 329, 12, 618, 456, 0, 171, 243, 479},
+ {"31X_LOU", 12, 9, 267, 447, 280, 88, 639, 479},
+ {"31X_SEI", 352, 12, 626, 479, 102, 98, 340, 479},
+ {"32J_CRO", 418, 7, 618, 477, 103, 58, 402, 438},
+ {"32M_MR", 13, 11, 175, 477, 184, 113, 476, 447},
+ {"32Q_MON", 375, 17, 623, 479, 248, 161, 341, 259},
+ {"32Q_RAC", 294, 11, 627, 479, 110, 152, 287, 479},
+ {"32Q_RAC2", 374, 13, 625, 479, 0, 101, 366, 479},
+ {"31O_SUIA", 11, 12, 175, 479, 186, 118, 490, 479},
+ {"41C_HUI", 345, 17, 626, 479, 69, 147, 330, 479},
+ {"41X2_CRO", 13, 13, 281, 479, 305, 113, 548, 427},
+ {"42C_BON", 15, 13, 347, 479, 368, 173, 525, 410},
+ {"43B1_MAI", 264, 15, 625, 479, 127, 154, 249, 296},
+ {"43B1_SEI", 17, 14, 369, 479, 390, 142, 639, 479},
+ {"43C_CON", 312, 11, 635, 479, 21, 137, 294, 476},
+ {"43C_DUR", 11, 10, 295, 479, 311, 166, 639, 479},
+ {"44C_BON", 17, 12, 331, 479, 358, 181, 531, 407},
+ {"4_MAI", 325, 14, 630, 479, 35, 48, 308, 363},
+ {"51L_LOU", 11, 11, 616, 161, 154, 165, 400, 479},
+ {"51L_PRI", 26, 19, 601, 153, 130, 167, 311, 479},
+ {"51M_LEB", 41, 29, 615, 188, 49, 200, 432, 479},
+ {"51M_MAN", 23, 19, 618, 179, 211, 195, 449, 479},
+ {"52A4_LAC", 12, 11, 258, 479, 273, 184, 465, 383},
+ {"52L_BOU", 12, 12, 190, 479, 307, 56, 592, 332},
+ {"52L_LOU", 8, 13, 604, 168, 135, 171, 413, 479},
+ {"52L_PRI", 20, 17, 610, 167, 336, 182, 639, 479},
+ {"53N_BON", 351, 13, 629, 479, 62, 119, 343, 418},
+ {"54I_BON", 343, 14, 623, 479, 72, 117, 339, 440},
+ {"61_BON", 10, 7, 311, 479, 336, 101, 581, 479},
+ {"61_DUC", 10, 14, 344, 473, 376, 156, 639, 479},
+ {"61_LEN", 13, 9, 269, 479, 285, 63, 590, 479},
+ {"62_DUC", 18, 21, 317, 479, 388, 154, 614, 479},
+};
+
+void CryOmni3DEngine_Versailles::setupDialogVariables() {
+#define SET_DIAL_VARIABLE(id, var) _dialogsMan.setupVariable(id, var)
+ SET_DIAL_VARIABLE(0, "JOUEUR-PARLE-HUISSIER-PETIT-LEVER");
+ SET_DIAL_VARIABLE(1, "HUBAS-PARLE-LEVER1");
+ SET_DIAL_VARIABLE(2, "HUBAS-PARLE-LEVER2");
+ SET_DIAL_VARIABLE(3, "LEBRUN-DIT-COLBERT");
+ SET_DIAL_VARIABLE(4, "LEBRUN-PARLE-ESQUISSE");
+ SET_DIAL_VARIABLE(5, "JOUEUR-PARLE-HUISSIER-GRAND-LEVER");
+ SET_DIAL_VARIABLE(6, "BONTEMPS-PARLE-MAINTENON");
+ SET_DIAL_VARIABLE(7, "BONTEMPS-PARLE-MAINTENON2");
+ SET_DIAL_VARIABLE(8, "BONTEMPS-DEMANDE-INDICE");
+ SET_DIAL_VARIABLE(9, "BONTEMPS-DIT-ENQUETE");
+ SET_DIAL_VARIABLE(10, "JOUEUR-CONFIE-MESSAGE-HUISSIER");
+ SET_DIAL_VARIABLE(11, "JOUEUR-PARLE-HUIMA1");
+ SET_DIAL_VARIABLE(12, "MONSEIGNEUR-ATTEND-ESQUISSES");
+ SET_DIAL_VARIABLE(13, "MONSEIGNEUR-PREVIENT-BONTEMPS");
+ SET_DIAL_VARIABLE(14, "JOUEUR-MENT-MONSEIGNEUR");
+ SET_DIAL_VARIABLE(15, "JOUEUR-ECOUTE-ALIAS");
+ SET_DIAL_VARIABLE(16, "JOUEUR-PARLE-HUCON");
+ SET_DIAL_VARIABLE(17, "BONTEMPS-ATTEND-OBJET-GALLERIE");
+ SET_DIAL_VARIABLE(18, "SUISSE-APOLLON-PARLE-CLEF");
+ SET_DIAL_VARIABLE(19, "SUISSE-CABINET-DEMANDE-AUTORISATION");
+ SET_DIAL_VARIABLE(20, "SUISSE-VU-AUTORISATION");
+ SET_DIAL_VARIABLE(21, "CROISSY-ACCEPTE-TEXTE");
+ SET_DIAL_VARIABLE(22, "JOUEUR-POSSEDE-CLEF-PETITE-PORTE");
+ SET_DIAL_VARIABLE(23, "SUISSE-REFUSE-CLEF");
+ SET_DIAL_VARIABLE(24, "LULLY-ATTEND-MISSION-JOUEUR");
+ SET_DIAL_VARIABLE(25, "LULLY-DONNE-MISSION1-JOUEUR");
+ SET_DIAL_VARIABLE(26, "LULLY-DONNE-MISSION-JOUEUR");
+ SET_DIAL_VARIABLE(27, "RACINE-REPOND-ETRANGERE");
+ SET_DIAL_VARIABLE(28, "RACINE-REPOND-PEUPLES");
+ SET_DIAL_VARIABLE(29, "LULLY-DONNE-MISSION2-JOUEUR");
+ SET_DIAL_VARIABLE(30, "LULLY-DIT-CHAT-PENDU-JOUEUR");
+ SET_DIAL_VARIABLE(31, "JOUEUR-DIT-PEUPLES-LULLY");
+ SET_DIAL_VARIABLE(32, "LALANDE-PARLE-BONTEMPS-SCENE3");
+ SET_DIAL_VARIABLE(33, "BONTEMPS-DONNE-AUTORISATION-CURIOSITES");
+ SET_DIAL_VARIABLE(34, "BONTEMPS-ATTEND-PAMPHLET");
+ SET_DIAL_VARIABLE(35, "BONTEMPS-VU-PAMPHLET-DECHIFFRE-LULLY");
+ SET_DIAL_VARIABLE(36, "CROISSY-DIT-INEPTIES");
+ SET_DIAL_VARIABLE(37, "CROISSY-ATTEND-PAMPHLET2");
+ SET_DIAL_VARIABLE(38, "CROISSY-ATTEND-MEDAILLE");
+ SET_DIAL_VARIABLE(39, "CROISSY-ATTEND-PAMPHLET2-2");
+ SET_DIAL_VARIABLE(40, "JOUEUR-PARLE-CROISSY1");
+ SET_DIAL_VARIABLE(41, "MONSIEUR-PARLE-LALANDE1");
+ SET_DIAL_VARIABLE(42, "MONSIEUR-ATTEND-FUSAIN");
+ SET_DIAL_VARIABLE(43, "MONSIEUR-DONNE-SOLUTION-MEDAILLES");
+ SET_DIAL_VARIABLE(44, "HUISSIER-DIT-DINER");
+ SET_DIAL_VARIABLE(45, "HUISSIER-DIT-PREVENIR-BONTEMPS");
+ SET_DIAL_VARIABLE(46, "JOUEUR-POSSEDE-PAMPHLET-RELIGION");
+ SET_DIAL_VARIABLE(47, "JOUEUR-PARLE-BONTEMPS-SCENE4");
+ SET_DIAL_VARIABLE(48, "BONTEMPS-VU-PAPIER-CROISSY");
+ SET_DIAL_VARIABLE(49, "BONTEMPS-ATTEND-OBJET-SCENE4");
+ SET_DIAL_VARIABLE(50, "BONTEMPS-VU-PAMPHLET-GOUVERNEMENT");
+ SET_DIAL_VARIABLE(51, "JOUEUR-PARLE-VAUBAN");
+ SET_DIAL_VARIABLE(52, "JOUEUR-PARLE-CODE-LOUVOIS");
+ SET_DIAL_VARIABLE(53, "LALANDE-ECOUTE-LOUVOIS");
+ SET_DIAL_VARIABLE(54, "JOUEUR-PARLE-LACHAIZE");
+ SET_DIAL_VARIABLE(55, "JOUEUR-PARLE-LACHAIZE2");
+ SET_DIAL_VARIABLE(56, "LACHAIZE-ATTEND-TEXTE");
+ SET_DIAL_VARIABLE(57, "LACHAIZE-VU-PAMPHLET-RELIGION");
+ SET_DIAL_VARIABLE(58, "LACHAIZE-DIT-REFORME");
+ SET_DIAL_VARIABLE(59, "LACHAIZE-PARLE-BOUILLON");
+ SET_DIAL_VARIABLE(60, "BOUILLON-DIT-DRAGONNADES");
+ SET_DIAL_VARIABLE(61, "JOUEUR-PARLE-BOUILLON");
+ SET_DIAL_VARIABLE(62, "LACHAIZE-TROUVE-ECROUELLES");
+ SET_DIAL_VARIABLE(63, "LACHAIZE-DIT-DRAGONNADES");
+ SET_DIAL_VARIABLE(64, "LACHAIZE-DEMANDE-TEXTE");
+ SET_DIAL_VARIABLE(65, "LACHAIZE-PARLE-ARCHITECTURE");
+ SET_DIAL_VARIABLE(66, "JOUEUR-DIT-DRAGONNADES");
+ SET_DIAL_VARIABLE(67, "BOUILLON-ATTEND-PAMPHLET");
+ SET_DIAL_VARIABLE(68, "BONTEMPS-PARLE-LUSTRE");
+ SET_DIAL_VARIABLE(69, "BONTEMPS-ATTEND-MEMORANDUM2");
+ SET_DIAL_VARIABLE(70, "BONTEMPS-DIT-PROMENADE");
+ SET_DIAL_VARIABLE(71, "BONTEMPS-ATTEND-MEMORANDUM");
+ SET_DIAL_VARIABLE(72, "LENOTRE-DIT-CALME");
+ SET_DIAL_VARIABLE(73, "MAINE-DIT-APOTHICAIRIE");
+ SET_DIAL_VARIABLE(74, "JOUEUR-PARLE-BONTEMPS-SCENE6");
+ SET_DIAL_VARIABLE(75, "{JOUEUR-ESSAYE-OUVRIR-PORTE-CHAMBRE}");
+ SET_DIAL_VARIABLE(76, "{JOUEUR-TROUVE-TITRE-ET-PAMPHLET}");
+ SET_DIAL_VARIABLE(77, "{JOUEUR-ESSAYE-OUVRIR-PORTE-SALON}");
+ SET_DIAL_VARIABLE(78, "{JOUEUR-MONTRE-PAPIER-ECRIT-ENCRE-SYMPATHIQUE}");
+ SET_DIAL_VARIABLE(79, "{JOUEUR-MONTRE-UN-PAMPHLET}");
+ SET_DIAL_VARIABLE(80, "{JOUEUR-MONTRE-TOUT-AUTRE-OBJET}");
+ SET_DIAL_VARIABLE(81, "{JOUEUR-MONTRE-PAMPHLET-ARTS}");
+ SET_DIAL_VARIABLE(82, "{JOUEUR-A-MONTRE-ESQUISSES-NON-TRIEES-LEBRUN}");
+ SET_DIAL_VARIABLE(83, "{JOUEUR-DONNE-ESQUISSES}");
+ SET_DIAL_VARIABLE(84, "{JOUEUR-SE-DIRIGE-VERS-MONSEIGNEUR-AVEC-ESQUISSES}");
+ SET_DIAL_VARIABLE(85, "{JOUEUR-PRESENTE-FAUX-CROQUIS3}");
+ SET_DIAL_VARIABLE(86, "{JOUEUR-PRESENTE-FAUX-CROQUIS2}");
+ SET_DIAL_VARIABLE(87, "{JOUEUR-PRESENTE-FAUX-CROQUIS}");
+ SET_DIAL_VARIABLE(88, "{LE JOUEUR-PRESENTE-ESQUISSES-TRIEES}");
+ SET_DIAL_VARIABLE(89, "{LE JOUEUR-PRESENTE-AUTRES-ESQUISSES-OU-ESQUISSE-NON-TRIEES}");
+ SET_DIAL_VARIABLE(90, "{JOUEUR-PRESENTE-PAMPHLET-SUR-LEBRUN}");
+ SET_DIAL_VARIABLE(91, "{JOUEUR-PRESENTE-TOUT-AUTRE-PAMPHLET-OU-LETTRE}");
+ SET_DIAL_VARIABLE(92, "{JOUEUR-MONTRE-ESQUISSE-DETRUITE}");
+ SET_DIAL_VARIABLE(93, "{JOUEUR-MONTRE-TITRE-FABLE-APPARU-SUR-ESQUISSE}");
+ SET_DIAL_VARIABLE(94, "{JOUEUR-MONTRE-AUTORISATION-DE-BONTEMPS}");
+ SET_DIAL_VARIABLE(95, "{LE JOUEUR-A-TENTE-OUVRIR-PETITE-PORTE}");
+ SET_DIAL_VARIABLE(96, "{JOUEUR-POSSEDE-CLE}");
+ SET_DIAL_VARIABLE(97, "{JOUEUR-PRESENTE-PAMPHLET-PARTITION}");
+ SET_DIAL_VARIABLE(98, "{JOUEUR-MONTRE-PAMPHLET-DECHIFFRE-PAR-LULLY}");
+ SET_DIAL_VARIABLE(99, "{JOUEUR-MONTRE-MEDAILLES-MONSIEUR}");
+ SET_DIAL_VARIABLE(100, "{JOUEUR-MONTRE-PAMPHLET-ARCHITECTURE}");
+ SET_DIAL_VARIABLE(101, "{JOUEUR-MONTRE-EPIGRAPHE-MEDAILLES}");
+ SET_DIAL_VARIABLE(102, "{JOUEUR-MONTRE-TOUT-AUTRE-CHOSE}");
+ SET_DIAL_VARIABLE(103, "{JOUEUR-POSSEDE-FUSAIN-MEDAILLES}");
+ SET_DIAL_VARIABLE(104, "{JOUEUR-MONTRE-FUSAIN-MEDAILLES}");
+ SET_DIAL_VARIABLE(105, "{JOUEUR-PRESENTE-OBJET-HUISSIER}");
+ SET_DIAL_VARIABLE(106, "{JOUEUR-APPROCHE-MADAME-MAINTENON}");
+ SET_DIAL_VARIABLE(107, "{JOUEUR-DONNE-REPAS}");
+ SET_DIAL_VARIABLE(108, "{JOUEUR-TROUVE-PLANS-VAUBAN}");
+ SET_DIAL_VARIABLE(109, "{JOUEUR-ALLER-BUREAU-LOUVOIS}");
+ SET_DIAL_VARIABLE(110, "{JOUEUR-MONTRE-PAMPHLET-RELIGION}");
+ SET_DIAL_VARIABLE(111, "{JOUEUR-MONTRE-PAMPHLET-GOUVERNEMENT}");
+ SET_DIAL_VARIABLE(112, "{JOUEUR-MONTRE-PAPIER-CROISSY}");
+ SET_DIAL_VARIABLE(113, "{JOUEUR-MONTRE-ECROUELLES}");
+ SET_DIAL_VARIABLE(114, "{LACHAIZE-TIENT-TEXTE}");
+ SET_DIAL_VARIABLE(115, "{JOUEUR-VU-PLANS-SALON-DIANE}");
+ SET_DIAL_VARIABLE(116, "{JOUEUR-VU-MEMORANDUM-DANS-LUSTRE-DU-SALON-DE-LA-GUERRE}");
+ SET_DIAL_VARIABLE(117, "{JOUEUR-VU-MEMORANDUM-DANS-LUSTRE-DU-SALON-APOLLON}");
+ SET_DIAL_VARIABLE(118, "{JOUEUR-MONTRE-MEMORANDUM}");
+ SET_DIAL_VARIABLE(119, "{JOUEUR-POSSEDE-CLEF-3-ET-4}");
+ SET_DIAL_VARIABLE(120, "{JOUEUR-DONNE-SIROP-DE-ROSE}");
+ SET_DIAL_VARIABLE(121, "{JOUEUR-DONNE-AUTRE-MEDICAMENT}");
+ SET_DIAL_VARIABLE(122, "{DUC_MAIN_A_PARLE}");
+ SET_DIAL_VARIABLE(123, "{LEVEL1_FINI}");
+ SET_DIAL_VARIABLE(124, "{LEVEL2_FINI}");
+ SET_DIAL_VARIABLE(125, "{LEVEL3_FINI}");
+ SET_DIAL_VARIABLE(126, "{LEVEL4_FINI}");
+ SET_DIAL_VARIABLE(127, "{LEVEL5_FINI}");
+ SET_DIAL_VARIABLE(128, "{LEVEL6_FINI}");
+ SET_DIAL_VARIABLE(129, "{LEVEL7_FINI}");
+ SET_DIAL_VARIABLE(130, "{JOUEUR_POSSEDE_PAMPHLET_ARCHI}");
+ SET_DIAL_VARIABLE(131, "{FAUSSE_ESQ_OK}");
+ SET_DIAL_VARIABLE(132, "{CURRENT_GAME_TIME1}");
+ SET_DIAL_VARIABLE(133, "{CURRENT_GAME_TIME2}");
+ SET_DIAL_VARIABLE(134, "{CURRENT_GAME_TIME3}");
+ SET_DIAL_VARIABLE(135, "{CURRENT_GAME_TIME4}");
+ SET_DIAL_VARIABLE(136, "{CURRENT_GAME_TIME5}");
+ SET_DIAL_VARIABLE(137, "{JOUEUR_POSSEDE_EPIGRAPHE}");
+#undef SET_DIAL_VARIABLE
+ for (unsigned int i = 0; i < ARRAYSIZE(videoSubSettings); i++) {
+ const VideoSubSetting &vss = videoSubSettings[i];
+ _dialogsMan.registerSubtitlesSettings(
+ vss.videoName,
+ DialogsManager::SubtitlesSettings(
+ vss.textLeft, vss.textTop, vss.textRight, vss.textBottom,
+ vss.drawLeft, vss.drawTop, vss.drawRight, vss.drawBottom));
+ }
+}
+
+void CryOmni3DEngine_Versailles::initPlacesStates() {
+#define SET_PLACE_STATE(id, init, filter, docImage) _placeStates[id] = PlaceState(init, filter, docImage)
+#define FILTER_EVENT(level, place) &CryOmni3DEngine_Versailles::filterEventLevel ## level ## Place ## place
+#define INIT_PLACE(level, place) &CryOmni3DEngine_Versailles::initPlaceLevel ## level ## Place ## place
+ if (_currentLevel == 1) {
+ _placeStates.resize(15);
+ SET_PLACE_STATE(1, nullptr, FILTER_EVENT(1, 1), "VS22");
+ SET_PLACE_STATE(2, nullptr, FILTER_EVENT(1, 2), "VS20");
+ SET_PLACE_STATE(3, INIT_PLACE(1, 3), FILTER_EVENT(1, 3), "VS19");
+ SET_PLACE_STATE(4, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(5, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(6, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(7, nullptr, nullptr, nullptr); // Filter is a leftover
+ SET_PLACE_STATE(8, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(9, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(10, nullptr, nullptr, "VS31");
+ SET_PLACE_STATE(11, nullptr, nullptr, "VS31");
+ SET_PLACE_STATE(12, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(13, nullptr, nullptr, "VS31");
+ SET_PLACE_STATE(14, nullptr, FILTER_EVENT(1, 14), nullptr);
+ } else if (_currentLevel == 2) {
+ _placeStates.resize(15);
+ SET_PLACE_STATE(1, nullptr, nullptr, "VS22");
+ SET_PLACE_STATE(2, nullptr, nullptr, "VS20");
+ SET_PLACE_STATE(3, nullptr, nullptr, "VS19");
+ SET_PLACE_STATE(4, nullptr, nullptr, "VS18");
+ SET_PLACE_STATE(5, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(6, nullptr, nullptr, "VS19");
+ SET_PLACE_STATE(7, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(8, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(9, nullptr, nullptr, "VS23");
+ SET_PLACE_STATE(10, nullptr, nullptr, "VS31");
+ SET_PLACE_STATE(11, nullptr, nullptr, "VS31");
+ SET_PLACE_STATE(12, nullptr, nullptr, "VS24");
+ SET_PLACE_STATE(13, nullptr, nullptr, "VS31");
+ SET_PLACE_STATE(14, nullptr, nullptr, nullptr);
+ } else if (_currentLevel == 3) {
+ _placeStates.resize(25);
+ SET_PLACE_STATE(1, nullptr, nullptr, "VS35");
+ SET_PLACE_STATE(2, nullptr, nullptr, "VS40");
+ SET_PLACE_STATE(3, nullptr, nullptr, "VS40");
+ SET_PLACE_STATE(4, nullptr, nullptr, "VS36");
+ SET_PLACE_STATE(5, nullptr, nullptr, "VS36");
+ SET_PLACE_STATE(6, nullptr, nullptr, "VS30");
+ SET_PLACE_STATE(7, nullptr, nullptr, "VS30");
+ SET_PLACE_STATE(8, nullptr, nullptr, "VS30");
+ SET_PLACE_STATE(9, nullptr, nullptr, "VS39");
+ SET_PLACE_STATE(10, nullptr, nullptr, "VS28");
+ SET_PLACE_STATE(11, nullptr, nullptr, "VS28");
+ SET_PLACE_STATE(12, nullptr, nullptr, "VS30");
+ SET_PLACE_STATE(13, nullptr, nullptr, "VS27");
+ SET_PLACE_STATE(14, nullptr, nullptr, "VS26");
+ SET_PLACE_STATE(15, nullptr, nullptr, "VS25");
+ SET_PLACE_STATE(16, nullptr, nullptr, "VS24");
+ SET_PLACE_STATE(17, nullptr, nullptr, "VS25");
+ SET_PLACE_STATE(18, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(19, nullptr, nullptr, "VS26");
+ SET_PLACE_STATE(20, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(21, nullptr, nullptr, "VS28");
+ SET_PLACE_STATE(22, nullptr, nullptr, "VS26");
+ SET_PLACE_STATE(23, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(24, nullptr, nullptr, "VS30");
+ } else if (_currentLevel == 4) {
+ _placeStates.resize(18);
+ SET_PLACE_STATE(1, nullptr, nullptr, "VS35");
+ SET_PLACE_STATE(2, nullptr, nullptr, "VS40");
+ SET_PLACE_STATE(3, nullptr, nullptr, "VS40");
+ SET_PLACE_STATE(4, nullptr, nullptr, "VS36");
+ SET_PLACE_STATE(5, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(6, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(7, nullptr, nullptr, "VS17");
+ SET_PLACE_STATE(8, nullptr, nullptr, "VS17");
+ SET_PLACE_STATE(9, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(10, nullptr, nullptr, "VS18");
+ SET_PLACE_STATE(11, nullptr, nullptr, "VS20");
+ SET_PLACE_STATE(12, nullptr, nullptr, "VS31");
+ SET_PLACE_STATE(13, nullptr, nullptr, "VS31");
+ SET_PLACE_STATE(14, nullptr, nullptr, "VS31");
+ SET_PLACE_STATE(15, nullptr, nullptr, "VS36");
+ SET_PLACE_STATE(16, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(17, nullptr, nullptr, nullptr);
+ } else if (_currentLevel == 5) {
+ _placeStates.resize(35);
+ SET_PLACE_STATE(1, nullptr, nullptr, "VS35");
+ SET_PLACE_STATE(2, nullptr, nullptr, "VS35");
+ SET_PLACE_STATE(3, nullptr, nullptr, "VS36");
+ SET_PLACE_STATE(4, nullptr, nullptr, "VS36");
+ SET_PLACE_STATE(5, nullptr, nullptr, "VS36");
+ SET_PLACE_STATE(6, nullptr, nullptr, "VS30");
+ SET_PLACE_STATE(7, nullptr, nullptr, "VS30");
+ SET_PLACE_STATE(8, nullptr, nullptr, "VS30");
+ SET_PLACE_STATE(9, nullptr, nullptr, "VS39");
+ SET_PLACE_STATE(10, nullptr, nullptr, "VS28");
+ SET_PLACE_STATE(11, nullptr, nullptr, "VS16");
+ SET_PLACE_STATE(12, nullptr, nullptr, "VS30");
+ SET_PLACE_STATE(13, nullptr, nullptr, "VS27");
+ SET_PLACE_STATE(14, nullptr, nullptr, "VS26");
+ SET_PLACE_STATE(15, nullptr, nullptr, "VS25");
+ SET_PLACE_STATE(16, nullptr, nullptr, "VS24");
+ SET_PLACE_STATE(17, nullptr, nullptr, "VS25");
+ SET_PLACE_STATE(18, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(19, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(20, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(21, nullptr, nullptr, "VS28");
+ SET_PLACE_STATE(22, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(23, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(24, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(25, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(26, nullptr, nullptr, "VS16");
+ SET_PLACE_STATE(27, nullptr, nullptr, "VS16");
+ SET_PLACE_STATE(28, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(29, nullptr, nullptr, "VS24");
+ SET_PLACE_STATE(30, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(31, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(32, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(33, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(34, nullptr, nullptr, nullptr);
+ } else if (_currentLevel == 6) {
+ _placeStates.resize(45);
+ SET_PLACE_STATE(1, nullptr, nullptr, "VS34");
+ SET_PLACE_STATE(2, nullptr, nullptr, "VS32");
+ SET_PLACE_STATE(3, nullptr, nullptr, "VS32");
+ SET_PLACE_STATE(4, nullptr, nullptr, "VS32");
+ SET_PLACE_STATE(5, nullptr, nullptr, "VS32");
+ SET_PLACE_STATE(6, nullptr, nullptr, "VS32");
+ SET_PLACE_STATE(7, nullptr, nullptr, "VS32");
+ SET_PLACE_STATE(8, nullptr, nullptr, "VS32");
+ SET_PLACE_STATE(9, nullptr, nullptr, "VS32");
+ SET_PLACE_STATE(10, nullptr, nullptr, "VS22");
+ SET_PLACE_STATE(11, nullptr, nullptr, "VS12");
+ SET_PLACE_STATE(12, nullptr, nullptr, "VS32");
+ SET_PLACE_STATE(13, nullptr, nullptr, "VS33");
+ SET_PLACE_STATE(14, nullptr, nullptr, "VS33");
+ SET_PLACE_STATE(15, nullptr, nullptr, "VS33");
+ SET_PLACE_STATE(16, nullptr, nullptr, "VS33");
+ SET_PLACE_STATE(17, nullptr, nullptr, "VS33");
+ SET_PLACE_STATE(18, nullptr, nullptr, "VS33");
+ SET_PLACE_STATE(19, nullptr, nullptr, "VS33");
+ SET_PLACE_STATE(20, nullptr, nullptr, "VS33");
+ SET_PLACE_STATE(21, nullptr, nullptr, "VS33");
+ SET_PLACE_STATE(22, nullptr, nullptr, "VS33");
+ SET_PLACE_STATE(23, nullptr, nullptr, "VS33");
+ SET_PLACE_STATE(24, nullptr, nullptr, "VS33");
+ SET_PLACE_STATE(25, nullptr, nullptr, "VS33");
+ SET_PLACE_STATE(26, nullptr, nullptr, "VS33");
+ SET_PLACE_STATE(27, nullptr, nullptr, "VS33");
+ SET_PLACE_STATE(28, nullptr, nullptr, "VS33");
+ SET_PLACE_STATE(29, nullptr, nullptr, "VS33");
+ SET_PLACE_STATE(30, nullptr, nullptr, "VS33");
+ SET_PLACE_STATE(31, nullptr, nullptr, "VS33");
+ SET_PLACE_STATE(32, nullptr, nullptr, "VS33");
+ SET_PLACE_STATE(33, nullptr, nullptr, "VS33");
+ SET_PLACE_STATE(34, nullptr, nullptr, "VS33");
+ SET_PLACE_STATE(35, nullptr, nullptr, "VS33");
+ SET_PLACE_STATE(36, nullptr, nullptr, "VS33");
+ SET_PLACE_STATE(37, nullptr, nullptr, "VS33");
+ SET_PLACE_STATE(38, nullptr, nullptr, "VS33");
+ SET_PLACE_STATE(39, nullptr, nullptr, "VS33");
+ SET_PLACE_STATE(40, nullptr, nullptr, "VS33");
+ SET_PLACE_STATE(41, nullptr, nullptr, "VS33");
+ SET_PLACE_STATE(42, nullptr, nullptr, "VS33");
+ SET_PLACE_STATE(43, nullptr, nullptr, "VS33");
+ SET_PLACE_STATE(44, nullptr, nullptr, "VS33");
+ } else if (_currentLevel == 7) {
+ _placeStates.resize(30);
+ SET_PLACE_STATE(1, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(2, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(3, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(4, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(5, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(6, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(7, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(8, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(9, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(10, nullptr, nullptr, "VS33");
+ SET_PLACE_STATE(11, nullptr, nullptr, "VS33");
+ SET_PLACE_STATE(12, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(13, nullptr, nullptr, "VS33");
+ SET_PLACE_STATE(14, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(15, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(16, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(17, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(18, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(19, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(20, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(21, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(22, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(23, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(24, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(25, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(26, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(27, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(28, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(29, nullptr, nullptr, nullptr);
+ } else if (_currentLevel == 8) {
+ _placeStates.resize(50);
+ SET_PLACE_STATE(1, nullptr, nullptr, "VS35");
+ SET_PLACE_STATE(2, nullptr, nullptr, "VS40");
+ SET_PLACE_STATE(3, nullptr, nullptr, "VS40");
+ SET_PLACE_STATE(4, nullptr, nullptr, "VS36");
+ SET_PLACE_STATE(5, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(6, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(7, nullptr, nullptr, "VS30");
+ SET_PLACE_STATE(8, nullptr, nullptr, "VS30");
+ SET_PLACE_STATE(9, nullptr, nullptr, "VS39");
+ SET_PLACE_STATE(10, nullptr, nullptr, "VS28");
+ SET_PLACE_STATE(11, nullptr, nullptr, "VS16");
+ SET_PLACE_STATE(12, nullptr, nullptr, "VS30");
+ SET_PLACE_STATE(13, nullptr, nullptr, "VS27");
+ SET_PLACE_STATE(14, nullptr, nullptr, "VS26");
+ SET_PLACE_STATE(15, nullptr, nullptr, "VS25");
+ SET_PLACE_STATE(16, nullptr, nullptr, "VS24");
+ SET_PLACE_STATE(17, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(18, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(19, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(20, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(21, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(22, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(23, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(24, nullptr, nullptr, "VS30");
+ SET_PLACE_STATE(25, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(26, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(27, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(28, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(29, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(30, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(31, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(32, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(33, nullptr, nullptr, "VS31");
+ SET_PLACE_STATE(34, nullptr, nullptr, "VS31");
+ SET_PLACE_STATE(35, nullptr, nullptr, "VS31");
+ SET_PLACE_STATE(36, nullptr, nullptr, "VS23");
+ SET_PLACE_STATE(37, nullptr, nullptr, "VS22");
+ SET_PLACE_STATE(38, nullptr, nullptr, "VS20");
+ SET_PLACE_STATE(39, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(40, nullptr, nullptr, "VS18");
+ SET_PLACE_STATE(41, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(42, nullptr, nullptr, "VS17");
+ SET_PLACE_STATE(43, nullptr, nullptr, "VS17");
+ SET_PLACE_STATE(44, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(45, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(46, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(47, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(48, nullptr, nullptr, nullptr);
+ SET_PLACE_STATE(49, nullptr, nullptr, "VS19");
+ }
+#undef INIT_PLACE
+#undef FILTER_EVENT
+#undef SET_PLACE_STATE
+}
+
+void CryOmni3DEngine_Versailles::setupLevelActionsMask() {
+ _actionMasks.clear();
+#define SET_MASK(placeId, placeState, oldActionId, newActionId) _actionMasks[PlaceStateActionKey(placeId, placeState, oldActionId)] = newActionId
+ if (_currentLevel == 1) {
+ SET_MASK(1, 0, 11015, 0);
+ SET_MASK(1, 0, 21015, 0);
+ SET_MASK(1, 1, 21011, 0);
+ SET_MASK(1, 1, 21012, 0);
+ SET_MASK(1, 1, 21013, 0);
+ SET_MASK(1, 1, 21014, 0);
+ // 2, 0 is empty
+ SET_MASK(2, 1, 51201, 0);
+ SET_MASK(2, 1, 21202, 0);
+ SET_MASK(2, 1, 21203, 0);
+ SET_MASK(2, 2, 51201, 0);
+ SET_MASK(2, 2, 21202, 0);
+ SET_MASK(2, 2, 21203, 0);
+ SET_MASK(2, 2, 11201, 1);
+ SET_MASK(2, 2, 21201, 1);
+ // 3, 0 is empty
+ SET_MASK(3, 1, 11301, 0);
+ SET_MASK(3, 1, 21301, 0);
+ // 14, 0 is empty
+ SET_MASK(14, 1, 31141, 0);
+ } else if (_currentLevel == 2) {
+ // 1, 0 is empty
+ SET_MASK(1, 1, 12101, 0);
+ SET_MASK(1, 1, 22101, 0);
+ SET_MASK(11, 0, 12111, 0);
+ SET_MASK(11, 0, 22111, 0);
+ // 11, 1 is empty
+ // 9, 0 is empty
+ SET_MASK(9, 1, 52903, 0);
+ SET_MASK(9, 1, 22903, 0);
+ SET_MASK(9, 1, 52902, 12902);
+ SET_MASK(9, 2, 52903, 0);
+ SET_MASK(9, 2, 22903, 0);
+ SET_MASK(9, 2, 52902, 0);
+ SET_MASK(9, 2, 22902, 1);
+ } else if (_currentLevel == 3) {
+ SET_MASK(13, 0, 13131, 0);
+ SET_MASK(13, 0, 23131, 0);
+ SET_MASK(13, 1, 13131, 0);
+ SET_MASK(13, 1, 23131, 0);
+ SET_MASK(13, 1, 33130, 0);
+ // 13, 2 is empty
+ SET_MASK(13, 3, 33130, 0);
+ // 14, 0 is empty
+ SET_MASK(14, 1, 23220, 0);
+ SET_MASK(15, 0, 13151, 43154);
+ SET_MASK(15, 0, 23151, 0);
+ // 15, 1 is empty
+ SET_MASK(17, 0, 13151, 0);
+ SET_MASK(17, 0, 23151, 0);
+ // 17, 1 is empty
+ // 16, 0 is empty
+ SET_MASK(16, 1, 43160, 0);
+ // 19, 0 is empty
+ SET_MASK(19, 1, 43190, 0);
+ SET_MASK(22, 0, 33220, 0);
+ SET_MASK(22, 1, 13220, 0);
+ SET_MASK(22, 1, 23220, 0);
+ SET_MASK(22, 2, 13220, 0);
+ SET_MASK(22, 2, 23220, 0);
+ SET_MASK(22, 2, 33220, 0);
+ } else if (_currentLevel == 4) {
+ // TODO: finish the boring job
+ error("TODO:");
+ } else if (_currentLevel == 5) {
+ error("TODO:");
+ } else if (_currentLevel == 6) {
+ error("TODO:");
+ } else if (_currentLevel == 7) {
+ // 9, 0 is empty
+ SET_MASK(9, 1, 37090, 0);
+ } else if (_currentLevel == 8) {
+ // Nothing to mask
+ } else {
+ error("Invalid level");
+ }
+#undef SET_MASK
+}
+
+void CryOmni3DEngine_Versailles::initWhoSpeaksWhere() {
+ _whoSpeaksWhere.clear();
+#define SET_WHO(placeId, actionId, dialog) _whoSpeaksWhere[PlaceActionKey(placeId, actionId)] = dialog
+ if (_currentLevel == 1) {
+ SET_WHO(1, 11015, "13F_HUI");
+ SET_WHO(1, 12101, "21F_BON");
+ SET_WHO(1, 52903, "21G_DAU");
+ SET_WHO(1, 52902, "21G_DAU");
+ SET_WHO(2, 11201, "11E_HUI");
+ SET_WHO(2, 51201, "11E_RAC");
+ SET_WHO(3, 11301, "11D_LEB");
+ SET_WHO(5, 12501, "21B1_HUI");
+ if (currentGameTime() >= 2) {
+ SET_WHO(2, 11201, "12E_HUI");
+ }
+ } else if (_currentLevel == 2) {
+ SET_WHO(1, 12101, "21F_BON");
+ SET_WHO(9, 52903, "21G_DAU");
+ SET_WHO(9, 52902, "21G_DAU");
+ SET_WHO(9, 12902, "22G_DAU");
+ SET_WHO(9, 11201, "11E_HUI");
+ SET_WHO(9, 12901, "21G_HUI");
+ SET_WHO(5, 12501, "21B1_HUI");
+ SET_WHO(10, 12130, "21Z_ALI");
+ SET_WHO(10, 12130, "21Z_MON");
+ SET_WHO(10, 12111, "24Z_BON");
+ SET_WHO(11, 12130, "21Z_MON");
+ SET_WHO(11, 12111, "24Z_BON");
+ SET_WHO(13, 12130, "21Z_ALI");
+ SET_WHO(13, 12130, "21Z_MON");
+ SET_WHO(13, 12111, "24Z_BON");
+ SET_WHO(12, 12121, "23I_LEB");
+ SET_WHO(10, 52130, "21Z_ALI");
+ SET_WHO(11, 52130, "21Z_ALI");
+ SET_WHO(13, 52130, "21Z_ALI");
+ SET_WHO(10, 52101, "21Z_MON");
+ if (currentGameTime() >= 2) {
+ SET_WHO(9, 52902, "22G_DAU");
+ }
+ } else if (_currentLevel == 3) {
+ SET_WHO(13, 13130, "31M_SUI");
+ SET_WHO(13, 13131, "32M_MR");
+ SET_WHO(10, 13100, "31O_SUIA");
+ SET_WHO(10, 13101, "31O_SUIP");
+ SET_WHO(22, 13220, "31L1_LUL");
+ SET_WHO(6, 13060, "31Q_SUI");
+ SET_WHO(15, 13150, "31J_SUI");
+ SET_WHO(17, 13150, "31J_SUI");
+ SET_WHO(3, 13030, "31X_BON");
+ SET_WHO(24, 53240, "32Q_MON");
+ SET_WHO(24, 13241, "32Q_RAC2");
+ SET_WHO(4, 53041, "31X_SEI");
+ SET_WHO(4, 53040, "31X_LOU");
+ SET_WHO(15, 13151, "32J_CRO");
+ SET_WHO(17, 13151, "32J_CRO");
+ } else if (_currentLevel == 4) {
+ SET_WHO(10, 14104, "41C_HUI");
+ SET_WHO(10, 14105, "42C_BON");
+ SET_WHO(16, 14161, "41X2_CRO");
+ SET_WHO(10, 54106, "43C_CON");
+ SET_WHO(10, 54106, "43C_DUR");
+ SET_WHO(9, 54091, "43B1_SEI");
+ SET_WHO(9, 14091, "43B1_SEI");
+ if (currentGameTime() >= 4) {
+ SET_WHO(9, 54091, "4_MAI");
+ SET_WHO(9, 14091, "4_MAI");
+ }
+ } else if (_currentLevel == 5) {
+ SET_WHO(27, 15270, "52A4_LAC");
+ SET_WHO(9, 15090, "53N_BON");
+ SET_WHO(13, 55130, "51M_MAN");
+ SET_WHO(13, 55131, "51M_MAN");
+ SET_WHO(14, 55140, "52L_LOU");
+ SET_WHO(14, 55140, "52L_PRI");
+ SET_WHO(14, 15142, "52L_BOU");
+ SET_WHO(13, 13130, "53M_SUI");
+ if (currentGameTime() >= 4) {
+ SET_WHO(9, 15090, "54I_BON");
+ }
+ } else if (_currentLevel == 6) {
+ SET_WHO(9, 16090, "61_LEN");
+ SET_WHO(19, 16190, "61_DUC");
+ SET_WHO(14, 16140, "61_BON");
+ if (_gameVariables[GameVariables::kDiscussedLabyrOrder] == 1) {
+ SET_WHO(19, 16190, "62_DUC");
+ }
+ }
+#undef SET_WHO
+}
+
+void CryOmni3DEngine_Versailles::initDocPeopleRecord() {
+ _docPeopleRecord.clear();
+#define SET_INFO(actionId, record) _docPeopleRecord[actionId] = record
+ SET_INFO(22501, "VC25");
+ SET_INFO(22401, "VC19");
+ SET_INFO(22402, "VC24");
+ SET_INFO(22403, "VC24");
+ SET_INFO(22404, "VC24");
+ SET_INFO(22405, "VC24");
+ SET_INFO(22406, "VC24");
+ SET_INFO(22407, "VC24");
+ SET_INFO(22408, "VC24");
+ SET_INFO(21201, "VC25");
+ SET_INFO(21202, "VS12");
+ SET_INFO(21203, "VA13");
+ SET_INFO(21011, "VC13");
+ SET_INFO(21012, "VC11");
+ SET_INFO(21013, "VC10");
+ SET_INFO(21014, "VC18");
+ SET_INFO(22901, "VC25");
+ SET_INFO(21015, "VC25");
+ SET_INFO(22101, "VC18");
+ SET_INFO(22903, "VC12");
+ SET_INFO(22902, "VC10");
+ SET_INFO(22131, "VC16");
+ SET_INFO(22111, "VC18");
+ SET_INFO(21301, "VA12");
+ SET_INFO(22121, "VA12");
+ SET_INFO(22103, "VC20");
+ SET_INFO(22102, "VC15");
+ SET_INFO(23100, "VC23");
+ SET_INFO(23101, "VC23");
+ SET_INFO(23130, "VC23");
+ SET_INFO(23060, "VC23");
+ SET_INFO(23150, "VC23");
+ SET_INFO(23220, "VA11");
+ SET_INFO(23131, "VC11");
+ SET_INFO(23241, "VA13");
+ SET_INFO(23151, "VR12");
+ SET_INFO(23030, "VC18");
+ SET_INFO(23040, "VR11");
+ SET_INFO(23041, "VR13");
+ SET_INFO(23240, "VC15");
+ SET_INFO(24104, "VC25");
+ SET_INFO(24105, "VC18");
+ SET_INFO(24106, "VC12");
+ SET_INFO(24107, "VC19");
+ SET_INFO(24102, "VC21");
+ SET_INFO(24103, "VC21");
+ SET_INFO(24081, "VC21");
+ SET_INFO(24101, "VC24");
+ SET_INFO(24092, "VC14");
+ SET_INFO(24091, "VR13");
+ SET_INFO(24161, "VR12");
+ SET_INFO(25270, "VC26");
+ SET_INFO(25261, "VC26");
+ //SET_INFO(25260, nullptr); // Don't put empty records
+ SET_INFO(25130, "VA12");
+ SET_INFO(25131, "VS12");
+ SET_INFO(25060, "VC23");
+ SET_INFO(25061, "VC22");
+ SET_INFO(25160, "VC23");
+ SET_INFO(25140, "VR11");
+ SET_INFO(25141, "VC16");
+ SET_INFO(25142, "VC20");
+ SET_INFO(25143, "VC15");
+ SET_INFO(25145, "VC17");
+ SET_INFO(25090, "VC18");
+ SET_INFO(26190, "VC13");
+ SET_INFO(24161, "VR12");
+ SET_INFO(26090, "VS13");
+ SET_INFO(26140, "VC18");
+ SET_INFO(27111, "VC21");
+#undef SET_INFO
+}
+
+} // End of namespace Versailles
+} // End of namespace CryOmni3D
diff --git a/engines/cryomni3d/versailles/dialogs.cpp b/engines/cryomni3d/versailles/dialogs.cpp
new file mode 100644
index 0000000000..ff5a9ff38b
--- /dev/null
+++ b/engines/cryomni3d/versailles/dialogs.cpp
@@ -0,0 +1,318 @@
+/* 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 "cryomni3d/versailles/engine.h"
+
+namespace CryOmni3D {
+namespace Versailles {
+
+bool CryOmni3DEngine_Versailles::preprocessDialog(const Common::String &sequence) {
+ if (_inventory.inInventoryByNameId(96) && _inventory.inInventoryByNameId(98)) {
+ _dialogsMan["{JOUEUR-TROUVE-TITRE-ET-PAMPHLET}"] = 'Y';
+ }
+ if (_inventory.inInventoryByNameId(126)) {
+ _dialogsMan["{JOUEUR_POSSEDE_EPIGRAPHE}"] = 'Y';
+ }
+
+ if (_currentLevel == 1 && _currentPlaceId == 3) {
+ playInGameVideo("11D_LEB1");
+ }
+
+ _dialogsMan["{JOUEUR-VU-MEMORANDUM-DANS-LUSTRE-DU-SALON-DE-LA-GUERRE}"] = 'N';
+ if (_currentLevel == 5 && _gameVariables[GameVariables::kSeenMemorandum] &&
+ !_inventory.inInventoryByNameId(140)) {
+ _dialogsMan["{JOUEUR-VU-MEMORANDUM-DANS-LUSTRE-DU-SALON-DE-LA-GUERRE}"] = 'Y';
+ }
+
+ if (_currentLevel == 1 && _currentPlaceId == 1 && currentGameTime() == 3 &&
+ sequence.hasPrefix("13F_HUI") && _gameVariables[GameVariables::kWarnedIncomplete] == 0 &&
+ _dialogsMan["{JOUEUR-TROUVE-TITRE-ET-PAMPHLET}"] == 'Y' &&
+ (!_inventory.inInventoryByNameId(96) || !_inventory.inInventoryByNameId(98))) {
+ displayMessageBoxWarp(18);
+ _gameVariables[GameVariables::kWarnedIncomplete] = 1;
+ return 0;
+ }
+ if (_currentLevel == 2 && _currentPlaceId == 11 && currentGameTime() == 4 &&
+ sequence.hasPrefix("24Z_BON") && _gameVariables[GameVariables::kWarnedIncomplete] == 0 &&
+ _dialogsMan["{JOUEUR-MONTRE-TITRE-FABLE-APPARU-SUR-ESQUISSE}"] == 'Y' &&
+ (!_inventory.inInventoryByNameId(101) || !_inventory.inInventoryByNameId(103))) {
+ displayMessageBoxWarp(18);
+ _gameVariables[GameVariables::kWarnedIncomplete] = 1;
+ return 0;
+ }
+ if (_currentLevel == 3 && _currentPlaceId == 10 && currentGameTime() == 3 &&
+ sequence.hasPrefix("31O_SUIA") && _gameVariables[GameVariables::kWarnedIncomplete] == 0 &&
+ _dialogsMan["CROISSY-ACCEPTE-TEXTE"] == 'Y' &&
+ (!_inventory.inInventoryByNameId(121) || !_inventory.inInventoryByNameId(119) ||
+ !_inventory.inInventoryByNameId(115) || _gameVariables[GameVariables::kGotMedaillesSolution] == 0)) {
+ displayMessageBoxWarp(18);
+ _gameVariables[GameVariables::kWarnedIncomplete] = 1;
+ return 0;
+ }
+ if (_currentLevel == 4 && _currentPlaceId == 10 && currentGameTime() == 3 &&
+ sequence.hasPrefix("42C_BON") && _gameVariables[GameVariables::kWarnedIncomplete] == 0 &&
+ _dialogsMan["{JOUEUR-MONTRE-PAMPHLET-RELIGION}"] == 'Y' &&
+ (!_inventory.inInventoryByNameId(127) || _gameVariables[GameVariables::kUsedPlanVauban1] == 0 ||
+ _gameVariables[GameVariables::kUsedPlanVauban2] == 0)) {
+ displayMessageBoxWarp(18);
+ _gameVariables[GameVariables::kWarnedIncomplete] = 1;
+ return 0;
+ }
+ if (_currentLevel == 5 && _currentPlaceId == 10 && currentGameTime() == 3 &&
+ sequence.hasPrefix("42C_BON") && _gameVariables[GameVariables::kWarnedIncomplete] == 0 &&
+ _dialogsMan["{JOUEUR-MONTRE-PAMPHLET-RELIGION}"] == 'Y' &&
+ (!_inventory.inInventoryByNameId(127) || _gameVariables[GameVariables::kUsedPlanVauban1] == 0 ||
+ _gameVariables[GameVariables::kUsedPlanVauban2] == 0)) {
+ displayMessageBoxWarp(18);
+ _gameVariables[GameVariables::kWarnedIncomplete] = 1;
+ return 0;
+ }
+ if (_currentLevel == 6 && _currentPlaceId == 14 && currentGameTime() == 2 &&
+ sequence.hasPrefix("61_BON") && _gameVariables[GameVariables::kWarnedIncomplete] == 0) {
+ displayMessageBoxWarp(19);
+ _gameVariables[GameVariables::kWarnedIncomplete] = 1;
+ return 0;
+ }
+ return 1;
+}
+
+void CryOmni3DEngine_Versailles::postprocessDialog(const Common::String &sequence) {
+ if (_currentLevel == 1) {
+ if (_dialogsMan["{LEVEL1_FINI}"] == 'Y') {
+ playTransitionEndLevel(1);
+ }
+ } else if (_currentLevel == 2) {
+ _dialogsMan["{JOUEUR-PRESENTE-FAUX-CROQUIS}"] = 'N';
+ _dialogsMan["{JOUEUR-PRESENTE-FAUX-CROQUIS2}"] = 'N';
+ _dialogsMan["{JOUEUR-PRESENTE-FAUX-CROQUIS3}"] = 'N';
+ _dialogsMan["{LE JOUEUR-PRESENTE-AUTRES-ESQUISSES-OU-ESQUISSE-NON-TRIEES}"] = 'N';
+ _dialogsMan["{LE JOUEUR-PRESENTE-ESQUISSES-TRIEES}"] = 'N';
+ _dialogsMan["{JOUEUR-MONTRE-TITRE-FABLE-APPARU-SUR-ESQUISSE}"] = 'N';
+ _dialogsMan["{JOUEUR-MONTRE-ESQUISSE-DETRUITE}"] = 'N';
+ if (_dialogsMan["{LEVEL2_FINI}"] == 'Y') {
+ playTransitionEndLevel(2);
+ }
+ } else if (_currentLevel == 3) {
+ if (currentGameTime() == 1 && _dialogsMan["LULLY-DONNE-MISSION1-JOUEUR"] == 'Y') {
+ setGameTime(2, 3);
+ }
+ if (!_gameVariables[GameVariables::kGotMedaillesSolution] &&
+ _dialogsMan["MONSIEUR-DONNE-SOLUTION-MEDAILLES"] == 'Y') {
+ playInGameVideo("32M_MR2");
+ _gameVariables[GameVariables::kGotMedaillesSolution] = 1;
+ }
+ if (!_gameVariables[GameVariables::kCollectePartition] &&
+ _dialogsMan["LULLY-DIT-CHAT-PENDU-JOUEUR"] == 'Y') {
+ _gameVariables[GameVariables::kCollectePartition] = 1;
+ collectObject(118);
+ setGameTime(3, 3);
+ }
+ if (currentGameTime() == 1 && _dialogsMan["CROISSY-ACCEPTE-TEXTE"] == 'Y') {
+ setGameTime(4, 3);
+ }
+ if (_dialogsMan["{LEVEL3_FINI}"] == 'Y') {
+ playTransitionEndLevel(3);
+ }
+ if (sequence == "32M_MR" && _dialogsMan["MONSIEUR-DONNE-SOLUTION-MEDAILLES"] == 'Y') {
+ _dialogsMan["{JOUEUR-MONTRE-MEDAILLES-MONSIEUR}"] = 'Y';
+ }
+ _dialogsMan["{JOUEUR-MONTRE-PAMPHLET-ARCHITECTURE}"] = 'N';
+ _dialogsMan["{JOUEUR-MONTRE-EPIGRAPHE-MEDAILLES}"] = 'N';
+ _dialogsMan["{JOUEUR-MONTRE-TOUT-AUTRE-CHOSE}"] = 'N';
+ } else if (_currentLevel == 4) {
+ if (_dialogsMan["{LEVEL4_FINI}"] == 'Y') {
+ playTransitionEndLevel(4);
+ }
+ } else if (_currentLevel == 5) {
+ if (sequence == "54I_BON" && _dialogsMan["BONTEMPS-DIT-PROMENADE"] == 'Y') {
+ collectObject(141);
+ playTransitionEndLevel(5);
+ }
+ if (sequence == "52A4_LAC" && _gameVariables[GameVariables::kStatePamphletReligion] != 3 &&
+ _dialogsMan["LACHAIZE-DIT-REFORME"] == 'Y' && _dialogsMan["LACHAIZE-DIT-DRAGONNADES"] == 'Y' &&
+ _dialogsMan["LACHAIZE-TROUVE-ECROUELLES"] == 'Y') {
+ _inventory.removeByNameId(125);
+ _gameVariables[GameVariables::kStatePamphletReligion] = 3;
+ collectObject(125);
+ _inventory.setSelectedObject(nullptr);
+ }
+ }
+}
+
+void CryOmni3DEngine_Versailles::updateGameTimeDialVariables() {
+ _dialogsMan["{CURRENT_GAME_TIME1}"] = 'N';
+ _dialogsMan["{CURRENT_GAME_TIME2}"] = 'N';
+ _dialogsMan["{CURRENT_GAME_TIME3}"] = 'N';
+ _dialogsMan["{CURRENT_GAME_TIME4}"] = 'N';
+ _dialogsMan["{CURRENT_GAME_TIME5}"] = 'N';
+ switch (currentGameTime()) {
+ case 1:
+ _dialogsMan["{CURRENT_GAME_TIME1}"] = 'Y';
+ break;
+ case 2:
+ _dialogsMan["{CURRENT_GAME_TIME2}"] = 'Y';
+ break;
+ case 3:
+ _dialogsMan["{CURRENT_GAME_TIME3}"] = 'Y';
+ break;
+ case 4:
+ _dialogsMan["{CURRENT_GAME_TIME4}"] = 'Y';
+ break;
+ case 5:
+ _dialogsMan["{CURRENT_GAME_TIME5}"] = 'Y';
+ break;
+ default:
+ error("Invalid current game time %d", currentGameTime());
+ }
+}
+
+void CryOmni3DEngine_Versailles::setupDialogShows() {
+ _dialogsMan.registerShowCallback("(BONTEMPS-MONTRE-TROISIEME-TITRE-DE-FABLE)",
+ &CryOmni3DEngine_Versailles::dialogShowBontempsShowThird);
+ _dialogsMan.registerShowCallback("(HUISSIER DONNE PAMPHLET SUR LA FAMILLE ROYALE)",
+ &CryOmni3DEngine_Versailles::dialogShowHuissierShowPamphlet);
+ _dialogsMan.registerShowCallback("(MONSEIGNEUR TRIE LES ESQUISSES)",
+ &CryOmni3DEngine_Versailles::dialogShowMonseigneurSorts);
+ _dialogsMan.registerShowCallback("(ANIMATION LE BRUN REGARDE LES ESQUISSES)",
+ &CryOmni3DEngine_Versailles::dialogShowLeBrunWatches);
+ _dialogsMan.registerShowCallback("(OUVERTURE DES PORTES)",
+ &CryOmni3DEngine_Versailles::dialogShowDoorsOpen);
+ _dialogsMan.registerShowCallback("(GARDE SUISSE DONNE CLEF PETITE PORTE)",
+ &CryOmni3DEngine_Versailles::dialogShowSwissGuardGives);
+ _dialogsMan.registerShowCallback("(LULLY CORRIGE LA PARTITION.)",
+ &CryOmni3DEngine_Versailles::dialogShowLullyCorrects);
+ _dialogsMan.registerShowCallback("(BONTEMPS DONNE AUTORISATION)",
+ &CryOmni3DEngine_Versailles::dialogShowBontempsGivesAuth);
+ _dialogsMan.registerShowCallback("(CROISSY PART)",
+ &CryOmni3DEngine_Versailles::dialogShowCroissyLeave);
+ _dialogsMan.registerShowCallback("(MAINTENON-DONNE-PAMPHLET-RELIGION)",
+ &CryOmni3DEngine_Versailles::dialogShowMaintenonGives);
+ _dialogsMan.registerShowCallback("(LA CHAIZE REND TEXTE)",
+ &CryOmni3DEngine_Versailles::dialogShowLaChaizeGivesBack);
+ _dialogsMan.registerShowCallback("(LA CHAIZE " "\x83" "CRIT DRAGONNADES)",
+ &CryOmni3DEngine_Versailles::dialogShowLaChaizeWrites);
+ _dialogsMan.registerShowCallback("(LACHAIZE-DONNE-PAMPHLET-JOUEUR)",
+ &CryOmni3DEngine_Versailles::dialogShowLaChaizeGivesPamphlet);
+ _dialogsMan.registerShowCallback("(BONTEMPS-DONNE-CLEF-DES-COMBLES)",
+ &CryOmni3DEngine_Versailles::dialogShowBontempsGivesKey);
+ _dialogsMan.registerShowCallback("(LE DUC DU MAINE S'EN VA)",
+ &CryOmni3DEngine_Versailles::dialogShowDuMaineLeaves);
+ _dialogsMan.registerShowCallback("(SC" "\xe9" "NE DE TRANSITION)",
+ &CryOmni3DEngine_Versailles::dialogShowTransitionScene);
+ _dialogsMan.registerShowCallback("(FIN DU JEU)", &CryOmni3DEngine_Versailles::dialogShowEndOfGame);
+ _dialogsMan.registerShowCallback("(LEBRUN-DONNE-FAUSSES-ESQUISSES)",
+ &CryOmni3DEngine_Versailles::dialogShowLeBrunGives);
+ _dialogsMan.registerShowCallback("(LEBRUN_S_EN_VA)",
+ &CryOmni3DEngine_Versailles::dialogShowLeBrunLeave);
+}
+
+void CryOmni3DEngine_Versailles::dialogShowBontempsShowThird() {
+ // Nothing to do
+}
+
+void CryOmni3DEngine_Versailles::dialogShowHuissierShowPamphlet() {
+ collectObject(101);
+ _inventory.setSelectedObject(nullptr);
+}
+
+void CryOmni3DEngine_Versailles::dialogShowMonseigneurSorts() {
+ _inventory.removeByNameId(105);
+ collectObject(106);
+ _gameVariables[GameVariables::kEsquissePainted] = 2;
+ _inventory.setSelectedObject(nullptr);
+ setGameTime(3, 2);
+ _dialogsMan["MONSEIGNEUR-ATTEND-ESQUISSES"] = 'N';
+}
+
+void CryOmni3DEngine_Versailles::dialogShowLeBrunWatches() {
+ // Nothing to do
+}
+
+void CryOmni3DEngine_Versailles::dialogShowDoorsOpen() {
+ // Nothing to do
+}
+
+void CryOmni3DEngine_Versailles::dialogShowSwissGuardGives() {
+ collectObject(123);
+ _dialogsMan["{JOUEUR-POSSEDE-CLE}"] = 'Y';
+}
+
+void CryOmni3DEngine_Versailles::dialogShowLullyCorrects() {
+ // Nothing to do
+}
+
+void CryOmni3DEngine_Versailles::dialogShowBontempsGivesAuth() {
+ collectObject(120);
+}
+
+void CryOmni3DEngine_Versailles::dialogShowCroissyLeave() {
+ // Nothing to do
+}
+
+void CryOmni3DEngine_Versailles::dialogShowMaintenonGives() {
+ collectObject(125);
+ _inventory.setSelectedObject(nullptr);
+}
+
+void CryOmni3DEngine_Versailles::dialogShowLaChaizeGivesBack() {
+ // Nothing to do
+}
+
+void CryOmni3DEngine_Versailles::dialogShowLaChaizeWrites() {
+ // Nothing to do
+}
+
+void CryOmni3DEngine_Versailles::dialogShowLaChaizeGivesPamphlet() {
+ // Nothing to do
+}
+
+void CryOmni3DEngine_Versailles::dialogShowBontempsGivesKey() {
+ collectObject(140);
+ _inventory.setSelectedObject(nullptr);
+}
+
+void CryOmni3DEngine_Versailles::dialogShowDuMaineLeaves() {
+ playInGameVideo("62S_DUC1");
+ _inventory.removeByNameId(144);
+ _inventory.setSelectedObject(nullptr);
+ setPlaceState(19, 1);
+}
+
+void CryOmni3DEngine_Versailles::dialogShowTransitionScene() {
+}
+
+void CryOmni3DEngine_Versailles::dialogShowEndOfGame() {
+ playTransitionEndLevel(6);
+}
+
+void CryOmni3DEngine_Versailles::dialogShowLeBrunGives() {
+ collectObject(107);
+ _inventory.setSelectedObject(nullptr);
+}
+
+void CryOmni3DEngine_Versailles::dialogShowLeBrunLeave() {
+ playInGameVideo("11D_LEB3");
+ setGameTime(2, 1);
+}
+
+} // End of namespace Versailles
+} // End of namespace CryOmni3D
diff --git a/engines/cryomni3d/versailles/dialogs_manager.cpp b/engines/cryomni3d/versailles/dialogs_manager.cpp
new file mode 100644
index 0000000000..4180a65d98
--- /dev/null
+++ b/engines/cryomni3d/versailles/dialogs_manager.cpp
@@ -0,0 +1,379 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "audio/decoders/wave.h"
+#include "common/file.h"
+#include "common/system.h"
+
+#include "cryomni3d/video/hnm_decoder.h"
+
+#include "cryomni3d/versailles/dialogs_manager.h"
+#include "cryomni3d/versailles/engine.h"
+
+namespace CryOmni3D {
+namespace Versailles {
+
+bool Versailles_DialogsManager::play(const Common::String &sequence) {
+ // Prepare with specific Versailles stuff
+ if (!_engine->preprocessDialog(sequence)) {
+ return false;
+ }
+
+ _engine->musicSetQuiet(true);
+
+ _engine->setCursor(181);
+ // No need to adjust hide cursor counter, there isn't any in ScummVM
+ bool cursorWasVisible = g_system->showMouse(true);
+
+ bool slowStop = false;
+ bool didSth = DialogsManager::play(sequence, slowStop);
+
+ g_system->showMouse(cursorWasVisible);
+
+ if (didSth && slowStop) {
+ if (_engine->showSubtitles()) {
+ bool skip = false;
+ unsigned int end = g_system->getMillis() + 2000;
+ while (!g_engine->shouldQuit() && g_system->getMillis() < end && !skip) {
+ g_system->updateScreen();
+ if (_engine->pollEvents() &&
+ (_engine->checkKeysPressed(1, Common::KEYCODE_SPACE) ||
+ _engine->getCurrentMouseButton() == 1)) {
+ skip = true;
+ }
+ }
+ }
+ }
+ _engine->postprocessDialog(sequence);
+
+ _engine->musicSetQuiet(false);
+
+ _lastImage.free();
+
+ _engine->waitMouseRelease();
+ return didSth;
+}
+
+void Versailles_DialogsManager::executeShow(const Common::String &show) {
+ Common::HashMap<Common::String, ShowCallback>::iterator showIt = _shows.find(show);
+
+ if (showIt == _shows.end()) {
+ error("Missing show %s", show.c_str());
+ }
+
+ _lastImage.free();
+
+ ShowCallback cb = showIt->_value;
+ (_engine->*cb)();
+}
+
+void Versailles_DialogsManager::playDialog(const Common::String &video, const Common::String &sound,
+ const Common::String &text, const SubtitlesSettings &settings) {
+ Common::String videoFName(video);
+ Common::String soundFName(sound);
+
+ videoFName += ".hnm";
+ // Don't look for HNS file here
+
+ while (soundFName.size() < 8) {
+ soundFName += '_';
+ }
+ soundFName += ".wav";
+
+ Video::HNMDecoder *videoDecoder = new Video::HNMDecoder(true);
+
+ if (!videoDecoder->loadFile(videoFName)) {
+ warning("Failed to open movie file %s/%s", video.c_str(), videoFName.c_str());
+ delete videoDecoder;
+ return;
+ }
+
+ Common::File *audioFile = new Common::File();
+ if (!audioFile->open(soundFName)) {
+ warning("Failed to open sound file %s/%s", sound.c_str(), soundFName.c_str());
+ delete videoDecoder;
+ delete audioFile;
+ return;
+ }
+
+ Audio::SeekableAudioStream *audioDecoder = Audio::makeWAVStream(audioFile, DisposeAfterUse::YES);
+ // We lost ownership of the audioFile just set it to nullptr and don't use it
+ audioFile = nullptr;
+
+ if (!audioDecoder) {
+ delete videoDecoder;
+ return;
+ }
+
+ g_system->showMouse(false);
+
+ uint16 width = videoDecoder->getWidth();
+ uint16 height = videoDecoder->getHeight();
+
+ // Preload first frame to draw subtitles from it
+ const Graphics::Surface *firstFrame = videoDecoder->decodeNextFrame();
+ assert(firstFrame != nullptr);
+
+ if (videoDecoder->hasDirtyPalette()) {
+ const byte *palette = videoDecoder->getPalette();
+ _engine->setupPalette(palette, 0, 256);
+ }
+
+ FontManager &fontManager = _engine->_fontManager;
+ _lastImage.create(firstFrame->w, firstFrame->h, firstFrame->format);
+ _lastImage.blitFrom(*firstFrame);
+
+ fontManager.setCurrentFont(7);
+ fontManager.setTransparentBackground(true);
+ fontManager.setForeColor(241);
+ fontManager.setLineHeight(22);
+ fontManager.setSpaceWidth(2);
+ fontManager.setCharSpacing(1);
+
+ if (_engine->showSubtitles()) {
+ Common::Rect block = settings.textRect;
+
+ unsigned int lines = fontManager.getLinesCount(text, block.width() - 8);
+ if (lines == 0) {
+ lines = 5;
+ }
+ unsigned int blockHeight = fontManager.lineHeight() * lines + 6;
+ block.setHeight(blockHeight);
+
+ if (block.bottom >= 480) {
+ block.bottom = 470;
+ warning("Dialog text is really too long");
+ }
+
+ // Make only the block area translucent inplace
+ Graphics::Surface blockSurface = _lastImage.getSubArea(block);
+ _engine->makeTranslucent(blockSurface, blockSurface);
+
+ fontManager.setSurface(&_lastImage);
+ block.grow(-4);
+ fontManager.setupBlock(block);
+ fontManager.displayBlockText(text);
+ }
+
+ g_system->copyRectToScreen(_lastImage.getPixels(), _lastImage.pitch, 0, 0, width, height);
+ g_system->updateScreen();
+
+ const Common::Rect &drawRect = settings.drawRect;
+
+ if (audioDecoder->getLength() == 0) {
+ // Empty wave file
+ delete audioDecoder;
+
+ unsigned int duration = 100 * text.size();
+ if (duration < 1000) {
+ duration = 1000;
+ }
+
+ bool skipWait = false;
+ unsigned int end = g_system->getMillis() + duration;
+ while (!g_engine->shouldQuit() && g_system->getMillis() < end && !skipWait) {
+ if (_engine->pollEvents() && _engine->checkKeysPressed(1, Common::KEYCODE_SPACE)) {
+ skipWait = true;
+ }
+ }
+ } else {
+ // Let start the show!
+ videoDecoder->start();
+
+ Audio::SoundHandle audioHandle;
+ _engine->_mixer->playStream(Audio::Mixer::kSpeechSoundType, &audioHandle, audioDecoder);
+ // We lost ownership of the audioDecoder just set it to nullptr and don't use it
+ audioDecoder = nullptr;
+
+ bool skipVideo = false;
+ while (!g_engine->shouldQuit() && _engine->_mixer->isSoundHandleActive(audioHandle) && !skipVideo) {
+ if (_engine->pollEvents() && _engine->checkKeysPressed(1, Common::KEYCODE_SPACE)) {
+ skipVideo = true;
+ }
+
+ if (videoDecoder->needsUpdate()) {
+ const Graphics::Surface *frame = videoDecoder->decodeNextFrame();
+
+ if (frame) {
+ if (videoDecoder->hasDirtyPalette()) {
+ const byte *palette = videoDecoder->getPalette();
+ _engine->setupPalette(palette, 0, 256);
+ }
+
+ // Only refresh the moving part of the animation
+ const Graphics::Surface subFrame = frame->getSubArea(drawRect);
+ g_system->copyRectToScreen(subFrame.getPixels(), subFrame.pitch, drawRect.left, drawRect.top,
+ subFrame.w, subFrame.h);
+ }
+ }
+ g_system->updateScreen();
+ }
+ _engine->_mixer->stopHandle(audioHandle);
+ }
+
+ // It's intentional that _lastImage is set with the first video image
+
+ delete videoDecoder;
+ g_system->showMouse(true);
+}
+
+void Versailles_DialogsManager::displayMessage(const Common::String &text) {
+ _engine->displayMessageBoxWarp(text);
+}
+
+unsigned int Versailles_DialogsManager::askPlayerQuestions(const Common::String &video,
+ const Common::StringArray &questions) {
+ if (_lastImage.empty()) {
+ loadFrame(video);
+ }
+
+ if (questions.size() == 0 || questions.size() > 5) {
+ return -1;
+ }
+
+ FontManager &fontManager = _engine->_fontManager;
+ fontManager.setCurrentFont(7);
+ fontManager.setTransparentBackground(true);
+ fontManager.setLineHeight(18);
+ fontManager.setSpaceWidth(2);
+ fontManager.setSurface(&_lastImage);
+
+ int16 tops[5];
+ int16 bottoms[5];
+ int16 currentHeight = 0;
+ unsigned int questionId = 0;
+ for (Common::StringArray::const_iterator it = questions.begin(); it != questions.end();
+ it++, questionId++) {
+ tops[questionId] = currentHeight;
+ unsigned int lines = fontManager.getLinesCount(*it, 598);
+ if (lines == 0) {
+ lines = 1;
+ }
+ currentHeight += 18 * lines;
+ bottoms[questionId] = currentHeight;
+ }
+
+ int offsetY = 480 - (bottoms[questions.size() - 1] - tops[0]);
+ if (offsetY > 402) {
+ offsetY = 402;
+ } else if (offsetY < 2) {
+ offsetY = 2;
+ }
+
+ for (questionId = 0; questionId < questions.size(); questionId++) {
+ tops[questionId] += offsetY;
+ bottoms[questionId] += offsetY;
+ }
+
+ _engine->setCursor(181);
+ Graphics::Surface alphaSurface = _lastImage.getSubArea(Common::Rect(0, offsetY - 2, 640, 480));
+ _engine->makeTranslucent(alphaSurface, alphaSurface);
+
+ bool finished = false;
+ bool update = true;
+ unsigned int selectedQuestion = -1;
+ while (!finished) {
+ if (update) {
+ update = false;
+ questionId = 0;
+ for (Common::StringArray::const_iterator it = questions.begin(); it != questions.end();
+ it++, questionId++) {
+ fontManager.setForeColor(selectedQuestion == questionId ? 241 : 245);
+ fontManager.setupBlock(Common::Rect(10, tops[questionId], 608, bottoms[questionId]));
+ fontManager.displayBlockText(*it);
+ }
+ g_system->copyRectToScreen(_lastImage.getPixels(), _lastImage.pitch, 0, 0, _lastImage.w,
+ _lastImage.h);
+ }
+ g_system->updateScreen();
+
+ if (_engine->pollEvents()) {
+ _engine->clearKeys();
+ if (g_engine->shouldQuit()) {
+ finished = true;
+ selectedQuestion = -1;
+ break;
+ }
+ Common::Point mousePos = _engine->getMousePos();
+ if (_engine->getDragStatus() == kDragStatus_Finished && selectedQuestion != -1u) {
+ finished = true;
+ } else if (mousePos.x >= 608 || mousePos.y < offsetY) {
+ if (selectedQuestion != -1u) {
+ selectedQuestion = -1;
+ update = true;
+ }
+ } else {
+ for (questionId = 0; questionId < questions.size(); questionId++) {
+ if (mousePos.y > tops[questionId] && mousePos.y < bottoms[questionId]) {
+ break;
+ }
+ }
+ if (questionId < questions.size()) {
+ if (selectedQuestion != questionId) {
+ selectedQuestion = questionId;
+ update = true;
+ }
+ } else {
+ selectedQuestion = -1;
+ update = true;
+ }
+ }
+ }
+ }
+
+ return selectedQuestion;
+}
+
+void Versailles_DialogsManager::loadFrame(const Common::String &video) {
+ Common::String videoFName(video);
+ int lastDotPos = videoFName.size() - 1;
+ for (; lastDotPos >= 0; --lastDotPos) {
+ if (videoFName[lastDotPos] == '.') {
+ break;
+ }
+ }
+ if (lastDotPos > -1) {
+ videoFName.erase(lastDotPos);
+ videoFName += ".hnm";
+ }
+
+ Video::HNMDecoder *videoDecoder = new Video::HNMDecoder();
+
+ if (!videoDecoder->loadFile(videoFName)) {
+ warning("Failed to open movie file %s/%s", video.c_str(), videoFName.c_str());
+ delete videoDecoder;
+ return;
+ }
+
+ if (videoDecoder->hasDirtyPalette()) {
+ const byte *palette = videoDecoder->getPalette();
+ _engine->setupPalette(palette, 0, 256);
+ }
+
+ // Preload first frame to draw subtitles from it
+ const Graphics::Surface *firstFrame = videoDecoder->decodeNextFrame();
+ _lastImage.create(firstFrame->w, firstFrame->h, firstFrame->format);
+ _lastImage.blitFrom(*firstFrame);
+}
+
+} // End of namespace Versailles
+} // End of namespace CryOmni3D
diff --git a/engines/cryomni3d/versailles/dialogs_manager.h b/engines/cryomni3d/versailles/dialogs_manager.h
new file mode 100644
index 0000000000..3c5028ff2f
--- /dev/null
+++ b/engines/cryomni3d/versailles/dialogs_manager.h
@@ -0,0 +1,68 @@
+/* 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 CRYOMNI3D_VERSAILLES_DIALOGS_MANAGER_H
+#define CRYOMNI3D_VERSAILLES_DIALOGS_MANAGER_H
+
+#include "common/hashmap.h"
+#include "common/hash-str.h"
+#include "graphics/managed_surface.h"
+
+#include "cryomni3d/dialogs_manager.h"
+#include "cryomni3d/font_manager.h"
+
+namespace CryOmni3D {
+namespace Versailles {
+
+class CryOmni3DEngine_Versailles;
+
+class Versailles_DialogsManager : public DialogsManager {
+public:
+ Versailles_DialogsManager(CryOmni3DEngine_Versailles *engine) : _engine(engine) { }
+
+ // This overload will hide the base one and this is what we want
+ bool play(const Common::String &sequence);
+
+ typedef void (CryOmni3DEngine_Versailles::*ShowCallback)();
+ void registerShowCallback(const Common::String &showName, ShowCallback callback) { _shows[showName] = callback; }
+
+protected:
+ void executeShow(const Common::String &show) override;
+ void playDialog(const Common::String &video, const Common::String &sound,
+ const Common::String &text, const SubtitlesSettings &settings) override;
+ void displayMessage(const Common::String &text) override;
+ unsigned int askPlayerQuestions(const Common::String &video,
+ const Common::StringArray &questions) override;
+
+private:
+ CryOmni3DEngine_Versailles *_engine;
+ Common::HashMap<Common::String, ShowCallback> _shows;
+
+ void loadFrame(const Common::String &video);
+
+ Graphics::ManagedSurface _lastImage;
+};
+
+} // End of namespace Versailles
+} // End of namespace CryOmni3D
+
+#endif
diff --git a/engines/cryomni3d/versailles/documentation.cpp b/engines/cryomni3d/versailles/documentation.cpp
new file mode 100644
index 0000000000..bbc924290c
--- /dev/null
+++ b/engines/cryomni3d/versailles/documentation.cpp
@@ -0,0 +1,2070 @@
+/* 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/file.h"
+#include "common/system.h"
+#include "graphics/managed_surface.h"
+#include "image/bmp.h"
+
+#include "cryomni3d/font_manager.h"
+#include "cryomni3d/mouse_boxes.h"
+#include "cryomni3d/sprites.h"
+
+#include "cryomni3d/versailles/documentation.h"
+#include "cryomni3d/versailles/engine.h"
+
+namespace CryOmni3D {
+namespace Versailles {
+
+const char *Versailles_Documentation::kAllDocsFile = "tous_doc.txt";
+const char *Versailles_Documentation::kLinksDocsFile = "lien_doc.txt";
+const Versailles_Documentation::TimelineEntry Versailles_Documentation::kTimelineEntries[] = {
+ { "1638", 340, 15 },
+ { "1643", 470, 30 },
+ { "1648", 380, 45 },
+ { "1649", 500, 60 },
+ { "1650", 420, 75 },
+ { "1651", 520, 90 },
+ { "1652", 450, 105 },
+ { "1654", 540, 120 },
+ { "1658", 470, 135 },
+ { "1659", 550, 150 },
+ { "1660", 480, 165 },
+ { "1661", 560, 180 },
+ { "1662", 490, 195 },
+ { "1663", 560, 210 },
+ { "1664", 490, 225 },
+ { "1665", 560, 240 },
+ { "1666", 490, 255 },
+ { "1667", 560, 270 },
+ { "1668", 490, 285 },
+ { "1670", 550, 300 },
+ { "1671", 480, 315 },
+ { "1672", 540, 330 },
+ { "1673", 470, 345 },
+ { "1674", 530, 360 },
+ { "1675", 460, 375 },
+ { "1678", 510, 390 },
+ { "1680", 430, 405 },
+ { "1681", 490, 420 },
+ { "1682", 400, 435 },
+ { "1683", 450, 450 },
+ { "1684", 156, 444 },
+ { "1685", 81, 439 },
+ { "1686", 140, 422 },
+ { "1687", 73, 413 },
+ { "1689", 128, 401 },
+ { "1697", 62, 389 },
+ { "1700", 121, 378 },
+ { "1701", 62, 366 },
+ { "1702", 121, 355 },
+ { "1709", 62, 344 },
+ { "1710", 121, 333 },
+ { "1712", 67, 322 },
+ { "1715", 128, 311 },
+};
+
+void Versailles_Documentation::init(const Sprites *sprites, FontManager *fontManager,
+ const Common::StringArray *messages, CryOmni3DEngine *engine) {
+ _sprites = sprites;
+ _fontManager = fontManager;
+ _messages = messages;
+ _engine = engine;
+
+ // Build list of records
+ Common::File allDocsFile;
+
+ if (!allDocsFile.open(kAllDocsFile)) {
+ error("Can't open %s", kAllDocsFile);
+ }
+
+ unsigned int allDocsSize = allDocsFile.size();
+ char *allDocs = new char[allDocsSize + 1];
+ char *end = allDocs + allDocsSize;
+ allDocsFile.read(allDocs, allDocsSize);
+ allDocs[allDocsSize] = '\0';
+ allDocsFile.close();
+
+ const char *patterns[] = { "FICH=", nullptr };
+ RecordInfo record;
+
+ char *currentPos = allDocs;
+ char *lastRecordName;
+ bool first = true;
+
+ while (true) {
+ currentPos = getDocPartAddress(currentPos, end, patterns);
+ if (!currentPos) {
+ break;
+ }
+ currentPos -= 5;
+ if (first) {
+ record.position = currentPos - allDocs;
+ record.id = 0;
+
+ lastRecordName = currentPos + 5;
+
+ first = false;
+ } else {
+ record.size = (currentPos - allDocs) - record.position;
+ //debug("Found record %s", lastRecordName);
+ _records[lastRecordName] = record;
+ _recordsOrdered.push_back(lastRecordName);
+
+ record.id++;
+ record.position = currentPos - allDocs;
+
+ lastRecordName = currentPos + 5;
+ }
+ // Next line
+ currentPos += strlen(currentPos) + 1;
+ }
+ record.size = allDocsSize - record.position;
+ _records[lastRecordName] = record;
+ _recordsOrdered.push_back(lastRecordName);
+
+ delete[] allDocs;
+}
+
+void Versailles_Documentation::handleDocArea() {
+ g_system->showMouse(false);
+
+ // Load all links lazily and free them at the end to not waste memory
+ // Maybe it's not really useful
+ getLinks("ALL00", _allLinks);
+
+ bool end = false;
+ while (!end) {
+ Common::String selectedRecord = docAreaHandleSummary();
+ if (selectedRecord == "") {
+ end = true;
+ } else if (selectedRecord == "VT00") {
+ selectedRecord = docAreaHandleTimeline();
+ if (selectedRecord != "") {
+ if (docAreaHandleRecords(selectedRecord) == 2) {
+ end = true;
+ }
+ }
+ } else {
+ if (docAreaHandleRecords(selectedRecord) == 2) {
+ end = true;
+ }
+ }
+ }
+
+ _allLinks.clear();
+
+ g_system->showMouse(true);
+}
+
+void Versailles_Documentation::handleDocInGame(const Common::String &record) {
+ _visitTrace.clear();
+ _currentRecord = record;
+
+ Graphics::ManagedSurface docSurface;
+ Common::String nextRecord;
+ MouseBoxes boxes(3);
+
+ g_system->showMouse(false);
+ bool end = false;
+ while (!end) {
+ inGamePrepareRecord(docSurface, boxes);
+ unsigned int action = inGameHandleRecord(docSurface, boxes, nextRecord);
+ switch (action) {
+ case 0:
+ // Back
+ if (!_visitTrace.empty()) {
+ _currentRecord = _visitTrace.back();
+ _visitTrace.pop_back();
+ break;
+ }
+ // No previous record, like a quit
+ // fall through
+ case 1:
+ // Quit
+ end = true;
+ break;
+ case 2:
+ // Follow hyperlink keeping trace
+ _visitTrace.push_back(_currentRecord);
+ _currentRecord = nextRecord;
+ break;
+ default:
+ error("Invalid case %d when displaying doc record", action);
+ }
+ }
+ g_system->showMouse(true);
+}
+
+Common::String Versailles_Documentation::docAreaHandleSummary() {
+ struct Category {
+ const char *record;
+ const char *bmp;
+ Common::Point imgPos;
+ Common::Rect linesPos;
+ const Common::String *title;
+ Graphics::Surface highlightedImg;
+
+ Category(const char *record_, const char *bmp_, const Common::Point &imgPos_,
+ const Common::Rect &linesPos_, const Common::String *title_) :
+ record(record_), bmp(bmp_), imgPos(imgPos_), linesPos(linesPos_), title(title_) { }
+ } categories[8] = {
+ Category(
+ "VA00",
+ "VA.bmp",
+ Common::Point(142, 402),
+ Common::Rect(174, 372, 284, 372),
+ &(*_messages)[68]),
+ Category(
+ "VR00",
+ "VR.bmp",
+ Common::Point(82, 187),
+ Common::Rect(89, 187, 217, 212),
+ &(*_messages)[69]),
+ Category(
+ "VC00",
+ "VC.bmp",
+ Common::Point(176, 105),
+ Common::Rect(177, 107, 292, 130),
+ &(*_messages)[70]),
+ Category(
+ "VT00",
+ "VH.bmp", //sic
+ Common::Point(283, 467),
+ Common::Rect(311, 451, 466, 451),
+ &(*_messages)[73]),
+ Category(
+ "VV00",
+ "VV.bmp",
+ Common::Point(68, 305),
+ Common::Rect(94, 292, 300, 292),
+ &(*_messages)[71]),
+ Category(
+ "VS00",
+ "VS.bmp",
+ Common::Point(321, 70),
+ Common::Rect(322, 71, 540, 94),
+ &(*_messages)[72]),
+ Category(
+ nullptr,
+ nullptr,
+ Common::Point(256, 212),
+ Common::Rect(),
+ nullptr),
+ Category(
+ nullptr,
+ nullptr,
+ Common::Point(600, 450),
+ Common::Rect(),
+ nullptr)
+ };
+ Image::BitmapDecoder bmpDecoder;
+ Common::File file;
+
+ for (unsigned int i = 0; i < ARRAYSIZE(categories); i++) {
+ if (!categories[i].bmp) {
+ // No BMP to load
+ continue;
+ }
+ if (!file.open(categories[i].bmp)) {
+ error("Failed to open BMP file: %s", categories[i].bmp);
+ }
+ if (!bmpDecoder.loadStream(file)) {
+ error("Failed to load BMP file: %s", categories[i].bmp);
+ }
+ categories[i].highlightedImg.copyFrom(*bmpDecoder.getSurface());
+ bmpDecoder.destroy();
+ file.close();
+ }
+
+ Image::ImageDecoder *imageDecoder = _engine->loadHLZ("SOM1.HLZ");
+ if (!imageDecoder) {
+ return "";
+ }
+ const Graphics::Surface *bgFrame = imageDecoder->getSurface();
+
+ Graphics::ManagedSurface docSurface;
+ docSurface.create(bgFrame->w, bgFrame->h, bgFrame->format);
+ docSurface.blitFrom(*bgFrame);
+
+ _fontManager->setCurrentFont(0);
+ _fontManager->setTransparentBackground(true);
+ _fontManager->setLineHeight(14);
+ _fontManager->setSpaceWidth(0);
+ _fontManager->setCharSpacing(1);
+ _fontManager->setSurface(&docSurface);
+
+ MouseBoxes boxes(8);
+ boxes.setupBox(0, 104, 335, 177, 408);
+ boxes.setupBox(1, 46, 122, 119, 195);
+ boxes.setupBox(2, 140, 40, 213, 113);
+ boxes.setupBox(3, 247, 402, 320, 475);
+ boxes.setupBox(4, 32, 240, 105, 313);
+ boxes.setupBox(5, 285, 5, 358, 78);
+ // No box for 6
+ boxes.setupBox(7, 0, 480 - _sprites->getCursor(225).getHeight(), 640, 480);
+
+ _engine->setupPalette(imageDecoder->getPalette(), imageDecoder->getPaletteStartIndex(),
+ imageDecoder->getPaletteColorCount());
+
+ _engine->setCursor(181);
+ g_system->showMouse(true);
+
+ bool redraw = true;
+ unsigned int hoveredBox = -1;
+ unsigned int selectedBox = -1;
+
+ while (selectedBox == -1u) {
+ if (redraw) {
+ // Draw without worrying of already modified areas, that's handled when recomputing hoveredBox
+ for (unsigned int i = 0; i < ARRAYSIZE(categories); i++) {
+ unsigned int foreColor = 243;
+ if (i == hoveredBox) {
+ foreColor = 241;
+ if (categories[hoveredBox].highlightedImg.getPixels() != nullptr) {
+ docSurface.transBlitFrom(categories[i].highlightedImg, categories[i].imgPos - Common::Point(36,
+ 65));
+ }
+ }
+ _fontManager->setForeColor(foreColor);
+ if (categories[i].title) {
+ unsigned int x = categories[i].linesPos.right - _fontManager->getStrWidth(*categories[i].title);
+ unsigned int y = categories[i].linesPos.bottom - _fontManager->getFontMaxHeight() - 5;
+ _fontManager->displayStr(x, y, *categories[i].title);
+
+ // Draw line to text
+ docSurface.vLine(categories[i].linesPos.left, categories[i].linesPos.top,
+ categories[i].linesPos.bottom, foreColor);
+ docSurface.hLine(categories[i].linesPos.left, categories[i].linesPos.bottom,
+ categories[i].linesPos.right - 1, foreColor); // minus 1 because hLine draws inclusive
+ }
+ }
+ docSurface.transBlitFrom(_sprites->getSurface(225), boxes.getBoxOrigin(7),
+ _sprites->getKeyColor(225));
+
+ g_system->copyRectToScreen(docSurface.getPixels(), docSurface.pitch, 0, 0, docSurface.w,
+ docSurface.h);
+
+ redraw = false;
+ }
+ g_system->updateScreen();
+
+ if (_engine->pollEvents()) {
+ if (!_engine->getCurrentMouseButton()) {
+ // Don't change highlighted icon when clicking
+ Common::Point mouse = _engine->getMousePos();
+ bool foundBox = false;
+ for (unsigned int i = 0; i < ARRAYSIZE(categories); i++) {
+ if (boxes.hitTest(i, mouse)) {
+ foundBox = true;
+ if (i != hoveredBox) {
+ hoveredBox = i;
+ redraw = true;
+ }
+ }
+ }
+ if (!foundBox && hoveredBox != -1u) {
+ if (categories[hoveredBox].highlightedImg.getPixels() != nullptr) {
+ // Restore original icon
+ const Common::Point &imgPos = categories[hoveredBox].imgPos;
+ docSurface.blitFrom(*bgFrame, Common::Rect(
+ imgPos.x - 36, imgPos.y - 65, imgPos.x + 37, imgPos.y + 8),
+ Common::Point(imgPos.x - 36, imgPos.y - 65));
+ }
+ hoveredBox = -1;
+ redraw = true;
+ }
+ }
+ if (_engine->getDragStatus() == kDragStatus_Finished) {
+ if (hoveredBox != -1u) {
+ selectedBox = hoveredBox;
+ }
+ }
+ if (_engine->checkKeysPressed(1, Common::KEYCODE_ESCAPE)) {
+ selectedBox = 7;
+ }
+ }
+ if (g_engine->shouldQuit()) {
+ selectedBox = 7;
+ }
+ }
+
+ g_system->showMouse(false);
+
+ delete imageDecoder;
+
+ if (selectedBox == 7) {
+ return "";
+ } else {
+ return categories[selectedBox].record;
+ }
+}
+
+Common::String Versailles_Documentation::docAreaHandleTimeline() {
+ Image::ImageDecoder *imageDecoder = _engine->loadHLZ("chrono1.HLZ");
+ if (!imageDecoder) {
+ return "";
+ }
+ const Graphics::Surface *bgFrame = imageDecoder->getSurface();
+
+ Graphics::ManagedSurface docSurface;
+ docSurface.create(bgFrame->w, bgFrame->h, bgFrame->format);
+ docSurface.blitFrom(*bgFrame);
+
+ _fontManager->setCurrentFont(1);
+ _fontManager->setTransparentBackground(true);
+ _fontManager->setLineHeight(14);
+ _fontManager->setSpaceWidth(0);
+ _fontManager->setCharSpacing(1);
+ _fontManager->setSurface(&docSurface);
+
+ _engine->setupPalette(imageDecoder->getPalette(), imageDecoder->getPaletteStartIndex(),
+ imageDecoder->getPaletteColorCount());
+
+ _fontManager->displayStr(78, 10, (*_messages)[73]);
+ docSurface.hLine(0, 39, 171, 241); // minus 1 because hLine draws inclusive
+
+ _fontManager->setCurrentFont(0);
+
+ MouseBoxes boxes(ARRAYSIZE(kTimelineEntries) + 1);
+ for (unsigned int box_id = 0; box_id < ARRAYSIZE(kTimelineEntries); box_id++) {
+ boxes.setupBox(box_id, kTimelineEntries[box_id].x, kTimelineEntries[box_id].y,
+ kTimelineEntries[box_id].x + 30, kTimelineEntries[box_id].y + 20);
+ }
+ const unsigned int leaveBoxId = ARRAYSIZE(kTimelineEntries);
+ boxes.setupBox(leaveBoxId, 639 - _sprites->getCursor(105).getWidth(),
+ 479 - _sprites->getCursor(105).getHeight(), 640, 480);
+
+ _engine->setCursor(181);
+ g_system->showMouse(true);
+
+ bool redraw = true;
+ unsigned int hoveredBox = -1;
+ unsigned int selectedBox = -1;
+
+ while (selectedBox == -1u) {
+ if (redraw) {
+ // Draw without worrying of already modified areas, that's handled when recomputing hoveredBox
+ for (unsigned int i = 0; i < ARRAYSIZE(kTimelineEntries); i++) {
+ _fontManager->setForeColor(i == hoveredBox ? 241 : 243);
+ _fontManager->displayStr(kTimelineEntries[i].x, kTimelineEntries[i].y, kTimelineEntries[i].year);
+ }
+ docSurface.transBlitFrom(_sprites->getSurface(105), boxes.getBoxOrigin(leaveBoxId),
+ _sprites->getKeyColor(105));
+
+ g_system->copyRectToScreen(docSurface.getPixels(), docSurface.pitch, 0, 0,
+ docSurface.w, docSurface.h);
+ redraw = false;
+ }
+ g_system->updateScreen();
+
+ if (_engine->pollEvents()) {
+ Common::Point mouse = _engine->getMousePos();
+ if (!_engine->getCurrentMouseButton()) {
+ // Don't change highlighted date when clicking
+ bool foundBox = false;
+ for (unsigned int i = 0; i < ARRAYSIZE(kTimelineEntries); i++) {
+ if (boxes.hitTest(i, mouse)) {
+ foundBox = true;
+ if (i != hoveredBox) {
+ hoveredBox = i;
+ redraw = true;
+ }
+ }
+ }
+ if (!foundBox && hoveredBox != -1u) {
+ hoveredBox = -1;
+ redraw = true;
+ }
+ }
+ if (_engine->getDragStatus() == kDragStatus_Finished) {
+ if (hoveredBox != -1u) {
+ selectedBox = hoveredBox;
+ }
+ if (boxes.hitTest(leaveBoxId, mouse)) {
+ selectedBox = leaveBoxId;
+ }
+ }
+ if (_engine->checkKeysPressed(1, Common::KEYCODE_ESCAPE)) {
+ selectedBox = leaveBoxId;
+ }
+ }
+ if (g_engine->shouldQuit()) {
+ selectedBox = leaveBoxId;
+ }
+ }
+
+ g_system->showMouse(false);
+
+ delete imageDecoder;
+
+ if (selectedBox == leaveBoxId) {
+ return "";
+ } else {
+ Common::String ret = "VT";
+ ret += kTimelineEntries[selectedBox].year;
+ return ret;
+ }
+}
+
+unsigned int Versailles_Documentation::docAreaHandleRecords(const Common::String &record) {
+ unsigned int action = -1;
+
+ _currentRecord = record;
+ _visitTrace.clear();
+
+ Graphics::ManagedSurface docSurface;
+ Common::String nextRecord;
+ MouseBoxes boxes(10 + ARRAYSIZE(kTimelineEntries));
+
+ while (true) {
+ if (action == -1u) {
+ _currentRecord.toUppercase();
+
+ //debug("Displaying %s", _currentRecord.c_str());
+ docAreaPrepareNavigation();
+ docAreaPrepareRecord(docSurface, boxes);
+ action = docAreaHandleRecord(docSurface, boxes, nextRecord);
+ }
+
+ switch (action) {
+ case 0:
+ action = -1;
+ // Back
+ if (!_visitTrace.empty()) {
+ _currentRecord = _visitTrace.back();
+ _visitTrace.pop_back();
+ break;
+ }
+ // No previous record, like a back to root
+ // fall through
+ case 1:
+ // Back to root
+ return 1;
+ case 2:
+ action = -1;
+ // Follow hyperlink keeping trace
+ _visitTrace.push_back(_currentRecord);
+ _currentRecord = nextRecord;
+ break;
+ case 3:
+ action = -1;
+ // Follow hyperlink losing trace
+ _visitTrace.clear();
+ _currentRecord = nextRecord;
+ break;
+ case 6:
+ // Quit
+ return 2;
+ case 7:
+ action = -1;
+ // General map
+ _visitTrace.clear();
+ nextRecord = docAreaHandleGeneralMap();
+ if (nextRecord == "") {
+ // Go back to current record
+ break;
+ } else if (nextRecord != "VS00") {
+ _currentRecord = nextRecord;
+ break;
+ }
+ // castle has been selected, display its map
+ // fall through
+ case 8:
+ action = -1;
+ // Castle map
+ _visitTrace.clear();
+ nextRecord = docAreaHandleCastleMap();
+ if (nextRecord == "") {
+ // Go back to current record
+ break;
+ } else if (nextRecord != "planG") {
+ _currentRecord = nextRecord;
+ break;
+ } else {
+ // We can't go up to previous case, so let's do a round
+ action = 7;
+ break;
+ }
+ case 9:
+ action = -1;
+ // Start of category
+ _currentRecord = _categoryStartRecord;
+ break;
+ default:
+ error("Invalid case %d when displaying doc record", action);
+ }
+ }
+ error("shouldn't be there");
+}
+
+void Versailles_Documentation::docAreaPrepareNavigation() {
+ _currentInTimeline = false;
+ _currentMapLayout = false;
+ _currentHasMap = false;
+ _currentLinks.clear();
+
+ if (_currentRecord.hasPrefix("VA")) {
+ _categoryStartRecord = "VA00";
+ _categoryEndRecord = "VA15";
+ _categoryTitle = (*_messages)[68];
+ } else if (_currentRecord.hasPrefix("VC")) {
+ _categoryStartRecord = "VC00";
+ _categoryEndRecord = "VC26";
+ _categoryTitle = (*_messages)[70];
+ } else if (_currentRecord.hasPrefix("VR")) {
+ _categoryStartRecord = "VR00";
+ _categoryEndRecord = "VR14";
+ _categoryTitle = (*_messages)[69];
+ } else if (_currentRecord.hasPrefix("VS")) {
+ _categoryStartRecord = "VS00";
+ _categoryEndRecord = "VS37";
+ _categoryTitle = (*_messages)[72];
+ unsigned int id = atoi(_currentRecord.c_str() + 2);
+ if (id >= 16 && id <= 40) {
+ _currentMapLayout = true;
+ }
+ if ((id >= 16 && id <= 31) ||
+ (id >= 35 && id <= 39)) {
+ _currentHasMap = true;
+ }
+ } else if (_currentRecord.hasPrefix("VT")) {
+ _categoryStartRecord = "VT00";
+ _categoryEndRecord = "VT1715";
+ _categoryTitle = (*_messages)[73];
+ _currentInTimeline = true;
+ } else if (_currentRecord.hasPrefix("VV")) {
+ _categoryStartRecord = "VV00";
+ _categoryEndRecord = "VV15";
+ _categoryTitle = (*_messages)[71];
+ }
+ getLinks(_currentRecord, _currentLinks);
+}
+
+void Versailles_Documentation::docAreaPrepareRecord(Graphics::ManagedSurface &surface,
+ MouseBoxes &boxes) {
+ boxes.reset();
+
+ setupRecordBoxes(true, boxes);
+
+ Common::String title, subtitle, caption;
+ Common::StringArray hyperlinks;
+ Common::String text = getRecordData(_currentRecord, title, subtitle, caption, hyperlinks);
+
+ drawRecordData(surface, text, title, subtitle, caption);
+
+ if (_currentInTimeline) {
+ surface.hLine(0, 39, 171, 241); // minus 1 because hLine draws inclusive
+ _fontManager->setCurrentFont(0);
+ _fontManager->setTransparentBackground(true);
+ _fontManager->setLineHeight(14);
+ _fontManager->setSpaceWidth(0);
+ _fontManager->setCharSpacing(1);
+ _fontManager->setSurface(&surface);
+ _fontManager->setForeColor(243);
+ for (unsigned int box_id = 10; box_id < ARRAYSIZE(kTimelineEntries) + 10; box_id++) {
+ boxes.display(box_id, *_fontManager);
+ }
+ }
+
+ drawRecordBoxes(surface, true, boxes);
+}
+
+unsigned int Versailles_Documentation::docAreaHandleRecord(Graphics::ManagedSurface &surface,
+ MouseBoxes &boxes, Common::String &nextRecord) {
+ // Hovering is only handled for timeline entries
+ _engine->setCursor(181);
+ g_system->showMouse(true);
+
+ bool first = true;
+ bool redraw = true;
+ unsigned int hoveredBox = -1;
+ unsigned int action = -1;
+
+ while (action == -1u) {
+ if (redraw) {
+ g_system->copyRectToScreen(surface.getPixels(), surface.pitch, 0, 0, surface.w, surface.h);
+ redraw = false;
+ }
+ g_system->updateScreen();
+
+ if (_engine->pollEvents() || first) {
+ first = false;
+ if (g_engine->shouldQuit()) {
+ // Fake the quit
+ action = 6;
+ }
+ Common::Point mouse = _engine->getMousePos();
+ if (_currentInTimeline) {
+ bool foundBox = false;
+ for (unsigned int i = 10; i < 10 + ARRAYSIZE(kTimelineEntries); i++) {
+ if (boxes.hitTest(i, mouse)) {
+ foundBox = true;
+ if (i != hoveredBox) {
+ _fontManager->setCurrentFont(0);
+ _fontManager->setTransparentBackground(true);
+ _fontManager->setSurface(&surface);
+ if (hoveredBox != -1u) {
+ // Restore the previous entry hovered
+ _fontManager->setForeColor(243);
+ boxes.display(hoveredBox, *_fontManager);
+ }
+ hoveredBox = i;
+ _fontManager->setForeColor(241);
+ boxes.display(hoveredBox, *_fontManager);
+ redraw = true;
+ }
+ }
+ }
+ if (!foundBox && hoveredBox != -1u) {
+ // Restore the previous entry hovered
+ _fontManager->setForeColor(243);
+ boxes.display(hoveredBox, *_fontManager);
+ hoveredBox = -1;
+ redraw = true;
+ }
+ } else if (_currentHasMap) { // Mutually exclusive with timeline
+ // No clash is possible for hoveredBox between timeline and map
+ if (boxes.hitTest(8, mouse)) {
+ if (hoveredBox != 8) {
+ _engine->setCursor(145);
+ hoveredBox = 8;
+ }
+ } else {
+ if (hoveredBox == 8) {
+ _engine->setCursor(181);
+ hoveredBox = -1;
+ }
+ }
+ }
+ if (_engine->getDragStatus() == kDragStatus_Pressed) {
+ if (boxes.hitTest(2, mouse) && _currentLinks.size()) {
+ Common::StringArray items;
+ for (Common::Array<LinkInfo>::const_iterator it = _currentLinks.begin(); it != _currentLinks.end();
+ it++) {
+ items.push_back(it->title);
+ }
+ Common::Rect iconRect = boxes.getBoxRect(2);
+ unsigned int selectedItem = handlePopupMenu(surface, Common::Point(iconRect.right, iconRect.top),
+ true, 20, items);
+ if (selectedItem != -1u) {
+ nextRecord = _currentLinks[selectedItem].record;
+ action = 2;
+ }
+ } else if (boxes.hitTest(3, mouse)) {
+ Common::StringArray items;
+ for (Common::Array<LinkInfo>::const_iterator it = _allLinks.begin(); it != _allLinks.end(); it++) {
+ items.push_back(it->title);
+ }
+ Common::Rect iconRect = boxes.getBoxRect(3);
+ unsigned int selectedItem = handlePopupMenu(surface, Common::Point(iconRect.right, iconRect.top),
+ true, 20, items);
+ if (selectedItem != -1u) {
+ nextRecord = _allLinks[selectedItem].record;
+ action = 3;
+ }
+ }
+ } else if (_engine->getDragStatus() == kDragStatus_Finished) {
+ if (boxes.hitTest(0, mouse)) {
+ // Back in history
+ action = 0;
+ } else if (boxes.hitTest(1, mouse)) {
+ // Handle summary menu
+ Common::StringArray items;
+ items.push_back((*_messages)[61]);
+ items.push_back((*_messages)[62]);
+ unsigned int selectedItem = handlePopupMenu(surface, boxes.getBoxOrigin(1), false, 20, items);
+ if (selectedItem == 0) {
+ action = 1;
+ } else if (selectedItem == 1) {
+ action = 7;
+ }
+ } else if (boxes.hitTest(4, mouse)) {
+ // Next
+ action = 4;
+ } else if (boxes.hitTest(5, mouse)) {
+ // Previous
+ action = 5;
+ } else if (boxes.hitTest(6, mouse)) {
+ // Handle quit menu
+ Common::StringArray items;
+ items.push_back((*_messages)[60]);
+ unsigned int selectedItem = handlePopupMenu(surface, boxes.getBoxOrigin(6), false, 20, items);
+ if (selectedItem == 0) {
+ action = 6;
+ }
+ } else if (_currentHasMap && boxes.hitTest(8, mouse)) {
+ // Map
+ action = 8;
+ } else if (boxes.hitTest(9, mouse)) {
+ // Category name
+ action = 9;
+ } else if (_currentInTimeline && hoveredBox != -1u) {
+ // Clicked on a timeline entry
+ nextRecord = "VT";
+ nextRecord += kTimelineEntries[hoveredBox - 10].year;
+ // Fake a global jump
+ action = 3;
+ }
+ }
+ if (action == 4 || action == 5) {
+ if (action == 4 && _currentRecord == _categoryEndRecord) {
+ action = -1;
+ continue;
+ }
+ if (action == 5 && _currentRecord == _categoryStartRecord) {
+ action = -1;
+ continue;
+ }
+ Common::HashMap<Common::String, RecordInfo>::iterator hmIt = _records.find(_currentRecord);
+ if (hmIt == _records.end()) {
+ // Shouldn't happen
+ action = -1;
+ continue;
+ }
+ unsigned int recordId = hmIt->_value.id;
+ if (action == 4) {
+ recordId++;
+ } else if (action == 5) {
+ recordId--;
+ }
+ assert(recordId < _recordsOrdered.size());
+ nextRecord = _recordsOrdered[recordId];
+ // Fake a global jump
+ action = 3;
+ }
+ }
+ }
+
+ g_system->showMouse(false);
+ _engine->setCursor(181);
+ return action;
+}
+
+Common::String Versailles_Documentation::docAreaHandleGeneralMap() {
+ struct Area {
+ Common::Rect areaPos;
+ const char *record;
+ const char *bmp;
+ unsigned int messageId;
+ const Common::String *message;
+ Common::Point messagePos;
+ Graphics::Surface highlightedImg;
+
+ Area(const Common::Point &areaPos_, const char *bmp_, unsigned int messageId_,
+ const char *record_ = nullptr) :
+ areaPos(areaPos_.x, areaPos_.y, areaPos_.x, areaPos_.y), record(record_), bmp(bmp_),
+ messageId(messageId_), message(nullptr) { }
+ Area(const Common::Rect &areaPos_, unsigned int messageId_, const char *record_ = nullptr) :
+ areaPos(areaPos_), record(record_), bmp(nullptr), messageId(messageId_), message(nullptr) { }
+ } areas[] = {
+ Area(Common::Point(174, 181), "APL.bmp", 74),
+ Area(Common::Point(422, 129), "CHAT.bmp", 75, "VS00"),
+ Area(Common::Point(193, 204), "COLN.bmp", 76, "VS02"),
+ Area(Common::Point(327, 269), "LABY.bmp", 77, "VS33"),
+ Area(Common::Point(327, 170), "LATN.bmp", 78),
+ Area(Common::Point(396, 271), "ORG.bmp", 79, "VS32"),
+ Area(Common::Point(385, 203), "PART.bmp", 80, "VS06"),
+ Area(Common::Point(212, 193), "TAP.bmp", 81),
+ Area(Common::Rect(0, 194, 154, 211), 86, "VS09"),
+ Area(Common::Rect(396, 229, 450, 268), 87),
+ Area(Common::Rect(394, 133, 450, 177), 88),
+ Area(Common::Rect(489, 376, 592, 479), 89, "VS07"),
+ Area(Common::Rect(327, 233, 386, 266), 90),
+ Area(Common::Rect(395, 18, 451, 60), 91),
+ Area(Common::Rect(383, 381, 477, 479), 92)
+ };
+
+ _fontManager->setCurrentFont(0);
+ _fontManager->setTransparentBackground(true);
+ _fontManager->setLineHeight(14);
+ _fontManager->setSpaceWidth(0);
+ _fontManager->setCharSpacing(1);
+
+ MouseBoxes boxes(ARRAYSIZE(areas) + 1);
+
+ Image::BitmapDecoder bmpDecoder;
+ Common::File file;
+
+ for (unsigned int i = 0; i < ARRAYSIZE(areas); i++) {
+ if (areas[i].bmp) {
+ if (!file.open(areas[i].bmp)) {
+ error("Failed to open BMP file: %s", areas[i].bmp);
+ }
+ if (!bmpDecoder.loadStream(file)) {
+ error("Failed to load BMP file: %s", areas[i].bmp);
+ }
+ areas[i].highlightedImg.copyFrom(*bmpDecoder.getSurface());
+ bmpDecoder.destroy();
+ file.close();
+ areas[i].areaPos.setWidth(areas[i].highlightedImg.w);
+ areas[i].areaPos.setHeight(areas[i].highlightedImg.h);
+ }
+ areas[i].message = &(*_messages)[areas[i].messageId];
+ unsigned int lineWidth = _fontManager->getStrWidth(*areas[i].message);
+ areas[i].messagePos.x = (areas[i].areaPos.left + areas[i].areaPos.right) / 2 - lineWidth / 2;
+ areas[i].messagePos.y = areas[i].areaPos.top - 40;
+ if (areas[i].messagePos.x < 8) {
+ areas[i].messagePos.x = 8;
+ } else if (areas[i].messagePos.x + lineWidth > 627) {
+ areas[i].messagePos.x = 627 - lineWidth;
+ }
+ if (areas[i].messagePos.y < 5) {
+ areas[i].messagePos.y = 5;
+ }
+ const Common::Rect &areaPos = areas[i].areaPos;
+ boxes.setupBox(i, areaPos.left, areaPos.top, areaPos.right, areaPos.bottom);
+ }
+ boxes.setupBox(ARRAYSIZE(areas), 639 - _sprites->getCursor(105).getWidth(),
+ 479 - _sprites->getCursor(105).getHeight(), 640, 480);
+
+ Image::ImageDecoder *imageDecoder = _engine->loadHLZ("PLANGR.HLZ");
+ if (!imageDecoder) {
+ return "";
+ }
+ const Graphics::Surface *bgFrame = imageDecoder->getSurface();
+
+ Graphics::ManagedSurface mapSurface;
+ mapSurface.create(bgFrame->w, bgFrame->h, bgFrame->format);
+ mapSurface.blitFrom(*bgFrame);
+
+ _fontManager->setSurface(&mapSurface);
+
+ _engine->setupPalette(imageDecoder->getPalette(), imageDecoder->getPaletteStartIndex(),
+ imageDecoder->getPaletteColorCount());
+
+ _engine->setCursor(181);
+ g_system->showMouse(true);
+
+ bool redraw = true;
+ unsigned int hoveredBox = -1;
+ unsigned int selectedBox = -1;
+
+ while (selectedBox == -1u) {
+ if (redraw) {
+ // Draw without worrying of already modified areas, that's handled when recomputing hoveredBox
+ if (hoveredBox != -1u) {
+ if (areas[hoveredBox].highlightedImg.getPixels() != nullptr) {
+ mapSurface.transBlitFrom(areas[hoveredBox].highlightedImg,
+ Common::Point(areas[hoveredBox].areaPos.left, areas[hoveredBox].areaPos.top));
+ } else {
+ unsigned int middleX = (areas[hoveredBox].areaPos.left + areas[hoveredBox].areaPos.right) / 2;
+ unsigned int middleY = (areas[hoveredBox].areaPos.top + areas[hoveredBox].areaPos.bottom) / 2;
+ unsigned int spriteX = middleX - _sprites->getCursor(163).getWidth() / 2;
+ unsigned int spriteY = middleY - _sprites->getCursor(163).getHeight() / 2;
+ mapSurface.transBlitFrom(_sprites->getSurface(163), Common::Point(spriteX, spriteY),
+ _sprites->getKeyColor(163));
+ }
+ _fontManager->setForeColor(areas[hoveredBox].record == nullptr ? 243 : 240);
+ Graphics::Surface subSurface = mapSurface.getSubArea(Common::Rect(areas[hoveredBox].messagePos.x -
+ 3,
+ areas[hoveredBox].messagePos.y - 3,
+ areas[hoveredBox].messagePos.x + _fontManager->getStrWidth(*areas[hoveredBox].message) + 3,
+ areas[hoveredBox].messagePos.y + _fontManager->getFontMaxHeight() + 8));
+ _engine->makeTranslucent(subSurface, subSurface);
+ _fontManager->displayStr(areas[hoveredBox].messagePos.x, areas[hoveredBox].messagePos.y,
+ *areas[hoveredBox].message);
+ }
+ mapSurface.transBlitFrom(_sprites->getSurface(105), boxes.getBoxOrigin(ARRAYSIZE(areas)),
+ _sprites->getKeyColor(105));
+ /*
+ // For debugging only
+ for(unsigned int i = 0; i < ARRAYSIZE(areas); i++) {
+ mapSurface.frameRect(areas[i].areaPos, 0);
+ }
+ */
+
+ g_system->copyRectToScreen(mapSurface.getPixels(), mapSurface.pitch, 0, 0, mapSurface.w,
+ mapSurface.h);
+
+ redraw = false;
+ }
+ g_system->updateScreen();
+
+ if (_engine->pollEvents()) {
+ Common::Point mouse = _engine->getMousePos();
+ if (!_engine->getCurrentMouseButton()) {
+ // Don't change highlighted icon when clicking
+ bool foundBox = false;
+ unsigned int oldHoveredBox = hoveredBox;
+ for (unsigned int i = 0; i < ARRAYSIZE(areas); i++) {
+ if (boxes.hitTest(i, mouse)) {
+ if (i != hoveredBox) {
+ hoveredBox = i;
+ redraw = true;
+ }
+ foundBox = true;
+ break;
+ }
+ }
+ if (!foundBox && hoveredBox != -1u) {
+ hoveredBox = -1;
+ redraw = true;
+ }
+ if (hoveredBox != oldHoveredBox && oldHoveredBox != -1u) {
+ // Restore original area
+ mapSurface.blitFrom(*bgFrame, areas[oldHoveredBox].areaPos,
+ Common::Point(areas[oldHoveredBox].areaPos.left, areas[oldHoveredBox].areaPos.top));
+ Common::Rect textRect(areas[oldHoveredBox].messagePos.x - 3,
+ areas[oldHoveredBox].messagePos.y - 3,
+ areas[oldHoveredBox].messagePos.x + _fontManager->getStrWidth(*areas[oldHoveredBox].message) + 3,
+ areas[oldHoveredBox].messagePos.y + _fontManager->getFontMaxHeight() + 8);
+ mapSurface.blitFrom(*bgFrame, textRect,
+ Common::Point(textRect.left, textRect.top));
+ }
+ }
+ if (_engine->getDragStatus() == kDragStatus_Finished) {
+ if (hoveredBox != -1u && areas[hoveredBox].record) {
+ selectedBox = hoveredBox;
+ } else if (boxes.hitTest(ARRAYSIZE(areas), mouse)) {
+ selectedBox = ARRAYSIZE(areas);
+ }
+ }
+ if (_engine->checkKeysPressed(1, Common::KEYCODE_ESCAPE)) {
+ selectedBox = ARRAYSIZE(areas);
+ }
+ if (g_engine->shouldQuit()) {
+ selectedBox = ARRAYSIZE(areas);
+ }
+ }
+ }
+
+ g_system->showMouse(false);
+
+ delete imageDecoder;
+
+ if (selectedBox == ARRAYSIZE(areas)) {
+ return "";
+ } else {
+ return areas[selectedBox].record;
+ }
+}
+
+Common::String Versailles_Documentation::docAreaHandleCastleMap() {
+ struct Area {
+ Common::Rect areaPos;
+ bool fillArea;
+ const char *record;
+ unsigned int messageId;
+ Common::String message;
+ Common::Point messagePos;
+ Common::Rect areaPos1;
+ Common::Rect areaPos2;
+
+ Area(const Common::Rect &areaPos_, const char *record_, bool fillArea_ = true,
+ unsigned int messageId_ = -1) :
+ areaPos(areaPos_), record(record_), fillArea(fillArea_), messageId(messageId_) { }
+ Area(const Common::Rect &areaPos_, const Common::Rect &areaPos1_,
+ const Common::Rect &areaPos2_, const char *record_, bool fillArea_ = true,
+ unsigned int messageId_ = -1) :
+ areaPos(areaPos_), areaPos1(areaPos1_), areaPos2(areaPos2_),
+ record(record_), fillArea(fillArea_), messageId(messageId_) { }
+ } areas[] = {
+ /* 0 */
+ Area(Common::Rect(212, 134, 239, 164), "VS16"),
+ Area(Common::Rect(74, 160, 89, 173), "VS24"),
+ Area(Common::Rect(93, 160, 109, 173), "VS25"),
+ Area(Common::Rect(130, 160, 154, 173), "VS26"),
+ Area(Common::Rect(158, 160, 171, 173), "VS27"),
+ Area(Common::Rect(199, 160, 209, 171), "VS28"),
+ Area(Common::Rect(74, 177, 89, 291), "VS31"),
+ Area(Common::Rect(158, 178, 195, 193), "VS30"),
+ Area(Common::Rect(199, 175, 209, 188), "VS29"),
+ Area(Common::Rect(112, 220, 160, 249), "VS35"),
+ /* 10 */
+ Area(Common::Rect(93, 227, 106, 240), "VS23"),
+ Area(Common::Rect(93, 244, 106, 257), "VS22"),
+ Area(Common::Rect(93, 261, 106, 274), "VS20"),
+ Area(Common::Rect(110, 255, 126, 269), "VS19"),
+ Area(Common::Rect(133, 255, 155, 271), "VS18"),
+ Area(Common::Rect(93, 285, 99, 295), "VS21"),
+ Area(Common::Rect(152, 279, 173, 288), "VS17"),
+ Area(Common::Rect(336, 113, 359, 136), Common::Rect(359, 116, 448, 134), Common::Rect(449, 113, 473, 136), "VS36"),
+ Area(Common::Rect(336, 328, 359, 351), Common::Rect(359, 331, 448, 348), Common::Rect(449, 328, 473, 351), "VS36"),
+ Area(Common::Rect(563, 0, 624, 139), "planG", false, 82),
+ /* 20 */
+ Area(Common::Rect(563, 300, 624, 462), "planG", false, 83),
+ Area(Common::Rect(0, 0, 205, 152), "planG", false, 84),
+ Area(Common::Rect(0, 318, 205, 465), "planG", false, 84),
+ Area(Common::Rect(160, 210, 329, 267), "VS40", false),
+ Area(Common::Rect(330, 158, 561, 315), "planG", false, 85),
+ };
+
+ _fontManager->setCurrentFont(0);
+ _fontManager->setTransparentBackground(true);
+ _fontManager->setLineHeight(14);
+ _fontManager->setSpaceWidth(0);
+ _fontManager->setCharSpacing(1);
+
+ MouseBoxes boxes(ARRAYSIZE(areas) + 1);
+
+ for (unsigned int i = 0; i < ARRAYSIZE(areas); i++) {
+ if (areas[i].messageId != -1u) {
+ areas[i].message = (*_messages)[areas[i].messageId];
+ } else {
+ areas[i].message = getRecordTitle(areas[i].record);
+ }
+ unsigned int lineWidth = _fontManager->getStrWidth(areas[i].message);
+ unsigned int right;
+ if (areas[i].areaPos2.right) {
+ right = areas[i].areaPos2.right;
+ } else {
+ right = areas[i].areaPos.right;
+ }
+ areas[i].messagePos.x = (areas[i].areaPos.left + right) / 2 - lineWidth / 2;
+ if (areas[i].fillArea) {
+ areas[i].messagePos.y = areas[i].areaPos.top - 30;
+ } else {
+ areas[i].messagePos.y = (areas[i].areaPos.top + areas[i].areaPos.bottom) / 2 - 50;
+ }
+ if (areas[i].messagePos.x < 5) {
+ areas[i].messagePos.x = 5;
+ } else if (areas[i].messagePos.x + lineWidth > 630) {
+ areas[i].messagePos.x = 630 - lineWidth;
+ }
+ if (areas[i].messagePos.y < 2) {
+ areas[i].messagePos.y = 2;
+ }
+ Common::Rect areaPos = areas[i].areaPos;
+ if (areas[i].areaPos2.right) {
+ areaPos.right = areas[i].areaPos2.right;
+ areaPos.bottom = areas[i].areaPos2.bottom;
+ }
+ boxes.setupBox(i, areaPos.left, areaPos.top, areaPos.right, areaPos.bottom);
+ }
+ boxes.setupBox(ARRAYSIZE(areas), 639 - _sprites->getCursor(105).getWidth(),
+ 479 - _sprites->getCursor(105).getHeight(), 640, 480);
+
+ Image::ImageDecoder *imageDecoder = _engine->loadHLZ("PLAN.HLZ");
+ if (!imageDecoder) {
+ return "";
+ }
+ const Graphics::Surface *bgFrame = imageDecoder->getSurface();
+
+ Graphics::ManagedSurface mapSurface;
+ mapSurface.create(bgFrame->w, bgFrame->h, bgFrame->format);
+ mapSurface.blitFrom(*bgFrame);
+
+ _fontManager->setSurface(&mapSurface);
+
+ _engine->setupPalette(imageDecoder->getPalette(), imageDecoder->getPaletteStartIndex(),
+ imageDecoder->getPaletteColorCount());
+
+ _engine->setCursor(181);
+ g_system->showMouse(true);
+
+ bool redraw = true;
+ unsigned int hoveredBox = -1;
+ unsigned int selectedBox = -1;
+
+ while (selectedBox == -1u) {
+ if (redraw) {
+ // Draw without worrying of already modified areas, that's handled when recomputing hoveredBox
+ if (hoveredBox != -1u) {
+ if (areas[hoveredBox].fillArea) {
+ Common::Rect rect(areas[hoveredBox].areaPos);
+ rect.bottom += 1; // fillRect needs to fill including the limit
+ rect.right += 1;
+ mapSurface.fillRect(rect, 243);
+ if (areas[hoveredBox].areaPos1.right) {
+ rect = Common::Rect(areas[hoveredBox].areaPos1);
+ rect.bottom += 1; // fillRect needs to fill including the limit
+ rect.right += 1;
+ mapSurface.fillRect(rect, 243);
+ }
+ if (areas[hoveredBox].areaPos2.right) {
+ rect = Common::Rect(areas[hoveredBox].areaPos2);
+ rect.bottom += 1; // fillRect needs to fill including the limit
+ rect.right += 1;
+ mapSurface.fillRect(rect, 243);
+ }
+ } else {
+ unsigned int middleX = (areas[hoveredBox].areaPos.left + areas[hoveredBox].areaPos.right) / 2;
+ unsigned int middleY = (areas[hoveredBox].areaPos.top + areas[hoveredBox].areaPos.bottom) / 2;
+ unsigned int spriteX = middleX - _sprites->getCursor(163).getWidth() / 2;
+ unsigned int spriteY = middleY - _sprites->getCursor(163).getHeight() / 2;
+ mapSurface.transBlitFrom(_sprites->getSurface(163), Common::Point(spriteX, spriteY),
+ _sprites->getKeyColor(163));
+ }
+ Common::Rect textRect(areas[hoveredBox].messagePos.x - 4,
+ areas[hoveredBox].messagePos.y,
+ areas[hoveredBox].messagePos.x + _fontManager->getStrWidth(areas[hoveredBox].message) + 5,
+ areas[hoveredBox].messagePos.y + _fontManager->getFontMaxHeight() + 5);
+ mapSurface.fillRect(textRect, 247);
+ _fontManager->setForeColor(strcmp(areas[hoveredBox].record, "planG") == 0 ? 243 : 241);
+ _fontManager->displayStr(areas[hoveredBox].messagePos.x, areas[hoveredBox].messagePos.y,
+ areas[hoveredBox].message);
+ }
+ mapSurface.transBlitFrom(_sprites->getSurface(105), boxes.getBoxOrigin(ARRAYSIZE(areas)),
+ _sprites->getKeyColor(105));
+ /*
+ // For debugging only
+ for(unsigned int i = 0; i < ARRAYSIZE(areas); i++) {
+ mapSurface.frameRect(areas[i].areaPos, 0);
+ if (areas[i].areaPos1.right) {
+ mapSurface.frameRect(areas[i].areaPos1, 0);
+ }
+ if (areas[i].areaPos2.right) {
+ mapSurface.frameRect(areas[i].areaPos2, 0);
+ }
+ }
+ */
+
+ g_system->copyRectToScreen(mapSurface.getPixels(), mapSurface.pitch, 0, 0,
+ mapSurface.w, mapSurface.h);
+
+ redraw = false;
+ }
+ g_system->updateScreen();
+
+ if (_engine->pollEvents()) {
+ Common::Point mouse = _engine->getMousePos();
+ if (!_engine->getCurrentMouseButton()) {
+ // Don't change highlighted icon when clicking
+ bool foundBox = false;
+ unsigned int oldHoveredBox = hoveredBox;
+ for (unsigned int i = 0; i < ARRAYSIZE(areas); i++) {
+ if (boxes.hitTest(i, mouse)) {
+ if (i != hoveredBox) {
+ hoveredBox = i;
+ redraw = true;
+ }
+ foundBox = true;
+ break;
+ }
+ }
+ if (!foundBox && hoveredBox != -1u) {
+ hoveredBox = -1;
+ redraw = true;
+ }
+ if (hoveredBox != oldHoveredBox && oldHoveredBox != -1u) {
+ // Restore original area
+ Common::Rect areaPos = areas[oldHoveredBox].areaPos;
+ if (areas[oldHoveredBox].areaPos2.right) {
+ areaPos.right = areas[oldHoveredBox].areaPos2.right;
+ areaPos.bottom = areas[oldHoveredBox].areaPos2.bottom;
+ }
+ areaPos.right += 1;
+ areaPos.bottom += 1;
+ mapSurface.blitFrom(*bgFrame, areaPos,
+ Common::Point(areaPos.left, areaPos.top));
+ Common::Rect textRect(areas[oldHoveredBox].messagePos.x - 4,
+ areas[oldHoveredBox].messagePos.y,
+ areas[oldHoveredBox].messagePos.x + _fontManager->getStrWidth(areas[oldHoveredBox].message) + 5,
+ areas[oldHoveredBox].messagePos.y + _fontManager->getFontMaxHeight() + 5);
+ mapSurface.blitFrom(*bgFrame, textRect,
+ Common::Point(textRect.left, textRect.top));
+ }
+ }
+ if (_engine->getDragStatus() == kDragStatus_Finished) {
+ if (hoveredBox != -1u && areas[hoveredBox].record) {
+ selectedBox = hoveredBox;
+ } else if (boxes.hitTest(ARRAYSIZE(areas), mouse)) {
+ selectedBox = ARRAYSIZE(areas);
+ }
+ }
+ if (_engine->checkKeysPressed(1, Common::KEYCODE_ESCAPE)) {
+ selectedBox = ARRAYSIZE(areas);
+ }
+ if (g_engine->shouldQuit()) {
+ selectedBox = ARRAYSIZE(areas);
+ }
+ }
+ }
+
+ g_system->showMouse(false);
+
+ delete imageDecoder;
+
+ if (selectedBox == ARRAYSIZE(areas)) {
+ return "";
+ } else {
+ return areas[selectedBox].record;
+ }
+}
+
+void Versailles_Documentation::inGamePrepareRecord(Graphics::ManagedSurface &surface,
+ MouseBoxes &boxes) {
+ _categoryStartRecord = "";
+ _categoryEndRecord = "";
+ _categoryTitle = "";
+ _currentLinks.clear();
+ _currentInTimeline = false;
+ _currentMapLayout = false;
+ _currentHasMap = false;
+
+ if (_currentRecord.hasPrefix("VS")) {
+ unsigned int id = atoi(_currentRecord.c_str() + 2);
+ if (id >= 16 && id <= 40) {
+ _currentMapLayout = true;
+ }
+ } else if (_currentRecord.hasPrefix("VT")) {
+ error("There shouldn't be the timeline in game");
+ }
+
+ boxes.reset();
+
+ setupRecordBoxes(false, boxes);
+
+ Common::String title, subtitle, caption;
+ Common::StringArray hyperlinks;
+ Common::String text = getRecordData(_currentRecord, title, subtitle, caption, hyperlinks);
+ convertHyperlinks(hyperlinks, _currentLinks);
+
+ drawRecordData(surface, text, title, subtitle, caption);
+ drawRecordBoxes(surface, false, boxes);
+}
+
+unsigned int Versailles_Documentation::inGameHandleRecord(Graphics::ManagedSurface &surface,
+ MouseBoxes &boxes, Common::String &nextRecord) {
+ _engine->setCursor(181);
+ g_system->showMouse(true);
+
+ unsigned int action = -1;
+
+ g_system->copyRectToScreen(surface.getPixels(), surface.pitch, 0, 0, surface.w, surface.h);
+
+ while (action == -1u) {
+ g_system->updateScreen();
+
+ if (_engine->pollEvents()) {
+ if (g_engine->shouldQuit()) {
+ // Fake the quit
+ action = 6;
+ }
+ Common::Point mouse = _engine->getMousePos();
+ if (_engine->getDragStatus() == kDragStatus_Pressed) {
+ if (boxes.hitTest(2, mouse) && _currentLinks.size()) {
+ Common::StringArray items;
+ for (Common::Array<LinkInfo>::const_iterator it = _currentLinks.begin(); it != _currentLinks.end();
+ it++) {
+ items.push_back(it->title);
+ }
+ Common::Rect iconRect = boxes.getBoxRect(2);
+ unsigned int selectedItem = handlePopupMenu(surface, Common::Point(iconRect.right, iconRect.top),
+ true, 20, items);
+ if (selectedItem != -1u) {
+ nextRecord = _currentLinks[selectedItem].record;
+ action = 2;
+ }
+ }
+ } else if (_engine->getDragStatus() == kDragStatus_Finished) {
+ if (boxes.hitTest(0, mouse)) {
+ // Back in history
+ action = 0;
+ } else if (boxes.hitTest(1, mouse)) {
+ // Quit
+ action = 1;
+ }
+ }
+ }
+ }
+
+ g_system->showMouse(false);
+ _engine->setCursor(181);
+ return action;
+}
+
+void Versailles_Documentation::drawRecordData(Graphics::ManagedSurface &surface,
+ const Common::String &text, const Common::String &title,
+ const Common::String &subtitle, const Common::String &caption) {
+ bool displayMap = false;
+ unsigned char foreColor = 247;
+ Common::String background;
+ Common::Rect blockTitle;
+ Common::Rect blockHLine;
+ Common::Rect blockSubTitle;
+ Common::Rect blockCaption;
+ Common::Rect blockContent1;
+ Common::Rect blockContent2;
+
+ if (_currentMapLayout) {
+ blockTitle = Common::Rect(30, 8, 361, 38);
+ blockHLine = Common::Rect(60, 35, 286, 35);
+ blockSubTitle = Common::Rect(60, 40, 361, 70);
+ blockCaption = Common::Rect(378, 293, 630, 344);
+ blockContent1 = Common::Rect(60, 60, 272, 295);
+ blockContent2 = Common::Rect(60, 295, 383, 437);
+ } else if (_currentInTimeline) {
+ blockTitle = Common::Rect(78, 10, 170, 33);
+ //blockHLine = Common::Rect();
+ blockSubTitle = Common::Rect(60, 40, 361, 70);
+ blockCaption = Common::Rect(378, 293, 630, 344);
+ blockContent1 = Common::Rect(47, 70, 420, 306);
+ blockContent2 = Common::Rect(174, 306, 414, 411);
+ } else if (_currentRecord == "VC02" ||
+ _currentRecord == "VC03" ||
+ _currentRecord == "VV01") {
+ blockTitle = Common::Rect(30, 8, 361, 38);
+ blockHLine = Common::Rect(60, 35, 378, 35);
+ blockSubTitle = Common::Rect(60, 40, 361, 70);
+ blockCaption = Common::Rect(378, 293, 630, 360);
+ blockContent1 = Common::Rect(60, 80, 351, 355);
+ blockContent2 = Common::Rect(60, 355, 605, 437);
+ } else if (_currentRecord == "VV13" ||
+ _currentRecord == "VV08") {
+ blockTitle = Common::Rect(30, 8, 361, 38);
+ blockHLine = Common::Rect(60, 35, 286, 35);
+ blockSubTitle = Common::Rect(60, 40, 361, 70);
+ blockCaption = Common::Rect(378, 422, 630, 480);
+ blockContent1 = Common::Rect(60, 60, 378, 285);
+ blockContent2 = Common::Rect(60, 285, 378, 437);
+ } else {
+ blockTitle = Common::Rect(30, 8, 361, 38);
+ blockHLine = Common::Rect(60, 35, 378, 35);
+ blockSubTitle = Common::Rect(60, 40, 361, 70);
+ blockCaption = Common::Rect(378, 293, 630, 360);
+ blockContent1 = Common::Rect(60, 80, 351, 345);
+ blockContent2 = Common::Rect(60, 345, 605, 437);
+ }
+ if (_currentInTimeline) {
+ background = "CHRONO1";
+ foreColor = 241;
+ } else {
+ background = _currentRecord;
+ }
+ background += ".HLZ";
+ Common::File backgroundFl;
+ if (!backgroundFl.open(background)) {
+ background = displayMap ? "pas_fonP.hlz" : "pas_fond.hlz";
+ } else {
+ backgroundFl.close();
+ }
+
+ Image::ImageDecoder *imageDecoder = _engine->loadHLZ(background);
+ const Graphics::Surface *bgFrame = imageDecoder->getSurface();
+
+ _engine->setupPalette(imageDecoder->getPalette(), imageDecoder->getPaletteStartIndex(),
+ imageDecoder->getPaletteColorCount());
+
+ surface.create(bgFrame->w, bgFrame->h, bgFrame->format);
+ surface.blitFrom(*bgFrame);
+
+ /*Common::String title, subtitle, caption;
+ Common::StringArray hyperlinks;
+
+ Common::String text = getRecordData(_currentRecord, title, subtitle, caption, hyperlinks);*/
+
+ unsigned int lineHeight = 21;
+ _fontManager->setCurrentFont(4);
+ _fontManager->setTransparentBackground(true);
+ _fontManager->setSpaceWidth(1);
+ _fontManager->setCharSpacing(1);
+ _fontManager->setForeColor(foreColor);
+ _fontManager->setSurface(&surface);
+
+ /*
+ surface.frameRect(blockContent1, foreColor);
+ surface.frameRect(blockContent2, foreColor);
+ surface.frameRect(blockTitle, foreColor);
+ surface.frameRect(blockSubTitle, foreColor);
+ surface.frameRect(blockCaption, foreColor);
+ */
+
+ Graphics::ManagedSurface backupSurface;
+ backupSurface.copyFrom(surface);
+
+ // This loop tries to adapt the interline space to make all the text fit in the blocks
+ while (true) {
+ _fontManager->setLineHeight(lineHeight);
+ _fontManager->setupBlock(blockContent1);
+ if (!_fontManager->displayBlockText(text)) {
+ // All text was drawn
+ break;
+ }
+
+ // Setup second zone
+ blockContent2.top = _fontManager->blockTextLastPos().y + lineHeight;
+ _fontManager->setupBlock(blockContent2);
+
+ if (!_fontManager->displayBlockText(text, _fontManager->blockTextRemaining())) {
+ // All text was drawn
+ break;
+ }
+
+ // Not all text could be drawn: shrink everything, restore image and do it again
+ lineHeight--;
+ surface.copyFrom(backupSurface);
+ }
+
+ _fontManager->setForeColor(foreColor);
+ _fontManager->setCurrentFont(0);
+ _fontManager->setTransparentBackground(true);
+ _fontManager->setLineHeight(20);
+ _fontManager->setCharSpacing(0);
+ _fontManager->setSpaceWidth(2);
+
+ //debug("Title: %s", title.c_str());
+ _fontManager->setupBlock(blockTitle);
+ _fontManager->displayBlockText(title);
+
+ _fontManager->setCurrentFont(6);
+ _fontManager->setLineHeight(14);
+ _fontManager->setSpaceWidth(1);
+
+ //debug("Subtitle: %s", subtitle.c_str());
+ _fontManager->setupBlock(blockSubTitle);
+ _fontManager->displayBlockText(subtitle);
+
+ if (!_currentInTimeline) {
+ surface.hLine(blockHLine.left, blockHLine.top, blockHLine.right - 1,
+ foreColor); // minus 1 because hLine draws inclusive
+ }
+
+ _fontManager->setSpaceWidth(0);
+
+ _fontManager->setupBlock(blockCaption);
+ _fontManager->displayBlockText(caption);
+}
+
+void Versailles_Documentation::setupRecordBoxes(bool inDocArea, MouseBoxes &boxes) {
+ // Layout of bar in doc area is Quit | Back | | Previous | Category | Next | | Trace | Hyperlinks | All records
+ // Layout of bar in game is ==> Trace | Hyperlinks | Quit
+ unsigned int allRecordsX = 640 - _sprites->getCursor(19).getWidth();
+ unsigned int hyperlinksX = allRecordsX - _sprites->getCursor(242).getWidth() - 10;
+ unsigned int traceX = hyperlinksX - _sprites->getCursor(105).getWidth() - 10;
+
+ if (_visitTrace.size()) {
+ boxes.setupBox(0, traceX, 480 - _sprites->getCursor(105).getHeight() - 3,
+ traceX + _sprites->getCursor(105).getWidth(), 480);
+ }
+ if (inDocArea) {
+ unsigned int backX = _sprites->getCursor(225).getWidth() + 10; //Right to quit button
+
+ _fontManager->setCurrentFont(0);
+ _fontManager->setTransparentBackground(true);
+ _fontManager->setSpaceWidth(0);
+ _fontManager->setCharSpacing(1);
+ unsigned int categoryHalfWidth = _fontManager->getStrWidth(_categoryTitle) / 2;
+ unsigned nextX = 320 + categoryHalfWidth + 20;
+ unsigned prevX = 320 - categoryHalfWidth - 20 - _sprites->getCursor(76).getWidth();
+
+ boxes.setupBox(3, allRecordsX, 480 - _sprites->getCursor(19).getHeight(),
+ allRecordsX + _sprites->getCursor(19).getWidth(), 480);
+ boxes.setupBox(1, backX, 480 - _sprites->getCursor(227).getHeight(),
+ backX + _sprites->getCursor(227).getWidth(), 480);
+ boxes.setupBox(9, 320 - categoryHalfWidth - 5, 480 - _sprites->getCursor(227).getHeight(),
+ 320 + categoryHalfWidth + 5, 480);
+ boxes.setupBox(4, nextX, 476 - _sprites->getCursor(72).getHeight(),
+ nextX + _sprites->getCursor(72).getWidth(), 476);
+ boxes.setupBox(5, prevX, 476 - _sprites->getCursor(76).getHeight(),
+ prevX + _sprites->getCursor(76).getWidth(), 476);
+ // Quit button
+ boxes.setupBox(6, 0, 480 - _sprites->getCursor(225).getHeight(),
+ _sprites->getCursor(225).getWidth(), 480);
+ // Map
+ boxes.setupBox(8, 403, 305, 622, 428);
+ if (_currentInTimeline) {
+ for (unsigned int box_id = 0; box_id < ARRAYSIZE(kTimelineEntries); box_id++) {
+ boxes.setupBox(10 + box_id, kTimelineEntries[box_id].x, kTimelineEntries[box_id].y,
+ kTimelineEntries[box_id].x + 30, kTimelineEntries[box_id].y + 15, kTimelineEntries[box_id].year);
+ }
+ }
+ } else {
+ unsigned int quitInGameX = 640 - _sprites->getCursor(105).getWidth();
+ boxes.setupBox(1, quitInGameX, 480 - _sprites->getCursor(105).getHeight(),
+ quitInGameX + _sprites->getCursor(105).getWidth(), 480);
+ }
+ boxes.setupBox(2, hyperlinksX, 480 - _sprites->getCursor(242).getHeight(),
+ hyperlinksX + _sprites->getCursor(242).getWidth(), 480);
+}
+
+void Versailles_Documentation::drawRecordBoxes(Graphics::ManagedSurface &surface, bool inDocArea,
+ MouseBoxes &boxes) {
+ if (_visitTrace.size()) {
+ surface.transBlitFrom(_sprites->getSurface(105), boxes.getBoxOrigin(0), _sprites->getKeyColor(105));
+ }
+ if (inDocArea) {
+ surface.transBlitFrom(_sprites->getSurface(19), boxes.getBoxOrigin(3), _sprites->getKeyColor(19));
+ surface.transBlitFrom(_sprites->getSurface(227), boxes.getBoxOrigin(1), _sprites->getKeyColor(227));
+
+ surface.fillRect(boxes.getBoxRect(9), 243);
+
+ _fontManager->setCurrentFont(0);
+ _fontManager->setTransparentBackground(true);
+ _fontManager->setSpaceWidth(0);
+ _fontManager->setCharSpacing(1);
+ _fontManager->setForeColor(240);
+ Common::Point catPos = boxes.getBoxOrigin(9);
+ catPos += Common::Point(5, 3);
+ _fontManager->displayStr(catPos.x, catPos.y, _categoryTitle);
+
+ if (_currentRecord == _categoryEndRecord) {
+ surface.transBlitFrom(_sprites->getSurface(75), boxes.getBoxOrigin(4), _sprites->getKeyColor(75));
+ } else {
+ surface.transBlitFrom(_sprites->getSurface(72), boxes.getBoxOrigin(4), _sprites->getKeyColor(72));
+ }
+ if (_currentRecord == _categoryStartRecord) {
+ surface.transBlitFrom(_sprites->getSurface(77), boxes.getBoxOrigin(5), _sprites->getKeyColor(77));
+ } else {
+ surface.transBlitFrom(_sprites->getSurface(76), boxes.getBoxOrigin(5), _sprites->getKeyColor(76));
+ }
+ surface.transBlitFrom(_sprites->getSurface(225), boxes.getBoxOrigin(6), _sprites->getKeyColor(225));
+ } else {
+ surface.transBlitFrom(_sprites->getSurface(105), boxes.getBoxOrigin(1), _sprites->getKeyColor(105));
+ }
+ if (_currentLinks.size()) {
+ surface.transBlitFrom(_sprites->getSurface(242), boxes.getBoxOrigin(2), _sprites->getKeyColor(242));
+ } else {
+ surface.transBlitFrom(_sprites->getSurface(244), boxes.getBoxOrigin(2), _sprites->getKeyColor(244));
+ }
+}
+
+unsigned int Versailles_Documentation::handlePopupMenu(const Graphics::ManagedSurface
+ &originalSurface,
+ const Common::Point &anchor, bool rightAligned, unsigned int itemHeight,
+ const Common::StringArray &items) {
+
+ unsigned int maxTextWidth = 0;
+
+ _fontManager->setCurrentFont(4);
+ _fontManager->setTransparentBackground(true);
+ _fontManager->setCharSpacing(1);
+
+ for (Common::StringArray::const_iterator it = items.begin(); it != items.end(); it++) {
+ unsigned int width = _fontManager->getStrWidth(*it);
+ if (width > maxTextWidth) {
+ maxTextWidth = width;
+ }
+ }
+
+ unsigned int width = maxTextWidth + 2 * kPopupMenuMargin;
+ unsigned int height = itemHeight * items.size() + 2 * kPopupMenuMargin;
+
+ unsigned int hiddenItems = 0;
+ int top = anchor.y - height;
+ while (top < 0) {
+ hiddenItems++;
+ top += itemHeight;
+ }
+ unsigned shownItems = items.size() - hiddenItems;
+
+ Common::Rect popupRect;
+ if (rightAligned) {
+ popupRect = Common::Rect(anchor.x - width, top, anchor.x, anchor.y);
+ } else {
+ popupRect = Common::Rect(anchor.x, top, anchor.x + width, anchor.y);
+ }
+
+ Graphics::ManagedSurface surface;
+ surface.copyFrom(originalSurface);
+
+ MouseBoxes boxes(shownItems);
+ for (unsigned int i = 0; i < shownItems; i++) {
+ boxes.setupBox(i, popupRect.left + kPopupMenuMargin,
+ popupRect.top + kPopupMenuMargin + i * itemHeight,
+ popupRect.right - kPopupMenuMargin,
+ popupRect.top + kPopupMenuMargin + (i + 1) * itemHeight);
+ }
+
+ _fontManager->setSurface(&surface);
+
+ bool fullRedraw = true;
+ bool redraw = true;
+ unsigned int hoveredBox = -1;
+ unsigned int action = -1;
+ unsigned int lastShownItem = items.size() - 1;
+ unsigned int firstShownItem = lastShownItem - shownItems + 1;
+
+ unsigned int slowScrollNextEvent = g_system->getMillis() + 250;
+
+ Common::Point mouse;
+
+ while (action == -1u) {
+ if (redraw) {
+ if (fullRedraw) {
+ surface.fillRect(popupRect, 247);
+ fullRedraw = false;
+ }
+ for (unsigned int i = 0; i < shownItems; i++) {
+ if (i == 0 && firstShownItem != 0) {
+ // There are items before the first one: display an arrow
+ surface.transBlitFrom(_sprites->getSurface(162),
+ Common::Point(popupRect.left + kPopupMenuMargin,
+ popupRect.top + kPopupMenuMargin + i * itemHeight + 3),
+ _sprites->getKeyColor(162));
+ } else if (i == shownItems - 1 && lastShownItem != items.size() - 1) {
+ // There are items after the last one: display an arrow
+ surface.transBlitFrom(_sprites->getSurface(185),
+ Common::Point(popupRect.left + kPopupMenuMargin,
+ popupRect.top + kPopupMenuMargin + i * itemHeight + 3),
+ _sprites->getKeyColor(185));
+ } else {
+ // Display the item text
+ _fontManager->setForeColor(i == hoveredBox ? 241 : 243);
+ _fontManager->displayStr(popupRect.left + kPopupMenuMargin,
+ popupRect.top + kPopupMenuMargin + i * itemHeight + 3, items[firstShownItem + i]);
+ }
+ }
+ g_system->copyRectToScreen(surface.getPixels(), surface.pitch, 0, 0, surface.w, surface.h);
+ redraw = false;
+ }
+ g_system->updateScreen();
+
+ if (_engine->pollEvents()) {
+ if (g_engine->shouldQuit()) {
+ // Fake the quit
+ break;
+ }
+ mouse = _engine->getMousePos();
+
+ unsigned int newHovered = -1;
+ for (unsigned int i = 0; i < shownItems; i++) {
+ if (boxes.hitTest(i, mouse)) {
+ newHovered = i;
+ break;
+ }
+ }
+
+ if (newHovered != hoveredBox) {
+ hoveredBox = newHovered;
+ redraw = true;
+ }
+ }
+
+ DragStatus dragStatus = _engine->getDragStatus();
+
+ if (hoveredBox == -1u) {
+ if (dragStatus == kDragStatus_Pressed) {
+ break;
+ } else {
+ continue;
+ }
+ }
+
+ // From there we only act if there is something hovered
+ if (hoveredBox == 0 && firstShownItem > 0) {
+ // Scroll up fast
+ firstShownItem--;
+ lastShownItem--;
+ slowScrollNextEvent = g_system->getMillis() + 250;
+ fullRedraw = true;
+ redraw = true;
+ } else if (hoveredBox == 1 && firstShownItem > 0) {
+ // Scroll up slow
+ if (g_system->getMillis() > slowScrollNextEvent) {
+ firstShownItem--;
+ lastShownItem--;
+ slowScrollNextEvent = g_system->getMillis() + 250;
+ fullRedraw = true;
+ redraw = true;
+ }
+ } else if (hoveredBox == shownItems - 2 && lastShownItem < items.size() - 1) {
+ // Scroll down slow
+ if (g_system->getMillis() > slowScrollNextEvent) {
+ firstShownItem++;
+ lastShownItem++;
+ slowScrollNextEvent = g_system->getMillis() + 250;
+ fullRedraw = true;
+ redraw = true;
+ }
+ } else if (hoveredBox == shownItems - 1 && lastShownItem < items.size() - 1) {
+ // Scroll down fast
+ firstShownItem++;
+ lastShownItem++;
+ slowScrollNextEvent = g_system->getMillis() + 250;
+ fullRedraw = true;
+ redraw = true;
+ } else if (dragStatus == kDragStatus_Finished) {
+ action = hoveredBox + firstShownItem;
+ continue;
+ }
+ }
+
+ // Restore original surface
+ g_system->copyRectToScreen(originalSurface.getPixels(), originalSurface.pitch, 0, 0,
+ originalSurface.w, originalSurface.h);
+ g_system->updateScreen();
+
+ _engine->waitMouseRelease();
+
+ return action;
+}
+
+/* Below is documentation files parsing */
+
+char *Versailles_Documentation::getDocPartAddress(char *start, char *end, const char *patterns[]) {
+ if (!start) {
+ return nullptr;
+ }
+ char *foundPos = nullptr;
+ const char *pattern;
+ unsigned int patternLen;
+ for (const char **patternP = patterns; *patternP && !foundPos; patternP++) {
+ pattern = *patternP;
+ patternLen = strlen(pattern);
+ /*debug("Matching %.10s", pattern);*/
+ for (char *p = start; p < end - patternLen - 1; p++) {
+ /*if (p == start || *p == '\r' || *p == '\0') {
+ debug("Line %.10s", p == start ? start : p+1);
+ }*/
+ if (p == start && !strncmp(p, pattern, patternLen)) {
+ foundPos = p;
+ break;
+ } else if ((*p == '\r' || *p == '\0') && !strncmp(p + 1, pattern, patternLen)) {
+ foundPos = p + 1;
+ break;
+ }
+ }
+ }
+ if (!foundPos) {
+ return nullptr;
+ }
+ /*debug("Matched %.10s", foundPos);*/
+ foundPos += patternLen;
+ char *eol = foundPos;
+ for (; *eol != '\r' && *eol != '\0'; eol++) {}
+ *eol = '\0';
+ return foundPos;
+}
+
+static bool hasEqualInLine(const char *text, const char *end) {
+ for (; text < end && *text && *text != '\r' && *text != '='; text++) { }
+ return text < end && *text == '=';
+}
+
+static const char *nextLine(const char *text, const char *end) {
+ for (; text < end && *text && *text != '\r'; text++) { }
+ return text < end ? text + 1 : end;
+}
+
+const char *Versailles_Documentation::getDocTextAddress(char *start, char *end) {
+ if (!start) {
+ return nullptr;
+ }
+ const char *foundPos = nullptr;
+ const char *p = start;
+ while (p < end) {
+ if (hasEqualInLine(p, end)) {
+ p = nextLine(p, end);
+ if (p < end && !hasEqualInLine(p, end)) {
+ // Only return the text that is after the last =
+ foundPos = p;
+ }
+ } else {
+ p = nextLine(p, end);
+ }
+ }
+ return foundPos;
+}
+
+const char *Versailles_Documentation::getRecordCaption(char *start, char *end) {
+ const char *patterns[] = { "LEGENDE=", "LEGENDE =", nullptr };
+ const char *ret = getDocPartAddress(start, end, patterns);
+ return ret;
+}
+
+const char *Versailles_Documentation::getRecordTitle(char *start, char *end) {
+ const char *patterns[] = { "TITRE=", "TITRE =", nullptr };
+ const char *ret = getDocPartAddress(start, end, patterns);
+ return ret;
+}
+
+const char *Versailles_Documentation::getRecordSubtitle(char *start, char *end) {
+ const char *patterns[] = { "SOUS-TITRE=", "SOUS_TITRE=", "SOUS-TITRE =", "SOUS_TITRE =", "SOUS TITRE=", nullptr };
+ char *ret = getDocPartAddress(start, end, patterns);
+ if (!ret) {
+ return nullptr;
+ }
+
+ unsigned int ln = strlen(ret);
+ char *p = ret + ln + 1; // Got to end of line and check next line
+ for (; p < end && *p && *p != '\r' && *p != '=' ; p++) { }
+ if (*p == '=') {
+ // Next line has a =, so it's not multiline
+ return ret;
+ }
+
+ if (*p == '\r') {
+ *p = '\0';
+ }
+ ret[ln] = '\r';
+
+ return ret;
+}
+
+void Versailles_Documentation::getRecordHyperlinks(char *start, char *end,
+ Common::StringArray &hyperlinks) {
+ const char *const hyperlinksPatterns[] = { "SAVOIR-PLUS 1=", "SAVOIR-PLUS 2=", "SAVOIR-PLUS 3=" };
+
+ hyperlinks.clear();
+ for (unsigned int hyperlinkId = 0; hyperlinkId < ARRAYSIZE(hyperlinksPatterns); hyperlinkId++) {
+ const char *patterns[] = { hyperlinksPatterns[hyperlinkId], nullptr };
+ const char *ret = getDocPartAddress(start, end, patterns);
+ if (ret) {
+ hyperlinks.push_back(ret);
+ }
+ }
+}
+
+Common::String Versailles_Documentation::getRecordTitle(const Common::String &record) {
+ Common::HashMap<Common::String, RecordInfo>::iterator it = _records.find(record);
+ if (it == _records.end()) {
+ return "";
+ }
+
+ const RecordInfo &recordInfo = it->_value;
+ Common::File allDocsFile;
+
+ if (!allDocsFile.open(kAllDocsFile)) {
+ error("Can't open %s", kAllDocsFile);
+ }
+ allDocsFile.seek(recordInfo.position);
+
+ char *recordData = new char[recordInfo.size + 1];
+ allDocsFile.read(recordData, recordInfo.size);
+ recordData[recordInfo.size] = '\0';
+ char *recordDataEnd = recordData + recordInfo.size + 1;
+
+ Common::String title = getRecordTitle(recordData, recordDataEnd);
+
+ delete[] recordData;
+
+ return title;
+}
+
+Common::String Versailles_Documentation::getRecordData(const Common::String &record,
+ Common::String &title, Common::String &subtitle, Common::String &caption,
+ Common::StringArray &hyperlinks) {
+ Common::HashMap<Common::String, RecordInfo>::iterator it = _records.find(record);
+ if (it == _records.end()) {
+ warning("Can't find %s record data", record.c_str());
+ return "";
+ }
+
+ const RecordInfo &recordInfo = it->_value;
+ Common::File allDocsFile;
+
+ if (!allDocsFile.open(kAllDocsFile)) {
+ error("Can't open %s", kAllDocsFile);
+ }
+ allDocsFile.seek(recordInfo.position);
+
+ char *recordData = new char[recordInfo.size + 1];
+ allDocsFile.read(recordData, recordInfo.size);
+ recordData[recordInfo.size] = '\0';
+ char *recordDataEnd = recordData + recordInfo.size + 1;
+
+ const char *titleP = getRecordTitle(recordData, recordDataEnd);
+ /*debug("Title: %s", titleP);*/
+ title = titleP ? titleP : "";
+ const char *subtitleP = getRecordSubtitle(recordData, recordDataEnd);
+ /*debug("SubTitle: %s", subtitleP);*/
+ subtitle = subtitleP ? subtitleP : "";
+ const char *captionP = getRecordCaption(recordData, recordDataEnd);
+ /*debug("Caption: %s", captionP);*/
+ caption = captionP ? captionP : "";
+ getRecordHyperlinks(recordData, recordDataEnd, hyperlinks);
+
+ Common::String text(getDocTextAddress(recordData, recordDataEnd));
+
+ delete[] recordData;
+
+ return text;
+}
+
+void Versailles_Documentation::convertHyperlinks(const Common::StringArray &hyperlinks,
+ Common::Array<LinkInfo> &links) {
+ for (Common::StringArray::const_iterator it = hyperlinks.begin(); it != hyperlinks.end(); it++) {
+ LinkInfo link;
+ link.record = *it;
+ link.record.toUppercase();
+ link.title = getRecordTitle(link.record);
+ links.push_back(link);
+ }
+}
+
+void Versailles_Documentation::loadLinksFile() {
+ if (_linksData) {
+ return;
+ }
+
+ Common::File linksFile;
+ if (!linksFile.open(kLinksDocsFile)) {
+ error("Can't open links file: %s", kLinksDocsFile);
+ }
+
+ _linksSize = linksFile.size();
+ _linksData = new char[_linksSize + 1];
+
+ linksFile.read(_linksData, _linksSize);
+ _linksData[_linksSize] = '\0';
+}
+
+void Versailles_Documentation::getLinks(const Common::String &record,
+ Common::Array<LinkInfo> &links) {
+ loadLinksFile();
+
+ links.clear();
+
+ Common::String pattern = "\r";
+ pattern += record;
+
+ const char *recordStart = strstr(_linksData, pattern.c_str());
+ if (!recordStart) {
+ return;
+ }
+
+ const char *p = recordStart + pattern.size(); // Go beyond the record name
+ for (; *p != '\r' && *p != '\0'; p++) { } // Goto next line
+ if (!*p) {
+ return;
+ }
+ p++;
+
+ bool finished = false;
+ while (!finished) {
+ if (!scumm_strnicmp(p, "REM=", 4)) {
+ // Comment: goto next line
+ for (; *p != '\r' && *p != '\0'; p++) { }
+ } else if (!scumm_strnicmp(p, "LIEN=", 5)) {
+ // Link: read it
+ const char *linkStart = p + 5;
+ const char *linkEnd = linkStart;
+ for (; *linkEnd != '\r' && *linkEnd != ' ' && *linkEnd != '\0'; linkEnd++) { }
+ LinkInfo link;
+ link.record = Common::String(linkStart, linkEnd);
+ link.record.toUppercase();
+ link.title = getRecordTitle(link.record);
+ links.push_back(link);
+ // Advance to end of link and finish the line
+ p = linkEnd;
+ for (; *p != '\r' && *p != '\0'; p++) { }
+ //debug("Link %s/%s", link.record.c_str(), link.title.c_str());
+ } else {
+ // Something else: we expect a blank line to continue, else we are on a new record
+ for (; *p != '\r' && *p != '\0'; p++) {
+ if (*p != ' ' && *p != '\t' && *p != '\n') {
+ finished = true;
+ break;
+ }
+ }
+ }
+ if (*p == '\0') {
+ break;
+ }
+ p++;
+ }
+}
+
+} // End of namespace Versailles
+} // End of namespace CryOmni3D
diff --git a/engines/cryomni3d/versailles/documentation.h b/engines/cryomni3d/versailles/documentation.h
new file mode 100644
index 0000000000..0a581d9822
--- /dev/null
+++ b/engines/cryomni3d/versailles/documentation.h
@@ -0,0 +1,143 @@
+/* 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 CRYOMNI3D_VERSAILLES_DOCUMENTATION_H
+#define CRYOMNI3D_VERSAILLES_DOCUMENTATION_H
+
+#include "common/hashmap.h"
+#include "common/hash-str.h"
+#include "common/rect.h"
+#include "common/str-array.h"
+#include "graphics/managed_surface.h"
+
+namespace CryOmni3D {
+class FontManager;
+class MouseBoxes;
+class Sprites;
+
+class CryOmni3DEngine;
+
+namespace Versailles {
+class Versailles_Documentation {
+public:
+ Versailles_Documentation() : _engine(nullptr), _fontManager(nullptr), _messages(nullptr),
+ _linksData(nullptr), _linksSize(0) { }
+ ~Versailles_Documentation() { delete _linksData; }
+
+ void init(const Sprites *sprites, FontManager *fontManager, const Common::StringArray *messages,
+ CryOmni3DEngine *engine);
+ void handleDocArea();
+ void handleDocInGame(const Common::String &record);
+
+private:
+ Common::String docAreaHandleSummary();
+ Common::String docAreaHandleTimeline();
+ Common::String docAreaHandleGeneralMap();
+ Common::String docAreaHandleCastleMap();
+ unsigned int docAreaHandleRecords(const Common::String &record);
+
+ void docAreaPrepareNavigation();
+ void docAreaPrepareRecord(Graphics::ManagedSurface &surface, MouseBoxes &boxes);
+ unsigned int docAreaHandleRecord(Graphics::ManagedSurface &surface, MouseBoxes &boxes,
+ Common::String &nextRecord);
+
+ void inGamePrepareRecord(Graphics::ManagedSurface &surface, MouseBoxes &boxes);
+ unsigned int inGameHandleRecord(Graphics::ManagedSurface &surface, MouseBoxes &boxes,
+ Common::String &nextRecord);
+
+ void setupRecordBoxes(bool inDocArea, MouseBoxes &boxes);
+ void setupTimelineBoxes(MouseBoxes &boxes);
+ void drawRecordData(Graphics::ManagedSurface &surface,
+ const Common::String &text, const Common::String &title,
+ const Common::String &subtitle, const Common::String &caption);
+ void drawRecordBoxes(Graphics::ManagedSurface &surface, bool inDocArea, MouseBoxes &boxes);
+
+ unsigned int handlePopupMenu(const Graphics::ManagedSurface &surface,
+ const Common::Point &anchor, bool rightAligned, unsigned int itemHeight,
+ const Common::StringArray &items);
+
+ struct RecordInfo {
+ unsigned int id;
+ unsigned int position;
+ unsigned int size;
+ };
+
+ struct LinkInfo {
+ Common::String record;
+ Common::String title;
+ };
+
+ struct TimelineEntry {
+ char year[8];
+ unsigned int x;
+ unsigned int y;
+ };
+ static const TimelineEntry kTimelineEntries[];
+
+ static char *getDocPartAddress(char *start, char *end, const char *patterns[]);
+ static const char *getDocTextAddress(char *start, char *end);
+ static const char *getRecordTitle(char *start, char *end);
+ static const char *getRecordSubtitle(char *start, char *end);
+ static const char *getRecordCaption(char *start, char *end);
+ static void getRecordHyperlinks(char *start, char *end, Common::StringArray &hyperlinks);
+
+ Common::String getRecordTitle(const Common::String &record);
+ Common::String getRecordData(const Common::String &record, Common::String &title,
+ Common::String &subtitle, Common::String &caption,
+ Common::StringArray &hyperlinks);
+ void convertHyperlinks(const Common::StringArray &hyperlinks, Common::Array<LinkInfo> &links);
+
+ void loadLinksFile();
+ void getLinks(const Common::String &record, Common::Array<LinkInfo> &links);
+
+ static const char *kAllDocsFile;
+ static const char *kLinksDocsFile;
+
+ static const unsigned int kPopupMenuMargin = 5;
+
+ CryOmni3DEngine *_engine;
+ FontManager *_fontManager;
+ const Sprites *_sprites;
+ const Common::StringArray *_messages;
+
+ Common::StringArray _recordsOrdered;
+ Common::HashMap<Common::String, RecordInfo> _records;
+ char *_linksData;
+ unsigned int _linksSize;
+
+ Common::Array<LinkInfo> _allLinks;
+
+ Common::StringArray _visitTrace;
+ Common::String _currentRecord;
+ Common::String _categoryStartRecord;
+ Common::String _categoryEndRecord;
+ Common::String _categoryTitle;
+ Common::Array<LinkInfo> _currentLinks;
+ bool _currentInTimeline;
+ bool _currentMapLayout;
+ bool _currentHasMap;
+};
+
+} // End of namespace Versailles
+} // End of namespace CryOmni3D
+
+#endif
diff --git a/engines/cryomni3d/versailles/engine.cpp b/engines/cryomni3d/versailles/engine.cpp
new file mode 100644
index 0000000000..b25efc7719
--- /dev/null
+++ b/engines/cryomni3d/versailles/engine.cpp
@@ -0,0 +1,1443 @@
+/* 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/config-manager.h"
+#include "common/system.h"
+#include "common/error.h"
+#include "common/file.h"
+#include "common/rect.h"
+#include "engines/util.h"
+#include "image/bmp.h"
+
+#include "cryomni3d/font_manager.h"
+#include "cryomni3d/dialogs_manager.h"
+#include "cryomni3d/fixed_image.h"
+#include "cryomni3d/omni3d.h"
+
+#include "cryomni3d/versailles/engine.h"
+
+#define DEBUG_FAST_START 1
+
+namespace CryOmni3D {
+namespace Versailles {
+
+const FixedImageConfiguration CryOmni3DEngine_Versailles::kFixedImageConfiguration = {
+ 45, 223, 243, 238, 226, 198, 136, 145, 99, 113,
+ 470
+};
+
+CryOmni3DEngine_Versailles::CryOmni3DEngine_Versailles(OSystem *syst,
+ const CryOmni3DGameDescription *gamedesc) : CryOmni3DEngine(syst, gamedesc),
+ _mainPalette(nullptr), _cursorPalette(nullptr), _transparentPaletteMap(nullptr),
+ _currentPlace(nullptr), _currentWarpImage(nullptr), _fixedImage(nullptr),
+ _transitionAnimateWarp(true), _forceRedrawWarp(false), _forcePaletteUpdate(false),
+ _fadedPalette(false), _loadedSave(-1), _dialogsMan(this),
+ _musicVolumeFactor(1.), _musicCurrentFile(nullptr) {
+}
+
+CryOmni3DEngine_Versailles::~CryOmni3DEngine_Versailles() {
+ delete _currentWarpImage;
+ delete[] _mainPalette;
+ delete[] _cursorPalette;
+ delete[] _transparentPaletteMap;
+
+ delete _fixedImage;
+}
+
+Common::Error CryOmni3DEngine_Versailles::run() {
+ CryOmni3DEngine::run();
+
+ const Common::FSNode gameDataDir(ConfMan.get("path"));
+ SearchMan.addSubDirectoryMatching(gameDataDir, "datas_v/sc_trans", 1);
+ SearchMan.addSubDirectoryMatching(gameDataDir, "datas_v/menu", 1);
+ SearchMan.addSubDirectoryMatching(gameDataDir, "datas_v/basedoc/fonds", 1);
+ SearchMan.addSubDirectoryMatching(gameDataDir, "datas_v/fonts", 1);
+ SearchMan.addSubDirectoryMatching(gameDataDir, "datas_v/spr8col", 1);
+ SearchMan.addSubDirectoryMatching(gameDataDir, "datas_v/spr8col/bmpok", 1);
+ SearchMan.addSubDirectoryMatching(gameDataDir, "datas_v/wam", 1);
+ SearchMan.addSubDirectoryMatching(gameDataDir, "datas_v/objets", 1);
+ SearchMan.addSubDirectoryMatching(gameDataDir, "datas_v/gto", 1);
+ SearchMan.addSubDirectoryMatching(gameDataDir, "datas_v/dial/flc_dial", 1);
+ SearchMan.addSubDirectoryMatching(gameDataDir, "datas_v/dial/voix", 1);
+ SearchMan.addSubDirectoryMatching(gameDataDir, "datas_v/textes", 1);
+ SearchMan.addSubDirectoryMatching(gameDataDir, "datas_v/music", 1);
+ SearchMan.addSubDirectoryMatching(gameDataDir, "datas_v/sound", 1);
+
+ setupMessages();
+
+ _dialogsMan.init(138, _messages[22]);
+ _gameVariables.resize(GameVariables::kMax);
+ _omni3dMan.init(75. / 180. * M_PI);
+
+ _dialogsMan.loadGTO("DIALOG1.GTO");
+ setupDialogVariables();
+ setupDialogShows();
+
+ setupPaintingsTitles();
+ setupImgScripts();
+
+ _mainPalette = new byte[3 * 256];
+ setupFonts();
+ setupSprites();
+ loadCursorsPalette();
+
+ // Objects need messages and sprites
+ setupObjects();
+
+ _transparentPaletteMap = new byte[256];
+ _transparentSrcStart = 0;
+ _transparentSrcStop = 240;
+ _transparentDstStart = 0;
+ _transparentDstStop = 248;
+ _transparentNewStart = 248;
+ _transparentNewStop = 254;
+
+ // Inventory has a size of 50
+ _inventory.init(50, new Common::Functor1Mem<unsigned int, void, Toolbar>(&_toolbar,
+ &Toolbar::inventoryChanged));
+
+ // Init toolbar after we have setup sprites and fonts
+ _toolbar.init(&_sprites, &_fontManager, &_messages, &_inventory, this);
+
+ _fixedImage = new ZonFixedImage(*this, _inventory, _sprites, &kFixedImageConfiguration);
+
+ // Documentation is needed by noone at init time, let's do it last
+ initDocPeopleRecord();
+ _docManager.init(&_sprites, &_fontManager, &_messages, this);
+
+ initGraphics(640, 480);
+ setMousePos(Common::Point(320, 200));
+
+ _isPlaying = false;
+ _isVisiting = false;
+
+#if !defined(DEBUG_FAST_START) || DEBUG_FAST_START<1
+ playTransitionEndLevel(-2);
+ if (g_engine->shouldQuit()) {
+ return Common::kNoError;
+ }
+ playTransitionEndLevel(-1);
+ if (g_engine->shouldQuit()) {
+ return Common::kNoError;
+ }
+#endif
+
+ bool stopGame = false;
+ while (!stopGame) {
+ bool exitLoop = false;
+ unsigned int nextStep = 0;
+#if defined(DEBUG_FAST_START) && DEBUG_FAST_START>=2
+ nextStep = 27;
+ // Called in options
+ syncOmni3DSettings();
+#endif
+ setCursor(181);
+ while (!exitLoop) {
+ _isPlaying = false;
+ if (!nextStep) {
+ nextStep = displayOptions();
+ }
+ if (nextStep == 40) {
+ // Quit action
+ exitLoop = true;
+ stopGame = true;
+ } else if (nextStep == 27 || nextStep == 28 || nextStep == 65) {
+ // New game, Load game, Next level
+ if (nextStep == 27) {
+ // New game
+#if !defined(DEBUG_FAST_START) || DEBUG_FAST_START<1
+ playTransitionEndLevel(0);
+ if (g_engine->shouldQuit()) {
+ stopGame = true;
+ exitLoop = true;
+ break;
+ }
+#endif
+ changeLevel(1);
+ } else if (nextStep == 28) {
+ // Load game
+ loadGame(_isVisiting, _loadedSave);
+ } else if (nextStep == 65) {
+ changeLevel(_currentLevel + 1);
+ }
+
+ _isPlaying = true;
+ _toolbar.setInventoryEnabled(!_isVisiting);
+ nextStep = 0;
+ _abortCommand = AbortNoAbort;
+
+ gameStep();
+
+ // We shouldn't return from gameStep without an abort command
+ assert(_abortCommand != AbortNoAbort);
+ switch (_abortCommand) {
+ default:
+ error("Invalid abortCommand: %d", _abortCommand);
+ // Shouldn't return
+ return Common::kUnknownError;
+ case AbortFinished:
+ case AbortGameOver:
+ // Go back to menu
+ exitLoop = true;
+ break;
+ case AbortQuit:
+ exitLoop = true;
+ stopGame = true;
+ break;
+ case AbortNewGame:
+ nextStep = 27;
+ break;
+ case AbortLoadGame:
+ nextStep = 28;
+ break;
+ case AbortNextLevel:
+ nextStep = 65;
+ break;
+ }
+ }
+ }
+ }
+ return Common::kNoError;
+}
+
+void CryOmni3DEngine_Versailles::setupFonts() {
+ Common::Array<Common::String> fonts;
+ fonts.push_back("font01.CRF");
+ fonts.push_back("font02.CRF");
+ fonts.push_back("font03.CRF");
+ fonts.push_back("font04.CRF");
+ fonts.push_back("font05.CRF");
+ fonts.push_back("font06.CRF");
+ fonts.push_back("font07.CRF");
+ fonts.push_back("font08.CRF");
+ fonts.push_back("font09.CRF");
+ fonts.push_back("font10.CRF");
+ fonts.push_back("font11.CRF");
+
+ _fontManager.loadFonts(fonts);
+}
+
+void CryOmni3DEngine_Versailles::setupSprites() {
+ Common::File file;
+
+ if (!file.open("all_spr.bin")) {
+ error("Failed to open all_spr.bin file");
+ }
+ _sprites.loadSprites(file);
+
+ for (unsigned int i = 0; i < _sprites.getSpritesCount(); i++) {
+ const Graphics::Cursor &cursor = _sprites.getCursor(i);
+ if (cursor.getWidth() != 32 || cursor.getHeight() != 32) {
+ _sprites.setSpriteHotspot(i, 8, 8);
+ } else {
+ _sprites.setSpriteHotspot(i, 16, 16);
+ }
+ }
+ _sprites.setupMapTable(kSpritesMapTable, kSpritesMapTableSize);
+
+
+ _sprites.setSpriteHotspot(181, 4, 0);
+ // Replace 2-keys, 3-keys and 4-keys icons with 1-key ones
+ _sprites.replaceSprite(80, 64);
+ _sprites.replaceSprite(84, 66);
+ _sprites.replaceSprite(93, 78);
+ _sprites.replaceSprite(97, 82);
+ _sprites.replaceSprite(92, 64);
+ _sprites.replaceSprite(96, 66);
+ _sprites.replaceSprite(116, 78);
+ _sprites.replaceSprite(121, 82);
+ _sprites.replaceSprite(115, 64);
+ _sprites.replaceSprite(120, 66);
+ _sprites.replaceSprite(135, 78);
+ _sprites.replaceSprite(140, 82);
+}
+
+void CryOmni3DEngine_Versailles::loadCursorsPalette() {
+ Image::BitmapDecoder bmpDecoder;
+
+ Common::File file;
+
+ if (!file.open("bou1_cA.bmp")) {
+ error("Failed to open BMP file");
+ }
+
+ if (!bmpDecoder.loadStream(file)) {
+ error("Failed to load BMP file");
+ }
+
+ _cursorPalette = new byte[3 * (bmpDecoder.getPaletteColorCount() +
+ bmpDecoder.getPaletteStartIndex())];
+ memset(_cursorPalette, 0, 3 * (bmpDecoder.getPaletteColorCount() +
+ bmpDecoder.getPaletteStartIndex()));
+ memcpy(_cursorPalette + 3 * bmpDecoder.getPaletteStartIndex(), bmpDecoder.getPalette(),
+ 3 * bmpDecoder.getPaletteColorCount());
+}
+
+void CryOmni3DEngine_Versailles::setupPalette(const byte *palette, uint start, uint num,
+ bool commit) {
+ memcpy(_mainPalette + 3 * start, palette, 3 * num);
+ copySubPalette(_mainPalette, _cursorPalette, 240, 8);
+
+ calculateTransparentMapping();
+ if (commit) {
+ setPalette(_mainPalette, 0, 256);
+ }
+}
+
+struct transparentScore {
+ unsigned int score;
+ byte redScaled;
+ byte greenScaled;
+
+ int dScore(transparentScore &other) { return abs((int)score - (int)other.score); }
+ int dRed(transparentScore &other) { return abs((int)redScaled - (int)other.redScaled); }
+ int dGreen(transparentScore &other) { return abs((int)greenScaled - (int)other.greenScaled); }
+};
+
+static transparentScore transparentCalculateScore(byte red, byte green, byte blue) {
+ transparentScore ret;
+ ret.score = 10 * (blue + 3 * (red + 2 * green)) / 30;
+ if (ret.score) {
+ ret.redScaled = ((unsigned int)red) * 256 / ret.score;
+ ret.greenScaled = ((unsigned int)green) * 256 / ret.score;
+ } else {
+ ret.redScaled = 0;
+ ret.greenScaled = 0;
+ }
+ return ret;
+}
+
+void CryOmni3DEngine_Versailles::calculateTransparentMapping() {
+ // Calculate colors proximity array
+ transparentScore *proximities = new transparentScore[256];
+
+ for (unsigned int i = _transparentSrcStart; i < _transparentSrcStop; i++) {
+ proximities[i] = transparentCalculateScore(_mainPalette[3 * i + 0], _mainPalette[3 * i + 1],
+ _mainPalette[3 * i + 2]);
+ }
+
+ unsigned int newColorsNextId = _transparentNewStart;
+ unsigned int newColorsCount = 0;
+ for (unsigned int i = _transparentDstStart; i < _transparentDstStop; i++) {
+ byte transparentRed = ((unsigned int)_mainPalette[3 * i + 0]) * 60 / 128;
+ byte transparentGreen = ((unsigned int)_mainPalette[3 * i + 1]) * 50 / 128;
+ byte transparentBlue = ((unsigned int)_mainPalette[3 * i + 2]) * 35 / 128;
+
+ // Find nearest color
+ transparentScore newColorScore = transparentCalculateScore(transparentRed, transparentGreen,
+ transparentBlue);
+ unsigned int distanceMin = -1u;
+ unsigned int nearestId = -1u;
+ for (unsigned int j = _transparentSrcStart; j < _transparentSrcStop; j++) {
+ if (j != i && newColorScore.dScore(proximities[j]) < 15) {
+ unsigned int distance = newColorScore.dRed(proximities[j]) + newColorScore.dGreen(proximities[j]);
+ if (distance < distanceMin) {
+ distanceMin = distance;
+ nearestId = j;
+ }
+ }
+ }
+
+ if (nearestId == -1u) {
+ // Couldn't find a good enough color, try to create one
+ if (_transparentNewStart != -1u && newColorsNextId <= _transparentNewStop) {
+ _mainPalette[3 * newColorsNextId + 0] = transparentRed;
+ _mainPalette[3 * newColorsNextId + 1] = transparentGreen;
+ _mainPalette[3 * newColorsNextId + 2] = transparentBlue;
+ nearestId = newColorsNextId;
+
+ newColorsCount++;
+ newColorsNextId++;
+ }
+ }
+
+ if (nearestId == -1u) {
+ // Couldn't allocate a new color, return the original one
+ nearestId = i;
+ }
+
+ _transparentPaletteMap[i] = nearestId;
+ }
+
+ delete[] proximities;
+}
+
+void CryOmni3DEngine_Versailles::makeTranslucent(Graphics::Surface &dst,
+ const Graphics::Surface &src) const {
+ assert(dst.w == src.w && dst.h == src.h);
+
+ const byte *srcP = (const byte *) src.getPixels();
+ byte *dstP = (byte *) dst.getPixels();
+ for (unsigned int y = 0; y < dst.h; y++) {
+ for (unsigned int x = 0; x < dst.w; x++) {
+ dstP[x] = _transparentPaletteMap[srcP[x]];
+ }
+ dstP += dst.pitch;
+ srcP += src.pitch;
+ }
+}
+
+bool CryOmni3DEngine_Versailles::hasPlaceDocumentation() {
+ return _placeStates[_currentPlaceId].docImage != nullptr;
+}
+
+bool CryOmni3DEngine_Versailles::displayPlaceDocumentation() {
+ if (!_placeStates[_currentPlaceId].docImage) {
+ return false;
+ }
+
+ _docManager.handleDocInGame(_placeStates[_currentPlaceId].docImage);
+ return true;
+}
+
+void CryOmni3DEngine_Versailles::syncOmni3DSettings() {
+ ConfMan.registerDefault("omni3d_speed", 0);
+ int confOmni3DSpeed = ConfMan.getInt("omni3d_speed");
+ if (confOmni3DSpeed == 0) {
+ _omni3dSpeed = 0;
+ } else if (confOmni3DSpeed == 1) {
+ _omni3dSpeed = 2;
+ } else if (confOmni3DSpeed == 2) {
+ _omni3dSpeed = 4;
+ } else if (confOmni3DSpeed == 3) {
+ _omni3dSpeed = -1;
+ } else if (confOmni3DSpeed == 4) {
+ _omni3dSpeed = -2;
+ } else {
+ // Invalid value
+ _omni3dSpeed = 0;
+ }
+}
+
+void CryOmni3DEngine_Versailles::syncSoundSettings() {
+ CryOmni3DEngine::syncSoundSettings();
+
+ int soundVolumeMusic = ConfMan.getInt("music_volume") / _musicVolumeFactor;
+
+ bool mute = false;
+ if (ConfMan.hasKey("mute")) {
+ mute = ConfMan.getBool("mute");
+ }
+
+ bool musicMute = mute || ConfMan.getBool("music_mute");
+
+ _mixer->muteSoundType(Audio::Mixer::kMusicSoundType, musicMute);
+ _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, soundVolumeMusic);
+}
+
+void CryOmni3DEngine_Versailles::playTransitionEndLevel(int level) {
+ musicStop();
+ _gameVariables[GameVariables::kWarnedIncomplete] = 0;
+
+ Common::String video;
+
+ unlockPalette();
+ switch (level) {
+ case -2:
+ video = "logo.hnm";
+ break;
+ case -1:
+ video = "a0_vf.hns";
+ break;
+ case 0:
+ video = "a1_vf.hns";
+ break;
+ case 1:
+ video = "a2_vf.hns";
+ break;
+ case 2:
+ video = "a3_vf.hns";
+ _inventory.removeByNameId(96);
+ _inventory.removeByNameId(104);
+ break;
+ case 3:
+ video = "a4_vf.hns";
+ break;
+ case 4:
+ video = "a5_vf.hns";
+ _inventory.removeByNameId(101);
+ _inventory.removeByNameId(127);
+ _inventory.removeByNameId(129);
+ _inventory.removeByNameId(130);
+ _inventory.removeByNameId(131);
+ _inventory.removeByNameId(132);
+ _inventory.removeByNameId(126);
+ break;
+ case 5:
+ video = "a6_vf.hns";
+ _inventory.removeByNameId(115);
+ break;
+ case 6:
+ video = "a7_vf.hns";
+ break;
+ case 7:
+ video = "a9_vf.hns";
+ break;
+ case 8:
+ video = "a8_vf.hns";
+ break;
+ default:
+ error("Invalid level : %d", level);
+ }
+
+ fadeOutPalette();
+ if (g_engine->shouldQuit()) {
+ _abortCommand = AbortQuit;
+ return;
+ }
+
+ fillSurface(0);
+
+ // Videos are like music because if you mute music in game it will mute videos soundtracks
+ playHNM(video, Audio::Mixer::kMusicSoundType);
+ clearKeys();
+ if (g_engine->shouldQuit()) {
+ _abortCommand = AbortQuit;
+ return;
+ }
+
+ fadeOutPalette();
+ if (g_engine->shouldQuit()) {
+ _abortCommand = AbortQuit;
+ return;
+ }
+
+ fillSurface(0);
+
+ if (level == 7 || level == 8) {
+ _abortCommand = AbortFinished;
+ } else {
+ _abortCommand = AbortNextLevel;
+ }
+}
+
+void CryOmni3DEngine_Versailles::changeLevel(int level) {
+ _currentLevel = level;
+
+ musicStop();
+ _mixer->stopAll();
+
+ if (_currentLevel == 1) {
+ _dialogsMan.reinitVariables();
+ for (Common::Array<unsigned int>::iterator it = _gameVariables.begin(); it != _gameVariables.end();
+ it++) {
+ *it = 0;
+ }
+ // TODO: countdown
+ _inventory.clear();
+ } else {
+ // TODO: to implement
+ error("New level %d is not implemented (yet)", level);
+ }
+
+ _gameVariables[GameVariables::kCurrentTime] = 1;
+
+ // keep back place state for level 2
+ int place8_state_backup;
+ if (level == 2) {
+ place8_state_backup = _placeStates[8].state;
+ }
+ _nextPlaceId = -1;
+ initNewLevel(_currentLevel);
+ // restore place state for level 2
+ if (level == 2) {
+ _placeStates[8].state = place8_state_backup;
+ }
+}
+
+void CryOmni3DEngine_Versailles::initNewLevel(int level) {
+ // SearchMan can't add several times the same basename
+ // We create several SearchSet with different names that we add to SearchMan instead
+
+ // Visiting uses all levels
+ SearchMan.remove("__visitFiles");
+
+ SearchMan.remove("__levelFiles_animacti");
+ SearchMan.remove("__levelFiles_warp");
+ SearchMan.remove("__levelFiles_img_fix");
+
+ const Common::FSNode gameDataDir(ConfMan.get("path"));
+ if (level >= 1 && level <= 7) {
+ Common::SearchSet *levelFilesAnimacti = new Common::SearchSet();
+ Common::SearchSet *levelFilesWarp = new Common::SearchSet();
+ Common::SearchSet *levelFilesImgFix = new Common::SearchSet();
+
+ levelFilesAnimacti->addSubDirectoryMatching(gameDataDir, Common::String::format(
+ "datas_v/animacti/level%d", level), 1);
+ levelFilesWarp->addSubDirectoryMatching(gameDataDir, Common::String::format(
+ "datas_v/warp/level%d/cyclo", level), 1);
+ levelFilesWarp->addSubDirectoryMatching(gameDataDir, Common::String::format(
+ "datas_v/warp/level%d/hnm", level), 1);
+ levelFilesImgFix->addSubDirectoryMatching(gameDataDir, Common::String::format(
+ "datas_v/img_fix/level%d", level), 1);
+
+ SearchMan.add("__levelFiles_animacti", levelFilesAnimacti);
+ SearchMan.add("__levelFiles_warp", levelFilesWarp);
+ SearchMan.add("__levelFiles_img_fix", levelFilesImgFix);
+ } else if (level == 8 && _isVisiting) {
+ // In visit mode, we take files from all levels, happily they have unique names
+ // Create a first SearchSet in which we will add all others to easily cleanup the mess
+ Common::SearchSet *visitFiles = new Common::SearchSet();
+
+ for (unsigned int lvl = 1; lvl <= 7; lvl++) {
+ Common::SearchSet *visitFilesAnimacti = new Common::SearchSet();
+ Common::SearchSet *visitFilesWarp = new Common::SearchSet();
+ Common::SearchSet *visitFilesImgFix = new Common::SearchSet();
+
+ visitFilesAnimacti->addSubDirectoryMatching(gameDataDir, Common::String::format(
+ "datas_v/animacti/level%d", lvl), 1);
+ visitFilesWarp->addSubDirectoryMatching(gameDataDir, Common::String::format(
+ "datas_v/warp/level%d/cyclo", lvl), 1);
+ visitFilesWarp->addSubDirectoryMatching(gameDataDir, Common::String::format(
+ "datas_v/warp/level%d/hnm", lvl), 1);
+ visitFilesImgFix->addSubDirectoryMatching(gameDataDir, Common::String::format(
+ "datas_v/img_fix/level%d", lvl), 1);
+
+ visitFiles->add(Common::String::format("__visitFiles_animacti_%d", lvl), visitFilesAnimacti);
+ visitFiles->add(Common::String::format("__visitFiles_warp_%d", lvl), visitFilesWarp);
+ visitFiles->add(Common::String::format("__visitFiles_img_fix_%d", lvl), visitFilesImgFix);
+ }
+
+ SearchMan.add("__visitFiles", visitFiles);
+ } else {
+ error("Invalid level %d", level);
+ }
+
+ // TODO: countdown
+ initPlacesStates();
+ initWhoSpeaksWhere();
+ setupLevelWarps(level);
+ updateGameTimeDialVariables();
+ _dialogsMan["{JOUEUR-ESSAYE-OUVRIR-PORTE-SALON}"] = 'Y';
+ _dialogsMan["{JOUEUR-ESSAYE-OUVRIR-PORTE-CHAMBRE}"] = 'Y';
+ setupLevelActionsMask();
+}
+
+void CryOmni3DEngine_Versailles::setupLevelWarps(int level) {
+ Common::File wamFile;
+ Common::String wamFName = Common::String::format("level%d.wam", level);
+ if (!wamFile.open(wamFName)) {
+ error("Can't open WAM file '%s'", wamFName.c_str());
+ }
+ _wam.loadStream(wamFile);
+
+ const LevelInitialState &initialState = kLevelInitialStates[level - 1];
+
+ if (_nextPlaceId == -1u) {
+ _nextPlaceId = initialState.placeId;
+ }
+ _omni3dMan.setAlpha(initialState.alpha);
+ _omni3dMan.setBeta(initialState.beta);
+}
+
+void CryOmni3DEngine_Versailles::setGameTime(unsigned int newTime, unsigned int level) {
+ if (_currentLevel != level) {
+ error("Level %u != current level %u", level, _currentLevel);
+ }
+
+ _gameVariables[GameVariables::kCurrentTime] = newTime;
+ updateGameTimeDialVariables();
+
+ if (_currentLevel == 1) {
+ if (newTime == 2) {
+ setPlaceState(1, 1);
+ setPlaceState(2, 1);
+ _whoSpeaksWhere[PlaceActionKey(2, 11201)] = "12E_HUI";
+ setPlaceState(3, 1);
+ } else if (newTime == 3) {
+ setPlaceState(2, 2);
+ }
+ } else if (_currentLevel == 2) {
+ if (newTime == 2) {
+ setPlaceState(9, 1);
+ _whoSpeaksWhere[PlaceActionKey(9, 52902)] = "22G_DAU";
+ } else if (newTime == 4) {
+ setPlaceState(10, 1);
+ setPlaceState(11, 1);
+ setPlaceState(12, 1);
+ setPlaceState(13, 1);
+ }
+ } else if (_currentLevel == 3) {
+ if (newTime == 2) {
+ if (_placeStates[13].state) {
+ setPlaceState(13, 3);
+ } else {
+ setPlaceState(13, 2);
+ }
+ setPlaceState(15, 1);
+ setPlaceState(17, 1);
+ } else if (newTime == 3) {
+ setPlaceState(10, 1);
+ setPlaceState(14, 1);
+ }
+ } else if (_currentLevel == 4) {
+ if (newTime == 2) {
+ setPlaceState(7, 1);
+ setPlaceState(8, 1);
+ setPlaceState(10, 1);
+ setPlaceState(16, 1);
+ } else if (newTime == 3) {
+ setPlaceState(10, 2);
+ setPlaceState(9, 1);
+ } else if (newTime == 4) {
+ setPlaceState(9, 2);
+ _whoSpeaksWhere[PlaceActionKey(9, 54091)] = "4_MAI";
+ _whoSpeaksWhere[PlaceActionKey(9, 14091)] = "4_MAI";
+ }
+ } else if (_currentLevel == 5) {
+ if (newTime == 2) {
+ setPlaceState(9, 1);
+ setPlaceState(13, 1);
+ } else if (newTime == 3) {
+ if (!_placeStates[16].state) {
+ setPlaceState(16, 2);
+ }
+ } else if (newTime == 4) {
+ _whoSpeaksWhere[PlaceActionKey(9, 15090)] = "54I_BON";
+ }
+ } else if (_currentLevel == 6) {
+ if (newTime == 2) {
+ setPlaceState(14, 1);
+ setPlaceState(19, 2);
+ }
+ }
+}
+
+void CryOmni3DEngine_Versailles::gameStep() {
+ while (!_abortCommand) {
+ if (_nextPlaceId != -1u) {
+ // TODO: check selected object == -2 if needed
+ if (_placeStates[_nextPlaceId].initPlace) {
+ (this->*_placeStates[_nextPlaceId].initPlace)();
+ // TODO: check selected object == -2 if needed
+ }
+ doPlaceChange();
+ musicUpdate();
+ }
+ if (_forcePaletteUpdate) {
+ redrawWarp();
+ }
+ unsigned int actionId = handleWarp();
+ debug("handleWarp returned %u", actionId);
+ // TODO: handle keyboard levels 4 and 5
+
+ // Get selected object there to detect when it has just been deselected
+ Object *selectedObject = _inventory.selectedObject();
+
+ _nextPlaceId = -1;
+ bool doEvent;
+ if (_placeStates[_currentPlaceId].filterEvent && !_isVisiting) {
+ doEvent = (this->*_placeStates[_currentPlaceId].filterEvent)(&actionId);
+ } else {
+ doEvent = true;
+ }
+
+ if (_abortCommand) {
+ break;
+ }
+
+ if (!selectedObject) {
+ // Execute only when no object was used before filtering event
+ if (actionId >= 1 && actionId < 10000) {
+ // Move to another place
+ if (doEvent) {
+ executeTransition(actionId);
+ }
+ } else if (actionId >= 10000 && actionId < 20000) {
+ // Speak
+ if (doEvent) {
+ executeSpeakAction(actionId);
+ // Force refresh of place
+ if (_nextPlaceId == -1u) {
+ _nextPlaceId = _currentPlaceId;
+ }
+ }
+ } else if (actionId >= 20000 && actionId < 30000) {
+ // Documentation
+ executeDocAction(actionId);
+ } else if (actionId >= 30000 && actionId < 40000) {
+ // Use
+ // In original game there is a handler for use actions but it's
+ // only for some events, we will implement them in the filterEvent handler
+ if (doEvent) {
+ error("Not implemented yet");
+ }
+ } else if (actionId >= 40000 && actionId < 50000) {
+ // See
+ executeSeeAction(actionId);
+ } else if (actionId >= 50000 && actionId < 60000) {
+ // Listening
+ // never filtered
+ executeSpeakAction(actionId);
+ // Force refresh of place
+ if (_nextPlaceId == -1u) {
+ _nextPlaceId = _currentPlaceId;
+ }
+ } else if (actionId == 66666) {
+ // Abort
+ assert(_abortCommand != AbortNoAbort);
+ return;
+ }
+ } else if (!actionId) {
+ // Click on nothing with an object: deselect it
+ _inventory.setSelectedObject(nullptr);
+ }
+ // TODO: selected_object == -2 if needed
+ }
+}
+
+void CryOmni3DEngine_Versailles::doGameOver() {
+ musicStop();
+ fadeOutPalette();
+ fillSurface(0);
+ // This test is not really relevant because it's for 2CDs edition but let's follow the code
+ if (_currentLevel < 4) {
+ playInGameVideo("1gameove");
+ } else {
+ playInGameVideo("4gameove");
+ }
+ fillSurface(0);
+ _abortCommand = AbortGameOver;
+}
+
+void CryOmni3DEngine_Versailles::doPlaceChange() {
+ const Place *nextPlace = _wam.findPlaceById(_nextPlaceId);
+ unsigned int state = _placeStates[_nextPlaceId].state;
+ if (state == -1u) {
+ state = 0;
+ }
+
+ if (state >= nextPlace->warps.size()) {
+ error("invalid warp %d/%d/%d", _currentLevel, _nextPlaceId, state);
+ }
+
+ Common::String warpFile = nextPlace->warps[state];
+ warpFile.toUppercase();
+ if (warpFile.size() > 0) {
+ if (warpFile.hasPrefix("NOT_MOVE")) {
+ // Don't move so do nothing but cancel place change
+ _nextPlaceId = -1;
+ } else {
+ _currentPlace = nextPlace;
+ if (!warpFile.hasPrefix("NOT_STOP")) {
+ if (_currentWarpImage) {
+ delete _currentWarpImage;
+ }
+ debug("Loading warp %s", warpFile.c_str());
+ _currentWarpImage = loadHLZ(warpFile);
+ if (!_currentWarpImage) {
+ error("Can't load warp %s", warpFile.c_str());
+ }
+#if 0
+ // This is not correct but to debug zones I think it's OK
+ Graphics::Surface *tmpSurf = (Graphics::Surface *) _currentWarpImage->getSurface();
+ for (Common::Array<Zone>::const_iterator it = _currentPlace->zones.begin();
+ it != _currentPlace->zones.end(); it++) {
+ Common::Rect tmp = it->rct;
+ tmp.bottom = tmpSurf->h - it->rct.top;
+ tmp.top = tmpSurf->h - it->rct.bottom;
+ tmpSurf->frameRect(tmp, 244);
+ }
+#endif
+ _currentPlace->setupWarpConstraints(_omni3dMan);
+ _omni3dMan.setSourceSurface(_currentWarpImage->getSurface());
+
+ setupPalette(_currentWarpImage->getPalette(), _currentWarpImage->getPaletteStartIndex(),
+ _currentWarpImage->getPaletteColorCount(), !_fadedPalette);
+
+ setMousePos(Common::Point(320, 240)); // Center of screen
+
+ _currentPlaceId = _nextPlaceId;
+ _nextPlaceId = -1;
+ }
+ }
+ } else {
+ error("invalid warp %d/%d/%d", _currentLevel, _nextPlaceId, state);
+ }
+}
+
+void CryOmni3DEngine_Versailles::setPlaceState(unsigned int placeId, unsigned int newState) {
+ unsigned int numStates = _wam.findPlaceById(placeId)->getNumStates();
+ unsigned int oldState = _placeStates[placeId].state;
+
+ if (newState > numStates) {
+ warning("CryOmni3DEngine_Versailles::setPlaceState: newState '%d' > numStates '%d'",
+ newState, numStates);
+ return;
+ }
+ _placeStates[placeId].state = newState;
+
+ if (_currentPlaceId == placeId && oldState != newState) {
+ // force reload of the place
+ _nextPlaceId = _currentPlaceId;
+ }
+}
+
+void CryOmni3DEngine_Versailles::executeTransition(unsigned int nextPlaceId) {
+ const Transition *transition;
+ unsigned int animationId = determineTransitionAnimation(_currentPlaceId, nextPlaceId, &transition);
+
+ _nextPlaceId = nextPlaceId;
+
+ Common::String animation = transition->animations[animationId];
+ animation.toUppercase();
+ debug("Transition animation: %s", animation.c_str());
+ if (animation.hasPrefix("NOT_FLI")) {
+ return;
+ }
+
+ if (_transitionAnimateWarp) {
+ animateWarpTransition(transition);
+ } else {
+ _transitionAnimateWarp = true;
+ }
+ if (musicWouldChange(_currentLevel, _nextPlaceId)) {
+ musicStop();
+ }
+ if (animation.hasPrefix("FADE_PAL")) {
+ _fadedPalette = true;
+ fadeOutPalette();
+ } else if (animation != "") {
+ _fadedPalette = false;
+ // Normally transitions don't overwrite the cursors colors and game doesn't restore palette
+ playInGameVideo(animation, false);
+ }
+
+ _omni3dMan.setAlpha(transition->dstAlpha);
+ _omni3dMan.setBeta(-transition->dstBeta);
+
+ unsigned int nextState = _placeStates[nextPlaceId].state;
+ if (nextState == -1u) {
+ nextState = 0;
+ }
+ const Place *nextPlace = _wam.findPlaceById(nextPlaceId);
+ Common::String warpFile = nextPlace->warps[nextState];
+ warpFile.toUppercase();
+ if (warpFile.hasPrefix("NOT_STOP")) {
+ unsigned int transitionNum;
+ // Determine transition to take
+ if (nextPlace->getNumTransitions() == 1) {
+ // Only one
+ transitionNum = 0;
+ } else if (nextPlace->findTransition(_currentPlaceId) == &nextPlace->transitions[0]) {
+ // Don't take the transition returning to where we come from
+ transitionNum = 1;
+ } else {
+ transitionNum = 0;
+ }
+ unsigned int nextNextPlaceId = nextPlace->transitions[transitionNum].dstId;
+
+ animationId = determineTransitionAnimation(nextPlaceId, nextNextPlaceId, &transition);
+ animation = transition->animations[animationId];
+
+ animation.toUppercase();
+ if (animation.hasPrefix("NOT_FLI")) {
+ return;
+ }
+ if (animation.hasPrefix("FADE_PAL")) {
+ _fadedPalette = true;
+ fadeOutPalette();
+ } else if (animation != "") {
+ _fadedPalette = false;
+ // Normally transitions don't overwrite the cursors colors and game doesn't restore palette
+ playInGameVideo(animation, false);
+ }
+
+ _nextPlaceId = nextNextPlaceId;
+
+ _omni3dMan.setAlpha(transition->dstAlpha);
+ _omni3dMan.setBeta(-transition->dstBeta);
+ }
+}
+
+void CryOmni3DEngine_Versailles::fakeTransition(unsigned int dstPlaceId) {
+ // No need of animation, caller will take care
+ // We just setup the camera in good place for the caller
+ const Place *srcPlace = _wam.findPlaceById(_currentPlaceId);
+ const Transition *transition = srcPlace->findTransition(dstPlaceId);
+
+ animateWarpTransition(transition);
+
+ _omni3dMan.setAlpha(transition->dstAlpha);
+ _omni3dMan.setBeta(-transition->dstBeta);
+}
+
+unsigned int CryOmni3DEngine_Versailles::determineTransitionAnimation(unsigned int srcPlaceId,
+ unsigned int dstPlaceId, const Transition **transition_) {
+ const Place *srcPlace = _wam.findPlaceById(srcPlaceId);
+ const Place *dstPlace = _wam.findPlaceById(dstPlaceId);
+ const Transition *transition = srcPlace->findTransition(dstPlaceId);
+
+ if (transition_) {
+ *transition_ = transition;
+ }
+
+ unsigned int srcNumStates = srcPlace->getNumStates();
+ unsigned int dstNumStates = dstPlace->getNumStates();
+ unsigned int animsNum = transition->getNumAnimations();
+
+ unsigned int srcState = _placeStates[srcPlaceId].state;
+ unsigned int dstState = _placeStates[dstPlaceId].state;
+
+ if (srcState >= srcNumStates) {
+ error("Invalid src state");
+ }
+
+ if (dstState >= dstNumStates) {
+ error("Invalid dst state");
+ }
+
+ if (animsNum <= 1) {
+ return 0;
+ }
+
+ if (srcNumStates == 2 && dstNumStates == 2) {
+ if (animsNum == 2) {
+ return dstState;
+ } else if (animsNum == 4) {
+ return srcState * 2 + dstState;
+ }
+ }
+
+ if (animsNum == dstNumStates) {
+ return dstState;
+ }
+
+ if (animsNum == srcNumStates) {
+ return srcState;
+ }
+
+ // Other case
+ return 0;
+}
+
+int CryOmni3DEngine_Versailles::handleWarp() {
+ bool exit = false;
+ bool leftButtonPressed = false;
+ bool firstDraw = true;
+ bool moving = true;
+ unsigned int actionId;
+ g_system->showMouse(true);
+ while (!leftButtonPressed && !exit) {
+ int xDelta = 0, yDelta = 0;
+ unsigned int movingCursor = -1;
+
+ pollEvents();
+ Common::Point mouse = getMousePos();
+
+ if (mouse.y < 100) {
+ movingCursor = 245;
+ yDelta = 100 - mouse.y;
+ } else if (mouse.y > 380) {
+ movingCursor = 224;
+ yDelta = 380 - mouse.y;
+ }
+ if (mouse.x < 100) {
+ movingCursor = 241;
+ xDelta = 100 - mouse.x;
+ } else if (mouse.x > 540) {
+ movingCursor = 228;
+ xDelta = 540 - mouse.x;
+ }
+ if (_omni3dSpeed > 0) {
+ xDelta <<= _omni3dSpeed;
+ yDelta <<= _omni3dSpeed;
+ } else if (_omni3dSpeed < 0) {
+ xDelta >>= -_omni3dSpeed;
+ yDelta >>= -_omni3dSpeed;
+ }
+ leftButtonPressed = (getCurrentMouseButton() == 1);
+
+ Common::Point mouseRev = _omni3dMan.mapMouseCoords(mouse);
+ mouseRev.y = 768 - mouseRev.y;
+
+ actionId = _currentPlace->hitTest(mouseRev);
+
+ exit = handleWarpMouse(&actionId, movingCursor);
+ if (g_engine->shouldQuit()) {
+ _abortCommand = AbortQuit;
+ exit = true;
+ }
+ if (exit) {
+ actionId = 66666;
+ }
+
+ if (firstDraw || xDelta || yDelta || _omni3dMan.hasSpeed()) {
+ bool useOldSpeed = false;
+ if (_omni3dSpeed <= 2) {
+ useOldSpeed = true;
+ }
+ _omni3dMan.updateCoords(xDelta, -yDelta, useOldSpeed);
+
+ const Graphics::Surface *result = _omni3dMan.getSurface();
+ g_system->copyRectToScreen(result->getPixels(), result->pitch, 0, 0, result->w, result->h);
+ if (!exit) {
+ // TODO: countdown
+ g_system->updateScreen();
+ if (firstDraw && _fadedPalette) {
+ fadeInPalette(_mainPalette);
+ _fadedPalette = false;
+ }
+ }
+ moving = true;
+ firstDraw = false;
+ } else if (moving) {
+ const Graphics::Surface *result = _omni3dMan.getSurface();
+ g_system->copyRectToScreen(result->getPixels(), result->pitch, 0, 0, result->w, result->h);
+ if (!exit) {
+ // TODO: countdown
+ g_system->updateScreen();
+ }
+ // TODO: cursorUseZones
+ moving = false;
+ } else {
+ if (!exit) {
+ // TODO: countdown
+ g_system->updateScreen();
+ }
+ }
+ if (!exit && !leftButtonPressed) {
+ g_system->delayMillis(50);
+ }
+ }
+ g_system->showMouse(false);
+ return actionId;
+}
+
+bool CryOmni3DEngine_Versailles::handleWarpMouse(unsigned int *actionId,
+ unsigned int movingCursor) {
+ PlaceStateActionKey mask = PlaceStateActionKey(_currentPlaceId, _placeStates[_currentPlaceId].state,
+ *actionId);
+ *actionId = _actionMasks.getVal(mask, *actionId);
+
+ if (getCurrentMouseButton() == 2 ||
+ getNextKey().keycode == Common::KEYCODE_SPACE) {
+ // Prepare background using alpha
+ const Graphics::Surface *original = _omni3dMan.getSurface();
+ bool mustRedraw = displayToolbar(original);
+ // Don't redraw if we abort game
+ if (_abortCommand != AbortNoAbort) {
+ return true;
+ }
+ if (mustRedraw) {
+ _forceRedrawWarp = true;
+ redrawWarp();
+ }
+ // Force a cycle to recalculate the correct mouse cursor
+ return false;
+ }
+
+ // TODO: countdown
+
+ const Object *selectedObj = _inventory.selectedObject();
+ if (selectedObj) {
+ if (*actionId != 0) {
+ setCursor(selectedObj->idSA());
+ } else {
+ setCursor(selectedObj->idSl());
+ }
+ } else if (*actionId >= 1 && *actionId < 10000) {
+ setCursor(243);
+ } else if (*actionId >= 10000 && *actionId < 20000) {
+ setCursor(113);
+ } else if (*actionId >= 20000 && *actionId < 30000) {
+ setCursor(198);
+ } else if (*actionId >= 30000 && *actionId < 40000) {
+ setCursor(99);
+ } else if (*actionId >= 40000 && *actionId < 50000) {
+ setCursor(145);
+ } else if (*actionId >= 50000 && *actionId < 60000) {
+ setCursor(136);
+ } else if (movingCursor != -1u) {
+ setCursor(movingCursor);
+ } else {
+ setCursor(45);
+ }
+ return false;
+}
+
+void CryOmni3DEngine_Versailles::animateWarpTransition(const Transition *transition) {
+ double srcAlpha = transition->srcAlpha;
+ double srcBeta = transition->srcBeta;
+
+ double oldDeltaAlpha = 1000., oldDeltaBeta = 1000.;
+
+ clearKeys();
+
+ bool exit = false;
+ while (!exit) {
+ double deltaAlpha = 2.*M_PI - srcAlpha + _omni3dMan.getAlpha();
+ if (deltaAlpha >= 2.*M_PI) {
+ deltaAlpha -= 2.*M_PI;
+ } else if (deltaAlpha < 0) {
+ deltaAlpha += 2.*M_PI;
+ }
+ int deltaAlphaI;
+ if (deltaAlpha < M_PI) {
+ deltaAlphaI = -(deltaAlpha * 512.);
+ } else {
+ deltaAlphaI = (2.*M_PI - deltaAlpha) * 512.;
+ }
+
+ double deltaBeta = -srcBeta - _omni3dMan.getBeta();
+ int deltaBetaI = -(deltaBeta * 512.);
+
+ if (_omni3dSpeed > 0) {
+ deltaAlphaI <<= 2;
+ deltaBetaI <<= 2;
+ } else if (_omni3dSpeed < 0) {
+ deltaAlphaI >>= 2;
+ deltaBetaI >>= 2;
+ }
+
+ _omni3dMan.updateCoords(deltaAlphaI, -deltaBetaI, false);
+
+ const Graphics::Surface *result = _omni3dMan.getSurface();
+ g_system->copyRectToScreen(result->getPixels(), result->pitch, 0, 0, result->w, result->h);
+ // TODO: countdown
+ g_system->updateScreen();
+
+ if (abs(oldDeltaAlpha - deltaAlpha) < 0.001 && abs(oldDeltaBeta - deltaBeta) < 0.001) {
+ exit = true;
+ }
+ oldDeltaAlpha = deltaAlpha;
+ oldDeltaBeta = deltaBeta;
+
+ if (pollEvents() && checkKeysPressed(2, Common::KEYCODE_ESCAPE, Common::KEYCODE_SPACE)) {
+ exit = true;
+ }
+
+ if (!exit) {
+ g_system->delayMillis(50);
+ }
+ }
+}
+
+void CryOmni3DEngine_Versailles::redrawWarp() {
+ setupPalette(_currentWarpImage->getPalette(), _currentWarpImage->getPaletteStartIndex(),
+ _currentWarpImage->getPaletteColorCount(), true);
+ if (_forceRedrawWarp) {
+ const Graphics::Surface *result = _omni3dMan.getSurface();
+ g_system->copyRectToScreen(result->getPixels(), result->pitch, 0, 0, result->w, result->h);
+ // TODO: countdown
+ g_system->updateScreen();
+ _forceRedrawWarp = false;
+ }
+ _forcePaletteUpdate = false;
+}
+
+void CryOmni3DEngine_Versailles::warpMsgBoxCB() {
+ pollEvents();
+}
+
+void CryOmni3DEngine_Versailles::animateCursor(const Object *obj) {
+ if (obj == nullptr) {
+ return;
+ }
+
+ g_system->showMouse(true);
+
+ for (unsigned int i = 4; i > 0; i--) {
+ // Wait 100ms
+ for (unsigned int j = 10; j > 0; j--) {
+ // pollEvents sleeps 10ms
+ pollEvents();
+ g_system->updateScreen();
+ }
+ setCursor(obj->idSA());
+ g_system->updateScreen();
+ // Wait 100ms
+ for (unsigned int j = 10; j > 0; j--) {
+ // pollEvents sleeps 10ms
+ pollEvents();
+ g_system->updateScreen();
+ }
+ setCursor(obj->idSl());
+ g_system->updateScreen();
+ }
+
+ g_system->showMouse(false);
+}
+
+void CryOmni3DEngine_Versailles::collectObject(unsigned int nameID, const ZonFixedImage *fimg,
+ bool showObject) {
+ Object *obj = _objects.findObjectByNameID(nameID);
+ _inventory.add(obj);
+ Object::ViewCallback cb = obj->viewCallback();
+ if (showObject && cb) {
+ (*cb)();
+ if (fimg) {
+ fimg->display();
+ } else {
+ _forceRedrawWarp = true;
+ redrawWarp();
+ }
+ }
+ animateCursor(obj);
+}
+
+void CryOmni3DEngine_Versailles::displayObject(const Common::String &imgName,
+ DisplayObjectHook hook) {
+ Image::ImageDecoder *imageDecoder = loadHLZ(imgName);
+ if (!imageDecoder) {
+ error("Can't display object");
+ }
+
+ if (imageDecoder->hasPalette()) {
+ // We don't need to calculate transparency but it's simpler to call this function
+ setupPalette(imageDecoder->getPalette(), imageDecoder->getPaletteStartIndex(),
+ imageDecoder->getPaletteColorCount());
+ }
+
+ const Graphics::Surface *image = imageDecoder->getSurface();
+
+ // Duplicate image to let hook modify it
+ Graphics::ManagedSurface dstSurface(image->w, image->h, image->format);
+ dstSurface.blitFrom(*image);
+
+ delete imageDecoder;
+ imageDecoder = nullptr;
+
+ if (hook) {
+ (this->*hook)(dstSurface);
+ }
+
+ g_system->copyRectToScreen(dstSurface.getPixels(), dstSurface.pitch, 0, 0,
+ dstSurface.w, dstSurface.h);
+ g_system->updateScreen();
+
+ setMousePos(Common::Point(320, 240)); // Center of screen
+ setCursor(181);
+
+ g_system->showMouse(true);
+
+ bool exitImg = false;
+ while (!g_engine->shouldQuit() && !exitImg) {
+ if (pollEvents()) {
+ if (getCurrentMouseButton() == 1) {
+ exitImg = true;
+ }
+ }
+ g_system->updateScreen();
+ }
+ waitMouseRelease();
+ clearKeys();
+
+ g_system->showMouse(false);
+ setMousePos(Common::Point(320, 240)); // Center of screen
+}
+
+void CryOmni3DEngine_Versailles::executeSeeAction(unsigned int actionId) {
+ if (_currentLevel == 7 && _currentPlaceId != 20) {
+ // Not enough time for paintings
+ displayMessageBoxWarp(14);
+ return;
+ }
+
+ const FixedImgCallback &cb = _imgScripts.getVal(actionId, nullptr);
+ if (cb != nullptr) {
+ handleFixedImg(cb);
+ } else {
+ warning("Image script %u not found", actionId);
+ }
+}
+
+void CryOmni3DEngine_Versailles::executeSpeakAction(unsigned int actionId) {
+ PlaceActionKey key(_currentPlaceId, actionId);
+ Common::HashMap<PlaceActionKey, Common::String>::iterator it = _whoSpeaksWhere.find(key);
+ g_system->showMouse(true);
+ bool doneSth = false;
+ if (it != _whoSpeaksWhere.end()) {
+ doneSth = _dialogsMan.play(it->_value);
+ }
+ g_system->showMouse(false);
+ _forcePaletteUpdate = true;
+ if (doneSth) {
+ setMousePos(Common::Point(320, 240)); // Center of screen
+ }
+}
+
+void CryOmni3DEngine_Versailles::executeDocAction(unsigned int actionId) {
+ if (_currentLevel == 7) {
+ // Not enough time for doc
+ displayMessageBoxWarp(13);
+ return;
+ }
+
+ Common::HashMap<unsigned int, const char *>::iterator it = _docPeopleRecord.find(actionId);
+ if (it == _docPeopleRecord.end() || !it->_value) {
+ warning("Missing documentation record for action %u", actionId);
+ return;
+ }
+
+ _docManager.handleDocInGame(it->_value);
+
+ _forcePaletteUpdate = true;
+ setMousePos(Common::Point(320, 240)); // Center of screen
+}
+
+void CryOmni3DEngine_Versailles::handleFixedImg(const FixedImgCallback &callback) {
+ if (!callback) {
+ return;
+ }
+
+ ZonFixedImage::CallbackFunctor *functor =
+ new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this, callback);
+ _fixedImage->run(functor);
+ // functor is deleted in ZoneFixedImage
+ functor = nullptr;
+
+ if (_nextPlaceId == -1u) {
+ _forcePaletteUpdate = true;
+ }
+}
+
+unsigned int CryOmni3DEngine_Versailles::getFakeTransition(unsigned int actionId) const {
+ for (const FakeTransitionActionPlace *ft = kFakeTransitions; ft->actionId != nullptr; ft++) {
+ if (ft->actionId == actionId) {
+ return ft->placeId;
+ }
+ }
+ return 0;
+}
+
+void CryOmni3DEngine_Versailles::playInGameVideo(const Common::String &filename,
+ bool restoreCursorPalette) {
+ if (!_isPlaying) {
+ return;
+ }
+
+ g_system->showMouse(false);
+ lockPalette(0, 241);
+ // Videos are like music because if you mute music in game it will mute videos soundtracks
+ playHNM(filename, Audio::Mixer::kMusicSoundType);
+ clearKeys();
+ unlockPalette();
+ if (restoreCursorPalette) {
+ // Restore cursors colors as 2 first ones may have been erased by the video
+ setPalette(&_cursorPalette[3 * 240], 240, 248);
+ }
+ g_system->showMouse(true);
+}
+
+} // End of namespace Versailles
+} // End of namespace CryOmni3D
diff --git a/engines/cryomni3d/versailles/engine.h b/engines/cryomni3d/versailles/engine.h
new file mode 100644
index 0000000000..4f56fd3a17
--- /dev/null
+++ b/engines/cryomni3d/versailles/engine.h
@@ -0,0 +1,453 @@
+/* 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 CRYOMNI3D_VERSAILLES_ENGINE_H
+#define CRYOMNI3D_VERSAILLES_ENGINE_H
+
+#include "common/events.h"
+#include "common/random.h"
+#include "common/array.h"
+#include "common/hashmap.h"
+#include "common/str.h"
+
+#include "cryomni3d/cryomni3d.h"
+#include "cryomni3d/omni3d.h"
+#include "cryomni3d/sprites.h"
+#include "cryomni3d/wam_parser.h"
+
+#include "cryomni3d/versailles/documentation.h"
+#include "cryomni3d/versailles/toolbar.h"
+#include "cryomni3d/versailles/dialogs_manager.h"
+
+namespace Graphics {
+class ManagedSurface;
+class Surface;
+}
+
+namespace CryOmni3D {
+struct FixedImageConfiguration;
+class ZonFixedImage;
+}
+
+namespace CryOmni3D {
+namespace Versailles {
+struct PlaceStateActionKey {
+ unsigned int placeId;
+ unsigned int placeState;
+ unsigned int actionId;
+ PlaceStateActionKey(unsigned int placeId_, unsigned int placeState_, unsigned int actionId_) :
+ placeId(placeId_), placeState(placeState_), actionId(actionId_) {}
+
+ bool operator==(const PlaceStateActionKey &other) const {
+ return other.placeId == placeId && other.placeState == placeState && other.actionId == actionId;
+ }
+};
+
+struct PlaceActionKey {
+ unsigned int placeId;
+ unsigned int actionId;
+ PlaceActionKey(unsigned int placeId_, unsigned int actionId_) :
+ placeId(placeId_), actionId(actionId_) {}
+
+ bool operator==(const PlaceActionKey &other) const {
+ return other.placeId == placeId && other.actionId == actionId;
+ }
+};
+}
+}
+
+namespace Common {
+template<>
+struct Hash<CryOmni3D::Versailles::PlaceStateActionKey> {
+ uint operator()(const CryOmni3D::Versailles::PlaceStateActionKey &k) const {
+ // placeState shouldn't be greater than 8 and placeId shouldn't be greater than 100
+ // originalActionId shouldn't be greater than 65536
+ return (k.placeId << 24 | k.placeState << 16) ^ k.actionId;
+ }
+};
+template<>
+struct Hash<CryOmni3D::Versailles::PlaceActionKey> {
+ uint operator()(const CryOmni3D::Versailles::PlaceActionKey &k) const {
+ // placeId shouldn't be greater than 100
+ // originalActionId shouldn't be greater than 65536
+ return (k.placeId << 16) ^ k.actionId;
+ }
+};
+}
+
+namespace CryOmni3D {
+namespace Versailles {
+
+class CryOmni3DEngine_Versailles;
+
+enum AbortCommand {
+ AbortNoAbort = 0,
+ AbortQuit = 1,
+ AbortLoadGame = 2,
+ AbortNewGame = 3,
+ AbortNextLevel = 5,
+ AbortFinished = 6,
+ AbortGameOver = 7
+};
+
+struct GameVariables {
+ enum Var {
+ // TODO: make these enum members more correct
+ kCollectPartition = 0, // 0
+ kUnlockPetitePorte,
+ kAlreadyCame31,
+ kDrawerStatus,
+ kCurrentTime,
+ kGotMedaillesSolution,
+ kDrawerFurnitureStatus,
+ kCollectePartition,
+ kCollectPamphletArchi,
+ kGotRevealedPaper, // OK
+ kCollectCle, // 10
+ kCollectCartonDessin,
+ kEsquissePainted,
+ kStateFauxCroquis,
+ kCollectNourriture,
+ kCollectPlume,
+ kStatePamphletReligion,
+ kCollectPetiteCle3,
+ kCollectGravure,
+ kCollectCordon,
+ kCollectPlanVauban, // 20
+ kCollectPlanVauban2,
+ kCollectEchelle,
+ kLostCordon,
+ kDescendreLustre,
+ kOrangerRatisse,
+ kDiscussedLabyrOrder,
+ kUsedBougieAllumee,
+ kStateBombe,
+ kInkSpilled, // OK
+ kCollectedPaperOnTable, // OK // 30
+ kCoffreUnlocked,
+ //kUselessVar,
+ kCollectedPaperInTrunk = 33, // OK
+ kUsingPinceauColor,
+ kUsedScissors, // OK
+ kUsedClefsCombles,
+ kHasPlayedLebrun, // OK
+ kWarnedIncomplete,
+ kUsedPlanVauban1,
+ kUsedPlanVauban2, // 40
+ kSeenMemorandum,
+ kCollectScissors, // OK
+ kSavedCountdown, // TODO: calculate it in real time
+ kMax
+ };
+};
+
+// For random sounds we set a constant ID and avoid to use it elsewhere
+struct SoundIds {
+ enum {
+ kOrgue = 0,
+ kLeb001,
+ kMax
+ };
+};
+
+struct PlaceState {
+ typedef void (CryOmni3DEngine_Versailles::*InitFunc)();
+ typedef bool (CryOmni3DEngine_Versailles::*FilterEventFunc)(unsigned int *event);
+
+ PlaceState() : initPlace(nullptr), filterEvent(nullptr), docImage(nullptr), state(0) {}
+ PlaceState(InitFunc initPlace_, FilterEventFunc filterEvent_, const char *docImage_) :
+ initPlace(initPlace_), filterEvent(filterEvent_), docImage(docImage_), state(0) {}
+
+ InitFunc initPlace;
+ FilterEventFunc filterEvent;
+ const char *docImage;
+ unsigned int state;
+};
+
+struct LevelInitialState {
+ unsigned int placeId;
+ double alpha;
+ double beta;
+};
+
+struct FakeTransitionActionPlace {
+ unsigned int actionId;
+ unsigned int placeId;
+};
+
+typedef void (CryOmni3DEngine_Versailles::*FixedImgCallback)(ZonFixedImage *);
+
+struct MsgBoxParameters {
+ int font;
+ byte foreColor;
+ unsigned int lineHeight;
+ unsigned int spaceWidth;
+ unsigned int charSpacing;
+ unsigned int initialWidth;
+ unsigned int incrementWidth;
+ unsigned int initialHeight;
+ unsigned int incrementHeight;
+ unsigned int timeoutChar;
+};
+
+class CryOmni3DEngine_Versailles : public CryOmni3DEngine {
+ friend class Versailles_DialogsManager;
+protected:
+ Common::Error run() override;
+
+public:
+ CryOmni3DEngine_Versailles(OSystem *syst, const CryOmni3DGameDescription *gamedesc);
+ virtual ~CryOmni3DEngine_Versailles();
+
+ void setupPalette(const byte *colors, uint start, uint num) override { setupPalette(colors, start, num, true); }
+ void makeTranslucent(Graphics::Surface &dst, const Graphics::Surface &src) const override;
+
+ virtual bool displayToolbar(const Graphics::Surface *original) override { return _toolbar.displayToolbar(original); };
+ virtual bool hasPlaceDocumentation() override;
+ virtual bool displayPlaceDocumentation() override;
+ virtual unsigned int displayOptions() override;
+ virtual bool shouldAbort() override { return g_engine->shouldQuit() || _abortCommand != AbortNoAbort; }
+
+
+private:
+ void setupFonts();
+ void setupSprites();
+ void loadCursorsPalette();
+ void calculateTransparentMapping();
+ void setupMessages();
+ void setupObjects();
+ void setupDialogVariables();
+ void setupImgScripts();
+ void setupPaintingsTitles();
+
+ void syncOmni3DSettings();
+ void syncSoundSettings();
+
+ void playTransitionEndLevel(int level);
+ void changeLevel(int level);
+ void initNewLevel(int level);
+ void setupLevelWarps(int level);
+ void initPlacesStates();
+ void initWhoSpeaksWhere();
+ void initDocPeopleRecord();
+ void setupLevelActionsMask();
+
+ unsigned int currentGameTime() const { return _gameVariables[GameVariables::kCurrentTime]; }
+ void setGameTime(unsigned int newTime, unsigned int level);
+ void updateGameTimeDialVariables();
+
+ void gameStep();
+ void doGameOver();
+
+ void setPlaceState(unsigned int placeId, unsigned int newState);
+ void doPlaceChange();
+ void executeTransition(unsigned int nextPlaceId);
+ void fakeTransition(unsigned int dstPlaceId);
+ unsigned int determineTransitionAnimation(unsigned int srcId, unsigned int dstId,
+ const Transition **transition);
+
+ unsigned int getFakeTransition(unsigned int actionId) const;
+
+ int handleWarp();
+ bool handleWarpMouse(unsigned int *actionId, unsigned int movingCuror);
+ void animateWarpTransition(const Transition *transition);
+ void redrawWarp();
+
+ void handleFixedImg(const FixedImgCallback &callback);
+ void executeSeeAction(unsigned int actionId);
+
+ void executeSpeakAction(unsigned int actionId);
+ void setupDialogShows();
+ bool preprocessDialog(const Common::String &sequence);
+ void postprocessDialog(const Common::String &sequence);
+
+ void executeDocAction(unsigned int actionId);
+
+ void drawMenuTitle(Graphics::ManagedSurface *surface, byte color);
+ unsigned int displayFilePicker(const Graphics::Surface *bgFrame, bool saveMode,
+ Common::String &saveName);
+ unsigned int displayYesNoBox(Graphics::ManagedSurface &surface, const Common::Rect &position,
+ unsigned int msg_id);
+ void displayMessageBox(const MsgBoxParameters &params, const Graphics::Surface *surface,
+ unsigned int msg_id, const Common::Point &position,
+ const Common::Functor0<void> &callback) { displayMessageBox(params, surface, _messages[msg_id], position, callback); }
+ void displayMessageBox(const MsgBoxParameters &params, const Graphics::Surface *surface,
+ const Common::String &msg, const Common::Point &position,
+ const Common::Functor0<void> &callback);
+ void displayMessageBoxWarp(const Common::String &message);
+ void displayMessageBoxWarp(unsigned int msg_id) { displayMessageBoxWarp(_messages[msg_id]); }
+ void displayCredits();
+
+ void warpMsgBoxCB();
+
+ bool canVisit() const;
+ Common::String getSaveFileName(bool visit, unsigned int saveNum) const;
+ void getSavesList(bool visit, Common::Array<Common::String> &saveNames);
+ void saveGame(bool visit, unsigned int saveNum, const Common::String &saveName) const;
+ bool loadGame(bool visit, unsigned int saveNum);
+
+ void animateCursor(const Object *object);
+ void collectObject(unsigned int nameID, const ZonFixedImage *fimg = nullptr,
+ bool showObject = true);
+ typedef void (CryOmni3DEngine_Versailles::*DisplayObjectHook)(Graphics::ManagedSurface &surface);
+ void displayObject(const Common::String &imgName, DisplayObjectHook hook = nullptr);
+
+ void setupPalette(const byte *colors, uint start, uint num, bool commit);
+
+ bool showSubtitles() const;
+
+ void playInGameVideo(const Common::String &filename, bool restoreCursorPalette = true);
+
+ unsigned int getMusicId(unsigned int level, unsigned int placeId) const;
+ bool musicWouldChange(unsigned int level, unsigned int placeId) const;
+ void musicUpdate();
+ void musicPause();
+ void musicResume();
+ void musicStop();
+ void musicSetQuiet(bool quiet);
+
+ Common::StringArray _messages;
+ static const unsigned int kSpritesMapTable[];
+ static const unsigned int kSpritesMapTableSize;
+ static const LevelInitialState kLevelInitialStates[];
+ static const FakeTransitionActionPlace kFakeTransitions[];
+ Common::HashMap<unsigned int, FixedImgCallback> _imgScripts;
+ Common::Array<Common::String> _paintingsTitles;
+
+ Toolbar _toolbar;
+
+ byte *_mainPalette;
+ byte *_cursorPalette;
+ bool _fadedPalette;
+ bool _forcePaletteUpdate;
+ bool _forceRedrawWarp;
+
+ byte *_transparentPaletteMap;
+ unsigned int _transparentSrcStart;
+ unsigned int _transparentSrcStop;
+ unsigned int _transparentDstStart;
+ unsigned int _transparentDstStop;
+ unsigned int _transparentNewStart;
+ unsigned int _transparentNewStop;
+
+ bool _isPlaying;
+ bool _isVisiting;
+ AbortCommand _abortCommand;
+ unsigned int _loadedSave;
+
+ int _omni3dSpeed;
+
+ unsigned int _currentLevel;
+ Versailles_DialogsManager _dialogsMan;
+
+ Omni3DManager _omni3dMan;
+ ZonFixedImage *_fixedImage;
+
+ Common::Array<unsigned int> _gameVariables;
+ Common::Array<PlaceState> _placeStates;
+ Common::HashMap<PlaceStateActionKey, unsigned int> _actionMasks;
+ Common::HashMap<PlaceActionKey, Common::String> _whoSpeaksWhere;
+ Common::HashMap<unsigned int, const char *> _docPeopleRecord;
+ bool _transitionAnimateWarp;
+ unsigned int _nextPlaceId;
+ WAMParser _wam;
+ unsigned int _currentPlaceId;
+ const Place *_currentPlace;
+ const Image::ImageDecoder *_currentWarpImage;
+
+ const char *_musicCurrentFile;
+ Audio::SoundHandle _musicHandle;
+ float _musicVolumeFactor;
+ static const char *kMusicFiles[8][8];
+
+ Versailles_Documentation _docManager;
+
+ static const MsgBoxParameters kWarpMsgBoxParameters;
+ static const MsgBoxParameters kFixedimageMsgBoxParameters;
+ static const FixedImageConfiguration kFixedImageConfiguration;
+
+ //Objects
+ template<unsigned int ID>
+ void genericDisplayObject();
+
+ // Fixed image
+ template<unsigned int ID>
+ void genericDumbImage(ZonFixedImage *fimg);
+ template<unsigned int ID>
+ void genericPainting(ZonFixedImage *fimg);
+#define IMG_CB(name) void img_ ## name(ZonFixedImage *fimg)
+ IMG_CB(31142);
+ IMG_CB(31142b);
+ IMG_CB(31142c);
+ IMG_CB(31142d);
+ IMG_CB(31143);
+ IMG_CB(31143b);
+ IMG_CB(31143c);
+ IMG_CB(31143d);
+ IMG_CB(41202);
+ IMG_CB(41202b);
+ IMG_CB(41801);
+ IMG_CB(41801b);
+ IMG_CB(41801c);
+ IMG_CB(41802);
+ IMG_CB(41802b);
+ IMG_CB(41802c);
+ IMG_CB(41802d);
+#undef IMG_CB
+
+#define FILTER_EVENT(level, place) bool filterEventLevel ## level ## Place ## place(unsigned int *event)
+#define INIT_PLACE(level, place) void initPlaceLevel ## level ## Place ## place()
+ FILTER_EVENT(1, 1);
+ FILTER_EVENT(1, 2);
+ INIT_PLACE(1, 3);
+ FILTER_EVENT(1, 3);
+ //FILTER_EVENT(1, 7); // Not used
+ FILTER_EVENT(1, 14);
+#undef FILTER_EVENT
+#undef INIT_PLACE
+
+ // Dialogs shows
+ void dialogShowBontempsShowThird();
+ void dialogShowHuissierShowPamphlet();
+ void dialogShowMonseigneurSorts();
+ void dialogShowLeBrunWatches();
+ void dialogShowDoorsOpen();
+ void dialogShowSwissGuardGives();
+ void dialogShowLullyCorrects();
+ void dialogShowBontempsGivesAuth();
+ void dialogShowCroissyLeave();
+ void dialogShowMaintenonGives();
+ void dialogShowLaChaizeGivesBack();
+ void dialogShowLaChaizeWrites();
+ void dialogShowLaChaizeGivesPamphlet();
+ void dialogShowBontempsGivesKey();
+ void dialogShowDuMaineLeaves();
+ void dialogShowTransitionScene();
+ void dialogShowEndOfGame();
+ void dialogShowLeBrunGives();
+ void dialogShowLeBrunLeave();
+};
+
+} // End of namespace Versailles
+} // End of namespace CryOmni3D
+
+#endif
diff --git a/engines/cryomni3d/versailles/logic.cpp b/engines/cryomni3d/versailles/logic.cpp
new file mode 100644
index 0000000000..1144318ec0
--- /dev/null
+++ b/engines/cryomni3d/versailles/logic.cpp
@@ -0,0 +1,1036 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "audio/audiostream.h"
+#include "audio/decoders/wave.h"
+#include "audio/mixer.h"
+#include "common/file.h"
+#include "common/system.h"
+
+#include "cryomni3d/fixed_image.h"
+
+#include "cryomni3d/versailles/engine.h"
+
+namespace CryOmni3D {
+namespace Versailles {
+
+static const char *imagesObjects[] = {
+ "PAMP.gif", // 0: 96
+ "PAPT_2.gif", // 1: 98
+ "PAML.gif", // 2: 101
+ "ESQ1.gif", // 3: 105a, 106b
+ "ESQ2.gif", // 4: 105b, 106c
+ "ESQ3.gif", // 5: 105c
+ "ESQ4.gif", // 6: 105d, 106a, 107a
+ "ESQ4T.gif", // 7: 107b
+ "ESQ4D.gif", // 8: 109
+ "PAMA.gif", // 9: 115
+ "PAMM1.gif", // 10: 118a
+ "PAMM2.gif", // 11: 118b
+ "MEDP.gif", // 12: 121a
+ "MEDP2.gif", // 13: 121b
+ "PAMR1.gif", // 14: 125a
+ "PAMR4.gif", // 15: 125b
+ "EPIL.gif", // 16: 126
+ "PAMG.gif", // 17: 127
+ "PBETE.gif", // 18: 129
+ "VAU2.gif", // 19: 131
+ "VAU3.gif", // 20: 132
+ "GRAV2.gif", // 21: 134
+ "MEM.gif", // 22: 137
+ "VS1.gif", // 23: 138
+ "VS2.gif", // 24: 139
+ "FAB.gif", // 25: 141
+ "LABYR.gif", // 26: 142
+};
+
+void CryOmni3DEngine_Versailles::setupObjects() {
+ _objects.reserve(51);
+#define SET_OBJECT(cursorId, nameId) _objects.push_back(Object(_sprites, cursorId, nameId))
+#define SET_OBJECT_CB(cursorId, nameId, cb) do { \
+ _objects.push_back(Object(_sprites, cursorId, nameId)); \
+ _objects.back().setViewCallback(new Common::Functor0Mem<void, CryOmni3DEngine_Versailles>(this, &CryOmni3DEngine_Versailles::cb)); \
+ } while (false)
+#define SET_OBJECT_GENERIC_CB(cursorId, nameId, imageId) SET_OBJECT_CB(cursorId, nameId, genericDisplayObject<imageId>)
+ SET_OBJECT(161, 93);
+ SET_OBJECT(107, 94);
+ SET_OBJECT(69, 95);
+ SET_OBJECT_GENERIC_CB(230, 96, 0);
+ SET_OBJECT(64, 97);
+ SET_OBJECT_GENERIC_CB(250, 98, 1);
+ SET_OBJECT(202, 99);
+ SET_OBJECT(235, 100);
+ SET_OBJECT_GENERIC_CB(167, 101, 2);
+ SET_OBJECT(191, 102);
+ SET_OBJECT(171, 103);
+ SET_OBJECT(47, 104);
+ SET_OBJECT(205, 105);
+ SET_OBJECT(214, 106);
+ SET_OBJECT(6, 107);
+ SET_OBJECT(58, 108);
+ SET_OBJECT_GENERIC_CB(5, 109, 8);
+ SET_OBJECT(38, 110);
+ SET_OBJECT(119, 113);
+ SET_OBJECT(186, 114);
+ SET_OBJECT_GENERIC_CB(246, 115, 9);
+ SET_OBJECT(80, 116);
+ SET_OBJECT(180, 117);
+ SET_OBJECT(34, 118);
+ SET_OBJECT(173, 119);
+ SET_OBJECT(81, 120);
+ SET_OBJECT(156, 121);
+ SET_OBJECT(143, 122);
+ SET_OBJECT(101, 123);
+ SET_OBJECT(204, 124);
+ SET_OBJECT(10, 125);
+ SET_OBJECT(112, 126); // TODO: EPIL.gif
+ SET_OBJECT_GENERIC_CB(90, 127, 17);
+ SET_OBJECT(216, 128);
+ SET_OBJECT_GENERIC_CB(32, 129, 18);
+ SET_OBJECT(37, 130);
+ SET_OBJECT_GENERIC_CB(134, 131, 19);
+ SET_OBJECT_GENERIC_CB(150, 132, 20);
+ SET_OBJECT(28, 133);
+ SET_OBJECT_GENERIC_CB(22, 134, 21);
+ SET_OBJECT(92, 135);
+ SET_OBJECT(115, 136); // Out of order in EXE
+ SET_OBJECT_GENERIC_CB(16, 137, 22);
+ SET_OBJECT_GENERIC_CB(237, 138, 23);
+ SET_OBJECT_GENERIC_CB(0, 139, 24);
+ SET_OBJECT(31, 140);
+ SET_OBJECT_GENERIC_CB(87, 141, 25);
+ SET_OBJECT(95, 142); // TODO: LABYR.gif
+ SET_OBJECT(157, 143);
+ SET_OBJECT(168, 144);
+ SET_OBJECT(65, 145);
+#undef SET_OBJECT
+}
+
+template<unsigned int ID>
+void CryOmni3DEngine_Versailles::genericDisplayObject() {
+ displayObject(imagesObjects[ID]);
+}
+
+// This array contains images for all paintings it must be kept in sync with _paintingsTitles
+static const char *imagesPaintings[] = {
+ "10E_1.GIF", // 0: 41201
+ nullptr, // 1: 41202
+ "10E_3.GIF", // 2: 41203
+ "10E_4.GIF", // 3: 41204
+ "10E_5.GIF", // 4: 41205
+ "10D_1.GIF", // 5: 41301
+ "10D_2.GIF", // 6: 41302
+ "20C_1.GIF", // 7: 42401
+ "20G_11.GIF", // 8: 42901
+ "20G_12.GIF", // 9: 42902
+ "20G_13.GIF", // 10: 42903
+ "20G_14.GIF", // 11: 42904
+ "20G_15.GIF", // 12: 42905
+ "20G_16.GIF", // 13: 42906
+ "20G_21.GIF", // 14: 42907
+ "20G_22.GIF", // 15: 42908
+ "20G_23.GIF", // 16: 42909
+ "20G_31.GIF", // 17: 42910
+ "20G_32.GIF", // 18: 42911
+ "20G_33.GIF", // 19: 42912
+ "20G_34.GIF", // 20: 42913
+ "20G_35.GIF", // 21: 42914
+ "20G_36.GIF", // 22: 42915
+ "30N_1.GIF", // 23: 43090
+ "30N_2.GIF", // 24: 43091
+ "30N_3.GIF", // 25: 43092
+ "30O_1.GIF", // 26: 43100
+ "30O_2.GIF", // 27: 43101
+ "30O_31.GIF", // 28: 43102
+ "30O_32.GIF", // 29: 43103
+ "30O_33.GIF", // 30: 43104
+ "30M_1.GIF", // 31: 43130
+ "30M_2.GIF", // 32: 43131
+ "30M_3.GIF", // 33: 43132
+ "30L_11.GIF", // 34: 43140
+ "30L_12.GIF", // 35: 43141
+ "30L_21.GIF", // 36: 43142
+ nullptr, // 37: 43143
+ "30L_32.GIF", // 38: 43144
+ "30J_11.GIF", // 39: 43150
+ "30J_12.GIF", // 40: 43151
+ "30J_13.GIF", // 41: 43152
+ "30J_21.GIF", // 42: 43153
+ "30J_22.GIF", // 43: 43154
+ "30J_31.GIF", // 44: 43155
+ "30J_32.GIF", // 45: 43156
+ "30J_33.GIF", // 46: 43157
+ "51A_1.GIF", // 47: 45260
+ // Now let's put dumb images, those without description and any special action, they are not synced with _paintingsTitles
+ "30Q_1.GIF", // 48: 43060
+ "30Q_2.GIF", // 49: 43061
+ "52M2.GIF", // 50: 45130 // Almost dumb
+ "53I_LUST.GIF", // 51: 45280 // Almost dumb
+ "DUC.GIF", // 52: 46001
+ "COQ.GIF", // 53: 46002
+ "CHAT.GIF", // 54: 46003
+ "DRAGON.GIF", // 55: 46004
+ "GRUE.GIF", // 56: 46005
+ "RENARD.GIF", // 57: 46006
+ "POULE.GIF", // 58: 46007
+ "LOUP.GIF", // 59: 46008
+ "MILAN.GIF", // 60: 46009
+ "GRENOU.GIF", // 61: 46010
+ "AIGLE.GIF", // 62: 46011
+ "SOURIS.GIF", // 63: 46012
+ "CYGNE.GIF", // 64: 46013 and 46440
+ "LOUPTETE.GIF", // 65: 46014
+ "CANNES.GIF", // 66: 46015
+};
+
+// Setup array for all see actions
+void CryOmni3DEngine_Versailles::setupImgScripts() {
+ // First all paintings to keep it simple for counting
+#define SET_SCRIPT_BY_ID(id) _imgScripts[id] = &CryOmni3DEngine_Versailles::img_ ## id
+#define SET_SCRIPT_BY_PAINTING(id, image) _imgScripts[id] = &CryOmni3DEngine_Versailles::genericPainting<image>
+ SET_SCRIPT_BY_PAINTING(41201, 0);
+ SET_SCRIPT_BY_ID(41202);
+ SET_SCRIPT_BY_PAINTING(41203, 2);
+ SET_SCRIPT_BY_PAINTING(41204, 3);
+ SET_SCRIPT_BY_PAINTING(41205, 4);
+ SET_SCRIPT_BY_PAINTING(41301, 5);
+ SET_SCRIPT_BY_PAINTING(41302, 6);
+ SET_SCRIPT_BY_PAINTING(42401, 7);
+ SET_SCRIPT_BY_PAINTING(42901, 8);
+ SET_SCRIPT_BY_PAINTING(42902, 9);
+ SET_SCRIPT_BY_PAINTING(42903, 10);
+ SET_SCRIPT_BY_PAINTING(42904, 11);
+ SET_SCRIPT_BY_PAINTING(42905, 12);
+ SET_SCRIPT_BY_PAINTING(42906, 13);
+ SET_SCRIPT_BY_PAINTING(42907, 14);
+ SET_SCRIPT_BY_PAINTING(42908, 15);
+ SET_SCRIPT_BY_PAINTING(42909, 16);
+ SET_SCRIPT_BY_PAINTING(42910, 17);
+ SET_SCRIPT_BY_PAINTING(42911, 18);
+ SET_SCRIPT_BY_PAINTING(42912, 19);
+ SET_SCRIPT_BY_PAINTING(42913, 20);
+ SET_SCRIPT_BY_PAINTING(42914, 21);
+ SET_SCRIPT_BY_PAINTING(42915, 22);
+ SET_SCRIPT_BY_PAINTING(43090, 23);
+ SET_SCRIPT_BY_PAINTING(43091, 24);
+ SET_SCRIPT_BY_PAINTING(43092, 25);
+ SET_SCRIPT_BY_PAINTING(43100, 26);
+ SET_SCRIPT_BY_PAINTING(43101, 27);
+ SET_SCRIPT_BY_PAINTING(43102, 28);
+ SET_SCRIPT_BY_PAINTING(43103, 29);
+ SET_SCRIPT_BY_PAINTING(43104, 30);
+ SET_SCRIPT_BY_PAINTING(43130, 31);
+ SET_SCRIPT_BY_PAINTING(43131, 32);
+ SET_SCRIPT_BY_PAINTING(43132, 33);
+ SET_SCRIPT_BY_PAINTING(43140, 34);
+ SET_SCRIPT_BY_PAINTING(43141, 35);
+ SET_SCRIPT_BY_PAINTING(43142, 36);
+ //SET_SCRIPT_BY_ID(43143); // TODO: implement it
+ SET_SCRIPT_BY_PAINTING(43144, 38);
+ SET_SCRIPT_BY_PAINTING(43150, 39);
+ SET_SCRIPT_BY_PAINTING(43151, 40);
+ SET_SCRIPT_BY_PAINTING(43152, 41);
+ SET_SCRIPT_BY_PAINTING(43153, 42);
+ SET_SCRIPT_BY_PAINTING(43154, 43);
+ SET_SCRIPT_BY_PAINTING(43155, 44);
+ SET_SCRIPT_BY_PAINTING(43156, 45);
+ SET_SCRIPT_BY_PAINTING(43157, 46);
+ SET_SCRIPT_BY_PAINTING(45260, 47);
+#undef SET_SCRIPT_BY_PAINTING
+ // From now dumb images (like paintings but without interrogation mark handling)
+#define SET_SCRIPT_BY_DUMB(id, image) _imgScripts[id] = &CryOmni3DEngine_Versailles::genericDumbImage<image>
+ SET_SCRIPT_BY_DUMB(43060, 48);
+ SET_SCRIPT_BY_DUMB(43061, 49);
+ SET_SCRIPT_BY_DUMB(46001, 52);
+ SET_SCRIPT_BY_DUMB(46002, 53);
+ SET_SCRIPT_BY_DUMB(46003, 54);
+ SET_SCRIPT_BY_DUMB(46004, 55);
+ SET_SCRIPT_BY_DUMB(46005, 56);
+ SET_SCRIPT_BY_DUMB(46006, 57);
+ SET_SCRIPT_BY_DUMB(46007, 58);
+ SET_SCRIPT_BY_DUMB(46008, 59);
+ SET_SCRIPT_BY_DUMB(46009, 60);
+ SET_SCRIPT_BY_DUMB(46010, 61);
+ SET_SCRIPT_BY_DUMB(46011, 62);
+ SET_SCRIPT_BY_DUMB(46012, 63);
+ SET_SCRIPT_BY_DUMB(46013, 64);
+ SET_SCRIPT_BY_DUMB(46014, 65);
+ SET_SCRIPT_BY_DUMB(46015, 66);
+ SET_SCRIPT_BY_DUMB(46440, 64); // Same as 46013
+#undef SET_SCRIPT_BY_DUMB
+ // From now specific handlers for anything that is not a painting
+ SET_SCRIPT_BY_ID(41801);
+ SET_SCRIPT_BY_ID(41802);
+ //SET_SCRIPT_BY_ID(43145); // TODO: implement it
+ //SET_SCRIPT_BY_ID(43146); // TODO: implement it
+ //SET_SCRIPT_BY_ID(43160); // TODO: implement it
+ //SET_SCRIPT_BY_ID(43190); // TODO: implement it
+ //SET_SCRIPT_BY_ID(44071); // TODO: implement it
+ //SET_SCRIPT_BY_ID(44161); // TODO: implement it
+ //SET_SCRIPT_BY_ID(45130); // TODO: implement it // Almost dumb
+ //SET_SCRIPT_BY_ID(45270); // TODO: implement it
+ //SET_SCRIPT_BY_ID(45280); // TODO: implement it // Almost dumb
+ //SET_SCRIPT_BY_ID(88001); // TODO: implement it
+ //SET_SCRIPT_BY_ID(88002); // TODO: implement it
+ //SET_SCRIPT_BY_ID(88003); // TODO: implement it
+ //SET_SCRIPT_BY_ID(88004); // TODO: implement it
+#undef SET_SCRIPT_BY_ID
+}
+
+// Generic handler for dumb fixed images
+template<unsigned int ID>
+void CryOmni3DEngine_Versailles::genericDumbImage(ZonFixedImage *fimg) {
+ fimg->load(imagesPaintings[ID]);
+ while (1) {
+ fimg->manage();
+ if (fimg->_exit || fimg->_zoneLow) {
+ fimg->_exit = true;
+ break;
+ }
+ }
+}
+
+// Generic handler for interrogation mark action: display the painting title
+#define HANDLE_QUESTION(ID) \
+ do { \
+ if (fimg->_zoneQuestion) { \
+ displayMessageBox(kFixedimageMsgBoxParameters, fimg->surface(), _paintingsTitles[ID], Common::Point(600, 400), \
+ Common::Functor0Mem<void, ZonFixedImage>(fimg, &ZonFixedImage::manage)); \
+ } \
+ } while (false)
+
+// Generic handler for paintings fixed images
+template<unsigned int ID>
+void CryOmni3DEngine_Versailles::genericPainting(ZonFixedImage *fimg) {
+ fimg->load(imagesPaintings[ID]);
+ while (1) {
+ fimg->manage();
+ if (fimg->_exit || fimg->_zoneLow) {
+ fimg->_exit = true;
+ break;
+ }
+ HANDLE_QUESTION(ID);
+ }
+}
+
+// Specific fixed images callbacks
+#define IMG_CB(name) void CryOmni3DEngine_Versailles::img_ ## name(ZonFixedImage *fimg)
+
+IMG_CB(31142) {
+ fimg->load("10D2_4.GIF");
+ while (1) {
+ fimg->manage();
+ if (fimg->_exit || fimg->_zoneLow) {
+ fimg->_exit = true;
+ break;
+ }
+ if (fimg->_zoneUse) {
+ displayMessageBox(kFixedimageMsgBoxParameters, fimg->surface(), 7,
+ fimg->getZoneCenter(fimg->_currentZone),
+ Common::Functor0Mem<void, ZonFixedImage>(fimg, &ZonFixedImage::manage));
+ }
+ }
+}
+
+IMG_CB(31142b) {
+ fimg->load("11D2_2.GIF");
+ while (1) {
+ fimg->manage();
+ if (fimg->_exit || fimg->_zoneLow) {
+ fimg->_exit = true;
+ break;
+ }
+ if (fimg->_zoneUse) {
+ if (_gameVariables[GameVariables::kCollectScissors] || _inventory.inInventoryByNameId(94)) {
+ // Empty drawer
+ ZonFixedImage::CallbackFunctor *functor =
+ new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this,
+ &CryOmni3DEngine_Versailles::img_31142d);
+ fimg->changeCallback(functor);
+ break;
+ } else {
+ // Drawer with scissors in it
+ ZonFixedImage::CallbackFunctor *functor =
+ new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this,
+ &CryOmni3DEngine_Versailles::img_31142c);
+ fimg->changeCallback(functor);
+ break;
+ }
+ }
+ }
+}
+
+IMG_CB(31142c) {
+ fimg->load("11D2_21.GIF");
+ while (1) {
+ fimg->manage();
+ if (fimg->_exit || fimg->_zoneLow) {
+ fimg->_exit = true;
+ break;
+ }
+ if (fimg->_zoneUse) {
+ if (!_inventory.inInventoryByNameId(94) && !_gameVariables[GameVariables::kCollectScissors]) {
+ collectObject(94, fimg);
+ }
+ _gameVariables[GameVariables::kCollectScissors] = 1;
+ // Display empty drawer
+ ZonFixedImage::CallbackFunctor *functor =
+ new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this,
+ &CryOmni3DEngine_Versailles::img_31142d);
+ fimg->changeCallback(functor);
+ break;
+ }
+ }
+}
+
+IMG_CB(31142d) {
+ fimg->load("11D2_22.GIF");
+ while (1) {
+ fimg->manage();
+ if (fimg->_exit || fimg->_zoneLow) {
+ fimg->_exit = true;
+ break;
+ }
+ if (fimg->_zoneUse) {
+ // Close drawer
+ ZonFixedImage::CallbackFunctor *functor =
+ new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this,
+ &CryOmni3DEngine_Versailles::img_31142b);
+ fimg->changeCallback(functor);
+ break;
+ }
+ }
+}
+
+IMG_CB(31143) {
+ fimg->load("10D2_3.GIF");
+ while (1) {
+ fimg->manage();
+ if (fimg->_exit || fimg->_zoneLow) {
+ fimg->_exit = true;
+ break;
+ }
+ if (fimg->_zoneUse) {
+ displayMessageBox(kFixedimageMsgBoxParameters, fimg->surface(), 7,
+ fimg->getZoneCenter(fimg->_currentZone),
+ Common::Functor0Mem<void, ZonFixedImage>(fimg, &ZonFixedImage::manage));
+ }
+ }
+}
+
+IMG_CB(31143b) {
+ fimg->load("11D2_1.GIF");
+ while (1) {
+ fimg->manage();
+ if (fimg->_exit || fimg->_zoneLow) {
+ fimg->_exit = true;
+ break;
+ }
+ if (fimg->_zoneUse) {
+ if (_inventory.inInventoryByNameId(96)) {
+ // Empty drawer
+ ZonFixedImage::CallbackFunctor *functor =
+ new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this,
+ &CryOmni3DEngine_Versailles::img_31143d);
+ fimg->changeCallback(functor);
+ break;
+ } else {
+ // Drawer with pamphlet about arts in it
+ ZonFixedImage::CallbackFunctor *functor =
+ new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this,
+ &CryOmni3DEngine_Versailles::img_31143c);
+ fimg->changeCallback(functor);
+ break;
+ }
+ }
+ }
+}
+
+IMG_CB(31143c) {
+ fimg->load("11D2_11.GIF");
+ while (1) {
+ fimg->manage();
+ if (fimg->_exit || fimg->_zoneLow) {
+ fimg->_exit = true;
+ break;
+ }
+ if (fimg->_zoneUse) {
+ if (!_inventory.inInventoryByNameId(96)) {
+ collectObject(96, fimg);
+ }
+ // Display empty drawer
+ ZonFixedImage::CallbackFunctor *functor =
+ new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this,
+ &CryOmni3DEngine_Versailles::img_31143d);
+ fimg->changeCallback(functor);
+ break;
+ }
+ }
+}
+
+IMG_CB(31143d) {
+ fimg->load("11D2_12.GIF");
+ while (1) {
+ fimg->manage();
+ if (fimg->_exit || fimg->_zoneLow) {
+ fimg->_exit = true;
+ break;
+ }
+ if (fimg->_zoneUse) {
+ // Close drawer
+ ZonFixedImage::CallbackFunctor *functor =
+ new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this,
+ &CryOmni3DEngine_Versailles::img_31143b);
+ fimg->changeCallback(functor);
+ break;
+ }
+ }
+}
+
+IMG_CB(41202) {
+ fimg->load("10E_20.GIF");
+ while (1) {
+ fimg->manage();
+ if (fimg->_exit || fimg->_zoneLow) {
+ fimg->_exit = true;
+ break;
+ }
+ HANDLE_QUESTION(1);
+ if (fimg->_zoneUse) {
+ if (fimg->_currentZone == 2 && !_inventory.inInventoryByNameId(97)) {
+ // Open the jar
+ ZonFixedImage::CallbackFunctor *functor =
+ new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this,
+ &CryOmni3DEngine_Versailles::img_41202b);
+ fimg->changeCallback(functor);
+ break;
+ } else {
+ displayMessageBox(kFixedimageMsgBoxParameters, fimg->surface(), 11,
+ fimg->getZoneCenter(fimg->_currentZone),
+ Common::Functor0Mem<void, ZonFixedImage>(fimg, &ZonFixedImage::manage));
+ }
+ }
+ }
+}
+
+IMG_CB(41202b) {
+ fimg->load("10E_21.GIF");
+ while (1) {
+ fimg->manage();
+ if (fimg->_exit) {
+ break;
+ }
+ HANDLE_QUESTION(1);
+ if (fimg->_zoneLow) {
+ // Go back to jars closed
+ ZonFixedImage::CallbackFunctor *functor =
+ new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this,
+ &CryOmni3DEngine_Versailles::img_41202);
+ fimg->changeCallback(functor);
+ break;
+ }
+ if (fimg->_zoneUse) {
+ if (!_inventory.inInventoryByNameId(97)) {
+ collectObject(97, fimg);
+ }
+ // Go back to jars closed
+ ZonFixedImage::CallbackFunctor *functor =
+ new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this,
+ &CryOmni3DEngine_Versailles::img_41202);
+ fimg->changeCallback(functor);
+ break;
+ }
+ }
+}
+
+IMG_CB(41801) {
+ fimg->load("12E2_10.GIF");
+ while (1) {
+ fimg->manage();
+ if (fimg->_exit || fimg->_zoneLow) {
+ fimg->_exit = true;
+ break;
+ }
+ if (fimg->_currentZone == 0) {
+ bool open = false;
+ if (fimg->_zoneUse) {
+ // Using without object
+ if (_gameVariables[GameVariables::kUsedScissors]) {
+ open = true;
+ } else {
+ // Closed
+ displayMessageBox(kFixedimageMsgBoxParameters, fimg->surface(), 8,
+ fimg->getZoneCenter(fimg->_currentZone),
+ Common::Functor0Mem<void, ZonFixedImage>(fimg, &ZonFixedImage::manage));
+ }
+ } else if (fimg->_usedObject && fimg->_usedObject->idOBJ() == 94) {
+ _gameVariables[GameVariables::kUsedScissors] = 1;
+ _inventory.removeByNameId(94);
+ open = true;
+ }
+ if (open) {
+ if (_gameVariables[GameVariables::kCollectedPaperInTrunk]) {
+ // Display empty trunk
+ ZonFixedImage::CallbackFunctor *functor =
+ new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this,
+ &CryOmni3DEngine_Versailles::img_41801c);
+ fimg->changeCallback(functor);
+ break;
+ } else {
+ // Display trunk with paper in it
+ // Animate opening
+ playInGameVideo("12E2_11");
+ // Force reload of the place
+ if (_nextPlaceId == -1u) {
+ _nextPlaceId = _currentPlaceId;
+ }
+
+ ZonFixedImage::CallbackFunctor *functor =
+ new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this,
+ &CryOmni3DEngine_Versailles::img_41801b);
+ fimg->changeCallback(functor);
+ break;
+ }
+ }
+ }
+ }
+}
+
+IMG_CB(41801b) {
+ fimg->load("12E2_11.GIF");
+ while (1) {
+ fimg->manage();
+ if (fimg->_exit) {
+ break;
+ }
+ if (fimg->_zoneLow) {
+ // Animate closing
+ playInGameVideo("12E2_13");
+ // Force reload of the place
+ if (_nextPlaceId == -1u) {
+ _nextPlaceId = _currentPlaceId;
+ }
+ fimg->_exit = true;
+ break;
+ }
+ if (fimg->_zoneUse) {
+ if (!_inventory.inInventoryByNameId(100)) {
+ collectObject(100, fimg);
+ }
+ _gameVariables[GameVariables::kCollectedPaperInTrunk] = 1;
+
+ // Go to empty trunk
+ ZonFixedImage::CallbackFunctor *functor =
+ new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this,
+ &CryOmni3DEngine_Versailles::img_41801c);
+ fimg->changeCallback(functor);
+ break;
+ }
+ }
+}
+
+IMG_CB(41801c) {
+ fimg->load("12E2_12.GIF");
+ while (1) {
+ fimg->manage();
+ if (fimg->_exit) {
+ break;
+ }
+ if (fimg->_zoneLow) {
+ // Animate closing
+ playInGameVideo("12E2_13");
+ // Force reload of the place
+ if (_nextPlaceId == -1u) {
+ _nextPlaceId = _currentPlaceId;
+ }
+ fimg->_exit = true;
+ break;
+ }
+ }
+}
+
+IMG_CB(41802) {
+ // Dispatch to the correct state
+ if (_gameVariables[GameVariables::kInkSpilled] &&
+ !_gameVariables[GameVariables::kCollectedPaperOnTable]) {
+ // Draw paper with ink on it
+ ZonFixedImage::CallbackFunctor *functor =
+ new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this,
+ &CryOmni3DEngine_Versailles::img_41802b);
+ fimg->changeCallback(functor);
+ return;
+ }
+ if (!_gameVariables[GameVariables::kInkSpilled] &&
+ _gameVariables[GameVariables::kCollectedPaperOnTable]) {
+ // Draw table with ink in inkpot and without paper
+ ZonFixedImage::CallbackFunctor *functor =
+ new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this,
+ &CryOmni3DEngine_Versailles::img_41802c);
+ fimg->changeCallback(functor);
+ return;
+ }
+ if (_gameVariables[GameVariables::kInkSpilled] &&
+ _gameVariables[GameVariables::kCollectedPaperOnTable]) {
+ // Draw table with ink directly on table
+ ZonFixedImage::CallbackFunctor *functor =
+ new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this,
+ &CryOmni3DEngine_Versailles::img_41802d);
+ fimg->changeCallback(functor);
+ return;
+ }
+
+ // There we have paper on table and ink is in its inkpot
+ fimg->load("12E2_20.GIF");
+ while (1) {
+ fimg->manage();
+ if (fimg->_exit || fimg->_zoneLow) {
+ fimg->_exit = true;
+ break;
+ }
+ if (fimg->_zoneUse && fimg->_currentZone == 1) {
+ // Collected paper
+ collectObject(95, fimg);
+ _gameVariables[GameVariables::kCollectedPaperOnTable] = 1;
+ setPlaceState(8, 1);
+ // Draw table with ink in inkpot and without paper
+ ZonFixedImage::CallbackFunctor *functor =
+ new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this,
+ &CryOmni3DEngine_Versailles::img_41802c);
+ fimg->changeCallback(functor);
+ break;
+ }
+ if (fimg->_zoneUse && fimg->_currentZone == 2) {
+ _gameVariables[GameVariables::kInkSpilled] = 1;
+ setPlaceState(8, 3);
+ // Draw paper with ink on it
+ ZonFixedImage::CallbackFunctor *functor =
+ new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this,
+ &CryOmni3DEngine_Versailles::img_41802b);
+ fimg->changeCallback(functor);
+ break;
+ }
+ if (fimg->_usedObject && fimg->_currentZone == 0) {
+ unsigned int objID = fimg->_usedObject->idOBJ();
+ if (objID == 100) {
+ playInGameVideo("12E2_24");
+ // Force reload of the place
+ if (_nextPlaceId == -1u) {
+ _nextPlaceId = _currentPlaceId;
+ }
+ _inventory.removeByNameId(100);
+ // Revealed paper
+ collectObject(98, fimg);
+ _gameVariables[GameVariables::kGotRevealedPaper] = 1;
+ setGameTime(3, 1);
+ } else if (objID == 96) {
+ // Pamphlet about arts
+ playInGameVideo("PAP_BRUL");
+ // Force reload of the place
+ if (_nextPlaceId == -1u) {
+ _nextPlaceId = _currentPlaceId;
+ }
+ doGameOver();
+ }
+ }
+ }
+}
+
+IMG_CB(41802b) {
+ // There we have paper on table with ink on it
+ fimg->load("12E2_21.GIF");
+ while (1) {
+ fimg->manage();
+ if (fimg->_exit || fimg->_zoneLow) {
+ fimg->_exit = true;
+ break;
+ }
+ if (fimg->_zoneUse && fimg->_currentZone == 1) {
+ // Collected paper with ink on it
+ collectObject(99, fimg);
+ _gameVariables[GameVariables::kCollectedPaperOnTable] = 1;
+ setPlaceState(8, 2);
+ // Draw table with ink spilled and without paper
+ ZonFixedImage::CallbackFunctor *functor =
+ new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this,
+ &CryOmni3DEngine_Versailles::img_41802d);
+ fimg->changeCallback(functor);
+ break;
+ }
+ if (fimg->_usedObject && fimg->_currentZone == 0) {
+ unsigned int objID = fimg->_usedObject->idOBJ();
+ if (objID == 100) {
+ playInGameVideo("12E2_24");
+ // Force reload of the place
+ if (_nextPlaceId == -1u) {
+ _nextPlaceId = _currentPlaceId;
+ }
+ _inventory.removeByNameId(100);
+ // Revealed paper
+ collectObject(98, fimg);
+ _gameVariables[GameVariables::kGotRevealedPaper] = 1;
+ setGameTime(3, 1);
+ } else if (objID == 96) {
+ // Pamphlet about arts
+ playInGameVideo("PAP_BRUL");
+ // Force reload of the place
+ if (_nextPlaceId == -1u) {
+ _nextPlaceId = _currentPlaceId;
+ }
+ doGameOver();
+ }
+ }
+ }
+}
+
+IMG_CB(41802c) {
+ // There we have ink in inkpot and without paper
+ fimg->load("12E2_22.GIF");
+ while (1) {
+ fimg->manage();
+ if (fimg->_exit || fimg->_zoneLow) {
+ fimg->_exit = true;
+ break;
+ }
+ if (fimg->_zoneUse && fimg->_currentZone == 1) {
+ _gameVariables[GameVariables::kInkSpilled] = 1;
+ setPlaceState(8, 2);
+ // Draw table with ink on it
+ ZonFixedImage::CallbackFunctor *functor =
+ new Common::Functor1Mem<ZonFixedImage *, void, CryOmni3DEngine_Versailles>(this,
+ &CryOmni3DEngine_Versailles::img_41802d);
+ fimg->changeCallback(functor);
+ break;
+ }
+ if (fimg->_usedObject && fimg->_currentZone == 0) {
+ unsigned int objID = fimg->_usedObject->idOBJ();
+ if (objID == 100) {
+ playInGameVideo("12E2_24");
+ // Force reload of the place
+ if (_nextPlaceId == -1u) {
+ _nextPlaceId = _currentPlaceId;
+ }
+ _inventory.removeByNameId(100);
+ // Revealed paper
+ collectObject(98, fimg);
+ _gameVariables[GameVariables::kGotRevealedPaper] = 1;
+ setGameTime(3, 1);
+ } else if (objID == 96) {
+ // Pamphlet about arts
+ playInGameVideo("PAP_BRUL");
+ // Force reload of the place
+ if (_nextPlaceId == -1u) {
+ _nextPlaceId = _currentPlaceId;
+ }
+ doGameOver();
+ }
+ }
+ }
+}
+
+IMG_CB(41802d) {
+ // There we have ink directly on table
+ fimg->load("12E2_23.GIF");
+ while (1) {
+ fimg->manage();
+ if (fimg->_exit || fimg->_zoneLow) {
+ fimg->_exit = true;
+ break;
+ }
+ if (fimg->_usedObject && fimg->_currentZone == 0) {
+ unsigned int objID = fimg->_usedObject->idOBJ();
+ if (objID == 100) {
+ playInGameVideo("12E2_24");
+ // Force reload of the place
+ if (_nextPlaceId == -1u) {
+ _nextPlaceId = _currentPlaceId;
+ }
+ _inventory.removeByNameId(100);
+ // Revealed paper
+ collectObject(98, fimg);
+ _gameVariables[GameVariables::kGotRevealedPaper] = 1;
+ setGameTime(3, 1);
+ } else if (objID == 96) {
+ // Pamphlet about arts
+ playInGameVideo("PAP_BRUL");
+ // Force reload of the place
+ if (_nextPlaceId == -1u) {
+ _nextPlaceId = _currentPlaceId;
+ }
+ doGameOver();
+ }
+ }
+ }
+}
+
+#undef IMG_CB
+
+// Init place and filter event
+#define FILTER_EVENT(level, place) bool CryOmni3DEngine_Versailles::filterEventLevel ## level ## Place ## place(unsigned int *event)
+#define INIT_PLACE(level, place) void CryOmni3DEngine_Versailles::initPlaceLevel ## level ## Place ## place()
+
+FILTER_EVENT(1, 1) {
+ if (*event > 0 && *event < 9999) {
+ _gameVariables[GameVariables::kWarnedIncomplete] = 0;
+ }
+ if (*event == 11015 && currentGameTime() < 3) {
+ return false;
+ } else {
+ return true;
+ }
+}
+
+FILTER_EVENT(1, 2) {
+ if (*event == 7 && currentGameTime() < 2) {
+ // Closed
+ displayMessageBoxWarp(2);
+ return false;
+ }
+
+ if (*event == 1 && currentGameTime() < 3) {
+ _dialogsMan.play("11E_HUI");
+ _forcePaletteUpdate = true;
+ // Force reload of the place
+ if (_nextPlaceId == -1u) {
+ _nextPlaceId = _currentPlaceId;
+ }
+ return false;
+ }
+
+ return true;
+}
+
+INIT_PLACE(1, 3) {
+ if (!_gameVariables[GameVariables::kHasPlayedLebrun]) {
+ Common::File *audioFile = new Common::File();
+ if (!audioFile->open("LEB001__.WAV")) {
+ warning("Failed to open sound file %s", "LEB001__.WAV");
+ delete audioFile;
+ return;
+ }
+
+ Audio::SeekableAudioStream *audioDecoder = Audio::makeWAVStream(audioFile, DisposeAfterUse::YES);
+ // We lost ownership of the audioFile just set it to nullptr and don't use it
+ audioFile = nullptr;
+ if (!audioDecoder) {
+ return;
+ }
+
+ _mixer->playStream(Audio::Mixer::kSpeechSoundType, nullptr, audioDecoder, SoundIds::kLeb001);
+ // We lost ownership of the audioDecoder just set it to nullptr and don't use it
+ audioDecoder = nullptr;
+
+ _gameVariables[GameVariables::kHasPlayedLebrun] = 1;
+ }
+}
+
+FILTER_EVENT(1, 3) {
+ if (*event == 11301) {
+ while (!g_engine->shouldQuit() && _mixer->isSoundIDActive(SoundIds::kLeb001)) {
+ g_system->updateScreen();
+ pollEvents();
+ }
+ clearKeys();
+ return true;
+ }
+
+ if (*event > 0 && *event < 10000) {
+ _mixer->stopID(SoundIds::kLeb001);
+ return true;
+ }
+ return true;
+}
+
+// Event 19 is not in this room: must be a leftover
+/*
+FILTER_EVENT(1, 7) {
+ if (*event == 19) {
+ // Too dark
+ displayMessageBoxWarp(7);
+ return false;
+ }
+
+ return true;
+}
+*/
+
+FILTER_EVENT(1, 14) {
+ if (*event == 31141 && _placeStates[14].state == 0) {
+ // Open the curtain
+ unsigned int fakePlaceId = getFakeTransition(*event);
+ fakeTransition(fakePlaceId);
+ playInGameVideo("10D2_1");
+ setPlaceState(14, 1);
+ // setPlaceState will force reload
+ // Don't pass the event as we try to avoid implementing use
+ return false;
+ }
+
+ if (*event != 31142 && *event != 31143) {
+ // Not for us
+ return true;
+ }
+
+ const char *video;
+ FixedImgCallback callback;
+
+ if (_currentLevel == 1 && _placeStates[14].state == 0) {
+ if (*event == 31142) {
+ video = "10D2_4";
+ callback = &CryOmni3DEngine_Versailles::img_31142;
+ } else if (*event == 31143) {
+ video = "10D2_3";
+ callback = &CryOmni3DEngine_Versailles::img_31143;
+ }
+ } else if (_currentLevel == 2 || _placeStates[14].state == 1) {
+ if (*event == 31142) {
+ video = "11D2_2";
+ callback = &CryOmni3DEngine_Versailles::img_31142b;
+ } else if (*event == 31143) {
+ video = "11D2_1";
+ callback = &CryOmni3DEngine_Versailles::img_31143b;
+ }
+ } else {
+ error("Invalid state in filter event 1/14: level: %d/ placeState: %d", _currentLevel,
+ _placeStates[14].state);
+ }
+
+ unsigned int fakePlaceId = getFakeTransition(*event);
+ fakeTransition(fakePlaceId);
+
+ playInGameVideo(video);
+
+ // Force reload of the place
+ if (_nextPlaceId == -1u) {
+ _nextPlaceId = _currentPlaceId;
+ }
+
+ handleFixedImg(callback);
+
+ // Don't pass the event as we try to avoid implementing use
+ return false;
+}
+
+#undef FILTER_EVENT
+#undef INIT_PLACE
+
+} // End of namespace Versailles
+} // End of namespace CryOmni3D
diff --git a/engines/cryomni3d/versailles/menus.cpp b/engines/cryomni3d/versailles/menus.cpp
new file mode 100644
index 0000000000..a60726f1a8
--- /dev/null
+++ b/engines/cryomni3d/versailles/menus.cpp
@@ -0,0 +1,1066 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+#include "audio/audiostream.h"
+#include "audio/decoders/wave.h"
+#include "common/config-manager.h"
+#include "common/file.h"
+#include "common/system.h"
+#include "graphics/managed_surface.h"
+#include "graphics/palette.h"
+#include "image/bmp.h"
+#include "image/image_decoder.h"
+
+#include "cryomni3d/mouse_boxes.h"
+#include "cryomni3d/font_manager.h"
+
+#include "cryomni3d/versailles/engine.h"
+
+namespace CryOmni3D {
+namespace Versailles {
+
+bool CryOmni3DEngine_Versailles::showSubtitles() const {
+ return ConfMan.getBool("subtitles");
+}
+
+void CryOmni3DEngine_Versailles::drawMenuTitle(Graphics::ManagedSurface *surface, byte color) {
+ int offY;
+
+ int oldFont = _fontManager.getCurrentFont();
+ _fontManager.setSurface(surface);
+ _fontManager.setForeColor(color);
+ _fontManager.setCurrentFont(1);
+ offY = _fontManager.getFontMaxHeight();
+ _fontManager.displayStr(144, 160 - offY, _messages[23]);
+ _fontManager.setCurrentFont(3);
+ offY = _fontManager.getFontMaxHeight();
+ _fontManager.displayStr(305, 160 - offY, _messages[24]);
+
+ surface->vLine(100, 146, 172, color);
+ surface->hLine(100, 172, 168, color); // minus 1 because hLine draws inclusive
+
+ _fontManager.setCurrentFont(oldFont);
+}
+
+unsigned int CryOmni3DEngine_Versailles::displayOptions() {
+ Common::Array<int> menuEntries;
+ menuEntries.push_back(26);
+ menuEntries.push_back(27);
+ menuEntries.push_back(28);
+ menuEntries.push_back(29);
+ menuEntries.push_back(48);
+ menuEntries.push_back(30);
+ menuEntries.push_back(32);
+#if 0
+ // Music on HDD setting
+ menuEntries.push_back(34);
+#endif
+ menuEntries.push_back(25);
+ menuEntries.push_back(-42);
+ menuEntries.push_back(43);
+ menuEntries.push_back(40);
+ // 1 is for volume box
+ MouseBoxes boxes(menuEntries.size() + 1);
+
+ bool end = false;
+
+ int drawState = 1;
+
+ unsigned int volumeCursorMiddleY = _sprites.getCursor(102).getHeight() / 2;
+ unsigned int volume = CLIP(ConfMan.getInt("sfx_volume"), 0, 256);
+ unsigned int soundVolumeY = ((283 * (256 - volume)) >> 8) + 101;
+ byte volumeForeColor = 243;
+
+ Graphics::ManagedSurface optionsSurface;
+ Image::ImageDecoder *imageDecoder = loadHLZ("option.hlz");
+ const Graphics::Surface *bgFrame = imageDecoder->getSurface();
+
+ optionsSurface.create(bgFrame->w, bgFrame->h, bgFrame->format);
+
+ setCursor(181);
+ g_system->showMouse(true);
+
+ unsigned int hoveredBox = -1;
+ unsigned int selectedBox;
+ int selectedMsg = 0;
+ unsigned int volumeBox;
+ bool resetScreen = true;
+ bool forceEvents = true;
+
+ while (!g_engine->shouldQuit() && !end) {
+ if (resetScreen) {
+ setPalette(imageDecoder->getPalette(), imageDecoder->getPaletteStartIndex(),
+ imageDecoder->getPaletteColorCount());
+ // _cursorPalette has only 248 colors as 8 last colors are for translucency
+ setPalette(_cursorPalette + 240 * 3, 240, 8);
+
+ _fontManager.setCurrentFont(3);
+ _fontManager.setTransparentBackground(true);
+ _fontManager.setForeColor(243);
+ _fontManager.setLineHeight(14);
+ _fontManager.setSpaceWidth(0);
+ _fontManager.setCharSpacing(1);
+ _fontManager.setSurface(&optionsSurface);
+ resetScreen = false;
+ }
+ if (drawState > 0) {
+ if (drawState == 1) {
+ optionsSurface.blitFrom(*bgFrame);
+ }
+ drawMenuTitle(&optionsSurface, 243);
+ _fontManager.setForeColor(volumeForeColor);
+ _fontManager.displayStr(550, 407, _messages[39]);
+ optionsSurface.vLine(544, 402, 429, volumeForeColor);
+ optionsSurface.hLine(544, 429, 613, volumeForeColor); // minus 1 because hLine draws inclusive
+
+ boxes.reset();
+ unsigned int boxId = 0;
+ unsigned int top = 195;
+ unsigned int bottom;
+ unsigned int width;
+
+ for (Common::Array<int>::iterator it = menuEntries.begin(); it != menuEntries.end(); it++) {
+ if (*it == 30 && !ConfMan.getBool("subtitles")) {
+ *it = 31;
+ } else if (*it == 32 && (ConfMan.getBool("mute") ||
+ ConfMan.getBool("music_mute"))) {
+ *it = 33;
+ }
+#if 0
+ else if (*it == 34) {
+ // What to do with music on HDD setting?
+ }
+#endif
+ else if (*it == 26 && !_isPlaying) {
+ *it = -26;
+ } else if (*it == 29 && !_isPlaying) {
+ *it = -29;
+ } else if (*it == -42 && canVisit()) {
+ *it = 42;
+ } else if (*it == 48) {
+ unsigned int omni3D_speed = ConfMan.getInt("omni3d_speed");
+ switch (omni3D_speed) {
+ case 1:
+ *it = 51;
+ break;
+ case 2:
+ *it = 52;
+ break;
+ case 3:
+ *it = 49;
+ break;
+ case 4:
+ *it = 50;
+ break;
+ }
+ }
+
+ if (*it > 0) {
+ int msgId = *it;
+ bottom = top;
+ top += 24;
+
+ // Patch on the fly the text displayed
+ if (_isVisiting) {
+ if (msgId == 26) {
+ msgId = 44;
+ } else if (msgId == 29) {
+ msgId = 45;
+ }
+ }
+
+ width = _fontManager.getStrWidth(_messages[msgId]);
+ //Common::Rect rct(144, top - 39, width + 144, bottom);
+ //optionsSurface.frameRect(rct, 0);
+ boxes.setupBox(boxId, 144, top - 39, width + 144, bottom);
+ if (boxId == hoveredBox) {
+ _fontManager.setForeColor(240);
+ } else {
+ _fontManager.setForeColor(243);
+ }
+ _fontManager.displayStr(144, top - 39, _messages[msgId]);
+ }
+ boxId++;
+ }
+
+ volumeBox = boxId;
+ boxes.setupBox(boxId, 525, 101, 570, 401);
+ optionsSurface.transBlitFrom(_sprites.getSurface(102), Common::Point(553, soundVolumeY),
+ _sprites.getKeyColor(102));
+
+ g_system->copyRectToScreen(optionsSurface.getPixels(), optionsSurface.pitch, 0, 0, optionsSurface.w,
+ optionsSurface.h);
+ drawState = 0;
+ }
+ g_system->updateScreen();
+
+ if (pollEvents() || forceEvents) { // always call pollEvents
+ forceEvents = false;
+ Common::Point mouse = getMousePos();
+ unsigned int boxId = 0;
+ Common::Array<int>::iterator it;
+ for (it = menuEntries.begin(); it != menuEntries.end(); it++) {
+ if (boxes.hitTest(boxId, mouse)) {
+ if (hoveredBox != boxId) {
+ hoveredBox = boxId;
+ drawState = 2;
+ }
+ // We met a hit, no need to look further
+ break;
+ }
+ boxId++;
+ }
+ if (it != menuEntries.end()) {
+ if (getDragStatus() == 2) {
+ selectedMsg = *it;
+ selectedBox = hoveredBox;
+ }
+ } else {
+ // no menu selected, check volume
+ if (boxes.hitTest(volumeBox, mouse)) {
+ if (volumeForeColor != 240) {
+ volumeForeColor = 240;
+ drawState = 1;
+ }
+ if (getCurrentMouseButton() == 1) {
+ if (soundVolumeY != getMousePos().y - volumeCursorMiddleY) {
+ soundVolumeY = CLIP(getMousePos().y - volumeCursorMiddleY, 101u, 384u);
+ drawState = 1;
+ volume = CLIP(((384 - soundVolumeY) << 8) / 283, 0u, 256u);
+ // Global setting
+ ConfMan.setInt("music_volume", volume);
+ ConfMan.setInt("speech_volume", volume);
+ ConfMan.setInt("sfx_volume", volume);
+ syncSoundSettings();
+ }
+ } else if (getDragStatus() == 2 &&
+ !_mixer->hasActiveChannelOfType(Audio::Mixer::kMusicSoundType) &&
+ _mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType) > 0) {
+ // Finished dragging
+ _mixer->stopID(SoundIds::kOrgue);
+ do {
+ Common::File *audioFile = new Common::File();
+ if (!audioFile->open("ORGUE.WAV")) {
+ warning("Failed to open sound file %s", "ORGUE.WAV");
+ delete audioFile;
+ break;
+ }
+
+ Audio::SeekableAudioStream *audioDecoder = Audio::makeWAVStream(audioFile, DisposeAfterUse::YES);
+ // We lost ownership of the audioFile just set it to nullptr and don't use it
+ audioFile = nullptr;
+ if (!audioDecoder) {
+ break;
+ }
+
+ _mixer->playStream(Audio::Mixer::kSFXSoundType, nullptr, audioDecoder, SoundIds::kOrgue);
+ // We lost ownership of the audioDecoder just set it to nullptr and don't use it
+ audioDecoder = nullptr;
+ } while (false);
+ }
+ } else {
+ if (hoveredBox != -1u) {
+ hoveredBox = -1;
+ drawState = 2;
+ }
+ if (volumeForeColor != 243) {
+ volumeForeColor = 243;
+ drawState = 1;
+ }
+ }
+ }
+ if (getNextKey().keycode == Common::KEYCODE_ESCAPE && _isPlaying) {
+ selectedMsg = 26;
+ }
+ if (selectedMsg == 27 || selectedMsg == 28 || selectedMsg == 40 || selectedMsg == 42) {
+ // New game, Load game, Quit, Visit
+ if (!_isPlaying || _isVisiting) {
+ end = true;
+ } else {
+ end = displayYesNoBox(optionsSurface, Common::Rect(235, 420, 505, 465), 57);
+ }
+ drawState = 1;
+ if (end) {
+ _isPlaying = false;
+ } else {
+ selectedMsg = 0;
+ }
+ }
+ if (selectedMsg == 25) {
+ // Documentation area
+ _docManager.handleDocArea();
+ drawState = 1;
+ resetScreen = true;
+ forceEvents = true;
+ waitMouseRelease();
+ selectedMsg = 0;
+ } else if (selectedMsg == 26) {
+ // Continue game
+ end = true;
+ } else if (selectedMsg == 28) {
+ Common::String saveName;
+ bool wasVisiting = _isVisiting;
+ _isVisiting = false;
+ unsigned int saveNumber = displayFilePicker(bgFrame, false, saveName);
+ if (saveNumber == -1u) {
+ _isVisiting = wasVisiting;
+ drawState = 1;
+ selectedMsg = 0;
+ } else {
+ _loadedSave = saveNumber;
+ _isPlaying = false;
+ end = true;
+ }
+ waitMouseRelease();
+ } else if (selectedMsg == 42) {
+ Common::String saveName;
+ bool wasVisiting = _isVisiting;
+ _isVisiting = true;
+ unsigned int saveNumber = displayFilePicker(bgFrame, false, saveName);
+ if (saveNumber == -1u) {
+ _isVisiting = wasVisiting;
+ drawState = 1;
+ selectedMsg = 0;
+ } else {
+ _loadedSave = saveNumber;
+ _isPlaying = false;
+ end = true;
+ }
+ waitMouseRelease();
+ } else if (selectedMsg == 29) {
+ Common::String saveName;
+ unsigned int saveNumber = displayFilePicker(bgFrame, true, saveName);
+ if (saveNumber != -1u) {
+ saveGame(_isVisiting, saveNumber, saveName);
+ }
+ drawState = 1;
+ selectedMsg = 0;
+ waitMouseRelease();
+ } else if (selectedMsg == 30) {
+ ConfMan.setBool("subtitles", false);
+ drawState = 1;
+ menuEntries[selectedBox] = 31;
+ selectedMsg = 0;
+ waitMouseRelease();
+ } else if (selectedMsg == 31) {
+ ConfMan.setBool("subtitles", true);
+ drawState = 1;
+ menuEntries[selectedBox] = 30;
+ selectedMsg = 0;
+ waitMouseRelease();
+ } else if (selectedMsg == 32) {
+ ConfMan.setBool("music_mute", true);
+ syncSoundSettings();
+ drawState = 1;
+ menuEntries[selectedBox] = 33;
+ selectedMsg = 0;
+ waitMouseRelease();
+ } else if (selectedMsg == 33) {
+ ConfMan.setBool("mute", false);
+ ConfMan.setBool("music_mute", false);
+ syncSoundSettings();
+ drawState = 1;
+ menuEntries[selectedBox] = 32;
+ selectedMsg = 0;
+ waitMouseRelease();
+ }
+#if 0
+ // Music on disk settings
+ else if (selectedMsg == 35) {
+ drawState = 1;
+ menuEntries[selectedBox] = 34;
+ selectedMsg = 0;
+ waitMouseRelease();
+ } else if (selectedMsg == 34) {
+ drawState = 1;
+ menuEntries[selectedBox] = 36;
+ selectedMsg = 0;
+ waitMouseRelease();
+ } else if (selectedMsg == 36) {
+ drawState = 1;
+ menuEntries[selectedBox] = 35;
+ selectedMsg = 0;
+ waitMouseRelease();
+ }
+#endif
+ else if (selectedMsg == 39) {
+ // Volume
+ selectedMsg = 0;
+ } else if (selectedMsg == 47) {
+ // Unknown
+ selectedMsg = 0;
+ } else if (selectedMsg == 48) {
+ ConfMan.setInt("omni3d_speed", 1);
+ drawState = 1;
+ menuEntries[selectedBox] = 51;
+ selectedMsg = 0;
+ waitMouseRelease();
+ } else if (selectedMsg == 51) {
+ ConfMan.setInt("omni3d_speed", 2);
+ drawState = 1;
+ menuEntries[selectedBox] = 52;
+ selectedMsg = 0;
+ waitMouseRelease();
+ } else if (selectedMsg == 52) {
+ ConfMan.setInt("omni3d_speed", 3);
+ drawState = 1;
+ menuEntries[selectedBox] = 49;
+ selectedMsg = 0;
+ waitMouseRelease();
+ } else if (selectedMsg == 49) {
+ ConfMan.setInt("omni3d_speed", 4);
+ drawState = 1;
+ menuEntries[selectedBox] = 50;
+ selectedMsg = 0;
+ waitMouseRelease();
+ } else if (selectedMsg == 50) {
+ ConfMan.setInt("omni3d_speed", 0);
+ drawState = 1;
+ menuEntries[selectedBox] = 48;
+ selectedMsg = 0;
+ waitMouseRelease();
+ } else if (selectedMsg == 43) {
+ displayCredits();
+ drawState = 1;
+ resetScreen = true;
+ forceEvents = true;
+ selectedMsg = 0;
+ waitMouseRelease();
+ }
+ }
+ }
+
+ g_system->showMouse(false);
+
+ if (selectedMsg == 42) {
+ _abortCommand = AbortLoadGame;
+ // For return value
+ selectedMsg = 28;
+ } else if (selectedMsg == 28) {
+ _abortCommand = AbortLoadGame;
+ } else if (selectedMsg == 40) {
+ _abortCommand = AbortQuit;
+ } else if (selectedMsg == 27) {
+ _abortCommand = AbortNewGame;
+ _isVisiting = false;
+ } else if (g_engine->shouldQuit()) {
+ // Fake a quit
+ selectedMsg = 40;
+ _abortCommand = AbortQuit;
+ }
+
+ ConfMan.flushToDisk();
+ syncOmni3DSettings();
+ musicUpdate();
+
+ delete imageDecoder;
+ return selectedMsg;
+}
+
+unsigned int CryOmni3DEngine_Versailles::displayYesNoBox(Graphics::ManagedSurface &surface,
+ const Common::Rect &position, unsigned int msg_id) {
+ unsigned int confirmWidth = _fontManager.getStrWidth(_messages[53]);
+ unsigned int cancelWidth = _fontManager.getStrWidth(_messages[54]);
+ unsigned int oldFont = _fontManager.getCurrentFont();
+
+ _fontManager.setSurface(&surface);
+ _fontManager.setForeColor(240);
+ _fontManager.setLineHeight(20);
+ surface.frameRect(position, 243);
+
+ _fontManager.setupBlock(Common::Rect(position.left + 5, position.top + 5, position.right - 5,
+ position.bottom - 5));
+ _fontManager.setCurrentFont(5);
+ _fontManager.displayBlockText(_messages[msg_id]);
+ _fontManager.setCurrentFont(3);
+
+ MouseBoxes boxes(2);
+ boxes.setupBox(1, position.left + 5, position.bottom - 15, position.left + confirmWidth,
+ position.bottom, &_messages[53]);
+ boxes.setupBox(0, position.right - cancelWidth - 5, position.bottom - 15, position.right,
+ position.bottom, &_messages[54]);
+
+ bool end = false;
+ bool redraw = true;
+ unsigned int result = -1u;
+
+ while (!end || redraw) {
+ if (redraw) {
+ for (unsigned int boxId = 0; boxId < 2; boxId++) {
+ if (boxId == result) {
+ _fontManager.setForeColor(240);
+ } else {
+ _fontManager.setForeColor(243);
+ }
+ boxes.display(boxId, _fontManager);
+ }
+ redraw = false;
+
+ g_system->copyRectToScreen(surface.getPixels(), surface.pitch, 0, 0, surface.w, surface.h);
+ }
+ g_system->updateScreen();
+
+ if (pollEvents()) {
+ Common::Point mouse = getMousePos();
+ unsigned int hit_result = -1u;
+ if (boxes.hitTest(1, mouse)) {
+ hit_result = 1;
+ } else if (boxes.hitTest(0, mouse)) {
+ hit_result = 0;
+ }
+ if (!end && hit_result != result) {
+ result = hit_result;
+ redraw = true;
+ }
+ if ((getCurrentMouseButton() == 1) && (result != -1u)) {
+ end = true;
+ }
+ Common::KeyCode keyPressed = getNextKey().keycode;
+ if (keyPressed == Common::KEYCODE_ESCAPE) {
+ result = 0;
+ redraw = true;
+ end = true;
+ } else if (keyPressed == Common::KEYCODE_RETURN) {
+ result = 1;
+ redraw = true;
+ end = true;
+ }
+ }
+ }
+ _fontManager.setCurrentFont(oldFont);
+ return result;
+}
+
+unsigned int CryOmni3DEngine_Versailles::displayFilePicker(const Graphics::Surface *bgFrame,
+ bool saveMode, Common::String &saveName) {
+ Graphics::ManagedSurface surface(bgFrame->w, bgFrame->h, bgFrame->format);
+ surface.blitFrom(*bgFrame);
+
+ drawMenuTitle(&surface, 243);
+
+ int subtitleId;
+ if (_isVisiting) {
+ subtitleId = saveMode ? 45 : 46;
+ } else {
+ subtitleId = saveMode ? 29 : 28;
+ }
+ _fontManager.displayStr(164, 214, _messages[subtitleId]);
+
+ // Draw an empty screen before we list saves
+ g_system->showMouse(false);
+ g_system->copyRectToScreen(surface.getPixels(), surface.pitch, 0, 0, surface.w, surface.h);
+ g_system->updateScreen();
+
+ Common::Array<Common::String> savesList;
+ getSavesList(_isVisiting, savesList);
+ Common::String saveNameBackup;
+
+ g_system->showMouse(true);
+
+ MouseBoxes boxes(10); // 6 files + Yes/No/Up/Down buttons
+
+ // Yes/No buttons
+ const Common::String &okMsg = _messages[53];
+ unsigned int okWidth = _fontManager.getStrWidth(okMsg);
+ boxes.setupBox(6, 246, 430, 246 + okWidth, 450, &okMsg);
+ const Common::String &cancelMsg = _messages[54];
+ unsigned int cancelWidth = _fontManager.getStrWidth(cancelMsg);
+ boxes.setupBox(7, 146, 430, 146 + cancelWidth, 450, &cancelMsg);
+
+ // Up/Down buttons
+ boxes.setupBox(8, 428, 320, 448, 340);
+ boxes.setupBox(9, 428, 360, 448, 380);
+ surface.transBlitFrom(_sprites.getSurface(162), Common::Point(428, 320), _sprites.getKeyColor(162));
+ surface.transBlitFrom(_sprites.getSurface(185), Common::Point(428, 360), _sprites.getKeyColor(185));
+
+ setCursor(181);
+
+ unsigned int fileListOffset = 0; // TODO: store in config
+
+ unsigned int boxHovered = -1;
+ unsigned int boxSelected = -1;
+
+ bool textCursorState = false;
+ unsigned int textCursorNextState = 0;
+ unsigned int textCursorPos = -1;
+
+ bool autoRepeatInhibit = false;
+ unsigned int autoRepeatDelay = 250;
+ unsigned int autoRepeatEndInhibit = 0;
+
+ bool finished = false;
+ bool filesListChanged = true;
+ bool redraw = false;
+ while (!finished) {
+ if (filesListChanged || redraw) {
+ if (filesListChanged) {
+ for (unsigned int file = 0, fileY = 280; file < 6; file++, fileY += 20) {
+ boxes.setupBox(file, 146, fileY, 408, fileY + 14, &savesList[file + fileListOffset]);
+ }
+ // Redraw background as file list changed
+ surface.blitFrom(*bgFrame, Common::Rect(116, 280, 408, 400), Common::Point(116, 280));
+ filesListChanged = false;
+ }
+ // Don't redraw the scroll buttons
+ for (unsigned int box = 0; box < 8; box++) {
+ if (box == boxSelected) {
+ // Selected
+ _fontManager.setForeColor(240);
+ } else if (box == 6 && boxSelected == -1u) {
+ // Ok and no file selected
+ _fontManager.setForeColor(245);
+ } else if (box == boxHovered) {
+ // Hovered
+ _fontManager.setForeColor(241);
+ } else {
+ // Other cases
+ _fontManager.setForeColor(243);
+ }
+
+ if (box == boxSelected && saveMode) {
+ Common::Rect boxRct = boxes.getBoxRect(box);
+ boxRct.top -= 2;
+ surface.blitFrom(*bgFrame, boxRct, Common::Point(boxRct.left, boxRct.top));
+ boxRct.top += 2;
+ if (textCursorState) {
+ surface.vLine(textCursorPos, boxRct.top, boxRct.top + 11, 240);
+ }
+ }
+ boxes.display(box, _fontManager);
+ if (box < 6) {
+ // Draw line below
+ surface.hLine(116, 280 + box * 20 + 15, 407, 243); // minus 1 because hLine draws inclusive
+
+ // Display file number
+ _fontManager.displayInt(126, 280 + box * 20, fileListOffset + box + 1);
+ }
+ }
+ redraw = false;
+ g_system->copyRectToScreen(surface.getPixels(), surface.pitch, 0, 0, surface.w, surface.h);
+ }
+
+ g_system->updateScreen();
+ pollEvents();
+ Common::KeyState key = getNextKey();
+ unsigned int mousePressed = getCurrentMouseButton();
+
+ if (!mousePressed) {
+ bool boxFound = false;
+ // Don't handle scroll arrows hovering
+ for (unsigned int box = 0; box < 8; box++) {
+ if (boxes.hitTest(box, getMousePos())) {
+ boxFound = true;
+ if (boxHovered != box) {
+ boxHovered = box;
+ redraw = true;
+ }
+ }
+ }
+ if (!boxFound && boxHovered != -1u) {
+ boxHovered = -1;
+ redraw = true;
+ }
+ }
+ if (key == Common::KEYCODE_RETURN || (mousePressed == 1 && boxHovered == 6)) {
+ // OK
+ if (boxSelected != -1u) {
+ Common::String &selectedSaveName = savesList[boxSelected + fileListOffset];
+ if (!selectedSaveName.size()) {
+ selectedSaveName = _messages[56]; // No name
+ }
+ redraw = true;
+ finished = true;
+ }
+ } else if (mousePressed == 1) {
+ if (boxHovered == 7) {
+ // Cancel
+ boxSelected = -1;
+ finished = true;
+ } else if (boxHovered != -1u && boxHovered != boxSelected) {
+ // This can only be a file
+ bool existingSave = (savesList[boxHovered + fileListOffset] != _messages[55]);
+ // Don't allow to save on slot 0 when visiting to avoid problems with original visit save
+ bool validSave = !(_isVisiting && saveMode && boxSelected == 0);
+ if ((saveMode || existingSave) && validSave) {
+ // Restore old name
+ if (saveMode && boxSelected != -1u) {
+ savesList[boxSelected + fileListOffset] = saveNameBackup;
+ filesListChanged = true;
+ }
+ boxSelected = boxHovered;
+ // Backup new one
+ saveNameBackup = savesList[boxSelected + fileListOffset];
+ // Not an existing save clear free name
+ if (!existingSave) {
+ savesList[boxSelected + fileListOffset] = "";
+ }
+ redraw = true;
+ }
+ }
+ }
+ if (boxSelected != -1u && saveMode) {
+ if (key.keycode != Common::KEYCODE_INVALID) {
+ // Reference means we edit in place
+ Common::String &selectedSaveName = savesList[boxSelected + fileListOffset];
+ if (key == Common::KEYCODE_BACKSPACE && selectedSaveName.size() > 0) {
+ selectedSaveName.deleteLastChar();
+ textCursorNextState = 0;
+ redraw = true;
+ } else if (key.ascii > 32 && key.ascii < 256 && selectedSaveName.size() < 20) {
+ selectedSaveName += key.ascii;
+ textCursorNextState = 0;
+ redraw = true;
+ }
+ }
+ if (g_system->getMillis() > textCursorNextState) {
+ textCursorNextState = g_system->getMillis() + 200; // Blink at 200ms period
+ unsigned int width = _fontManager.getStrWidth(savesList[boxSelected + fileListOffset]);
+ Common::Rect boxRct = boxes.getBoxRect(boxSelected);
+ textCursorPos = boxRct.left + width;
+ textCursorState = !textCursorState;
+ redraw = true;
+ }
+ }
+ if (!autoRepeatInhibit) {
+ bool autoRepeatTrigger = false;
+ unsigned int oldFileListOffset = fileListOffset;
+ if (mousePressed) {
+ if (boxes.hitTest(8, getMousePos()) && fileListOffset > 0) {
+ fileListOffset--;
+ autoRepeatTrigger = true;
+ } else if (boxes.hitTest(9, getMousePos()) && fileListOffset < 99 - 6) {
+ fileListOffset++;
+ autoRepeatTrigger = true;
+ }
+ } else if (key == Common::KEYCODE_UP) {
+ if (fileListOffset > 0) {
+ fileListOffset--;
+ autoRepeatTrigger = true;
+ }
+ } else if (key == Common::KEYCODE_DOWN) {
+ if (fileListOffset < 99 - 6) {
+ fileListOffset++;
+ autoRepeatTrigger = true;
+ }
+ } else if (key == Common::KEYCODE_PAGEUP) {
+ if (fileListOffset > 6) {
+ fileListOffset -= 6;
+ } else {
+ fileListOffset = 0;
+ }
+ } else if (key == Common::KEYCODE_PAGEDOWN) {
+ if (fileListOffset < 99 - 6 - 6) {
+ fileListOffset += 6;
+ } else {
+ fileListOffset = 99 - 6;
+ }
+ }
+ if (autoRepeatTrigger) {
+ // Restore old name
+ if (saveMode && boxSelected != -1u) {
+ savesList[boxSelected + oldFileListOffset] = saveNameBackup;
+ }
+ boxHovered = -1;
+ boxSelected = -1;
+ autoRepeatInhibit = true;
+ autoRepeatEndInhibit = g_system->getMillis() + autoRepeatDelay;
+ filesListChanged = true;
+ }
+ }
+ if (autoRepeatInhibit && g_system->getMillis() > autoRepeatEndInhibit) {
+ autoRepeatInhibit = false;
+ autoRepeatDelay = 60; // Next rounds will wait 60ms after first one
+ }
+ if (!mousePressed && key == Common::KEYCODE_INVALID) {
+ // Nothing was clicked or pressed: set back autoRepeatDelay to 250ms
+ autoRepeatDelay = 250;
+ }
+ }
+ if (boxSelected != -1u) {
+ saveName = savesList[boxSelected + fileListOffset];
+ // TODO: save list offset
+ return boxSelected + fileListOffset + 1;
+ } else {
+ return -1;
+ }
+}
+
+const MsgBoxParameters CryOmni3DEngine_Versailles::kWarpMsgBoxParameters = {
+ 9, 241, 22, 2, 1, 36, 18, 20, 10, 5
+};
+
+const MsgBoxParameters CryOmni3DEngine_Versailles::kFixedimageMsgBoxParameters = {
+ 3, 241, 22, 2, 1, 40, 20, 20, 10, 3
+};
+
+void CryOmni3DEngine_Versailles::displayMessageBox(const MsgBoxParameters &params,
+ const Graphics::Surface *surface, const Common::String &msg, const Common::Point &position,
+ const Common::Functor0<void> &callback) {
+ Graphics::ManagedSurface dstSurface;
+ dstSurface.create(surface->w, surface->h, surface->format);
+ dstSurface.blitFrom(*surface);
+
+ _fontManager.setSurface(&dstSurface);
+ _fontManager.setCurrentFont(params.font);
+ _fontManager.setTransparentBackground(true);
+ _fontManager.setForeColor(params.foreColor);
+ _fontManager.setLineHeight(params.lineHeight);
+ _fontManager.setSpaceWidth(params.spaceWidth);
+ _fontManager.setCharSpacing(params.charSpacing);
+
+ unsigned int width = params.initialWidth;
+ unsigned int height = params.initialHeight;
+ unsigned int lineCount = 0;
+ Common::Point pt = position;
+ Common::Rect rct;
+
+ bool notEnough = true;
+ bool tooLarge = false;
+
+ while (notEnough && !tooLarge) {
+ width += params.incrementWidth;
+ height += params.incrementHeight;
+ rct = Common::Rect::center(pt.x, pt.y, width, height);
+ if (rct.left < 10) {
+ rct.left = 10;
+ if (pt.x < 320) {
+ pt.x += 10;
+ }
+ }
+ if (rct.right >= 630) {
+ rct.right = 630;
+ if (pt.x > 320) {
+ pt.x -= 10;
+ }
+ }
+ if (rct.top <= 10) {
+ rct.top = 10;
+ if (pt.y < 240) {
+ pt.y += 10;
+ }
+ }
+ if (rct.bottom >= 470) {
+ rct.bottom = 470;
+ if (pt.y > 235) { // sic.
+ pt.y -= 10;
+ }
+ }
+ if (rct.left == 10 && rct.top == 10 && rct.right == 630 && rct.bottom == 470) {
+ tooLarge = true;
+ }
+ lineCount = _fontManager.getLinesCount(msg, rct.width() - 12);
+ if (lineCount && lineCount * _fontManager.lineHeight() + 18 < (unsigned int)rct.height()) {
+ notEnough = false;
+ }
+ }
+ rct.setHeight(lineCount * _fontManager.lineHeight() + 12);
+ if (rct.bottom > 479) {
+ rct.bottom = 479;
+ }
+
+ Graphics::Surface subSurface = dstSurface.getSubArea(rct);
+ makeTranslucent(subSurface, surface->getSubArea(rct));
+ rct.grow(-6);
+ _fontManager.setupBlock(rct);
+ _fontManager.displayBlockText(msg);
+ // TODO: countdown
+
+ g_system->copyRectToScreen(dstSurface.getPixels(), dstSurface.pitch, 0, 0,
+ dstSurface.w, dstSurface.h);
+
+ waitMouseRelease();
+ unsigned int disappearTime = g_system->getMillis() + msg.size() * params.timeoutChar * 10;
+ bool finished = false;
+ while (!finished) {
+ g_system->updateScreen();
+
+ callback();
+
+ if (g_system->getMillis() > disappearTime) {
+ finished = true;
+ }
+ if (getCurrentMouseButton() == 1) {
+ finished = true;
+ }
+ }
+
+ // Restore image
+ g_system->copyRectToScreen(surface->getPixels(), surface->pitch, 0, 0, surface->w, surface->h);
+}
+
+void CryOmni3DEngine_Versailles::displayMessageBoxWarp(const Common::String &message) {
+ Common::Point mousePos = getMousePos();
+ mousePos += Common::Point(0, 32);
+ if (mousePos.x > 639) {
+ mousePos.x = 639;
+ }
+ if (mousePos.y > 479) {
+ mousePos.y = 479;
+ }
+ displayMessageBox(kWarpMsgBoxParameters, _omni3dMan.getSurface(), message, mousePos,
+ Common::Functor0Mem<void, CryOmni3DEngine_Versailles>(this,
+ &CryOmni3DEngine_Versailles::warpMsgBoxCB));
+}
+
+void CryOmni3DEngine_Versailles::displayCredits() {
+ waitMouseRelease();
+
+ Graphics::ManagedSurface creditsSurface;
+ Image::ImageDecoder *imageDecoder = loadHLZ("credits.hlz");
+ if (!imageDecoder) {
+ return;
+ }
+
+ const Graphics::Surface *bgFrame = imageDecoder->getSurface();
+
+ byte palette[256 * 3];
+ memset(palette, 0, 256 * 3);
+ // getPalette returns the first color not index 0
+ memcpy(palette + 3 * imageDecoder->getPaletteStartIndex(), imageDecoder->getPalette(),
+ 3 * imageDecoder->getPaletteColorCount());
+ copySubPalette(palette, _cursorPalette, 240, 8);
+
+ creditsSurface.create(bgFrame->w, bgFrame->h, bgFrame->format);
+
+ _fontManager.setCurrentFont(3);
+ _fontManager.setTransparentBackground(true);
+ _fontManager.setForeColor(243);
+ _fontManager.setLineHeight(14);
+ _fontManager.setSpaceWidth(0);
+ _fontManager.setCharSpacing(1);
+ _fontManager.setSurface(&creditsSurface);
+
+ Common::File creditsFile;
+ if (!creditsFile.open("credits.txt")) {
+ warning("Failed to open credits file: %s", "credits.txt");
+ delete imageDecoder;
+ return;
+ }
+
+ g_system->showMouse(false);
+
+ char line[256];
+ bool end = false;
+ bool calculatedScreen = false;
+ unsigned int lineHeight = 20;
+ unsigned int currentY = 0;
+ int32 fileOffset = 0;
+ bool skipScreen = false;
+
+ while (!end && creditsFile.readLine(line, ARRAYSIZE(line))) {
+ // Remove line ending
+ line[strlen(line) - 1] = '\0';
+ if (!strncmp(line, "###", 3)) {
+ // Prefix for commands
+ if (!strncmp(line + 3, "ECRAN", 5)) {
+ // ECRAN command
+ if (calculatedScreen) {
+ g_system->copyRectToScreen(creditsSurface.getPixels(), creditsSurface.pitch, 0, 0,
+ creditsSurface.w, creditsSurface.h);
+ if (skipScreen) {
+ // Just display palette
+ setPalette(palette, 0, 256);
+ } else {
+ fadeInPalette(palette);
+ }
+ skipScreen = false;
+ // Wait
+ unsigned int endScreenTime = g_system->getMillis() + 6000;
+ while (g_system->getMillis() < endScreenTime && !skipScreen) {
+ g_system->updateScreen();
+ if (pollEvents()) {
+ if (getCurrentMouseButton() == 1) {
+ skipScreen = true;
+ }
+ Common::KeyCode kc = getNextKey().keycode;
+ while (kc != Common::KEYCODE_INVALID) {
+ if (kc == Common::KEYCODE_SPACE) {
+ skipScreen = true;
+ break;
+ } else if (kc == Common::KEYCODE_ESCAPE) {
+ skipScreen = true;
+ end = true;
+ break;
+ }
+ kc = getNextKey().keycode;
+ }
+ clearKeys();
+ }
+ if (g_engine->shouldQuit()) {
+ skipScreen = true;
+ end = true;
+ }
+ }
+ if (!skipScreen) {
+ fadeOutPalette();
+ fillSurface(0);
+ }
+ currentY = 0;
+ fileOffset = creditsFile.pos();
+ calculatedScreen = false;
+ } else {
+ // We just finished calculated all lines, roll back and display them
+ creditsFile.seek(fileOffset, SEEK_SET);
+ calculatedScreen = true;
+ if (currentY <= 480 - lineHeight) {
+ // Center in screen
+ currentY = (480 - lineHeight) / 2 - currentY / 2;
+ } else {
+ currentY = 3;
+ }
+ creditsSurface.blitFrom(*bgFrame);
+ }
+ } else if (!strcmp(line + 3, "T0")) {
+ _fontManager.setCurrentFont(1);
+ lineHeight = _fontManager.getFontMaxHeight() + 10;
+ } else if (!strcmp(line + 3, "T1")) {
+ _fontManager.setCurrentFont(2);
+ lineHeight = _fontManager.getFontMaxHeight() + 10;
+ } else if (!strcmp(line + 3, "T2")) {
+ _fontManager.setCurrentFont(4);
+ lineHeight = _fontManager.getFontMaxHeight() + 10;
+ } else if (!strcmp(line + 3, "T3")) {
+ _fontManager.setCurrentFont(2);
+ lineHeight = _fontManager.getFontMaxHeight() + 10;
+ } else if (!strcmp(line + 3, "T4")) {
+ _fontManager.setCurrentFont(5);
+ lineHeight = _fontManager.getFontMaxHeight() + 10;
+ } else if (!strcmp(line + 3, "T5")) {
+ _fontManager.setCurrentFont(6);
+ lineHeight = _fontManager.getFontMaxHeight() + 10;
+ } else {
+ warning("Unknown ### command : %s", line + 3);
+ }
+ } else {
+ // Text
+ if (calculatedScreen) {
+ unsigned int width = _fontManager.getStrWidth(line);
+ // Center around 315
+ _fontManager.displayStr(315 - width / 2, currentY, line);
+ }
+ currentY += lineHeight;
+ }
+ }
+ g_system->showMouse(true);
+}
+
+} // End of namespace Versailles
+} // End of namespace CryOmni3D
diff --git a/engines/cryomni3d/versailles/music.cpp b/engines/cryomni3d/versailles/music.cpp
new file mode 100644
index 0000000000..524c5244a4
--- /dev/null
+++ b/engines/cryomni3d/versailles/music.cpp
@@ -0,0 +1,281 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "audio/audiostream.h"
+#include "audio/decoders/wave.h"
+
+#include "common/config-manager.h"
+#include "common/error.h"
+#include "common/file.h"
+
+#include "cryomni3d/versailles/engine.h"
+
+namespace CryOmni3D {
+namespace Versailles {
+
+const char *CryOmni3DEngine_Versailles::kMusicFiles[8][8] = {
+ { "1amb", }, // Level 1
+ { "2amb", "2amb2", "2amb1" }, // Level 2
+ { "3amb", "3amb1", "3amb2" }, // Level 3
+ { "4amb", "4amb1" }, // Level 4
+ { "5amb1", "5amb2" }, // Level 5
+ { "6amb1", "6amb2", "6amb3", "6amb4" }, // Level 6
+ { "7amb", }, // Level 7
+ { "3amb", "3amb1", "3amb2", "2amb", "2amb1", "2amb2", "4amb" }, // Level 8
+};
+
+void CryOmni3DEngine_Versailles::musicUpdate() {
+ if (!_isPlaying || _currentLevel <= 0 ||
+ _mixer->isSoundTypeMuted(Audio::Mixer::kMusicSoundType) ||
+ _mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType) == 0) {
+ // No music in all of these cases
+ musicStop();
+ return;
+ }
+
+ unsigned int musicId = getMusicId(_currentLevel, _currentPlaceId);
+ const char *musicBName = kMusicFiles[_currentLevel - 1][musicId];
+ assert(musicBName != nullptr);
+
+ // Ensure sound is playing in all cases
+ musicResume();
+
+ if (musicBName == _musicCurrentFile) {
+ // Same file, nothing more to do
+ return;
+ }
+
+ // New file, stop the old one first
+ musicStop();
+
+ Common::String musicFName = musicBName;
+ musicFName += ".wav";
+
+ Common::File *musicFile = new Common::File();
+ if (!musicFile->open(musicFName)) {
+ warning("Failed to open music file %s/%s", musicBName, musicFName.c_str());
+ delete musicFile;
+ return;
+ }
+
+ Audio::SeekableAudioStream *musicDecoder = Audio::makeWAVStream(musicFile, DisposeAfterUse::YES);
+ // We lost ownership of the musicFile just set it to nullptr and don't use it
+ musicFile = nullptr;
+
+ if (!musicDecoder) {
+ warning("Failed to decode music file %s/%s", musicBName, musicFName.c_str());
+ return;
+ }
+
+ Audio::AudioStream *loopStream = Audio::makeLoopingAudioStream(musicDecoder, 0);
+ // We lost ownership of musicDecoder just set it to nullptr and don't use it
+ musicDecoder = nullptr;
+
+ _mixer->playStream(Audio::Mixer::kMusicSoundType, &_musicHandle, loopStream);
+ _musicCurrentFile = musicBName;
+}
+
+void CryOmni3DEngine_Versailles::musicPause() {
+ _mixer->pauseHandle(_musicHandle, true);
+}
+
+void CryOmni3DEngine_Versailles::musicResume() {
+ _mixer->pauseHandle(_musicHandle, false);
+}
+
+void CryOmni3DEngine_Versailles::musicStop() {
+ // Fade the music first
+ if (_mixer->isSoundHandleActive(_musicHandle)) {
+ // We recreate the real channel volume to decrease this one 2 by 2
+ int musicVol = _mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType);
+ byte channelVol = _mixer->getChannelVolume(_musicHandle);
+ int realVolume = (musicVol * channelVol) / Audio::Mixer::kMaxChannelVolume;
+ bool skip = false;
+ while (realVolume > 0 && !skip) {
+ // pollEvents waits for 10ms
+ realVolume -= 2;
+ channelVol = CLIP((realVolume * Audio::Mixer::kMaxChannelVolume) / musicVol, 0, 255);
+ _mixer->setChannelVolume(_musicHandle, channelVol);
+ if (pollEvents() && checkKeysPressed(1, Common::KEYCODE_SPACE)) {
+ skip = true;
+ }
+ }
+ }
+ _mixer->stopHandle(_musicHandle);
+ _musicCurrentFile = nullptr;
+}
+
+void CryOmni3DEngine_Versailles::musicSetQuiet(bool quiet) {
+ float newFactor = quiet ? 3.5f : 1.f;
+ if (newFactor != _musicVolumeFactor) {
+ _musicVolumeFactor = newFactor;
+ syncSoundSettings();
+ }
+}
+
+bool CryOmni3DEngine_Versailles::musicWouldChange(unsigned int level, unsigned int placeId) const {
+ unsigned int musicId = getMusicId(level, placeId);
+ const char *musicFile = kMusicFiles[_currentLevel - 1][musicId];
+
+ return musicFile != _musicCurrentFile;
+}
+
+unsigned int CryOmni3DEngine_Versailles::getMusicId(unsigned int level,
+ unsigned int placeId) const {
+ // No need of place state
+ switch (level) {
+ case 1:
+ // Only one music
+ return 0;
+ case 2:
+ switch (placeId) {
+ case 4:
+ return 1;
+ case 10:
+ case 11:
+ case 13:
+ return 2;
+ default:
+ return 0;
+ }
+ case 3:
+ switch (placeId) {
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ return 2;
+ case 6:
+ case 7:
+ case 8:
+ case 12:
+ case 24:
+ return 1;
+ default:
+ return 0;
+ }
+ case 4:
+ switch (placeId) {
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ return 1;
+ default:
+ return 0;
+ }
+ case 5:
+ switch (placeId) {
+ case 6:
+ case 7:
+ case 8:
+ case 12:
+ case 26:
+ case 27:
+ case 30:
+ case 31:
+ return 1;
+ default:
+ return 0;
+ }
+ case 6:
+ switch (placeId) {
+ case 1:
+ return 3;
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ return 0;
+ case 14:
+ case 16:
+ case 17:
+ case 19:
+ case 20:
+ case 22:
+ case 24:
+ case 26:
+ case 27:
+ case 32:
+ case 34:
+ case 38:
+ case 44:
+ return 2;
+ default:
+ return 1;
+ }
+ case 7:
+ return 0;
+ case 8:
+ switch (placeId) {
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ return 2;
+ case 6:
+ case 7:
+ case 8:
+ return 1;
+ case 9:
+ case 10:
+ case 11:
+ return 0;
+ case 12:
+ return 1;
+ case 13:
+ case 14:
+ case 15:
+ case 16:
+ return 0;
+ case 24:
+ return 1;
+ case 33:
+ case 34:
+ case 35:
+ return 5;
+ case 36:
+ case 37:
+ case 38:
+ case 39:
+ return 3;
+ case 40:
+ return 4;
+ case 42:
+ case 43:
+ case 44:
+ return 6;
+ default:
+ return 0;
+
+ }
+ default:
+ error("Invalid level %d when choosing music", level);
+ }
+}
+
+} // End of namespace Versailles
+} // End of namespace CryOmni3D
diff --git a/engines/cryomni3d/versailles/saveload.cpp b/engines/cryomni3d/versailles/saveload.cpp
new file mode 100644
index 0000000000..8afefa35ec
--- /dev/null
+++ b/engines/cryomni3d/versailles/saveload.cpp
@@ -0,0 +1,315 @@
+/* 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/archive.h"
+#include "common/config-manager.h"
+#include "common/file.h"
+#include "common/savefile.h"
+#include "common/system.h"
+
+#include "cryomni3d/versailles/engine.h"
+
+#define DEBUG_SAVE
+
+namespace CryOmni3D {
+namespace Versailles {
+
+#define SAVE_DESCRIPTION_LEN 20
+
+Common::String CryOmni3DEngine_Versailles::getSaveFileName(bool visit, unsigned int saveNum) const {
+ return Common::String::format("%s%s.%04u", _targetName.c_str(), visit ? "_visit" : "", saveNum);
+}
+
+bool CryOmni3DEngine_Versailles::canVisit() const {
+ // Build a custom SearchSet
+ const Common::FSNode gameDataDir(ConfMan.get("path"));
+ Common::SearchSet visitsSearchSet;
+ visitsSearchSet.addSubDirectoryMatching(gameDataDir, "datas_v/savegame/visite", 1);
+ return visitsSearchSet.hasFile("game0001.sav");
+}
+
+void CryOmni3DEngine_Versailles::getSavesList(bool visit, Common::StringArray &saveNames) {
+ Common::SaveFileManager *saveMan = g_system->getSavefileManager();
+
+ char saveName[SAVE_DESCRIPTION_LEN + 1];
+ saveName[SAVE_DESCRIPTION_LEN] = '\0';
+ Common::String pattern = Common::String::format("%s%s.????", _targetName.c_str(),
+ visit ? "_visit" : "");
+ Common::StringArray filenames = saveMan->listSavefiles(pattern);
+ sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..)
+
+ saveNames.clear();
+ saveNames.reserve(100);
+
+ int num = 1;
+ int slotNum;
+
+ if (visit) {
+ // Add bootstrap visit
+ const Common::FSNode gameDataDir(ConfMan.get("path"));
+ Common::SearchSet visitsSearchSet;
+ visitsSearchSet.addSubDirectoryMatching(gameDataDir, "datas_v/savegame/visite", 1);
+ if (visitsSearchSet.hasFile("game0001.sav")) {
+ Common::File visitFile;
+ if (!visitFile.open("game0001.sav", visitsSearchSet)) {
+ error("Can't load visit file");
+ }
+ visitFile.read(saveName, SAVE_DESCRIPTION_LEN);
+ saveNames.push_back(saveName);
+ } else {
+ warning("visiting mode but no bootstrap");
+ // No bootstrap visit, too bad
+ saveNames.push_back(_messages[55]); //Fill with free slot
+ }
+ num++;
+ }
+
+ for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end();
+ ++file) {
+ // Obtain the last 4 digits of the filename, since they correspond to the save slot
+ slotNum = atoi(file->c_str() + file->size() - 4);
+
+ if (slotNum >= 1 && slotNum <= 99) {
+ while (num < slotNum) {
+ saveNames.push_back(_messages[55]); //Fill with free slot
+ num++;
+ }
+
+ num++;
+#ifdef DEBUG_SAVE
+ Common::InSaveFile *in = _saveFileMan->openRawFile(*file);
+#else
+ Common::InSaveFile *in = _saveFileMan->openForLoading(*file);
+#endif
+ if (in) {
+ in->read(saveName, SAVE_DESCRIPTION_LEN);
+ saveNames.push_back(saveName);
+ delete in;
+ }
+ }
+ }
+
+ for (unsigned int i = saveNames.size(); i < 100; i++) {
+ saveNames.push_back(_messages[55]);
+ }
+}
+
+void CryOmni3DEngine_Versailles::saveGame(bool visit, unsigned int saveNum,
+ const Common::String &saveName) const {
+ if (visit && saveNum == 1) {
+ error("Can't erase bootstrap visit");
+ }
+
+ Common::String saveFileName = getSaveFileName(visit, saveNum);
+
+ Common::OutSaveFile *out;
+
+ if (!(out = _saveFileMan->openForSaving(saveFileName,
+#ifdef DEBUG_SAVE
+ false
+#else
+ true
+#endif
+ ))) {
+ return;
+ }
+
+ // Write save name
+ char saveNameC[SAVE_DESCRIPTION_LEN];
+ memset(saveNameC, 0, sizeof(saveNameC));
+ strncpy(saveNameC, saveName.c_str(), sizeof(saveNameC));
+ out->write(saveNameC, sizeof(saveNameC));
+
+ // dummy values
+ out->writeUint32LE(0);
+ out->writeUint32BE(0);
+ out->writeUint32BE(0);
+
+ // Dialog variables
+ assert(_dialogsMan.size() < 200);
+ for (unsigned int i = 0; i < _dialogsMan.size(); i++) {
+ out->writeByte(_dialogsMan[i]);
+ }
+ for (unsigned int i = _dialogsMan.size(); i < 200; i++) {
+ out->writeByte(0);
+ }
+
+ // Inventory
+ assert(_inventory.size() == 50);
+ for (Inventory::const_iterator it = _inventory.begin(); it != _inventory.end(); it++) {
+ unsigned int objId = -1;
+ if (*it != nullptr) {
+ // Inventory contains pointers to objects stored in _objects
+ objId = *it - _objects.begin();
+ }
+ out->writeUint32BE(objId);
+ }
+ // Offset of inventory in toolbar
+ out->writeUint32BE(_toolbar.inventoryOffset());
+
+ // Level, place, warp position
+ out->writeUint32BE(_currentLevel);
+ out->writeUint32BE(_currentPlaceId);
+ out->writeDoubleBE(_omni3dMan.getAlpha());
+ out->writeDoubleBE(_omni3dMan.getBeta());
+
+ // Places states
+ assert(_placeStates.size() < 100);
+ Common::Array<PlaceState>::const_iterator placeIt = _placeStates.begin();
+ for (unsigned int i = 0; placeIt != _placeStates.end(); placeIt++, i++) {
+ out->writeUint32BE(placeIt->state);
+ }
+ for (unsigned int i = _placeStates.size(); i < 100; i++) {
+ out->writeUint32BE(0);
+ }
+
+ // Game variables
+ assert(_gameVariables.size() < 100);
+ for (Common::Array<unsigned int>::const_iterator it = _gameVariables.begin();
+ it != _gameVariables.end(); it++) {
+ out->writeUint32BE(*it);
+ }
+ for (unsigned int i = _gameVariables.size(); i < 100; i++) {
+ out->writeUint32BE(0);
+ }
+
+ out->finalize();
+
+ delete out;
+}
+
+bool CryOmni3DEngine_Versailles::loadGame(bool visit, unsigned int saveNum) {
+ Common::SeekableReadStream *in;
+
+ if (visit && saveNum == 1) {
+ // Load bootstrap visit
+ const Common::FSNode gameDataDir(ConfMan.get("path"));
+ Common::SearchSet visitsSearchSet;
+ visitsSearchSet.addSubDirectoryMatching(gameDataDir, "datas_v/savegame/visite", 1);
+ Common::File *visitFile = new Common::File();
+ if (!visitFile->open("game0001.sav", visitsSearchSet)) {
+ delete visitFile;
+ error("Can't load visit file");
+ }
+ in = visitFile;
+ } else {
+ Common::String saveFileName = getSaveFileName(visit, saveNum);
+
+#ifdef DEBUG_SAVE
+ in = _saveFileMan->openRawFile(saveFileName);
+#else
+ in = _saveFileMan->openForLoading(saveFileName);
+#endif
+ }
+
+ if (!in || in->size() != 1260) {
+ return false;
+ }
+
+ musicStop();
+
+ // Load save name but don't use it
+ char saveNameC[SAVE_DESCRIPTION_LEN];
+ in->read(saveNameC, sizeof(saveNameC));
+
+ // dummy values
+ in->readUint32LE();
+ in->readUint32BE();
+ in->readUint32BE();
+
+ // Dialog variables
+ assert(_dialogsMan.size() < 200);
+ for (unsigned int i = 0; i < _dialogsMan.size(); i++) {
+ _dialogsMan[i] = in->readByte();
+ }
+ for (unsigned int i = _dialogsMan.size(); i < 200; i++) {
+ in->readByte();
+ }
+
+ // Inventory
+ assert(_inventory.size() == 50);
+ for (Inventory::iterator it = _inventory.begin(); it != _inventory.end(); it++) {
+ unsigned int objId = in->readUint32BE();
+ if (objId >= _objects.size()) {
+ objId = -1;
+ }
+ if (objId != -1u) {
+ *it = _objects.begin() + objId;
+ } else {
+ *it = nullptr;
+ }
+ }
+ // Offset of inventory in toolbar
+ _toolbar.setInventoryOffset(in->readUint32BE());
+
+ // Level, place, warp position
+ _currentLevel = in->readUint32BE();
+ // Use nextPlace to force place move
+ _nextPlaceId = in->readUint32BE();
+
+ // Store alpha and beta for later use
+ double alpha = in->readDoubleBE();
+ double beta = in->readDoubleBE();
+
+ // Places states
+ // Store them and use them once we called initNewLevel, we can't call it before because it needs _gameVariables (and especially kCurrentTime) to be correctly set
+ uint32 placesStates[100];
+ for (unsigned int i = 0; i < 100; i++) {
+ placesStates[i] = in->readUint32BE();
+ }
+
+ // Game variables
+ assert(_gameVariables.size() < 100);
+ for (Common::Array<unsigned int>::iterator it = _gameVariables.begin(); it != _gameVariables.end();
+ it++) {
+ *it = in->readUint32BE();
+ }
+ for (unsigned int i = _gameVariables.size(); i < 100; i++) {
+ in->readUint32BE();
+ }
+
+ delete in;
+
+ if (_gameVariables[GameVariables::kCurrentTime] == 0) {
+ _gameVariables[GameVariables::kCurrentTime] = 1;
+ }
+
+ // Everything has been loaded, setup new level
+ // We will set places states and warp coordinates just after that to avoid them from being reset
+ initNewLevel(_currentLevel);
+
+ _omni3dMan.setAlpha(alpha);
+ _omni3dMan.setBeta(beta);
+
+ // _placeStates has just been resized in initNewLevel
+ unsigned int i = 0;
+ for (Common::Array<PlaceState>::iterator placeIt = _placeStates.begin();
+ placeIt != _placeStates.end() && i < ARRAYSIZE(placesStates); placeIt++, i++) {
+ placeIt->state = placesStates[i];
+ }
+
+ // TODO: countdown
+
+ return true;
+}
+
+} // End of namespace Versailles
+} // End of namespace CryOmni3D
diff --git a/engines/cryomni3d/versailles/toolbar.cpp b/engines/cryomni3d/versailles/toolbar.cpp
new file mode 100644
index 0000000000..e0168e29cc
--- /dev/null
+++ b/engines/cryomni3d/versailles/toolbar.cpp
@@ -0,0 +1,599 @@
+/* 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/system.h"
+
+#include "cryomni3d/cryomni3d.h"
+
+#include "cryomni3d/versailles/toolbar.h"
+
+namespace CryOmni3D {
+namespace Versailles {
+
+void Toolbar::init(const Sprites *sprites, FontManager *fontManager,
+ const Common::Array<Common::String> *messages, Inventory *inventory,
+ CryOmni3DEngine *engine) {
+ _sprites = sprites;
+ _fontManager = fontManager;
+ _messages = messages;
+ _inventory = inventory;
+ _engine = engine;
+
+ _bgSurface.create(640, 60, Graphics::PixelFormat::createFormatCLUT8());
+ _destSurface.create(640, 60, Graphics::PixelFormat::createFormatCLUT8());
+
+ // Inventory
+ addZone(51, 56, Common::Point(211, 8), &Toolbar::callbackInventory<0>);
+ addZone(51, 56, Common::Point(258, 8), &Toolbar::callbackInventory<1>);
+ addZone(51, 56, Common::Point(305, 8), &Toolbar::callbackInventory<2>);
+ addZone(51, 56, Common::Point(352, 8), &Toolbar::callbackInventory<3>);
+ addZone(51, 56, Common::Point(399, 8), &Toolbar::callbackInventory<4>);
+ addZone(51, 56, Common::Point(446, 8), &Toolbar::callbackInventory<5>);
+ addZone(51, 56, Common::Point(493, 8), &Toolbar::callbackInventory<6>);
+ addZone(51, 56, Common::Point(540, 8), &Toolbar::callbackInventory<7>);
+
+ // Documentation
+ const Graphics::Cursor &cursorDoc = _sprites->getCursor(133);
+ Common::Point docPos(627 - cursorDoc.getWidth(), 42 - cursorDoc.getHeight());
+ addZone(133, 137, docPos, &Toolbar::callbackDocumentation);
+
+ // Options
+ const Graphics::Cursor &cursorOpt = _sprites->getCursor(225);
+ Common::Point optPos(0, 60 - cursorOpt.getHeight());
+ addZone(225, 225, optPos, &Toolbar::callbackOptions);
+
+ // Previous or next
+ addZone(183, -1, Common::Point(190, 18), &Toolbar::callbackInventoryPrev);
+ addZone(240, -1, Common::Point(574, 18), &Toolbar::callbackInventoryNext);
+ // View
+ addZone(142, -1, Common::Point(158, 12), &Toolbar::callbackViewObject);
+}
+
+Toolbar::~Toolbar() {
+ _bgSurface.free();
+ _destSurface.free();
+}
+
+void Toolbar::inventoryChanged(unsigned int newPosition) {
+ if (newPosition != -1u && newPosition > _inventoryOffset) {
+ _inventoryOffset = newPosition - 7;
+ }
+ // Refresh
+ updateZones();
+}
+
+void Toolbar::addZone(uint16 cursorMainId, uint16 cursorSecondaryId, Common::Point position,
+ ZoneCallback callback) {
+ const Graphics::Cursor &cursorMain = _sprites->getCursor(cursorMainId);
+ Common::Rect rct(cursorMain.getWidth(), cursorMain.getHeight());
+ rct.moveTo(position);
+
+ // By default it's the secondary image
+ Zone zone = { rct, cursorMainId, cursorSecondaryId, callback, true, false };
+ _zones.push_back(zone);
+}
+
+Common::Array<Toolbar::Zone>::const_iterator Toolbar::hitTestZones(const Common::Point &mousePos)
+const {
+ Common::Array<Zone>::const_iterator it;
+ for (it = _zones.begin(); it != _zones.end(); it++) {
+ if (!it->hidden && it->rect.contains(mousePos) && it->callback) {
+ break;
+ }
+ }
+ return it;
+}
+
+unsigned int Toolbar::captureEvent(const Common::Point &mousePos, unsigned int dragStatus) {
+ unsigned int result = 0;
+ Common::Array<Zone>::const_iterator it = hitTestZones(mousePos);
+ if (it != _zones.end()) {
+ result = (this->*(it->callback))(dragStatus);
+ }
+ return result;
+}
+
+void Toolbar::updateZones() {
+ _zones[8].secondary = !_engine->hasPlaceDocumentation();
+
+ Inventory::const_iterator inventoryIt, inventorySelectedIt;
+ if (!_inventoryEnabled) {
+ _inventoryMaxOffset = 0;
+ _inventoryOffset = 0;
+ _zones[10].secondary = true;
+ _zones[11].secondary = true;
+ } else {
+ _inventoryMaxOffset = 0;
+ // Find an object in inventory after the 8 first
+ for (inventoryIt = _inventory->begin() + 8; inventoryIt != _inventory->end(); inventoryIt++) {
+ if (*inventoryIt != nullptr) {
+ _inventoryMaxOffset = (inventoryIt - _inventory->begin()) - 7;
+ }
+ }
+ _zones[10].secondary = !_inventoryMaxOffset;
+ _zones[11].secondary = !_inventoryMaxOffset;
+ if (_inventoryOffset > _inventoryMaxOffset) {
+ // Clamp inventory offset to its max
+ _inventoryOffset = _inventoryMaxOffset;
+ }
+ inventoryIt = _inventory->begin() + _inventoryOffset;
+ inventorySelectedIt = _inventory->begin() + _inventorySelected;
+ }
+ // Inventory zones are from 0 to 7
+ for (Common::Array<Zone>::iterator zoneIt = _zones.begin(); zoneIt != _zones.begin() + 8;
+ zoneIt++, inventoryIt++) {
+ if (!_inventoryEnabled) {
+ zoneIt->hidden = true;
+ zoneIt->imageMain = 0;
+ zoneIt->imageSecondary = 0;
+ zoneIt->secondary = false;
+ } else if (inventoryIt >= _inventory->end() || *inventoryIt == nullptr) {
+ // Nothing in inventory at this position
+ zoneIt->hidden = false;
+ zoneIt->imageMain = 51;
+ zoneIt->imageSecondary = 56;
+ zoneIt->secondary = true;
+ } else {
+ // Setup inventory icon
+ zoneIt->hidden = false;
+ zoneIt->imageMain = (*inventoryIt)->idCA();
+ zoneIt->imageSecondary = (*inventoryIt)->idCl();
+ zoneIt->secondary = (inventorySelectedIt != inventoryIt);
+ }
+ }
+}
+
+unsigned int Toolbar::callbackInventory(unsigned int invId, unsigned int dragStatus) {
+ if (!_inventoryEnabled) {
+ return 0;
+ }
+
+ invId += _inventoryOffset;
+ Object *obj = nullptr;
+ if (invId < _inventory->size()) {
+ obj = (*_inventory)[invId];
+ }
+ if (obj == nullptr) {
+ return 0;
+ }
+
+ if (!obj->valid()) {
+ return 0;
+ }
+
+ switch (dragStatus) {
+ case kDragStatus_Pressed:
+ _inventorySelected = invId;
+ _engine->setCursor(181);
+ _zones[12].secondary = (obj->viewCallback() == nullptr);
+ _inventory_button_dragging = true;
+ return 1;
+ case kDragStatus_Dragging:
+ if (_inventorySelected == invId) {
+ return 0;
+ }
+ _inventorySelected = invId;
+ _zones[12].secondary = (obj->viewCallback() == nullptr);
+ _inventory_button_dragging = true;
+ return 1;
+ case kDragStatus_Finished:
+ _engine->setCursor(obj->idSl());
+ _inventory->setSelectedObject(obj);
+ _inventorySelected = invId;
+ return 1;
+ default:
+ return 0;
+ }
+
+}
+
+unsigned int Toolbar::callbackInventoryPrev(unsigned int dragStatus) {
+ if (!_inventoryEnabled) {
+ return 0;
+ }
+
+ if (dragStatus == kDragStatus_Pressed && _inventoryOffset > 0) {
+ // Restart auto repeat only if there could be something
+ _engine->setAutoRepeatClick(150);
+ _inventoryOffset--;
+ return 1;
+ }
+ // In any other case we didn't do anything
+ return 0;
+}
+
+unsigned int Toolbar::callbackInventoryNext(unsigned int dragStatus) {
+ if (!_inventoryEnabled) {
+ return 0;
+ }
+
+ if (dragStatus == kDragStatus_Pressed && _inventoryOffset < _inventoryMaxOffset) {
+ _engine->setAutoRepeatClick(150);
+ _inventoryOffset++;
+ return 1;
+ }
+ // In any other case we didn't do anything
+ return 0;
+}
+
+unsigned int Toolbar::callbackViewObject(unsigned int dragStatus) {
+ if (!_inventoryEnabled) {
+ return 0;
+ }
+
+ _mouse_in_view_object = true;
+
+ if (_inventorySelected == -1u) {
+ // Nothing selected in toolbar
+ return 0;
+ }
+ Inventory::const_iterator inventorySelectedIt = _inventory->begin() + _inventorySelected;
+ Object *selectedObject = *inventorySelectedIt;
+ if (selectedObject == nullptr || selectedObject->viewCallback() == nullptr) {
+ // Nothing to view, the sprite isn't even displayed
+ return 0;
+ }
+
+ switch (dragStatus) {
+ case kDragStatus_NoDrag:
+ _backup_selected_object = selectedObject;
+ _engine->setCursor(181);
+ return 0;
+ case kDragStatus_Pressed:
+ case kDragStatus_Dragging:
+ return 1;
+ case kDragStatus_Finished:
+ // Just clicked
+ g_system->showMouse(false);
+ (*selectedObject->viewCallback())();
+ g_system->showMouse(true);
+ _parentMustRedraw = true;
+ _shortExit = true;
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+unsigned int Toolbar::callbackOptions(unsigned int dragStatus) {
+ _mouse_in_options = true;
+
+ switch (dragStatus) {
+ case kDragStatus_NoDrag:
+ _backup_selected_object = _inventory->selectedObject();
+ _engine->setCursor(181);
+ return 0;
+ case kDragStatus_Pressed:
+ case kDragStatus_Dragging:
+ // Nothing to do, we wait release
+ return 0;
+ case kDragStatus_Finished:
+ // Just clicked
+ _engine->displayOptions();
+ _parentMustRedraw = true;
+ _shortExit = true;
+ _engine->setMousePos(Common::Point(320, 240)); // Center of screen
+ // Displaying options hides the mouse
+ g_system->showMouse(true);
+ return 0;
+ default:
+ return 0;
+ }
+}
+
+unsigned int Toolbar::callbackDocumentation(unsigned int dragStatus) {
+ _mouse_in_options = true;
+
+ switch (dragStatus) {
+ case kDragStatus_NoDrag:
+ case kDragStatus_Pressed:
+ case kDragStatus_Dragging:
+ // Nothing to do, we wait release
+ return 0;
+ case kDragStatus_Finished:
+ // Just clicked
+ if (_engine->displayPlaceDocumentation()) {
+ _parentMustRedraw = true;
+ _shortExit = true;
+ _engine->setMousePos(Common::Point(320, 240)); // Center of screen
+ }
+ return 0;
+ default:
+ return 0;
+ }
+}
+
+void Toolbar::drawToolbar(const Graphics::Surface *original) {
+ if (_position > 60) {
+ _position = 60;
+ }
+
+ if (_position != 0) {
+ // Not entirely drawn, we must copy a part of the original image
+ Common::Rect rct(0, 420, 640, 420 + _position);
+ _destSurface.copyRectToSurface(*original, 0, 0, rct);
+ }
+
+ if (_position == 60) {
+ // Entirely hidden, just stop there, we have nothing to draw
+ return;
+ }
+
+ // Not entirely hidden, we must display the transparent background prepared for us
+ Common::Rect rct(0, _position, 640, 60);
+ _destSurface.copyRectToSurface(_bgSurface, 0, _position, rct);
+
+ // Now draw the various zones on the surface
+ for (Common::Array<Zone>::const_iterator it = _zones.begin(); it != _zones.end(); it++) {
+ if (it->hidden) {
+ continue;
+ }
+
+ uint16 spriteId = it->secondary ? it->imageSecondary : it->imageMain;
+ if (spriteId == uint16(-1)) {
+ continue;
+ }
+
+ Common::Rect dst = it->rect;
+ dst.translate(0, _position);
+
+ // Clip the rectangle to fit inside the surface
+ dst.clip(Common::Rect(_destSurface.w, _destSurface.h));
+
+ if (dst.isEmpty()) {
+ continue;
+ }
+
+ const Graphics::Surface &sprite = _sprites->getSurface(spriteId);
+ _destSurface.transBlitFrom(sprite, Common::Rect(dst.width(), dst.height()), dst,
+ _sprites->getKeyColor(spriteId));
+ }
+
+ // And now draw the object description if needed
+ if (_inventoryEnabled && _inventoryHovered != -1u) {
+ Object *obj = (*_inventory)[_inventoryHovered];
+
+ unsigned int zoneId = _inventoryHovered - _inventoryOffset;
+ if (zoneId >= 8) {
+ // The object is hidden: huh?
+ return;
+ }
+
+ _fontManager->setSurface(&_destSurface);
+ _fontManager->setForeColor(243);
+ _fontManager->setCurrentFont(5);
+ _fontManager->setTransparentBackground(true);
+ const Common::String &objName = (*_messages)[obj->idOBJ()];
+ unsigned int x = 195 - _fontManager->getStrWidth(objName);
+ unsigned int startX = _zones[zoneId].rect.left + kTextOffset;
+ _fontManager->displayStr(x, 38 + _position, objName);
+ _destSurface.hLine(x, 54 + _position, startX - 1, 243); // minus 1 because hLine draws inclusive
+ _destSurface.vLine(startX, 42 + _position, 54 + _position, 243);
+ }
+}
+
+bool Toolbar::displayToolbar(const Graphics::Surface *original) {
+ /**
+ * In game there are 2 functions to handle toolbar: one in warp and one in fixed images
+ * This one is the warp one and fixed images have a more asynchronous one during pop-up/down phases
+ * Let's make it simple for now
+ */
+
+ // WORKAROUND: Set cursor here to be more consistent: it's thumb cursor just before showing until just after showed
+ _engine->setCursor(181);
+
+ _parentMustRedraw = false;
+ _shortExit = false;
+
+ // Prepare the background of the toolbar by making it translucent
+ // Get the lowest part of the image
+ const Graphics::Surface subset = original->getSubArea(Common::Rect(0, original->h - _bgSurface.h,
+ _bgSurface.w, original->h));
+ _engine->makeTranslucent(_bgSurface, subset);
+
+ // Draw the original surface on the surface to use for toolbar
+ g_system->copyRectToScreen(original->getPixels(), original->pitch, 0, 0, original->w, original->h);
+
+ // WORKAROUND: Reset the inventory status at init to let sprites highlighted until toolbar is hidden
+ _inventorySelected = -1;
+ _inventoryHovered = -1;
+ _zones[12].secondary = true;
+
+ updateZones();
+
+ for (_position = 60; _position > 0; _position--) {
+ // Make the toolbar go up
+ drawToolbar(original);
+ g_system->copyRectToScreen(_destSurface.getPixels(), _destSurface.pitch, 0,
+ original->h - _destSurface.h, _destSurface.w, _destSurface.h);
+ g_system->updateScreen();
+ // pollEvents will slow down the animation because it waits 10ms
+ _engine->pollEvents();
+ if (g_engine->shouldQuit()) {
+ return false;
+ }
+ }
+
+ // Flush events
+ _engine->clearKeys();
+ _engine->waitMouseRelease();
+
+ handleToolbarEvents(original);
+ if (g_engine->shouldQuit()) {
+ return false;
+ }
+
+ if (_shortExit) {
+ return _parentMustRedraw;
+ }
+
+ for (_position = 0; _position <= 60; _position++) {
+ // Make the toolbar go up
+ drawToolbar(original);
+ g_system->copyRectToScreen(_destSurface.getPixels(), _destSurface.pitch, 0,
+ original->h - _destSurface.h, _destSurface.w, _destSurface.h);
+ g_system->updateScreen();
+ // pollEvents will slow down the animation because it waits 10ms
+ _engine->pollEvents();
+ if (g_engine->shouldQuit()) {
+ return false;
+ }
+ }
+
+ return _parentMustRedraw;
+}
+
+void Toolbar::handleToolbarEvents(const Graphics::Surface *original) {
+ bool mouseInsideToolbar;
+ bool exitToolbar = false;
+ bool redrawToolbar;
+
+ // Don't have anything hovered for now
+ _inventoryHovered = -1;
+ _inventorySelected = -1;
+ _inventory->setSelectedObject(nullptr);
+ _backup_selected_object = nullptr;
+
+ // Refresh zones because we erased selected object
+ updateZones();
+
+ // No need of original surface because the toolbar is fully displayed
+ drawToolbar(original);
+
+ // TODO: countdown
+
+ g_system->copyRectToScreen(_destSurface.getPixels(), _destSurface.pitch, 0,
+ original->h - _destSurface.h, _destSurface.w, _destSurface.h);
+ g_system->updateScreen();
+
+ _engine->setCursor(181);
+
+ mouseInsideToolbar = (_engine->getMousePos().y > 388);
+
+ while (!exitToolbar) {
+ _mouse_in_options = false;
+ _mouse_in_view_object = false;
+
+ _engine->pollEvents();
+ if (g_engine->shouldQuit()) {
+ exitToolbar = true;
+ break;
+ }
+
+ redrawToolbar = false;
+ if (_engine->checkKeysPressed(2, Common::KEYCODE_ESCAPE, Common::KEYCODE_SPACE) ||
+ _engine->getCurrentMouseButton() == 2) {
+ _engine->waitMouseRelease();
+ exitToolbar = true;
+ break;
+ }
+
+ Common::Point mousePosInToolbar = _engine->getMousePos();
+ mousePosInToolbar -= Common::Point(0, 420);
+
+ if (captureEvent(mousePosInToolbar, _engine->getDragStatus())) {
+ // Something has changed with the zones handling, update zones
+ updateZones();
+ redrawToolbar = true;
+ } else if (_engine->getDragStatus() == kDragStatus_Pressed) {
+ // A click happened and wasn't handled, deselect object
+ _inventorySelected = -1;
+ _inventory->setSelectedObject(nullptr);
+ _engine->setCursor(181);
+ // Reset view object
+ _zones[12].secondary = true;
+ updateZones();
+ redrawToolbar = true;
+ }
+
+ if (!mouseInsideToolbar) {
+ mouseInsideToolbar = (_engine->getMousePos().y > 388);
+ } else if (_engine->getMousePos().y <= 388) {
+ // mouseInsideToolbar is true and the mouse is outside the toolbar
+ exitToolbar = true;
+ break;
+ }
+
+ if (_engine->getCurrentMouseButton() == 1) {
+ // When the mouse button is down, nothing is selected
+ // It's selected on release
+ _inventory->setSelectedObject(nullptr);
+ }
+
+ if (_backup_selected_object != nullptr && !(_mouse_in_options || _mouse_in_view_object) &&
+ !_engine->getCurrentMouseButton()) {
+ _inventory->setSelectedObject(_backup_selected_object);
+ _engine->setCursor(_backup_selected_object->idSl());
+ _backup_selected_object = nullptr;
+ }
+
+ // Hover the inventory objects
+ if (_inventory->selectedObject() == nullptr /* || _inventory_button_dragging */) {
+ // The 2nd above condition is maybe useless because when the mouse button is down the selected object is always null
+ bool shouldHover = false;
+ Common::Array<Zone>::const_iterator zoneIt = hitTestZones(mousePosInToolbar);
+ unsigned int zoneId = zoneIt - _zones.begin();
+ unsigned int inventoryId = zoneId + _inventoryOffset;
+ if (zoneId < 8 && inventoryId < _inventory->size() && (*_inventory)[inventoryId] != nullptr) {
+ // It's the inventory
+ shouldHover = true;
+ if (_inventoryHovered != inventoryId && (*_inventory)[inventoryId]->valid()) {
+ // It's not the one currently hovered and it's a valid object
+ _inventoryHovered = inventoryId;
+ redrawToolbar = true;
+ }
+ }
+ if (!shouldHover && _inventoryHovered != -1u && !_mouse_in_view_object) {
+ // Remove hovering
+ _inventoryHovered = -1;
+ _inventorySelected = -1;
+ updateZones();
+ if (!_inventory->selectedObject()) {
+ // Reset back the cursor if nothing is selected
+ _engine->setCursor(181);
+ }
+ // Remove view
+ _zones[12].secondary = true;
+ redrawToolbar = true;
+ }
+ _inventory_button_dragging = false;
+ }
+
+ if (_parentMustRedraw) {
+ break;
+ }
+
+ if (redrawToolbar) {
+ drawToolbar(original);
+ g_system->copyRectToScreen(_destSurface.getPixels(), _destSurface.pitch, 0,
+ original->h - _destSurface.h, _destSurface.w, _destSurface.h);
+ }
+
+ g_system->updateScreen();
+ }
+
+ // Hide description when finished and selected object
+ // WORKAROUND: moved to the start to keep the selected object hilighted until the toolbar disappearance
+}
+
+} // End of namespace Versailles
+} // End of namespace CryOmni3D
diff --git a/engines/cryomni3d/versailles/toolbar.h b/engines/cryomni3d/versailles/toolbar.h
new file mode 100644
index 0000000000..11517cc10b
--- /dev/null
+++ b/engines/cryomni3d/versailles/toolbar.h
@@ -0,0 +1,117 @@
+/* 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 CRYOMNI3D_VERSAILLES_TOOLBAR_H
+#define CRYOMNI3D_VERSAILLES_TOOLBAR_H
+
+#include "common/array.h"
+#include "common/rect.h"
+#include "graphics/managed_surface.h"
+#include "graphics/surface.h"
+
+#include "cryomni3d/font_manager.h"
+#include "cryomni3d/objects.h"
+#include "cryomni3d/sprites.h"
+
+namespace CryOmni3D {
+class CryOmni3DEngine;
+
+namespace Versailles {
+
+
+class Toolbar {
+public:
+ Toolbar() : _sprites(nullptr), _fontManager(nullptr), _inventory(nullptr),
+ _messages(nullptr), _inventoryOffset(0), _engine(nullptr),
+ _inventoryHovered(-1), _inventorySelected(-1), _inventoryEnabled(true),
+ _position(60) { }
+ ~Toolbar();
+
+ void init(const Sprites *sprites, FontManager *fontManager,
+ const Common::Array<Common::String> *messages, Inventory *inventory, CryOmni3DEngine *engine);
+
+ Graphics::Surface &getBackgroundSurface() { return _bgSurface; }
+ bool displayToolbar(const Graphics::Surface *original);
+ void inventoryChanged(unsigned int newPosition);
+ unsigned int inventoryOffset() const { return _inventoryOffset; }
+ void setInventoryOffset(unsigned int offset) { _inventoryOffset = offset; }
+ void setInventoryEnabled(bool enabled) { _inventoryEnabled = enabled; }
+
+private:
+ typedef unsigned int (Toolbar::*ZoneCallback)(unsigned int dragStatus);
+ struct Zone {
+ Common::Rect rect;
+ uint16 imageMain;
+ uint16 imageSecondary;
+ ZoneCallback callback;
+ bool secondary;
+ bool hidden;
+ };
+ Common::Array<Zone> _zones;
+ const Sprites *_sprites;
+ FontManager *_fontManager;
+ const Common::Array<Common::String> *_messages;
+ Inventory *_inventory;
+ CryOmni3DEngine *_engine;
+
+ static const unsigned int kTextOffset = 13;
+
+ void addZone(uint16 cursorMainId, uint16 cursorSecondaryId, Common::Point position,
+ ZoneCallback callback);
+ void updateZones();
+ Common::Array<Zone>::const_iterator hitTestZones(const Common::Point &mousePos) const;
+ unsigned int captureEvent(const Common::Point &mousePos, unsigned int dragStatus);
+ void drawToolbar(const Graphics::Surface *original);
+ void handleToolbarEvents(const Graphics::Surface *original);
+
+ bool _inventoryEnabled;
+ unsigned int _inventoryMaxOffset;
+ unsigned int _inventoryOffset;
+ unsigned int _inventoryHovered;
+ unsigned int _inventorySelected;
+
+ Object *_backup_selected_object;
+ bool _mouse_in_options;
+ bool _mouse_in_view_object;
+ bool _inventory_button_dragging;
+
+ bool _parentMustRedraw;
+ bool _shortExit;
+ unsigned int _position;
+
+ Graphics::Surface _bgSurface;
+ Graphics::ManagedSurface _destSurface;
+
+ template<unsigned int N>
+ unsigned int callbackInventory(unsigned int dragStatus) { return callbackInventory(N, dragStatus); }
+ unsigned int callbackInventory(unsigned int invId, unsigned int dragStatus);
+ unsigned int callbackInventoryPrev(unsigned int dragStatus);
+ unsigned int callbackInventoryNext(unsigned int dragStatus);
+ unsigned int callbackViewObject(unsigned int dragStatus);
+ unsigned int callbackOptions(unsigned int dragStatus);
+ unsigned int callbackDocumentation(unsigned int dragStatus);
+};
+
+} // End of namespace Versailles
+} // End of namespace CryOmni3D
+
+#endif
diff --git a/engines/cryomni3d/video/hnm_decoder.cpp b/engines/cryomni3d/video/hnm_decoder.cpp
new file mode 100644
index 0000000000..1e52744c59
--- /dev/null
+++ b/engines/cryomni3d/video/hnm_decoder.cpp
@@ -0,0 +1,392 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/debug.h"
+#include "common/endian.h"
+#include "common/system.h"
+#include "common/stream.h"
+#include "common/file.h"
+#include "common/textconsole.h"
+
+#include "audio/decoders/raw.h"
+
+#include "cryomni3d/video/hnm_decoder.h"
+#include "cryomni3d/image/codecs/hlz.h"
+
+namespace Video {
+
+// When no sound display a frame every 80ms
+HNMDecoder::HNMDecoder(bool loop) : _regularFrameDelay(80), _videoTrack(nullptr),
+ _audioTrack(nullptr), _stream(nullptr), _loop(loop) {
+}
+
+HNMDecoder::~HNMDecoder() {
+ close();
+
+ // We don't deallocate _videoTrack and _audioTrack as they are owned by base class
+}
+
+bool HNMDecoder::loadStream(Common::SeekableReadStream *stream) {
+ close();
+
+ uint32 tag = stream->readUint32BE();
+
+ /* For now, only HNM4, HNM6 in the future */
+ if (tag != MKTAG('H', 'N', 'M', '4')) {
+ close();
+ return false;
+ }
+
+ //uint32 ukn = stream->readUint32BE();
+ stream->skip(4);
+ uint16 width = stream->readUint16LE();
+ uint16 height = stream->readUint16LE();
+ //uint32 filesize = stream->readUint32LE();
+ stream->skip(4);
+ uint32 frameCount = stream->readUint32LE();
+ //uint32 tabOffset = stream->readUint32LE();
+ stream->skip(4);
+ uint16 soundBits = stream->readUint16LE();
+ uint16 soundChannels = stream->readUint16LE();
+ uint32 frameSize = stream->readUint32LE();
+
+ char unknownStr[16];
+ char copyright[16];
+ stream->read(unknownStr, sizeof(unknownStr));
+ stream->read(copyright, sizeof(copyright));
+
+ if (_loop) {
+ // This will force loop mode
+ frameCount = 0;
+ }
+
+ _videoTrack = new HNM4VideoTrack(width, height, frameSize, frameCount, _regularFrameDelay);
+ if (soundBits != 0 && soundChannels != 0) {
+ // HNM4 is 22050Hz
+ _audioTrack = new DPCMAudioTrack(soundChannels, soundBits, 22050, getSoundType());
+ } else {
+ _audioTrack = nullptr;
+ }
+ addTrack(_videoTrack);
+ addTrack(_audioTrack);
+
+ _stream = stream;
+
+ return true;
+}
+
+void HNMDecoder::close() {
+ VideoDecoder::close();
+ // Tracks are cleant by VideoDecoder::close
+ _videoTrack = nullptr;
+ _audioTrack = nullptr;
+
+ delete _stream;
+ _stream = nullptr;
+}
+
+void HNMDecoder::readNextPacket() {
+ // We are called to feed a frame
+ // Each chunk is packetized and a packet seems to contain only one frame
+ uint32 superchunkRemaining = _stream->readUint32LE();
+ if (!superchunkRemaining) {
+ if (!_loop) {
+ error("End of file but still requesting data");
+ } else {
+ // Looping: read back from start of file, skip header and read a new super chunk header
+ _videoTrack->restart();
+ _stream->seek(64, SEEK_SET);
+ superchunkRemaining = _stream->readUint32LE();
+ }
+ }
+ superchunkRemaining = (superchunkRemaining & 0x00ffffff) - 4;
+
+ while (superchunkRemaining) {
+ uint32 chunkSize = _stream->readUint32LE();
+ uint16 chunkType = _stream->readUint16BE();
+ //uint16 ukn = _stream->readUint16LE();
+ _stream->skip(2);
+
+ if (chunkType == MKTAG16('P', 'L')) {
+ _videoTrack->decodePalette(_stream, chunkSize - 8);
+ } else if (chunkType == MKTAG16('I', 'Z')) {
+ _stream->skip(4);
+ _videoTrack->decodeIntraframe(_stream, chunkSize - 8 - 4);
+ } else if (chunkType == MKTAG16('I', 'U')) {
+ _videoTrack->decodeInterframe(_stream, chunkSize - 8);
+ } else if (chunkType == MKTAG16('S', 'D')) {
+ if (_audioTrack) {
+ Audio::Timestamp duration = _audioTrack->decodeSound(_stream, chunkSize - 8);
+ _videoTrack->setFrameDelay(duration.msecs());
+ } else {
+ error("Shouldn't have audio data");
+ }
+ } else {
+ error("Got %d chunk: size %d", chunkType, chunkSize);
+ }
+
+ superchunkRemaining -= chunkSize;
+ }
+}
+
+HNMDecoder::HNM4VideoTrack::HNM4VideoTrack(uint32 width, uint32 height, uint32 frameSize,
+ uint32 frameCount, uint32 regularFrameDelay) :
+ _frameCount(frameCount), _regularFrameDelay(regularFrameDelay), _nextFrameStartTime(0) {
+
+ restart();
+
+ _curFrame = -1;
+ memset(_palette, 0, 256 * 3);
+
+ if (width * height != frameSize) {
+ error("Invalid frameSize");
+ }
+
+ // We will use _frameBufferC/P as the surface pixels, just init there with nullptr to avoid unintended usage of surface
+ const Graphics::PixelFormat &f = Graphics::PixelFormat::createFormatCLUT8();
+ _surface.init(width, height, width * f.bytesPerPixel, nullptr, f);
+ _frameBufferC = new byte[frameSize];
+ memset(_frameBufferC, 0, frameSize);
+ _frameBufferP = new byte[frameSize];
+ memset(_frameBufferP, 0, frameSize);
+}
+
+HNMDecoder::HNM4VideoTrack::~HNM4VideoTrack() {
+ // Don't free _surface as we didn't used create() but init()
+ delete[] _frameBufferC;
+ _frameBufferC = nullptr;
+ delete[] _frameBufferP;
+ _frameBufferP = nullptr;
+}
+
+void HNMDecoder::HNM4VideoTrack::setFrameDelay(uint32 frameDelay) {
+ if (_nextFrameDelay == -1u) {
+ _nextFrameDelay = frameDelay;
+ } else if (_nextNextFrameDelay == -1u) {
+ _nextNextFrameDelay = frameDelay;
+ } else {
+ _nextNextFrameDelay += frameDelay;
+ }
+}
+
+
+void HNMDecoder::HNM4VideoTrack::decodePalette(Common::SeekableReadStream *stream, uint32 size) {
+ while (true) {
+ if (size < 2) {
+ break;
+ }
+ unsigned int start = stream->readByte();
+ unsigned int count = stream->readByte();
+ size -= 2;
+
+ if (start == 255 && count == 255) {
+ break;
+ }
+ if (count == 0) {
+ count = 256;
+ }
+
+ if (size < count * 3) {
+ error("Invalid palette chunk data");
+ }
+ if (start + count > 256) {
+ error("Invalid palette start/count values");
+ }
+
+ size -= count * 3;
+ byte *palette_ptr = &_palette[start * 3];
+ for (; count > 0; count--) {
+ byte r = stream->readByte();
+ byte g = stream->readByte();
+ byte b = stream->readByte();
+ *(palette_ptr++) = r * 4;
+ *(palette_ptr++) = g * 4;
+ *(palette_ptr++) = b * 4;
+ }
+ }
+ _dirtyPalette = true;
+
+ if (size > 0) {
+ stream->skip(size);
+ }
+}
+
+void HNMDecoder::HNM4VideoTrack::decodeInterframe(Common::SeekableReadStream *stream, uint32 size) {
+ SWAP(_frameBufferC, _frameBufferP);
+
+ uint16 width = _surface.w;
+ bool eop = false;
+
+ unsigned int currentPos = 0;
+
+ while (!eop) {
+ if (size < 1) {
+ warning("Not enough data in chunk for interframe block");
+ break;
+ }
+ byte countFlgs = stream->readByte();
+ size -= 1;
+ byte count = countFlgs & 0x3f;
+ byte flgs = (countFlgs >> 6) & 0x3;
+
+ if (count == 0) {
+ byte c;
+ switch (flgs) {
+ case 0:
+ if (size < 1) {
+ error("Not enough data for case 0");
+ }
+ // Move in image
+ c = stream->readByte();
+ currentPos += c;
+ size -= 1;
+ break;
+ case 1:
+ if (size < 1) {
+ error("Not enough data for case 1");
+ }
+ // New pixels
+ c = stream->readByte();
+ _frameBufferC[currentPos] = c;
+ c = stream->readByte();
+ _frameBufferC[currentPos + width] = c;
+ currentPos++;
+ size -= 2;
+ break;
+ case 2:
+ // New line
+ currentPos += width;
+ break;
+ case 3:
+ // End of picture
+ eop = true;
+ break;
+ }
+ } else {
+ if (size < 2) {
+ error("Not enough data for count > 0");
+ }
+
+ bool negative = (flgs & 0x2) != 0;
+ bool previous = (flgs & 0x1) != 0;
+ int offset = stream->readUint16LE();
+ size -= 2;
+
+ if (negative) {
+ offset -= 0x10000;
+ }
+ offset += currentPos;
+ if (offset < 0) {
+ error("Invalid offset");
+ }
+
+ byte *ptr;
+ if (previous) {
+ ptr = _frameBufferP;
+ } else {
+ ptr = _frameBufferC;
+ }
+ for (; count > 0; count--) {
+ _frameBufferC[currentPos] = ptr[offset];
+ _frameBufferC[currentPos + width] = ptr[offset + width];
+ currentPos++;
+ offset++;
+ }
+ }
+ }
+ _surface.setPixels(_frameBufferC);
+
+ _curFrame++;
+ _nextFrameStartTime += _nextFrameDelay != -1u ? _nextFrameDelay : _regularFrameDelay;
+ _nextFrameDelay = _nextNextFrameDelay;
+ _nextNextFrameDelay = -1u;
+
+ if (size > 0) {
+ stream->skip(size);
+ }
+}
+
+void HNMDecoder::HNM4VideoTrack::decodeIntraframe(Common::SeekableReadStream *stream, uint32 size) {
+ Image::HLZDecoder::decodeFrameInPlace(*stream, size, _frameBufferC);
+ memcpy(_frameBufferP, _frameBufferC, _surface.w * _surface.h);
+ _surface.setPixels(_frameBufferC);
+
+ _curFrame++;
+ _nextFrameStartTime += _nextFrameDelay != -1u ? _nextFrameDelay : _regularFrameDelay;
+ _nextFrameDelay = _nextNextFrameDelay;
+ _nextNextFrameDelay = -1u;
+}
+
+HNMDecoder::DPCMAudioTrack::DPCMAudioTrack(uint16 channels, uint16 bits, unsigned int sampleRate,
+ Audio::Mixer::SoundType soundType) : AudioTrack(soundType), _channels(channels), _bits(bits),
+ _audioStream(nullptr), _gotLUT(false), _lastSample(0) {
+ if (bits != 16) {
+ error("Unsupported audio bits");
+ }
+ if (channels != 2) {
+ warning("Unsupported %d audio channels", channels);
+ }
+ _audioStream = Audio::makeQueuingAudioStream(sampleRate, false);
+}
+
+HNMDecoder::DPCMAudioTrack::~DPCMAudioTrack() {
+ delete _audioStream;
+}
+
+Audio::Timestamp HNMDecoder::DPCMAudioTrack::decodeSound(Common::SeekableReadStream *stream,
+ uint32 size) {
+ if (!_gotLUT) {
+ if (size < 256 * sizeof(*_lut)) {
+ error("Invalid first sound chunk");
+ }
+ stream->read(_lut, 256 * sizeof(*_lut));
+ size -= 256 * sizeof(*_lut);
+#ifndef SCUMM_LITTLE_ENDIAN
+ for (unsigned int i = 0; i < 256; i++) {
+ _lut[i] = FROM_LE_16(_lut[i]);
+ }
+#endif
+ _gotLUT = true;
+ }
+
+ if (size > 0) {
+ uint16 *out = (uint16 *)malloc(size * sizeof(*out));
+ uint16 *p = out;
+ uint16 sample = _lastSample;
+ for (uint32 i = 0; i < size; i++, p++) {
+ byte deltaId = stream->readByte();
+ sample += _lut[deltaId];
+ *p = sample;
+ }
+ _lastSample = sample;
+
+ byte flags = Audio::FLAG_16BITS;
+#ifdef SCUMM_LITTLE_ENDIAN
+ flags |= Audio::FLAG_LITTLE_ENDIAN;
+#endif
+ // channels is 2 but we are MONO!
+ _audioStream->queueBuffer((byte *)out, size * sizeof(*out), DisposeAfterUse::YES, flags);
+ }
+ return Audio::Timestamp(0, size, 22050);
+}
+
+} // End of namespace Video
diff --git a/engines/cryomni3d/video/hnm_decoder.h b/engines/cryomni3d/video/hnm_decoder.h
new file mode 100644
index 0000000000..681dd87e4b
--- /dev/null
+++ b/engines/cryomni3d/video/hnm_decoder.h
@@ -0,0 +1,129 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef CRYOMNI3D_VIDEO_HNM_DECODER_H
+#define CRYOMNI3D_VIDEO_HNM_DECODER_H
+
+#include "common/rational.h"
+#include "graphics/pixelformat.h"
+#include "video/video_decoder.h"
+#include "graphics/surface.h"
+#include "audio/audiostream.h"
+
+namespace Common {
+class SeekableReadStream;
+}
+
+namespace Video {
+
+/**
+ * Decoder for HNM videos.
+ *
+ * Video decoder used in engines:
+ * - cryomni3d
+ */
+class HNMDecoder : public VideoDecoder {
+public:
+ HNMDecoder(bool loop = false);
+ virtual ~HNMDecoder();
+ bool loadStream(Common::SeekableReadStream *stream);
+ void readNextPacket();
+ void close();
+
+ void setRegularFrameDelay(uint32 regularFrameDelay) { _regularFrameDelay = regularFrameDelay; }
+
+private:
+ class HNM4VideoTrack : public VideoTrack {
+ public:
+ HNM4VideoTrack(uint32 width, uint32 height, uint32 frameSize, uint32 frameCount,
+ uint32 regularFrameDelay);
+ ~HNM4VideoTrack();
+
+ // When _frameCount is 0, it means we are looping
+ bool endOfTrack() const { return (_frameCount == 0) ? false : VideoTrack::endOfTrack(); }
+ uint16 getWidth() const { return _surface.w; }
+ uint16 getHeight() const { return _surface.h; }
+ Graphics::PixelFormat getPixelFormat() const { return _surface.format; }
+ int getCurFrame() const { return _curFrame; }
+ int getFrameCount() const { return _frameCount; }
+ uint32 getNextFrameStartTime() const { return _nextFrameStartTime; }
+ const Graphics::Surface *decodeNextFrame() { return &_surface; }
+ const byte *getPalette() const { _dirtyPalette = false; return _palette; }
+ bool hasDirtyPalette() const { return _dirtyPalette; }
+
+ /** Decode a video chunk. */
+ void decodePalette(Common::SeekableReadStream *stream, uint32 size);
+ void decodeInterframe(Common::SeekableReadStream *stream, uint32 size);
+ void decodeIntraframe(Common::SeekableReadStream *stream, uint32 size);
+
+ void restart() { _nextFrameDelay = -1; _nextNextFrameDelay = -1; }
+ void setFrameDelay(uint32 frameDelay);
+
+ private:
+ Graphics::Surface _surface;
+
+ uint32 _regularFrameDelay;
+ uint32 _nextFrameDelay;
+ uint32 _nextNextFrameDelay;
+ uint32 _nextFrameStartTime;
+
+ uint32 _frameCount;
+ int _curFrame;
+
+ byte _palette[256 * 3];
+ mutable bool _dirtyPalette;
+
+ byte *_frameBufferC;
+ byte *_frameBufferP;
+ };
+
+ class DPCMAudioTrack : public AudioTrack {
+ public:
+ DPCMAudioTrack(uint16 channels, uint16 bits, unsigned int sampleRate,
+ Audio::Mixer::SoundType soundType);
+ ~DPCMAudioTrack();
+
+ Audio::Timestamp decodeSound(Common::SeekableReadStream *stream, uint32 size);
+ protected:
+ Audio::AudioStream *getAudioStream() const { return _audioStream; }
+ private:
+ uint16 _channels;
+ uint16 _bits;
+ Audio::QueuingAudioStream *_audioStream;
+ bool _gotLUT;
+ uint16 _lut[256];
+ uint16 _lastSample;
+ };
+
+ bool _loop;
+
+ uint32 _regularFrameDelay;
+ // These two pointer are owned by VideoDecoder
+ HNM4VideoTrack *_videoTrack;
+ DPCMAudioTrack *_audioTrack;
+
+ Common::SeekableReadStream *_stream;
+};
+
+} // End of namespace Video
+
+#endif
diff --git a/engines/cryomni3d/wam_parser.cpp b/engines/cryomni3d/wam_parser.cpp
new file mode 100644
index 0000000000..a27e3ab69e
--- /dev/null
+++ b/engines/cryomni3d/wam_parser.cpp
@@ -0,0 +1,216 @@
+/* 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/stream.h"
+
+#include "engines/cryomni3d/wam_parser.h"
+#include "engines/cryomni3d/omni3d.h"
+
+namespace CryOmni3D {
+
+void WAMParser::loadStream(Common::ReadStream &stream) {
+ char str[16];
+
+ _places.clear();
+
+ stream.readByte();
+ stream.readByte();
+ stream.read(str, 16);
+ stream.readUint32LE();
+
+ unsigned int nPlaces = stream.readByte();
+ //debug("nPlaces = %u", nPlaces);
+ for (unsigned int i = 0; i < nPlaces; i++) {
+ Place place;
+ unsigned int nWarps = stream.readByte();
+ //debug("nWarps = %u", nWarps);
+ for (unsigned int k = 0; k < 8; k++) {
+ stream.read(str, 16);
+ //debug("Warp: %.16s", str);
+ if (nWarps > 0) {
+ place.warps.push_back(str);
+ nWarps--;
+ }
+ }
+ place.placeId = stream.readUint32LE();
+ // Normally placeId should be unique but it's not always the case
+ // In original game the last place is considered but we try to be efficient and stop at the first place in findPlaceById
+ // Let's be a little less efficient at startup by removing duplicates
+ Place *oldPlace = findPlaceById_(place.placeId);
+ if (oldPlace) {
+ debug("Found duplicate place %u at %u, removing it", place.placeId,
+ (unsigned int)(oldPlace - _places.begin()));
+ _places.erase(oldPlace);
+ }
+ //debug("nPlaceId = %u", place.placeId);
+ stream.readUint32LE();
+ unsigned int nTransitions = stream.readByte();
+ //debug("nTransitions = %u", nTransitions);
+ unsigned int nZones = stream.readByte();
+ //debug("nZones = %u", nZones);
+ for (unsigned int j = 0; j < nTransitions; j++) {
+ Transition trans;
+ stream.readUint32LE();
+ unsigned int nAnimations = stream.readByte();
+ for (unsigned int k = 0; k < 8; k++) {
+ stream.read(str, 16);
+ if (nAnimations > 0) {
+ trans.animations.push_back(str);
+ nAnimations--;
+ }
+ }
+ stream.readUint32LE();
+ trans.dstId = stream.readUint32LE();
+ stream.readByte();
+ trans.srcAlpha = stream.readDoubleLE();
+ trans.srcBeta = stream.readDoubleLE();
+ trans.dstAlpha = stream.readDoubleLE();
+ trans.dstBeta = stream.readDoubleLE();
+ place.transitions.push_back(trans);
+ }
+ for (unsigned int j = 0; j < nZones; j++) {
+ Zone zone;
+ zone.zoneId = stream.readSint32LE();
+ zone.rct.left = stream.readSint32LE();
+ zone.rct.top = stream.readSint32LE();
+ zone.rct.setWidth(stream.readSint32LE());
+ zone.rct.setHeight(stream.readSint32LE());
+ zone.action = stream.readSint32LE();
+ place.zones.push_back(zone);
+ }
+ _places.push_back(place);
+ }
+}
+
+const Place *WAMParser::findPlaceById(unsigned int placeId) const {
+ for (Common::Array<Place>::const_iterator it = _places.begin(); it != _places.end(); it++) {
+ if (it->placeId == placeId) {
+ return it;
+ }
+ }
+ return nullptr;
+}
+
+Place *WAMParser::findPlaceById_(unsigned int placeId) {
+ for (Common::Array<Place>::iterator it = _places.begin(); it != _places.end(); it++) {
+ if (it->placeId == placeId) {
+ return it;
+ }
+ }
+ return nullptr;
+}
+
+void Place::setupWarpConstraints(Omni3DManager &omni3d) const {
+ int16 iAlphaMin, iAlphaMax;
+ bool alphaConstraint = false;
+
+ omni3d.clearConstraints();
+ for (Common::Array<Zone>::const_iterator it = zones.begin(); it != zones.end(); it++) {
+ if (it->action == 100000) {
+ int16 aMin = it->rct.left;
+ if (aMin < 0) {
+ aMin += 2048;
+ }
+ int16 aMax = aMin + it->rct.width();
+ if (aMax > 2048) {
+ aMax -= 2048;
+ }
+ // debug("x1=%d x2=%d", aMin, aMax);
+ if (aMax < aMin) {
+ int16 tmp = aMax;
+ aMax = aMin;
+ aMin = tmp;
+ }
+ if (alphaConstraint) {
+ if (aMin < iAlphaMax && aMax > iAlphaMax) {
+ iAlphaMax = aMax;
+ }
+ if (aMin < iAlphaMin && aMax > iAlphaMin) {
+ iAlphaMin = aMin;
+ }
+ } else {
+ iAlphaMin = aMin;
+ iAlphaMax = aMax;
+ alphaConstraint = true;
+ }
+ } else if (it->action == 200000) {
+ double betaMin = ((int)it->rct.bottom - (768 / 2)) / 768. * M_PI;
+ omni3d.setBetaMinConstraint(betaMin);
+ } else if (it->action == 300000) {
+ double betaMax = ((int)it->rct.top - (768 / 2)) / 768. * M_PI;
+ omni3d.setBetaMaxConstraint(betaMax);
+ }
+ }
+ if (alphaConstraint) {
+ double alphaMin = (1 - iAlphaMin / 2048.) * M_PI * 2.;
+ alphaMin += 75. / 180. * M_PI_2;
+ if (alphaMin < 0.) {
+ alphaMin += M_PI * 2.;
+ } else if (alphaMin > M_PI * 2.) {
+ alphaMin -= M_PI * 2.;
+ }
+ double alphaMax = (1 - iAlphaMax / 2048.) * M_PI * 2.;
+ alphaMax -= 75. / 180. * M_PI_2;
+ if (alphaMax < 0.) {
+ alphaMax += M_PI * 2.;
+ } else if (alphaMax > M_PI * 2.) {
+ alphaMax -= M_PI * 2.;
+ }
+ omni3d.setAlphaConstraints(alphaMin, alphaMax);
+ }
+}
+
+unsigned int Place::hitTest(const Common::Point &point) const {
+ for (Common::Array<Zone>::const_iterator it = zones.begin(); it != zones.end(); it++) {
+ if (it->action) {
+ if (it->rct.contains(point)) {
+ return it->action;
+ }
+ if (it->rct.left < 0) {
+ Common::Rect rct = it->rct;
+ rct.translate(2048, 0);
+ if (rct.contains(point)) {
+ return it->action;
+ }
+ } else if (it->rct.right > 2048) {
+ Common::Rect rct = it->rct;
+ rct.translate(-2048, 0);
+ if (rct.contains(point)) {
+ return it->action;
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+const Transition *Place::findTransition(unsigned int nextPlaceId) const {
+ for (Common::Array<Transition>::const_iterator it = transitions.begin(); it != transitions.end();
+ it++) {
+ if (it->dstId == nextPlaceId) {
+ return it;
+ }
+ }
+ return nullptr;
+}
+
+} // End of namespace CryOmni3D
diff --git a/engines/cryomni3d/wam_parser.h b/engines/cryomni3d/wam_parser.h
new file mode 100644
index 0000000000..6f678afd11
--- /dev/null
+++ b/engines/cryomni3d/wam_parser.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 CRYOMNI3D_WAM_PARSER_H
+#define CRYOMNI3D_WAM_PARSER_H
+
+#include "common/array.h"
+#include "common/rect.h"
+#include "common/str.h"
+
+namespace Common {
+class ReadStream;
+}
+
+namespace CryOmni3D {
+
+class Omni3DManager;
+
+struct Zone {
+ unsigned int zoneId;
+ unsigned int action;
+ Common::Rect rct;
+};
+
+struct Transition {
+ unsigned int dstId;
+ double srcAlpha;
+ double srcBeta;
+ double dstAlpha;
+ double dstBeta;
+ Common::Array<Common::String> animations;
+ unsigned int getNumAnimations() const { return animations.size(); }
+};
+
+struct Place {
+ unsigned int placeId;
+ Common::Array<Common::String> warps;
+ Common::Array<Transition> transitions;
+ Common::Array<Zone> zones;
+
+ unsigned int getNumStates() const { return warps.size(); }
+ unsigned int getNumTransitions() const { return transitions.size(); }
+ void setupWarpConstraints(Omni3DManager &omni3d) const;
+ unsigned int hitTest(const Common::Point &point) const;
+ const Transition *findTransition(unsigned int nextPlaceId) const;
+};
+
+class WAMParser {
+public:
+ void loadStream(Common::ReadStream &stream);
+ const Place *findPlaceById(unsigned int placeId) const;
+
+private:
+ // For duplicate finding
+ // We use a different name because else it gets chosen before the const one and fails because it's private
+ Place *findPlaceById_(unsigned int placeId);
+ Common::Array<Place> _places;
+};
+
+} // End of namespace CryOmni3D
+
+#endif