Glypha3/Source/Play.c

1 line
43 KiB
C
Executable File

//============================================================================
//----------------------------------------------------------------------------
// 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.
}