aboutsummaryrefslogtreecommitdiff
path: root/engines/cryomni3d/versailles/engine.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/cryomni3d/versailles/engine.cpp')
-rw-r--r--engines/cryomni3d/versailles/engine.cpp1443
1 files changed, 1443 insertions, 0 deletions
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