diff --git a/Glypha III Read Me.txt b/Glypha III Read Me.txt new file mode 100755 index 0000000..2dd21a0 --- /dev/null +++ b/Glypha III Read Me.txt @@ -0,0 +1 @@ + ­=­=­=­=­=­=­=­=­=­=­ ­ Glypha III ­ ­=­=­=­=­=­=­=­=­=­=­ 1) It's freeware - enjoy 2) The source code is available (in C) - if you're interested, look around for it in Developer forums 3) To play, hit the Help key on your extended keyboard or select Help from the menu - read the directions 4) Requires System 6.0.5 or more recent, 256 colors, and at least a 13" monitor 5) This version is fat (PowerPC native and 68K) 6) If the game looks small, switch your monitor to 640 x 480 7) Have fun \ No newline at end of file diff --git a/GlyphaIII.68K.project b/GlyphaIII.68K.project new file mode 100755 index 0000000..f01a63a Binary files /dev/null and b/GlyphaIII.68K.project differ diff --git a/GlyphaIII.68K.project.rsrc b/GlyphaIII.68K.project.rsrc new file mode 100755 index 0000000..e69de29 diff --git a/GlyphaIII.PPC.project b/GlyphaIII.PPC.project new file mode 100755 index 0000000..4e3817d Binary files /dev/null and b/GlyphaIII.PPC.project differ diff --git a/Source/Enemy.c b/Source/Enemy.c new file mode 100755 index 0000000..64719cb --- /dev/null +++ b/Source/Enemy.c @@ -0,0 +1 @@ + //============================================================================ //---------------------------------------------------------------------------- // 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); } } } } \ No newline at end of file diff --git a/Source/Externs.h b/Source/Externs.h new file mode 100755 index 0000000..663e715 --- /dev/null +++ b/Source/Externs.h @@ -0,0 +1 @@ + //============================================================================ //---------------------------------------------------------------------------- // Externs.h //---------------------------------------------------------------------------- //============================================================================ #define kPutInFront (WindowPtr)-1L #define kNormalUpdates TRUE #define kHelpKeyASCII 0x05 #define kPageUpKeyASCII 0x0B #define kPageDownKeyASCII 0x0C #define kUpArrowKeyASCII 0x1E #define kDownArrowKeyASCII 0x1F #define kDownArrowKeyMap 122 // key map offset for down arrow #define kRightArrowKeyMap 123 // key map offset for right arrow #define kLeftArrowKeyMap 124 // key map offset for left arrow #define kAKeyMap 7 #define kEKeyMap 9 #define kPKeyMap 36 #define kQKeyMap 11 #define kSKeyMap 6 #define kColonMap 0x2E #define kQuoteMap 0x20 #define kCommandKeyMap 48 #define kEscKeyMap 50 #define kSpaceBarMap 54 #define kBirdSound 1 #define kBirdPriority 80 #define kBonusSound 2 #define kBonusPriority 85 #define kBoom1Sound 3 #define kBoom1Priority 115 #define kBoom2Sound 4 #define kBoom2Priority 110 #define kSplashSound 5 #define kSplashPriority 75 #define kFlapSound 6 #define kFlapPriority 70 #define kGrateSound 8 #define kGratePriority 40 #define kLightningSound 9 #define kLightningPriority 100 #define kMusicSound 10 #define kMusicPriority 120 #define kScreechSound 12 #define kScreechPriority 50 #define kSpawnSound 13 #define kSpawnPriority 90 #define kWalkSound 14 #define kWalkPriority 30 #define kFlap2Sound 15 #define kFlap2Priority 20 #define kScrape2Sound 16 #define kScrape2Priority 10 #define kLavaHeight 456 #define kRoofHeight 2 #define kGravity 4 #define kIdle -1 // enemy & player mode #define kFlying 0 // enemy & player mode #define kWalking 1 // enemy & player mode #define kSinking 2 // player mode #define kSpawning 3 // enemy mode #define kFalling 4 // enemy mode & player mode #define kEggTimer 5 // enemy mode #define kDeadAndGone 6 // enemy mode #define kBones 7 // player mode #define kLurking 10 // hand mode #define kOutGrabeth 11 // hand mode #define kClutching 12 // hand mode #define kWaiting 15 // eye mode #define kStalking 16 // eye mode #define kInitNumLives 5 #define kMaxEnemies 8 #define kDontFlapVel 8 #define kOwl 0 #define kWolf 1 #define kJackal 2 //-------------------------------------------------------------- Structs typedef struct { Rect dest, wasDest, wrap; short h, v; short wasH, wasV; short hVel, vVel; short srcNum, mode; short frame; Boolean facingRight, flapping; Boolean walking, wrapping; Boolean clutched; } playerType; typedef struct { Rect dest, wasDest; short h, v; short wasH, wasV; short hVel, vVel; short srcNum, mode; short kind, frame; short heightSmell, targetAlt; short flapImpulse, pass; short maxHVel, maxVVel; Boolean facingRight; } enemyType; typedef struct { Rect dest; short mode; } handInfo; typedef struct { Rect dest; short mode, opening; short srcNum, frame; Boolean killed, entering; } eyeInfo; typedef struct { short prefVersion, filler; Str255 highName; Str15 highNames[10]; long highScores[10]; short highLevel[10]; short wasVolume; } prefsInfo; //-------------------------------------------------------------- Prototypes void GenerateEnemies (void); // Enemies.c void MoveEnemies (void); void InitHandLocation (void); void HandleHand (void); void InitEye (void); void KillOffEye (void); void HandleEye (void); void CheckPlayerEnemyCollision (void); void DrawPlatforms (short); // Graphics.c void ScrollHelp (short); void OpenHelp (void); void CloseWall (void); void OpenHighScores (void); void UpdateLivesNumbers (void); void UpdateScoreNumbers (void); void UpdateLevelNumbers (void); void GenerateLightning (short h, short v); void FlashObelisks (Boolean); void StrikeLightning (void); void DumpBackToWorkMap (void); void DumpMainToWorkMap (void); void AddToUpdateRects (Rect *); void DrawTorches (void); void CopyAllRects (void); void DrawFrame (void); void MenusReflectMode (void); // Interface.c void DoMenuChoice (long); void HandleEvent (void); void InitNewGame (void); // Play.c void PlayGame (void); Boolean SavePrefs (prefsInfo *, short); // Prefs.c Boolean LoadPrefs (prefsInfo *, short); void ToolBoxInit (void); // SetUpTakeDown.c void CheckEnvirons (void); void OpenMainWindow (void); void InitMenubar (void); void InitVariables (void); void ShutItDown (void); void PlayExternalSound (short, short); // Sound.c void InitSound (void); void KillSound (void); short RandomInt (short); // Utilities.c void RedAlert (StringPtr); void FindOurDevice (void); void LoadGraphic (short); void CreateOffScreenPixMap (Rect *, CGrafPtr *); void CreateOffScreenBitMap (Rect *, GrafPtr *); void ZeroRectCorner (Rect *); void FlashShort (short); void LogNextTick (long); void WaitForNextTick (void); Boolean TrapExists (short); Boolean DoWeHaveGestalt (void); void CenterAlert (short); short RectWide (Rect *); short RectTall (Rect *); void CenterRectInRect (Rect *, Rect *); void PasStringCopy (StringPtr, StringPtr); void CenterDialog (short); void DrawDefaultButton (DialogPtr); void PasStringCopyNum (StringPtr, StringPtr, short); void GetDialogString (DialogPtr, short, StringPtr); void SetDialogString (DialogPtr, short, StringPtr); void SetDialogNumToStr (DialogPtr, short, long ); void GetDialogNumFromStr (DialogPtr, short, long *); void DisableControl (DialogPtr, short); #ifdef powerc extern pascal void SetSoundVol(short level); // for old Sound Manager extern pascal void GetSoundVol(short *level) THREEWORDINLINE(0x4218, 0x10B8, 0x0260); #endif \ No newline at end of file diff --git a/Source/Graphics.c b/Source/Graphics.c new file mode 100755 index 0000000..eac71b2 --- /dev/null +++ b/Source/Graphics.c @@ -0,0 +1 @@ + //============================================================================ //---------------------------------------------------------------------------- // Graphics.c //---------------------------------------------------------------------------- //============================================================================ // I like to isolate all the graphic routines - put them in their own file. // This way all the thousands of Rect variables and Pixmaps have a place to go. // Anyway, this file contains all the drawing routines. #include "Externs.h" #include #define kUpperEyeHeight 100 #define kLowerEyeHeight 200 #define kNumLightningPts 8 #define kMaxNumUpdateRects 32 void QuickUnionRect (Rect *, Rect *, Rect *); void CheckPlayerWrapAround (void); void DrawHand (void); void DrawEye (void); void DrawPlayer (void); void CheckEnemyWrapAround (short); void DrawEnemies (void); Rect backSrcRect, workSrcRect, obSrcRect, playerSrcRect; Rect numberSrcRect, idleSrcRect, enemyWalkSrcRect, enemyFlySrcRect; Rect obeliskRects[4], playerRects[11], numbersSrc[11], numbersDest[11]; Rect updateRects1[kMaxNumUpdateRects], updateRects2[kMaxNumUpdateRects]; Rect flameSrcRect, flameDestRects[2], flameRects[4], eggSrcRect; Rect platformSrcRect, platformCopyRects[9], helpSrcRect, eyeSrcRect; Rect helpSrc, helpDest, handSrcRect, handRects[2], eyeRects[4]; Point leftLightningPts[kNumLightningPts], rightLightningPts[kNumLightningPts]; CGrafPtr backSrcMap, workSrcMap, obeliskSrcMap, playerSrcMap, eyeSrcMap; CGrafPtr numberSrcMap, idleSrcMap, enemyWalkSrcMap, enemyFlySrcMap; CGrafPtr flameSrcMap, eggSrcMap, platformSrcMap, helpSrcMap, handSrcMap; GrafPtr playerMaskMap, enemyWalkMaskMap, enemyFlyMaskMap, eggMaskMap; GrafPtr handMaskMap, eyeMaskMap; RgnHandle playRgn; short numUpdateRects1, numUpdateRects2; Boolean whichList, helpOpen, scoresOpen; extern handInfo theHand; extern eyeInfo theEye; extern prefsInfo thePrefs; extern playerType thePlayer; extern enemyType theEnemies[]; extern Rect enemyRects[24]; extern WindowPtr mainWindow; extern long theScore, wasTensOfThousands; extern short livesLeft, levelOn, numEnemies; extern Boolean evenFrame; //============================================================== Functions //-------------------------------------------------------------- DrawPlatforms // This function draws all the platforms on the background pixmap and theÉ // work pixmap. It needs to know merely how many of them to draw. void DrawPlatforms (short howMany) { if (howMany > 3) // If there are more than 3 platformsÉ { // Draw a platform to background pixmap. CopyBits(&((GrafPtr)platformSrcMap)->portBits, &((GrafPtr)backSrcMap)->portBits, &platformCopyRects[2], &platformCopyRects[7], srcCopy, playRgn); // Draw a platform to work pixmap. CopyBits(&((GrafPtr)backSrcMap)->portBits, &((GrafPtr)workSrcMap)->portBits, &platformCopyRects[7], &platformCopyRects[7], srcCopy, playRgn); // Add rectangle to update list to be drawn to screen. AddToUpdateRects(&platformCopyRects[7]); // Ditto for a second platform. CopyBits(&((GrafPtr)platformSrcMap)->portBits, &((GrafPtr)backSrcMap)->portBits, &platformCopyRects[4], &platformCopyRects[8], srcCopy, playRgn); CopyBits(&((GrafPtr)backSrcMap)->portBits, &((GrafPtr)workSrcMap)->portBits, &platformCopyRects[8], &platformCopyRects[8], srcCopy, playRgn); AddToUpdateRects(&platformCopyRects[8]); } else // If there are 3 or less platformsÉ { CopyBits(&((GrafPtr)platformSrcMap)->portBits, &((GrafPtr)backSrcMap)->portBits, &platformCopyRects[3], &platformCopyRects[7], srcCopy, playRgn); CopyBits(&((GrafPtr)backSrcMap)->portBits, &((GrafPtr)workSrcMap)->portBits, &platformCopyRects[7], &platformCopyRects[7], srcCopy, playRgn); AddToUpdateRects(&platformCopyRects[7]); CopyBits(&((GrafPtr)platformSrcMap)->portBits, &((GrafPtr)backSrcMap)->portBits, &platformCopyRects[5], &platformCopyRects[8], srcCopy, playRgn); CopyBits(&((GrafPtr)backSrcMap)->portBits, &((GrafPtr)workSrcMap)->portBits, &platformCopyRects[8], &platformCopyRects[8], srcCopy, playRgn); AddToUpdateRects(&platformCopyRects[8]); } if (howMany > 5) // If there are more than 5 platformsÉ { CopyBits(&((GrafPtr)platformSrcMap)->portBits, &((GrafPtr)backSrcMap)->portBits, &platformCopyRects[0], &platformCopyRects[6], srcCopy, playRgn); CopyBits(&((GrafPtr)backSrcMap)->portBits, &((GrafPtr)workSrcMap)->portBits, &platformCopyRects[6], &platformCopyRects[6], srcCopy, playRgn); AddToUpdateRects(&platformCopyRects[6]); } else // If there are 5 or less platformsÉ { CopyBits(&((GrafPtr)platformSrcMap)->portBits, &((GrafPtr)backSrcMap)->portBits, &platformCopyRects[1], &platformCopyRects[6], srcCopy, playRgn); CopyBits(&((GrafPtr)backSrcMap)->portBits, &((GrafPtr)workSrcMap)->portBits, &platformCopyRects[6], &platformCopyRects[6], srcCopy, playRgn); AddToUpdateRects(&platformCopyRects[6]); } } //-------------------------------------------------------------- ScrollHelp // This function scrolls the help screen. You pass it a number of pixelsÉ // to scroll up or down (positive or negative number). void ScrollHelp (short scrollDown) { OffsetRect(&helpSrc, 0, scrollDown); // Move the source rectangle. if (helpSrc.bottom > 398) // Check to see we don't go too far. { helpSrc.bottom = 398; helpSrc.top = helpSrc.bottom - 199; } else if (helpSrc.top < 0) { helpSrc.top = 0; helpSrc.bottom = helpSrc.top + 199; } // Draw "scrolled" help screen. CopyBits(&((GrafPtr)helpSrcMap)->portBits, &(((GrafPtr)mainWindow)->portBits), &helpSrc, &helpDest, srcCopy, 0L); } //-------------------------------------------------------------- OpenHelp // Bring up the help screen. This is a kind of "wipe" or "barn door" effect. void OpenHelp (void) { Rect wallSrc, wallDest; short i; SetRect(&helpSrc, 0, 0, 231, 0); // Initialize source and destination rects. helpDest = helpSrc; OffsetRect(&helpDest, 204, 171); SetRect(&wallSrc, 0, 0, 231, 199); OffsetRect(&wallSrc, 204, 171); wallDest = wallSrc; for (i = 0; i < 199; i ++) // Loop through 1 pixel at a time. { LogNextTick(1L); // Speed governor. helpSrc.bottom++; // Grow help source rect. helpDest.bottom++; // Grow help dest as well. wallSrc.bottom--; // Shrink wall source. wallDest.top++; // Shrink wall dest. // So, as the help graphic grows, the wall graphicÉ // shrinks. Thus it is as though the wall isÉ // lifting up to expose the help screen beneath. // Copy slightly larger help screen. CopyBits(&((GrafPtr)helpSrcMap)->portBits, &(((GrafPtr)mainWindow)->portBits), &helpSrc, &helpDest, srcCopy, 0L); // Copy slightly smaller wall graphic. CopyBits(&((GrafPtr)backSrcMap)->portBits, &(((GrafPtr)mainWindow)->portBits), &wallSrc, &wallDest, srcCopy, 0L); WaitForNextTick(); // Speed governor. } helpOpen = TRUE; // When done, set flag to indicate help is open. } //-------------------------------------------------------------- CloseWall // Close the wall over whatever screen is up (help screen or high scores). // Since the wall just comes down over the opening - covering whatever was beneath,É // it's simpler than the above function. void CloseWall (void) { Rect wallSrc, wallDest; short i; SetRect(&wallSrc, 0, 0, 231, 0); // Initialize source and dest rects. wallDest = wallSrc; OffsetRect(&wallDest, 204, 370); OffsetRect(&wallSrc, 204, 171); for (i = 0; i < 199; i ++) // Do it one pixel at a time. { wallSrc.bottom++; // Grow bottom of wall source. wallDest.top--; // Move down wall dest. // Draw wall coming down. CopyBits(&((GrafPtr)backSrcMap)->portBits, &(((GrafPtr)mainWindow)->portBits), &wallSrc, &wallDest, srcCopy, 0L); } // Note, no speed governing (why bother?). } //-------------------------------------------------------------- OpenHighScores // This function is practically identical to the OpenHelp(). The only realÉ // difference is that we must first draw all the high scores offscreen beforeÉ // lifting the wall to reveal them. void OpenHighScores (void) { RGBColor theRGBColor, wasColor; Rect wallSrc, wallDest; Rect scoreSrc, scoreDest; Str255 scoreStr; short i, scoreWide; SetRect(&scoreSrc, 0, 0, 231, 0); // Initialize source and dest rects. OffsetRect(&scoreSrc, 204, 171); scoreDest = scoreSrc; SetRect(&wallSrc, 0, 0, 231, 199); OffsetRect(&wallSrc, 204, 171); wallDest = wallSrc; SetPort((GrafPtr)workSrcMap); // We'll draw scores to the work pixmap. PaintRect(&wallSrc); // Paint it black. GetForeColor(&wasColor); // Save the foreground color. TextFont(geneva); // Use Geneva 12 point Bold font. TextSize(12); TextFace(bold); Index2Color(132, &theRGBColor); // Get the 132nd color in RGB form. RGBForeColor(&theRGBColor); // Make this color the pen color. MoveTo(scoreSrc.left + 36, scoreSrc.top + 20); // Get pen in right position to draw. DrawString("\pGlypha III High Scores"); // Draw the title. TextFont(geneva); // Use Geneva 9 point Bold font. TextSize(9); TextFace(bold); for (i = 0; i < 10; i++) // Walk through all 10 high scores. { Index2Color(133, &theRGBColor); // Use color 133 (in palette). RGBForeColor(&theRGBColor); NumToString((long)i + 1L, scoreStr); // Draw "place" (1, 2, 3, É). MoveTo(scoreSrc.left + 8, scoreSrc.top + 40 + (i * 16)); DrawString(scoreStr); Index2Color(128, &theRGBColor); // Use color 128 (from palette). RGBForeColor(&theRGBColor); MoveTo(scoreSrc.left + 32, scoreSrc.top + 40 + (i * 16)); DrawString(thePrefs.highNames[i]); // Draw the high score name (Sue, É). Index2Color(164, &theRGBColor); // Use color 164 (from palette). RGBForeColor(&theRGBColor); NumToString(thePrefs.highScores[i], scoreStr); scoreWide = StringWidth(scoreStr); // Right justify. MoveTo(scoreSrc.left + 191 - scoreWide, scoreSrc.top + 40 + (i * 16)); DrawString(scoreStr); // Draw the high score (12,000, É). Index2Color(134, &theRGBColor); // Use color 134 (from palette). RGBForeColor(&theRGBColor); NumToString(thePrefs.highLevel[i], scoreStr); scoreWide = StringWidth(scoreStr); // Right justify. MoveTo(scoreSrc.left + 223 - scoreWide, scoreSrc.top + 40 + (i * 16)); DrawString(scoreStr); // Draw highest level (12, 10, É). } RGBForeColor(&wasColor); // Restore foreground color. SetPort((GrafPtr)mainWindow); for (i = 0; i < 199; i ++) // Now the standard scroll functions. { LogNextTick(1L); scoreSrc.bottom++; scoreDest.bottom++; wallSrc.bottom--; wallDest.top++; CopyBits(&((GrafPtr)workSrcMap)->portBits, &(((GrafPtr)mainWindow)->portBits), &scoreSrc, &scoreDest, srcCopy, 0L); CopyBits(&((GrafPtr)backSrcMap)->portBits, &(((GrafPtr)mainWindow)->portBits), &wallSrc, &wallDest, srcCopy, 0L); WaitForNextTick(); } scoresOpen = TRUE; // Flag that the scores are up. } //-------------------------------------------------------------- UpdateLivesNumbers // During a game, this function is called to reflect the current number of lives. // This is "lives remaining", so 1 is subtracted before displaying it to the screen. // The lives is "wrapped around" after 99. So 112 lives will display as 12. It'sÉ // a lot easier to handle numbers this way (it beats a recursive function that mightÉ // potentially draw across the entire screen. void UpdateLivesNumbers (void) { short digit; digit = (livesLeft - 1) / 10; // Get the "10's" digit. digit = digit % 10L; // Keep it less than 10 (0 -> 9). if ((digit == 0) && ((livesLeft - 1) < 10)) digit = 10; // Use a "blank" space if zero and less than 10. // Draw digit. CopyBits(&((GrafPtr)numberSrcMap)->portBits, &((GrafPtr)backSrcMap)->portBits, &numbersSrc[digit], &numbersDest[0], srcCopy, 0L); CopyBits(&((GrafPtr)backSrcMap)->portBits, &(((GrafPtr)mainWindow)->portBits), &numbersDest[0], &numbersDest[0], srcCopy, 0L); digit = (livesLeft - 1) % 10; // Get 1's digit. // Draw digit. CopyBits(&((GrafPtr)numberSrcMap)->portBits, &((GrafPtr)backSrcMap)->portBits, &numbersSrc[digit], &numbersDest[1], srcCopy, 0L); CopyBits(&((GrafPtr)backSrcMap)->portBits, &(((GrafPtr)mainWindow)->portBits), &numbersDest[1], &numbersDest[1], srcCopy, 0L); } //-------------------------------------------------------------- UpdateScoreNumbers // This function works just like the above function. However, we allow theÉ // score to go to 6 digits (999,999) before rolling over. Note however, thatÉ // in both the case of the score, number of lives, etc., the game does in factÉ // keep track of the "actual" number. It is just that only so many digits areÉ // being displayed. void UpdateScoreNumbers (void) { long digit; digit = theScore / 100000L; // Get "hundreds of thousands" digit. digit = digit % 10L; // Clip off anything greater than 9. if ((digit == 0) && (theScore < 1000000L)) digit = 10; // Use blank space if zero. // Draw digit. CopyBits(&((GrafPtr)numberSrcMap)->portBits, &((GrafPtr)backSrcMap)->portBits, &numbersSrc[digit], &numbersDest[2], srcCopy, 0L); CopyBits(&((GrafPtr)backSrcMap)->portBits, &(((GrafPtr)mainWindow)->portBits), &numbersDest[2], &numbersDest[2], srcCopy, 0L); digit = theScore / 10000L; // Get "tens of thousands" digit. if (digit > wasTensOfThousands) // Check for "extra life" here. { livesLeft++; // Increment number of lives. UpdateLivesNumbers(); // Reflect new lives on screen. wasTensOfThousands = digit; // Note that life was given. } digit = digit % 10L; // Clip off anything greater than 9. if ((digit == 0) && (theScore < 100000L)) digit = 10; // Use blank space if zero. // Draw digit. CopyBits(&((GrafPtr)numberSrcMap)->portBits, &((GrafPtr)backSrcMap)->portBits, &numbersSrc[digit], &numbersDest[3], srcCopy, 0L); CopyBits(&((GrafPtr)backSrcMap)->portBits, &(((GrafPtr)mainWindow)->portBits), &numbersDest[3], &numbersDest[3], srcCopy, 0L); digit = theScore / 1000L; // Handle "thousands" digit. digit = digit % 10L; if ((digit == 0) && (theScore < 10000L)) digit = 10; CopyBits(&((GrafPtr)numberSrcMap)->portBits, &((GrafPtr)backSrcMap)->portBits, &numbersSrc[digit], &numbersDest[4], srcCopy, 0L); CopyBits(&((GrafPtr)backSrcMap)->portBits, &(((GrafPtr)mainWindow)->portBits), &numbersDest[4], &numbersDest[4], srcCopy, 0L); digit = theScore / 100L; // Handle 100's digit. digit = digit % 10L; if ((digit == 0) && (theScore < 1000L)) digit = 10; CopyBits(&((GrafPtr)numberSrcMap)->portBits, &((GrafPtr)backSrcMap)->portBits, &numbersSrc[digit], &numbersDest[5], srcCopy, 0L); CopyBits(&((GrafPtr)backSrcMap)->portBits, &(((GrafPtr)mainWindow)->portBits), &numbersDest[5], &numbersDest[5], srcCopy, 0L); digit = theScore / 10L; // Handle 10's digit. digit = digit % 10L; if ((digit == 0) && (theScore < 100L)) digit = 10; CopyBits(&((GrafPtr)numberSrcMap)->portBits, &((GrafPtr)backSrcMap)->portBits, &numbersSrc[digit], &numbersDest[6], srcCopy, 0L); CopyBits(&((GrafPtr)backSrcMap)->portBits, &(((GrafPtr)mainWindow)->portBits), &numbersDest[6], &numbersDest[6], srcCopy, 0L); digit = theScore % 10L; // Handle 1's digit. CopyBits(&((GrafPtr)numberSrcMap)->portBits, &((GrafPtr)backSrcMap)->portBits, &numbersSrc[digit], &numbersDest[7], srcCopy, 0L); CopyBits(&((GrafPtr)backSrcMap)->portBits, &(((GrafPtr)mainWindow)->portBits), &numbersDest[7], &numbersDest[7], srcCopy, 0L); } //-------------------------------------------------------------- UpdateLevelNumbers // Blah, blah, blah. Just like the above functions but handles displaying theÉ // level the player is on. We allow 3 digits here (up to 999) before wrapping. void UpdateLevelNumbers (void) { short digit; digit = (levelOn + 1) / 100; // Do 100's digit. digit = digit % 10L; if ((digit == 0) && ((levelOn + 1) < 1000)) digit = 10; CopyBits(&((GrafPtr)numberSrcMap)->portBits, &((GrafPtr)backSrcMap)->portBits, &numbersSrc[digit], &numbersDest[8], srcCopy, 0L); CopyBits(&((GrafPtr)backSrcMap)->portBits, &(((GrafPtr)mainWindow)->portBits), &numbersDest[8], &numbersDest[8], srcCopy, 0L); digit = (levelOn + 1) / 10; // Do 10's digit. digit = digit % 10L; if ((digit == 0) && ((levelOn + 1) < 100)) digit = 10; CopyBits(&((GrafPtr)numberSrcMap)->portBits, &((GrafPtr)backSrcMap)->portBits, &numbersSrc[digit], &numbersDest[9], srcCopy, 0L); CopyBits(&((GrafPtr)backSrcMap)->portBits, &(((GrafPtr)mainWindow)->portBits), &numbersDest[9], &numbersDest[9], srcCopy, 0L); digit = (levelOn + 1) % 10; // Do 1's digit. CopyBits(&((GrafPtr)numberSrcMap)->portBits, &((GrafPtr)backSrcMap)->portBits, &numbersSrc[digit], &numbersDest[10], srcCopy, 0L); CopyBits(&((GrafPtr)backSrcMap)->portBits, &(((GrafPtr)mainWindow)->portBits), &numbersDest[10], &numbersDest[10], srcCopy, 0L); } //-------------------------------------------------------------- GenerateLightning // This function takes a point (h and v) and then generates two lightning boltsÉ // (one from the tip of each obelisk) to the point. It does this by generatingÉ // a list of segments (as the lightning is broken up into segements). The drawingÉ // counterpart to this function will draw a line connecting these segements (a sortÉ // of dot-to-dot). void GenerateLightning (short h, short v) { #define kLeftObeliskH 172 #define kLeftObeliskV 250 #define kRightObeliskH 468 #define kRightObeliskV 250 #define kWander 16 short i, leftDeltaH, rightDeltaH, leftDeltaV, rightDeltaV, range; leftDeltaH = h - kLeftObeliskH; // Determine the h and v distances betweenÉ rightDeltaH = h - kRightObeliskH; // obelisks and the target point. leftDeltaV = v - kLeftObeliskV; rightDeltaV = v - kRightObeliskV; for (i = 0; i < kNumLightningPts; i++) // Calculate an even spread of points betweenÉ { // obelisk tips and the target point. leftLightningPts[i].h = (leftDeltaH * i) / (kNumLightningPts - 1) + kLeftObeliskH; leftLightningPts[i].v = (leftDeltaV * i) / (kNumLightningPts - 1) + kLeftObeliskV; rightLightningPts[i].h = (rightDeltaH * i) / (kNumLightningPts - 1) + kRightObeliskH; rightLightningPts[i].v = (rightDeltaV * i) / (kNumLightningPts - 1) + kRightObeliskV; } range = kWander * 2 + 1; // Randomly scatter the points verticallyÉ for (i = 1; i < kNumLightningPts - 1; i++) // but NOT the 1st or last points. { leftLightningPts[i].v += RandomInt(range) - kWander; rightLightningPts[i].v += RandomInt(range) - kWander; } } //-------------------------------------------------------------- FlashObelisks // This function either draws the obelisks "normal" or draws them inverted. // They're drawn "inverted" as if emanating energy or lit up by the boltsÉ // of lightning. The flag "flashThem" specifies how to draw them. void FlashObelisks (Boolean flashThem) { if (flashThem) // Draw them "inverted" { CopyBits(&((GrafPtr)obeliskSrcMap)->portBits, &(((GrafPtr)mainWindow)->portBits), &obeliskRects[0], &obeliskRects[2], srcCopy, 0L); CopyBits(&((GrafPtr)obeliskSrcMap)->portBits, &(((GrafPtr)mainWindow)->portBits), &obeliskRects[1], &obeliskRects[3], srcCopy, 0L); } else // Draw them "normal" { CopyBits(&((GrafPtr)backSrcMap)->portBits, &(((GrafPtr)mainWindow)->portBits), &obeliskRects[2], &obeliskRects[2], srcCopy, 0L); CopyBits(&((GrafPtr)backSrcMap)->portBits, &(((GrafPtr)mainWindow)->portBits), &obeliskRects[3], &obeliskRects[3], srcCopy, 0L); } } //-------------------------------------------------------------- StrikeLightning // This function draws the lightning bolts. The PenMode() is set to patXOrÉ // so that the lines are drawn inverted (colorwise). This way, drawing theÉ // lightning twice over will leave no pixels disturbed. void StrikeLightning (void) { short i; SetPort((GrafPtr)mainWindow); // Draw straight to screen. PenSize(1, 2); // Use a tall pen. PenMode(patXor); // Use XOR mode. // Draw lightning bolts with inverted pen. MoveTo(leftLightningPts[0].h, leftLightningPts[0].v); for (i = 0; i < kNumLightningPts - 1; i++) // Draw left lightning bolt. { MoveTo(leftLightningPts[i].h, leftLightningPts[i].v); LineTo(leftLightningPts[i + 1].h - 1, leftLightningPts[i + 1].v); } MoveTo(rightLightningPts[0].h, rightLightningPts[0].v); for (i = 0; i < kNumLightningPts - 1; i++) // Draw right lightning bolt. { MoveTo(rightLightningPts[i].h, rightLightningPts[i].v); LineTo(rightLightningPts[i + 1].h - 1, rightLightningPts[i + 1].v); } PenNormal(); // Return pen to normal. } //-------------------------------------------------------------- DumpBackToWorkMap // Simple handy function that copies the entire background pixmap to theÉ // work pixmap. void DumpBackToWorkMap (void) { CopyBits(&((GrafPtr)backSrcMap)->portBits, &((GrafPtr)workSrcMap)->portBits, &backSrcRect, &backSrcRect, srcCopy, 0L); } //-------------------------------------------------------------- DumpBackToWorkMap // Simple handy function that copies the entire work pixmap to theÉ // screen. void DumpMainToWorkMap (void) { CopyBits(&(((GrafPtr)mainWindow)->portBits), &((GrafPtr)workSrcMap)->portBits, &backSrcRect, &backSrcRect, srcCopy, 0L); } //-------------------------------------------------------------- QuickUnionRect // The Mac Toolbox gives you a UnionRect() function, but, like any ToolboxÉ // routine, if we can do it faster, we ought to. Well, the function belowÉ // is quick because (among other reasons), it assumes that the two rectsÉ // being compared are the same size. void QuickUnionRect (Rect *rect1, Rect *rect2, Rect *whole) { if (rect1->left < rect2->left) // See if we're to use rect1's left. { whole->left = rect1->left; whole->right = rect2->right; } else // Use rect2's left. { whole->left = rect2->left; whole->right = rect1->right; } if (rect1->top < rect2->top) // See if we're to use rect1's top. { whole->top = rect1->top; whole->bottom = rect2->bottom; } else // Use rect2's top. { whole->top = rect2->top; whole->bottom = rect1->bottom; } } //-------------------------------------------------------------- AddToUpdateRects // This is an elegant way to handle game animation. It has some drawbacks, butÉ // for ease of use, you may not be able to beat it. The idea is that any timeÉ // you want something drawn to the screen (copied from an offscreen pixmap toÉ // the screen) you pass the rectangle to this routine. This routine then addsÉ // the rectangle to a growing list of these rectangles. When the game reachesÉ // drawing phase, another routine copies all these rectangles. It is assumed, É // nonetheless, that you have copied the little graphic offscreen that you wantÉ // moved to the screen (the shpinx or whatever). This routine will take care ofÉ // drawing the shinx or whatever to the screen. void AddToUpdateRects (Rect *theRect) { if (whichList) // We alternate every odd frame between two listsÉ { // in order to hold a copy of rects from last frame. if (numUpdateRects1 < (kMaxNumUpdateRects - 1)) { // If we are below the maximum # of rects we can handleÉ // Add the rect to the list (array). updateRects1[numUpdateRects1] = *theRect; // Increment the number of rects held in list. numUpdateRects1++; // Do simple bounds checking (clip to screen). if (updateRects1[numUpdateRects1].left < 0) updateRects1[numUpdateRects1].left = 0; else if (updateRects1[numUpdateRects1].right > 640) updateRects1[numUpdateRects1].right = 640; if (updateRects1[numUpdateRects1].top < 0) updateRects1[numUpdateRects1].top = 0; else if (updateRects1[numUpdateRects1].bottom > 480) updateRects1[numUpdateRects1].bottom = 480; } } else // Exactly like the above section, but with the other list. { if (numUpdateRects2 < (kMaxNumUpdateRects - 1)) { updateRects2[numUpdateRects2] = *theRect; numUpdateRects2++; if (updateRects2[numUpdateRects2].left < 0) updateRects2[numUpdateRects2].left = 0; else if (updateRects2[numUpdateRects2].right > 640) updateRects2[numUpdateRects2].right = 640; if (updateRects2[numUpdateRects2].top < 0) updateRects2[numUpdateRects2].top = 0; else if (updateRects2[numUpdateRects2].bottom > 480) updateRects2[numUpdateRects2].bottom = 480; } } } //-------------------------------------------------------------- CheckPlayerWrapAround // This handles drawing wrap-around. It is such that, when a player walks partlyÉ // off the right edge of the screen, you see the player peeking through on the leftÉ // side of the screen. Since we can't (shouldn't) assume that the physical screenÉ // memory wraps around, we'll draw the right player clipped against the right edgeÉ // of the screen and draw a SECOND PLAYER on the left edge (clipped to the left). void CheckPlayerWrapAround (void) { Rect wrapRect, wasWrapRect, src; if (thePlayer.dest.right > 640) // Player off right edge of screen. { thePlayer.wrapping = TRUE; // Set "wrapping" flag. wrapRect = thePlayer.dest; // Start out with copy of player bounds. wrapRect.left -= 640; // Offset it a screenwidth to left. wrapRect.right -= 640; // Ditto with old location. wasWrapRect = thePlayer.wasDest; wasWrapRect.left -= 640; wasWrapRect.right -= 640; if (thePlayer.mode == kBones) // Draw second bones. { src = playerRects[thePlayer.srcNum]; src.bottom = src.top + thePlayer.frame; CopyMask(&((GrafPtr)playerSrcMap)->portBits, &((GrafPtr)playerMaskMap)->portBits, &((GrafPtr)workSrcMap)->portBits, &src, &src, &wrapRect); } else // Draw second player (not bones). { CopyMask(&((GrafPtr)playerSrcMap)->portBits, &((GrafPtr)playerMaskMap)->portBits, &((GrafPtr)workSrcMap)->portBits, &playerRects[thePlayer.srcNum], &playerRects[thePlayer.srcNum], &wrapRect); } thePlayer.wrap = wrapRect; AddToUpdateRects(&wrapRect); // Add this to our list of update rects. } else if (thePlayer.dest.left < 0) // Else if off the left edgeÉ { thePlayer.wrapping = TRUE; // Set "wrapping" flag. wrapRect = thePlayer.dest; // Start out with copy of player bounds. wrapRect.left += 640; // Offset it a screenwidth to right. wrapRect.right += 640; // Ditto with old location. wasWrapRect = thePlayer.wasDest; wasWrapRect.left += 640; wasWrapRect.right += 640; if (thePlayer.mode == kBones) // Draw second bones. { src = playerRects[thePlayer.srcNum]; src.bottom = src.top + thePlayer.frame; CopyMask(&((GrafPtr)playerSrcMap)->portBits, &((GrafPtr)playerMaskMap)->portBits, &((GrafPtr)workSrcMap)->portBits, &src, &src, &wrapRect); } else // Draw second player (not bones). { CopyMask(&((GrafPtr)playerSrcMap)->portBits, &((GrafPtr)playerMaskMap)->portBits, &((GrafPtr)workSrcMap)->portBits, &playerRects[thePlayer.srcNum], &playerRects[thePlayer.srcNum], &wrapRect); } thePlayer.wrap = wrapRect; AddToUpdateRects(&wrapRect); // Add this to our list of update rects. } else thePlayer.wrapping = FALSE; // Otherwise, we're not wrapping. } //-------------------------------------------------------------- DrawTorches // This handles drawing the two torch's flames. It chooses randomly fromÉ // 4 torch graphics and draws right over the old torches. void DrawTorches (void) { short who; who = RandomInt(4); if (evenFrame) // Only draw 1 torch - left on even framesÉ { CopyBits(&((GrafPtr)flameSrcMap)->portBits, &((GrafPtr)workSrcMap)->portBits, &flameRects[who], &flameDestRects[0], srcCopy, 0L); AddToUpdateRects(&flameDestRects[0]); } else // and draw the right torch on odd frames. { // We do this even/odd thing for speed. Why draw both? CopyBits(&((GrafPtr)flameSrcMap)->portBits, &((GrafPtr)workSrcMap)->portBits, &flameRects[who], &flameDestRects[1], srcCopy, 0L); AddToUpdateRects(&flameDestRects[1]); } } //-------------------------------------------------------------- DrawHand // This function takes care of drawing the hand offscreen. There are onlyÉ // two (well really three) choices - hand open, hand clutching (or no handÉ // in which case both options are skipped). void DrawHand (void) { if (theHand.mode == kOutGrabeth) // Fingers open. { CopyMask(&((GrafPtr)handSrcMap)->portBits, &((GrafPtr)handMaskMap)->portBits, &((GrafPtr)workSrcMap)->portBits, &handRects[0], &handRects[0], &theHand.dest); AddToUpdateRects(&theHand.dest); } else if (theHand.mode == kClutching) // Fingers clenched. { CopyMask(&((GrafPtr)handSrcMap)->portBits, &((GrafPtr)handMaskMap)->portBits, &((GrafPtr)workSrcMap)->portBits, &handRects[1], &handRects[1], &theHand.dest); AddToUpdateRects(&theHand.dest); } } //-------------------------------------------------------------- DrawEye // This function draws the eye (if it's floating about - stalking). void DrawEye (void) { if (theEye.mode == kStalking) { CopyMask(&((GrafPtr)eyeSrcMap)->portBits, &((GrafPtr)eyeMaskMap)->portBits, &((GrafPtr)workSrcMap)->portBits, &eyeRects[theEye.srcNum], &eyeRects[theEye.srcNum], &theEye.dest); AddToUpdateRects(&theEye.dest); } } //-------------------------------------------------------------- CopyAllRects // This function goes through the list of "update rects" and copies from anÉ // offscreen pixmap to the main screen. It is at this instant (during theÉ // execution of the below function) that the screen actually changes. TheÉ // whole rest of Glypha is, in essence, there only to lead up, ultimately, É // to this function. void CopyAllRects (void) { short i; if (whichList) // Every other frame, we alternate which list we use. { // Copy new graphics to screen (sphinxes, player, etc.). for (i = 0; i < numUpdateRects1; i++) { CopyBits(&((GrafPtr)workSrcMap)->portBits, &(((GrafPtr)mainWindow)->portBits), &updateRects1[i], &updateRects1[i], srcCopy, playRgn); } // Patch up old graphics from last frame (old sphinx locations, etc.). for (i = 0; i < numUpdateRects2; i++) { CopyBits(&((GrafPtr)workSrcMap)->portBits, &(((GrafPtr)mainWindow)->portBits), &updateRects2[i], &updateRects2[i], srcCopy, playRgn); } // Clean up offscreen (get rid of sphinxes, etc.). for (i = 0; i < numUpdateRects1; i++) { CopyBits(&((GrafPtr)backSrcMap)->portBits, &((GrafPtr)workSrcMap)->portBits, &updateRects1[i], &updateRects1[i], srcCopy, playRgn); } numUpdateRects2 = 0; // Reset number of rects to zero. whichList = !whichList; // Toggle flag to use other list next frame. } else { // Copy new graphics to screen (sphinxes, player, etc.). for (i = 0; i < numUpdateRects2; i++) { CopyBits(&((GrafPtr)workSrcMap)->portBits, &(((GrafPtr)mainWindow)->portBits), &updateRects2[i], &updateRects2[i], srcCopy, playRgn); } // Patch up old graphics from last frame (old sphinx locations, etc.). for (i = 0; i < numUpdateRects1; i++) { CopyBits(&((GrafPtr)workSrcMap)->portBits, &(((GrafPtr)mainWindow)->portBits), &updateRects1[i], &updateRects1[i], srcCopy, playRgn); } // Clean up offscreen (get rid of sphinxes, etc.). for (i = 0; i < numUpdateRects2; i++) { CopyBits(&((GrafPtr)backSrcMap)->portBits, &((GrafPtr)workSrcMap)->portBits, &updateRects2[i], &updateRects2[i], srcCopy, playRgn); } numUpdateRects1 = 0; // Reset number of rects to zero. whichList = !whichList; // Toggle flag to use other list next frame. } } //-------------------------------------------------------------- DrawPlayer // Although called "DrawPlayer()", this function actually does its drawingÉ // offscreen. It is the above routine that will finally copy our offscreenÉ // work to the main screen. Anyway, the below function draws the playerÉ // offscreen in the correct position and state. void DrawPlayer (void) { Rect src; if ((evenFrame) && (thePlayer.mode == kIdle)) { // On even frames, we'll draw the "flashed" graphic of the player. // If you've played Glypha, you notice that the player begins aÉ // game flashing alternately between bones and a normal player. CopyMask(&((GrafPtr)idleSrcMap)->portBits, &((GrafPtr)playerMaskMap)->portBits, &((GrafPtr)workSrcMap)->portBits, &idleSrcRect, &playerRects[thePlayer.srcNum], &thePlayer.dest); } else if (thePlayer.mode == kBones) { // If the player is dead and a pile of bonesÉ src = playerRects[thePlayer.srcNum]; src.bottom = src.top + thePlayer.frame; CopyMask(&((GrafPtr)playerSrcMap)->portBits, &((GrafPtr)playerMaskMap)->portBits, &((GrafPtr)workSrcMap)->portBits, &src, &src, &thePlayer.dest); } else // Else, if the player is neither idle nor deadÉ { CopyMask(&((GrafPtr)playerSrcMap)->portBits, &((GrafPtr)playerMaskMap)->portBits, &((GrafPtr)workSrcMap)->portBits, &playerRects[thePlayer.srcNum], &playerRects[thePlayer.srcNum], &thePlayer.dest); } // Now we add the player to the update rect list. AddToUpdateRects(&thePlayer.dest); // Record old locations. thePlayer.wasH = thePlayer.h; thePlayer.wasV = thePlayer.v; // Record old bounds rect. thePlayer.wasDest = thePlayer.dest; } //-------------------------------------------------------------- CheckEnemyWrapAround // This function both determines whether or not an enemy (sphinx) is wrapping around. // If it is, the "second" wrapped-around enemy is drawn. void CheckEnemyWrapAround (short who) { Rect wrapRect, wasWrapRect, src; if (theEnemies[who].dest.right > 640) // Is enemy off the right edge of screen? { wrapRect = theEnemies[who].dest; // Copy bounds. wrapRect.left -= 640; // Offset bounds copy to left (one screen width). wrapRect.right -= 640; // Ditto with old bounds. wasWrapRect = theEnemies[who].wasDest; wasWrapRect.left -= 640; wasWrapRect.right -= 640; // Handle "egg" enemies. if ((theEnemies[who].mode == kFalling) || (theEnemies[who].mode == kEggTimer)) { // Handle "egg" enemy sinking into platform. if ((theEnemies[who].mode == kEggTimer) && (theEnemies[who].frame < 24)) { src = eggSrcRect; src.bottom = src.top + theEnemies[who].frame; } else src = eggSrcRect; CopyMask(&((GrafPtr)eggSrcMap)->portBits, &((GrafPtr)eggMaskMap)->portBits, &((GrafPtr)workSrcMap)->portBits, &src, &src, &wrapRect); } else // Otherwise, if enemy not an eggÉ { CopyMask(&((GrafPtr)enemyFlySrcMap)->portBits, &((GrafPtr)enemyFlyMaskMap)->portBits, &((GrafPtr)workSrcMap)->portBits, &enemyRects[theEnemies[who].srcNum], &enemyRects[theEnemies[who].srcNum], &wrapRect); } AddToUpdateRects(&wrapRect); // Add bounds to update rect list. } else if (theEnemies[who].dest.left < 0) // Check to see if enemy off left edge instead. { wrapRect = theEnemies[who].dest; // Make a copy of enemy bounds. wrapRect.left += 640; // Offset it right one screens width. wrapRect.right += 640; // Ditto with old bounds. wasWrapRect = theEnemies[who].wasDest; wasWrapRect.left += 640; wasWrapRect.right += 640; if ((theEnemies[who].mode == kFalling) || (theEnemies[who].mode == kEggTimer)) { // Blah, blah, blah. This is just like the above. if ((theEnemies[who].mode == kEggTimer) && (theEnemies[who].frame < 24)) { src = eggSrcRect; src.bottom = src.top + theEnemies[who].frame; } else src = eggSrcRect; CopyMask(&((GrafPtr)eggSrcMap)->portBits, &((GrafPtr)eggMaskMap)->portBits, &((GrafPtr)workSrcMap)->portBits, &src, &src, &wrapRect); } else { CopyMask(&((GrafPtr)enemyFlySrcMap)->portBits, &((GrafPtr)enemyFlyMaskMap)->portBits, &((GrafPtr)workSrcMap)->portBits, &enemyRects[theEnemies[who].srcNum], &enemyRects[theEnemies[who].srcNum], &wrapRect); } AddToUpdateRects(&wrapRect); } } //-------------------------------------------------------------- DrawEnemies // This function draws all the sphinx enemies (or eggs if they're in that state). // It doesn't handle wrap-around (the above function does) but it does call it. void DrawEnemies (void) { Rect src; short i; for (i = 0; i < numEnemies; i++) // Go through all enemies. { switch (theEnemies[i].mode) // Handle the different modes as seperate cases. { case kSpawning: // Spawning enemies are "growing" out of the platform. src = enemyRects[theEnemies[i].srcNum]; src.bottom = src.top + theEnemies[i].frame; CopyMask(&((GrafPtr)enemyWalkSrcMap)->portBits, &((GrafPtr)enemyWalkMaskMap)->portBits, &((GrafPtr)workSrcMap)->portBits, &src, &src, &theEnemies[i].dest); AddToUpdateRects(&theEnemies[i].dest); // Don't need to check wrap-around, when enemiesÉ // spawn, they're never on the edge of screen. theEnemies[i].wasDest = theEnemies[i].dest; theEnemies[i].wasH = theEnemies[i].h; theEnemies[i].wasV = theEnemies[i].v; break; case kFlying: // Flying enemies are air borne (gee). CopyMask(&((GrafPtr)enemyFlySrcMap)->portBits, &((GrafPtr)enemyFlyMaskMap)->portBits, &((GrafPtr)workSrcMap)->portBits, &enemyRects[theEnemies[i].srcNum], &enemyRects[theEnemies[i].srcNum], &theEnemies[i].dest); AddToUpdateRects(&theEnemies[i].dest); CheckEnemyWrapAround(i); // I like the word "air bourne". theEnemies[i].wasDest = theEnemies[i].dest; theEnemies[i].wasH = theEnemies[i].h; theEnemies[i].wasV = theEnemies[i].v; break; case kWalking: // Walking enemies are walking. Enemies. CopyMask(&((GrafPtr)enemyWalkSrcMap)->portBits, &((GrafPtr)enemyWalkMaskMap)->portBits, &((GrafPtr)workSrcMap)->portBits, &enemyRects[theEnemies[i].srcNum], &enemyRects[theEnemies[i].srcNum], &theEnemies[i].dest); AddToUpdateRects(&theEnemies[i].dest); // Don't need to check wrap-around, enemies walkÉ // only briefly, and never off edge of screen. theEnemies[i].wasDest = theEnemies[i].dest; theEnemies[i].wasH = theEnemies[i].h; theEnemies[i].wasV = theEnemies[i].v; break; case kFalling: // Falling enemies are in fact eggs! CopyMask(&((GrafPtr)eggSrcMap)->portBits, &((GrafPtr)eggMaskMap)->portBits, &((GrafPtr)workSrcMap)->portBits, &eggSrcRect, &eggSrcRect, &theEnemies[i].dest); AddToUpdateRects(&theEnemies[i].dest); CheckEnemyWrapAround(i); // Check for wrap around. theEnemies[i].wasDest = theEnemies[i].dest; theEnemies[i].wasH = theEnemies[i].h; theEnemies[i].wasV = theEnemies[i].v; break; case kEggTimer: // These are idle, perhaps hatching, eggs. if (theEnemies[i].frame < 24) { // Below countdown = 24, the egss are sinkingÉ src = eggSrcRect; // into the platform (hatch time!). src.bottom = src.top + theEnemies[i].frame; } else src = eggSrcRect; CopyMask(&((GrafPtr)eggSrcMap)->portBits, &((GrafPtr)eggMaskMap)->portBits, &((GrafPtr)workSrcMap)->portBits, &src, &src, &theEnemies[i].dest); AddToUpdateRects(&theEnemies[i].dest); CheckEnemyWrapAround(i); // Check for wrap around. theEnemies[i].wasDest = theEnemies[i].dest; theEnemies[i].wasH = theEnemies[i].h; theEnemies[i].wasV = theEnemies[i].v; break; } } } //-------------------------------------------------------------- DrawFrame // This function is the "master" drawing function that calls all the aboveÉ // routines. It is called once per frame. void DrawFrame (void) { DrawTorches(); // Gee, draws the torches? DrawHand(); // Draws the hand? DrawEye(); // A clue to easing your documentation demandsÉ DrawPlayer(); // is to use "smart" names for your functions. CheckPlayerWrapAround(); // Check for player wrap-around. DrawEnemies(); // Handle all sphinx-type enemy drawing. CopyAllRects(); // Put it all onscreen. } \ No newline at end of file diff --git a/Source/Interface.c b/Source/Interface.c new file mode 100755 index 0000000..47e8f80 --- /dev/null +++ b/Source/Interface.c @@ -0,0 +1 @@ + //============================================================================ //---------------------------------------------------------------------------- // Interface.c //---------------------------------------------------------------------------- //============================================================================ // I put all interface related code in here. Interface would include eventÉ // handling, menus, dialog boxes, etc. All the user interaction that takesÉ // place before and after an actual game is in play. #include "Externs.h" #include #define kAppleMenuID 128 #define iAbout 1 #define kGameMenuID 129 #define iNewGame 1 #define iPauseGame 2 #define iEndGame 3 #define iQuit 5 #define kOptionsMenuID 130 #define iSettings 1 #define iHelp 2 #define iHighScores 3 #define kAboutPictID 132 void DoAppleMenu (short); void DoGameMenu (short); void DoOptionsMenu (short); void UpdateMainWindow (void); void HandleMouseEvent (EventRecord *); void HandleKeyEvent (EventRecord *); void HandleUpdateEvent (EventRecord *); void HandleOSEvent (EventRecord *); void HandleHighLevelEvent (EventRecord *); void DoAbout (void); void DoGameSettings (void); Rect mainWindowRect; WindowPtr mainWindow; MenuHandle appleMenu, gameMenu, optionsMenu; Boolean switchedOut, quitting, canPlay, openTheScores; extern prefsInfo thePrefs; extern Rect backSrcRect, workSrcRect; extern CGrafPtr backSrcMap, workSrcMap; extern Boolean pausing, playing, helpOpen, scoresOpen; //============================================================== Functions //-------------------------------------------------------------- MenusReflectMode // Depending on whether a game is in progress (paused) or not, I wantÉ // menu items grayed out in one case and not grayed out in the other. // This function, when called, displays the menus correctly dependingÉ // on the mode we're in (playing or not playing, pausing or not). void MenusReflectMode (void) { if (playing) // If a game is in progressÉ { DisableItem(gameMenu, iNewGame); // Cannot begin another New Game. EnableItem(gameMenu, iPauseGame); // Can Pause Game. if (pausing) // If we are pausedÉ SetItem(gameMenu, iPauseGame, "\pResume Game"); // Rename item "Resume Game". else // If we are not pausedÉ SetItem(gameMenu, iPauseGame, "\pPause Game"); // Rename item "Pause Game". EnableItem(gameMenu, iEndGame); // Can End Game. DisableItem(optionsMenu, 0); // Cannot change game settings. } else // Else, if Glypha is idleÉ { EnableItem(gameMenu, iNewGame); // Can begin a New Game. DisableItem(gameMenu, iPauseGame); // Cannot Pause Game. SetItem(gameMenu, iPauseGame, "\pPause Game"); // Rename item "Pause Game". DisableItem(gameMenu, iEndGame); // Cannot End Game. EnableItem(optionsMenu, 0); // Can change game settings. } } //-------------------------------------------------------------- DoAppleMenu // This function takes care of handling the Apple menu (Desk Assecories and theÉ // About box). void DoAppleMenu (short theItem) { Str255 daName; GrafPtr wasPort; short daNumber; switch (theItem) // Depending on the item selectedÉ { case iAbout: // If the About item was selectedÉ if ((scoresOpen) || (helpOpen)) // If high scores or help screens upÉ { CloseWall(); // hide them. scoresOpen = FALSE; // High scores no longer open. helpOpen = FALSE; // Help screen is no longer open. // Uncheck help & high scores menu items. CheckItem(optionsMenu, iHelp, helpOpen); CheckItem(optionsMenu, iHighScores, scoresOpen); } DoAbout(); // Bring up the About dialog. break; default: // If any other item was selected (DA)É GetItem(appleMenu, theItem, daName); // Get the name of the item selected. GetPort(&wasPort); // Remember our port. daNumber = OpenDeskAcc(daName); // Launch the Desk Accesory. SetPort((GrafPtr)wasPort); // When we return, restore port. break; } } //-------------------------------------------------------------- DoGameMenu // This function handles a users interaction with the Game menu. QuittingÉ // Glypha, starting a new game, resuming a paused game are handled here. void DoGameMenu (short theItem) { switch (theItem) // Depending on menu item selectedÉ { case iNewGame: // If user selected New Game itemÉ if ((scoresOpen) || (helpOpen)) // If high scores or help screen is up,É { // close them first. CloseWall(); scoresOpen = FALSE; helpOpen = FALSE; CheckItem(optionsMenu, iHelp, helpOpen); CheckItem(optionsMenu, iHighScores, scoresOpen); } InitNewGame(); // Initialize variables for a new game. MenusReflectMode(); // Properly gray out the right menu items. break; case iPauseGame: // If user selected Pause Game itemÉ if (pausing) // If we are paused, resume playing. { pausing = FALSE; // Turn off pausing flag. DumpBackToWorkMap(); // Restore off screen just in case. } // Actually pausing a game (not resuming)É break; // is not really handled here. It's handledÉ // directly within the main game loop. case iEndGame: // Ending a game in progress isn't reallyÉ break; // handled here - this is a dummy item. // Ending a game is handled within the mainÉ // game loop by looking for the 'command'É // and 'E' key explicitly. case iQuit: // If user selected Quit itemÉ quitting = TRUE; // Set quitting flag to TRUE. break; } } //-------------------------------------------------------------- DoOptionsMenu // This function handles the Options menu. Options include game settings,É // displaying the high scores, and bringing up the Help screen. void DoOptionsMenu (short theItem) { switch (theItem) // Depending on which item the user selectedÉ { case iSettings: // If user selected Game Settings itemÉ if ((scoresOpen) || (helpOpen)) // Close high scores or help screen. { CloseWall(); scoresOpen = FALSE; helpOpen = FALSE; CheckItem(optionsMenu, iHelp, helpOpen); CheckItem(optionsMenu, iHighScores, scoresOpen); } DoGameSettings(); // Bring up game settings dialog. break; case iHelp: // If user selected Help itemÉ if (helpOpen) // If Help open, close it. { CloseWall(); helpOpen = FALSE; } else // Else, if Help is not open - open it. { if (scoresOpen) // If the High Scores are up though,É { CloseWall(); // Close them first. scoresOpen = FALSE; CheckItem(optionsMenu, iHighScores, scoresOpen); } OpenHelp(); // Now open the Help screen. } CheckItem(optionsMenu, iHelp, helpOpen); break; case iHighScores: // If user selected High ScoresÉ if (scoresOpen) // If the High Scores are up, close them. { CloseWall(); scoresOpen = FALSE; } else // If the High Scores are not upÉ { if (helpOpen) // First see if Help is open. { CloseWall(); // And close the Help screen. helpOpen = FALSE; CheckItem(optionsMenu, iHelp, helpOpen); } OpenHighScores(); // Now open the High Scores. } CheckItem(optionsMenu, iHighScores, scoresOpen); break; } } //-------------------------------------------------------------- DoMenuChoice // This is the main menu-handling function. It examines which menu was selectedÉ // by the user and passes on to the appropriate function, the item within thatÉ // menu that was selected. void DoMenuChoice (long menuChoice) { short theMenu, theItem; if (menuChoice == 0) // A little error checking. return; theMenu = HiWord(menuChoice); // Extract which menu was selected. theItem = LoWord(menuChoice); // Extract which item it was that was selected. switch (theMenu) // Now, depending upon which menu was selectedÉ { case kAppleMenuID: // If the Apple menu selectedÉ DoAppleMenu(theItem); // Call the function that handles the Apple menu. break; case kGameMenuID: // If the Game menu selectedÉ DoGameMenu(theItem); // Call the function that handles the Game menu. break; case kOptionsMenuID: // If the Options menu selectedÉ DoOptionsMenu(theItem); // Call the function that handles the Options menu. break; } HiliteMenu(0); // "De-invert" menu. } //-------------------------------------------------------------- UpdateMainWindow // This is a simple function that simply copies the contents from theÉ // background offscreen pixmap to the main screen. It is primarilyÉ // called in response to an update event, but could be called any timeÉ // when I want to force the screen to be redrawn. void UpdateMainWindow (void) { CopyBits(&((GrafPtr)backSrcMap)->portBits, &(((GrafPtr)mainWindow)->portBits), &mainWindowRect, &mainWindowRect, srcCopy, 0L); } //-------------------------------------------------------------- HandleMouseEvent // Mouse clicks come here. This is standard event-handling drivel. No different // from any other standard Mac program (game or otherwise). void HandleMouseEvent (EventRecord *theEvent) { WindowPtr whichWindow; Point localPoint; long menuChoice; short thePart; // Determine window and where in window. thePart = FindWindow(theEvent->where, &whichWindow); switch (thePart) // Depending on where mouse was clickedÉ { case inSysWindow: // In a Desk Accesory. SystemClick(theEvent, whichWindow); // (Is this stuff obsolete yet?) break; case inMenuBar: // Selected a menu item. menuChoice = MenuSelect(theEvent->where); if (canPlay) // Call menu handling routine. DoMenuChoice(menuChoice); break; case inDrag: // Like the lazy bastard I amÉ case inGoAway: // I'll just ignore these. case inGrow: // But, hey, the window isn'tÉ case inZoomIn: // movable or growable! case inZoomOut: break; case inContent: // Click in the window itself. FlashObelisks(TRUE); // Do lightning animation. LogNextTick(3); // Lightning will hit cursor location. localPoint = theEvent->where; GlobalToLocal(&localPoint); GenerateLightning(localPoint.h, localPoint.v); StrikeLightning(); WaitForNextTick(); StrikeLightning(); LogNextTick(2); WaitForNextTick(); PlayExternalSound(kLightningSound, kLightningPriority); LogNextTick(3); GenerateLightning(localPoint.h, localPoint.v); StrikeLightning(); WaitForNextTick(); StrikeLightning(); LogNextTick(2); WaitForNextTick(); LogNextTick(3); GenerateLightning(localPoint.h, localPoint.v); StrikeLightning(); WaitForNextTick(); StrikeLightning(); LogNextTick(2); WaitForNextTick(); PlayExternalSound(kLightningSound, kLightningPriority); LogNextTick(3); GenerateLightning(localPoint.h, localPoint.v); StrikeLightning(); WaitForNextTick(); StrikeLightning(); LogNextTick(2); WaitForNextTick(); FlashObelisks(FALSE); break; } } //-------------------------------------------------------------- HandleKeyEvent // More standard issue. This function handles any keystrokes when no game is // in session. Command-key strokes handled here too. void HandleKeyEvent (EventRecord *theEvent) { char theChar; Boolean commandDown; theChar = theEvent->message & charCodeMask; // Extract key hit. commandDown = ((theEvent->modifiers & cmdKey) != 0); // See if command key down. if (commandDown) // If command key down, call menuÉ { // handling routine. if (canPlay) DoMenuChoice(MenuKey(theChar)); } else { if (helpOpen) // Handle special keys if the helpÉ { // screen is up. if (theChar == kUpArrowKeyASCII) // Up arrow key scrolls help down. { if (theEvent->what == autoKey) ScrollHelp(-3); else ScrollHelp(-1); } else if (theChar == kDownArrowKeyASCII) // Down arrow key scrolls help up. { if (theEvent->what == autoKey) ScrollHelp(3); else ScrollHelp(1); } else if (theChar == kPageDownKeyASCII) // Handle page down for help screen. { ScrollHelp(199); } else if (theChar == kPageUpKeyASCII) // Handle page up for help. { ScrollHelp(-199); } else if ((theChar == kHelpKeyASCII) && (!playing)) { // Hitting Help key closes helpÉ CloseWall(); // (if it's already open). helpOpen = FALSE; CheckItem(optionsMenu, iHelp, helpOpen); } } else if ((theChar == kHelpKeyASCII) && (!playing)) { // Else, if help not open and HelpÉ if (scoresOpen) // key is hit, open Help. { // Close high scores if open. CloseWall(); scoresOpen = FALSE; CheckItem(optionsMenu, iHighScores, scoresOpen); } OpenHelp(); // Open help. CheckItem(optionsMenu, iHelp, helpOpen); } } } //-------------------------------------------------------------- HandleUpdateEvent // This function handles update events. Standard event-handling stuff. void HandleUpdateEvent (EventRecord *theEvent) { if ((WindowPtr)theEvent->message == mainWindow) { SetPort((GrafPtr)mainWindow); // Don't forget this line, BTW. BeginUpdate((GrafPtr)mainWindow); // I did once and it took meÉ UpdateMainWindow(); // ages to track down that bug. EndUpdate((GrafPtr)mainWindow); // Well, it took me a week I think. canPlay = TRUE; } } //-------------------------------------------------------------- HandleOSEvent // Handle switchin in and out events. Standard event-handling stuff. void HandleOSEvent (EventRecord *theEvent) { if (theEvent->message & 0x01000000) // If suspend or resume eventÉ { if (theEvent->message & 0x00000001) // Specifically, if resume eventÉ switchedOut = FALSE; // I keep thinking I should do more here. else // Or if suspend eventÉ switchedOut = TRUE; // What am I forgetting? } } //-------------------------------------------------------------- HandleHighLevelEvent // Again, it's a fact I'm lazy. AppleEvents are fairly easy to implement butÉ // a nightmare to try and explain. Filling out the below function is left asÉ // and exercise to the reader. void HandleHighLevelEvent (EventRecord *theEvent) { // theErr = AEProcessAppleEvent(theEvent); } //-------------------------------------------------------------- HandleEvent // Standard event stuff. This is the culling function that calls all the aboveÉ // functions. It looks for an event and if it detects one, it calls the appropriateÉ // function above to handle it. void HandleEvent (void) { EventRecord theEvent; long sleep = 1L; Boolean itHappened; // See if an event is queued up. itHappened = WaitNextEvent(everyEvent, &theEvent, sleep, 0L); if (itHappened) // Ah, an event. I live for events! { switch (theEvent.what) // And what kind of event be ya'? { case mouseDown: // Aiy! Y' be a mouse click do ya'? HandleMouseEvent(&theEvent); break; case keyDown: // Key down, key held down events. case autoKey: HandleKeyEvent(&theEvent); break; case updateEvt: // Something needs redrawing! HandleUpdateEvent(&theEvent); break; case osEvt: // Switching in and out events. HandleOSEvent(&theEvent); break; case kHighLevelEvent: // Hmmmm. A "what" event? HandleHighLevelEvent(&theEvent); break; } } else if (openTheScores) // Check for "auto open" flag. { // If TRUE, set the flag back toÉ openTheScores = FALSE; // FALSE and open the high scores. OpenHighScores(); } } //-------------------------------------------------------------- DoAbout // This handles the About dialog. It brings up the About box in aÉ // simple centered window with no drag bar, close box or anything. // Leaving the dialog is handled with a simple mouse click. void DoAbout (void) { Rect aboutRect; WindowPtr aboutWindow; SetRect(&aboutRect, 0, 0, 325, 318); // Bring up centered window. CenterRectInRect(&aboutRect, &qd.screenBits.bounds); aboutWindow = GetNewCWindow(129, 0L, kPutInFront); MoveWindow((GrafPtr)aboutWindow, aboutRect.left, aboutRect.top, TRUE); ShowWindow((GrafPtr)aboutWindow); SetPort((GrafPtr)aboutWindow); LoadGraphic(kAboutPictID); // Draw About dialog graphic. do // Make sure button not downÉ { // before proceeding. } while (Button()); // Proceed. do // And now wait until the mouseÉ { // is pressed before closing theÉ } // window (ABout dialog). while (!Button()); FlushEvents(everyEvent, 0); // Flush the queue. if (aboutWindow != 0L) DisposeWindow(aboutWindow); // Close the About dialog. } //-------------------------------------------------------------- DoGameSettings // This one however is a good and proper dialog box. It handles the meagerÉ // preference settings for Glypha. Nothing fancy here to report. Just aÉ // straight-forward dialog calling routine. void DoGameSettings (void) { #define kGameSettingsDialogID 133 DialogPtr theDial; long newVolume; short i, item; Boolean leaving; CenterDialog(kGameSettingsDialogID); // Center dialog, then call up. theDial = GetNewDialog(kGameSettingsDialogID, 0L, kPutInFront); SetPort((GrafPtr)theDial); ShowWindow((GrafPtr)theDial); // Make visible (after centering). DrawDefaultButton(theDial); // Draw border around Okay button. FlushEvents(everyEvent, 0); // Put in a default sound volume. SetDialogNumToStr(theDial, 3, (long)thePrefs.wasVolume); SelIText(theDial, 3, 0, 1024); // Select it. leaving = FALSE; while (!leaving) { ModalDialog(0L, &item); // Simple modal dialog filtering. if (item == 1) // Did user hit the Okay button? { // Well see if volume entered is legal. GetDialogNumFromStr(theDial, 3, &newVolume); if ((newVolume >= 0) && (newVolume <= 7)) { // If it is legal, we'll note it and quit. thePrefs.wasVolume = (short)newVolume; SetSoundVol((short)newVolume); leaving = TRUE; // Bye. } else // Otherwise, the volume entered is wrong. { // So we'll Beep, enter the last legalÉ SysBeep(1); // value and select the text again. SetDialogNumToStr(theDial, 3, (long)thePrefs.wasVolume); SelIText(theDial, 3, 0, 1024); } } else if (item == 2) // Did the user hit the "Clear Scores"É { // button? for (i = 0; i < 10; i++) // Walk through and zero scores. { PasStringCopy("\pNemo", thePrefs.highNames[i]); thePrefs.highScores[i] = 0L; thePrefs.highLevel[i] = 0; openTheScores = TRUE; // Bring up scores when dialog quits. } DisableControl(theDial, 2); // Gray out Clear Scores button. } } DisposDialog(theDial); // Clean up before going. } \ No newline at end of file diff --git a/Source/Main.c b/Source/Main.c new file mode 100755 index 0000000..72dc92d --- /dev/null +++ b/Source/Main.c @@ -0,0 +1 @@ + //============================================================================ //---------------------------------------------------------------------------- // Glypha III 1.0.1 // by Scheherazade //---------------------------------------------------------------------------- //============================================================================ // Here is the "main" file for Glypha. Here is where the game begins and ends. // Also included are the preference calls. #include "Externs.h" #include #define kPrefsVersion 0x0001 void ReadInPrefs (void); void WriteOutPrefs (void); void main (void); prefsInfo thePrefs; short wasVolume; extern Boolean quitting, playing, pausing, evenFrame; //============================================================== Functions //-------------------------------------------------------------- ReadInPrefs // This function loads up the preferences. If the preferences // aren't found, all settings are set to their defaults. void ReadInPrefs (void) { short i; // Call LoadPrefs() function - returns TRUE if it worked. if (LoadPrefs(&thePrefs, kPrefsVersion)) SetSoundVol(thePrefs.wasVolume); else // If LoadPrefs() failed, set defaults. { thePrefs.prefVersion = kPrefsVersion; // version of prefs thePrefs.filler = 0; // just padding PasStringCopy("\pYour Name", thePrefs.highName); // last highscores name for (i = 0; i < 10; i++) // loop through scores { PasStringCopy("\pNemo", thePrefs.highNames[i]); // put "Nemo" in name thePrefs.highScores[i] = 0L; // set highscore to 0 thePrefs.highLevel[i] = 0; // level attained = 0 } GetSoundVol(&thePrefs.wasVolume); } // Get sound volume so we can restore it. GetSoundVol(&wasVolume); } //-------------------------------------------------------------- WriteOutPrefs // This function writes out the preferences to disk and restores // the sound volume to its setting before Glypha was launched. void WriteOutPrefs (void) { if (!SavePrefs(&thePrefs, kPrefsVersion)) SysBeep(1); SetSoundVol(wasVolume); } //-------------------------------------------------------------- main // This is the main function. Every C program has one of these. // First it initializes our program and then falls into a loop // until the user chooses to quit. At that point, it cleans up // and exits. void main (void) { long tickWait; ToolBoxInit(); // Call function that initializes the ToolBox managers. CheckEnvirons(); // Check the Mac we're on to see if we can run. OpenMainWindow(); // Open up the main window - it will fill the monitor. InitVariables(); // Initialize Glypha's variables. InitSound(); // Create sound channels and load up sounds. InitMenubar(); // Set up the game's menubar. ReadInPrefs(); // Load up the preferences. do // Here begins the main loop. { HandleEvent(); // Check for events. if ((playing) && (!pausing)) PlayGame(); // If user began game, drop in game loop. (play mode) else // If no game, animate the screen. (idle mode) { tickWait = TickCount() + 2L; evenFrame = !evenFrame; DrawTorches(); // Flicker torches. CopyAllRects(); // Refresh screen. do // Wait for 2 Ticks to pass to keep fast Macs at bay. { } while (TickCount() < tickWait); } } while (!quitting); KillSound(); // Dispose of sound channels. ShutItDown(); // Dispose of other structures. WriteOutPrefs(); // Save preferences to disk. } \ No newline at end of file diff --git a/Source/Play.c b/Source/Play.c new file mode 100755 index 0000000..1758c92 --- /dev/null +++ b/Source/Play.c @@ -0,0 +1 @@ + //============================================================================ //---------------------------------------------------------------------------- // 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. } \ No newline at end of file diff --git a/Source/Prefs.c b/Source/Prefs.c new file mode 100755 index 0000000..1ea72d1 --- /dev/null +++ b/Source/Prefs.c @@ -0,0 +1 @@ + //============================================================================ //---------------------------------------------------------------------------- // Prefs.c //---------------------------------------------------------------------------- //============================================================================ // This is a slick little file that I re-use and re-use. I wrote it toÉ // seemlessly handle System 6 or System 7 with but a single call. You needÉ // to define your own "prefs" struct, but these routines will read and writeÉ // it to the System folder. #include "Externs.h" #include // Needed for creating a folder. #include // Needed for the Gestalt() call. #include // I can't remember why I needed this. #define kPrefCreatorType 'zade' // Change this to reflect your apps creator. #define kPrefFileType 'zadP' // Change this to reflect your prefs type. #define kPrefFileName "\pGlypha Prefs" // Change this to reflect the name for your prefs. #define kDefaultPrefFName "\pPreferences" // Name of prefs folder (System 6 only). #define kPrefsStringsID 160 // For easy localization. #define kPrefsFNameIndex 1 // This one works with the previous constant. Boolean CanUseFindFolder (void); Boolean GetPrefsFPath (long *, short *); Boolean CreatePrefsFolder (short *); Boolean GetPrefsFPath6 (short *); Boolean WritePrefs (long *, short *, prefsInfo *); Boolean WritePrefs6 (short *, prefsInfo *); OSErr ReadPrefs (long *, short *, prefsInfo *); OSErr ReadPrefs6 (short *, prefsInfo *); Boolean DeletePrefs (long *, short *); Boolean DeletePrefs6 (short *); //============================================================== Functions //-------------------------------------------------------------- CanUseFindFolder // Returns TRUE if we can use the FindFolder() call (a System 7 nicety). Boolean CanUseFindFolder (void) { OSErr theErr; long theFeature; if (!DoWeHaveGestalt()) // Darn, have to check for Gestalt() first. return(FALSE); // If no Gestalt(), probably don't have FindFolder(). theErr = Gestalt(gestaltFindFolderAttr, &theFeature); if (theErr != noErr) // Use selector for FindFolder() attribute. return(FALSE); // Now do a bit test specifically for FindFolder(). if (!BitTst(&theFeature, 31 - gestaltFindFolderPresent)) return(FALSE); else return(TRUE); } //-------------------------------------------------------------- GetPrefsFPath // This function gets the file path to the Preferences folder (for System 7). // It is called only if we can use FindFolder() (see previous function). Boolean GetPrefsFPath (long *prefDirID, short *systemVolRef) { OSErr theErr; // Here's the wiley FindFolder() call. theErr = FindFolder(kOnSystemDisk, kPreferencesFolderType, kCreateFolder, systemVolRef, prefDirID); // It returns to us the directory and volume ref.É if (theErr != noErr) // Assuming it worked at all! return(FALSE); return(TRUE); } //-------------------------------------------------------------- CreatePrefsFolder // This function won't be necessary for System 7, for System 6 though, it createsÉ // a folder ("Preferences") in the System folder and returns whether or not it worked. Boolean CreatePrefsFolder (short *systemVolRef) { HFileParam fileParamBlock; Str255 folderName; OSErr theErr; // Here's our localization. Rather thanÉ // hard-code the name "Preferences" in the codeÉ // we pull up the text from a string resource. GetIndString(folderName, kPrefsStringsID, kPrefsFNameIndex); // Set up a file parameter block. fileParamBlock.ioVRefNum = *systemVolRef; fileParamBlock.ioDirID = 0; fileParamBlock.ioNamePtr = folderName; fileParamBlock.ioCompletion = 0L; // And create a directory (folder). theErr = PBDirCreate((HParmBlkPtr)&fileParamBlock, FALSE); if (theErr != noErr) // See that it worked. { RedAlert("\pPrefs Creation Error"); return(FALSE); } return(TRUE); } //-------------------------------------------------------------- GetPrefsFPath6 // If ever there was a case to drop support for System 6 (and require System 7),É // this is it. Look at how insidious handling System 6 files can be. The followingÉ // function is the "System 6 pedigree" of the above GetPrefsFPath() function. NoteÉ // that the GetPrefsFPath() function was ONE CALL! TWO LINES OF CODE! The belowÉ // function is like a page or so. Anyway, this function is called if Glypha isÉ // running under System 6 and essentially returns a volume reference pointing toÉ // the preferences folder. Boolean GetPrefsFPath6 (short *systemVolRef) { Str255 folderName, whoCares; SysEnvRec thisWorld; CInfoPBRec catalogInfoPB; DirInfo *directoryInfo = (DirInfo *) &catalogInfoPB; HFileInfo *fileInfo = (HFileInfo *) &catalogInfoPB; WDPBRec workingDirPB; long prefDirID; OSErr theErr; // Yokelization. GetIndString(folderName, kPrefsStringsID, kPrefsFNameIndex); // SysEnvirons() for System folder volRef. theErr = SysEnvirons(2, &thisWorld); if (theErr != noErr) return(FALSE); // Okay, here's the volume reference. *systemVolRef = thisWorld.sysVRefNum; fileInfo->ioVRefNum = *systemVolRef; // Set up another parameter block. fileInfo->ioDirID = 0; // Ignored. fileInfo->ioFDirIndex = 0; // Irrelevant. fileInfo->ioNamePtr = folderName; // Directory we're looking for. fileInfo->ioCompletion = 0L; theErr = PBGetCatInfo(&catalogInfoPB, FALSE); if (theErr != noErr) // Did we fail to find Prefs folder? { if (theErr != fnfErr) // If it WASN'T a file not found errorÉ { // then something more sinister is afoot. RedAlert("\pPrefs Filepath Error"); } // Otherwise, need to create prefs folder. if (!CreatePrefsFolder(systemVolRef)) return(FALSE); // Again - can we find the prefs folder? directoryInfo->ioVRefNum = *systemVolRef; directoryInfo->ioFDirIndex = 0; directoryInfo->ioNamePtr = folderName; theErr = PBGetCatInfo(&catalogInfoPB, FALSE); if (theErr != noErr) { RedAlert("\pPrefs GetCatInfo() Error"); return(FALSE); } } prefDirID = directoryInfo->ioDrDirID; // Alright, the dir. ID for prefs folder. workingDirPB.ioNamePtr = whoCares; // Now convert working dir. into a "real"É workingDirPB.ioVRefNum = *systemVolRef; // dir. ID so we can get volume number. workingDirPB.ioWDIndex = 0; workingDirPB.ioWDProcID = 0; workingDirPB.ioWDVRefNum = 0; workingDirPB.ioCompletion = 0L; theErr = PBGetWDInfo(&workingDirPB, FALSE); if (theErr != noErr) { RedAlert("\pPrefs PBGetWDInfo() Error"); } // The volume where directory is located. *systemVolRef = workingDirPB.ioWDVRefNum; workingDirPB.ioNamePtr = whoCares; workingDirPB.ioWDDirID = prefDirID; // Okay, finally, with a directory ID, É workingDirPB.ioVRefNum = *systemVolRef; // and a "hard" volume numberÉ workingDirPB.ioWDProcID = 0; // É workingDirPB.ioCompletion = 0L; // É theErr = PBOpenWD(&workingDirPB, FALSE); // we can create a working directoryÉ if (theErr != noErr) // control block with which to accessÉ { // files in the prefs folder. RedAlert("\pPrefs PBOpenWD() Error"); } *systemVolRef = workingDirPB.ioVRefNum; return(TRUE); } //-------------------------------------------------------------- WritePrefs // This is the System 7 version that handles all the above functions when youÉ // want to write out the preferences file. It is called by SavePrefs() belowÉ // if we're running under System 7. It creates an FSSpec record to holdÉ // information about where the preferences file is located, creates Glypha'sÉ // preferences if they are not found, opens the prefences file, writes outÉ // the preferences, and the closes the prefs. Bam, bam, bam. Boolean WritePrefs (long *prefDirID, short *systemVolRef, prefsInfo *thePrefs) { OSErr theErr; short fileRefNum; long byteCount; FSSpec theSpecs; Str255 fileName = kPrefFileName; // Create FSSpec record from volume ref and dir ID. theErr = FSMakeFSSpec(*systemVolRef, *prefDirID, fileName, &theSpecs); if (theErr != noErr) // See if it failed. { // An fnfErr means file not found error (no prefs). if (theErr != fnfErr) // If that weren't the problem, we're cooked. RedAlert("\pPrefs FSMakeFSSpec() Error"); // If it was an fnfErr, create the prefs. theErr = FSpCreate(&theSpecs, kPrefCreatorType, kPrefFileType, smSystemScript); if (theErr != noErr) // If we fail to create the prefs, bail. RedAlert("\pPrefs FSpCreate() Error"); } // Okay, we either found or made a pref file, open it. theErr = FSpOpenDF(&theSpecs, fsRdWrPerm, &fileRefNum); if (theErr != noErr) // As per usual, if we fail, bail. RedAlert("\pPrefs FSpOpenDF() Error"); byteCount = sizeof(*thePrefs); // Get number of bytes to write (your prefs struct). // And, write out the preferences. theErr = FSWrite(fileRefNum, &byteCount, thePrefs); if (theErr != noErr) // Say no more. RedAlert("\pPrefs FSWrite() Error"); theErr = FSClose(fileRefNum); // Close the prefs file. if (theErr != noErr) // Tic, tic. RedAlert("\pPrefs FSClose() Error"); return(TRUE); } //-------------------------------------------------------------- WritePrefs6 // This is the System 6 equivalent of the above function. It handles prefsÉ // opening, writing and closing for System 6. Boolean WritePrefs6 (short *systemVolRef, prefsInfo *thePrefs) { OSErr theErr; short fileRefNum; long byteCount; Str255 fileName = kPrefFileName; // Attempt to open prefs file. theErr = FSOpen(fileName, *systemVolRef, &fileRefNum); if (theErr != noErr) // If it failed, maybe the prefs don't exist. { // An fnfErr means file not found. if (theErr != fnfErr) // See if in fact that WASN'T the reason. RedAlert("\pPrefs FSOpen() Error"); // If fnfErr WAS the problem, create the prefs. theErr = Create(fileName, *systemVolRef, kPrefCreatorType, kPrefFileType); if (theErr != noErr) RedAlert("\pPrefs Create() Error"); // Open the prefs file. theErr = FSOpen(fileName, *systemVolRef, &fileRefNum); if (theErr != noErr) RedAlert("\pPrefs FSOpen() Error"); } byteCount = sizeof(*thePrefs); // Get number of bytes to write out. // Write the prefs out. theErr = FSWrite(fileRefNum, &byteCount, thePrefs); if (theErr != noErr) RedAlert("\pPrefs FSWrite() Error"); // And close the prefs file. theErr = FSClose(fileRefNum); if (theErr != noErr) RedAlert("\pPrefs FSClose() Error"); return(TRUE); } //-------------------------------------------------------------- SavePrefs // This is the single function called externally to save the preferences. // You pass it a pointer to your preferences struct and a version number. // One of the fields in your preferences struct should be a version numberÉ // (short prefVersion). This function determines if we're on System 6 or 7É // and then calls the appropriate routines. It returns TRUE if all went wellÉ // or FALSE if any step failed. Boolean SavePrefs (prefsInfo *thePrefs, short versionNow) { long prefDirID; short systemVolRef; Boolean canUseFSSpecs; thePrefs->prefVersion = versionNow; // Set prefVersion to versionNow. canUseFSSpecs = CanUseFindFolder(); // See if we can use FindFolder(). if (canUseFSSpecs) // If so (System 7) take this route. { // Get a path to Preferences folder. if (!GetPrefsFPath(&prefDirID, &systemVolRef)) return(FALSE); } else // Here's the System 6 version. { if (!GetPrefsFPath6(&systemVolRef)) return(FALSE); } if (canUseFSSpecs) // Write out the preferences. { if (!WritePrefs(&prefDirID, &systemVolRef, thePrefs)) return(FALSE); } else { if (!WritePrefs6(&systemVolRef, thePrefs)) return(FALSE); } return(TRUE); } //-------------------------------------------------------------- ReadPrefs // This is the System 7 version for reading in the preferences. It handlesÉ // opening the prefs, reading in the data to your prefs struct and closingÉ // the file. OSErr ReadPrefs (long *prefDirID, short *systemVolRef, prefsInfo *thePrefs) { OSErr theErr; short fileRefNum; long byteCount; FSSpec theSpecs; Str255 fileName = kPrefFileName; // Get an FSSpec record to the prefs file. theErr = FSMakeFSSpec(*systemVolRef, *prefDirID, fileName, &theSpecs); if (theErr != noErr) { if (theErr == fnfErr) // If it doesn't exist, return - we'll use defaults. return(theErr); else // If some other file error occured, bail. RedAlert("\pPrefs FSMakeFSSpec() Error"); } // Open the prefs file. theErr = FSpOpenDF(&theSpecs, fsRdWrPerm, &fileRefNum); if (theErr != noErr) RedAlert("\pPrefs FSpOpenDF() Error"); byteCount = sizeof(*thePrefs); // Determine the number of bytes to read in. // Read 'em into your prefs struct. theErr = FSRead(fileRefNum, &byteCount, thePrefs); if (theErr != noErr) // If there was an error reading the fileÉ { // close the file and we'll revert to defaults. if (theErr == eofErr) theErr = FSClose(fileRefNum); else // If closing failed, bail. RedAlert("\pPrefs FSRead() Error"); return(theErr); } theErr = FSClose(fileRefNum); // Close the prefs file. if (theErr != noErr) RedAlert("\pPrefs FSClose() Error"); return(theErr); } //-------------------------------------------------------------- ReadPrefs6 // This is the System 6 version of the above function. It's basically the same,É // but doesn't have the luxury of using FSSpec records. OSErr ReadPrefs6 (short *systemVolRef, prefsInfo *thePrefs) { OSErr theErr; short fileRefNum; long byteCount; Str255 fileName = kPrefFileName; // Attempt to open the prefs file. theErr = FSOpen(fileName, *systemVolRef, &fileRefNum); if (theErr != noErr) // Did opening the file fail? { if (theErr == fnfErr) // It did - did it fail because it doesn't exist? return(theErr); // Okay, then we'll revert to default settings. else // Otherwise, we have a more serious problem. RedAlert("\pPrefs FSOpen() Error"); } // Get number of bytes to read in. byteCount = sizeof(*thePrefs); // Read in the stream of data into prefs struct. theErr = FSRead(fileRefNum, &byteCount, thePrefs); if (theErr != noErr) // Did the read fail? { // Maybe we're reading too much data (new prefs vers). if (theErr == eofErr) // That's okay, we'll use defaults for now. theErr = FSClose(fileRefNum); else RedAlert("\pPrefs FSRead() Error"); return(theErr); } // Close the prefs file. theErr = FSClose(fileRefNum); if (theErr != noErr) RedAlert("\pPrefs FSClose() Error"); return(theErr); } //-------------------------------------------------------------- DeletePrefs // It can happen that you introduce a game with only a few preference settingsÉ // but then later update your game and end up having to add additional settingsÉ // to be stored in your games preferences. In this case, the size of the oldÉ // prefs won't match the size of the new. Or even if the size is the same, youÉ // may have re-ordered the prefs and attempting to load the old prefs will resultÉ // in garbage. It is for this reason that I use the "versionNeed" variable andÉ // the "prefVersion" field in the prefs struct. In such a case, the below functionÉ // will be called to delte the old prefs. When the prefs are then written out, aÉ // new pref file will be created. This particular function is the System 7 versionÉ // for deleting the old preferences. Boolean DeletePrefs (long *dirID, short *volRef) { FSSpec theSpecs; Str255 fileName = kPrefFileName; OSErr theErr; // Create an FSSec record. theErr = FSMakeFSSpec(*volRef, *dirID, fileName, &theSpecs); if (theErr != noErr) // Test to see if it worked. return(FALSE); else // If it workedÉ theErr = FSpDelete(&theSpecs); // delete the file. if (theErr != noErr) return(FALSE); return(TRUE); } //-------------------------------------------------------------- DeletePrefs6 // This is the System 6 version for deleting a preferences file (see above function). Boolean DeletePrefs6 (short *volRef) { Str255 fileName = kPrefFileName; OSErr theErr; theErr = FSDelete(fileName, *volRef); // Delete the prefs file. if (theErr != noErr) return(FALSE); return(TRUE); } //-------------------------------------------------------------- LoadPrefs // Here is the single call for loading in preferences. It handles all theÉ // above function onvolved with opening and reading in preferences. ItÉ // determines whether we are on System 6 or 7 (FSSpecs) and makes the rightÉ // calls. Boolean LoadPrefs (prefsInfo *thePrefs, short versionNeed) { long prefDirID; OSErr theErr; short systemVolRef; Boolean canUseFSSpecs, noProblems; canUseFSSpecs = CanUseFindFolder(); // See if we can use FSSpecs (System 7). if (canUseFSSpecs) { // Get a path to the prefs file. noProblems = GetPrefsFPath(&prefDirID, &systemVolRef); if (!noProblems) return(FALSE); } else { // Gets path to prefs file (System 6). noProblems = GetPrefsFPath6(&systemVolRef); if (!noProblems) return(FALSE); } if (canUseFSSpecs) { // Attempt to read prefs. theErr = ReadPrefs(&prefDirID, &systemVolRef, thePrefs); if (theErr == eofErr) // Fail the read? Maybe an old prefs version. { // Delete it - we'll create a new one later. noProblems = DeletePrefs(&prefDirID, &systemVolRef); return(FALSE); // Meanwhile, we'll use defaults. } else if (theErr != noErr) return(FALSE); } else { // Attempt to read prefs (System 6). theErr = ReadPrefs6(&systemVolRef, thePrefs); if (theErr == eofErr) // Fail the read? Maybe an old prefs version. { // Delete it - we'll create a new one later. noProblems = DeletePrefs6(&systemVolRef); return(FALSE); // Meanwhile, we'll use defaults. } else if (theErr != noErr) return(FALSE); } // Okay, maybe the read worked, but we stillÉ // need to check the version number to seeÉ // if it's current. if (thePrefs->prefVersion != versionNeed) { // We'll delete the file if old version. if (canUseFSSpecs) { noProblems = DeletePrefs(&prefDirID, &systemVolRef); return(FALSE); } else { noProblems = DeletePrefs6(&systemVolRef); return(FALSE); } } return(TRUE); } \ No newline at end of file diff --git a/Source/SetUpTakeDown.c b/Source/SetUpTakeDown.c new file mode 100755 index 0000000..ec9d1b0 --- /dev/null +++ b/Source/SetUpTakeDown.c @@ -0,0 +1 @@ + //============================================================================ //---------------------------------------------------------------------------- // SetUpTakeDown.c //---------------------------------------------------------------------------- //============================================================================ // The below functions are grouped here becuase they are only called once whenÉ // Glypha either starts up or quits. #include "Externs.h" #include // Need "Palettes.h" for the depth calls. #define kHandPictID 128 // These are all the resource ID's forÉ #define kHandMaskID 129 // the various PICT's were going toÉ #define kBackgroundPictID 130 // load up into offscreen pixmaps andÉ #define kHelpPictID 131 // offscreen bitmaps. #define kObeliskPictID 134 #define kPlayerPictID 135 #define kPlayerMaskID 136 #define kNumberPictID 137 #define kIdlePictID 138 #define kEnemyWalkPictID 139 #define kEnemyFlyPictID 140 #define kEnemyWalkMaskID 141 #define kEnemyFlyMaskID 142 #define kFlamePictID 143 #define kEggPictID 144 #define kEggMaskID 145 #define kPlatformPictID 146 #define kEyePictID 147 #define kEyeMaskID 148 Boolean DoWeHaveColor (void); // Prototypes to below functions that areÉ Boolean DoWeHaveSystem605 (void); // called locally. These aren't reallyÉ short WhatsOurDepth (void); // necessary, but I don't enjoy relying onÉ Boolean CanWeDisplay8Bit (GDHandle); // the compiler to second guess me. void SwitchToDepth (short, Boolean); short wasDepth; // Only global variable defined here. // Other global variables defined elsewhere. extern Rect mainWindowRect, backSrcRect, workSrcRect, obSrcRect, playerSrcRect; extern Rect numberSrcRect, idleSrcRect, enemyWalkSrcRect, enemyFlySrcRect; extern Rect obeliskRects[4], playerRects[11], numbersSrc[11], numbersDest[11]; extern Rect platformRects[6], touchDownRects[6], enemyRects[24], handSrcRect; extern Rect flameSrcRect, flameDestRects[2], flameRects[4], platformCopyRects[9]; extern Rect enemyInitRects[5], eggSrcRect, platformSrcRect, helpSrcRect; extern Rect handRects[2], grabZone, eyeSrcRect, eyeRects[4]; extern GDHandle thisGDevice; extern CGrafPtr backSrcMap, workSrcMap, obeliskSrcMap, playerSrcMap; extern CGrafPtr numberSrcMap, idleSrcMap, enemyWalkSrcMap, enemyFlySrcMap; extern CGrafPtr flameSrcMap, eggSrcMap, platformSrcMap, helpSrcMap, handSrcMap; extern CGrafPtr eyeSrcMap; extern GrafPtr playerMaskMap, enemyWalkMaskMap, enemyFlyMaskMap, eggMaskMap; extern GrafPtr handMaskMap, eyeMaskMap; extern WindowPtr mainWindow; extern RgnHandle playRgn; extern MenuHandle appleMenu, gameMenu, optionsMenu; extern long theScore, wasTensOfThousands; extern short numLedges, beginOnLevel, levelOn, livesLeft; extern Boolean quitting, playing, pausing, switchedOut, canPlay, whichList; extern Boolean helpOpen, scoresOpen, openTheScores; //============================================================== Functions //-------------------------------------------------------------- ToolBoxInit // Here's the first function you ever call in Glypha (or any Mac program forÉ // that matter). The calls herein MUST be called before you do anything else. // Otherwise, you'll get all sorts of System Errors. void ToolBoxInit (void) { InitGraf(&qd.thePort); // Initialize QuickDraw variables for our program. InitFonts(); // Initialize fonts. FlushEvents(everyEvent, 0); // Clear event queue of any pending events. InitWindows(); // Initialize the Window Manager. InitMenus(); // Ditto for the Menu Manager. TEInit(); // blah, blah Text Edit. InitDialogs(0L); // blah, blah Dialog Manager. InitCursor(); // Set the cursor to the arrow cursor and init. MaxApplZone(); // Grab application memory. MoreMasters(); // Allocate a block of master pointers. MoreMasters(); // And allocate more. MoreMasters(); // And more. MoreMasters(); // Hey, lets do it again too. switchedOut = FALSE; // We "should" be the foreground app at this time. GetDateTime((unsigned long *)&qd.randSeed); // Randomize random seed. } //-------------------------------------------------------------- DoWeHaveColor // Simple function that returns TRUE if we're running on a Mac thatÉ // is running Color Quickdraw. Boolean DoWeHaveColor (void) { SysEnvRec thisWorld; SysEnvirons(2, &thisWorld); // Call the old SysEnvirons() function. return (thisWorld.hasColorQD); // And return whether it has Color QuickDraw. } //-------------------------------------------------------------- DoWeHaveSystem605 // Another simple "environment" function. Returns TRUE if the Mac we're runningÉ // on has System 6.0.5 or more recent. (6.0.5 introduced the ability to switchÉ // color depths.) Boolean DoWeHaveSystem605 (void) { SysEnvRec thisWorld; Boolean haveIt; SysEnvirons(2, &thisWorld); // Call the old SysEnvirons() function. if (thisWorld.systemVersion >= 0x0605) haveIt = TRUE; // Check the System version for 6.0.5É else // or more recent haveIt = FALSE; return (haveIt); } //-------------------------------------------------------------- WhatsOurDepth // Function returns the current bit depth. For example, 4 = 16 colors, 8 = 256É // colors. Note, this function assumes System 6.0.5 or more recent and ColorÉ // Quickdraw capable. You should haved called the previous two functions beforeÉ // attempting this call. short WhatsOurDepth (void) { short thisDepth; char wasState; if (thisGDevice != 0L) // Make sure we have device handle. { wasState = HGetState((Handle)thisGDevice); // Remember the handle's state. HLock((Handle)thisGDevice); // Lock the device handle down. // Get it's depth (pixelSize). thisDepth = (**(**thisGDevice).gdPMap).pixelSize; HSetState((Handle)thisGDevice, wasState); // Restore handle's state. } else RedAlert("\pUnknown Error."); // Post generic error message. return (thisDepth); // Return screen depth. } //-------------------------------------------------------------- CanWeDisplay8Bit // Simple function that returns TRUE if the current device (monitor) isÉ // capable of displaying 256 colors (or grays). This function, the one above,É // and many following functions assume we have System 6.0.5 or more recent andÉ // are capable of Color QuickDraw. Boolean CanWeDisplay8Bit (GDHandle theDevice) { short canDepth; Boolean canDo; canDo = FALSE; // Assume intially that we can't display 8 bit. // Call HasDepth() to see if monitor supports 8 bit. canDepth = HasDepth(theDevice, 8, 1, 0); if (canDepth != 0) // If HasDepth() returned anything other than 0É canDo = TRUE; // we can indeed switch it to 8 bit. return (canDo); // Return the result. } //-------------------------------------------------------------- SwitchToDepth // This handy function forces the device (monitor) in question to switchÉ // to a specific depth. We'll call this function if we need to switch toÉ // 8-bit (256 colors). We should have called the above function first inÉ // order to be sure that we CAN in fact switch this monitor to 8-bit. void SwitchToDepth (short newDepth, Boolean doColor) { OSErr theErr; short colorFlag; char tagByte; if (doColor) // We can switch to either colors or grays. colorFlag = 1; else colorFlag = 0; if (thisGDevice != 0L) // Make sure we have a device. { // Remember handle's state (as usual). tagByte = HGetState((Handle)thisGDevice); HLock((Handle)thisGDevice); // Lock it. // Call SetDepth() (System 6.0.5 or more recent). theErr = SetDepth(thisGDevice, newDepth, 1, colorFlag); // Restore handle's state. HSetState((Handle)thisGDevice, tagByte); if (theErr != noErr) // If call failed, go to error dialog. RedAlert("\pUnknown Error."); } else // Call error dialog if no device handle. RedAlert("\pUnknown Error."); } //-------------------------------------------------------------- CheckEnvirons // This is the "wrapper" function that calls all the above functions. // After calling ToolBoxInit(), Glypha will call this function to seeÉ // if the current Mac we're running on is capable of running Glypha. void CheckEnvirons (void) { if (!DoWeHaveColor()) // We absolutely need Color QuickDraw. RedAlert("\pGlypha II only runs in 256 colors."); if (!DoWeHaveSystem605()) // We absolutely need System 6.0.5. or more recent. RedAlert("\pGlypha II requires System 6.0.5 or better to run."); // If we passed the above 2 tests, get aÉ FindOurDevice(); // handle to the main device (monitor). wasDepth = WhatsOurDepth(); // Find out our monitor's depth. if (wasDepth != 8) // If it's not 8-bit we'll need to switch depths. { // Test 1st to see if monitor capable of 8-bit. if (CanWeDisplay8Bit(thisGDevice)) SwitchToDepth(8, TRUE); // If so, switch to 256 colors. else // If not, we have to quit. RedAlert("\pGlypha II only runs in 256 colors."); } } //-------------------------------------------------------------- OpenMainWindow // This is a simple function that merely opens up a large black window andÉ // centers it in the monitor. It assumes a 'WIND' (window) resource, which if youÉ // look at the resource file in ResEdit, you'll find it also has a 'WCTB'É // (window color table) resource. The 'WCTB' resource specifies a contentÉ // color of black - thus this window comes up black. void OpenMainWindow (void) { SetRect(&mainWindowRect, 0, 0, 640, 460); // Our window size. mainWindow = GetNewCWindow(128, 0L, kPutInFront); // Load window from resource. // Make it the right size. SizeWindow((GrafPtr)mainWindow, mainWindowRect.right - mainWindowRect.left, mainWindowRect.bottom - mainWindowRect.top, FALSE); // Center the window. MoveWindow((GrafPtr)mainWindow, (qd.screenBits.bounds.right - 640) / 2, ((qd.screenBits.bounds.bottom - 480) / 2) + LMGetMBarHeight(), TRUE); ShowWindow((GrafPtr)mainWindow); // Now display it. SetPort((GrafPtr)mainWindow); // Make its port current. ClipRect(&mainWindowRect); // Set its clip region. CopyRgn(mainWindow->clipRgn, mainWindow->visRgn); // Set its visRgn. ForeColor(blackColor); // Set its pen color to black. BackColor(whiteColor); // Set background color white. } //-------------------------------------------------------------- InitMenubar // This function loads up the menus and displays the menu bar. void InitMenubar (void) { appleMenu = GetMenu(128); // Get the Apple menu (AboutÉ). if (appleMenu == 0L) // See that it loaded okay. RedAlert("\pCouldn't Load Menus Error"); AddResMenu(appleMenu, 'DRVR'); // Add Desk Accesories. InsertMenu(appleMenu, 0); // Add to menu bar. gameMenu = GetMenu(129); // Next load the Game menu. if (gameMenu == 0L) // Make sure it loaded as well. RedAlert("\pCouldn't Load Menus Error"); InsertMenu(gameMenu, 0); // And add it next to the menu bar. optionsMenu = GetMenu(130); // Finally load the Options menu. if (optionsMenu == 0L) RedAlert("\pCouldn't Load Menus Error"); InsertMenu(optionsMenu, 0); // And insert it. DrawMenuBar(); // Now display the menu bar. } //-------------------------------------------------------------- InitVariables // This function is called only once. It initializes all the global variablesÉ // used by Glypha. It may not in fact be necessary to initialize many of theseÉ // (your compiler should ensure that all globals are set to zero when your appÉ // launches), but it's a good idea to NEVER TRUST YOUR COMPILER. void InitVariables (void) { short i; quitting = FALSE; // I won't describe every one of these, many areÉ playing = FALSE; // self explanatory. Suffice it to say that firstÉ pausing = FALSE; // I'm initializing all the Boolean variables. canPlay = FALSE; whichList = TRUE; helpOpen = FALSE; scoresOpen = FALSE; openTheScores = FALSE; numLedges = 3; // Number of ledges in the "arena". beginOnLevel = 1; // First level to begin playing. levelOn = 0; // Level number player is on. livesLeft = kInitNumLives; // Number of player lives. theScore = 0L; // Player's score (notice it's a long!). wasTensOfThousands = 0L; // For tracking when player gets an extra life. GenerateLightning(320, 240); // Initialize a lightning bolt. backSrcRect = mainWindowRect; // Create background offscreen pixmap. ZeroRectCorner(&backSrcRect); backSrcMap = 0L; CreateOffScreenPixMap(&backSrcRect, &backSrcMap); LoadGraphic(kBackgroundPictID); workSrcRect = mainWindowRect; // Create "work" offscreen pixmap. ZeroRectCorner(&workSrcRect); workSrcMap = 0L; CreateOffScreenPixMap(&workSrcRect, &workSrcMap); SetRect(&obSrcRect, 0, 0, 20, 418); obeliskSrcMap = 0L; // Create pixmap to hold "obelisk" graphics. CreateOffScreenPixMap(&obSrcRect, &obeliskSrcMap); LoadGraphic(kObeliskPictID); SetRect(&obeliskRects[0], 0, 0, 20, 209); OffsetRect(&obeliskRects[0], 0, 0); SetRect(&obeliskRects[1], 0, 0, 20, 209); OffsetRect(&obeliskRects[1], 0, 209); SetRect(&obeliskRects[2], 0, 0, 20, 209); OffsetRect(&obeliskRects[2], 161, 250); SetRect(&obeliskRects[3], 0, 0, 20, 209); OffsetRect(&obeliskRects[3], 457, 250); SetRect(&playerSrcRect, 0, 0, 48, 436); playerSrcMap = 0L; // Create pixmap to hold "player" graphics. CreateOffScreenPixMap(&playerSrcRect, &playerSrcMap); LoadGraphic(kPlayerPictID); playerMaskMap = 0L; CreateOffScreenBitMap(&playerSrcRect, &playerMaskMap); LoadGraphic(kPlayerMaskID); SetRect(&enemyWalkSrcRect, 0, 0, 48, 576); enemyWalkSrcMap = 0L; // Create pixmap to hold "enemy" graphics. CreateOffScreenPixMap(&enemyWalkSrcRect, &enemyWalkSrcMap); LoadGraphic(kEnemyWalkPictID); enemyWalkMaskMap = 0L; CreateOffScreenBitMap(&enemyWalkSrcRect, &enemyWalkMaskMap); LoadGraphic(kEnemyWalkMaskID); SetRect(&enemyFlySrcRect, 0, 0, 64, 480); enemyFlySrcMap = 0L; CreateOffScreenPixMap(&enemyFlySrcRect, &enemyFlySrcMap); LoadGraphic(kEnemyFlyPictID); enemyFlyMaskMap = 0L; CreateOffScreenBitMap(&enemyFlySrcRect, &enemyFlyMaskMap); LoadGraphic(kEnemyFlyMaskID); for (i = 0; i < 12; i++) { // Set up enemy source rectangles. SetRect(&enemyRects[i], 0, 0, 48, 48); OffsetRect(&enemyRects[i], 0, 48 * i); } for (i = 0; i < 12; i++) { SetRect(&enemyRects[i + 12], 0, 0, 64, 40); OffsetRect(&enemyRects[i + 12], 0, 40 * i); } SetRect(&enemyInitRects[0], 0, 0, 48, 1); OffsetRect(&enemyInitRects[0], 72, 284); SetRect(&enemyInitRects[1], 0, 0, 48, 1); OffsetRect(&enemyInitRects[1], 520, 284); SetRect(&enemyInitRects[2], 0, 0, 48, 1); OffsetRect(&enemyInitRects[2], 72, 105); SetRect(&enemyInitRects[3], 0, 0, 48, 1); OffsetRect(&enemyInitRects[3], 520, 105); SetRect(&enemyInitRects[4], 0, 0, 48, 1); OffsetRect(&enemyInitRects[4], 296, 190); SetRect(&eggSrcRect, 0, 0, 24, 24); eggSrcMap = 0L; // Create pixmap to hold "egg" graphics. CreateOffScreenPixMap(&eggSrcRect, &eggSrcMap); LoadGraphic(kEggPictID); eggMaskMap = 0L; CreateOffScreenBitMap(&eggSrcRect, &eggMaskMap); LoadGraphic(kEggMaskID); SetRect(&eyeSrcRect, 0, 0, 48, 124); eyeSrcMap = 0L; // Create pixmap to hold "eye" graphics. CreateOffScreenPixMap(&eyeSrcRect, &eyeSrcMap); LoadGraphic(kEyePictID); eyeMaskMap = 0L; CreateOffScreenBitMap(&eyeSrcRect, &eyeMaskMap); LoadGraphic(kEyeMaskID); for (i = 0; i < 4; i++) { SetRect(&eyeRects[i], 0, 0, 48, 31); OffsetRect(&eyeRects[i], 0, i * 31); } SetRect(&handSrcRect, 0, 0, 56, 114); handSrcMap = 0L; // Create pixmap to hold "mummy hand" graphics. CreateOffScreenPixMap(&handSrcRect, &handSrcMap); LoadGraphic(kHandPictID); handMaskMap = 0L; CreateOffScreenBitMap(&handSrcRect, &handMaskMap); LoadGraphic(kHandMaskID); SetRect(&handRects[0], 0, 0, 56, 57); OffsetRect(&handRects[0], 0, 0); SetRect(&handRects[1], 0, 0, 56, 57); OffsetRect(&handRects[1], 0, 57); SetRect(&grabZone, 0, 0, 96, 108); OffsetRect(&grabZone, 48, 352); SetRect(&idleSrcRect, 0, 0, 48, 48); idleSrcMap = 0L; // Create pixmap to hold "shielded player". CreateOffScreenPixMap(&idleSrcRect, &idleSrcMap); LoadGraphic(kIdlePictID); SetRect(&flameSrcRect, 0, 0, 16, 64); flameSrcMap = 0L; // Create pixmap to hold "torch" graphics. CreateOffScreenPixMap(&flameSrcRect, &flameSrcMap); LoadGraphic(kFlamePictID); SetRect(&flameDestRects[0], 0, 0, 16, 16); OffsetRect(&flameDestRects[0], 87, 325); SetRect(&flameDestRects[1], 0, 0, 16, 16); OffsetRect(&flameDestRects[1], 535, 325); for (i = 0; i < 4; i++) { SetRect(&flameRects[i], 0, 0, 16, 16); OffsetRect(&flameRects[i], 0, i * 16); } SetRect(&numberSrcRect, 0, 0, 8, 121); numberSrcMap = 0L; // Create pixmap to hold "score numbers". CreateOffScreenPixMap(&numberSrcRect, &numberSrcMap); LoadGraphic(kNumberPictID); for (i = 0; i < 11; i++) { SetRect(&numbersSrc[i], 0, 0, 8, 11); OffsetRect(&numbersSrc[i], 0, 11 * i); } SetRect(&numbersDest[0], 0, 0, 8, 11); // # of lives digit 1 OffsetRect(&numbersDest[0], 229, 443); SetRect(&numbersDest[1], 0, 0, 8, 11); // # of lives digit 2 OffsetRect(&numbersDest[1], 237, 443); SetRect(&numbersDest[2], 0, 0, 8, 11); // score digit 1 OffsetRect(&numbersDest[2], 293, 443); SetRect(&numbersDest[3], 0, 0, 8, 11); // score digit 2 OffsetRect(&numbersDest[3], 301, 443); SetRect(&numbersDest[4], 0, 0, 8, 11); // score digit 3 OffsetRect(&numbersDest[4], 309, 443); SetRect(&numbersDest[5], 0, 0, 8, 11); // score digit 4 OffsetRect(&numbersDest[5], 317, 443); SetRect(&numbersDest[6], 0, 0, 8, 11); // score digit 5 OffsetRect(&numbersDest[6], 325, 443); SetRect(&numbersDest[7], 0, 0, 8, 11); // score digit 6 OffsetRect(&numbersDest[7], 333, 443); SetRect(&numbersDest[8], 0, 0, 8, 11); // # of level digit 1 OffsetRect(&numbersDest[8], 381, 443); SetRect(&numbersDest[9], 0, 0, 8, 11); // # of level digit 2 OffsetRect(&numbersDest[9], 389, 443); SetRect(&numbersDest[10], 0, 0, 8, 11); // # of level digit 3 OffsetRect(&numbersDest[10], 397, 443); SetRect(&playerRects[0], 0, 0, 48, 37); OffsetRect(&playerRects[0], 0, 0); SetRect(&playerRects[1], 0, 0, 48, 37); OffsetRect(&playerRects[1], 0, 37); SetRect(&playerRects[2], 0, 0, 48, 37); OffsetRect(&playerRects[2], 0, 74); SetRect(&playerRects[3], 0, 0, 48, 37); OffsetRect(&playerRects[3], 0, 111); SetRect(&playerRects[4], 0, 0, 48, 48); OffsetRect(&playerRects[4], 0, 148); SetRect(&playerRects[5], 0, 0, 48, 48); OffsetRect(&playerRects[5], 0, 196); SetRect(&playerRects[6], 0, 0, 48, 48); OffsetRect(&playerRects[6], 0, 244); SetRect(&playerRects[7], 0, 0, 48, 48); OffsetRect(&playerRects[7], 0, 292); SetRect(&playerRects[8], 0, 0, 48, 37); // falling bones rt. OffsetRect(&playerRects[8], 0, 340); SetRect(&playerRects[9], 0, 0, 48, 37); // falling bones lf. OffsetRect(&playerRects[9], 0, 377); SetRect(&playerRects[10], 0, 0, 48, 22); // pile of bones OffsetRect(&playerRects[10], 0, 414); MoveTo(0, 0); // Generate clipping region that excludes the obelisks. playRgn = NewRgn(); // Create empty new region. OpenRgn(); // Open region for definition. LineTo(0, 450); // Walk around the vertices of our region. LineTo(161, 450); LineTo(161, 269); LineTo(172, 250); LineTo(182, 269); LineTo(182, 450); LineTo(457, 450); LineTo(457, 269); LineTo(468, 250); LineTo(478, 269); LineTo(478, 450); LineTo(640, 450); LineTo(640, 0); LineTo(0, 0); CloseRgn(playRgn); // Stop region definition. MoveHHi((Handle)playRgn); HLock((Handle)playRgn); SetRect(&platformSrcRect, 0, 0, 191, 192); platformSrcMap = 0L; // Create pixmap to hold "platform" graphic. CreateOffScreenPixMap(&platformSrcRect, &platformSrcMap); LoadGraphic(kPlatformPictID); for (i = 0; i < 6; i++) { SetRect(&platformCopyRects[i], 0, 0, 191, 32); OffsetRect(&platformCopyRects[i], 0, 32 * i); } SetRect(&platformCopyRects[6], 233, 190, 424, 222); SetRect(&platformCopyRects[7], 0, 105, 191, 137); SetRect(&platformCopyRects[8], 449, 105, 640, 137); // These are the platforms. See diagram for numbering. SetRect(&platformRects[0], 206, 424, 433, 438); //_______________ SetRect(&platformRects[1], -256, 284, 149, 298); // SetRect(&platformRects[2], 490, 284, 896, 298); //--3-- --4-- SetRect(&platformRects[3], -256, 105, 149, 119); // --5-- SetRect(&platformRects[4], 490, 105, 896, 119); //--1-- --2-- SetRect(&platformRects[5], 233, 190, 407, 204); //_____--0--_____ for (i = 0; i < 6; i++) { // "Hot rects" to sense if player landing on platform. touchDownRects[i] = platformRects[i]; touchDownRects[i].left += 23; touchDownRects[i].right -= 23; touchDownRects[i].bottom = touchDownRects[i].top; touchDownRects[i].top = touchDownRects[i].bottom - 11; } SetRect(&helpSrcRect, 0, 0, 231, 398); helpSrcMap = 0L; // Create pixmap to hold "help screen" graphic. CreateOffScreenPixMap(&helpSrcRect, &helpSrcMap); LoadGraphic(kHelpPictID); SetPort((GrafPtr)mainWindow); } //-------------------------------------------------------------- ShutItDown // This function is called when the player has chosen to quit Glypha. // It "should" probably do more, but in fact all it does is to restoreÉ // their Mac's monitor back to the depth it was before they launchedÉ // Glypha (recall that we may have changed it to 8-bit). void ShutItDown (void) { if (wasDepth != WhatsOurDepth()) // Need only switch if wasn't 8-bit. SwitchToDepth(wasDepth, TRUE); // Switch back to out "old" depth. } \ No newline at end of file diff --git a/Source/Sound.c b/Source/Sound.c new file mode 100755 index 0000000..6bc8543 --- /dev/null +++ b/Source/Sound.c @@ -0,0 +1 @@ + //============================================================================ //---------------------------------------------------------------------------- // Sound.c //---------------------------------------------------------------------------- //============================================================================ // This file handles all sound routines. It handles 2 concurrent soundÉ // channels allowing 2 sounds to be played simultaneously. It also handlesÉ // a system of priorites whereby you can ensure that "important" sounds don'tÉ // get cut off by "lesser" sounds. In that there are 2 channels however,É // "lesser" sounds are not discounted outright - both channels are consideredÉ // to determine if one of the channels is not playing at all (priority = 0) orÉ // playing a sound of an even lesser priority. Make sense? #include #include "Externs.h" #define kMaxSounds 17 // Number of sounds to load. #define kBaseBufferSoundID 1000 // ID of first sound (assumed sequential). #define kSoundDone 913 // Just a number I chose. #define kSoundDone2 749 // Just a number I chose. void PlaySound1 (short, short); void PlaySound2 (short, short); pascal void ExternalCallBack (SndChannelPtr, SndCommand *); pascal void ExternalCallBack2 (SndChannelPtr, SndCommand *); void LoadAllSounds (void); OSErr LoadBufferSounds (void); OSErr DumpBufferSounds (void); OSErr OpenSoundChannel (void); OSErr CloseSoundChannel (void); SndCallBackUPP externalCallBackUPP, externalCallBackUPP2; SndChannelPtr externalChannel, externalChannel2; Ptr theSoundData[kMaxSounds]; short externalPriority, externalPriority2; Boolean channelOpen, soundOn; //============================================================== Functions //-------------------------------------------------------------- PlaySound1 // This function takes a sound ID and a priority, and forces that sound to // play through channel 1 - and saves the priority globally. As well, a // callback command is queues up in channel 1. void PlaySound1 (short soundID, short priority) { SndCommand theCommand; OSErr theErr; theCommand.cmd = flushCmd; // Send 1st a flushCmd to clear the sound queue. theCommand.param1 = 0; theCommand.param2 = 0L; theErr = SndDoImmediate(externalChannel, &theCommand); theCommand.cmd = quietCmd; // Send quietCmd to stop any current sound. theCommand.param1 = 0; theCommand.param2 = 0L; theErr = SndDoImmediate(externalChannel, &theCommand); externalPriority = priority; // Copy priority to global variable. theCommand.cmd = bufferCmd; // Then, send a bufferCmd to channel 1. theCommand.param1 = 0; // The sound played will be soundID. theCommand.param2 = (long)(theSoundData[soundID]); theErr = SndDoImmediate(externalChannel, &theCommand); theCommand.cmd = callBackCmd; // Lastly, queue up a callBackCmd to notify usÉ theCommand.param1 = kSoundDone; // when the sound has finished playing. theCommand.param2 = SetCurrentA5(); theErr = SndDoCommand(externalChannel, &theCommand, TRUE); } //-------------------------------------------------------------- PlaySound2 // This function is identical to the above function except that it handlesÉ // playing sounds through channel 2. void PlaySound2 (short soundID, short priority) { SndCommand theCommand; OSErr theErr; theCommand.cmd = flushCmd; // Send 1st a flushCmd to clear the sound queue. theCommand.param1 = 0; theCommand.param2 = 0L; theErr = SndDoImmediate(externalChannel2, &theCommand); theCommand.cmd = quietCmd; // Send quietCmd to stop any current sound. theCommand.param1 = 0; theCommand.param2 = 0L; theErr = SndDoImmediate(externalChannel2, &theCommand); externalPriority2 = priority; // Copy priority to global variable. theCommand.cmd = bufferCmd; // Then, send a bufferCmd to channel 1. theCommand.param1 = 0; // The sound played will be soundID. theCommand.param2 = (long)(theSoundData[soundID]); theErr = SndDoImmediate(externalChannel2, &theCommand); theCommand.cmd = callBackCmd; // Lastly, queue up a callBackCmd to notify usÉ theCommand.param1 = kSoundDone2; // when the sound has finished playing. theCommand.param2 = SetCurrentA5(); theErr = SndDoCommand(externalChannel2, &theCommand, TRUE); } //-------------------------------------------------------- PlayExternalSound // This function is probably poorly named for this application. I lifted thisÉ // whole library from one of my games and chopped it down for purposes of Glypha. // The original game treated "external" and "cockpit" sounds as seperate channelsÉ // (such that cockpit sounds could only "override" other cockpit sounds andÉ // external sounds could only override other external sounds. // In any event, this is the primary function called from throughout Glypha. // This function is called with a sound ID and a priority (just some number) andÉ // the function then determines if one of the two sound channels is free to playÉ // the sound. It determines this by way of priorities. If a sound channel isÉ // idle and playing no sound, its channel priority is 0. Since the priority ofÉ // the sound you want to play is assumed to be greater than 0, it will, withoutÉ // a doubt, be allowed to play on an idle channel. If however there is alreadyÉ // a sound playing (the channel's priority is not equal to 0), the sound with theÉ // largest priority wins. Mind you though that there are two channels to chooseÉ // between. Therefore, the function compares the priority passed in with theÉ // sound channel with the lowest priority. void PlayExternalSound (short soundID, short priority) { // A little error-checking. if ((soundID >= 0) && (soundID < kMaxSounds)) { if (soundOn) // More error-checking. { // Find channel with lowest priority. if (externalPriority < externalPriority2) { // Compare priority with that of channel 1. if (priority >= externalPriority) PlaySound1(soundID, priority); } else { // Compare priority with that of channel 2. if (priority >= externalPriority2) PlaySound2(soundID, priority); } } } } //-------------------------------------------------------- ExternalCallBack // Callback routine. If this looks ugly, blame Apple's Universal Headers. // The callback routine is called after a sound finishes playing. TheÉ // callback routine is extremely useful in that it enables us to know whenÉ // to set the sound channels priority back to 0 (meaning no sound playing). // Keep in mind (by the way) that this funciton is called at interrupt timeÉ // and thus may not cause memory to be moved. Also, note that also becauseÉ // of the interupt situation, we need to handle setting A5 to point to ourÉ // app's A5 and then set it back again. RoutineDescriptor ExternalCallBackRD = BUILD_ROUTINE_DESCRIPTOR(uppSndCallBackProcInfo, ExternalCallBack); pascal void ExternalCallBack (SndChannelPtr theChannel, SndCommand *theCommand) { long thisA5, gameA5; if (theCommand->param1 == kSoundDone) // See if it's OUR callback. { gameA5 = theCommand->param2; // Extract our A5 from sound command. thisA5 = SetA5(gameA5); // Point A5 to our app (save off current A5). externalPriority = 0; // Set global to reflect no sound playing. thisA5 = SetA5(thisA5); // Restire A5. } } //-------------------------------------------------------- ExternalCallBack2 // This function is identical to the above function but handles sound channel 2. RoutineDescriptor ExternalCallBackRD2 = BUILD_ROUTINE_DESCRIPTOR(uppSndCallBackProcInfo, ExternalCallBack2); pascal void ExternalCallBack2 (SndChannelPtr theChannel, SndCommand *theCommand) { long thisA5, gameA5; if (theCommand->param1 == kSoundDone2) // See if it's OUR callback. { gameA5 = theCommand->param2; // Extract our A5 from sound command. thisA5 = SetA5(gameA5); // Point A5 to our app (save off current A5). externalPriority2 = 0; // Set global to reflect no sound playing. thisA5 = SetA5(thisA5); // Restire A5. } } //-------------------------------------------------------- LoadBufferSounds // This function loads up all the sounds we'll need in the game and thenÉ // strips off their header so that we can pass them as buffer commands. // Sounds are stored in our resource fork as 'snd ' resources. There is aÉ // 20 byte header that we need to remove in order to use bufferCmd's. // This function is called only once, when the game loads up. OSErr LoadBufferSounds (void) { Handle theSound; long soundDataSize; OSErr theErr; short i; theErr = noErr; // Assume no errors. for (i = 0; i < kMaxSounds; i++) // Walk through all sounds. { // Load 'snd ' from resource. theSound = GetResource('snd ', i + kBaseBufferSoundID); if (theSound == 0L) // Make sure it loaded okay. return (ResError()); // Return reason it failed (if it did). HLock(theSound); // If we got this far, lock sound down. // Calculate size of sound minus header. soundDataSize = GetHandleSize(theSound) - 20L; HUnlock(theSound); // Okay, unlock. // Create pointer the size calculated above. theSoundData[i] = NewPtr(soundDataSize); if (theSoundData[i] == 0L) // See if we created it okay. return (MemError()); // If failed, return the reason why. HLock(theSound); // Okay, lock the sound handle again. // Copy sound data (minus header) to our pointer. BlockMove((Ptr)(*theSound + 20L), theSoundData[i], soundDataSize); HUnlock(theSound); // Unlock sound handle again. ReleaseResource(theSound); // And toss it from memory. } return (theErr); } //-------------------------------------------------------- DumpBufferSounds // This function is called when Glypha exits (quits). All those nasty pointersÉ // we created in the above function are reclaimed. OSErr DumpBufferSounds (void) { OSErr theErr; short i; theErr = noErr; for (i = 0; i < kMaxSounds; i++) // Go through all sound pointers. { if (theSoundData[i] != 0L) // Make sure it exists. DisposPtr(theSoundData[i]); // Dispose of it. theSoundData[i] = 0L; // Make sure it reflects its "nonexistence". } return (theErr); } //-------------------------------------------------------- OpenSoundChannel // This should perhaps be called OpenSoundChannels() since it opens two. // It is called once (at initialization) to set up the two sound channelsÉ // we will use throughout Glypha. For purposes of speed, 8-bit sound channelsÉ // with no interpolation and monophonic are opened. They'll use the sampledÉ // synthesizer (digitized sound) and be assigned their respective callbackÉ // routines. OSErr OpenSoundChannel (void) { OSErr theErr; #if USESROUTINEDESCRIPTORS externalCallBackUPP = &ExternalCallBackRD; // Handle Universal Header ugliness. externalCallBackUPP2 = &ExternalCallBackRD2; #else externalCallBackUPP = (SndCallBackUPP) &ExternalCallBack; externalCallBackUPP2 = (SndCallBackUPP) &ExternalCallBack2; #endif theErr = noErr; // Assume no errors. if (channelOpen) // Error checking. return (theErr); externalChannel = 0L; theErr = SndNewChannel(&externalChannel, // Open channel 1. sampledSynth, initNoInterp + initMono, (SndCallBackUPP)externalCallBackUPP); if (theErr == noErr) // See if it worked. channelOpen = TRUE; externalChannel2 = 0L; theErr = SndNewChannel(&externalChannel2, // Open channel 2. sampledSynth, initNoInterp + initMono, (SndCallBackUPP)externalCallBackUPP2); if (theErr == noErr) // See if it worked. channelOpen = TRUE; return (theErr); } //-------------------------------------------------------- CloseSoundChannel // This function is called only upon quitting Glypha. Both sound channelsÉ // we created above are closed down. OSErr CloseSoundChannel (void) { OSErr theErr; theErr = noErr; if (!channelOpen) // Error checking. return (theErr); if (externalChannel != 0L) // Dispose of channel 1 (if open). theErr = SndDisposeChannel(externalChannel, TRUE); externalChannel = 0L; // Flag it closed. if (externalChannel2 != 0L) // Dispose of channel 2 (if open). theErr = SndDisposeChannel(externalChannel2, TRUE); externalChannel2 = 0L; // Flag it closed. if (theErr == noErr) channelOpen = FALSE; return (theErr); } //-------------------------------------------------------- InitSound // All the above initialization routines are handled by this one function. // This single function is the only one that needs to be called - it handlesÉ // calling the functions that load the sounds and create the sound channels. // It is called from main() when Glypha is loading up and going through itsÉ // initialization phase. void InitSound (void) { OSErr theErr; soundOn = TRUE; // Note that initialization of sounds has occurredÉ // (or rather is just about to this instant!). externalChannel = 0L; // Flag channels as nonexistant. externalChannel2 = 0L; externalPriority = 0; // Set priorities to 0 (no sound playing). externalPriority2 = 0; // Load up all sounds (see above function). theErr = LoadBufferSounds(); if (theErr != noErr) // If it fails, we'll quit Glypha. RedAlert("\pFailed Loading Sounds"); // Open up the two sound channels. theErr = OpenSoundChannel(); if (theErr != noErr) // If that fails we'll quit Glypha as well. RedAlert("\pFailed To Open Sound Channels"); } //-------------------------------------------------------- KillSound // Complementary to the above function, this one is called only when GlyphaÉ // quits and it handles all the "shut-down" routines. It also is called fromÉ // main(), but it is called last - just as Glypha is quitting. void KillSound (void) { OSErr theErr; theErr = DumpBufferSounds(); // Kill all sound pointers. theErr = CloseSoundChannel(); // Close down the sound channels. } \ No newline at end of file diff --git a/Source/Utilities.c b/Source/Utilities.c new file mode 100755 index 0000000..67d3565 --- /dev/null +++ b/Source/Utilities.c @@ -0,0 +1 @@ + //============================================================================ //---------------------------------------------------------------------------- // Utilities.c //---------------------------------------------------------------------------- //============================================================================ // These functions are sort of universal utility functions. They aren't specificÉ // to Glypha per se. I use these (and others) in many, many games. Many of themÉ // as well are useful for any app you might write for the Mac. #include "Externs.h" #define kActive 0 #define kInactive 255 GDHandle thisGDevice; long tickNext; //============================================================== Functions //-------------------------------------------------------------- RandomInt // Takes a short (range) and returns a random number from zero to range - 1. short RandomInt (short range) { register long rawResult; rawResult = Random(); if (rawResult < 0L) rawResult *= -1L; rawResult = (rawResult * (long)range) / 32768L; return ((short)rawResult); } //-------------------------------------------------------------- RedAlert // Generic error function. This is called when there is no hope of recovering // from the error. A simple alert is brought up and the text passed in (theStr) // is displayed. When the user clicks the Okay button, we quit to the Finder. void RedAlert (StringPtr theStr) { #define kRedAlertID 128 short whoCares; ParamText(theStr, "\p", "\p", "\p"); // Replace ^0 in alert with error mssg. whoCares = Alert(kRedAlertID, 0L); // Bring up alert. ExitToShell(); // Quit to Finder. } //-------------------------------------------------------------- FindOurDevice // Get a handle to the MainDevice (monitor with the Menubar). void FindOurDevice (void) { thisGDevice = GetMainDevice(); if (thisGDevice == 0L) // If a nil handle is returned... RedAlert("\pCouldn't Find Our Device"); // call our universal error alert. } //-------------------------------------------------------------- LoadGraphic // Handy function that loads a PICT graphic, get's its bounds and draws it. // The port drawn to is assumed the current port. No scaling is done. void LoadGraphic (short resID) { Rect bounds; PicHandle thePicture; thePicture = GetPicture(resID); // Load graphic from resource fork. if (thePicture == 0L) // Check to see if nil (did it load?) RedAlert("\pA Graphic Couldn't Be Loaded"); HLock((Handle)thePicture); // If we made it this far, lock handle. bounds = (*thePicture)->picFrame; // Get a copy of the picture's bounds. HUnlock((Handle)thePicture); // We can unlock the picture now. OffsetRect(&bounds, -bounds.left, -bounds.top); // Offset bounds rect to (0, 0). DrawPicture(thePicture, &bounds); // Draw picture to current port. ReleaseResource((Handle)thePicture); // Dispose of picture from heap. } //-------------------------------------------------------------- CreateOffScreenPixMap // Handles the creation of an offscreen pixmap. Depth is assumed to be that of theÉ // current gDevice. If the allocation fails (low memory, etc.) we quit to Finder. void CreateOffScreenPixMap (Rect *theRect, CGrafPtr *offScreen) { CTabHandle thisColorTable; GDHandle oldDevice; CGrafPtr newCGrafPtr; Ptr theseBits; long sizeOfOff, offRowBytes; OSErr theErr; short thisDepth; oldDevice = GetGDevice(); SetGDevice(thisGDevice); newCGrafPtr = 0L; newCGrafPtr = (CGrafPtr)NewPtrClear(sizeof(CGrafPort)); if (newCGrafPtr != 0L) { OpenCPort(newCGrafPtr); thisDepth = (**(*newCGrafPtr).portPixMap).pixelSize; offRowBytes = ((((long)thisDepth * (long)(theRect->right - theRect->left)) + 15L) >> 4L) << 1L; sizeOfOff = (long)(theRect->bottom - theRect->top) * offRowBytes; OffsetRect(theRect, -theRect->left, -theRect->top); theseBits = NewPtr(sizeOfOff); if (theseBits != 0L) { (**(*newCGrafPtr).portPixMap).baseAddr = theseBits; (**(*newCGrafPtr).portPixMap).rowBytes = (short)offRowBytes + 0x8000; (**(*newCGrafPtr).portPixMap).bounds = *theRect; thisColorTable = (**(**thisGDevice).gdPMap).pmTable; theErr = HandToHand((Handle *)&thisColorTable); (**(*newCGrafPtr).portPixMap).pmTable = thisColorTable; ClipRect(theRect); RectRgn(newCGrafPtr->visRgn, theRect); ForeColor(blackColor); BackColor(whiteColor); EraseRect(theRect); } else { CloseCPort(newCGrafPtr); DisposePtr((Ptr)newCGrafPtr); newCGrafPtr = 0L; RedAlert("\pCouldn't Allocate Enough Memory"); } } else RedAlert("\pCouldn't Allocate Enough Memory"); *offScreen = newCGrafPtr; SetGDevice(oldDevice); } //-------------------------------------------------------------- CreateOffScreenBitMap // Creates an offscreen bitmap. Depth is of course 1 (b & w). If this functionÉ // fails to create the bitmap, we post an alert and quit to the Finder. void CreateOffScreenBitMap (Rect *theRect, GrafPtr *offScreen) { GrafPtr theBWPort; BitMap theBitMap; long theRowBytes; theBWPort = (GrafPtr)(NewPtr(sizeof(GrafPort))); OpenPort(theBWPort); theRowBytes = (long)((theRect->right - theRect->left + 15L) / 16L) * 2L; theBitMap.rowBytes = (short)theRowBytes; theBitMap.baseAddr = NewPtr((long)theBitMap.rowBytes * (theRect->bottom - theRect->top)); if (theBitMap.baseAddr == 0L) RedAlert("\pCouldn't Create Bitmaps"); theBitMap.bounds = *theRect; if (MemError() != noErr) RedAlert("\pCouldn't Create Bitmaps"); SetPortBits(&theBitMap); ClipRect(theRect); RectRgn(theBWPort->visRgn, theRect); EraseRect(theRect); *offScreen = theBWPort; } //-------------------------------------------------------------- ZeroRectCorner // Offset rect to (0, 0). This means the upper left corner of the rect is // moved to the origin - to (0, 0) - to the upperleft corner of the port. void ZeroRectCorner (Rect *theRect) { theRect->right -= theRect->left; // Move right edge by amount of left. theRect->bottom -= theRect->top; // Move bottom edge by amount of top. theRect->left = 0; // Can now set left to zero. theRect->top = 0; // Can set top edge to zero as well. } //-------------------------------------------------------------- FlashShort // This is a simple debugging function that will display the short passed to itÉ // in the upper left corner of the screen. It's a handy way to watch the valueÉ // of a variable while the program is running. void FlashShort (short theValue) { GrafPtr wasPort, tempPort; Str255 tempStr; Rect tempRect; GetPort(&wasPort); // Remember old grafPort. tempPort = (GrafPtr)NewPtrClear(sizeof(GrafPort)); OpenPort(tempPort); // Create a new empty port. NumToString((long)theValue, tempStr); // Convert value passed in to a string. MoveTo(20, 40); // Move the pen to the upperleft corner. SetRect(&tempRect, 18, 20, 122, 42); // Create a rect up there as well. EraseRect(&tempRect); // Erase the rect (to make a white hole). DrawString(tempStr); // And draw our text into that hole. ClosePort(tempPort); // Get rid of out temp port. SetPort((GrafPtr)wasPort); // And set port back to the old one. } //-------------------------------------------------------------- LogNextTick // Simple function to set a global (tickNext) to the current TickCount() plusÉ // some offset. We'll then wait for TickCount() to exceed that global. We useÉ // this function and the function below to regulate animation speeds (rememberÉ // your game may be run on a slow Mac or a fast one - we need a way to keep theÉ // motion consistent. I love when the comments are longer than the function. // (Not really.) void LogNextTick (long howMany) { tickNext = TickCount() + howMany; // Get machine's TickCount() and add to it. } //-------------------------------------------------------------- WaitForNextTick // This is the companion function to the above function (LogNextTick()). // We do nothing but loop until TickCount() catches up with (or passes) ourÉ // global variable tickNext. void WaitForNextTick (void) { do { } while (TickCount() < tickNext); // Loop until TickCount() catches up. } //-------------------------------------------------------------- TrapExists // A nice "test function" that test for the existence of some ToolBox trap. // Returns TRUE if the function exists, FALSE if it doesn't. Boolean TrapExists (short trapNumber) { #define kUnimpTrap 0x9F // Test trap number against unimplemented trap number. return ((NGetTrapAddress(trapNumber, ToolTrap) != NGetTrapAddress(kUnimpTrap, ToolTrap))); } //-------------------------------------------------------------- DoWeHaveGestalt // This function specifically tests for the availablity of the Gestalt() function. // It returns TRUE if Gestalt() exists, FALSE if it doesn't. Boolean DoWeHaveGestalt (void) { #define kGestaltTrap 0xAD // Call above function (TrapExists()) with the Gestalt() trap number. return (TrapExists(kGestaltTrap)); } //-------------------------------------------------------------- CenterAlert // Handy function to center any alert within the main monitor. void CenterAlert (short alertID) { AlertTHndl alertHandle; Rect theScreen, alertRect; short horiOff, vertOff; Byte wasState; theScreen = qd.screenBits.bounds; // Get main monitor's bounds. theScreen.top += LMGetMBarHeight(); // Account for menubar height. // Get handle to alert resource. alertHandle = (AlertTHndl)GetResource('ALRT', alertID); if (alertHandle != 0L) // Make sure we got it! { // Remember its "state" (locked, etc.) wasState = HGetState((Handle)alertHandle); HLock((Handle)alertHandle); // We'll lock it. // Get a copy of it's bounds and zero. alertRect = (**alertHandle).boundsRect; OffsetRect(&alertRect, -alertRect.left, -alertRect.top); // Calculate offsets for centering bounds. horiOff = ((theScreen.right - theScreen.left) - alertRect.right) / 2; vertOff = ((theScreen.bottom - theScreen.top) - alertRect.bottom) / 3; // And offset the bounds copy. OffsetRect(&alertRect, horiOff, vertOff + LMGetMBarHeight()); // Set alerts bounds to our centered rect. (**alertHandle).boundsRect = alertRect; HSetState((Handle)alertHandle, wasState); } } //-------------------------------------------------------------- RectWide // Handy function for returning the absolute width of a rectangle. short RectWide (Rect *theRect) { return (theRect->right - theRect->left); } //-------------------------------------------------------------- RectTall // Handy function for returning the absolute height of a rectangle. short RectTall (Rect *theRect) { return (theRect->bottom - theRect->top); } //-------------------------------------------------------------- CenterRectInRect // Nice utility function that takes two rectangles and centers the firstÉ // rectangle within the second. void CenterRectInRect (Rect *rectA, Rect *rectB) { short widthA, tallA; widthA = RectWide(rectA); // Get width of 1st rect. tallA = RectTall(rectA); // Get height of 1st rect. // Do the math (center horizontally). rectA->left = rectB->left + (RectWide(rectB) - widthA) / 2; rectA->right = rectA->left + widthA; // Do the math (center vertically). rectA->top = rectB->top + (RectTall(rectB) - tallA) / 2; rectA->bottom = rectA->top + tallA; } //-------------------------------------------------------------- PasStringCopy // This is a nice function that helps to free you from dealing with C strings. // It takes one Pascal-style string and copies it to a second. void PasStringCopy (StringPtr p1, StringPtr p2) { register short stringLength; stringLength = *p2++ = *p1++; // Get 1st string's length. while (--stringLength >= 0) // Loop through each character in 1st string. *p2++ = *p1++; // And copy to 2nd string. } //-------------------------------------------------------------- CenterDialog // Like CenterAlert(), this function centers a Dialog on the main monitor. void CenterDialog (short dialogID) { DialogTHndl dlogHandle; Rect theScreen, dlogBounds; short hPos, vPos; Byte wasState; theScreen = qd.screenBits.bounds; // Get main monitor's bounds. theScreen.top += LMGetMBarHeight(); // Add menuBar's height. // Load up dialog from resource. dlogHandle = (DialogTHndl)GetResource('DLOG', dialogID); if (dlogHandle != 0L) // If it loaded....! { // Remember handle state. wasState = HGetState((Handle)dlogHandle); HLock((Handle)dlogHandle); // We're going to lock it. // Get a copy of the dialog's bounds. dlogBounds = (**dlogHandle).boundsRect; OffsetRect(&dlogBounds, -dlogBounds.left, -dlogBounds.top); // Calculate how much to offset. hPos = ((theScreen.right - theScreen.left) - dlogBounds.right) / 2; vPos = ((theScreen.bottom - theScreen.top) - dlogBounds.bottom) / 3; // Offset ourt copy of the bounds. OffsetRect(&dlogBounds, hPos, vPos + LMGetMBarHeight()); // Set dlg's bounds to centered rect. (**dlogHandle).boundsRect = dlogBounds; HSetState((Handle)dlogHandle, wasState);// Restore handle's state. } } //-------------------------------------------------------------- DrawDefaultButton // A nice dialog function. This draws the bold default outline aroundÉ // item #1 in the dialog passed in. void DrawDefaultButton (DialogPtr theDialog) { Rect itemRect; Handle itemHandle; short itemType; // Get at the item's bounds. GetDItem(theDialog, 1, &itemType, &itemHandle, &itemRect); InsetRect(&itemRect, -4, -4); // Inset (outset?) bounds by -4 pixels. PenSize(3, 3); // Set the pen 3 pixels thick. FrameRoundRect(&itemRect, 16, 16); // Draw the button outline. PenNormal(); // And restore pen to 1 pixel thick. } //-------------------------------------------------------------- PasStringCopyNum // Another function to keep you from using C strings. This one copies only aÉ // certain number of characters from one Pascal-style string to a second. void PasStringCopyNum (StringPtr p1, StringPtr p2, short charsToCopy) { short i; if (charsToCopy > *p1) // If trying to copy more chars than there areÉ charsToCopy = *p1; // Reduce the number of chars to copy to this size *p2 = charsToCopy; // Set 2nd string's length to charsToCopy. *p2++; // Point to first character in 2nd string. *p1++; // Point to first character in 1st string. for (i = 0; i < charsToCopy; i++) *p2++ = *p1++; // Copy the specified number of chars over. } //-------------------------------------------------------------- GetDialogString // Handy dialog function that returns a dialog item string. This will beÉ // especially handy for getting the high score name the player enters. void GetDialogString (DialogPtr theDialog, short item, StringPtr theString) { Rect itemRect; Handle itemHandle; short itemType; // Get handle to dialog item. GetDItem(theDialog, item, &itemType, &itemHandle, &itemRect); GetIText(itemHandle, theString); // Extract text from item handle. } //-------------------------------------------------------------- SetDialogString // Like the above function, but this one sets a dialog items string to whateverÉ // you pass in. We'll use this to set a default high score name. void SetDialogString (DialogPtr theDialog, short item, StringPtr theString) { Rect itemRect; Handle itemHandle; short itemType; // Get handle to dialog item. GetDItem(theDialog, item, &itemType, &itemHandle, &itemRect); SetIText(itemHandle, theString); // Set the items text to theString. } //-------------------------------------------------------------- SetDialogNumToStr // This one is like SetDialogString() above, but it takes a number (long)É // instead of a string (the function will convert the long to a string for us). void SetDialogNumToStr (DialogPtr theDialog, short item, long theNumber) { Str255 theString; Rect itemRect; Handle itemHandle; short itemType; NumToString(theNumber, theString); // Convert long to a string. GetDItem(theDialog, item, &itemType, &itemHandle, &itemRect); SetIText(itemHandle, theString); // Set the item's text to this number/string. } //-------------------------------------------------------------- GetDialogNumFromStr // This one is like GetDialogString() above, but returns a long (number)É // instead of a string (it does this by converting the string to a long). void GetDialogNumFromStr (DialogPtr theDialog, short item, long *theNumber) { Str255 theString; Rect itemRect; Handle itemHandle; short itemType; // Get a handle to the dialog item. GetDItem(theDialog, item, &itemType, &itemHandle, &itemRect); GetIText(itemHandle, theString); // Get the item's text. StringToNum(theString, theNumber); // Convert the text to a long. } //-------------------------------------------------------------- DisableControl // Another dialog utility for "graying out" buttons or other controls in a dialog. void DisableControl (DialogPtr theDialog, short whichItem) { Rect iRect; Handle iHandle; short iType; // Get a handle to the dialog item. GetDItem(theDialog, whichItem, &iType, &iHandle, &iRect); // Set it's "hilite state" to "grayed out". HiliteControl((ControlHandle)iHandle, kInactive); } \ No newline at end of file