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