Glypha3/Source/Graphics.c

1 line
40 KiB
C
Executable File
Raw Blame History

//============================================================================
//----------------------------------------------------------------------------
// 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 <Palettes.h>
#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<68>
// 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<6D>
{ // 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<6D>
{
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<6D>
{
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<6D>
{
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<6C>
// 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<69>
// shrinks. Thus it is as though the wall is<69>
// 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,<2C>
// 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<61>
// difference is that we must first draw all the high scores offscreen before<72>
// 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, <20>).
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, <20>).
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, <20>).
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, <20>).
}
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<68>
// 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<68>
// score to go to 6 digits (999,999) before rolling over. Note however, that<61>
// in both the case of the score, number of lives, etc., the game does in fact<63>
// keep track of the "actual" number. It is just that only so many digits are<72>
// 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<68>
// 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<74>
// (one from the tip of each obelisk) to the point. It does this by generating<6E>
// a list of segments (as the lightning is broken up into segements). The drawing<6E>
// counterpart to this function will draw a line connecting these segements (a sort<72>
// 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<65>
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<65>
{ // 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<6C>
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<74>
// 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<4F>
// so that the lines are drawn inverted (colorwise). This way, drawing the<68>
// 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<68>
// 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<68>
// 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<6F>
// routine, if we can do it faster, we ought to. Well, the function below<6F>
// is quick because (among other reasons), it assumes that the two rects<74>
// 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<75>
// for ease of use, you may not be able to beat it. The idea is that any time<6D>
// you want something drawn to the screen (copied from an offscreen pixmap to<74>
// the screen) you pass the rectangle to this routine. This routine then adds<64>
// the rectangle to a growing list of these rectangles. When the game reaches<65>
// drawing phase, another routine copies all these rectangles. It is assumed, <20>
// nonetheless, that you have copied the little graphic offscreen that you want<6E>
// moved to the screen (the shpinx or whatever). This routine will take care of<6F>
// drawing the shinx or whatever to the screen.
void AddToUpdateRects (Rect *theRect)
{
if (whichList) // We alternate every odd frame between two lists<74>
{ // 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<6C>
// 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<6C>
// off the right edge of the screen, you see the player peeking through on the left<66>
// side of the screen. Since we can't (shouldn't) assume that the physical screen<65>
// memory wraps around, we'll draw the right player clipped against the right edge<67>
// 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<67>
{
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<6F>
// 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<65>
{
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<6C>
// two (well really three) choices - hand open, hand clutching (or no hand<6E>
// 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<61>
// offscreen pixmap to the main screen. It is at this instant (during the<68>
// execution of the below function) that the screen actually changes. The<68>
// whole rest of Glypha is, in essence, there only to lead up, ultimately, <20>
// 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<6E>
// offscreen. It is the above routine that will finally copy our offscreen<65>
// work to the main screen. Anyway, the below function draws the player<65>
// 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<65>
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<61>
{
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<67>
{
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<65>
// 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<6C>
// 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<6E>
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<76>
// 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<64>
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.
}