From 33b770ac60354b3a8bea89bc5babe3f5c0ebf4dc Mon Sep 17 00:00:00 2001 From: Filippos Karapetis Date: Wed, 15 Oct 2014 11:33:14 +0300 Subject: MADS: Move all the anim and text view code into a common class The animation and text players are more or less common among all MADS games --- engines/mads/menu_views.cpp | 782 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 782 insertions(+) create mode 100644 engines/mads/menu_views.cpp (limited to 'engines/mads/menu_views.cpp') diff --git a/engines/mads/menu_views.cpp b/engines/mads/menu_views.cpp new file mode 100644 index 0000000000..6acf6cdf9f --- /dev/null +++ b/engines/mads/menu_views.cpp @@ -0,0 +1,782 @@ +/* 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/scummsys.h" +#include "mads/game.h" +#include "mads/mads.h" +#include "mads/menu_views.h" +#include "mads/resources.h" +#include "mads/scene.h" +#include "mads/screen.h" + +namespace MADS { + +MenuView::MenuView(MADSEngine *vm) : FullScreenDialog(vm) { + _breakFlag = false; + _redrawFlag = true; + _palFlag = false; +} + +void MenuView::show() { + Scene &scene = _vm->_game->_scene; + EventsManager &events = *_vm->_events; + _vm->_screenFade = SCREEN_FADE_FAST; + + scene._spriteSlots.reset(true); + display(); + + events.setEventTarget(this); + events.hideCursor(); + + while (!_breakFlag && !_vm->shouldQuit()) { + if (_redrawFlag) { + scene._kernelMessages.update(); + + _vm->_game->_scene.drawElements(_vm->_game->_fx, _vm->_game->_fx); + _redrawFlag = false; + } + + _vm->_events->waitForNextFrame(); + _vm->_game->_fx = kTransitionNone; + doFrame(); + } + + events.setEventTarget(nullptr); + _vm->_sound->stop(); +} + +void MenuView::display() { + _vm->_palette->resetGamePalette(4, 8); + + FullScreenDialog::display(); +} + +bool MenuView::onEvent(Common::Event &event) { + if (event.type == Common::EVENT_KEYDOWN || event.type == Common::EVENT_LBUTTONDOWN) { + _breakFlag = true; + _vm->_dialogs->_pendingDialog = DIALOG_MAIN_MENU; + return true; + } + + return false; +} + +/*------------------------------------------------------------------------*/ + +char TextView::_resourceName[100]; +#define TEXTVIEW_LINE_SPACING 2 +#define TEXT_ANIMATION_DELAY 100 +#define TV_NUM_FADE_STEPS 40 +#define TV_FADE_DELAY_MILLI 50 + +void TextView::execute(MADSEngine *vm, const Common::String &resName) { + assert(resName.size() < 100); + Common::strlcpy(_resourceName, resName.c_str(), sizeof(_resourceName)); + vm->_dialogs->_pendingDialog = DIALOG_TEXTVIEW; +} + +TextView::TextView(MADSEngine *vm) : MenuView(vm) { + _animating = false; + _panSpeed = 0; + _spareScreen = nullptr; + _scrollCount = 0; + _lineY = -1; + _scrollTimeout = 0; + _panCountdown = 0; + _translationX = 0; + _screenId = -1; + + _font = _vm->_font->getFont(FONT_CONVERSATION); + _vm->_palette->resetGamePalette(4, 0); + + load(); +} + +TextView::~TextView() { +} + +void TextView::load() { + Common::String scriptName(_resourceName); + scriptName += ".txr"; + + if (!_script.open(scriptName)) + error("Could not open resource %s", _resourceName); + + processLines(); +} + +void TextView::processLines() { + if (_script.eos()) + error("Attempted to read past end of response file"); + + while (!_script.eos()) { + // Read in the next line + _script.readLine(_currentLine, 79); + char *p = _currentLine + strlen(_currentLine) - 1; + if (*p == '\n') + *p = '\0'; + + // Commented out line, so go loop for another + if (_currentLine[0] == '#') + continue; + + // Process the line + char *cStart = strchr(_currentLine, '['); + if (cStart) { + while (cStart) { + // Loop for possible multiple commands on one line + char *cEnd = strchr(_currentLine, ']'); + if (!cEnd) + error("Unterminated command '%s' in response file", _currentLine); + + *cEnd = '\0'; + processCommand(); + + // Copy rest of line (if any) to start of buffer + Common::strlcpy(_currentLine, cEnd + 1, sizeof(_currentLine)); + + cStart = strchr(_currentLine, '['); + } + + if (_currentLine[0]) { + processText(); + break; + } + + } else { + processText(); + break; + } + } +} + +void TextView::processCommand() { + Scene &scene = _vm->_game->_scene; + Common::String scriptLine(_currentLine + 1); + scriptLine.toUppercase(); + const char *paramP; + const char *commandStr = scriptLine.c_str(); + + if (!strncmp(commandStr, "BACKGROUND", 10)) { + // Set the background + paramP = commandStr + 10; + resetPalette(); + int screenId = getParameter(¶mP); + + SceneInfo *sceneInfo = SceneInfo::init(_vm); + sceneInfo->load(screenId, 0, "", 0, scene._depthSurface, scene._backgroundSurface); + scene._spriteSlots.fullRefresh(); + _redrawFlag = true; + + } else if (!strncmp(commandStr, "GO", 2)) { + _animating = true; + + } else if (!strncmp(commandStr, "PAN", 3)) { + // Set panning values + paramP = commandStr + 3; + int panX = getParameter(¶mP); + int panY = getParameter(¶mP); + int panSpeed = getParameter(¶mP); + + if ((panX != 0) || (panY != 0)) { + _pan = Common::Point(panX, panY); + _panSpeed = panSpeed; + } + + } else if (!strncmp(commandStr, "DRIVER", 6)) { + // Set the driver to use + paramP = commandStr + 7; + + if (!strncmp(paramP, "#SOUND.00", 9)) { + int driverNum = paramP[9] - '0'; + _vm->_sound->init(driverNum); + } + } else if (!strncmp(commandStr, "SOUND", 5)) { + // Set sound number + paramP = commandStr + 5; + int soundId = getParameter(¶mP); + _vm->_sound->command(soundId); + + } else if (!strncmp(commandStr, "COLOR", 5) && ((commandStr[5] == '0') || + (commandStr[5] == '1'))) { + // Set the text colors + int index = commandStr[5] - '0'; + paramP = commandStr + 6; + + byte r = getParameter(¶mP); + byte g = getParameter(¶mP); + byte b = getParameter(¶mP); + + _vm->_palette->setEntry(5 + index, r, g, b); + + } else if (!strncmp(commandStr, "SPARE", 5)) { + // Sets a secondary background number that can be later switched in with a PAGE command + paramP = commandStr + 6; + int spareIndex = commandStr[5] - '0'; + assert(spareIndex < 4); + int screenId = getParameter(¶mP); + + // Load the spare background + SceneInfo *sceneInfo = SceneInfo::init(_vm); + sceneInfo->_width = MADS_SCREEN_WIDTH; + sceneInfo->_height = MADS_SCENE_HEIGHT; + _spareScreens[spareIndex].setSize(MADS_SCREEN_WIDTH, MADS_SCENE_HEIGHT); + sceneInfo->loadMadsV1Background(screenId, "", SCENEFLAG_TRANSLATE, + _spareScreens[spareIndex]); + delete sceneInfo; + + } else if (!strncmp(commandStr, "PAGE", 4)) { + // Signals to change to a previous specified secondary background + paramP = commandStr + 4; + int spareIndex = getParameter(¶mP); + + // Only allow background switches if one isn't currently in progress + if (!_spareScreen && _spareScreens[spareIndex].getPixels() != nullptr) { + _spareScreen = &_spareScreens[spareIndex]; + _translationX = 0; + } + + } else { + error("Unknown response command: '%s'", commandStr); + } +} + +int TextView::getParameter(const char **paramP) { + if ((**paramP != '=') && (**paramP != ',')) + return 0; + + int result = 0; + ++*paramP; + while ((**paramP >= '0') && (**paramP <= '9')) { + result = result * 10 + (**paramP - '0'); + ++*paramP; + } + + return result; +} + +void TextView::processText() { + int xStart; + + if (!strcmp(_currentLine, "***")) { + // Special signifier for end of script + _scrollCount = _font->getHeight() * 13; + _lineY = -1; + return; + } + + _lineY = 0; + + // Lines are always centered, except if line contains a '@', in which case the + // '@' marks the position that must be horizontally centered + char *centerP = strchr(_currentLine, '@'); + if (centerP) { + *centerP = '\0'; + xStart = (MADS_SCREEN_WIDTH / 2) - _font->getWidth(_currentLine); + + // Delete the @ character and shift back the remainder of the string + char *p = centerP + 1; + if (*p == ' ') ++p; + strcpy(centerP, p); + + } else { + int lineWidth = _font->getWidth(_currentLine); + xStart = (MADS_SCREEN_WIDTH - lineWidth) / 2; + } + + // Add the new line to the list of pending lines + TextLine tl; + tl._pos = Common::Point(xStart, MADS_SCENE_HEIGHT); + tl._line = _currentLine; + tl._textDisplayIndex = -1; + _textLines.push_back(tl); +} + +void TextView::display() { + FullScreenDialog::display(); +} + +void TextView::resetPalette() { + _vm->_palette->resetGamePalette(8, 8); + _vm->_palette->setEntry(5, 0, 63, 63); + _vm->_palette->setEntry(6, 0, 45, 45); +} + +void TextView::doFrame() { + Scene &scene = _vm->_game->_scene; + if (!_animating) + return; + + // Only update state if wait period has expired + uint32 currTime = g_system->getMillis(); + + // If a screen transition is in progress and it's time for another column, handle it + if (_spareScreen) { + byte *srcP = _spareScreen->getBasePtr(_translationX, 0); + byte *bgP = scene._backgroundSurface.getBasePtr(_translationX, 0); + byte *screenP = (byte *)_vm->_screen.getBasePtr(_translationX, 0); + + for (int y = 0; y < MADS_SCENE_HEIGHT; ++y, srcP += MADS_SCREEN_WIDTH, + bgP += MADS_SCREEN_WIDTH, screenP += MADS_SCREEN_WIDTH) { + *bgP = *srcP; + *screenP = *srcP; + } + + // Flag the column of the screen is modified + _vm->_screen.copyRectToScreen(Common::Rect(_translationX, 0, + _translationX + 1, MADS_SCENE_HEIGHT)); + + // Keep moving the column to copy to the right + if (++_translationX == MADS_SCREEN_WIDTH) { + // Surface transition is complete + _spareScreen = nullptr; + } + } + + // Make sure it's time for an update + if (currTime < _scrollTimeout) + return; + _scrollTimeout = g_system->getMillis() + TEXT_ANIMATION_DELAY; + _redrawFlag = true; + + // If any panning values are set, pan the background surface + if ((_pan.x != 0) || (_pan.y != 0)) { + if (_panCountdown > 0) { + --_panCountdown; + return; + } + + // Handle horizontal panning + if (_pan.x != 0) { + byte *lineTemp = new byte[_pan.x]; + for (int y = 0; y < MADS_SCENE_HEIGHT; ++y) { + byte *pixelsP = (byte *)scene._backgroundSurface.getBasePtr(0, y); + + // Copy the first X pixels into temp buffer, move the rest of the line + // to the start of the line, and then move temp buffer pixels to end of line + Common::copy(pixelsP, pixelsP + _pan.x, lineTemp); + Common::copy(pixelsP + _pan.x, pixelsP + MADS_SCREEN_WIDTH, pixelsP); + Common::copy(lineTemp, lineTemp + _pan.x, pixelsP + MADS_SCREEN_WIDTH - _pan.x); + } + + delete[] lineTemp; + } + + // Handle vertical panning + if (_pan.y != 0) { + // Store the bottom Y lines into a temp buffer, move the rest of the lines down, + // and then copy the stored lines back to the top of the screen + byte *linesTemp = new byte[_pan.y * MADS_SCREEN_WIDTH]; + byte *pixelsP = (byte *)scene._backgroundSurface.getBasePtr(0, MADS_SCENE_HEIGHT - _pan.y); + Common::copy(pixelsP, pixelsP + MADS_SCREEN_WIDTH * _pan.y, linesTemp); + + for (int y = MADS_SCENE_HEIGHT - 1; y >= _pan.y; --y) { + byte *destP = (byte *)scene._backgroundSurface.getBasePtr(0, y); + byte *srcP = (byte *)scene._backgroundSurface.getBasePtr(0, y - _pan.y); + Common::copy(srcP, srcP + MADS_SCREEN_WIDTH, destP); + } + + Common::copy(linesTemp, linesTemp + _pan.y * MADS_SCREEN_WIDTH, + (byte *)scene._backgroundSurface.getPixels()); + delete[] linesTemp; + } + + // Flag for a full screen refresh + scene._spriteSlots.fullRefresh(); + } + + // Scroll all active text lines up + for (int i = _textLines.size() - 1; i >= 0; --i) { + TextLine &tl = _textLines[i]; + if (tl._textDisplayIndex != -1) + // Expire the text line that's already on-screen + scene._textDisplay.expire(tl._textDisplayIndex); + + tl._pos.y--; + if (tl._pos.y < 0) { + _textLines.remove_at(i); + } else { + tl._textDisplayIndex = scene._textDisplay.add(tl._pos.x, tl._pos.y, + 0x605, -1, tl._line, _font); + } + } + + if (_scrollCount > 0) { + // Handling final scrolling of text off of screen + if (--_scrollCount == 0) { + scriptDone(); + return; + } + } else { + // Handling a text row + if (++_lineY == (_font->getHeight() + TEXTVIEW_LINE_SPACING)) + processLines(); + } +} + +void TextView::scriptDone() { + _breakFlag = true; + _vm->_dialogs->_pendingDialog = DIALOG_MAIN_MENU; +} + +/*------------------------------------------------------------------------*/ + +char AnimationView::_resourceName[100]; + +void AnimationView::execute(MADSEngine *vm, const Common::String &resName) { + assert(resName.size() < 100); + Common::strlcpy(_resourceName, resName.c_str(), sizeof(_resourceName)); + vm->_dialogs->_pendingDialog = DIALOG_ANIMVIEW; +} + +AnimationView::AnimationView(MADSEngine *vm) : MenuView(vm) { + _redrawFlag = false; + + _soundDriverLoaded = false; + _previousUpdate = 0; + _screenId = -1; + _resetPalette = false; + _resyncMode = NEVER; + _v1 = 0; + _v2 = -1; + _resourceIndex = -1; + _currentAnimation = nullptr; + _sfx = 0; + _soundFlag = _bgLoadFlag = true; + _showWhiteBars = true; + _manualFrameNumber = 0; + _manualSpriteSet = nullptr; + _manualStartFrame = _manualEndFrame = 0; + _manualFrame2 = 0; + _animFrameNumber = 0; + _nextCyclingActive = false; + _sceneInfo = SceneInfo::init(_vm); + + load(); +} + +AnimationView::~AnimationView() { + delete _currentAnimation; + delete _sceneInfo; +} + +void AnimationView::load() { + Common::String resName(_resourceName); + if (!resName.hasSuffix(".")) + resName += ".res"; + + if (!_script.open(resName)) + error("Could not open resource %s", resName.c_str()); + + processLines(); +} + +void AnimationView::display() { + Scene &scene = _vm->_game->_scene; + _vm->_palette->initPalette(); + Common::fill(&_vm->_palette->_cyclingPalette[0], &_vm->_palette->_cyclingPalette[PALETTE_SIZE], 0); + + _vm->_palette->resetGamePalette(1, 8); + scene._spriteSlots.reset(); + scene._paletteCycles.clear(); + + MenuView::display(); +} + +bool AnimationView::onEvent(Common::Event &event) { + // Wait for the Escape key or a mouse press + if (((event.type == Common::EVENT_KEYDOWN) && (event.kbd.keycode == Common::KEYCODE_ESCAPE)) || + (event.type == Common::EVENT_RBUTTONUP)) { + scriptDone(); + return true; + } + + return false; +} + +void AnimationView::doFrame() { + Scene &scene = _vm->_game->_scene; + + if (_resourceIndex == -1 || _currentAnimation->freeFlag()) { + if (++_resourceIndex == (int)_resources.size()) { + scriptDone(); + } else { + scene._frameStartTime = 0; + loadNextResource(); + } + } else if (_currentAnimation->getCurrentFrame() == 1) { + scene._cyclingActive = _nextCyclingActive; + } + + if (_currentAnimation) { + ++scene._frameStartTime; + _currentAnimation->update(); + _redrawFlag = true; + } +} + +void AnimationView::loadNextResource() { + Scene &scene = _vm->_game->_scene; + Palette &palette = *_vm->_palette; + ResourceEntry &resEntry = _resources[_resourceIndex]; + Common::Array paletteCycles; + + if (resEntry._bgFlag) + palette.resetGamePalette(1, 8); + + palette._mainPalette[253 * 3] = palette._mainPalette[253 * 3 + 1] + = palette._mainPalette[253 * 3 + 2] = 0xb4; + palette.setPalette(&palette._mainPalette[253 * 3], 253, 1); + + // Free any previous messages + scene._kernelMessages.reset(); + + // Handle the bars at the top/bottom + if (resEntry._showWhiteBars) { + // For animations the screen has been clipped to the middle 156 rows. + // So although it's slightly messy, bypass our screen class entirely, + // and draw the horizontal lines directly on the physiacl screen surface + Graphics::Surface *s = g_system->lockScreen(); + s->hLine(0, 20, MADS_SCREEN_WIDTH, 253); + s->hLine(0, 179, MADS_SCREEN_WIDTH, 253); + g_system->unlockScreen(); + } + + // Load the new animation + delete _currentAnimation; + _currentAnimation = Animation::init(_vm, &scene); + int flags = ANIMFLAG_ANIMVIEW | (resEntry._bgFlag ? ANIMFLAG_LOAD_BACKGROUND : 0); + _currentAnimation->load(scene._backgroundSurface, scene._depthSurface, + resEntry._resourceName, flags, &paletteCycles, _sceneInfo); + + // Signal for a screen refresh + scene._spriteSlots.fullRefresh(); + + // If a sound driver has been specified, then load the correct one + if (!_currentAnimation->_header._soundName.empty()) { + const char *chP = strchr(_currentAnimation->_header._soundName.c_str(), '.'); + assert(chP); + + // Handle both Rex naming (xxx.009) and naming in later games (e.g. xxx.ph9) + int driverNum = atoi(chP + 3); + // HACK for Dragon + if (_currentAnimation->_header._soundName == "#SOUND.DRG") + driverNum = 9; + _vm->_sound->init(driverNum); + } + + // Handle any manual setup + if (_currentAnimation->_header._manualFlag) { + _manualFrameNumber = _currentAnimation->_header._spritesIndex; + _manualSpriteSet = _currentAnimation->getSpriteSet(_manualFrameNumber); + } + + // Set the sound data for the animation + _vm->_sound->setEnabled(resEntry._soundFlag); + + Common::String dsrName = _currentAnimation->_header._dsrName; + if (!dsrName.empty()) + _vm->_audio->setSoundGroup(dsrName); + + // Start the new animation + _currentAnimation->startAnimation(0); + + // Handle the palette and cycling palette + scene._cyclingActive = false; + Common::copy(&palette._mainPalette[0], &palette._mainPalette[PALETTE_SIZE], + &palette._cyclingPalette[0]); + + _vm->_game->_fx = (ScreenTransition)resEntry._fx; + _nextCyclingActive = paletteCycles.size() > 0; + if (!_vm->_game->_fx) { + palette.setFullPalette(palette._mainPalette); + } + + scene.initPaletteAnimation(paletteCycles, _nextCyclingActive && !_vm->_game->_fx); +} + +void AnimationView::scriptDone() { + _breakFlag = true; + _vm->_dialogs->_pendingDialog = DIALOG_MAIN_MENU; +} + +void AnimationView::processLines() { + if (_script.eos()) { + // end of script, end animation + scriptDone(); + return; + } + + while (!_script.eos()) { + // Get in next line + _currentLine.clear(); + char c; + while (!_script.eos() && (c = _script.readByte()) != '\n') { + if (c != '\r' && c != '\0') + _currentLine += c; + } + + // Process the line + while (!_currentLine.empty()) { + if (_currentLine.hasPrefix("-")) { + _currentLine.deleteChar(0); + + processCommand(); + } else { + // Get resource name + Common::String resName; + while (!_currentLine.empty() && (c = _currentLine[0]) != ' ') { + _currentLine.deleteChar(0); + resName += c; + } + + // Add resource into list along with any set state information + _resources.push_back(ResourceEntry(resName, _sfx, _soundFlag, + _bgLoadFlag, _showWhiteBars)); + + // Fx resets between resource entries + _sfx = 0; + } + + // Skip any spaces + while (_currentLine.hasPrefix(" ")) + _currentLine.deleteChar(0); + } + } +} + +void AnimationView::processCommand() { + // Get the command character + char commandChar = toupper(_currentLine[0]); + _currentLine.deleteChar(0); + + // Handle the command + switch (commandChar) { + case 'B': + _soundFlag = !_soundFlag; + break; + case 'H': + // -h[:ex] Disable EMS / XMS high memory support + if (_currentLine.hasPrefix(":")) + _currentLine.deleteChar(0); + while (_currentLine.hasPrefix("e") || _currentLine.hasPrefix("x")) + _currentLine.deleteChar(0); + break; + case 'O': + // -o:xxx Specify opening special effect + assert(_currentLine[0] == ':'); + _currentLine.deleteChar(0); + _sfx = getParameter(); + break; + case 'P': + // Switch to CONCAT mode, which is ignored anyway + break; + case 'R': { + // Resynch timer (always, beginning, never) + assert(_currentLine[0] == ':'); + _currentLine.deleteChar(0); + + char v = toupper(_currentLine[0]); + _currentLine.deleteChar(0); + if (v == 'N') + _resyncMode = NEVER; + else if (v == 'A') + _resyncMode = ALWAYS; + else if (v == 'B') + _resyncMode = BEGINNING; + else + error("Unknown parameter"); + break; + } + case 'W': + // Switch white bars being visible + _showWhiteBars = !_showWhiteBars; + break; + case 'X': + // Exit after animation finishes. Ignore + break; + case 'D': + // Unimplemented and ignored in the original. Ignore as well + break; + case 'Y': + // Reset palette on startup + _resetPalette = true; + break; + default: + error("Unknown command char: '%c'", commandChar); + } +} + +int AnimationView::getParameter() { + int result = 0; + + while (!_currentLine.empty()) { + char c = _currentLine[0]; + + if (c >= '0' && c <= '9') { + _currentLine.deleteChar(0); + result = result * 10 + (c - '0'); + } else { + break; + } + } + + return result; +} + +void AnimationView::checkResource(const Common::String &resourceName) { + //bool hasSuffix = false; + +} + +int AnimationView::scanResourceIndex(const Common::String &resourceName) { + int foundIndex = -1; + + if (_v1) { + const char *chP = strchr(resourceName.c_str(), '\\'); + if (!chP) { + chP = strchr(resourceName.c_str(), '*'); + } + + Common::String resName = chP ? Common::String(chP + 1) : resourceName; + + if (_v2 != 3) { + assert(_resIndex.size() == 0); + } + + // Scan for the resource name + for (uint resIndex = 0; resIndex < _resIndex.size(); ++resIndex) { + ResIndexEntry &resEntry = _resIndex[resIndex]; + if (resEntry._resourceName.compareToIgnoreCase(resourceName)) { + foundIndex = resIndex; + break; + } + } + } + + if (foundIndex >= 0) { + // TODO + } + return -1; +} + +} // End of namespace MADS -- cgit v1.2.3