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