//networkphysics.cpp
//sends and receives physics data to and from the network
//also stores and loads packets to and from the replay log, 
//as these share the same format as network packets.

#include <string.h>
#include <stdio.h>
#include <math.h>
#include <network_tool.h>
#include "network.h"
#include "entities.h"
#include "vectors.h"
#include "carphysics.h"
#include "particles.h"
#include "tracks.h"
#include "gameframe.h"
#include "networkphysics.h"
#include "log.h"
#include "error.h"
#include "gameinitexit.h"
#include "gamemem.h"
#include "gamesound.h"
#include "gametime.h"
#include "interfacemultiplayer.h"
#include "collision.h"
#include "environment.h"

int gReplayIndex=0;
int gSynchsRecevied=0;

//compressed subset of tWheelPhysics for network broadcasts
typedef struct{
	unsigned char rotation,glow;
	char angularVelo;
	char slipVelo;
	char surfaceType;
} tWheelNetPhysics;

//compressed subset of tCarPhysics for network broadcasts
typedef struct{
	unsigned char lightFlags;
	unsigned char rpm,throttle; 
	unsigned char shiftDelay,handbrake;
	unsigned short damage;
	char steering;
	tWheelNetPhysics wheels[kMaxWheels];
	tVector3 cameraPos;
} tCarNetPhysics; 

//a structure containing all the physics data necessary 
//to transmit a cars state for network broadcasts
typedef struct{
	tPhysicsMessage entity;
	tCarNetPhysics phys;
} tCarPhysicsMessage;

typedef struct{
	tPhysicsMessage entity;
	char resends;
	char filler[sizeof(tCarNetPhysics)-1];
} tSolidPhysicsMessage;


#define kMaxGameChatMessages 4
#define kMaxOutboxSize (kNetworkToolPacketSize/sizeof(tCarPhysicsMessage))
int gNumGameChatMessages=0;
tGameChatMessage gGameChatMessagesRecv[kMaxGameChatMessages];
tCarPhysicsMessage gHostOutbox[kMaxOutboxSize];
int gOutboxPos=0,gOutboxSender=-1;
float gLastOutboxSend=0;

void GameChatMessagesScroll()
{
	for(int i=0;i<gNumGameChatMessages-1;i++)
		gGameChatMessagesRecv[i]=gGameChatMessagesRecv[i+1];
	gNumGameChatMessages--;
}

void InsertGameChatMessage(char *message)
{
	while(gNumGameChatMessages>=kMaxGameChatMessages)
		GameChatMessagesScroll();
	gGameChatMessagesRecv[gNumGameChatMessages].timestamp=TimeGetSeconds();
	strcpy(gGameChatMessagesRecv[gNumGameChatMessages].message,message);
	gNumGameChatMessages++;
}

void InsertToOutbox(tCarPhysicsMessage *msg,int from)
{
	for(int i=0;i<gOutboxPos;i++)
		if(gHostOutbox[i].entity.id==msg->entity.id)
		{
			int mFrame=msg->entity.frame;
			int bFrame=gHostOutbox[i].entity.frame;
			S32Swap(mFrame);
			S32Swap(bFrame);
			if(bFrame<mFrame)
				gHostOutbox[i]=*msg;
			return;
		}
	if(gOutboxPos<kMaxOutboxSize)
		gHostOutbox[gOutboxPos++]=*msg;
	if(gOutboxPos==1)
		gOutboxSender=from;
	else if(gOutboxSender!=from)
		gOutboxSender=-1;
}

//converts 6 shorts to a 3x3 rotation matrix
void ShortVectorsToMatrix(tVector3Short dirZ,tVector3Short dirY,tMatrix3 m)
{
	MatrixGetZVector(m)->x=dirZ.x*(1/32767.0);
	MatrixGetZVector(m)->y=dirZ.y*(1/32767.0);
	MatrixGetZVector(m)->z=dirZ.z*(1/32767.0);
	MatrixGetYVector(m)->x=dirY.x*(1/32767.0);
	MatrixGetYVector(m)->y=dirY.y*(1/32767.0);
	MatrixGetYVector(m)->z=dirY.z*(1/32767.0);
	*MatrixGetXVector(m)=*MatrixGetYVector(m)%*MatrixGetZVector(m);
}

//compresses generic entity physics data into a tPhysicsMessage
tVector3 PreparePhysicsMessage(tGameEntity *entity,tPhysicsMessage *message)
{
	message->prioritized=false;
	message->id=entity->id;
	message->pos=entity->pos;
	tVector3 v=Vector(0,0,0),a=Vector(0,0,0);
	tVector3 rz=Vector(0,0,0),ry=Vector(0,0,0);
	for(int i=0;i<8;i++)//smooth suspension inbalance
	{
		rz=rz+*MatrixGetZVector(entity->lastRVelos[i]);	
		ry=ry+*MatrixGetYVector(entity->lastRVelos[i]);	
		a=a+entity->lastAccel[i];
		v=v+entity->lastVelos[i];
	}
	a=a*1.0/8;
	
	v=entity->velo;
	v.y=0;
	a.y=0;
	for(int i=0;i<16;i++)
		v.y+=entity->lastVelos[i].y;
	for(int i=0;i<32;i++)
		a.y+=entity->lastAccel[i].y;
	v.y/=16;
	a.y/=32;
	
	if(entity->physicsType==kPhysicsTypeSolid)
	{
		rz=*MatrixGetZVector(entity->rVelo);	
		ry=*MatrixGetYVector(entity->rVelo);	
		v=entity->velo;
	}
	
	//if(sqr(v)>sqr(3))
	message->velo.x=v.x*255;
	message->velo.y=v.y*255;
	message->velo.z=v.z*255;
	if(sqr(entity->accel)<sqr(a))
		a=entity->accel;
	message->accel.x=a.x*255;
	message->accel.y=a.y*255;
	message->accel.z=a.z*255;
	message->frame=gFrameCount;

	tVector3 z=*MatrixGetZVector(entity->dir);	
	message->dirZ.x=fabs(z.x)<1?z.x*32767:sign(z.x)*32767;
	message->dirZ.y=fabs(z.y)<1?z.y*32767:sign(z.y)*32767;
	message->dirZ.z=fabs(z.z)<1?z.z*32767:sign(z.z)*32767;

	tVector3 y=*MatrixGetYVector(entity->dir);
	message->dirY.x=fabs(y.x)<1?y.x*32767:sign(y.x)*32767;
	message->dirY.y=fabs(y.y)<1?y.y*32767:sign(y.y)*32767;
	message->dirY.z=fabs(y.z)<1?y.z*32767:sign(y.z)*32767;
	
	rz=!Vector(rz.x,0,rz.z);
	ry=Vector(0,1,0);
/*	rz=!rz;
	tVector3 rx=!(ry%rz);
	ry=!(rz%rx);*/
	
	message->rVeloZ.x=fabs(rz.x)<1?rz.x*32767:sign(rz.x)*32767;
	message->rVeloZ.y=fabs(rz.y)<1?rz.y*32767:sign(rz.y)*32767;
	message->rVeloZ.z=fabs(rz.z)<1?rz.z*32767:sign(rz.z)*32767;

	message->rVeloY.x=fabs(ry.x)<1?ry.x*32767:sign(ry.x)*32767;
	message->rVeloY.y=fabs(ry.y)<1?ry.y*32767:sign(ry.y)*32767;
	message->rVeloY.z=fabs(ry.z)<1?ry.z*32767:sign(ry.z)*32767;
	return rz;
}


#define kMinPacketDelay 5
#define kMaxPacketDelay 30
#define kMaxPacketRVeloDiff 0.005

float RVeloDiff(tMatrix3 v1,tMatrix3 v2)
{
	float result=~(*MatrixGetXVector(v1)-*MatrixGetXVector(v2));
	result+=~(*MatrixGetYVector(v1)-*MatrixGetYVector(v2));
	result+=~(*MatrixGetZVector(v1)-*MatrixGetZVector(v2));
	return result;
}

float RemoteCarDistance(tGameEntity *entity)
{
	float minDist=INFINITY;
	for(int i=0;i<gGameInfo->numPlayers;i++)
		if(gCarEntities[i]->physicsMachine==kPhysicsRemote)
		{
			float dist=sqr(entity->pos-gCarEntities[i]->remoteCameraPos);
			if(dist<minDist)
				minDist=dist;
		}
	return sqrt(minDist);
}

int SendNewPacket(tGameEntity *entity,tVector3 rz)
{
	int lastPacket=gFrameCount-entity->lastPacketSent;
	float dist=RemoteCarDistance(entity)*0.001;
	float score=0;
	float veloChangePerc;
	if(~entity->velo>1)
	{
		veloChangePerc=~(entity->velo-entity->lastVeloSent)/~entity->velo;
		/*if(veloChangePerc>0.3&&dist<0.25&&~(entity->velo-entity->lastVeloSent)>1)
		{
			//printf("fp\n");
			return 2;
		}*/
	}
	else
		veloChangePerc=~(entity->velo-entity->lastVeloSent);
	score+=veloChangePerc;
	score+=~(rz-entity->lastRZSent)*kFPS;
	score+=~(entity->lastDirZSent-*MatrixGetZVector(entity->dir));
	score*=lastPacket;
	score-=dist;
	if(lastPacket>kFPS*0.5)
		return 1;
	else if(score<0.6||lastPacket<5)
		return 0;
	else
		return 1;
}

int SendNewPacketLog(tGameEntity *entity,tVector3 rz)
{
	int lastPacket=gFrameCount-entity->lastPacketSaved;
	float score=0;
	float veloChangePerc;
	if(~entity->velo>1)
	{
		veloChangePerc=~(entity->velo-entity->lastVeloSaved)/~entity->velo;
		/*if(veloChangePerc>0.3&&dist<0.25&&~(entity->velo-entity->lastVeloSent)>1)
		{
			//printf("fp\n");
			return 2;
		}*/
	}
	else
		veloChangePerc=~(entity->velo-entity->lastVeloSaved);
	score+=veloChangePerc;
	score+=~(rz-entity->lastRZSaved)*kFPS;
	score+=~(entity->lastDirZSaved-*MatrixGetZVector(entity->dir));
	score*=lastPacket;
	if(lastPacket>kFPS*0.5)
		return 1;
	else if(score<0.4||lastPacket<3)
		return 0;
	else
		return 1;
}


void PhysicsMessageSwap(tPhysicsMessage* m)
{
	S32Swap(m->frame);
	F32Swap(m->pos.x);
	F32Swap(m->pos.y);
	F32Swap(m->pos.z);
	S16Swap(m->id);
	S16Swap(m->velo.x);
	S16Swap(m->velo.y);
	S16Swap(m->velo.z);
	S16Swap(m->accel.x);
	S16Swap(m->accel.y);
	S16Swap(m->accel.z);
	S16Swap(m->dirZ.x);
	S16Swap(m->dirZ.y);
	S16Swap(m->dirZ.z);
	S16Swap(m->dirY.x);
	S16Swap(m->dirY.y);
	S16Swap(m->dirY.z);
	S16Swap(m->rVeloZ.x);
	S16Swap(m->rVeloZ.y);
	S16Swap(m->rVeloZ.z);
	S16Swap(m->rVeloY.x);
	S16Swap(m->rVeloY.y);
	S16Swap(m->rVeloY.z);
}

//sends out a car's state to other players (and logs it for the replay log)
void SendCarPhysicsMessage(tGameEntity *entity)
{
	tCarPhysics *phys=(tCarPhysics*)entity->physics;
	tCarPhysicsMessage message;
	
	//compress position, etc..
	tVector3 rz=PreparePhysicsMessage(entity,&message.entity);
	
	//compress car data
	message.phys.lightFlags=phys->lightFlags;
	message.phys.handbrake=phys->handbrake*255;
	float shiftDelay=gFrameCount*kFrameTime-phys->lastGearSwitch;
	message.phys.shiftDelay=(shiftDelay<1)?shiftDelay*255:255;
	message.phys.rpm=phys->rpm*(255.0/10000.0);
	message.phys.throttle=phys->throttle*255;
//	message.phys.brake=phys->brake*255;
//	message.phys.gear=phys->gear;
	message.phys.steering=fabs(phys->steering)<1?phys->steering*127:sign(phys->steering)*127;
	message.phys.damage=phys->damage;
	U16Swap(message.phys.damage);
	message.phys.cameraPos=gCameraEntity->pos;
	F32Swap(message.phys.cameraPos.x);
	F32Swap(message.phys.cameraPos.y);	
	F32Swap(message.phys.cameraPos.z);
	for(int i=0;i<phys->car.numWheels;i++)
	{
		message.phys.wheels[i].rotation=phys->wheels[i].rotation*(255.0/(2*PI));
		message.phys.wheels[i].slipVelo=phys->wheels[i].slipVelo;
		message.phys.wheels[i].surfaceType=phys->wheels[i].surfaceType;
		message.phys.wheels[i].glow=phys->wheels[i].glow*255.0;
		if(fabs(phys->wheels[i].angularVelo)<127)
			message.phys.wheels[i].angularVelo=phys->wheels[i].angularVelo;
		else
			message.phys.wheels[i].angularVelo=127*sign(phys->wheels[i].angularVelo);	
	}
	for(int i=phys->car.numWheels;i<kMaxWheels;i++)
	{
		message.phys.wheels[i].rotation=0;
		message.phys.wheels[i].slipVelo=0;
		message.phys.wheels[i].surfaceType=0;
		message.phys.wheels[i].glow=0;
		message.phys.wheels[i].angularVelo=0;
	}

	PhysicsMessageSwap(&message.entity);

	if(SendNewPacketLog(entity,rz))
	{
		LogPacket(sizeof(tCarPhysicsMessage),&message,kMessageTypePhysics);
		entity->lastPacketSaved=gFrameCount;
		entity->lastVeloSaved=entity->velo;
		entity->lastRZSaved=rz;
		entity->lastDirZSaved=*MatrixGetZVector(entity->dir);

	}
	//broadcast packet over network
	if(gGameInfo->network)
		if(int prio=SendNewPacket(entity,rz))
		{
			if(prio==2)
				message.entity.prioritized=true;
			if(gGameInfo->playerID)
				NetworkSendPacket(kMessageTypePhysics,&message,sizeof(tCarPhysicsMessage),kMessagePriorityNormal,kMessageSendToHostOnly);
			else
				InsertToOutbox(&message,0);
			entity->lastPacketSent=gFrameCount;
			entity->lastVeloSent=entity->velo;
			entity->lastRZSent=rz;
			entity->lastDirZSent=*MatrixGetZVector(entity->dir);
		}
}

void SendSolidPhysicsMessage(tGameEntity *entity,int resends)
{
	static int lastRockUpdate=0;
	if(entity->lastFrame==gFrameCount&&gFrameCount>0)
	{
		tSolidPhysicsMessage message;
		for(int i=0;i<sizeof(tCarNetPhysics)-1;i++)
			message.filler[i]=0;
		PreparePhysicsMessage(entity,&message.entity);
		message.resends=resends;
		PhysicsMessageSwap(&message.entity);
		LogPacket(sizeof(tSolidPhysicsMessage),&message,kMessageTypePhysics);

		if(gGameInfo->network)
			if(gGameInfo->playerID)
			{
				if(gFrameCount<lastRockUpdate)
					lastRockUpdate=0;
				if(gFrameCount>lastRockUpdate+kFPS*0.25)
				{
					NetworkSendPacket(kMessageTypePhysics,&message,sizeof(tSolidPhysicsMessage),kMessagePriorityHigh,kMessageSendToHostOnly);
					lastRockUpdate=gFrameCount;
				}
				else
					entity->lastFrame++;
			}
			else
				InsertToOutbox((tCarPhysicsMessage*)&message,0);
	}
}

void AddRockUpdateToOutbox()
{	
	tGameEntity *entity=(tGameEntity*)gFirstEntity->next;
	if(gOutboxPos==kMaxOutboxSize)
		return;
	while(entity!=gFirstEntity)
	{
		if(entity->physicsType==kPhysicsTypeSolid)
		{
			tSolidEntityPhysics *ent=(tSolidEntityPhysics*)FileGetParsedDataPtr(entity->physicsData,kParserTypeSolidEntityDesc,sizeof(tSolidEntityPhysics));
			if(ent->movable&&ent->mass>=kSolidEntityNetworkMass)
				if(entity->lastActivity<gFrameCount-kFPS)
				if(entity->lastFrame<gFrameCount-kFPS)
				{
					float minDist=INFINITY;
					for(int i=0;i<gGameInfo->numPlayers;i++)
						if(sqr(gCarEntities[i]->pos-entity->pos)<minDist)
							minDist=sqr(gCarEntities[i]->pos-entity->pos);
					if(minDist>sqr(80))
					{
						entity->lastFrame=gFrameCount;
						SendSolidPhysicsMessage(entity,0);
						return;
					}
				}
		}
		entity=(tGameEntity*)entity->next;
	}
}

void CleanOutbox()
{	
	for(int i=0;i<gOutboxPos;i++)
	{
		tGameEntity *entity=(tGameEntity*)gFirstEntity->next;
						
		//see if we find the entity it belongs to
		while(entity!=gFirstEntity&&i<gOutboxPos)
		{
			//is this the entity the message refers to?
			short id=gHostOutbox[i].entity.id;
			S16Swap(id);
			if(entity->id==id)
			{
				if(entity->physicsType==kPhysicsTypeSolid)
				{
					if(((tSolidPhysicsMessage*)gHostOutbox+i)->resends==0||((tSolidPhysicsMessage*)gHostOutbox+i)->resends>3)
					{
						memmove(gHostOutbox+i,gHostOutbox+i+1,sizeof(tSolidPhysicsMessage)*(gOutboxPos-i-1));
						gOutboxPos--;
						entity=gFirstEntity;

					}
					else
						((tSolidPhysicsMessage*)gHostOutbox+i)->resends--;
				}
				else
				{
					memmove(gHostOutbox+i,gHostOutbox+i+1,sizeof(tSolidPhysicsMessage)*(gOutboxPos-i-1));
					gOutboxPos--;
					entity=gFirstEntity;
				}
			}
			entity=(tGameEntity*)entity->next;
		}
	}
}

//sends out physics information for this frame
void NetworkSendPhysics()
{
	tGameEntity *entity=(tGameEntity*)gFirstEntity->next;
	while(entity!=gFirstEntity)
	{
		if(entity->physicsMachine==kPhysicsLocal)
		{
			switch(entity->physicsType)
			{
				case kPhysicsTypeCar:
					SendCarPhysicsMessage(entity);
					break;
				
				case kPhysicsTypeSolid:
					SendSolidPhysicsMessage(entity,2);
					break;
			}
		}
		entity=(tGameEntity*)entity->next;
	}
	float t=TimeGetSeconds();
	if(gGameInfo->playerID==0&&gGameInfo->network)
	{
		int fastPath=false;
		for(int i=0;i<gOutboxPos;i++)
			if(gHostOutbox[i].entity.prioritized)
				fastPath=true;
		if(gOutboxPos>0&&(gLastOutboxSend<t-0.06||gOutboxPos==kMaxOutboxSize||fastPath))//send packet every 60ms
		{
			AddRockUpdateToOutbox();
			if(gOutboxSender!=-1)
				NetworkSendPacket(kMessageTypePhysics,gHostOutbox,sizeof(tCarPhysicsMessage)*gOutboxPos,kMessagePriorityNormal,kMessageSendToAllButSelf,gOutboxSender);
			else
				NetworkSendPacket(kMessageTypePhysics,gHostOutbox,sizeof(tCarPhysicsMessage)*gOutboxPos,kMessagePriorityNormal,kMessageSendToAllButSelf);
			gLastOutboxSend=t;
			CleanOutbox();
		}
	}
}

void KillPlayer(int i)
{
	//PrintConsoleString("Removing Player %d from game",i);
	tGameEntity *ent=gCarEntities[i];
	ent->pos=Vector(-10000,-10000,-10000);
	ent->netPos=Vector(-10000,-10000,-10000);
	ent->velo=Vector(0,0,0);
	ent->id=-1;
	tCarPhysics *phys=(tCarPhysics*)ent->physics;
	phys->position=0;
	phys->checkPoint=0;
	for(int i=0;i<kMaxLaps+1;i++)
		phys->lapTimes[i]=0;	
	gGameInfo->netID[i]=-1;
	gGameInfo->playerCarNames[i][0]='\0';
	int numPlayers=0;
	for(int i=0;i<gGameInfo->numPlayers;i++)
		if(gCarEntities[i]->id>=0)
			numPlayers++;
	if(numPlayers==1)
	{
		tCarPhysics *lastPhys=(tCarPhysics*)gCarEntities[0];
		lastPhys->lead=0;
	}
	
	if(gReplayViewedEntityID==i)
	{
		gViewedEntity=gCarEntities[0];
		gReplayViewedEntityID=0;
	}
}

//receive physics data from other players on the network
void NetworkReceivePhysics()
{
	if(gGameInfo->network)
	{
		tNetworkPacket message;
		int exit=false;
		while(NetworkReceivePacket(&message))
		{
			switch(message.what)
			{
				case kMessageTypePhysics:					
					if(gGameInfo->playerID==0)
						InsertToOutbox((tCarPhysicsMessage*)message.data,message.from);
					//we have received a physics state update
					for(int i=0;i<message.size/sizeof(tCarPhysicsMessage);i++)
					{
						//log the packet for replay
						LogPacket(sizeof(tCarPhysicsMessage),(char*)message.data+i*sizeof(tCarPhysicsMessage),kMessageTypePhysics);

						tPhysicsMessage *physMessage=(tPhysicsMessage*)((char*)message.data+i*sizeof(tCarPhysicsMessage));
						PhysicsMessageSwap(physMessage);
						//printf("rec: %d. %d,%d\n",physMessage->id-gCarEntities[0]->id,physMessage->frame,gFrameCount);
						
						tGameEntity *entity=(tGameEntity*)gFirstEntity->next;
						
						//see if we find the entity it belongs to
						while(entity!=gFirstEntity)
						{
							//is this the entity the message refers to?
							if(entity->id==physMessage->id&&entity!=gCarEntities[gGameInfo->playerID])
								//is this the most recent physics update for the entity?
								//(we want to avoid packets coming in in the wrong order messung up everything)
								if(entity->lastFrame<physMessage->frame)//&&entity->lastCollFrame<=physMessage->frame)
								{
									//entity->lastCollFrame=0;
									//decompress physics info
									entity->netPos=physMessage->pos;
									entity->netVelo.x=physMessage->velo.x/255.0;
									entity->netVelo.y=physMessage->velo.y/255.0;
									entity->netVelo.z=physMessage->velo.z/255.0;
									entity->collVelo=entity->netVelo;
									entity->accel.x=physMessage->accel.x/255.0;
									entity->accel.y=physMessage->accel.y/255.0;
									entity->accel.z=physMessage->accel.z/255.0;
									
									entity->lastFrame=physMessage->frame;
									if(entity->lastFrame>gFrameCount)
										entity->lastFrame=gFrameCount;
									ShortVectorsToMatrix(physMessage->dirZ,physMessage->dirY,entity->netDir);
									ShortVectorsToMatrix(physMessage->rVeloZ,physMessage->rVeloY,entity->rVelo);
									
									//is this a car?
									if(entity->physicsType==kPhysicsTypeCar)
									{
										//decompress car physics info
										tCarPhysics *phys=(tCarPhysics*)entity->physics;
										tCarNetPhysics *inPhys=&(((tCarPhysicsMessage*)message.data)+i)->phys;
										phys->lightFlags=inPhys->lightFlags;
										phys->rpm=inPhys->rpm/(255.0/10000.0);
										phys->throttle=inPhys->throttle/255.0;
										phys->steering=inPhys->steering/127.0;
										phys->handbrake=inPhys->handbrake/255.0;
//										phys->brake=inPhys->brake/255.0;
//										phys->gear=inPhys->gear;
										phys->lastGearSwitch=gFrameCount*kFrameTime-inPhys->shiftDelay/255.0;
										unsigned short damage=inPhys->damage;
										U16Swap(damage);
										phys->damage=damage;
										
										for(int i=0;i<kMaxWheels;i++)
										{
											phys->wheels[i].rotation=inPhys->wheels[i].rotation/(255.0/(2*PI));
											phys->wheels[i].slipVelo=inPhys->wheels[i].slipVelo;
											phys->wheels[i].surfaceType=inPhys->wheels[i].surfaceType;
											phys->wheels[i].glow=inPhys->wheels[i].glow/255.0;
											phys->wheels[i].angularVelo=inPhys->wheels[i].angularVelo;
										}
										entity->remoteCameraPos=inPhys->cameraPos;
										F32Swap(entity->remoteCameraPos.x);
										F32Swap(entity->remoteCameraPos.y);
										F32Swap(entity->remoteCameraPos.z);
										
										//dead recogning
										int frameDiff=gFrameCount-physMessage->frame;
										//entity->netPos=entity->netPos+entity->netVelo*kFrameTime*frameDiff+entity->accel*kFrameTime*kFrameTime*frameDiff*frameDiff*0.5;
										//entity->netVelo=entity->netVelo+entity->accel*kFrameTime*frameDiff;
										int collIndex=kNumLastCollisions-1;
										while(frameDiff>0)
										{
											while(phys->lastCollisions[collIndex].frameCount<gFrameCount-frameDiff)
												collIndex++;
											entity->regData=NULL;
											if(phys->lastCollisions[collIndex].frameCount==gFrameCount-frameDiff)
												ApplyImpulse(entity,phys->lastCollisions[collIndex].attackPoint,phys->lastCollisions[collIndex].veloDiff,phys->lastCollisions[collIndex].rotationFactor,true);
											entity->netPos=entity->netPos+entity->netVelo*kFrameTime;
											entity->netVelo=entity->netVelo+entity->accel*kFrameTime;
											MatrixMult(entity->netDir,entity->rVelo,entity->netDir);
											frameDiff--;
										}
									}
									else if(entity->physicsType==kPhysicsTypeSolid)
									{
										entity->pos=entity->netPos;
										entity->velo=entity->netVelo;
										MatrixCopy(entity->netDir,entity->dir);

										int frameDiff=gFrameCount-physMessage->frame;
										while(frameDiff>0)
										{
											if(entity->lastActivity+kFPS>gFrameCount-frameDiff)
											{
												entity->velo.y-=gEnvironment->gravity*kFrameTime;
												float dampfactor=0;
												float v=~entity->velo;
												if(v<7)dampfactor=(7-v)/7;
												*MatrixGetXVector(entity->rVelo)=Vector(1,0,0)+(1-dampfactor*kFrameTime)*(*MatrixGetXVector(entity->rVelo)-Vector(1,0,0));
												*MatrixGetYVector(entity->rVelo)=Vector(0,1,0)+(1-dampfactor*kFrameTime)*(*MatrixGetYVector(entity->rVelo)-Vector(0,1,0));
												*MatrixGetZVector(entity->rVelo)=Vector(0,0,1)+(1-dampfactor*kFrameTime)*(*MatrixGetZVector(entity->rVelo)-Vector(0,0,1));
												entity->velo=(1-dampfactor*kFrameTime)*entity->velo;
												
												entity->pos=entity->pos+entity->velo*kFrameTime;
												MatrixMult(entity->dir,entity->rVelo,entity->dir);
											}
											MatrixReAdjust(entity->rVelo);
											
											//Check if the entity is moving, and update lastActivity
											if(entity->velo*entity->velo>kMinActivityVelo*kMinActivityVelo)
												entity->lastActivity=gFrameCount-frameDiff;
												
											if(entity->lastActivity+kFPS>gFrameCount-frameDiff)
												if(!((gFrameCount-frameDiff)% kSolidCollisionRate))
													SolidCheckCollision(entity);
											frameDiff--;
										}										
									}
									
									/*if(physMessage->prioritized)
									{
										entity->pos=entity->netPos;
										entity->velo=entity->netVelo;
										MatrixCopy(entity->netDir,entity->dir);
									}*/
								}
							entity=(tGameEntity*)entity->next;
						}	
					}
					break;

				case kMessageTypeChat:
					InsertGameChatMessage(((tChatMessage*)message.data)->str);
					ChatBufferInsert(((tChatMessage*)message.data),gChatBuffer);
					if(((tChatMessage*)message.data)->flags&(kChatFlagSystem|kChatFlagAlert))
						PlayInterfaceSound(FileGetReference("systemchat.wav"));
					else
						PlayInterfaceSound(FileGetReference("chat.wav"));
					break;
				
				case kMessageTypeBye:
					{
						if(message.from==0)
							sprintf(gDisconnectString,"%s quit and stopped hosting.",gGameInfo->playerNames[0]);
						int netID=message.from;
						int id=-1;
						//find the players id
						for(int i=0;i<gGameInfo->numNetPlayers;i++)
							if(gGameInfo->netID[i]==netID)	
								id=i;
						
						if(id!=-1)
						{
							gGameInfo->playerVersions[id]=-2;
							/*tChatMessage m;
							m.flags=kChatFlagSystem;
							sprintf(m.str,"### %s is quitting.",gGameInfo->playerNames[id]);
							SoundReInit();
							PlayInterfaceSound(FileGetReference("systemchat.wav"));
							InsertGameChatMessage(m.str);
							ChatBufferInsert(&m,gChatBuffer);*/
						}
					}
					break;

				case kMessageTypeKicked:
					sprintf(gDisconnectString,"You have been disconnected by the host.");
				case kMessageTypeGameTerminated:
					gGameEnd=kEndGame;
					exit=true;
					break;
					
				case kMessageTypeEndGame:
					gGameEnd=kEndGameNoLeave;
					break;
					
				case kMessageTypePause:
					if(gNetPauseTime==0)
					{
						gNetPauseTime=*(int*)message.data;
						S32Swap(gNetPauseTime);
					}
					break;
				
				case kMessageTypeSynch:
					gSynchsRecevied++;
					break;
					
				case kMessageTypeResume:
					if(gPaused)
					{
						gNetPauseTime=0;
						for(int i=1;i<gGameInfo->numNetPlayers;i++)
							if(gGameInfo->netID[i]==-1)
								gSynchsRecevied++;
						NetworkSynch(gGameInfo->numNetPlayers-gSynchsRecevied);
						gSynchsRecevied=0;
						UnPauseGame();
					}
					break;
					
				case kMessageTypePlayerLeft:
					{
						int netID=message.from;
						int id=-1;
						//find the players id
						for(int i=0;i<gGameInfo->numPlayers;i++)
							if(gGameInfo->netID[i]==netID)	
								id=i;
						if(id!=-1)
						{
							tChatMessage m;
							m.flags=kChatFlagSystem;
							if(gGameInfo->playerVersions[id]!=-2)
								sprintf(m.str,"### %s is disconnected due to network problems.",gGameInfo->playerNames[id]);
							else	
								sprintf(m.str,"### %s quit the game.",gGameInfo->playerNames[id]);
							SoundReInit();
							PlayInterfaceSound(FileGetReference("systemchat.wav"));
							InsertGameChatMessage(m.str);
							ChatBufferInsert(&m,gChatBuffer);
							KillPlayer(id);
						}
					}
					break;
					
				case kMessageTypeID:
					NetworkQueuePacket(&message);
					exit=true;
					break;
				
				case kMessageTypeFinishTime:
					{
						tFinishTimeMessage *m=(tFinishTimeMessage*)message.data;
						S32Swap(m->time);
						S32Swap(m->player);
						//printf("%d,%d\n",m->time,m->player);
						tCarPhysics *phys=(tCarPhysics*)gCarEntities[m->player]->physics;
						phys->finishTime=m->time;
					}
					break;
					
				case kMessageTypePlayerJoined:
					if(gGameInfo->demolition||gGameInfo->numNetPlayers<=1)
					{
						NetworkQueuePacket(&message);
						gGameEnd=kEndGameNoLeave;
						exit=true;
					}
					else
					{
						UInt8 reason=kJoinDeniedInProgress;
						NetworkSendPacket(kMessageTypeJoinDenied,&reason,sizeof(UInt8),kMessagePriorityHigh,kMessageSendToAllButSelf);
					}
					break;			
			}
			
			
			//release the message.
			NetworkDisposePacket(&message);
			
			if(exit)
				return;
		}
	}
}

#define kMaxLookAhead 0.5
//similar to NetworkReceivePhysics but does not wait for packets from the network,
//instead it processes packets in the replay log.
void LogReplayFrame()
{
	int type,size;
	char message[1024];
	while(LogGetPacket(gReplayIndex,&type,&size,gGameInfo->numLaps==-1?kLogGhostLog:kLogReplayLog,message))
	{
		switch(type)
		{
			case kMessageTypePhysics:
				{
					tPhysicsMessage *physMessage=(tPhysicsMessage*)((char*)message);
					PhysicsMessageSwap(physMessage);
					//if(physMessage->id<gCarEntities[0]->id)
					//	physMessage->id=gCarEntities[0]->id;

					if(physMessage->frame>gFrameCount)return;
					tGameEntity *entity=(tGameEntity*)gFirstEntity->next;
					//see if we find the entity it belongs to
					while(entity!=gFirstEntity)
					{
						//is this the entity the message refers to?
						if(entity->id==physMessage->id)
						{
							//decompress physics info
							entity->pos=physMessage->pos;
							entity->velo.x=physMessage->velo.x/255.0;
							entity->velo.y=physMessage->velo.y/255.0;
							entity->velo.z=physMessage->velo.z/255.0;
							entity->accel.x=physMessage->accel.x/255.0;
							entity->accel.y=physMessage->accel.y/255.0;
							entity->accel.z=physMessage->accel.z/255.0;
//							ShortVectorsToMatrix(physMessage->dirZ,physMessage->dirY,entity->dir);
							ShortVectorsToMatrix(physMessage->dirZ,physMessage->dirY,entity->netDir);
							ShortVectorsToMatrix(physMessage->rVeloZ,physMessage->rVeloY,entity->rVelo);
							
							//is this a car?
							if(entity->physicsType==kPhysicsTypeCar)
							{
								//decompress car physics info
								tCarPhysics *phys=(tCarPhysics*)entity->physics;
								tCarNetPhysics *inPhys=&((tCarPhysicsMessage*)message)->phys;
								phys->lightFlags=inPhys->lightFlags;
								phys->rpm=inPhys->rpm/(255.0/10000.0);;
								phys->throttle=inPhys->throttle/255.0;
								phys->steering=inPhys->steering/127.0;
								phys->handbrake=inPhys->handbrake/255.0;
//									phys->brake=inPhys->brake/255.0;
//									phys->gear=inPhys->gear;
								phys->lastGearSwitch=gFrameCount*kFrameTime-inPhys->shiftDelay/255.0;
								unsigned short damage=inPhys->damage;
								U16Swap(damage);
								phys->damage=damage;
								
								for(int i=0;i<kMaxWheels;i++)
								{
									phys->wheels[i].rotation=inPhys->wheels[i].rotation/(255.0/(2*PI));
									phys->wheels[i].slipVelo=inPhys->wheels[i].slipVelo;
									phys->wheels[i].surfaceType=inPhys->wheels[i].surfaceType;
									phys->wheels[i].glow=inPhys->wheels[i].glow/255.0;
									phys->wheels[i].angularVelo=inPhys->wheels[i].angularVelo;
								}
							}
						}
						entity=(tGameEntity*)entity->next;
					}	
				}
				break;
		}
		gReplayIndex++;
	}
}

void LogReplayGhostFrame()
{
	int type,size;
	char message[1024];
	while(LogGetPacket(gReplayIndex,&type,&size,kLogGhostLog,message))
	{
		switch(type)
		{
			case kMessageTypePhysics:
				{
					tPhysicsMessage *physMessage=(tPhysicsMessage*)message;
					PhysicsMessageSwap(physMessage);
					
					if(physMessage->frame>gFrameCount-gCurrentLapStart+gBestLapStart)
						return;

					gGhostEntity->pos=physMessage->pos;
					gGhostEntity->velo.x=physMessage->velo.x/255.0;
					gGhostEntity->velo.y=physMessage->velo.y/255.0;
					gGhostEntity->velo.z=physMessage->velo.z/255.0;
					gGhostEntity->lastFrame=physMessage->frame;
					ShortVectorsToMatrix(physMessage->dirZ,physMessage->dirY,gGhostEntity->dir);
					
					tCarPhysics *phys=(tCarPhysics*)gGhostEntity->physics;
					tCarNetPhysics *inPhys=(tCarNetPhysics*)((char*)message+sizeof(tPhysicsMessage));
					phys->lightFlags=inPhys->lightFlags;
					phys->rpm=inPhys->rpm/(255.0/10000.0);;
					phys->throttle=inPhys->throttle/255.0;
					phys->steering=inPhys->steering/127.0;
					for(int i=0;i<kMaxWheels;i++)
					{
						phys->wheels[i].rotation=inPhys->wheels[i].rotation/(255.0/(2*PI));
						phys->wheels[i].slipVelo=inPhys->wheels[i].slipVelo;
						phys->wheels[i].surfaceType=inPhys->wheels[i].surfaceType;
					}
				}	
				break;
		}
		gReplayIndex++;
	}
	gGhostEntity->pos=Vector(10000,10000,10000);
	gGhostEntity->velo=Vector(0,0,0);
}

