/*

------------------------------------------------------------------

Black Nebula

File :				main3d.c
Programmer:		Colin Adams
Date:				29/3/91
Last Modified :	10/6/91

Description:

Controls the main loop of the player's ship.

------------------------------------------------------------------

*/

#define AMIGA_INCLUDES
#include "3d.h"

#define FIRE   1
#define RIGHT  2
#define LEFT   4
#define DOWN   8
#define UP    16

#define DELAY_TIME 2


/*	------------------------------------------------------------------
		External Variables
		------------------------------------------------------------------
*/

extern short swapflag;
extern object myship;
extern int player_score, player_scale;
extern struct RastPort my_rast_port, back_rast_port;
extern struct View my_view, back_view;
extern struct ViewPort my_view_port, back_view_port;
extern struct BitMap my_bit_map;
extern struct BitMap back_bit_map;
extern void AddNewObject(short, short, short, short);
extern void SpinObjCentres(void);
extern int GetPointAngle(int *, short, short);

/*	------------------------------------------------------------------
		Variables
		------------------------------------------------------------------
*/

static int last_draw = 0;
int player_missiles;
short U_rot = 270, W_rot = 180;
short View2_Angle;
int cos_angle[360], sin_angle[360];
char rotate[4];

/* pointers to bitmaps for console update */

PLANEPTR front_alt[4], back_alt[4];
PLANEPTR front_sp[4], back_sp[4];

PLANEPTR front_radar[66][4];
PLANEPTR back_radar[66][4];

char colour_mask[16][4];
UBYTE on_pat[8], off_pat[8];

/*	------------------------------------------------------------------
		Routines
		------------------------------------------------------------------
*/

void SetUpConsole(void)
/* precalculation of bitmap pointers for fast drawing */
{
	unsigned char i, j;
	
	for(i=0; i<16; i++)
	{
		colour_mask[i][0] = i & 1;
		colour_mask[i][1] = i & 2;
		colour_mask[i][2] = i & 4;
		colour_mask[i][3] = i & 8;
	}
	
	for(i=0; i<DEPTH; i++)
	{
		front_alt[i] = my_bit_map.Planes[i] + (G/8) * 225;
		front_sp[i] = my_bit_map.Planes[i] + (G/8) * 245;
		
		back_alt[i] = back_bit_map.Planes[i] + (G/8) * 225;
		back_sp[i] = back_bit_map.Planes[i] + (G/8) * 245;	
		
		for(j=0; j<66; j++)
		{
			front_radar[j][i] = my_bit_map.Planes[i] + (G/8) * (182+j) + 16;
			back_radar[j][i] = back_bit_map.Planes[i] + (G/8) * (182+j) + 16;
		}
	}
	
	on_pat[0] = 1;
	off_pat[0] = ~1;
	
	on_pat[1] = 2;
	off_pat[1] = ~2;
	
	on_pat[2] = 4;
	off_pat[2] = ~4;
	
	on_pat[3] = 8;
	off_pat[3] = ~8;
	
	on_pat[4] = 16;
	off_pat[4] = ~16;
	
	on_pat[5] = 32;
	off_pat[5] = ~32;
	
	on_pat[6] = 64;
	off_pat[6] = ~64;
	
	on_pat[7] = 128;
	off_pat[7] = ~128;
	
}

void UpdateAlt(void)
/* updates the altmeter on the user display */
{
	int byte = ey / (MAX_WORLD_Y/9);
	unsigned int i;
		
	for(i=0; i<DEPTH; i++)
	{
		register PLANEPTR temp, temp2;
		register unsigned int j;
		
		temp = front_alt[i] + byte;
		temp2 = back_alt[i] + byte;
		
		for(j=1; j<=9; j++)
		{
			register unsigned int colour;
			
			if(byte)
			{
				if(j>5)
					colour = j + 4;
				else
					colour = 13 - j;
		
				if(colour_mask[colour][i])
				{
					*temp2 = 255; /* fill byte with colour */
					*temp = 255;
				}
				else
				{
					*temp2 = 0; /* set to background colour */
					*temp = 0;
				}
			}
			
			if(byte<9)
			{
				*(temp + 1) = 0; /* clear to right side */
				*(temp2 + 1) = 0;
			}
			
			temp += G/8;
			temp2 += G/8;
		}
	}
}

void UpdateSpeed(void)
/* 
Updates the speed meter on the user display.  For some strange
reason this code doesn't work on an Amiga 3000 the way it does on
a 2000.  ie. bugs
*/
{
	unsigned int i;
		
	for(i=0; i<DEPTH; i++)
	{
		register PLANEPTR temp, temp2;
		register unsigned int j;
		
		temp = front_sp[i] + velocity;
		temp2 = back_sp[i] + velocity;
		
		for(j=1; j<=9; j++)
		{
			register unsigned int colour;
			
			if(velocity)
			{
				if(j>5)
					colour = j + 4;
				else
					colour = 13 - j;
			
				if(colour_mask[colour][i])
				{
					*temp2 = 255; /* fill byte with colour */
					*temp = 255;
				}
				else
				{
					*temp2 = 0; /* set to background colour */
					*temp = 0;
				}
			}
			
			if(velocity<9)
			{
				*(temp + 1) = 0; /* clear to right side */
				*(temp2 + 1) = 0;
			}
			
			temp += G/8;
			temp2 += G/8;
		}
	}
}

void UpdateRadar(void)
/* updates the radar on the user display */
{

	static PLANEPTR last_bytes_front[TOTAL_MAX_OBJECTS][DEPTH];
	static PLANEPTR last_bytes_back[TOTAL_MAX_OBJECTS][DEPTH];
	
	unsigned int i, loop;
	object *obj;
	
	for(loop=0; loop<last_draw; loop++)
	{
		for(i=0; i<DEPTH; i++)
		{
			*last_bytes_front[loop][i] = 0;
			*last_bytes_back[loop][i] = 0;
		}
	}
	
	last_draw = no_objects + 1;
	
	for(loop=0; loop<=no_objects; loop++)
	{
		register int y, byte, offset, colour, x;
		
		if(loop!=no_objects)
		{
			obj = active_list[loop];
			
			x = obj->trans_x;
			y = obj->trans_z / (MAX_WORLD_Z/65);
			
			switch(obj->type)
			{
				case FIXED: colour = 9; break;
				case ENEMYMISSILE: colour = 10; break;
				case MYMISSILE: colour = 1; break;
				case SHUTTLE: colour = 14; break;
				case KRAIT: colour = 12; break;
				case GECKO: colour = 15; break;
				case THARGOID: colour = 11; break;
				case MAMBA: colour = 8; break;
				case GUIDEDMISSILE: colour = 3; break;
				case EXPLOSION: colour = 0; break;
			}
		}
		else
		{
			x = ex;
			y = ez / (MAX_WORLD_Z/65);
			colour = 8;
		}
		
		byte = x/(MAX_WORLD_X/7); /* get byte number */
		offset = x- (byte*(MAX_WORLD_X/7)); /* get bit offset */
		offset = 7 - (offset/((MAX_WORLD_X/7)/7));
		
		for(i=0; i<DEPTH; i++)
		{
			register PLANEPTR temp, temp2;
			register UBYTE s_read;
			
			temp = front_radar[y][i] + byte;
			temp2 = back_radar[y][i] + byte;
		
			last_bytes_front[loop][i] = temp;
			last_bytes_back[loop][i] = temp2;
			
			if(colour_mask[colour][i]) /* set bit in this plane */
			{
				s_read = *temp2;
				s_read |= on_pat[offset];
				*temp2 = s_read;
				
				s_read = *temp;
				s_read |= on_pat[offset];
				*temp = s_read;
			}
			else /* have to clear the bit in this plane */
			{
				s_read = *temp2;
				s_read &= off_pat[offset];
				*temp2 = s_read;
				
				s_read = *temp;
				s_read &= off_pat[offset];
				*temp = s_read;
			}
		}
	}
}
	
void Setupangles(void)
{
	FILE *fp;
	int i, j;
	double d;
	
	if(!(fp=fopen("moretrig.pre","r")))
	{
		printf("Calculating fixed point sin/cos\n");
		
		for(i=0; i<=359; i++)
		{
			d = sin(deg(i))*1000;
			j = d;
		
			sin_angle[i] = -j;
		
			d = cos(deg(i))*1000;
			j = d;
		
			cos_angle[i] = j;
		
		}
		
		for(i=0; i<=4096; i++)
		{
			double t = i;
			anti_sin[i] = asin(t/4096) * 57.29578;
		}
		
		if(!(fp=fopen("moretrig.pre","w")))
		{
			printf("Failed to save sin/cos data!\n");
			return;
		}
		
		fwrite((void *) &sin_angle[0], sizeof(int), 360, fp);
		fwrite((void *) &cos_angle[0], sizeof(int), 360, fp);
		fwrite((void *) &anti_sin[0], sizeof(int), 4097, fp);
		fclose(fp);
	}
	else
	{
		fread((void *) &sin_angle[0], sizeof(int), 360, fp);
		fread((void *) &cos_angle[0], sizeof(int), 360, fp);
		fread((void *) &anti_sin[0], sizeof(int), 4097, fp);
		fclose(fp);
	}
}

void PrintShip(char *name)
{
	struct rastport *temp = rastport;
	rastport = &my_rast_port;
	SetAPen(rastport, 0);
	RectFill(rastport, 129, 183, 189, 243);
	SetAPen(rastport, 11);
	Move(rastport, 132, 215);
	Text(rastport, name, strlen(name));
	rastport = &back_rast_port;
	SetAPen(rastport, 0);
	RectFill(rastport, 129, 183, 189, 243);
	SetAPen(rastport, 11);
	Move(rastport, 132, 215);
	Text(rastport, name, strlen(name));
	rastport = temp;
}

void Print2Ship(char *name)
{
	struct rastport *temp = rastport;
	rastport = &my_rast_port;
	SetAPen(rastport, 11);
	Move(rastport, 132, 230);
	Text(rastport, name, strlen(name));
	rastport = &back_rast_port;
	SetAPen(rastport, 11);
	Move(rastport, 132, 230);
	Text(rastport, name, strlen(name));
	rastport = temp;
}

void IntroShips(void)
/*
Spins each ship in the game around for a while.
Note the hidden surface bug in the shuttle!
*/
{
	int counter = 10, timeout = 0, timeoutlim = 300;
	UBYTE value = 0;
	
	AddNewObject(counter, 4000, 360, 2000);
	PrintShip("Player"); Print2Ship("Ship");
	
	while(value!=FIRE)
	{
		register int i;
		
		value = Joystick();
		
		timeout++;
		
		if(timeout==timeoutlim) /* make it blow up! */
		{
			active_list[0]->i_am_dying = 1;
			active_list[0]->explode = 1;
			StartSound(2);
		}
			
		if(no_objects==0) /* create a new object */
		{
			Delay(100);
			counter--;
			if(counter<0) counter = 10;
			if(counter==9) counter = 7;
			
			switch(counter)
			{
				case 0: PrintShip("Enemy"); Print2Ship("Missile"); break;
				case 1: PrintShip("Space"); Print2Ship("Station"); break;
				case 2: PrintShip("Shuttle"); break;
				case 3: PrintShip("Krait"); break;
				case 4: PrintShip("Gecko"); break;
				case 5: PrintShip("Thargoid"); break;
				case 6: PrintShip("Cruiser"); break;
				case 7: PrintShip("Player"); Print2Ship("Missile"); break;
				case 10: PrintShip("Player"); Print2Ship("Ship"); break;
			}
				
			KillAllObjects();
			SetUp3d();
			AddNewObject(counter, 4000, 360, 2000);
			timeoutlim = (active_list[0]->numpoints > 10) ? 150 : 300;
			timeout = 0;
		}
		
		for(i=0; i<no_objects; i++)
		{
			register object *obj = active_list[i];

			if(!obj->i_am_dying)
				obj->drawme = 1;
			else
				continue;
				
			if(obj->type==EXPLOSION)
			{
				int rad;
			
				if(--obj->timeinflight==0)
				{
					obj->i_am_dying = 1;
					break;
				}
				
				rad = getrandom(0,10);
				
				if(obj->up_or_down)
				{
					if(obj->trans_y<MAX_WORLD_Y-100)
						obj->trans_y += rad;
				}
				else
				{
					if(obj->trans_y>100)
						obj->trans_y -= rad;
				}
				
				if(rad>5)
					rad = getrandom(1, 6);
				else
					rad = -getrandom(1, 6);
				
				Rotate_Obj_Rel(obj, rad+2, rad-1, rad+1);
			
				obj->trans_x += FastCos(obj->velocity, obj->heading);
				obj->trans_z -= FastSin(obj->velocity, obj->heading);

				if(obj->trans_x<0)
					obj->trans_x = MAX_WORLD_X;
				else if(obj->trans_x > MAX_WORLD_X)
					obj->trans_x = 0;
			
				if(obj->trans_z<0)
					obj->trans_z = MAX_WORLD_Z;
				else if(obj->trans_z>MAX_WORLD_Z)
					obj->trans_z = 0;
			}
			else
					Rotate_Obj_Rel(obj, 5, 5, 5);
		}
	
		/* still interactive */
		
		if(value & LEFT)
		{
			U_rot+=2;
			W_rot+=2;
		}
		else if(value & RIGHT)
		{
			U_rot-=2;
			W_rot-=2;
			
		}

		if(value & UP)
		{
			if(ey <= MAX_WORLD_Y-Y_OFFSET)
			{
				UpdateAlt();
				ey += Y_OFFSET;
			}
		}
		else if(value & DOWN)
		{
			if(ey>0)
			{
				ey -= Y_OFFSET;
				UpdateAlt();
			}
		}

		if(U_rot>359)
			U_rot-=360;
		else if(U_rot<0)
			U_rot+= 360;
		
		if(W_rot>359)
			W_rot-=360;
		else if(W_rot<0)
			W_rot+= 360;
			
		ux.numer = cos_angle[U_rot];
		uz.numer = sin_angle[U_rot];
		
		wx.numer = cos_angle[W_rot];
		wz.numer = sin_angle[W_rot];

		View_Angle = U_rot;

		SpinObjCentres();		
		CalculateObjects();
		ClearObjects();
		DisplayObjects();
				
 		if(swapflag)
		{
			rastport = &my_rast_port;
			swapflag = 0;
			WaitBOVP(&my_view_port);
			LoadView(&back_view);
		}
		else
		{
			rastport = &back_rast_port;
			swapflag = 1;
			WaitBOVP(&back_view_port);
			LoadView(&my_view);
		}
	}
}

void PrintView(char *name)
{
	register struct rastport *temp = rastport;
	rastport = &my_rast_port;
	SetAPen(rastport, 0);
	RectFill(rastport, 245, 196, 280,210);
	SetAPen(rastport, 11);
	Move(rastport, 245, 206);
	Text(rastport, name, strlen(name));
	rastport = &back_rast_port;
	SetAPen(rastport, 0);
	RectFill(rastport, 245, 196, 280 , 210);
	SetAPen(rastport, 11);
	Move(rastport, 245, 206);
	Text(rastport, name, strlen(name));
	rastport = temp;
}

void PrintRot(void)
{
	register struct rastport *temp = rastport;
	register short t = U_rot;
	
	rotate[0] = t / 100;
	t = t - (rotate[0]*100);
	rotate[1] = t / 10;
	t = t - (rotate[1]*10);

	rotate[2] = t + '0';
	rotate[0] += '0';
	rotate[1] += '0';
	
	rastport = &my_rast_port;
	SetAPen(rastport, 0);
	RectFill(rastport, 245, 210, 280, 221);
	SetAPen(rastport, 11);
	Move(rastport, 245, 220);
	
	Text(rastport, rotate, 3);
	rastport = &back_rast_port;
	SetAPen(rastport, 0);
	RectFill(rastport, 245, 210, 280 , 221);
	SetAPen(rastport, 11);
	Move(rastport, 245, 220);
	Text(rastport, rotate, 3);
	rastport = temp;
}

void Do3d(void)
{
	SetUp3d();
	KillAllObjects();
	
	AddObject("cube.object", FIXED);			 
	AddObject("ops_missile.object", ENEMYMISSILE);
	AddObject("missile.object", MYMISSILE);
	AddObject("krait.object", KRAIT);
	AddObject("shuttle.object",SHUTTLE);
	AddObject("gecko.object", GECKO);
	AddObject("thargoid.object", THARGOID);
	AddObject("cruiser.object", MAMBA);
	AddObject("guided.object",GUIDEDMISSILE);
	AddObject("player.object",PLAYER);
	
	SetUpViews();
	SetUpConsole();
	
	player_score = player_scale = last_draw = 0;
	
	Delay(25);

	IntroShips();
	
	SetUp3d();
	KillAllObjects();
	loadpic("screen.iff");

	/* clear radar area and screen display */	
	{
		struct rastport *temp = rastport;
		rastport = &my_rast_port;
		SetAPen(rastport, 0);
		RectFill(rastport, 129, 183, 189, 243);
		RectFill(rastport, X_MIN, Y_MIN, X_MAX, Y_MAX);
		rastport = &back_rast_port;
		SetAPen(rastport, 0);
		RectFill(rastport, 129, 183, 189, 243);
		RectFill(rastport, X_MIN, Y_MIN, X_MAX, Y_MAX);
		rastport = temp;
	}

	ez = 4000;
	player_missiles = 25;
	
	PrintView("Front");
	PrintRot();

	while(!am_i_dead)
	{
		register UBYTE value = Joystick();
		register int speed;
		static int fireframe = 21;
		
		fireframe++;
		
		if(value & FIRE)
		{
			if(fireframe>5)
			{
				int tx = ex + FastCos(150, View_Angle);
				int tz = ez - FastSin(150, View_Angle);
			
				AddNewObject(MYMISSILE, tx, ey-100, tz);
				StartSound(0);
				fireframe = 0;
			}
		}
					
		if(value & LEFT)
		{
			U_rot+=2;
			W_rot+=2;
			PrintRot();
		}
		else if(value & RIGHT)
		{
			U_rot-=2;
			W_rot-=2;
			PrintRot();
			
		}

		if(value & UP)
		{
			if(ey <= MAX_WORLD_Y-Y_OFFSET)
			{
				UpdateAlt();
				ey += Y_OFFSET;
			}
		}
		else if(value & DOWN)
		{
			if(ey>0)
			{
				ey -= Y_OFFSET;
				UpdateAlt();
			}
		}

		value = Keyboard(); /* read keyboard */
		
		if(value==0x40) /* space, increase speed */
		{
			if(velocity<9)
			{
				velocity++;
				UpdateSpeed();
			}
		}
		else if(value==0x41) /* backspace, decrease speed */
		{
			if(velocity)
			{
				velocity--;
				UpdateSpeed(); /* only updates if changed */
			}
		}
		else if(value==0x45) /* escape */
			break;
		else if(value==0x19) /* P key, pause */
			while(Keyboard()!=0x16); /* wait for a u key press */
		else if(value==0x50) /* front view */
		{
			PrintView("Front");
			view_mode = 0;
		}
		else if(value==0x51) /* left view */
		{
			PrintView("Left");
			view_mode = 1;
		}
		else if(value==0x52) /* right view */
		{
			PrintView("Right");
			view_mode = 3;
		}
		else if(value==0x53) /* back view */
		{
			PrintView("Rear");
			view_mode = 2;
		}
		else if(value==0x37)
		{
			if(fireframe>5 && player_missiles)
			{
				int tx = ex + FastCos(150, View_Angle);
				int tz = ez - FastSin(150, View_Angle);
			
				AddNewObject(GUIDEDMISSILE, tx, ey-100, tz);
				StartSound(0);
				fireframe = 0;
				player_missiles--;
			}
		}
		
		/* correct rotations in case it went over 360 or under 0 */
		
		if(U_rot>359)
			U_rot-=360;
		else if(U_rot<0)
			U_rot+= 360;
		
		if(W_rot>359)
			W_rot-=360;
		else if(W_rot<0)
			W_rot+= 360;
			
		switch(view_mode)
		{
			case 0: View_Angle = U_rot; break;
			case 1: View_Angle = U_rot + 90; break;
			case 2: View_Angle = U_rot + 180; break;
			case 3: View_Angle = U_rot + 270; break;
		}
		
		if(View_Angle>359) View_Angle -= 360;
		
		View2_Angle = View_Angle - 90;
		
		if(View2_Angle<0) View2_Angle += 360;
		
		ux.numer = cos_angle[View_Angle];
		uz.numer = sin_angle[View_Angle];
		
		wx.numer = cos_angle[View2_Angle];
		wz.numer = sin_angle[View2_Angle];

		/* Very simple code to move the ship according to velocity */
		
		speed = velocity * 2;
		ex += FastCos(speed, U_rot);
		ez -= FastSin(speed, U_rot);
		
		/* Keep ship in the world */
		
		if(ex<0)
			ex = MAX_WORLD_X;
		else if(ex>MAX_WORLD_X)
			ex = 0;
			
		if(ez<0)
			ez = MAX_WORLD_Z;
		else if(ez>MAX_WORLD_Z)
			ez = 0;
		
		SpinObjCentres();
		MoveObjects();
		UpdateObjects();
		CalculateObjects();
		ClearObjects();
		UpdateRadar();
		DisplayObjects();
				
		/* swap views */
		
 		if(swapflag)
		{
			rastport = &my_rast_port;
			swapflag = 0;
			WaitBOVP(&my_view_port);
			LoadView(&back_view);
		}
		else
		{
			rastport = &back_rast_port;
			swapflag = 1;
			WaitBOVP(&back_view_port);
			LoadView(&my_view);
		}
	}
	
	if(am_i_dead)
	/*
	A really cool extern death sequence!  Soon the wonder of external
	views won't be confined to the death sequence
	*/
	{
		int i;
		
		StartSound(1);
		
		PrintView("Death");
		AddNewObject(PLAYER, ex-100, ey-50, ez-50); /* add a ship then blow it up! */
		active_list[no_objects-1]->i_am_dying = 1;
		active_list[no_objects-1]->explode = 1;
		
		for(i=0; i<30; i++)
		{
			register int px, py, pz, speed;
			
			speed = velocity * 2;
			ex += FastCos(speed, U_rot);
			ez -= FastSin(speed, U_rot);
		
			/* Keep ship in the world */
			
			if(ex<0)
				ex = MAX_WORLD_X;
			else if(ex>MAX_WORLD_X)
				ex = 0;
			
			if(ez<0)
				ez = MAX_WORLD_Z;
			else if(ez>MAX_WORLD_Z)
				ez = 0;
		
			
			if(ex>(MAX_WORLD_X/2))
				px = ex - 1000;
			else
				px = ex + 1000;
			
			py = ey+150;
			
			if(ez>(MAX_WORLD_Y/2))
				pz = ez - 1000;
			else
				pz = ez + 1000;
				
			
			SpinObjCentres();
			
			{
				int dummy;
				View_Angle = GetPointAngle(&dummy, px, pz);
			}
			
			View2_Angle = View_Angle - 90;
		
			if(View2_Angle<0) View2_Angle += 360;
		
			ux.numer = cos_angle[View_Angle];
			uz.numer = sin_angle[View_Angle];
		
			wx.numer = cos_angle[View2_Angle];
			wz.numer = sin_angle[View2_Angle];

			{
				register int tempx, tempy, tempz;
				
				tempx = ex;
				tempy = ey;
				tempz = ez;
				ex = px;
				ey = py;
				ez = pz;
				
				
				MoveObjects();
				CalculateObjects();
				ClearObjects();
				UpdateRadar();
				DisplayObjects();
				
				ex = tempx;
				ey = tempy;
				ez = tempz;
			}
				
			/* swap views */
		
	 		if(swapflag)
			{
				rastport = &my_rast_port;
				swapflag = 0;
				WaitBOVP(&my_view_port);
				LoadView(&back_view);
			}
			else
			{
				rastport = &back_rast_port;
				swapflag = 1;
				WaitBOVP(&back_view_port);
				LoadView(&my_view);
			}
		}
	}
	
	KillAllObjects();
	FreeView();
}

/* end of module */