/* * MOON ROCKS by Alan Bland. This is an example game using the * GameSmith Development System. You are free to use portions of * this source code for any purpose whatsoever. This isn't * necessarily the best way to use GDS for this type of game, * but it shows usage of many of the components of GDS (anims, * anim complexes, sounds, scrolling background, multiple viewports, * RastPort usage, background collision detection). * * This game won't work on an OCS Amiga because of the large * scrolling superbitmap. It may also require 1 meg of chip ram. * * The elvis animation is based on the "tiny elvis" public domain * screen hack for Microsoft Windows. I changed it around quite * a bit to reduce the color palette and to make different dancing * motions from the original. * * Call CYBERMIGA BBS at 1-303-939-9923. Lots of Amiga files! */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "GameSmith:GameSmith.h" #include "GameSmith:include/libraries/libptrs.h" #include "GameSmith:include/proto/all_regargs.h" /* header file for anim complexes */ #include "lc.h" #include "rs.h" #define DDEPTH 3 #define NUM_COLORS 8 #define VP2_HEIGHT 19 #define VP2_DEPTH 3 #define VP2_NUM_COLORS 8 #define DWIDTH 320 #define DHEIGHT (200-VP2_HEIGHT-3) #define DISP_MODE 0 #define X_SCROLLPOS (DWIDTH/3) #define Y_SCROLLTOP (DHEIGHT/4) #define Y_SCROLLBOT (DHEIGHT/2) /* this defines the vertical position below which the spacecraft * can collide with background objects. see main loop comments. */ #define GROUND_THRESHOLD (bmheight-52) /* this is how close (pixels) we must land to the target to win the game */ #define CLOSE_ENOUGH 250 #define MAX_FUEL 3000 #define LOW_FUEL 300 /* physical constants */ #define MAX_ALTITUDE 70000.0 #define MAX_RANGE 50000.0 #define GRAVITY 5.0 #define THRUST 10.0 #define SAFE_SPEED 200.0 /* To be strictly accurate, I think this should be 60 for NTSC, 50 for PAL */ #define TICKS_PER_SEC 60 /* joystick responsiveness - set to 0 for fastest */ #define JOY_INTERVAL 3 /* how often to animate the rock star */ #define DANCE_DELAY 5 unsigned char time_to_dance; short dance_time; unsigned char rockstar_visible; /* is he visible? */ unsigned long rockstar_time; /* next time that he changes animation */ unsigned char allow_rockstar; /* allow him to materialize */ /* * The VP2 bitmap has several different control panel images which we * scroll into view as necessary. The top image is the normal panel. * These constants define the scroll offset for each image. */ #define VP_PAUSED VP2_HEIGHT #define VP_NOWHERE (VP2_HEIGHT*2) #define VP_VISIBLE (VP2_HEIGHT*3) #define VP_CRASHED (VP2_HEIGHT*4) #define VP_SUCCESS (VP2_HEIGHT*5) unsigned long lunar_cmap[NUM_COLORS]; unsigned long vp2_cmap[VP2_NUM_COLORS]; /* * vp is the viewport for the main viewing area. * vp2 is the control panel at the bottom of the screen. */ struct gs_viewport vp2 = { NULL, /* ptr to next viewport */ vp2_cmap, /* ptr to color table */ VP2_NUM_COLORS, /* number of colors in table */ NULL, /* ptr to user copper list */ VP2_HEIGHT,DWIDTH,VP2_DEPTH, /* height, width, depth */ 0,0, /* bmheight, bmwidth */ DHEIGHT+2,0, /* top & left viewport offsets */ 0,0, /* X & Y bitmap offsets */ 0, /* flags */ NULL,NULL, /* 2.xx & above compatibility stuff */ NULL,NULL, /* bitmap pointers */ NULL, /* future expansion */ 0,0,0,0 /* display clip (use nominal) */ }; struct gs_viewport vp = { &vp2, /* ptr to next viewport */ lunar_cmap, /* ptr to color table */ NUM_COLORS, /* number of colors in table */ NULL, /* ptr to user copper list */ DHEIGHT,DWIDTH,DDEPTH, /* height, width, depth */ 0,0, /* bmheight, bmwidth */ 0,0, /* top & left viewport offsets */ 0,0, /* X & Y bitmap offsets */ 0, /* flags */ NULL,NULL, /* 2.xx & above compatibility stuff */ NULL,NULL, /* bitmap pointers */ NULL, /* future expansion */ 0,0,0,0 /* display clip (use nominal) */ }; struct display_struct lunar_display = { NULL, /* ptr to previous display view */ NULL,NULL, /* 2.xx & above compatibility stuff */ 0,0, /* X and Y display offsets (1.3 style) */ DISP_MODE, /* display mode ID */ 4,4, /* sprite priorities */ GSV_DOUBLE|GSV_SCROLLABLE, /* flags (double buffered, scrollable) */ &vp, /* ptr to 1st viewport */ NULL /* future expansion */ }; struct anim_cplx *lem; /* the spacecraft anim complex */ struct anim_cplx *rockstar; /* the rock star anim complex */ struct display_struct *display; /* points at lunar_display if it got created ok */ struct Interrupt *scroller=NULL; /* interrupt handler for smooth scrolling */ int dlist=-1; /* display list used with anim system */ int bmwidth; int bmheight; struct RastPort rp; /* RastPort for writing text to the control panel */ struct BitMap *cp_bitmaps[2]; /* need to double-buffer bitmaps for RastPort */ struct TextAttr sysfont = { /* control panel will be topaz.8 */ "topaz.font", 8, 0, 0 }; struct TextFont *myfont; short elapsed_mins; short elapsed_secs; short elapsed_ticks; /* * The MED module uses channels 0 and 1. Sound effects are on 2 and 3 * except for the PING sound, which is only played without music so it's * on one of the music channels. */ #include "libproto.h" struct MMD0 *tune; struct Library *MEDPlayerBase; /* * structures and channel assignments for each sound. */ #define THRUST_CHANNEL CHANNEL2 #define WHOOP_CHANNEL CHANNEL3 #define PING_CHANNEL CHANNEL1 #define FILL_CHANNEL CHANNEL3 #define KABOOM_CHANNEL CHANNEL2 #define LANDED_CHANNEL CHANNEL3 struct sound_struct thrust; struct sound_struct whoop; struct sound_struct kaboom; struct sound_struct landed; struct sound_struct ping; struct sound_struct fill; /* the "lc" anim complex contains zillions of short anims showing the spacecraft at various rotation angles (increments of 15 degrees), with and without flames. the "lc_flame" array lists the anims with flames in clockwise rotation order. "lc_noflame" lists the anims without flames. */ #define LC_ANIM_COUNT 24 short lc_flame[LC_ANIM_COUNT] = { FLAME00, FLAME15, FLAME30, FLAME45, FLAME60, FLAME75, FLAME90, FLAME105, FLAME120, FLAME135, FLAME150, FLAME165, FLAME180, FLAME195, FLAME210, FLAME225, FLAME240, FLAME255, FLAME270, FLAME285, FLAME300, FLAME315, FLAME330, FLAME345 }; short lc_noflame[LC_ANIM_COUNT] = { LEM00, LEM15, LEM30, LEM45, LEM60, LEM75, LEM90, LEM105, LEM120, LEM135, LEM150, LEM165, LEM180, LEM195, LEM210, LEM225, LEM240, LEM255, LEM270, LEM285, LEM300, LEM315, LEM330, LEM345 }; /* sines and cosines are pre-computed for each 15 degree rotational position. this makes the game fast enough to run on an old 68000 Amiga without a math chip. */ double sinval[LC_ANIM_COUNT] = { -1.000000, -0.966168, -0.866519, -0.707840, -0.500941, -0.259916, 0.000000, 0.258771, 0.499914, 0.707002, 0.865927, 0.965862, 1.000000, 0.966015, 0.866223, 0.707421, 0.500428, 0.259344, 0.000593, -0.258199, -0.499401, -0.706583, -0.865630, -0.965708 }; double cosval[LC_ANIM_COUNT] = { -0.000889, 0.257913, 0.499144, 0.706373, 0.865482, 0.965631, 1.000000, 0.965939, 0.866075, 0.707212, 0.500171, 0.259058, 0.000296, -0.258485, -0.499658, -0.706792, -0.865778, -0.965785, -1.000000, -0.966092, -0.866371, -0.707630, -0.500684, -0.259630 }; /* The "craft" structure defines the current physical attributes of the spacecraft. */ struct { int fuel; /* fuel remaining */ int index; /* array index into lc_flame/lc_noflame which indicates the current rotation of the spacecraft */ int collision; /* did the craft collide with something */ double altitude; /* altitude above surface */ double range; /* position along surface */ double vel_x; /* velocity x component */ double vel_y; /* velocity y component */ double vel; /* velocity vector */ double prev_vel; /* previous velocity - need for landing speed */ } craft; /* * calculate the new position of the spacecraft. "burn" is 1 if we are * currently burning fuel, 0 if not. we maintain a coordinate system * using altitude and range, and convert to screen x,y coordinates. * return value indicates whether we actually burned any fuel. * you could probably make this function faster... */ int compute_pos(int burn) { double v0; double x0; double accel_x; double accel_y; int did_burn; /* burn some fuel if there's any left */ if (burn && craft.fuel) { accel_x = (THRUST - GRAVITY) * cosval[craft.index]; accel_y = -(THRUST - GRAVITY) * sinval[craft.index]; --craft.fuel; did_burn = 1; } else { accel_x = 0; accel_y = -GRAVITY; did_burn = 0; } v0 = craft.vel_x; craft.vel_x = v0 + accel_x; x0 = craft.range; craft.range = x0 + v0 + (accel_x/2); if (craft.range < 0) { /* bounce off the edge of the moon (lose some velocity) */ craft.range = 0; craft.vel_x = -craft.vel_x/3; accel_x = -accel_x/3; } else if (craft.range >= MAX_RANGE) { craft.range = MAX_RANGE; craft.vel_x = -craft.vel_x/3; accel_x = -accel_x/3; } v0 = craft.vel_y; craft.vel_y = v0 + accel_y; x0 = craft.altitude; craft.altitude = x0 + v0 + (accel_y/2); if (craft.altitude < 0) { craft.altitude = 0; craft.vel_y = 0; accel_y = 0; } else if (craft.altitude >= MAX_ALTITUDE) { craft.altitude = MAX_ALTITUDE; craft.vel_y = 0; } /* calculate velocity vector */ craft.prev_vel = craft.vel; craft.vel = sqrt(craft.vel_x * craft.vel_x + craft.vel_y * craft.vel_y); /* convert lunar position to bitmap coordinates */ lem->anim->x = craft.range * bmwidth / MAX_RANGE; lem->anim->y = (bmheight-30) - (craft.altitude * (bmheight-30) / MAX_ALTITUDE); return did_burn; } /* * collision handler for craft-to-background checks. the pallete has been * created such that the background colors of interest are in the third * bitplane (colors 4,5,6,7). we want to ignore the mountains but collide * with the rocks on the ground, so the collision handler is enabled only * below a certain altitude. see the main loop for where we enable and * disable the collision handler. */ void grounded(struct anim_struct *anim, struct coll_bg_struct *point, int color) { craft.collision = color; } /* * show current stats on the control panel */ void show_stats(void) { char cpbuf[30]; /* update current information on the control panel */ SetAPen(&rp, 3); /* green */ /* divide altitude and velocity by 10 for a more reasonable output */ sprintf(cpbuf, "%05d", (int)craft.altitude / 10); Move(&rp, 22, 12); Text(&rp, cpbuf, 5); sprintf(cpbuf, "%05d", (int)craft.vel / 10); Move(&rp, 85, 12); Text(&rp, cpbuf, 5); if (craft.fuel <= LOW_FUEL) { SetAPen(&rp, 2); /* red */ } sprintf(cpbuf, "%05d", (int)craft.fuel); Move(&rp, 153, 12); Text(&rp, cpbuf, 5); SetAPen(&rp, 3); /* green */ sprintf(cpbuf, "%02d:%02d", elapsed_mins, elapsed_secs); Move(&rp, 219, 12); Text(&rp, cpbuf, 5); } /* * VB interrupt handler: scroll the display when the spacecraft * gets near one of the edges. */ unsigned long vbcounter; void __interrupt __saveds scroll(void) { int dx, dy; ++vbcounter; /* keep track of elapsed time */ if (++elapsed_ticks == TICKS_PER_SEC) { elapsed_ticks = 0; if (++elapsed_secs == 60) { elapsed_secs = 0; ++elapsed_mins; } } /* keep track of when it's ok for the rock star to dance */ if (--dance_time <= 0) { time_to_dance = 1; dance_time = DANCE_DELAY; } /* don't scroll if showing the explosion */ if (lem->seq == BOOM) { return; } if (lem->anim->x < vp.xoff + X_SCROLLPOS) { dx = vp.xoff + X_SCROLLPOS - lem->anim->x; } else if (lem->anim->x > vp.xoff + DWIDTH - X_SCROLLPOS) { dx = vp.xoff + DWIDTH - X_SCROLLPOS - lem->anim->x; } else { dx = 0; } if (lem->anim->y < vp.yoff + Y_SCROLLTOP) { dy = vp.yoff + Y_SCROLLTOP - lem->anim->y; } else if (lem->anim->y > vp.yoff + DHEIGHT - Y_SCROLLBOT) { dy = vp.yoff + DHEIGHT - Y_SCROLLBOT - lem->anim->y; } else { dy = 0; } if (dx != 0 || dy != 0) { gs_scroll_vp(display, 0, -dx, -dy, 1); } } /* * delay specified number of vb ticks, sync with display */ void mydelay(int vbticks) { int n = vbcounter + vbticks; while (vbcounter < n); while (display->flags & GSV_FLIP); /* while page not flipped yet */ } /* * display an error message (we should never need this) */ struct IntuiText hdrtext = { 1,0,JAM2,10,16,NULL,NULL,NULL }; struct IntuiText fataltext = { 2,0,JAM2,10,32,NULL,NULL,&hdrtext }; struct IntuiText canceltext = { 1,0,JAM2,7,3,NULL,"Cancel",NULL }; void myerror(char *text) { hdrtext.IText = "Bad news from the moon..."; fataltext.IText = text; AutoRequest(NULL, &fataltext, NULL, &canceltext, 0, 0, 600, 90); } /* * cleanup all resources and exit */ void cleanup(void) { gs_close_sound(); gs_free_sound(&thrust); gs_free_sound(&whoop); gs_free_sound(&ping); gs_free_sound(&fill); gs_free_sound(&kaboom); gs_free_sound(&landed); if (dlist > -1) _gs_free_display_list(dlist); if (scroller) _gs_remove_vb_server(scroller); if (lem) gs_free_cplx(lem, 1); if (rockstar) gs_free_cplx(rockstar, 1); if (display) gs_remove_display(display); /* since we allocated our own bitmaps we must free them */ if (vp.bitmap1) gs_free_bitmap(vp.bitmap1); if (vp.bitmap2) gs_free_bitmap(vp.bitmap2); if (vp2.bitmap1) gs_free_bitmap(vp2.bitmap1); if (vp2.bitmap2) gs_free_bitmap(vp2.bitmap2); /* free up MED stuff */ if (tune) { StopPlayer(); UnLoadModule(tune); FreePlayer(); } if (MEDPlayerBase) CloseLibrary(MEDPlayerBase); gs_close_libs(); exit(0); } /* * setup all game data */ int setup(void) { struct anim_load_struct load = { "lc.cplx", 0, 0, 0, 0, 8, 0, 1, ANIMLOAD_NOCOLOR }; struct anim_load_struct loadrs = { "rs.cplx", 0, 0, 0, 0, 8, 0, 1, ANIMLOAD_NOCOLOR }; struct loadILBM_struct loadimg = { "landscape.iff", 0, 0, lunar_cmap, NUM_COLORS, 0, 0, 0, 0, 0, 0, 0, ILBM_COLOR | ILBM_ALLOC2, 0xff, 0xff }; struct loadILBM_struct loadimg2 = { "panel.iff", 0, 0, vp2_cmap, VP2_NUM_COLORS, 0, 0, 0, 0, 0, 0, 0, ILBM_COLOR | ILBM_ALLOC2, 0xff, 0xff }; int result; int page; /* open amiga libraries */ if (gs_open_libs(DOS|GRAPHICS|INTUITION|MATHDBLB|MATHDBLT|MATHTRANS,0)) { exit(1); } /* Open the MEDplayer library */ MEDPlayerBase=(struct Library *)OpenLibrary("medplayer.library",0); if (!MEDPlayerBase) { myerror("Need medplayer.library"); return -1; } /* Load the mod */ tune = LoadModule("mod.MoonRocks"); if (!tune) { myerror("Couldn't load mod.MoonRocks"); return -1; } /* Initialize the MED player */ if (GetPlayer(0)) { myerror("Can't Initialize MED player!"); } /* * load the lunar landscape. this creates two superbitmaps for the * double-buffered scrolling viewport, and fills in the color table * used by all game objects. */ result = gs_loadILBM(&loadimg); if (result) { myerror(loadimg.file); return result; } /* viewport will use the bitmaps just allocated */ vp.bitmap1 = loadimg.bitmap1; vp.bitmap2 = loadimg.bitmap2; bmwidth=vp.bitmap1->BytesPerRow*8; bmheight=vp.bitmap1->Rows; /* load the control panel image */ result = gs_loadILBM(&loadimg2); if (result) { myerror(loadimg.file); return result; } vp2.bitmap1 = loadimg2.bitmap1; vp2.bitmap2 = loadimg2.bitmap2; cp_bitmaps[0] = vp2.bitmap1; cp_bitmaps[1] = vp2.bitmap2; /* control panel needs a RastPort so we can draw text to it */ InitRastPort(&rp); rp.BitMap = vp2.bitmap1; SetDrMd(&rp, JAM2); SetBPen(&rp, 0); /* black */ /* force topaz.8 in the control panel */ myfont = OpenFont(&sysfont); if (!myfont) { myerror("Can't open topaz.8 font"); return -1; } SetFont(&rp, myfont); /* load the spacecraft anim */ if (result = gs_load_anim(&load)) { myerror(load.filename); return result; } /* get pointer to the loaded anim */ lem = load.anim_ptr.cplx; /* load the rock star anim */ if (result = gs_load_anim(&loadrs)) { myerror(loadrs.filename); return result; } /* get pointer to the loaded anim */ rockstar = loadrs.anim_ptr.cplx; /* create the display */ #ifdef NTSC_MONITOR_ID if (GfxBase->LibNode.lib_Version >= 36) /* if WB 2.0 or higher */ { /* this defeats mode promotion on AGA machines */ if (ModeNotAvailable(NTSC_MONITOR_ID)) { lunar_display.modes = PAL_MONITOR_ID; } else { lunar_display.modes = NTSC_MONITOR_ID; } } #endif result = gs_create_display(&lunar_display); if (result) { myerror("gs_create_display failed"); return result; } display = &lunar_display; if ((dlist=_gs_get_display_list()) < 0) { myerror("gs_get_display_list failed"); return result; } /* perform other anim initialization */ gs_init_anim(dlist,display->vp->bitmap1, display->vp->bitmap2, NULL); gs_set_anim_bounds(dlist,0, 0, bmwidth-1, bmheight-1); gs_random(0); /* setup the sound system and load the sounds */ if (gs_open_sound(0,1,-10,2560)) { myerror("gs_open_sound failed"); return -1; } /* sound of the thrusters */ thrust.flags = SND_FAST; if (result=gs_load_iff_sound(&thrust,0,"thrust.snd")) { myerror("cannot load thrust.snd"); return result; } thrust.repeat = 0; /* loop forever */ /* low fuel alert */ whoop.flags = SND_FAST; if (result=gs_load_iff_sound(&whoop,0,"whoop.snd")) { myerror("cannot load whoop.snd"); return result; } whoop.repeat = 0; /* loop forever */ /* a nice explosion */ kaboom.flags = SND_FAST; if (result=gs_load_raw_sound(&kaboom,"kaboom.snd")) { myerror("cannot load kaboom.snd"); return result; } /* startrek bridge sound */ ping.flags = SND_FAST; if (result=gs_load_raw_sound(&ping,"ping.snd")) { myerror("cannot load ping.snd"); return result; } ping.repeat = 0; /* loop forever */ /* sound played when filling the gas tank */ fill.flags = SND_FAST; if (result=gs_load_iff_sound(&fill,0,"fill.snd")) { myerror("cannot load fill.snd"); return result; } fill.repeat = 0; /* loop forever */ /* sound played when landing is successful */ landed.flags = SND_FAST; if (result=gs_load_iff_sound(&landed,0,"landed.snd")) { myerror("cannot load landed.snd"); return result; } /* add the anims to the display */ if (result = gs_add_anim_cplx(dlist,lem, 0, 0, lc_noflame[craft.index], 0)) { myerror("gs_add_anim_cplx failed"); return result; } if (result = gs_add_anim_cplx(dlist,rockstar, 0, 0, DANCE1, 0)) { myerror("gs_add_anim_cplx failed"); return result; } gs_draw_anims(dlist); page = gs_next_anim_page(dlist); rp.BitMap = cp_bitmaps[page]; /* even more anim initialization */ gs_show_display(display,1); gs_flip_display(display,1); /* setup the scrolling routine */ scroller = gs_add_vb_server(&scroll, 0); if (!scroller) { myerror("gs_add_vb_server failed"); return -5; } /* setup collision handler for things on the ground */ gs_set_collision_bg(dlist, grounded); return 0; } /* * flip to the next display page, making sure that the * rastport bitmap for the control panel is in sync. */ void flipper(void) { int page; gs_draw_anims(dlist); page = gs_next_anim_page(dlist); rp.BitMap = cp_bitmaps[page]; gs_flip_display(display,1); } /* * Start background noise - depending on whether the rock star is * visible, it's either the startrek bridge sound, or some music. */ void start_background_noise(void) { if (rockstar_visible) { /* Continue playing the mod */ ContModule(tune); } else { gs_start_sound(&ping, PING_CHANNEL); } } /* * Stop whichever background noise is playing. */ void stop_background_noise(void) { if (rockstar_visible) { /* Stop playing the mod */ StopPlayer(); } else { gs_stop_sound(PING_CHANNEL); } } /* * animate the dancing rock star. time_to_dance is set in the vb interrupt * when enough time has elapsed to do the next dance frame. rockstar_time is * when it's time to switch to a different dance, or when to make him * materialize if he's currently invisible. */ void dance(void) { if (rockstar_visible) { /* switch to a different dance animation every once in awhile */ if (vbcounter >= rockstar_time) { if (rockstar->seq == DANCE1) { gs_set_cplx_seq(rockstar, DANCE2, rockstar->anim->x, rockstar->anim->y); } else { gs_set_cplx_seq(rockstar, DANCE1, rockstar->anim->x, rockstar->anim->y); } /* switch again at a random time */ rockstar_time = vbcounter + (gs_random(5) + 5) * TICKS_PER_SEC; } if (time_to_dance) { time_to_dance = 0; gs_anim_cplx(rockstar, rockstar->anim->x, rockstar->anim->y); } } else if (vbcounter >= rockstar_time && allow_rockstar) { /* time for him to materialize */ gs_enable_cplx(rockstar); rockstar_visible = 1; /* he materializes in the reclining position and must stand up */ gs_set_cplx_seq(rockstar, STANDUP, rockstar->anim->x, rockstar->anim->y); gs_set_cplx_cell(rockstar, 0); /* he'll start dancing in three seconds */ rockstar_time = vbcounter + 3 * TICKS_PER_SEC; /* stop the ping and start the music he'll be dancing to */ gs_stop_sound(PING_CHANNEL); PlayModule(tune); } } void show_panel(int offset) { gs_scroll_vp(display, 1, 0, offset, 1); } /* * user paused the game */ void pause_game(int current_panel) { /* user pressed LMB to get here, wait for it to be released */ while (gs_joystick(0)&JOY_BUTTON1) {} /* scroll the control panel to show the pause display */ show_panel(VP_PAUSED - current_panel); /* wait for response */ while (1) { if (gs_joystick(0) & JOY_BUTTON1) { /* mouse button - resume game */ /* but wait for LMB to be released first */ while (gs_joystick(0)&JOY_BUTTON1) {} /* scroll the control panel to return to the game display */ show_panel(-VP_PAUSED + current_panel); return; } if (gs_joystick(1) & JOY_BUTTON1) { /* joystick button - quit game */ Permit(); cleanup(); } /* dance continues even while paused */ dance(); mydelay(1); flipper(); } } /* * landed at fuel station */ void refuel(void) { stop_background_noise(); mydelay(25); gs_start_sound(&fill, FILL_CHANNEL); craft.vel_x = 0; craft.vel_y = 0; craft.index = 0; gs_set_cplx_seq(lem, LEM00, lem->anim->x, lem->anim->y); /* fill the tank */ while (craft.fuel < MAX_FUEL) { craft.fuel += 3; if (craft.fuel > MAX_FUEL) { craft.fuel = MAX_FUEL; } if (gs_joystick(0)&JOY_BUTTON1) { pause_game(0); } show_stats(); gs_anim_cplx(lem, lem->anim->x, lem->anim->y); dance(); mydelay(1); flipper(); } gs_stop_sound(FILL_CHANNEL); mydelay(25); start_background_noise(); /* wait for liftoff */ while (1) { if (gs_joystick(0)&JOY_BUTTON1) { pause_game(0); } if (gs_joystick(1) & JOY_BUTTON1) { /* need to give the craft a good kick to get it away from the * fuel platform. otherwise the collision detection in the * main loop will bring us right back here. */ craft.altitude += 500; craft.collision = 0; return; } /* continue page-flipping to show the clock ticking */ show_stats(); gs_anim_cplx(lem, lem->anim->x, lem->anim->y); dance(); mydelay(1); flipper(); } } /* * spacecraft crashed into something */ void crash(void) { int i; stop_background_noise(); gs_stop_sound(THRUST_CHANNEL); gs_stop_sound(WHOOP_CHANNEL); gs_start_sound(&kaboom, KABOOM_CHANNEL); /* show the crashed control panel */ show_panel(VP_CRASHED); /* adjust anim position because explosion is bigger than craft */ lem->anim->x -= 40; lem->anim->y -= 20; gs_set_cplx_seq(lem, BOOM, lem->anim->x, lem->anim->y); gs_set_cplx_cell(lem, 0); /* animate the explosion for a few seconds */ for (i = 0; i < 90; i++) { if (gs_joystick(0)&JOY_BUTTON1) { pause_game(VP_CRASHED); } show_stats(); gs_anim_cplx(lem, lem->anim->x, lem->anim->y); dance(); /* explosion sequence is slower than main animation */ mydelay(5); flipper(); } /* restore the main control panel */ show_panel(-VP_CRASHED); /* in case he appeared while we crashed... */ stop_background_noise(); } /* * successful landing. returns non-zero if won the game, else 0. */ int safe_landing(void) { int panel; int dist; /* wait for joystick button to be released */ while (gs_joystick(1)&JOY_BUTTON1) {} gs_stop_sound(THRUST_CHANNEL); craft.vel_x = 0; craft.vel_y = 0; craft.index = 0; gs_set_cplx_seq(lem, LEM00, lem->anim->x, lem->anim->y); /* did we land near the rock star? */ if (rockstar_visible) { dist = abs(rockstar->anim->x - lem->anim->x); if (dist <= CLOSE_ENOUGH) { /* we have a winner! */ panel = VP_SUCCESS; show_panel(panel); gs_stop_sound(WHOOP_CHANNEL); /* Stop playing the mod */ StopPlayer(); mydelay(5); gs_start_sound(&landed, LANDED_CHANNEL); mydelay(5); /* what for congrats to finish, then restart the mod */ while (gs_sound_check() & LANDED_CHANNEL) {} PlayModule(tune); } else { /* didn't land close enough */ panel = VP_VISIBLE; show_panel(panel); } } else { /* he's not even visible yet! */ panel = VP_NOWHERE; show_panel(panel); } /* wait for liftoff */ while (1) { if (gs_joystick(0)&JOY_BUTTON1) { pause_game(panel); } if (gs_joystick(1) & JOY_BUTTON1) { craft.altitude += 300; craft.collision = 0; /* flip back to the main control panel */ show_panel(-panel); /* return result of landing close */ if (panel == VP_SUCCESS) { stop_background_noise(); return 1; } else { start_background_noise(); return 0; } } show_stats(); gs_anim_cplx(lem, lem->anim->x, lem->anim->y); dance(); mydelay(5); flipper(); } } /* * main program: set everything up and then play the game (duh...) */ void main(void) { int result; unsigned char joy; int burn; int thrusting = 0; int joycount; /* setup the animation system */ if ((result = setup()) != 0) { char err[60]; sprintf(err, "setup failed (%d)", result); myerror(err); cleanup(); } /* play multiple times until mouse button is pressed */ Forbid(); while (1) { /* * spacecraft begins with a full tank, at a random position and * velocity. what's really cool about this is that the scroll * interrupt will automatically reposition the superbitmap so * that the spacecraft is visible. the main loop never has * to care what the physical display looks like! */ craft.fuel = MAX_FUEL; craft.altitude = MAX_ALTITUDE; craft.range = (gs_random(bmwidth - 200) + 100) * MAX_RANGE / bmwidth; craft.vel_x = gs_random(200) - 100; if (craft.vel_x > 0) { craft.index = LC_ANIM_COUNT-1; } else { craft.index = 1; } craft.vel_y = 0; craft.collision = 0; compute_pos(0); /* reset the elapsed time counter */ elapsed_mins = 0; elapsed_secs = 0; elapsed_ticks = 0; /* place rock star at a random ground position */ rockstar->anim->x = gs_random(bmwidth - 100) + 50; rockstar->anim->y = bmheight-30; /* but he's invisible for awhile */ gs_clear_cplx(rockstar); rockstar_visible = 0; allow_rockstar = 1; /* he will materialize at a random time at least 40 seconds after the start of the game. */ rockstar_time = vbcounter + (40 + gs_random(90)) * TICKS_PER_SEC; joycount = JOY_INTERVAL; show_stats(); start_background_noise(); /* * Here is the main game loop. Simply watch the joystick and move * the anim around appropriately. We continue in this inner loop * until we land near the target or we crash. */ while (1) { if (gs_joystick(0)&JOY_BUTTON1) { pause_game(0); } /* * Check the joystick. Left and right will rotate the spacecraft * 15 degrees in either direction. Button will burn some fuel. */ joy = gs_joystick(1); /* * Check rotational motion every JOY_INTERVAL times through this * loop. If we check it too often, the spacecraft rotates too * fast to be playable. Thrust button is checked every time. */ if (--joycount <= 0) { joycount = JOY_INTERVAL; if (joy & JOY_RIGHT) { /* rotate clockwise */ if (++craft.index == LC_ANIM_COUNT) craft.index = 0; } else if (joy & JOY_LEFT) { /* rotate counter-clockwise */ if (--craft.index < 0) craft.index = LC_ANIM_COUNT-1; } } if (joy & JOY_BUTTON1) { /* calculate the next spacecraft position with full throttle */ burn = compute_pos(1); } else { /* calculate the next spacecraft position with no throttle */ burn = compute_pos(0); } /* show the spacecraft with or without flame depending on whether we burned any fuel */ if (burn) { /* we burned fuel this time. show the spacecraft with flame */ gs_set_cplx_seq(lem, lc_flame[craft.index], lem->anim->x, lem->anim->y); /* turn on the thrust sound if it's not already on */ if (!thrusting) { thrusting = 1; gs_start_sound(&thrust, THRUST_CHANNEL); } if (craft.fuel == LOW_FUEL) { /* we're running on fumes! */ gs_start_sound(&whoop, WHOOP_CHANNEL); } else if (craft.fuel == 0) { /* not even any fumes left! */ gs_stop_sound(WHOOP_CHANNEL); gs_stop_sound(THRUST_CHANNEL); stop_background_noise(); thrusting = 0; } } else { /* no fuel burned this time, show spacecraft without flame */ gs_set_cplx_seq(lem, lc_noflame[craft.index], lem->anim->x, lem->anim->y); /* turn off thrust sound if necessary */ if (thrusting) { gs_stop_sound(THRUST_CHANNEL); thrusting = 0; } } /* we want background collision checking to occur only when the * spacecraft is near the bottom of the bitmap (i.e. it can crash * into the rocks on the ground but not the mountains in the * distance). we check the ANIM_COLLISION_BG bit in the flags * so that we enable or disable collision checking only once * each time we cross the threshold. */ if (lem->anim->y > GROUND_THRESHOLD && !(lem->anim->flags & ANIM_COLLISION_BG)) { gs_enable_cplx_collision_bg(lem); } else if (lem->anim->y <= GROUND_THRESHOLD && (lem->anim->flags & ANIM_COLLISION_BG)) { gs_disable_cplx_collision_bg(lem); } /* show current stats */ show_stats(); /* show the next spacecraft animation frame as determined above */ gs_anim_cplx(lem, lem->anim->x, lem->anim->y); /* rock star is dancing */ dance(); /* update the off-screen bitmap, then display it */ mydelay(1); flipper(); /* if we landed on the fuel platform, fill 'er up */ /* this happens if collide with background color white or yellow */ /* make sure landed slowly and more-or-less upright */ if (craft.collision == 5 || craft.collision == 6) { if (craft.prev_vel <= SAFE_SPEED && (craft.index == 0 || craft.index == 1 || craft.index == LC_ANIM_COUNT-1)) { /* landed on platform ok */ if (craft.fuel < MAX_FUEL) { gs_stop_sound(THRUST_CHANNEL); thrusting = 0; refuel(); } } else { /* crashed on platform */ crash(); break; } } else if (craft.collision) { /* collided with a rock */ crash(); break; } else if (craft.altitude == 0) { /* on the ground, let's see how we landed */ gs_stop_sound(THRUST_CHANNEL); gs_stop_sound(WHOOP_CHANNEL); /* don't let rock star appear nearby if he's not visible yet */ if (!rockstar_visible) { gs_stop_sound(PING_CHANNEL); allow_rockstar = 0; } craft.vel = 0; mydelay(2); if (craft.prev_vel <= SAFE_SPEED && (craft.index == 0 || craft.index == 1 || craft.index == LC_ANIM_COUNT-1)) { /* a gentle upright landing! */ thrusting = 0; if (safe_landing()) { /* won the game, start over */ break; } /* otherwise continue the search */ allow_rockstar = 1; /* if we sat on the ground waiting for him to appear, don't let him appear right away */ if (!rockstar_visible) { rockstar_time = vbcounter + (gs_random(5) + 20) * TICKS_PER_SEC; } } else { /* too fast or tilted or hit a rock */ crash(); break; } } } } }