home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Collection of Hack-Phreak Scene Programs
/
cleanhpvac.zip
/
cleanhpvac
/
QF2.ZIP
/
QF.C
< prev
next >
Wrap
C/C++ Source or Header
|
1995-04-10
|
70KB
|
2,057 lines
/**********************************************************************\
* *
* QF.C -- Quickfire! *
* *
* Source code by Diana Gruber *
* *
* Copyright 1993, 1995 Diana Gruber. All Rights Reserved. *
* Last modified: April 10, 1995 *
* *
* This source code is provided "as is" without any warranties, etc. *
* *
* This particular incarnation of Quickfire was written two ways. *
* The first way uses regular Mode X scrolling, as documented in *
* the Action Arcade Adventure Set book. To activate this scrolling *
* method, the term "ModeX" must be defined. The easiest way to do *
* this is to uncomment this line in the file DEFS.H: *
* *
* //#define ModeX *
* *
* The Mode X method works in real and protected mode, and should *
* work with Borland C++ or Microsoft C/C++ with Fastgraph/Light. *
* The second method involves virtual buffers and Mode 13h (19 *
* decimal). In this method, all the backgrounds are built in RAM *
* and blitted to the screen. To see this method, comment out the *
* #define ModeX, as above. *
* *
* The Mode 13h method requires protected mode. I used Watcom C/C++ *
* 10.0 with the Rational Systems (Tenberry) DOS/4GW DOS extender. *
* The DOS extender run-time module (DOS4GW.EXE) must be in the path *
* for this version to run. Fastgraph is required, Fastgraph/Light *
* won't work. *
* *
* Results: *
* *
* In general, the Mode 13h method is faster than the Mode X method *
* because the Mode X method waits for the vertical retrace. To turn *
* off the wait for the vertical retrace, uncomment this line: *
* *
* // fg_waitvr(0); *
* *
* in the init_graphics() function in the LOADGAME.C file. When you *
* run in Mode X with the retrace check off, the game is about 5% *
* faster than in Mode 13h, but it flickers like crazy. *
* *
* Files required to recompile: *
* *
* QF.C -- animation functions and function main *
* LOADGAME.C-- initialization, termination, etc. *
* DEFS.H -- global declarations and definitions *
* *
* Files required to run: *
* *
* QFX.EXE -- the program (mode X version) *
* QF13H.EXE -- the program (mode 13h version) *
* QF.PCX -- background tiles *
* QF1.PCX -- title screen *
* QF.BMP -- sprite bitmaps *
* QF.LEV -- level map data *
* *
* More information about constructing a scrolling game is in the *
* book Action Arcade Adventure Set Coriolis Group, 1994) by Diana *
* Gruber. *
* *
\**********************************************************************/
#include "defs.h"
/* uncomment this line if you want to use the put_tile macro instead
of the put_tile function */
// #define put_tile(i,j) copytile((int)backtile[i+tile_orgx][j+tile_orgy],i,j,hidden);
/**********************************************************************\
* *
* main *
* *
\**********************************************************************/
void main()
{
register short i,j;
unsigned long time1, time2;
fg_initpm();
/* Autodetect VGA. If it isn't there, exit. */
#ifdef ModeX
if ((fg_testmode(20,4) == 0))
#else
if ((fg_testmode(19,1) == 0))
#endif
{
printf("\nvga required\n");
exit(0);
}
/* initialize the graphics environment */
init_graphics();
/* load the keyboard handler */
fg_kbinit(1);
/* load the level data */
load_level();
/* load the sprite data */
load_sprite();
/* initialize some global variables */
init_globals();
/* start the intro sequence */
intro_screens();
/* build the first screen of tiles on the hidden page */
tile_orgx = 0;
screen_orgx = 16;
tile_orgy = 6;
screen_orgy = 16;
for (i = 0; i < 22; i++)
for (j = 0; j < 15; j++)
put_tile(i,j);
/* position the visual screen on the launch tube */
warp(0,96);
/* start Quickfire hurtling down the launch tube */
launch_sequence();
/* initialize the frame count here */
frames = 0;
fg_waitfor(1);
time1 = fg_getclock();
/* start the action -- play the game */
activate_level();
/* count the frames and exit now */
time2 = fg_getclock();
time1 = ((time2-time1) * 10) /182;
if (time1 > 0)
frames = frames/time1;
sprintf(abort_string,"%lu frames per second \n\n",frames);
terminate_game();
}
/**********************************************************************\
* *
* activate_level *
* This is the main control function for Quickfire. It controls *
* each frame of animation, and in fact a frame is defined as one *
* iteration of the main loop of this function. The whole function *
* is just a continuous loop, repeating the sequence of activating *
* the sprites, redrawing the tiles, drawing the sprites, and *
* flipping the pages. *
* *
\**********************************************************************/
void near activate_level()
{
OBJp node;
OBJp nextnode;
for(;;)
{
/* increment the frame count for the benchmark test */
frames++;
/* Generate a random number for use later */
random_number = irandom(0,1000);
/* spawn a new enemy intermittently based on the hit value */
if (random_number > hit_value)
start_enemy();
/* traverse the linked list the first time, doing all the
action functions which update values, but don't draw anything */
for (node=bottom_node; node!=(OBJp)NULL; node=nextnode)
{
nextnode = node->next;
node->action(node);
}
player_go();
score->action(score);
/* draw the tiles that have changed on the hidden page */
rebuild_hidden();
/* apply all the bitmaps on the hidden screen */
apply_sprite(player);
for (node=bottom_node; node!=(OBJp)NULL; node=node->next)
apply_sprite(node);
apply_sprite(score);
/* do a page flip */
swap();
if (scrolled)
{
page_copy();
scrolled = FALSE;
}
/* check the keyboard handler, exit if escape key pressed */
if (fg_kbtest(KB_ESC))
return;
}
}
/**********************************************************************\
* *
* adjust_layout *
* When you scroll the background, you need to keep track of which *
* tiles have changed. This data is stored in the layout arrays. *
* When the screen shifts the layout arrays must be shifted by an *
* equal amount. *
* *
\**********************************************************************/
void near adjust_layout_down()
{
register short i;
for (i = 0; i < 22; i++)
memcpy(layout[hidden][i],&layout[visual][i][1],14);
}
void near adjust_layout_right()
{
register short i;
for (i = 0; i < 20; i++)
memcpy(layout[hidden][i],layout[visual][i+2],15);
}
void near adjust_layout_up()
{
register short i;
for (i = 0; i < 22; i++)
memcpy(&layout[hidden][i][1],layout[visual][i],14);
}
/**********************************************************************\
* *
* apply_sprite *
* There are three steps to applying the sprite to the background. *
* First you must calculate which tiles are overwritten. Then you *
* need to update the layout array for those tiles. Finally you can *
* display the bitmap. *
* *
\**********************************************************************/
void near apply_sprite(OBJp objp)
{
register short i,j;
short x,y;
short tile_x,tile_y;
short max_tilex, min_tiley;
char *p;
/* find the current x and y position of the sprite */
x = objp->x + objp->image->xoffset;
y = objp->y + objp->image->yoffset;
/* this is the tile at the lower left corner of the sprite */
tile_x = x/16 - tile_orgx;
tile_y = y/16 - tile_orgy;
/* how many tiles total need to be replaced depends on the size
of the bitmap */
max_tilex = (x+objp->image->width)/16 - tile_orgx;
min_tiley = (y-objp->image->height)/16 - tile_orgy;
/* if you are trying to replace a tile beyond the screen borders, your
bitmap is out of range so just skip the whole thing */
if (tile_x < 0 || max_tilex > 21 || tile_y > 14 || min_tiley < 0)
return;
/* set the layout array values to true, meaning the tile has been
overwritten and must be redrawn next frame */
for (i = tile_x; i <= max_tilex; i++)
{
p = layout[hidden][i] + min_tiley;
for (j = min_tiley; j <= tile_y; j++)
{
*p++ = TRUE;
}
}
/* now draw the bitmap */
put_sprite(objp->image,x,y);
}
/**********************************************************************\
* *
* bullet_go *
* This is the bullet's main action function. This function adjusts *
* the x coordinate of the bullet (the y coordinate is constant) and *
* tests if the bullet is off the screen. It also handles collision *
* detection with all the enemy fighters. This function is for the *
* player's bullets only. Enemy bullets are handled in enemy_bullet_ *
* go. *
* *
\**********************************************************************/
void near bullet_go(OBJp objp)
{
short max_x;
register short i;
/* increment the bullet's horizontal position, always moves right */
objp->x += objp->xspeed;
/* check if the bullet has moved off the screen */
max_x = (tile_orgx + objp->tile_xmax) * 16;
/* if it has moved offf the screen, kill it by setting the action
function to kill for the next frame */
if (objp->x > max_x)
objp->action = &kill_bullet;
/* Do collision detections for all the enemies in the enemy array.
This is a simple rectangular collision detection based on the
position, width and height of the enemy fighter. */
for (i = 0; i < nenemies; i++)
{
if (objp->x>enemy[i]->x && objp->x<enemy[i]->x+enemy[i]->image->width && objp->y<enemy[i]->y && objp->y>enemy[i]->y-enemy[i]->image->height)
{
/* if a collision is detected, make sure the enemy is not
currently exploding */
if (enemy[i]->frame > 0)
{
/* spawn a new object, an explosion to attach to the enemy fighter */
start_explosion(enemy[i]);
/* kill this bullet next frame */
objp->action = &kill_bullet;
/* set the enemy frame to -1 to signal it is exploding and
can't explode again */
enemy[i]->frame = -1;
/* stop doing collision detections because one bullet can't
blow up more than one plane */
break;
}
}
}
}
/**********************************************************************\
* *
* do_explosion *
* This is the main action function for the explosion object. Other *
* functions affecting this object are start_explosion and kill_ *
* explosion. The attached sprite is the enemy fighter that is *
* exploding. *
* *
\**********************************************************************/
void near do_explosion(OBJp objp)
{
/* If the explosion has reached the frame 3 state, at which point
the bitmap is bigger than the airplane, it is time to kill the
airplane. */
if (objp->frame > 3)
{
/* if the attached sprite is NULL that means the airplane was
already killed so skip it. Otherwise, kill the enemy plane
by setting it's action function to a kill function, and it
will kill itself next frame */
if (objp->attached_sprite != (OBJp)NULL)
objp->attached_sprite->action = &kill_enemy;
/* after the attached plane has been killed, the explosion
moves at a slower speed because smoke drifts slower than
metal */
objp->x += objp->xspeed;
objp->y += objp->yspeed;
}
else
{
/* if the enemy plane still exists, then the explosion should
be firmly attached to it. Base the x and y coordinates of
the explosion on the position of the enemy. Note, these
coordinates define the center of the explosion. */
if (objp->attached_sprite != (OBJp)NULL)
{
objp->x = objp->attached_sprite->x+16;
objp->y = objp->attached_sprite->y-4;
}
/* it is possible for the explosion to be at less than frame 3
but there is no attached sprite. That happens when the enemy
plane has drifted off the edge of the screen. Just display
the sprite if you can */
else
{
objp->x += objp->xspeed;
objp->y += objp->yspeed;
}
}
/* Increment the explosion frame only sometimes, and not at the
same speed for each explosion. So base the frame increment on
the global random number */
if (random_number > 500 || objp->frame < 0)
objp->frame++;
/* define which sprite will be displayed this frame */
objp->image = explosion[objp->frame];
/* We only have 10 frames for this object. If the frame has gone
over 10, it is time to kill the object. Also be sure we do not
try to display an 11th frame, which will cause a null pointer */
if (objp->frame > 10)
{
objp->image = explosion[10];
objp->action = &kill_object;
}
}
/**********************************************************************\
* *
* do_player_explosion *
* This function is similar to the do_explosion function except that *
* this time it is the Quickfire plane that is hit, not the enemy *
* plane. If this were a real game, after several hits (3 or 5) *
* the player would die and you would need to restart the level or *
* something. However, this is just a demo game so we let Quickfire *
* have unlimited lives. *
* *
\**********************************************************************/
void near do_player_explosion(OBJp objp)
{
/* the explosion is firmly attached to the Quickfire plane */
objp->x += player->xspeed;
objp->y += player->yspeed;
/* increment the explosion frame based on the random number */
if (random_number > 500 || objp->frame < 0)
objp->frame++;
objp->image = explosion[objp->frame];
/* Stop the explosion at the third frame. It's just a little
bitty explosion, after all. Save the big explosions for the
enemies */
if (objp->frame > 2)
{
objp->image = explosion[2];
objp->action = &kill_player_explosion;
}
}
/**********************************************************************\
* *
* enemy_bullet_go *
* This is the main action function for the enemy bullets. The other *
* functions affecting this object are start_enemy_bullet and kill_ *
* enemy_bullet. This function adjusts the position of the bullet *
* and does a collision detection. *
* *
\**********************************************************************/
void near enemy_bullet_go(OBJp objp)
{
/* adjust the horizontal position of the bullet. Speed is constant. */
objp->x += objp->xspeed;
/* If the enemy bullet has traveled beyond the left edge of the screen,
it is time to kill it. Just set its action function to a kill
function and it will kill itself next frame. */
if (objp->x < tile_orgx*16)
objp->action = &kill_enemy_bullet;
/* Do a collision detection with the player. */
else if (objp->x>player->x && objp->x<player->x+player->image->width && objp->y<player->y && objp->y>player->y-player->image->height-3)
{
/* If a collision is detected, make sure the player is not currently
suffering a hit. If the player already has an attached explosion,
skip this hit. Otherwise, spawn an explosion. */
if (player->attached_sprite == (OBJp)NULL)
start_player_explosion(objp);
/* either way, it's time to kill this bullet */
objp->action = &kill_enemy_bullet;
}
}
/**********************************************************************\
* *
* enemy_go *
* This is the main action function for the enemy fighter. This *
* function gets called once each frame for each fighter until the *
* kill_enemy action function is called. This function does two *
* things: it updates the x and y coordinates of the figher, and if *
* the player is within range, it may or may not fire a bullet at *
* the player. It will not fire a bullet if it has been hit and *
* is currently exploding. *
* *
\**********************************************************************/
void near enemy_go(OBJp objp)
{
short ymin,ymax;
short dif;
/* update the x and y positions */
objp->x += objp->xspeed;
objp->y += objp->yspeed;
/* check if the fighter is going off the top or bottom of the screen */
ymin = tile_orgy*16 + objp->image->height;
ymax = tile_orgy*16 + 239;
/* if going off the top or bottom of screen, change vertical direction */
if (objp->y <= ymin)
{
objp->yspeed = irandom(0,9);
objp->y = ymin;
}
else if (objp->y > ymax)
{
objp->yspeed = irandom(-9,0);
objp->y = ymax;
}
/* if fighter has gone beyond the left edge of the screen, kill it */
else if (objp->x <= tile_orgx*16)
objp->action = &kill_enemy;
/* Fighter is on the screen and it is not hit yet. You can tell it
is not hit because it has no attached sprite. That is, it is not
exploding. */
else if (objp->attached_sprite == (OBJp)NULL) /* not hit yet */
{
/* look at the difference between the player and the enemy */
dif = objp->y - player->y;
/* Check the random number. The enemy plane changes direction
approximately once every 20 frames */
if (random_number < 50)
objp->yspeed = irandom(-9,9);
/* If the player is within 64 pixels of the enemy, fire a bullet
approximately every 4th frame. If you fire a bullet, get a
new random number because we don't want all the enemies firing
at the same time. */
else if (random_number % 4 == 0 && dif > -64 && dif < 64)
{
start_enemy_bullet(objp);
random_number = irandom(0,1000);
}
}
}
/**********************************************************************\
* *
* kill_bullet *
* This is where the player's bullet kills itself. It just removes *
* itself from the linked list. *
* *
\**********************************************************************/
void near kill_bullet(OBJp objp)
{
OBJp node;
/* remove object from linked list */
kill_object(objp);
/* decrement the number of bullets to keep the count accurate */
nbullets--;
}
/**********************************************************************\
* *
* kill_enemy *
* This is where the enemy fighter removes itself from the linked list *
* and also from the enemy array. *
* *
\**********************************************************************/
void near kill_enemy(OBJp objp)
{
OBJp node;
register int i;
int enemy_no;
if (node->attached_sprite != (OBJp)NULL)
node->attached_sprite->attached_sprite = (OBJp)NULL;
/* which enemy is it? */
for (i = 0; i < nenemies; i++)
{
if (enemy[i] == objp)
{
enemy_no = i;
break;
}
}
/* decrement the enemy count */
nenemies--;
/* the other enemies fill in the space in the enemy array vacated
by this enemy */
for (i = enemy_no; i < nenemies; i++)
enemy[i] = enemy[i+1];
enemy[nenemies] = (OBJp)NULL;
/* remove from linked list */
kill_object(objp);
}
/**********************************************************************\
* *
* kill_enemy_bullet *
* This is the action function where the enemy bullet kills itself *
* by removing itself from the linked list. *
* *
\**********************************************************************/
void near kill_enemy_bullet(OBJp objp)
{
/* remove object from linked list */
kill_object(objp);
/* decrement number of enemy bullets on the screen */
nenemy_bullets--;
}
/**********************************************************************\
* *
* kill_object *
* Remove an object from a linked list *
* *
\**********************************************************************/
void near kill_object(OBJp objp)
{
OBJp node;
node = objp;
if (node == bottom_node)
{
bottom_node = node->next;
if (bottom_node != (OBJp)NULL)
bottom_node->prev = (OBJp)NULL;
}
else if (node == top_node)
{
top_node = node->prev;
top_node->next = (OBJp)NULL;
}
else
{
node->prev->next = node->next;
node->next->prev = node->prev;
}
free(node);
}
/**********************************************************************\
* *
* kill_player_explosion *
* The little explosion on the Quickfire plane kills itself by *
* removing itself from the linked list. Note, the player plane *
* is never killed, just the explosion attached to it. *
* *
\**********************************************************************/
void near kill_player_explosion(OBJp objp)
{
/* remove object from linked list */
kill_object(objp);
/* The player now has no attached sprite so it is eligible for
another explosion. */
player->attached_sprite = (OBJp)NULL;
}
/**********************************************************************\
* *
* launch_sequence -- with palette cycling *
* The Quickfire fighter does a roll then locks into position in the *
* launch shoot. It proceeds down the launch shoot with some palette *
* cycling to cause a propulsion effect. Meanwhile the computer is *
* being benchmarked informally. How fast Quickfire exits the *
* launch shoot will determine how fast Quickfire does rolls later on. *
* *
\**********************************************************************/
void near launch_sequence()
{
register short i,j;
unsigned long time1, time2;
long nframes;
scrolled = FALSE;
player->y = 94;
player->frame = 2;
player->image = fighter[player->frame];
apply_sprite(player);
swap();
/* begin benchmark */
time1 = fg_getclock();
nframes = 0;
/* roll fighter shorto position */
for (i = 0; i < 2; i++)
{
for (j = 32*3; j > 2; j-=3)
{
nframes++;
if (nframes % 8 == 0)
{
player->frame++;
if (player->frame > 7)
player->frame = 0;
player->image = fighter[player->frame];
}
player->y+=2;
rebuild_hidden();
apply_sprite(player);
swap();
fg_setdacs(64,32,&launch_palette[j]);
if (fg_kbtest(KB_ESC)) terminate_game();
}
}
player->image = fighter[2];
/* begin proceeding through launch shoot */
for (i = 0; i < 1; i++)
{
for (j = 32*3; j > 2; j-=3)
{
nframes++;
player->x++;
rebuild_hidden();
apply_sprite(player);
swap();
fg_setdacs(64,32,&launch_palette[j]);
if (scrolled)
{
page_copy();
scrolled = FALSE;
}
if (fg_kbtest(KB_ESC)) terminate_game();
}
}
for (i = 0; i < 5; i++)
{
for (j = 32*3; j > 11; j-=12)
{
nframes++;
player->x+=4;
scroll_right(4);
rebuild_hidden();
apply_sprite(player);
swap();
fg_setdacs(64,32,&launch_palette[j]);
if (scrolled)
{
page_copy();
scrolled = FALSE;
}
if (fg_kbtest(KB_ESC)) terminate_game();
}
}
for (i = 0; i < 2; i++)
{
for (j = 32*3; j > 15; j-=15)
{
nframes++;
player->x+=5;
scroll_right(4);
rebuild_hidden();
apply_sprite(player);
swap();
fg_setdacs(64,32,&launch_palette[j]);
if (scrolled)
{
page_copy();
scrolled = FALSE;
}
if (fg_kbtest(KB_ESC)) terminate_game();
}
}
for (i = 0; i < 3; i++)
{
for (j = 32*3; j > 15; j-=15)
{
nframes++;
player->x+=8;
scroll_right(8);
rebuild_hidden();
apply_sprite(player);
swap();
fg_setdacs(64,32,&launch_palette[j]);
if (scrolled)
{
page_copy();
scrolled = FALSE;
}
if (fg_kbtest(KB_ESC)) terminate_game();
}
}
for (i = 0; i < 4; i++)
{
for (j = 32*3; j > 15; j-=15)
{
nframes++;
player->x+=12;
scroll_right(12);
rebuild_hidden();
apply_sprite(player);
swap();
fg_setdacs(64,32,&launch_palette[j]);
if (scrolled)
{
page_copy();
scrolled = FALSE;
}
if (fg_kbtest(KB_ESC)) terminate_game();
}
}
/* exit launch shoot */
for (i = 0; i < 21; i++)
{
nframes++;
player->x+=12;
scroll_right(8);
rebuild_hidden();
apply_sprite(player);
swap();
if (scrolled)
{
page_copy();
scrolled = FALSE;
}
if (fg_kbtest(KB_ESC)) terminate_game();
}
/* 1 frame so roll fighter plane into position */
player->image = fighter[1];
/* figure out the frame rate from the benchmark for later animation timing */
time2 = fg_getclock();
time1 = ((time2-time1) * 10) / 182;
if (time1 > 0)
framerate = (short)(nframes/time1);
frame_factor = framerate/40 + 1;
if (frame_factor < 1) frame_factor = 1;
}
/**********************************************************************\
* *
* new_score *
* This is the action function for the score object. The action *
* function only gets activated when the score gets changed. The *
* rest of the time the score's action function is put_score. For *
* speed, the numbers are drawn only once and then the whole score *
* is stored in a bitmap so it can be redisplayed every frame until *
* it changes. *
* *
\**********************************************************************/
void near new_score(OBJp objp)
{
short nchar;
char string[16];
/* Convert the (long) score to a character string. Assume 10 digits
is enough */
ltoa(player_score,string,10);
/* calculate the length of the string */
nchar = strlen(string);
/* clear an area in video memory below the tile space where nothing
else is going on */
fg_setcolor(0);
fg_rect(0,59,680,684);
/* set the color to white and display the score */
fg_setcolor(255);
put_bstring(string,nchar,0,684);
/* the width of the bitmap will depend on the number of characters */
objp->image->width = nchar*6;
/* do an fg_getimage to put the score in a bitmap in RAM */
fg_move(0,684);
fg_getimage(objp->image->bitmap,objp->image->width,objp->image->height);
/* update the x and y positions of the score (always the same place
in the upper left corner) */
objp->x = tile_orgx*16 + screen_orgx + 8;
objp->y = tile_orgy*16 + screen_orgy + 10;
/* the default action function for the score object is put score */
objp->action = &put_score;
}
/**********************************************************************\
* *
* page_copy *
* Do a full-screen copy from the visual page to the hidden page. *
* Usually this is done after a scroll. Also copy the layout array. *
* *
\**********************************************************************/
void near page_copy()
{
/* copy the visual page to the hidden page */
#ifdef ModeX
fg_transfer(0,351,vpo,vpb,0,hpb,0,0);
#else
fg_vbcopy(0,351,vpo,vpb,0,hpb,workvb,workvb);
#endif
/* also copy the layout */
memcpy(layout[hidden],layout[visual],22*15);
}
/**********************************************************************\
* *
* player_go *
* This is the only action function for the Quickfire plane, also *
* known as the player. A lot of things happen in this function, *
* including the artificial intelligence required when the game *
* goes into demo mode. All keystrokes are intercepted in this *
* function. *
* *
\**********************************************************************/
void near player_go()
{
short tile_x,tile_y;
short scroll_y;
OBJp node;
short keystroke;
short dif;
/* initialize some useful constants */
scroll_y = 0;
player->xspeed = 5;
/* calculate the current position of the player in tile space */
tile_x = player->x/16 - tile_orgx;
/* Increment the timer. If the timer exceeds the target amount, it
is time to change direction of the airplane. We also assume at
this point that the program has gone into demo mode ("autopilot").
In other words, if the keyboard has not been touched in n frames,
let the program run itself. */
player_timer++;
if (player_timer >= player_time_target)
{
autopilot = TRUE;
player->yspeed = 0;
player->xspeed = 5;
/* Since we haven't collected a keystroke in a while, we will
make one up. In fact, we will make up several depending on
a random value. We assume there are 16 cases. These cases
can be manipulated to make Quickfire respond differently in
demo mode. */
up = 0; down = 0; right = 0; left = 0; ctrl = 0;
keystroke = irandom(0,16);
switch(keystroke)
{
case 0:
up = TRUE; break;
case 1:
down = TRUE; break;
case 2:
right = TRUE; break;
case 3:
left = TRUE; break;
case 4:
ctrl = TRUE; break;
case 5:
left = TRUE; down = TRUE; break;
case 6:
left = TRUE; up = TRUE; break;
case 7:
right = TRUE; up = TRUE; break;
case 8:
right = TRUE; down = TRUE; break;
case 9:
up = TRUE; left = TRUE; ctrl = TRUE; break;
case 10:
down = TRUE; right = TRUE; ctrl = TRUE; break;
case 11:
left = TRUE; ctrl = TRUE; break;
case 12:
down = TRUE; left = TRUE; ctrl = TRUE; break;
case 13:
up = TRUE; left = TRUE; ctrl = TRUE; break;
case 14:
down = TRUE; left = TRUE; ctrl = TRUE; break;
case 15:
up = TRUE; right = TRUE; ctrl = TRUE; break;
case 16:
down = TRUE; right = TRUE; ctrl = TRUE; break;
}
player_timer = 0;
player_time_target = irandom(25,80);
}
/* one last chance... is a key being pressed? If it is, ignore all
that stuff above and let the player override the autopilot */
if (fg_kbtest(0))
{
up = 0; down = 0; right = 0; left = 0; ctrl = 0; player_timer=0; autopilot = FALSE;
}
/* If we are in autopilot mode, let's make some decisions regarding
motion and firing bullets. */
if (autopilot)
{
/* if there is an enemy on the screen, maybe we want to shoot at it. */
if (enemy[0] != (OBJp)NULL)
{
if (nbullets == 0)
frame_count = 0;
/* setting ctrl and left at the same time will cause a roll */
right = FALSE;
ctrl = TRUE;
left = TRUE;
/* get the vertical difference between the player and the
closest enemy */
dif = player->y - enemy[0]->y;
/* If the closest enemy is below us, change direction to
down. If he is above us, go up. */
if (dif > 0)
up = TRUE;
else
down = TRUE;
/* reset the time target */
player_time_target = 12;
/* sometimes, for variety, don't roll */
if (random_number > 500)
left = FALSE;
}
else ctrl = 0;
}
/* case where Quickfire is going up */
if (fg_kbtest(KB_UP) || up)
{
/* Start the roll by changing the sprite. Divide by the frame
factor. On a slow computer, the frame factor will be 1 so
change the sprite every frame. On a faster computer, change
the sprite after 2 or 3 frames, otherwise the plane rolls too
fast */
frame_count++;
if (frame_count < 8*frame_factor)
player->frame = frame_count/frame_factor;
else
player->frame = 0;
/* if you are getting close to the top of the screen, only decrement
2 pixels per frame, otherwise go ahead and climb 4 pixels per
frame */
if (player->y > 64 )
{
if(player->yspeed > -4)
player->yspeed--;
}
else
player->yspeed = -2;
}
/* case where Quickfire is going down */
else if (fg_kbtest(KB_DOWN) || down)
{
frame_count++;
if (frame_count < 8*frame_factor)
player->frame = frame_count/frame_factor;
else
player->frame = 0;
if (frame_count > 32000)
frame_count = 0;
/* if you are getting close to the bottom of the screen, only decrement
2 pixels per frame, otherwise go ahead and descend 4 pixels per
frame */
if (player->y < world_maxy - 32)
{
if (player->yspeed < 4)
player->yspeed++;
}
else
player->yspeed = 2;
}
/* case where Quickfire is going right */
if (fg_kbtest(KB_RIGHT) || right)
{
/* if you are at the far right edge of the screen, just travel at
the same rate as the auto scroll */
if (tile_x >= player->tile_xmax)
player->xspeed = 8;
/* elsewhere on the screen you can go right faster than the
autoscroll */
else
player->xspeed = 15;
}
/* if the left arrow key is pressed, Quickfire slows way down */
else if (fg_kbtest(KB_LEFT) || left)
{
player->xspeed = 1;
}
/* nothing happening, Quickfire should be in upright position */
else
{
frame_count = 0;
player->frame = 0;
}
/* decide which sprite will be displayed this frame */
player->image = fighter[player->frame];
/* Quickfire can't go past far left side of screen */
if (tile_x < player->tile_xmin)
player->xspeed = MAX(player->xspeed,8);
/* increment x coordinate according to speed of player */
player->x += player->xspeed;
/* also adjust the y coordinate, keeping plane within world limits */
player->y += player->yspeed;
player->y = MIN(player->y,world_maxy-3);
player->y = MAX(player->y,32);
/* calculate y position in tile space */
tile_y = player->y/16 - tile_orgy;
/* are we going to need to scroll up or down? */
if (tile_y > player->tile_ymax && (tile_orgy < nrows - 15 || screen_orgy <= 36))
scroll_y = player->yspeed;
else if (tile_y < player->tile_ymin && (tile_orgy > 0 || screen_orgy > 4))
scroll_y = player->yspeed;
/* Try to do the scroll. If the scroll fails it is because you have
come to the end of the map and you need to adjust everything for the
wrap around. Adjust the x coordinates of all the objects in the
linked list, and also adjust the tile origin. */
if (scroll_right_down(scroll_y) == -1)
{
player->x = (player->x - (tile_orgx*16)) + (54*16) - player->xspeed;
for (node=bottom_node; node!=(OBJp)NULL; node=node->next)
node->x = (node->x - (tile_orgx*16)) + (54*16) - node->xspeed;
tile_orgx = 54;
}
/* If you need to shoot this frame, spawn a bullet. Don't spawn
more than 8 bullets at a time. */
if (fg_kbtest(KB_CTRL) || ctrl)
{
bullet_count++;
player_timer=0;
start_bullet();
if (bullet_count >= 8)
{
bullet_count = 0;
ctrl = FALSE;
autopilot = FALSE;
}
}
}
/**********************************************************************\
* *
* put_sprite *
* This function is called each frame for each sprite currently *
* visible. The world_x and world_y coordinates refer to the location *
* of the sprite on the greater "map", or in other words, it is the *
* object's position with respect to the sky and the clouds. *
* *
\**********************************************************************/
void near put_sprite(SPRITE *frame, short world_x, short world_y)
{
register short x,y;
/* convert x and y world space to x and y physical space */
x = world_x - (tile_orgx*16);
y = world_y - (tile_orgy*16) + hpo;
/* draw the bitmap at the proper location */
fg_move(x,y);
fg_clpimage(frame->bitmap,frame->width,frame->height);
}
/**********************************************************************\
* *
* put_tile *
* apply tile to screen or buffer *
* *
\**********************************************************************/
void put_tile(short i,short j)
{
short x1,x2,x3,y1,y2,y3;
short tileno;
unsigned short index;
tileno = (short)backtile[i+tile_orgx][j+tile_orgy];
x1 = (tileno%20) * 16;
x2 = x1 + 15;
y1 = (tileno/20) * 16 + tpo;
y2 = y1 + 15;
x3 = i * 16;
y3 = j * 16+hpo +15;
#ifdef ModeX
fg_transfer(x1,x2,y1,y2,x3,y3,0,0);
#else
fg_vbcopy(x1,x2,y1,y2,x3,y3,workvb,workvb);
#endif
}
/**********************************************************************\
* *
* rebuild_hidden *
* This function is called exactly once each frame. It's purpose is *
* to redraw the screen on the hidden page by replacing only those *
* tiles which have changed. Since all sprites on the screen are *
* assumed to be constantly moving, we replace all tiles that were *
* covered by sprites on the previous frame. *
* *
\**********************************************************************/
void near rebuild_hidden()
{
register short i,j;
char *p;
/* here is where the layout array performs its magic */
p = layout[hidden][0];
/* Traverse the layout array and every time you find a tile that
has changed, replace it. After the tile is replaced, naturally
that element in the array is set to 0 or "FALSE". */
for (i = 0; i < 22; i++)
{
for (j = 0; j < 15; j++)
{
if (*p)
{
put_tile(i,j);
*p = FALSE;
}
p++;
}
}
}
/**********************************************************************\
* *
* put_score -- put score on the screen *
* This is the simpler score action function that gets called every *
* frame the score has not changed. All it does is calculate the x *
* and y coordinates of the score based on the origin of the visual *
* screen. *
* *
\**********************************************************************/
void near put_score(OBJp objp)
{
objp->x = tile_orgx*16 + screen_orgx + 8;
objp->y = tile_orgy*16 + screen_orgy + 10;
}
/**********************************************************************\
* *
* scroll_right *
* Move the screen to the right by npixels. Quickfire automatically *
* scrolls right every frame, but sometimes it scrolls right and up, *
* and sometimes it scrolls right and down. *
* *
\**********************************************************************/
int scroll_right(short npixels)
{
register short i;
/* if possible, just move the visible area within the physical page */
if (screen_orgx <= 32-npixels)
{
screen_orgx+=npixels;
}
/* the origin is out of range, we need to do a full scroll */
else if (tile_orgx < ncols - 22)
{
/* transfer the relevant part of the visual page to the hidden page */
#ifdef ModeX
fg_transfer(32,351,vpo,vpb,0,hpb,0,0);
#else
fg_vbcopy(32,351,vpo,vpb,0,hpb,workvb,workvb);
#endif
/* change the origin in tile space and screen space */
tile_orgx+=2;
screen_orgx-=(32-npixels);
/* fill in two columns of tiles at the right side of the page */
for(i = 0; i < 15; i++)
{
put_tile(21,i);
put_tile(20,i);
}
/* fix that layout array */
adjust_layout_right();
/* set a global to signal a page copy later in the frame */
scrolled = TRUE;
}
else /* can't scroll right */
{
return(-1);
}
return(OK);
}
/**********************************************************************\
* *
* scroll_right_down *
* *
* Master scrolling function. Assumes you are going to scroll right *
* and down. If you are going to scroll right and up or just right, *
* it branches to other functions. *
* *
* Scrolling right and down, there are 4 cases: *
* *
* - scroll by adjusting screen coordinates only *
* - adjust x screen coordinates and y tiles *
* - adjust x tiles and y screen coordinates *
* - adjust x and y tiles *
* *
* The last case is the most time-consuming because you must replace *
* a total of 52 tiles, that is, 2 columns of 15 and one row of 22 *
* tiles. *
* *
\**********************************************************************/
int scroll_right_down(short scroll_y)
{
register short i;
short scroll_x;
/* Scroll_x is a constant. We always scroll 8 pixels in Quickfire. */
scroll_x = 8;
/* if you are not scrolling up or down, just scroll right */
if (scroll_y == 0)
return(scroll_right(scroll_x));
/* if you are scrolling up, go do that */
else if (scroll_y < 0)
return(scroll_right_up(scroll_x,scroll_y));
/* can we get away with just changing the screen origin? */
else if (screen_orgx <= 32-scroll_x && screen_orgy <= 40-scroll_y)
{
screen_orgx+=scroll_x;
screen_orgy+=scroll_y;
}
/* You have reached the end of the world map. Can't scroll, will
have to wrap around. Return an error code. */
else if (tile_orgx >= ncols - 22)
{
return(-1);
}
/* you are at the bottom of the screen, can't scroll down, so
just scroll right */
else if (tile_orgy >= nrows - 15)
{
return(scroll_right(scroll_x));
}
/* we are going to need to adjust the screen by 2 columns of tiles */
else if (screen_orgx > 32-scroll_x)
{
/* but this time we don't have to adjust any rows */
if (screen_orgy <= 40-scroll_y)
{
#ifdef ModeX
fg_transfer(32,351,vpo,vpb,0,hpb,0,0);
#else
fg_vbcopy(32,351,vpo,vpb,0,hpb,workvb,workvb);
#endif
tile_orgx+=2;
screen_orgx-=(32-scroll_x);
screen_orgy+=scroll_y;
for(i = 0; i < 15; i++)
{
put_tile(21,i);
put_tile(20,i);
}
adjust_layout_right();
scrolled = TRUE;
}
/* here we will adjust 2 columns and one row of tiles (slowest case) */
else
{
#ifdef ModeX
fg_transfer(32,351,16+vpo,vpb,0,223+hpo,0,0);
#else
fg_vbcopy(32,351,16+vpo,vpb,0,223+hpo,workvb,workvb);
#endif
tile_orgx+=2;
tile_orgy++;
screen_orgx-=(32-scroll_x);
screen_orgy-=(16-scroll_y);
for(i = 0; i < 15; i++)
{
put_tile(21,i);
put_tile(20,i);
}
for(i = 0; i < 22; i++)
put_tile(i,14);
for (i = 0; i < 20; i++)
memcpy(layout[hidden][i],&layout[visual][i+2][1],14);
scrolled = TRUE;
}
}
/* in this case we only have to adjust one row of tiles, columns
stay the same */
else
{
#ifdef ModeX
fg_transfer(0,351,16+vpo,vpb,0,223+hpo,0,0);
#else
fg_vbcopy(0,351,16+vpo,vpb,0,223+hpo,workvb,workvb);
#endif
tile_orgy++;
screen_orgx+=scroll_x;
screen_orgy-=(16-scroll_y);
for(i = 0; i < 22; i++)
{
put_tile(i,14);
}
adjust_layout_down();
scrolled = TRUE;
}
return(OK);
}
/**********************************************************************\
* *
* scroll_right_up *
* This function works exactly the same as scroll_right_down, except *
* it scrolls up instead of down. *
* *
\**********************************************************************/
int scroll_right_up(short scroll_x,short scroll_y)
{
register short i;
/* case where only screen origin coordinates need to be changed */
if (screen_orgx <= 32-scroll_x && screen_orgy >= -scroll_y)
{
screen_orgx+=scroll_x;
screen_orgy+=scroll_y;
}
/* can't scroll, need to wrap around */
else if (tile_orgx >= ncols - 22)
{
return(-1);
}
/* top of map, scroll right only */
else if (tile_orgy <= 0)
{
return(scroll_right(scroll_x));
}
/* case where columns only need to be replaced */
else if (screen_orgx > 32-scroll_x)
{
if (screen_orgy >= -scroll_y)
{
#ifdef ModeX
fg_transfer(32,351,vpo,vpb,0,hpb,0,0);
#else
fg_vbcopy(32,351,vpo,vpb,0,hpb,workvb,workvb);
#endif
tile_orgx+=2;
screen_orgx-=(32-scroll_x);
screen_orgy+=scroll_y;
for(i = 0; i < 15; i++)
{
put_tile(21,i);
put_tile(20,i);
}
adjust_layout_right();
scrolled = TRUE;
}
/* must replace 2 columns and one row of tiles */
else
{
#ifdef ModeX
fg_transfer(32,351,vpo,223+vpo,0,hpb,0,0);
#else
fg_vbcopy(32,351,vpo,223+vpo,0,hpb,workvb,workvb);
#endif
tile_orgx+=2;
tile_orgy--;
screen_orgx-=(32-scroll_x);
screen_orgy+=(16+scroll_y);
for(i = 0; i < 15; i++)
{
put_tile(21,i);
put_tile(20,i);
}
for(i = 0; i < 22; i++)
put_tile(i,0);
for (i = 0; i < 20; i++)
memcpy(&layout[hidden][i][1],layout[visual][i+2],14);
scrolled = TRUE;
}
}
/* adjust only one row of tiles */
else
{
#ifdef ModeX
fg_transfer(0,351,vpo,223+vpo,0,hpb,0,0);
#else
fg_vbcopy(0,351,vpo,223+vpo,0,hpb,workvb,workvb);
#endif
tile_orgy--;
screen_orgx+=scroll_x;
screen_orgy+=(16+scroll_y);
for(i = 0; i < 22; i++)
put_tile(i,0);
adjust_layout_up();
scrolled = TRUE;
}
return(OK);
}
/**********************************************************************\
* *
* start_bullet *
* Spawn a new object which is a bullet coming from the Quickfire *
* plane, and fired toward the enemy plane. *
* *
\**********************************************************************/
void near start_bullet()
{
OBJp node;
/* nine bullets on the screen is enough */
if (nbullets > 9) return;
/* allocate space for the bullet node */
node = (OBJp)malloc(sizeof(OBJ)+3);
if (node == (OBJp)NULL) return;
/* the bullet starts 38 pixels ahead of the Quickfire plane */
node->x = player->x+38;
/* the y position of the bullet depends on whether the plane
is upside down or in a roll */
if (player->image == fighter[4])
node->y = player->y+player->image->yoffset-12;
else
node->y = player->y+player->image->yoffset-2;
/* assign some values */
node->xspeed = 27;
node->yspeed = 0;
node->frame = 0;
node->tile_xmin = 2;
node->tile_xmax = 21;
node->tile_ymin = 0;
node->tile_ymax = 14;
/* assign the sprite */
node->image = sprite[4];
/* assign the action function */
node->action = &bullet_go;
/* insert the new object at the top of the linked list */
if (bottom_node == (OBJp)NULL)
{
bottom_node = node;
node->prev = (OBJp)NULL;
}
else
{
node->prev = top_node;
node->prev->next = node;
}
top_node = node;
node->next = (OBJp)NULL;
/* increment the bullet count */
nbullets++;
/* add another bullet if flying sideways */
if (player->image == fighter[2] || player->image == fighter[6])
{
node = (OBJp)malloc(sizeof(OBJ)+3);
if (node == (OBJp)NULL) return;
node->x = player->x+38;
node->y = player->y+player->image->yoffset-26;
node->xspeed = 27;
node->yspeed = 0;
node->frame = 0;
node->tile_xmin = 2;
node->tile_xmax = 21;
node->tile_ymin = 0;
node->tile_ymax = 14;
node->image = sprite[4];
node->action = &bullet_go;
if (bottom_node == (OBJp)NULL)
{
bottom_node = node;
node->prev = (OBJp)NULL;
}
else
{
node->prev = top_node;
node->prev->next = node;
}
top_node = node;
node->next = (OBJp)NULL;
nbullets++;
}
/* or if within a roll */
else if (player->image == fighter[1] || player->image == fighter[3] ||
player->image == fighter[5] || player->image == fighter[7])
{
node = (OBJp)malloc(sizeof(OBJ)+3);
if (node == (OBJp)NULL) return;
node->x = player->x+38;
node->y = player->y+player->image->yoffset-20;
node->xspeed = 27;
node->yspeed = 0;
node->frame = 0;
node->tile_xmin = 2;
node->tile_xmax = 21;
node->tile_ymin = 0;
node->tile_ymax = 14;
node->image = sprite[4];
node->action = &bullet_go;
if (bottom_node == (OBJp)NULL)
{
bottom_node = node;
node->prev = (OBJp)NULL;
}
else
{
node->prev = top_node;
node->prev->next = node;
}
top_node = node;
node->next = (OBJp)NULL;
nbullets++;
}
}
/**********************************************************************\
* *
* start_enemy_bullet *
* This is where the enemy fighter spawns a bullet to shoot at you. *
* *
\**********************************************************************/
void near start_enemy_bullet(OBJp objp)
{
OBJp node;
/* 9 enemy bullets is enough */
if (nenemy_bullets > 9) return;
/* allocate space for the new object */
node = (OBJp)malloc(sizeof(OBJ)+3);
if (node == (OBJp)NULL) return;
/* assign appropriate values */
node->xspeed = -12;
node->yspeed = 0;
node->frame = 0;
node->tile_xmin = 2;
node->tile_xmax = 21;
node->tile_ymin = 0;
node->tile_ymax = 14;
/* what kind of bullet is fired depends on what kind of plane it is */
if (objp->image == sprite[5])
{
node->image = sprite[6];
node->y = objp->y-22;
node->x = objp->x;
}
else
{
node->image = sprite[8];
node->y = objp->y-2;
node->x = objp->x+26;
}
/* assign the action function */
node->action = &enemy_bullet_go;
/* insert this object at the top of the linked list */
if (bottom_node == (OBJp)NULL )
{
bottom_node = node;
node->prev = (OBJp)NULL;
}
else
{
node->prev = top_node;
node->prev->next = node;
}
top_node = node;
node->next = (OBJp)NULL;
/* increment the enemy bullet count */
nenemy_bullets++;
}
/**********************************************************************\
* *
* start_enemy *
* This is where we start a new enemy fighter. This happens at random *
* intervals when there are less than 5 fighters in the playfield. *
* *
\**********************************************************************/
void near start_enemy()
{
OBJp node;
/* if there are already too many enemies on the screen, return */
if (nenemies >= MAXENEMIES) return;
/* allocate space for this object */
node = (OBJp)malloc(sizeof(OBJ)+3);
if (node == (OBJp)NULL) return;
/* assign values to the variables */
node->xspeed = 7;
node->frame = 1;
node->yspeed = irandom(-3,3);
node->tile_xmin = 2;
node->tile_xmax = 21;
node->tile_ymin = 0;
node->tile_ymax = 14;
/* every 4th airplane is a big sprite */
if (random_number%4 == 0)
node->image = sprite[5];
else
node->image = sprite[7];
/* assign the action function */
node->action = &enemy_go;
/* there is no attached sprite until the enemy starts to explode */
node->attached_sprite = (OBJp)NULL;
/* start flying at the far right side of the screen */
node->x = tile_orgx*16+280;
/* sometimes at the top, sometimes at the bottom */
if (random_number%2 == 0)
node->y = tile_orgy*16+239;
else
node->y = tile_orgy*16 + node->image->height;
/* insert this node at the top of the linked list */
if (bottom_node == (OBJp)NULL )
{
bottom_node = node;
node->prev = (OBJp)NULL;
}
else
{
node->prev = top_node;
node->prev->next = node;
}
top_node = node;
node->next = (OBJp)NULL;
/* also insert this node in the enemy array */
enemy[nenemies] = node;
/* increment the enemy count */
nenemies++;
}
/**********************************************************************\
* *
* start_explosion *
* This function spawns a new object with is the explosion which will *
* be attached to one of the enemy fighters. *
* *
\**********************************************************************/
void near start_explosion(OBJp objp)
{
OBJp node;
/* allocate space for the object */
node = (OBJp)malloc(sizeof(OBJ)+3);
if (node == (OBJp)NULL) return;
/* assign values to the variables */
node->xspeed = 0;
node->yspeed = objp->yspeed/2;
node->tile_xmin = 2;
node->tile_xmax = 21;
node->tile_ymin = 0;
node->tile_ymax = 14;
/* the sprite will be the first frame explosion bitmap */
node->image = explosion[0];
node->x = objp->x+16;
node->y = objp->y-4;
/* start the frame at -1 because the action function will increment
it to 0 before it is displayed */
node->frame = -1;
/* Insert at the top of the linked list. Note, we know this object
will never be at the bottom of the list because it must be
attached to another object, which means the list can not be
empty. */
node->prev = top_node;
node->prev->next = node;
top_node = node;
node->next = (OBJp)NULL;
/* set up the links between the explosion and the enemy plane */
node->attached_sprite = objp;
node->attached_sprite->attached_sprite = node;
/* assign the action function */
node->action = do_explosion;
/* You just scored a hit, so increment the score. The score depends
on how far away the enemy plane was when you hit it. */
player_score += (node->attached_sprite->x - tile_orgx*16+1000);
/* reset the score object's action function */
score->action = &new_score;
/* increment the hit counter */
nhits++;
}
/**********************************************************************\
* *
* start_player_explosion *
* When the player gets hit there is a little explosion sprite that *
* causes no damage. Spawn it here. *
* *
\**********************************************************************/
void near start_player_explosion(OBJp objp)
{
OBJp node;
/* allocate space for the new object */
node = (OBJp)malloc(sizeof(OBJ));
if (node == (OBJp)NULL) return;
/* assign some variables */
node->xspeed = player->xspeed;
node->yspeed = player->yspeed;
node->tile_xmin = 2;
node->tile_xmax = 21;
node->tile_ymin = 0;
node->tile_ymax = 14;
/* assign the sprite */
node->image = explosion[0];
node->x = objp->x;
node->y = objp->y;
/* start the frame at -1 because the action function will increment
it to 0 before it is displayed */
node->frame = -1;
/* assign the action function */
node->action = &do_player_explosion;
/* insert this object at the top of the linked list */
node->prev = top_node;
node->prev->next = node;
top_node = node;
node->next = (OBJp)NULL;
/* set up the links between the explosion and the Quickfire plane */
node->attached_sprite = player;
node->attached_sprite->attached_sprite = node;
}
/**********************************************************************\
* *
* swap *
* Swap the hidden and visual pages by changing the coordinates for *
* the top and bottom of each page, moving the origin of the visual *
* page, and wait for the vertical retrace. *
* *
\**********************************************************************/
void near swap()
{
int x1,x2,y1,y2;
/* vpo = visual page offset, vpb = visual page bottom */
/* hpo = hidden page offset, vpb = hidden page bottom */
vpo = 240 - vpo;
hpo = 240 - hpo;
vpb = vpo+239;
hpb = hpo+239;
/* adjust the globals */
visual = !visual;
hidden = !hidden;
#ifdef ModeX
/* move the visual screen origin to the correct location on the new
visual page */
fg_pan(screen_orgx,screen_orgy+vpo);
#else
x1 = screen_orgx;
x2 = x1+319;
y1 = screen_orgy+vpo;
y2 = y1+199;
fg_vbpaste(x1,x2,y1,y2,0,199);
#endif
}
/**********************************************************************\
* *
* warp *
* Move visible screen to any location on the world map. *
* *
\**********************************************************************/
void near warp(short x,short y)
{
register short i,j;
/* calculate the x and y origins in tile space and screen space */
if (x < 16)
{
tile_orgx = x/16;
screen_orgx = x%16;
}
else
{
tile_orgx = x/16 - 1;
screen_orgx = 16 + x%16;
}
if (y < 16)
{
tile_orgy = y/16;
screen_orgy = y%16;
}
else if (y > world_maxy - 16)
{
tile_orgy = y/16-2;
screen_orgy = 32 + y%16;
}
else
{
tile_orgy = y/16 - 1;
screen_orgy = 16 + y%16;
}
/* rebuild the hidden page by putting all the tiles on it */
for (i = 0; i < 22; i++)
for (j = 0; j < 15; j++)
put_tile(i,j);
/* set the layout arrays to all zeros */
clear_layout();
/* make the hidden page visual */
swap();
/* copy the visual page to the hidden page */
page_copy();
}