Hooked up the pathfinder so that it seems to work. Animation opcode 0x12.
[scummvm-innocent.git] / engines / saga / sprite.cpp
blob6a5bc03206e0de0eb69b2217f8b24452b4b895ac
1 /* ScummVM - Graphic Adventure Engine
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.
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.
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.
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.
21 * $URL$
22 * $Id$
26 // Sprite management module
28 #include "saga/saga.h"
30 #include "saga/gfx.h"
31 #include "saga/scene.h"
32 #include "saga/resource.h"
33 #include "saga/font.h"
35 #include "saga/sprite.h"
36 #include "saga/render.h"
38 namespace Saga {
40 #define RID_IHNM_ARROW_SPRITES 13
41 #define RID_IHNM_SAVEREMINDER_SPRITES 14
42 #define RID_IHNMDEMO_ARROW_SPRITES 8
43 #define RID_IHNMDEMO_SAVEREMINDER_SPRITES 9
45 Sprite::Sprite(SagaEngine *vm) : _vm(vm) {
46 debug(8, "Initializing sprite subsystem...");
48 // Load sprite module resource context
49 _spriteContext = _vm->_resource->getContext(GAME_RESOURCEFILE);
50 if (_spriteContext == NULL) {
51 error("Sprite::Sprite resource context not found");
54 _decodeBufLen = DECODE_BUF_LEN;
56 _decodeBuf = (byte *)malloc(_decodeBufLen);
57 if (_decodeBuf == NULL) {
58 memoryError("Sprite::Sprite");
61 if (_vm->getGameId() == GID_ITE) {
62 loadList(_vm->getResourceDescription()->mainSpritesResourceId, _mainSprites);
63 _arrowSprites = _saveReminderSprites = _inventorySprites = _mainSprites;
64 #ifdef ENABLE_IHNM
65 } else if (_vm->getGameId() == GID_IHNM) {
66 if (_vm->getFeatures() & GF_IHNM_DEMO) {
67 loadList(RID_IHNMDEMO_ARROW_SPRITES, _arrowSprites);
68 loadList(RID_IHNMDEMO_SAVEREMINDER_SPRITES, _saveReminderSprites);
69 } else {
70 loadList(RID_IHNM_ARROW_SPRITES, _arrowSprites);
71 loadList(RID_IHNM_SAVEREMINDER_SPRITES, _saveReminderSprites);
73 #endif
74 } else {
75 error("Sprite: unknown game type");
79 Sprite::~Sprite(void) {
80 debug(8, "Shutting down sprite subsystem...");
81 _mainSprites.freeMem();
82 if (_vm->getGameId() == GID_IHNM) {
83 _inventorySprites.freeMem();
84 _arrowSprites.freeMem();
85 _saveReminderSprites.freeMem();
87 free(_decodeBuf);
90 void Sprite::loadList(int resourceId, SpriteList &spriteList) {
91 SpriteInfo *spriteInfo;
92 byte *spriteListData = 0;
93 size_t spriteListLength = 0;
94 uint16 oldSpriteCount;
95 uint16 newSpriteCount;
96 uint16 spriteCount;
97 int i;
98 int outputLength, inputLength;
99 uint32 offset;
100 const byte *spritePointer;
101 const byte *spriteDataPointer;
103 _vm->_resource->loadResource(_spriteContext, resourceId, spriteListData, spriteListLength);
105 if (spriteListLength == 0) {
106 return;
109 MemoryReadStreamEndian readS(spriteListData, spriteListLength, _spriteContext->isBigEndian);
111 spriteCount = readS.readUint16();
113 debug(9, "Sprites: %d", spriteCount);
115 oldSpriteCount = spriteList.spriteCount;
116 newSpriteCount = spriteList.spriteCount + spriteCount;
118 spriteList.infoList = (SpriteInfo *)realloc(spriteList.infoList, newSpriteCount * sizeof(*spriteList.infoList));
119 if (spriteList.infoList == NULL) {
120 memoryError("Sprite::loadList");
123 spriteList.spriteCount = newSpriteCount;
125 bool bigHeader = _vm->getGameId() == GID_IHNM || _vm->isMacResources();
127 for (i = oldSpriteCount; i < spriteList.spriteCount; i++) {
128 spriteInfo = &spriteList.infoList[i];
129 if (bigHeader)
130 offset = readS.readUint32();
131 else
132 offset = readS.readUint16();
134 if (offset >= spriteListLength) {
135 // ITE Mac demos throw this warning
136 warning("Sprite::loadList offset exceeded");
137 spriteList.spriteCount = i;
138 return;
141 spritePointer = spriteListData;
142 spritePointer += offset;
144 if (bigHeader) {
145 MemoryReadStreamEndian readS2(spritePointer, 8, _spriteContext->isBigEndian);
147 spriteInfo->xAlign = readS2.readSint16();
148 spriteInfo->yAlign = readS2.readSint16();
150 spriteInfo->width = readS2.readUint16();
151 spriteInfo->height = readS2.readUint16();
153 spriteDataPointer = spritePointer + readS2.pos();
154 } else {
155 MemoryReadStreamEndian readS2(spritePointer, 4);
157 spriteInfo->xAlign = readS2.readSByte();
158 spriteInfo->yAlign = readS2.readSByte();
160 spriteInfo->width = readS2.readByte();
161 spriteInfo->height = readS2.readByte();
162 spriteDataPointer = spritePointer + readS2.pos();
165 outputLength = spriteInfo->width * spriteInfo->height;
166 inputLength = spriteListLength - (spriteDataPointer - spriteListData);
167 decodeRLEBuffer(spriteDataPointer, inputLength, outputLength);
168 spriteInfo->decodedBuffer = (byte *) malloc(outputLength);
169 if (spriteInfo->decodedBuffer == NULL) {
170 memoryError("Sprite::loadList");
173 #ifdef ENABLE_IHNM
174 // IHNM sprites are upside-down, for reasons which i can only
175 // assume are perverse. To simplify things, flip them now. Not
176 // at drawing time.
178 if (_vm->getGameId() == GID_IHNM) {
179 byte *src = _decodeBuf + spriteInfo->width * (spriteInfo->height - 1);
180 byte *dst = spriteInfo->decodedBuffer;
182 for (int j = 0; j < spriteInfo->height; j++) {
183 memcpy(dst, src, spriteInfo->width);
184 src -= spriteInfo->width;
185 dst += spriteInfo->width;
187 } else
188 #endif
189 memcpy(spriteInfo->decodedBuffer, _decodeBuf, outputLength);
192 free(spriteListData);
195 void Sprite::getScaledSpriteBuffer(SpriteList &spriteList, int spriteNumber, int scale, int &width, int &height, int &xAlign, int &yAlign, const byte *&buffer) {
196 SpriteInfo *spriteInfo;
198 if (spriteList.spriteCount <= spriteNumber) {
199 // this can occur in IHNM while loading a saved game from chapter 1-5 when being in the end chapter
200 warning("spriteList.spriteCount <= spriteNumber");
201 return;
204 spriteInfo = &spriteList.infoList[spriteNumber];
206 if (scale < 256) {
207 xAlign = (spriteInfo->xAlign * scale) >> 8;
208 yAlign = (spriteInfo->yAlign * scale) >> 8;
209 height = (spriteInfo->height * scale + 0x7f) >> 8;
210 width = (spriteInfo->width * scale + 0x7f) >> 8;
211 scaleBuffer(spriteInfo->decodedBuffer, spriteInfo->width, spriteInfo->height, scale);
212 buffer = _decodeBuf;
213 } else {
214 xAlign = spriteInfo->xAlign;
215 yAlign = spriteInfo->yAlign;
216 height = spriteInfo->height;
217 width = spriteInfo->width;
218 buffer = spriteInfo->decodedBuffer;
222 void Sprite::drawClip(const Point &spritePointer, int width, int height, const byte *spriteBuffer, bool clipToScene) {
223 int clipWidth;
224 int clipHeight;
225 Common::Rect clipRect = clipToScene ? _vm->_scene->getSceneClip() : _vm->getDisplayClip();
227 int i, j, jo, io;
228 byte *bufRowPointer;
229 const byte *srcRowPointer;
231 bufRowPointer = _vm->_gfx->getBackBufferPixels() + _vm->_gfx->getBackBufferPitch() * spritePointer.y;
232 srcRowPointer = spriteBuffer;
234 clipWidth = CLIP(width, 0, clipRect.right - spritePointer.x);
235 clipHeight = CLIP(height, 0, clipRect.bottom - spritePointer.y);
237 jo = 0;
238 io = 0;
239 if (spritePointer.x < clipRect.left) {
240 jo = clipRect.left - spritePointer.x;
242 if (spritePointer.y < clipRect.top) {
243 io = clipRect.top - spritePointer.y;
244 bufRowPointer += _vm->_gfx->getBackBufferPitch() * io;
245 srcRowPointer += width * io;
248 for (i = io; i < clipHeight; i++) {
249 for (j = jo; j < clipWidth; j++) {
250 assert(_vm->_gfx->getBackBufferPixels() <= (byte *)(bufRowPointer + j + spritePointer.x));
251 assert((_vm->_gfx->getBackBufferPixels() + (_vm->getDisplayInfo().width *
252 _vm->getDisplayInfo().height)) > (byte *)(bufRowPointer + j + spritePointer.x));
253 assert((const byte *)spriteBuffer <= (const byte *)(srcRowPointer + j));
254 assert(((const byte *)spriteBuffer + (width * height)) > (const byte *)(srcRowPointer + j));
256 if (*(srcRowPointer + j) != 0) {
257 *(bufRowPointer + j + spritePointer.x) = *(srcRowPointer + j);
260 bufRowPointer += _vm->_gfx->getBackBufferPitch();
261 srcRowPointer += width;
264 int x1 = MAX<int>(spritePointer.x, 0);
265 int y1 = MAX<int>(spritePointer.y, 0);
266 int x2 = MIN<int>(MAX<int>(spritePointer.x + clipWidth, 0), clipRect.right);
267 int y2 = MIN<int>(MAX<int>(spritePointer.y + clipHeight, 0), clipRect.bottom);
269 if (x2 > x1 && y2 > y1)
270 _vm->_render->addDirtyRect(Common::Rect(x1, y1, x2, y2));
273 void Sprite::draw(SpriteList &spriteList, int32 spriteNumber, const Point &screenCoord, int scale, bool clipToScene) {
274 const byte *spriteBuffer = NULL;
275 int width = 0;
276 int height = 0;
277 int xAlign = 0;
278 int yAlign = 0;
279 Point spritePointer;
281 getScaledSpriteBuffer(spriteList, spriteNumber, scale, width, height, xAlign, yAlign, spriteBuffer);
283 spritePointer.x = screenCoord.x + xAlign;
284 spritePointer.y = screenCoord.y + yAlign;
286 drawClip(spritePointer, width, height, spriteBuffer, clipToScene);
289 void Sprite::draw(SpriteList &spriteList, int32 spriteNumber, const Rect &screenRect, int scale, bool clipToScene) {
290 const byte *spriteBuffer = NULL;
291 int width = 0;
292 int height = 0;
293 int xAlign = 0;
294 int spw;
295 int yAlign = 0;
296 int sph;
297 Point spritePointer;
299 getScaledSpriteBuffer(spriteList, spriteNumber, scale, width, height, xAlign, yAlign, spriteBuffer);
300 spw = (screenRect.width() - width) / 2;
301 sph = (screenRect.height() - height) / 2;
302 if (spw < 0) {
303 spw = 0;
305 if (sph < 0) {
306 sph = 0;
308 spritePointer.x = screenRect.left + xAlign + spw;
309 spritePointer.y = screenRect.top + yAlign + sph;
310 drawClip(spritePointer, width, height, spriteBuffer, clipToScene);
313 bool Sprite::hitTest(SpriteList &spriteList, int spriteNumber, const Point &screenCoord, int scale, const Point &testPoint) {
314 const byte *spriteBuffer = NULL;
315 int i, j;
316 const byte *srcRowPointer;
317 int width = 0;
318 int height = 0;
319 int xAlign = 0;
320 int yAlign = 0;
321 Point spritePointer;
323 getScaledSpriteBuffer(spriteList, spriteNumber, scale, width, height, xAlign, yAlign, spriteBuffer);
325 spritePointer.x = screenCoord.x + xAlign;
326 spritePointer.y = screenCoord.y + yAlign;
328 if ((testPoint.y < spritePointer.y) || (testPoint.y >= spritePointer.y + height)) {
329 return false;
331 if ((testPoint.x < spritePointer.x) || (testPoint.x >= spritePointer.x + width)) {
332 return false;
334 i = testPoint.y - spritePointer.y;
335 j = testPoint.x - spritePointer.x;
336 srcRowPointer = spriteBuffer + j + i * width;
337 return *srcRowPointer != 0;
340 void Sprite::drawOccluded(SpriteList &spriteList, int spriteNumber, const Point &screenCoord, int scale, int depth) {
341 const byte *spriteBuffer = NULL;
342 int x, y;
343 byte *destRowPointer;
344 const byte *sourceRowPointer;
345 const byte *sourcePointer;
346 byte *destPointer;
347 byte *maskPointer;
348 int width = 0;
349 int height = 0;
350 int xAlign = 0;
351 int yAlign = 0;
353 ClipData clipData;
355 // BG mask variables
356 int maskWidth;
357 int maskHeight;
358 byte *maskBuffer;
359 size_t maskBufferLength;
360 byte *maskRowPointer;
361 int maskZ;
363 if (!_vm->_scene->isBGMaskPresent()) {
364 draw(spriteList, spriteNumber, screenCoord, scale);
365 return;
368 _vm->_scene->getBGMaskInfo(maskWidth, maskHeight, maskBuffer, maskBufferLength);
370 getScaledSpriteBuffer(spriteList, spriteNumber, scale, width, height, xAlign, yAlign, spriteBuffer);
372 clipData.destPoint.x = screenCoord.x + xAlign;
373 clipData.destPoint.y = screenCoord.y + yAlign;
375 clipData.sourceRect.left = 0;
376 clipData.sourceRect.top = 0;
377 clipData.sourceRect.right = width;
378 clipData.sourceRect.bottom = height;
380 clipData.destRect = _vm->_scene->getSceneClip();
382 if (!clipData.calcClip()) {
383 return;
386 // Finally, draw the occluded sprite
388 sourceRowPointer = spriteBuffer + clipData.drawSource.x + (clipData.drawSource.y * width);
389 destRowPointer = _vm->_gfx->getBackBufferPixels() + clipData.drawDest.x + (clipData.drawDest.y * _vm->_gfx->getBackBufferPitch());
390 maskRowPointer = maskBuffer + clipData.drawDest.x + (clipData.drawDest.y * maskWidth);
392 for (y = 0; y < clipData.drawHeight; y++) {
393 sourcePointer = sourceRowPointer;
394 destPointer = destRowPointer;
395 maskPointer = maskRowPointer;
396 for (x = 0; x < clipData.drawWidth; x++) {
397 if (*sourcePointer != 0) {
398 maskZ = *maskPointer & SPRITE_ZMASK;
399 if (maskZ > depth) {
400 *destPointer = *sourcePointer;
403 sourcePointer++;
404 destPointer++;
405 maskPointer++;
407 destRowPointer += _vm->_gfx->getBackBufferPitch();
408 maskRowPointer += maskWidth;
409 sourceRowPointer += width;
412 _vm->_render->addDirtyRect(Common::Rect(clipData.drawSource.x, clipData.drawSource.y,
413 clipData.drawSource.x + width, clipData.drawSource.y + height));
416 void Sprite::decodeRLEBuffer(const byte *inputBuffer, size_t inLength, size_t outLength) {
417 int bg_runcount;
418 int fg_runcount;
419 byte *outPointer;
420 byte *outPointerEnd;
421 int c;
423 if (outLength > _decodeBufLen) { // TODO: may we should make dynamic growing?
424 error("Sprite::decodeRLEBuffer outLength > _decodeBufLen");
427 outPointer = _decodeBuf;
428 outPointerEnd = _decodeBuf + outLength;
429 outPointerEnd--;
431 memset(outPointer, 0, outLength);
433 MemoryReadStream readS(inputBuffer, inLength);
435 while (!readS.eos() && (outPointer < outPointerEnd)) {
436 bg_runcount = readS.readByte();
437 if (readS.eos())
438 break;
439 fg_runcount = readS.readByte();
441 for (c = 0; c < bg_runcount && !readS.eos(); c++) {
442 *outPointer = (byte) 0;
443 if (outPointer < outPointerEnd)
444 outPointer++;
445 else
446 return;
449 for (c = 0; c < fg_runcount && !readS.eos(); c++) {
450 *outPointer = readS.readByte();
451 if (readS.eos())
452 break;
453 if (outPointer < outPointerEnd)
454 outPointer++;
455 else
456 return;
461 void Sprite::scaleBuffer(const byte *src, int width, int height, int scale) {
462 byte skip = 256 - scale; // skip factor
463 byte vskip = 0x80, hskip;
464 byte *dst = _decodeBuf;
466 for (int i = 0; i < height; i++) {
467 vskip += skip;
469 if (vskip < skip) { // We had an overflow
470 src += width;
471 } else {
472 hskip = 0x80;
474 for (int j = 0; j < width; j++) {
475 *dst++ = *src++;
477 hskip += skip;
478 if (hskip < skip) // overflow
479 dst--;
486 } // End of namespace Saga