/*
 * XBoing - An X11 blockout style computer game
 *
 * (c) Copyright 1993, 1994, 1995, Justin C. Kibell, All Rights Reserved
 *
 * The X Consortium, and any party obtaining a copy of these files from
 * the X Consortium, directly or indirectly, is granted, free of charge, a
 * full and unrestricted irrevocable, world-wide, paid up, royalty-free,
 * nonexclusive right and license to deal in this software and
 * documentation files (the "Software"), including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons who receive
 * copies from any such party to do so.  This license includes without
 * limitation a license to do the foregoing actions under any patents of
 * the party supplying this software to the X Consortium.
 *
 * In no event shall the author be liable to any party for direct, indirect,
 * special, incidental, or consequential damages arising out of the use of
 * this software and its documentation, even if the author has been advised
 * of the possibility of such damage.
 *
 * The author specifically disclaims any warranties, including, but not limited
 * to, the implied warranties of merchantability and fitness for a particular
 * purpose.  The software provided hereunder is on an "AS IS" basis, and the
 * author has no obligation to provide maintenance, support, updates,
 * enhancements, or modifications.
 */

/* 
 * =========================================================================
 *
 * $Id: gun.c,v 1.1.1.1 1994/12/16 01:36:44 jck Exp $
 * $Source: /usr5/legends/jck/xb/master/xboing/gun.c,v $
 * $Revision: 1.1.1.1 $
 * $Date: 1994/12/16 01:36:44 $
 *
 * $Log: gun.c,v $
 * Revision 1.1.1.1  1994/12/16  01:36:44  jck
 * The XBoing distribution requires configuration management. This is why the
 * cvs utility is being used. This is the initial import of all source etc..
 *
 *
 * =========================================================================
 */

/*
 *  Include file dependencies:
 */

#include <stdio.h>
#include <math.h>
#include <assert.h>
#include <xpm.h>
#include <X11/Xos.h>

#include "bitmaps/guns/bullet.xpm"
#include "bitmaps/guns/tink.xpm"

#include "error.h"
#include "audio.h"
#include "score.h"
#include "init.h"
#include "mess.h"
#include "main.h"
#include "stage.h"
#include "blocks.h"
#include "paddle.h"
#include "misc.h"
#include "level.h"
#include "ball.h"
#include "special.h"
#include "eyedude.h"

#include "gun.h"

/*
 *  Internal macro definitions:
 */

#define BULLET_DY			-7

#define BULLET_WIDTH		7
#define BULLET_HEIGHT		16

#define BULLET_START_Y		(PLAY_HEIGHT - 40)

#define BULLET_WC			(BULLET_WIDTH / 2)
#define BULLET_HC			(BULLET_HEIGHT / 2)

#define TINK_WIDTH			10
#define TINK_HEIGHT			5

#define TINK_WC				(TINK_WIDTH / 2)
#define TINK_HC				(TINK_HEIGHT / 2)

#define X2COL(col, x) 		(col = x / colWidth)
#define Y2ROW(row, y) 		(row = y / rowHeight)

#define BULLET_FRAME_RATE	3

/* Should be the same */
#define MAX_MOVING_BULLETS	40	
#define MAX_TINKS			40	

#define TINK_DELAY			100	

/*
 *  Internal type declarations:
 */

#if NeedFunctionPrototypes
static int 	ResetBulletStart(Display *display, Window window);
static void CheckTinks(Display *display, Window window);
static void AddTink(Display *display, Window window, int xpos);
static void ClearTinks(void);
static void UpdateBullet(Display *display, Window window);
static int	StartABullet(Display *display, Window window, int xpos);
static void ClearBullet(int i);
static void DrawTheTink(Display *display, Window window, int x, int y);
static void EraseTheTink(Display *display, Window window, int x, int y);
static void DrawBullet(Display *display, Window window, int i);
static int CheckForBulletCollision(Display *display, Window window, 
	int x, int y);
static int CheckBallBulletCollision(Display *display, Window window, 
	int bx, int by, int j);
static int CheckEyeDudeBulletCollision(Display *display, Window window, 
	int bx, int by);
#else
static int CheckEyeDudeBulletCollision();
static int 	ResetBulletStart();
static void CheckTinks();
static void AddTink();
static void ClearTinks();
static void UpdateBullet();
static int 	StartABullet();
static void ClearBullet();
static void DrawTheTink();
static void EraseTheTink();
static void DrawBullet();
static int CheckForBulletCollision();
static int CheckBallBulletCollision();
#endif

static struct 
{
	int xpos;			/* x position of tink centre */
	int	clearFrame;		/* Last frame to clear it */
} tinks[MAX_TINKS];

static struct 
{
	int xpos;			/* x position of bullet */
	int ypos;			/* y position of bullet */
	int oldypos;		/* previous y position */
	int dy;				/* Change in y positoon */
} bullets[MAX_MOVING_BULLETS];

/*
 *  Internal variable declarations:
 */

static Pixmap bulletPixmap, bulletMask;
static Pixmap tinkPixmap, tinkMask;
static int numBullets, unlimitedBullets;

#if NeedFunctionPrototypes
void InitialiseBullet(Display *display, Window window, Colormap colormap)
#else
void InitialiseBullet(display, window, colormap)
	Display *display;
	Window window;
	Colormap colormap;
#endif
{
    XpmAttributes   attributes;
	int		    XpmErrorStatus;

    attributes.valuemask = XpmColormap;
	attributes.colormap = colormap;

	/* Create the xpm pixmap bullet */
	XpmErrorStatus = XpmCreatePixmapFromData(display, window, bullet_xpm,
		&bulletPixmap, &bulletMask, &attributes);
	HandleXPMError(display, XpmErrorStatus, "InitialiseBullet(bullet)");

	/* Create the xpm pixmap tink for bullet */
	XpmErrorStatus = XpmCreatePixmapFromData(display, window, tink_xpm,
		&tinkPixmap, &tinkMask, &attributes);
	HandleXPMError(display, XpmErrorStatus, "InitialiseBullet(tink)");

    /* Free the xpm pixmap attributes */
	XpmFreeAttributes(&attributes);

	SetNumberBullets(4);
	ClearTinks();
	ClearBullets();
}

#if NeedFunctionPrototypes
static void CheckTinks(Display *display, Window window)
#else
static void CheckTinks(display, window)
	Display *display;
	Window window;
#endif
{
	int i;

	/* Clear and tinks that need to be cleared */
	for (i = 0; i < MAX_TINKS; i++)
	{
		/* Is this tink active */
		if (tinks[i].xpos != -1)
		{
			/* Time to clear tink? */
			if (frame >= tinks[i].clearFrame)
			{
				/* Clear the tink! */
				EraseTheTink(display, window, tinks[i].xpos, 2);
					
				/* Free the tink up for another */
				tinks[i].xpos = -1;
				tinks[i].clearFrame = 0;
			}
		}
	}
}

#if NeedFunctionPrototypes
static void AddTink(Display *display, Window window, int xpos)
#else
static void AddTink(display, window, xpos)
	Display *display;
	Window window;
	int xpos;
#endif
{
	int i;

	/* Cycle through tinks and try to add one */
	for (i = 0; i < MAX_TINKS; i++)
	{
		/* Is this tink free? */
		if (tinks[i].xpos == -1)
		{
			/* Set the tink array position */
			tinks[i].xpos = xpos;
			tinks[i].clearFrame = frame + TINK_DELAY;

			/* Draw the new found tink! */
			DrawTheTink(display, window, xpos, 2);

			if (noSound == False) playSoundFile("shoot", 80);

			return;
		}
	}

	/* Full tink array - lots of shooting? */
	WarningMessage("Cannot draw tink - tink array full.");
}

#if NeedFunctionPrototypes
static void ClearTinks(void)
#else
static void ClearTinks()
#endif
{
	int i;

	/* Initialise tinks array to empty */
	for (i = 0; i < MAX_TINKS; i++)
	{
		tinks[i].xpos = -1;
		tinks[i].clearFrame = 0;
	}
}


#if NeedFunctionPrototypes
static void UpdateBullet(Display *display, Window window)
#else
static void UpdateBullet(display, window)
	Display *display;
	Window window;
#endif
{
	int i, j;
	int row, col;
	int ballX, ballY;
	struct aBlock *blockP;

	/* Obtain the position of the ball */
	GetBallPosition(&ballX, &ballY, 0);

	/* Draw all bullets that need updating */
	for (i = 0; i < MAX_MOVING_BULLETS; i++)
	{
		/* Is this bullet active */
		if (bullets[i].xpos != -1)
		{
			/* Update bullet position using dy value */	
			bullets[i].ypos = bullets[i].oldypos + bullets[i].dy;

			/* Has the bullet gone off the top edge */
			if (bullets[i].ypos < -BULLET_HC)
			{
				/* Clear the bullet from the screen */
				EraseTheBullet(display, window, 
					bullets[i].xpos, bullets[i].oldypos);

				/* Draw a tink on the top edge */
				AddTink(display, window, bullets[i].xpos);

				/* Free the bullet up for another */
				ClearBullet(i);

				continue;
			}

			for (j = 0; j < MAX_BALLS; j++)
			{
				if (balls[j].active == True)
				{
					/* Has the bullet killed the ball */
					if (CheckBallBulletCollision(display, window, 
						bullets[i].xpos, bullets[i].ypos, j))
					{
						/* Clear the bullet from the screen */
						EraseTheBullet(display, window, 
							bullets[i].xpos, bullets[i].oldypos);
						ClearBullet(i);

						/* Kill the ball off */
						ClearBallNow(display, window, j);

						/* Play the lovel ahhh pop sound for ball shot */
						if (noSound == False) playSoundFile("ballshot", 50);
						break;
					}
				}
			}

			/* Convert the new bullet pos to rows and cols for collision */
			X2COL(col, bullets[i].xpos);
			Y2ROW(row, bullets[i].ypos);

			/* Pointer to the correct block we need - speed things up */
			blockP = &blocks[row][col];

			if (getEyeDudeMode() == EYEDUDE_WALK)
			{
				/* See if the bullet has hit the active eyedude */
				if (CheckEyeDudeBulletCollision(display, window, 
					bullets[i].xpos, bullets[i].ypos) == True)
				{
					/* Clear the bullet from the screen */
					EraseTheBullet(display, window, 
						bullets[i].xpos, bullets[i].oldypos);

					/* Ok so the eyedude has been hit - arrggh */
					ChangeEyeDudeMode(EYEDUDE_DIE);

					/* Free the bullet up for another */
					ClearBullet(i);
				}
			}

			/* Check if the bullet has hit a brick or something */
			if (CheckForBulletCollision(display, window, 
				bullets[i].xpos, bullets[i].ypos) == True)
			{
				/* Clear the bullet from the screen */
				EraseTheBullet(display, window, 
					bullets[i].xpos, bullets[i].oldypos);

				/* Switch on the type of block hit */
				switch (blockP->blockType)
				{
					case COUNTER_BLK:
						if (blockP->counterSlide == 0)
						{
							/* Counter has counted down to 0 so kill off */
							DrawBlock(display, window, row, col, KILL_BLK);
						}
						else
						{
							/* Decrement counter block and draw new one */
							blockP->counterSlide--;
							DrawBlock(display, window, row, col, 
								COUNTER_BLK);
						}
						break;

					case HYPERSPACE_BLK:
						/* Do nothing - redraw the block */
						DrawBlock(display, window, row, col, HYPERSPACE_BLK);
						break;

					case BLACK_BLK:
						/* Do nothing - redraw the block */
						DrawBlock(display, window, row, col, BLACK_BLK);
						break;

					case REVERSE_BLK:		
					case MGUN_BLK:		
					case STICKY_BLK:		
					case WALLOFF_BLK:		
					case MULTIBALL_BLK:		
					case PAD_EXPAND_BLK:		
					case PAD_SHRINK_BLK:		
					case DEATH_BLK:		
						/* Shoot the block times to kill it */
						blockP->counterSlide--;

						if (blockP->counterSlide == 0)
						{
							/* Ok then a hit, explode that block */
							DrawBlock(display, window, row, col, KILL_BLK);
						}
						break;

					default:
						/* Ok then a hit, explode that block */
						DrawBlock(display, window, row, col, KILL_BLK);
						break;
				}

				/* Free the bullet up for another */
				ClearBullet(i);
			}
			else
				DrawBullet(display, window, i);

			/* Keep track of old position */
			bullets[i].oldypos = bullets[i].ypos;

		}	/* Bullet active? */
	}	/* For loop */
}


#if NeedFunctionPrototypes
static int StartABullet(Display *display, Window window, int xpos)
#else
static int StartABullet(display, window, xpos)
	Display *display;
	Window window;
	int xpos;
#endif
{
	int i;

	/* Cycle through bullets and try to add one */
	for (i = 0; i < MAX_MOVING_BULLETS; i++)
	{
		/* Is this bullet free? */
		if (bullets[i].xpos == -1)
		{
			/* Set the bullet array position */
			bullets[i].xpos = xpos;

			/* Get out of here */
			return True;
		}
		
		/* Break out as the machine gun is not active */
		if (fastGun == False) return False;
	}

	/* Full moving bullet array - lots of shooting? */
	WarningMessage("Cannot draw bullet - bullet array full.");

	return False;
}

#if NeedFunctionPrototypes
static void ClearBullet(int i)
#else
static void ClearBullet(i)
	int i;
#endif
{
	/* Setup the bullet entry */
	bullets[i].xpos 			= -1;
	bullets[i].ypos 			= BULLET_START_Y;
	bullets[i].oldypos 			= BULLET_START_Y;
	bullets[i].dy 				= BULLET_DY;
}

#if NeedFunctionPrototypes
void ClearBullets(void)
#else
void ClearBullets()
#endif
{
	int i;

	/* Initialise bullets array to empty */
	for (i = 0; i < MAX_MOVING_BULLETS; i++)
		ClearBullet(i);
}

#if NeedFunctionPrototypes
void FreeBullet(Display *display)
#else
void FreeBullet(display)
	Display *display;
#endif
{
	if (bulletPixmap)	XFreePixmap(display, bulletPixmap);
 	if (bulletMask) 	XFreePixmap(display, bulletMask);

	if (tinkPixmap)	XFreePixmap(display, tinkPixmap);
 	if (tinkMask) 	XFreePixmap(display, tinkMask);
}

#if NeedFunctionPrototypes
void SetNumberBullets(int num)
#else
void SetNumberBullets(num)
	int num;
#endif
{
	/* Set the number of bullets available */
	numBullets = num;
}

#if NeedFunctionPrototypes
void IncNumberBullets(void)
#else
void IncNumberBullets()
#endif
{
	/* Increment the number of bullets */
	numBullets++;

	/* But don't give to many */
	if (numBullets > MAX_BULLETS) 
		numBullets = MAX_BULLETS;
}

#if NeedFunctionPrototypes
void SetUnlimitedBullets(int state)
#else
void SetUnlimitedBullets(state)
	int state;
#endif
{
	/* Set the unlimit bullets state */
	unlimitedBullets = state;
}

#if NeedFunctionPrototypes
void DecNumberBullets(void)
#else
void DecNumberBullets()
#endif
{
	/* Only decrement number of bullets if the unlimited ammo is off */
	if (unlimitedBullets == False)
	{
		/* Decrement the number of bullets */
		numBullets--;

		/* But not to far */
		if (numBullets < 0) 
			numBullets = 0;
	}
}

#if NeedFunctionPrototypes
int GetNumberBullets(void)
#else
int GetNumberBullets()
#endif
{
	assert(numBullets >= 0);

	/* How many bullets do I have */
	return numBullets;
}

#if NeedFunctionPrototypes
void shootBullet(Display *display, Window window)
#else
void shootBullet(display, window)
	Display *display;
	Window window;
#endif
{
	/* Only shoot if no bullet is active and bullets and ball active */
	if ((GetNumberBullets() > 0) && (IsBallWaiting() == False))
	{
		/* Reset the bullet to the starting possy to go forward */
		if (ResetBulletStart(display, window) == True)
		{
			/* Remove a bullet from the ammunition */
			DeleteABullet(display);

			/* Play a shooting sound */
			if (noSound == False) playSoundFile("shotgun", 50);
		}
	}
	else if (GetNumberBullets() == 0)
	{
		/* Play an trigger clicking sound */
		if (noSound == False) playSoundFile("click", 99);
	}
}

#if NeedFunctionPrototypes
void EraseTheBullet(Display *display, Window window, int x, int y)
#else
void EraseTheBullet(display, window, x, y)
	Display *display;
	Window window;
	int x;
	int y;
#endif
{
	/* Erase the bullet pixmap from the window */
    XClearArea(display, window, x - BULLET_WC, y - BULLET_HC, 
		BULLET_WIDTH, BULLET_HEIGHT, False);
}

#if NeedFunctionPrototypes
static void DrawTheTink(Display *display, Window window, int x, int y)
#else
static void DrawTheTink(display, window, x, y)
	Display *display;
	Window window;
	int x;
	int y;
#endif
{
	/* Draw the tink pixmap into the window */
    RenderShape(display, window, tinkPixmap, tinkMask,
		x - TINK_WC, y - TINK_HC, TINK_WIDTH, TINK_HEIGHT, False);
}

#if NeedFunctionPrototypes
static void EraseTheTink(Display *display, Window window, int x, int y)
#else
static void EraseTheTink(display, window, x, y)
	Display *display;
	Window window;
	int x;
	int y;
#endif
{
	/* Erase the tink pixmap from the window */
    XClearArea(display, window, x - TINK_WC, y - TINK_HC, 
		TINK_WIDTH, TINK_HEIGHT, False);
}

#if NeedFunctionPrototypes
void DrawTheBullet(Display *display, Window window, int x, int y)
#else
void DrawTheBullet(display, window, x, y)
	Display *display;
	Window window;
	int x;
	int y;
#endif
{
	/* Draw the bullet pixmap into the window */
    RenderShape(display, window, bulletPixmap, bulletMask,
		x - BULLET_WC, y - BULLET_HC, BULLET_WIDTH, BULLET_HEIGHT, False);
}

#if NeedFunctionPrototypes
static void DrawBullet(Display *display, Window window, int i)
#else
static void DrawBullet(display, window, i)
	Display *display;
	Window window;
	int i;
#endif
{
	/* Clear the window of the bullet in the old position */
	XClearArea(display, window, bullets[i].xpos - BULLET_WC, 
		bullets[i].oldypos - BULLET_HC, BULLET_WIDTH, BULLET_HEIGHT, False);

	/* Now draw the new bullet in the new position */
	DrawTheBullet(display, window, bullets[i].xpos, bullets[i].ypos);
}

#if NeedFunctionPrototypes
static int CheckEyeDudeBulletCollision(Display *display, Window window, 
	int bx, int by)
#else
static int CheckEyeDudeBulletCollision(display, window, bx, by)
	Display *display;
	Window window;
	int bx;
	int by;
#endif
{
	/*
	 * Check if the bullet has hit an eye dude.
	 */

	int eyeX, eyeY;

	GetEyeDudePosition(&eyeX, &eyeY);

    /* Check if any part of the bullets coords is inside the eyedudes box */
    if (((bx + BULLET_WC) >= (eyeX - EYEDUDE_WC)) &&
   	    ((bx - BULLET_WC) <= (eyeX + EYEDUDE_WC)) &&
        ((by + BULLET_HC) >= (eyeY - EYEDUDE_WC)) &&
        ((by - BULLET_HC) <= (eyeY + EYEDUDE_WC)))
		return True;
	else
		return False;
}

#if NeedFunctionPrototypes
static int CheckBallBulletCollision(Display *display, Window window, 
	int bx, int by, int j)
#else
static int CheckBallBulletCollision(display, window, bx, by, j)
	Display *display;
	Window window;
	int bx;
	int by;
	int j;
#endif
{
	int ballX, ballY;

	GetBallPosition(&ballX, &ballY, j);

    /* Check if any part of the bullets coords is inside the balls box */
    if (((bx + BULLET_WC) >= (ballX - BALL_WC)) &&
   	    ((bx - BULLET_WC) <= (ballX + BALL_WC)) &&
        ((by + BULLET_HC) >= (ballY - BALL_HC)) &&
        ((by - BULLET_HC) <= (ballY + BALL_HC)))
		return True;
	else
		return False;
}

#if NeedFunctionPrototypes
static int CheckForBulletCollision(Display *display, Window window, 
	int x, int y)
#else
static int CheckForBulletCollision(display, window, x, y)
	Display *display;
	Window window;
	int x;
	int y;
#endif
{
    /* Check for bullet to block collision */
    int row, col;
    struct aBlock *blockP;

    /* Get the row and col for block where bullet is */
    X2COL(col, x);
    Y2ROW(row, y);

    blockP = &blocks[row][col];

    /* If blocks is occupied then check for collision */
    if (blockP->occupied == 1 && blockP->exploding == False)
    {
        /* Check if x adjusted for bullet width is in block region */
        if (((x + BULLET_WC) > blockP->x) &&
          ((x - BULLET_WC) < (blockP->x + blockP->width)))
        {
            if (((y + BULLET_HC) > blockP->y) &&
              ((y - BULLET_HC) < (blockP->y + blockP->height)))
            {
                /* Collision */
                return True;
            }
        }
    }

    /* No collision if reached here */
    return False;
}

#if NeedFunctionPrototypes
static int ResetBulletStart(Display *display, Window window)
#else
static int ResetBulletStart(display, window)
	Display *display;
	Window window;
#endif
{
	int status, size;

	/* Start a bullet on the way if possible */
	if (fastGun == True) 
	{
		/* Obtain the size of the paddle */
		size = GetPaddleSize();

		/* Shoot 2 bullets - dual fire!!!!! Power */
		status = StartABullet(display, window, paddlePos - (size / 3));
		status = StartABullet(display, window, paddlePos + (size / 3));
	}
	else
		status = StartABullet(display, window, paddlePos);

	/* Return status of bullet */
	return status;
}

#if NeedFunctionPrototypes
void HandleBulletMode(Display *display, Window window)
#else
void HandleBulletMode(display, window)
	Display *display;
	Window window;
#endif
{
	/* Update all the bullets that may be moving */
	if ((frame % BULLET_FRAME_RATE) == 0)
		UpdateBullet(display, window);

	/* Clear any tinks that are due to be cleared */
	CheckTinks(display, window);
}

