1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 * Copyright 2020 Google
22 *
23 */
24
25 #include "common/file.h"
26 #include "hadesch/video.h"
27 #include "hadesch/pod_image.h"
28 #include "hadesch/tag_file.h"
29 #include "hadesch/hadesch.h"
30 #include "common/system.h"
31 #include "video/smk_decoder.h"
32 #include "audio/decoders/aiff.h"
33 #include "hadesch/pod_file.h"
34 #include "hadesch/baptr.h"
35 #include "common/translation.h"
36
37 static const int kVideoMaxW = 1280;
38 static const int kVideoMaxH = 480;
39
40 namespace Hadesch {
41
getDebug() const42 Common::String LayerId::getDebug() const {
43 if (_idx == -1 && _qualifier == "")
44 return "[" + _name + "]";
45 return Common::String::format("[%s/%s/%d]",
46 _name.c_str(), _qualifier.c_str(), _idx);
47 }
48
operator ==(const LayerId & b) const49 bool LayerId::operator== (const LayerId &b) const {
50 return _name == b._name && _idx == b._idx && _qualifier == b._qualifier;
51 }
52
loop()53 PlayAnimParams PlayAnimParams::loop() {
54 return PlayAnimParams(true, true);
55 }
56
keepLastFrame()57 PlayAnimParams PlayAnimParams::keepLastFrame() {
58 return PlayAnimParams(false, true);
59 }
60
disappear()61 PlayAnimParams PlayAnimParams::disappear() {
62 return PlayAnimParams(false, false);
63 }
64
getKeepLastFrame()65 bool PlayAnimParams::getKeepLastFrame() {
66 return _keepLastFrame;
67 }
68
isLoop()69 bool PlayAnimParams::isLoop() {
70 return _loop;
71 }
72
getSpeed()73 int PlayAnimParams::getSpeed() {
74 return _msperframe;
75 }
76
getFirstFrame()77 int PlayAnimParams::getFirstFrame() {
78 return _firstFrame;
79 }
80
getLastFrame()81 int PlayAnimParams::getLastFrame() {
82 return _lastFrame;
83 }
84
partial(int first,int last) const85 PlayAnimParams PlayAnimParams::partial(int first, int last) const {
86 PlayAnimParams ret(*this);
87 ret._firstFrame = first;
88 ret._lastFrame = last;
89 return ret;
90 }
91
speed(int msperframe) const92 PlayAnimParams PlayAnimParams::speed(int msperframe) const {
93 PlayAnimParams ret(*this);
94 ret._msperframe = msperframe;
95 return ret;
96 }
97
backwards() const98 PlayAnimParams PlayAnimParams::backwards() const {
99 PlayAnimParams ret(*this);
100 ret._firstFrame = _lastFrame;
101 ret._lastFrame = _firstFrame;
102 return ret;
103 }
104
PlayAnimParams(bool isLoop,bool isKeepLastFrame)105 PlayAnimParams::PlayAnimParams(bool isLoop, bool isKeepLastFrame) {
106 _loop = isLoop;
107 _keepLastFrame = isKeepLastFrame;
108 _firstFrame = 0;
109 _lastFrame = -1;
110 _msperframe = kDefaultSpeed;
111 }
112
layerComparator(const Layer & a,const Layer & b)113 int VideoRoom::layerComparator(const Layer &a, const Layer &b) {
114 if (a.zValue == b.zValue)
115 return a.genCounter - b.genCounter;
116 return b.zValue - a.zValue;
117 }
118
VideoRoom(const Common::String & dir,const Common::String & pod,const Common::String & assetMapFile)119 VideoRoom::VideoRoom(const Common::String &dir, const Common::String &pod,
120 const Common::String &assetMapFile) : _layers(layerComparator) {
121 Common::String podPath = g_vm->getCDScenesPath() + dir + "/" + pod + ".pod";
122 _podFile = Common::SharedPtr<PodFile>(new PodFile(podPath));
123 _podFile->openStore(podPath);
124 _smkPath = g_vm->getCDScenesPath() + dir;
125 Common::SharedPtr<Common::SeekableReadStream> assetMapStream(assetMapFile != "" ? openFile(assetMapFile) : nullptr);
126 if (assetMapStream) {
127 _assetMap = TextTable(assetMapStream, 2);
128 }
129
130 _videoPixels = sharedPtrByteAlloc(kVideoMaxW * kVideoMaxH);
131 _hotZone = -1;
132 _mouseEnabled = true;
133 _videoOffset = Common::Point(0, 0);
134 _videoSurfOffset = Common::Point(0, 0);
135 _pan = 0;
136 _panSpeed = 0;
137 _panCallback = -1;
138 _pannable = false;
139 _leftEdge = false;
140 _rightEdge = false;
141 _heroBeltEnabled = true;
142 _userPanStartLeftCallback = -1;
143 _userPanStartRightCallback = -1;
144 _userPanEndLeftCallback = -1;
145 _userPanEndRightCallback = -1;
146 _videoZ = 0;
147 _layerGenCounter = 0;
148 _finalFade = 0x100;
149 _finalFadeSpeed = 0;
150 _draggingPtr = 0;
151 _isDragging = 0;
152 }
153
~VideoRoom()154 VideoRoom::~VideoRoom() {
155 if (_videoDecoder) {
156 _videoDecoder->stop();
157 _videoDecoder.reset();
158 }
159 for (unsigned i = 0; i < _anims.size(); i++)
160 g_system->getMixer()->stopHandle(_anims[i]._soundHandle);
161 }
162
setPannable(bool val)163 void VideoRoom::setPannable(bool val) {
164 _pannable = val;
165 }
166
setHotzoneEnabled(const Common::String & name,bool enabled)167 void VideoRoom::setHotzoneEnabled(const Common::String &name, bool enabled) {
168 _hotZones.setHotzoneEnabled(name, enabled);
169 }
170
enableHotzone(const Common::String & name)171 void VideoRoom::enableHotzone(const Common::String &name) {
172 _hotZones.setHotzoneEnabled(name, true);
173 }
174
disableHotzone(const Common::String & name)175 void VideoRoom::disableHotzone(const Common::String &name) {
176 _hotZones.setHotzoneEnabled(name, false);
177 }
178
setLayerEnabled(const LayerId & name,bool val)179 void VideoRoom::setLayerEnabled(const LayerId &name, bool val) {
180 for (Common::SortedArray<Layer>::iterator it = _layers.begin(); it != _layers.end(); it++) {
181 if (it->name == name)
182 it->isEnabled = val;
183 }
184 }
185
setLayerParallax(const LayerId & name,int val)186 void VideoRoom::setLayerParallax(const LayerId &name, int val) {
187 for (Common::SortedArray<Layer>::iterator it = _layers.begin(); it != _layers.end(); it++) {
188 if (it->name == name)
189 it->parallax = val;
190 }
191 }
192
setColorScale(const LayerId & name,int val)193 void VideoRoom::setColorScale(const LayerId &name, int val) {
194 for (Common::SortedArray<Layer>::iterator it = _layers.begin(); it != _layers.end(); it++) {
195 if (it->name == name)
196 it->colorScale = val;
197 }
198 }
199
setScale(const LayerId & name,int val)200 void VideoRoom::setScale(const LayerId &name, int val) {
201 for (Common::SortedArray<Layer>::iterator it = _layers.begin(); it != _layers.end(); it++) {
202 if (it->name == name)
203 it->scale = val;
204 }
205 }
206
getNumFrames(const LayerId & name)207 int VideoRoom::getNumFrames(const LayerId &name) {
208 for (Common::SortedArray<Layer>::iterator it = _layers.begin(); it != _layers.end(); it++) {
209 if (it->name == name)
210 return it->renderable->getNumFrames();
211 }
212
213 return 0;
214 }
215
startAnimationInternal(const LayerId & name,int zValue,int msperframe,bool loop,bool fixedFrame,int first,int last,Common::Point offset)216 void VideoRoom::startAnimationInternal(const LayerId &name, int zValue, int msperframe, bool loop,
217 bool fixedFrame,
218 int first, int last, Common::Point offset) {
219 int32 startms = g_vm->getCurrentTime();
220 Common::Array<Layer> modifiedZ;
221 for (Common::SortedArray<Layer>::iterator it = _layers.begin(); it != _layers.end();) {
222 if (it->name == name) {
223 it->isEnabled = true;
224 if (fixedFrame)
225 it->renderable->selectFrame(first);
226 else
227 it->renderable->startAnimation(startms, msperframe, loop, first, last);
228 it->offset = offset;
229 if (it->zValue != zValue) {
230 Layer l = *it;
231 l.zValue = zValue;
232 modifiedZ.push_back(l);
233 it = _layers.erase(it);
234 } else {
235 it++;
236 }
237 } else {
238 it++;
239 }
240 }
241 // This is slow but should rarely happen
242 if (!modifiedZ.empty()) {
243 for (Common::Array<Layer>::iterator it = modifiedZ.begin(); it != modifiedZ.end(); it++) {
244 _layers.insert(*it);
245 }
246 }
247 }
248
selectFrame(const LayerId & name,int zValue,int frame,Common::Point offset)249 void VideoRoom::selectFrame(const LayerId &name, int zValue, int frame, Common::Point offset) {
250 if (!doesLayerExist(name)) {
251 addAnimLayerInternal(name, zValue);
252 }
253
254 startAnimationInternal(name, zValue, kDefaultSpeed, true, true, frame, frame, offset);
255 }
256
getLayerFrame(const Hadesch::LayerId & name)257 PodImage VideoRoom::getLayerFrame(const Hadesch::LayerId &name) {
258 for (Common::SortedArray<Layer>::iterator it = _layers.begin(); it != _layers.end(); it++) {
259 if (it->name == name) {
260 return it->renderable->getFrame(g_vm->getCurrentTime());
261 }
262 }
263
264 return PodImage();
265 }
266
getAnimFrameNum(const Hadesch::LayerId & name)267 int VideoRoom::getAnimFrameNum(const Hadesch::LayerId &name) {
268 for (Common::SortedArray<Layer>::iterator it = _layers.begin(); it != _layers.end(); it++) {
269 if (it->name == name) {
270 return it->renderable->getAnimationFrameNum(g_vm->getCurrentTime());
271 }
272 }
273
274 return -1;
275 }
276
stopAnim(const LayerId & name)277 void VideoRoom::stopAnim(const LayerId &name) {
278 for (Common::SortedArray<Layer>::iterator it = _layers.begin(); it != _layers.end(); it++) {
279 if (it->name == name) {
280 it->isEnabled = false;
281 }
282 }
283 for (unsigned i = 0; i < _anims.size(); i++) {
284 if (_anims[i]._animName == name) {
285 g_system->getMixer()->stopHandle(_anims[i]._soundHandle);
286 _anims[i]._finished = true;
287 }
288 }
289 }
290
purgeAnim(const LayerId & name)291 void VideoRoom::purgeAnim(const LayerId &name) {
292 for (Common::SortedArray<Layer>::iterator it = _layers.begin(); it != _layers.end();) {
293 if (it->name == name) {
294 it = _layers.erase(it);
295 } else
296 it++;
297 }
298 }
299
dumpLayers()300 void VideoRoom::dumpLayers() {
301 debug("Current layers:");
302 for (Common::SortedArray<Layer>::iterator it = _layers.begin(); it != _layers.end(); it++) {
303 debug(" %s %s", it->name.getDebug().c_str(), it->isEnabled ? "enabled" : "disabled");
304 }
305 }
306
finish()307 void VideoRoom::finish() {
308 for (unsigned i = 0; i < _anims.size(); i++) {
309 g_system->getMixer()->stopHandle(_anims[i]._soundHandle);
310 _anims[i]._finished = true;
311 }
312 }
313
pause()314 void VideoRoom::pause() {
315 for (unsigned i = 0; i < _anims.size(); i++) {
316 g_system->getMixer()->pauseHandle(_anims[i]._soundHandle, true);
317 }
318 }
319
unpause()320 void VideoRoom::unpause() {
321 for (unsigned i = 0; i < _anims.size(); i++) {
322 g_system->getMixer()->pauseHandle(_anims[i]._soundHandle, false);
323 }
324 }
325
doesLayerExist(const LayerId & name)326 bool VideoRoom::doesLayerExist(const LayerId &name) {
327 for (Common::SortedArray<Layer>::iterator it = _layers.begin(); it != _layers.end(); it++) {
328 if (it->name == name) {
329 return true;
330 }
331 }
332 return false;
333 }
334
isAnimationFinished(const LayerId & name,int time)335 bool VideoRoom::isAnimationFinished(const LayerId &name, int time) {
336 for (Common::SortedArray<Layer>::iterator it = _layers.begin(); it != _layers.end(); it++) {
337 if (it->name == name) {
338 return it->renderable->isAnimationFinished(time);
339 }
340 }
341
342 return true;
343 }
344
addLayer(Renderable * rend,const LayerId & name,int zValue,bool isEnabled,Common::Point offset)345 void VideoRoom::addLayer(Renderable *rend, const LayerId &name, int zValue,
346 bool isEnabled, Common::Point offset) {
347 Layer l;
348 l.renderable = Common::SharedPtr<Renderable>(rend);
349 l.name = name;
350 l.isEnabled = isEnabled;
351 l.offset = offset;
352 l.zValue = zValue;
353 l.parallax = 0;
354 l.colorScale = 0x100;
355 l.scale = 100;
356 l.genCounter = _layerGenCounter++;
357 _layers.insert(l);
358 }
359
computeHotZone(int time,Common::Point mousePos)360 void VideoRoom::computeHotZone(int time, Common::Point mousePos) {
361 bool wasEdge = _leftEdge || _rightEdge;
362 _leftEdge = false;
363 _rightEdge = false;
364
365 Common::SharedPtr<HeroBelt> belt = g_vm->getHeroBelt();
366
367 if (_heroBeltEnabled)
368 belt->computeHotZone(time, mousePos, _mouseEnabled);
369 if (_heroBeltEnabled && belt->isOverHeroBelt()) {
370 _hotZone = -1;
371 return;
372 }
373 if (!_mouseEnabled) {
374 _hotZone = -1;
375 return;
376 }
377 Common::Point canvasPos = mousePos + Common::Point(_pan, 0);
378 int wasHotZone = _hotZone;
379 _hotZone = _hotZones.pointToIndex(canvasPos);
380 if (_hotZone >= 0) {
381 if (wasHotZone < 0) {
382 _startHotTime = time;
383 }
384 return;
385 }
386 _leftEdge = (mousePos.x < 20) && _pan > 0;
387 _rightEdge = (mousePos.x > 620) && _pan < 640;
388 if ((!wasEdge && (_leftEdge || _rightEdge)))
389 _edgeStartTime = time;
390 _hotZone = -1;
391 }
392
setHotZoneOffset(const Common::String & name,Common::Point offset)393 void VideoRoom::setHotZoneOffset(const Common::String &name, Common::Point offset) {
394 _hotZones.setHotZoneOffset(name, offset);
395 }
396
mapClick(Common::Point mousePos)397 Common::String VideoRoom::mapClick(Common::Point mousePos) {
398 if (!_mouseEnabled) {
399 return "";
400 }
401 Common::Point canvasPos = mousePos + Common::Point(_pan, 0) + _viewportOffset;
402 return _hotZones.pointToName(canvasPos);
403 }
404
getHotZone()405 Common::String VideoRoom::getHotZone() {
406 return _hotZones.indexToName(_hotZone);
407 }
408
getCursorAnimationFrame(int time)409 int VideoRoom::getCursorAnimationFrame(int time) {
410 if (_hotZone < 0)
411 return -1;
412 if (_hotZones.indexToICSH(_hotZone) != 0)
413 return -1;
414 return (time - _startHotTime) / kDefaultSpeed;
415 }
416
isVideoPlaying()417 bool VideoRoom::isVideoPlaying() {
418 return !!_videoDecoder;
419 }
420
fadeOut(int ms,const EventHandlerWrapper & callback)421 void VideoRoom::fadeOut(int ms, const EventHandlerWrapper &callback) {
422 _finalFadeCallback = callback;
423 _finalFadeSpeed = -(kDefaultSpeed * 0x100) / ms;
424 }
425
nextFrame(Common::SharedPtr<GfxContext> context,int time,bool stopVideo)426 void VideoRoom::nextFrame(Common::SharedPtr<GfxContext> context, int time, bool stopVideo) {
427 context->clear();
428
429 Common::SharedPtr<HeroBelt> belt = g_vm->getHeroBelt();
430
431 if (!_mouseEnabled) {
432 _cursor = (time / 200) % 10 + 3;
433 } else if (_heroBeltEnabled && belt->isOverHeroBelt()) {
434 _cursor = belt->getCursor(time);
435 } else if (g_vm->getPersistent()->_currentRoomId == kMonsterPuzzle) {
436 _cursor = 17;
437 } else if (_hotZone >= 0) {
438 _cursor = _hotZones.indexToCursor(_hotZone, (time - _startHotTime) / kDefaultSpeed);
439 } else if (_leftEdge && _pannable) {
440 _cursor = 14;
441 } else if (_rightEdge && _pannable) {
442 _cursor = 16;
443 } else
444 _cursor = 0;
445
446 if (_leftEdge && (time > _edgeStartTime + 2000) && _panSpeed == 0 && _pannable && _mouseEnabled) {
447 g_vm->handleEvent(_userPanStartLeftCallback);
448 panLeftAnim(_userPanEndLeftCallback);
449 }
450 if (_rightEdge && (time > _edgeStartTime + 2000) && _panSpeed == 0 && _pannable && _mouseEnabled) {
451 g_vm->handleEvent(_userPanStartRightCallback);
452 panRightAnim(_userPanEndRightCallback);
453 }
454
455 if (_panSpeed != 0) {
456 _pan += _panSpeed;
457 if (_pan <= 0) {
458 g_vm->handleEvent(_panCallback);
459 _panCallback = -1;
460 _pan = 0;
461 _panSpeed = 0;
462 }
463 if (_pan >= 640) {
464 g_vm->handleEvent(_panCallback);
465 _panCallback = -1;
466 _pan = 640;
467 _panSpeed = 0;
468 }
469 }
470
471 if (_finalFadeSpeed != 0) {
472 _finalFade += _finalFadeSpeed;
473 if (_finalFade <= 0 && _finalFadeSpeed < 0) {
474 _finalFade = 0;
475 _finalFadeSpeed = 0;
476 _finalFadeCallback();
477 }
478 if (_finalFade >= 0x100 && _finalFadeSpeed > 0) {
479 _finalFade = 0x100;
480 _finalFadeSpeed = 0;
481 _finalFadeCallback();
482 }
483 }
484
485 Common::SortedArray<Layer>::iterator layersIterator = _layers.begin();
486
487 for (; layersIterator != _layers.end() && layersIterator->zValue > _videoZ; layersIterator++) {
488 if (!layersIterator->isEnabled)
489 continue;
490 const PodImage &pi = layersIterator->renderable->getFrame(time);
491
492 pi.render (context,
493 layersIterator->offset + Common::Point(_pan * layersIterator->parallax / 640, 0) + kZeroPoint,
494 layersIterator->colorScale, layersIterator->scale);
495 }
496
497 if (_videoDecoder && !_videoDecoder->endOfVideo() && _videoDecoder->needsUpdate()) {
498 const Graphics::Surface *surf = _videoDecoder->decodeNextFrame();
499 _videoW = MIN<int>(surf->w, kVideoMaxW);
500 _videoH = MIN<int>(surf->h, kVideoMaxH);
501 _videoSurfOffset = _videoOffset;
502 memcpy(_videoPixels.get(),
503 surf->getPixels(), _videoW * _videoH);
504 const byte *pal = _videoDecoder->getPalette();
505 if (pal)
506 memcpy(_videoPalette, pal, 256 * 3);
507 }
508
509 if (_videoDecoder && _videoW && _videoH) {
510 context->blitVideo(_videoPixels.get(), _videoW, _videoW, _videoH, _videoPalette, _videoSurfOffset + kZeroPoint);
511 }
512
513 for (; layersIterator != _layers.end(); layersIterator++) {
514 if (!layersIterator->isEnabled)
515 continue;
516 const PodImage &pi = layersIterator->renderable->getFrame(time);
517
518 pi.render (context,
519 layersIterator->offset + Common::Point(_pan * layersIterator->parallax / 640, 0) + kZeroPoint,
520 layersIterator->colorScale, layersIterator->scale);
521 }
522
523 if (stopVideo) {
524 _subtitles.clear();
525 _countQueuedSubtitles.clear();
526 }
527
528 while (!_subtitles.empty() && time > _subtitles.front().maxTime) {
529 _countQueuedSubtitles[_subtitles.front().ID]--;
530 _subtitles.pop();
531 }
532
533 if (_subtitles.empty())
534 _countQueuedSubtitles.clear();
535
536 if (_videoDecoder && (_videoDecoder->endOfVideo() || (stopVideo && !_mouseEnabled))) {
537 debug("videoEnd: %s", _videoDecoderEndEvent.getDebugString().c_str());
538 _videoDecoder.reset();
539 g_vm->handleEvent(_videoDecoderEndEvent);
540 }
541
542 for (unsigned i = 0; i < _anims.size(); i++) {
543 if (_anims[i]._finished)
544 continue;
545
546 bool soundFinished = !g_system->getMixer()->isSoundHandleActive(_anims[i]._soundHandle);
547 const LayerId &animName = _anims[i]._animName;
548 bool animFinished = isAnimationFinished(animName, time);
549 bool subFinished = (_countQueuedSubtitles.empty() || _countQueuedSubtitles[_anims[i]._subtitleID] == 0);
550 bool stopped = stopVideo && _anims[i]._skippable;
551
552 if (stopped) {
553 g_system->getMixer()->stopHandle(_anims[i]._soundHandle);
554 if (_anims[i]._keepLastFrame)
555 for (Common::SortedArray<Layer>::iterator it = _layers.begin(); it != _layers.end(); it++)
556 if (it->name == _anims[i]._animName)
557 it->renderable->selectFrame(-1);
558 }
559
560 if ((soundFinished && animFinished && subFinished) || stopped) {
561 _anims[i]._finished = true;
562 if (!_anims[i]._keepLastFrame)
563 setLayerEnabled(animName, false);
564 g_vm->handleEvent(_anims[i]._callbackEvent);
565 // Release the reference
566 _anims[i]._callbackEvent = EventHandlerWrapper();
567 }
568 }
569
570 Common::Point viewPoint = Common::Point(_pan, 0) + kZeroPoint + _viewportOffset;
571
572 if (_heroBeltEnabled) {
573 belt->render(context, time, viewPoint);
574 }
575
576 context->fade(_finalFade);
577 if (!_subtitles.empty())
578 context->renderSubtitle(_subtitles.front().line, viewPoint);
579
580 context->renderToScreen(viewPoint);
581 }
582
panLeftAnim(EventHandlerWrapper callback)583 void VideoRoom::panLeftAnim(EventHandlerWrapper callback) {
584 _panSpeed = -6; // TODO: check this speed
585 _panCallback = callback;
586 }
587
panRightAnim(EventHandlerWrapper callback)588 void VideoRoom::panRightAnim(EventHandlerWrapper callback) {
589 _panSpeed = +6; // TODO: check this speed
590 _panCallback = callback;
591 }
592
panRightInstant()593 void VideoRoom::panRightInstant() {
594 _pan = 640;
595 }
596
addStaticLayer(const LayerId & name,int zValue,Common::Point offset)597 void VideoRoom::addStaticLayer(const LayerId &name, int zValue, Common::Point offset) {
598 PodFile pf2(name.getDebug());
599 if (!pf2.openStore(Common::SharedPtr<Common::SeekableReadStream>(openFile(mapAsset(name) + ".pod")))) {
600 debug("Animation %s isn't found", name.getDebug().c_str());
601 return;
602 }
603 for (int idx = 1; ; idx++) {
604 PodImage pi;
605 if (!pi.loadImage(pf2, idx)) {
606 break;
607 }
608 Common::Array <PodImage> arr;
609 arr.push_back(pi);
610 addLayer(new Renderable(arr), name, zValue, true, offset);
611 }
612 }
613
addAnimLayerInternal(const LayerId & name,int zValue,Common::Point offset)614 void VideoRoom::addAnimLayerInternal(const LayerId &name, int zValue, Common::Point offset) {
615 Common::SharedPtr<Common::SeekableReadStream> rs(openFile(mapAsset(name) + ".pod"));
616 if (!rs) {
617 debug("Animation %s isn't found", name.getDebug().c_str());
618 return;
619 }
620
621 PodFile pf2(name.getDebug());
622 pf2.openStore(rs);
623
624 addLayer(new Renderable(pf2.loadImageArray()), name, zValue, false,
625 offset);
626 }
627
Renderable(Common::Array<PodImage> images)628 Renderable::Renderable(Common::Array<PodImage> images) {
629 _images = images;
630 _msperframe = kDefaultSpeed;
631 _loop = false;
632 _first = 0;
633 _last = 0;
634 _startms = 0;
635 }
636
getLen()637 int Renderable::getLen() {
638 return _first < _last ? _last - _first + 1 : _first - _last + 1;
639 }
640
math_mod(int a,int b)641 int math_mod(int a, int b) {
642 int m = a % b;
643 while (m < 0)
644 m += ABS(b);
645 return m;
646 }
647
getFrame(int time)648 const PodImage &Renderable::getFrame(int time) {
649 return _images[getAnimationFrameNum(time)];
650 }
651
getAnimationFrameNum(int time)652 int Renderable::getAnimationFrameNum(int time) {
653 int f = ((time - _startms) / _msperframe);
654 if (f < 0)
655 f = 0;
656 if (_loop)
657 f %= getLen();
658 if (f >= getLen())
659 f = getLen() - 1;
660 if (_first <= _last)
661 return math_mod(_first + f, _images.size());
662 else
663 return math_mod(_first - f, _images.size());
664 }
665
startAnimation(int startms,int msperframe,bool loop,int first,int last)666 void Renderable::startAnimation(int startms, int msperframe, bool loop, int first, int last) {
667 _loop = loop;
668 _msperframe = msperframe;
669 _startms = startms;
670 _first = math_mod(first, _images.size());
671 _last = math_mod(last, _images.size());
672 }
673
selectFrame(int frame)674 void Renderable::selectFrame(int frame) {
675 _loop = true;
676 _msperframe = kDefaultSpeed;
677 _startms = 0;
678 frame = math_mod(frame, _images.size());
679 _first = frame;
680 _last = frame;
681 }
682
isAnimationFinished(int time)683 bool Renderable::isAnimationFinished(int time) {
684 return !_loop && time > _startms + getLen() * _msperframe;
685 }
686
loadHotZones(const Common::String & hotzoneFile,bool enable,Common::Point offset)687 void VideoRoom::loadHotZones(const Common::String &hotzoneFile, bool enable, Common::Point offset) {
688 Common::SharedPtr<Common::SeekableReadStream> hzFile(openFile(hotzoneFile));
689 if (!hzFile) {
690 debug("Couldn't open %s", hotzoneFile.c_str());
691 return;
692 }
693
694 _hotZones.readHotzones(hzFile, enable, offset);
695 }
696
pushHotZones(const Common::String & hotzoneFile,bool enable,Common::Point offset)697 void VideoRoom::pushHotZones(const Common::String &hotzoneFile, bool enable,
698 Common::Point offset) {
699 // TODO: optimize this? maybe. Or maybe not
700 _hotZoneStack.push_back(_hotZones);
701 _hotZones = HotZoneArray();
702 loadHotZones(hotzoneFile, enable, offset);
703 }
704
popHotZones()705 void VideoRoom::popHotZones() {
706 // TODO: optimize this? maybe. Or maybe not
707 _hotZones = _hotZoneStack.back();
708 _hotZoneStack.pop_back();
709 }
710
getCursor()711 int VideoRoom::getCursor() {
712 return _cursor;
713 }
714
getDragged()715 PodImage *VideoRoom::getDragged() {
716 if (_isDragging)
717 return &_draggedImage[_draggingPtr];
718 else
719 return nullptr;
720 }
721
clearDrag()722 void VideoRoom::clearDrag() {
723 _isDragging = false;
724 }
725
drag(const Common::String & name,int frame,Common::Point hotspot)726 void VideoRoom::drag(const Common::String &name, int frame, Common::Point hotspot) {
727 Common::SharedPtr<Common::SeekableReadStream> rs(openFile(mapAsset(name) + ".pod"));
728 if (!rs) {
729 debug("Animation %s isn't found", name.c_str());
730 return;
731 }
732
733 PodFile pf2(name);
734 pf2.openStore(rs);
735 PodImage pi;
736
737 pi.loadImage(pf2, 1 + frame);
738 pi.setHotspot(hotspot);
739
740 _draggingPtr = (_draggingPtr + 1) % ARRAYSIZE(_draggedImage);
741
742 _draggedImage[_draggingPtr] = pi;
743 _isDragging = true;
744 }
745
cancelVideo()746 void VideoRoom::cancelVideo() {
747 if (_videoDecoder) {
748 _videoDecoder.reset();
749 _videoDecoderEndEvent = 0;
750 _videoW = 0;
751 _videoH = 0;
752 }
753 }
754
playVideo(const Common::String & name,int zValue,EventHandlerWrapper callbackEvent,Common::Point offset)755 void VideoRoom::playVideo(const Common::String &name, int zValue,
756 EventHandlerWrapper callbackEvent,
757 Common::Point offset) {
758 cancelVideo();
759 Common::SharedPtr<Video::SmackerDecoder> decoder
760 = Common::SharedPtr<Video::SmackerDecoder>(new Video::SmackerDecoder());
761
762 Common::File *file = new Common::File;
763 Common::String mappedName = _assetMap.get(name, 1);
764 if (mappedName == "") {
765 mappedName = name;
766 }
767 if (!file->open(_smkPath + "/" + mappedName + ".SMK") || !decoder->loadStream(file)) {
768 debug("Video file %s can't be opened", name.c_str());
769 g_vm->handleEvent(callbackEvent);
770 return;
771 }
772
773 decoder->start();
774 _videoDecoder = decoder;
775 _videoDecoderEndEvent = callbackEvent;
776 _videoOffset = offset;
777 _videoZ = zValue;
778 }
779
openFile(const Common::String & name)780 Common::SeekableReadStream *VideoRoom::openFile(const Common::String &name) {
781 Common::SeekableReadStream *stream = _podFile->getFileStream(name);
782 if (stream)
783 return stream;
784 return g_vm->getWdPodFile()->getFileStream(name);
785 }
786
mapAsset(const Common::String & name)787 Common::String VideoRoom::mapAsset(const Common::String &name) {
788 Common::String mappedName = _assetMap.get(name, 1);
789 if (mappedName == "") {
790 return name;
791 }
792 return mappedName;
793 }
794
mapAsset(const LayerId & name)795 Common::String VideoRoom::mapAsset(const LayerId &name) {
796 return mapAsset(name.getFilename());
797 }
798
getAudioStream(const Common::String & soundName)799 Audio::RewindableAudioStream *VideoRoom::getAudioStream(const Common::String &soundName) {
800 Common::SeekableReadStream *stream = openFile(
801 mapAsset(soundName) + ".aif");
802 if (!stream) {
803 debug("Audio %s isn't found", soundName.c_str());
804 return nullptr;
805 }
806 return Audio::makeAIFFStream(stream, DisposeAfterUse::YES);
807 }
808
playSoundInternal(const Common::String & soundName,EventHandlerWrapper callbackEvent,bool loop,bool skippable,Audio::Mixer::SoundType soundType,int subtitleID)809 void VideoRoom::playSoundInternal(const Common::String &soundName, EventHandlerWrapper callbackEvent, bool loop,
810 bool skippable, Audio::Mixer::SoundType soundType, int subtitleID) {
811 Audio::RewindableAudioStream *rewSoundStream;
812 Audio::AudioStream *soundStream;
813 Animation anim;
814 g_system->getMixer()->stopHandle(anim._soundHandle);
815 rewSoundStream = getAudioStream(soundName);
816 soundStream = loop ? Audio::makeLoopingAudioStream(rewSoundStream, 0) : rewSoundStream;
817 anim._animName = soundName;
818 anim._callbackEvent = callbackEvent;
819 anim._finished = false;
820 anim._keepLastFrame = false;
821 anim._skippable = skippable;
822 anim._subtitleID = subtitleID;
823 g_system->getMixer()->playStream(soundType, &anim._soundHandle, soundStream,
824 -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::YES);
825 _anims.push_back(anim);
826 }
827
playSFX(const Common::String & soundName,EventHandlerWrapper callbackEvent)828 void VideoRoom::playSFX(const Common::String &soundName, EventHandlerWrapper callbackEvent) {
829 playSoundInternal(soundName, callbackEvent, false, false, Audio::Mixer::kSFXSoundType);
830 }
831
playMusic(const Common::String & soundName,EventHandlerWrapper callbackEvent)832 void VideoRoom::playMusic(const Common::String &soundName, EventHandlerWrapper callbackEvent) {
833 playSoundInternal(soundName, callbackEvent, false, false, Audio::Mixer::kMusicSoundType);
834 }
835
playSpeech(const TranscribedSound & sound,EventHandlerWrapper callbackEvent)836 void VideoRoom::playSpeech(const TranscribedSound &sound,
837 EventHandlerWrapper callbackEvent) {
838 int subID = g_vm->genSubtitleID();
839 playSoundInternal(sound.soundName, callbackEvent, false, true, Audio::Mixer::kSpeechSoundType, subID);
840 playSubtitles(sound.transcript, subID);
841 }
842
playSFXLoop(const Common::String & soundName)843 void VideoRoom::playSFXLoop(const Common::String &soundName) {
844 playSoundInternal(soundName, EventHandlerWrapper(), true, false, Audio::Mixer::kSFXSoundType);
845 }
846
playMusicLoop(const Common::String & soundName)847 void VideoRoom::playMusicLoop(const Common::String &soundName) {
848 playSoundInternal(soundName, EventHandlerWrapper(), true, false, Audio::Mixer::kMusicSoundType);
849 }
850
playAnimWithSoundInternal(const LayerId & animName,const Common::String & soundName,Audio::Mixer::SoundType soundType,int zValue,PlayAnimParams params,EventHandlerWrapper callbackEvent,Common::Point offset,int subtitleID)851 void VideoRoom::playAnimWithSoundInternal(const LayerId &animName,
852 const Common::String &soundName,
853 Audio::Mixer::SoundType soundType,
854 int zValue,
855 PlayAnimParams params,
856 EventHandlerWrapper callbackEvent,
857 Common::Point offset, int subtitleID) {
858 Audio::AudioStream *soundStream;
859
860 if (!doesLayerExist(animName)) {
861 addAnimLayerInternal(animName, zValue);
862 }
863
864 startAnimationInternal(animName, zValue, params.getSpeed(), params.isLoop(),
865 false, params.getFirstFrame(), params.getLastFrame(), offset);
866 Animation anim;
867 g_system->getMixer()->stopHandle(anim._soundHandle);
868 if (params.isLoop()) {
869 soundStream = Audio::makeLoopingAudioStream(getAudioStream(soundName), 0);
870 } else {
871 soundStream = getAudioStream(soundName);
872 }
873 anim._animName = animName;
874 anim._callbackEvent = callbackEvent;
875 anim._finished = false;
876 anim._keepLastFrame = params.getKeepLastFrame();
877 anim._skippable = false;
878 anim._subtitleID = subtitleID;
879 g_system->getMixer()->playStream(soundType, &anim._soundHandle, soundStream,
880 -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::YES);
881 _anims.push_back(anim);
882 }
883
playAnimWithSpeech(const LayerId & animName,const TranscribedSound & sound,int zValue,PlayAnimParams params,EventHandlerWrapper callbackEvent,Common::Point offset)884 void VideoRoom::playAnimWithSpeech(const LayerId &animName,
885 const TranscribedSound &sound,
886 int zValue,
887 PlayAnimParams params,
888 EventHandlerWrapper callbackEvent,
889 Common::Point offset) {
890 int subID = g_vm->genSubtitleID();
891 playAnimWithSoundInternal(animName, sound.soundName, Audio::Mixer::kSpeechSoundType, zValue, params, callbackEvent, offset, subID);
892 playSubtitles(sound.transcript, subID);
893 }
894
playAnimWithSFX(const LayerId & animName,const Common::String & soundName,int zValue,PlayAnimParams params,EventHandlerWrapper callbackEvent,Common::Point offset)895 void VideoRoom::playAnimWithSFX(const LayerId &animName,
896 const Common::String &soundName,
897 int zValue,
898 PlayAnimParams params,
899 EventHandlerWrapper callbackEvent,
900 Common::Point offset) {
901 playAnimWithSoundInternal(animName, soundName, Audio::Mixer::kSFXSoundType, zValue, params, callbackEvent, offset);
902 }
903
playAnimWithMusic(const LayerId & animName,const Common::String & soundName,int zValue,PlayAnimParams params,EventHandlerWrapper callbackEvent,Common::Point offset)904 void VideoRoom::playAnimWithMusic(const LayerId &animName,
905 const Common::String &soundName,
906 int zValue,
907 PlayAnimParams params,
908 EventHandlerWrapper callbackEvent,
909 Common::Point offset) {
910 playAnimWithSoundInternal(animName, soundName, Audio::Mixer::kMusicSoundType, zValue, params, callbackEvent, offset);
911 }
912
playAnim(const LayerId & animName,int zValue,PlayAnimParams params,EventHandlerWrapper callbackEvent,Common::Point offset)913 void VideoRoom::playAnim(const LayerId &animName, int zValue,
914 PlayAnimParams params,
915 EventHandlerWrapper callbackEvent,
916 Common::Point offset) {
917 if (!doesLayerExist(animName)) {
918 addAnimLayerInternal(animName, zValue);
919 }
920
921 startAnimationInternal(animName, zValue, params.getSpeed(), params.isLoop(), false,
922 params.getFirstFrame(), params.getLastFrame(), offset);
923 Animation anim;
924 anim._callbackEvent = callbackEvent;
925 anim._finished = false;
926 anim._keepLastFrame = params.getKeepLastFrame();
927 anim._animName = animName;
928 anim._skippable = false;
929 _anims.push_back(anim);
930 }
931
playAnimLoop(const LayerId & animName,int zValue,Common::Point offset)932 void VideoRoom::playAnimLoop(const LayerId &animName, int zValue, Common::Point offset) {
933 playAnim(animName, zValue, PlayAnimParams::loop(), EventHandlerWrapper(), offset);
934 }
935
playAnimKeepLastFrame(const LayerId & animName,int zValue,EventHandlerWrapper callbackEvent,Common::Point offset)936 void VideoRoom::playAnimKeepLastFrame(const LayerId &animName, int zValue, EventHandlerWrapper callbackEvent,
937 Common::Point offset) {
938 playAnim(animName, zValue, PlayAnimParams::keepLastFrame(), callbackEvent, offset);
939 }
940
setUserPanCallback(EventHandlerWrapper leftStart,EventHandlerWrapper leftEnd,EventHandlerWrapper rightStart,EventHandlerWrapper rightEnd)941 void VideoRoom::setUserPanCallback(EventHandlerWrapper leftStart,
942 EventHandlerWrapper leftEnd,
943 EventHandlerWrapper rightStart,
944 EventHandlerWrapper rightEnd) {
945 _userPanStartLeftCallback = leftStart;
946 _userPanEndLeftCallback = leftEnd;
947 _userPanStartRightCallback = rightStart;
948 _userPanEndRightCallback = rightEnd;
949 }
950
951 class StatuePlayEnd : public EventHandler {
952 public:
operator ()()953 void operator()() override {
954 Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
955 room->playAnim(_animName, _zValue,
956 PlayAnimParams::disappear().partial(_lastLoopFrame, -1),
957 EventHandlerWrapper(), _offset);
958 room->enableMouse();
959 }
960
StatuePlayEnd(const LayerId & animName,int zValue,Common::Point offset,int lastLoopFrame)961 StatuePlayEnd(const LayerId &animName, int zValue, Common::Point offset, int lastLoopFrame) {
962 _animName = animName;
963 _zValue = zValue;
964 _offset = offset;
965 _lastLoopFrame = lastLoopFrame;
966 }
967 private:
968 LayerId _animName;
969 int _zValue;
970 int _lastLoopFrame;
971 Common::Point _offset;
972 };
973
974 class StatuePlayTwoThirdsLoop : public EventHandler {
975 public:
operator ()()976 void operator()() override {
977 Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
978 room->playAnim(_animName, _zValue,
979 PlayAnimParams::loop().partial(_firstLoopFrame, _lastLoopFrame),
980 EventHandlerWrapper(), _offset);
981 }
982
StatuePlayTwoThirdsLoop(const LayerId & animName,int zValue,Common::Point offset,int firstLoopFrame,int lastLoopFrame)983 StatuePlayTwoThirdsLoop(const LayerId &animName, int zValue, Common::Point offset,
984 int firstLoopFrame, int lastLoopFrame) {
985 _animName = animName;
986 _zValue = zValue;
987 _offset = offset;
988 _firstLoopFrame = firstLoopFrame;
989 _lastLoopFrame = lastLoopFrame;
990 }
991 private:
992 LayerId _animName;
993 int _zValue;
994 int _firstLoopFrame, _lastLoopFrame;
995 Common::Point _offset;
996 };
997
playStatueSMK(StatueId id,const LayerId & animName,int zValue,const Common::Array<Common::String> & smkNames,int startOfLoop,int startOfEnd,Common::Point offset)998 void VideoRoom::playStatueSMK(StatueId id, const LayerId &animName, int zValue,
999 const Common::Array<Common::String> &smkNames,
1000 int startOfLoop, int startOfEnd,
1001 Common::Point offset) {
1002 int phase = g_vm->getPersistent()->_statuePhase[id] % smkNames.size();
1003 playVideo(smkNames[phase], zValue,
1004 Common::SharedPtr<EventHandler>(new StatuePlayEnd(animName, zValue, offset, startOfEnd)));
1005 if (!doesLayerExist(animName)) {
1006 addAnimLayerInternal(animName, zValue);
1007 stopAnim(animName);
1008 }
1009 playAnim(animName, zValue, PlayAnimParams::keepLastFrame().partial(0, startOfEnd - 1),
1010 Common::SharedPtr<EventHandler>(new StatuePlayTwoThirdsLoop(animName, zValue, offset, startOfLoop, startOfEnd - 1)), offset);
1011
1012 g_vm->getPersistent()->_statuesTouched[id] = true;
1013 g_vm->getPersistent()->_statuePhase[id] = (phase + 1) % smkNames.size();
1014 disableMouse();
1015 }
1016
resetFade()1017 void VideoRoom::resetFade() {
1018 _finalFade = 0x100;
1019 _finalFadeSpeed = 0;
1020 _finalFadeCallback = EventHandlerWrapper();
1021 }
1022
resetLayers()1023 void VideoRoom::resetLayers() {
1024 _layers.clear();
1025 }
1026
hideString(const Common::String & font,size_t maxLen,const Common::String & extraId)1027 void VideoRoom::hideString(const Common::String &font, size_t maxLen, const Common::String &extraId) {
1028 Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
1029 for (unsigned i = 0; i < maxLen; i++) {
1030 room->stopAnim(LayerId(font, i, extraId));
1031 }
1032 }
1033
renderString(const Common::String & font,const Common::U32String & str,Common::Point startPos,int zVal,int fontDelta,const Common::String & extraId)1034 void VideoRoom::renderString(const Common::String &font, const Common::U32String &str, Common::Point startPos, int zVal, int fontDelta, const Common::String &extraId) {
1035 Common::Point curPos = startPos;
1036 bool small = font == "smallascii";
1037 for (unsigned i = 0; i < str.size(); i++) {
1038 uint32 c = str[i];
1039 if (c == ' ') {
1040 curPos += Common::Point(small ? 6 : 20, 0);
1041 continue;
1042 }
1043 LayerId l(font, i, extraId);
1044 selectFrame(l, zVal, c + fontDelta, curPos);
1045 PodImage pi(getLayerFrame(l));
1046 curPos += Common::Point(pi.getWidth() + pi.getOffset().x + (small ? 1 : 3), 0);
1047 }
1048 }
1049
loadFontWidth(const Common::String & font)1050 void VideoRoom::loadFontWidth(const Common::String &font) {
1051 if (_fontWidths.contains(font)) {
1052 return;
1053 }
1054
1055 Common::SharedPtr<Common::SeekableReadStream> rs(openFile(mapAsset(font) + ".pod"));
1056 if (!rs) {
1057 _fontWidths[font].clear();
1058 debug("Animation %s isn't found", font.c_str());
1059 return;
1060 }
1061
1062 PodFile pf2(font);
1063 pf2.openStore(rs);
1064
1065 Common::Array <PodImage> pi = pf2.loadImageArray();
1066 bool small = font == "smallascii";
1067
1068 for (unsigned i = 0; i < pi.size(); i++)
1069 _fontWidths[font].push_back(pi[i].getWidth()+pi[i].getOffset().x + (small ? 1 : 3));
1070 }
1071
computeStringWidth(const Common::String & font,const Common::U32String & str,int fontDelta)1072 int VideoRoom::computeStringWidth(const Common::String &font, const Common::U32String &str, int fontDelta) {
1073 int width = 0;
1074 bool small = font == "smallascii";
1075 if (!_fontWidths.contains(font)) {
1076 loadFontWidth(font);
1077 }
1078 for (unsigned i = 0; i < str.size(); i++) {
1079 uint32 c = str[i];
1080 if (c == ' ') {
1081 width += small ? 6 : 20;
1082 continue;
1083 }
1084 if (c + fontDelta < _fontWidths[font].size())
1085 width += _fontWidths[font][c + fontDelta];
1086 }
1087
1088 return width;
1089 }
1090
renderStringCentered(const Common::String & font,const Common::U32String & str,Common::Point centerPos,int zVal,int fontDelta,const Common::String & extraId)1091 void VideoRoom::renderStringCentered(const Common::String &font, const Common::U32String &str, Common::Point centerPos, int zVal, int fontDelta, const Common::String &extraId) {
1092 int width = computeStringWidth(font, str, fontDelta);
1093 renderString(font, str, centerPos - Common::Point(width / 2, 0), zVal, fontDelta, extraId);
1094 }
1095
playSubtitles(const char * text,int subID)1096 void VideoRoom::playSubtitles(const char *text, int subID) {
1097 int delay = g_vm->getSubtitleDelayPerChar();
1098 if (delay <= 0)
1099 return;
1100 Common::U32String s = g_vm->translate(text);
1101 Common::Array<Common::U32String> lines;
1102 int32 countTime = g_vm->getCurrentTime();
1103 g_vm->wrapSubtitles(s, lines);
1104 for (uint i = 0; i < lines.size(); i++) {
1105 SubtitleLine l;
1106 l.line = lines[i];
1107 l.ID = subID;
1108 countTime += delay * MAX<uint>(l.line.size(), 20);
1109 l.maxTime = countTime;
1110 _subtitles.push(l);
1111 _countQueuedSubtitles[subID]++;
1112 }
1113 }
1114 }
1115