diff options
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 >oFileName) { + 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 >oFile); + + 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 ¤tZone) { + 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 ¤tZone); + + 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, ®, ®Bits) + + 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 ¶ms, 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 ¶ms, 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 ¶ms, + 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 |