scummvm/engines/mads/sprites.cpp

423 lines
11 KiB
C++

/* 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 "engines/util.h"
#include "graphics/palette.h"
#include "mads/mads.h"
#include "mads/screen.h"
#include "mads/msurface.h"
#include "mads/sprites.h"
namespace MADS {
enum {
kEndOfLine = 0,
kEndOfSprite = 1,
kMarker = 2
};
#define TRANSPARENT_COLOR_INDEX 0xFF
class DepthEntry {
public:
int depth;
int index;
DepthEntry(int depthAmt, int indexVal) { depth = depthAmt; index = indexVal; }
};
bool sortHelper(const DepthEntry &entry1, const DepthEntry &entry2) {
return entry1.depth < entry2.depth;
}
typedef Common::List<DepthEntry> DepthList;
/*------------------------------------------------------------------------*/
MSprite::MSprite() : MSurface() {
_transparencyIndex = TRANSPARENT_COLOR_INDEX;
}
MSprite::MSprite(Common::SeekableReadStream *source, const Common::Array<RGB6> &palette,
const Common::Rect &bounds): MSurface(), _transparencyIndex(TRANSPARENT_COLOR_INDEX),
_offset(Common::Point(bounds.left, bounds.top)) {
// Load the sprite data
create(bounds.width(), bounds.height());
loadSprite(source, palette);
}
MSprite::~MSprite() {
}
void MSprite::loadSprite(Common::SeekableReadStream *source,
const Common::Array<RGB6> &palette) {
byte *outp, *lineStart;
bool newLine = false;
outp = getPixels();
lineStart = getPixels();
int spriteSize = this->w * this->h;
byte transIndex = getTransparencyIndex();
Common::fill(outp, outp + spriteSize, transIndex);
for (;;) {
byte cmd1, cmd2, count, pixel;
if (newLine) {
outp = lineStart + this->w;
lineStart = outp;
newLine = false;
}
cmd1 = source->readByte();
if (cmd1 == 0xFC)
break;
else if (cmd1 == 0xFF)
newLine = true;
else if (cmd1 == 0xFD) {
while (!newLine) {
count = source->readByte();
if (count == 0xFF) {
newLine = true;
} else {
pixel = source->readByte();
while (count--)
*outp++ = (pixel == 0xFD) ? getTransparencyIndex() : pixel;
}
}
} else {
while (!newLine) {
cmd2 = source->readByte();
if (cmd2 == 0xFF) {
newLine = true;
} else if (cmd2 == 0xFE) {
count = source->readByte();
pixel = source->readByte();
while (count--)
*outp++ = (pixel == 0xFD) ? getTransparencyIndex() : pixel;
} else {
*outp++ = (cmd2 == 0xFD) ? getTransparencyIndex() : cmd2;
}
}
}
}
// Do a final iteration over the sprite to convert it's pixels to
// the final positions in the main palette
spriteSize = this->w * this->h;
for (outp = getPixels(); spriteSize > 0; --spriteSize, ++outp) {
if (*outp != transIndex)
*outp = palette[*outp]._palIndex;
}
}
byte MSprite::getTransparencyIndex() const {
return _transparencyIndex;
}
/*------------------------------------------------------------------------*/
MADSEngine *SpriteSlot::_vm = nullptr;
SpriteSlot::SpriteSlot() {
_flags = IMG_STATIC;
_seqIndex = 0;
_spritesIndex = 0;
_frameNumber = 0;
_depth = 0;
_scale = 0;
}
SpriteSlot::SpriteSlot(SpriteFlags type, int seqIndex) {
_flags = type;
_seqIndex = seqIndex;
_spritesIndex = 0;
_frameNumber = 0;
_depth = 0;
_scale = 0;
}
bool SpriteSlot::operator==(const SpriteSlotSubset &other) const {
return (_spritesIndex == other._spritesIndex) && (_frameNumber == other._frameNumber) &&
(_position == other._position) && (_depth == other._depth) &&
(_scale == other._scale);
}
void SpriteSlot::copy(const SpriteSlotSubset &other) {
_spritesIndex = other._spritesIndex;
_frameNumber = other._frameNumber;
_position = other._position;
_depth = other._depth;
_scale = other._scale;
}
/*------------------------------------------------------------------------*/
SpriteSlots::SpriteSlots(MADSEngine *vm) : _vm(vm) {
SpriteSlot::_vm = vm;
}
void SpriteSlots::reset(bool flag) {
_vm->_game->_scene._textDisplay.reset();
if (flag)
_vm->_game->_scene._sprites.clear();
Common::Array<SpriteSlot>::clear();
push_back(SpriteSlot(IMG_REFRESH, -1));
}
void SpriteSlots::deleteEntry(int index) {
remove_at(index);
}
void SpriteSlots::setDirtyAreas() {
Scene &scene = _vm->_game->_scene;
for (uint i = 0; i < size(); ++i) {
if ((*this)[i]._flags >= IMG_STATIC) {
scene._dirtyAreas[i].setSpriteSlot(&(*this)[i]);
scene._dirtyAreas[i]._textActive = ((*this)[i]._flags <= IMG_STATIC) ? 0 : 1;
(*this)[i]._flags = IMG_STATIC;
}
}
}
void SpriteSlots::fullRefresh(bool clearAll) {
if (clearAll)
Common::Array<SpriteSlot>::clear();
push_back(SpriteSlot(IMG_REFRESH, -1));
}
void SpriteSlots::deleteTimer(int seqIndex) {
for (uint idx = 0; idx < size(); ++idx) {
SpriteSlot &slot = (*this)[idx];
if (slot._seqIndex == seqIndex) {
slot._flags = IMG_ERASE;
return;
}
}
}
int SpriteSlots::add() {
SpriteSlot ss;
push_back(ss);
return size() - 1;
}
void SpriteSlots::drawBackground() {
Scene &scene = _vm->_game->_scene;
// Initial draw loop for any active sprites in the background
for (uint i = 0; i < size(); ++i) {
SpriteSlot &spriteSlot = (*this)[i];
DirtyArea &dirtyArea = scene._dirtyAreas[i];
if (spriteSlot._flags >= IMG_STATIC) {
// Foreground sprite, so we can ignore it
dirtyArea._active = false;
} else {
dirtyArea._active = true;
dirtyArea.setSpriteSlot(&spriteSlot);
if (spriteSlot._flags == IMG_DELTA) {
// Background object, so need to draw it
assert(spriteSlot._frameNumber > 0);
SpriteAsset *asset = scene._sprites[spriteSlot._spritesIndex];
MSprite *frame = asset->getFrame(spriteSlot._frameNumber - 1);
Common::Point pt = spriteSlot._position;
if (spriteSlot._scale != -1) {
// Adjust the drawing position
pt.x -= frame->w / 2;
pt.y -= frame->h - 1;
}
if (spriteSlot._depth <= 1) {
scene._backgroundSurface.transBlitFrom(*frame, pt, frame->getTransparencyIndex());
} else if (scene._depthStyle == 0) {
scene._backgroundSurface.copyFrom(*frame, pt, spriteSlot._depth, &scene._depthSurface,
-1, false, frame->getTransparencyIndex());
} else {
scene._backgroundSurface.transBlitFrom(*frame, pt, frame->getTransparencyIndex());
}
}
}
}
// Mark any remaning sprite slot dirty areas as inactive
for (uint i = size(); i < SPRITE_SLOTS_MAX_SIZE; ++i)
scene._dirtyAreas[i]._active = false;
// Flag any active text display
for (uint i = 0; i < scene._textDisplay.size(); ++i) {
TextDisplay &textDisplay = scene._textDisplay[i];
DirtyArea &dirtyArea = scene._dirtyAreas[i + SPRITE_SLOTS_MAX_SIZE];
if (textDisplay._expire >= 0 || !textDisplay._active) {
dirtyArea._active = false;
} else {
dirtyArea._active = true;
dirtyArea.setTextDisplay(&textDisplay);
}
}
}
void SpriteSlots::drawSprites(MSurface *s) {
DepthList depthList;
Scene &scene = _vm->_game->_scene;
// Get a list of sprite object depths for active objects
for (uint i = 0; i < size(); ++i) {
SpriteSlot &spriteSlot = (*this)[i];
if (spriteSlot._flags >= IMG_STATIC) {
DepthEntry rec(16 - spriteSlot._depth, i);
depthList.push_back(rec);
}
}
// Sort the list in order of the depth
Common::sort(depthList.begin(), depthList.end(), sortHelper);
// Loop through each of the objects
DepthList::iterator i;
for (i = depthList.begin(); i != depthList.end(); ++i) {
DepthEntry &de = *i;
SpriteSlot &slot = (*this)[de.index];
assert(slot._spritesIndex < (int)scene._sprites.size());
SpriteAsset &spriteSet = *scene._sprites[slot._spritesIndex];
// Get the sprite frame
int frameNumber = ABS(slot._frameNumber);
bool flipped = slot._frameNumber < 0;
assert(frameNumber > 0);
MSprite *sprite = spriteSet.getFrame(frameNumber - 1);
if ((slot._scale < 100) && (slot._scale != -1)) {
// Scaled drawing
s->copyFrom(*sprite, slot._position, slot._depth, &scene._depthSurface,
slot._scale, flipped, sprite->getTransparencyIndex());
} else {
int xp, yp;
if (slot._scale == -1) {
xp = slot._position.x - scene._posAdjust.x;
yp = slot._position.y - scene._posAdjust.y;
} else {
xp = slot._position.x - (sprite->w / 2) - scene._posAdjust.x;
yp = slot._position.y - sprite->h - scene._posAdjust.y + 1;
}
if (slot._depth > 1) {
// Draw the frame with depth processing
s->copyFrom(*sprite, Common::Point(xp, yp), slot._depth, &scene._depthSurface,
-1, flipped, sprite->getTransparencyIndex());
} else {
BaseSurface *spr = sprite;
if (flipped) {
// Create a flipped copy of the sprite temporarily
spr = sprite->flipHorizontal();
}
// No depth, so simply draw the image
s->transBlitFrom(*spr, Common::Point(xp, yp), sprite->getTransparencyIndex());
// Free sprite if it was a flipped one
if (flipped) {
spr->free();
delete spr;
}
}
}
}
}
void SpriteSlots::cleanUp() {
for (int i = (int)size() - 1; i >= 0; --i) {
if ((*this)[i]._flags < IMG_STATIC)
remove_at(i);
}
}
/*------------------------------------------------------------------------*/
SpriteSets::~SpriteSets() {
clear();
}
int SpriteSets::add(SpriteAsset *asset, int idx) {
if (idx) {
assert(idx == 1);
delete _uiSprites;
_uiSprites = asset;
return SPRITE_SLOTS_MAX_SIZE;
} else {
assert(size() < SPRITE_SLOTS_MAX_SIZE);
push_back(asset);
return (int)size() - 1;
}
}
int SpriteSets::addSprites(const Common::String &resName, int flags) {
return add(new SpriteAsset(_vm, resName, flags));
}
void SpriteSets::clear() {
for (uint i = 0; i < size(); ++i)
delete (*this)[i];
Common::Array<SpriteAsset *>::clear();
delete _uiSprites;
_uiSprites = nullptr;
}
void SpriteSets::remove(int idx) {
if (idx == SPRITE_SLOTS_MAX_SIZE) {
delete _uiSprites;
_uiSprites = nullptr;
} else if (idx >= 0 && idx < (int)size()) {
delete (*this)[idx];
if (idx < ((int)size() - 1)) {
(*this)[idx] = nullptr;
} else {
do {
remove_at(size() - 1);
} while (size() > 0 && (*this)[size() - 1] == nullptr);
}
}
}
SpriteAsset *&SpriteSets::operator[](int idx) {
return (idx == SPRITE_SLOTS_MAX_SIZE) ? _uiSprites :
Common::Array<SpriteAsset *>::operator[](idx);
}
} // End of namespace MADS