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

sprite.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.
 *
 * 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-1-2-1/engines/agi/sprite.cpp $
 * $Id: sprite.cpp 51311 2010-07-26 14:21:59Z sev $
 *
 */

#include "agi/agi.h"
#include "agi/sprite.h"
#include "agi/graphics.h"

namespace Agi {

/**
 * Sprite structure.
 * This structure holds information on visible and priority data of
 * a rectangular area of the AGI screen. Sprites are chained in two
 * circular lists, one for updating and other for non-updating sprites.
 */
00038 struct Sprite {
00039       VtEntry *v;       /**< pointer to view table entry */
00040       int16 xPos;             /**< x coordinate of the sprite */
00041       int16 yPos;             /**< y coordinate of the sprite */
00042       int16 xSize;                  /**< width of the sprite */
00043       int16 ySize;                  /**< height of the sprite */
00044       uint8 *buffer;                /**< buffer to store background data */
};

/*
 * Sprite pool replaces dynamic allocation
 */
#undef ALLOC_DEBUG


#define POOL_SIZE 68000       // Gold Rush mine room needs > 50000
                                          // Speeder bike challenge needs > 67000

void *SpritesMgr::poolAlloc(int size) {
      uint8 *x;

      // Adjust size to sizeof(void *) boundary to prevent data misalignment
      // errors.
      const int alignPadding = sizeof(void*) - 1;
      size = (size + alignPadding) & ~alignPadding;

      x = _poolTop;
      _poolTop += size;

      if (_poolTop >= (uint8 *)_spritePool + POOL_SIZE) {
            debugC(1, kDebugLevelMain | kDebugLevelResources, "not enough memory");
            _poolTop = x;
            return NULL;
      }

      return x;
}

// Note: it's critical that pool_release() is called in the exact
//         reverse order of pool_alloc()
void SpritesMgr::poolRelease(void *s) {
      _poolTop = (uint8 *)s;
}

/*
 * Blitter functions
 */

// Blit one pixel considering the priorities
void SpritesMgr::blitPixel(uint8 *p, uint8 *end, uint8 col, int spr, int width, int *hidden) {
      int epr = 0, pr = 0;    // effective and real priorities

      // CM: priority 15 overrides control lines and is ignored when
      //     tracking effective priority. This tweak is needed to fix
      //     Sarien bug #451768, and should not affect Sierra games because
      //     sprites shouldn't have priority 15 (like the AGI Mouse
      //     demo "mouse pointer")
      //
      // Update: this solution breaks other games, and can't be used.

      if (p >= end)
            return;

      // Check if we're on a control line
      if ((pr = *p & 0xf0) < 0x30) {
            uint8 *p1;
            // Yes, get effective priority going down
            for (p1 = p; p1 < end && (epr = *p1 & 0xf0) < 0x30; p1 += width)
                ;
            if (p1 >= end)
                  epr = 0x40;
      } else {
            epr = pr;
      }

      if (spr >= epr) {
            // Keep control line information visible, but put our
            // priority over water (0x30) surface
            if (_vm->getFeatures() & (GF_AGI256 | GF_AGI256_2))
                  *(p + FROM_SBUF16_TO_SBUF256_OFFSET) = col; // Write to 256 color buffer
            else
                  *p = (pr < 0x30 ? pr : spr) | col; // Write to 16 color (+control line/priority info) buffer

            *hidden = false;

            // Except if our priority is 15, which should never happen
            // (fixes Sarien bug #451768)
            //
            // Update: breaks other games, can't be used
            //
            // if (spr == 0xf0)
            //      *p = spr | col;
      }
}


int SpritesMgr::blitCel(int x, int y, int spr, ViewCel *c, bool agi256_2) {
      uint8 *p0, *p, *q = NULL, *end;
      int i, j, t, m, col;
      int hidden = true;

      // Fixes Sarien bug #477841 (crash in PQ1 map C4 when y == -2)
      if (y < 0)
            y = 0;
      if (x < 0)
            x = 0;
      if (y >= _HEIGHT)
            y = _HEIGHT - 1;
      if (x >= _WIDTH)
            x = _WIDTH - 1;

      q = c->data;
      t = c->transparency;
      m = c->mirror;
      spr <<= 4;
      p0 = &_vm->_game.sbuf16c[x + y * _WIDTH + m * (c->width - 1)];

      end = _vm->_game.sbuf16c + _WIDTH * _HEIGHT;

      for (i = 0; i < c->height; i++) {
            p = p0;
            while (*q) {
                  col = agi256_2 ? *q : (*q & 0xf0) >> 4; // Uses whole byte for color info with AGI256-2
                  for (j = agi256_2 ? 1 : *q & 0x0f; j; j--, p += 1 - 2 * m) { // No RLE with AGI256-2
                        if (col != t) {
                              blitPixel(p, end, col, spr, _WIDTH, &hidden);
                        }
                  }
                  q++;
            }
            p0 += _WIDTH;
            q++;
      }

      return hidden;
}

void SpritesMgr::objsSaveArea(Sprite *s) {
      int y;
      int16 xPos = s->xPos, yPos = s->yPos;
      int16 xSize = s->xSize, ySize = s->ySize;
      uint8 *p0, *q;

      if (xPos + xSize > _WIDTH)
            xSize = _WIDTH - xPos;

      if (xPos < 0) {
            xSize += xPos;
            xPos = 0;
      }

      if (yPos + ySize > _HEIGHT)
            ySize = _HEIGHT - yPos;

      if (yPos < 0) {
            ySize += yPos;
            yPos = 0;
      }

      if (xSize <= 0 || ySize <= 0)
            return;

      p0 = &_vm->_game.sbuf[xPos + yPos * _WIDTH];
      q = s->buffer;
      for (y = 0; y < ySize; y++) {
            memcpy(q, p0, xSize);
            q += xSize;
            p0 += _WIDTH;
      }
}

void SpritesMgr::objsRestoreArea(Sprite *s) {
      int y, offset;
      int16 xPos = s->xPos, yPos = s->yPos;
      int16 xSize = s->xSize, ySize = s->ySize;
      uint8 *q;
      uint32 pos0;

      if (xPos + xSize > _WIDTH)
            xSize = _WIDTH - xPos;

      if (xPos < 0) {
            xSize += xPos;
            xPos = 0;
      }

      if (yPos + ySize > _HEIGHT)
            ySize = _HEIGHT - yPos;

      if (yPos < 0) {
            ySize += yPos;
            yPos = 0;
      }

      if (xSize <= 0 || ySize <= 0)
            return;

      pos0 = xPos + yPos * _WIDTH;
      q = s->buffer;
      offset = _vm->_game.lineMinPrint * CHAR_LINES;
      for (y = 0; y < ySize; y++) {
            memcpy(&_vm->_game.sbuf[pos0], q, xSize);
            _gfx->putPixelsA(xPos, yPos + y + offset, xSize, &_vm->_game.sbuf16c[pos0]);
            q += xSize;
            pos0 += _WIDTH;
      }

      // WORKAROUND (see ScummVM bug #1945716)
      // When set.view command is called, current code cannot detect  this situation while updating
      // Thus we force removal of the old sprite
      if (s->v && s->v->viewReplaced) {
            commitBlock(xPos, yPos, xPos + xSize, yPos + ySize);
            s->v->viewReplaced = false;
      }
}


/**
 * Condition to determine whether a sprite will be in the 'updating' list.
 */
00258 bool SpritesMgr::testUpdating(VtEntry *v, AgiEngine *agi) {
      // Sanity check (see Sarien bug #779302)
      if (~agi->_game.dirView[v->currentView].flags & RES_LOADED)
            return false;

      return (v->flags & (ANIMATED | UPDATE | DRAWN)) == (ANIMATED | UPDATE | DRAWN);
}

/**
 * Condition to determine whether a sprite will be in the 'non-updating' list.
 */
00269 bool SpritesMgr::testNotUpdating(VtEntry *v, AgiEngine *vm) {
      // Sanity check (see Sarien bug #779302)
      if (~vm->_game.dirView[v->currentView].flags & RES_LOADED)
            return false;

      return (v->flags & (ANIMATED | UPDATE | DRAWN)) == (ANIMATED | DRAWN);
}

/**
 * Convert sprite priority to y value.
 */
00280 int SpritesMgr::prioToY(int p) {
      int i;

      if (p == 0)
            return -1;

      for (i = 167; i >= 0; i--) {
            if (_vm->_game.priTable[i] < p)
                  return i;
      }

      return -1;        // (p - 5) * 12 + 48;
}

/**
 * Create and initialize a new sprite structure.
 */
00297 Sprite *SpritesMgr::newSprite(VtEntry *v) {
      Sprite *s;
      s = (Sprite *)poolAlloc(sizeof(Sprite));
      if (s == NULL)
            return NULL;

      s->v = v;         // link sprite to associated view table entry
      s->xPos = v->xPos;
      s->yPos = v->yPos - v->ySize + 1;
      s->xSize = v->xSize;
      s->ySize = v->ySize;
      s->buffer = (uint8 *)poolAlloc(s->xSize * s->ySize);
      v->s = s;         // link view table entry to this sprite

      return s;
}

/**
 * Insert sprite in the specified sprite list.
 */
00317 void SpritesMgr::sprAddlist(SpriteList &l, VtEntry *v) {
      Sprite *s = newSprite(v);
      l.push_back(s);
}

/**
 * Sort sprites from lower y values to build a sprite list.
 */
00325 void SpritesMgr::buildList(SpriteList &l, bool (*test)(VtEntry *, AgiEngine *)) {
      int i, j, k;
      VtEntry *v;
      VtEntry *entry[0x100];
      int yVal[0x100];
      int minY = 0xff, minIndex = 0;

      // fill the arrays with all sprites that satisfy the 'test'
      // condition and their y values
      i = 0;
      for (v = _vm->_game.viewTable; v < &_vm->_game.viewTable[MAX_VIEWTABLE]; v++) {
            if ((*test)(v, _vm)) {
                  entry[i] = v;
                  yVal[i] = v->flags & FIXED_PRIORITY ? prioToY(v->priority) : v->yPos;
                  i++;
            }
      }

      debugC(5, kDebugLevelSprites, "buildList() --> entries %d", i);

      // now look for the smallest y value in the array and put that
      // sprite in the list
      for (j = 0; j < i; j++) {
            minY = 0xff;

            for (k = 0; k < i; k++) {
                  if (yVal[k] < minY) {
                        minIndex = k;
                        minY = yVal[k];
                  }
            }

            yVal[minIndex] = 0xff;
            sprAddlist(l, entry[minIndex]);
      }
}

/**
 * Build list of updating sprites.
 */
00365 void SpritesMgr::buildUpdBlitlist() {
      buildList(_sprUpd, testUpdating);
}

/**
 * Build list of non-updating sprites.
 */
00372 void SpritesMgr::buildNonupdBlitlist() {
      buildList(_sprNonupd, testNotUpdating);
}

/**
 * Clear the given sprite list.
 */
00379 void SpritesMgr::freeList(SpriteList &l) {
      SpriteList::iterator iter;
      for (iter = l.reverse_begin(); iter != l.end(); ) {
            Sprite* s = *iter;

            poolRelease(s->buffer);
            poolRelease(s);
            iter = l.reverse_erase(iter);
      }
}

/**
 * Copy sprites from the pic buffer to the screen buffer, and check if
 * sprites of the given list have moved.
 */
00394 void SpritesMgr::commitSprites(SpriteList &l, bool immediate) {
      SpriteList::iterator iter;
      for (iter = l.begin(); iter != l.end(); ++iter) {
            Sprite *s = *iter;
            int x1, y1, x2, y2;

            x1 = MIN((int)MIN(s->v->xPos, s->v->xPos2), MIN(s->v->xPos + s->v->celData->width, s->v->xPos2 + s->v->celData2->width));
            x2 = MAX((int)MAX(s->v->xPos, s->v->xPos2), MAX(s->v->xPos + s->v->celData->width, s->v->xPos2 + s->v->celData2->width));
            y1 = MIN((int)MIN(s->v->yPos, s->v->yPos2), MIN(s->v->yPos - s->v->celData->height, s->v->yPos2 - s->v->celData2->height));
            y2 = MAX((int)MAX(s->v->yPos, s->v->yPos2), MAX(s->v->yPos - s->v->celData->height, s->v->yPos2 - s->v->celData2->height));

            s->v->celData2 = s->v->celData;

            commitBlock(x1, y1, x2, y2, immediate);

            if (s->v->stepTimeCount != s->v->stepTime)
                  continue;

            if (s->v->xPos == s->v->xPos2 && s->v->yPos == s->v->yPos2) {
                  s->v->flags |= DIDNT_MOVE;
                  continue;
            }

            s->v->xPos2 = s->v->xPos;
            s->v->yPos2 = s->v->yPos;
            s->v->flags &= ~DIDNT_MOVE;
      }
}

/**
 * Erase all sprites in the given list.
 */
00426 void SpritesMgr::eraseSprites(SpriteList &l) {
      SpriteList::iterator iter;
      for (iter = l.reverse_begin(); iter != l.end(); --iter) {
            Sprite *s = *iter;
            objsRestoreArea(s);
      }

      freeList(l);
}

/**
 * Blit all sprites in the given list.
 */
00439 void SpritesMgr::blitSprites(SpriteList& l) {
      int hidden;
      SpriteList::iterator iter;

      for (iter = l.begin(); iter != l.end(); ++iter) {
            Sprite *s = *iter;

            objsSaveArea(s);
            debugC(8, kDebugLevelSprites, "blitSprites(): s->v->entry = %d (prio %d)", s->v->entry, s->v->priority);
            hidden = blitCel(s->xPos, s->yPos, s->v->priority, s->v->celData, s->v->viewData->agi256_2);

            if (s->v->entry == 0) { // if ego, update f1
                  _vm->setflag(fEgoInvisible, hidden);
            }
      }
}

/*
 * Public functions
 */

void SpritesMgr::commitUpdSprites() {
      commitSprites(_sprUpd);
}

void SpritesMgr::commitNonupdSprites() {
      commitSprites(_sprNonupd);
}

// check moves in both lists
void SpritesMgr::commitBoth() {
      commitUpdSprites();
      commitNonupdSprites();
}

/**
 * Erase updating sprites.
 * This function follows the list of all updating sprites and restores
 * the visible and priority data of their background buffers back to
 * the AGI screen.
 *
 * @see erase_nonupd_sprites()
 * @see erase_both()
 */
00483 void SpritesMgr::eraseUpdSprites() {
      eraseSprites(_sprUpd);
}

/**
 * Erase non-updating sprites.
 * This function follows the list of all non-updating sprites and restores
 * the visible and priority data of their background buffers back to
 * the AGI screen.
 *
 * @see erase_upd_sprites()
 * @see erase_both()
 */
00496 void SpritesMgr::eraseNonupdSprites() {
      eraseSprites(_sprNonupd);
}

/**
 * Erase all sprites.
 * This function follows the lists of all updating and non-updating
 * sprites and restores the visible and priority data of their background
 * buffers back to the AGI screen.
 *
 * @see erase_upd_sprites()
 * @see erase_nonupd_sprites()
 */
00509 void SpritesMgr::eraseBoth() {
      eraseUpdSprites();
      eraseNonupdSprites();
}

/**
 * Blit updating sprites.
 * This function follows the list of all updating sprites and blits
 * them on the AGI screen.
 *
 * @see blit_nonupd_sprites()
 * @see blit_both()
 */
00522 void SpritesMgr::blitUpdSprites() {
      debugC(7, kDebugLevelSprites, "blitUpdSprites()");
      buildUpdBlitlist();
      blitSprites(_sprUpd);
}

/**
 * Blit non-updating sprites.
 * This function follows the list of all non-updating sprites and blits
 * them on the AGI screen.
 *
 * @see blit_upd_sprites()
 * @see blit_both()
 */
00536 void SpritesMgr::blitNonupdSprites() {
      debugC(7, kDebugLevelSprites, "blitNonupdSprites()");
      buildNonupdBlitlist();
      blitSprites(_sprNonupd);
}

/**
 * Blit all sprites.
 * This function follows the lists of all updating and non-updating
 * sprites and blits them on the AGI screen.
 *
 * @see blit_upd_sprites()
 * @see blit_nonupd_sprites()
 */
00550 void SpritesMgr::blitBoth() {
      blitNonupdSprites();
      blitUpdSprites();
}

/**
 * Add view to picture.
 * This function is used to implement the add.to.pic AGI command. It
 * copies the specified cel from a view resource on the current picture.
 * This cel is not a sprite, it can't be moved or removed.
 * @param view  number of view resource
 * @param loop  number of loop in the specified view resource
 * @param cel   number of cel in the specified loop
 * @param x     x coordinate to place the view
 * @param y     y coordinate to place the view
 * @param pri   priority to use
 * @param mar   if < 4, create a margin around the the base of the cel
 */
00568 void SpritesMgr::addToPic(int view, int loop, int cel, int x, int y, int pri, int mar) {
      ViewCel *c = NULL;
      int x1, y1, x2, y2, y3;
      uint8 *p1, *p2;

      debugC(3, kDebugLevelSprites, "addToPic(view=%d, loop=%d, cel=%d, x=%d, y=%d, pri=%d, mar=%d)", view, loop, cel, x, y, pri, mar);

      _vm->recordImageStackCall(ADD_VIEW, view, loop, cel, x, y, pri, mar);

      // Was hardcoded to 8, changed to pri_table[y] to fix Gold
      // Rush (see Sarien bug #587558)
      if (pri == 0)
            pri = _vm->_game.priTable[y];

      c = &_vm->_game.views[view].loop[loop].cel[cel];

      x1 = x;
      y1 = y - c->height + 1;
      x2 = x + c->width - 1;
      y2 = y;

      if (x1 < 0) {
            x2 -= x1;
            x1 = 0;
      }
      if (y1 < 0) {
            y2 -= y1;
            y1 = 0;
      }
      if (x2 >= _WIDTH)
            x2 = _WIDTH - 1;
      if (y2 >= _HEIGHT)
            y2 = _HEIGHT - 1;

      eraseBoth();

      debugC(4, kDebugLevelSprites, "blitCel(%d, %d, %d, c)", x, y, pri);
      blitCel(x1, y1, pri, c, _vm->_game.views[view].agi256_2);

      // If margin is 0, 1, 2, or 3, the base of the cel is
      // surrounded with a rectangle of the corresponding priority.
      // If margin >= 4, this extra margin is not shown.
      if (mar < 4) {
            // add rectangle around object, don't clobber control
            // info in priority data. The box extends to the end of
            // its priority band!
            y3 = (y2 / 12) * 12;

            // SQ1 needs +1 (see Sarien bug #810331)
            if (_vm->getGameID() == GID_SQ1)
                  y3++;

            // don't let box extend below y.
            if (y3 > y2) y3 = y2;

            p1 = &_vm->_game.sbuf16c[x1 + y3 * _WIDTH];
            p2 = &_vm->_game.sbuf16c[x2 + y3 * _WIDTH];

            for (y = y3; y <= y2; y++) {
                  if ((*p1 >> 4) >= 4)
                        *p1 = (mar << 4) | (*p1 & 0x0f);

                  if ((*p2 >> 4) >= 4)
                        *p2 = (mar << 4) | (*p2 & 0x0f);

                  p1 += _WIDTH;
                  p2 += _WIDTH;
            }

            debugC(4, kDebugLevelSprites, "pri box: %d %d %d %d (%d)", x1, y3, x2, y2, mar);
            p1 = &_vm->_game.sbuf16c[x1 + y3 * _WIDTH];
            p2 = &_vm->_game.sbuf16c[x1 + y2 * _WIDTH];
            for (x = x1; x <= x2; x++) {
                  if ((*p1 >> 4) >= 4)
                        *p1 = (mar << 4) | (*p1 & 0x0f);

                  if ((*p2 >> 4) >= 4)
                        *p2 = (mar << 4) | (*p2 & 0x0f);

                  p1++;
                  p2++;
            }
      }

      blitBoth();

      commitBlock(x1, y1, x2, y2, true);
}

/**
 * Show object and description
 * This function shows an object from the player's inventory, displaying
 * a message box with the object description.
 * @param n  Number of the object to show
 */
00663 void SpritesMgr::showObj(int n) {
      ViewCel *c;
      Sprite s;
      int x1, y1, x2, y2;

      _vm->agiLoadResource(rVIEW, n);
      if (!(c = &_vm->_game.views[n].loop[0].cel[0]))
            return;

      x1 = (_WIDTH - c->width) / 2;
      y1 = 112;
      x2 = x1 + c->width - 1;
      y2 = y1 + c->height - 1;

      s.xPos = x1;
      s.yPos = y1;
      s.xSize = c->width;
      s.ySize = c->height;
      s.buffer = (uint8 *)malloc(s.xSize * s.ySize);
      s.v = 0;

      objsSaveArea(&s);
      blitCel(x1, y1, 15, c, _vm->_game.views[n].agi256_2);
      commitBlock(x1, y1, x2, y2, true);
      _vm->messageBox(_vm->_game.views[n].descr);
      objsRestoreArea(&s);
      commitBlock(x1, y1, x2, y2, true);

      free(s.buffer);
}

void SpritesMgr::commitBlock(int x1, int y1, int x2, int y2, bool immediate) {
      int i, w, offset;
      uint8 *q;

      if (!_vm->_game.pictureShown)
            return;

      x1 = CLIP(x1, 0, _WIDTH - 1);
      x2 = CLIP(x2, 0, _WIDTH - 1);
      y1 = CLIP(y1, 0, _HEIGHT - 1);
      y2 = CLIP(y2, 0, _HEIGHT - 1);

      debugC(7, kDebugLevelSprites, "commitBlock(%d, %d, %d, %d)", x1, y1, x2, y2);

      w = x2 - x1 + 1;
      q = &_vm->_game.sbuf16c[x1 + _WIDTH * y1];
      offset = _vm->_game.lineMinPrint * CHAR_LINES;

      for (i = y1; i <= y2; i++) {
            _gfx->putPixelsA(x1, i + offset, w, q);
            q += _WIDTH;
      }

      _gfx->flushBlockA(x1, y1 + offset, x2, y2 + offset);

      if (immediate)
            _gfx->doUpdate();
}

SpritesMgr::SpritesMgr(AgiEngine *agi, GfxMgr *gfx) {
      _vm = agi;
      _gfx = gfx;

      _spritePool = (uint8 *)malloc(POOL_SIZE);
      _poolTop = _spritePool;
}

SpritesMgr::~SpritesMgr() {
      free(_spritePool);
}

} // End of namespace Agi

Generated by  Doxygen 1.6.0   Back to index