scummvm/engines/saga2/dispnode.cpp

1025 lines
29 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*
* Based on the original sources
* Faery Tale II -- The Halls of the Dead
* (c) 1993-1996 The Wyrmkeep Entertainment Co.
*/
#include "saga2/saga2.h"
#include "saga2/blitters.h"
#include "saga2/spelshow.h"
#include "saga2/player.h"
#include "saga2/sensor.h"
#include "saga2/mouseimg.h"
namespace Saga2 {
uint8 bubbleColorTable[] = { 1, 0, 0, 0 };
DisplayNode *DisplayNodeList::head;
bool centerActorIndicatorEnabled;
/* ===================================================================== *
Imports
* ===================================================================== */
extern int16 currentMapNum;
extern WorldMapData *mapList;
extern StaticPoint16 fineScroll;
extern SpriteSet *objectSprites, // object sprites
*spellSprites; // spell effect sprites
ActorAppearance *tempAppearance; // test structure
/* ===================================================================== *
Test spell crap
* ===================================================================== */
bool InCombatPauseKludge(void);
//void updateSpellPos( int32 delTime );
//-----------------------------------------------------------------------
// build the list of stuff to draw (like guns)
uint8 identityColors[256] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111,
112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127,
128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143,
144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159,
160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175,
176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191,
192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207,
208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223,
224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255
};
//-----------------------------------------------------------------------
// build the list of stuff to draw (like guns)
void buildDisplayList(void) {
g_vm->_mainDisplayList->buildObjects(true);
g_vm->_activeSpells->buildList();
}
//-----------------------------------------------------------------------
// Update all objects which have no motion task
void updateObjectAppearances(int32 deltaTime) {
g_vm->_mainDisplayList->updateOStates(deltaTime);
#ifdef WEWANTSPELLSTOSTOPINCOMBAT
if (!InCombatPauseKludge())
#endif
g_vm->_activeSpells->updateStates(deltaTime);
}
//-----------------------------------------------------------------------
// Draw all sprites on the display list
void drawDisplayList(void) {
g_vm->_mainDisplayList->draw();
}
void DisplayNodeList::init(uint16 s) {
for (int i = 0; i < s; i++) {
displayList[i].efx = NULL;
displayList[i].nextDisplayed = NULL;
displayList[i].object = NULL;
displayList[i].type = nodeTypeObject;
}
}
//-----------------------------------------------------------------------
// DisplayNode stuff
DisplayNode::DisplayNode() {
nextDisplayed = nullptr;
sortDepth = 0;
object = nullptr;
flags = 0; // various flags
type = nodeTypeObject;
efx = nullptr;
}
TilePoint DisplayNode::SpellPos(void) {
if (efx)
return efx->current;
return Nowhere;
}
inline void DisplayNode::updateEffect(const int32 deltaTime) {
if (efx) efx->updateEffect(deltaTime);
}
//-----------------------------------------------------------------------
// Update router
void DisplayNodeList::updateOStates(const int32 deltaTime) {
if (count)
for (uint16 i = 0; i < count; i++)
displayList[i].updateObject(deltaTime);
}
void DisplayNodeList::updateEStates(const int32 deltaTime) {
if (count)
for (uint16 i = 0; i < count; i++)
displayList[i].updateEffect(deltaTime);
}
//-----------------------------------------------------------------------
// Draw router
void DisplayNodeList::draw(void) {
DisplayNode *dn;
SpriteSet *objectSet,
*spellSet;
objectSet = objectSprites;
if (objectSet == NULL)
error("Object sprites have been dumped!\n");
spellSet = spellSprites;
if (spellSet == NULL)
error("Spell sprites have been dumped!\n");
for (dn = DisplayNodeList::head; dn; dn = dn->nextDisplayed) {
if (dn->type == nodeTypeEffect)
dn->drawEffect();
else
dn->drawObject();
}
}
//-----------------------------------------------------------------------
// This routine searches through the map and finds the 64
// objects or actors which are closest to the center view point.
void DisplayNodeList::buildObjects(bool fromScratch) {
GameObject *sortList[maxDisplayed + 1];
int16 distList[maxDisplayed + 1];
int16 sortCount = 0;
int16 i;
int16 viewSize = kTileRectHeight;
// Distance at which characters should be loaded.
int16 loadDist = viewSize + viewSize / 2;
// Run through list generated from previous incarnation,
// and put to bed all actors which are too far away from the
// view region.
for (i = 0; i < count; i++) {
DisplayNode *dn = &displayList[i];
GameObject *obj = dn->object;
TilePoint objLoc = obj->getLocation();
int16 dist;
// Compute distance from object to screen center.
dist = ABS(viewCenter.u - objLoc.u)
+ ABS(viewCenter.v - objLoc.v);
// Determine if the object is beyond the screen threshold
if ((dist >= loadDist
|| obj->IDParent() != currentWorld->thisID())) {
// Mark this object as being off-screen
obj->setOnScreen(false);
// If it's an actor
if (isActor(obj)) {
Actor *a = (Actor *)obj;
// Release the actor appearance if loaded
if (a->appearance != NULL) {
ReleaseActorAppearance(a->appearance);
a->appearance = NULL;
}
}
}
}
if (currentWorld == NULL) return;
DispRegionObjectIterator iter(currentWorld, viewCenter, loadDist);
GameObject *obj;
ObjectID id;
int16 dist;
Actor *centerActor = getCenterActor();
if (fromScratch)
// Reset the list...
DisplayNodeList::head = NULL;
for (id = iter.first(&obj, &dist);
id != Nothing;
id = iter.next(&obj, &dist)) {
// Of object is anywhere near screen center,
// then insert object into array, sorted by
// distance.
// Also, don't add object to display list if it's
// invisible.
if (!(obj->isInvisible())) {
// Special processing for actors to "wake up"
if (isActor(id)) {
Actor *a = (Actor *)obj;
// If actor is newly entered to the arena
// (appearance == NULL), then load the
// actor's appearance.
if (a->appearance == NULL) {
a->appearance =
LoadActorAppearance(a->appearanceID, sprStandBank);
}
}
// An insertion sort which has been clamped
// to a limited number of items.
for (i = sortCount; i > 0;) {
if (dist >= distList[i - 1]) break;
i--;
distList[i + 1] = distList[i];
sortList[i + 1] = sortList[i];
}
if (i < maxDisplayed) {
distList[i] = dist;
sortList[i] = obj;
if (sortCount < maxDisplayed) sortCount++;
}
}
}
// Build display nodes for each of the objects.
count = sortCount;
for (i = 0; i < sortCount; i++) {
DisplayNode *dn = &displayList[i];
GameObject *ob = sortList[i];
DisplayNode **search;
TilePoint oLoc = ob->getLocation();
dn->nextDisplayed = NULL;
dn->object = ob;
dn->type = nodeTypeObject;
dn->flags = 0;
if (centerActorIndicatorEnabled
&& isActor(dn->object)
&& ((Actor *)dn->object) == centerActor)
dn->flags |= DisplayNode::displayIndicator;
// Various test data
// dn->spriteFrame = 0;
// Convert object coordinates to screen coords
TileToScreenCoords(oLoc, dn->screenCoords);
// REM: At this point we could reject some more off-screen
// objects.
// Set the sort depth for this object
dn->sortDepth = dn->screenCoords.y + oLoc.z / 2;
// Find where we belong on the sorted list
for (search = &DisplayNodeList::head;
*search;
search = &(*search)->nextDisplayed) {
if ((*search)->sortDepth >= dn->sortDepth) break;
}
// Insert into the sorted list
dn->nextDisplayed = *search;
*search = dn;
}
}
//-----------------------------------------------------------------------
// Update normal objects
void DisplayNode::updateObject(const int32 deltaTime) {
GameObject *obj = object;
if (obj->isMoving()) return;
if (isActor(obj)) {
Actor *a = (Actor *)obj;
a->updateAppearance(deltaTime);
}
}
//-----------------------------------------------------------------------
// Draw sprites for normal objects
#if DINO
const int maxSpriteWidth = 320,
maxSpriteHeight = 320,
maxSpriteBaseLine = 50;
#else
const int maxSpriteWidth = 32,
maxSpriteHeight = 120,
maxSpriteBaseLine = 16;
#endif
void DisplayNode::drawObject(void) {
ColorTable mainColors, // colors for object
leftColors, // colors for left-hand object
rightColors; // colors for right-hand object
SpriteComponent scList[3],
*sc;
int16 bodyIndex, // drawing order of body
leftIndex, // drawing order of left
rightIndex, // drawing order of right
partCount; // number of sprite parts
bool ghostIt = false;
GameObject *obj = object;
ProtoObj *proto = obj->proto();
Point16 drawPos;
SpriteSet *ss;
Sprite *bodySprite;
ActorAppearance *aa = nullptr;
SpriteSet *sprPtr = nullptr;
TilePoint objCoords = obj->getLocation(),
tCoords,
mCoords;
MetaTile *mt;
RipTable *rt;
tCoords.u = (objCoords.u >> kTileUVShift) & kPlatMask;
tCoords.v = (objCoords.v >> kTileUVShift) & kPlatMask;
mCoords.u = objCoords.u >> (kTileUVShift + kPlatShift);
mCoords.v = objCoords.v >> (kTileUVShift + kPlatShift);
mCoords.z = 0;
// Do not display objects that are on a ripped roof
if ((mt = mapList[currentMapNum].lookupMeta(mCoords)) != NULL) {
if ((rt = mt->ripTable(currentMapNum)) != NULL) {
if (objCoords.z >= rt->zTable[tCoords.u][tCoords.v]) {
// Disable hit-test on the object's box
hitBox.width = -1;
hitBox.height = -1;
obj->setOnScreen(false);
obj->setObscured(false);
return;
}
}
}
TileToScreenCoords(objCoords, screenCoords);
drawPos.x = screenCoords.x + fineScroll.x;
drawPos.y = screenCoords.y + fineScroll.y;
// If it's an object, then the drawing is fairly straight
// forward.
if (isObject(obj)) {
ObjectSpriteInfo sprInfo;
// Reject any sprites which fall off the edge of the screen.
if (drawPos.x < -32
|| drawPos.x > kTileRectX + kTileRectWidth + 32
|| drawPos.y < -32
|| drawPos.y > kTileRectY + kTileRectHeight + 100) {
// Disable hit-test on the object's box
hitBox.width = -1;
hitBox.height = -1;
// Mark as being off screen
obj->setOnScreen(false);
obj->setObscured(false);
return;
}
if (!obj->isOnScreen()) {
SenseInfo info;
obj->setOnScreen(true);
if (getCenterActor()->canSenseSpecificObject(info, maxSenseRange, obj->thisID()))
obj->setSightedByCenter(true);
else {
obj->setSightedByCenter(false);
obj->setObscured(false);
}
obj->_data.sightCtr = 5;
} else {
if (--obj->_data.sightCtr == 0) {
SenseInfo info;
if (getCenterActor()->canSenseSpecificObject(info, maxSenseRange, obj->thisID()))
obj->setSightedByCenter(true);
else {
obj->setSightedByCenter(false);
obj->setObscured(false);
}
obj->_data.sightCtr = 5;
}
}
// Figure out which sprite to show
sprInfo = proto->getSprite(obj, ProtoObj::objOnGround);
// Build the color translation table for the object
obj->getColorTranslation(mainColors);
// Fill in the SpriteComponent structure
sc = &scList[0];
sc->sp = sprInfo.sp;
sc->offset.x = scList->offset.y = 0;
sc->colorTable = mainColors;
sc->flipped = sprInfo.flipped;
partCount = 1;
bodyIndex = 0;
} else {
Actor *a = (Actor *)obj;
ActorAnimation *anim;
ActorPose *pose;
int16 poseFlags;
if (!a->isDead() && objCoords.z < -proto->height - 8) {
// The actor is under water so display the bubbles sprite
drawPos.y += objCoords.z;
objCoords.z = 0;
// Disable hit-test on the object's box
hitBox.width = -1;
hitBox.height = -1;
// Reject any sprites which fall off the edge of the screen.
if (drawPos.x < -maxSpriteWidth
|| drawPos.x > kTileRectX + kTileRectWidth + maxSpriteWidth
|| drawPos.y < -maxSpriteBaseLine
|| drawPos.y > kTileRectY + kTileRectHeight + maxSpriteHeight) {
// Mark as being off screen
a->setOnScreen(false);
a->setObscured(false);
return;
}
buildColorTable(
mainColors,
bubbleColorTable,
ARRAYSIZE(bubbleColorTable));
if (a->kludgeCount < 0 || ++a->kludgeCount >= kBubbleSpriteCount)
a->kludgeCount = 0;
sc = &scList[0];
sc->sp = spellSprites->sprite(
kBaseBubbleSpriteIndex + a->kludgeCount);
sc->offset.x = scList->offset.y = 0;
sc->colorTable = mainColors;
sc->flipped = false;
partCount = 1;
bodyIndex = 0;
} else {
// Reject any sprites which fall off the edge of the screen.
if (drawPos.x < -maxSpriteWidth
|| drawPos.x > kTileRectX + kTileRectWidth + maxSpriteWidth
|| drawPos.y < -maxSpriteBaseLine
|| drawPos.y > kTileRectY + kTileRectHeight + maxSpriteHeight) {
// Disable hit-test on the object's box
hitBox.width = -1;
hitBox.height = -1;
// Mark as being off screen
a->setOnScreen(false);
a->setObscured(false);
return;
}
if (a->hasEffect(actorInvisible)) {
if (!isPlayerActor(a)
&& !(getCenterActor()->hasEffect(actorSeeInvis))) {
hitBox.width = -1;
hitBox.height = -1;
return;
}
ghostIt = true;
}
if (!a->isOnScreen()) {
SenseInfo info;
a->setOnScreen(true);
if (getCenterActor()->canSenseSpecificActor(info, maxSenseRange, a))
a->setSightedByCenter(true);
else {
a->setSightedByCenter(false);
a->setObscured(false);
}
a->_data.sightCtr = 5;
} else {
if (--a->_data.sightCtr == 0) {
SenseInfo info;
if (getCenterActor()->canSenseSpecificActor(info, maxSenseRange, a))
a->setSightedByCenter(true);
else {
a->setSightedByCenter(false);
a->setObscured(false);
}
a->_data.sightCtr = 5;
}
}
aa = a->appearance;
if (aa == nullptr)
return;
// Fetch the animation series, and determine which
// pose in the series is the current one.
anim = aa->animation(a->currentAnimation);
pose = aa->pose(anim, a->currentFacing, a->currentPose);
if (anim == nullptr)
return;
assert(anim->start[0] < 10000);
assert(anim->start[1] < 10000);
assert(anim->start[2] < 10000);
assert(pose->rightObjectOffset.x < 1000);
assert(pose->rightObjectOffset.x > -1000);
assert(pose->rightObjectOffset.y < 1000);
assert(pose->rightObjectOffset.y > -1000);
assert(pose->leftObjectOffset.x < 1000);
assert(pose->leftObjectOffset.x > -1000);
assert(pose->leftObjectOffset.y < 1000);
assert(pose->leftObjectOffset.y > -1000);
// washHandle( aa->spriteBanks[pose->actorFrameBank] );
// If the new sprite is loaded, then we can go
// ahead and show it. If it's not, then we can
// pause for a frame or two until it is loaded.
// However, if the previous frame isn't loaded
// either, then we need to go ahead and force
// the new frame to finish loaded (handled by
// lockResource())
if (aa->isBankLoaded(pose->actorFrameBank)
|| !aa->isBankLoaded(a->poseInfo.actorFrameBank)) {
ActorPose pTemp = *pose;
// Initiate a load of the sprite bank needed.
/* if (!RHandleLoading(
(RHANDLE)(aa->spriteBanks[pose->actorFrameBank]) ))
{
aa->loadSpriteBanks( (1<<pose->actorFrameBank) );
} */
aa->requestBank(pose->actorFrameBank);
// Indicate that animation is OK.
a->animationFlags &= ~animateNotLoaded;
// Set up which bank and frame to use.
a->poseInfo = pTemp;
} else {
// Indicate that animation isn't loaded
a->animationFlags |= animateNotLoaded;
// Initiate a load of the sprite bank needed.
/* if (!RHandleLoading(
(RHANDLE)(aa->spriteBanks[pose->actorFrameBank]) ))
{
aa->loadSpriteBanks( (1<<pose->actorFrameBank) );
}
*/
aa->requestBank(pose->actorFrameBank);
}
// For actors, start by assuming that the actor has
// nothing in either hand.
bodyIndex = 0;
rightIndex = leftIndex = -2;
partCount = 1;
poseFlags = a->poseInfo.flags;
a->getColorTranslation(mainColors);
// Do various tests to see what the actor is
// carrying in each hand, and what drawing
// order should be used for these objects.
if (a->leftHandObject != Nothing) {
partCount++;
if (poseFlags & ActorPose::leftObjectInFront) {
leftIndex = 1;
} else {
leftIndex = 0;
bodyIndex = 1;
}
}
if (a->rightHandObject != Nothing) {
partCount++;
if (poseFlags & ActorPose::rightObjectInFront) {
if (leftIndex == 1
&& poseFlags & ActorPose::leftOverRight) {
leftIndex = 2;
rightIndex = 1;
} else {
rightIndex = partCount - 1;
}
} else {
if (leftIndex == 0
&& poseFlags & ActorPose::leftOverRight) {
rightIndex = 0;
leftIndex = 1;
bodyIndex = 2;
} else {
rightIndex = 0;
bodyIndex++;
if (leftIndex != -2) leftIndex++;
}
}
}
// REM: Locking bug...
// ss = (SpriteSet *)RLockHandle( aa->sprites );
sprPtr = aa->spriteBanks[a->poseInfo.actorFrameBank];
ss = sprPtr;
if (ss == nullptr)
return;
// Fill in the SpriteComponent structure for body
sc = &scList[bodyIndex];
assert(a->poseInfo.actorFrameIndex < ss->count);
sc->sp = ss->sprite(a->poseInfo.actorFrameIndex);
sc->offset.x = sc->offset.y = 0;
// Color remapping info
sc->colorTable = mainColors;
// sc->colorTable = aa->schemeList ? mainColors : identityColors;
sc->flipped = (poseFlags & ActorPose::actorFlipped);
assert(sc->sp != NULL);
assert(sc->sp->size.x > 0);
assert(sc->sp->size.y > 0);
assert(sc->sp->size.x < 255);
assert(sc->sp->size.y < 255);
// If we were carrying something in the left hand,
// then fill in the component structure for it.
if (leftIndex >= 0) {
GameObject *ob = GameObject::objectAddress(a->leftHandObject);
ProtoObj *prot = ob->proto();
ob->getColorTranslation(leftColors);
sc = &scList[leftIndex];
sc->sp = prot->getOrientedSprite(
ob,
a->poseInfo.leftObjectIndex);
assert(sc->sp != NULL);
sc->offset = a->poseInfo.leftObjectOffset;
assert(sc->offset.x < 1000);
assert(sc->offset.x > -1000);
assert(sc->offset.y < 1000);
assert(sc->offset.y > -1000);
sc->colorTable = leftColors;
sc->flipped = (poseFlags & ActorPose::leftObjectFlipped);
}
// If we were carrying something in the right hand,
// then fill in the component structure for it.
if (rightIndex >= 0) {
GameObject *ob = GameObject::objectAddress(a->rightHandObject);
ProtoObj *prot = ob->proto();
ob->getColorTranslation(rightColors);
sc = &scList[rightIndex];
sc->sp = prot->getOrientedSprite(
ob,
a->poseInfo.rightObjectIndex);
assert(sc->sp != NULL);
assert(sc->sp->size.x > 0);
assert(sc->sp->size.y > 0);
assert(sc->sp->size.x < 255);
assert(sc->sp->size.y < 255);
sc->offset = a->poseInfo.rightObjectOffset;
assert(sc->offset.x < 1000);
assert(sc->offset.x > -1000);
assert(sc->offset.y < 1000);
assert(sc->offset.y > -1000);
sc->colorTable = rightColors;
sc->flipped = (poseFlags & ActorPose::rightObjectFlipped);
}
}
}
if (!ghostIt && obj->isGhosted())
ghostIt = true;
int16 effectFlags = 0;
bool obscured;
if (ghostIt) effectFlags |= sprFXGhosted;
if (obj->isSightedByCenter() && objRoofRipped(obj))
effectFlags |= sprFXGhostIfObscured;
effectFlags |= sprFXTerrainMask;
DrawCompositeMaskedSprite(
g_vm->_backPort,
scList,
partCount,
drawPos,
objCoords,
effectFlags,
&obscured);
if (effectFlags & sprFXGhostIfObscured)
obj->setObscured(obscured);
// Record the extent box that the sprite was drawn
// at, in order to facilitate mouse picking functions
// later on in the event loop.
bodySprite = scList[bodyIndex].sp;
hitBox.x = drawPos.x
+ (scList[bodyIndex].flipped
? -bodySprite->size.x - bodySprite->offset.x
: bodySprite->offset.x)
- fineScroll.x;
hitBox.y = drawPos.y + bodySprite->offset.y - fineScroll.y;
hitBox.width = bodySprite->size.x;
hitBox.height = bodySprite->size.y;
if (flags & displayIndicator) {
Point16 indicatorCoords;
gPixelMap &indicator = *mouseCursors[kMouseCenterActorIndicatorImage];
indicatorCoords.x = hitBox.x + fineScroll.x + (hitBox.width - indicator.size.x) / 2;
indicatorCoords.y = hitBox.y + fineScroll.y - indicator.size.y - 2;
TBlit(g_vm->_backPort.map, &indicator, indicatorCoords.x, indicatorCoords.y);
}
}
//-----------------------------------------------------------------------
// Do mouse hit-test on objects
ObjectID pickObject(const StaticPoint32 &mouse, StaticTilePoint &objPos) {
DisplayNode *dn;
ObjectID result = Nothing;
int32 dist = maxint32;
SpriteSet *objectSet;
objectSet = objectSprites;
if (objectSet == NULL)
error("Object sprites have been dumped!");
for (dn = DisplayNodeList::head; dn; dn = dn->nextDisplayed) {
if (dn->type == nodeTypeObject) {
GameObject *obj = dn->object;
if (obj->parent() == currentWorld && dn->hitBox.ptInside(mouse.x, mouse.y)) {
TilePoint loc = obj->getLocation();
int32 newDist = loc.u + loc.v;
if (newDist < dist) {
Point16 testPoint;
SpriteSet *ss;
Sprite *spr;
ActorAppearance *aa = nullptr;
SpriteSet *sprPtr = nullptr;
bool flipped = true;
testPoint.x = mouse.x - dn->hitBox.x;
testPoint.y = mouse.y - dn->hitBox.y;
// If it's an object, then the test is fairly straight
// forward.
if (isObject(obj)) {
ObjectSpriteInfo sprInfo;
sprInfo = obj->proto()->getSprite(obj, ProtoObj::objOnGround);
spr = sprInfo.sp;
flipped = sprInfo.flipped;
} else {
Actor *a = (Actor *)obj;
aa = a->appearance;
if (aa == NULL) continue;
sprPtr = aa->spriteBanks[a->poseInfo.actorFrameBank];
ss = sprPtr;
if (ss == nullptr)
continue;
spr = ss->sprite(a->poseInfo.actorFrameIndex);
flipped =
(a->poseInfo.flags & ActorPose::actorFlipped) ? 1 : 0;
}
if (GetSpritePixel(spr, flipped, testPoint)) {
dist = newDist;
result = obj->thisID();
objPos.set(loc.u, loc.v, loc.z);
objPos.z += MAX(-spr->offset.y - testPoint.y, 0);
} else if (result == Nothing) { // If no object found yet
Point16 testPoint2;
int16 minX, maxX;
// Try checking a wider area for mouse hit
testPoint2.y = testPoint.y;
minX = MAX(0, testPoint.x - 6);
maxX = MIN(dn->hitBox.width - 1, testPoint.x + 6);
// scan a horizontal strip of the character for a hit.
// If we find a hit, go ahead and set result anyway
// If we later find a real hit, then it will overwrite
// the results of this one.
for (testPoint2.x = minX; testPoint2.x <= maxX; testPoint2.x++) {
if (GetSpritePixel(spr, flipped, testPoint2)) {
result = obj->thisID();
objPos.set(loc.u, loc.v, loc.z);
objPos.z += MAX(-spr->offset.y - testPoint.y, 0);
break;
}
}
}
}
}
}
}
return result;
}
//-----------------------------------------------------------------------
// Adds spell effects into the dispplay list
//
// NOTE : all spell effects are currently placed behind any real stuff
// they can also easily be placed in front
void DisplayNodeList::buildEffects(bool) {
if (count) {
for (int i = 0; i < count; i++) {
DisplayNode *dn = DisplayNodeList::head;
if (displayList[i].efx->isHidden() || displayList[i].efx->isDead())
continue;
// make sure it knows it's not a real object
displayList[i].type = nodeTypeEffect;
displayList[i].sortDepth = displayList[i].efx->screenCoords.y + displayList[i].efx->current.z / 2;
if (dn) {
int32 sd = displayList[i].sortDepth;
while (dn->nextDisplayed && dn->nextDisplayed->sortDepth <= sd)
dn = dn->nextDisplayed;
}
if (dn == DisplayNodeList::head) {
displayList[i].nextDisplayed = DisplayNodeList::head;
DisplayNodeList::head = &displayList[i];
} else {
displayList[i].nextDisplayed = dn->nextDisplayed;
dn->nextDisplayed = &displayList[i];
}
}
}
}
bool DisplayNodeList::dissipated(void) {
if (count) {
for (int i = 0; i < count; i++) {
if (displayList[i].efx && !displayList[i].efx->isDead())
return false;
}
}
return true;
}
//-----------------------------------------------------------------------
// Draw sprites for spell effects
//
// NOTE : all spell effects currently use the center actor for their
// sprites.
void DisplayNode::drawEffect(void) {
if (efx) efx->drawEffect();
}
void Effectron::drawEffect(void) {
ColorTable eColors; // colors for object
bool obscured = false;
Point16 drawPos;
TilePoint objCoords = SpellPos();
SpriteComponent scList[3],
*sc;
if (isHidden() || isDead())
return;
drawPos.x = screenCoords.x + fineScroll.x;
drawPos.y = screenCoords.y + fineScroll.y;
// Reject any sprites which fall off the edge of the screen.
if (drawPos.x < -32
|| drawPos.x > kTileRectX + kTileRectWidth + 32
|| drawPos.y < -32
|| drawPos.y > kTileRectY + kTileRectHeight + 100) {
// Disable hit-test on the object's box
hitBox.width = -1;
hitBox.height = -1;
return;
}
TileToScreenCoords(objCoords, screenCoords);
sc = &scList[0];
//sc->sp = (*spellSprites)->sprite( spriteID() );
sc->sp = spellSprites->sprite(spriteID()); //tempSpellSpriteIDs[rand()%39] );
sc->offset.x = scList->offset.y = 0;
(*g_vm->_sdpList)[parent->spell]->
getColorTranslation(eColors, this);
sc->colorTable = eColors;
sc->flipped = false;
obscured = (visiblePixelsInSprite(sc->sp,
sc->flipped,
sc->colorTable,
drawPos,
current,
0) <= 5);
DrawCompositeMaskedSprite(
g_vm->_backPort,
scList,
1,
drawPos,
objCoords,
((obscured) && //objectFlags & GameObject::objectObscured ) &&
0
? sprFXGhosted : sprFXTerrainMask));
}
/* ===================================================================== *
Misc. functions
* ===================================================================== */
//-----------------------------------------------------------------------
// Enable or disable the center actor indicator
void setCenterActorIndicator(bool enabled) {
centerActorIndicatorEnabled = enabled;
}
} // end of namespace Saga2