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