//============================================================================ //---------------------------------------------------------------------------- // Play.c //---------------------------------------------------------------------------- //============================================================================ // This (rather large) file handles all player routines while a game is inÉ // progress. It gets the player's input, moves the player, tests for collisionsÉ // and generally handles the "main game loop". Enemies and actually drawingÉ // the graphics to the screen are handled in other files. #include "Externs.h" #define kFlapImpulse 48 #define kGlideImpulse 12 #define kAirResistance 2 #define kMaxHVelocity 192 #define kMaxVVelocity 512 #define kNumLightningStrikes 5 void SetUpLevel (void); void ResetPlayer (Boolean); void OffAMortal (void); void DoCommandKey (void); void GetPlayerInput (void); void HandlePlayerIdle (void); void HandlePlayerFlying (void); void HandlePlayerWalking (void); void HandlePlayerSinking (void); void HandlePlayerFalling (void); void HandlePlayerBones (void); void MovePlayer (void); void CheckTouchDownCollision (void); void CheckPlatformCollision (void); void KeepPlayerOnPlatform (void); void CheckLavaRoofCollision (void); void SetAndCheckPlayerDest (void); void HandleLightning (void); void FinishLightning (void); void HandleCountDownTimer (void); void CheckHighScore (void); playerType thePlayer; enemyType theEnemies[kMaxEnemies]; KeyMap theKeys; Rect platformRects[6], touchDownRects[6], enemyRects[24]; Rect enemyInitRects[5]; long theScore, wasTensOfThousands; short numLedges, beginOnLevel, levelOn, livesLeft, lightH, lightV; short lightningCount, numEnemies, countDownTimer; Boolean playing, pausing, flapKeyDown, evenFrame; Boolean doEnemyFlapSound, doEnemyScrapeSound; extern handInfo theHand; extern prefsInfo thePrefs; extern Rect playerRects[11], mainWindowRect; extern short numUpdateRects1, numUpdateRects2, numOwls; extern Boolean quitting, openTheScores; //============================================================== Functions //-------------------------------------------------------------- InitNewGame // This funciton sets up variables and readies for a new game. It is calledÉ // only when a the user selects "New Game" - during the course of a game, itÉ // is not called again. void InitNewGame (void) { // Initialize a number of game variables. countDownTimer = 0; // Zero count down timer. numLedges = 3; // Initial number of ledges (platforms). beginOnLevel = 1; // Ledge (platform) the player is on (center ledge). levelOn = 0; // Game level on (first level). livesLeft = kInitNumLives; // Number of player lives remaining. theScore = 0L; // Player's score (a long - can go to 2 billion). playing = TRUE; // Flag playing. pausing = FALSE; // Not paused. evenFrame = TRUE; // Set an initial state for evenFrame. wasTensOfThousands = 0L; // Used for noting when player gets an extra life. numOwls = 4; // Number of "owl" enemies for this level. numUpdateRects1 = 0; // Init number of "update" rectangles. numUpdateRects2 = 0; // (see Render.c to see what these do) InitHandLocation(); // Get the mummy hand down in the lava. theHand.mode = kLurking; // Flag the hand in "lurking" mode. SetUpLevel(); // Set up platforms for first level (wave). DumpBackToWorkMap(); // Copy background offscreen to "work" offscreen. UpdateLivesNumbers(); // Display number of lives remaining on screen. UpdateScoreNumbers(); // Display the player's score (zero at this point). UpdateLevelNumbers(); // Display the level (wave) the player is on. GenerateEnemies(); // Prepare all enemies for this level. ResetPlayer(TRUE); // Initialize all player variables and put on ledge. } //-------------------------------------------------------------- SetUpLevel // Primarily, this function is called to set up the ledges for theÉ // current level (wave) the player is on. It determines how manyÉ // are required and then draws these offscreen. It also flashesÉ // the obelisks and strikes the lightning. void SetUpLevel (void) { short wasLedges, waveMultiple; KillOffEye(); // Return eye to the aether. wasLedges = numLedges; // Remember number of ledges. waveMultiple = levelOn % 5; // Waves repeat every 5th wave (but harder!). switch (waveMultiple) // See which of the 5 we're on. { case 0: // Waves 0, 5, 10, É numLedges = 5; // have 5 ledges (platforms) on screen. break; case 1: // Waves 1, 6, 11, É numLedges = 6; // are up to 6 ledges (platforms) on screen. break; case 2: // Waves 2, 7, 12, É numLedges = 5; // return to 5 ledges (platforms) on screen. break; case 3: // Waves 3, 8, 13, É numLedges = 3; // drop to 3 ledges (platforms) on screen. break; case 4: // Waves 4, 9, 14, É numLedges = 6; // and return to 6 ledges (platforms) on screen. break; } if (wasLedges != numLedges) // No need to redraw if platforms are unchanged. DrawPlatforms(numLedges); FlashObelisks(TRUE); // Flash the obelisks. GenerateLightning(320, 429); // Lightning strikes platform 0. StrikeLightning(); LogNextTick(2); WaitForNextTick(); StrikeLightning(); GenerateLightning(95, 289); // Lightning strikes platform 1. StrikeLightning(); LogNextTick(2); WaitForNextTick(); StrikeLightning(); GenerateLightning(95, 110); // Lightning strikes platform 3. StrikeLightning(); LogNextTick(2); WaitForNextTick(); StrikeLightning(); GenerateLightning(320, 195); // Lightning strikes platform 5. StrikeLightning(); LogNextTick(2); WaitForNextTick(); StrikeLightning(); GenerateLightning(545, 110); // Lightning strikes platform 4. StrikeLightning(); LogNextTick(2); WaitForNextTick(); StrikeLightning(); GenerateLightning(545, 289); // Lightning strikes platform 2. StrikeLightning(); LogNextTick(2); WaitForNextTick(); StrikeLightning(); FlashObelisks(FALSE); // "Unflash" obelisks (return to normal state). // Play lightning sound! PlayExternalSound(kLightningSound, kLightningPriority); UpdateLevelNumbers(); // Display the current level on screen. } //-------------------------------------------------------------- ResetPlayer // This function prepares the player - it places the player and his/her mountÉ // in their proper starting location (depending on which platform they are toÉ // begin on), and it sets all the player's variables to their initial state. void ResetPlayer (Boolean initialPlace) { short location; thePlayer.srcNum = 5; // Set which graphic (frame) the player is to use. thePlayer.frame = 320; // This variable will be used as a coutndown timer. if (initialPlace) // If "initialPlace" is TRUE, É location = 0; // the player is to begin on the lowest platform. else // Otherwise, a random location is chosen. location = RandomInt(numLedges); switch (location) // Move player horizontally and vertically to theirÉ { // proper location (based on ledge # they're on). case 0: thePlayer.h = 296 << 4; // Bottom center ledge. thePlayer.v = 377 << 4; // We're scaling by 16. break; case 1: thePlayer.h = 102 << 4; // Lower left ledge. thePlayer.v = 237 << 4; break; case 2: thePlayer.h = 489 << 4; // Lower right ledge. thePlayer.v = 237 << 4; break; case 3: thePlayer.h = 102 << 4; // Top left ledge. thePlayer.v = 58 << 4; break; case 4: thePlayer.h = 489 << 4; // Top right ledge. thePlayer.v = 58 << 4; break; case 5: thePlayer.h = 296 << 4; // Top central ledge. thePlayer.v = 143 << 4; break; } // Assign destination rectangle. thePlayer.dest = playerRects[thePlayer.srcNum]; ZeroRectCorner(&thePlayer.dest); OffsetRect(&thePlayer.dest, thePlayer.h >> 4, thePlayer.v >> 4); thePlayer.wasDest = thePlayer.dest; thePlayer.hVel = 0; // Player initially has no velocity. thePlayer.vVel = 0; thePlayer.facingRight = TRUE; // We're facing to the right. thePlayer.flapping = FALSE; // We're not flapping our wings initially. thePlayer.wrapping = FALSE; // We can't be wrapping around the edge of the screen. thePlayer.clutched = FALSE; // The hand ain't got us. thePlayer.mode = kIdle; // Our mode is "idle" - waiting to be "born". if (lightningCount == 0) // Prepare for a lightning display to "birth" us. { lightH = thePlayer.dest.left + 24; lightV = thePlayer.dest.bottom - 24; lightningCount = kNumLightningStrikes; } } //-------------------------------------------------------------- OffAMortal // Alas, 'tis here that a player is brought who loses a life. void OffAMortal (void) { livesLeft--; // Decrememnt number of player lives left. if (livesLeft > 0) // Indeed, are there lives remaining? { ResetPlayer(FALSE); // Good, start a new one off. UpdateLivesNumbers(); // Make note of the number of lives remaining. } else // Otherwise, we are at the dreaded "Game Over". playing = FALSE; // Set flag to drop us out of game loop. } //-------------------------------------------------------------- DoCommandKey // This function handles the case when the user has held down the commandÉ // key. Note, this only applies to input when a game is in session - otherwiseÉ // a standard event loop handles command keys and everything else. void DoCommandKey (void) { if (BitTst(&theKeys, kEKeyMap)) // Test for "command - E"É { playing = FALSE; // which would indicate "End Game". } else if (BitTst(&theKeys, kPKeyMap)) // Otherwise, see if it's "command - P". { pausing = TRUE; // This means the player is pausing the game. MenusReflectMode(); // Gray-out menus etc. DumpMainToWorkMap(); // Save screen to offscreen. } else if (BitTst(&theKeys, kQKeyMap)) // Or perhaps the player hit "command - Q". { playing = FALSE; // Set flag to drop out of game loop. quitting = TRUE; // Set flag to drop out of Glypha. } } //-------------------------------------------------------------- GetPlayerInput // This function looks for keystrokes when a game is underway. We don't useÉ // the more conventional event routines (like GetNextEvent()), because they'reÉ // notoriously slow, allow background tasks, introduce possible INIT problems,É // and we don't have to. Instead, we'll rely on GetKeys() (which has its ownÉ // set of problems - but we deal with them). void GetPlayerInput (void) { thePlayer.flapping = FALSE; // Assume we're not flapping. thePlayer.walking = FALSE; // Assume too we're not walking. GetKeys(theKeys); // Get the current keyboard keymap. if (BitTst(&theKeys, kCommandKeyMap)) // See first if command key downÉ DoCommandKey(); // and handle those seperately. else // If not command key, continue. { // Look for one of the two "flap" keys. if ((BitTst(&theKeys, kSpaceBarMap)) || (BitTst(&theKeys, kDownArrowKeyMap))) { if (thePlayer.mode == kIdle) // Handle special case when player is idle. { thePlayer.mode = kWalking; // Set the player's mode now to walking. thePlayer.frame = 0; // Used to note "state" of walking. } // Otherwise, if player is flying or walkingÉ else if ((thePlayer.mode == kFlying) || (thePlayer.mode == kWalking)) { if (!flapKeyDown) // If flap key was not down last frameÉ { // (this is to prevent "automatic fire"). // Give player lift. thePlayer.vVel -= kFlapImpulse; flapKeyDown = TRUE; // Note that the flap key is down. // Play the "flap" sound. PlayExternalSound(kFlapSound, kFlapPriority); // Set player flag to indicate flapping. thePlayer.flapping = TRUE; } } } else flapKeyDown = FALSE; // If flap key not down, remember this. // Test now for one of three "right" keys. if ((BitTst(&theKeys, kRightArrowKeyMap) || BitTst(&theKeys, kSKeyMap) || BitTst(&theKeys, kQuoteMap)) && (thePlayer.hVel < kMaxHVelocity)) { if (thePlayer.mode == kIdle) // Handle special case when player idle. { // They are to begin walking (no longer idle). thePlayer.mode = kWalking; thePlayer.frame = 0; } else if ((thePlayer.mode == kFlying) || (thePlayer.mode == kWalking)) { // If flying or walking, player moves right. if (!thePlayer.facingRight) // If facing left, player does an about face. { thePlayer.facingRight = TRUE; if (thePlayer.clutched) { thePlayer.dest.left += 18; thePlayer.dest.right += 18; thePlayer.h = thePlayer.dest.left << 4; thePlayer.wasH = thePlayer.h; thePlayer.wasDest = thePlayer.dest; } } // Otherwise, if facing right alreadyÉ else { // If flying, add to their horizontal velocity. if (thePlayer.mode == kFlying) thePlayer.hVel += kGlideImpulse; else // If walking, set flag to indicate a step. thePlayer.walking = TRUE; } } } // Test now for one of three "left" keys. else if ((BitTst(&theKeys, kLeftArrowKeyMap) || BitTst(&theKeys, kAKeyMap) || BitTst(&theKeys, kColonMap)) && (thePlayer.hVel > -kMaxHVelocity)) { if (thePlayer.mode == kIdle) // Handle special case when player idle. { thePlayer.mode = kWalking; thePlayer.frame = 0; } else if ((thePlayer.mode == kFlying) || (thePlayer.mode == kWalking)) { // If flying or walking, player moves left. if (thePlayer.facingRight) // If facing right, player does an about face. { // Flag player facing left. thePlayer.facingRight = FALSE; if (thePlayer.clutched) // Handle case where player gripped by hand. { // An about face handled a bit differently. thePlayer.dest.left -= 18; thePlayer.dest.right -= 18; thePlayer.h = thePlayer.dest.left << 4; thePlayer.wasH = thePlayer.h; thePlayer.wasDest = thePlayer.dest; } } else // Otherwise, player already facing left. { // So player will move left. if (thePlayer.mode == kFlying) thePlayer.hVel -= kGlideImpulse; else thePlayer.walking = TRUE; } } } } } //-------------------------------------------------------------- HandlePlayerIdle // Following are a number of functions handling the player's different "modes". // This first function handles the player when in "idle" mode. When idle, theÉ // player is standing on a platform - having just been "born". This is when theÉ // player is in a "safe" mode - meaning no enemy can kill them. The player remainsÉ // in idle mode until they hit a key to flap or move or until a timer (thePlayer.frame)É // counts down to zero. void HandlePlayerIdle (void) { thePlayer.frame--; // Count down the timer. if (thePlayer.frame == 0) // See if timer has reached zero yet. thePlayer.mode = kWalking; // If so, player is no longer idle. SetAndCheckPlayerDest(); // Keep player on platform. } //-------------------------------------------------------------- HandlePlayerFlying // This function handles a player in "flying" mode. In flying mode, the playerÉ // is alive and not standing/walking on any platform. A plyaer remains in flyingÉ // mode until the player dies (collides unfavorably with an enemy), is caught byÉ // the hand, or comes near the top of a platform (in which case they land andÉ // switch to walking mode). While in flying mode, gravity pulls the player downÉ // while friction acts to slow the player down. void HandlePlayerFlying (void) { if (thePlayer.hVel > 0) // If player has a positive hori. velocityÉ { // subtract frictional constant from velocity. thePlayer.hVel -= kAirResistance; if (thePlayer.hVel < 0) // Don't let it go negative (otherwise, youÉ thePlayer.hVel = 0; // can get a "yo-yo" effect set up). } else if (thePlayer.hVel < 0) // Otherwise, if horizontal velocity negativeÉ { // add firctional constant to hori. velocity. thePlayer.hVel += kAirResistance; if (thePlayer.hVel > 0) thePlayer.hVel = 0; } thePlayer.vVel += kGravity; // Add gravity to player's vertical velocity. if (thePlayer.vVel > kMaxVVelocity) // Don't allow player to fall too fast. thePlayer.vVel = kMaxVVelocity; else if (thePlayer.vVel < -kMaxVVelocity) thePlayer.vVel = -kMaxVVelocity; // And don't allow player to climb too fast. thePlayer.h += thePlayer.hVel; // Add velocities to players position. thePlayer.v += thePlayer.vVel; // Now we determine which graphic to use. if (thePlayer.facingRight) // There are the set of right-facing graphics. { thePlayer.srcNum = 1; // Assume standard right-facing graphic. if (thePlayer.vVel < -kDontFlapVel) // Now we jump through a series of hoopsÉ { // simply to determine whether we'll useÉ if (thePlayer.flapping) // the graphic of the player with the wingsÉ thePlayer.srcNum = 0; // up (srcNum = 0) or with the wings downÉ else // (srcNum = 1). thePlayer.srcNum = 1; } else if (thePlayer.vVel > kDontFlapVel) { if (thePlayer.flapping) thePlayer.srcNum = 1; else thePlayer.srcNum = 0; } else if (thePlayer.flapping) thePlayer.srcNum = 0; } else // If the player is facing leftÉ { // We jump through a similar set of hoopsÉ thePlayer.srcNum = 2; // this time choosing between srcNum = 2 É if (thePlayer.vVel < -kDontFlapVel) // and srcNum = 3. { if (thePlayer.flapping) thePlayer.srcNum = 3; else thePlayer.srcNum = 2; } else if (thePlayer.vVel > kDontFlapVel) { if (thePlayer.flapping) thePlayer.srcNum = 2; else thePlayer.srcNum = 3; } else if (thePlayer.flapping) thePlayer.srcNum = 3; } SetAndCheckPlayerDest(); // Check for wrap-around, etc. CheckLavaRoofCollision(); // See if player hit top or bottom of screen. CheckPlayerEnemyCollision(); // See if player hit an enemy. CheckPlatformCollision(); // See if player collided with platform. CheckTouchDownCollision(); // See if player has landed on platform. } //-------------------------------------------------------------- HandlePlayerWalking // This function handles a player in "walking" mode. They remain in this modeÉ // until they walk off a platform's edge, flap to lift off the platform, orÉ // collide unfavorably with an enemy (die). While in walking mode, we need onlyÉ // determine which frame of animation to display (if the player is taking steps)É // and check for the usual set of collisions. void HandlePlayerWalking (void) { short desiredHVel; if (thePlayer.walking) // This means user is actively holding downÉ { // the left or right key. if (evenFrame) // Now we jump through a number of hoopsÉ { // in order to get a semi-realisticÉ if (thePlayer.facingRight) // "stepping" animation going. We take stepsÉ { // only on "even frames". if (thePlayer.srcNum == 4) desiredHVel = 208; else desiredHVel = 128; } else { if (thePlayer.srcNum == 7) desiredHVel = -208; else desiredHVel = -128; } if (thePlayer.hVel < desiredHVel) { thePlayer.hVel += 80; // Move player right. if (thePlayer.hVel > desiredHVel) { // This is the case where player is walking. thePlayer.hVel = desiredHVel; PlayExternalSound(kWalkSound, kWalkPriority); } else // In this case, player is skidding. PlayExternalSound(kScreechSound, kScreechPriority); } else { thePlayer.hVel -= 80; // Move player to the left. if (thePlayer.hVel < desiredHVel) { // Player is stepping to left. thePlayer.hVel = desiredHVel; PlayExternalSound(kWalkSound, kWalkPriority); } else // Player is skidding to a stop. PlayExternalSound(kScreechSound, kScreechPriority); } } } else // If user is not actively holding down theÉ { // left or right key, bring player to a stop. thePlayer.hVel -= thePlayer.hVel / 4; if ((thePlayer.hVel < 4) && (thePlayer.hVel > -4)) thePlayer.hVel = 0; // If close to zero (within 4), stop player. else // Othewrwise, play the skidding sound. PlayExternalSound(kScreechSound, kScreechPriority); } if (thePlayer.vVel > kMaxVVelocity) // Keep player from moving too quicklyÉ thePlayer.vVel = kMaxVVelocity; // left or right. else if (thePlayer.vVel < -kMaxVVelocity) thePlayer.vVel = -kMaxVVelocity; thePlayer.h += thePlayer.hVel; // Move player horizontally and verticallyÉ thePlayer.v += thePlayer.vVel; // by the corresponding velocity. if (thePlayer.walking) // "If player holding down left or right keysÉ". { if (evenFrame) // Here's where we toggle between the twoÉ { // frames of "stepping" animation. if (thePlayer.facingRight) thePlayer.srcNum = 9 - thePlayer.srcNum; else thePlayer.srcNum = 13 - thePlayer.srcNum; } } else // If the player not holding down keysÉ { // draw the player just standing there. if (thePlayer.facingRight) thePlayer.srcNum = 5; else thePlayer.srcNum = 6; } SetAndCheckPlayerDest(); // Check for wrap-around and all that. CheckTouchDownCollision(); // See if player still on platform. KeepPlayerOnPlatform(); // Don't let player "sink through" ledge. CheckPlayerEnemyCollision(); // See if player hit an enemy. } //-------------------------------------------------------------- HandlePlayerSinking // When the player is in "sinking" mode, they are on a one-way ticket to death. // The player is sinking into the lava. We put the player into this mode (ratherÉ // than kill them outright) so that we can have a number of frames of them slowlyÉ // slipping beneath the surface of the lava. When the get below the surface ofÉ // the lava, they will be officially "killed" and a new player will be "born", void HandlePlayerSinking (void) { thePlayer.hVel = 0; // Don't allow horizontal motion. thePlayer.vVel = 16; // They will sink at this constant rate. if (thePlayer.dest.top > kLavaHeight) // See if they slipped below the surface. OffAMortal(); // If they did, kill 'em. thePlayer.v += thePlayer.vVel; // Otherwise, move them down a notch. SetAndCheckPlayerDest(); // Check for wrap-around, etc. } //-------------------------------------------------------------- HandlePlayerFalling // "Falling" refers to a player who is dead already but is still careeningÉ // down the screen as a skeleton. If (when) the player lands on a ledge theyÉ // will turn into a pile of bones for a short duration. If instead they fallÉ // into the lava, they'll sink. In any event, it is then that they areÉ // officially pronounced dead and a new player is born. void HandlePlayerFalling (void) { if (thePlayer.hVel > 0) // Handle horizontal air resistance. { thePlayer.hVel -= kAirResistance; if (thePlayer.hVel < 0) thePlayer.hVel = 0; } else if (thePlayer.hVel < 0) { thePlayer.hVel += kAirResistance; if (thePlayer.hVel > 0) thePlayer.hVel = 0; } thePlayer.vVel += kGravity; // Add in effect of gravity. if (thePlayer.vVel > kMaxVVelocity) // Keep player from falling too fast. thePlayer.vVel = kMaxVVelocity; else if (thePlayer.vVel < -kMaxVVelocity) thePlayer.vVel = -kMaxVVelocity; thePlayer.h += thePlayer.hVel; // Move player's x and y (h and v)É thePlayer.v += thePlayer.vVel; // by amount of velocity in each direction. SetAndCheckPlayerDest(); // Check for wrap-around, etc. CheckLavaRoofCollision(); // See if they hit roof or lava. CheckPlatformCollision(); // See if they crashed to a ledge. } //-------------------------------------------------------------- HandlePlayerBones // This is when the player is just a static pile of bones on a platform. TheyÉ // have been killed by an enemy and now are waiting to slip away so that a newÉ // player can be born. void HandlePlayerBones (void) { if (evenFrame) // To slow it down a bit, action only occursÉ { // on the even frames. thePlayer.frame--; // Drop the counter down by one. if (thePlayer.frame == 0) // When counter reaches zero, player officially dies. OffAMortal(); else // Otherwise, player's bones are sinking. thePlayer.dest.top = thePlayer.dest.bottom - thePlayer.frame; } } //-------------------------------------------------------------- MovePlayer // This function is the sort of "master movement" function. It looksÉ // at what mode a player is in and calls the appropriate function fromÉ // above. Arcade games (at least this one) tend to be very "modal" inÉ // this way. It's the actions of the user and the enemies in the gameÉ // that cause the player's mode to move from one state to another. void MovePlayer (void) { switch (thePlayer.mode) // Check the "mode" the player is in. { case kIdle: // Invulnerable - standing there - just born. HandlePlayerIdle(); break; case kFlying: // Flapping, floating, airborne. HandlePlayerFlying(); break; case kWalking: // On terra firma. Standing or walking on ledge. HandlePlayerWalking(); break; case kSinking: // Trapped in the lava - going down. HandlePlayerSinking(); break; case kFalling: // Dead - a skeleton falling to earth. HandlePlayerFalling(); break; case kBones: // Dead - a static pile of bones on a ledge. HandlePlayerBones(); break; } } //-------------------------------------------------------------- CheckTouchDownCollision // This function determines whether or not the player is landed on a ledge. // It does this by doing a rectangle collision between the player's boundingÉ // rectangle and an imaginary rectangle enclosing an area above the ledges. // I call these imaginary rectangles "touchDownRects[]". The trick was thatÉ // you don't want the player to have to "hit" the top of a ledge in order toÉ // land on it - there is an arbitrary distance above a ledge where, if the playerÉ // is within this area, the legs ought to come out and the player flagged asÉ // walking. As well, this same function is used for a walking player to seeÉ // if they are still on the ledge (they may walk off the edge). void CheckTouchDownCollision (void) { Rect testRect, whoCares; short i, offset; Boolean sected; sected = FALSE; // Assume not on ledge. for (i = 0; i < numLedges; i++) // Go through all ledges. { testRect = touchDownRects[i]; // Here's the imaginary rect. if (thePlayer.mode == kWalking) // We need an offset if player walkingÉ OffsetRect(&testRect, 0, 11); // since the player graphic is taller. if (SectRect(&thePlayer.dest, &testRect, &whoCares)) { // Does the player's rect intersect? if (thePlayer.mode == kFlying) // Okay, it does, is the player airborne? { thePlayer.mode = kWalking; // Put player into walking mode. if (thePlayer.facingRight) // Assign correct graphic for player. thePlayer.srcNum = 5; else thePlayer.srcNum = 6; if (thePlayer.vVel > 0) // Stop player from falling further. thePlayer.vVel = 0; thePlayer.dest.bottom += 11; // "Grow" player's bounding rect. thePlayer.wasDest.bottom += 11; // Move player so standing on top of ledge. offset = thePlayer.dest.bottom - testRect.bottom - 1; thePlayer.dest.bottom -= offset; thePlayer.dest.top -= offset; thePlayer.v = thePlayer.dest.top << 4; // Play brief collision sound. PlayExternalSound(kGrateSound, kGratePriority); } sected = TRUE; // Make note that we've landed. } } if (!sected) // Now, if we didn't collideÉ { // were we walking? if (thePlayer.mode == kWalking) // Did we walk off the ledge? { thePlayer.mode = kFlying; // Set player to flying mode. thePlayer.dest.bottom -= 11; // Resize player's bounding rect. thePlayer.wasDest.bottom -= 11; } } } //-------------------------------------------------------------- CheckPlatformCollision // Unlike the above function, this one tests the player's bounding rect againstÉ // the bounding rect of each ledge (not an imaginary rect above the ledge). ThisÉ // function is primarily for (then) collisions off the bottom and sides of theÉ // ledges. In this way, the ledges are "solid" - not able to be passed through. void CheckPlatformCollision (void) { Rect hRect, vRect, whoCares; short i, offset; for (i = 0; i < numLedges; i++) // Walk through all ledges. { // Test rectangle overlap. if (SectRect(&thePlayer.dest, &platformRects[i], &whoCares)) { // If player intersecting ledgeÉ hRect.left = thePlayer.dest.left; // Create our special test rect. hRect.right = thePlayer.dest.right; hRect.top = thePlayer.wasDest.top; hRect.bottom = thePlayer.wasDest.bottom; // Determine if the player hit theÉ // top/bottom of the ledge or theÉ // sides of the ledge. if (SectRect(&hRect, &platformRects[i], &whoCares)) { // We're fairly sure the player hitÉ // the left or right edge of ledge. if (thePlayer.h > thePlayer.wasH) // If player was heading rightÉ { // player will bounce to left. offset = thePlayer.dest.right - platformRects[i].left; thePlayer.dest.left -= offset; thePlayer.dest.right -= offset; thePlayer.h = thePlayer.dest.left << 4; if (thePlayer.hVel > 0) // We bounce back with 1/2 our vel. thePlayer.hVel = -(thePlayer.hVel >> 1); else thePlayer.hVel = thePlayer.hVel >> 1; } // Else if player was heading leftÉ else if (thePlayer.h < thePlayer.wasH) { // player will bounce right. offset = platformRects[i].right - thePlayer.dest.left; thePlayer.dest.left += offset; thePlayer.dest.right += offset; thePlayer.h = thePlayer.dest.left << 4; if (thePlayer.hVel < 0) // We bounce back with 1/2 our vel. thePlayer.hVel = -(thePlayer.hVel >> 1); else thePlayer.hVel = thePlayer.hVel >> 1; } // Play impact sound. PlayExternalSound(kGrateSound, kGratePriority); } else // It doesn't look like we hit theÉ { // the left or right edge of ledge. vRect.left = thePlayer.wasDest.left; vRect.right = thePlayer.wasDest.right; vRect.top = thePlayer.dest.top; vRect.bottom = thePlayer.dest.bottom; // So we'll test top/bottom collision. if (SectRect(&vRect, &platformRects[i], &whoCares)) { // We've decided we've hit top/bottom. if (thePlayer.wasV < thePlayer.v) { // If we were heading down (hit top)É // keep player on top of ledge. offset = thePlayer.dest.bottom - platformRects[i].top; thePlayer.dest.top -= offset; thePlayer.dest.bottom -= offset; thePlayer.v = thePlayer.dest.top << 4; // Play collision sound. if (thePlayer.vVel > kDontFlapVel) PlayExternalSound(kGrateSound, kGratePriority); // If we were falling bones (dead)É if (thePlayer.mode == kFalling) { // we'll bounce. if ((thePlayer.dest.right - 16) > platformRects[i].right) { thePlayer.hVel = 16; if (thePlayer.vVel > 0) thePlayer.vVel = -(thePlayer.vVel >> 1); else thePlayer.vVel = thePlayer.vVel >> 1; } else if ((thePlayer.dest.left + 16) < platformRects[i].left) { thePlayer.hVel = -16; if (thePlayer.vVel > 0) thePlayer.vVel = -(thePlayer.vVel >> 1); else thePlayer.vVel = thePlayer.vVel >> 1; } else // If we were nearly stoppedÉ { // turn into pile of bones. PlayExternalSound(kBoom1Sound, kBoom1Priority); thePlayer.vVel = 0; thePlayer.mode = kBones; thePlayer.frame = 22; thePlayer.dest.top = thePlayer.dest.bottom - 22; thePlayer.v = thePlayer.dest.top << 4; thePlayer.srcNum = 10; } } else // Okay, if we weren't falling bonesÉ { // bounce the player (-1/2 vel.). if (thePlayer.vVel > 0) thePlayer.vVel = -(thePlayer.vVel >> 1); else thePlayer.vVel = thePlayer.vVel >> 1; } } // If the player was instead moving upÉ else if (thePlayer.wasV > thePlayer.v) { // the player likely hit the bottom ofÉ // the ledge. Keep player below ledge. offset = platformRects[i].bottom - thePlayer.dest.top; thePlayer.dest.top += offset; thePlayer.dest.bottom += offset; thePlayer.v = thePlayer.dest.top << 4; // Play collision sound. PlayExternalSound(kGrateSound, kGratePriority); if (thePlayer.vVel < 0) // Bounce player down (-1/2 vel.). thePlayer.vVel = -(thePlayer.vVel >> 1); else thePlayer.vVel = thePlayer.vVel >> 1; } } } } } } //-------------------------------------------------------------- KeepPlayerOnPlatform // This is an alignment function. It is called only if the player is standing orÉ // walking on a ledge. It is designed to keep the player's mount's (bird's)É // feet firmly planted on the ledge. Consider that, with the addition of gravityÉ // to a player's downward velocity, there is a problem where the player can appearÉ // to slowly sink down through the ledge. There may be any number of methods youÉ // might want to try to prevent this from becoming a problem in the first place, É // but my experience has been that all the methods I've tried have flaws - correctingÉ // for those flaws points out other flaws and you start getting a messy sort ofÉ // patchwork. Should you ever get it to work, the mess that is your function has comeÉ // to resemble the Knot of ????. void KeepPlayerOnPlatform (void) { Rect whoCares; short i, offset; for (i = 0; i < numLedges; i++) // For each ledge for this waveÉ { // test for a collision. if ((SectRect(&thePlayer.dest, &platformRects[i], &whoCares)) && (thePlayer.vVel > 0)) { // If collided (player sinking), forceÉ // player to top of ledge. offset = thePlayer.dest.bottom - platformRects[i].top - 1; thePlayer.dest.top -= offset; thePlayer.dest.bottom -= offset; thePlayer.v = thePlayer.dest.top * 16; } } if (thePlayer.vVel > 0) // Set player's vertical velocity to zero. thePlayer.vVel = 0; } //-------------------------------------------------------------- CheckLavaRoofCollision // This is a simple high/low test to see if the player has either bounced offÉ // the roof of the "arena" or dipped down into the lava below. void CheckLavaRoofCollision (void) { short offset; if (thePlayer.dest.bottom > kLavaHeight) // See if player in lava. { if (thePlayer.mode == kFalling) // If falling (dead), "Splash!" PlayExternalSound(kSplashSound, kSplashPriority); else // If flying (alive), "Yeow!" PlayExternalSound(kBirdSound, kBirdPriority); thePlayer.mode = kSinking; // Irregardless, player is now sinking. } else if (thePlayer.dest.top < kRoofHeight) // See if player hit roof. { // Move player to below roof. offset = kRoofHeight - thePlayer.dest.top; thePlayer.dest.top += offset; thePlayer.dest.bottom += offset; thePlayer.v = thePlayer.dest.top * 16; // Play collision sound. PlayExternalSound(kGrateSound, kGratePriority); thePlayer.vVel = thePlayer.vVel / -2; // Rebound player (-1/2 vel.). } } //-------------------------------------------------------------- SetAndCheckPlayerDest // This function keeps our player's screen coordinates and "scaled" coordinatesÉ // in agreement. As well, it checks for wrap-around and handles it. void SetAndCheckPlayerDest (void) { short wasTall, wasWide; // Remember width and height of player. wasTall = thePlayer.dest.bottom - thePlayer.dest.top; wasWide = thePlayer.dest.right - thePlayer.dest.left; // Convert scaled coords to screen coords. thePlayer.dest.left = thePlayer.h >> 4; thePlayer.dest.right = thePlayer.dest.left + wasWide; thePlayer.dest.top = thePlayer.v >> 4; thePlayer.dest.bottom = thePlayer.dest.top + wasTall; if (thePlayer.dest.left > 640) // Has player left right side of arena? { // Wrap player back to left side of screen. OffsetRect(&thePlayer.dest, -640, 0); thePlayer.h = thePlayer.dest.left << 4; OffsetRect(&thePlayer.wasDest, -640, 0); } else if (thePlayer.dest.right < 0) // Else, has player left left side of screen? { // Wrap player around to right side of screen. OffsetRect(&thePlayer.dest, 640, 0); thePlayer.h = thePlayer.dest.left << 4; OffsetRect(&thePlayer.wasDest, 640, 0); } } //-------------------------------------------------------------- HandleLightning // Lightning is handled here. Obelisks are flashed, lightning is generated, É // lighting strikes, and the lightning counter decremented. This is prettyÉ // nice - we can just set "lightningCount" to a non-zero number and thisÉ // function will strike lightning every fram until the counter returns to zero. void HandleLightning (void) { if (lightningCount > 0) // Is lightning to strik this frame? { // Special frame when obelisks are lit. if (lightningCount == kNumLightningStrikes) FlashObelisks(TRUE); GenerateLightning(lightH, lightV); // Create new lightning "segments". StrikeLightning(); // Draw lightning on screen. } } //-------------------------------------------------------------- FinishLightning // This undoes what the lightning did. It "undraws" the lightning and returnsÉ // the obelisks to their "non lit" state. I see that it is HERE where the counterÉ // is decremented and not in the function above. void FinishLightning (void) { if (lightningCount > 0) { StrikeLightning(); // Undraw lightning (exclusive Or). lightningCount--; // Descrement lightning counter. if (lightningCount == 0) // If this is the last lightning strikeÉ FlashObelisks(FALSE); // return obelisk to normal. // "BOOOOM!" PlayExternalSound(kLightningSound, kLightningPriority); } } //-------------------------------------------------------------- HandleCountDownTimer // This is a pretty boring function. It is here so that when one level ends,É // the next one does begin immediately. It gives the player a few seconds ofÉ // breathing time. Essentially, to engage it, we need merely set "countDownTimer"É // to a positive number. Each frame the counter gets decremented. When itÉ // reaches zero, the level is advanced to the next wave. void HandleCountDownTimer (void) { if (countDownTimer == 0) // If already zero, do nothing. return; else // Otherwise, if greater than zeroÉ { countDownTimer--; // decrememnt counter. if (countDownTimer == 0) // Did it just hit zero? { countDownTimer = 0; // Well, just to be sure (dumb line of code). levelOn++; // Increment the level (wave) we're on. UpdateLevelNumbers(); // Display new level on screen. SetUpLevel(); // Set up the platforms. GenerateEnemies(); // Ready nemesis. } } } //-------------------------------------------------------------- PlayGame // Here is the "core" of the "game loop". When a player has elected toÉ // begin a game, Glypha falls into this function and remains in a loopÉ // herein until the player either quits, or loses their last "bird". // Each pass through the main loop below constitutes one "frame" of the game. void PlayGame (void) { #define kTicksPerFrame 2L Point offsetPt; long waitUntil; offsetPt.h = 0; // Set up ShieldCursor() point. offsetPt.v = 20; ShieldCursor(&mainWindowRect, offsetPt); // Hide the cursor. waitUntil = TickCount() + kTicksPerFrame; // Set up speed governor variable. do // Main game loop!!!! { MovePlayer(); // Move the player's bird. MoveEnemies(); // Move all sphinx enemies. HandleHand(); // Handle the mummy hand (may do nothing). HandleEye(); // Handle eye (probably will do nothing). DrawFrame(); // Draw the whole scene for this frame. HandleLightning(); // Draw lightning (is applicable). do // Here is where the speed is governed. { } while (TickCount() < waitUntil); waitUntil = TickCount() + kTicksPerFrame; evenFrame = !evenFrame; // Toggle "evenFrame" variable. GetPlayerInput(); // Get the player's input (keystrokes). HandleCountDownTimer(); // Handle countdown (may do nothing). FinishLightning(); // Undraw lightning (if it needs undoing). } while ((playing) && (!pausing)); // Stay in loop until dead, paused or quit. if ((!playing) && (!quitting)) // If the player died! { // Then play some sweet music. PlayExternalSound(kMusicSound, kMusicPriority); CheckHighScore(); // And see if they're on the high scores. } ShowCursor(); // Before we go, restore the cursor. MenusReflectMode(); // Set the menus grayed-out state correctly. FlushEvents(everyEvent, 0); // Flush any events in the queue. } //-------------------------------------------------------------- CheckHighScore // This function handles testing to see if the player's score is in the É // high scores. If that is the case, the function prompts the user forÉ // a name to enter, and sorts and stores off the new score list. void CheckHighScore (void) { #define kHighNameDialogID 130 Str255 placeStr, tempStr; DialogPtr theDial; short i, item; Boolean leaving; if (theScore > thePrefs.highScores[9]) // To see if on high scores, we needÉ { // merely see if the last guy is beat out. openTheScores = TRUE; // Will automatically bring up high scores. // Play some congratulatory music. PlayExternalSound(kBonusSound, kMusicPriority - 1); i = 8; // Find where new score fits in list. while ((theScore > thePrefs.highScores[i]) && (i >= 0)) { // We'll bump everyone down as we look. thePrefs.highScores[i + 1] = thePrefs.highScores[i]; thePrefs.highLevel[i + 1] = thePrefs.highLevel[i]; PasStringCopy(thePrefs.highNames[i], thePrefs.highNames[i + 1]); i--; } i++; // i is our place in list (zero based). thePrefs.highScores[i] = theScore; // Pop the new score in place. thePrefs.highLevel[i] = levelOn + 1; // Drop in the new highest level. NumToString((long)i + 1L, placeStr); // Convert place to a string to displayÉ ParamText(placeStr, "\p", "\p", "\p"); // in the dialog (via ParamText()). InitCursor(); // Show cursor. CenterDialog(kHighNameDialogID); // Center the dialog and then bring it up. theDial = GetNewDialog(kHighNameDialogID, 0L, kPutInFront); SetPort((GrafPtr)theDial); ShowWindow((GrafPtr)theDial); // Make dialog visible. DrawDefaultButton(theDial); // Draw outline around "Okay" button. FlushEvents(everyEvent, 0); // Flush any events queued up. // Put a default name in text edit box. SetDialogString(theDial, 2, thePrefs.highName); SelIText(theDial, 2, 0, 1024); // Select the whole text edit string. leaving = FALSE; // Flag for noting when player hit "Okay". while (!leaving) // Simple modal dialog loop. { ModalDialog(0L, &item); // Use standard filtering. if (item == 1) // If player hit the "Okay" buttonÉ { // Get the name entered in text edit box. GetDialogString(theDial, 2, tempStr); // Copy the name into high score list. PasStringCopyNum(tempStr, thePrefs.highNames[i], 15); PasStringCopy(thePrefs.highNames[i], thePrefs.highName); leaving = TRUE; // We're gone! } } DisposDialog(theDial); // Clean up. } else // But if player didn't get on high scoresÉ openTheScores = FALSE; // no need to rub their face in it. }