02061d74c2
(as received from Jonas Echterhoff)
953 lines
29 KiB
C++
953 lines
29 KiB
C++
//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);
|
|
}
|
|
|