/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #define FORBIDDEN_SYMBOL_EXCEPTION_getenv #include "common/config-manager.h" #include "common/file.h" #include "common/rational.h" #include "common/memstream.h" #include "common/punycode.h" #include "common/substream.h" #include "audio/audiostream.h" #include "graphics/macgui/mactext.h" #ifdef USE_PNG #include "image/png.h" #endif #include "director/director.h" #include "director/debugger.h" #include "director/cast.h" #include "director/frame.h" #include "director/score.h" #include "director/movie.h" #include "director/sound.h" #include "director/channel.h" #include "director/sprite.h" #include "director/window.h" #include "director/castmember/castmember.h" #include "director/castmember/filmloop.h" #include "director/castmember/transition.h" namespace Director { #include "director/palette-fade.h" Score::Score(Movie *movie) { _movie = movie; _window = movie->getWindow(); _vm = _movie->getVM(); _lingo = _vm->getLingo(); _soundManager = _window->getSoundManager(); _puppetTempo = 0; _puppetPalette = false; _paletteTransitionIndex = 0; memset(_paletteSnapshotBuffer, 0, 768); _labels = nullptr; _currentFrameRate = 20; _nextFrame = 0; _currentLabel = 0; _nextFrameTime = 0; _nextFrameDelay = 0; _lastTempo = 0; _waitForChannel = 0; _waitForChannelCue = 0; _waitForVideoChannel = 0; _cursorDirty = false; _waitForClick = false; _waitForClickCursor = false; _activeFade = false; _exitFrameCalled = false; _stopPlayCalled = false; _playState = kPlayNotStarted; _numChannelsDisplayed = 0; _skipTransition = false; _curFrameNumber = 1; _framesStream = nullptr; _currentFrame = nullptr; _disableGoPlayUpdateStage = false; } Score::~Score() { for (uint i = 0; i < _channels.size(); i++) delete _channels[i]; if (_labels) for (auto &it : *_labels) delete it; delete _labels; for (auto &it : _scoreCache) delete it; if (_framesStream) delete _framesStream; if (_currentFrame) { delete _currentFrame; } } void Score::setPuppetTempo(int16 puppetTempo) { _puppetTempo = puppetTempo; } CastMemberID Score::getCurrentPalette() { return g_director->_lastPalette; } bool Score::processImmediateFrameScript(Common::String s, int id) { s.trim(); // In D2/D3 this specifies immediately the sprite/field properties if (!s.compareToIgnoreCase("moveableSprite") || !s.compareToIgnoreCase("editableText")) { _immediateActions[id] = true; } return false; } bool Score::processFrozenPlayScript() { // Unfreeze the play script if the special flag is set if (g_lingo->_playDone) { g_lingo->_playDone = false; if (_window->thawLingoPlayState()) { Symbol currentScript; LingoState *state = _window->getLingoState(); if (state && !state->callstack.empty()) currentScript = state->callstack.front()->sp; g_lingo->switchStateFromWindow(); bool completed = g_lingo->execute(); if (!completed) { debugC(3, kDebugLingoExec, "Score::processFrozenPlayScript(): State froze again mid-thaw, interrupting"); return false; } else if (currentScript == g_lingo->_currentInputEvent) { // script that just completed was the current input event, clear the flag debugC(3, kDebugEvents, "Score::processFrozenPlayScript(): Input event completed"); g_lingo->_currentInputEvent = Symbol(); } } } return true; } bool Score::processFrozenScripts(bool recursion, int count) { if (!processFrozenPlayScript()) return false; // Unfreeze any in-progress scripts and attempt to run them // to completion. bool limit = count != 0; uint32 remainCount = recursion ? _window->frozenLingoRecursionCount() : _window->frozenLingoStateCount(); while (remainCount && (limit ? count > 0 : true)) { _window->thawLingoState(); LingoState *state = _window->getLingoState(); Symbol currentScript; if (state && !state->callstack.empty()) currentScript = state->callstack.front()->sp; g_lingo->switchStateFromWindow(); bool completed = g_lingo->execute(); if (!completed || (recursion ? _window->frozenLingoRecursionCount() : _window->frozenLingoStateCount()) >= remainCount) { debugC(3, kDebugLingoExec, "Score::processFrozenScripts(): State froze again mid-thaw, interrupting"); // Workaround for if a state gets moved to to the play state if (currentScript == g_lingo->_currentInputEvent && state == _window->getLingoPlayState()) { debugC(3, kDebugEvents, "Score::processFrozenScripts(): Input event got moved to the play state, clearing block"); g_lingo->_currentInputEvent = Symbol(); } return false; } else if (currentScript == g_lingo->_currentInputEvent) { // script that just completed was the current input event, clear the flag debugC(3, kDebugEvents, "Score::processFrozenScripts(): Input event completed"); g_lingo->_currentInputEvent = Symbol(); } remainCount = recursion ? _window->frozenLingoRecursionCount() : _window->frozenLingoStateCount(); count -= 1; } return true; } uint16 Score::getLabel(Common::String &label) { if (!_labels) { warning("Score::getLabel: No labels set"); return 0; } for (auto &i : *_labels) { if (i->name.equalsIgnoreCase(label)) { return i->number; } } return 0; } Common::String *Score::getLabelList() { Common::String *res = new Common::String; for (auto &i : *_labels) { *res += i->name; *res += '\r'; } return res; } Common::String *Score::getFrameLabel(uint id) { for (auto &i : *_labels) { if (i->number == id) { return new Common::String(i->name); break; } } return new Common::String; } void Score::setStartToLabel(Common::String &label) { uint16 num = getLabel(label); if (num == 0) warning("Label %s not found", label.c_str()); else _nextFrame = num; } void Score::gotoLoop() { // This command has the playback head continuously return to the first marker to the left and then loop back. // If no marker are to the left of the playback head, the playback head continues to the right. if (_labels == nullptr) { _nextFrame = 1; return; } else { _nextFrame = _currentLabel; } _window->_skipFrameAdvance = true; } int Score::getCurrentLabelNumber() { if (!_labels) return 0; int frame = 0; for (auto &i : *_labels) { if (i->number <= _curFrameNumber) frame = i->number; } return frame; } void Score::gotoNext() { // we can just try to use the current frame and get the next label _nextFrame = getNextLabelNumber(_curFrameNumber); } void Score::gotoPrevious() { // we actually need the frame of the label prior to the most recent label. _nextFrame = getPreviousLabelNumber(getCurrentLabelNumber()); } int Score::getNextLabelNumber(int referenceFrame) { if (_labels == nullptr || _labels->size() == 0) return 0; for (auto &it : *_labels) { if (it->number > referenceFrame) { return it->number; } } // if no markers are to the right of the playback head, // return the last marker return _labels->back()->number; } int Score::getPreviousLabelNumber(int referenceFrame) { if (_labels == nullptr || _labels->size() == 0) return 0; // One label if (_labels->begin() == _labels->end()) return (*_labels->begin())->number; Common::SortedArray