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  */
22 
23 #include "common/file.h"
24 #include "common/system.h"
25 #include "common/macresman.h"
26 
27 #include "graphics/primitives.h"
28 #include "graphics/macgui/macwindowmanager.h"
29 
30 #include "director/director.h"
31 #include "director/cast.h"
32 #include "director/lingo/lingo.h"
33 #include "director/movie.h"
34 #include "director/window.h"
35 #include "director/score.h"
36 #include "director/castmember.h"
37 #include "director/cursor.h"
38 #include "director/channel.h"
39 #include "director/sound.h"
40 #include "director/sprite.h"
41 #include "director/util.h"
42 
43 namespace Director {
44 
Window(int id,bool scrollable,bool resizable,bool editable,Graphics::MacWindowManager * wm,DirectorEngine * vm,bool isStage)45 Window::Window(int id, bool scrollable, bool resizable, bool editable, Graphics::MacWindowManager *wm, DirectorEngine *vm, bool isStage)
46 	: MacWindow(id, scrollable, resizable, editable, wm), Object<Window>("Window") {
47 	_vm = vm;
48 	_isStage = isStage;
49 	_stageColor = _wm->_colorBlack;
50 	_puppetTransition = nullptr;
51 	_soundManager = new DirectorSound(this);
52 
53 	_currentMovie = nullptr;
54 	_mainArchive = nullptr;
55 	_macBinary = nullptr;
56 	_nextMovie.frameI = -1;
57 	_newMovieStarted = true;
58 
59 	_objType = kWindowObj;
60 	_startFrame = _vm->getStartMovie().startFrame;
61 
62 	_windowType = -1;
63 	_titleVisible = true;
64 	updateBorderType();
65 
66 	_retPC = 0;
67 	_retScript = nullptr;
68 	_retContext = nullptr;
69 	_retFreezeContext = false;
70 	_retLocalVars = nullptr;
71 }
72 
~Window()73 Window::~Window() {
74 	delete _soundManager;
75 	delete _currentMovie;
76 	if (_macBinary) {
77 		delete _macBinary;
78 		_macBinary = nullptr;
79 	}
80 }
81 
invertChannel(Channel * channel,const Common::Rect & destRect)82 void Window::invertChannel(Channel *channel, const Common::Rect &destRect) {
83 	const Graphics::Surface *mask;
84 
85 	// in D3, we have inverted QDshape
86 	if (channel->_sprite->isQDShape() && channel->_sprite->_ink == kInkTypeMatte)
87 		mask = channel->_sprite->getQDMatte();
88 	else
89 		mask = channel->getMask(true);
90 
91 	Common::Rect srcRect = channel->getBbox();
92 	srcRect.clip(destRect);
93 
94 	// let compiler to optimize it
95 	int xoff = srcRect.left - channel->getBbox().left;
96 	int yoff = srcRect.top - channel->getBbox().top;
97 
98 	if (_wm->_pixelformat.bytesPerPixel == 1) {
99 		for (int i = 0; i < srcRect.height(); i++) {
100 			byte *src = (byte *)_composeSurface->getBasePtr(srcRect.left, srcRect.top + i);
101 			const byte *msk = mask ? (const byte *)mask->getBasePtr(xoff, yoff + i) : nullptr;
102 
103 			for (int j = 0; j < srcRect.width(); j++, src++)
104 				if (!mask || (msk && !(*msk++)))
105 					*src = _wm->inverter(*src);
106 		}
107 	} else {
108 
109 		for (int i = 0; i < srcRect.height(); i++) {
110 			uint32 *src = (uint32 *)_composeSurface->getBasePtr(srcRect.left, srcRect.top + i);
111 			const uint32 *msk = mask ? (const uint32 *)mask->getBasePtr(xoff, yoff + i) : nullptr;
112 
113 			for (int j = 0; j < srcRect.width(); j++, src++)
114 				if (!mask || (msk && !(*msk++)))
115 					*src = _wm->inverter(*src);
116 		}
117 	}
118 }
119 
render(bool forceRedraw,Graphics::ManagedSurface * blitTo)120 bool Window::render(bool forceRedraw, Graphics::ManagedSurface *blitTo) {
121 	if (!_currentMovie)
122 		return false;
123 
124 	if (forceRedraw) {
125 		blitTo->clear(_stageColor);
126 		markAllDirty();
127 	} else {
128 		if (_dirtyRects.size() == 0 && _currentMovie->_videoPlayback == false)
129 			return false;
130 
131 		mergeDirtyRects();
132 	}
133 
134 	if (!blitTo)
135 		blitTo = _composeSurface;
136 	Channel *hiliteChannel = _currentMovie->getScore()->getChannelById(_currentMovie->_currentHiliteChannelId);
137 
138 	for (Common::List<Common::Rect>::iterator i = _dirtyRects.begin(); i != _dirtyRects.end(); i++) {
139 		const Common::Rect &r = *i;
140 		_dirtyChannels = _currentMovie->getScore()->getSpriteIntersections(r);
141 
142 		bool shouldClear = true;
143 		for (Common::List<Channel *>::iterator j = _dirtyChannels.begin(); j != _dirtyChannels.end(); j++) {
144 			if ((*j)->_visible && r == (*j)->getBbox() && (*j)->isTrail()) {
145 				shouldClear = false;
146 				break;
147 			}
148 		}
149 
150 		if (shouldClear)
151 			blitTo->fillRect(r, _stageColor);
152 
153 		for (int pass = 0; pass < 2; pass++) {
154 			for (Common::List<Channel *>::iterator j = _dirtyChannels.begin(); j != _dirtyChannels.end(); j++) {
155 				if ((*j)->isActiveVideo() && (*j)->isVideoDirectToStage()) {
156 					if (pass == 0)
157 						continue;
158 				} else {
159 					if (pass == 1)
160 						continue;
161 				}
162 
163 				if ((*j)->_visible) {
164 					inkBlitFrom(*j, r, blitTo);
165 					if ((*j) == hiliteChannel)
166 						invertChannel(hiliteChannel, r);
167 				}
168 			}
169 		}
170 	}
171 
172 	_dirtyRects.clear();
173 	_contentIsDirty = true;
174 
175 	return true;
176 }
177 
setStageColor(uint32 stageColor,bool forceReset)178 void Window::setStageColor(uint32 stageColor, bool forceReset) {
179 	if (stageColor != _stageColor || forceReset) {
180 		_stageColor = stageColor;
181 		reset();
182 		markAllDirty();
183 	}
184 }
185 
reset()186 void Window::reset() {
187 	resize(_composeSurface->w, _composeSurface->h, true);
188 	_composeSurface->clear(_stageColor);
189 	_contentIsDirty = true;
190 }
191 
inkBlitFrom(Channel * channel,Common::Rect destRect,Graphics::ManagedSurface * blitTo)192 void Window::inkBlitFrom(Channel *channel, Common::Rect destRect, Graphics::ManagedSurface *blitTo) {
193 	Common::Rect srcRect = channel->getBbox();
194 	destRect.clip(srcRect);
195 
196 	DirectorPlotData pd = channel->getPlotData();
197 	pd.destRect = destRect;
198 	pd.dst = blitTo;
199 
200 	if (pd.ms) {
201 		inkBlitShape(&pd, srcRect);
202 	} else if (pd.srf) {
203 		if (channel->isStretched()) {
204 			srcRect = channel->getBbox(true);
205 			inkBlitStretchSurface(&pd, srcRect, channel->getMask());
206 		} else {
207 			inkBlitSurface(&pd, srcRect, channel->getMask());
208 		}
209 	} else {
210 		if (debugChannelSet(kDebugImages, 2))
211 			warning("Window::inkBlitFrom: No source surface: spriteType: %d, castType: %d, castId: %s", channel->_sprite->_spriteType, channel->_sprite->_cast ? channel->_sprite->_cast->_type : 0, channel->_sprite->_castId.asString().c_str());
212 	}
213 }
214 
inkBlitShape(DirectorPlotData * pd,Common::Rect & srcRect)215 void Window::inkBlitShape(DirectorPlotData *pd, Common::Rect &srcRect) {
216 	if (!pd->ms)
217 		return;
218 
219 	// Preprocess shape colours
220 	switch (pd->ink) {
221 	case kInkTypeNotTrans:
222 	case kInkTypeNotReverse:
223 	case kInkTypeNotGhost:
224 		return;
225 	case kInkTypeReverse:
226 		pd->ms->foreColor = 0;
227 		pd->ms->backColor = 0;
228 		break;
229 	default:
230 		break;
231 	}
232 
233 	Common::Rect fillAreaRect((int)srcRect.width(), (int)srcRect.height());
234 	fillAreaRect.moveTo(srcRect.left, srcRect.top);
235 	Graphics::MacPlotData plotFill(pd->dst, nullptr, &g_director->getPatterns(), pd->ms->pattern, srcRect.left, srcRect.top, 1, pd->ms->backColor);
236 
237 	Common::Rect strokeRect(MAX((int)srcRect.width() - pd->ms->lineSize, 0), MAX((int)srcRect.height() - pd->ms->lineSize, 0));
238 	strokeRect.moveTo(srcRect.left, srcRect.top);
239 	Graphics::MacPlotData plotStroke(pd->dst, nullptr, &g_director->getPatterns(), 1, strokeRect.left, strokeRect.top, pd->ms->lineSize, pd->ms->backColor);
240 
241 	switch (pd->ms->spriteType) {
242 	case kRectangleSprite:
243 		pd->ms->pd = &plotFill;
244 		Graphics::drawFilledRect(fillAreaRect, pd->ms->foreColor, g_director->getInkDrawPixel(), pd);
245 		// fall through
246 	case kOutlinedRectangleSprite:
247 		// if we have lineSize <= 0, means we are not drawing anything. so we may return directly.
248 		if (pd->ms->lineSize <= 0)
249 			break;
250 		pd->ms->pd = &plotStroke;
251 		Graphics::drawRect(strokeRect, pd->ms->foreColor, g_director->getInkDrawPixel(), pd);
252 		break;
253 	case kRoundedRectangleSprite:
254 		pd->ms->pd = &plotFill;
255 		Graphics::drawRoundRect(fillAreaRect, 12, pd->ms->foreColor, true, g_director->getInkDrawPixel(), pd);
256 		// fall through
257 	case kOutlinedRoundedRectangleSprite:
258 		if (pd->ms->lineSize <= 0)
259 			break;
260 		pd->ms->pd = &plotStroke;
261 		Graphics::drawRoundRect(strokeRect, 12, pd->ms->foreColor, false, g_director->getInkDrawPixel(), pd);
262 		break;
263 	case kOvalSprite:
264 		pd->ms->pd = &plotFill;
265 		Graphics::drawEllipse(fillAreaRect.left, fillAreaRect.top, fillAreaRect.right, fillAreaRect.bottom, pd->ms->foreColor, true, g_director->getInkDrawPixel(), pd);
266 		// fall through
267 	case kOutlinedOvalSprite:
268 		if (pd->ms->lineSize <= 0)
269 			break;
270 		pd->ms->pd = &plotStroke;
271 		Graphics::drawEllipse(strokeRect.left, strokeRect.top, strokeRect.right, strokeRect.bottom, pd->ms->foreColor, false, g_director->getInkDrawPixel(), pd);
272 		break;
273 	case kLineTopBottomSprite:
274 		pd->ms->pd = &plotStroke;
275 		Graphics::drawLine(strokeRect.left, strokeRect.top, strokeRect.right, strokeRect.bottom, pd->ms->foreColor, g_director->getInkDrawPixel(), pd);
276 		break;
277 	case kLineBottomTopSprite:
278 		pd->ms->pd = &plotStroke;
279 		Graphics::drawLine(strokeRect.left, strokeRect.bottom, strokeRect.right, strokeRect.top, pd->ms->foreColor, g_director->getInkDrawPixel(), pd);
280 		break;
281 	default:
282 		warning("Window::inkBlitFrom: Expected shape type but got type %d", pd->ms->spriteType);
283 	}
284 }
285 
inkBlitSurface(DirectorPlotData * pd,Common::Rect & srcRect,const Graphics::Surface * mask)286 void Window::inkBlitSurface(DirectorPlotData *pd, Common::Rect &srcRect, const Graphics::Surface *mask) {
287 	if (!pd->srf)
288 		return;
289 
290 	// TODO: Determine why colourization causes problems in Warlock
291 	if (pd->sprite == kTextSprite)
292 		pd->applyColor = false;
293 
294 	pd->srcPoint.y = abs(srcRect.top - pd->destRect.top);
295 	for (int i = 0; i < pd->destRect.height(); i++, pd->srcPoint.y++) {
296 		if (_wm->_pixelformat.bytesPerPixel == 1) {
297 			pd->srcPoint.x = abs(srcRect.left - pd->destRect.left);
298 			const byte *msk = mask ? (const byte *)mask->getBasePtr(pd->srcPoint.x, pd->srcPoint.y) : nullptr;
299 
300 			for (int j = 0; j < pd->destRect.width(); j++, pd->srcPoint.x++) {
301 				if (!mask || (msk && !(*msk++))) {
302 					(g_director->getInkDrawPixel())(pd->destRect.left + j, pd->destRect.top + i,
303 											preprocessColor(pd, *((byte *)pd->srf->getBasePtr(pd->srcPoint.x, pd->srcPoint.y))), pd);
304 				}
305 			}
306 		} else {
307 			pd->srcPoint.x = abs(srcRect.left - pd->destRect.left);
308 			const uint32 *msk = mask ? (const uint32 *)mask->getBasePtr(pd->srcPoint.x, pd->srcPoint.y) : nullptr;
309 
310 			for (int j = 0; j < pd->destRect.width(); j++, pd->srcPoint.x++) {
311 				if (!mask || (msk && !(*msk++))) {
312 					(g_director->getInkDrawPixel())(pd->destRect.left + j, pd->destRect.top + i,
313 											preprocessColor(pd, *((int *)pd->srf->getBasePtr(pd->srcPoint.x, pd->srcPoint.y))), pd);
314 				}
315 			}
316 		}
317 	}
318 }
319 
inkBlitStretchSurface(DirectorPlotData * pd,Common::Rect & srcRect,const Graphics::Surface * mask)320 void Window::inkBlitStretchSurface(DirectorPlotData *pd, Common::Rect &srcRect, const Graphics::Surface *mask) {
321 	if (!pd->srf)
322 		return;
323 
324 	// TODO: Determine why colourization causes problems in Warlock
325 	if (pd->sprite == kTextSprite)
326 		pd->applyColor = false;
327 
328 	int scaleX = SCALE_THRESHOLD * srcRect.width() / pd->destRect.width();
329 	int scaleY = SCALE_THRESHOLD * srcRect.height() / pd->destRect.height();
330 
331 	pd->srcPoint.y = abs(srcRect.top - pd->destRect.top);
332 
333 	for (int i = 0, scaleYCtr = 0; i < pd->destRect.height(); i++, scaleYCtr += scaleY, pd->srcPoint.y++) {
334 		if (_wm->_pixelformat.bytesPerPixel == 1) {
335 			pd->srcPoint.x = abs(srcRect.left - pd->destRect.left);
336 			const byte *msk = mask ? (const byte *)mask->getBasePtr(pd->srcPoint.x, pd->srcPoint.y) : nullptr;
337 
338 			for (int xCtr = 0, scaleXCtr = 0; xCtr < pd->destRect.width(); xCtr++, scaleXCtr += scaleX, pd->srcPoint.x++) {
339 				if (!mask || !(*msk++)) {
340 				(g_director->getInkDrawPixel())(pd->destRect.left + xCtr, pd->destRect.top + i,
341 										preprocessColor(pd, *((byte *)pd->srf->getBasePtr(scaleXCtr / SCALE_THRESHOLD, scaleYCtr / SCALE_THRESHOLD))), pd);
342 				}
343 			}
344 		} else {
345 			pd->srcPoint.x = abs(srcRect.left - pd->destRect.left);
346 			const uint32 *msk = mask ? (const uint32 *)mask->getBasePtr(pd->srcPoint.x, pd->srcPoint.y) : nullptr;
347 
348 			for (int xCtr = 0, scaleXCtr = 0; xCtr < pd->destRect.width(); xCtr++, scaleXCtr += scaleX, pd->srcPoint.x++) {
349 				if (!mask || !(*msk++)) {
350 				(g_director->getInkDrawPixel())(pd->destRect.left + xCtr, pd->destRect.top + i,
351 										preprocessColor(pd, *((int *)pd->srf->getBasePtr(scaleXCtr / SCALE_THRESHOLD, scaleYCtr / SCALE_THRESHOLD))), pd);
352 				}
353 			}
354 		}
355 	}
356 }
357 
preprocessColor(DirectorPlotData * p,uint32 src)358 int Window::preprocessColor(DirectorPlotData *p, uint32 src) {
359 	// HACK: Right now this method is just used for adjusting the colourization on text
360 	// sprites, as it would be costly to colourize the chunks on the fly each
361 	// time a section needs drawing. It's ugly but mostly works.
362 	if (p->sprite == kTextSprite) {
363 		switch(p->ink) {
364 		case kInkTypeMask:
365 			src = (src == p->backColor ? p->foreColor : 0xff);
366 			break;
367 		case kInkTypeReverse:
368 			src = (src == p->foreColor ? 0 : p->colorWhite);
369 			break;
370 		case kInkTypeNotReverse:
371 			src = (src == p->backColor ? p->colorWhite : 0);
372 			break;
373 			// looks like this part is wrong, maybe it's very same as reverse?
374 			// check warlock/DATA/WARLOCKSHIP/ENG/ABOUT to see more detail.
375 //		case kInkTypeGhost:
376 //			src = (src == p->foreColor ? p->backColor : p->colorWhite);
377 //			break;
378 		case kInkTypeNotGhost:
379 			src = (src == p->backColor ? p->colorWhite : p->backColor);
380 			break;
381 		case kInkTypeNotCopy:
382 			src = (src == p->foreColor ? p->backColor : p->foreColor);
383 			break;
384 		case kInkTypeNotTrans:
385 			src = (src == p->foreColor ? p->backColor : p->colorWhite);
386 			break;
387 		default:
388 			break;
389 		}
390 	}
391 
392 	return src;
393 }
394 
getMousePos()395 Common::Point Window::getMousePos() {
396 	return g_system->getEventManager()->getMousePos() - Common::Point(_innerDims.left, _innerDims.top);
397 }
398 
setVisible(bool visible,bool silent)399 void Window::setVisible(bool visible, bool silent) {
400 	// setting visible triggers movie load
401 	if (!_currentMovie && !silent) {
402 		Common::String movieName = getName();
403 		setNextMovie(movieName);
404 	}
405 
406 	BaseMacWindow::setVisible(visible);
407 
408 	if (visible)
409 		_wm->setActiveWindow(_id);
410 }
411 
setNextMovie(Common::String & movieFilenameRaw)412 bool Window::setNextMovie(Common::String &movieFilenameRaw) {
413 	Common::String movieFilename = pathMakeRelative(movieFilenameRaw);
414 
415 	bool fileExists = false;
416 	Common::File file;
417 	if (file.open(Common::Path(movieFilename, _vm->_dirSeparator))) {
418 		fileExists = true;
419 		file.close();
420 	}
421 
422 	debug(1, "Window::setNextMovie: '%s' -> '%s' -> '%s'", movieFilenameRaw.c_str(), convertPath(movieFilenameRaw).c_str(), movieFilename.c_str());
423 
424 	if (!fileExists) {
425 		warning("Movie %s does not exist", movieFilename.c_str());
426 		return false;
427 	}
428 
429 	_nextMovie.movie = movieFilename;
430 	return true;
431 }
432 
updateBorderType()433 void Window::updateBorderType() {
434 	if (_isStage) {
435 		setBorderType(3);
436 	} else if (!_titleVisible) {
437 		setBorderType(2);
438 	} else {
439 		setBorderType(MAX(0, MIN(_windowType, 16)));
440 	}
441 }
442 
step()443 bool Window::step() {
444 	// finish last movie
445 	if (_currentMovie && _currentMovie->getScore()->_playState == kPlayStopped) {
446 		debugC(3, kDebugEvents, "\n@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
447 		debugC(3, kDebugEvents, "@@@@   Finishing movie '%s' in '%s'", utf8ToPrintable(_currentMovie->getMacName()).c_str(), _currentPath.c_str());
448 		debugC(3, kDebugEvents, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");
449 
450 		_currentMovie->getScore()->stopPlay();
451 		debugC(1, kDebugEvents, "Finished playback of movie '%s'", utf8ToPrintable(_currentMovie->getMacName()).c_str());
452 
453 		if (_vm->getGameGID() == GID_TESTALL) {
454 			_nextMovie = getNextMovieFromQueue();
455 		}
456 	}
457 
458 	// prepare next movie
459 	if (!_nextMovie.movie.empty()) {
460 		_soundManager->changingMovie();
461 
462 		_newMovieStarted = true;
463 
464 		_currentPath = getPath(_nextMovie.movie, _currentPath);
465 
466 		Cast *sharedCast = nullptr;
467 		if (_currentMovie) {
468 			sharedCast = _currentMovie->getSharedCast();
469 			_currentMovie->_sharedCast = nullptr;
470 		}
471 
472 		delete _currentMovie;
473 		_currentMovie = nullptr;
474 
475 		Archive *mov = openMainArchive(_currentPath + Common::lastPathComponent(_nextMovie.movie, g_director->_dirSeparator));
476 
477 		if (!mov) {
478 			warning("nextMovie: No movie is loaded");
479 
480 			if (_vm->getGameGID() == GID_TESTALL) {
481 				return true;
482 			}
483 
484 			return false;
485 		}
486 
487 		_currentMovie = new Movie(this);
488 		_currentMovie->setArchive(mov);
489 
490 		debug(0, "\n@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
491 		debug(0, "@@@@   Switching to movie '%s' in '%s'", utf8ToPrintable(_currentMovie->getMacName()).c_str(), _currentPath.c_str());
492 		debug(0, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");
493 
494 		g_lingo->resetLingo();
495 		Common::String sharedCastPath = getSharedCastPath();
496 		if (!sharedCastPath.empty()) {
497 			if (sharedCast && sharedCast->_castArchive
498 					&& sharedCast->_castArchive->getPathName().equalsIgnoreCase(sharedCastPath)) {
499 				// if we are not deleting shared cast, then we need to clear those previous widget pointer
500 				sharedCast->releaseCastMemberWidget();
501 				_currentMovie->_sharedCast = sharedCast;
502 			} else {
503 				delete sharedCast;
504 				_currentMovie->loadSharedCastsFrom(sharedCastPath);
505 			}
506 		} else {
507 			delete sharedCast;
508 		}
509 
510 		_nextMovie.movie.clear();
511 	}
512 
513 	// play current movie
514 	if (_currentMovie) {
515 		switch (_currentMovie->getScore()->_playState) {
516 		case kPlayNotStarted:
517 			{
518 				debug(0, "\n@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
519 				debug(0, "@@@@   Loading movie '%s' in '%s'", utf8ToPrintable(_currentMovie->getMacName()).c_str(), _currentPath.c_str());
520 				debug(0, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");
521 
522 				bool goodMovie = _currentMovie->loadArchive();
523 
524 				// If we came in a loop, then skip as requested
525 				if (!_nextMovie.frameS.empty()) {
526 					_currentMovie->getScore()->setStartToLabel(_nextMovie.frameS);
527 					_nextMovie.frameS.clear();
528 				}
529 
530 				if (_nextMovie.frameI != -1) {
531 					_currentMovie->getScore()->setCurrentFrame(_nextMovie.frameI);
532 					_nextMovie.frameI = -1;
533 				}
534 
535 				if (!debugChannelSet(-1, kDebugCompileOnly) && goodMovie) {
536 					debugC(1, kDebugEvents, "Starting playback of movie '%s'", _currentMovie->getMacName().c_str());
537 					_currentMovie->getScore()->startPlay();
538 					if (_startFrame != -1) {
539 						_currentMovie->getScore()->setCurrentFrame(_startFrame);
540 						_startFrame = -1;
541 					}
542 				} else {
543 					return false;
544 				}
545 			}
546 			// fall through
547 		case kPlayStarted:
548 			debugC(3, kDebugEvents, "\n@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
549 			debugC(3, kDebugEvents, "@@@@   Stepping movie '%s' in '%s'", utf8ToPrintable(_currentMovie->getMacName()).c_str(), _currentPath.c_str());
550 			debugC(3, kDebugEvents, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");
551 			_currentMovie->getScore()->step();
552 			return true;
553 		default:
554 			return false;
555 		}
556 	}
557 
558 	return false;
559 }
560 
getSharedCastPath()561 Common::String Window::getSharedCastPath() {
562 	Common::Array<Common::String> namesToTry;
563 	if (_vm->getVersion() < 400) {
564 		if (g_director->getPlatform() == Common::kPlatformWindows) {
565 			namesToTry.push_back("SHARDCST.MMM");
566 		} else {
567 			namesToTry.push_back("Shared Cast");
568 		}
569 	} else if (_vm->getVersion() < 500) {
570 		namesToTry.push_back("Shared.dir");
571 		namesToTry.push_back("Shared.dxr");
572 	} else {
573 		// TODO: Does D5 actually support D4-style shared cast?
574 		namesToTry.push_back("Shared.cst");
575 		namesToTry.push_back("Shared.cxt");
576 	}
577 
578 	for (uint i = 0; i < namesToTry.size(); i++) {
579 		Common::File f;
580 		if (f.open(Common::Path(_currentPath + namesToTry[i], _vm->_dirSeparator))) {
581 			f.close();
582 			return _currentPath + namesToTry[i];
583 		}
584 	}
585 
586 	return Common::String();
587 }
588 
589 } // End of namespace Director
590