1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 #include "common/scummsys.h"
24 #include "engines/util.h"
25 #include "graphics/palette.h"
26 #include "mads/mads.h"
27 #include "mads/screen.h"
28 #include "mads/msurface.h"
29 #include "mads/sprites.h"
30
31 namespace MADS {
32
33 enum {
34 kEndOfLine = 0,
35 kEndOfSprite = 1,
36 kMarker = 2
37 };
38
39 #define TRANSPARENT_COLOR_INDEX 0xFF
40
41 class DepthEntry {
42 public:
43 int depth;
44 int index;
45
DepthEntry(int depthAmt,int indexVal)46 DepthEntry(int depthAmt, int indexVal) { depth = depthAmt; index = indexVal; }
47 };
48
sortHelper(const DepthEntry & entry1,const DepthEntry & entry2)49 bool sortHelper(const DepthEntry &entry1, const DepthEntry &entry2) {
50 return entry1.depth < entry2.depth;
51 }
52
53 typedef Common::List<DepthEntry> DepthList;
54
55 /*------------------------------------------------------------------------*/
56
MSprite()57 MSprite::MSprite() : MSurface() {
58 _transparencyIndex = TRANSPARENT_COLOR_INDEX;
59 }
60
MSprite(Common::SeekableReadStream * source,const Common::Array<RGB6> & palette,const Common::Rect & bounds)61 MSprite::MSprite(Common::SeekableReadStream *source, const Common::Array<RGB6> &palette,
62 const Common::Rect &bounds): MSurface(), _transparencyIndex(TRANSPARENT_COLOR_INDEX),
63 _offset(Common::Point(bounds.left, bounds.top)) {
64 // Load the sprite data
65 create(bounds.width(), bounds.height());
66 loadSprite(source, palette);
67 }
68
~MSprite()69 MSprite::~MSprite() {
70 }
71
loadSprite(Common::SeekableReadStream * source,const Common::Array<RGB6> & palette)72 void MSprite::loadSprite(Common::SeekableReadStream *source,
73 const Common::Array<RGB6> &palette) {
74 byte *outp, *lineStart;
75 bool newLine = false;
76
77 outp = getPixels();
78 lineStart = getPixels();
79 int spriteSize = this->w * this->h;
80 byte transIndex = getTransparencyIndex();
81 Common::fill(outp, outp + spriteSize, transIndex);
82
83 for (;;) {
84 byte cmd1, cmd2, count, pixel;
85
86 if (newLine) {
87 outp = lineStart + this->w;
88 lineStart = outp;
89 newLine = false;
90 }
91
92 cmd1 = source->readByte();
93
94 if (cmd1 == 0xFC)
95 break;
96 else if (cmd1 == 0xFF)
97 newLine = true;
98 else if (cmd1 == 0xFD) {
99 while (!newLine) {
100 count = source->readByte();
101 if (count == 0xFF) {
102 newLine = true;
103 } else {
104 pixel = source->readByte();
105 while (count--)
106 *outp++ = (pixel == 0xFD) ? getTransparencyIndex() : pixel;
107 }
108 }
109 } else {
110 while (!newLine) {
111 cmd2 = source->readByte();
112 if (cmd2 == 0xFF) {
113 newLine = true;
114 } else if (cmd2 == 0xFE) {
115 count = source->readByte();
116 pixel = source->readByte();
117 while (count--)
118 *outp++ = (pixel == 0xFD) ? getTransparencyIndex() : pixel;
119 } else {
120 *outp++ = (cmd2 == 0xFD) ? getTransparencyIndex() : cmd2;
121 }
122 }
123 }
124 }
125
126 // Do a final iteration over the sprite to convert it's pixels to
127 // the final positions in the main palette
128 spriteSize = this->w * this->h;
129 for (outp = getPixels(); spriteSize > 0; --spriteSize, ++outp) {
130 if (*outp != transIndex)
131 *outp = palette[*outp]._palIndex;
132 }
133 }
134
getTransparencyIndex() const135 byte MSprite::getTransparencyIndex() const {
136 return _transparencyIndex;
137 }
138
139 /*------------------------------------------------------------------------*/
140
141 MADSEngine *SpriteSlot::_vm = nullptr;
142
SpriteSlot()143 SpriteSlot::SpriteSlot() {
144 _flags = IMG_STATIC;
145 _seqIndex = 0;
146 _spritesIndex = 0;
147 _frameNumber = 0;
148 _depth = 0;
149 _scale = 0;
150 }
151
SpriteSlot(SpriteFlags type,int seqIndex)152 SpriteSlot::SpriteSlot(SpriteFlags type, int seqIndex) {
153 _flags = type;
154 _seqIndex = seqIndex;
155 _spritesIndex = 0;
156 _frameNumber = 0;
157 _depth = 0;
158 _scale = 0;
159 }
160
operator ==(const SpriteSlotSubset & other) const161 bool SpriteSlot::operator==(const SpriteSlotSubset &other) const {
162 return (_spritesIndex == other._spritesIndex) && (_frameNumber == other._frameNumber) &&
163 (_position == other._position) && (_depth == other._depth) &&
164 (_scale == other._scale);
165 }
166
copy(const SpriteSlotSubset & other)167 void SpriteSlot::copy(const SpriteSlotSubset &other) {
168 _spritesIndex = other._spritesIndex;
169 _frameNumber = other._frameNumber;
170 _position = other._position;
171 _depth = other._depth;
172 _scale = other._scale;
173 }
174
175 /*------------------------------------------------------------------------*/
176
SpriteSlots(MADSEngine * vm)177 SpriteSlots::SpriteSlots(MADSEngine *vm) : _vm(vm) {
178 SpriteSlot::_vm = vm;
179 }
180
reset(bool flag)181 void SpriteSlots::reset(bool flag) {
182 _vm->_game->_scene._textDisplay.reset();
183
184 if (flag)
185 _vm->_game->_scene._sprites.clear();
186
187 Common::Array<SpriteSlot>::clear();
188 push_back(SpriteSlot(IMG_REFRESH, -1));
189 }
190
deleteEntry(int index)191 void SpriteSlots::deleteEntry(int index) {
192 remove_at(index);
193 }
194
setDirtyAreas()195 void SpriteSlots::setDirtyAreas() {
196 Scene &scene = _vm->_game->_scene;
197
198 for (uint i = 0; i < size(); ++i) {
199 if ((*this)[i]._flags >= IMG_STATIC) {
200 scene._dirtyAreas[i].setSpriteSlot(&(*this)[i]);
201
202 scene._dirtyAreas[i]._textActive = ((*this)[i]._flags <= IMG_STATIC) ? 0 : 1;
203 (*this)[i]._flags = IMG_STATIC;
204 }
205 }
206 }
207
fullRefresh(bool clearAll)208 void SpriteSlots::fullRefresh(bool clearAll) {
209 if (clearAll)
210 Common::Array<SpriteSlot>::clear();
211
212 push_back(SpriteSlot(IMG_REFRESH, -1));
213 }
214
deleteTimer(int seqIndex)215 void SpriteSlots::deleteTimer(int seqIndex) {
216 for (uint idx = 0; idx < size(); ++idx) {
217 SpriteSlot &slot = (*this)[idx];
218 if (slot._seqIndex == seqIndex) {
219 slot._flags = IMG_ERASE;
220 return;
221 }
222 }
223 }
224
add()225 int SpriteSlots::add() {
226 SpriteSlot ss;
227 push_back(ss);
228 return size() - 1;
229 }
230
drawBackground()231 void SpriteSlots::drawBackground() {
232 Scene &scene = _vm->_game->_scene;
233
234 // Initial draw loop for any active sprites in the background
235 for (uint i = 0; i < size(); ++i) {
236 SpriteSlot &spriteSlot = (*this)[i];
237 DirtyArea &dirtyArea = scene._dirtyAreas[i];
238
239 if (spriteSlot._flags >= IMG_STATIC) {
240 // Foreground sprite, so we can ignore it
241 dirtyArea._active = false;
242 } else {
243 dirtyArea._active = true;
244 dirtyArea.setSpriteSlot(&spriteSlot);
245
246 if (spriteSlot._flags == IMG_DELTA) {
247 // Background object, so need to draw it
248 assert(spriteSlot._frameNumber > 0);
249 SpriteAsset *asset = scene._sprites[spriteSlot._spritesIndex];
250 MSprite *frame = asset->getFrame(spriteSlot._frameNumber - 1);
251
252 Common::Point pt = spriteSlot._position;
253 if (spriteSlot._scale != -1) {
254 // Adjust the drawing position
255 pt.x -= frame->w / 2;
256 pt.y -= frame->h - 1;
257 }
258
259 if (spriteSlot._depth <= 1) {
260 scene._backgroundSurface.transBlitFrom(*frame, pt, frame->getTransparencyIndex());
261 } else if (scene._depthStyle == 0) {
262 scene._backgroundSurface.copyFrom(*frame, pt, spriteSlot._depth, &scene._depthSurface,
263 -1, false, frame->getTransparencyIndex());
264 } else {
265 scene._backgroundSurface.transBlitFrom(*frame, pt, frame->getTransparencyIndex());
266 }
267 }
268 }
269 }
270
271 // Mark any remaning sprite slot dirty areas as inactive
272 for (uint i = size(); i < SPRITE_SLOTS_MAX_SIZE; ++i)
273 scene._dirtyAreas[i]._active = false;
274
275 // Flag any active text display
276 for (uint i = 0; i < scene._textDisplay.size(); ++i) {
277 TextDisplay &textDisplay = scene._textDisplay[i];
278 DirtyArea &dirtyArea = scene._dirtyAreas[i + SPRITE_SLOTS_MAX_SIZE];
279
280 if (textDisplay._expire >= 0 || !textDisplay._active) {
281 dirtyArea._active = false;
282 } else {
283 dirtyArea._active = true;
284 dirtyArea.setTextDisplay(&textDisplay);
285 }
286 }
287 }
288
drawSprites(MSurface * s)289 void SpriteSlots::drawSprites(MSurface *s) {
290 DepthList depthList;
291 Scene &scene = _vm->_game->_scene;
292
293 // Get a list of sprite object depths for active objects
294 for (uint i = 0; i < size(); ++i) {
295 SpriteSlot &spriteSlot = (*this)[i];
296 if (spriteSlot._flags >= IMG_STATIC) {
297 DepthEntry rec(16 - spriteSlot._depth, i);
298 depthList.push_back(rec);
299 }
300 }
301
302 // Sort the list in order of the depth
303 Common::sort(depthList.begin(), depthList.end(), sortHelper);
304
305 // Loop through each of the objects
306 DepthList::iterator i;
307 for (i = depthList.begin(); i != depthList.end(); ++i) {
308 DepthEntry &de = *i;
309 SpriteSlot &slot = (*this)[de.index];
310 assert(slot._spritesIndex < (int)scene._sprites.size());
311 SpriteAsset &spriteSet = *scene._sprites[slot._spritesIndex];
312
313 // Get the sprite frame
314 int frameNumber = ABS(slot._frameNumber);
315 bool flipped = slot._frameNumber < 0;
316
317 assert(frameNumber > 0);
318 MSprite *sprite = spriteSet.getFrame(frameNumber - 1);
319
320 if ((slot._scale < 100) && (slot._scale != -1)) {
321 // Scaled drawing
322 s->copyFrom(*sprite, slot._position, slot._depth, &scene._depthSurface,
323 slot._scale, flipped, sprite->getTransparencyIndex());
324 } else {
325 int xp, yp;
326
327 if (slot._scale == -1) {
328 xp = slot._position.x - scene._posAdjust.x;
329 yp = slot._position.y - scene._posAdjust.y;
330 } else {
331 xp = slot._position.x - (sprite->w / 2) - scene._posAdjust.x;
332 yp = slot._position.y - sprite->h - scene._posAdjust.y + 1;
333 }
334
335 if (slot._depth > 1) {
336 // Draw the frame with depth processing
337 s->copyFrom(*sprite, Common::Point(xp, yp), slot._depth, &scene._depthSurface,
338 -1, flipped, sprite->getTransparencyIndex());
339 } else {
340 BaseSurface *spr = sprite;
341 if (flipped) {
342 // Create a flipped copy of the sprite temporarily
343 spr = sprite->flipHorizontal();
344 }
345
346 // No depth, so simply draw the image
347 s->transBlitFrom(*spr, Common::Point(xp, yp), sprite->getTransparencyIndex());
348
349 // Free sprite if it was a flipped one
350 if (flipped) {
351 spr->free();
352 delete spr;
353 }
354 }
355 }
356 }
357 }
358
cleanUp()359 void SpriteSlots::cleanUp() {
360 for (int i = (int)size() - 1; i >= 0; --i) {
361 if ((*this)[i]._flags < IMG_STATIC)
362 remove_at(i);
363 }
364 }
365
366 /*------------------------------------------------------------------------*/
367
~SpriteSets()368 SpriteSets::~SpriteSets() {
369 clear();
370 }
371
add(SpriteAsset * asset,int idx)372 int SpriteSets::add(SpriteAsset *asset, int idx) {
373 if (idx) {
374 assert(idx == 1);
375 delete _uiSprites;
376 _uiSprites = asset;
377
378 return SPRITE_SLOTS_MAX_SIZE;
379 } else {
380 assert(size() < SPRITE_SLOTS_MAX_SIZE);
381 push_back(asset);
382
383 return (int)size() - 1;
384 }
385 }
386
addSprites(const Common::String & resName,int flags)387 int SpriteSets::addSprites(const Common::String &resName, int flags) {
388 return add(new SpriteAsset(_vm, resName, flags));
389 }
390
clear()391 void SpriteSets::clear() {
392 for (uint i = 0; i < size(); ++i)
393 delete (*this)[i];
394 Common::Array<SpriteAsset *>::clear();
395
396 delete _uiSprites;
397 _uiSprites = nullptr;
398 }
399
remove(int idx)400 void SpriteSets::remove(int idx) {
401 if (idx == SPRITE_SLOTS_MAX_SIZE) {
402 delete _uiSprites;
403 _uiSprites = nullptr;
404 } else if (idx >= 0 && idx < (int)size()) {
405 delete (*this)[idx];
406
407 if (idx < ((int)size() - 1)) {
408 (*this)[idx] = nullptr;
409 } else {
410 do {
411 remove_at(size() - 1);
412 } while (size() > 0 && (*this)[size() - 1] == nullptr);
413 }
414 }
415 }
416
operator [](int idx)417 SpriteAsset *&SpriteSets::operator[](int idx) {
418 return (idx == SPRITE_SLOTS_MAX_SIZE) ? _uiSprites :
419 Common::Array<SpriteAsset *>::operator[](idx);
420 }
421
422 } // End of namespace MADS
423