

/************************************************************************************
	ACTION MAN PS	(c) 1998-9 ISL

	pad.c:		Controller pad routines
   (ctrller.c in Glover - additional code for dead-zone handling)

************************************************************************************/

#include "glover.h"

// Dummies for back-compat


/*
volatile UBYTE gameon=NO;
void initPadMaps()
{
}
*/

//#define MULTITAP_SUPPORT	1
//#define PAD_PRINTF PRINTF
//#define PAD_PRINTF DB
//#define PAD_PRINTF0 if(padNo==0) DB
#define PAD_PRINTF
#define PAD_PRINTF0

UBYTE dualShockOn=TRUE;

typedef struct _PadPacket {
     SHORT	digital;
     uchar	rightX;
     uchar	rightY;
     uchar	leftX;
     uchar	leftY;
} PadPacket;


PadDataType		padData;
static uchar	HWalign[6]={0,1,0xFF,0xFF,0xFF,0xFF};
UBYTE			analog[8];
USHORT			pad[8], debounce[8];
USHORT			leftx[8], lefty[8];
USHORT			rightx[8], righty[8];
USHORT			bothdebounces, bothpads;

static int lastTouched=0;

// glover bits
SHORT nleftx[8];
SHORT nlefty[8];
SHORT nrightx[8];
SHORT nrighty[8];

/*
short NormaliseJoy(short axis)
{	// basically ignore inside the dead zone, and scale the remainder back up to 127
	// hey, we could scale it up to +/-4096
	int nAxis,aAxis;

	axis=-(127-axis);
	aAxis=ABS(axis);

	if(aAxis<DEADZONE)
		nAxis=0;
	else
	{
		nAxis=(aAxis-DEADZONE)*DEADMULT;
		if(nAxis>4096)
			nAxis=4096;
		if(axis<0)
			nAxis=-nAxis;
	}

	return(nAxis);
}
*/

// Alternative deadzone removal code that doesn't have a horizontal deadzone when the vertical is at it's limit...

void NormaliseJoy2(int x,int y,short *nx, short *ny)
{
	int ax,ay;
	x = -(127 - x);
	y = -(127 - y);
	ax = ABS(x);
	ay = ABS(y);

	if(ax < DEADZONE && ay < DEADZONE)
	{
		*nx = 0;
		*ny = 0;
	}
	else if (ax <= ay)
	{
//		*ny = (ay - DEADZONE) * 4096 / (128 - DEADZONE);
		*ny = (ay - DEADZONE) * DEADMULT;
		*nx = (ax * (int)(*ny)) >>7;
	}
	else
	{
//		*nx = (ax - DEADZONE) * 4096 / (128 - DEADZONE);
		*nx = (ax - DEADZONE) * DEADMULT;
		*ny = (ay * (int)(*nx)) >>7;
	}
	if(*nx >4096) *nx = 4096;
	if(*ny >4096) *ny = 4096;

	if(x < 0)
		*nx = -*nx;
	if(y < 0)
		*ny = -*ny;
}

int padGetWhenLastUsed(void)
{
	//PAD_PRINTF("lastTouched=%d, iframe=%d\n", lastTouched, iframe);
	return(lastTouched);
}

void padResetWhenLastUsed(void)
{
	lastTouched=iframe;
	//PAD_PRINTF("Resetting lastTouched to %d\n", lastTouched);
}

/**************************************************************************
	FUNCTION:	padInitialise()
	PURPOSE:	Initialise pads
	PARAMETERS:	
	RETURNS:	
**************************************************************************/

void padInitialise()
{
	int		loop;

#ifdef MULTITAP_SUPPORT
	PadInitMtap(padData.buffer[0],padData.buffer[1]);
#else
	PadInitDirect(padData.buffer[0],padData.buffer[1]);
#endif
	VSync(5);
	PadStartCom();
	padData.numPads[0] = 1;
	padData.numPads[1] = 1;
	VSync(5);
	for(loop=0; loop<8; loop++)
	{
		padData.digital[loop] = 0;
		padData.analogX[loop] = 128;
		padData.analogY[loop] = 128;
		padData.analogX2[loop] = 128;
		padData.analogY2[loop] = 128;

		padData.newShock[loop] = 0;
		
		pad[loop] = 0;
		debounce[loop] = 0;
		analog[loop] = 0;
		leftx[loop] = 128;
		lefty[loop] = 128;
		rightx[loop] = 128;
		righty[loop] = 128;


	}
	bothdebounces = bothpads = 0;
}


static inline UBYTE padGetTypeFromPacket(UBYTE id)
{
	switch(id)
	{
	case 4:
		return PADTYPE_DIGITAL;
	case 7:
		return PADTYPE_ANALOG;
	}
	return PADTYPE_NONE;
}


static void padHandlePort(int port)
{
	int			padLp, HWport, padNo, state, currPad, loop;
	PadPacket	*packet;
	UBYTE		id;

	if (*padData.buffer[port])
	{
		ASSERT(padData.buffer[port]);

		if(port == 0)
			PAD_PRINTF("(port %d empty)\n",port);

		for(padLp=0; padLp<4; padLp++)
		{
			padNo = padLp|(port<<2);
			padData.present[padNo] = PADTYPE_NONE;
			padData.state[padNo] = PadStateDiscon;
			padData.digital[padNo] = 0;

			padData.newShock[padNo] = 0;	// Ensure that if an analog pad is plugged in, it gets initialised (New by Fred)

		}
		return;
	}

#ifdef MULTITAP_SUPPORT
	if (PadChkMtap(port<<4)==1)					// SORT THIS OUT - COULD BE GREEDY
		padData.numPads[port] = 4;
	else
#endif
		padData.numPads[port] = 1;

	for(padLp=0; padLp<padData.numPads[port]; padLp++)
	{
//		padNo = padLp|(port<<2);
//		if(port == 0)
//			padNo = padLp;
//		else
//			padNo = padLp + padData.numPads[0];


		padNo = padLp|(port<<2);
		HWport = padLp|(port<<4);
		state = PadGetState(HWport);

		if(padLp == 0 && port == 0)
			PAD_PRINTF("(Pad 0 state is currently %d)\n",state);	//padData.state[padNo]);

/*
#define PadStateDiscon		0
#define PadStateFindPad		1
#define PadStateFindCTP1	2
#define PadStateFindCTP2	3
#define PadStateReqInfo		4
#define PadStateExecCmd		5
#define PadStateStable		6
*/

		switch(state)										// Handle (dis)connection states
		{
		case PadStateDiscon:
			PAD_PRINTF0("  Controller %d disconnected\n",padNo);

// We get one frame of "disconnected" when switching controller modes, so only flag it as having been
// disconnected if it's for longer than a frame (in which case it gets handled by the fast "port check" above)
// (Fred mod)
			if(!padData.newShock[padNo])
			{
  				padData.present[padNo] = PADTYPE_NONE;
				padData.state[padNo] = PadStateDiscon;
			}

			continue;
		case PadStateFindPad:
			PAD_PRINTF0("  Find controller %d connection (checking)\n",padNo);
// Fred mod, for the same reason as the previous one
			if(!padData.newShock[padNo])
			{
				padData.state[padNo] = PadStateFindPad;
			}
			continue;
		case PadStateFindCTP1:
			if (padData.state[padNo]!=PadStateStable)
			{
				PAD_PRINTF0("  Check for controller %d connection with controllers other than DUAL SHOCK (Complete the acquisition of controller information)\n",padNo);
				padData.state[padNo] = PadStateStable;
				currPad = PadInfoMode(HWport, InfoModeCurID, 0);
				switch(currPad)
				{
				case 4:
					PAD_PRINTF0("%d: Standard Pad Connected\n", padNo);
					padData.present[padNo] = PADTYPE_DIGITAL;
					padData.digital[padNo] = 0;
					break;
				case 7:
					PAD_PRINTF0("%d: Old Analog Pad (Red Mode) Connected\n", padNo);
					padData.present[padNo] = PADTYPE_ANALOG;
					padData.digital[padNo] = 0;
					break;
				}
				continue;
			}
			break;
		case PadStateReqInfo:
			PAD_PRINTF0("  Actuator information %d being retrieved (data being retrieved)\n",padNo);
			continue;

		case PadStateStable:

			if(padData.newShock[padNo])
			{
				switch(padData.newShock[padNo])
				{
				case 10:
					PAD_PRINTF0("NewShock setting Act/Align countdown %d\n",padNo);
					padData.motor[padNo][0] = 0;
					padData.motor[padNo][1] = 0;

					PadSetAct(HWport, padData.motor[padNo], 2);
					PadSetActAlign(HWport,HWalign);

					padData.newShock[padNo]=9;
					break;
				case 9:
					PAD_PRINTF0("NewShock countdown complete %d\n",padNo);
					padData.newShock[padNo]=0;
					break;
				default:
					break;
				}
			}

			else if (padData.state[padNo]!=PadStateStable)
			{
				PAD_PRINTF0("  Retrieval of actuator information %d completed, or library-controller communication completed\n",padNo);
				padData.state[padNo] = PadStateStable;
				currPad = PadInfoMode(HWport, InfoModeCurExID, 0);
				if(currPad)
				{
					PAD_PRINTF0("%d: Found DUAL SHOCK controller %d\n", padNo,currPad);
					padData.present[padNo] = PADTYPE_DUALSHOCK;
					padData.digital[padNo] = 0;

// The trouble is that on my 3rd party pad. ca;;omg "PadSetMainMode" causes
// a couple of "FindPad" returns before it becomes "stable" again.
// So we need to prevent "SetMainMode" from being called when this is the case
// Hence this "if" is a new addition by yours truly, and there's a couple of places
// where "NewShock" gets reset. Fred

					if(!padData.newShock[padNo])
					{
						padData.newShock[padNo] = 10;
						PadSetMainMode(HWport,1,3);
					}

// Old-stylee code removed by Actionman in favour of the "newShock" countdown.
// This bit is now delayed using "padData.newShock"
/*
					while(PadSetActAlign(HWport,HWalign)==0)
					{
				        for(loop=0; loop<6;loop++)
			                VSync(0);
					}
					padData.motor[padNo][0] = 0;
					padData.motor[padNo][1] = 0;
					PadSetAct(HWport, padData.motor[padNo], 2);
*/

				}
				else
				{
					PAD_PRINTF0("  Found unsupported extended controller %d\n",padNo);
					padData.present[padNo] = PADTYPE_NONE;
				}
				continue;
			}
			break;
		}
		if (padData.numPads[port]==4)
		{
			packet = (PadPacket *)&(padData.buffer[port][(padLp*8)+2+2]);
			id = padData.buffer[port][(padLp*8)+1+2]>>4;
		}
		else
		{
			packet = (PadPacket *)&(padData.buffer[port][(padLp*8)+2+0]);
			id = padData.buffer[port][(padLp*8)+1+0]>>4;
		}
		currPad = padData.present[padNo];
		if (currPad!=PADTYPE_DUALSHOCK)				// Dodgy old analog controller morphs into other ones
		{
			padData.present[padNo] = padGetTypeFromPacket(id);
			if (currPad!=padData.present[padNo])
			{
				switch(padData.present[padNo])
				{
				case PADTYPE_DIGITAL:
					PAD_PRINTF0("%d: Morphing controller became digital\n", padNo);
					break;
				case PADTYPE_ANALOG:
					PAD_PRINTF0("%d: Morphing controller became red analog\n", padNo);
					break;
				case PADTYPE_NONE:
					PAD_PRINTF0("%d: Morphing controller became unsupported\n", padNo);
					break;
				}
			}
		}
		if (padData.present[padNo]!=PADTYPE_NONE)
		{
			padData.digital[padNo] = ~packet->digital;
			padData.analogX[padNo] = packet->leftX;
			padData.analogY[padNo] = packet->leftY;
			padData.analogX2[padNo] = packet->rightX;
			padData.analogY2[padNo] = packet->rightY;
		}
		else
			padData.digital[padNo] = 0;

//		printf("pad type %d, digital data %d\n",currPad,padData.digital[padNo]);
	}	
}


/**************************************************************************
	FUNCTION:	padHandler()
	PURPOSE:	Handle pad reading/connection etc.
	PARAMETERS:	
	RETURNS:	
**************************************************************************/

void padHandler()
{
	int		HWport;
	ushort	temp,old, bothold;
	int		loop;

	padHandlePort(0);
	padHandlePort(1);

	bothold = bothpads;
	bothpads = 0;


	//PAD_PRINTF("pads:- ");

	for(loop=0; loop<8; loop++)
	{
		old = pad[loop];
		pad[loop] = padData.digital[loop];
		analog[loop] = (padData.present[loop]==PADTYPE_ANALOG)||(padData.present[loop]==PADTYPE_DUALSHOCK);
		leftx[loop] = padData.analogX[loop];
		lefty[loop] = padData.analogY[loop];
		rightx[loop] = padData.analogX2[loop];
		righty[loop] = padData.analogY2[loop];

		temp = (pad[loop] ^ old);
		debounce[loop] = (temp & pad[loop]);
		bothpads |= padData.digital[loop];

		//PAD_PRINTF("%x ",pad[loop]);


		if (padData.present[loop]==PADTYPE_DUALSHOCK && dualShockOn)
		{

// This bit's changed since the "newShock" variable went in
			if (padData.newShock[loop])
			{

// AM Stylee - delay for a number of loops
/*
				{
					if (padData.newShock[loop]==1)
					{
						padData.motor[loop][0] = 0;
						padData.motor[loop][1] = 0;
						HWport = ((loop & 3)|((loop>>2)<<4));
						PadSetAct(HWport, padData.motor[loop], 2);
						PadSetActAlign(HWport,HWalign);
					}
					PAD_PRINTF("NewShock post-setting delay %d= %d\n",loop,padData.newShock[loop]);
					padData.newShock[loop]--;
				}
*/

// Fred Stylee: Check for state being stable
/*
				int state;
				HWport = ((loop & 3)|((loop>>2)<<4));
				state = PadGetState(HWport);

				switch(padData.newShock[loop])
				{
				case 10:
					if(state == PadStateStable)
					{
						PAD_PRINTF("NewShock setting Act/Align countdown %d\n",loop);
						padData.motor[loop][0] = 0;
						padData.motor[loop][1] = 0;

						PadSetAct(HWport, padData.motor[loop], 2);
						PadSetActAlign(HWport,HWalign);

						padData.newShock[loop]=9;
					}
					break;
				case 9:
					PAD_PRINTF("NewShock countdown complete %d\n",loop);
					padData.newShock[loop]=0;
					break;
				default:
				}
*/


/*

				int state;

//				if (showProgress==0)	// (Action man's loading bar)

// Actionman set the "Act" and "ActAlign" after a fixed number of frames.
// I'm checking the state, and doing it when the Pad's clear to be communicated with.

// And after that, there's a "newShock" delay during which the pad won't be locked.

				HWport = ((loop & 3)|((loop>>2)<<4));
				state = PadGetState(HWport);

				if(state == PadStateStable && padData.newShock[loop] == 10)
				{
					PAD_PRINTF("NewShock setting Act/Align countdown %d\n",loop);
					padData.motor[loop][0] = 0;
					padData.motor[loop][1] = 0;
					HWport = ((loop & 3)|((loop>>2)<<4));
					PadSetAct(HWport, padData.motor[loop], 2);
					PadSetActAlign(HWport,HWalign);
//					padData.newShock[loop]--;
					padData.newShock[loop]=1;
				}

				if(padData.newShock[loop] < 10)
				{
					PAD_PRINTF("NewShock post-setting delay %d= %d\n",loop,padData.newShock[loop]);
					padData.newShock[loop]--;
				}
*/
			}
			else
			{
				if ((padData.shock[loop]>>16)!=padData.motor[loop][1])
				{
					if (padData.shockDiv[loop]==0)
						padData.shockDiv[loop] = 1;
					padData.currShock[loop] += (padData.shock[loop]-padData.currShock[loop])/padData.shockDiv[loop];
					padData.motor[loop][1] = (padData.currShock[loop]>>16);
				}
				if (padData.buzzTime[loop]>0)
				{
					padData.buzzTime[loop]--;
					padData.motor[loop][0] = 1;
				}
				else
					padData.motor[loop][0] = 0;
			}
		}
		else
		{
			padData.motor[loop][0] = 0;
			padData.motor[loop][1] = 0;
		}

// ---- dead-zone normalisation stuff from old glover joypad code ----

		if(analog[loop]==YES)
		{
/*
			nleftx[loop]=NormaliseJoy(leftx[loop]);
			nlefty[loop]=NormaliseJoy(lefty[loop]);
			nrightx[loop]=NormaliseJoy(rightx[loop]);
			nrighty[loop]=NormaliseJoy(righty[loop]);
*/
			NormaliseJoy2(leftx[loop],lefty[loop],&nleftx[loop],&nlefty[loop]);
			NormaliseJoy2(rightx[loop],righty[loop],&nrightx[loop],&nrighty[loop]);

/*			if(loop==0)
			{
				DB("xy = %d %d, nx ny = %d %d\n",leftx[loop],lefty[loop],nleftx[loop],nlefty[loop]);
			}
*/
		}
		else
		{
			nleftx[loop]=0;
			nlefty[loop]=0;
			nrightx[loop]=0;
			nrighty[loop]=0;
		}

		if(!(nleftx[loop]|nlefty[loop]))
		{	// emulate analog with dpad
			nleftx[loop]=0;
			nlefty[loop]=0;

			if(pad[loop]&PAD_LEFT)
				nleftx[loop]=-4096;
			else
				if(pad[loop]&PAD_RIGHT)
					nleftx[loop]=4096;

			if(pad[loop]&PAD_DOWN)
				nlefty[loop]=4096;
			else
				if(pad[loop]&PAD_UP)
					nlefty[loop]=-4096;
		}
// ---- end of Glover's normalisation code ----
		if(pad[loop])
			lastTouched=iframe;

		if(nleftx[loop]|nlefty[loop]|nrightx[loop]|nrighty[loop])
			lastTouched=iframe;
	}
	temp = (bothpads ^ bothold);
	bothdebounces = (temp & bothpads);


// Move Pad 4 down to Pad 1
	//PAD_PRINTF("\n");
	if(padData.numPads[0] == 1)
	{
		pad[1] = pad[4];
		debounce[1] = debounce[4];

		leftx[1] = leftx[4];
		lefty[1] = lefty[4];
		rightx[1] = rightx[4];
		righty[1] = righty[4];

		nleftx[1] = nleftx[4];
		nlefty[1] = nlefty[4];
		nrightx[1] = nrightx[4];
		nrighty[1] = nrighty[4];

		analog[1] = analog[4];
	}
}


