From 0cc0df0194540aacc99d015ff053923eb7ad0ffc Mon Sep 17 00:00:00 2001 From: John Calhoun Date: Wed, 27 Jan 2016 21:31:30 -0800 Subject: [PATCH] Initial check-in. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Written in C for the Macintosh in the 1990’s, this is a shareware arcade-style game. There is both a 68K and Power-PC project file (I forget what IDE they were for) and the resource file for both (.rsrc). --- Glypha III Read Me.txt | 1 + GlyphaIII.68K.project | Bin 0 -> 99592 bytes GlyphaIII.68K.project.rsrc | 0 GlyphaIII.PPC.project | Bin 0 -> 273864 bytes Source/Enemy.c | 1 + Source/Externs.h | 1 + Source/Graphics.c | 1 + Source/Interface.c | 1 + Source/Main.c | 1 + Source/Play.c | 1 + Source/Prefs.c | 1 + Source/SetUpTakeDown.c | 1 + Source/Sound.c | 1 + Source/Utilities.c | 1 + 14 files changed, 11 insertions(+) create mode 100755 Glypha III Read Me.txt create mode 100755 GlyphaIII.68K.project create mode 100755 GlyphaIII.68K.project.rsrc create mode 100755 GlyphaIII.PPC.project create mode 100755 Source/Enemy.c create mode 100755 Source/Externs.h create mode 100755 Source/Graphics.c create mode 100755 Source/Interface.c create mode 100755 Source/Main.c create mode 100755 Source/Play.c create mode 100755 Source/Prefs.c create mode 100755 Source/SetUpTakeDown.c create mode 100755 Source/Sound.c create mode 100755 Source/Utilities.c 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 0000000000000000000000000000000000000000..f01a63a2247686cf8892b768586f4f6b850ad9f3 GIT binary patch literal 99592 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 Y4UAR!V%|4*Uw-2qr{KO@cppFee;-LGQvd(} literal 0 HcmV?d00001 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