mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-15 06:08:35 +00:00
1115 lines
32 KiB
C++
1115 lines
32 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 3 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, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Copyright 2020 Google
|
|
*
|
|
*/
|
|
|
|
#include "common/file.h"
|
|
#include "hadesch/video.h"
|
|
#include "hadesch/pod_image.h"
|
|
#include "hadesch/tag_file.h"
|
|
#include "hadesch/hadesch.h"
|
|
#include "common/system.h"
|
|
#include "video/smk_decoder.h"
|
|
#include "audio/decoders/aiff.h"
|
|
#include "hadesch/pod_file.h"
|
|
#include "hadesch/baptr.h"
|
|
#include "common/macresman.h"
|
|
|
|
static const int kVideoMaxW = 1280;
|
|
static const int kVideoMaxH = 480;
|
|
|
|
namespace Hadesch {
|
|
|
|
Common::String LayerId::getDebug() const {
|
|
if (_idx == -1 && _qualifier == "")
|
|
return "[" + _name + "]";
|
|
return Common::String::format("[%s/%s/%d]",
|
|
_name.c_str(), _qualifier.c_str(), _idx);
|
|
}
|
|
|
|
bool LayerId::operator== (const LayerId &b) const {
|
|
return _name == b._name && _idx == b._idx && _qualifier == b._qualifier;
|
|
}
|
|
|
|
PlayAnimParams PlayAnimParams::loop() {
|
|
return PlayAnimParams(true, true);
|
|
}
|
|
|
|
PlayAnimParams PlayAnimParams::keepLastFrame() {
|
|
return PlayAnimParams(false, true);
|
|
}
|
|
|
|
PlayAnimParams PlayAnimParams::disappear() {
|
|
return PlayAnimParams(false, false);
|
|
}
|
|
|
|
bool PlayAnimParams::getKeepLastFrame() {
|
|
return _keepLastFrame;
|
|
}
|
|
|
|
bool PlayAnimParams::isLoop() {
|
|
return _loop;
|
|
}
|
|
|
|
int PlayAnimParams::getSpeed() {
|
|
return _msperframe;
|
|
}
|
|
|
|
int PlayAnimParams::getFirstFrame() {
|
|
return _firstFrame;
|
|
}
|
|
|
|
int PlayAnimParams::getLastFrame() {
|
|
return _lastFrame;
|
|
}
|
|
|
|
PlayAnimParams PlayAnimParams::partial(int first, int last) const {
|
|
PlayAnimParams ret(*this);
|
|
ret._firstFrame = first;
|
|
ret._lastFrame = last;
|
|
return ret;
|
|
}
|
|
|
|
PlayAnimParams PlayAnimParams::speed(int msperframe) const {
|
|
PlayAnimParams ret(*this);
|
|
ret._msperframe = msperframe;
|
|
return ret;
|
|
}
|
|
|
|
PlayAnimParams PlayAnimParams::backwards() const {
|
|
PlayAnimParams ret(*this);
|
|
ret._firstFrame = _lastFrame;
|
|
ret._lastFrame = _firstFrame;
|
|
return ret;
|
|
}
|
|
|
|
PlayAnimParams::PlayAnimParams(bool isLoop, bool isKeepLastFrame) {
|
|
_loop = isLoop;
|
|
_keepLastFrame = isKeepLastFrame;
|
|
_firstFrame = 0;
|
|
_lastFrame = -1;
|
|
_msperframe = kDefaultSpeed;
|
|
}
|
|
|
|
int VideoRoom::layerComparator(const Layer &a, const Layer &b) {
|
|
if (a.zValue == b.zValue)
|
|
return a.genCounter - b.genCounter;
|
|
return b.zValue - a.zValue;
|
|
}
|
|
|
|
VideoRoom::VideoRoom(const Common::String &dir, const Common::String &pod,
|
|
const Common::String &assetMapFile) : _layers(layerComparator) {
|
|
Common::String podPath = g_vm->getCDScenesPath() + dir + "/" + pod + ".pod";
|
|
_podFile = Common::SharedPtr<PodFile>(new PodFile(podPath));
|
|
_podFile->openStore(Common::Path(podPath));
|
|
_smkPath = g_vm->getCDScenesPath() + dir;
|
|
Common::SharedPtr<Common::SeekableReadStream> assetMapStream(assetMapFile != "" ? openFile(assetMapFile) : nullptr);
|
|
if (assetMapStream) {
|
|
_assetMap = TextTable(assetMapStream, 2);
|
|
}
|
|
|
|
_videoPixels = sharedPtrByteAlloc(kVideoMaxW * kVideoMaxH);
|
|
_hotZone = -1;
|
|
_mouseEnabled = true;
|
|
_videoOffset = Common::Point(0, 0);
|
|
_videoSurfOffset = Common::Point(0, 0);
|
|
_pan = 0;
|
|
_panSpeed = 0;
|
|
_panCallback = -1;
|
|
_pannable = false;
|
|
_leftEdge = false;
|
|
_rightEdge = false;
|
|
_heroBeltEnabled = true;
|
|
_userPanStartLeftCallback = -1;
|
|
_userPanStartRightCallback = -1;
|
|
_userPanEndLeftCallback = -1;
|
|
_userPanEndRightCallback = -1;
|
|
_videoZ = 0;
|
|
_layerGenCounter = 0;
|
|
_finalFade = 0x100;
|
|
_finalFadeSpeed = 0;
|
|
_draggingPtr = 0;
|
|
_isDragging = 0;
|
|
}
|
|
|
|
VideoRoom::~VideoRoom() {
|
|
if (_videoDecoder) {
|
|
_videoDecoder->stop();
|
|
_videoDecoder.reset();
|
|
}
|
|
for (unsigned i = 0; i < _anims.size(); i++)
|
|
g_system->getMixer()->stopHandle(_anims[i]._soundHandle);
|
|
}
|
|
|
|
void VideoRoom::setPannable(bool val) {
|
|
_pannable = val;
|
|
}
|
|
|
|
void VideoRoom::setHotzoneEnabled(const Common::String &name, bool enabled) {
|
|
_hotZones.setHotzoneEnabled(name, enabled);
|
|
}
|
|
|
|
void VideoRoom::enableHotzone(const Common::String &name) {
|
|
_hotZones.setHotzoneEnabled(name, true);
|
|
}
|
|
|
|
void VideoRoom::disableHotzone(const Common::String &name) {
|
|
_hotZones.setHotzoneEnabled(name, false);
|
|
}
|
|
|
|
void VideoRoom::setLayerEnabled(const LayerId &name, bool val) {
|
|
for (Common::SortedArray<Layer>::iterator it = _layers.begin(); it != _layers.end(); it++) {
|
|
if (it->name == name)
|
|
it->isEnabled = val;
|
|
}
|
|
}
|
|
|
|
void VideoRoom::setLayerParallax(const LayerId &name, int val) {
|
|
for (Common::SortedArray<Layer>::iterator it = _layers.begin(); it != _layers.end(); it++) {
|
|
if (it->name == name)
|
|
it->parallax = val;
|
|
}
|
|
}
|
|
|
|
void VideoRoom::setColorScale(const LayerId &name, int val) {
|
|
for (Common::SortedArray<Layer>::iterator it = _layers.begin(); it != _layers.end(); it++) {
|
|
if (it->name == name)
|
|
it->colorScale = val;
|
|
}
|
|
}
|
|
|
|
void VideoRoom::setScale(const LayerId &name, int val) {
|
|
for (Common::SortedArray<Layer>::iterator it = _layers.begin(); it != _layers.end(); it++) {
|
|
if (it->name == name)
|
|
it->scale = val;
|
|
}
|
|
}
|
|
|
|
int VideoRoom::getNumFrames(const LayerId &name) {
|
|
for (Common::SortedArray<Layer>::iterator it = _layers.begin(); it != _layers.end(); it++) {
|
|
if (it->name == name)
|
|
return it->renderable->getNumFrames();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void VideoRoom::startAnimationInternal(const LayerId &name, int zValue, int msperframe, bool loop,
|
|
bool fixedFrame,
|
|
int first, int last, Common::Point offset) {
|
|
int32 startms = g_vm->getCurrentTime();
|
|
Common::Array<Layer> modifiedZ;
|
|
for (Common::SortedArray<Layer>::iterator it = _layers.begin(); it != _layers.end();) {
|
|
if (it->name == name) {
|
|
it->isEnabled = true;
|
|
if (fixedFrame)
|
|
it->renderable->selectFrame(first);
|
|
else
|
|
it->renderable->startAnimation(startms, msperframe, loop, first, last);
|
|
it->offset = offset;
|
|
if (it->zValue != zValue) {
|
|
Layer l = *it;
|
|
l.zValue = zValue;
|
|
modifiedZ.push_back(l);
|
|
it = _layers.erase(it);
|
|
} else {
|
|
it++;
|
|
}
|
|
} else {
|
|
it++;
|
|
}
|
|
}
|
|
// This is slow but should rarely happen
|
|
if (!modifiedZ.empty()) {
|
|
for (Common::Array<Layer>::iterator it = modifiedZ.begin(); it != modifiedZ.end(); it++) {
|
|
_layers.insert(*it);
|
|
}
|
|
}
|
|
}
|
|
|
|
void VideoRoom::selectFrame(const LayerId &name, int zValue, int frame, Common::Point offset) {
|
|
if (!doesLayerExist(name)) {
|
|
addAnimLayerInternal(name, zValue);
|
|
}
|
|
|
|
startAnimationInternal(name, zValue, kDefaultSpeed, true, true, frame, frame, offset);
|
|
}
|
|
|
|
PodImage VideoRoom::getLayerFrame(const Hadesch::LayerId &name) {
|
|
for (Common::SortedArray<Layer>::iterator it = _layers.begin(); it != _layers.end(); it++) {
|
|
if (it->name == name) {
|
|
return it->renderable->getFrame(g_vm->getCurrentTime());
|
|
}
|
|
}
|
|
|
|
return PodImage();
|
|
}
|
|
|
|
int VideoRoom::getAnimFrameNum(const Hadesch::LayerId &name) {
|
|
for (Common::SortedArray<Layer>::iterator it = _layers.begin(); it != _layers.end(); it++) {
|
|
if (it->name == name) {
|
|
return it->renderable->getAnimationFrameNum(g_vm->getCurrentTime());
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
void VideoRoom::stopAnim(const LayerId &name) {
|
|
for (Common::SortedArray<Layer>::iterator it = _layers.begin(); it != _layers.end(); it++) {
|
|
if (it->name == name) {
|
|
it->isEnabled = false;
|
|
}
|
|
}
|
|
for (unsigned i = 0; i < _anims.size(); i++) {
|
|
if (_anims[i]._animName == name) {
|
|
g_system->getMixer()->stopHandle(_anims[i]._soundHandle);
|
|
_anims[i]._finished = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void VideoRoom::purgeAnim(const LayerId &name) {
|
|
for (Common::SortedArray<Layer>::iterator it = _layers.begin(); it != _layers.end();) {
|
|
if (it->name == name) {
|
|
it = _layers.erase(it);
|
|
} else
|
|
it++;
|
|
}
|
|
}
|
|
|
|
void VideoRoom::dumpLayers() {
|
|
debug("Current layers:");
|
|
for (Common::SortedArray<Layer>::iterator it = _layers.begin(); it != _layers.end(); it++) {
|
|
debug(" %s %s", it->name.getDebug().c_str(), it->isEnabled ? "enabled" : "disabled");
|
|
}
|
|
}
|
|
|
|
void VideoRoom::finish() {
|
|
for (unsigned i = 0; i < _anims.size(); i++) {
|
|
g_system->getMixer()->stopHandle(_anims[i]._soundHandle);
|
|
_anims[i]._finished = true;
|
|
}
|
|
}
|
|
|
|
void VideoRoom::pause() {
|
|
for (unsigned i = 0; i < _anims.size(); i++) {
|
|
g_system->getMixer()->pauseHandle(_anims[i]._soundHandle, true);
|
|
}
|
|
}
|
|
|
|
void VideoRoom::unpause() {
|
|
for (unsigned i = 0; i < _anims.size(); i++) {
|
|
g_system->getMixer()->pauseHandle(_anims[i]._soundHandle, false);
|
|
}
|
|
}
|
|
|
|
bool VideoRoom::doesLayerExist(const LayerId &name) {
|
|
for (Common::SortedArray<Layer>::iterator it = _layers.begin(); it != _layers.end(); it++) {
|
|
if (it->name == name) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool VideoRoom::isAnimationFinished(const LayerId &name, int time) {
|
|
for (Common::SortedArray<Layer>::iterator it = _layers.begin(); it != _layers.end(); it++) {
|
|
if (it->name == name) {
|
|
return it->renderable->isAnimationFinished(time);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void VideoRoom::addLayer(Renderable *rend, const LayerId &name, int zValue,
|
|
bool isEnabled, Common::Point offset) {
|
|
Layer l;
|
|
l.renderable = Common::SharedPtr<Renderable>(rend);
|
|
l.name = name;
|
|
l.isEnabled = isEnabled;
|
|
l.offset = offset;
|
|
l.zValue = zValue;
|
|
l.parallax = 0;
|
|
l.colorScale = 0x100;
|
|
l.scale = 100;
|
|
l.genCounter = _layerGenCounter++;
|
|
_layers.insert(l);
|
|
}
|
|
|
|
void VideoRoom::computeHotZone(int time, Common::Point mousePos) {
|
|
bool wasEdge = _leftEdge || _rightEdge;
|
|
_leftEdge = false;
|
|
_rightEdge = false;
|
|
|
|
Common::SharedPtr<HeroBelt> belt = g_vm->getHeroBelt();
|
|
|
|
if (_heroBeltEnabled)
|
|
belt->computeHotZone(time, mousePos, _mouseEnabled);
|
|
if (_heroBeltEnabled && belt->isOverHeroBelt()) {
|
|
_hotZone = -1;
|
|
return;
|
|
}
|
|
if (!_mouseEnabled) {
|
|
_hotZone = -1;
|
|
return;
|
|
}
|
|
Common::Point canvasPos = mousePos + Common::Point(_pan, 0);
|
|
int wasHotZone = _hotZone;
|
|
_hotZone = _hotZones.pointToIndex(canvasPos);
|
|
if (_hotZone >= 0) {
|
|
if (wasHotZone < 0) {
|
|
_startHotTime = time;
|
|
}
|
|
return;
|
|
}
|
|
_leftEdge = (mousePos.x < 20) && _pan > 0;
|
|
_rightEdge = (mousePos.x > 620) && _pan < 640;
|
|
if ((!wasEdge && (_leftEdge || _rightEdge)))
|
|
_edgeStartTime = time;
|
|
_hotZone = -1;
|
|
}
|
|
|
|
void VideoRoom::setHotZoneOffset(const Common::String &name, Common::Point offset) {
|
|
_hotZones.setHotZoneOffset(name, offset);
|
|
}
|
|
|
|
Common::String VideoRoom::mapClick(Common::Point mousePos) {
|
|
if (!_mouseEnabled) {
|
|
return "";
|
|
}
|
|
Common::Point canvasPos = mousePos + Common::Point(_pan, 0) + _viewportOffset;
|
|
return _hotZones.pointToName(canvasPos);
|
|
}
|
|
|
|
Common::String VideoRoom::getHotZone() {
|
|
return _hotZones.indexToName(_hotZone);
|
|
}
|
|
|
|
int VideoRoom::getCursorAnimationFrame(int time) {
|
|
if (_hotZone < 0)
|
|
return -1;
|
|
if (_hotZones.indexToICSH(_hotZone) != 0)
|
|
return -1;
|
|
return (time - _startHotTime) / kDefaultSpeed;
|
|
}
|
|
|
|
bool VideoRoom::isVideoPlaying() {
|
|
return !!_videoDecoder;
|
|
}
|
|
|
|
void VideoRoom::fadeOut(int ms, const EventHandlerWrapper &callback) {
|
|
_finalFadeCallback = callback;
|
|
_finalFadeSpeed = -(kDefaultSpeed * 0x100) / ms;
|
|
}
|
|
|
|
void VideoRoom::nextFrame(Common::SharedPtr<GfxContext> context, int time, bool stopVideo) {
|
|
context->clear();
|
|
|
|
Common::SharedPtr<HeroBelt> belt = g_vm->getHeroBelt();
|
|
|
|
if (!_mouseEnabled) {
|
|
_cursor = (time / 200) % 10 + 3;
|
|
} else if (_heroBeltEnabled && belt->isOverHeroBelt()) {
|
|
_cursor = belt->getCursor(time);
|
|
} else if (g_vm->getPersistent()->_currentRoomId == kMonsterPuzzle) {
|
|
_cursor = 17;
|
|
} else if (_hotZone >= 0) {
|
|
_cursor = _hotZones.indexToCursor(_hotZone, (time - _startHotTime) / kDefaultSpeed);
|
|
} else if (_leftEdge && _pannable) {
|
|
_cursor = 14;
|
|
} else if (_rightEdge && _pannable) {
|
|
_cursor = 16;
|
|
} else
|
|
_cursor = 0;
|
|
|
|
if (_leftEdge && (time > _edgeStartTime + 2000) && _panSpeed == 0 && _pannable && _mouseEnabled) {
|
|
g_vm->handleEvent(_userPanStartLeftCallback);
|
|
panLeftAnim(_userPanEndLeftCallback);
|
|
}
|
|
if (_rightEdge && (time > _edgeStartTime + 2000) && _panSpeed == 0 && _pannable && _mouseEnabled) {
|
|
g_vm->handleEvent(_userPanStartRightCallback);
|
|
panRightAnim(_userPanEndRightCallback);
|
|
}
|
|
|
|
if (_panSpeed != 0) {
|
|
_pan += _panSpeed;
|
|
if (_pan <= 0) {
|
|
g_vm->handleEvent(_panCallback);
|
|
_panCallback = -1;
|
|
_pan = 0;
|
|
_panSpeed = 0;
|
|
}
|
|
if (_pan >= 640) {
|
|
g_vm->handleEvent(_panCallback);
|
|
_panCallback = -1;
|
|
_pan = 640;
|
|
_panSpeed = 0;
|
|
}
|
|
}
|
|
|
|
if (_finalFadeSpeed != 0) {
|
|
_finalFade += _finalFadeSpeed;
|
|
if (_finalFade <= 0 && _finalFadeSpeed < 0) {
|
|
_finalFade = 0;
|
|
_finalFadeSpeed = 0;
|
|
_finalFadeCallback();
|
|
}
|
|
if (_finalFade >= 0x100 && _finalFadeSpeed > 0) {
|
|
_finalFade = 0x100;
|
|
_finalFadeSpeed = 0;
|
|
_finalFadeCallback();
|
|
}
|
|
}
|
|
|
|
Common::SortedArray<Layer>::iterator layersIterator = _layers.begin();
|
|
|
|
for (; layersIterator != _layers.end() && layersIterator->zValue > _videoZ; layersIterator++) {
|
|
if (!layersIterator->isEnabled)
|
|
continue;
|
|
const PodImage &pi = layersIterator->renderable->getFrame(time);
|
|
|
|
pi.render (context,
|
|
layersIterator->offset + Common::Point(_pan * layersIterator->parallax / 640, 0) + kZeroPoint,
|
|
layersIterator->colorScale, layersIterator->scale);
|
|
}
|
|
|
|
if (_videoDecoder && !_videoDecoder->endOfVideo() && _videoDecoder->needsUpdate()) {
|
|
const Graphics::Surface *surf = _videoDecoder->decodeNextFrame();
|
|
_videoW = MIN<int>(surf->w, kVideoMaxW);
|
|
_videoH = MIN<int>(surf->h, kVideoMaxH);
|
|
_videoSurfOffset = _videoOffset;
|
|
memcpy(_videoPixels.get(),
|
|
surf->getPixels(), _videoW * _videoH);
|
|
const byte *pal = _videoDecoder->getPalette();
|
|
if (pal)
|
|
memcpy(_videoPalette, pal, 256 * 3);
|
|
}
|
|
|
|
if (_videoDecoder && _videoW && _videoH) {
|
|
context->blitVideo(_videoPixels.get(), _videoW, _videoW, _videoH, _videoPalette, _videoSurfOffset + kZeroPoint);
|
|
}
|
|
|
|
for (; layersIterator != _layers.end(); layersIterator++) {
|
|
if (!layersIterator->isEnabled)
|
|
continue;
|
|
const PodImage &pi = layersIterator->renderable->getFrame(time);
|
|
|
|
pi.render (context,
|
|
layersIterator->offset + Common::Point(_pan * layersIterator->parallax / 640, 0) + kZeroPoint,
|
|
layersIterator->colorScale, layersIterator->scale);
|
|
}
|
|
|
|
if (stopVideo) {
|
|
_subtitles.clear();
|
|
_countQueuedSubtitles.clear();
|
|
}
|
|
|
|
while (!_subtitles.empty() && time > _subtitles.front().maxTime) {
|
|
_countQueuedSubtitles[_subtitles.front().ID]--;
|
|
_subtitles.pop();
|
|
}
|
|
|
|
if (_subtitles.empty())
|
|
_countQueuedSubtitles.clear();
|
|
|
|
if (_videoDecoder && (_videoDecoder->endOfVideo() || (stopVideo && !_mouseEnabled))) {
|
|
debug("videoEnd: %s", _videoDecoderEndEvent.getDebugString().c_str());
|
|
_videoDecoder.reset();
|
|
g_vm->handleEvent(_videoDecoderEndEvent);
|
|
}
|
|
|
|
for (unsigned i = 0; i < _anims.size(); i++) {
|
|
if (_anims[i]._finished)
|
|
continue;
|
|
|
|
bool soundFinished = !g_system->getMixer()->isSoundHandleActive(_anims[i]._soundHandle);
|
|
const LayerId &animName = _anims[i]._animName;
|
|
bool animFinished = isAnimationFinished(animName, time);
|
|
bool subFinished = (_countQueuedSubtitles.empty() || _countQueuedSubtitles[_anims[i]._subtitleID] == 0);
|
|
bool stopped = stopVideo && _anims[i]._skippable;
|
|
|
|
if (stopped) {
|
|
g_system->getMixer()->stopHandle(_anims[i]._soundHandle);
|
|
if (_anims[i]._keepLastFrame)
|
|
for (Common::SortedArray<Layer>::iterator it = _layers.begin(); it != _layers.end(); it++)
|
|
if (it->name == _anims[i]._animName)
|
|
it->renderable->selectFrame(-1);
|
|
}
|
|
|
|
if ((soundFinished && animFinished && subFinished) || stopped) {
|
|
_anims[i]._finished = true;
|
|
if (!_anims[i]._keepLastFrame)
|
|
setLayerEnabled(animName, false);
|
|
g_vm->handleEvent(_anims[i]._callbackEvent);
|
|
// Release the reference
|
|
_anims[i]._callbackEvent = EventHandlerWrapper();
|
|
}
|
|
}
|
|
|
|
Common::Point viewPoint = Common::Point(_pan, 0) + kZeroPoint + _viewportOffset;
|
|
|
|
if (_heroBeltEnabled) {
|
|
belt->render(context, time, viewPoint);
|
|
}
|
|
|
|
context->fade(_finalFade);
|
|
if (!_subtitles.empty())
|
|
context->renderSubtitle(_subtitles.front().line, viewPoint);
|
|
|
|
context->renderToScreen(viewPoint);
|
|
}
|
|
|
|
void VideoRoom::panLeftAnim(EventHandlerWrapper callback) {
|
|
_panSpeed = -6; // TODO: check this speed
|
|
_panCallback = callback;
|
|
}
|
|
|
|
void VideoRoom::panRightAnim(EventHandlerWrapper callback) {
|
|
_panSpeed = +6; // TODO: check this speed
|
|
_panCallback = callback;
|
|
}
|
|
|
|
void VideoRoom::panRightInstant() {
|
|
_pan = 640;
|
|
}
|
|
|
|
void VideoRoom::addStaticLayer(const LayerId &name, int zValue, Common::Point offset) {
|
|
PodFile pf2(name.getDebug());
|
|
if (!pf2.openStore(Common::SharedPtr<Common::SeekableReadStream>(openFile(mapAsset(name) + ".pod")))) {
|
|
debug("Animation %s isn't found", name.getDebug().c_str());
|
|
return;
|
|
}
|
|
for (int idx = 1; ; idx++) {
|
|
PodImage pi;
|
|
if (!pi.loadImage(pf2, idx)) {
|
|
break;
|
|
}
|
|
Common::Array <PodImage> arr;
|
|
arr.push_back(pi);
|
|
addLayer(new Renderable(arr), name, zValue, true, offset);
|
|
}
|
|
}
|
|
|
|
void VideoRoom::addAnimLayerInternal(const LayerId &name, int zValue, Common::Point offset) {
|
|
Common::SharedPtr<Common::SeekableReadStream> rs(openFile(mapAsset(name) + ".pod"));
|
|
if (!rs) {
|
|
debug("Animation %s isn't found", name.getDebug().c_str());
|
|
return;
|
|
}
|
|
|
|
PodFile pf2(name.getDebug());
|
|
pf2.openStore(rs);
|
|
|
|
addLayer(new Renderable(pf2.loadImageArray()), name, zValue, false,
|
|
offset);
|
|
}
|
|
|
|
Renderable::Renderable(Common::Array<PodImage> images) {
|
|
_images = images;
|
|
_msperframe = kDefaultSpeed;
|
|
_loop = false;
|
|
_first = 0;
|
|
_last = 0;
|
|
_startms = 0;
|
|
}
|
|
|
|
int Renderable::getLen() {
|
|
return _first < _last ? _last - _first + 1 : _first - _last + 1;
|
|
}
|
|
|
|
int math_mod(int a, int b) {
|
|
int m = a % b;
|
|
while (m < 0)
|
|
m += ABS(b);
|
|
return m;
|
|
}
|
|
|
|
const PodImage &Renderable::getFrame(int time) {
|
|
return _images[getAnimationFrameNum(time)];
|
|
}
|
|
|
|
int Renderable::getAnimationFrameNum(int time) {
|
|
int f = ((time - _startms) / _msperframe);
|
|
if (f < 0)
|
|
f = 0;
|
|
if (_loop)
|
|
f %= getLen();
|
|
if (f >= getLen())
|
|
f = getLen() - 1;
|
|
if (_first <= _last)
|
|
return math_mod(_first + f, _images.size());
|
|
else
|
|
return math_mod(_first - f, _images.size());
|
|
}
|
|
|
|
void Renderable::startAnimation(int startms, int msperframe, bool loop, int first, int last) {
|
|
_loop = loop;
|
|
_msperframe = msperframe;
|
|
_startms = startms;
|
|
_first = math_mod(first, _images.size());
|
|
_last = math_mod(last, _images.size());
|
|
}
|
|
|
|
void Renderable::selectFrame(int frame) {
|
|
_loop = true;
|
|
_msperframe = kDefaultSpeed;
|
|
_startms = 0;
|
|
frame = math_mod(frame, _images.size());
|
|
_first = frame;
|
|
_last = frame;
|
|
}
|
|
|
|
bool Renderable::isAnimationFinished(int time) {
|
|
return !_loop && time > _startms + getLen() * _msperframe;
|
|
}
|
|
|
|
void VideoRoom::loadHotZones(const Common::String &hotzoneFile, bool enable, Common::Point offset) {
|
|
Common::SharedPtr<Common::SeekableReadStream> hzFile(openFile(hotzoneFile));
|
|
if (!hzFile) {
|
|
debug("Couldn't open %s", hotzoneFile.c_str());
|
|
return;
|
|
}
|
|
|
|
_hotZones.readHotzones(hzFile, enable, offset);
|
|
}
|
|
|
|
void VideoRoom::pushHotZones(const Common::String &hotzoneFile, bool enable,
|
|
Common::Point offset) {
|
|
// TODO: optimize this? maybe. Or maybe not
|
|
_hotZoneStack.push_back(_hotZones);
|
|
_hotZones = HotZoneArray();
|
|
loadHotZones(hotzoneFile, enable, offset);
|
|
}
|
|
|
|
void VideoRoom::popHotZones() {
|
|
// TODO: optimize this? maybe. Or maybe not
|
|
_hotZones = _hotZoneStack.back();
|
|
_hotZoneStack.pop_back();
|
|
}
|
|
|
|
int VideoRoom::getCursor() {
|
|
return _cursor;
|
|
}
|
|
|
|
PodImage *VideoRoom::getDragged() {
|
|
if (_isDragging)
|
|
return &_draggedImage[_draggingPtr];
|
|
else
|
|
return nullptr;
|
|
}
|
|
|
|
void VideoRoom::clearDrag() {
|
|
_isDragging = false;
|
|
}
|
|
|
|
void VideoRoom::drag(const Common::String &name, int frame, Common::Point hotspot) {
|
|
Common::SharedPtr<Common::SeekableReadStream> rs(openFile(mapAsset(name) + ".pod"));
|
|
if (!rs) {
|
|
debug("Animation %s isn't found", name.c_str());
|
|
return;
|
|
}
|
|
|
|
PodFile pf2(name);
|
|
pf2.openStore(rs);
|
|
PodImage pi;
|
|
|
|
pi.loadImage(pf2, 1 + frame);
|
|
pi.setHotspot(hotspot);
|
|
|
|
_draggingPtr = (_draggingPtr + 1) % ARRAYSIZE(_draggedImage);
|
|
|
|
_draggedImage[_draggingPtr] = pi;
|
|
_isDragging = true;
|
|
}
|
|
|
|
void VideoRoom::cancelVideo() {
|
|
if (_videoDecoder) {
|
|
_videoDecoder.reset();
|
|
_videoDecoderEndEvent = 0;
|
|
_videoW = 0;
|
|
_videoH = 0;
|
|
}
|
|
}
|
|
|
|
void VideoRoom::playVideo(const Common::String &name, int zValue,
|
|
EventHandlerWrapper callbackEvent,
|
|
Common::Point offset) {
|
|
cancelVideo();
|
|
Common::SharedPtr<Video::SmackerDecoder> decoder
|
|
= Common::SharedPtr<Video::SmackerDecoder>(new Video::SmackerDecoder());
|
|
|
|
Common::String mappedName = _assetMap.get(name, 1);
|
|
if (mappedName == "") {
|
|
mappedName = name;
|
|
}
|
|
Common::SeekableReadStream *stream = Common::MacResManager::openFileOrDataFork(Common::Path(_smkPath + "/" + mappedName + ".SMK"));
|
|
|
|
if (!stream || !decoder->loadStream(stream)) {
|
|
debug("Video file %s can't be opened", name.c_str());
|
|
g_vm->handleEvent(callbackEvent);
|
|
return;
|
|
}
|
|
|
|
decoder->start();
|
|
_videoDecoder = decoder;
|
|
_videoDecoderEndEvent = callbackEvent;
|
|
_videoOffset = offset;
|
|
_videoZ = zValue;
|
|
}
|
|
|
|
Common::SeekableReadStream *VideoRoom::openFile(const Common::String &name) {
|
|
Common::SeekableReadStream *stream = _podFile->getFileStream(name);
|
|
if (stream)
|
|
return stream;
|
|
return g_vm->getWdPodFile()->getFileStream(name);
|
|
}
|
|
|
|
Common::String VideoRoom::mapAsset(const Common::String &name) {
|
|
Common::String mappedName = _assetMap.get(name, 1);
|
|
if (mappedName == "") {
|
|
return name;
|
|
}
|
|
return mappedName;
|
|
}
|
|
|
|
Common::String VideoRoom::mapAsset(const LayerId &name) {
|
|
return mapAsset(name.getFilename());
|
|
}
|
|
|
|
Audio::RewindableAudioStream *VideoRoom::getAudioStream(const Common::String &soundName) {
|
|
Common::SeekableReadStream *stream = openFile(
|
|
mapAsset(soundName) + ".aif");
|
|
if (!stream) {
|
|
debug("Audio %s isn't found", soundName.c_str());
|
|
return nullptr;
|
|
}
|
|
return Audio::makeAIFFStream(stream, DisposeAfterUse::YES);
|
|
}
|
|
|
|
void VideoRoom::playSoundInternal(const Common::String &soundName, EventHandlerWrapper callbackEvent, bool loop,
|
|
bool skippable, Audio::Mixer::SoundType soundType, int subtitleID) {
|
|
Audio::RewindableAudioStream *rewSoundStream;
|
|
Audio::AudioStream *soundStream;
|
|
Animation anim;
|
|
g_system->getMixer()->stopHandle(anim._soundHandle);
|
|
rewSoundStream = getAudioStream(soundName);
|
|
soundStream = loop ? Audio::makeLoopingAudioStream(rewSoundStream, 0) : rewSoundStream;
|
|
anim._animName = soundName;
|
|
anim._callbackEvent = callbackEvent;
|
|
anim._finished = false;
|
|
anim._keepLastFrame = false;
|
|
anim._skippable = skippable;
|
|
anim._subtitleID = subtitleID;
|
|
g_system->getMixer()->playStream(soundType, &anim._soundHandle, soundStream,
|
|
-1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::YES);
|
|
_anims.push_back(anim);
|
|
}
|
|
|
|
void VideoRoom::playSFX(const Common::String &soundName, EventHandlerWrapper callbackEvent) {
|
|
playSoundInternal(soundName, callbackEvent, false, false, Audio::Mixer::kSFXSoundType);
|
|
}
|
|
|
|
void VideoRoom::playMusic(const Common::String &soundName, EventHandlerWrapper callbackEvent) {
|
|
playSoundInternal(soundName, callbackEvent, false, false, Audio::Mixer::kMusicSoundType);
|
|
}
|
|
|
|
void VideoRoom::playSpeech(const TranscribedSound &sound,
|
|
EventHandlerWrapper callbackEvent) {
|
|
int subID = g_vm->genSubtitleID();
|
|
playSoundInternal(sound.soundName, callbackEvent, false, true, Audio::Mixer::kSpeechSoundType, subID);
|
|
playSubtitles(sound.transcript, subID);
|
|
}
|
|
|
|
void VideoRoom::playSFXLoop(const Common::String &soundName) {
|
|
playSoundInternal(soundName, EventHandlerWrapper(), true, false, Audio::Mixer::kSFXSoundType);
|
|
}
|
|
|
|
void VideoRoom::playMusicLoop(const Common::String &soundName) {
|
|
playSoundInternal(soundName, EventHandlerWrapper(), true, false, Audio::Mixer::kMusicSoundType);
|
|
}
|
|
|
|
void VideoRoom::playAnimWithSoundInternal(const LayerId &animName,
|
|
const Common::String &soundName,
|
|
Audio::Mixer::SoundType soundType,
|
|
int zValue,
|
|
PlayAnimParams params,
|
|
EventHandlerWrapper callbackEvent,
|
|
Common::Point offset, int subtitleID) {
|
|
Audio::AudioStream *soundStream;
|
|
|
|
if (!doesLayerExist(animName)) {
|
|
addAnimLayerInternal(animName, zValue);
|
|
}
|
|
|
|
startAnimationInternal(animName, zValue, params.getSpeed(), params.isLoop(),
|
|
false, params.getFirstFrame(), params.getLastFrame(), offset);
|
|
Animation anim;
|
|
g_system->getMixer()->stopHandle(anim._soundHandle);
|
|
if (params.isLoop()) {
|
|
soundStream = Audio::makeLoopingAudioStream(getAudioStream(soundName), 0);
|
|
} else {
|
|
soundStream = getAudioStream(soundName);
|
|
}
|
|
anim._animName = animName;
|
|
anim._callbackEvent = callbackEvent;
|
|
anim._finished = false;
|
|
anim._keepLastFrame = params.getKeepLastFrame();
|
|
anim._skippable = false;
|
|
anim._subtitleID = subtitleID;
|
|
g_system->getMixer()->playStream(soundType, &anim._soundHandle, soundStream,
|
|
-1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::YES);
|
|
_anims.push_back(anim);
|
|
}
|
|
|
|
void VideoRoom::playAnimWithSpeech(const LayerId &animName,
|
|
const TranscribedSound &sound,
|
|
int zValue,
|
|
PlayAnimParams params,
|
|
EventHandlerWrapper callbackEvent,
|
|
Common::Point offset) {
|
|
int subID = g_vm->genSubtitleID();
|
|
playAnimWithSoundInternal(animName, sound.soundName, Audio::Mixer::kSpeechSoundType, zValue, params, callbackEvent, offset, subID);
|
|
playSubtitles(sound.transcript, subID);
|
|
}
|
|
|
|
void VideoRoom::playAnimWithSFX(const LayerId &animName,
|
|
const Common::String &soundName,
|
|
int zValue,
|
|
PlayAnimParams params,
|
|
EventHandlerWrapper callbackEvent,
|
|
Common::Point offset) {
|
|
playAnimWithSoundInternal(animName, soundName, Audio::Mixer::kSFXSoundType, zValue, params, callbackEvent, offset);
|
|
}
|
|
|
|
void VideoRoom::playAnimWithMusic(const LayerId &animName,
|
|
const Common::String &soundName,
|
|
int zValue,
|
|
PlayAnimParams params,
|
|
EventHandlerWrapper callbackEvent,
|
|
Common::Point offset) {
|
|
playAnimWithSoundInternal(animName, soundName, Audio::Mixer::kMusicSoundType, zValue, params, callbackEvent, offset);
|
|
}
|
|
|
|
void VideoRoom::playAnim(const LayerId &animName, int zValue,
|
|
PlayAnimParams params,
|
|
EventHandlerWrapper callbackEvent,
|
|
Common::Point offset) {
|
|
if (!doesLayerExist(animName)) {
|
|
addAnimLayerInternal(animName, zValue);
|
|
}
|
|
|
|
startAnimationInternal(animName, zValue, params.getSpeed(), params.isLoop(), false,
|
|
params.getFirstFrame(), params.getLastFrame(), offset);
|
|
Animation anim;
|
|
anim._callbackEvent = callbackEvent;
|
|
anim._finished = false;
|
|
anim._keepLastFrame = params.getKeepLastFrame();
|
|
anim._animName = animName;
|
|
anim._skippable = false;
|
|
_anims.push_back(anim);
|
|
}
|
|
|
|
void VideoRoom::playAnimLoop(const LayerId &animName, int zValue, Common::Point offset) {
|
|
playAnim(animName, zValue, PlayAnimParams::loop(), EventHandlerWrapper(), offset);
|
|
}
|
|
|
|
void VideoRoom::playAnimKeepLastFrame(const LayerId &animName, int zValue, EventHandlerWrapper callbackEvent,
|
|
Common::Point offset) {
|
|
playAnim(animName, zValue, PlayAnimParams::keepLastFrame(), callbackEvent, offset);
|
|
}
|
|
|
|
void VideoRoom::setUserPanCallback(EventHandlerWrapper leftStart,
|
|
EventHandlerWrapper leftEnd,
|
|
EventHandlerWrapper rightStart,
|
|
EventHandlerWrapper rightEnd) {
|
|
_userPanStartLeftCallback = leftStart;
|
|
_userPanEndLeftCallback = leftEnd;
|
|
_userPanStartRightCallback = rightStart;
|
|
_userPanEndRightCallback = rightEnd;
|
|
}
|
|
|
|
class StatuePlayEnd : public EventHandler {
|
|
public:
|
|
void operator()() override {
|
|
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
|
|
room->playAnim(_animName, _zValue,
|
|
PlayAnimParams::disappear().partial(_lastLoopFrame, -1),
|
|
EventHandlerWrapper(), _offset);
|
|
room->enableMouse();
|
|
}
|
|
|
|
StatuePlayEnd(const LayerId &animName, int zValue, Common::Point offset, int lastLoopFrame) {
|
|
_animName = animName;
|
|
_zValue = zValue;
|
|
_offset = offset;
|
|
_lastLoopFrame = lastLoopFrame;
|
|
}
|
|
private:
|
|
LayerId _animName;
|
|
int _zValue;
|
|
int _lastLoopFrame;
|
|
Common::Point _offset;
|
|
};
|
|
|
|
class StatuePlayTwoThirdsLoop : public EventHandler {
|
|
public:
|
|
void operator()() override {
|
|
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
|
|
room->playAnim(_animName, _zValue,
|
|
PlayAnimParams::loop().partial(_firstLoopFrame, _lastLoopFrame),
|
|
EventHandlerWrapper(), _offset);
|
|
}
|
|
|
|
StatuePlayTwoThirdsLoop(const LayerId &animName, int zValue, Common::Point offset,
|
|
int firstLoopFrame, int lastLoopFrame) {
|
|
_animName = animName;
|
|
_zValue = zValue;
|
|
_offset = offset;
|
|
_firstLoopFrame = firstLoopFrame;
|
|
_lastLoopFrame = lastLoopFrame;
|
|
}
|
|
private:
|
|
LayerId _animName;
|
|
int _zValue;
|
|
int _firstLoopFrame, _lastLoopFrame;
|
|
Common::Point _offset;
|
|
};
|
|
|
|
void VideoRoom::playStatueSMK(StatueId id, const LayerId &animName, int zValue,
|
|
const Common::Array<Common::String> &smkNames,
|
|
int startOfLoop, int startOfEnd,
|
|
Common::Point offset) {
|
|
int phase = g_vm->getPersistent()->_statuePhase[id] % smkNames.size();
|
|
playVideo(smkNames[phase], zValue,
|
|
Common::SharedPtr<EventHandler>(new StatuePlayEnd(animName, zValue, offset, startOfEnd)));
|
|
if (!doesLayerExist(animName)) {
|
|
addAnimLayerInternal(animName, zValue);
|
|
stopAnim(animName);
|
|
}
|
|
playAnim(animName, zValue, PlayAnimParams::keepLastFrame().partial(0, startOfEnd - 1),
|
|
Common::SharedPtr<EventHandler>(new StatuePlayTwoThirdsLoop(animName, zValue, offset, startOfLoop, startOfEnd - 1)), offset);
|
|
|
|
g_vm->getPersistent()->_statuesTouched[id] = true;
|
|
g_vm->getPersistent()->_statuePhase[id] = (phase + 1) % smkNames.size();
|
|
disableMouse();
|
|
}
|
|
|
|
void VideoRoom::resetFade() {
|
|
_finalFade = 0x100;
|
|
_finalFadeSpeed = 0;
|
|
_finalFadeCallback = EventHandlerWrapper();
|
|
}
|
|
|
|
void VideoRoom::resetLayers() {
|
|
_layers.clear();
|
|
}
|
|
|
|
void VideoRoom::hideString(const Common::String &font, size_t maxLen, const Common::String &extraId) {
|
|
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
|
|
for (unsigned i = 0; i < maxLen; i++) {
|
|
room->stopAnim(LayerId(font, i, extraId));
|
|
}
|
|
}
|
|
|
|
void VideoRoom::renderString(const Common::String &font, const Common::U32String &str, Common::Point startPos, int zVal, int fontDelta, const Common::String &extraId) {
|
|
Common::Point curPos = startPos;
|
|
bool small = font == "smallascii";
|
|
for (unsigned i = 0; i < str.size(); i++) {
|
|
uint32 c = str[i];
|
|
if (c == ' ') {
|
|
curPos += Common::Point(small ? 6 : 20, 0);
|
|
continue;
|
|
}
|
|
LayerId l(font, i, extraId);
|
|
selectFrame(l, zVal, c + fontDelta, curPos);
|
|
PodImage pi(getLayerFrame(l));
|
|
curPos += Common::Point(pi.getWidth() + pi.getOffset().x + (small ? 1 : 3), 0);
|
|
}
|
|
}
|
|
|
|
void VideoRoom::loadFontWidth(const Common::String &font) {
|
|
if (_fontWidths.contains(font)) {
|
|
return;
|
|
}
|
|
|
|
Common::SharedPtr<Common::SeekableReadStream> rs(openFile(mapAsset(font) + ".pod"));
|
|
if (!rs) {
|
|
_fontWidths[font].clear();
|
|
debug("Animation %s isn't found", font.c_str());
|
|
return;
|
|
}
|
|
|
|
PodFile pf2(font);
|
|
pf2.openStore(rs);
|
|
|
|
Common::Array <PodImage> pi = pf2.loadImageArray();
|
|
bool small = font == "smallascii";
|
|
|
|
for (unsigned i = 0; i < pi.size(); i++)
|
|
_fontWidths[font].push_back(pi[i].getWidth()+pi[i].getOffset().x + (small ? 1 : 3));
|
|
}
|
|
|
|
int VideoRoom::computeStringWidth(const Common::String &font, const Common::U32String &str, int fontDelta) {
|
|
int width = 0;
|
|
bool small = font == "smallascii";
|
|
if (!_fontWidths.contains(font)) {
|
|
loadFontWidth(font);
|
|
}
|
|
for (unsigned i = 0; i < str.size(); i++) {
|
|
uint32 c = str[i];
|
|
if (c == ' ') {
|
|
width += small ? 6 : 20;
|
|
continue;
|
|
}
|
|
if (c + fontDelta < _fontWidths[font].size())
|
|
width += _fontWidths[font][c + fontDelta];
|
|
}
|
|
|
|
return width;
|
|
}
|
|
|
|
void VideoRoom::renderStringCentered(const Common::String &font, const Common::U32String &str, Common::Point centerPos, int zVal, int fontDelta, const Common::String &extraId) {
|
|
int width = computeStringWidth(font, str, fontDelta);
|
|
renderString(font, str, centerPos - Common::Point(width / 2, 0), zVal, fontDelta, extraId);
|
|
}
|
|
|
|
void VideoRoom::playSubtitles(const char *text, int subID) {
|
|
int delay = g_vm->getSubtitleDelayPerChar();
|
|
if (delay <= 0)
|
|
return;
|
|
Common::U32String s = g_vm->translate(text);
|
|
Common::Array<Common::U32String> lines;
|
|
int32 countTime = g_vm->getCurrentTime();
|
|
g_vm->wrapSubtitles(s, lines);
|
|
for (uint i = 0; i < lines.size(); i++) {
|
|
SubtitleLine l;
|
|
l.line = lines[i];
|
|
l.ID = subID;
|
|
countTime += delay * MAX<uint>(l.line.size(), 20);
|
|
l.maxTime = countTime;
|
|
_subtitles.push(l);
|
|
_countQueuedSubtitles[subID]++;
|
|
}
|
|
}
|
|
}
|