Logo Search packages:      
Sourcecode: scummvm version File versions  Download package

screen.cpp

/* 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.
 *
 * Additional copyright for this file:
 * Copyright (C) 1994-1998 Revolution Software Ltd.
 *
 * 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.
 *
 * $URL: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/tags/release-0-11-1/engines/sword2/screen.cpp $
 * $Id: screen.cpp 30944 2008-02-23 22:50:18Z sev $
 */

// ---------------------------------------------------------------------------
// BUILD_DISPLAY.CPP    like the old spr_engi but slightly more aptly named
// ---------------------------------------------------------------------------


#include "common/system.h"
#include "common/events.h"

#include "sword2/sword2.h"
#include "sword2/defs.h"
#include "sword2/header.h"
#include "sword2/console.h"
#include "sword2/logic.h"
#include "sword2/maketext.h"
#include "sword2/mouse.h"
#include "sword2/object.h"
#include "sword2/resman.h"
#include "sword2/screen.h"
#include "sword2/sound.h"

namespace Sword2 {

Screen::Screen(Sword2Engine *vm, int16 width, int16 height) {
      _vm = vm;

      _dirtyGrid = _buffer = NULL;

      _vm->_system->initSize(width, height);

      _screenWide = width;
      _screenDeep = height;

      _gridWide = width / CELLWIDE;
      _gridDeep = height / CELLDEEP;

      if ((width % CELLWIDE) || (height % CELLDEEP))
            error("Bad cell size");

      _dirtyGrid = (byte *)calloc(_gridWide, _gridDeep);
      if (!_dirtyGrid)
            error("Could not initialise dirty grid");

      _buffer = (byte *)malloc(width * height);
      if (!_buffer)
            error("Could not initialise display");

      for (int i = 0; i < ARRAYSIZE(_blockSurfaces); i++)
            _blockSurfaces[i] = NULL;

      _lightMask = NULL;
      _needFullRedraw = false;

      memset(&_thisScreen, 0, sizeof(_thisScreen));

      _fps = 0;
      _frameCount = 0;
      _cycleTime = 0;

      _lastPaletteRes = 0;

      _scrollFraction = 16;

      _largestLayerArea = 0;
      _largestSpriteArea = 0;

      strcpy(_largestLayerInfo,  "largest layer:  none registered");
      strcpy(_largestSpriteInfo, "largest sprite: none registered");

      _fadeStatus = RDFADE_NONE;
      _renderAverageTime = 60;

      _layer = 0;
}

Screen::~Screen() {
      free(_buffer);
      free(_dirtyGrid);
      closeBackgroundLayer();
      free(_lightMask);
}

/**
 * @return the graphics detail setting
 */

int8 Screen::getRenderLevel() {
      return _renderLevel;
}

void Screen::setRenderLevel(int8 level) {
      _renderLevel = level;

      switch (_renderLevel) {
      case 0:
            // Lowest setting: no fancy stuff
            _renderCaps = 0;
            break;
      case 1:
            // Medium-low setting: transparency-blending
            _renderCaps = RDBLTFX_SPRITEBLEND;
            break;
      case 2:
            // Medium-high setting: transparency-blending + shading
            _renderCaps = RDBLTFX_SPRITEBLEND | RDBLTFX_SHADOWBLEND;
            break;
      case 3:
            // Highest setting: transparency-blending + shading +
            // edge-blending + improved stretching
            _renderCaps = RDBLTFX_SPRITEBLEND | RDBLTFX_SHADOWBLEND | RDBLTFX_EDGEBLEND;
            break;
      }
}

/**
 * Tell updateDisplay() that the scene needs to be completely updated.
 */

void Screen::setNeedFullRedraw() {
      _needFullRedraw = true;
}

/**
 * Mark an area of the screen as dirty, first generation.
 */

void Screen::markAsDirty(int16 x0, int16 y0, int16 x1, int16 y1) {
      int16 gridX0 = x0 / CELLWIDE;
      int16 gridY0 = y0 / CELLDEEP;
      int16 gridX1 = x1 / CELLWIDE;
      int16 gridY1 = y1 / CELLDEEP;

      for (int16 i = gridY0; i <= gridY1; i++)
            for (int16 j = gridX0; j <= gridX1; j++)
                  _dirtyGrid[i * _gridWide + j] = 2;
}

/**
 * This function has two purposes: It redraws the scene, and it handles input
 * events, palette fading, etc. It should be called at a high rate (> 20 per
 * second), but the scene is usually only redrawn about 12 times per second,
 * except when then screen is scrolling.
 *
 * @param redrawScene If true, redraw the scene.
 */

void Screen::updateDisplay(bool redrawScene) {
      _vm->parseInputEvents();
      fadeServer();

      if (redrawScene) {
            int i;

            // Note that the entire scene is always rendered, which is less
            // than optimal, but at least we can try to be intelligent
            // about updating the screen afterwards.

            if (_needFullRedraw) {
                  // Update the entire screen. This is necessary when
                  // scrolling, fading, etc.

                  _vm->_system->copyRectToScreen(_buffer + MENUDEEP * _screenWide, _screenWide, 0, MENUDEEP, _screenWide, _screenDeep - 2 * MENUDEEP);
                  _needFullRedraw = false;
            } else {
                  // Update only the dirty areas of the screen

                  int j, x, y;
                  int stripWide;

                  for (i = 0; i < _gridDeep; i++) {
                        stripWide = 0;

                        for (j = 0; j < _gridWide; j++) {
                              if (_dirtyGrid[i * _gridWide + j]) {
                                    stripWide++;
                              } else if (stripWide) {
                                    x = CELLWIDE * (j - stripWide);
                                    y = CELLDEEP * i;
                                    _vm->_system->copyRectToScreen(_buffer + y * _screenWide + x, _screenWide, x, y, stripWide * CELLWIDE, CELLDEEP);
                                    stripWide = 0;
                              }
                        }

                        if (stripWide) {
                              x = CELLWIDE * (j - stripWide);
                              y = CELLDEEP * i;
                              _vm->_system->copyRectToScreen(_buffer + y * _screenWide + x, _screenWide, x, y, stripWide * CELLWIDE, CELLDEEP);
                              stripWide = 0;
                        }
                  }
            }

            // Age the dirty cells one generation. This way we keep track
            // of both the cells that were updated this time, and the ones
            // that were updated the last time.

            for (i = 0; i < _gridWide * _gridDeep; i++)
                  _dirtyGrid[i] >>= 1;
      }

      // We always need to update because of fades, menu animations, etc.
      _vm->_system->updateScreen();
}

/**
 * Fill the screen buffer with palette colour zero. Note that it does not
 * touch the menu areas of the screen.
 */

void Screen::clearScene() {
      memset(_buffer + MENUDEEP * _screenWide, 0, _screenWide * RENDERDEEP);
      _needFullRedraw = true;
}

void Screen::buildDisplay() {
      if (_thisScreen.new_palette) {
            // start the layer palette fading up
            startNewPalette();

            // should be reset to zero at start of each screen change
            _largestLayerArea = 0;
            _largestSpriteArea = 0;
      }

      // Does this ever happen?
      if (!_thisScreen.background_layer_id)
            return;

      // there is a valid screen to run

      setScrollTarget(_thisScreen.scroll_offset_x, _thisScreen.scroll_offset_y);
      _vm->_mouse->animateMouse();
      startRenderCycle();

      byte *file = _vm->_resman->openResource(_thisScreen.background_layer_id);

      MultiScreenHeader screenLayerTable;

      screenLayerTable.read(file + ResHeader::size());

      // Render at least one frame, but if the screen is scrolling, and if
      // there is time left, we will render extra frames to smooth out the
      // scrolling.

      do {
            // first background parallax + related anims
            if (screenLayerTable.bg_parallax[0]) {
                  renderParallax(_vm->fetchBackgroundParallaxLayer(file, 0), 0);
                  drawBackPar0Frames();
            }

            // second background parallax + related anims
            if (screenLayerTable.bg_parallax[1]) {
                  renderParallax(_vm->fetchBackgroundParallaxLayer(file, 1), 1);
                  drawBackPar1Frames();
            }

            // normal backround layer (just the one!)
            renderParallax(_vm->fetchBackgroundLayer(file), 2);

            // sprites & layers
            drawBackFrames(); // background sprites
            drawSortFrames(file);   // sorted sprites & layers
            drawForeFrames(); // foreground sprites

            // first foreground parallax + related anims

            if (screenLayerTable.fg_parallax[0]) {
                  renderParallax(_vm->fetchForegroundParallaxLayer(file, 0), 3);
                  drawForePar0Frames();
            }

            // second foreground parallax + related anims

            if (screenLayerTable.fg_parallax[1]) {
                  renderParallax(_vm->fetchForegroundParallaxLayer(file, 1), 4);
                  drawForePar1Frames();
            }

            _vm->_debugger->drawDebugGraphics();
            _vm->_fontRenderer->printTextBlocs();
            _vm->_mouse->processMenu();

            updateDisplay();

            _frameCount++;
            if (_vm->getMillis() > _cycleTime) {
                  _fps = _frameCount;
                  _frameCount = 0;
                  _cycleTime = _vm->getMillis() + 1000;
            }
      } while (!endRenderCycle());

      _vm->_resman->closeResource(_thisScreen.background_layer_id);
}

/**
 * Fades down and displays a message on the screen.
 * @param text The message
 * @param time The number of seconds to display the message, or 0 to display it
 *             until the user clicks the mouse or presses a key.
 */

void Screen::displayMsg(byte *text, int time) {
      byte pal[256 * 4];
      byte oldPal[256 * 4];

      debug(2, "DisplayMsg: %s", text);

      if (getFadeStatus() != RDFADE_BLACK) {
            fadeDown();
            waitForFade();
      }

      _vm->_mouse->setMouse(0);
      _vm->_mouse->setLuggage(0);
      _vm->_mouse->closeMenuImmediately();

      clearScene();

      byte *text_spr = _vm->_fontRenderer->makeTextSprite(text, 640, 187, _vm->_speechFontId);

      FrameHeader frame;

      frame.read(text_spr);

      SpriteInfo spriteInfo;

      spriteInfo.x = _screenWide / 2 - frame.width / 2;
      if (!time)
            spriteInfo.y = _screenDeep / 2 - frame.height / 2 - MENUDEEP;
      else
            spriteInfo.y = 400 - frame.height;
      spriteInfo.w = frame.width;
      spriteInfo.h = frame.height;
      spriteInfo.scale = 0;
      spriteInfo.scaledWidth = 0;
      spriteInfo.scaledHeight = 0;
      spriteInfo.type = RDSPR_DISPLAYALIGN | RDSPR_NOCOMPRESSION | RDSPR_TRANS;
      spriteInfo.blend = 0;
      spriteInfo.data = text_spr + FrameHeader::size();
      spriteInfo.colourTable = 0;

      uint32 rv = drawSprite(&spriteInfo);
      if (rv)
            error("Driver Error %.8x (in DisplayMsg)", rv);

      memcpy(oldPal, _palette, sizeof(oldPal));
      memset(pal, 0, sizeof(pal));

      pal[187 * 4 + 0] = 255;
      pal[187 * 4 + 1] = 255;
      pal[187 * 4 + 2] = 255;

      setPalette(0, 256, pal, RDPAL_FADE);
      fadeUp();
      free(text_spr);
      waitForFade();

      if (time > 0) {
            uint32 targetTime = _vm->getMillis() + (time * 1000);
            _vm->sleepUntil(targetTime);
      } else {
            while (!_vm->_quit) {
                  MouseEvent *me = _vm->mouseEvent();
                  if (me && (me->buttons & (RD_LEFTBUTTONDOWN | RD_RIGHTBUTTONDOWN)))
                        break;

                  if (_vm->keyboardEvent())
                        break;

                  updateDisplay();
                  _vm->_system->delayMillis(50);
            }
      }

      fadeDown();
      waitForFade();
      clearScene();
      setPalette(0, 256, oldPal, RDPAL_FADE);
      fadeUp();
}

void Screen::drawBackPar0Frames() {
      // frame attached to 1st background parallax
      for (uint i = 0; i < _curBgp0; i++)
            processImage(&_bgp0List[i]);
}

void Screen::drawBackPar1Frames() {
      // frame attached to 2nd background parallax
      for (uint i = 0; i < _curBgp1; i++)
            processImage(&_bgp1List[i]);
}

void Screen::drawBackFrames() {
      // background sprite, fixed to main background
      for (uint i = 0; i < _curBack; i++)
            processImage(&_backList[i]);
}

void Screen::drawSortFrames(byte *file) {
      uint i, j;

      // Sort the sort list. Used to be a separate function, but it was only
      // called once, right before calling drawSortFrames().

      if (_curSort > 1) {
            for (i = 0; i < _curSort - 1; i++) {
                  for (j = 0; j < _curSort - 1; j++) {
                        if (_sortList[_sortOrder[j]].sort_y > _sortList[_sortOrder[j + 1]].sort_y) {
                              SWAP(_sortOrder[j], _sortOrder[j + 1]);
                        }
                  }
            }
      }

      // Draw the sorted frames - layers, shrinkers & normal flat sprites

      for (i = 0; i < _curSort; i++) {
            if (_sortList[_sortOrder[i]].layer_number) {
                  // it's a layer - minus 1 for true layer number
                  // we need to know from the BuildUnit because the
                  // layers will have been sorted in random order
                  processLayer(file, _sortList[_sortOrder[i]].layer_number - 1);
            } else {
                  // it's a sprite
                  processImage(&_sortList[_sortOrder[i]]);
            }
      }
}

void Screen::drawForeFrames() {
      // foreground sprite, fixed to main background
      for (uint i = 0; i < _curFore; i++)
            processImage(&_foreList[i]);
}

void Screen::drawForePar0Frames() {
      // frame attached to 1st foreground parallax
      for (uint i = 0; i < _curFgp0; i++)
            processImage(&_fgp0List[i]);
}

void Screen::drawForePar1Frames() {
      // frame attached to 2nd foreground parallax
      for (uint i = 0; i < _curFgp1; i++)
            processImage(&_fgp1List[i]);
}

void Screen::processLayer(byte *file, uint32 layer_number) {
      LayerHeader layer_head;

      layer_head.read(_vm->fetchLayerHeader(file, layer_number));

      SpriteInfo spriteInfo;

      spriteInfo.x = layer_head.x;
      spriteInfo.y = layer_head.y;
      spriteInfo.w = layer_head.width;
      spriteInfo.scale = 0;
      spriteInfo.scaledWidth = 0;
      spriteInfo.scaledHeight = 0;
      spriteInfo.h = layer_head.height;
      spriteInfo.type = RDSPR_TRANS | RDSPR_RLE256FAST;
      spriteInfo.blend = 0;
      spriteInfo.data = file + ResHeader::size() + layer_head.offset;
      spriteInfo.colourTable = 0;

      // check for largest layer for debug info

      uint32 current_layer_area = layer_head.width * layer_head.height;

      if (current_layer_area > _largestLayerArea) {
            _largestLayerArea = current_layer_area;
            sprintf(_largestLayerInfo,
                  "largest layer:  %s layer(%d) is %dx%d",
                  _vm->_resman->fetchName(_thisScreen.background_layer_id),
                  layer_number, layer_head.width, layer_head.height);
      }

      uint32 rv = drawSprite(&spriteInfo);
      if (rv)
            error("Driver Error %.8x in processLayer(%d)", rv, layer_number);
}

void Screen::processImage(BuildUnit *build_unit) {
      byte *file = _vm->_resman->openResource(build_unit->anim_resource);
      byte *colTablePtr = NULL;

      byte *frame = _vm->fetchFrameHeader(file, build_unit->anim_pc);

      AnimHeader anim_head;
      CdtEntry cdt_entry;
      FrameHeader frame_head;

      anim_head.read(_vm->fetchAnimHeader(file));
      cdt_entry.read(_vm->fetchCdtEntry(file, build_unit->anim_pc));
      frame_head.read(frame);

      // so that 0-colour is transparent
      uint32 spriteType = RDSPR_TRANS;

      if (anim_head.blend)
            spriteType |= RDSPR_BLEND;

      // if the frame is to be flipped (only really applicable to frames
      // using offsets)
      if (cdt_entry.frameType & FRAME_FLIPPED)
            spriteType |= RDSPR_FLIP;

      if (cdt_entry.frameType & FRAME_256_FAST) {
            // scaling, shading & blending don't work with RLE256FAST
            // but the same compression can be decompressed using the
            // RLE256 routines!

            // NOTE: If this restriction refers to drawSprite(), I don't
            // think we have it any more. But I'm not sure.

            if (build_unit->scale || anim_head.blend || build_unit->shadingFlag)
                  spriteType |= RDSPR_RLE256;
            else
                  spriteType |= RDSPR_RLE256FAST;
      } else {
            switch (anim_head.runTimeComp) {
            case NONE:
                  spriteType |= RDSPR_NOCOMPRESSION;
                  break;
            case RLE256:
                  spriteType |= RDSPR_RLE256;
                  break;
            case RLE16:
                  spriteType |= RDSPR_RLE16;
                  // points to just after last cdt_entry, ie.
                  // start of colour table
                  colTablePtr = _vm->fetchAnimHeader(file) + AnimHeader::size() + anim_head.noAnimFrames * CdtEntry::size();
                  break;
            }
      }

      // if we want this frame to be affected by the shading mask,
      // add the status bit
      if (build_unit->shadingFlag)
            spriteType |= RDSPR_SHADOW;

      SpriteInfo spriteInfo;

      spriteInfo.x = build_unit->x;
      spriteInfo.y = build_unit->y;
      spriteInfo.w = frame_head.width;
      spriteInfo.h = frame_head.height;
      spriteInfo.scale = build_unit->scale;
      spriteInfo.scaledWidth = build_unit->scaled_width;
      spriteInfo.scaledHeight = build_unit->scaled_height;
      spriteInfo.type = spriteType;
      spriteInfo.blend = anim_head.blend;
      // points to just after frame header, ie. start of sprite data
      spriteInfo.data = frame + FrameHeader::size();
      spriteInfo.colourTable = colTablePtr;

      // check for largest layer for debug info
      uint32 current_sprite_area = frame_head.width * frame_head.height;

      if (current_sprite_area > _largestSpriteArea) {
            _largestSpriteArea = current_sprite_area;
            sprintf(_largestSpriteInfo,
                  "largest sprite: %s frame(%d) is %dx%d",
                  _vm->_resman->fetchName(build_unit->anim_resource),
                  build_unit->anim_pc,
                  frame_head.width,
                  frame_head.height);
      }

      if (_vm->_logic->readVar(SYSTEM_TESTING_ANIMS)) { // see anims.cpp
            // bring the anim into the visible screen
            // but leave extra pixel at edge for box
            if (spriteInfo.x + spriteInfo.scaledWidth >= 639)
                  spriteInfo.x = 639 - spriteInfo.scaledWidth;

            if (spriteInfo.y + spriteInfo.scaledHeight >= 399)
                  spriteInfo.y = 399 - spriteInfo.scaledHeight;

            if (spriteInfo.x < 1)
                  spriteInfo.x = 1;

            if (spriteInfo.y < 1)
                  spriteInfo.y = 1;

            // create box to surround sprite - just outside sprite box
            _vm->_debugger->_rectX1 = spriteInfo.x - 1;
            _vm->_debugger->_rectY1 = spriteInfo.y - 1;
            _vm->_debugger->_rectX2 = spriteInfo.x + spriteInfo.scaledWidth;
            _vm->_debugger->_rectY2 = spriteInfo.y + spriteInfo.scaledHeight;
      }

      uint32 rv = drawSprite(&spriteInfo);
      if (rv) {
            error("Driver Error %.8x with sprite %s (%d) in processImage",
                  rv,
                  _vm->_resman->fetchName(build_unit->anim_resource),
                  build_unit->anim_resource);
      }

      // release the anim resource
      _vm->_resman->closeResource(build_unit->anim_resource);
}

void Screen::resetRenderLists() {
      // reset the sort lists - do this before a logic loop
      // takes into account the fact that the start of the list is pre-built
      // with the special sortable layers

      _curBgp0 = 0;
      _curBgp1 = 0;
      _curBack = 0;
      // beginning of sort list is setup with the special sort layers
      _curSort = _thisScreen.number_of_layers;
      _curFore = 0;
      _curFgp0 = 0;
      _curFgp1 = 0;

      if (_curSort) {
            // there are some layers - so rebuild the sort order list
            for (uint i = 0; i < _curSort; i++)
                  _sortOrder[i] = i;
      }
}

void Screen::registerFrame(byte *ob_mouse, byte *ob_graph, byte *ob_mega, BuildUnit *build_unit) {
      ObjectGraphic obGraph(ob_graph);
      ObjectMega obMega(ob_mega);

      assert(obGraph.getAnimResource());

      byte *file = _vm->_resman->openResource(obGraph.getAnimResource());

      AnimHeader anim_head;
      CdtEntry cdt_entry;
      FrameHeader frame_head;

      anim_head.read(_vm->fetchAnimHeader(file));
      cdt_entry.read(_vm->fetchCdtEntry(file, obGraph.getAnimPc()));
      frame_head.read(_vm->fetchFrameHeader(file, obGraph.getAnimPc()));

      // update player graphic details for on-screen debug info
      if (_vm->_logic->readVar(ID) == CUR_PLAYER_ID) {
            _vm->_debugger->_graphType = obGraph.getType();
            _vm->_debugger->_graphAnimRes = obGraph.getAnimResource();
            // counting 1st frame as 'frame 1'
            _vm->_debugger->_graphAnimPc = obGraph.getAnimPc() + 1;
            _vm->_debugger->_graphNoFrames = anim_head.noAnimFrames;
      }

      // fill in the BuildUnit structure for this frame

      build_unit->anim_resource = obGraph.getAnimResource();
      build_unit->anim_pc = obGraph.getAnimPc();
      build_unit->layer_number = 0;

      // Affected by shading mask?
      if (obGraph.getType() & SHADED_SPRITE)
            build_unit->shadingFlag = true;
      else
            build_unit->shadingFlag = false;

      // Check if this frame has offsets ie. this is a scalable mega frame

      int scale = 0;

      if (cdt_entry.frameType & FRAME_OFFSET) {
            scale = obMega.calcScale();

            // calc final render coordinates (top-left of sprite), based
            // on feet coords & scaled offsets

            // add scaled offsets to feet coords
            build_unit->x = obMega.getFeetX() + (cdt_entry.x * scale) / 256;
            build_unit->y = obMega.getFeetY() + (cdt_entry.y * scale) / 256;

            // Work out new width and height. Always divide by 256 after
            // everything else, to maintain accurary
            build_unit->scaled_width = ((scale * frame_head.width) / 256);
            build_unit->scaled_height = ((scale * frame_head.height) / 256);
      } else {
            // It's a non-scaling anim. Get render coords for sprite, from cdt
            build_unit->x = cdt_entry.x;
            build_unit->y = cdt_entry.y;

            // Get width and height
            build_unit->scaled_width = frame_head.width;
            build_unit->scaled_height = frame_head.height;
      }

      // either 0 or required scale, depending on whether 'scale' computed
      build_unit->scale = scale;

      // calc the bottom y-coord for sorting purposes
      build_unit->sort_y = build_unit->y + build_unit->scaled_height - 1;

      if (ob_mouse) {
            // passed a mouse structure, so add to the _mouseList
            _vm->_mouse->registerMouse(ob_mouse, build_unit);

      }

      _vm->_resman->closeResource(obGraph.getAnimResource());
}

void Screen::registerFrame(byte *ob_mouse, byte *ob_graph, byte *ob_mega) {
      ObjectGraphic obGraph(ob_graph);

      // check low word for sprite type
      switch (obGraph.getType() & 0x0000ffff) {
      case BGP0_SPRITE:
            assert(_curBgp0 < MAX_bgp0_sprites);
            registerFrame(ob_mouse, ob_graph, ob_mega, &_bgp0List[_curBgp0]);
            _curBgp0++;
            break;
      case BGP1_SPRITE:
            assert(_curBgp1 < MAX_bgp1_sprites);
            registerFrame(ob_mouse, ob_graph, ob_mega, &_bgp1List[_curBgp1]);
            _curBgp1++;
            break;
      case BACK_SPRITE:
            assert(_curBack < MAX_back_sprites);
            registerFrame(ob_mouse, ob_graph, ob_mega, &_backList[_curBack]);
            _curBack++;
            break;
      case SORT_SPRITE:
            assert(_curSort < MAX_sort_sprites);
            _sortOrder[_curSort] = _curSort;
            registerFrame(ob_mouse, ob_graph, ob_mega, &_sortList[_curSort]);
            _curSort++;
            break;
      case FORE_SPRITE:
            assert(_curFore < MAX_fore_sprites);
            registerFrame(ob_mouse, ob_graph, ob_mega, &_foreList[_curFore]);
            _curFore++;
            break;
      case FGP0_SPRITE:
            assert(_curFgp0 < MAX_fgp0_sprites);
            registerFrame(ob_mouse, ob_graph, ob_mega, &_fgp0List[_curFgp0]);
            _curFgp0++;
            break;
      case FGP1_SPRITE:
            assert(_curFgp1 < MAX_fgp1_sprites);
            registerFrame(ob_mouse, ob_graph, ob_mega, &_fgp1List[_curFgp1]);
            _curFgp1++;
            break;
      default:
            // NO_SPRITE no registering!
            break;
      }
}

// FIXME:
//
// The original credits used a different font. I think it's stored in the
// font.clu file, but I don't know how to interpret it.
//
// The original used the entire screen. This version cuts off the top and
// bottom of the screen, because that's where the menus would usually be.
//
// The original had some sort of smoke effect at the bottom of the screen.

enum {
      LINE_LEFT,
      LINE_CENTER,
      LINE_RIGHT
};

struct CreditsLine {
      char *str;
      byte type;
      int top;
      int height;
      byte *sprite;

      CreditsLine() {
            str = NULL;
            sprite = NULL;
      }

      ~CreditsLine() {
            free(str);
            free(sprite);
            str = NULL;
            sprite = NULL;
      }
};

#define CREDITS_FONT_HEIGHT 25
#define CREDITS_LINE_SPACING 20

void Screen::rollCredits() {
      uint32 loopingMusicId = _vm->_sound->getLoopingMusicId();

      // Prepare for the credits by fading down, stoping the music, etc.

      _vm->_mouse->setMouse(0);

      _vm->_sound->muteFx(true);
      _vm->_sound->muteSpeech(true);

      waitForFade();
      fadeDown();
      waitForFade();

      _vm->_mouse->closeMenuImmediately();

      // There are three files which I believe are involved in showing the
      // credits:
      //
      // credits.bmp  - The "Smacker" logo, stored as follows:
      //
      //     width     2 bytes, little endian
      //     height    2 bytes, little endian
      //     palette   3 * 256 bytes
      //     data      width * height bytes
      //
      //     Note that the maximum colour component in the palette is 0x3F.
      //     This is the same resolution as the _paletteMatch table. I doubt
      //     that this is a coincidence, but let's use the image palette
      //     directly anyway, just to be safe.
      //
      // credits.clu  - The credits text
      //
      //     This is simply a text file with CRLF line endings.
      //     '^' is not shown, but used to mark the center of the line.
      //     '@' is used as a placeholder for the "Smacker" logo. At least
      //     when it appears alone.
      //     Remaining lines are centered.
      //     The German version also contains character code 9 for no
      //     apparent reason. We ignore them.
      //
      // fonts.clu    - The credits font?
      //
      //     FIXME: At this time I don't know how to interpret fonts.clu. For
      //     now, let's just the standard speech font instead.

      SpriteInfo spriteInfo;
      Common::File f;
      int i;

      // Read the "Smacker" logo

      uint16 logoWidth = 0;
      uint16 logoHeight = 0;
      byte *logoData = NULL;
      byte palette[256 * 4];

      if (f.open("credits.bmp")) {
            logoWidth = f.readUint16LE();
            logoHeight = f.readUint16LE();

            for (i = 0; i < 256; i++) {
                  palette[i * 4 + 0] = f.readByte() << 2;
                  palette[i * 4 + 1] = f.readByte() << 2;
                  palette[i * 4 + 2] = f.readByte() << 2;
                  palette[i * 4 + 3] = 0;
            }

            logoData = (byte *)malloc(logoWidth * logoHeight);

            f.read(logoData, logoWidth * logoHeight);
            f.close();
      } else {
            warning("Can't find credits.bmp");
            memset(palette, 0, sizeof(palette));
            palette[14 * 4 + 0] = 252;
            palette[14 * 4 + 1] = 252;
            palette[14 * 4 + 2] = 252;
            palette[14 * 4 + 3] = 0;
      }

      setPalette(0, 256, palette, RDPAL_INSTANT);

      // Read the credits text

      Common::Array<CreditsLine *> creditsLines;

      int lineCount = 0;
      int lineTop = 400;
      int paragraphStart = 0;
      bool hasCenterMark = false;

      if (!f.open("credits.clu")) {
            warning("Can't find credits.clu");
            return;
      }

      while (1) {
            char buffer[80];
            char *line = f.readLine(buffer, sizeof(buffer));

            if (!line || *line == 0) {
                  if (!hasCenterMark) {
                        for (i = paragraphStart; i < lineCount; i++)
                              creditsLines[i]->type = LINE_CENTER;
                  }
                  paragraphStart = lineCount;
                  hasCenterMark = false;
                  if (paragraphStart == lineCount)
                        lineTop += CREDITS_LINE_SPACING;

                  if (!line)
                        break;

                  continue;
            }

            // Replace invalid character codes to avoid the credits to show
            // the 'dud' symbol.

            for (byte *ptr = (byte *)line; *ptr; ptr++) {
                  // The German credits contains character code 9. We
                  // replace them with spaces.
                  if (*ptr == 9)
                        *ptr = 32;

                  // The Spanish credits contains character code 170. We
                  // replace them with dots.
                  if (*ptr == 170)
                        *ptr = '.';
            }

            char *center_mark = strchr(line, '^');

            if (center_mark) {
                  // The current paragraph has at least one center mark.
                  hasCenterMark = true;

                  if (center_mark != line) {
                        creditsLines.push_back(new CreditsLine);

                        // The center mark is somewhere inside the
                        // line. Split it into left and right side.
                        *center_mark = 0;

                        creditsLines[lineCount]->top = lineTop;
                        creditsLines[lineCount]->height = CREDITS_FONT_HEIGHT;
                        creditsLines[lineCount]->type = LINE_LEFT;
                        creditsLines[lineCount]->str = strdup(line);

                        lineCount++;
                        *center_mark = '^';
                  }

                  line = center_mark;
            }

            creditsLines.push_back(new CreditsLine);

            creditsLines[lineCount]->top = lineTop;

            if (*line == '^') {
                  creditsLines[lineCount]->type = LINE_RIGHT;
                  line++;
            } else
                  creditsLines[lineCount]->type = LINE_LEFT;

            if (strcmp(line, "@") == 0) {
                  creditsLines[lineCount]->height = logoHeight;
                  lineTop += logoHeight;
            } else {
                  creditsLines[lineCount]->height = CREDITS_FONT_HEIGHT;
                  lineTop += CREDITS_LINE_SPACING;
            }

            creditsLines[lineCount]->str = strdup(line);
            lineCount++;
      }

      f.close();

      // We could easily add some ScummVM stuff to the credits, if we wanted
      // to. On the other hand, anyone with the attention span to actually
      // read all the credits probably already knows. :-)

      // Start the music and roll the credits

      // The credits music (which can also be heard briefly in the "carib"
      // cutscene) is played once.

      _vm->_sound->streamCompMusic(309, false);

      clearScene();
      fadeUp(0);

      spriteInfo.scale = 0;
      spriteInfo.scaledWidth = 0;
      spriteInfo.scaledHeight = 0;
      spriteInfo.type = RDSPR_DISPLAYALIGN | RDSPR_NOCOMPRESSION | RDSPR_TRANS;
      spriteInfo.blend = 0;

      int startLine = 0;
      int scrollPos = 0;

      bool abortCredits = false;

      int scrollSteps = lineTop + CREDITS_FONT_HEIGHT;
      uint32 musicStart = _vm->getMillis();

      // Ideally the music should last just a tiny bit longer than the
      // credits. Note that musicTimeRemaining() will return 0 if the music
      // is muted, so we need a sensible fallback for that case.

      uint32 musicLength = MAX((int32)(1000 * (_vm->_sound->musicTimeRemaining() - 3)), 25 * (int32)scrollSteps);

      while (scrollPos < scrollSteps && !_vm->_quit) {
            clearScene();

            for (i = startLine; i < lineCount; i++) {
                  if (!creditsLines[i])
                        continue;

                  // Free any sprites that have scrolled off the screen

                  if (creditsLines[i]->top + creditsLines[i]->height < scrollPos) {
                        debug(2, "Freeing line %d: '%s'", i, creditsLines[i]->str);

                        delete creditsLines[i];
                        creditsLines[i] = NULL;

                        startLine = i + 1;
                  } else if (creditsLines[i]->top < scrollPos + 400) {
                        if (!creditsLines[i]->sprite) {
                              debug(2, "Creating line %d: '%s'", i, creditsLines[i]->str);
                              creditsLines[i]->sprite = _vm->_fontRenderer->makeTextSprite((byte *)creditsLines[i]->str, 600, 14, _vm->_speechFontId, 0);
                        }

                        FrameHeader frame;

                        frame.read(creditsLines[i]->sprite);

                        spriteInfo.y = creditsLines[i]->top - scrollPos;
                        spriteInfo.w = frame.width;
                        spriteInfo.h = frame.height;
                        spriteInfo.data = creditsLines[i]->sprite + FrameHeader::size();

                        switch (creditsLines[i]->type) {
                        case LINE_LEFT:
                              spriteInfo.x = RENDERWIDE / 2 - 5 - frame.width;
                              break;
                        case LINE_RIGHT:
                              spriteInfo.x = RENDERWIDE / 2 + 5;
                              break;
                        case LINE_CENTER:
                              if (strcmp(creditsLines[i]->str, "@") == 0) {
                                    spriteInfo.data = logoData;
                                    spriteInfo.x = (RENDERWIDE - logoWidth) / 2;
                                    spriteInfo.w = logoWidth;
                                    spriteInfo.h = logoHeight;
                              } else
                                    spriteInfo.x = (RENDERWIDE - frame.width) / 2;
                              break;
                        }

                        if (spriteInfo.data)
                              drawSprite(&spriteInfo);
                  } else
                        break;
            }

            updateDisplay();

            KeyboardEvent *ke = _vm->keyboardEvent();

            if (ke && ke->kbd.keycode == Common::KEYCODE_ESCAPE) {
                  if (!abortCredits) {
                        abortCredits = true;
                        fadeDown();
                  }
            }

            if (abortCredits && getFadeStatus() == RDFADE_BLACK)
                  break;

            _vm->sleepUntil(musicStart + (musicLength * scrollPos) / scrollSteps);
            scrollPos++;
      }

      // We're done. Clean up and try to put everything back where it was
      // before the credits.

      for (i = 0; i < lineCount; i++) {
            delete creditsLines[i];
      }

      free(logoData);

      if (!abortCredits) {
            fadeDown();

            // The music should either have stopped or be about to stop, so
            // wait for it to really happen.

            while (_vm->_sound->musicTimeRemaining() && !_vm->_quit) {
                  updateDisplay(false);
                  _vm->_system->delayMillis(100);
            }
      }

      if (_vm->_quit)
            return;

      waitForFade();

      _vm->_sound->muteFx(false);
      _vm->_sound->muteSpeech(false);

      if (loopingMusicId)
            _vm->_sound->streamCompMusic(loopingMusicId, true);
      else
            _vm->_sound->stopMusic(false);

      if (!_vm->_mouse->getMouseStatus() || _vm->_mouse->isChoosing())
            _vm->_mouse->setMouse(NORMAL_MOUSE_ID);

      if (_vm->_logic->readVar(DEAD))
            _vm->_mouse->buildSystemMenu();
}

// This image used to be shown by CacheNewCluster() while copying a data file
// from the CD to the hard disk. ScummVM doesn't do that, so the image is never
// shown. It'd be nice if we could do something useful with it some day...

void Screen::splashScreen() {
      byte *bgfile = _vm->_resman->openResource(2950);

      initialiseBackgroundLayer(NULL);
      initialiseBackgroundLayer(NULL);
      initialiseBackgroundLayer(_vm->fetchBackgroundLayer(bgfile));
      initialiseBackgroundLayer(NULL);
      initialiseBackgroundLayer(NULL);

      setPalette(0, 256, _vm->fetchPalette(bgfile), RDPAL_FADE);
      renderParallax(_vm->fetchBackgroundLayer(bgfile), 2);

      closeBackgroundLayer();

      byte *loadingBar = _vm->_resman->openResource(2951);
      byte *frame = _vm->fetchFrameHeader(loadingBar, 0);

      AnimHeader animHead;
      CdtEntry cdt;
      FrameHeader frame_head;

      animHead.read(_vm->fetchAnimHeader(loadingBar));
      cdt.read(_vm->fetchCdtEntry(loadingBar, 0));
      frame_head.read(_vm->fetchFrameHeader(loadingBar, 0));

      SpriteInfo barSprite;

      barSprite.x = cdt.x;
      barSprite.y = cdt.y;
      barSprite.w = frame_head.width;
      barSprite.h = frame_head.height;
      barSprite.scale = 0;
      barSprite.scaledWidth = 0;
      barSprite.scaledHeight = 0;
      barSprite.type = RDSPR_RLE256FAST | RDSPR_TRANS;
      barSprite.blend = 0;
      barSprite.colourTable = 0;
      barSprite.data = frame + FrameHeader::size();

      drawSprite(&barSprite);

      fadeUp();
      waitForFade();

      for (int i = 0; i < animHead.noAnimFrames; i++) {
            frame = _vm->fetchFrameHeader(loadingBar, i);
            barSprite.data = frame + FrameHeader::size();
            drawSprite(&barSprite);
            updateDisplay();
            _vm->_system->delayMillis(30);
      }

      _vm->_resman->closeResource(2951);

      fadeDown();
      waitForFade();
}

} // End of namespace Sword2

Generated by  Doxygen 1.6.0   Back to index