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  * Based on the original sources
23  *   Faery Tale II -- The Halls of the Dead
24  *   (c) 1993-1996 The Wyrmkeep Entertainment Co.
25  */
26 
27 #include "saga2/saga2.h"
28 #include "saga2/blitters.h"
29 #include "saga2/spelshow.h"
30 #include "saga2/player.h"
31 #include "saga2/sensor.h"
32 #include "saga2/mouseimg.h"
33 
34 namespace Saga2 {
35 
36 uint8 bubbleColorTable[] = { 1, 0, 0, 0 };
37 
38 DisplayNode                     *DisplayNodeList::head;
39 
40 bool                            centerActorIndicatorEnabled;
41 
42 /* ===================================================================== *
43    Imports
44  * ===================================================================== */
45 
46 extern WorldMapData *mapList;
47 
48 extern StaticPoint16 fineScroll;
49 
50 extern SpriteSet    *objectSprites,        // object sprites
51                     *spellSprites;        // spell effect sprites
52 
53 ActorAppearance     *tempAppearance;        // test structure
54 
55 /* ===================================================================== *
56    Test spell crap
57  * ===================================================================== */
58 
59 bool InCombatPauseKludge(void);
60 //void updateSpellPos( int32 delTime );
61 
62 //-----------------------------------------------------------------------
63 //	build the list of stuff to draw (like guns)
64 
65 uint8 identityColors[256] = {
66 	0,   1,   2,   3,   4,   5,   6,   7,   8,   9,  10,  11,  12,  13,  14,  15,
67 	16,  17,  18,  19,  20,  21,  22,  23,  24,  25,  26,  27,  28,  29,  30,  31,
68 	32,  33,  34,  35,  36,  37,  38,  39,  40,  41,  42,  43,  44,  45,  46,  47,
69 	48,  49,  50,  51,  52,  53,  54,  55,  56,  57,  58,  59,  60,  61,  62,  63,
70 	64,  65,  66,  67,  68,  69,  70,  71,  72,  73,  74,  75,  76,  77,  78,  79,
71 	80,  81,  82,  83,  84,  85,  86,  87,  88,  89,  90,  91,  92,  93,  94,  95,
72 	96,  97,  98,  99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111,
73 	112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127,
74 	128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143,
75 	144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159,
76 	160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175,
77 	176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191,
78 	192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207,
79 	208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223,
80 	224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
81 	240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255
82 };
83 
84 //-----------------------------------------------------------------------
85 //	build the list of stuff to draw (like guns)
86 
buildDisplayList(void)87 void buildDisplayList(void) {
88 	g_vm->_mainDisplayList->buildObjects(true);
89 	g_vm->_activeSpells->buildList();
90 }
91 
92 //-----------------------------------------------------------------------
93 //	Update all objects which have no motion task
94 
updateObjectAppearances(int32 deltaTime)95 void updateObjectAppearances(int32 deltaTime) {
96 	g_vm->_mainDisplayList->updateOStates(deltaTime);
97 #ifdef WEWANTSPELLSTOSTOPINCOMBAT
98 	if (!InCombatPauseKludge())
99 #endif
100 		g_vm->_activeSpells->updateStates(deltaTime);
101 }
102 
103 //-----------------------------------------------------------------------
104 //	Draw all sprites on the display list
105 
drawDisplayList(void)106 void drawDisplayList(void) {
107 	g_vm->_mainDisplayList->draw();
108 }
109 
init(uint16 s)110 void  DisplayNodeList::init(uint16 s) {
111 	for (int i = 0; i < s; i++) {
112 		displayList[i].efx = NULL;
113 		displayList[i].nextDisplayed = NULL;
114 		displayList[i].object = NULL;
115 		displayList[i].type = nodeTypeObject;
116 	}
117 }
118 //-----------------------------------------------------------------------
119 // DisplayNode stuff
120 
DisplayNode()121 DisplayNode::DisplayNode() {
122 	nextDisplayed = nullptr;
123 	sortDepth = 0;
124 	object = nullptr;
125 	flags = 0;                  // various flags
126 	type = nodeTypeObject;
127 	efx = nullptr;
128 }
129 
SpellPos(void)130 TilePoint DisplayNode::SpellPos(void) {
131 	if (efx)
132 		return efx->current;
133 	return Nowhere;
134 }
135 
136 
updateEffect(const int32 deltaTime)137 inline void DisplayNode::updateEffect(const int32 deltaTime) {
138 	if (efx)   efx->updateEffect(deltaTime);
139 }
140 
141 //-----------------------------------------------------------------------
142 //	Update router
143 
updateOStates(const int32 deltaTime)144 void DisplayNodeList::updateOStates(const int32 deltaTime) {
145 	if (count)
146 		for (uint16 i = 0; i < count; i++)
147 			displayList[i].updateObject(deltaTime);
148 }
149 
updateEStates(const int32 deltaTime)150 void DisplayNodeList::updateEStates(const int32 deltaTime) {
151 	if (count)
152 		for (uint16 i = 0; i < count; i++)
153 			displayList[i].updateEffect(deltaTime);
154 }
155 
156 //-----------------------------------------------------------------------
157 //	Draw router
158 
draw(void)159 void DisplayNodeList::draw(void) {
160 	DisplayNode     *dn;
161 	SpriteSet       *objectSet,
162 	                *spellSet;
163 
164 	objectSet = objectSprites;
165 	if (objectSet == NULL)
166 		error("Object sprites have been dumped!\n");
167 	spellSet = spellSprites;
168 	if (spellSet == NULL)
169 		error("Spell sprites have been dumped!\n");
170 
171 	for (dn = DisplayNodeList::head; dn; dn = dn->nextDisplayed) {
172 		if (dn->type == nodeTypeEffect)
173 			dn->drawEffect();
174 		else
175 			dn->drawObject();
176 	}
177 }
178 
179 //-----------------------------------------------------------------------
180 //	This routine searches through the map and finds the 64
181 //	objects or actors which are closest to the center view point.
182 
buildObjects(bool fromScratch)183 void DisplayNodeList::buildObjects(bool fromScratch) {
184 	GameObject      *sortList[maxDisplayed + 1];
185 	int16           distList[maxDisplayed + 1];
186 	int16           sortCount = 0;
187 	int16           i;
188 	int16           viewSize = kTileRectHeight;
189 
190 	//  Distance at which characters should be loaded.
191 	int16           loadDist = viewSize + viewSize / 2;
192 
193 	//  Run through list generated from previous incarnation,
194 	//  and put to bed all actors which are too far away from the
195 	//  view region.
196 
197 	for (i = 0; i < count; i++) {
198 		DisplayNode *dn = &displayList[i];
199 		GameObject  *obj = dn->object;
200 		TilePoint   objLoc = obj->getLocation();
201 		int16       dist;
202 
203 		//  Compute distance from object to screen center.
204 		dist =      ABS(viewCenter.u - objLoc.u)
205 		            +   ABS(viewCenter.v - objLoc.v);
206 
207 		//  Determine if the object is beyond the screen threshold
208 		if ((dist >= loadDist
209 		        ||  obj->IDParent() != currentWorld->thisID())) {
210 			//  Mark this object as being off-screen
211 			obj->setOnScreen(false);
212 
213 			//  If it's an actor
214 			if (isActor(obj)) {
215 				Actor       *a = (Actor *)obj;
216 
217 				//  Release the actor appearance if loaded
218 				if (a->_appearance != NULL) {
219 					ReleaseActorAppearance(a->_appearance);
220 					a->_appearance = NULL;
221 				}
222 			}
223 		}
224 	}
225 
226 	if (currentWorld == NULL) return;
227 
228 	DispRegionObjectIterator    iter(currentWorld, viewCenter, loadDist);
229 	GameObject *obj = nullptr;
230 	ObjectID id;
231 	int16 dist = 0;
232 	Actor *centerActor = getCenterActor();
233 
234 	if (fromScratch)
235 		//  Reset the list...
236 		DisplayNodeList::head = NULL;
237 
238 	for (id = iter.first(&obj, &dist);
239 	        id != Nothing;
240 	        id = iter.next(&obj, &dist)) {
241 		//  Of object is anywhere near screen center,
242 		//  then insert object into array, sorted by
243 		//  distance.
244 
245 		//  Also, don't add object to display list if it's
246 		//  invisible.
247 		if (!(obj->isInvisible())) {
248 			//  Special processing for actors to "wake up"
249 			if (isActor(id)) {
250 				Actor   *a = (Actor *)obj;
251 
252 				//  If actor is newly entered to the arena
253 				//  (appearance == NULL), then load the
254 				//  actor's appearance.
255 				if (a->_appearance == NULL) {
256 					a->_appearance =
257 					    LoadActorAppearance(a->_appearanceID, sprStandBank);
258 				}
259 			}
260 
261 			//  An insertion sort which has been clamped
262 			//  to a limited number of items.
263 			for (i = sortCount; i > 0;) {
264 				if (dist >= distList[i - 1]) break;
265 				i--;
266 				distList[i + 1] = distList[i];
267 				sortList[i + 1] = sortList[i];
268 			}
269 
270 			if (i < maxDisplayed) {
271 				distList[i] = dist;
272 				sortList[i] = obj;
273 
274 				if (sortCount < maxDisplayed) sortCount++;
275 			}
276 		}
277 	}
278 
279 	//  Build display nodes for each of the objects.
280 
281 	count = sortCount;
282 
283 	for (i = 0; i < sortCount; i++) {
284 		DisplayNode *dn = &displayList[i];
285 		GameObject  *ob = sortList[i];
286 		DisplayNode **search;
287 		TilePoint oLoc = ob->getLocation();
288 		dn->nextDisplayed = NULL;
289 		dn->object = ob;
290 
291 		dn->type = nodeTypeObject;
292 
293 		dn->flags = 0;
294 		if (centerActorIndicatorEnabled
295 		        &&  isActor(dn->object)
296 		        && ((Actor *)dn->object) == centerActor)
297 			dn->flags |= DisplayNode::displayIndicator;
298 
299 		//  Various test data
300 //		dn->spriteFrame = 0;
301 
302 		//  Convert object coordinates to screen coords
303 		TileToScreenCoords(oLoc, dn->screenCoords);
304 
305 		//  REM: At this point we could reject some more off-screen
306 		//  objects.
307 
308 		//  Set the sort depth for this object
309 		dn->sortDepth = dn->screenCoords.y + oLoc.z / 2;
310 
311 		//  Find where we belong on the sorted list
312 		for (search = &DisplayNodeList::head;
313 		        *search;
314 		        search = &(*search)->nextDisplayed) {
315 			if ((*search)->sortDepth >= dn->sortDepth) break;
316 		}
317 
318 		//  Insert into the sorted list
319 		dn->nextDisplayed = *search;
320 		*search = dn;
321 	}
322 }
323 
324 //-----------------------------------------------------------------------
325 //	Update normal objects
326 
updateObject(const int32 deltaTime)327 void DisplayNode::updateObject(const int32 deltaTime) {
328 	GameObject  *obj = object;
329 
330 	if (obj->isMoving()) return;
331 
332 	if (isActor(obj)) {
333 		Actor   *a = (Actor *)obj;
334 
335 		a->updateAppearance(deltaTime);
336 	}
337 }
338 
339 //-----------------------------------------------------------------------
340 //	Draw sprites for normal objects
341 
342 #if DINO
343 const int       maxSpriteWidth = 320,
344                 maxSpriteHeight = 320,
345                 maxSpriteBaseLine = 50;
346 #else
347 const int       maxSpriteWidth = 32,
348                 maxSpriteHeight = 120,
349                 maxSpriteBaseLine = 16;
350 #endif
351 
drawObject(void)352 void DisplayNode::drawObject(void) {
353 	ColorTable      mainColors,             // colors for object
354 	                leftColors,             // colors for left-hand object
355 	                rightColors;            // colors for right-hand object
356 	SpriteComponent scList[3],
357 	                *sc;
358 	int16           bodyIndex,              // drawing order of body
359 	                leftIndex,              // drawing order of left
360 	                rightIndex,             // drawing order of right
361 	                partCount;              // number of sprite parts
362 	bool            ghostIt = false;
363 	GameObject  *obj = object;
364 	ProtoObj    *proto = obj->proto();
365 	Point16     drawPos;
366 	SpriteSet   *ss;
367 	Sprite      *bodySprite;
368 	ActorAppearance *aa = nullptr;
369 	SpriteSet   *sprPtr = nullptr;
370 
371 	TilePoint   objCoords = obj->getLocation(),
372 	            tCoords,
373 	            mCoords;
374 	MetaTile    *mt;
375 	RipTable    *rt;
376 
377 	tCoords.u = (objCoords.u >> kTileUVShift) & kPlatMask;
378 	tCoords.v = (objCoords.v >> kTileUVShift) & kPlatMask;
379 	mCoords.u = objCoords.u >> (kTileUVShift + kPlatShift);
380 	mCoords.v = objCoords.v >> (kTileUVShift + kPlatShift);
381 	mCoords.z = 0;
382 
383 	//  Do not display objects that are on a ripped roof
384 	if ((mt = mapList[g_vm->_currentMapNum].lookupMeta(mCoords)) != NULL) {
385 		if ((rt = mt->ripTable(g_vm->_currentMapNum)) != NULL) {
386 			if (objCoords.z >= rt->zTable[tCoords.u][tCoords.v]) {
387 				//  Disable hit-test on the object's box
388 				hitBox.width = -1;
389 				hitBox.height = -1;
390 
391 				obj->setOnScreen(false);
392 				obj->setObscured(false);
393 				return;
394 			}
395 		}
396 	}
397 
398 	TileToScreenCoords(objCoords, screenCoords);
399 
400 	drawPos.x = screenCoords.x + fineScroll.x;
401 	drawPos.y = screenCoords.y + fineScroll.y;
402 
403 	//  If it's an object, then the drawing is fairly straight
404 	//  forward.
405 	if (isObject(obj)) {
406 		ObjectSpriteInfo    sprInfo;
407 
408 		//  Reject any sprites which fall off the edge of the screen.
409 		if (drawPos.x < -32
410 		        || drawPos.x > kTileRectX + kTileRectWidth + 32
411 		        || drawPos.y < -32
412 		        || drawPos.y > kTileRectY + kTileRectHeight + 100) {
413 			//  Disable hit-test on the object's box
414 			hitBox.width = -1;
415 			hitBox.height = -1;
416 
417 			//  Mark as being off screen
418 			obj->setOnScreen(false);
419 			obj->setObscured(false);
420 			return;
421 		}
422 
423 		if (!obj->isOnScreen()) {
424 			SenseInfo   info;
425 
426 			obj->setOnScreen(true);
427 
428 			if (getCenterActor()->canSenseSpecificObject(info, maxSenseRange, obj->thisID()))
429 				obj->setSightedByCenter(true);
430 			else {
431 				obj->setSightedByCenter(false);
432 				obj->setObscured(false);
433 			}
434 
435 			obj->_data.sightCtr = 5;
436 		} else {
437 			if (--obj->_data.sightCtr == 0) {
438 				SenseInfo   info;
439 
440 				if (getCenterActor()->canSenseSpecificObject(info, maxSenseRange, obj->thisID()))
441 					obj->setSightedByCenter(true);
442 				else {
443 					obj->setSightedByCenter(false);
444 					obj->setObscured(false);
445 				}
446 
447 				obj->_data.sightCtr = 5;
448 			}
449 		}
450 
451 		//  Figure out which sprite to show
452 		sprInfo = proto->getSprite(obj, ProtoObj::objOnGround);
453 
454 		//  Build the color translation table for the object
455 		obj->getColorTranslation(mainColors);
456 
457 		//  Fill in the SpriteComponent structure
458 		sc = &scList[0];
459 		sc->sp = sprInfo.sp;
460 		sc->offset.x = scList->offset.y = 0;
461 		sc->colorTable = mainColors;
462 
463 		sc->flipped = sprInfo.flipped;
464 
465 		partCount = 1;
466 		bodyIndex = 0;
467 
468 	} else {
469 		Actor           *a = (Actor *)obj;
470 		ActorAnimation  *anim;
471 		ActorPose       *pose;
472 		int16           poseFlags;
473 
474 		if (!a->isDead() && objCoords.z < -proto->height - 8) {
475 			//  The actor is under water so display the bubbles sprite
476 			drawPos.y += objCoords.z;
477 			objCoords.z = 0;
478 
479 			//  Disable hit-test on the object's box
480 			hitBox.width = -1;
481 			hitBox.height = -1;
482 
483 			//  Reject any sprites which fall off the edge of the screen.
484 			if (drawPos.x < -maxSpriteWidth
485 			        || drawPos.x > kTileRectX + kTileRectWidth + maxSpriteWidth
486 			        || drawPos.y < -maxSpriteBaseLine
487 			        || drawPos.y > kTileRectY + kTileRectHeight + maxSpriteHeight) {
488 				//  Mark as being off screen
489 				a->setOnScreen(false);
490 				a->setObscured(false);
491 				return;
492 			}
493 
494 			buildColorTable(
495 			    mainColors,
496 			    bubbleColorTable,
497 			    ARRAYSIZE(bubbleColorTable));
498 
499 			if (a->_kludgeCount < 0 || ++a->_kludgeCount >= kBubbleSpriteCount)
500 				a->_kludgeCount = 0;
501 
502 			sc = &scList[0];
503 			sc->sp = spellSprites->sprite(
504 			             kBaseBubbleSpriteIndex + a->_kludgeCount);
505 			sc->offset.x = scList->offset.y = 0;
506 			sc->colorTable = mainColors;
507 			sc->flipped = false;
508 
509 			partCount = 1;
510 			bodyIndex = 0;
511 		} else {
512 			//  Reject any sprites which fall off the edge of the screen.
513 			if (drawPos.x < -maxSpriteWidth
514 			        || drawPos.x > kTileRectX + kTileRectWidth + maxSpriteWidth
515 			        || drawPos.y < -maxSpriteBaseLine
516 			        || drawPos.y > kTileRectY + kTileRectHeight + maxSpriteHeight) {
517 				//  Disable hit-test on the object's box
518 				hitBox.width = -1;
519 				hitBox.height = -1;
520 
521 				//  Mark as being off screen
522 				a->setOnScreen(false);
523 				a->setObscured(false);
524 				return;
525 			}
526 
527 			if (a->hasEffect(actorInvisible)) {
528 				if (!isPlayerActor(a)
529 				        &&  !(getCenterActor()->hasEffect(actorSeeInvis))) {
530 					hitBox.width = -1;
531 					hitBox.height = -1;
532 					return;
533 				}
534 				ghostIt = true;
535 			}
536 
537 			if (!a->isOnScreen()) {
538 				SenseInfo   info;
539 
540 				a->setOnScreen(true);
541 
542 				if (getCenterActor()->canSenseSpecificActor(info, maxSenseRange, a))
543 					a->setSightedByCenter(true);
544 				else {
545 					a->setSightedByCenter(false);
546 					a->setObscured(false);
547 				}
548 
549 				a->_data.sightCtr = 5;
550 			} else {
551 				if (--a->_data.sightCtr == 0) {
552 					SenseInfo   info;
553 
554 					if (getCenterActor()->canSenseSpecificActor(info, maxSenseRange, a))
555 						a->setSightedByCenter(true);
556 					else {
557 						a->setSightedByCenter(false);
558 						a->setObscured(false);
559 					}
560 
561 					a->_data.sightCtr = 5;
562 				}
563 			}
564 
565 			aa = a->_appearance;
566 
567 			if (aa == nullptr)
568 				return;
569 
570 			//  Fetch the animation series, and determine which
571 			//  pose in the series is the current one.
572 			anim = aa->animation(a->_currentAnimation);
573 			pose = aa->pose(anim, a->_currentFacing, a->_currentPose);
574 
575 			if (anim == nullptr)
576 				return;
577 
578 			assert(anim->start[0] <  10000);
579 			assert(anim->start[1] <  10000);
580 			assert(anim->start[2] <  10000);
581 
582 			assert(pose->rightObjectOffset.x < 1000);
583 			assert(pose->rightObjectOffset.x > -1000);
584 			assert(pose->rightObjectOffset.y < 1000);
585 			assert(pose->rightObjectOffset.y > -1000);
586 
587 			assert(pose->leftObjectOffset.x < 1000);
588 			assert(pose->leftObjectOffset.x > -1000);
589 			assert(pose->leftObjectOffset.y < 1000);
590 			assert(pose->leftObjectOffset.y > -1000);
591 
592 			//          washHandle( aa->spriteBanks[pose->actorFrameBank] );
593 
594 			//  If the new sprite is loaded, then we can go
595 			//  ahead and show it. If it's not, then we can
596 			//  pause for a frame or two until it is loaded.
597 			//  However, if the previous frame isn't loaded
598 			//  either, then we need to go ahead and force
599 			//  the new frame to finish loaded (handled by
600 			//  lockResource())
601 			if (aa->isBankLoaded(pose->actorFrameBank)
602 			        || !aa->isBankLoaded(a->_poseInfo.actorFrameBank)) {
603 				ActorPose   pTemp = *pose;
604 
605 				//  Initiate a load of the sprite bank needed.
606 				/*  if (!RHandleLoading(
607 				            (RHANDLE)(aa->spriteBanks[pose->actorFrameBank]) ))
608 				    {
609 				        aa->loadSpriteBanks( (1<<pose->actorFrameBank) );
610 				    } */
611 
612 				aa->requestBank(pose->actorFrameBank);
613 
614 				//  Indicate that animation is OK.
615 				a->_animationFlags &= ~animateNotLoaded;
616 
617 				//  Set up which bank and frame to use.
618 				a->_poseInfo = pTemp;
619 			} else {
620 				//  Indicate that animation isn't loaded
621 				a->_animationFlags |= animateNotLoaded;
622 
623 				//  Initiate a load of the sprite bank needed.
624 				/*  if (!RHandleLoading(
625 				            (RHANDLE)(aa->spriteBanks[pose->actorFrameBank]) ))
626 				    {
627 				        aa->loadSpriteBanks( (1<<pose->actorFrameBank) );
628 				    }
629 				    */
630 				aa->requestBank(pose->actorFrameBank);
631 			}
632 
633 			//  For actors, start by assuming that the actor has
634 			//  nothing in either hand.
635 
636 			bodyIndex = 0;
637 			rightIndex = leftIndex = -2;
638 			partCount = 1;
639 			poseFlags = a->_poseInfo.flags;
640 
641 			a->getColorTranslation(mainColors);
642 
643 			//  Do various tests to see what the actor is
644 			//  carrying in each hand, and what drawing
645 			//  order should be used for these objects.
646 
647 			if (a->_leftHandObject != Nothing) {
648 				partCount++;
649 
650 				if (poseFlags & ActorPose::leftObjectInFront) {
651 					leftIndex = 1;
652 				} else {
653 					leftIndex = 0;
654 					bodyIndex = 1;
655 				}
656 			}
657 
658 			if (a->_rightHandObject != Nothing) {
659 				partCount++;
660 
661 				if (poseFlags & ActorPose::rightObjectInFront) {
662 					if (leftIndex == 1
663 					        &&  poseFlags & ActorPose::leftOverRight) {
664 						leftIndex = 2;
665 						rightIndex = 1;
666 					} else {
667 						rightIndex = partCount - 1;
668 					}
669 				} else {
670 					if (leftIndex == 0
671 					        &&  poseFlags & ActorPose::leftOverRight) {
672 						rightIndex = 0;
673 						leftIndex = 1;
674 						bodyIndex = 2;
675 					} else {
676 						rightIndex = 0;
677 						bodyIndex++;
678 						if (leftIndex != -2) leftIndex++;
679 					}
680 				}
681 			}
682 
683 
684 			//  REM: Locking bug...
685 
686 			//          ss = (SpriteSet *)RLockHandle( aa->sprites );
687 			sprPtr = aa->spriteBanks[a->_poseInfo.actorFrameBank];
688 			ss = sprPtr;
689 			if (ss == nullptr)
690 				return;
691 
692 			//  Fill in the SpriteComponent structure for body
693 			sc = &scList[bodyIndex];
694 			assert(a->_poseInfo.actorFrameIndex < ss->count);
695 			sc->sp = ss->sprite(a->_poseInfo.actorFrameIndex);
696 			sc->offset.x = sc->offset.y = 0;
697 			//  Color remapping info
698 			sc->colorTable = mainColors;
699 			//          sc->colorTable = aa->schemeList ? mainColors : identityColors;
700 			sc->flipped = (poseFlags & ActorPose::actorFlipped);
701 
702 			assert(sc->sp != NULL);
703 			assert(sc->sp->size.x > 0);
704 			assert(sc->sp->size.y > 0);
705 			assert(sc->sp->size.x < 255);
706 			assert(sc->sp->size.y < 255);
707 
708 			//  If we were carrying something in the left hand,
709 			//  then fill in the component structure for it.
710 			if (leftIndex >= 0) {
711 				GameObject *ob = GameObject::objectAddress(a->_leftHandObject);
712 				ProtoObj *prot = ob->proto();
713 
714 				ob->getColorTranslation(leftColors);
715 
716 				sc = &scList[leftIndex];
717 				sc->sp =    prot->getOrientedSprite(
718 				                ob,
719 				                a->_poseInfo.leftObjectIndex);
720 				assert(sc->sp != NULL);
721 				sc->offset = a->_poseInfo.leftObjectOffset;
722 				assert(sc->offset.x < 1000);
723 				assert(sc->offset.x > -1000);
724 				assert(sc->offset.y < 1000);
725 				assert(sc->offset.y > -1000);
726 				sc->colorTable = leftColors;
727 				sc->flipped = (poseFlags & ActorPose::leftObjectFlipped);
728 			}
729 
730 			//  If we were carrying something in the right hand,
731 			//  then fill in the component structure for it.
732 			if (rightIndex >= 0) {
733 				GameObject *ob = GameObject::objectAddress(a->_rightHandObject);
734 				ProtoObj *prot = ob->proto();
735 
736 				ob->getColorTranslation(rightColors);
737 
738 				sc = &scList[rightIndex];
739 				sc->sp =    prot->getOrientedSprite(
740 				                ob,
741 				                a->_poseInfo.rightObjectIndex);
742 				assert(sc->sp != NULL);
743 				assert(sc->sp->size.x > 0);
744 				assert(sc->sp->size.y > 0);
745 				assert(sc->sp->size.x < 255);
746 				assert(sc->sp->size.y < 255);
747 				sc->offset = a->_poseInfo.rightObjectOffset;
748 				assert(sc->offset.x < 1000);
749 				assert(sc->offset.x > -1000);
750 				assert(sc->offset.y < 1000);
751 				assert(sc->offset.y > -1000);
752 				sc->colorTable = rightColors;
753 				sc->flipped = (poseFlags & ActorPose::rightObjectFlipped);
754 			}
755 		}
756 	}
757 
758 	if (!ghostIt && obj->isGhosted())
759 		ghostIt = true;
760 
761 	int16       effectFlags = 0;
762 	bool        obscured;
763 
764 	if (ghostIt) effectFlags |= sprFXGhosted;
765 
766 	if (obj->isSightedByCenter() && objRoofRipped(obj))
767 		effectFlags |= sprFXGhostIfObscured;
768 
769 	effectFlags |= sprFXTerrainMask;
770 
771 	DrawCompositeMaskedSprite(
772 	    g_vm->_backPort,
773 	    scList,
774 	    partCount,
775 	    drawPos,
776 	    objCoords,
777 	    effectFlags,
778 	    &obscured);
779 
780 	if (effectFlags & sprFXGhostIfObscured)
781 		obj->setObscured(obscured);
782 
783 	//  Record the extent box that the sprite was drawn
784 	//  at, in order to facilitate mouse picking functions
785 	//  later on in the event loop.
786 	bodySprite = scList[bodyIndex].sp;
787 	hitBox.x =      drawPos.x
788 	                + (scList[bodyIndex].flipped
789 	                   ?   -bodySprite->size.x - bodySprite->offset.x
790 	                   :   bodySprite->offset.x)
791 	                -   fineScroll.x;
792 	hitBox.y = drawPos.y + bodySprite->offset.y - fineScroll.y;
793 	hitBox.width = bodySprite->size.x;
794 	hitBox.height = bodySprite->size.y;
795 
796 	if (flags & displayIndicator) {
797 		Point16     indicatorCoords;
798 		gPixelMap   &indicator = *mouseCursors[kMouseCenterActorIndicatorImage];
799 
800 		indicatorCoords.x = hitBox.x + fineScroll.x + (hitBox.width - indicator.size.x) / 2;
801 		indicatorCoords.y = hitBox.y + fineScroll.y - indicator.size.y - 2;
802 
803 		TBlit(g_vm->_backPort.map, &indicator, indicatorCoords.x, indicatorCoords.y);
804 	}
805 }
806 
807 //-----------------------------------------------------------------------
808 //	Do mouse hit-test on objects
809 
pickObject(const StaticPoint32 & mouse,StaticTilePoint & objPos)810 ObjectID pickObject(const StaticPoint32 &mouse, StaticTilePoint &objPos) {
811 	DisplayNode     *dn;
812 	ObjectID        result = Nothing;
813 	int32           dist = maxint32;
814 	SpriteSet       *objectSet;
815 
816 	objectSet = objectSprites;
817 	if (objectSet == NULL)
818 		error("Object sprites have been dumped!");
819 
820 	for (dn = DisplayNodeList::head; dn; dn = dn->nextDisplayed) {
821 		if (dn->type == nodeTypeObject) {
822 			GameObject  *obj = dn->object;
823 
824 			if (obj->parent() == currentWorld && dn->hitBox.ptInside(mouse.x, mouse.y)) {
825 				TilePoint   loc = obj->getLocation();
826 				int32       newDist = loc.u + loc.v;
827 
828 				if (newDist < dist) {
829 					Point16     testPoint;
830 					SpriteSet   *ss;
831 					Sprite      *spr;
832 					ActorAppearance *aa = nullptr;
833 					SpriteSet   *sprPtr = nullptr;
834 					bool        flipped = true;
835 
836 					testPoint.x = mouse.x - dn->hitBox.x;
837 					testPoint.y = mouse.y - dn->hitBox.y;
838 
839 					//  If it's an object, then the test is fairly straight
840 					//  forward.
841 					if (isObject(obj)) {
842 						ObjectSpriteInfo    sprInfo;
843 
844 						sprInfo = obj->proto()->getSprite(obj, ProtoObj::objOnGround);
845 						spr = sprInfo.sp;
846 						flipped = sprInfo.flipped;
847 					} else {
848 						Actor   *a = (Actor *)obj;
849 
850 						aa = a->_appearance;
851 
852 						if (aa == NULL) continue;
853 
854 						sprPtr = aa->spriteBanks[a->_poseInfo.actorFrameBank];
855 						ss = sprPtr;
856 						if (ss == nullptr)
857 							continue;
858 
859 						spr = ss->sprite(a->_poseInfo.actorFrameIndex);
860 						flipped =
861 						    (a->_poseInfo.flags & ActorPose::actorFlipped) ? 1 : 0;
862 					}
863 
864 					if (GetSpritePixel(spr, flipped, testPoint)) {
865 						dist = newDist;
866 						result = obj->thisID();
867 						objPos.set(loc.u, loc.v, loc.z);
868 						objPos.z += MAX(-spr->offset.y - testPoint.y, 0);
869 					} else if (result == Nothing) { //  If no object found yet
870 						Point16     testPoint2;
871 						int16       minX, maxX;
872 
873 						//  Try checking a wider area for mouse hit
874 
875 						testPoint2.y = testPoint.y;
876 						minX = MAX(0, testPoint.x - 6);
877 						maxX = MIN(dn->hitBox.width - 1, testPoint.x + 6);
878 
879 						//  scan a horizontal strip of the character for a hit.
880 						//  If we find a hit, go ahead and set result anyway
881 						//  If we later find a real hit, then it will overwrite
882 						//  the results of this one.
883 						for (testPoint2.x = minX; testPoint2.x <= maxX; testPoint2.x++) {
884 							if (GetSpritePixel(spr, flipped, testPoint2)) {
885 								result = obj->thisID();
886 								objPos.set(loc.u, loc.v, loc.z);
887 								objPos.z += MAX(-spr->offset.y - testPoint.y, 0);
888 								break;
889 							}
890 						}
891 					}
892 				}
893 			}
894 		}
895 	}
896 
897 	return result;
898 }
899 
900 //-----------------------------------------------------------------------
901 //	Adds spell effects into the dispplay list
902 //
903 //  NOTE : all spell effects are currently placed behind any real stuff
904 //         they can also easily be placed in front
905 
buildEffects(bool)906 void DisplayNodeList::buildEffects(bool) {
907 	if (count) {
908 		for (int i = 0; i < count; i++) {
909 			DisplayNode *dn = DisplayNodeList::head;
910 
911 			if (displayList[i].efx->isHidden() || displayList[i].efx->isDead())
912 				continue;
913 			// make sure it knows it's not a real object
914 			displayList[i].type = nodeTypeEffect;
915 
916 			displayList[i].sortDepth = displayList[i].efx->screenCoords.y + displayList[i].efx->current.z / 2;
917 			if (dn) {
918 				int32 sd = displayList[i].sortDepth;
919 				while (dn->nextDisplayed && dn->nextDisplayed->sortDepth <= sd)
920 					dn = dn->nextDisplayed;
921 			}
922 
923 			if (dn == DisplayNodeList::head) {
924 				displayList[i].nextDisplayed = DisplayNodeList::head;
925 				DisplayNodeList::head = &displayList[i];
926 			} else {
927 				displayList[i].nextDisplayed = dn->nextDisplayed;
928 				dn->nextDisplayed = &displayList[i];
929 			}
930 
931 		}
932 	}
933 }
934 
dissipated(void)935 bool DisplayNodeList::dissipated(void) {
936 	if (count) {
937 		for (int i = 0; i < count; i++) {
938 			if (displayList[i].efx && !displayList[i].efx->isDead())
939 				return false;
940 		}
941 	}
942 	return true;
943 }
944 
945 //-----------------------------------------------------------------------
946 //	Draw sprites for spell effects
947 //
948 //  NOTE : all spell effects currently use the center actor for their
949 //         sprites.
950 
drawEffect(void)951 void DisplayNode::drawEffect(void) {
952 	if (efx)   efx->drawEffect();
953 }
954 
drawEffect(void)955 void Effectron::drawEffect(void) {
956 	ColorTable      eColors;                // colors for object
957 	bool obscured = false;
958 	Point16         drawPos;
959 	TilePoint       objCoords = SpellPos();
960 	SpriteComponent scList[3],
961 	                *sc;
962 
963 	if (isHidden() || isDead())
964 		return;
965 
966 	drawPos.x = screenCoords.x + fineScroll.x;
967 	drawPos.y = screenCoords.y + fineScroll.y;
968 
969 	//  Reject any sprites which fall off the edge of the screen.
970 	if (drawPos.x < -32
971 	        || drawPos.x > kTileRectX + kTileRectWidth + 32
972 	        || drawPos.y < -32
973 	        || drawPos.y > kTileRectY + kTileRectHeight + 100) {
974 		//  Disable hit-test on the object's box
975 		hitBox.width = -1;
976 		hitBox.height = -1;
977 		return;
978 	}
979 
980 	TileToScreenCoords(objCoords, screenCoords);
981 
982 	sc = &scList[0];
983 	//sc->sp = (*spellSprites)->sprite( spriteID() );
984 	sc->sp = spellSprites->sprite(spriteID());   //tempSpellSpriteIDs[rand()%39] );
985 	sc->offset.x = scList->offset.y = 0;
986 
987 	(*g_vm->_sdpList)[parent->spell]->
988 	getColorTranslation(eColors, this);
989 
990 	sc->colorTable = eColors;
991 	sc->flipped = false;
992 
993 	obscured = (visiblePixelsInSprite(sc->sp,
994 	                                  sc->flipped,
995 	                                  sc->colorTable,
996 	                                  drawPos,
997 	                                  current,
998 	                                  0) <= 5);
999 
1000 	DrawCompositeMaskedSprite(
1001 	    g_vm->_backPort,
1002 	    scList,
1003 	    1,
1004 	    drawPos,
1005 	    objCoords,
1006 	    ((obscured) &&    //objectFlags & GameObject::objectObscured ) &&
1007 	     0
1008 	     ? sprFXGhosted : sprFXTerrainMask));
1009 
1010 }
1011 
1012 /* ===================================================================== *
1013    Misc. functions
1014  * ===================================================================== */
1015 
1016 //-----------------------------------------------------------------------
1017 //	Enable or disable the center actor indicator
1018 
setCenterActorIndicator(bool enabled)1019 void setCenterActorIndicator(bool enabled) {
1020 	centerActorIndicatorEnabled = enabled;
1021 }
1022 
1023 } // end of namespace Saga2
1024