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/config-manager.h"
24 #include "common/file.h"
25 #include "common/memstream.h"
26 #include "common/substream.h"
27 
28 #include "audio/audiostream.h"
29 
30 #include "graphics/macgui/mactext.h"
31 
32 #ifdef USE_PNG
33 #include "image/png.h"
34 #endif
35 
36 #include "director/director.h"
37 #include "director/cast.h"
38 #include "director/castmember.h"
39 #include "director/score.h"
40 #include "director/frame.h"
41 #include "director/movie.h"
42 #include "director/sound.h"
43 #include "director/cursor.h"
44 #include "director/channel.h"
45 #include "director/sprite.h"
46 #include "director/window.h"
47 #include "director/util.h"
48 #include "director/lingo/lingo.h"
49 
50 namespace Director {
51 
Score(Movie * movie)52 Score::Score(Movie *movie) {
53 	_movie = movie;
54 	_window = movie->getWindow();
55 	_vm = _movie->getVM();
56 	_lingo = _vm->getLingo();
57 
58 	_soundManager = _window->getSoundManager();
59 
60 	_puppetTempo = 0x00;
61 	_puppetPalette = false;
62 	_lastPalette = 0;
63 
64 	_labels = nullptr;
65 
66 	_currentFrameRate = 20;
67 	_currentFrame = 0;
68 	_nextFrame = 0;
69 	_currentLabel = 0;
70 	_nextFrameTime = 0;
71 	_waitForChannel = 0;
72 	_cursorDirty = false;
73 	_waitForClick = false;
74 	_waitForClickCursor = false;
75 	_activeFade = 0;
76 	_playState = kPlayNotStarted;
77 
78 	_numChannelsDisplayed = 0;
79 
80 	_framesRan = 0; // used by kDebugFewFramesOnly and kDebugScreenshot
81 }
82 
~Score()83 Score::~Score() {
84 	for (uint i = 0; i < _frames.size(); i++)
85 		delete _frames[i];
86 
87 	for (uint i = 0; i < _channels.size(); i++)
88 		delete _channels[i];
89 
90 	if (_labels)
91 		for (Common::SortedArray<Label *>::iterator it = _labels->begin(); it != _labels->end(); ++it)
92 			delete *it;
93 
94 	delete _labels;
95 }
96 
getCurrentPalette()97 int Score::getCurrentPalette() {
98 	return _frames[_currentFrame]->_palette.paletteId;
99 }
100 
resolvePaletteId(int id)101 int Score::resolvePaletteId(int id) {
102 	// TODO: Palette ID should be a CastMemberID to allow for palettes in different casts
103 	// 255 represent system palette in D2
104 	if (id == 255) {
105 		id = g_director->getCurrentMovie()->getCast()->_defaultPalette;
106 	} else if (id > 0) {
107 		CastMember *member = _movie->getCastMember(CastMemberID(id, 0));
108 		id = (member && member->_type == kCastPalette) ? ((PaletteCastMember *)member)->getPaletteId() : 0;
109 	}
110 
111 	return id;
112 }
113 
processImmediateFrameScript(Common::String s,int id)114 bool Score::processImmediateFrameScript(Common::String s, int id) {
115 	s.trim();
116 
117 	// In D2/D3 this specifies immediately the sprite/field properties
118 	if (!s.compareToIgnoreCase("moveableSprite") || !s.compareToIgnoreCase("editableText")) {
119 		_immediateActions[id] = true;
120 	}
121 
122 	return false;
123 }
124 
getLabel(Common::String & label)125 uint16 Score::getLabel(Common::String &label) {
126 	if (!_labels) {
127 		warning("Score::getLabel: No labels set");
128 		return 0;
129 	}
130 
131 	for (Common::SortedArray<Label *>::iterator i = _labels->begin(); i != _labels->end(); ++i) {
132 		if ((*i)->name.equalsIgnoreCase(label)) {
133 			return (*i)->number;
134 		}
135 	}
136 
137 	return 0;
138 }
139 
getLabelList()140 Common::String *Score::getLabelList() {
141 	Common::String *res = new Common::String;
142 
143 	for (Common::SortedArray<Label *>::iterator i = _labels->begin(); i != _labels->end(); ++i) {
144 		*res += (*i)->name;
145 		*res += '\n';
146 	}
147 
148 	return res;
149 }
150 
getFrameLabel(uint id)151 Common::String *Score::getFrameLabel(uint id) {
152 	for (Common::SortedArray<Label *>::iterator i = _labels->begin(); i != _labels->end(); ++i) {
153 		if ((*i)->number == id) {
154 			return new Common::String((*i)->name);
155 			break;
156 		}
157 	}
158 
159 	return new Common::String;
160 }
161 
setStartToLabel(Common::String & label)162 void Score::setStartToLabel(Common::String &label) {
163 	uint16 num = getLabel(label);
164 
165 	if (num == 0)
166 		warning("Label %s not found", label.c_str());
167 	else
168 		_nextFrame = num;
169 }
170 
gotoLoop()171 void Score::gotoLoop() {
172 	// This command has the playback head contonuously return to the first marker to to the left and then loop back.
173 	// If no marker are to the left of the playback head, the playback head continues to the right.
174 	if (_labels == NULL) {
175 		_nextFrame = 1;
176 		return;
177 	} else {
178 		_nextFrame = _currentLabel;
179 	}
180 
181 	_vm->_skipFrameAdvance = true;
182 }
183 
getCurrentLabelNumber()184 int Score::getCurrentLabelNumber() {
185 	Common::SortedArray<Label *>::iterator i;
186 
187 	if (!_labels)
188 		return 0;
189 
190 	int frame = 0;
191 
192 	for (i = _labels->begin(); i != _labels->end(); ++i) {
193 		if ((*i)->number <= _currentFrame)
194 			frame = (*i)->number;
195 	}
196 
197 	return frame;
198 }
199 
gotoNext()200 void Score::gotoNext() {
201 	// we can just try to use the current frame and get the next label
202 	_nextFrame = getNextLabelNumber(_currentFrame);
203 }
204 
gotoPrevious()205 void Score::gotoPrevious() {
206 	// we actually need the frame of the label prior to the most recent label.
207 	_nextFrame = getPreviousLabelNumber(getCurrentLabelNumber());
208 }
209 
getNextLabelNumber(int referenceFrame)210 int Score::getNextLabelNumber(int referenceFrame) {
211 	if (_labels == NULL || _labels->size() == 0)
212 		return 0;
213 
214 	Common::SortedArray<Label *>::iterator i;
215 
216 	for (i = _labels->begin(); i != _labels->end(); ++i) {
217 		if ((*i)->number >= referenceFrame) {
218 			int n = (*i)->number;
219 			++i;
220 			if (i != _labels->end()) {
221 				// return to the first marker to to the right
222 				return (*i)->number;
223 			} else {
224 				// if no markers are to the right of the playback head,
225 				// the playback head goes to the first marker to the left
226 				return n;
227 			}
228 		}
229 	}
230 
231 	// If there are not markers to the left,
232 	// the playback head goes to frame 1, (Director frame array start from 1, engine from 0)
233 	return 0;
234 }
235 
getPreviousLabelNumber(int referenceFrame)236 int Score::getPreviousLabelNumber(int referenceFrame) {
237 	if (_labels == NULL || _labels->size() == 0)
238 		return 0;
239 
240 	// One label
241 	if (_labels->begin() == _labels->end())
242 		return (*_labels->begin())->number;
243 
244 	Common::SortedArray<Label *>::iterator previous = _labels->begin();
245 	Common::SortedArray<Label *>::iterator i;
246 
247 	for (i = (previous + 1); i != _labels->end(); ++i, ++previous) {
248 		if ((*i)->number >= referenceFrame)
249 			return (*previous)->number;
250 	}
251 
252 	return 0;
253 }
254 
startPlay()255 void Score::startPlay() {
256 	_currentFrame = 1;
257 	_playState = kPlayStarted;
258 	_nextFrameTime = 0;
259 
260 	_lastPalette = _movie->getCast()->_defaultPalette;
261 	_vm->setPalette(resolvePaletteId(_lastPalette));
262 
263 	if (_frames.size() <= 1) {	// We added one empty sprite
264 		warning("Score::startLoop(): Movie has no frames");
265 		_playState = kPlayStopped;
266 	}
267 
268 	// All frames in the same movie have the same number of channels
269 	if (_playState != kPlayStopped)
270 		for (uint i = 0; i < _frames[1]->_sprites.size(); i++)
271 			_channels.push_back(new Channel(_frames[1]->_sprites[i], i));
272 
273 	if (_vm->getVersion() >= 300)
274 		_movie->processEvent(kEventStartMovie);
275 }
276 
step()277 void Score::step() {
278 	if (_playState == kPlayStopped)
279 		return;
280 
281 	if (!_movie->_userEventQueue.empty()) {
282 		_lingo->processEvents(_movie->_userEventQueue);
283 	} else if (_vm->getVersion() >= 300 && !_window->_newMovieStarted && _playState != kPlayStopped) {
284 		_movie->processEvent(kEventIdle);
285 	}
286 
287 	update();
288 
289 	if (debugChannelSet(-1, kDebugFewFramesOnly) || debugChannelSet(-1, kDebugScreenshot)) {
290 		warning("Score::startLoop(): ran frame %0d", _framesRan);
291 		_framesRan++;
292 	}
293 
294 	if (debugChannelSet(-1, kDebugFewFramesOnly) && _framesRan > 9) {
295 		warning("Score::startLoop(): exiting due to debug few frames only");
296 		_playState = kPlayStopped;
297 		return;
298 	}
299 
300 	if (debugChannelSet(-1, kDebugScreenshot))
301 		screenShot();
302 }
303 
stopPlay()304 void Score::stopPlay() {
305 	if (_vm->getVersion() >= 300)
306 		_movie->processEvent(kEventStopMovie);
307 	_lingo->executePerFrameHook(-1, 0);
308 }
309 
update()310 void Score::update() {
311 	if (_activeFade) {
312 		if (!_soundManager->fadeChannel(_activeFade))
313 			_activeFade = 0;
314 	}
315 
316 	if (!debugChannelSet(-1, kDebugFast)) {
317 		bool keepWaiting = false;
318 
319 		if (_waitForChannel) {
320 			if (_soundManager->isChannelActive(_waitForChannel)) {
321 				keepWaiting = true;
322 			} else {
323 				_waitForChannel = 0;
324 			}
325 		} else if (_waitForClick) {
326 			if (g_system->getMillis() >= _nextFrameTime + 1000) {
327 				_waitForClickCursor = !_waitForClickCursor;
328 				renderCursor(_movie->getWindow()->getMousePos());
329 				_nextFrameTime = g_system->getMillis();
330 			}
331 			keepWaiting = true;
332 		} else if (g_system->getMillis() < _nextFrameTime && !_nextFrame) {
333 			keepWaiting = true;
334 		}
335 
336 		if (keepWaiting) {
337 			if (_movie->_videoPlayback) {
338 				updateWidgets(true);
339 				_window->render();
340 			}
341 			return;
342 		}
343 	}
344 
345 	// For previous frame
346 	if (!_window->_newMovieStarted && !_vm->_playbackPaused) {
347 		// When Lingo::func_goto* is called, _nextFrame is set
348 		// and _skipFrameAdvance is set to true.
349 		// exitFrame is not called in this case.
350 		if (!_vm->_skipFrameAdvance && _vm->getVersion() >= 400) {
351 			_movie->processEvent(kEventExitFrame);
352 		}
353 
354 		// If there is a transition, the perFrameHook is called
355 		// after each transition subframe instead.
356 		if (_frames[_currentFrame]->_transType == 0) {
357 			_lingo->executePerFrameHook(_currentFrame, 0);
358 		}
359 	}
360 
361 	_vm->_skipFrameAdvance = false;
362 
363 	// the exitFrame event handler may have stopped this movie
364 	if (_playState == kPlayStopped)
365 		return;
366 
367 	if (!_vm->_playbackPaused) {
368 		if (_nextFrame)
369 			_currentFrame = _nextFrame;
370 		else if (!_window->_newMovieStarted)
371 			_currentFrame++;
372 	}
373 
374 	_nextFrame = 0;
375 
376 	if (_currentFrame >= _frames.size()) {
377 		Window *window = _vm->getCurrentWindow();
378 		if (!window->_movieStack.empty()) {
379 			MovieReference ref = window->_movieStack.back();
380 			window->_movieStack.pop_back();
381 			if (!ref.movie.empty()) {
382 				_playState = kPlayStopped;
383 				window->setNextMovie(ref.movie);
384 				window->_nextMovie.frameI = ref.frameI;
385 				return;
386 			}
387 
388 			_currentFrame = ref.frameI;
389 		} else {
390 			if (debugChannelSet(-1, kDebugNoLoop)) {
391 				_playState = kPlayStopped;
392 				return;
393 			}
394 
395 			_currentFrame = 1;
396 		}
397 	}
398 
399 	Common::SortedArray<Label *>::iterator i;
400 	if (_labels != NULL) {
401 		for (i = _labels->begin(); i != _labels->end(); ++i) {
402 			if ((*i)->number == _currentFrame) {
403 				_currentLabel = _currentFrame;
404 			}
405 		}
406 	}
407 
408 	debugC(1, kDebugImages, "******************************  Current frame: %d", _currentFrame);
409 
410 	uint initialCallStackSize = _window->_callstack.size();
411 
412 	_lingo->executeImmediateScripts(_frames[_currentFrame]);
413 
414 	if (_vm->getVersion() >= 600) {
415 		// _movie->processEvent(kEventBeginSprite);
416 		// TODO Director 6 step: send beginSprite event to any sprites whose span begin in the upcoming frame
417 		// _movie->processEvent(kEventPrepareFrame);
418 		// TODO: Director 6 step: send prepareFrame event to all sprites and the script channel in upcoming frame
419 	}
420 
421 	// Window is drawn between the prepareFrame and enterFrame events (Lingo in a Nutshell, p.100)
422 	renderFrame(_currentFrame);
423 	_window->_newMovieStarted = false;
424 
425 	// Enter and exit from previous frame
426 	if (!_vm->_playbackPaused) {
427 		_movie->processEvent(kEventEnterFrame); // Triggers the frame script in D2-3, explicit enterFrame handlers in D4+
428 		if ((_vm->getVersion() >= 300 && _vm->getVersion() < 400) || _movie->_allowOutdatedLingo) {
429 			// Movie version of enterFrame, for D3 only. The D3 Interactivity Manual claims
430 			// "This handler executes before anything else when the playback head moves."
431 			// but this is incorrect. The frame script is executed first.
432 			_movie->processEvent(kEventStepMovie);
433 		}
434 		if (_movie->_timeOutPlay)
435 			_movie->_lastTimeOut = _vm->getMacTicks();
436 	}
437 	// TODO Director 6 - another order
438 
439 	// TODO: Figure out when exactly timeout events are processed
440 	if (_vm->getMacTicks() - _movie->_lastTimeOut >= _movie->_timeOutLength) {
441 		_movie->processEvent(kEventTimeout);
442 		_movie->_lastTimeOut = _vm->getMacTicks();
443 	}
444 
445 	// If we have more call stack frames than we started with, then we have a newly
446 	// added frozen context. We'll deal with that later.
447 	if (_window->_callstack.size() == initialCallStackSize) {
448 		// We may have a frozen Lingo context from func_goto.
449 		// Now that we've entered a new frame, let's unfreeze that context.
450 		if (g_lingo->_freezeContext) {
451 			debugC(1, kDebugLingoExec, "Score::update(): Unfreezing Lingo context");
452 			g_lingo->_freezeContext = false;
453 			g_lingo->execute();
454 		}
455 	}
456 
457 	byte tempo = _frames[_currentFrame]->_tempo;
458 	if (tempo) {
459 		_puppetTempo = 0;
460 	} else if (_puppetTempo) {
461 		tempo = _puppetTempo;
462 	}
463 
464 	if (tempo) {
465 		if (tempo > 161) {
466 			// Delay
467 			_nextFrameTime = g_system->getMillis() + (256 - tempo) * 1000;
468 		} else if (tempo <= 120) {
469 			// FPS
470 			_currentFrameRate = tempo;
471 			_nextFrameTime = g_system->getMillis() + 1000.0 / (float)_currentFrameRate;
472 		} else {
473 			if (tempo == 128) {
474 				_waitForClick = true;
475 				_waitForClickCursor = false;
476 				renderCursor(_movie->getWindow()->getMousePos());
477 			} else if (tempo == 135) {
478 				// Wait for sound channel 1
479 				_waitForChannel = 1;
480 			} else if (tempo == 134) {
481 				// Wait for sound channel 2
482 				_waitForChannel = 2;
483 			} else {
484 				warning("STUB: tempo %d", tempo);
485 			}
486 			_nextFrameTime = g_system->getMillis();
487 		}
488 	} else {
489 		_nextFrameTime = g_system->getMillis() + 1000.0 / (float)_currentFrameRate;
490 	}
491 
492 	if (debugChannelSet(-1, kDebugSlow))
493 		_nextFrameTime += 1000;
494 }
495 
renderFrame(uint16 frameId,RenderMode mode)496 void Score::renderFrame(uint16 frameId, RenderMode mode) {
497 	// Force cursor update if a new movie's started.
498 	if (_window->_newMovieStarted)
499 		renderCursor(_movie->getWindow()->getMousePos(), true);
500 
501 	if (!renderTransition(frameId))
502 		renderSprites(frameId, mode);
503 
504 	int currentPalette = _frames[frameId]->_palette.paletteId;
505 	if (!_puppetPalette && currentPalette != _lastPalette && currentPalette) {
506 		_lastPalette = currentPalette;
507 		g_director->setPalette(resolvePaletteId(currentPalette));
508 	}
509 
510 	_window->render();
511 
512 	playSoundChannel(frameId);
513 	playQueuedSound(); // this is currently only used in FPlayXObj
514 
515 	if (_cursorDirty) {
516 		renderCursor(_movie->getWindow()->getMousePos());
517 		_cursorDirty = false;
518 	}
519 }
520 
renderTransition(uint16 frameId)521 bool Score::renderTransition(uint16 frameId) {
522 	Frame *currentFrame = _frames[frameId];
523 	TransParams *tp = _window->_puppetTransition;
524 
525 	if (tp) {
526 		_window->playTransition(tp->duration, tp->area, tp->chunkSize, tp->type, frameId);
527 
528 		delete _window->_puppetTransition;
529 		_window->_puppetTransition = nullptr;
530 		return true;
531 	} else if (currentFrame->_transType) {
532 		_window->playTransition(currentFrame->_transDuration, currentFrame->_transArea, currentFrame->_transChunkSize, currentFrame->_transType, frameId);
533 		return true;
534 	} else {
535 		return false;
536  }
537 }
538 
renderSprites(uint16 frameId,RenderMode mode)539 void Score::renderSprites(uint16 frameId, RenderMode mode) {
540 	if (_window->_newMovieStarted)
541 		mode = kRenderForceUpdate;
542 
543 	_movie->_videoPlayback = false;
544 
545 	for (uint16 i = 0; i < _channels.size(); i++) {
546 		Channel *channel = _channels[i];
547 		Sprite *currentSprite = channel->_sprite;
548 		Sprite *nextSprite = _frames[frameId]->_sprites[i];
549 
550 		// widget content has changed and needs a redraw.
551 		// this doesn't include changes in dimension or position!
552 		bool widgetRedrawn = channel->updateWidget();
553 
554 		if (channel->isActiveVideo()) {
555 			channel->updateVideoTime();
556 			_movie->_videoPlayback = true;
557 		}
558 
559 		if (channel->isDirty(nextSprite) || widgetRedrawn || mode == kRenderForceUpdate) {
560 			if (!currentSprite->_trails)
561 				_window->addDirtyRect(channel->getBbox());
562 
563 			channel->setClean(nextSprite, i);
564 			// Check again to see if a video has just been started by setClean.
565 			if (channel->isActiveVideo())
566 				_movie->_videoPlayback = true;
567 
568 			_window->addDirtyRect(channel->getBbox());
569 			debugC(2, kDebugImages, "Score::renderSprites(): CH: %-3d castId: %s [ink: %d, puppet: %d, moveable: %d, visible: %d] [bbox: %d,%d,%d,%d] [type: %d fg: %d bg: %d] [script: %s]", i, currentSprite->_castId.asString().c_str(), currentSprite->_ink, currentSprite->_puppet, currentSprite->_moveable, channel->_visible, PRINT_RECT(channel->getBbox()), currentSprite->_spriteType, currentSprite->_foreColor, currentSprite->_backColor, currentSprite->_scriptId.asString().c_str());
570 		} else {
571 			channel->setClean(nextSprite, i, true);
572 		}
573 
574 		// update editable text channel after we render the sprites. because for the current frame, we may get those sprites only when we finished rendering
575 		// (because we are creating widgets and setting active state when we rendering sprites)
576 		if (channel->isActiveText())
577 			_movie->_currentEditableTextChannel = i;
578 	}
579 }
580 
renderCursor(Common::Point pos,bool forceUpdate)581 void Score::renderCursor(Common::Point pos, bool forceUpdate) {
582 	if (_window != _vm->getCursorWindow()) {
583 		// The cursor is outside of this window.
584 		return;
585 	}
586 
587 	if (_waitForClick) {
588 		_vm->setCursor(_waitForClickCursor ? kCursorMouseDown : kCursorMouseUp);
589 		return;
590 	}
591 
592 	if (!_channels.empty() && _playState != kPlayStopped) {
593 		uint spriteId = 0;
594 
595 		for (int i = _channels.size() - 1; i >= 0; i--)
596 			if (_channels[i]->isMouseIn(pos) && !_channels[i]->_cursor.isEmpty()) {
597 				spriteId = i;
598 				break;
599 			}
600 
601 		if (!_channels[spriteId]->_cursor.isEmpty()) {
602 			if (!forceUpdate && _currentCursor == _channels[spriteId]->_cursor)
603 				return;
604 
605 			// try to use the cursor read from exe file.
606 			// currently, we are using mac arrow to represent custom win cursor since we didn't find where it stores. So i comment it out here.
607 //			if (g_director->getPlatform() == Common::kPlatformWindows && _channels[spriteId]->_cursor._cursorType == Graphics::kMacCursorCustom)
608 //				_vm->_wm->replaceCursor(_channels[spriteId]->_cursor._cursorType, g_director->_winCursor[_channels[spriteId]->_cursor._cursorResId]);
609 			_vm->_wm->replaceCursor(_channels[spriteId]->_cursor._cursorType, &_channels[spriteId]->_cursor);
610 			_currentCursor = _channels[spriteId]->_cursor.getRef();
611 			return;
612 		}
613 	}
614 
615 	if (!forceUpdate && _currentCursor == _defaultCursor)
616 		return;
617 
618 	_vm->_wm->replaceCursor(_defaultCursor._cursorType, &_defaultCursor);
619 	_currentCursor = _defaultCursor.getRef();
620 }
621 
updateWidgets(bool hasVideoPlayback)622 void Score::updateWidgets(bool hasVideoPlayback) {
623 	for (uint16 i = 0; i < _channels.size(); i++) {
624 		Channel *channel = _channels[i];
625 		CastMember *cast = channel->_sprite->_cast;
626 		if (cast && (cast->_type != kCastDigitalVideo || hasVideoPlayback) && cast->isModified()) {
627 			channel->replaceWidget();
628 			_window->addDirtyRect(channel->getBbox());
629 		}
630 	}
631 }
632 
screenShot()633 void Score::screenShot() {
634 	Graphics::Surface rawSurface = _window->getSurface()->rawSurface();
635 	const Graphics::PixelFormat requiredFormat_4byte(4, 8, 8, 8, 8, 0, 8, 16, 24);
636 	Graphics::Surface *newSurface = rawSurface.convertTo(requiredFormat_4byte, _vm->getPalette());
637 	Common::String currentPath = _vm->getCurrentPath().c_str();
638 	Common::replace(currentPath, Common::String(g_director->_dirSeparator), "-"); // exclude dir separator from screenshot filename prefix
639 	Common::String prefix = Common::String::format("%s%s", currentPath.c_str(), _movie->getMacName().c_str());
640 	Common::String filename = dumpScriptName(prefix.c_str(), kMovieScript, _framesRan, "png");
641 
642 	Common::DumpFile screenshotFile;
643 	if (screenshotFile.open(filename)) {
644 #ifdef USE_PNG
645 		Image::writePNG(screenshotFile, *newSurface);
646 #else
647 		warning("Screenshot requested, but PNG support is not compiled in");
648 #endif
649 	}
650 
651 	newSurface->free();
652 }
653 
getSpriteIDFromPos(Common::Point pos)654 uint16 Score::getSpriteIDFromPos(Common::Point pos) {
655 	for (int i = _channels.size() - 1; i >= 0; i--)
656 		if (_channels[i]->isMouseIn(pos))
657 			return i;
658 
659 	return 0;
660 }
661 
getMouseSpriteIDFromPos(Common::Point pos)662 uint16 Score::getMouseSpriteIDFromPos(Common::Point pos) {
663 	for (int i = _channels.size() - 1; i >= 0; i--)
664 		if (_channels[i]->isMouseIn(pos) && _channels[i]->_sprite->respondsToMouse())
665 			return i;
666 
667 	return 0;
668 }
669 
getActiveSpriteIDFromPos(Common::Point pos)670 uint16 Score::getActiveSpriteIDFromPos(Common::Point pos) {
671 	for (int i = _channels.size() - 1; i >= 0; i--)
672 		if (_channels[i]->isMouseIn(pos) && _channels[i]->_sprite->isActive())
673 			return i;
674 
675 	return 0;
676 }
677 
checkSpriteIntersection(uint16 spriteId,Common::Point pos)678 bool Score::checkSpriteIntersection(uint16 spriteId, Common::Point pos) {
679 	if (_channels[spriteId]->getBbox().contains(pos))
680 		return true;
681 
682 	return false;
683 }
684 
getSpriteIntersections(const Common::Rect & r)685 Common::List<Channel *> Score::getSpriteIntersections(const Common::Rect &r) {
686 	Common::List<Channel *>intersections;
687 
688 	for (uint i = 0; i < _channels.size(); i++) {
689 		if (!_channels[i]->isEmpty() && !r.findIntersectingRect(_channels[i]->getBbox()).isEmpty())
690 			intersections.push_back(_channels[i]);
691 	}
692 
693 	return intersections;
694 }
695 
getSpriteIdByMemberId(CastMemberID id)696 uint16 Score::getSpriteIdByMemberId(CastMemberID id) {
697 	for (uint i = 0; i < _channels.size(); i++)
698 		if (_channels[i]->_sprite->_castId == id)
699 			return i;
700 
701 	return 0;
702 }
703 
getSpriteById(uint16 id)704 Sprite *Score::getSpriteById(uint16 id) {
705 	Channel *channel = getChannelById(id);
706 
707 	if (channel) {
708 		return channel->_sprite;
709 	} else {
710 		warning("Score::getSpriteById(): sprite on frame %d with id %d not found", _currentFrame, id);
711 		return nullptr;
712 	}
713 }
714 
getOriginalSpriteById(uint16 id)715 Sprite *Score::getOriginalSpriteById(uint16 id) {
716 	Frame *frame = _frames[_currentFrame];
717 	if (id < frame->_sprites.size())
718 		return frame->_sprites[id];
719 	warning("Score::getOriginalSpriteById(%d): out of bounds", id);
720 	return nullptr;
721 }
722 
getChannelById(uint16 id)723 Channel *Score::getChannelById(uint16 id) {
724 	if (id >= _channels.size()) {
725 		warning("Score::getChannelById(%d): out of bounds", id);
726 		return nullptr;
727 	}
728 
729 	return _channels[id];
730 }
731 
playSoundChannel(uint16 frameId)732 void Score::playSoundChannel(uint16 frameId) {
733 	Frame *frame = _frames[frameId];
734 
735 	debugC(5, kDebugLoading, "playSoundChannel(): Sound1 %s Sound2 %s", frame->_sound1.asString().c_str(), frame->_sound2.asString().c_str());
736 	DirectorSound *sound = _window->getSoundManager();
737 
738 	if (sound->isChannelPuppet(1)) {
739 		sound->playPuppetSound(1);
740 	} else if (frame->_soundType1 >= kMinSampledMenu && frame->_soundType1 <= kMaxSampledMenu) {
741 		sound->playExternalSound(frame->_soundType1, frame->_sound1.member, 1);
742 	} else {
743 		sound->playCastMember(frame->_sound1, 1);
744 	}
745 
746 	if (sound->isChannelPuppet(2)) {
747 		sound->playPuppetSound(2);
748 	} else if (frame->_soundType2 >= kMinSampledMenu && frame->_soundType2 <= kMaxSampledMenu) {
749 		sound->playExternalSound(frame->_soundType2, frame->_sound2.member, 2);
750 	} else {
751 		sound->playCastMember(frame->_sound2, 2);
752 	}
753 
754 	// Channels above 2 are only usable by Lingo.
755 	if (g_director->getVersion() >= 400) {
756 		sound->playPuppetSound(3);
757 		sound->playPuppetSound(4);
758 	}
759 }
760 
playQueuedSound()761 void Score::playQueuedSound() {
762 	DirectorSound *sound = _window->getSoundManager();
763 	sound->playFPlaySound();
764 }
765 
loadFrames(Common::SeekableReadStreamEndian & stream,uint16 version)766 void Score::loadFrames(Common::SeekableReadStreamEndian &stream, uint16 version) {
767 	debugC(1, kDebugLoading, "****** Loading frames VWSC");
768 
769 	//stream.hexdump(stream.size());
770 
771 	uint32 size = stream.readUint32();
772 	size -= 4;
773 
774 	if (version < kFileVer400) {
775 		_numChannelsDisplayed = 30;
776 	} else if (version >= kFileVer400 && version < kFileVer500) {
777 		uint32 frame1Offset = stream.readUint32();
778 		uint32 numFrames = stream.readUint32();
779 		uint16 framesVersion = stream.readUint16();
780 		uint16 spriteRecordSize = stream.readUint16();
781 		uint16 numChannels = stream.readUint16();
782 		size -= 14;
783 
784 		if (framesVersion > 13) {
785 			_numChannelsDisplayed = stream.readUint16();
786 		} else {
787 			if (framesVersion <= 7)	// Director5
788 				_numChannelsDisplayed = 48;
789 			else
790 				_numChannelsDisplayed = 120;	// D6
791 
792 			stream.readUint16(); // Skip
793 		}
794 
795 		size -= 2;
796 
797 		warning("STUB: Score::loadFrames. frame1Offset: %x numFrames: %x version: %x spriteRecordSize: %x numChannels: %x numChannelsDisplayed: %x",
798 			frame1Offset, numFrames, framesVersion, spriteRecordSize, numChannels, _numChannelsDisplayed);
799 		// Unknown, some bytes - constant (refer to contuinity).
800 	} else if (version >= kFileVer500) {
801 		//what data is up the top of D5 VWSC?
802 		uint32 unk1 = stream.readUint32();
803 		uint32 unk2 = stream.readUint32();
804 
805 		uint16 unk3, unk4, unk5, unk6;
806 
807 		if (unk2 > 0) {
808 			uint32 blockSize = stream.readUint32() - 1;
809 			stream.readUint32();
810 			stream.readUint32();
811 			stream.readUint32();
812 			stream.readUint32();
813 			for (uint32 skip = 0; skip < blockSize * 4; skip++)
814 				stream.readByte();
815 
816 			//header number two... this is our actual score entry point.
817 			unk1 = stream.readUint32();
818 			unk2 = stream.readUint32();
819 			stream.readUint32();
820 			unk3 = stream.readUint16();
821 			unk4 = stream.readUint16();
822 			unk5 = stream.readUint16();
823 			unk6 = stream.readUint16();
824 		} else {
825 			unk3 = stream.readUint16();
826 			unk4 = stream.readUint16();
827 			unk5 = stream.readUint16();
828 			unk6 = stream.readUint16();
829 			size -= 16;
830 		}
831 		warning("STUB: Score::loadFrames. unk1: %x unk2: %x unk3: %x unk4: %x unk5: %x unk6: %x", unk1, unk2, unk3, unk4, unk5, unk6);
832 	}
833 
834 	uint16 channelSize;
835 	uint16 channelOffset;
836 
837 	Frame *initial = new Frame(this, _numChannelsDisplayed);
838 	// Push a frame at frame#0 position.
839 	// This makes all indexing simpler
840 	_frames.push_back(initial);
841 
842 	// This is a representation of the channelData. It gets overridden
843 	// partically by channels, hence we keep it and read the score from left to right
844 	//
845 	// TODO Merge it with shared cast
846 	byte channelData[kChannelDataSize];
847 	memset(channelData, 0, kChannelDataSize);
848 
849 	while (size != 0 && !stream.eos()) {
850 		uint16 frameSize = stream.readUint16();
851 		debugC(3, kDebugLoading, "++++++++++ score frame %d (frameSize %d) size %d", _frames.size(), frameSize, size);
852 
853 		if (frameSize > 0) {
854 			Frame *frame = new Frame(this, _numChannelsDisplayed);
855 			size -= frameSize;
856 			frameSize -= 2;
857 
858 			while (frameSize != 0) {
859 
860 				if (_vm->getVersion() < 400) {
861 					channelSize = stream.readByte() * 2;
862 					channelOffset = stream.readByte() * 2;
863 					frameSize -= channelSize + 2;
864 				} else {
865 					channelSize = stream.readUint16();
866 					channelOffset = stream.readUint16();
867 					frameSize -= channelSize + 4;
868 				}
869 
870 				assert(channelOffset + channelSize < kChannelDataSize);
871 				stream.read(&channelData[channelOffset], channelSize);
872 			}
873 
874 			Common::MemoryReadStreamEndian *str = new Common::MemoryReadStreamEndian(channelData, ARRAYSIZE(channelData), stream.isBE());
875 			// str->hexdump(str->size(), 32);
876 			frame->readChannels(str, version);
877 			delete str;
878 
879 			debugC(8, kDebugLoading, "Score::loadFrames(): Frame %d actionId: %s", _frames.size(), frame->_actionId.asString().c_str());
880 
881 			_frames.push_back(frame);
882 		} else {
883 			warning("zero sized frame!? exiting loop until we know what to do with the tags that follow.");
884 			size = 0;
885 		}
886 	}
887 }
888 
setSpriteCasts()889 void Score::setSpriteCasts() {
890 	// Update sprite cache of cast pointers/info
891 	for (uint16 i = 0; i < _frames.size(); i++) {
892 		for (uint16 j = 0; j < _frames[i]->_sprites.size(); j++) {
893 			_frames[i]->_sprites[j]->setCast(_frames[i]->_sprites[j]->_castId);
894 
895 			debugC(1, kDebugImages, "Score::setSpriteCasts(): Frame: %d Channel: %d castId: %s type: %d", i, j, _frames[i]->_sprites[j]->_castId.asString().c_str(), _frames[i]->_sprites[j]->_spriteType);
896 		}
897 	}
898 }
899 
loadLabels(Common::SeekableReadStreamEndian & stream)900 void Score::loadLabels(Common::SeekableReadStreamEndian &stream) {
901 	if (debugChannelSet(5, kDebugLoading)) {
902 		debug("Score::loadLabels()");
903 		stream.hexdump(stream.size());
904 	}
905 
906 	_labels = new Common::SortedArray<Label *>(compareLabels);
907 	uint16 count = stream.readUint16() + 1;
908 	uint32 offset = count * 4 + 2;
909 
910 	uint16 frame = stream.readUint16();
911 	uint32 stringPos = stream.readUint16() + offset;
912 
913 	for (uint16 i = 1; i < count; i++) {
914 		uint16 nextFrame = stream.readUint16();
915 		uint32 nextStringPos = stream.readUint16() + offset;
916 		uint32 streamPos = stream.pos();
917 
918 		stream.seek(stringPos);
919 		Common::String label;
920 
921 		for (uint32 j = stringPos; j < nextStringPos; j++) {
922 			label += stream.readByte();
923 		}
924 		label = _movie->getCast()->decodeString(label).encode(Common::kUtf8);
925 
926 		_labels->insert(new Label(label, frame));
927 		stream.seek(streamPos);
928 
929 		frame = nextFrame;
930 		stringPos = nextStringPos;
931 	}
932 
933 	Common::SortedArray<Label *>::iterator j;
934 
935 	debugC(2, kDebugLoading, "****** Loading labels");
936 	for (j = _labels->begin(); j != _labels->end(); ++j) {
937 		debugC(2, kDebugLoading, "Frame %d, Label '%s'", (*j)->number, utf8ToPrintable((*j)->name).c_str());
938 	}
939 }
940 
compareLabels(const void * a,const void * b)941 int Score::compareLabels(const void *a, const void *b) {
942 	return ((const Label *)a)->number - ((const Label *)b)->number;
943 }
944 
loadActions(Common::SeekableReadStreamEndian & stream)945 void Score::loadActions(Common::SeekableReadStreamEndian &stream) {
946 	debugC(2, kDebugLoading, "****** Loading Actions VWAC");
947 
948 	uint16 count = stream.readUint16() + 1;
949 	uint32 offset = count * 4 + 2;
950 
951 	byte id = stream.readByte();
952 
953 	byte subId = stream.readByte(); // I couldn't find how it used in continuity (except print). Frame actionId = 1 byte.
954 	uint32 stringPos = stream.readUint16() + offset;
955 
956 	for (uint16 i = 1; i <= count; i++) {
957 		uint16 nextId = stream.readByte();
958 		byte nextSubId = stream.readByte();
959 		uint32 nextStringPos = stream.readUint16() + offset;
960 		uint32 streamPos = stream.pos();
961 
962 		stream.seek(stringPos);
963 
964 		Common::String script = stream.readString(0, nextStringPos - stringPos);
965 		_actions[i] = script.decode(Common::kMacRoman).encode(Common::kUtf8);
966 
967 		debugC(3, kDebugLoading, "Action index: %d id: %d nextId: %d subId: %d, code: %s", i, id, nextId, subId, _actions[i].c_str());
968 
969 		stream.seek(streamPos);
970 
971 		id = nextId;
972 		subId = nextSubId;
973 		stringPos = nextStringPos;
974 
975 		if ((int32)stringPos == stream.size())
976 			break;
977 	}
978 
979 	bool *scriptRefs = (bool *)calloc(_actions.size() + 1, sizeof(bool));
980 
981 	// Now let's scan which scripts are actually referenced
982 	for (uint i = 0; i < _frames.size(); i++) {
983 		if ((uint)_frames[i]->_actionId.member <= _actions.size())
984 			scriptRefs[_frames[i]->_actionId.member] = true;
985 
986 		for (uint16 j = 0; j <= _frames[i]->_numChannels; j++) {
987 			if ((uint)_frames[i]->_sprites[j]->_scriptId.member <= _actions.size())
988 				scriptRefs[_frames[i]->_sprites[j]->_scriptId.member] = true;
989 		}
990 	}
991 
992 	Common::HashMap<uint16, Common::String>::iterator j;
993 
994 	if (ConfMan.getBool("dump_scripts"))
995 		for (j = _actions.begin(); j != _actions.end(); ++j) {
996 			if (!j->_value.empty())
997 				_movie->getCast()->dumpScript(j->_value.c_str(), kScoreScript, j->_key);
998 		}
999 
1000 	for (j = _actions.begin(); j != _actions.end(); ++j) {
1001 		if (!scriptRefs[j->_key]) {
1002 			// Check if it is empty
1003 			bool empty = true;
1004 			Common::U32String u32Script(j->_value);
1005 			for (const Common::u32char_type_t *ptr = u32Script.c_str(); *ptr; ptr++)
1006 				if (!(*ptr == ' ' || *ptr == '-' || *ptr == '\n' || *ptr == '\r' || *ptr == '\t' || *ptr == CONTINUATION)) {
1007 					empty = false;
1008 					break;
1009 				}
1010 
1011 			if (!empty)
1012 				warning("Action id %d is not referenced, the code is:\n-----\n%s\n------", j->_key, j->_value.c_str());
1013 
1014 			continue;
1015 		}
1016 		if (!j->_value.empty()) {
1017 			_movie->getMainLingoArch()->addCode(j->_value, kScoreScript, j->_key);
1018 
1019 			processImmediateFrameScript(j->_value, j->_key);
1020 		}
1021 	}
1022 
1023 	free(scriptRefs);
1024 }
1025 
1026 } // End of namespace Director
1027