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 "agi/agi.h"
24 #include "agi/sprite.h"
25 #include "agi/graphics.h"
26 #include "agi/text.h"
27
28 namespace Agi {
29
SpritesMgr(AgiEngine * agi,GfxMgr * gfx)30 SpritesMgr::SpritesMgr(AgiEngine *agi, GfxMgr *gfx) {
31 _vm = agi;
32 _gfx = gfx;
33 }
34
~SpritesMgr()35 SpritesMgr::~SpritesMgr() {
36 _spriteRegularList.clear();
37 _spriteStaticList.clear();
38 }
39
sortSpriteHelper(const Sprite & entry1,const Sprite & entry2)40 static bool sortSpriteHelper(const Sprite &entry1, const Sprite &entry2) {
41 if (entry1.sortOrder == entry2.sortOrder) {
42 // If sort-order is the same, we sort according to given order
43 // which makes this sort stable.
44 return entry1.givenOrderNr < entry2.givenOrderNr;
45 }
46 return entry1.sortOrder < entry2.sortOrder;
47 }
48
buildRegularSpriteList()49 void SpritesMgr::buildRegularSpriteList() {
50 ScreenObjEntry *screenObj = NULL;
51 uint16 givenOrderNr = 0;
52
53 freeList(_spriteRegularList);
54 for (screenObj = _vm->_game.screenObjTable; screenObj < &_vm->_game.screenObjTable[SCREENOBJECTS_MAX]; screenObj++) {
55 if ((screenObj->flags & (fAnimated | fUpdate | fDrawn)) == (fAnimated | fUpdate | fDrawn)) {
56 buildSpriteListAdd(givenOrderNr, screenObj, _spriteRegularList);
57 givenOrderNr++;
58 }
59 }
60
61 // Now sort this list
62 Common::sort(_spriteRegularList.begin(), _spriteRegularList.end(), sortSpriteHelper);
63 // warning("buildRegular: %d", _spriteRegularList.size());
64 }
65
buildStaticSpriteList()66 void SpritesMgr::buildStaticSpriteList() {
67 ScreenObjEntry *screenObj = NULL;
68 uint16 givenOrderNr = 0;
69
70 freeList(_spriteStaticList);
71 for (screenObj = _vm->_game.screenObjTable; screenObj < &_vm->_game.screenObjTable[SCREENOBJECTS_MAX]; screenObj++) {
72 if ((screenObj->flags & (fAnimated | fUpdate | fDrawn)) == (fAnimated | fDrawn)) { // DIFFERENCE IN HERE!
73 buildSpriteListAdd(givenOrderNr, screenObj, _spriteStaticList);
74 givenOrderNr++;
75 }
76 }
77
78 // Now sort this list
79 Common::sort(_spriteStaticList.begin(), _spriteStaticList.end(), sortSpriteHelper);
80 }
81
buildAllSpriteLists()82 void SpritesMgr::buildAllSpriteLists() {
83 buildStaticSpriteList();
84 buildRegularSpriteList();
85 }
86
buildSpriteListAdd(uint16 givenOrderNr,ScreenObjEntry * screenObj,SpriteList & spriteList)87 void SpritesMgr::buildSpriteListAdd(uint16 givenOrderNr, ScreenObjEntry *screenObj, SpriteList &spriteList) {
88 Sprite spriteEntry;
89
90 // Check, if screen object points to currently loaded view, if not don't add it
91 if (!(_vm->_game.dirView[screenObj->currentViewNr].flags & RES_LOADED))
92 return;
93
94 spriteEntry.givenOrderNr = givenOrderNr;
95 // warning("sprite add objNr %d", screenObjPtr->objectNr);
96 if (screenObj->flags & fFixedPriority) {
97 spriteEntry.sortOrder = _gfx->priorityToY(screenObj->priority);
98 // warning(" - priorityToY (fixed) %d -> %d", screenObj->priority, spriteEntry.sortOrder);
99 } else {
100 spriteEntry.sortOrder = screenObj->yPos;
101 // warning(" - Ypos %d -> %d", screenObjPtr->yPos, spriteEntry.sortOrder);
102 }
103
104 spriteEntry.screenObjPtr = screenObj;
105 spriteEntry.xPos = screenObj->xPos;
106 spriteEntry.yPos = (screenObj->yPos) - (screenObj->ySize) + 1;
107 spriteEntry.xSize = screenObj->xSize;
108 spriteEntry.ySize = screenObj->ySize;
109
110 // Checking, if xPos/yPos/right/bottom are valid and do not go outside of playscreen (visual screen)
111 // Original AGI did not do this (but it then resulted in memory corruption)
112 if (spriteEntry.xPos < 0) {
113 warning("buildSpriteListAdd(): ignoring screen obj %d, b/c xPos (%d) < 0", screenObj->objectNr, spriteEntry.xPos);
114 return;
115 }
116 if (spriteEntry.yPos < 0) {
117 warning("buildSpriteListAdd(): ignoring screen obj %d, b/c yPos (%d) < 0", screenObj->objectNr, spriteEntry.yPos);
118 return;
119 }
120 int16 xRight = spriteEntry.xPos + spriteEntry.xSize;
121 if (xRight > SCRIPT_HEIGHT) {
122 warning("buildSpriteListAdd(): ignoring screen obj %d, b/c rightPos (%d) > %d", screenObj->objectNr, xRight, SCRIPT_WIDTH);
123 return;
124 }
125 int16 yBottom = spriteEntry.yPos + spriteEntry.ySize;
126 if (yBottom > SCRIPT_HEIGHT) {
127 warning("buildSpriteListAdd(): ignoring screen obj %d, b/c bottomPos (%d) > %d", screenObj->objectNr, yBottom, SCRIPT_HEIGHT);
128 return;
129 }
130
131 // warning("list-add: %d, %d, original yPos: %d, ySize: %d", spriteEntry.xPos, spriteEntry.yPos, screenObj->yPos, screenObj->ySize);
132 spriteEntry.backgroundBuffer = (uint8 *)malloc(spriteEntry.xSize * spriteEntry.ySize * 2); // for visual + priority data
133 assert(spriteEntry.backgroundBuffer);
134 spriteList.push_back(spriteEntry);
135 }
136
freeList(SpriteList & spriteList)137 void SpritesMgr::freeList(SpriteList &spriteList) {
138 SpriteList::iterator iter;
139 for (iter = spriteList.reverse_begin(); iter != spriteList.end(); iter--) {
140 Sprite &sprite = *iter;
141
142 free(sprite.backgroundBuffer);
143 }
144 spriteList.clear();
145 }
146
freeRegularSprites()147 void SpritesMgr::freeRegularSprites() {
148 freeList(_spriteRegularList);
149 }
150
freeStaticSprites()151 void SpritesMgr::freeStaticSprites() {
152 freeList(_spriteStaticList);
153 }
154
freeAllSprites()155 void SpritesMgr::freeAllSprites() {
156 freeList(_spriteRegularList);
157 freeList(_spriteStaticList);
158 }
159
eraseSprites(SpriteList & spriteList)160 void SpritesMgr::eraseSprites(SpriteList &spriteList) {
161 SpriteList::iterator iter;
162 // warning("eraseSprites - count %d", spriteList.size());
163 for (iter = spriteList.reverse_begin(); iter != spriteList.end(); iter--) {
164 Sprite &sprite = *iter;
165 _gfx->block_restore(sprite.xPos, sprite.yPos, sprite.xSize, sprite.ySize, sprite.backgroundBuffer);
166 }
167
168 freeList(spriteList);
169 }
170
171 /**
172 * Erase updating sprites.
173 * This function follows the list of all updating sprites and restores
174 * the visible and priority data of their background buffers back to
175 * the AGI screen.
176 *
177 * @see erase_nonupd_sprites()
178 * @see erase_both()
179 */
eraseRegularSprites()180 void SpritesMgr::eraseRegularSprites() {
181 eraseSprites(_spriteRegularList);
182 }
183
eraseStaticSprites()184 void SpritesMgr::eraseStaticSprites() {
185 eraseSprites(_spriteStaticList);
186 }
187
eraseSprites()188 void SpritesMgr::eraseSprites() {
189 eraseSprites(_spriteRegularList);
190 eraseSprites(_spriteStaticList);
191 }
192
193 /**
194 * Draw all sprites in the given list.
195 */
drawSprites(SpriteList & spriteList)196 void SpritesMgr::drawSprites(SpriteList &spriteList) {
197 SpriteList::iterator iter;
198 // warning("drawSprites");
199
200 for (iter = spriteList.begin(); iter != spriteList.end(); ++iter) {
201 Sprite &sprite = *iter;
202 ScreenObjEntry *screenObj = sprite.screenObjPtr;
203
204 _gfx->block_save(sprite.xPos, sprite.yPos, sprite.xSize, sprite.ySize, sprite.backgroundBuffer);
205 //debugC(8, kDebugLevelSprites, "drawSprites(): s->v->entry = %d (prio %d)", s->viewPtr->entry, s->viewPtr->priority);
206 // warning("sprite %d (view %d), priority %d, sort %d, givenOrder %d", screenObj->objectNr, screenObj->currentView, screenObj->priority, sprite.sortOrder, sprite.givenOrderNr);
207 drawCel(screenObj);
208 }
209 }
210
211 /**
212 * Blit updating sprites.
213 * This function follows the list of all updating sprites and blits
214 * them on the AGI screen.
215 *
216 * @see blit_nonupd_sprites()
217 * @see blit_both()
218 */
drawRegularSpriteList()219 void SpritesMgr::drawRegularSpriteList() {
220 debugC(7, kDebugLevelSprites, "drawRegularSpriteList()");
221 drawSprites(_spriteRegularList);
222 }
223
drawStaticSpriteList()224 void SpritesMgr::drawStaticSpriteList() {
225 //debugC(7, kDebugLevelSprites, "drawRegularSpriteList()");
226 drawSprites(_spriteStaticList);
227 }
228
drawAllSpriteLists()229 void SpritesMgr::drawAllSpriteLists() {
230 drawSprites(_spriteStaticList);
231 drawSprites(_spriteRegularList);
232 }
233
drawCel(ScreenObjEntry * screenObj)234 void SpritesMgr::drawCel(ScreenObjEntry *screenObj) {
235 int16 curX = screenObj->xPos;
236 int16 baseX = screenObj->xPos;
237 int16 curY = screenObj->yPos;
238 AgiViewCel *celPtr = screenObj->celData;
239 byte *celDataPtr = celPtr->rawBitmap;
240 uint8 remainingCelHeight = celPtr->height;
241 uint8 celWidth = celPtr->width;
242 byte celClearKey = celPtr->clearKey;
243 byte viewPriority = screenObj->priority;
244 byte screenPriority = 0;
245 byte curColor = 0;
246 byte isViewHidden = true;
247
248 // Adjust vertical position, given yPos is lower left, but we need upper left
249 curY = curY - celPtr->height + 1;
250
251 while (remainingCelHeight) {
252 for (int16 loopX = 0; loopX < celWidth; loopX++) {
253 curColor = *celDataPtr++;
254
255 if (curColor != celClearKey) {
256 screenPriority = _gfx->getPriority(curX, curY);
257 if (screenPriority <= 2) {
258 // control data found
259 if (_gfx->checkControlPixel(curX, curY, viewPriority)) {
260 _gfx->putPixel(curX, curY, GFX_SCREEN_MASK_VISUAL, curColor, 0);
261 isViewHidden = false;
262 }
263 } else if (screenPriority <= viewPriority) {
264 _gfx->putPixel(curX, curY, GFX_SCREEN_MASK_ALL, curColor, viewPriority);
265 isViewHidden = false;
266 }
267
268 }
269 curX++;
270 }
271
272 // go to next vertical position
273 remainingCelHeight--;
274 curX = baseX;
275 curY++;
276 }
277
278 if (screenObj->objectNr == 0) { // if ego, update if ego is visible at the moment
279 _vm->setFlag(VM_FLAG_EGO_INVISIBLE, isViewHidden);
280 }
281 }
282
283
showSprite(ScreenObjEntry * screenObj)284 void SpritesMgr::showSprite(ScreenObjEntry *screenObj) {
285 int16 x = 0;
286 int16 y = 0;
287 int16 width = 0;
288 int16 height = 0;
289
290 int16 view_height_prev = 0;
291 int16 view_width_prev = 0;
292
293 int16 y2 = 0;
294 int16 height1 = 0;
295 int16 height2 = 0;
296
297 int16 x2 = 0;
298 int16 width1 = 0;
299 int16 width2 = 0;
300
301 if (!_vm->_game.pictureShown)
302 return;
303
304 view_height_prev = screenObj->ySize_prev;
305 view_width_prev = screenObj->xSize_prev;
306
307 screenObj->ySize_prev = screenObj->ySize;
308 screenObj->xSize_prev = screenObj->xSize;
309
310 if (screenObj->yPos < screenObj->yPos_prev) {
311 y = screenObj->yPos_prev;
312 y2 = screenObj->yPos;
313
314 height1 = view_height_prev;
315 height2 = screenObj->ySize;
316 } else {
317 y = screenObj->yPos;
318 y2 = screenObj->yPos_prev;
319
320 height1 = screenObj->ySize;
321 height2 = view_height_prev;
322 }
323
324 if ((y2 - height2) > (y - height1)) {
325 height = height1;
326 } else {
327 height = y - y2 + height2;
328 }
329
330 if (screenObj->xPos > screenObj->xPos_prev) {
331 x = screenObj->xPos_prev;
332 x2 = screenObj->xPos;
333 width1 = view_width_prev;
334 width2 = screenObj->xSize;
335 } else {
336 x = screenObj->xPos;
337 x2 = screenObj->xPos_prev;
338 width1 = screenObj->xSize;
339 width2 = view_width_prev;
340 }
341
342 if ((x2 + width2) < (x + width1)) {
343 width = width1;
344 } else {
345 width = width2 + x2 - x;
346 }
347
348 if ((x + width) > 161) {
349 width = 161 - x;
350 }
351
352 if (1 < (height - y)) {
353 height = y + 1;
354 }
355
356 // render this block
357 int16 upperY = y - height + 1;
358 _gfx->render_Block(x, upperY, width, height);
359 }
360
showSprites(SpriteList & spriteList)361 void SpritesMgr::showSprites(SpriteList &spriteList) {
362 SpriteList::iterator iter;
363 ScreenObjEntry *screenObjPtr = NULL;
364
365 for (iter = spriteList.begin(); iter != spriteList.end(); ++iter) {
366 Sprite &sprite = *iter;
367 screenObjPtr = sprite.screenObjPtr;
368
369 showSprite(screenObjPtr);
370
371 if (screenObjPtr->stepTimeCount == screenObjPtr->stepTime) {
372 if ((screenObjPtr->xPos == screenObjPtr->xPos_prev) && (screenObjPtr->yPos == screenObjPtr->yPos_prev)) {
373 screenObjPtr->flags |= fDidntMove;
374 } else {
375 screenObjPtr->xPos_prev = screenObjPtr->xPos;
376 screenObjPtr->yPos_prev = screenObjPtr->yPos;
377 screenObjPtr->flags &= ~fDidntMove;
378 }
379 }
380 }
381 g_system->updateScreen();
382 //g_system->delayMillis(20);
383 }
384
showRegularSpriteList()385 void SpritesMgr::showRegularSpriteList() {
386 debugC(7, kDebugLevelSprites, "showRegularSpriteList()");
387 showSprites(_spriteRegularList);
388 }
389
showStaticSpriteList()390 void SpritesMgr::showStaticSpriteList() {
391 debugC(7, kDebugLevelSprites, "showStaticSpriteList()");
392 showSprites(_spriteStaticList);
393 }
394
showAllSpriteLists()395 void SpritesMgr::showAllSpriteLists() {
396 showSprites(_spriteStaticList);
397 showSprites(_spriteRegularList);
398 }
399
400 /**
401 * Show object and description
402 * This function shows an object from the player's inventory, displaying
403 * a message box with the object description.
404 * @param n Number of the object to show
405 */
showObject(int16 viewNr)406 void SpritesMgr::showObject(int16 viewNr) {
407 ScreenObjEntry screenObj;
408 uint8 *backgroundBuffer = NULL;
409
410 _vm->agiLoadResource(RESOURCETYPE_VIEW, viewNr);
411 _vm->setView(&screenObj, viewNr);
412
413 screenObj.ySize_prev = screenObj.celData->height;
414 screenObj.xSize_prev = screenObj.celData->width;
415 screenObj.xPos_prev = ((SCRIPT_WIDTH - 1) - screenObj.xSize) / 2;
416 screenObj.xPos = screenObj.xPos_prev;
417 screenObj.yPos_prev = SCRIPT_HEIGHT - 1;
418 screenObj.yPos = screenObj.yPos_prev;
419 screenObj.priority = 15;
420 screenObj.flags = fFixedPriority; // Original AGI did "| fFixedPriority" on uninitialized memory
421 screenObj.objectNr = 255; // ???
422
423 backgroundBuffer = (uint8 *)malloc(screenObj.xSize * screenObj.ySize * 2); // for visual + priority data
424
425 _gfx->block_save(screenObj.xPos, (screenObj.yPos - screenObj.ySize + 1), screenObj.xSize, screenObj.ySize, backgroundBuffer);
426 drawCel(&screenObj);
427 showSprite(&screenObj);
428
429 _vm->_text->messageBox((char *)_vm->_game.views[viewNr].description);
430
431 _gfx->block_restore(screenObj.xPos, (screenObj.yPos - screenObj.ySize + 1), screenObj.xSize, screenObj.ySize, backgroundBuffer);
432 showSprite(&screenObj);
433
434 free(backgroundBuffer);
435 }
436
437 /**
438 * Add view to picture.
439 * This function is used to implement the add.to.pic AGI command. It
440 * copies the specified cel from a view resource on the current picture.
441 * This cel is not a sprite, it can't be moved or removed.
442 * @param view number of view resource
443 * @param loop number of loop in the specified view resource
444 * @param cel number of cel in the specified loop
445 * @param x x coordinate to place the view
446 * @param y y coordinate to place the view
447 * @param pri priority to use
448 * @param mar if < 4, create a margin around the the base of the cel
449 */
addToPic(int16 viewNr,int16 loopNr,int16 celNr,int16 xPos,int16 yPos,int16 priority,int16 border)450 void SpritesMgr::addToPic(int16 viewNr, int16 loopNr, int16 celNr, int16 xPos, int16 yPos, int16 priority, int16 border) {
451 debugC(3, kDebugLevelSprites, "addToPic(view=%d, loop=%d, cel=%d, x=%d, y=%d, pri=%d, border=%d)", viewNr, loopNr, celNr, xPos, yPos, priority, border);
452
453 _vm->recordImageStackCall(ADD_VIEW, viewNr, loopNr, celNr, xPos, yPos, priority, border);
454
455 ScreenObjEntry *screenObj = &_vm->_game.addToPicView;
456 screenObj->objectNr = -1; // addToPic-view
457
458 _vm->setView(screenObj, viewNr);
459 _vm->setLoop(screenObj, loopNr);
460 _vm->setCel(screenObj, celNr);
461
462 screenObj->xSize_prev = screenObj->xSize;
463 screenObj->ySize_prev = screenObj->ySize;
464 screenObj->xPos_prev = xPos;
465 screenObj->xPos = xPos;
466 screenObj->yPos_prev = yPos;
467 screenObj->yPos = yPos;
468 screenObj->flags = fIgnoreObjects | fIgnoreHorizon | fFixedPriority;
469 screenObj->priority = 15;
470 _vm->fixPosition(screenObj);
471 if (priority == 0) {
472 screenObj->flags = fIgnoreHorizon;
473 }
474 screenObj->priority = priority;
475
476 eraseSprites();
477
478 // bugs related to this code: required by Gold Rush (see Sarien bug #587558)
479 if (screenObj->priority == 0) {
480 screenObj->priority = _gfx->priorityFromY(screenObj->yPos);
481 }
482 drawCel(screenObj);
483
484 if (border <= 3) {
485 // Create priority-box
486 addToPicDrawPriorityBox(screenObj, border);
487 }
488 buildAllSpriteLists();
489 drawAllSpriteLists();
490 showSprite(screenObj);
491 }
492
493 // bugs previously related to this:
494 // Sarien bug #247)
addToPicDrawPriorityBox(ScreenObjEntry * screenObj,int16 border)495 void SpritesMgr::addToPicDrawPriorityBox(ScreenObjEntry *screenObj, int16 border) {
496 int16 priorityFromY = _gfx->priorityFromY(screenObj->yPos);
497 int16 priorityHeight = 0;
498 int16 curY = 0;
499 int16 curX = 0;
500 int16 height = 0;
501 int16 width = 0;
502 int16 offsetX = 0;
503
504 // Figure out the height of the box
505 curY = screenObj->yPos;
506 do {
507 priorityHeight++;
508 if (curY <= 0)
509 break;
510 curY--;
511 } while (_gfx->priorityFromY(curY) == priorityFromY);
512
513 // box height may not be larger than the actual view
514 if (screenObj->ySize < priorityHeight)
515 priorityHeight = screenObj->ySize;
516
517 // now actually draw lower horizontal line
518 curY = screenObj->yPos;
519 curX = screenObj->xPos;
520
521 width = screenObj->xSize;
522 while (width) {
523 _gfx->putPixel(curX, curY, GFX_SCREEN_MASK_PRIORITY, 0, border);
524 curX++;
525 width--;
526 }
527
528 if (priorityHeight > 1) {
529 // Actual rectangle is needed
530 curY = screenObj->yPos;
531 curX = screenObj->xPos;
532 offsetX = screenObj->xSize - 1;
533
534 height = priorityHeight - 1;
535 while (height) {
536 curY--;
537 height--;
538 _gfx->putPixel(curX, curY, GFX_SCREEN_MASK_PRIORITY, 0, border); // left line
539 _gfx->putPixel(curX + offsetX, curY, GFX_SCREEN_MASK_PRIORITY, 0, border); // right line
540 }
541
542 // and finally the upper horizontal line
543 width = screenObj->xSize - 2;
544 curX++;
545 while (width > 0) {
546 _gfx->putPixel(curX, curY, GFX_SCREEN_MASK_PRIORITY, 0, border);
547 curX++;
548 width--;
549 }
550 }
551 }
552
553 } // End of namespace Agi
554