/* 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 = _engine->showMouse(true); bool slowStop = false; bool didSth = DialogsManager::play(sequence, slowStop); _engine->showMouse(cursorWasVisible); if (didSth && slowStop) { if (_engine->showSubtitles()) { bool skip = false; uint end = g_system->getMillis() + 2000; while (!_engine->shouldAbort() && g_system->getMillis() < end && !skip) { g_system->updateScreen(); g_system->delayMillis(10); 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::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) { // Don't look for HNS file here Common::String videoFName(_engine->prepareFileName(video, "hnm")); Common::String soundFName(sound); if (_padAudioFileName) { while (soundFName.size() < 8) { soundFName += '_'; } } soundFName = _engine->prepareFileName(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; } _engine->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; uint lines = fontManager.getLinesCount(text, block.width() - 8); if (lines == 0) { lines = 5; } uint 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; uint duration = 100 * text.size(); if (duration < 1000) { duration = 1000; } bool skipWait = false; uint end = g_system->getMillis() + duration; while (!_engine->shouldAbort() && g_system->getMillis() < end && !skipWait) { g_system->updateScreen(); g_system->delayMillis(10); 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 (!_engine->shouldAbort() && _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(); g_system->delayMillis(10); } _engine->_mixer->stopHandle(audioHandle); } // It's intentional that _lastImage is set with the first video image delete videoDecoder; _engine->showMouse(true); } void Versailles_DialogsManager::displayMessage(const Common::String &text) { _engine->displayMessageBoxWarp(text); } uint Versailles_DialogsManager::askPlayerQuestions(const Common::String &video, const Common::StringArray &questions) { if (_lastImage.empty()) { loadFrame(video); } if (questions.size() == 0 || questions.size() > 5) { return uint(-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; uint questionId = 0; for (Common::StringArray::const_iterator it = questions.begin(); it != questions.end(); it++, questionId++) { tops[questionId] = currentHeight; uint 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; uint selectedQuestion = uint(-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(); g_system->delayMillis(10); if (_engine->pollEvents()) { _engine->clearKeys(); if (_engine->shouldAbort()) { finished = true; selectedQuestion = uint(-1); break; } Common::Point mousePos = _engine->getMousePos(); if (_engine->getDragStatus() == kDragStatus_Finished && selectedQuestion != uint(-1)) { finished = true; } else if (mousePos.x >= 608 || mousePos.y < offsetY) { if (selectedQuestion != uint(-1)) { selectedQuestion = uint(-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 = uint(-1); update = true; } } } } return selectedQuestion; } void Versailles_DialogsManager::loadFrame(const Common::String &video) { Common::String videoFName(_engine->prepareFileName(video, "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; } // Preload first frame to draw questions on it const Graphics::Surface *firstFrame = videoDecoder->decodeNextFrame(); _lastImage.create(firstFrame->w, firstFrame->h, firstFrame->format); _lastImage.blitFrom(*firstFrame); if (videoDecoder->hasDirtyPalette()) { const byte *palette = videoDecoder->getPalette(); _engine->setupPalette(palette, 0, 256); } } } // End of namespace Versailles } // End of namespace CryOmni3D