mirror of
https://github.com/libretro/scummvm.git
synced 2025-01-10 20:01:25 +00:00
b45197642e
As the warning is emitted on parsing the class constructor, just move it out of the class definition.
380 lines
11 KiB
C++
380 lines
11 KiB
C++
/* 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 {
|
|
|
|
Versailles_DialogsManager::Versailles_DialogsManager(CryOmni3DEngine_Versailles *engine,
|
|
bool padAudioFileName) :
|
|
_engine(engine), _padAudioFileName(padAudioFileName) {
|
|
}
|
|
|
|
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<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) {
|
|
// 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
|