G4Enemy.c

 
//============================================================================
//----------------------------------------------------------------------------
//                                  Enemy.c
//----------------------------------------------------------------------------
//============================================================================
 
// This file contains all enemy related functions (enemy "AI").  It handlesÉ
// the enemy decision making proccess, moves the enemies, etc.
 
#include "G4Externs.h"
 
 
#define kEnemyImpulse           8
 
#define kOwlMaxHVel             96
#define kOwlMaxVVel             320
#define kOwlHeightSmell         96
#define kOwlFlapImpulse         32
 
#define kWolfMaxHVel            128
#define kWolfMaxVVel            400
#define kWolfHeightSmell        160
#define kWolfFlapImpulse        48
 
#define kJackalMaxHVel          192
#define kJackalMaxVVel          512
#define kJackalHeightSmell      240
#define kJackalFlapImpulse      72
 
 
Boolean SetEnemyInitialLocation (Rect *);
void SetEnemyAttributes (short);
short AssignNewAltitude (void);
void InitEnemy (short, Boolean);
void CheckEnemyPlatformHit (short);
void CheckEnemyRoofCollision (short);
void HandleIdleEnemies (short);
void HandleFlyingEnemies (short);
void HandleWalkingEnemy (short);
void HandleSpawningEnemy (short);
void HandleFallingEnemy (short);
void HandleEggEnemy (short);
void ResolveEnemyPlayerHit (short);
 
 
handInfo    theHand;
eyeInfo     theEye;
Rect        grabZone;
short       deadEnemies, spawnedEnemies, numEnemiesThisLevel, numOwls;
 
extern  playerType  thePlayer;
extern  enemyType   theEnemies[kMaxEnemies];
extern  Rect        platformRects[6], enemyInitRects[5];
extern  long        theScore;
extern  short       numLedges, numEnemies, countDownTimer;
extern  short       levelOn;
extern  Boolean     evenFrame, doEnemyFlapSound, doEnemyScrapeSound;
 
 
//==============================================================  Functions
//--------------------------------------------------------------  SetEnemyInitialLocation
 
// When a new enemy is about to be "born", this function is called to determineÉ
// the enemies starting location.  The only thing important here is that the enemyÉ
// appears on a valid platform for the particular level we're on.  As well, whichÉ
// platform he (it) appears on should be random.
 
Boolean SetEnemyInitialLocation (Rect *theRect)
{
    short       where, possibilities;
    Boolean     facing;
    
    possibilities = numLedges - 1;      // Determine number of valid platforms.
    where = RandomInt(possibilities);   // Choose one at random.
    *theRect = enemyInitRects[where];   // Initially place enemy at default location.
    
    switch (where)                      // Determine if enemy facing left or right.
    {                                   // It depends upon which platform they're on.
        case 0:                         // These are the left-most platforms.
        case 2:
        facing = TRUE;                  // Enemy will face right.
        break;
        
        case 3:                         // Special case for the center platform.
        if (RandomInt(2) == 0)          // Enemy randomly faces either left or right.
            facing = TRUE;
        else
            facing = FALSE;
        break;
        
        default:                        // Catch remaining (right-most) platforms.
        facing = FALSE;                 // Enemy will face left.
        break;
    }
    
    if ((levelOn % 5) == 4)             // Handle special case for Egg Wave
    {                                   // Re-define enemy bounds.
        theRect->left += 12 + RandomInt(48) - 24;
        theRect->right = theRect->left + 24;
        theRect->top = theRect->bottom - 24;
    }
    
    return (facing);
}
 
//--------------------------------------------------------------  SetEnemyAttributes
 
// Depending upon the type of enemy this function is passed (there are threeÉ
// types of sphinx enemies), this function sets up that enemies variousÉ
// attributes - such as maximum vertical velocity, etc.
 
void SetEnemyAttributes (short i)
{
    short       h;
                                            // Point enemy toward center of screen.
    h = (theEnemies[i].dest.left + theEnemies[i].dest.right) >> 1;
    if (h < 320)                            // If enemy in left half of screenÉ
        theEnemies[i].facingRight = TRUE;   // the enemy will face to the right.
    else                                    // Otherwise, if in right half of screenÉ
        theEnemies[i].facingRight = FALSE;  // face to the left.
    
    switch (theEnemies[i].kind)             // Okay, depending upon what "kind" of enemyÉ
    {                                       // we're dealing with....
        case kOwl:                          // The owl is the simplest (wimpiest) enemy.
        if (theEnemies[i].facingRight)      // Choose which graphic to use.
            theEnemies[i].srcNum = 0;
        else
            theEnemies[i].srcNum = 2;
                                            // Set owl's velocity limitations.
        theEnemies[i].maxHVel = kOwlMaxHVel;
        theEnemies[i].maxVVel = kOwlMaxVVel;
                                            // This is the distance within which he willÉ
                                            // pursue the player (it's strictly Y distance).
        theEnemies[i].heightSmell = kOwlHeightSmell;
                                            // This is how powerful the owl's "flap" is.
        theEnemies[i].flapImpulse = kOwlFlapImpulse;
        break;
        
        case kWolf:                         // The wolf sphinx is of medium difficulty.
        if (theEnemies[i].facingRight)      // Choose which graphic to use.
            theEnemies[i].srcNum = 4;
        else
            theEnemies[i].srcNum = 6;
                                            // Set wolf's velocity limitations.
        theEnemies[i].maxHVel = kWolfMaxHVel;
        theEnemies[i].maxVVel = kWolfMaxVVel;
                                            // This is the distance within which he willÉ
                                            // pursue the player (it's strictly Y distance).
        theEnemies[i].heightSmell = kWolfHeightSmell;
                                            // This is how powerful the wolf's "flap" is.
        theEnemies[i].flapImpulse = kWolfFlapImpulse;
        break;
        
        case kJackal:                       // The jackal is the swiftest, toughest enemy.
        if (theEnemies[i].facingRight)      // Choose which graphic to use.
            theEnemies[i].srcNum = 8;
        else
            theEnemies[i].srcNum = 10;
                                            // Set jackal's velocity limitations.
        theEnemies[i].maxHVel = kJackalMaxHVel;
        theEnemies[i].maxVVel = kJackalMaxVVel;
                                            // This is the distance within which he willÉ
                                            // pursue the player (it's strictly Y distance).
        theEnemies[i].heightSmell = kJackalHeightSmell;
                                            // This is how powerful the jackal's "flap" is.
        theEnemies[i].flapImpulse = kJackalFlapImpulse;
        break;
    }
}
 
//--------------------------------------------------------------  AssignNewAltitude
 
// The sphinxes "patrol" specific altitudes in the arena.  After wrapping aroundÉ
// the screen a few times, they randomly select a new altitude to patrol (thisÉ
// keeps the player from finding a "safe" place to stand.  This function choosesÉ
// a new altitude for the enemy to patrol.
 
short AssignNewAltitude (void)
{
    short       which, altitude;
    
    which = RandomInt(4);       // There are only 4 "patrol altitudes".
    switch (which)              // Depending on which random number came upÉ
    {
        case 0:                 // This is just below the ceiling.
        altitude = 65 << 4;
        break;
        
        case 1:                 // This is below the top platforms but above theÉ
        altitude = 150 << 4;    // center platform.
        break;
        
        case 2:                 // This is just below the center platform.
        altitude = 245 << 4;
        break;
        
        case 3:                 // This is striahgt across the lava pit.
        altitude = 384 << 4;
        break;
    }
    
    return (altitude);
}
 
//--------------------------------------------------------------  InitEnemy
 
// This resets an enemies info.  It is called when a new enemy is to be born.
// It is called if an egg is about to hatch, if a new level has begun, or ifÉ
// if it is simply time to add a new enemy.
 
void InitEnemy (short i, Boolean reincarnated)
{
    Boolean     facing;
    
    if (spawnedEnemies < numEnemiesThisLevel)   // New enemy to appear (in other wordsÉ
    {                                           // this enemy is not hatched).
                                                // Call function to set new location.
        facing = SetEnemyInitialLocation(&theEnemies[i].dest);
        theEnemies[i].wasDest = theEnemies[i].dest;
        theEnemies[i].h = theEnemies[i].dest.left << 4;
        theEnemies[i].v = theEnemies[i].dest.top << 4;
        theEnemies[i].wasH = theEnemies[i].h;   // Reset "old locations" variables.
        theEnemies[i].wasV = theEnemies[i].v;
                                                // Assign the "patrol altitude".
        theEnemies[i].targetAlt = theEnemies[i].v - (40 << 4);
        theEnemies[i].hVel = 0;                 // Zero velocity vraiables.
        theEnemies[i].vVel = 0;
        theEnemies[i].pass = 0;                 // Zero number of times wrapped around.
        if ((levelOn % 5) == 4)                 // If this is an Egg WaveÉ
            theEnemies[i].mode = kEggTimer;     // set enemy in "wait to hatch" mode.
        else                                    // Otherwise, just sut enemy inÉ
            theEnemies[i].mode = kIdle;         // idle mode.
        if (i < numOwls)                        // Determine what kind of enemy.
            theEnemies[i].kind = kOwl;
        else if (i > (numOwls + 6)) 
            theEnemies[i].kind = kJackal;
        else
            theEnemies[i].kind = kWolf;
        theEnemies[i].facingRight = facing;
        SetEnemyAttributes(i);                  // Initialize enemy attributes.
        
        if (reincarnated)                       // If this is an egg that will hatchÉ
            theEnemies[i].frame = RandomInt(48) + 8 + (numOwls * 32);
        else
            theEnemies[i].frame = RandomInt(48) + 32 + (64 * i) + (numOwls * 32);
        
        if ((levelOn % 5) == 4)                 // If this is an Egg Wave
            theEnemies[i].kind--;               // Decrement "kind" (since it's incrementedÉ
                                                // when they hatch).
        spawnedEnemies++;                       // Keep track of number of enemies active.
    }
}
 
//--------------------------------------------------------------  GenerateEnemies
 
// This function is called only for a new level.  It goes through andÉ
// intializes a whole host of enemies in one go.
 
void GenerateEnemies (void)
{
    short       i;
    
    if ((levelOn % 5) == 4)         // If this is an Egg WaveÉ
    {
        numEnemies = kMaxEnemies;   // we insist upon the maximum number of enemies.
        numEnemiesThisLevel = numEnemies;
    }
    else                            // If not an egg wave, use a formula to determineÉ
    {                               // the max number of enemies that are to be active.
        numEnemies = ((levelOn / 5) + 2) * 2;
        if (numEnemies > kMaxEnemies)
            numEnemies = kMaxEnemies;
        numEnemiesThisLevel = numEnemies * 2;
    }
    
    deadEnemies = 0;                // No dead enemies yet.
    
                                    // Use formula to determine the number of owlsÉ
                                    // to appear.  This number goes down as the levelsÉ
                                    // increase.  It is used not merely to determineÉ
                                    // how many owls are to appear, but also how manyÉ
                                    // of the more advanced enemies.  For example, whenÉ
                                    // numOwls goes down to zero, all the enemies willÉ
                                    // be of the more advanced breed (wolves and jackals).
    numOwls = 4 - ((levelOn + 2) / 5);
    if (numOwls < 0)
        numOwls = 0;
    
    spawnedEnemies = 0;             // No enemies have been "born" yet.
                                    // Go through and set up all the enemies.
    for (i = 0; i < numEnemies; i++)
        InitEnemy(i, FALSE);
}
 
//--------------------------------------------------------------  CheckEnemyPlatformHit
 
// This is the enemy counterpart to a similarly named function that tests forÉ
// player collsions with the platforms.
 
void CheckEnemyPlatformHit (short h)
{
    Rect        hRect, vRect, whoCares;
    short       i, offset;
    
    for (i = 0; i < numLedges; i++)                 // Test all platforms.
    {                                               // Do a simple bounds test.
        if (SectRect(&theEnemies[h].dest, &platformRects[i], &whoCares))
        {                                           // If the enemy has hit the platformÉ
            hRect.left = theEnemies[h].dest.left;   // Determine if enemy hit platform sides.
            hRect.right = theEnemies[h].dest.right;
            hRect.top = theEnemies[h].wasDest.top;
            hRect.bottom = theEnemies[h].wasDest.bottom;
                                                    // Test this new special rect to see ifÉ
                                                    // the enemy hit on of the platform sides.
            if (SectRect(&hRect, &platformRects[i], &whoCares))
            {                                       // If enemy hit from side, see which side.
                                                    // We handle left and right seperatrelyÉ
                                                    // so that there's no ambiguity as toÉ
                                                    // what the new velocity and locationÉ
                                                    // of the enemy is.  If we did not do itÉ
                                                    // this way, there is the chance that anÉ
                                                    // enemy get's "stuck" on the edge ofÉ
                                                    // a platform (due to round-off errors).
                if (theEnemies[h].h > theEnemies[h].wasH)
                {                                   // Enemy was moving right (hit left side).
                    offset = theEnemies[h].dest.right - platformRects[i].left;
                                                    // Slide enemy "off" platform.
                    theEnemies[h].dest.left -= offset;
                    theEnemies[h].dest.right -= offset;
                    theEnemies[h].h = theEnemies[h].dest.left << 4;
                    theEnemies[h].wasH = theEnemies[h].h;
                                                    // Bounce enemy (negate velocity).
                    if (theEnemies[h].hVel > 0)
                        theEnemies[h].hVel = -(theEnemies[h].hVel >> 1);
                    else
                        theEnemies[h].hVel = theEnemies[h].hVel >> 1;
                }
                if (theEnemies[h].h < theEnemies[h].wasH)
                {                                   // Enemy was moving left (hit right side).
                    offset = platformRects[i].right - theEnemies[h].dest.left;
                                                    // Slide enemy "off" platform.
                    theEnemies[h].dest.left += offset;
                    theEnemies[h].dest.right += offset;
                    theEnemies[h].h = theEnemies[h].dest.left << 4;
                    theEnemies[h].wasH = theEnemies[h].h;
                                                    // Bounce enemy (negate velocity).
                    if (theEnemies[h].hVel < 0)
                        theEnemies[h].hVel = -(theEnemies[h].hVel >> 1);
                    else
                        theEnemies[h].hVel = theEnemies[h].hVel >> 1;
                }
                doEnemyScrapeSound = TRUE;          // Play a collision sound.
                                                    // Flip enemy to face opposite direction.
                theEnemies[h].facingRight = !theEnemies[h].facingRight;
            }
            else                                    // Enemy didn't hit from side.
            {                                       // See if enemy hit top/bottom.
                vRect.left = theEnemies[h].wasDest.left;
                vRect.right = theEnemies[h].wasDest.right;
                vRect.top = theEnemies[h].dest.top;
                vRect.bottom = theEnemies[h].dest.bottom;
                                                    // Special "test rect" for top/bottom hit.
                if (SectRect(&vRect, &platformRects[i], &whoCares))
                {                                   // If hit the top/bottom of platformÉ
                    if (theEnemies[h].mode == kFalling)
                    {                               // Was the enemy a falling egg?
                                                    // Bounce egg (with some inelasticity).
                        theEnemies[i].hVel -= (theEnemies[i].hVel >> 3);
                                                    // When the eggs velocity is betweenÉ
                                                    // +/- 8, consider the egg at rest.
                        if ((theEnemies[i].hVel < 8) && (theEnemies[i].hVel > -8))
                        {
                            if (theEnemies[i].hVel > 0)
                                theEnemies[i].hVel--;
                            else if (theEnemies[i].hVel < 0)
                                theEnemies[i].hVel++;
                        }
                    }
                                                    // Specifically, did enemy hit the top?
                    if (theEnemies[h].v > theEnemies[h].wasV)
                    {                               // Enemy heading down (hit platform top).
                        offset = theEnemies[h].dest.bottom - platformRects[i].top;
                                                    // Move enemy up off platform.
                        theEnemies[h].dest.top -= offset;
                        theEnemies[h].dest.bottom -= offset;
                        theEnemies[h].v = theEnemies[h].dest.top << 4;
                        theEnemies[h].wasV = theEnemies[h].v;
                        if (theEnemies[h].vVel > kDontFlapVel)
                            doEnemyScrapeSound = TRUE;
                                                    // "Bounce" enemy.
                        if (theEnemies[h].vVel > 0)
                            theEnemies[h].vVel = -(theEnemies[h].vVel >> 1);
                        else
                            theEnemies[h].vVel = theEnemies[h].vVel >> 1;
                        if ((theEnemies[h].vVel < 8) && (theEnemies[h].vVel > -8) && 
                                (theEnemies[h].hVel == 0) && (theEnemies[h].mode == kFalling))
                        {                       // Here we handle an egg come to rest.
                            if (((theEnemies[h].dest.right - 8) > platformRects[i].right) && 
                                    (theEnemies[h].hVel == 0))
                            {                   // Special case where egg right on edge.
                                theEnemies[h].hVel = 32;
                            }
                            else if (((theEnemies[h].dest.left + 8) < platformRects[i].left) && 
                                    (theEnemies[h].hVel == 0))
                            {                   // Special case where egg right on edge.
                                theEnemies[h].hVel = -32;
                            }
                            else                // If egg not on the edge of platformÉ
                            {                   // switch to "timer" mode.
                                theEnemies[h].mode = kEggTimer;
                                theEnemies[h].frame = (numOwls * 96) + 128;
                                theEnemies[h].vVel = 0;
                            }
                        }
                    }
                    if (theEnemies[h].v < theEnemies[h].wasV)
                    {                           // Enemy was rising - hit bottom of platform.
                        offset = theEnemies[h].dest.top - platformRects[i].bottom;
                                                // Slide enemy off platform.
                        theEnemies[h].dest.top -= offset;
                        theEnemies[h].dest.bottom -= offset;
                        theEnemies[h].v = theEnemies[h].dest.top << 4;
                        theEnemies[h].wasV = theEnemies[h].v;
                                                // Play collision sound.
                        doEnemyScrapeSound = TRUE;
                                                // "Bounce" enemy downward from platform.
                        if (theEnemies[h].vVel < 0)
                            theEnemies[h].vVel = -(theEnemies[h].vVel >> 2);
                        else
                            theEnemies[h].vVel = theEnemies[h].vVel >> 2;
                        if ((theEnemies[h].vVel < 8) && (theEnemies[h].vVel > -8) && 
                                (theEnemies[h].hVel == 0) && (theEnemies[h].mode == kFalling))
                        {
                            theEnemies[h].mode = kEggTimer;
                            theEnemies[h].frame = (numOwls * 96) + 128;
                            theEnemies[h].vVel = 0;
                        }
                    }
                }
            }
        }
    }
}
 
//--------------------------------------------------------------  CheckEnemyRoofCollision
 
// Like the player counterpart, this function checks to see if an enemy has hitÉ
// the ceiling or the lava.  It handles the consequences of both cases.
 
void CheckEnemyRoofCollision (short i)
{
    short       offset;
    
    if (theEnemies[i].dest.top < (kRoofHeight - 2))
    {                   // If enemy has hit the ceilingÉ
        offset = kRoofHeight - theEnemies[i].dest.top;
                        // Move enemy down to a "legal" altitude.
        theEnemies[i].dest.top += offset;
        theEnemies[i].dest.bottom += offset;
        theEnemies[i].v = theEnemies[i].dest.top << 4;
                        // Play a collision sound.
        doEnemyScrapeSound = TRUE;
                        // Bounce enemy downward.
        theEnemies[i].vVel = -(theEnemies[i].vVel >> 2);
    }
    else if (theEnemies[i].dest.top > kLavaHeight)
    {                   // If enemy has fallen into lavaÉ
                        // kill that enemy.
        theEnemies[i].mode = kDeadAndGone;
        deadEnemies++;
                        // Play a splash sound.
        {
            short left = theEnemies[i].dest.left;
            short right = theEnemies[i].dest.right;
            short bottom = theEnemies[i].dest.bottom;
            short delta = (right - left);
            short temp = left * 32;
            short splashItr;
            
            for(splashItr = 0; splashItr < 32; splashItr++)
            {
                StartPixelShatter(temp / 32, bottom, (theEnemies[i].hVel * 0.75), -(theEnemies[i].vVel * 0.75), kShatterLavaSplash);
                temp += delta;
            }
        }
 
        PlayExternalSound(kSplashSound, kSplashPriority);
                        // Call up another from the ranks.
        InitEnemy(i, TRUE);
    }
}
 
//--------------------------------------------------------------  HandleIdleEnemies
 
// The following functions handle the various enemy modes.  Enemies areÉ
// considered to be in a specific mode and each mode is handled differently.
// Idle enemies are ones who are "invisible" - not yet born.  While idle, aÉ
// timer is ticking down - when it reaches zero, the enemy appears.
 
void HandleIdleEnemies (short i)
{
    theEnemies[i].frame--;                  // Decrement timer.
    if (theEnemies[i].frame <= 0)           // If timer is zero or lessÉ
    {
        theEnemies[i].mode = kSpawning;     // enemy is "born".
        theEnemies[i].wasH = theEnemies[i].h;
        theEnemies[i].wasV = theEnemies[i].v;
        theEnemies[i].hVel = 0;
        theEnemies[i].vVel = 0;
        theEnemies[i].frame = 0;
        SetEnemyAttributes(i);              // Initialize enemy attributes.
        PlayExternalSound(kSpawnSound, kSpawnPriority);
    }
}
 
//--------------------------------------------------------------  HandleFlyingEnemies
 
// Once an enemy takes off from a platform, they will always be in flying modeÉ
// unless they should be killed.  This function handles the flying mode.
 
void HandleFlyingEnemies (short i)
{
    short       dist;
    Boolean     shouldFlap;
                                // Take into account gravity pulling enemy down.
    theEnemies[i].vVel += kGravity;
                                // Get absolute difference in enemy/player altitude.
    dist = thePlayer.dest.top - theEnemies[i].dest.top;
    if (dist < 0)
        dist = -dist;
                                // See if the player is within the enemy's "seek" range.
    if ((dist < theEnemies[i].heightSmell) && 
            ((thePlayer.mode == kFlying) || (thePlayer.mode == kWalking)))
    {                           // Enemy will actively seek the player.
        if (thePlayer.dest.left < theEnemies[i].dest.left)
        {                       // Determine if quicker to go left or right to get player.
            dist = theEnemies[i].dest.left - thePlayer.dest.left;
            if (dist < 320)     // Closest route is to the left.
                theEnemies[i].facingRight = FALSE;
            else                // Closest route is to the right.
                theEnemies[i].facingRight = TRUE;
        }
        else if (thePlayer.dest.left > theEnemies[i].dest.left)
        {                       // Determine if quicker to go left or right to get player.
            dist = thePlayer.dest.left - theEnemies[i].dest.left;
            if (dist < 320)     // Closest route is to the right.
                theEnemies[i].facingRight = TRUE;
            else                // Closest route is to the left.
                theEnemies[i].facingRight = FALSE;
        }
                                // Seek an altitude 16 pixels above player.
        if (((theEnemies[i].v + 16) > thePlayer.v) && (evenFrame))
            shouldFlap = TRUE;
        else
            shouldFlap = FALSE;
    }
    else                        // Else, player not within enemy's "seek" altitude.
    {                           // Flap if necessary to maintain "patrol altitude".
        if ((theEnemies[i].v > theEnemies[i].targetAlt) && (evenFrame))
            shouldFlap = TRUE;
        else
            shouldFlap = FALSE;
    }
    
    if (shouldFlap)             // If the enemy has determined that it needs to flapÉ
    {                           // Give the enemy lift & play the flap sound.
        theEnemies[i].vVel -= theEnemies[i].flapImpulse;
        doEnemyFlapSound = TRUE;
    }
                                // Enemy never hovers - must move right or left.
    if (theEnemies[i].facingRight)
    {                           // If enemy facing right - move enemy to the right.
        theEnemies[i].hVel += kEnemyImpulse;
        if (theEnemies[i].hVel > theEnemies[i].maxHVel)
            theEnemies[i].hVel = theEnemies[i].maxHVel;
                                // Determine correct graphic for enemy.
        switch (theEnemies[i].kind)
        {
            case kOwl:
            if (shouldFlap)
                theEnemies[i].srcNum = 12;
            else
                theEnemies[i].srcNum = 13;
            break;
            
            case kWolf:
            if (shouldFlap)
                theEnemies[i].srcNum = 16;
            else
                theEnemies[i].srcNum = 17;
            break;
            
            case kJackal:
            if (shouldFlap)
                theEnemies[i].srcNum = 20;
            else
                theEnemies[i].srcNum = 21;
            break;
        }
        
    }
    else                        // If enemy not facing right (left) move to the left.
    {
        theEnemies[i].hVel -= kEnemyImpulse;
        if (theEnemies[i].hVel < -theEnemies[i].maxHVel)
            theEnemies[i].hVel = -theEnemies[i].maxHVel;
                                // Determine correct graphic for enemy.
        switch (theEnemies[i].kind)
        {
            case kOwl:
            if (shouldFlap)
                theEnemies[i].srcNum = 14;
            else
                theEnemies[i].srcNum = 15;
            break;
            
            case kWolf:
            if (shouldFlap)
                theEnemies[i].srcNum = 18;
            else
                theEnemies[i].srcNum = 19;
            break;
            
            case kJackal:
            if (shouldFlap)
                theEnemies[i].srcNum = 22;
            else
                theEnemies[i].srcNum = 23;
            break;
        }
    }
                                        // Move enemy horizontally based on hori velocity.
    theEnemies[i].h += theEnemies[i].hVel;
    theEnemies[i].dest.left = theEnemies[i].h >> 4;
    theEnemies[i].dest.right = theEnemies[i].dest.left + 64;
                                        // Move enemy vertically based on vertical velocity.
    theEnemies[i].v += theEnemies[i].vVel;
    theEnemies[i].dest.top = theEnemies[i].v >> 4;
    theEnemies[i].dest.bottom = theEnemies[i].dest.top + 40;
                                        // Check for wrap-around.
    if (theEnemies[i].dest.left > 640)
    {                                   // If off right edge, wrap around to left side.
        OffsetRect(&theEnemies[i].dest, -640, 0);
        theEnemies[i].h = theEnemies[i].dest.left << 4;
        OffsetRect(&theEnemies[i].wasDest, -640, 0);
        theEnemies[i].pass++;           // Increment number of "wrap-arounds" for this enemy.
        if (theEnemies[i].pass > 2)     // After two screen passes (wrap arounds)É
        {                               // enemy patrols a new altitude.
            theEnemies[i].targetAlt = AssignNewAltitude();
            theEnemies[i].pass = 0;
        }
    }
    else if (theEnemies[i].dest.right < 0)
    {                                   // If off left edge, wrap around to right side.
        OffsetRect(&theEnemies[i].dest, 640, 0);
        theEnemies[i].h = theEnemies[i].dest.left << 4;
        OffsetRect(&theEnemies[i].wasDest, 640, 0);
        theEnemies[i].pass++;
        if (theEnemies[i].pass > 2)
        {
            theEnemies[i].targetAlt = AssignNewAltitude();
            theEnemies[i].pass = 0;
        }
    }
                                        // Throw a touch of friction into the mix.
    theEnemies[i].vVel -= theEnemies[i].vVel >> 4;
                                        // Keep enemies from moving excessively fast.
    if (theEnemies[i].vVel > theEnemies[i].maxVVel)
        theEnemies[i].vVel = theEnemies[i].maxVVel;
    else if (theEnemies[i].vVel < -theEnemies[i].maxVVel)
        theEnemies[i].vVel = -theEnemies[i].maxVVel;
    
    CheckEnemyRoofCollision(i);         // Check for lava/celing collisions.
    CheckEnemyPlatformHit(i);           // Check for platform collisions.
}
 
//--------------------------------------------------------------  HandleWalkingEnemy
 
// This is a brief mode for an enemy.  When an enemy has hatched from an egg, itÉ
// walks only for 8 game frames at which point it takes off and flies for the restÉ
// of its life.
 
void HandleWalkingEnemy (short i)
{
    if (theEnemies[i].facingRight)      // If enemy facing right, walk to the right.
    {
        theEnemies[i].dest.left += 6;   // Move enemy to right.
        theEnemies[i].dest.right += 6;
        switch (theEnemies[i].kind)     // Determine correct graphic for walking enemy.
        {
            case kOwl:
            theEnemies[i].srcNum = 1 - theEnemies[i].srcNum;
            break;
            
            case kWolf:
            theEnemies[i].srcNum = 9 - theEnemies[i].srcNum;
            break;
            
            case kJackal:
            theEnemies[i].srcNum = 17 - theEnemies[i].srcNum;
            break;
        }
        theEnemies[i].hVel = 6 << 4;
    }
    else                                // If enemy not facing right (left), walk to the left.
    {
        theEnemies[i].dest.left -= 6;   // Move enemy to left.
        theEnemies[i].dest.right -= 6;
        switch (theEnemies[i].kind)     // Determine correct graphic for walking enemy.
        {
            case kOwl:
            theEnemies[i].srcNum = 5 - theEnemies[i].srcNum;
            break;
            
            case kWolf:
            theEnemies[i].srcNum = 13 - theEnemies[i].srcNum;
            break;
            
            case kJackal:
            theEnemies[i].srcNum = 21 - theEnemies[i].srcNum;
            break;
        }
        theEnemies[i].hVel = -6 << 4;
    }
    
    theEnemies[i].frame++;              // Increment number of frames it has walked for.
    if (theEnemies[i].frame >= 8)       // If over 8, enemy takes off an flies.
    {
        theEnemies[i].mode = kFlying;   // Switch to flying mode.
        theEnemies[i].frame = 0;        // Reset "frame" variable.
        switch (theEnemies[i].kind)     // Determine correct graphic for flying enemy.
        {
            case kOwl:
            if (theEnemies[i].facingRight)
                theEnemies[i].srcNum = 12;
            else
                theEnemies[i].srcNum = 14;
            break;
            
            case kWolf:
            if (theEnemies[i].facingRight)
                theEnemies[i].srcNum = 16;
            else
                theEnemies[i].srcNum = 18;
            break;
            
            case kJackal:
            if (theEnemies[i].facingRight)
                theEnemies[i].srcNum = 20;
            else
                theEnemies[i].srcNum = 22;
            break;
        }
                                        // Re-size enemy bounds to a "flying" size.
        theEnemies[i].dest.left -= 8;
        theEnemies[i].dest.right += 8;
        theEnemies[i].dest.bottom = theEnemies[i].dest.top + 40;
        theEnemies[i].h = theEnemies[i].dest.left * 16;
        theEnemies[i].v = theEnemies[i].dest.top * 16;
    }
}
 
//--------------------------------------------------------------  HandleSpawningEnemy
 
// This is an enemy "rising out of a platform".  Either an egg has just hatchedÉ
// or a brand new enemy has been introduced.  Irregardless, the sphinx is born.
// When the enemy is at its full height, it will begin to walk.
 
void HandleSpawningEnemy (short i)
{
    theEnemies[i].frame++;              // Advance timer.
    if (theEnemies[i].frame >= 48)      // If timer >= 48, enemy begins to walk.
    {
        theEnemies[i].mode = kWalking;
        theEnemies[i].frame = 0;
        
        switch (theEnemies[i].kind)     // Determine appropriate graphic.
        {
            case kOwl:
            if (theEnemies[i].facingRight)
                theEnemies[i].srcNum = 0;
            else
                theEnemies[i].srcNum = 2;
            break;
            
            case kWolf:
            if (theEnemies[i].facingRight)
                theEnemies[i].srcNum = 4;
            else
                theEnemies[i].srcNum = 6;
            break;
            
            case kJackal:
            if (theEnemies[i].facingRight)
                theEnemies[i].srcNum = 8;
            else
                theEnemies[i].srcNum = 10;
            break;
        }
    }
    else                            // If not full height, use "timer" to determine height.
        theEnemies[i].dest.top = theEnemies[i].dest.bottom - theEnemies[i].frame;
}
 
//--------------------------------------------------------------  HandleFallingEnemy
 
// A "falling" enemy is an air borne egg.  The enemy was killed, turned into an egg, É
// and the egg is in freefall.  If the egg comes to rest, it will begin a countdownÉ
// until it is hatched.
 
void HandleFallingEnemy (short i)
{                                   // Take into account gravity - accelerate egg down.
    theEnemies[i].vVel += kGravity;
                                    // Don't allow velocities to skyrocket.
    if (theEnemies[i].vVel > theEnemies[i].maxVVel)
        theEnemies[i].vVel = theEnemies[i].maxVVel;
    else if (theEnemies[i].vVel < -theEnemies[i].maxVVel)
        theEnemies[i].vVel = -theEnemies[i].maxVVel;
    
    if (evenFrame)                  // Apply friction on even frames (who knows).
    {                               // "Friction" is 1/32nd of the velocity.
        theEnemies[i].hVel -= (theEnemies[i].hVel >> 5);
        if ((theEnemies[i].hVel < 32) && (theEnemies[i].hVel > -32))
        {
            if (theEnemies[i].hVel > 0)
                theEnemies[i].hVel--;
            else if (theEnemies[i].hVel < 0)
                theEnemies[i].hVel++;
        }
    }
                                    // Move egg horizontally.
    theEnemies[i].h += theEnemies[i].hVel;
    theEnemies[i].dest.left = theEnemies[i].h >> 4;
    theEnemies[i].dest.right = theEnemies[i].dest.left + 24;
                                    // Move egg vertically.
    theEnemies[i].v += theEnemies[i].vVel;
    theEnemies[i].dest.top = theEnemies[i].v >> 4;
    theEnemies[i].dest.bottom = theEnemies[i].dest.top + 24;
                                    // Check for wrap around.
    if (theEnemies[i].dest.left > 640)
    {
        OffsetRect(&theEnemies[i].dest, -640, 0);
        theEnemies[i].h = theEnemies[i].dest.left << 4;
        OffsetRect(&theEnemies[i].wasDest, -640, 0);
    }
    else if (theEnemies[i].dest.right < 0)
    {
        OffsetRect(&theEnemies[i].dest, 640, 0);
        theEnemies[i].h = theEnemies[i].dest.left << 4;
        OffsetRect(&theEnemies[i].wasDest, 640, 0);
    }
    
    CheckEnemyRoofCollision(i); // See if egg hit ceiling or lava.
    CheckEnemyPlatformHit(i);   // Handle platform hit (it is here it determines ifÉ
                                // egg has come to rest and should begin countdown).
}
 
//--------------------------------------------------------------  HandleEggEnemy
 
// This is the "idle" egg mode.  This is a static egg, sitting peacefully onÉ
// a platform.  Waiting patiently so it might hatch into a death-sphinx andÉ
// slaughter the player.
 
void HandleEggEnemy (short i)
{
    short       center;
    
    theEnemies[i].frame--;              // Decrement the egg timer!
    if (theEnemies[i].frame < 24)       // When it falls below 24, egg starts shrinking.
    {                                   // Use "frame" to determine height of egg.
        theEnemies[i].dest.top = theEnemies[i].dest.bottom - theEnemies[i].frame;
        if (theEnemies[i].frame <= 0)   // When the egg is completely flat (gone)É
        {                               // then BOOM! a sphinx is spawned!
            theEnemies[i].frame = 0;
            PlayExternalSound(kSpawnSound, kSpawnPriority);
            center = (theEnemies[i].dest.left + theEnemies[i].dest.right) >> 1;
                                        // Resize enemy bounds to new "walking enemy" size.
            theEnemies[i].dest.left = center - 24;
            theEnemies[i].dest.right = center + 24;
            theEnemies[i].wasDest = theEnemies[i].dest;
            theEnemies[i].h = theEnemies[i].dest.left << 4;
            theEnemies[i].v = theEnemies[i].dest.top << 4;
                                        // Set up all other enemy variables.
            theEnemies[i].wasH = theEnemies[i].h;
            theEnemies[i].wasV = theEnemies[i].v;
            theEnemies[i].hVel = 0;
            theEnemies[i].vVel = 0;
            theEnemies[i].mode = kSpawning;
            theEnemies[i].kind++;
            if (theEnemies[i].kind > kJackal)
                theEnemies[i].kind = kJackal;
            SetEnemyAttributes(i);
        }
    }
}
 
//--------------------------------------------------------------  MoveEnemies
 
// This is the "master" enemy function.  It goes through all the enemiesÉ
// and calls the above functions depending upon an enemy's mode.
 
void MoveEnemies (void)
{
    short       i;
    
    doEnemyFlapSound = FALSE;       // Intially, assume no flap or scrape sounds.
    doEnemyScrapeSound = FALSE;
                                    // Go through each enemy.
    for (i = 0; i < numEnemies; i++)
    {
        switch (theEnemies[i].mode)
        {                           // Handle enemy according to mode it is in.
            case kIdle:             // Enemy not born yet.
            HandleIdleEnemies(i);
            break;
            
            case kFlying:           // Enemy air borne.
            HandleFlyingEnemies(i);
            break;
            
            case kWalking:          // Enemy just born, walking off platform.
            HandleWalkingEnemy(i);
            break;
            
            case kSpawning:         // Enemy growing from a platform.
            HandleSpawningEnemy(i);
            break;
            
            case kFalling:          // Enemy is an egg in flight.
            HandleFallingEnemy(i);
            break;
            
            case kEggTimer:         // Enemy is a patient, idle, silent egg.
            HandleEggEnemy(i);
            break;
            
            case kDeadAndGone:      // Enemy no more - gone for good this level.
            break;
        }
    }
                                    // If any sounds were flagged, play them.
    if (doEnemyFlapSound)
        PlayExternalSound(kFlap2Sound, kFlap2Priority);
    if (doEnemyScrapeSound)
        PlayExternalSound(kScrape2Sound, kScrape2Priority);
                                    // See if enough enemies were killed to advance toÉ
                                    // next level (wave).
    if ((deadEnemies >= numEnemiesThisLevel) && (countDownTimer == 0))
        countDownTimer = 30;
}
 
//--------------------------------------------------------------  InitHandLocation
 
// This simply sets up the hand.  Puts it deep in the lava (off bottom of screen).
 
void InitHandLocation (void)
{
    SetRect(&theHand.dest, 0, 0, 56, 57);
    OffsetRect(&theHand.dest, 48, 460);
}
 
//--------------------------------------------------------------  HandleHand
 
// This is the hand "AI".  The hand, like the sphinx enemies, has modes.
 
void HandleHand (void)
{
    Rect        whoCares;
    short       hDiff, vDiff, pull, speed;
    
    switch (theHand.mode)
    {
        case kLurking:              // Hand is down, waiting for player to stray near.
        if ((thePlayer.mode == kFlying) && (SectRect(&thePlayer.dest, &grabZone, &whoCares)))
        {                           // If player flies near, hand begins to reach out.
            theHand.mode = kOutGrabeth;
            InitHandLocation();
        }
        break;
        
        case kOutGrabeth:           // Hand is either coming after or has a hold of player.
        case kClutching:
        if (SectRect(&thePlayer.dest, &grabZone, &whoCares))
        {                           // See if player in the "grab/clutch zone".
            hDiff = theHand.dest.left - thePlayer.dest.left;
            vDiff = theHand.dest.top - thePlayer.dest.top;
                                    // Ah!  Player caught.  Move player to correctÉ
                                    // location relative to the hand (so the playerÉ
                                    // appears to, in fact, be held).
            if (thePlayer.facingRight)
                hDiff -= 3;
            else
                hDiff -= 21;
            vDiff -= 29;
                                    // How hard/fast the hand moves depends on level.
            speed = (levelOn >> 3) + 1;
            if (hDiff < 0)
            {
                theHand.dest.left += speed;
                theHand.dest.right += speed;
            }
            else if (hDiff > 0)
            {
                theHand.dest.left -= speed;
                theHand.dest.right -= speed;
            }
            if (vDiff < 0)
            {
                theHand.dest.top += speed;
                theHand.dest.bottom += speed;
            }
            else if (vDiff > 0)
            {
                theHand.dest.top -= speed;
                theHand.dest.bottom -= speed;
            }
                                    // Determine absolute distance player is from hand.
            if (hDiff < 0)
                hDiff = -hDiff;
            if (vDiff < 0)
                vDiff = -vDiff;
            
            if ((hDiff < 8) && (vDiff < 8))
            {                       // If player in the "hot zone", player is nabbed!
                theHand.mode = kClutching;
                thePlayer.clutched = TRUE;
                                    // Player's movement is severely dampened.
                thePlayer.hVel = thePlayer.hVel >> 3;
                thePlayer.vVel = thePlayer.vVel >> 3;
                                    // Hand pulls player down (strength is greater onÉ
                                    // higher levels).
                pull = levelOn << 2;
                if (pull > 48)      // Set an absolute limit on hand strength.
                    pull = 48;
                                    // Pull player donw!
                thePlayer.vVel += pull;
                theHand.dest.top = thePlayer.dest.top + 29;
                theHand.dest.bottom = theHand.dest.top + 57;
                if (thePlayer.facingRight)
                    theHand.dest.left = thePlayer.dest.left + 3;
                else
                    theHand.dest.left = thePlayer.dest.left + 21;
                theHand.dest.right = theHand.dest.left + 58;
            }
            else                    // If player not in "sweet spot", hand is seeking.
            {
                thePlayer.clutched = FALSE;
                theHand.mode = kOutGrabeth;
            }
        }
        else                        // Player not even close to handÉ
        {                           // Hand sinks back down into lava.
            theHand.dest.top++;
            theHand.dest.bottom++;
                                    // When hand is off screen, hand resumes lurking.
            if (theHand.dest.top > 460)
                theHand.mode = kLurking;
            else
                theHand.mode = kOutGrabeth;
            thePlayer.clutched = FALSE;
        }
        break;
    }
}
 
//--------------------------------------------------------------  InitEye
 
// This initializes all the eye's variables.
 
void InitEye (void)
{
    SetRect(&theEye.dest, 0, 0, 48, 31);
    OffsetRect(&theEye.dest, 296, 97);
    theEye.mode = kWaiting;
    theEye.frame = (numOwls + 2) * 720;
    theEye.srcNum = 0;
    theEye.opening = 1;
    theEye.killed = FALSE;
    theEye.entering = FALSE;
}
 
//--------------------------------------------------------------  KillOffEye
 
// This function handles a "slain" eye!
 
void KillOffEye (void)
{
    if (theEye.mode == kStalking)
    {
        theEye.killed = TRUE;
        theEye.opening = 1;
        theEye.entering = FALSE;
        if (theEye.srcNum == 0)
            theEye.srcNum = 1;
    }
    else
        InitEye();
}
 
//--------------------------------------------------------------  HandleEye
 
// But of course, the eye has modes as well.  This function handles the eyeÉ
// depending upon the mode it is in.
 
void HandleEye (void)
{
    short       diffH, diffV, speed;
    
    if (theEye.mode == kStalking)       // Eye is alive!
    {
        speed = (levelOn >> 4) + 1;     // How fast it moves depends on level.
        if (speed > 3)
            speed = 3;
                                        // When eye appears or dies, it is stationary.
        if ((theEye.killed) || (theEye.entering))
        {
            speed = 0;
        }
        else if ((thePlayer.mode != kFlying) && (thePlayer.mode != kWalking))
        {
            diffH = theEye.dest.left - 296;
            diffV = theEye.dest.bottom - 128;
        }
        else
        {
            diffH = theEye.dest.left - thePlayer.dest.left;
            diffV = theEye.dest.bottom - thePlayer.dest.bottom;
        }
                                        // Find direction to player (no wrap-around for eye).
        if (diffH > 0)
        {
            if (diffH < speed)
                theEye.dest.left -= diffH;
            else
                theEye.dest.left -= speed;
            theEye.dest.right = theEye.dest.left + 48;
        }
        else if (diffH < 0)
        {
            if (-diffH < speed)
                theEye.dest.left -= diffH;
            else
                theEye.dest.left += speed;
            theEye.dest.right = theEye.dest.left + 48;
        }
        if (diffV > 0)
        {
            if (diffV < speed)
                theEye.dest.bottom -= diffV;
            else
                theEye.dest.bottom -= speed;
            theEye.dest.top = theEye.dest.bottom - 31;
        }
        else if (diffV < 0)
        {
            if (-diffV < speed)
                theEye.dest.bottom -= diffV;
            else
                theEye.dest.bottom += speed;
            theEye.dest.top = theEye.dest.bottom - 31;
        }
        
        theEye.frame++;                 // Increment eye frame (timer).
                                        // Determine correct graphic for eye.
        if (theEye.srcNum != 0)
        {
            if (theEye.frame > 3)       // "Eye-closing frame" holds for 3 frames.
            {
                theEye.frame = 0;
                theEye.srcNum += theEye.opening;
                if (theEye.srcNum > 3)
                {
                    theEye.srcNum = 3;
                    theEye.opening = -1;
                    if (theEye.killed)
                        InitEye();
                }
                else if (theEye.srcNum <= 0)
                {
                    theEye.srcNum = 0;
                    theEye.opening = 1;
                    theEye.frame = 0;
                    theEye.entering = FALSE;
                }
            }
        }
        else if (theEye.frame > 256)
        {
            theEye.srcNum = 1;
            theEye.opening = 1;
            theEye.frame = 0;
        }
                                        // Get absolute distance from eye to player.
        diffH = theEye.dest.left - thePlayer.dest.left;
        diffV = theEye.dest.bottom - thePlayer.dest.bottom;
        if (diffH < 0)
            diffH = -diffH;
        if (diffV < 0)
            diffV = -diffV;
                                        // See if player close enough to be killed!
        if ((diffH < 16) && (diffV < 16) && (!theEye.entering) && 
                (!theEye.killed))       // Close enough to call it a kill.
        {
            if (theEye.srcNum == 0)     // If eye was open, player is killed.
            {                           // Strike lightning (hit the player).
            
                StartPixelShatterRect(&thePlayer.dest, 0, 0, kShatterPlayerDeath);
                thePlayer.electrical = 15;
                                                        // Player is smokin' bones!
                thePlayer.mode = kFalling;
                if (thePlayer.facingRight)
                    thePlayer.srcNum = 8;
                else
                    thePlayer.srcNum = 9;
                thePlayer.dest.bottom = thePlayer.dest.top + 37;
                PlayExternalSound(kBoom2Sound, kBoom2Priority);
            }
            else                        // If the eye was "blinking", IT was killed!
            {                           // Player killed the eye!
                if (lightningCount == 0)
                {                       // Strike the eye with lightning!
                    lightH = theEye.dest.left + 24;
                    lightV = theEye.dest.top + 16;
                                        // Hit 'er with 15 bolts!
                    lightningCount = 15;
                }
                theScore += 2000L;      // A big 2000 points for killing the eye!
                UpdateScoreNumbers();   // Refresh score display.
                PlayExternalSound(kBonusSound, kBonusPriority);
                
                KillOffEye();           // Slay eye!
            }                           // Hey, anyone remember that giant eye fromÉ
        }                               // Johnny Socko and his Flying Robot?
    }                                   // As a kid, I thought that was cool!
    else if (theEye.frame > 0)          // Eye has not yet appeared, but waits, lurking!
    {
        theEye.frame--;                 // Decrement eye timer.
        if (theEye.frame <= 0)          // When timer hits zero, eye appears!
        {
            theEye.mode = kStalking;    // The eye is after the player!
            if (lightningCount == 0)    // Strike lightning at eye!
            {
                lightH = theEye.dest.left + 24;
                lightV = theEye.dest.top + 16;
                lightningCount = 6;
            }
            theEye.srcNum = 3;
            theEye.opening = 1;
            theEye.entering = TRUE;
        }
    }   
}
 
//--------------------------------------------------------------  ResolveEnemyPlayerHit
 
// Okay, a bounds test determined that the player and an enemy have collided.
// This function looks at the two and determines who wins or if it's a draw.
 
void ResolveEnemyPlayerHit (short i)
{
    short       wasVel, diff, h, v;
    
    if ((theEnemies[i].mode == kFalling) || (theEnemies[i].mode == kEggTimer))
    {                       // Okay, if the enemy is an eggÉ
        deadEnemies++;      // simple - the enemy dies.
        
        theEnemies[i].mode = kDeadAndGone;
        theScore += 500L;   // Add that to our score!
        {
            Point pt;
            pt.h = theEnemies[i].dest.left;
            pt.v = theEnemies[i].dest.top;
            StartScoreFloater(500, pt);
        }
        UpdateScoreNumbers();
        PlayExternalSound(kBonusSound, kBonusPriority);
        InitEnemy(i, TRUE); // Reset the enemy (I guess you could say they're reincarnated.
    }
    else                    // Now, here's a real, live sphinx enemy.
    {                       // Get their difference in altitude.
        diff = (theEnemies[i].dest.top + 25) - (thePlayer.dest.top + 19);
        
        if (diff < -2)      // Player is bested.  :(
        {                   // Strike player with lightning.
            thePlayer.electrical = 15;
            StartPixelShatterRect(&thePlayer.dest, theEnemies[i].hVel, theEnemies[i].vVel, kShatterPlayerDeath);
 
            // Player is bones.
            thePlayer.mode = kFalling;
            if (thePlayer.facingRight)
                thePlayer.srcNum = 8;
            else
                thePlayer.srcNum = 9;
            thePlayer.dest.bottom = thePlayer.dest.top + 37;
            PlayExternalSound(kBoom2Sound, kBoom2Priority);
        }
        else if (diff > 2)  // Yes!  Enemy is killed!
        {                   // Well ... we can't kill an enemy who is spawning.
            if ((theEnemies[i].mode == kSpawning) && (theEnemies[i].frame < 16))
                return;
                            // Resize enemy bounds (use an egg bounds).
            h = (theEnemies[i].dest.left + theEnemies[i].dest.right) >> 1;
            if (theEnemies[i].mode == kSpawning)
                v = theEnemies[i].dest.bottom - 2;
            else
                v = (theEnemies[i].dest.top + theEnemies[i].dest.bottom) >> 1;
            theEnemies[i].dest.left = h - 12;
            theEnemies[i].dest.right = h + 12;
            if (theEnemies[i].mode == kSpawning)
                theEnemies[i].dest.top = v - 24;
            else
                theEnemies[i].dest.top = v - 12;
            theEnemies[i].dest.bottom = theEnemies[i].dest.top + 24;
            theEnemies[i].h = theEnemies[i].dest.left << 4;
            theEnemies[i].v = theEnemies[i].dest.top << 4;
            
            StartPixelShatterRect(&theEnemies[i].dest, thePlayer.hVel, thePlayer.vVel, kShatterEnemyDeath);
            
                            // Enemy is a falling egg!
            theEnemies[i].mode = kFalling;
            theEnemies[i].wasDest = theEnemies[i].dest;
            theEnemies[i].wasH = theEnemies[i].h;
            theEnemies[i].wasV = theEnemies[i].v;
                            // Give player points based on enemy kind.
            {
                unsigned long newScore;
                Point pt;
 
                switch (theEnemies[i].kind)
                {
                    case kOwl:
                    newScore = 500L;
                    break;
                    
                    case kWolf:
                    newScore = 1000L;
                    break;
                    
                    case kJackal:
                    newScore = 1500L;
                    break;
                }
                
                theScore += newScore;
                            
                pt.h = theEnemies[i].dest.left;
                pt.v = theEnemies[i].dest.top;
                StartScoreFloater(newScore, pt);
            }
 
            UpdateScoreNumbers();
            PlayExternalSound(kBoom2Sound, kBoom2Priority);
        }
        else        // Rare case - neither the player nor the enemy get killed.
        {           // They'll bounce off one another.
            if (theEnemies[i].hVel > 0)
                theEnemies[i].facingRight = TRUE;
            else
                theEnemies[i].facingRight = FALSE;
            PlayExternalSound(kScreechSound, kScreechPriority);
            
            StartPixelShatter(  (theEnemies[i].dest.left + thePlayer.dest.right) / 2, 
                                (theEnemies[i].dest.top + thePlayer.dest.bottom) / 2, 0,0, kShatterPlayerEnemyScrape);
        }
        
        wasVel = thePlayer.hVel;
        thePlayer.hVel = theEnemies[i].hVel;
        theEnemies[i].hVel = wasVel;
        wasVel = thePlayer.vVel;
        thePlayer.vVel = theEnemies[i].vVel;
        theEnemies[i].vVel = wasVel;
    }
}
 
//--------------------------------------------------------------  CheckPlayerEnemyCollision
 
// This is a simple "bounds test" for determining player/enemy collisions.
 
void CheckPlayerEnemyCollision (void)
{
    Rect        whoCares, playTest, wrapTest;
    short       i;
    
    playTest = thePlayer.dest;      // Make a copy of player's bounds.
    InsetRect(&playTest, 8, 8);     // Shrink it by 8 pixels all 'round.
    if (thePlayer.wrapping)         // Need to test 2 players if "wraparounding".
        wrapTest = thePlayer.wrap;
    InsetRect(&wrapTest, 8, 8);
                                    // Test all enemies.
    for (i = 0; i < numEnemies; i++)
    {                               // Ignore non-existant enemies.
        if ((theEnemies[i].mode != kIdle) && (theEnemies[i].mode != kDeadAndGone))
        {                           // Simple bounds test.
            if (SectRect(&playTest, &theEnemies[i].dest, &whoCares))
            {                       // Call function to determine who wins (or tie).
                ResolveEnemyPlayerHit(i);
            }                       // If "wrap-arounding", test other rect.
            else if (thePlayer.wrapping)
            {
                if (SectRect(&wrapTest, &theEnemies[i].dest, &whoCares))
                    ResolveEnemyPlayerHit(i);
            }
        }
    }
}