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