// // ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» // .  º GRAVITATION º // ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ  // . . v1.3 . // // . by . // //  James Shaughnessy . // // (C) 1998 // Comments/questions appreciated to: james@manc.u-net.com // Net Yaroze web page: http://www.netyaroze-europe.com/~shaughnj // Debug flag //#define DEBUG #include #include #include "pad.h" #include "grav.h" void main() { // Must call once to init Graphics and some variables VidModeChange(); // Must init once PadInit(); InitSoundSystem(); InitFrameBuffer(); SpriteInit(); InitGame(); // Set to NTSC if TRIANGLE is held down on PAD1 in case // TV cannot display PAL signal (can change via menu) // Signal setting with a ping sound if ((padStatus = PadRead()) & PAD1triangle) { VidModeChange(); SsUtKeyOn(vab_id, LIFELOSS_SOUND, 0, 62, 0, SFX_VOL, SFX_VOL); } while(!EXIT_GAME) { SsSetMVol(127, 127); // Restore main volume GameSelectionScreen(); // Main Front-end screen // Point to start of MAP_NUM's demo record memory (PAL/NTSC separate) pdemodata = (u_char *)(DEMO_ADDR+(VidMode*DEMO_SIZE)+(MAP_NUM*DEMO_SIZE/9)); // Reset pointer index demoindex = 0; // MAIN LOOP while(GAME_ON) { // Read in controller data from last vertical blank padStatus = PadRead(); // Pause game if START is pressed (only on an acive pad to prevent // ruinses), or if PAD1 is removed (it is the LAW!) if (padStatus & PAD1start || (player2.playing && padStatus & PAD2start) || (*bb0 == 0xff) ) // PAD1 removed { if (padStatus & PAD2start) unPAUSE = 0xffff<<16; else unPAUSE = 0xffff; SsSetMVol(32, 32); // 1/4-volume ("is that the 'phone ringing?") GAME_PAUSED = TRUE; } else if (DEMO_MODE == TRUE) // Cross/Select quits demo { if (padStatus & (PAD1cross | PAD2cross | PAD1select | PAD2select)) { GAME_ON = FALSE; QUIT_GAME = TRUE; } } // Pause loop while (GAME_PAUSED) { // Read in controller data from last vertical blank padStatus = PadRead(); // Key off engine loop sounds while paused if (player1.thrustsfx) { SsUtKeyOff(player1.voice, vab_id, THRUST_SOUND, 0, THRUST_PITCH); player1.thrustsfx = FALSE; } if (player2.playing && player2.thrustsfx) { SsUtKeyOff(player2.voice, vab_id, THRUST_SOUND, 0, THRUST_PITCH); player2.thrustsfx = FALSE; } // Un-pause game any button (only on PAD that paused the game) if (padStatus & unPAUSE && !(padStatus & PAD1start || (player2.playing && padStatus & PAD2start))) { SsSetMVol(127, 127); // restore volume ("DOH, tell 'em I'm busy!") GAME_PAUSED = FALSE; } // Wait for next vertical blank VSync(0); } // Quit game if SELECT is pressed (only on an active pad) if ( (padStatus & PAD1select) || (player2.playing && (padStatus & PAD2select)) ) { GAME_ON = FALSE; QUIT_GAME = TRUE; } if (DEMO_MODE == FALSE) { // Test PADs and act on players' variables (THRUST, TURN, SPIRALSHOT etc) if (player1.alive) { if (!player1.invulnerable && WEAPONS_ON) { if (FireButtonPressed(1)) FireBullet(1); // FIRE else if (player1.autofire && notcountermodX && padStatus & PAD1FIRE) FireBullet(1); // AUTOFIRE if (FireSpecialPressed(1)) // FIRESPECIAL FireSpecial(1); else if (player1.autofire && !player1.mines && notcountermodX && padStatus & PAD1FIRESPECIAL) FireSpecial(1); // AUTOFIRESPECIAL if (player1.laserfire && (padStatus & PAD1FIRE)) LaserFire(PLAYER1); if (player1.spiralshot) SpiralFire(PLAYER1); } if (padStatus & PAD1cross) player1.thrust = TRUE; else player1.thrust = FALSE; if (padStatus & PAD1triangle) player1.reversethrust = TRUE; else player1.reversethrust = FALSE; if (padStatus & PAD1square) player1.airbrake = TRUE; else player1.airbrake = FALSE; if ( (*(bb0+1) >> 4) == 7 && !player1.landed) // ANALOG CONTROL { if (PAD1analogX < 96 || PAD1analogX > 160) { if (player1.reversesteer) { if (PAD1analogX < 128) player1.analogangle -= (PAD1analogX-96)<<1; else player1.analogangle -= (PAD1analogX-160)<<1; } else { if (PAD1analogX < 128) player1.analogangle += (PAD1analogX-96)<<1; else player1.analogangle += (PAD1analogX-160)<<1; } // Bound to limits and set actual player angle if (player1.analogangle >= 9216) player1.analogangle -= 9216; if (player1.analogangle < 0) player1.analogangle += 9216; player1.angle = (player1.analogangle >> 8); } } else // DIGITAL CONTROL { if (player1.reversesteer) { if (padStatus & PAD1right && !player1.landed && countermod2) if (--player1.angle < 0) player1.angle = 35; if (padStatus & PAD1left && !player1.landed && countermod2) if (++player1.angle > 35) player1.angle = 0; } else { if (padStatus & PAD1left && !player1.landed && countermod2) if (--player1.angle < 0) player1.angle = 35; if (padStatus & PAD1right && !player1.landed && countermod2) if (++player1.angle > 35) player1.angle = 0; } } } if (player2.playing && player2.alive) { if (!player2.invulnerable && WEAPONS_ON) { if (FireButtonPressed(2)) FireBullet(2); // FIRE else if (player2.autofire && notcountermodX && padStatus & PAD2FIRE) FireBullet(2); // AUTOFIRE if (FireSpecialPressed(2)) // FIRESPECIAL FireSpecial(2); else if (player2.autofire && !player2.mines && notcountermodX && padStatus & PAD2FIRESPECIAL) FireSpecial(2); // AUTOFIRESPECIAL if (player2.laserfire && (padStatus & PAD2FIRE)) LaserFire(PLAYER2); if (player2.spiralshot) SpiralFire(PLAYER2); } if (padStatus & PAD2cross) player2.thrust = TRUE; else player2.thrust = FALSE; if (padStatus & PAD2triangle) player2.reversethrust = TRUE; else player2.reversethrust = FALSE; if (padStatus & PAD2square) player2.airbrake = TRUE; else player2.airbrake = FALSE; if ( (*(bb1+1) >> 4) == 7 && !player2.landed) // ANALOG CONTROL { if (PAD2analogX < 96 || PAD2analogX > 160) { if (player2.reversesteer) { if (PAD2analogX < 128) player2.analogangle -= (PAD2analogX-96)<<1; else player2.analogangle -= (PAD2analogX-160)<<1; } else { if (PAD2analogX < 128) player2.analogangle += (PAD2analogX-96)<<1; else player2.analogangle += (PAD2analogX-160)<<1; } if (player2.analogangle >= 9216) player2.analogangle -= 9216; if (player2.analogangle < 0) player2.analogangle += 9216; player2.angle = (player2.analogangle >> 8); } } else // DIGITAL CONTROL { if (player2.reversesteer) { if (padStatus & PAD2right && !player2.landed && countermod2) if (--player2.angle < 0) player2.angle = 35; if (padStatus & PAD2left && !player2.landed && countermod2) if (++player2.angle > 35) player2.angle = 0; } else { if (padStatus & PAD2left && !player2.landed && countermod2) if (--player2.angle < 0) player2.angle = 35; if (padStatus & PAD2right && !player2.landed && countermod2) if (++player2.angle > 35) player2.angle = 0; } } } } else // DEMO_MODE == TRUE (NOTE 1 PLAYER DEMOS ONLY AT THE MOMENT! ***) { // Get demo data byte demobyte = pdemodata[demoindex++]; if (demobyte & 128) { QUIT_GAME = TRUE; // End of DEMO, or out of time GAME_ON = FALSE; } else if (player1.alive) { if (demobyte & 1) { FireBullet(1); } if (demobyte & 2) { FireSpecial(1); } if (demobyte & 4) { player1.thrust = TRUE; } else { player1.thrust = FALSE; } if (demobyte & 8) { player1.reversethrust = TRUE; } else { player1.reversethrust = FALSE; } if (demobyte & 16) { player1.airbrake = TRUE; } else { player1.airbrake = FALSE; } if (demobyte & 32) { if (--player1.angle < 0) player1.angle = 35; } if (demobyte & 64) { if (++player1.angle > 35) player1.angle = 0; } } } // playerdead := players' death flags in bits 1 and 2 playerdead = UpdatePlayers(); if (playerdead & PLAYER1) { SsUtKeyOn(vab_id, EXPLODE_SOUND, 0, 52, 0, SFX_VOL, SFX_VOL); player1.exploding = DEATH_PAUSE; // Player 1 killed **! player1.alive = FALSE; player1.thrust = FALSE; player1.reversethrust = FALSE; // Tests to see if player 2 is within range, if so die too this // avoids the possibility of a collision but with P1 surviving if (player2.playing && player2.alive && !player2.invulnerable && abs(player1.x - player2.x) < 15 && abs(player1.y - player2.y) < 15) { SsUtKeyOn(vab_id, EXPLODE_SOUND, 0, 50, 0, SFX_VOL, SFX_VOL); player2.exploding = DEATH_PAUSE; player2.alive = FALSE; player2.thrust = FALSE; player2.reversethrust = FALSE; playerdead = 0; // prevent +'ve bit-test below } } if (playerdead & PLAYER2) { SsUtKeyOn(vab_id, EXPLODE_SOUND, 0, 52, 0, SFX_VOL, SFX_VOL); player2.exploding = DEATH_PAUSE; // Player 2 killed **! player2.alive = FALSE; player2.thrust = FALSE; player2.reversethrust = FALSE; // Tests to see if player 1 is within range, if so die too this avoids // possibility of a collision but with P1 surviving (hence very unfair) if (player1.alive && !player1.invulnerable && abs(player1.x - player2.x) < 15 && abs(player1.y - player2.y) < 15) { SsUtKeyOn(vab_id, EXPLODE_SOUND, 0, 50, 0, SFX_VOL, SFX_VOL); player1.exploding = DEATH_PAUSE; player1.alive = FALSE; player1.thrust = FALSE; player1.reversethrust = FALSE; } } // Centre players on screen (must bound PXscr. straight after) P1scr.x = player1.x + 231; if (player2.playing) { P2scr.x = player2.x + 231; if (VidMode) { P1scr.y = player1.y - 57; P2scr.y = player2.y - 57; } else { P1scr.y = player1.y - 53; P2scr.y = player2.y - 53; } } else if (VidMode) P1scr.y = player1.y - 121; else P1scr.y = player1.y - 113; // Bound screen offsets if (P1scr.x < 384) P1scr.x = 384; else if (P1scr.x > 704) P1scr.x = 704; if (P1scr.y < 0) P1scr.y = 0; else if (P1scr.y > maxyoffset + splitLine) P1scr.y = maxyoffset + splitLine; if (player2.playing) { if (P2scr.x < 384) P2scr.x = 384; else if (P2scr.x > 704) P2scr.x = 704; if (P2scr.y < 0) P2scr.y = 0; else if (P2scr.y > maxyoffset) P2scr.y = maxyoffset; } #ifdef DEBUG if (padStatus & PAD1circle) { printf("hsyncs = %d\n", hsyncs); player1.threewayfire = TRUE; player1.backshot = TRUE; player1.mines = MAX_MINES; player1.gate = 8; player1.reverseG = TRUE; player2.threewayfire = TRUE; player2.backshot = TRUE; player2.mines = MAX_MINES; player2.gate = 8; player2.reverseG = TRUE; // Sound test SsUtKeyOn(vab_id, MINE_SOUND, 0, 53, 0, SFX_VOL, SFX_VOL); } #endif // Wait for end of drawing, then a vertical blank DrawSync(0); #ifdef DEBUG hsyncs = VSync(0); #else VSync(0); #endif // Copy virtual screen area(s) to visual screen MoveImage(&P1scr, 0, 0); if (player2.playing) if (VidMode) MoveImage(&P2scr, 0, 128); else MoveImage(&P2scr, 0, 120); DrawSync(0); DrawOnscreenStuff(); // Signal Display (using single buffer) GsSwapDispBuff(); // RestoreOLDareas (uses xold and yold) to allow safe bullet/mine placement PutPlayerBKImage(1); if (player2.playing) PutPlayerBKImage(2); counter++; if (!(counter%256) && PODS_ON && DEMO_MODE == FALSE) if (!pod.out && !(rand() % POD_CHANCE)) // does not always create POD CreatePOD(); // depends on position legality // *** GAME TIMERS *** countermod2 = counter%2; // Frame timers if (VidMode) notcountermodX = !(counter%5); else notcountermodX = !(counter%6); // Invulnerability timers if (player1.invulnerable) { if (VidMode) player1.invulnerable--; else // NTSC provision if (!notcountermodX) player1.invulnerable--; } if (player2.invulnerable) { if (VidMode) player2.invulnerable--; else // NTSC provision if (!notcountermodX) player2.invulnerable--; } // RACE TIME timers: 1 unit = 20 ms for both PAL and NTSC if (GAME_TYPE == RACE) { if (player1.racing) { if (VidMode) player1.racetime++; else if (!notcountermodX) // NTSC provision player1.racetime++; } if (player2.playing && player2.racing) { if (VidMode) player2.racetime++; else if (!notcountermodX) // NTSC provision player2.racetime++; } // Timer display digit calculatation if (player1.racetime < 30000) // 29999 = 9:59.98 max visible time { P1Time[0].u = 6 * (player1.racetime / 3000); // minutes P1Time[2].u = 6 * ((player1.racetime/500)%6); // tens of seconds P1Time[3].u = 6 * ((player1.racetime/50)%10); // second units P1Time[5].u = 6 * ((player1.racetime/5)%10); // tenths of seconds P1Time[6].u = 6 * ((player1.racetime<<1)%10); // split seconds } if (player2.playing) { if (player2.racetime < 30000) { P2Time[0].u = 6 * (player2.racetime / 3000); P2Time[2].u = 6 * ((player2.racetime/500)%6); P2Time[3].u = 6 * ((player2.racetime/50)%10); P2Time[5].u = 6 * ((player2.racetime/5)%10); P2Time[6].u = 6 * ((player2.racetime<<1)%10); } } } } // Ensure engine loop sounds are keyed off if (player1.thrustsfx) { SsUtKeyOff(player1.voice, vab_id, THRUST_SOUND, 0, THRUST_PITCH); player1.thrustsfx = FALSE; } if (player2.playing && player2.thrustsfx) { SsUtKeyOff(player2.voice, vab_id, THRUST_SOUND, 0, THRUST_PITCH); player2.thrustsfx = FALSE; } if (!EXIT_GAME) CongratulateWinner(); } // Clean up and Exit to system ResetGraph(0); } // Initialises game structures void InitGame() { int map; // Set all course records to a pretty good challenge! for (map=0; map < NUM_MAPS; map++) { Record[map].i[0] = 9; // "JIM" by default Record[map].i[1] = 8; Record[map].i[2] = 12; } Record[0].racetime = 2336; Record[1].racetime = 3879; Record[2].racetime = 4656; Record[3].racetime = 2597; Record[4].racetime = 5092; Record[5].racetime = 2008; Record[6].racetime = 3521; Record[7].racetime = 2096; Record[8].racetime = 4749; // Set default player input initials to AAA for both players DefaultInitials[0][0] = 0; DefaultInitials[0][1] = 0; DefaultInitials[0][2] = 0; DefaultInitials[1][0] = 0; DefaultInitials[1][1] = 0; DefaultInitials[1][2] = 0; // Start with double buffering GsDefDispBuff(0, 0, 384, 0); GsSwapDispBuff(); VSync(0); } // Initialize sprites // Uses the brightness flag to determine whether bullets are WHITE or BLACK // when clearing/drawing sprite void SpriteInit() { int i, j, k; RECT r; GsIMAGE im; // Initialise Ordering Tables for (i=0; i<2; i++) { WorldOT[i].length = OT_LENGTH; WorldOT[i].org = OTTags[i]; GsClearOt(0, 0, &WorldOT[i]); } // init Bullet sprites for (i = 0; i < MAX_BULLETS; i++) { P1BulletSprite[i].attribute = 0x02000040; // 15bit direct P1BulletSprite[i].w = 1; P1BulletSprite[i].h = 1; P1BulletSprite[i].tpage = GetTPage(2, 0, 320, 0); P1BulletSprite[i].u = 63; P1BulletSprite[i].v = 1; P1BulletSprite[i].r = P1BulletSprite[i].g = P1BulletSprite[i].b = 0; // Brightness to 0, use flag to set P1BulletSprite[i].mx = 0; P1BulletSprite[i].my = 0; P2BulletSprite[i].attribute = 0x02000040; // 15bit direct P2BulletSprite[i].w = 1; P2BulletSprite[i].h = 1; P2BulletSprite[i].tpage = GetTPage(2, 0, 320, 0); P2BulletSprite[i].u = 63; P2BulletSprite[i].v = 1; P2BulletSprite[i].r = P2BulletSprite[i].g = P2BulletSprite[i].b = 0; // Brightness to 0, use flag to set P2BulletSprite[i].mx = 0; P2BulletSprite[i].my = 0; } // Init players' digital timer sprites for (i = 0; i < 7; i++) { P1Time[i].attribute = 0x02000000; P2Time[i].attribute = 0x02000000; P1Time[i].h = P2Time[i].h = 9; P1Time[i].tpage = GetTPage(2, 0, 256, 256); P2Time[i].tpage = GetTPage(2, 0, 256, 256); P1Time[i].u = P2Time[i].u = 0; // Variable depending on digit P1Time[i].v = P2Time[i].v = 16; P1Time[i].r = P1Time[i].g = P1Time[i].b = 128; P2Time[i].r = P2Time[i].g = P2Time[i].b = 128; P1Time[i].mx = P1Time[i].my = 0; } P1Time[0].x = P2Time[0].x = 16; P1Time[1].x = P2Time[1].x = 21; P1Time[2].x = P2Time[2].x = 23; P1Time[3].x = P2Time[3].x = 28; P1Time[4].x = P2Time[4].x = 33; P1Time[5].x = P2Time[5].x = 35; P1Time[6].x = P2Time[6].x = 40; P1Time[0].w = P2Time[0].w = 6; P1Time[1].w = P2Time[1].w = 3; P1Time[2].w = P2Time[2].w = 6; P1Time[3].w = P2Time[3].w = 6; P1Time[4].w = P2Time[4].w = 4; P1Time[5].w = P2Time[5].w = 6; P1Time[6].w = P2Time[6].w = 6; P1Time[1].u = P2Time[1].u = 60; // Offset; rest are set in P1Time[4].u = P2Time[4].u = 64; // drawonscreenstuff() // Init player lives-left sprites P1lives.attribute = 0x02000000; // 15bit direct P1lives.w = 37; P1lives.h = 8; P1lives.tpage = GetTPage(2, 0, 256, 256); P1lives.u = 0; P1lives.v = 0; P1lives.r = P1lives.g = P1lives.b = 128; P1lives.mx = P1lives.my = 0; P2lives.attribute = 0x02000000; // 15bit direct P2lives.w = 37; P2lives.h = 8; P2lives.tpage = GetTPage(2, 0, 256, 256); P2lives.u = 0; P2lives.v = 8; P2lives.r = P2lives.g = P2lives.b = 128; P2lives.mx = P2lives.my = 0; // Init Lap display sprites P1LapSprite.attribute = 0x02000000; // 15bit direct P1LapSprite.x = 284; P1LapSprite.w = 20; P1LapSprite.h = 7; P1LapSprite.tpage = GetTPage(2, 0, 256, 256); P1LapSprite.u = 0; P1LapSprite.v = 25; P1LapSprite.r = P1LapSprite.g = P1LapSprite.b = 128; P1LapSprite.mx = P1LapSprite.my = 0; P2LapSprite.attribute = 0x02000000; // 15bit direct P2LapSprite.x = 284; P2LapSprite.w = 20; P2LapSprite.h = 7; P2LapSprite.tpage = GetTPage(2, 0, 256, 256); P2LapSprite.u = 0; P2LapSprite.v = 25; P2LapSprite.r = P2LapSprite.g = P2LapSprite.b = 128; P2LapSprite.mx = P2LapSprite.my = 0; // "DEMO" sprite DemoSprite.attribute = 0x02000000; // 15bit direct DemoSprite.x = 145; DemoSprite.y = 242; DemoSprite.w = 28; DemoSprite.h = 9; DemoSprite.tpage = GetTPage(2, 0, 256, 256); DemoSprite.u = 82; DemoSprite.v = 15; DemoSprite.r = DemoSprite.g = DemoSprite.b = 128; DemoSprite.mx = DemoSprite.my = 0; DemoSprite.scalex = DemoSprite.scaley = ONE; DemoSprite.rotate = 0; // Init Gate display sprites P1GateSprite[0].attribute = 0x02000000; // 15bit direct P1GateSprite[0].x = 257; P1GateSprite[0].w = 18; P1GateSprite[0].h = 7; P1GateSprite[0].tpage = GetTPage(2, 0, 256, 256); P1GateSprite[0].u = 38; P1GateSprite[0].v = 0; P1GateSprite[0].r = P1GateSprite[0].g = P1GateSprite[0].b = 128; P1GateSprite[0].mx = P1GateSprite[0].my = 0; P2GateSprite[0].attribute = 0x02000000; // 15bit direct P2GateSprite[0].x = 257; P2GateSprite[0].w = 18; P2GateSprite[0].h = 7; P2GateSprite[0].tpage = GetTPage(2, 0, 256, 256); P2GateSprite[0].u = 38; P2GateSprite[0].v = 0; P2GateSprite[0].r = P2GateSprite[0].g = P2GateSprite[0].b = 128; P2GateSprite[0].mx = P2GateSprite[0].my = 0; P1GateSprite[1].attribute = 0x02000000; // 15bit direct P1GateSprite[1].x = 275; P1GateSprite[1].w = 6; P1GateSprite[1].h = 7; P1GateSprite[1].tpage = GetTPage(2, 0, 256, 256); P1GateSprite[1].u = 56; P1GateSprite[1].v = 0; P1GateSprite[1].r = P1GateSprite[1].g = P1GateSprite[1].b = 128; P1GateSprite[1].mx = P1GateSprite[1].my = 0; P2GateSprite[1].attribute = 0x02000000; // 15bit direct P2GateSprite[1].x = 275; P2GateSprite[1].w = 6; P2GateSprite[1].h = 7; P2GateSprite[1].tpage = GetTPage(2, 0, 256, 256); P2GateSprite[1].u = 56; P2GateSprite[1].v = 0; P2GateSprite[1].r = P2GateSprite[1].g = P2GateSprite[1].b = 128; P2GateSprite[1].mx = P2GateSprite[1].my = 0; // Init Menu/title screen sprites for (k = 0; k < 8; k++) { for (j=0; j < 2; j++) { for (i=0; i < 2; i++) { menu[i][j][k].attribute = 0x02000000; menu[i][j][k].x = 120; menu[i][j][k].y = 76 + 16 * k; menu[i][j][k].h = 8; menu[i][j][k].tpage = GetTPage(2, 0, 0, 256); menu[i][j][k].r = menu[i][j][k].g = menu[i][j][k].b = 128; menu[i][j][k].mx = menu[i][j][k].my = 0; } } } menu[0][0][0].u = 0; menu[0][0][0].v = 144; // START GAME menu[0][0][1].u = 84; menu[0][0][1].v = 144; // MAP menu[0][0][2].u = 0; menu[0][0][2].v = 152; // RACE menu[0][1][2].u = 36; menu[0][1][2].v = 152; // DOGFIGHT menu[0][0][3].u = 0; menu[0][0][3].v = 168; // PLAYERS menu[0][0][4].u = 0; menu[0][0][4].v = 184; // CD << -> >> menu[0][1][4].u = 56; menu[0][1][4].v = 184; // CD << [] >> menu[0][0][5].u = 116; menu[0][0][5].v = 144; // OPTIONS menu[0][0][6].u = 80; menu[0][0][6].v = 160; // HELP menu[0][0][7].u = 64; menu[0][0][7].v = 168; // QUIT menu[1][0][0].u = 176; menu[1][0][0].v = 144; // BACK menu[1][0][1].u = 116; menu[1][0][1].v = 152; // PODS menu[1][0][2].u = 160; menu[1][0][2].v = 152; // WEAPONS menu[1][0][3].u = 116; menu[1][0][3].v = 160; // SPLIT LINE menu[1][0][4].u = 116; menu[1][0][4].v = 168; // SFX VOL menu[1][0][5].u = 116; menu[1][0][5].v = 176; // CD VOL menu[1][0][6].u = 0; menu[1][0][6].v = 176; // SCREEN ADJUST menu[1][0][7].u = 164; menu[1][0][7].v = 176; // VID MODE menu[0][0][0].w = 82; menu[1][0][0].w = 36; menu[0][0][1].w = 28; menu[1][0][1].w = 36; menu[0][0][2].w = 34; menu[1][0][2].w = 62; menu[0][1][2].w = 66; // dogfight menu[0][0][3].w = 58; menu[1][0][3].w = 70; menu[0][0][4].w = 56; menu[1][0][4].w = 52; menu[0][1][4].w = 56; menu[0][0][5].w = 56; menu[1][0][5].w = 46; menu[0][0][6].w = 34; menu[1][0][6].w = 106; menu[0][0][7].w = 32; menu[1][0][7].w = 62; menu[0][1][1].x = 152; menu[0][1][1].v = 160; menu[0][1][1].w = 8; // map # menu[0][1][3].x = 186; menu[0][1][3].u = 190; menu[0][1][3].v = 160; // (ONE / TWO) (players) menu[0][1][3].w = 18; menu[1][1][1].x = 162; menu[1][1][1].u = 176; menu[1][1][1].v = 168; // OFF / ON (pods) menu[1][1][1].w = 24; menu[1][1][2].x = 188; menu[1][1][2].u = 176; menu[1][1][2].v = 168; // OFF / ON (weapons) menu[1][1][2].w = 24; menu[1][1][3].x = 196; menu[1][1][3].u = 176; menu[1][1][3].v = 168; // OFF / ON (split line) menu[1][1][3].w = 24; menu[1][1][4].x = 188; menu[1][1][4].u = 186; // SFX volume bar border menu[1][1][4].w = 68; menu[1][1][4].v = 14; menu[1][1][5].x = 188; menu[1][1][5].u = 186; // CD volume bar border menu[1][1][5].w = 68; menu[1][1][5].v = 14; menu[1][1][7].x = 186; menu[1][1][7].v = 184; // NTSC/PAL menu[1][1][7].w = 35; // Init menu map icon -- uses total clut change 0,451 and 0,450 // to show race route (pixel 191 line colour) // Put TIM info into im GsGetTimInfo(((u_long *)LEVELS_ADDR)+1, &im); // Load image and clut into frame buffer setRECT(&r, im.px, im.py, im.pw, im.ph); LoadImage(&r, im.pixel); setRECT(&r, im.cx, im.cy, im.cw, im.ch); LoadImage(&r, im.clut); setRECT(&r, im.cx, im.cy-1, im.cw, im.ch); LoadImage(&r, im.clut); setRECT(&r, im.cx, im.cy, 1, 1); MoveImage(&r, 191, im.cy-1); // Copy clut pixel 0 to 191 (black, no transparancy) // Mapicon TIM (levels.tim) loads in at (172, 288) mapicon.attribute = (im.pmode<<24); // 8-bit CLUT (at 0,451) mapicon.x = 18; mapicon.y = 192; mapicon.w = 64; mapicon.h = 51; mapicon.tpage = GetTPage(im.pmode, 0, 256, 256); mapicon.u = 16; // 9x9 64x51 icons mapicon.v = 32; mapicon.cx = 0; mapicon.cy = 451; mapicon.r = mapicon.g = mapicon.b = 128; mapicon.mx = 0; mapicon.my = 0; mapicon.scalex = ONE; mapicon.scaley = ONE; mapicon.rotate = 0; // Menu record time sprites for (i = 0; i < 10; i++) { menurecordtime[i].attribute = 0x02000000; menurecordtime[i].y = 172; menurecordtime[i].w = 7; menurecordtime[i].h = 11; menurecordtime[i].tpage = GetTPage(2, 0, 0, 256); menurecordtime[i].u = 108; menurecordtime[i].v = 29; menurecordtime[i].r = menurecordtime[i].g = menurecordtime[i].b = 128; menurecordtime[i].mx = 0; menurecordtime[i].my = 0; menurecordtime[i].scalex = ONE; menurecordtime[i].scaley = ONE; menurecordtime[i].rotate = 0; } menurecordtime[1].u = 188; menurecordtime[4].u = 196; menurecordtime[7].u = 108; // Hi-score initials uv menurecordtime[7].v = 40; // default here to "AAA" menurecordtime[8].u = 108; // but is set to "___" by menurecordtime[8].v = 40; // Init game (for debug) menurecordtime[9].u = 108; menurecordtime[9].v = 40; menurecordtime[7].w = 6; menurecordtime[8].w = 6; menurecordtime[9].w = 6; menurecordtime[0].x = 18; menurecordtime[1].x = 23; menurecordtime[2].x = 28; menurecordtime[3].x = 35; menurecordtime[4].x = 40; menurecordtime[5].x = 44; menurecordtime[6].x = 51; menurecordtime[7].x = 64; // J x-pos on screen menurecordtime[8].x = 69; // I menurecordtime[9].x = 74; // M menurecordtime[7].h = 7; menurecordtime[8].h = 7; menurecordtime[9].h = 7; DrawSync(0); } // Initialized the sound effects to sound RAM void InitSoundSystem() { // Set main volume SsSetMVol(127,127); // Put samples in sound RAM and get vab_id vab_id = SsVabTransfer((u_char *)VH_ADDR, (u_char *)VB_ADDR, 1, 1); // Get SEQ access num // access_num = SsSeqOpen(u_long *)SEQ_ADDR, vab_id); // Init CD music SsSetSerialAttr(SS_CD, SS_MIX, SS_SON); SsSetSerialVol(SS_CD, CD_VOL, CD_VOL); } // Draws next frame on the virtual screen // Updates gate // Returns death flag, 0, 1, 2 or 3 (3=both players dead) int UpdatePlayers() { int returnval = 0; if (player1.thrust || player1.reversethrust) // THRUST sound { if (!player1.thrustsfx) { player1.voice = SsUtKeyOn(vab_id, THRUST_SOUND, 0, THRUST_PITCH, 0, SFX_VOL, SFX_VOL); player1.thrustsfx = TRUE; } } else if (player1.thrustsfx) { SsUtKeyOff(player1.voice, vab_id, THRUST_SOUND, 0, THRUST_PITCH); player1.thrustsfx = FALSE; } if (player2.playing) { if (player2.thrust || player2.reversethrust) // THRUST sound { if (!player2.thrustsfx) { player2.voice = SsUtKeyOn(vab_id, THRUST_SOUND, 0, THRUST_PITCH, 0, SFX_VOL, SFX_VOL); player2.thrustsfx = TRUE; } } else if (player2.thrustsfx) { SsUtKeyOff(player2.voice, vab_id, THRUST_SOUND, 0, THRUST_PITCH); player2.thrustsfx = FALSE; } } if (player1.alive) { // Engine thrust / reversethrust if (player1.thrust) { if (!player1.lap && !GAME_TYPE) // Start race timer { player1.lap = 1; // lap 1 player1.racing = TRUE; } player1.vx += ((EnginePower*SIN[player1.angle])>>12); player1.vy -= ((EnginePower*COS[player1.angle])>>12); // Change CLUT pixel for flames if (counter%3) CLUT[19] = 31; else CLUT[19] = 1023; } else CLUT[19] = CLUT[17]; // Restore CLUT pixel if (player1.reversethrust) { player1.vx -= ((EnginePower*SIN[player1.angle])>>12); player1.vy += ((EnginePower*COS[player1.angle])>>12); // Change CLUT pixel (black flame) if (counter%3) CLUT[19] = 0; else CLUT[19] = CLUT[17]; } // Air-Brake if (player1.airbrake && !player1.invulnerable) { player1.vx -= ((player1.vx*AirBrakePower)>>12); player1.vy -= ((player1.vy*AirBrakePower)>>12); } // Add Gravity Force to y velocity minus air resistance on each x and y if (!player1.landed) { player1.vx -= ((player1.vx*AirResistance)>>12); if (player1.reverseG) { player1.vy -= ((player1.vy*AirResistance)>>12) + GravityForce; if (player1.highG) player1.vy -= GravityForce; } else { player1.vy -= ((player1.vy*AirResistance)>>12) - GravityForce; if (player1.highG) player1.vy += GravityForce; } } else { // Round off location to avoid reverse-thrust death while landed // And add ONE-1 as a reverse G bug fix (problem while landing upsidedown) player1.py = (player1.y<<12); if (player1.reverseG) player1.py += 4095; if (!player2.playing && player1.lap == 4) GAME_ON = FALSE; // Landed & 1P time-trial finished } // Move ship by component velocities player1.px += player1.vx; player1.py += player1.vy; // BOUNCE off edges of screen if (player1.px < 0 || player1.px > 2560000) { player1.px -= player1.vx; player1.vx = -player1.vx; // Prevent the bug of getting stuck outside screen if (player1.px < 0) player1.px = 0; else if (player1.px > 2560000) player1.px = 2560000; } if (player1.py < 0 || player1.py > 2035712) { player1.py -= player1.vy; player1.vy = -player1.vy; // Prevent the bug of getting stuck outside screen if (player1.py < 0) player1.py = 0; else if (player1.py > 2035712) player1.py = 2035712; } // Set actual integer screen coords for ship (ONLY manipulate .px & .py) player1.x = (player1.px>>12); player1.y = (player1.py>>12); } if (player2.alive && player2.playing) { // Engine thrust / reversethrust if (player2.thrust) { if (!player2.lap && !GAME_TYPE) // Start race timer { player2.lap = 1; // lap 1 player2.racing = TRUE; } player2.vx += ((EnginePower*SIN[player2.angle])>>12); player2.vy -= ((EnginePower*COS[player2.angle])>>12); // Change CLUT pixel for flames if (counter%3) CLUT[27] = 31; else CLUT[27] = 1023; } else CLUT[27] = CLUT[25]; // Restore CLUT pixel if (player2.reversethrust) { player2.vx -= ((EnginePower*SIN[player2.angle])>>12); player2.vy += ((EnginePower*COS[player2.angle])>>12); // Change CLUT pixel (black flame) if (counter%3) CLUT[27] = 0; else CLUT[27] = CLUT[25]; } // Air-Brake if (player2.airbrake && !player2.invulnerable) { player2.vx -= ((player2.vx*AirBrakePower)>>12); player2.vy -= ((player2.vy*AirBrakePower)>>12); } // Add Gravity Force to y velocity minus air resistance on each x and y if (!player2.landed) { player2.vx -= ((player2.vx*AirResistance)>>12); if (player2.reverseG) { player2.vy -= ((player2.vy*AirResistance)>>12) + GravityForce; if (player2.highG) player2.vy -= GravityForce; } else { player2.vy -= ((player2.vy*AirResistance)>>12) - GravityForce; if (player2.highG) player2.vy += GravityForce; } } else { // Round off location to avoid reverse-thrust death while landed // And add ONE-1 as a reverse G bug fix (problem while landing upsidedown) player2.py = (player2.y<<12); if (player2.reverseG) player2.py += 4095; } // Move ship by component velocities player2.px += player2.vx; player2.py += player2.vy; // BOUNCE off edges of screen if (player2.px < 0 || player2.px > 2560000) { player2.px -= player2.vx; player2.vx = -player2.vx; // Prevent the bug of getting stuck outside screen when invulnerable if (player2.px < 0) player2.px = 0; else if (player2.px > 2560000) player2.px = 2560000; } if (player2.py < 0 || player2.py > 2035712) { player2.py -= player2.vy; player2.vy = -player2.vy; // Prevent the bug of getting stuck outside screen when invulnerable if (player2.py < 0) player2.py = 0; else if (player2.py > 2035712) player2.py = 2035712; } // Set actual integer screen coords for ship (ONLY manipulate .px & .py) player2.x = (player2.px>>12); player2.y = (player2.py>>12); } // CLEAR ALL BULLETS AND MINES (and the POD) ClearBullets(); ClearMines(); if (pod.out) ClearPOD(); // SaveNEWarea where players will be drawn (uses x and y) SavePlayerBKImage(1); if (player2.playing) SavePlayerBKImage(2); DrawSync(0); // DRAW ALL BULLETS AND MINES (POD drawn after players to avoid collision) DrawBullets(); DrawMines(); // Physically draw players on the virtual screen. Draws both explosions // first so player 1 can collide with player 2's explosion. // If P1 game over P2 wins by default, but if P2 game over too WINNER // changed to DRAW if (player1.exploding) if (Explode(1)) { if (!--player1.lives) // LOSE A LIFE { if (GAME_TYPE == DOGFIGHT) { WINNER = PLAYER2; GAME_ON = FALSE; } else // GAME_TYPE == RACE { player1.lives++; // Restore life ResetPlayer(1); if (player2.lap == 4) GAME_ON = FALSE; // Race over } } else ResetPlayer(1); } if (player2.exploding) if (Explode(2)) { if (!--player2.lives) // LOSE A LIFE { if (GAME_TYPE == DOGFIGHT) { // Both players died at the same time with 1 life each if (!player1.lives) WINNER = DRAW; // A Draw game else WINNER = PLAYER1; GAME_ON = FALSE; } else // GAME_TYPE == RACE { player2.lives++; // Restore life ResetPlayer(2); if (player1.lap == 4) GAME_ON = FALSE; // Race over } } else ResetPlayer(2); } // Tests if a player has crossed next gate (if GAME_TYPE == RACE) // Uses simultaneous equations to find intersection of line defining // the gate, and the line defining the ship's velocity. The algorithm is // optimised for accuracy and flexibility rather than speed -- gates // can be in any position with posts any distance apart. The extra // time to calculate gate-crossing is made up with the fact that during // a race players will not need to fire anywhere near as many bullets // etc. as during a dogfight; Mmmmm, nice. Eq. of a line is y=mx+c if (GAME_TYPE == RACE) { if (player1.racing) { Gx1 = GATE[player1.gate-1][0].x-5; Gy1 = GATE[player1.gate-1][0].y-5; Gx2 = GATE[player1.gate-1][1].x-5; Gy2 = GATE[player1.gate-1][1].y-5; if (Gx1 - Gx2) { if (Gy1 - Gy2) Gm = ((float)(Gy2 - Gy1)) / ((float)(Gx2 - Gx1)); else Gm = 0; } else Gm = 512; if (player1.vx) { if (player1.vy) Pm = ((float)(player1.vy)) / ((float)(player1.vx)); else Pm = 0; } else Pm = 512; // Simultaneous equation solved (line intersection coords) intersectx = ((float)(player1.y-Gy1) - Pm * (float)(player1.x-Gx1)) / ((float)(Gm - Pm)); intersecty = Gm * intersectx; if (InBox((short)intersectx, (short)intersecty, 0, 0, Gx2-Gx1, Gy2-Gy1)) { if (InBox((short)intersectx+Gx1, (short)intersecty+Gy1, player1.x, player1.y, player1.xold, player1.yold)) { if (++player1.gate > 8) { player1.gate = 1; if (++player1.lap > 3) { player1.racing = FALSE; // Stops timer if (WINNER == NULL) WINNER = PLAYER1; else { if (player1.racetime < player2.racetime) WINNER = PLAYER1; else if (player1.racetime > player2.racetime) WINNER = PLAYER2; else WINNER = DRAW; // Exactly the same time! } } } if (player1.racing) SsUtKeyOn(vab_id, GATE_SOUND, 0, 48, 0, SFX_VOL, SFX_VOL); else // Finished race SsUtKeyOn(vab_id, GATE_SOUND, 0, 53, 0, SFX_VOL, SFX_VOL); } } } if (player2.playing && player2.racing) { Gx1 = GATE[player2.gate-1][0].x-5; Gy1 = GATE[player2.gate-1][0].y-5; Gx2 = GATE[player2.gate-1][1].x-5; Gy2 = GATE[player2.gate-1][1].y-5; if (Gx1 - Gx2) { if (Gy1 - Gy2) Gm = ((float)(Gy2 - Gy1)) / ((float)(Gx2 - Gx1)); else Gm = 0; } else Gm = 512; if (player2.vx) { if (player2.vy) Pm = ((float)(player2.vy)) / ((float)(player2.vx)); else Pm = 0; } else Pm = 512; // Simultaneous equation solved (line intersection coords) intersectx = ((float)(player2.y-Gy1) - Pm * (float)(player2.x-Gx1)) / ((float)(Gm - Pm)); intersecty = Gm * intersectx; if (InBox((short)intersectx, (short)intersecty, 0, 0, Gx2-Gx1, Gy2-Gy1)) { if (InBox((short)intersectx+Gx1, (short)intersecty+Gy1, player2.x, player2.y, player2.xold, player2.yold)) { if (++player2.gate > 8) { player2.gate = 1; if (++player2.lap > 3) { player2.racing = FALSE; // Stops timer if (WINNER == NULL) WINNER = PLAYER2; else { if (player2.racetime < player1.racetime) WINNER = PLAYER2; else if (player2.racetime > player1.racetime) WINNER = PLAYER1; else WINNER = DRAW; // Exactly the same time! } } } if (player2.racing) SsUtKeyOn(vab_id, GATE_SOUND, 0, 48, 0, SFX_VOL, SFX_VOL); else // Finished race SsUtKeyOn(vab_id, GATE_SOUND, 0, 53, 0, SFX_VOL, SFX_VOL); } } } } // CALL DRAWPLAYER(X) WITHIN UPDATEPLAYERXXX WHICH // BOTH PERFORM LANDING BOUNCE FROM RETURN VALUES // Test order changes depending on who is invulnerable if (GAME_ON) { // If player2 is invulnerable, draw first to allow collision if (!player2.invulnerable) { if (!player1.exploding) if (UpdatePlayerOne()) returnval += 1; if (player2.playing) if (!player2.exploding) if (UpdatePlayerTwo()) returnval += 2; } else { if (player2.playing) if (!player2.exploding) if (UpdatePlayerTwo()) returnval += 2; if (!player1.exploding) if (UpdatePlayerOne()) returnval += 1; } } else // Restores winner sprite at end of game { // for tidyness and aids 'smug mode' if (GAME_TYPE == DOGFIGHT) { if (WINNER == PLAYER1 && !player1.exploding) DrawPlayer(1); else if (player2.playing && WINNER == PLAYER2 && !player2.exploding) DrawPlayer(2); } else // GAME_TYPE == RACE { if (!player1.exploding) DrawPlayer(1); if (player2.playing && !player2.exploding) DrawPlayer(2); } } // Tests if anyone has collected POD (and CollectPOD within function), // if not, Draw it if (pod.out) { if (!PlayerHitPOD()) DrawPOD(); } // Save previous location player1.xold = player1.x; player1.yold = player1.y; if (player2.playing) { player2.xold = player2.x; player2.yold = player2.y; } return (returnval); } // Tests if a coordinate (x,y) is in the box defined by (x1,y1)-(x2,y2) // with a five pixel safety margin to prevent occasional misses u_char InBox(short x, short y, short x1, short y1, short x2, short y2) { if ( ((x >= (x1-5) && x <= (x2+5)) || (x >= (x2-5) && x <= (x1+5))) && ((y >= (y1-5) && y <= (y2+5)) || (y >= (y2-5) && y <= (y1+5))) ) return (TRUE); else return (FALSE); } // Updates players individually performing crash/land tests // Sepatated for ReverseG for speed reasons (not for size) int UpdatePlayerOne() { int val = 0; u_long sndVOL; crashorland = DrawPlayer(1); if (player1.reverseG) { if (crashorland & 1 && !player1.invulnerable) val = 1; // Player 1 crashed else if ((crashorland & 2) && // on flat ground & upright (player1.angle == 17 || player1.angle == 18 || player1.angle == 19)) { if (player1.vx > 1024 || player1.vx < -1024 || player1.vy < -1024) { if ((sndVOL = (SFX_VOL*-player1.vy)>>12) > SFX_VOL) sndVOL = SFX_VOL; SsUtKeyOn(vab_id, THUD_SOUND, 0, 48, 0, sndVOL, sndVOL); player1.vy = ((-player1.vy*3277)>>12); // bounce (*0.8) player1.vx = ((player1.vx*2956)>>12); // lose speed (/1.4) player1.py += player1.vy; player1.angle = 18; player1.analogangle = 4608; // 18*256 } else { player1.angle = 18; player1.analogangle = 4608; player1.vx = 0; player1.vy = 0; player1.landed = TRUE; } } else player1.landed = FALSE; } else { if (crashorland & 1 && !player1.invulnerable) val = 1; // Player 1 crashed else if ((crashorland & 2) && // on flat ground & upright (player1.angle == 35 || player1.angle == 0 || player1.angle == 1)) { if (player1.vx > 1024 || player1.vx < -1024 || player1.vy > 1024) { if ((sndVOL = (SFX_VOL*player1.vy)>>12) > SFX_VOL) sndVOL = SFX_VOL; SsUtKeyOn(vab_id, THUD_SOUND, 0, 48, 0, sndVOL, sndVOL); player1.vy = ((-player1.vy*3277)>>12); // bounce (*0.8) player1.vx = ((player1.vx*2956)>>12); // lose speed (/1.4) player1.py += player1.vy; player1.angle = 0; player1.analogangle = 0; } else { player1.angle = 0; player1.analogangle = 0; player1.vx = 0; player1.vy = 0; player1.landed = TRUE; } } else player1.landed = FALSE; } return (val); } // see above int UpdatePlayerTwo() { int val = 0; u_long sndVOL; crashorland = DrawPlayer(2); if (player2.reverseG) { if (crashorland & 1 && !player2.invulnerable) val = 2; // Player 2 crashed else if ((crashorland & 2) && // on flat ground & 'down'right (player2.angle == 17 || player2.angle == 18 || player2.angle == 19)) { if (player2.vx > 1024 || player2.vx < -1024 || player2.vy < -1024) { if ((sndVOL = (SFX_VOL*-player2.vy)>>12) > SFX_VOL) sndVOL = SFX_VOL; SsUtKeyOn(vab_id, THUD_SOUND, 0, 48, 0, sndVOL, sndVOL); player2.vy = ((-player2.vy*3277)>>12); // bounce (*0.8) player2.vx = ((player2.vx*2956)>>12); // lose speed (/1.4) player2.py += player2.vy; player2.angle = 18; player2.analogangle = 4608; // 18*256 } else { player2.angle = 18; player2.analogangle = 4608; player2.vx = 0; player2.vy = 0; player2.landed = TRUE; } } else player2.landed = FALSE; } else { if (crashorland & 1 && !player2.invulnerable) val = 2; // Player 2 crashed else if ((crashorland & 2) && // on flat ground & upright (player2.angle == 35 || player2.angle == 0 || player2.angle == 1)) { if (player2.vx > 1024 || player2.vx < -1024 || player2.vy > 1024) { if ((sndVOL = (SFX_VOL*player2.vy)>>12) > SFX_VOL) sndVOL = SFX_VOL; SsUtKeyOn(vab_id, THUD_SOUND, 0, 48, 0, sndVOL, sndVOL); player2.vy = ((-player2.vy*3277)>>12); // bounce (*0.8) player2.vx = ((player2.vx*2956)>>12); // lose speed (/1.4) player2.py += player2.vy; player2.angle = 0; player2.analogangle = 0; } else { player2.angle = 0; player2.analogangle = 0; player2.vx = 0; player2.vy = 0; player2.landed = TRUE; } } else player2.landed = FALSE; } return (val); } // Resets player to starting location (restores GetImage & saves new) void ResetPlayer(u_char player) { PLAYER *plr; POINT *strt; if (player == 1) { CLUT[19] = CLUT[17]; // Thrust flame off plr = &player1; strt = &StartP1; } else { CLUT[27] = CLUT[25]; // Thrust flame off plr = &player2; strt = &StartP2; } PutPlayerBKImage(player); plr->alive = TRUE; plr->thrust = FALSE; plr->reversethrust = FALSE; plr->x = plr->xold = strt->x; plr->y = plr->yold = strt->y; plr->px = (strt->x<<12); plr->py = (strt->y<<12); plr->vx = plr->vy = 0; plr->angle = 0; plr->invulnerable = 0; plr->mines = 0; plr->autofire = FALSE; plr->backshot = FALSE; plr->singleshot = FALSE; plr->laserfire = FALSE; plr->threewayfire = FALSE; plr->reversesteer = FALSE; plr->reverseG = FALSE; plr->highG = FALSE; plr->spiralshot = FALSE; SavePlayerBKImage(player); } // creates POD using random factors // tests for legal position // Stereo balance depends on POD location as a hint to it's location (mmm nice) void CreatePOD() { u_long balanceL, balanceR; // Sound balance RECT ninebynine; // Random looation to put POD, if not legal, ignore call pod.x = 8 + rand()%622; pod.y = 8 + rand()%494; // Set stereo sound balance if (pod.x < 315) { balanceL = SFX_VOL; balanceR = (SFX_VOL * (6+pod.x)) / 320; } else { balanceL = (SFX_VOL * (635-pod.x)) / 320; balanceR = SFX_VOL; } if (!GetPixel(pod.x+5, pod.y+5)) // Test pod centre pixel { // if non-zero, no POD if (GAME_TYPE) pod.type = rand() % NUM_PODS; // DOGFIGHT pods (all 15) else pod.type = rand() % (NUM_PODS-3) + 2; // RACE pods (2-13) pod.out = (AVERAGE_POD_LIFE>>1) + (rand() % AVERAGE_POD_LIFE); podarea.y = 256 + 9 * pod.type; ninebynine.x = pod.x + 384; ninebynine.y = pod.y; ninebynine.w = 9; ninebynine.h = 9; // Save POD area to 320,15 (9x9) MoveImage(&ninebynine, 320, 15); // Deploy sound effect SsUtKeyOn(vab_id, DEPLOY_SOUND, 0, 53, 0, balanceL, balanceR); } DrawSync(0); } // Tests if a player is within range of POD for collection, if // so do collection and return Boolean u_char PlayerHitPOD() { // Tests players' locations (P2 has priority if exactly the same frame. // Rare, but only fair, as player 2 normally gets the dud controller!) if (player2.playing && player2.alive) { if ((abs(pod.x - player2.x - 3) < 10 && abs(pod.y - player2.y - 3) < 10)) { CollectPOD(PLAYER2); return (TRUE); } } if (player1.alive) { if ((abs(pod.x - player1.x - 3) < 10 && abs(pod.y - player1.y - 3) < 10)) { CollectPOD(PLAYER1); return (TRUE); } } return (FALSE); } // player collects POD void CollectPOD(u_char player) { PLAYER *plr, *opp, *rplr; if (player == PLAYER1) { plr = &player1; opp = &player2; } else // player == PLAYER2 { plr = &player2; opp = &player1; } // Random player (1 in 6 chance of player getting bad pod themself) if (rand()%6) rplr = opp; else rplr = plr; pod.out = FALSE; // Pickup sound effect SsUtKeyOn(vab_id, PICKUP_SOUND, 0, 53, 0, SFX_VOL, SFX_VOL); switch (pod.type) { case 0 : if (plr->lives < MAX_LIVES) plr->lives++; SsUtKeyOn(vab_id, LIFEGAIN_SOUND, 0, 52, 0, SFX_VOL, SFX_VOL); break; case 1 : if (opp->playing) { if (opp->lives > 1) opp->lives--; SsUtKeyOn(vab_id, LIFELOSS_SOUND, 0, 52, 0, SFX_VOL, SFX_VOL); } break; case 2 : plr->laserfire = TRUE; break; case 3 : plr->threewayfire = TRUE; break; case 4 : plr->backshot = TRUE; break; case 5 : plr->autofire = TRUE; break; case 6 : plr->mines = MAX_MINES; break; case 7 : rplr->laserfire = FALSE; rplr->threewayfire = FALSE; rplr->backshot = FALSE; rplr->autofire = FALSE; rplr->mines = 0; rplr->spiralshot = FALSE; break; case 8 : rplr->singleshot = TRUE; break; case 9 : rplr->reversesteer = TRUE; break; case 10 : rplr->reverseG = TRUE; break; case 11 : rplr->highG = TRUE; break; case 12 : plr->singleshot = FALSE; plr->reversesteer = FALSE; plr->reverseG = FALSE; plr->highG = FALSE; break; case 13 : plr->spiralshot = TRUE; break; case 14 : plr->invulnerable = INVULN_LIFE; SsUtKeyOn(vab_id, LIFEGAIN_SOUND, 0, 48, 0, SFX_VOL, SFX_VOL); break; default : break; } } // Draws POD on virtual screen (and updates timer) void DrawPOD() { if (--pod.out) MoveImage(&podarea, pod.x + 384, pod.y); } // clears POD from virtual screen // by restoring backarea void ClearPOD() { MoveImage(&podsavearea, pod.x + 384, pod.y); } // Initialises bullets into first empty slot in bulletlist void FireBullet(u_char player) { u_long bul; long playerangle1, playerangle2; // Used with THREEWAYFIRE if (player == 1) { // Find empty slot in list for (bul=0; bul < MAX_BULLETS; bul++) { if (!P1Bullet[bul].out) { P1Bullet[bul].x = 28672 + player1.px + (SIN[player1.angle]<<3); P1Bullet[bul].y = 28672 + player1.py - (COS[player1.angle]<<3); P1Bullet[bul].vx = player1.vx + ((BulletPower * SIN[player1.angle])>>12); P1Bullet[bul].vy = player1.vy - ((BulletPower * COS[player1.angle])>>12); if (!(P1Bullet[bul].x < 0 || P1Bullet[bul].x > 2617344 // not outofBounds || P1Bullet[bul].y < 0 || P1Bullet[bul].y > 2093056)) { P1Bullet[bul].out = BULLET_LIFE; SsUtKeyOn(vab_id, SHOOT1_SOUND, 0, 53, 0, SFX_VOL, SFX_VOL); } break; // escape loop } if (player1.singleshot) break; } if (player1.threewayfire) // THREEWAYFIRE for (bul=0; bul < MAX_BULLETS-2; bul+=2) { if (!P1Bullet[bul].out && !P1Bullet[bul+1].out) { playerangle1 = player1.angle - 1; playerangle2 = player1.angle + 1; if (playerangle1 < 0) playerangle1 = 35; if (playerangle2 == 36) playerangle2 = 0; P1Bullet[bul].x = 28672 + player1.px + (SIN[playerangle1]<<3); P1Bullet[bul].y = 28672 + player1.py - (COS[playerangle1]<<3); P1Bullet[bul].vx = player1.vx + ((BulletPower * SIN[playerangle1])>>12); P1Bullet[bul].vy = player1.vy - ((BulletPower * COS[playerangle1])>>12); P1Bullet[bul+1].x = 28672 + player1.px + (SIN[playerangle2]<<3); P1Bullet[bul+1].y = 28672 + player1.py - (COS[playerangle2]<<3); P1Bullet[bul+1].vx = player1.vx + ((BulletPower * SIN[playerangle2])>>12); P1Bullet[bul+1].vy = player1.vy - ((BulletPower * COS[playerangle2])>>12); if (!(P1Bullet[bul].x < 0 || P1Bullet[bul].x > 2617344 // not outofBounds || P1Bullet[bul].y < 0 || P1Bullet[bul].y > 2093056 || P1Bullet[bul+1].x < 0 || P1Bullet[bul+1].x > 2617344 || P1Bullet[bul+1].y < 0 || P1Bullet[bul+1].y > 2093056)) { P1Bullet[bul].out = BULLET_LIFE; P1Bullet[bul+1].out = BULLET_LIFE; } break; // escape loop } if (player1.singleshot) break; } } else // if (player == 2) { // Find empty slot in list for (bul=0; bul < MAX_BULLETS; bul++) { if (!P2Bullet[bul].out) { P2Bullet[bul].out = BULLET_LIFE; P2Bullet[bul].x = 28672 + player2.px + (SIN[player2.angle]<<3); P2Bullet[bul].y = 28672 + player2.py - (COS[player2.angle]<<3); P2Bullet[bul].vx = player2.vx + ((BulletPower * SIN[player2.angle])>>12); P2Bullet[bul].vy = player2.vy - ((BulletPower * COS[player2.angle])>>12); if (!(P2Bullet[bul].x < 0 || P2Bullet[bul].x > 2617344 // not outofBounds || P2Bullet[bul].y < 0 || P2Bullet[bul].y > 2093056)) { P2Bullet[bul].out = BULLET_LIFE; SsUtKeyOn(vab_id, SHOOT1_SOUND, 0, 53, 0, SFX_VOL, SFX_VOL); } break; // escape loop } if (player2.singleshot) break; } if (player2.threewayfire) // THREEWAYFIRE for (bul=0; bul < MAX_BULLETS-2; bul+=2) { if (!P2Bullet[bul].out && !P2Bullet[bul+1].out) { playerangle1 = player2.angle - 1; playerangle2 = player2.angle + 1; if (playerangle1 < 0) playerangle1 = 35; if (playerangle2 == 36) playerangle2 = 0; P2Bullet[bul].x = 28672 + player2.px + (SIN[playerangle1]<<3); P2Bullet[bul].y = 28672 + player2.py - (COS[playerangle1]<<3); P2Bullet[bul].vx = player2.vx + ((BulletPower * SIN[playerangle1])>>12); P2Bullet[bul].vy = player2.vy - ((BulletPower * COS[playerangle1])>>12); P2Bullet[bul+1].x = 28672 + player2.px + (SIN[playerangle2]<<3); P2Bullet[bul+1].y = 28672 + player2.py - (COS[playerangle2]<<3); P2Bullet[bul+1].vx = player2.vx + ((BulletPower * SIN[playerangle2])>>12); P2Bullet[bul+1].vy = player2.vy - ((BulletPower * COS[playerangle2])>>12); if (!(P2Bullet[bul].x < 0 || P2Bullet[bul].x > 2617344 // not outofBounds || P2Bullet[bul].y < 0 || P2Bullet[bul].y > 2093056 || P2Bullet[bul+1].x < 0 || P2Bullet[bul+1].x > 2617344 || P2Bullet[bul+1].y < 0 || P2Bullet[bul+1].y > 2093056)) { P2Bullet[bul].out = BULLET_LIFE; P2Bullet[bul+1].out = BULLET_LIFE; } break; // escape loop } if (player2.singleshot) break; } } } // Fires special weapon(s) void FireSpecial(u_char player) { u_long bul, mine; if (player == 1) { if (player1.mines) { for (mine=0; mine < MAX_MINES; mine++) { // LAY A MINE if (!P1Mine[mine].out) { P1Mine[mine].x = 7 + player1.x - (SIN[player1.angle]>>9); P1Mine[mine].y = 7 + player1.y + (COS[player1.angle]>>9); // Test for viable placement if (!GetPixel(P1Mine[mine].x, P1Mine[mine].y)) { P1Mine[mine].out = MINE_LIFE; PutFBPixel(pix.x, pix.y, 2); // DRAW MINE // use this if player runs out: player1.mines--; SsUtKeyOn(vab_id, MINE_SOUND, 0, 47, 0, SFX_VOL, SFX_VOL); } break; // escape loop } if (player1.singleshot) break; } } if (player1.backshot) // BACKSHOT for (bul=0; bul < MAX_BULLETS; bul++) { if (!P1Bullet[bul].out) { P1Bullet[bul].x = 28672 + player1.px - ((20480 * SIN[player1.angle])>>12); P1Bullet[bul].y = 28672 + player1.py + ((20480 * COS[player1.angle])>>12); P1Bullet[bul].vx = player1.vx - ((BulletPower * SIN[player1.angle])>>12); P1Bullet[bul].vy = player1.vy + ((BulletPower * COS[player1.angle])>>12); if (!(P1Bullet[bul].x < 0 || P1Bullet[bul].x > 2617344 // not outofBounds || P1Bullet[bul].y < 0 || P1Bullet[bul].y > 2093056)) { P1Bullet[bul].out = BULLET_LIFE; SsUtKeyOn(vab_id, SHOOT2_SOUND, 0, 53, 0, SFX_VOL, SFX_VOL); } break; // escape loop } if (player1.singleshot) break; } } else // player == 2 { if (player2.mines) { for (mine=0; mine < MAX_MINES; mine++) { // LAY A MINE if (!P2Mine[mine].out) { P2Mine[mine].x = 7 + player2.x - (SIN[player2.angle]>>9); P2Mine[mine].y = 7 + player2.y + (COS[player2.angle]>>9); // Test for viable placement if (!GetPixel(P2Mine[mine].x, P2Mine[mine].y)) { P2Mine[mine].out = MINE_LIFE; PutFBPixel(pix.x, pix.y, 2); // DRAW MINE // use this if player runs out: player2.mines--; SsUtKeyOn(vab_id, MINE_SOUND, 0, 47, 0, SFX_VOL, SFX_VOL); } break; // escape loop } if (player2.singleshot) break; } } if (player2.backshot) // BACKSHOT for (bul=0; bul < MAX_BULLETS; bul++) { if (!P2Bullet[bul].out) { P2Bullet[bul].x = 28672 + player2.px - ((20480 * SIN[player2.angle])>>12); P2Bullet[bul].y = 28672 + player2.py + ((20480 * COS[player2.angle])>>12); P2Bullet[bul].vx = player2.vx - ((BulletPower * SIN[player2.angle])>>12); P2Bullet[bul].vy = player2.vy + ((BulletPower * COS[player2.angle])>>12); if (!(P2Bullet[bul].x < 0 || P2Bullet[bul].x > 2617344 // not outofBounds || P2Bullet[bul].y < 0 || P2Bullet[bul].y > 2093056)) { P2Bullet[bul].out = BULLET_LIFE; SsUtKeyOn(vab_id, SHOOT2_SOUND, 0, 53, 0, SFX_VOL, SFX_VOL); } break; // escape loop } if (player2.singleshot) break; } } } // Fires another fast bullet as a kind of laser void LaserFire(u_char player) { u_long bul; if (player == PLAYER1) { // Find empty slot in list for (bul=0; bul < MAX_BULLETS-1; bul+=2) { if (!P1Bullet[bul].out && !P1Bullet[bul+1].out) { P1Bullet[bul].x = 28672 + player1.px + ((27000 * SIN[player1.angle])>>12); P1Bullet[bul].y = 28672 + player1.py - ((27000 * COS[player1.angle])>>12); P1Bullet[bul].vx = player1.vx + (SIN[player1.angle]<<2); P1Bullet[bul].vy = player1.vy - (COS[player1.angle]<<2); if (!(P1Bullet[bul].x < 0 || P1Bullet[bul].x > 2617344 // not outofBounds || P1Bullet[bul].y < 0 || P1Bullet[bul].y > 2093056)) P1Bullet[bul].out = BULLET_LIFE; P1Bullet[bul+1].x = 28672 + player1.px + ((30000 * SIN[player1.angle])>>12); P1Bullet[bul+1].y = 28672 + player1.py - ((30000 * COS[player1.angle])>>12); P1Bullet[bul+1].vx = player1.vx + (SIN[player1.angle]<<2); P1Bullet[bul+1].vy = player1.vy - (COS[player1.angle]<<2); if (!(P1Bullet[bul+1].x < 0 || P1Bullet[bul+1].x > 2617344 // not outofBounds || P1Bullet[bul+1].y < 0 || P1Bullet[bul+1].y > 2093056)) P1Bullet[bul+1].out = BULLET_LIFE; break; // escape loop } if (player1.singleshot) break; } } else // player == PLAYER2 { // Find empty slot in list for (bul=0; bul < MAX_BULLETS-1; bul+=2) { if (!P2Bullet[bul].out && !P2Bullet[bul+1].out) { P2Bullet[bul].x = 28672 + player2.px + ((27000 * SIN[player2.angle])>>12); P2Bullet[bul].y = 28672 + player2.py - ((27000 * COS[player2.angle])>>12); P2Bullet[bul].vx = player2.vx + (SIN[player2.angle]<<2); P2Bullet[bul].vy = player2.vy - (COS[player2.angle]<<2); if (!(P2Bullet[bul].x < 0 || P2Bullet[bul].x > 2617344 // not outofBounds || P2Bullet[bul].y < 0 || P2Bullet[bul].y > 2093056)) P2Bullet[bul].out = BULLET_LIFE; P2Bullet[bul+1].x = 28672 + player2.px + ((30000 * SIN[player2.angle])>>12); P2Bullet[bul+1].y = 28672 + player2.py - ((30000 * COS[player2.angle])>>12); P2Bullet[bul+1].vx = player2.vx + (SIN[player2.angle]<<2); P2Bullet[bul+1].vy = player2.vy - (COS[player2.angle]<<2); if (!(P2Bullet[bul+1].x < 0 || P2Bullet[bul+1].x > 2617344 // not outofBounds || P2Bullet[bul+1].y < 0 || P2Bullet[bul+1].y > 2093056)) P2Bullet[bul+1].out = BULLET_LIFE; break; // escape loop } if (player2.singleshot) break; } } } // Updates and fires spiralshot void SpiralFire(u_char player) { u_long bul, angle; if (player == PLAYER1) { // Starts halfway up the list to reduce risk of normal bullet deficiency for (bul=MAX_BULLETS/2; bul < MAX_BULLETS; bul++) { if (!P1Bullet[bul].out) { angle = player1.spiralshot%36; P1Bullet[bul].x = 28672 + player1.px + ((24576 * SIN[angle])>>12); P1Bullet[bul].y = 28672 + player1.py - ((24576 * COS[angle])>>12); P1Bullet[bul].vx = player1.vx + ((12288 * SIN[angle])>>12); P1Bullet[bul].vy = player1.vy - ((12288 * COS[angle])>>12); if (!(P1Bullet[bul].x < 0 || P1Bullet[bul].x > 2617344 // not outofBounds || P1Bullet[bul].y < 0 || P1Bullet[bul].y > 2093056)) P1Bullet[bul].out = BULLET_LIFE; break; // escape loop } } player1.spiralshot++; } else // player == PLAYER2 { // Starts halfway up the list to reduce risk of normal bullet deficiency for (bul=1; bul < MAX_BULLETS; bul++) { if (!P2Bullet[bul].out) { angle = player2.spiralshot%36; P2Bullet[bul].x = 28672 + player2.px + ((24576 * SIN[angle])>>12); P2Bullet[bul].y = 28672 + player2.py - ((24576 * COS[angle])>>12); P2Bullet[bul].vx = player2.vx + ((12288 * SIN[angle])>>12); P2Bullet[bul].vy = player2.vy - ((12288 * COS[angle])>>12); if (!(P2Bullet[bul].x < 0 || P2Bullet[bul].x > 2617344 // not outofBounds || P2Bullet[bul].y < 0 || P2Bullet[bul].y > 2093056)) P2Bullet[bul].out = BULLET_LIFE; break; // escape loop } } player2.spiralshot++; } } // Clears all bullets from screen void ClearBullets() { int bul; // Set packet working base GsSetWorkBase((PACKET *)GpuPacketArea[0]); // Clear Ordering table GsClearOt(0, 0, &WorldOT[0]); // Clears bullet only if bullet is out and not new for (bul=0; bul < MAX_BULLETS; bul++) { if (P1Bullet[bul].out && P1Bullet[bul].out < BULLET_LIFE) { // PutFBPixel(P1Bullet[bul].xold + 384, P1Bullet[bul].yold, 0); P1BulletSprite[bul].attribute = 0x02000000; // 15bit direct P1BulletSprite[bul].x = (P1Bullet[bul].xold>>12) + 384; P1BulletSprite[bul].y = (P1Bullet[bul].yold>>12); GsSortFastSprite(&P1BulletSprite[bul], &WorldOT[0], 0); } if (P2Bullet[bul].out && P2Bullet[bul].out < BULLET_LIFE) { // PutFBPixel(P2Bullet[bul].xold + 384, P2Bullet[bul].yold, 0); P2BulletSprite[bul].attribute = 0x02000000; // 15bit direct P2BulletSprite[bul].x = (P2Bullet[bul].xold>>12) + 384; P2BulletSprite[bul].y = (P2Bullet[bul].yold>>12); GsSortFastSprite(&P2BulletSprite[bul], &WorldOT[0], 0); } } // Draw sprites in OT (to the virtual screen so no need to VSync() ) GsDrawOt(&WorldOT[0]); DrawSync(0); } // Draws all bullets on screen void DrawBullets() { int bul; // Set packet working base GsSetWorkBase((PACKET *)GpuPacketArea[0]); // Clear Ordering table GsClearOt(0, 0, &WorldOT[0]); for (bul=0; bul < MAX_BULLETS; bul++) { if (P1Bullet[bul].out) { P1Bullet[bul].x += P1Bullet[bul].vx; P1Bullet[bul].y += P1Bullet[bul].vy; if (P1Bullet[bul].x < 0 || P1Bullet[bul].x > 2617344 // Out of bounds || P1Bullet[bul].y < 0 || P1Bullet[bul].y > 2093056) P1Bullet[bul].out = FALSE; else { if (!GetPixel((P1Bullet[bul].x>>12), (P1Bullet[bul].y>>12))) { if (--P1Bullet[bul].out) { P1BulletSprite[bul].attribute = 0x02000040; // 15bit direct P1BulletSprite[bul].x = (P1Bullet[bul].x>>12) + 384; P1BulletSprite[bul].y = (P1Bullet[bul].y>>12); GsSortFastSprite(&P1BulletSprite[bul], &WorldOT[0], 0); P1Bullet[bul].xold = P1Bullet[bul].x; P1Bullet[bul].yold = P1Bullet[bul].y; } } else P1Bullet[bul].out = FALSE; } } if (P2Bullet[bul].out) { P2Bullet[bul].x += P2Bullet[bul].vx; P2Bullet[bul].y += P2Bullet[bul].vy; if (P2Bullet[bul].x < 0 || P2Bullet[bul].x > 2617344 // Out of bounds || P2Bullet[bul].y < 0 || P2Bullet[bul].y > 2093056) P2Bullet[bul].out = FALSE; else { if (!GetPixel((P2Bullet[bul].x>>12), (P2Bullet[bul].y>>12))) { if (--P2Bullet[bul].out) { P2BulletSprite[bul].attribute = 0x02000040; // 15bit direct P2BulletSprite[bul].x = (P2Bullet[bul].x>>12) + 384; P2BulletSprite[bul].y = (P2Bullet[bul].y>>12); GsSortFastSprite(&P2BulletSprite[bul], &WorldOT[0], 0); P2Bullet[bul].xold = P2Bullet[bul].x; P2Bullet[bul].yold = P2Bullet[bul].y; } } else P2Bullet[bul].out = FALSE; } } } // Draw sprites in OT (to the virtual screen so no need to VSync() ) GsDrawOt(&WorldOT[0]); DrawSync(0); } // Clears all mines from screen void ClearMines() { int mine; for (mine=0; mine < MAX_MINES; mine++) { if (P1Mine[mine].out) PutFBPixel(P1Mine[mine].x + 384, P1Mine[mine].y, 0); if (P2Mine[mine].out) PutFBPixel(P2Mine[mine].x + 384, P2Mine[mine].y, 0); } DrawSync(0); } // Updates all mine timers, detonate if zero void DrawMines() { int mine; for (mine=0; mine < MAX_MINES; mine++) { if (P1Mine[mine].out) if (--P1Mine[mine].out) PutFBPixel(P1Mine[mine].x + 384, P1Mine[mine].y, 2); else ExplodeMine(P1Mine[mine], PLAYER1); if (P2Mine[mine].out) if (--P2Mine[mine].out) PutFBPixel(P2Mine[mine].x + 384, P2Mine[mine].y, 2); else ExplodeMine(P2Mine[mine], PLAYER2); } DrawSync(0); } // Explodes a timed mine. Uses bullets out of player bulletlist void ExplodeMine(MINE playermine, u_char player) { int bul, i; short x, y; x = playermine.x; y = playermine.y; if (player == PLAYER1) { for (bul=0; bul < MAX_BULLETS-4; bul++) // 4 bullet shrapnel if (!P1Bullet[bul].out && !P1Bullet[bul+1].out && !P1Bullet[bul+2].out && !P1Bullet[bul+3].out) { for (i=0; i < 4; i++) { P1Bullet[bul+i].x = (x<<12); P1Bullet[bul+i].y = (y<<12); P1Bullet[bul+i].vx = (rand()>>1) - 8192; // random velocity P1Bullet[bul+i].vy = (rand()>>1) - 8192; // -8192 to 8192 units/sec P1Bullet[bul+i].out = BULLET_LIFE; } SsUtKeyOn(vab_id, MINEEXPL_SOUND, 0, 48, 0, SFX_VOL, SFX_VOL); break; // escape loop } } else // if (player == PLAYER2) { for (bul=0; bul < MAX_BULLETS-4; bul++) // 4 bullet shrapnel if (!P2Bullet[bul].out && !P2Bullet[bul+1].out && !P2Bullet[bul+2].out && !P2Bullet[bul+3].out) { for (i=0; i < 4; i++) { P2Bullet[bul+i].x = (x<<12); P2Bullet[bul+i].y = (y<<12); P2Bullet[bul+i].vx = (rand()>>1) - 8192; // random velocity P2Bullet[bul+i].vy = (rand()>>1) - 8192; // -8192 to 8192 units/sec P2Bullet[bul+i].out = BULLET_LIFE; } SsUtKeyOn(vab_id, MINEEXPL_SOUND, 0, 48, 0, SFX_VOL, SFX_VOL); break; // escape loop } } } // Test to see if a fire button has been pressed, if so return 1. // If the button was already pressed return 0. u_char FireButtonPressed(u_char player) { if (player == 1) { if (!(padStatus & PAD1FIRE)) return (P1BUTTONDOWN = FALSE); else { if (P1BUTTONDOWN == TRUE) return (FALSE); else return (P1BUTTONDOWN = TRUE); } } else // if (player == 2) { if (!(padStatus & PAD2FIRE)) return (P2BUTTONDOWN = FALSE); else { if (P2BUTTONDOWN == TRUE) return (FALSE); else return (P2BUTTONDOWN = TRUE); } } return (FALSE); } // Test to see if firespecial button has been pressed, if so return 1. // If the button was already pressed return 0. u_char FireSpecialPressed(u_char player) { if (player == 1) { if (!(padStatus & PAD1FIRESPECIAL)) return (P1SPECIALDOWN = FALSE); else { if (P1SPECIALDOWN == TRUE) return (FALSE); else return (P1SPECIALDOWN = TRUE); } } else // if (player == 2) { if (!(padStatus & PAD2FIRESPECIAL)) return (P2SPECIALDOWN = FALSE); else { if (P2SPECIALDOWN == TRUE) return (FALSE); else return (P2SPECIALDOWN = TRUE); } } return (FALSE); } // Initialises players/bullets/POD variables void InitPlayers(u_char num) { int bul; D_CACHE = (u_short *)D_CACHE_ADDR; // Pointer to the 1K D-Cache (~3ff) // used for fast collision detection player1.playing = TRUE; // Set splitLine position (y set below) splitline.x0 = 0; splitline.x1 = 319; splitline.r = 172; splitline.g = 172; splitline.b = 172; // If player2 is playing split screen, else full if (num == 2) { player2.playing = TRUE; if (VidMode) { P1scr.h = 128; P2scr.h = 128; maxyoffset = 384; splitline.y0 = 127; splitline.y1 = 127; } else { P1scr.h = 120; P2scr.h = 120; maxyoffset = 392; splitline.y0 = 119; splitline.y1 = 119; } if (SPLIT_LINE) splitLine = 1; else splitLine = 0; } else { player2.playing = FALSE; if (VidMode) { P1scr.h = 256; maxyoffset = 256; } else { P1scr.h = 240; maxyoffset = 272; } splitLine = 0; // regardless of SPLIT_LINE } if (GAME_TYPE == DOGFIGHT) { player1.lives = NUM_LIVES; player2.lives = NUM_LIVES; } else // Infinite lives in race mode { player1.lives = 1; player2.lives = 1; } player1.alive = TRUE; player2.alive = TRUE; player1.thrust = FALSE; player2.thrust = FALSE; player1.reversethrust = FALSE; player2.reversethrust = FALSE; player1.thrustsfx = FALSE; player2.thrustsfx = FALSE; player1.airbrake = FALSE; player2.airbrake = FALSE; player1.exploding = 0; player2.exploding = 0; player1.laserfire = FALSE; player2.laserfire = FALSE; player1.threewayfire = FALSE; player2.threewayfire = FALSE; player1.backshot = FALSE; player2.backshot = FALSE; player1.autofire = FALSE; player2.autofire = FALSE; player1.singleshot = FALSE; player2.singleshot = FALSE; player1.reversesteer = FALSE; player2.reversesteer = FALSE; player1.reverseG = FALSE; player2.reverseG = FALSE; player1.highG = FALSE; player2.highG = FALSE; player1.spiralshot = FALSE; player2.spiralshot = FALSE; player1.mines = 0; player2.mines = 0; player1.invulnerable = FALSE; player2.invulnerable = FALSE; player1.racing = FALSE; // N/A when gametype = dogfight player2.racing = FALSE; // True as soon as player takes off player1.gate = 1; player2.gate = 1; player1.lap = 0; player2.lap = 0; player1.racetime = 0; player2.racetime = 0; player1.xold = player1.x = StartP1.x; player1.yold = player1.y = StartP1.y; player2.xold = player2.x = StartP2.x; player2.yold = player2.y = StartP2.y; player1.px = (StartP1.x<<12); player1.py = (StartP1.y<<12); player2.px = (StartP2.x<<12); player2.py = (StartP2.y<<12); player1.vx = 0; player1.vy = 0; player2.vx = 0; player2.vy = 0; player1.angle = 0; // (0-35) 0 = up, 1 = 10 degrees to right player2.angle = 0; player1.analogangle = 0; player2.analogangle = 0; WINNER = DRAW; // Default win status // Resets bulletlist flags for (bul=0; bul < MAX_BULLETS; bul++) { P1Bullet[bul].out = FALSE; P2Bullet[bul].out = FALSE; } // Resets minelist flags for (bul=0; bul < MAX_MINES; bul++) { P1Mine[bul].out = FALSE; P2Mine[bul].out = FALSE; } // Sets pod out flag to FALSE (0 frames left) pod.out = FALSE; } // Toggle video mode PAL/NTSC void VidModeChange() { if (Init_Once) { VidMode = 1 - GetVideoMode(); ResetGraph(0); } else { // Initialize variables scr.x = 0; scr.y = 0; scr.w = 320; if (VidMode) scr.h = 256; else scr.h = 240; // Clear visible screen ClearImage(&scr, 0, 0, 0); DrawSync(0); // Set virtual screen area of frame buffer vir.x = 384; vir.y = 0; vir.w = 640; vir.h = 512; // Set both players screen widths (always 320) P1scr.w = 320; P2scr.w = 320; } if (VidMode) // PAL rate physics { GravityForce = GRAVITY_FORCE; EnginePower = ENGINE_POWER; BulletPower = BULLET_POWER; AirBrakePower = AIR_BRAKE_POWER; AirResistance = AIR_RESISTANCE; } else // NTSC rate physics { GravityForce = GRAVITY_FORCE * 2 / 3; EnginePower = ENGINE_POWER * 2 / 3; BulletPower = BULLET_POWER * 4 / 5; AirBrakePower = AIR_BRAKE_POWER * 5 / 6; AirResistance = AIR_RESISTANCE * 3 / 4; } SetVideoMode(VidMode); if (VidMode) { GsInitGraph(320, 256, GsOFSGPU, 0, 0); // Set display environment to allow PAL 256 lines GsDISPENV.screen.y = 20; // PAL video offset GsDISPENV.screen.h = 256; // MUST set to get 256 } else { GsInitGraph(320, 240, GsOFSGPU, 0, 0); GsDISPENV.screen.y = 0; // Set to default GsDISPENV.screen.h = 0; // values (0 & 240)? } VSync(0); GsSwapDispBuff(); Init_Once = TRUE; } // Load map mapnum from MAPADDR, & inits player map variables // Also creates the 1bit collision mask at mask_addr void MapLoad(u_char mapnum) { u_long maskoffset; u_long *MASK = (u_long *)MASK_ADDR; int i; u_short x = 0, y = 0; u_char pixel = 0; // Clear Virtual screen ClearImage(&vir, 0, 0, 0); // Clear 40k memory mask for (i=0; i < 10240; i++) MASK[i] = 0x00000000; // Clear 4 bytes (32 pixels) // Reset memory offset offset = 0; if (mapnum == 0) { ADDR = (u_char *)MAPADDR_01; if (GAME_TYPE == RACE) { StartP1.x = 49; StartP1.y = 473; StartP2.x = 72; StartP2.y = 473; } else { StartP1.x = 61; StartP1.y = 473; StartP2.x = 569; StartP2.y = 473; } } else if (mapnum == 1) { ADDR = (u_char *)MAPADDR_02; if (GAME_TYPE == RACE) { StartP1.x = 91; StartP1.y = 370; StartP2.x = 112; StartP2.y = 370; } else { StartP1.x = 101; StartP1.y = 370; StartP2.x = 532; StartP2.y = 120; } } else if (mapnum == 2) { ADDR = (u_char *)MAPADDR_03; if (GAME_TYPE == RACE) { StartP1.x = 432; StartP1.y = 230; StartP2.x = 458; StartP2.y = 230; } else { StartP1.x = 32; StartP1.y = 46; StartP2.x = 586; StartP2.y = 84; } } else if (mapnum == 3) { ADDR = (u_char *)MAPADDR_04; if (GAME_TYPE == RACE) { StartP1.x = 289; StartP1.y = 491; StartP2.x = 335; StartP2.y = 491; } else { StartP1.x = 119; StartP1.y = 171; StartP2.x = 574; StartP2.y = 292; } } else if (mapnum == 4) { ADDR = (u_char *)MAPADDR_05; if (GAME_TYPE == RACE) { StartP1.x = 23; StartP1.y = 486; StartP2.x = 42; StartP2.y = 486; } else { StartP1.x = 269; StartP1.y = 250; StartP2.x = 358; StartP2.y = 250; } } else if (mapnum == 5) { ADDR = (u_char *)MAPADDR_06; if (GAME_TYPE == RACE) { StartP1.x = 41; StartP1.y = 466; StartP2.x = 80; StartP2.y = 479; } else { StartP1.x = 75; StartP1.y = 241; StartP2.x = 273; StartP2.y = 406; } } else if (mapnum == 6) { ADDR = (u_char *)MAPADDR_07; if (GAME_TYPE == RACE) { StartP1.x = 38; StartP1.y = 482; StartP2.x = 65; StartP2.y = 482; } else { StartP1.x = 138; StartP1.y = 404; StartP2.x = 440; StartP2.y = 60; } } else if (mapnum == 7) { ADDR = (u_char *)MAPADDR_08; if (GAME_TYPE == RACE) { StartP1.x = 372; StartP1.y = 492; StartP2.x = 342; StartP2.y = 492; } else { StartP1.x = 539; StartP1.y = 488; StartP2.x = 54; StartP2.y = 489; } } else if (mapnum == 8) { ADDR = (u_char *)MAPADDR_09; if (GAME_TYPE == RACE) { StartP1.x = 105; StartP1.y = 262; StartP2.x = 199; StartP2.y = 262; } else { StartP1.x = 361; StartP1.y = 262; StartP2.x = 199; StartP2.y = 262; } } else // blank map { StartP1.x = 300; StartP1.y = 256; StartP2.x = 340; StartP2.y = 256; return; } do { pixel = ReadByte(); // read byte if (pixel) // non zero value { PutFBPixel(x+384, y, pixel); // draw dot // Sets pixel on collision mask maskoffset = x + (y<<9)+(y<<7); // = quicker than x+y*640 MASK[(maskoffset>>5)] |= (1<<(maskoffset%32)); // set bit if (++x == 640) { x=0; y++; } } else // zero - black line { pixel = ReadByte(); // read length of black line (must be non-zero) x+=pixel; if (x >= 640) { x-=640; // add onto x; end of scanline y++; } } } while ((x < 640) && (y < 512)); DrawSync(0); if (GAME_TYPE == RACE) CreateRaceGates(mapnum); } // Creates and draws the race gates on the virtual screen // and adds to the bullet collision mask map void CreateRaceGates(u_char mapnum) { u_long maskoffset; u_long *MASK = (u_long *)MASK_ADDR; short x, y; u_char gate; if (mapnum == 0) { GATE[0][0].x = 130; GATE[0][0].y = 250; GATE[0][1].x = 10; GATE[0][1].y = 250; GATE[1][0].x = 143; GATE[1][0].y = 144; GATE[1][1].x = 133; GATE[1][1].y = 76; GATE[2][0].x = 316; GATE[2][0].y = 374; GATE[2][1].x = 316; GATE[2][1].y = 300; GATE[3][0].x = 496; GATE[3][0].y = 144; GATE[3][1].x = 528; GATE[3][1].y = 80; GATE[4][0].x = 617; GATE[4][0].y = 250; GATE[4][1].x = 501; GATE[4][1].y = 250; GATE[5][0].x = 487; GATE[5][0].y = 356; GATE[5][1].x = 417; GATE[5][1].y = 432; GATE[6][0].x = 320; GATE[6][0].y = 211; GATE[6][1].x = 320; GATE[6][1].y = 140; GATE[7][0].x = 216; GATE[7][0].y = 432; GATE[7][1].x = 138; GATE[7][1].y = 360; } else if (mapnum == 1) { GATE[0][0].x = 624; GATE[0][0].y = 133; GATE[0][1].x = 552; GATE[0][1].y = 133; GATE[1][0].x = 88; GATE[1][0].y = 383; GATE[1][1].x = 8; GATE[1][1].y = 383; GATE[2][0].x = 518; GATE[2][0].y = 133; GATE[2][1].x = 428; GATE[2][1].y = 175; GATE[3][0].x = 208; GATE[3][0].y = 333; GATE[3][1].x = 122; GATE[3][1].y = 383; GATE[4][0].x = 295; GATE[4][0].y = 120; GATE[4][1].x = 295; GATE[4][1].y = 8; GATE[5][0].x = 420; GATE[5][0].y = 320; GATE[5][1].x = 335; GATE[5][1].y = 264; GATE[6][0].x = 298; GATE[6][0].y = 227; GATE[6][1].x = 240; GATE[6][1].y = 161; GATE[7][0].x = 180; GATE[7][0].y = 248; GATE[7][1].x = 8; GATE[7][1].y = 248; } else if (mapnum == 2) { GATE[0][0].x = 382; GATE[0][0].y = 62; GATE[0][1].x = 411; GATE[0][1].y = 4; GATE[1][0].x = 369; GATE[1][0].y = 246; GATE[1][1].x = 414; GATE[1][1].y = 245; GATE[2][0].x = 631; GATE[2][0].y = 400; GATE[2][1].x = 590; GATE[2][1].y = 401; GATE[3][0].x = 67; GATE[3][0].y = 425; GATE[3][1].x = 30; GATE[3][1].y = 393; GATE[4][0].x = 222; GATE[4][0].y = 58; GATE[4][1].x = 216; GATE[4][1].y = 6; GATE[5][0].x = 131; GATE[5][0].y = 222; GATE[5][1].x = 76; GATE[5][1].y = 233; GATE[6][0].x = 619; GATE[6][0].y = 313; GATE[6][1].x = 583; GATE[6][1].y = 297; GATE[7][0].x = 473; GATE[7][0].y = 137; GATE[7][1].x = 472; GATE[7][1].y = 101; } else if (mapnum == 3) { GATE[0][0].x = 355; GATE[0][0].y = 95; GATE[0][1].x = 295; GATE[0][1].y = 95; GATE[1][0].x = 377; GATE[1][0].y = 60; GATE[1][1].x = 377; GATE[1][1].y = 17; GATE[2][0].x = 571; GATE[2][0].y = 91; GATE[2][1].x = 505; GATE[2][1].y = 91; GATE[3][0].x = 463; GATE[3][0].y = 184; GATE[3][1].x = 404; GATE[3][1].y = 184; GATE[4][0].x = 624; GATE[4][0].y = 357; GATE[4][1].x = 539; GATE[4][1].y = 357; GATE[5][0].x = 233; GATE[5][0].y = 347; GATE[5][1].x = 246; GATE[5][1].y = 289; GATE[6][0].x = 68; GATE[6][0].y = 309; GATE[6][1].x = 8; GATE[6][1].y = 309; GATE[7][0].x = 196; GATE[7][0].y = 409; GATE[7][1].x = 196; GATE[7][1].y = 372; } else if (mapnum == 4) { GATE[0][0].x = 62; GATE[0][0].y = 52; GATE[0][1].x = 102; GATE[0][1].y = 12; GATE[1][0].x = 137; GATE[1][0].y = 451; GATE[1][1].x = 122; GATE[1][1].y = 398; GATE[2][0].x = 206; GATE[2][0].y = 42; GATE[2][1].x = 206; GATE[2][1].y = 2; GATE[3][0].x = 460; GATE[3][0].y = 293; GATE[3][1].x = 485; GATE[3][1].y = 247; GATE[4][0].x = 539; GATE[4][0].y = 53; GATE[4][1].x = 539; GATE[4][1].y = 6; GATE[5][0].x = 575; GATE[5][0].y = 502; GATE[5][1].x = 556; GATE[5][1].y = 451; GATE[6][0].x = 384; GATE[6][0].y = 316; GATE[6][1].x = 410; GATE[6][1].y = 279; GATE[7][0].x = 67; GATE[7][0].y = 124; GATE[7][1].x = 8; GATE[7][1].y = 124; } else if (mapnum == 5) { GATE[0][0].x = 515; GATE[0][0].y = 152; GATE[0][1].x = 440; GATE[0][1].y = 96; GATE[1][0].x = 328; GATE[1][0].y = 78; GATE[1][1].x = 328; GATE[1][1].y = 5; GATE[2][0].x = 210; GATE[2][0].y = 88; GATE[2][1].x = 101; GATE[2][1].y = 127; GATE[3][0].x = 438; GATE[3][0].y = 290; GATE[3][1].x = 365; GATE[3][1].y = 358; GATE[4][0].x = 464; GATE[4][0].y = 494; GATE[4][1].x = 423; GATE[4][1].y = 418; GATE[5][0].x = 200; GATE[5][0].y = 420; GATE[5][1].x = 161; GATE[5][1].y = 492; GATE[6][0].x = 256; GATE[6][0].y = 359; GATE[6][1].x = 168; GATE[6][1].y = 294; GATE[7][0].x = 420; GATE[7][0].y = 236; GATE[7][1].x = 348; GATE[7][1].y = 178; } else if (mapnum == 6) { GATE[0][0].x = 103; GATE[0][0].y = 421; GATE[0][1].x = 11; GATE[0][1].y = 421; GATE[1][0].x = 530; GATE[1][0].y = 337; GATE[1][1].x = 458; GATE[1][1].y = 337; GATE[2][0].x = 98; GATE[2][0].y = 246; GATE[2][1].x = 14; GATE[2][1].y = 246; GATE[3][0].x = 530; GATE[3][0].y = 152; GATE[3][1].x = 457; GATE[3][1].y = 152; GATE[4][0].x = 98; GATE[4][0].y = 76; GATE[4][1].x = 10; GATE[4][1].y = 76; GATE[5][0].x = 471; GATE[5][0].y = 65; GATE[5][1].x = 471; GATE[5][1].y = 9; GATE[6][0].x = 617; GATE[6][0].y = 246; GATE[6][1].x = 550; GATE[6][1].y = 246; GATE[7][0].x = 472; GATE[7][0].y = 432; GATE[7][1].x = 472; GATE[7][1].y = 487; } else if (mapnum == 7) { GATE[0][0].x = 534; GATE[0][0].y = 264; GATE[0][1].x = 472; GATE[0][1].y = 266; GATE[1][0].x = 563; GATE[1][0].y = 115; GATE[1][1].x = 518; GATE[1][1].y = 108; GATE[2][0].x = 375; GATE[2][0].y = 64; GATE[2][1].x = 360; GATE[2][1].y = 26; GATE[3][0].x = 359; GATE[3][0].y = 115; GATE[3][1].x = 325; GATE[3][1].y = 140; GATE[4][0].x = 284; GATE[4][0].y = 208; GATE[4][1].x = 288; GATE[4][1].y = 173; GATE[5][0].x = 112; GATE[5][0].y = 70; GATE[5][1].x = 118; GATE[5][1].y = 32; GATE[6][0].x = 152; GATE[6][0].y = 313; GATE[6][1].x = 124; GATE[6][1].y = 343; GATE[7][0].x = 402; GATE[7][0].y = 319; GATE[7][1].x = 384; GATE[7][1].y = 354; } else if (mapnum == 8) { GATE[0][0].x = 74; GATE[0][0].y = 72; GATE[0][1].x = 31; GATE[0][1].y = 31; GATE[1][0].x = 280; GATE[1][0].y = 64; GATE[1][1].x = 280; GATE[1][1].y = 5; GATE[2][0].x = 529; GATE[2][0].y = 31; GATE[2][1].x = 490; GATE[2][1].y = 77; GATE[3][0].x = 618; GATE[3][0].y = 250; GATE[3][1].x = 555; GATE[3][1].y = 250; GATE[4][0].x = 530; GATE[4][0].y = 473; GATE[4][1].x = 489; GATE[4][1].y = 430; GATE[5][0].x = 280; GATE[5][0].y = 499; GATE[5][1].x = 280; GATE[5][1].y = 440; GATE[6][0].x = 73; GATE[6][0].y = 431; GATE[6][1].x = 31; GATE[6][1].y = 473; GATE[7][0].x = 185; GATE[7][0].y = 294; GATE[7][1].x = 126; GATE[7][1].y = 294; } // ... // Draws 8 gate pairs (a type of POD), and adds to collision mask for (gate=0; gate < 8; gate++) { gaterect.y = 256 + 8 * gate; MoveImage (&gaterect, GATE[gate][0].x + 384, GATE[gate][0].y); MoveImage (&gaterect, GATE[gate][1].x + 384, GATE[gate][1].y); for (y=0; y < 8; y++) { for (x=0; x < 8; x++) { maskoffset = x+GATE[gate][0].x + ((y+GATE[gate][0].y)<<9)+((y+GATE[gate][0].y)<<7); MASK[(maskoffset>>5)] |= (1<<(maskoffset%32)); // set bit maskoffset = x+GATE[gate][1].x + ((y+GATE[gate][1].y)<<9)+((y+GATE[gate][1].y)<<7); MASK[(maskoffset>>5)] |= (1<<(maskoffset%32)); // set bit } } } DrawSync(0); } // Reads next byte from .MAP data u_char ReadByte() { return (ADDR[offset++]); } // Creates my own CLUT (256 pixels on frame buffer from 383,0 to 383,255) // 'y' element defines palette index // Then it draws the POD graphics at 320,15 (9xNUMPODS*9) // and winner.dat at 0,256 (228x75) // and player lives graphics at 320,239 (37x16) void InitFrameBuffer() { short i; u_long c; u_long *frontendscreen = (u_long *)FE_ADDR; RECT fedat; // Clear screens VSync(0); ClearImage(&scr, 0, 0, 0); ClearImage(&vir, 0, 0, 0); DrawSync(0); // Set clipping area to virtual screen GsSetClip2D(&vir); GsSetDrawBuffClip(); // Reset memory offset offset = 0; // These are permantly set pix.w = 1; pix.h = 1; tmp.w = 15; tmp.h = 15; Clut.x = 383; Clut.y = 0; Clut.w = 1; Clut.h = 1; podarea.x = 375; podarea.y = 256; // variable, use 256 + 9 * (POD-1) podarea.w = 9; podarea.h = 9; podsavearea.x = 320; podsavearea.y = 15; podsavearea.w = 9; podsavearea.h = 9; gaterect.x = 367; gaterect.y = 256; // variable, use 256 + 9 * (gate-1) gaterect.w = 8; gaterect.h = 8; // DRAW CLUT at (383,0) - (383,255) & initialise 16-bit CLUT for (i=0; i < NUM_COLOURS; i++) { c = (PALETTE[i*3]>>3) + (PALETTE[i*3+1]<<2) + (PALETTE[i*3+2]<<7); // RGB elements LoadImage(&Clut, &c); // Plot pixel Clut.y++; CLUT[i] = (short)c; } DrawSync(0); // Loads graphics data from main memory to frame buffer fedat.x = 0; fedat.y = 256; fedat.w = 384; fedat.h = 196; LoadImage (&fedat, frontendscreen); DrawSync(0); } // *** Note about putpixels: *** // I have found it to be about 10% faster to use this PutFBPixel // than using ClearImage(&apixelrect, r, g, b) even though it // calls the function and assigns a variable. // A single call to Clear/Load/Move/SaveImage seems to take at LEAST // one whole HSYNC, even when it's just a pixel. This does not matter // as it is only used to draw the map, ie not in the game. PutPixels // are done in the D-Cache, and bullets are sprites for speed. // The ships are drawn manually in the D-Cache for accurate collision // detection, ie NOT GsSPRITES, (but only uses two Load/StoreImages for // each ship, so still quick) // PutFBPixel that puts a single pixel onto the frame buffer // Used only in MapLoad() void PutFBPixel(short x, short y, u_char col) { // Sets CLUT index to colour Clut.y = col; // Virtual screen coordinates MoveImage(&Clut, x, y); } // GetPixel returns TRUE or FALSE if map pixel colour is non-zero // 'register' attempts to use a very quick register for the variables u_char GetPixel(short x, short y) { register u_long maskoffset = x+(y<<9)+(y<<7); // = quicker than x + y * 640 register u_long *MASK = (u_long *)MASK_ADDR; if ((MASK[(maskoffset>>5)]) & (1<<(maskoffset%32))) return (TRUE); else return (FALSE); } // Puts a 16bit pixel onto 15x15 image on the D_CACHE // Uses an assertion to detect overshooting 1K limit (into program space) void PutDCPixel(short x, short y, u_char col) { Assert(x+y*15 < 1024); // Safety assertion D_CACHE[(x-y+(y<<4))] = CLUT[col]; // x + y * 15; } // Gets a boolean pixel result from 15x15 image on the D_CACHE // used for pixel-pixel collision detection // Uses an assertion to detect overshooting 1K limit (into program space) u_char GetDCPixel(short x, short y) { Assert(x+y*15 < 1024); // Safety assertion if (D_CACHE[(x-y+(y<<4))]) // x + y * 15; return (TRUE); else return (FALSE); } // Saves area where player will be drawn for restoration at next frame void SavePlayerBKImage(u_char player) { if (player == 1) { tmp.x = player1.x + 384; tmp.y = player1.y; MoveImage(&tmp, 320, 0); } else { tmp.x = player2.x + 384; tmp.y = player2.y; MoveImage(&tmp, 335, 0); } DrawSync(0); } // Restores a saved image area void PutPlayerBKImage(u_char player) { if (player == 1) { tmp.x = 320; tmp.y = 0; MoveImage(&tmp, player1.xold + 384, player1.yold); } else // if (player == 2) { tmp.x = 335; tmp.y = 0; MoveImage(&tmp, player2.xold + 384, player2.yold); } DrawSync(0); } // Draws explosion cell at players position // Uses playerX.exploding to determine frame cell // and x and y values for coords // If last frame (exploding=1) it // returns TRUE flag: player has lost a life u_char Explode(u_char player) { short x, y; // Pointer to sprite data ADDR = (u_char *)SPR_ADDR; // Reset memory offset offset = 0; if (player == 1) { if (--player1.exploding) { if (player1.exploding > DEATH_PAUSE - 18) { tmp.x = player1.x + 384; tmp.y = player1.y; // STORE PLAYER BACKIMAGE ON D-CACHE StoreImage(&tmp, (u_long *)D_CACHE_ADDR); offset = 4320 + 240 * (DEATH_PAUSE - player1.exploding); // DRAW EXPLOSION CELL ON D-CACHE for (y=0; y < 15; y++) for (x=0; x < 15; x++) { if ((SPRpixel = ReadByte())) PutDCPixel(x, y, SPRpixel); } // RESTORE PLAYER BACKIMAGE FROM D-CACHE LoadImage(&tmp, (u_long *)D_CACHE_ADDR); } } else return (TRUE); } else // if (player == 2) { if (--player2.exploding) { if (player2.exploding > DEATH_PAUSE - 18) { tmp.x = player2.x + 384; tmp.y = player2.y; // STORE PLAYER BACKIMAGE ON D-CACHE StoreImage(&tmp, (u_long *)D_CACHE_ADDR); offset = 4320 + 240 * (DEATH_PAUSE - player2.exploding); // DRAW EXPLOSION CELL ON D-CACHE for (y=0; y < 15; y++) for (x=0; x < 15; x++) { if ((SPRpixel = ReadByte())) PutDCPixel(x, y, SPRpixel); } // RESTORE PLAYER BACKIMAGE FROM D-CACHE LoadImage(&tmp, (u_long *)D_CACHE_ADDR); } } else return (TRUE); } return (FALSE); } // Physically draws a player on the virtual screen at x, y // returns collision info. Puts an identical copy of the PlayerBKImage // from the frame buffer into the D-Cache for manipulation // and then copies to virtual screen // Returns bit1 if collided, bit 2 (&2) if ship is onflatground u_char DrawPlayer(u_char player) { u_char collided = FALSE, onflatground = FALSE; short x, y; // Pointer to sprite data ADDR = (u_char *)SPR_ADDR; // Reset memory offset offset = 0; if (player == 1) { tmp.x = player1.x + 384; tmp.y = player1.y; // STORE PLAYER BACKIMAGE ON D-CACHE StoreImage(&tmp, (u_long *)D_CACHE_ADDR); // Looks at image to see if landing is possible if (!player1.invulnerable) onflatground = TestForFlatGround(player1.reverseG); // DRAW SHIP ON DCACHE WITH COLLISION DETECTION RETURN if (player1.angle >= 0 && player1.angle <= 8) { offset = player1.angle * 240; for (y=0; y < 15; y++) for (x=0; x < 15; x++) { if ((SPRpixel = ReadByte())) { if (!GetDCPixel(x, y)) PutDCPixel(x, y, SPRpixel); else collided = TRUE; } } } else if (player1.angle >= 9 && player1.angle <= 17) { offset = (player1.angle - 9) * 240; for (x=14; x >= 0; x--) for (y=0; y < 15; y++) { if ((SPRpixel = ReadByte())) { if (!GetDCPixel(x, y)) PutDCPixel(x, y, SPRpixel); else collided = TRUE; } } } else if (player1.angle >= 18 && player1.angle <= 26) { offset = (player1.angle - 18) * 240; for (y=14; y >= 0; y--) for (x=14; x >= 0; x--) { if ((SPRpixel = ReadByte())) { if (!GetDCPixel(x, y)) PutDCPixel(x, y, SPRpixel); else collided = TRUE; } } } else if (player1.angle > 27 && player1.angle <= 35) { offset = (36 - player1.angle) * 240; for (y=0; y < 15; y++) for (x=14; x >= 0; x--) { if ((SPRpixel = ReadByte())) { if (!GetDCPixel(x, y)) PutDCPixel(x, y, SPRpixel); else collided = TRUE; } } } else if (player1.angle == 27) { offset = 0; for (x=0; x < 15; x++) for (y=14; y >= 0; y--) { if ((SPRpixel = ReadByte())) { if (!GetDCPixel(x, y)) PutDCPixel(x, y, SPRpixel); else collided = TRUE; } } } // RESTORE PLAYER BACKIMAGE FROM D-CACHE LoadImage(&tmp, (u_long *)D_CACHE_ADDR); } else // if (player == 2) { tmp.x = player2.x + 384; tmp.y = player2.y; // STORE PLAYER BACKIMAGE ON D-CACHE StoreImage(&tmp, (u_long *)D_CACHE_ADDR); // Looks at image to see if landing is possible if (!player2.invulnerable) onflatground = TestForFlatGround(player2.reverseG); // DRAW SHIP ON DCACHE WITH COLLISION DETECTION RETURN if (player2.angle >= 0 && player2.angle <= 8) { offset = 2160 + player2.angle * 240; for (y=0; y < 15; y++) for (x=0; x < 15; x++) { if ((SPRpixel = ReadByte())) { if (!GetDCPixel(x, y)) PutDCPixel(x, y, SPRpixel); else collided = TRUE; } } } else if (player2.angle >= 9 && player2.angle <= 17) { offset = 2160 + (player2.angle - 9) * 240; for (x=14; x >= 0; x--) for (y=0; y < 15; y++) { if ((SPRpixel = ReadByte())) { if (!GetDCPixel(x, y)) PutDCPixel(x, y, SPRpixel); else collided = TRUE; } } } else if (player2.angle >= 18 && player2.angle <= 26) { offset = 2160 + (player2.angle - 18) * 240; for (y=14; y >= 0; y--) for (x=14; x >= 0; x--) { if ((SPRpixel = ReadByte())) { if (!GetDCPixel(x, y)) PutDCPixel(x, y, SPRpixel); else collided = TRUE; } } } else if (player2.angle > 27 && player2.angle <= 35) { offset = 2160 + (36 - player2.angle) * 240; for (y=0; y < 15; y++) for (x=14; x >= 0; x--) { if ((SPRpixel = ReadByte())) { if (!GetDCPixel(x, y)) PutDCPixel(x, y, SPRpixel); else collided = TRUE; } } } else if (player2.angle == 27) { offset = 2160; for (x=0; x < 15; x++) for (y=14; y >= 0; y--) { if ((SPRpixel = ReadByte())) { if (!GetDCPixel(x, y)) PutDCPixel(x, y, SPRpixel); else collided = TRUE; } } } // RESTORE PLAYER BACKIMAGE FROM D-CACHE LoadImage(&tmp, (u_long *)D_CACHE_ADDR); } return (collided + (onflatground<<1)); // Return info } // Tests the D_CACHE graphic for flat ground // (non-zero pixels under landing pads of ship) u_char TestForFlatGround(u_char reverseG) { u_char flat = FALSE; if (reverseG) { if (D_CACHE[17] && D_CACHE[27]) flat = TRUE; } else { if (D_CACHE[197] && D_CACHE[207]) flat = TRUE; } return (flat); } // Initialises on-screen sprites (clock, lives etc.) void InitOnscreenStuff() { short i; if (VidMode) { ypos1 = 117 - splitLine; ypos2 = 240; } else { ypos1 = 109; ypos2 = 224; } // Resets all digits (avoids actually seeing the clocks reset) P1Time[0].u = 0; P2Time[0].u = 0; P1Time[2].u = 0; P2Time[2].u = 0; P1Time[3].u = 0; P2Time[3].u = 0; P1Time[5].u = 0; P2Time[5].u = 0; P1Time[6].u = 0; P2Time[6].u = 0; DemoSprite.y = ypos2+2; if (player2.playing) { P1lives.y = ypos1; P2lives.y = ypos2; for (i = 0; i < 7; i++) { // timer y-location (2 player) P1Time[i].y = ypos1; P2Time[i].y = ypos2; } P1LapSprite.y = ypos1+2; P2LapSprite.y = ypos2+2; P1GateSprite[0].y = ypos1+2; P1GateSprite[1].y = ypos1+2; P2GateSprite[0].y = ypos2+2; P2GateSprite[1].y = ypos2+2; } else // 1-player { P1lives.y = ypos2; for (i = 0; i < 7; i++) // timer y-location (1 player) P1Time[i].y = ypos2; P1LapSprite.y = ypos2+2; P1GateSprite[0].y = ypos2+2; P1GateSprite[1].y = ypos2+2; } } // Draws the on-screen lives count, invulnerability timer bar, race time etc. // *** WARNING: This function decrements the players lives count for speed, // but post-increments them back *** void DrawOnscreenStuff() { short i; // Set clipping area to visible screen GsSetClip2D(&scr); GsSetDrawBuffClip(); // Set packet working base GsSetWorkBase((PACKET *)GpuPacketArea[0]); // Clear Ordering table GsClearOt(0, 0, &WorldOT[0]); if (GAME_TYPE == DOGFIGHT) { // Set sprite width to numlives if has lives if (player1.lives) P1lives.w = 1 + 6 * (--player1.lives); if (player2.lives) P2lives.w = 1 + 6 * (--player2.lives); // Copy Lives areas to visible screen if (player2.playing) { if (player1.lives++) { P1lives.x = 314 - 6 * player1.lives; GsSortFastSprite(&P1lives, &WorldOT[0], 0); } if (player2.lives++) { P2lives.x = 314 - 6 * player2.lives; GsSortFastSprite(&P2lives, &WorldOT[0], 0); } } else // single-player game { if (player1.lives++) { P1lives.x = 314 - 6 * player1.lives; GsSortFastSprite(&P1lives, &WorldOT[0], 0); } } } else // (GAME_TYPE == RACE) { // DRAW TIME/LAP/GATE INFO if (player1.racetime < 30000) { for (i = 0; i < 7; i++) GsSortFastSprite(&P1Time[i], &WorldOT[0], 0); } P1LapSprite.u = 20 * player1.lap; // Lap sprite offset GsSortFastSprite(&P1LapSprite, &WorldOT[0], 0); P1GateSprite[1].u = 56 + 6 * (player1.gate-1); // Gate sprite offset if (player1.racing) { GsSortFastSprite(&P1GateSprite[0], &WorldOT[0], 0); GsSortFastSprite(&P1GateSprite[1], &WorldOT[0], 0); } if (player2.playing) { if (player2.racetime < 30000) { for (i = 0; i < 7; i++) GsSortFastSprite(&P2Time[i], &WorldOT[0], 0); } P2LapSprite.u = 20 * player2.lap; // Lap sprite offset GsSortFastSprite(&P2LapSprite, &WorldOT[0], 0); P2GateSprite[1].u = 56 + 6 * (player2.gate-1); // Gate sprite offset if (player2.racing) { GsSortFastSprite(&P2GateSprite[0], &WorldOT[0], 0); GsSortFastSprite(&P2GateSprite[1], &WorldOT[0], 0); } } } if (player1.invulnerable) { if (player2.playing) RedBox(12, ypos1 + 3, (player1.invulnerable>>1)+1, 5); else RedBox(12, ypos2 + 3, (player1.invulnerable>>1)+1, 5); } if (player2.invulnerable) { RedBox(12, ypos2 + 3, (player2.invulnerable>>1)+1, 5); } if (DEMO_MODE) // sort "DEMO" sprite GsSortFastSprite(&DemoSprite, &WorldOT[0], 0); // Draws grey line across centre if required if (splitLine && player2.playing) GsSortLine(&splitline, &WorldOT[0], 0); // Draw sprites in OT GsDrawOt(&WorldOT[0]); // Restore clipping area to virtual screen GsSetClip2D(&vir); GsSetDrawBuffClip(); DrawSync(0); } // At end of game display congratulation message void CongratulateWinner() { int timer = 0; u_char quit = FALSE; GsSPRITE scrL, scrR; GsSPRITE winnerSPR, playerSPR, plrnumSPR, drawgameSPR; GsSPRITE newrecordSPR; // Copies final screen for display as sprites MoveImage(&scr, 384, 256); // Copy to second buffer too to avoid initial flicker MoveImage(&scr, 384, 0); DrawSync(0); VSync(0); // Set to Double Buffering GsDefDispBuff(0, 0, 384, 0); GsSwapDispBuff(); VSync(0); // Set clipping area to visible screen GsSetClip2D(&scr); GsSetDrawBuffClip(); VSync(0); if (QUIT_GAME) return; // Ignore winner if chose // Setup backimage sprites scrL.attribute = scrR.attribute = 0x02000000; scrL.x = 160; scrR.x = 160; scrL.w = scrR.w = 160; scrL.mx = 160; scrR.mx = 0; winnerSPR.attribute = playerSPR.attribute = plrnumSPR.attribute = drawgameSPR.attribute = 0x02000000; winnerSPR.w = playerSPR.w = 148; plrnumSPR.w = 80; drawgameSPR.w = 228; winnerSPR.h = playerSPR.h = plrnumSPR.h = drawgameSPR.h = 25; winnerSPR.u = playerSPR.u = drawgameSPR.u = 0; plrnumSPR.u = 148; winnerSPR.v = 58; if (WINNER == PLAYER2) plrnumSPR.v = 83; else plrnumSPR.v = 58; playerSPR.v = 83; drawgameSPR.v = 108; winnerSPR.mx = 74; playerSPR.mx = 74; plrnumSPR.mx = 40; drawgameSPR.mx = 114; winnerSPR.my = playerSPR.my = plrnumSPR.my = drawgameSPR.my = 13; winnerSPR.tpage = playerSPR.tpage = plrnumSPR.tpage = drawgameSPR.tpage = GetTPage(2, 0, 0, 256); winnerSPR.r = winnerSPR.g = winnerSPR.b = 128; playerSPR.r = playerSPR.g = playerSPR.b = 128; plrnumSPR.r = plrnumSPR.g = plrnumSPR.b = 128; drawgameSPR.r = drawgameSPR.g = drawgameSPR.b = 128; winnerSPR.scalex = winnerSPR.scaley = playerSPR.scalex = playerSPR.scaley = plrnumSPR.scalex = plrnumSPR.scaley = drawgameSPR.scalex = drawgameSPR.scaley = 0; winnerSPR.rotate = winnerSPR.rotate = playerSPR.rotate = playerSPR.rotate = plrnumSPR.rotate = plrnumSPR.rotate = drawgameSPR.rotate = drawgameSPR.rotate = 0; winnerSPR.x = 160; playerSPR.x = 115; plrnumSPR.x = 235; drawgameSPR.x = 160; if (VidMode) { scrL.h = scrR.h = 256; scrL.y = 128; scrR.y = 128; scrL.my = 128; scrR.my = 128; winnerSPR.y = 112; playerSPR.y = 144; plrnumSPR.y = 144; drawgameSPR.y = 128; } else { scrL.h = scrR.h = 240; scrL.y = 120; scrR.y = 120; scrL.my = 120; scrR.my = 120; winnerSPR.y = 104; playerSPR.y = 136; plrnumSPR.y = 136; drawgameSPR.y = 120; } scrL.tpage = GetTPage(2, 0, 384, 256); scrR.tpage = GetTPage(2, 0, 544, 256); scrL.u = 0; scrL.v = 0; scrR.u = 32; scrR.v = 0; scrL.r = scrL.g = scrL.b = scrR.r = scrR.g = scrR.b = 128; scrL.scalex = scrL.scaley = scrR.scalex = scrR.scaley = ONE; scrL.rotate = scrR.rotate = 0; newrecordSPR.attribute = 0x02000000; newrecordSPR.x = 8; // y set below (depends on winner, ie ypos1/ypos2 newrecordSPR.w = 50; newrecordSPR.h = 7; newrecordSPR.tpage = GetTPage(2, 0, 256, 256); newrecordSPR.u = 38; newrecordSPR.v = 8; newrecordSPR.mx = 0; newrecordSPR.my = 0; newrecordSPR.r = newrecordSPR.g = newrecordSPR.b = 128; newrecordSPR.scalex = newrecordSPR.scaley = ONE; newrecordSPR.rotate = 0; // BEAT RECORD TIME? if (WINNER == PLAYER2) { if (player2.lap == 4 && player2.racetime < Record[MAP_NUM].racetime) { Record[MAP_NUM].racetime = player2.racetime; Record[MAP_NUM].i[0] = DefaultInitials[1][0]; Record[MAP_NUM].i[1] = DefaultInitials[1][1]; Record[MAP_NUM].i[2] = DefaultInitials[1][2]; ENTER_NAME = TRUE; NEW_RECORD = PLAYER2; } } else // WINNER == PLAYER1 or DRAW { if (player1.lap == 4 && player1.racetime < Record[MAP_NUM].racetime) { Record[MAP_NUM].racetime = player1.racetime; Record[MAP_NUM].i[0] = DefaultInitials[0][0]; Record[MAP_NUM].i[1] = DefaultInitials[0][1]; Record[MAP_NUM].i[2] = DefaultInitials[0][2]; ENTER_NAME = TRUE; NEW_RECORD = PLAYER1; } } // Congratulation loop. Waits for a keypress after 4 seconds while (1) // Exits infinite loop using break { // Exit with any key only after 2 seconds if (timer > 100) if ((padStatus = PadRead())) quit = TRUE; activeBuff = GsGetActiveBuff(); // Set packet work base GsSetWorkBase((PACKET *)GpuPacketArea[activeBuff]); // Clear the Ordering Table GsClearOt(0, 0, &WorldOT[activeBuff]); if (quit) // Shrink all sprites { if (scrL.scalex == ONE) // Re-copies visible to backsprite, MoveImage(&scr, 384, 256); // so winner info shrinks with screen if (scrL.scalex > 1) { scrL.scalex -= 64; scrL.scaley -= 64; scrR.scalex -= 64; scrR.scaley -= 64; } else break; // Exit while loop } else { if (NEW_RECORD) { if (NEW_RECORD == PLAYER2 || !player2.playing) newrecordSPR.y = ypos2 - 8; else newrecordSPR.y = ypos1 - 8; GsSortFastSprite(&newrecordSPR, &WorldOT[activeBuff], 0); } if (WINNER && player2.playing) // Put sprites in OT { if (timer < 8) { winnerSPR.scalex += 512; winnerSPR.scaley += 512; playerSPR.scalex += 512; playerSPR.scaley += 512; plrnumSPR.scalex += 512; plrnumSPR.scaley += 512; } else if (timer == 8) SsUtKeyOn(vab_id, EXPLODE_SOUND, 0, 42, 0, SFX_VOL, SFX_VOL); else if (timer == 9) SsUtKeyOn(vab_id, EXPLODE_SOUND, 0, 42, 0, SFX_VOL, SFX_VOL); GsSortSprite(&plrnumSPR, &WorldOT[activeBuff], 0); GsSortSprite(&playerSPR, &WorldOT[activeBuff], 0); GsSortSprite(&winnerSPR, &WorldOT[activeBuff], 0); } else if (player2.playing) // Draw game { if (timer < 8) { drawgameSPR.scalex += 512; drawgameSPR.scaley += 512; } else if (timer == 8) SsUtKeyOn(vab_id, EXPLODE_SOUND, 0, 42, 0, SFX_VOL, SFX_VOL); else if (timer == 9) SsUtKeyOn(vab_id, EXPLODE_SOUND, 0, 42, 0, SFX_VOL, SFX_VOL); GsSortSprite(&drawgameSPR, &WorldOT[activeBuff], 0); } } GsSortSprite(&scrR, &WorldOT[activeBuff], 0); GsSortSprite(&scrL, &WorldOT[activeBuff], 0); // Wait for end of drawing and a vertical blank DrawSync(0); VSync(0); GsSwapDispBuff(); // Register clear to black in OT GsSortClear(0, 0, 0, &WorldOT[activeBuff]); // Draw sprites in OT GsDrawOt(&WorldOT[activeBuff]); timer++; } // Clear screen VSync(0); ClearImage(&scr, 0, 0, 0); DrawSync(0); } // Main game Front-End // uses a timer to set SRAND seed value void GameSelectionScreen() { GsSPRITE gravita, tion; GsSPRITE v1, byme; GsGLINE bg; // Background image (320 vertical gradation lines) GsBOXF sfxvol, cdvol; // Boxes for sfx/cd volume adjust int i, j, k; u_int seed = 0; u_long demo_timer = 1; // Times length of inactivity (DEMO_PAUSE) u_long pad = NULL; // Temporary padStatus var u_long padtimer = 0; // used in key repeat u_char cdplay = CD_PLAYING; u_char gametype = GAME_TYPE_BAK; u_char map = MAP_NUM_BAK; // restore settings u_char players = NUM_PLAYERS; u_char pods_on = PODS_ON; u_char weapons_on = WEAPONS_ON; int menuitem = 0; int menucolumn = 0; int initialnum = 0; // Name entry initial # // Set clipping area to visible screen GsSetClip2D(&scr); GsSetDrawBuffClip(); // Init volume boxes sfxvol.attribute = cdvol.attribute = 0; sfxvol.x = 190; sfxvol.y = 142; cdvol.x = 190; cdvol.y = 158; sfxvol.w = 1 + (SFX_VOL>>1); cdvol.w = 1 + (CD_VOL>>1); sfxvol.h = cdvol.h = 4; sfxvol.r = 240; sfxvol.g = sfxvol.b = 0; cdvol.r = 240; cdvol.g = cdvol.b = 0; // Init Sprites gravita.attribute = tion.attribute = v1.attribute = byme.attribute = 0x02000000; gravita.x = tion.x = 910; if (VidMode) gravita.y = tion.y = 128; else gravita.y = tion.y = 120; gravita.w = 185; tion.w = 108; gravita.h = tion.h = 29; v1.x = 283; v1.y = 40; byme.x = 125; v1.w = 26; byme.w = 183; v1.h = byme.h = 11; gravita.tpage = tion.tpage = v1.tpage = byme.tpage = GetTPage(2, 0, 0, 256); gravita.u = tion.u = 0; gravita.v = 0; tion.v = 29; v1.u = 0; byme.u = 32; v1.v = byme.v = 133; gravita.r = gravita.g = gravita.b = 128; tion.r = tion.g = tion.b = 128; v1.r = v1.g = v1.b = byme.r = byme.g = byme.b = 128; gravita.mx = 146; gravita.my = 14; tion.mx = -39; tion.my = 14; v1.mx = v1.my = byme.mx = byme.my = 0; gravita.scalex = gravita.scaley = 16384; tion.scalex = tion.scaley = 16384; gravita.rotate = tion.rotate = 0; v1.scalex = v1.scaley = byme.scalex = byme.scaley = ONE; v1.rotate = byme.rotate = 0; // Init background image (blue fade) bg.attribute = 0; bg.x0 = 0; bg.x1 = 0; bg.y0 = 0; bg.y1 = 255; bg.b0 = 0; bg.b1 = 128; bg.r0 = bg.g0 = bg.r1 = bg.g1 = 0; // Clear screens VSync(0); ClearImage(&scr, 0, 0, 0); ClearImage(&vir, 0, 0, 0); DrawSync(0); // Pause for 1 second if (!QUIT_GAME) for (i=0; i < 50; i++) VSync(0); if (counter) i = 500; // Skip scrolly bit else i = 0; // Reset timer while (!QUIT_GAME) // INTRO sequence, uses break { // to exit infinite loop activeBuff = GsGetActiveBuff(); if ((padStatus = PadRead())) break; // Move Title if (i < 500) // Move left by 3 per frame { gravita.x-=3; tion.x-=3; } else if (i == 500) // Centre and make v.small { gravita.x = tion.x = 160; gravita.scalex = gravita.scaley = 0; tion.scalex = tion.scaley = 0; } else if (i < 509) // Enlarge by 512 per frame { gravita.scalex += 512; gravita.scaley += 512; tion.scalex += 512; tion.scaley += 512; } else if (i == 509) // Boom! SsUtKeyOn(vab_id, EXPLODE_SOUND, 0, 42, 0, SFX_VOL, SFX_VOL); else if (i == 510) // Boom! SsUtKeyOn(vab_id, EXPLODE_SOUND, 0, 42, 0, SFX_VOL, SFX_VOL); // Set packet work base GsSetWorkBase((PACKET *)GpuPacketArea[activeBuff]); // Clear the Ordering Table GsClearOt(0, 0, &WorldOT[activeBuff]); // Put sprites in OT GsSortSprite(&gravita, &WorldOT[activeBuff], 0); GsSortSprite(&tion, &WorldOT[activeBuff], 0); // Wait for end of drawing and a vertical blank DrawSync(0); VSync(0); GsSwapDispBuff(); // Register clear to black in OT GsSortClear(0, 0, 0, &WorldOT[activeBuff]); // Draw sprites in OT GsDrawOt(&WorldOT[activeBuff]); if (++i > 750) break; } // Clear screens VSync(0); ClearImage(&scr, 0, 0, 0); ClearImage(&vir, 0, 0, 0); DrawSync(0); // Pause for 1 second for (i=0; i < 50; i++) VSync(0); // Set normal position/size of title gravita.x = tion.x = 160; gravita.y = tion.y = 20; gravita.scalex = gravita.scaley = ONE; tion.scalex = tion.scaley = ONE; splitLine = SPLIT_LINE; // restore DEMO_MODE = FALSE; GAME_ON = FALSE; // Wait for start while (!GAME_ON) { // Read in controller data from last vertical blank // Set pad variables if ((padStatus = PadRead()) != pad) { pad = padStatus2 = padStatus; padtimer = 0; } else padStatus2 = 0; padtimer++; if (padStatus2) { SsUtKeyOn(vab_id, BUTTON_SOUND, 0, 48, 0, SFX_VOL, SFX_VOL); demo_timer = 1; // Reset demo start timer } else // Menu inactive, inc timer { if (!ENTER_NAME && !(menuitem == 4 && cdplay)) demo_timer++; } activeBuff = GsGetActiveBuff(); if (ENTER_NAME == FALSE) { // If cross is not being held down if (!(padStatus & (PAD1cross | PAD2cross))) { // Looping menu if (padStatus2 & (PAD1up | PAD2up) && (--menuitem < 0)) menuitem = 7; if (padStatus2 & (PAD1down | PAD2down) && (++menuitem > 7)) menuitem = 0; if (padStatus2 & (PAD1triangle | PAD2triangle)) menucolumn = 1 - menucolumn; } // Reset race records to NULL if (padStatus2 == (PAD1L1 | PAD1L2 | PAD1R1 | PAD1R2 | PAD1select)) { SsUtKeyOn(vab_id, LIFELOSS_SOUND, 0, 48, 0, SFX_VOL, SFX_VOL); ResetAllRecords(); } if (padStatus2 & (PAD1start | PAD2start)) GAME_ON = TRUE; if (!(demo_timer%DEMO_PAUSE)) // Auto-activate DEMO MDOE { if (++DEMO_MAP_NUM == NUM_MAPS) // Next map DEMO_MAP_NUM = 0; DEMO_MODE = TRUE; GAME_ON = TRUE; } } else // ENTER_NAME == TRUE // ENTER NAME CODE HERE *** { if (WINNER == PLAYER2) { if (padStatus2 & PAD2left) // Set hi-score initials P2 { if (--DefaultInitials[1][initialnum] < 0) DefaultInitials[1][initialnum] = 27; } else if (padStatus2 & PAD2right) { if (++DefaultInitials[1][initialnum] > 27) DefaultInitials[1][initialnum] = 0; } if (padStatus2 & PAD2square) { if (initialnum) initialnum--; } if (padStatus2 & PAD2cross) { if (DefaultInitials[1][initialnum] == 27) // Delete / back { // reset to "_" Record[map].i[initialnum] = DefaultInitials[1][initialnum] = 26; if (initialnum) initialnum--; } else // Set initial { Record[map].i[initialnum] = DefaultInitials[1][initialnum]; if (++initialnum == 3) { ENTER_NAME = FALSE; // resume normal menu selection padStatus2 = 0; } } } // Set initial on screen Record[map].i[initialnum] = DefaultInitials[1][initialnum]; } else // WINNER == PLAYER1 or DRAW (player1 enters name if equal new record { if (padStatus2 & PAD1left) // Set hi-score initials P1 { if (--DefaultInitials[0][initialnum] < 0) DefaultInitials[0][initialnum] = 27; } else if (padStatus2 & PAD1right) { if (++DefaultInitials[0][initialnum] > 27) DefaultInitials[0][initialnum] = 0; } if (padStatus2 & PAD1square) { if (initialnum) initialnum--; } if (padStatus2 & PAD1cross) { if (DefaultInitials[0][initialnum] == 27) // Delete / back { // reset to "_" Record[map].i[initialnum] = DefaultInitials[0][initialnum] = 26; if (initialnum) initialnum--; } else // Set initial { Record[map].i[initialnum] = DefaultInitials[0][initialnum]; if (++initialnum == 3) { ENTER_NAME = FALSE; // resume normal menu selection padStatus2 = 0; } } } // Set initial on screen Record[map].i[initialnum] = DefaultInitials[0][initialnum]; } } if (!ENTER_NAME && menucolumn == 0) { if (menuitem == 0) // "START GAME" { if (padStatus2 & (PAD1cross | PAD2cross)) GAME_ON = TRUE; // start game } else if (menuitem == 1) // "MAP #1-9" { if (padStatus2 & (PAD1cross | PAD2cross)) { if (++map == NUM_MAPS) map = 0; } else if (padStatus2 & (PAD1right | PAD2right)) if (++map == NUM_MAPS) map = NUM_MAPS - 1; if (padStatus2 & (PAD1left | PAD2left)) if (map-- == 0) map = 0; if (padStatus2 & (PAD1circle | PAD2circle)) { GAME_ON = TRUE; // Start demo replay DEMO_MODE = TRUE; } } else if (menuitem == 2) // "RACE DOGFIGHT MODE" { if (padStatus2 & (PAD1cross | PAD2cross)) gametype = 1 - gametype; else if (padStatus2 & (PAD1right | PAD2right)) gametype = DOGFIGHT; if (padStatus2 & (PAD1left | PAD2left)) gametype = RACE; } else if (menuitem == 3) // "PLAYERS ONE TWO" { if (padStatus2 & (PAD1cross | PAD2cross)) players = 3 - players; else if (padStatus2 & (PAD1right | PAD2right)) players = 2; if (padStatus2 & (PAD1left | PAD2left)) players = 1; } else if (menuitem == 4) // CD PLAY { if (padStatus2 & (PAD1cross | PAD2cross)) { if (!cdplay) cdplay = TRUE; else cdplay = FALSE; if (cdplay) CdPlay(1, tracks, trk); // start CD-DA else CdPlay(0, tracks, trk); trk = 0; // stop CD-DA & reset trk } else if (padStatus2 & (PAD1right | PAD2right)) { if (cdplay) { if (++trk == NUM_TRACKS) { trk = 0; cdplay = TRUE; } CdPlay(1, tracks, trk); // Skip to next track } } else if (padStatus2 & (PAD1left | PAD2left)) { if (cdplay) { if (trk-- == 0) { trk = 0; cdplay = TRUE; } CdPlay(1, tracks, trk); // Skip to previous track } } } else if (menuitem == 5) { if (padStatus2 & (PAD1cross | PAD2cross)) { menuitem = 0; menucolumn = 1; } } else if (menuitem == 6) { if (padStatus2 & (PAD1cross | PAD2cross)) { ShowHelpScreen(); } } else if (menuitem == 7) { if (padStatus2 & (PAD1cross | PAD2cross)) { EXIT_GAME = TRUE; break; } } } else if (!ENTER_NAME && menucolumn == 1) { if (menuitem == 0) // "BACK" { if (padStatus2 & (PAD1cross | PAD2cross)) menucolumn = 0; } else if (menuitem == 1) // "PODS ON OFF" { if (padStatus2 & (PAD1cross | PAD2cross)) pods_on = 1 - pods_on; else if (padStatus2 & (PAD1right | PAD2right)) pods_on = FALSE; if (padStatus2 & (PAD1left | PAD2left)) pods_on = TRUE; } else if (menuitem == 2) // "WEAPONS ON OFF" { if (padStatus2 & (PAD1cross | PAD2cross)) weapons_on = 1 - weapons_on; else if (padStatus2 & (PAD1right | PAD2right)) weapons_on = FALSE; if (padStatus2 & (PAD1left | PAD2left)) weapons_on = TRUE; } else if (menuitem == 3) // "SPLIT LINE ON OFF" { if (padStatus2 & (PAD1cross | PAD2cross)) SPLIT_LINE = 1 - SPLIT_LINE; else if (padStatus2 & (PAD1right | PAD2right)) SPLIT_LINE = FALSE; if (padStatus2 & (PAD1left | PAD2left)) SPLIT_LINE = TRUE; } else if (menuitem == 4) // "SFX VOL [===== ]" { if (padStatus & (PAD1right | PAD2right)) { SFX_VOL+=2; if (SFX_VOL > 127) SFX_VOL = 127; } else if (padStatus & (PAD1left | PAD2left)) { SFX_VOL-=2; if (SFX_VOL < 0) SFX_VOL = 0; } } else if (menuitem == 5) // "CD VOL [=======]" { if (padStatus & (PAD1right | PAD2right)) { CD_VOL+=2; if (CD_VOL > 127) CD_VOL = 127; SsSetSerialVol(SS_CD, CD_VOL, CD_VOL); } else if (padStatus & (PAD1left | PAD2left)) { CD_VOL-=2; if (CD_VOL < 0) CD_VOL = 0; SsSetSerialVol(SS_CD, CD_VOL, CD_VOL); } } else if (menuitem == 6) // "SCREEN ADJUST" { if (padStatus & (PAD1cross | PAD2cross)) { ScreenAdjust(); } } else if (menuitem == 7) // "VID MODE PAL NTSC" { if (padStatus2 & (PAD1cross | PAD2cross)) { VidModeChange(); } else if (padStatus2 & (PAD1right | PAD2right)) if (VidMode) VidModeChange(); if (padStatus2 & (PAD1left | PAD2left)) if (!VidMode) VidModeChange(); } } // Set all menu sprite colours to normal for (k = 0; k < 8; k++) for (j=0; j < 2; j++) for (i=0; i < 2; i++) menu[i][j][k].r = menu[i][j][k].g = menu[i][j][k].b = 128; // Highlight selected menu field sprites if (ENTER_NAME == FALSE) { for (j = 0; j < 2; j++) { menu[menucolumn][j][menuitem].r = menu[menucolumn][j][menuitem].g = menu[menucolumn][j][menuitem].b = abs( ((seed%33)<<3)-128 ); } } // Show course line (change clut address) only if RACE mode selected if (gametype == RACE) mapicon.cy = 451; else mapicon.cy = 450; if (VidMode) { byme.y = 236; // Set y values mapicon.y = 192; for (i = 0; i < 7; i++) menurecordtime[i].y = 172; menurecordtime[7].y = 175; menurecordtime[8].y = 175; menurecordtime[9].y = 175; } else { byme.y = 220; mapicon.y = 176; for (i = 0; i < 10; i++) menurecordtime[i].y = 156; menurecordtime[7].y = 159; menurecordtime[8].y = 159; menurecordtime[9].y = 159; } menu[0][1][1].u = 8 * map; // Set u values menu[0][1][3].u = 190 + 18 * (players-1); menu[1][1][1].u = 176 + 24 * pods_on; menu[1][1][2].u = 176 + 24 * weapons_on; menu[1][1][3].u = 176 + 24 * SPLIT_LINE; menu[1][1][7].u = 116 + 36 * VidMode; sfxvol.w = 1 + (SFX_VOL>>1); // Set SFX/CD VOLUME boxes cdvol.w = 1 + (CD_VOL>>1); mapicon.u = 16 + 64 * (map%3); // Set icon uv offset mapicon.v = 32 + 51 * (map/3); // Set record digits if (Record[map].racetime < 30000) { menurecordtime[0].u = 108 + 8 * (Record[map].racetime / 3000); // minutes menurecordtime[2].u = 108 + 8 * ((Record[map].racetime/500)%6); // tens of seconds menurecordtime[3].u = 108 + 8 * ((Record[map].racetime/50)%10); // second units menurecordtime[5].u = 108 + 8 * ((Record[map].racetime/5)%10); // tenths of seconds menurecordtime[6].u = 108 + 8 * ((Record[map].racetime<<1)%10); // split seconds } else // no time set - show -:--.-- { menurecordtime[0].u = 204; menurecordtime[2].u = 204; menurecordtime[3].u = 204; menurecordtime[5].u = 204; menurecordtime[6].u = 204; } // Set record initials uv values menurecordtime[7].u = 108 + 6 * (Record[map].i[0]%14); menurecordtime[7].v = (Record[map].i[0] > 13) ? 47 : 40; menurecordtime[8].u = 108 + 6 * (Record[map].i[1]%14); menurecordtime[8].v = (Record[map].i[1] > 13) ? 47 : 40; menurecordtime[9].u = 108 + 6 * (Record[map].i[2]%14); menurecordtime[9].v = (Record[map].i[2] > 13) ? 47 : 40; // Reset Initials brightnesses menurecordtime[7].r = menurecordtime[7].g = menurecordtime[7].b = 128; menurecordtime[8].r = menurecordtime[8].g = menurecordtime[8].b = 128; menurecordtime[9].r = menurecordtime[9].g = menurecordtime[9].b = 128; // Flash initial while entering if (ENTER_NAME) { menurecordtime[7+initialnum].r = menurecordtime[7+initialnum].g = menurecordtime[7+initialnum].b = abs( ((seed%33)<<3)-128 ); } // Set packet work base GsSetWorkBase((PACKET *)GpuPacketArea[activeBuff]); // Clear the Ordering Table GsClearOt(0, 0, &WorldOT[activeBuff]); // Put sprites in OT GsSortSprite(&gravita, &WorldOT[activeBuff], 0); GsSortSprite(&tion, &WorldOT[activeBuff], 0); GsSortFastSprite(&v1, &WorldOT[activeBuff], 0); GsSortFastSprite(&byme, &WorldOT[activeBuff], 0); GsSortFastSprite(&mapicon, &WorldOT[activeBuff], 0); if (gametype == RACE) for (i=0; i<10; i++) GsSortFastSprite(&menurecordtime[i], &WorldOT[activeBuff], 0); if (menucolumn == 0) { GsSortFastSprite(&menu[0][0][0], &WorldOT[activeBuff], 0); GsSortFastSprite(&menu[0][0][1], &WorldOT[activeBuff], 0); GsSortFastSprite(&menu[0][1][1], &WorldOT[activeBuff], 0); GsSortFastSprite(&menu[0][gametype][2], &WorldOT[activeBuff], 0); GsSortFastSprite(&menu[0][0][3], &WorldOT[activeBuff], 0); GsSortFastSprite(&menu[0][1][3], &WorldOT[activeBuff], 0); GsSortFastSprite(&menu[0][cdplay][4], &WorldOT[activeBuff], 0); GsSortFastSprite(&menu[0][0][5], &WorldOT[activeBuff], 0); GsSortFastSprite(&menu[0][0][6], &WorldOT[activeBuff], 0); GsSortFastSprite(&menu[0][0][7], &WorldOT[activeBuff], 0); } else // menucolumn == 1 { GsSortFastSprite(&menu[1][0][0], &WorldOT[activeBuff], 0); GsSortFastSprite(&menu[1][0][1], &WorldOT[activeBuff], 0); GsSortFastSprite(&menu[1][1][1], &WorldOT[activeBuff], 0); GsSortFastSprite(&menu[1][0][2], &WorldOT[activeBuff], 0); GsSortFastSprite(&menu[1][1][2], &WorldOT[activeBuff], 0); GsSortFastSprite(&menu[1][0][3], &WorldOT[activeBuff], 0); GsSortFastSprite(&menu[1][1][3], &WorldOT[activeBuff], 0); GsSortFastSprite(&menu[1][0][4], &WorldOT[activeBuff], 0); GsSortFastSprite(&menu[1][0][5], &WorldOT[activeBuff], 0); GsSortFastSprite(&menu[1][0][7], &WorldOT[activeBuff], 0); GsSortFastSprite(&menu[1][1][4], &WorldOT[activeBuff], 0); GsSortFastSprite(&menu[1][1][5], &WorldOT[activeBuff], 0); GsSortFastSprite(&menu[1][0][6], &WorldOT[activeBuff], 0); GsSortFastSprite(&menu[1][1][7], &WorldOT[activeBuff], 0); if (SFX_VOL) GsSortBoxFill(&sfxvol, &WorldOT[activeBuff], 0); if (CD_VOL) GsSortBoxFill(&cdvol, &WorldOT[activeBuff], 0); } for (i=0; i < 320; i++) { bg.x0 = bg.x1 = i; GsSortGLine(&bg, &WorldOT[activeBuff], 0); } // Wait for end of drawing and a vertical blank DrawSync(0); VSync(0); GsSwapDispBuff(); // Register clear to black in OT GsSortClear(0, 0, 0, &WorldOT[activeBuff]); // Draw sprites in OT GsDrawOt(&WorldOT[activeBuff]); seed++; // inc. seed value } // Random number generator seed value different every time // a new game is started. The seed value is incremented // until START GAME is selected (the human element) srand(seed); // SET random seed // Clear screen VSync(0); ClearImage(&scr, 0, 0, 0); DrawSync(0); // Restore to Single Buffering GsDefDispBuff(0, 0, 0, 0); GsSwapDispBuff(); VSync(0); // Restore clipping area to virtual screen GsSetClip2D(&vir); GsSetDrawBuffClip(); CD_PLAYING = cdplay; MAP_NUM = MAP_NUM_BAK = map; // store in global variables GAME_TYPE = GAME_TYPE_BAK = gametype; NUM_PLAYERS = players; PODS_ON = pods_on; WEAPONS_ON = weapons_on; if (GAME_ON) // INITIALIZE GAME { if (DEMO_MODE == FALSE) { MapLoad(map); // Draws gates if necessary InitPlayers(players); } else // INIT DEMO_MODE { GAME_TYPE = RACE; // Force race mode if (!(demo_timer%DEMO_PAUSE)) // Auto start? { MAP_NUM = DEMO_MAP_NUM; // Must store active map here MapLoad(DEMO_MAP_NUM); // Draws gates if necessay } else { MapLoad(map); // Loads selected map if forced } InitPlayers(1); // 1 player only } InitOnscreenStuff(); NEW_RECORD = FALSE; ENTER_NAME = FALSE; QUIT_GAME = FALSE; } } // Shows help info TIM from title screen void ShowHelpScreen() { GsSPRITE helpsprite; GsIMAGE im; RECT r; // Put TIM info into im GsGetTimInfo((u_long *)HELP_ADDR+1, &im); // Load image and clut into frame buffer setRECT(&r, im.px, im.py, im.pw, im.ph); LoadImage(&r, im.pixel); setRECT(&r, im.cx, im.cy, im.cw, im.ch); LoadImage(&r, im.clut); helpsprite.attribute = (im.pmode<<24); helpsprite.x = (320-im.pw*2)/2-1; if (VidMode) helpsprite.y = (256-im.ph)/2-1; else helpsprite.y = (240-im.ph)/2-1; helpsprite.w = im.pw*2; helpsprite.h = im.ph; helpsprite.tpage = GetTPage(im.pmode, 0, im.px, im.py); helpsprite.u = 0; helpsprite.v = 0; helpsprite.cx = im.cx; helpsprite.cy = im.cy; helpsprite.mx = 0; helpsprite.my = 0; helpsprite.r = helpsprite.g = helpsprite.b = 128; helpsprite.scalex = helpsprite.scaley = ONE; helpsprite.rotate = 0; DrawSync(0); // Show TIM while cross is held down while ((padStatus = PadRead()) & (PAD1cross | PAD2cross)) { activeBuff = GsGetActiveBuff(); // Set packet work base GsSetWorkBase((PACKET *)GpuPacketArea[activeBuff]); // Clear the Ordering Table GsClearOt(0, 0, &WorldOT[activeBuff]); // Sort sprite in OT GsSortFastSprite(&helpsprite, &WorldOT[activeBuff], 0); // Wait for end of drawing, a vertical blank then swap buffers DrawSync(0); VSync(0); GsSwapDispBuff(); // Register Clear in OT GsSortClear(0, 0, 0, &WorldOT[activeBuff]); // Draw the Ordering Table GsDrawOt(&WorldOT[activeBuff]); } DrawSync(0); // Clear both Ordering Tables before returning GsClearOt(0, 0, &WorldOT[activeBuff]); GsClearOt(0, 0, &WorldOT[1-activeBuff]); } // Adjusts screen position void ScreenAdjust() { // Show red box while cross is held down if (VidMode) RedBox(384*activeBuff, 0, 320, 256); else RedBox(384*activeBuff, 0, 320, 240); if (padStatus & (PAD1left | PAD2left) && GsDISPENV.screen.x > -12) GsDISPENV.screen.x--; else if (padStatus & (PAD1right | PAD2right)) GsDISPENV.screen.x++; if (padStatus & (PAD1up | PAD2up) && GsDISPENV.screen.y > 0) GsDISPENV.screen.y--; else if (padStatus & (PAD1down | PAD2down)) GsDISPENV.screen.y++; } // Draws a red box anywhere on the frame buffer // Cannot use GsLINES as they get clipped to the clipping area void RedBox(int xpos, int ypos, int width, int height) { RECT hline = {xpos, ypos, width, 1}; RECT vline = {xpos, ypos, 1, height}; ClearImage(&vline, 248, 0, 0); ClearImage(&hline, 248, 0, 0); hline.y = ypos + height-1; vline.x = xpos + width-1; ClearImage(&vline, 248, 0, 0); ClearImage(&hline, 248, 0, 0); } // Resets race records to 10 minutes (NULL) void ResetAllRecords() { int map; for (map=0; map < NUM_MAPS; map++) { Record[map].racetime = 30000; // 10 minutes Record[map].i[0] = 26; // "___" by default Record[map].i[1] = 26; Record[map].i[2] = 26; } } // ****** PAD routines ****** // call once only in program initialisation void PadInit (void) { GetPadBuf(&bb0, &bb1); } // call once per VSync(0) u_long PadRead(void) { PAD1analogX = *(bb0+6); PAD2analogX = *(bb1+6); return(~(*(bb0+3) | *(bb0+2) << 8 | *(bb1+3) << 16 | *(bb1+2) << 24)); }