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