#include <exec/types.h>
#include <exec/memory.h>
#include <graphics/view.h>
#include <intuition/intuition.h>
#include <graphics/gfxmacros.h>
#include <proto/exec.h>
#include <proto/graphics.h>
#include <proto/intuition.h>
#include <dos.h>
#include <proto/dos.h>
#include <math.h>
#include <exec/devices.h>
#include <devices/audio.h>
#include <hardware/custom.h>

#define CUE 0
#define BLK 5
#define GREENBAIZE 0
#define BLUEBAIZE 1

#define BASE 200 /* V.important constant. Used in ALL physical calcs. */
#define BASESP 1000
#define BASEII 4 /* For root calcs which need plenty of precision */

#define TABLELENGTH (272 * BASE) /* 272 is actual length in pixels */
#define TABLEWIDTH ((TABLELENGTH * 5 / 8)-BASE)

#define X1CORNER 24
#define Y1CORNER 62
#define X2CORNER (X1CORNER + TABLELENGTH / BASE)
#define Y2CORNER (Y1CORNER + TABLEWIDTH / BASE)

#define BALLRADIUS (7 * BASE) /* 7 pixels */
#define BALLDIAMETER (BALLRADIUS * 2)
#define SCRNBALLDIAMETER (BALLDIAMETER / BASE)
#define SEMICIRCRAD (TABLEWIDTH/6)
#define PLACEPACK (TABLELENGTH * 5 / 8)
#define PACKHORIZSEP (BALLDIAMETER - 2*BASE)
#define POCKET (BALLRADIUS + 5*BASE)
#define SCRNPOCKET (POCKET / BASE)
#define CUETIP 10
#define MAXCUELENGTH 80

#define CLOCKWISE 1
#define ANTICLOCKWISE 2
#define SMALL 1
#define LARGE 2
#define UP 1
#define DOWN 2
#define NOHIT -1
#define ON TRUE
#define OFF FALSE
#define HIT 3
#define UPDATE 3
#define ICON 2
#define VIEW 1
#define NOREFRESH 2

#define RACKED 1
#define BROKEN 0
#define OPEN 2
#define SPREAD 3

#undef SINGLE

enum icons { PLYR1,PLYR2,PRACTICE,DEMO,PLAY,QUIT,_BAIZE,_BALL1,_BALL2,
	SINGLE,BEST3,BEST5,UNLIMITED,STRIP,BALL10,BALL16,
	OPPONENTBASE,ICON17,ICON18,ICON19,STRIPPERBASE };

enum args { GAMETYPE,GAMELENGTH,BAIZE,BALL1,BALL2,BALLCOUNT,OPPONENT,STRIPPER };

#define HUMANPLYR 0
#define COMPLYR   1

#define FRICTION (BASESP / 300)
#define SPINFRICTION 3
#define SPEEDINC (BASESP / 16)
#define MAXSPEED (BASESP * 4)
#define MINSPEED (BASESP / 4)

#define ESC 69
#define RETURN 68
#define SPACE 64
#define HELP 95
#define LCSR 79
#define RCSR 78
#define UCSR 76
#define DCSR 77
#define F1 80
#define F2 81
#define F3 82
#define F4 83
#define F5 84
#define F6 85
#define F7 86
#define F8 87
#define F9 88
#define F10 89

#define FLASH 2

#define JOYFIRE 0xbfe001
#define LMOUSE 1
#define RMOUSE 2

#define MEMSIZE 7000

void DoControl(void);
void DrawCue(short);
void ChangePower();
short ChangeSpin(void);
void DrawCross(short);
void MoveCue(short,short);
void DoAssigns(void);
void SetUpTable(void);
void DoOptions(void);
short GetInput(void);
char UpdateBallPos(void);
void WaitBeam(short);
short CheckPotted(short);
void CheckBounce(short);
void CheckCollision(void);
void MakeCollision(short,short);
void DrawBall(short,short);
void DrawBallDirect(short,short,short);
short FindAngle(WORD,WORD,WORD);
void SuperImpose(struct RastPort *,short,short,struct RastPort *,
	short,short,short,short,short,short);
void WriteMessage(short,short,short,short);
void ScrubMessage(void);
void PlayShot(void);
void CheckResult(void);
void CheckWhitePotted(void);
short TouchingBall(short);
void PlayComputerShot(void);
void CheckBallDrag(void);
void CopyBalls(struct ballstruct*,struct ballstruct*);
void CopyPlayer(short,short);
WORD QuickRoot(long);
short GetMouse(void);
char LoadIFF(struct IFFGraphic *,struct Screen *,char);
char ClickDelay(short);
void CueJumpAnim(void);
void CheckAngle(short *);
UWORD Rand(UWORD);
long MyRead(struct FileHandle *,long *,long);
char OpenIt(void);
void CloseIt(void);

APTR GetDeviceBlock(ULONG);
void FreeDeviceBlock(struct IOAudio *,BYTE);
void Audio_PerVol(struct IOAudio *,UWORD,UWORD);
void Audio_Write(struct IOAudio *,UBYTE *,UWORD);
void PlaySound(struct audioinfostruct *,short);

/******************* Graphics defines ******************/
struct NewScreen ns =
{
	0,0,			/* Left edge, top */
	320,256,		/* Width, height */
	5,			/* Depth (32 colours) */
	0,1,			/* Detail, block pens */
	0,			/* Modes for ViewPort */
	CUSTOMSCREEN | SCREENQUIET,
				/* Screen type */
	NULL,		/* Text attributes */
	NULL,		/* Title */
	NULL,		/* Gadget list */
	NULL			/* Custom bitmap pointer */
};

struct NewWindow nw =
{
	0,0,			/* Starting corner */
	320,256,		/* Width, height */
	0,1,			/* detail, block pens */
	RAWKEY,		/* IDCMP flags */
	BORDERLESS | ACTIVATE | RMBTRAP,
				/* Window flags */
	NULL,		/* Pointer to first gadget */
	NULL,		/* Pointer to checkmark */
	NULL,		/* title */
	NULL,		/* screen pointer (don't know yet) */
	NULL,		/* bitmap pointer */
	0,0,0,0,		/* window not sized */
	CUSTOMSCREEN	/* type of screen */
};

/* Extra padding for use with tempmask. Must set ballmask to point to */
/* first 'real' byte (8) at start of program */

USHORT chip crossmask[5] = { 0x2000,0x2000,0xd800,0x2000,0x2000 };

USHORT chip mask[37] = { 0,0,0,0,0,0,0,0,0,0,0,0,
	0x3e0,0xff8,0x1ffc,0x1ffc,0x3ffe,0x3ffe,
	0x3ffe,0x3ffe,0x3ffe,0x1ffc,0x1ffc,0xff8,0x3e0,
	0,0,0,0,0,0,0,0,0,0,0,0 };

USHORT chip mask2[10] = { 0x3e0,0xff0,0xff8,0x1ff8,0x1ff8,0x1ff8,
	0x1ff0,0x1ff0,0xfe0,0x780 };

USHORT chip hybridmask[13] =
	{ 0x3e0,0xff8,0x7fc,0x3fc,0x1fe,0xfe,0x7e,0x3e,0x1e,0xc,0x4,0,0 };
	
USHORT chip hybridmask2[10] =
	{ 0x3e0,0x7f0,0x3f8,0x1f8,0x0f8,0x078,0x030,0x010,0,0 };

USHORT chip miniball[8] =
	{ 0x3c00,0x7e00,0xff00,0xff00,0xff00,0xff00,0x7e00,0x3c00 };

USHORT *ballmask = &mask[12];
USHORT *ballmask2 = mask2;
USHORT chip tempmask[256];

USHORT chip buffer[20];

/********************* System defines ************************/
struct Screen 		*s,*s2;
struct Window 		*w,*w2;
struct RastPort	*rp,*rp2,bufrp;
struct BitMap		*bm,*bm2,bufbm;
struct ViewPort 	*vp;
struct ColorMap 	*cm;
struct IntuiMessage *msg;
struct FileHandle	*fh;
struct Preferences  prefs;

UWORD *colorpalette;

char *bp0,*bp1,*bp2,*bp3,*bp4;

extern UWORD chip pointeron[24];
extern UWORD chip pointeroff[8];

long oldkeydelay,oldkeyspeed;

/******************* Audio Defines ******************/

#define SAMPLES 8
#define AM2 106 /* Audio Master II info size */
#define BUFWAVELEN 100

enum sound { COLLIDE,STRIKE,BOUNCE,POTTED,CHEER,LONGCHEER,CHANT,CRASH };

#define AUDIO_LEN (sizeof(struct IOAudio))
#define PRECIDENCE 127
#define CLOSE_DEVICE 1

#define BUFFER 0
#define RESET  1

struct IOAudio *audio[4];
char Channel_Map[] = { 1,2,4,8 },*bufwave;

struct audioinfostruct
{
	struct IOAudio *audio;
	char *wave,bufstate,channel,flag;
	long wavelen;
	short volume,period;
	char filename[30];
};

struct audioinfostruct audioinfo[SAMPLES] = {
{ 0,0,0,0,BUFFER,702-AM2,0,320,"FNPAUDIO:collide.sam" },
{ 0,0,0,1,BUFFER,544-AM2,0,600,"FNPAUDIO:strike.sam" },
{ 0,0,0,2,RESET,448-AM2,0,640,"FNPAUDIO:bounce.sam" },
{ 0,0,0,3,BUFFER,1386-AM2,0,315,"FNPAUDIO:pot.sam" },
{ 0,0,0,2,RESET,19708-AM2,0,550,"FNPAUDIO:cheer.sam" },
{ 0,0,0,2,RESET,36682-AM2,0,550,"FNPAUDIO:cheer2.sam" },
{ 0,0,0,2,RESET,20432-AM2,0,536,"FNPAUDIO:chant.sam" },
{ 0,0,0,2,RESET,18960-AM2,0,286,"FNPAUDIO:crash.sam" }};

/********************* Gameplay defines **********************/

UWORD rgb[32];
UWORD rgb2[32] = {
	0x000,0xfb9,0xd86,0x593,0x030,0x400,0x610,0x820,
	0x150,0xfc0,0xf00,0x382,0xfff,0xfff,0xfcc,0x270,
	0x444,0xb8b,0x000,0xfff,0xf83,0xfc7,0x000,0x000,
	0x000,0xfff,0x888,0x000,0x9cf,0x000,0x000,0x000 };

enum col {
 _back,_leather,_dkleather,_ltbaize,_dkbaize,_dkwalnut,_walnut,_ltwalnut,
 _baize,_yellow,_red,_ltbaize2,_white,_highyel,_highred,_ltbaize3,
 _cross,_ptr17,_ptr18,_ptr19,_cuecol,_ltcuecol,_22,_23,
 _black,_text1,_text2 };

enum balltype { white,black,yellow,red,anyball,baize };
char ballpalette[] = { _white,_black,_yellow,_red, 0,_baize };

char ball15colour[16] = {
white,yellow,red,yellow,yellow,black,red,red,
yellow,red,yellow,yellow,red,yellow,red,red };

char ball9colour[10] = {
white,red,red,red,red,black,red,red,red,red };

char ballplanes[] = { 4,16,1,2, 0,23 };

struct ballstruct
{
	enum balltype colour;
	char potted,justpotted,hasmoved,lasthit,olddx,olddy;
	USHORT x,y;
	short speed,angle,scrnx,scrny,oldscrnx,oldscrny;
};
struct ballstruct ball[16],tempball[16];

struct pixelsavestruct
{
	char col;
	short x,y;
};
struct pixelsavestruct pixelsave[32];

struct blitdatstruct
{
	short x,y,dx,dy;
};
struct blitdatstruct blitdat,blitdat2;

char *quickarcsin;
short *memblock,*quicksin,*quickcos,
	cueangle=270,cuespeed=MINSPEED,powerdirn=UP,
	spinx,spiny,swerve,swervetotal,
	gametype,gamelength,stripper,opponent,
	defaultdemo,enableoptions=TRUE,flipoffer,
	cloth,cueline=ON,firsthit,dotspacing=8,cheer=ON,
	plyrbreak,turn,comp1,comp2,
	balls=16,dragcheat,cheatangle=-1,cheatball,
	foul,endofgame,quitgame,winner,matches,coin,packstate,newbreak;

USHORT seed; /* random element */

USHORT pocketx[6] = { (TABLELENGTH-BALLRADIUS),(TABLELENGTH-BALLRADIUS),
	(TABLELENGTH/2),(TABLELENGTH/2),BALLRADIUS,BALLRADIUS };

USHORT pockety[6] = { (TABLEWIDTH-BALLRADIUS),BALLRADIUS,
	(TABLEWIDTH-BALLRADIUS),BALLRADIUS,(TABLEWIDTH-BALLRADIUS),BALLRADIUS };
	
char angleused[360];	

char joydelay;

extern struct Custom far custom;

/********* Loader stuff *************/

extern struct IFFGraphic
{
char		FileName[30];	/* Name of IFF file */
BYTE		ReqDepth;		/* How many bitplanes wish to be Displayed */  
USHORT	*RGB;		/* RGB colour values, filled by LoadIFF */ 
short	Width;		/* Width of graphic, filled by LoadIFF */ 
short	Height;		/* Height of graphic, filled by LoadIFF */ 
BYTE		Depth;		/* Depth of graphic, filled by LoadIFF */ 
};

extern char arg[8],bufarg[8],demoarg[8];

struct IFFGraphic
tableIFF =  { "FNPGRAPHICS:table.img",5,NULL },
offerIFF = { "FNPGRAPHICS:offer.img",5,rgb },
offer2IFF = { "FNPGRAPHICS:offer2.img",5,rgb };

UWORD RGBbaize[] = {
0x593,0x030,0x150,0x382,0x270, /* green */
0x368,0x023,0x034,0x156,0x145, /* blue */
0x965,0x421,0x632,0x854,0x743, /* brown */
0x777,0x333,0x444,0x666,0x555 }; /* grey */

UWORD RGBball[] = { 0xf00,0xf40,0xfa0,0x390,0x05f,0x608,0xf55 };

UWORD RGBroom[16];

/********** Strip stuff *****************/

struct IFFGraphic roomIFF = { "FNPGRAPHICS:room.img",5,RGBroom };

struct IFFGraphic stripimageIFF[4] = {
{ "FNPGRAPHICS:claire_sally.img",5,rgb },
{ "FNPGRAPHICS:michael.img",5,rgb },
{ "FNPGRAPHICS:claire_sally.img",5,rgb },
{ "FNPGRAPHICS:josh.img",5,rgb }};

struct IFFGraphic striptextIFF[4] = {
{ "FNPGRAPHICS:quotes.img",5,rgb },
{ "FNPGRAPHICS:quotes.img",5,rgb },
{ "FNPGRAPHICS:quotes.img",5,rgb },
{ "FNPGRAPHICS:quotes2.img",5,rgb }};

struct stripstruct
{
	short imgx1[4],imgy1[4],imgdx[4],imgdy[4],imgx2[4],imgy2[4],
		 textx[5],texty[5],textdx[5],textdy[5];
};

struct stripstruct stripinfo[4] = {
{ 192,129,65,0, 0,0,0,0, 60,60,60,60,
  139,139,139,139, 19,19,19,19, 68,68,68,68,
  0,113,89,0,207, 0,0,39,39,39, 112,200,117,88,88, 32,39,39,32,32 },

{ 195,128,61,0, 0,0,0,0, 65,65,65,58,
  149,149,149,149, 11,11,11,11, 57,57,57,57,
  135,0,0,237,148, 80,172,211,153,153, 110,147,118,82,88, 32,39,41,53,32 },

{ 211,140,70,0, 139,139,139,139, 67,67,67,67,
  112,112,112,112, 27,27,27,27, 90,90,90,90,
  0,135,0,224,245, 133,113,80,113,80, 117,88,133,81,75, 39,32,53,39,32 },

{ 170,108,57,0, 0,0,0,0, 56,56,50,50,
  171,171,161,161, 17,28,23,23, 50,51,50,50,
  0,0,171,0,139, 60,0,0,106,60, 139,171,149,131,113, 46,60,53,46,53 }};

/******* Message stuff *****************/

#define NEW 0
#define FOLLOW 1

enum msg { MSG_PLACECUEBALL,MSG_FOUL,MSG_WINS,MSG_MICHAEL,MSG_JOSH,
	MSG_CLAIRE,MSG_SALLY,MSG_PLAYER1,MSG_PLAYER2,MSG_QUITTING,
	MSG_VS,MSG_GAME,MSG_OF,MSG_0,MSG_1,MSG_2,MSG_3,MSG_4,MSG_5,
	MSG_GOODSHOT,MSG_SHOTCANCELLED,MSG_BADLUCK,MSG_WELLDONE,
	MSG_QUITGAME,MSG_ISTHECHAMP,MSG_OOOPS };

short cursor;

struct messagestruct { short x1,y1,x2,y2; };
struct messagestruct message[] = {
{ 0,0,122,13 }, { 123,0,174,13 }, { 175,0,214,13 }, { 215,0,275,13 },
{ 276,0,313,13 },

{ 0,14,44,31 }, { 45,14,82,31 }, { 83,14,144,31 },
{ 145,14,208,31 }, { 209,14,284,31 }, { 285,14,301,31 },

{ 0,32,44,45 }, { 45,32,60,45 }, { 240,60,253,77 }, { 61,32,64,45 },
{ 65,32,72,45 }, { 73,32,80,45 }, { 81,32,89,45 }, { 90,32,97,45 },
{ 98,32,196,45 }, { 197,32,317,45 },

{ 0,46,74,59 }, { 75,46,160,59 },
	
{ 0,60,127,77 }, { 128,60,239,77 }, { 240,60,299,77 }};

/************ Player stuff **************/

struct playerstruct
{
	enum balltype colour;
	char goes,wins;
	char nametag;
	short maxpower;
	char cuepulls,skill;
};

struct playerstruct player[8] = {
{ 0,0,0,0,MAXSPEED,0,0 }, /* Filled 1 */
{ 0,0,0,0,MAXSPEED,0,0 }, /* Filled 2 */

{ 0,0,0,MSG_PLAYER1,MAXSPEED,1,3 }, /* Player 1 */
{ 0,0,0,MSG_PLAYER2,MAXSPEED,1,3 }, /* Player 2 */
{ 0,0,0,MSG_CLAIRE,MAXSPEED,2,0 }, /* Claire */
{ 0,0,0,MSG_MICHAEL,MAXSPEED+MAXSPEED/2,0,2 }, /* Mike */
{ 0,0,0,MSG_SALLY,MAXSPEED-MAXSPEED/4,1,3 }, /* Sally */
{ 0,0,0,MSG_JOSH,MAXSPEED,3,3 }}; /* Josh */

/**********************/
main()
{
	short i,match;
	
	if (!OpenIt()) { CloseIt(); return; }
	DoAssigns();
	if (Loader(1)) do
	{
		coin=-1; DoOptions();
		switch(gamelength)
		{
			case BEST3:
			case BEST5:
				for(match=0; match<matches; match++)
				{
					WriteMessage(MSG_GAME,5,5,NEW);
					WriteMessage(MSG_1+match,0,5,FOLLOW);
					WriteMessage(MSG_OF,0,5,FOLLOW);
					WriteMessage((MSG_1-1)+matches,0,5,FOLLOW);
					SetUpTable(); Delay(100); DoControl();
					if (player[0].wins>matches/2 ||
						player[1].wins>matches/2 ||
						quitgame) break;
					WriteMessage(player[1-winner].nametag,5,5,NEW);
					WriteMessage(MSG_0+player[1-winner].wins,15,5,FOLLOW);
					Delay(65);
					WriteMessage(player[winner].nametag,5,5,NEW);
					WriteMessage(MSG_0+player[winner].wins,15,5,FOLLOW);
					Delay(65);			
				}
				if (!quitgame)
				{
					FadeScreen(s,DOWN); SetRast(rp,_back);
					WriteMessage(player[winner].nametag,60,80,NEW);
					WriteMessage(MSG_ISTHECHAMP,0,80,FOLLOW);
					FadeScreen(s,UP); ClickDelay(400);
				} break;
			case SINGLE:
				SetUpTable(); DoControl(); break;
			case UNLIMITED:
				do
				{ SetUpTable(); DoControl(); } while (!quitgame);
				break;
			case STRIP:
			{
				short image=0,text=1;
				
				for(match=0; match<4;
					match++,image++,text++)
				{
					WriteMessage(MSG_GAME,5,5,NEW);
					WriteMessage(MSG_1+match,0,5,FOLLOW);
					WriteMessage(MSG_OF,0,5,FOLLOW);
					WriteMessage(MSG_4,0,5,FOLLOW);
					SetUpTable(); Delay(100); DoControl();
					if (quitgame) break;
					if (winner==COMPLYR) { text=0; image=0; }
					
					FadeScreen(s,DOWN);
				
					LoadIFF(&striptextIFF[stripper],s,NOREFRESH);
					
					BltBitMap(bm,stripinfo[stripper].textx[text],
						stripinfo[stripper].texty[text],&bufbm,0,85,
						stripinfo[stripper].textdx[text],
						stripinfo[stripper].textdy[text],0xc0,31,buffer);
					
					LoadIFF(&stripimageIFF[stripper],s,NOREFRESH);
					
					BltBitMap(bm,stripinfo[stripper].imgx1[image],
						stripinfo[stripper].imgy1[image],&bufbm,250,0,
						stripinfo[stripper].imgdx[image],
						stripinfo[stripper].imgdy[image],0xc0,31,buffer);
					
					for (i=0; i<16; i++)
					{ rgb[16+i] = rgb[i]; rgb[i] = RGBroom[i]; }
					
					BltBitMap(bm2,0,0,bm,0,0,320,256,0xc0,31,buffer);
			
					SuperImpose(&bufrp,0,85,rp,101,28,
						stripinfo[stripper].textdx[text],
						stripinfo[stripper].textdy[text],0,7);
					
					SuperImpose(&bufrp,250,0,rp,
						stripinfo[stripper].imgx2[image],
						stripinfo[stripper].imgy2[image],
						stripinfo[stripper].imgdx[image],
						stripinfo[stripper].imgdy[image],0,16);
					
					FadeScreen(s,UP);
					
					if (match==3) PlaySound(&audioinfo[LONGCHEER],64);
					ClickDelay(600);
					if (match==3 || winner==COMPLYR) break;
					arg[OPPONENT]++; DoOptions();
				}
				break;
			}
		}
/*		enableoptions = FALSE;
		if (flipoffer = 1-flipoffer)
		{
			short flash=0xf00,dirn=-0x100,count=0;

			LoadIFF(&offer2IFF,s,VIEW);			
			while (!(GetMouse()) && ++count < 1500)
			{
				flash+=dirn;
				if (flash==0xf00 || flash==0x400) dirn = -dirn;
				colorpalette[1] = flash;
				MakeScreen(s); RethinkDisplay();
				Delay(2);
			} 
		}
		else { LoadIFF(&offerIFF,s,VIEW); ClickDelay(1500); }
*/		
	} while (Loader(0));
	CloseIt();
}
/**********************/
void CueJumpAnim()
{
	short y=125,x=180,oldx,oldy,crash=FALSE,
		forcex=quicksin[ball[CUE].angle] * 7 / BASE,
		forcey=-12,bounce=0,onscreen=TRUE;

	ball[CUE].potted = TRUE; ball[CUE].justpotted = TRUE;
	ball[CUE].scrnx = ball[CUE].oldscrnx;
	ball[CUE].scrny = ball[CUE].oldscrny;
	DrawBall(CUE,OFF);

	if (forcex > 4) { forcey = -16; crash = TRUE; }
	
	ScreenToFront(s2); ActivateWindow(w2);
	SetAPen(rp2,8); PlaySound(&audioinfo[POTTED],64);

	while(1)
	{
		if (onscreen)
		{
			BltBitMap(bm2,x,y,&bufbm,0,100,8,8,0xc0,31,buffer);		
			BltPattern(rp2,miniball,x,y,x+7,y+7,2);
		}
		else Delay(1);
		oldx = x; oldy = y;
		y+=forcey++/2; x+=forcex;
		if (y >= 220+bounce*4)
		{
			y=220+bounce*2; forcey=-forcey/4; bounce++;
			PlaySound(&audioinfo[BOUNCE],32 + 32/bounce);
		}
		if (bounce==3) Delay(75);
		if (onscreen)
		{
			WaitBeam(oldy+10);
			BltBitMap(&bufbm,0,100,bm2,oldx,oldy,8,8,0xc0,31,buffer);
		}
		if (bounce==3) break;
		if (x < 0 || x > 310)
		{
			if (crash) break;
			onscreen=FALSE;
		}
	}
	if (crash)
	{
		PlaySound(&audioinfo[CRASH],64); Delay(100);
		PlaySound(&audioinfo[CHEER],64);
	}	
	WriteMessage(MSG_OOOPS,30,5,NEW);
	ScreenToFront(s); ActivateWindow(w);
}
/*************************/
void DoControl()
{
	short input,i,key=0;
	
	if (gametype==PRACTICE || dragcheat) SetPointer(w,pointeron,11,11,0,0);
	WriteMessage(player[turn].nametag,5,5,NEW); GetInput();
	quitgame = FALSE; endofgame = FALSE;
	
	do
	{
		input = GetInput();
		
		if (gametype==DEMO || (gametype==PLYR1 && turn==COMPLYR))
		{
			if (input==ESC) quitgame=TRUE;
			else PlayComputerShot();
		}
		else switch(input)
		{
			case ESC: quitgame=TRUE; break;
			
			case RETURN:
				{
					BOOL play=TRUE;
				
					if (ChangeSpin()==FALSE) play=FALSE;
					else do { input = GetInput(); ChangePower(); }
					while (!(input==RETURN || input==ESC));
					
					if (input==ESC) play=FALSE;
					if (play) PlayShot();
					else
					{
						WriteMessage(MSG_SHOTCANCELLED,5,5,NEW);	
						cuespeed=MINSPEED; Delay(50);
						WriteMessage(player[turn].nametag,5,5,NEW);	
					}
				} break;
				
			case LCSR: MoveCue(CLOCKWISE,SMALL); break;
			case RCSR: MoveCue(ANTICLOCKWISE,SMALL); break;
			case UCSR: MoveCue(CLOCKWISE,LARGE); break;
			case DCSR: MoveCue(ANTICLOCKWISE,LARGE); break;
			
			case SPACE: do ChangePower(); while (cuespeed < (MAXSPEED/2));
				PlayShot(); break;
			
			case F1: DrawCue(OFF);
			 	if ((dotspacing+=2) > 12) dotspacing=4;
				DrawCue(ON); break;
			
			case F2: player[2].cuepulls = 1-player[2].cuepulls;
				player[0].cuepulls = player[2].cuepulls;
				player[3].cuepulls = 1-player[3].cuepulls;
				player[1].cuepulls = player[3].cuepulls;
				cuespeed=MAXSPEED; DrawCue(OFF);
				DrawCue(HIT); DrawCue(ON); cuespeed=MINSPEED; break;
				
			case F3: DrawCue(OFF); cueline=(1-cueline); DrawCue(ON); break;
			case F4: /* cheer toggle */
				if (cheer = 1-cheer) PlaySound(&audioinfo[CHEER],64);
				break;			
			
			case F5: /* smash balls */
				if (gametype==PRACTICE || dragcheat)
				{
					for (i=0; i<balls; i++)
					{
						ball[i].angle = Rand(360);
						ball[i].speed = MAXSPEED;
					}
					cuespeed = MAXSPEED; PlayShot();
				} break;

			case F6: if (gametype==PRACTICE || dragcheat)
				{
					DrawCue(OFF); SetUpTable();
					WriteMessage(player[turn].nametag,5,5,NEW);
				} break;
					
			case 37: /* "hogan" win game cheat!!! */ key = 10; break;
			case 24: if (key==10) key = 20; break;
			case 36: if (key==20) key = 30; break;
			case 32: if (key==30) key = 40; break;
			case 54: if (key==40) { endofgame=TRUE; winner=HUMANPLYR; }
					break;
			
			case 25: /* "pixie" dragball cheat!!! */ key = 1; break;
			case 23: if (key==1) key = 2; if (key==3) key = 4; break;
			case 50: if (key==2) key = 3; break;
			case 18: if (key==4) 
					{
						if (dragcheat=1-dragcheat) 
							SetPointer(w,pointeron,11,11,0,0);
						else SetPointer(w,pointeroff,1,1,0,0);
					} break;
			
			case HELP: if (gametype==PRACTICE || dragcheat)
					 PlayComputerShot(); break;
		}
		if (quitgame)
		{
			if (gametype==DEMO)
			{
				if (defaultdemo) for(i=0; i<8; i++) arg[i] = bufarg[i];
				defaultdemo=FALSE;
				WriteMessage(MSG_QUITTING,5,5,NEW); Delay(50);
			}
			else
			{
				WriteMessage(MSG_QUITGAME,5,5,NEW);
				do input = GetInput();
					while (input!=21 && input!=54);
				if (input==54)
				{
					WriteMessage(player[turn].nametag,5,5,NEW);
					quitgame=FALSE;
				}
				else { WriteMessage(MSG_QUITTING,5,5,NEW); Delay(50); }
			}
		}
		if (endofgame && gametype!=PRACTICE)
		{
			
			player[winner].wins++;
			if (gametype==PLYR1)
			{
				if (winner==HUMANPLYR)
				{
					WriteMessage(MSG_WELLDONE,5,5,NEW);
					if (cheer) PlaySound(&audioinfo[LONGCHEER],64);
				}
				else WriteMessage(MSG_BADLUCK,5,5,NEW);
				Delay(75);
			}
			else if (cheer) PlaySound(&audioinfo[LONGCHEER],64);
			WriteMessage(player[winner].nametag,5,5,NEW);
			WriteMessage(MSG_WINS,0,5,FOLLOW);
			Delay(100); ScrubMessage();
			if (defaultdemo) for(i=0; i<8; i++) arg[i] = bufarg[i];
			defaultdemo=FALSE;
		}
		if (gametype==PRACTICE || dragcheat) CheckBallDrag();
	
	} while(!quitgame && !endofgame);
	DrawCue(OFF); SetPointer(w,pointeroff,1,1,0,0);
}
/**********************/
void DrawCue(status)
short status;
{
	short i,j,x1,y1,x2,y2,dx,dy,dirnlen=0,cuelen=MAXCUELENGTH,
		viewline=cueline;
	
	if (gametype==DEMO || (gametype==PLYR1 && turn==COMPLYR)) viewline=0;
	
	rp->Mask = 31;
	if (status == ON)
	{
		x1 = X1CORNER + (ball[CUE].x + CUETIP * quicksin[cueangle]
			* quicksin[90+spinx/12]/BASE
			- spinx/100 * quickcos[cueangle]) / BASE;
			
		y1 = (Y2CORNER-1) - (ball[CUE].y + CUETIP*quickcos[cueangle]
			* quicksin[90+spinx/12]/BASE
			+ spinx/100 * quicksin[cueangle]) / BASE;
		do
		{
			x2 = x1 +	cuelen * quicksin[cueangle]/BASE;
			y2 = y1 - cuelen * quickcos[cueangle]/BASE;
			cuelen--;
	
		} while (x2 < 1 || x2 > 318 || y2 < 33 || y2 > 254);
	
		blitdat.x = (x1 < x2)? x1-1:x2-1;
		blitdat.y = (y1 < y2)? y1-1:y2-1;
		blitdat.dx = abs(x1-x2)+3;
		blitdat.dy = abs(y1-y2)+3;
				
		BltBitMap(bm,blitdat.x,blitdat.y,&bufbm,
			0,0,blitdat.dx,blitdat.dy,0xc0,31,buffer);
	
		SetAPen(rp,_cuecol);
		for (dx=-1; dx<2; dx++) for (dy=-1; dy<2; dy++)
		{ Move(rp,x1,y1); Draw(rp,x2+dx,y2+dy); }
	
		SetAPen(rp,_ltcuecol); Move(rp,x1,y1); Draw(rp,x2,y2);
		
		SetAPen(rp,_black); WritePixel(rp,x1,y1);

		x1 = X1CORNER + ball[CUE].x/BASE - CUETIP*quicksin[cueangle]/BASE;
		y1 = (Y2CORNER-1) - ball[CUE].y/BASE + CUETIP*quickcos[cueangle]/BASE;

		if (viewline)
		{
			SetAPen(rp,_white);
	
			for (i=0; i<32; i++,dirnlen+=dotspacing)
			{
				x2 = x1 -	dirnlen * quicksin[cueangle]/BASE;
				y2 = y1 +	dirnlen * quickcos[cueangle]/BASE;

				for (j=0; j<2; j++)
				{
					if (y2 < (Y1CORNER+7)) y2 += ((Y1CORNER+7)-y2)<<1;
					if (y2 > (Y2CORNER-7)) y2 -= (y2-(Y2CORNER-7))<<1;
					if (y2 < (Y1CORNER+7)) y2 += ((Y1CORNER+7)-y2)<<1;
					if (x2 < (X1CORNER+7)) x2 += ((X1CORNER+7)-x2)<<1;
					if (x2 > (X2CORNER-7)) x2 -= (x2-(X2CORNER-7))<<1;
					if (x2 < (X1CORNER+7)) x2 += ((X1CORNER+7)-x2)<<1;
				}
				pixelsave[i].col = ReadPixel(rp,x2,y2);
				pixelsave[i].x = x2;
				pixelsave[i].y = y2;
			}
			for (i=0; i<32; i++)
				WritePixel(rp,pixelsave[i].x,pixelsave[i].y);
		}
	}
	else if (status==OFF)
	{
		if (viewline)
		{
			WaitBOVP(vp);
			for (i=31; i>=0; i--)
			{
				SetAPen(rp,pixelsave[i].col);
				WritePixel(rp,pixelsave[i].x,pixelsave[i].y);
			}
		}
		WaitBeam(blitdat.y+blitdat.dy);
		BltBitMap(&bufbm,0,0,bm,
			blitdat.x,blitdat.y,blitdat.dx,blitdat.dy,0xc0,31,buffer);
	}
	else if (status==HIT)
	{
		char firstrun = 0,stage;
		short offset;
		
		for (stage=0; stage<player[turn].cuepulls+1; stage++)
		for (i=0; i<=180; i+=15)
		{
			if (stage==player[turn].cuepulls)
				offset = quicksin[i]*60*cuespeed/MAXSPEED/BASE;

			else offset = (BASE-quickcos[i+i])
				*15*cuespeed/MAXSPEED/BASE;
			
			cuelen = MAXCUELENGTH;

			x1 = X1CORNER + (ball[CUE].x + CUETIP * quicksin[cueangle]
				* quicksin[90+spinx/12]/BASE
				- spinx/100 * quickcos[cueangle]
				+ offset*quicksin[cueangle]) / BASE;
			
			y1 = (Y2CORNER-1) - (ball[CUE].y + CUETIP*quickcos[cueangle]
				* quicksin[90+spinx/12]/BASE
				+ spinx/100 * quicksin[cueangle]
				+ offset*quickcos[cueangle]) / BASE;

			if (x1 < 1 || x1 > 318 || y1 < 33 || y1 > 254) continue;

			do
			{
				x2 = x1 +	cuelen * quicksin[cueangle]/BASE;
				y2 = y1 - cuelen * quickcos[cueangle]/BASE;
				cuelen--;
	
			} while (x2 < 1 || x2 > 318 || y2 < 33 || y2 > 254);
	
			if (!firstrun)
			{
				Delay(1); WaitBeam(blitdat.y+blitdat.dy);
				BltBitMap(&bufbm,0,0,bm,blitdat.x,blitdat.y,
					blitdat.dx,blitdat.dy,0xc0,31,buffer);
			}

			blitdat.x = (x1 < x2)? x1-1:x2-1;
			blitdat.y = (y1 < y2)? y1-1:y2-1;
			blitdat.dx = abs(x1-x2)+3;
			blitdat.dy = abs(y1-y2)+3;
			
			BltBitMap(bm,blitdat.x,blitdat.y,&bufbm,
				0,0,blitdat.dx,blitdat.dy,0xc0,31,buffer);
		
			SetAPen(rp,_cuecol);
			for (dx=-1; dx<2; dx++) for (dy=-1; dy<2; dy++)
			{ Move(rp,x1,y1); Draw(rp,x2+dx,y2+dy); }
	
			SetAPen(rp,_ltcuecol); Move(rp,x1,y1); Draw(rp,x2,y2);
			SetAPen(rp,_black); WritePixel(rp,x1,y1);
			firstrun = 0;
		}
		WaitBeam(blitdat.y+blitdat.dy);
		BltBitMap(&bufbm,0,0,bm,blitdat.x,blitdat.y,
			blitdat.dx,blitdat.dy,0xc0,31,buffer);
	}
}
/***********************/
void MakeCollision(i,j)
short i,j;
{
	short dx,dy,volume,theta,dxi,dxj,dyi,dyj,
		reldx,reldy,relspeed,relangle,
		passx,passy,transfer,intersect,jump=FALSE;

	dx = ball[j].x - ball[i].x;
	dy = ball[j].y - ball[i].y;
	intersect = BALLDIAMETER - QuickRoot(dx*dx + dy*dy);
	
	if (i==CUE && ball[CUE].speed > (MAXSPEED*3/4) &&
		spiny < -600 && !(Rand(2))) { jump=TRUE; CueJumpAnim(); }
	
	/* Find pure offsets */
	dxi = ball[i].speed * quicksin[ball[i].angle] / BASE;
	dyi = ball[i].speed * quickcos[ball[i].angle] / BASE;
	dxj = ball[j].speed * quicksin[ball[j].angle] / BASE;
	dyj = ball[j].speed * quickcos[ball[j].angle] / BASE;
	
	/* Ball i relative offsets - makes ball j static */
	reldx = dxi - dxj;
	reldy = dyi - dyj;
	
	relspeed = QuickRoot(reldx*reldx + reldy*reldy);

	/* Reposition balls so that they collide at surface */
	if (relspeed > BASE && packstate==SPREAD)
	{
		dx = (ball[j].x - dxj * intersect / relspeed) -
			(ball[i].x - dxi * intersect / relspeed);
		
		dy = (ball[j].y - dyj * intersect / relspeed) -
			(ball[i].y - dyi * intersect / relspeed);
	}
	theta = FindAngle(dx,dy,BALLDIAMETER);
	/* Common angle between balls */
	
	relangle = FindAngle(reldx,reldy,relspeed);
	/* Find angle of relative velocity */
	
	/* Power transfer from one ball to another */
	transfer = abs(quickcos[abs(theta - relangle)])*relspeed*3/BASE/4;
		
	passx = transfer * quicksin[theta] / BASE;
	passy = transfer * quickcos[theta] / BASE;

	if (i==CUE && spiny)
	{
		dxi += dxi * spiny / 800 * 
			abs(quickcos[abs(theta - relangle)]) / BASE;
		dyi += dyi * spiny / 800 * 
			abs(quickcos[abs(theta - relangle)]) / BASE;
		spiny=0; swerve=0;
	}	

	dxj += passx; dyj += passy;
	dxi -= passx; dyi -= passy;
			
	ball[i].speed = QuickRoot(dxi*dxi + dyi*dyi);
	if (ball[i].speed > MAXSPEED) ball[i].speed = MAXSPEED;
	else if (ball[i].speed < 6) ball[i].speed = 6;
	if (ball[i].speed) ball[i].angle = FindAngle(dxi,dyi,ball[i].speed);
	
	ball[j].speed = QuickRoot(dxj*dxj + dyj*dyj);
	if (ball[j].speed > MAXSPEED) ball[j].speed = MAXSPEED;
	else if (ball[j].speed < 6) ball[j].speed = 6;
	if (ball[j].speed) ball[j].angle = FindAngle(dxj,dyj,ball[j].speed);
	
	if (i==CUE && spinx)
	{
		ball[CUE].angle-=spinx/25;
		CheckAngle(&ball[CUE].angle);
		spinx=0; swerve=0;
	}
	ball[i].lasthit = j; ball[j].lasthit = i;
	
	if (cheatangle!=-1 && j==cheatball)
	{
		ball[j].angle = cheatangle;
		if (ball[j].speed < ball[CUE].speed>>2)
			ball[j].speed = ball[CUE].speed>>2; 
	}
	cheatangle=-1;
	
	if ((packstate!=BROKEN || !newbreak) && !jump)
	{
		/* Prevent multiple collisions on break */
		if ((volume = 16 + (transfer<<6)/MAXSPEED) > 64) volume = 64;
		PlaySound(&audioinfo[COLLIDE],volume);
		if (packstate==RACKED) packstate=BROKEN;
	}	
	/* Check for a foul shot here */
	if (i==CUE && !firsthit) firsthit=j;
}

/*********************/
#define ENCRYPTCODE 0xd6

long MyRead(fh,ptr,len)
struct FileHandle *fh;
long *ptr;
long len;
{
	long retnlen;
	
	retnlen = Read((BPTR)fh,ptr,len);
	len = retnlen;
	{
		register char *ptr2 = (char *)ptr;
		
		while (len > 3)
		{
			*ptr2++ ^= ENCRYPTCODE; *ptr2++ ^= ENCRYPTCODE;
			*ptr2++ ^= ENCRYPTCODE; *ptr2++ ^= ENCRYPTCODE;
			len -= 4;
		}
		while (len) { *ptr2++ ^= ENCRYPTCODE; len--; }
	}
	return retnlen;
}
