/******************************************************************************************
	CRTest PS   (c) 1999-2001 ISL

	platform.c:			The yummy scrummy platform source
******************************************************************************************/

#include "global.h"
#include "collide.h"
#include "platform.h"
#include "map_draw.h"
#include "bff_load.h"
#include "maths.h"

#include "overlays.h"
#include "main.h"
#include <limits.h>

#include "charactr.h"
#include "enemies.h"	// for the shoulderpushing check
#include "gametext.h"
#include "demo.h"
#include "sound.h"

// Rather than checking all the corners and all the edges and all the planes of a collision box...
// Let's knock 'em out a little bit quicker, huh?

char platcoll_is_enemy = 1;
extern int inv_messagetimer;			// A timer for printing 'Too heavy / Not enough room' etc messages while trying to pick things up 
extern char *inv_currentmessage;

int platcollNearStaticBox(COLLBOX *pB, COLLDATA *pD)
{
	int min,max;

// x
	min = pB->pX - pD->radius;
	max = pB->pX + pB->sX + pD->radius;

	if(pD->pPos->vx < min && pD->oldPos.vx < min)
		return 0;
	if(pD->pPos->vx > max && pD->oldPos.vx > max)
		return 0;

// z
	min = pB->pZ - pD->radius;
	max = pB->pZ + pB->sZ + pD->radius;

	if(pD->pPos->vz < min && pD->oldPos.vz < min)
		return 0;
	if(pD->pPos->vz > max && pD->oldPos.vz > max)
		return 0;

// y
	min = pB->pY - pD->radius;
	max = pB->pY + pB->sY + pD->radius;

	if(pD->pPos->vy < min && pD->oldPos.vy < min)
		return 0;
	if(pD->pPos->vy > max && pD->oldPos.vy > max)
		return 0;

	return 1;
}

// Now let's see that again, with a moving box
int platcollNearMovingBox(COLLBOX *pB, COLLDATA *pD, VECTOR *cumpos, VECTOR *oldpos)
{
	int min,max;

// x
	if(oldpos->vx < cumpos->vx)
	{
		min = pB->pX + oldpos->vx - pD->radius;
		max = pB->pX + cumpos->vx + pB->sX + pD->radius;
	}
	else
	{
		min = pB->pX + cumpos->vx - pD->radius;
		max = pB->pX + oldpos->vx + pB->sX + pD->radius;
	}

	if(pD->pPos->vx < min && pD->oldPos.vx < min)
		return 0;
	if(pD->pPos->vx > max && pD->oldPos.vx > max)
		return 0;


// z
	if(oldpos->vz < cumpos->vz)
	{
		min = pB->pZ + oldpos->vz - pD->radius;
		max = pB->pZ + cumpos->vz + pB->sZ + pD->radius;
	}
	else
	{
		min = pB->pZ + cumpos->vz - pD->radius;
		max = pB->pZ + oldpos->vz + pB->sZ + pD->radius;
	}

	if(pD->pPos->vz < min && pD->oldPos.vz < min)
		return 0;
	if(pD->pPos->vz > max && pD->oldPos.vz > max)
		return 0;

// y (note inversion, coz the size is -ve)
	if(oldpos->vy < cumpos->vy)
	{
		min = pB->pY + oldpos->vy + pB->sY - pD->radius;
		max = pB->pY + cumpos->vy + pD->radius;
	}
	else
	{
		min = pB->pY + cumpos->vy + pB->sY - pD->radius;
		max = pB->pY + oldpos->vy + pD->radius;
	}

	if(pD->pPos->vy < min && pD->oldPos.vy < min)
		return 0;
	if(pD->pPos->vy > max && pD->oldPos.vy > max)
		return 0;

	return 1;
}

// =======================================================================================
// I'll do these things for platforms, with a view to copying 'em into the main collision code
// when it's free. Luv, Fred.

int platcollPlanesCheck(HITDATA *pHitData,COLLDATA *pD,COLLBOX *pB,int oldflag, int t)
{
	long 	x1,x2;
	long 	y1,y2;
	long 	z1,z2,f;


	VECTOR delta;
	int this_t;
//	int t;
//	int plane;
	VECTOR *hitP=&pD->hitPos;
	long r=pD->radius;
	VECTOR *newP = pD->pPos;
	VECTOR *oldP = &pD->oldPos;

	if(!oldflag)
	{
		x1=pB->pX;
		x2=x1+(pB->sX);
		y1=pB->pY;
		y2=y1+(pB->sY);
		z1=pB->pZ;
		z2=z1+(pB->sZ);
//		t = INT_MAX;	// if we're checking old and new, we want to pass t in
	}
	else
	{
		x1=pB->oldpX;
		x2=x1+(pB->sX);
		y1=pB->oldpY;
		y2=y1+(pB->sY);
		z1=pB->oldpZ;
		z2=z1+(pB->sZ);
		// t = t;
	}

	delta.vx = newP->vx - oldP->vx;
	delta.vy = newP->vy - oldP->vy;
	delta.vz = newP->vz - oldP->vz;

// 0 = "D_XZ", which is the top (Y low)

// Like collide, we;re using "newp" for both rather than a newp/oldp split...
	if(newP->vy+r > y2 && newP->vy-r < y2)
	{
		if(newP->vy <= oldP->vy)
			this_t = 0;
		else
			this_t = 4096 * ((y2-r) - oldP->vy) / (newP->vy - oldP->vy);
// Similarly, we're use newX/Z as the isect point rather than actually calculating it properly.
		if((newP->vx >= x1) && (newP->vx < x2) && (newP->vz >= z1) && (newP->vz < z2) && this_t < t)
		{
			t = this_t;
			hitP->vx = newP->vx;
			hitP->vy = y2-r;
			hitP->vz = newP->vz;
			pHitData->plane = D_XZPLANE;
		}
	}

// 1 = U_XZ. which is the bottom (Y-Hi)

	if ( ((newP->vy-r) < y1) && ((newP->vy+r) > y1) )
	{
		if(newP->vy >= oldP->vy)
			this_t = 0;
		else
			this_t = 4096 * ((y1+r) - oldP->vy) / (newP->vy - oldP->vy);

		if((newP->vx >= x1) && (newP->vx < x2) && (newP->vz >= z1) && (newP->vz < z2) && this_t < t)
		{
			t = this_t;
			hitP->vx = newP->vx;
			hitP->vy = y1+r;
			hitP->vz = newP->vz;
			pHitData->plane = U_XZPLANE;
		}
	}

// 2 = D_YZ (X-Hi)
	if ( ((newP->vx+r) > x2) && ((newP->vx-r) < x2) )
	{
		if(newP->vx >= oldP->vx)
			this_t = 0;
		else
			this_t = 4096 * ((x2+r) - oldP->vx) / (newP->vx - oldP->vx);
		if((newP->vy >= y2) && (newP->vy < y1) && (newP->vz >= z1) && (newP->vz < z2) && this_t < t)
		{
			t = this_t;
			hitP->vx = (x2+r)+4096;
			hitP->vy = newP->vy;
			hitP->vz = newP->vz;
			pHitData->plane = D_YZPLANE;
		}
	}

// 3 = U_YZ (X-Lo)
	if ( ((newP->vx-r)<x1) && ((newP->vx+r)>x1) )
	{
		if(newP->vx <= oldP->vx)
			this_t = 0;
		else
			this_t = 4096 * ((x1-r) - oldP->vx) / (newP->vx - oldP->vx);

		if((newP->vy >= y2) && (newP->vy < y1) && (newP->vz >= z1) && (newP->vz < z2) && this_t < t)
		{
			t = this_t;
			hitP->vx = (x1-r)-4096;
			hitP->vy = newP->vy;
			hitP->vz = newP->vz;
			pHitData->plane = U_YZPLANE;
		}
	}

// 4 = D_XY (Z-Hi)
	if ( ((newP->vz+r)>z2) && ((newP->vz-r)<z2) )
	{
		if(newP->vz >= oldP->vz)
			this_t = 0;
		else
			this_t = 4096 * ((z2+r) - oldP->vz) / (newP->vz - oldP->vz);
		if((newP->vx>=x1) && (newP->vx<x2) && (newP->vy>=y2) && (newP->vy<y1) && this_t < t)
		{
			t = this_t;
			hitP->vx = newP->vx;
			hitP->vy = newP->vy;
			hitP->vz = z2+r;
			pHitData->plane = D_XYPLANE;
		}
	}

// 5 = U_XY(Z-Lo)
	if ( ((newP->vz-r)<z1) && ((newP->vz+r)>z1) )
	{
		if(newP->vz <= oldP->vz)
			this_t = 0;
		else
			this_t = 4096 * ((z1-r) - oldP->vz) / (newP->vz - oldP->vz);
		if((newP->vx>=x1) && (newP->vx<x2) && (newP->vy>=y2) && (newP->vy<y1) && this_t < t)
		{
			t = this_t;
			hitP->vx = newP->vx;
			hitP->vy = newP->vy;
			hitP->vz = z1-r;
			pHitData->plane = U_XYPLANE;
		}
	}

// Tidying up
	if(t != INT_MAX)
	{
		pHitData->type = PLANE;
		CopyVector(&(pHitData->pos), &(pD->hitPos));
		pHitData->pCollBox=pB;
	}
	return t;
}

// =======================================================================================

void platcollCheckOneMovingBox(COLLBOX *pB1, COLLDATA *pD, int *pnPolysHit, VECTOR *pCumPos, VECTOR *pOldPos)
{
	static COLLBOX cB;
	VECTOR retpos;


/*
	if (
		  (pD==&cameraColl
		|| pD==&cameraProbeColl[0]
		|| pD==&cameraProbeColl[1]
		  )
		&& (cB.flags&SPECIALWITHMOVE)==C_NOCAMCOLLISION ){
		return;
	}
*/
	//if (pD==&cameraColl) printf ("hmm\n");


// copy the stuff that doesn't get modified
	cB.sX = pB1->sX;
	cB.sY = pB1->sY;
	cB.sZ = pB1->sZ;
	cB.flags = pB1->flags;
	cB.rampheader = pB1->rampheader;

// do the maths for the stuff that changes during the copy...
// add offset to platcoll, so it is movable

	cB.pX = pB1->pX + pCumPos->vx;
	cB.pY = pB1->pY + pCumPos->vy;
	cB.pZ = pB1->pZ + pCumPos->vz;

	cB.oldpX = pB1->pX + pOldPos->vx;
	cB.oldpY = pB1->pY + pOldPos->vy;
	cB.oldpZ = pB1->pZ + pOldPos->vz;

//	printf("pxyz = %d %d %d\n",cB.pX,cB.pY,cB.pZ);

// we really should do a quick boundy thing before all these calls...

	switch(cB.flags&TYPEPART)
	{
		case BOX:
			{
			int t;

			t = platcollPlanesCheck(&pHitData[*pnPolysHit],pD,&cB,0,INT_MAX);
			if((cB.flags & SPECIALPART)==COLLBOXFLAG_MOVE
				&&
			(	  pCumPos->vx != pOldPos->vx
				||pCumPos->vy != pOldPos->vy
				||pCumPos->vz != pOldPos->vz
				)
				)
			{
				t = platcollPlanesCheck(&pHitData[*pnPolysHit],pD,&cB,1,t);
			}

			if(t != INT_MAX)
			{
				pD->flags|=cB.flags;
				(*pnPolysHit)++;
			}
			else
			{
			if(CheckCorners(&cB, pD, &pHitData[*pnPolysHit]))
			{
//				printf("hitC\n");
//				pD->nCornerHits++;
				pD->flags|=cB.flags;
				(*pnPolysHit)++;
//				pD->flags|=(cB.flags&SPECIALWITHMOVE)>>8;
			}
			// Check Edges
			else if(CheckEdges(&cB, pD, &pHitData[*pnPolysHit]))
			{
//				printf("hitE\n");
				pD->flags|=cB.flags;
				(*pnPolysHit)++;
//				pD->flags|=(cB.flags&SPECIALWITHMOVE)>>8;
			}
			}
/*
			// Check Planes
			else if(CheckPlanes(&cB, pD, &pHitData[*pnPolysHit]))
			{
//				printf("hitP\n");
				(*pnPolysHit)++;
//				pD->nPlaneHits++;
//				pD->flags|=(cB.flags&SPECIALWITHMOVE)>>8;
*/

// When it comes to pushing pushable platforms around, we're only interested in planes, rather than corners
// (which means that for test purposes, we need a bigger platform to play with, because the current ones keep
// on returning corner collision...)

			}

			break;
		case  RAMP:
			if(CheckRamps(&cB, pD, &pHitData[*pnPolysHit]))
			{
//				printf("hitR\n");
//				pD->flags|=(cB.flags&SPECIALWITHMOVE)>>8;
//				pD->nRampHits++;
				pD->flags|=cB.flags;
				(*pnPolysHit)++;
			}
			break;
		default:
			//printf("Error - single moving box had a coll box. not described properly!\n");
			break;
	}
}


// ---- Called if a box/ramp collision happened, and the platform's "conveyor" flag was set ----

// If it's a conveyor platform, then we need to move in the given direction
// Note - switchable on/off/n/s/e/w conveyors

// Are there any sloping conveyors in this game?
// (coz if there are, they need to be done at ramp time. But n/s/e/w can be done here)

// tbd - this should be when "on", not when "touching", surely?
// (We could do that by checking th collision type & the normal)
void platformConveyorCollision(COLLDATA *pD,PLATFORMWORK *platform,HITDATA *pH,COLLBOX *box)
{
	if(platform->info.conveyor.speed)
	{
		VECTOR vel;
		vel.vx = platform->info.conveyor.speed * rsin(platform->def->definfo.conveyor.ya) >> 12;
		vel.vy = 0;
		vel.vz = platform->info.conveyor.speed * rcos(platform->def->definfo.conveyor.ya) >> 12;

		pD->pPos->vx += (vel.vx << 9);
		pD->pPos->vy += (vel.vy << 9);
		pD->pPos->vz += (vel.vz << 9);
	}
}

// ---- Called if a box/ramp collision happened, and the platform's "pushable" flag was set ----

void platformPushableCollision(COLLDATA *pD,PLATFORMWORK *platform,HITDATA *pH,COLLBOX *box)
{
// okay.
// We don't want the thing to move until the chicken is actually in some kind of "pushing" state.
// The chicken won't even enter that state if, for example, it's not stood on the floor.
// So:-
// Check if impact plane is okay for pushing. If so
//   Return flag (& direction) (& "pushable but won't move any further") to chicken code.
// If it is pushing the platform back, don't allow it to slide along
//   If chicken is in pushing state, push platform.
//		pH = &pHitData[oldPolysHit];
	if(pH->type == PLANE)	// insist on pushing the planes, not the corners
	{
		VECTOR pushdirn;
		int pushes = 0;

		switch(pH->plane)
		{
		case U_XYPLANE:	// z-lo
			pushdirn.vx = 0;
			pushdirn.vy = 0;
			pushdirn.vz = 4096;
			pushes = 1;
			break;
		case D_XYPLANE:	// z-hi
			pushdirn.vx = 0;
			pushdirn.vy = 0;
			pushdirn.vz = -4096;
			pushes = 1;
			break;
		case U_YZPLANE:	// x-lo
			pushdirn.vx = 4096;
			pushdirn.vy = 0;
			pushdirn.vz = 0;
			pushes = 1;
			break;
		case D_YZPLANE:	// x-hi
			pushdirn.vx = -4096;
			pushdirn.vy = 0;
			pushdirn.vz = 0;
			pushes = 1;
			break;

		default:
			break;
		}

		if(pushes)
		{
			switch(platformWillPush(platform,&pushdirn))
			{
				case PLATWILLPUSH_OK:
	//				DB("Platform will push!\n");
					if(chickenReadyToPushPlatform(pD, platform, box, &pushdirn))
					{
	//					DB("chicken ready. Pushdirn = %d %d\n",pushdirn.vx,pushdirn.vz);
						platform->info.pushable.pushvec.vx = pushdirn.vx;
						platform->info.pushable.pushvec.vy = pushdirn.vy;
						platform->info.pushable.pushvec.vz = pushdirn.vz;

						if(platform->noisy.pSample && (platform->noisy.playing == FALSE))
						{
							platform->noisy.playing = TRUE;
							soundPlaySamplePanFade(platform->noisy.pSample, 255, 0, (VECTOR *)&(platform->cumPos));
						}

					}
					else
					{
	//					DB("chicken not ready\n");
					}
					break;

				case PLATWILLPUSH_WRONGCHAR:
				//	if(characterGetMainCharacter()->pEnemy->readData.nameCRC == CRC_FETCHER)
					// Not just Fetcher - Ginger can't push the Egg tray in the egg room, only rocky
					{
						if(pReadPad->digital[0] & controlMaps[controlMap].action)
						{
							inv_messagetimer = 12;
							inv_currentmessage = TEXTSTR(TEXTSTR_HELP_NOPUSH);		
						}
					}

				case PLATWILLPUSH_ENDOFRUN:
				case PLATWILLPUSH_BLOCKED:
				case PLATWILLPUSH_NOT_ON_AXIS:
					// Tell the chicken to go into it's pushing anim, but don't set the platform moving
/*					if(chickenReadyToPushPlatform(pD, platform, box, &pushdirn))
					{
					}
					else
					{
					}
*/
					chickenReadyToPushPlatform(pD, platform, box, &pushdirn);
					soundStopSample(platform->noisy.pSample);

					break;



/*				case PLATWILLPUSH_NOT_ON_AXIS:
					// The chicken's trying to push it sideways

 					chickenPressingAgainstPlatform(pD, platform, box, &pushdirn);


					break;
*/
			}
		}
	}
}

// ---- Called if a box/ramp collision happened, and the platform's "rotating" flag was set ----


void platformRotatingCollision(COLLDATA *pD,PLATFORMWORK *plat,HITDATA *pH,COLLBOX *box)
{
	VECTOR plat_relative;
	VECTOR v;
	VECTOR v2;
	short da;

	switch(plat->def->definfo.rotating.axis)
	{
	case PLATROTATE_XAXIS:
		DB("Collision with x-axis spin\n");
		break;
	case PLATROTATE_YAXIS:
	// We need to grab a new platform-relative point
		v.vx = pD->pPos->vx - ((plat->cumPos.vx)<<9);
		v.vy = pD->pPos->vy - ((plat->cumPos.vy)<<9);
		v.vz = pD->pPos->vz - ((plat->cumPos.vz)<<9);

		da = plat->def->definfo.rotating.speed;

		v2.vx = (v.vx * rcos(da) + v.vz * rsin(da))>>12;
		v2.vy = v.vy;
		v2.vz = (-v.vx * rsin(da) + v.vz * rcos(da))>>12;

		pD->pPos->vx = v2.vx + ((plat->cumPos.vx)<<9);
		pD->pPos->vy = v2.vy + ((plat->cumPos.vy)<<9);
		pD->pPos->vz = v2.vz + ((plat->cumPos.vz)<<9);

		pH->pos.vx = pD->pPos->vx;
		pH->pos.vz = pD->pPos->vz;
		break;
	case PLATROTATE_ZAXIS:
		DB("Collision with z-axis spin\n");
		break;
	}
}

// ------------- Routine to call from collide, which checks all the platforms --------------

/*
		if(platform->def->flags & PLATFLAG_ROTATING)
		{
// Rotate the actor sphere into platform-space
// Oh hang on, that won't work because of the "adjust collision" stuff assuming edges & corners are axis-aligned
		}
*/

void platformCheckCollision(COLLDATA *pD, int *pnPolysHit)
{
	PLATFORMWORK *platform;
	int dX,dY,dZ;
	VECTOR accpos,accold;
	int margin;

//	int which = 0;
//	return;

// The radius to allow for is the colldata's radius plus the magnitude of the colldata's velocity
	{
		VECTOR vel;
		vel.vx = pD->pPos->vx - pD->oldPos.vx;
		vel.vy = pD->pPos->vy - pD->oldPos.vy;
		vel.vz = pD->pPos->vz - pD->oldPos.vz;
		margin = Magnitude(&vel) + pD->radius;
	}

	for(platform = platform_list; platform; platform = platform->next)
	{
//		which++;

		if(!platform->active)
			continue;
		if(platform->def->flags & PLATFLAG_NOCOLLISION)
			continue;
		if(platcoll_is_enemy && (platform->def->flags & PLATFLAG_NONMECOLLISION))
			continue;



		dX=((pD->pPos->vx)-((platform->cumPos.vx)<<9));
		dY=((pD->pPos->vy)-((platform->cumPos.vy)<<9));
		dZ=((pD->pPos->vz)-((platform->cumPos.vz)<<9));

// What's needed is a "bunch of collboxes" bounding box, rather than a radius

// Okay, we've got one. We just need to check it.
/*
		platformdef->coll_bounds.minx = tot_min.vx;
		platformdef->coll_bounds.miny = tot_min.vy;
		platformdef->coll_bounds.minz = tot_min.vz;

		platformdef->coll_bounds.maxx = tot_min.vx;
		platformdef->coll_bounds.maxy = tot_min.vy;
		platformdef->coll_bounds.maxz = tot_min.vz;
*/

// Oi reckon that this calculation ov's with large numbers
// (What we want is a bounding box check of all sub-boxes of a platform, anyways)
//		if( (dX*dX)+(dY*dY)+(dZ*dZ) < (platform->coll_radius + margin) * (platform->coll_radius + margin))
		{
			int i,oldPolysHit;
			COLLBOX *box;

			oldPolysHit=*pnPolysHit;

			box = platform->def->coll_bounds.pBoxes;

//			printf("which %d has %d boxes\n",which,platform->def->nCollBoxes);
			for(i=0; i<platform->def->coll_bounds.nCollBoxes; i++)
			{
				accpos.vx = platform->cumPos.vx<<9;
				accpos.vy = platform->cumPos.vy<<9;
				accpos.vz = platform->cumPos.vz<<9;
				accold.vx = platform->oldPos.vx << 9;
				accold.vy = platform->oldPos.vy << 9;
				accold.vz = platform->oldPos.vz << 9;

				if(platcollNearMovingBox(box, pD, &accpos,&accold))
				{
					int check_old_pos;


					check_old_pos = 1;

					// RFW - If you're PUSHING a pushable platform, don't check the old position
					if(platform->def->flags & PLATFLAG_PUSHABLE)
					{
						// Hmmm. TBD: We should check that the caller is actually the main character, but can't at the moment.
						if(characterVars.pMainChar->pEnemy->state == STATE_SHOULDERPUSHING)
						{
							check_old_pos = 0;
						}
					}


					if(check_old_pos)
					{
						platcollCheckOneMovingBox(box, pD, pnPolysHit, &accpos, &accold);
					}
					else
					{
						platcollCheckOneMovingBox(box, pD, pnPolysHit, &accpos, &accpos);	// :)
					}
					if((*pnPolysHit)>oldPolysHit)
						pHitData[(*pnPolysHit)-1].pPlatHit = platform;
				}
				box++;
			}

			if(*pnPolysHit>oldPolysHit)
			{// there must have been a hit on this platform, so record it

// Try adding in the movement of the platform here.
// (Unlike Glover which updated both old AND newpos, according to the hitlist, in the platform code)
// Because the world mesh is dealt with after the platforms, this *should* mean that platforms never
// drive you out of the level.
// Prob - laaaaag.....
// Prob solution - display the platform at it's old position

				pD->pPos->vx += (accpos.vx - accold.vx);
				pD->pPos->vy += (accpos.vy - accold.vy);
				pD->pPos->vz += (accpos.vz - accold.vz);

				if(platform->def->flags & PLATFORM_SPRINGBOARD)
				{
					// if the spring board is moving down stop bouncy flag being set due to fuckwit tester
					//DB("old y %d y %d\n",accold.vy,accpos.vy);
					if (accpos.vy<=accold.vy)
					{
						//DB("we are on a spring board\n");
						pD->flags|=COLLBOXFLAG_BOUNCY;	// I think this is the one to use
					}
				}

				if(platform->def->flags & PLATFLAG_CONVEYOR)
				{
					platformConveyorCollision(pD,platform,&pHitData[oldPolysHit],box);
				}
				if(platform->def->flags & PLATFLAG_PUSHABLE)
				{
					platformPushableCollision(pD,platform,&pHitData[oldPolysHit],box);
				}
				if(platform->def->flags & PLATFLAG_ROTATING)
				{
					platformRotatingCollision(pD,platform,&pHitData[oldPolysHit],box);
				}
			}
		}
	}
}








