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