
//
// Feel free to use/distribute/modify as long as credit/copyrights for myself 
// are included.

// Classes: Ship, Enemy, Spinner

#ifndef __BPObj__
#define __BPObj__

#include <stdlib.h>
#include <vgagl.h>

#include "Sprite.h"
#include "sasteroids.h"
#include "Explosion.h"
#include "FastMath.h"


//////////////////////////////////////////////////////////////////////////////
// Player's ship.  Only create one instance of it in the game.
//
#define SHIPTURNS 32
#define TURNANGLE (256/SHIPTURNS)

class Ship : public Sprite {
    public:

    	Ship( int sh, FixedPoint m ) : 
    		Sprite(sh,m), Vpos(0)
    		{
    		}

	
	// if shield on, returns the 'energy level' (approx to time
	// remaining) for the shield, between 0 and 100
	inline int shieldPercent() const
			{ 
			if (Vshield.isAlive())
				// make sure int math done here
				return (100*(int)Vshield.timeLeft()) / (int)VshieldMax ;
			else	return 0;
			}

	// shake the ship around for a bit
	inline void bounce(int time) 
			{ 
			Vbounce=time; 
			}

    	inline void draw()
    		{
    		if (Vbounce)
    			{
    			// shake the ship around a bit on screen
    			VFxold = VFx;
    			VFx += (Vbounce&1) ? -INT2FP(1) : INT2FP(1);
			}
			
    		Sprite::draw();

    		if (Vshield.isAlive())
    			Gbit[SHIELD].put( FP2INT(VFx)-size(), FP2INT(VFy)-size() );
//    			gl_circle( FP2INT(VFx), FP2INT(VFy), size(), 254 );

		if (Vbounce)
			{
			Vbounce--;
			VFx = VFxold;
			}
		}
	    	
	inline void tick() 
		{
		Sprite::tick();
		Vshield.tick();

		// timer for showing thrust flame behind
		// player ship
		if (VthrustCnt) 
			{
			VthrustCnt--;
			if (VthrustCnt<=0) 
				{
				VthrustCnt=0;
				Ship::setBitmap();
				}
			}
		}
		    	
    	inline void setBitmap();

    	inline void rotLeft( int t )
    		{
		Vangle -= TURNANGLE*t;
		this->Vpos    -= t;
		this->Vpos    %= SHIPTURNS;
		this->Ship::setBitmap();
		}

    	inline void rotRight( int t)
    		{
		Vangle += TURNANGLE*t;
		this->Vpos    += t;
		this->Vpos    %= SHIPTURNS;
		this->Ship::setBitmap();
		}
		
#define MAXSPEED 12
	inline void thrust(FixedPoint Fscale )
		{
		
		VFxvel += FPMULT(FastMath::Fcos( Vangle ), Fscale);
		VFyvel += FPMULT(FastMath::Fsin( Vangle ), Fscale);

		int speed = (int)sqrt( FP2INT(FPMULT(VFxvel,VFxvel)+
		                              FPMULT(VFyvel,VFyvel)) );

		// don't let the ship travel too fast.
		if (speed>MAXSPEED)
			{
			// scale x,y velocities down.
			VFxvel *= MAXSPEED; VFxvel /= speed;
			VFyvel *= MAXSPEED; VFyvel /= speed;
			}		
					    
		VthrustCnt = 5;
		Ship::setBitmap();
		}

	inline void  shieldOn()
		{
		if (this->shieldCnt)
			{
			shieldCnt--;
			Vshield.revive( VshieldMax );
			}
		}

	inline void  hyper()
		{
		setFpos( INT2FP((rand()>>5)%WIDTH), 
		        INT2FP((rand()>>5)%HEIGHT) );
		}

	// the player ship extends the die function by keeping track
	// how many 'lives' left it has.
	void die()
		{
		if (Vlives) Vlives--;
		Sprite::die();
		}

	void  ships( int x ) 
		{ Vlives=x; }

	int   ships() const 
		{ return Vlives; }

	void  addShip()
		{ Vlives++; }

	void reset() { Ship::reset( INT2FP(WIDTH>>1), INT2FP(HEIGHT>>1) ); }

	void reset(int newX, int newY)
		{
		setFpos( newX, newY );
		setFvel( 0, 0 );

		Vangle = 192;	// 192 is 270 in normal degrees
		this->Vpos = 0;
		
		this->shieldCnt = 1;
		this->VshieldMax=200;
		this->Vshield.die();
		
		this->VthrustCnt = 0;
		this->Ship::setBitmap();
		};

	inline int shielded() const { return Vshield.isAlive(); }
	inline void shieldMax(int max) { VshieldMax=max; }
	
	static void	init();	// fill ship bitmap arrays with data

    	int	 shieldCnt;

    private:

	static SBitmap	ship[SHIPTURNS], shipThr[SHIPTURNS];
	
	unsigned char Vpos;
	Life	 Vshield;
    	int	 VthrustCnt;
	int	VshieldMax;
	int	Vlives, Vbounce;    
	FixedPoint VFxold,VFyold;	
    };

inline void Ship::setBitmap()
    		{
    		// set the player ship to the appropriate bitmap
    		// ie, rotate position and thrust flame.
    		if (VthrustCnt)  Sprite::setBitmap((shipThr[Vpos]));
    		else		Sprite::setBitmap((ship[Vpos]));
    		}

//////////////////////////////////////////////////////////////////////////////
// Enemy Ship
//
class Enemy : public Sprite 
    {
    public:

    	Enemy( int sh, FixedPoint m, const Ship& p, Sprite& bul ) : 
    		Sprite(sh,m,Gbit[ENEMY]), Vplayer(&p), Vbul(&bul)
    		{
    		}

	void tick() 
		{
		Sprite::tick();

		if (isAlive())
			{
			if ( ((rand()>>5)&127)==1 )
				{
				VFyvel = -VFyvel;
				}
			else
				{
				int i = (Vplayer->VFy > VFy ) ? 1 : -1;
				VFyvel += ((rand()&255) > 64) 
					    ? INT2FP(i)>>1 : -INT2FP(i)>>1;
					    
					
				// if enemy has slowed down in the x-dir
				// due to bouncing off of an asteroid,
				// accelerate back up to speed.
				if (Vdir>0)
					{
					if (VFxvel<VFspeed)
						VFxvel += INT2FP(1)>>3;
					else VFxvel = VFspeed;
					}
				else
					{
					if (VFxvel>VFspeed)
						VFxvel -= INT2FP(1)>>3;
					else VFxvel = -VFspeed;
					}
				}
			// don't move too fast in the y-dir.
			if (ABS(VFyvel)>VFspeed) 
				if (VFyvel<0) VFyvel=-VFspeed;
				else VFyvel = VFspeed;
				
			// switch horizontal direction every once in a while
			if ( ((rand()>>5)&255)==1 )
				{
				Vdir = -Vdir;
				VFxvel = -VFxvel;
				}
			
			if ( !Vbul->isAlive() )
				if ( ((rand()>>2)%(1200)) < VshotsPerMin )
				    	{
					Vbul->revive(100);
				    	Vbul->setFpos( VFx, VFy );
				    	Vbul->towards( *Vplayer, INT2FP(5)>>1 );
				    	}
			}
		}

	void newShip( FixedPoint Fspeed, int shots )
		{
		VFspeed = Fspeed;
		VshotsPerMin = shots;	// avg # of shots per minutes to fire
					// off at the player
		FixedPoint Fstart;
		
		if ( ((rand()>>5)&255)>127 )
			{
			// move left to right
			Fstart=0;
			setFvel( Fspeed, 0);
			Vdir=1;
			}
		else
			{
			// move right to left
			Fstart=INT2FP(WIDTH-1);
			setFvel( -Fspeed, 0);
			Vdir=-1;
			}

		setFpos( Fstart, INT2FP((rand()>>5)%HEIGHT) );

		this->revive();
		};

    private:
	int		VshotsPerMin;
	int		Vdir;
	FixedPoint	VFspeed;
	const   Ship	*Vplayer;
	Sprite	*Vbul;
    };

#define SPINNERTURNS 32

class Spinner : public Sprite 
    {
    public:

    	Spinner( int sh, FixedPoint m, const Ship& p ) : 
    		Sprite(sh,m), Vplayer(&p), Vpos(0)
    		{
    		}
    		
	void move()
		{
		int i;
		i = (Vplayer->VFy > VFy ) ? 1 : -1;
			
		// move away when player is waiting to place new
		// ship on screen. We don't want the enemy to
		// be hovering around the drop spot, making it 
		// hard to start the game.
		if (!Vplayer->isAlive()) {i*=-1;}
			
		VFyvel += ( (rand()&255) > 64) ? INT2FP(i)>>1 : -INT2FP(i)>>1;
		if (ABS(VFyvel)>INT2FP(2)) VFyvel>>=1;

		i = (Vplayer->VFx > VFx ) ? 1 : -1;
		if (!Vplayer->isAlive()) {i*=-1;}
		VFxvel += ( (rand()&255) > 64) ? INT2FP(i)>>1 : -INT2FP(i)>>1;
		if (ABS(VFxvel)>INT2FP(3)) VFxvel>>=1;
			
		// switch horizontal direction every once in a while
		if ( ((rand()>>5)&511)==1 )
			{
			VFyvel *= -1;
			}
		if ( ((rand()>>5)&511)==1 )
			{
			VFxvel *= -1;
			}		
		}
		
	void tick() 
		{
		Sprite::tick();
		
		if (isAlive())
		    {
		    if (Vmorphing)
			{
			if (Vcount++>3)
				{
				Vcount=0;
				Vpos++;
				if (Vpos==10)
					{
					Vpos=0;
					Vmorphing=0;
					setBitmap(spinnerBitmaps[0]);
					}
				else
					setBitmap(morphBitmaps[Vpos]);
				}
			}
		    else
			{	
			// rotate the image
			Vpos++; Vpos %=SPINNERTURNS;
			Sprite::setBitmap( spinnerBitmaps[Vpos] );
			}
		    move();
		    }
		}

	void newShip()
		{
		setFvel(0,0);
		VhitCount=0;
		Vmorphing=1;
		Vpos=0;
		Vcount=0;
		this->revive();
		setBitmap( morphBitmaps[Vpos] );
		};

	static void init();
	int	VhitCount;
	
    private:
	static SBitmap	   spinnerBitmaps[SPINNERTURNS];
	static SBitmap   morphBitmaps[10];

	int	Vpos,Vcount,Vmorphing;
	const   Ship	*Vplayer;
	Sprite	*Vbul;
    };

#endif
