mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-15 06:08:35 +00:00
423 lines
11 KiB
C++
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
|