//============================================================================ //---------------------------------------------------------------------------- // Enemy.c //---------------------------------------------------------------------------- //============================================================================ // This file contains all enemy related functions (enemy "AI"). It handlesÉ // the enemy decision making proccess, moves the enemies, etc. #include "Externs.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, lightningCount, numEnemies, countDownTimer; extern short levelOn, lightH, lightV; 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. 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). if (lightningCount == 0) { lightH = thePlayer.dest.left + 24; lightV = thePlayer.dest.bottom - 24; lightningCount = 6; // Strike 6 times! } // 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! 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. if (lightningCount == 0) { lightH = thePlayer.dest.left + 24; lightV = thePlayer.dest.bottom - 24; lightningCount = 6; } // 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; // 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. switch (theEnemies[i].kind) { case kOwl: theScore += 500L; break; case kWolf: theScore += 1000L; break; case kJackal: theScore += 1500L; break; } 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); } 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); } } } }