846 lines
26 KiB
C++
846 lines
26 KiB
C++
|
//gameframe.cpp
|
||
|
//Calculate a single physics frame
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#include <math.h>
|
||
|
#include "gametime.h"
|
||
|
#include "entities.h"
|
||
|
#include "carphysics.h"
|
||
|
#include "gameframe.h"
|
||
|
#include "controls.h"
|
||
|
#include "gamemem.h"
|
||
|
#include "particles.h"
|
||
|
#include "renderframe.h"
|
||
|
#include "collision.h"
|
||
|
#include "networkphysics.h"
|
||
|
#include "gamesound.h"
|
||
|
#include "gameinitexit.h"
|
||
|
#include "config.h"
|
||
|
#include "parser.h"
|
||
|
#include "roads.h"
|
||
|
#include "gamesystem.h"
|
||
|
#include "interfaceutil.h"
|
||
|
#include "text.h"
|
||
|
#include "random.h"
|
||
|
#include "log.h"
|
||
|
#include "sky.h"
|
||
|
#include "tracker.h"
|
||
|
#include "environment.h"
|
||
|
|
||
|
#define kGraphFrameTimeCount 10 //number of frames used to calculate average FPS
|
||
|
|
||
|
int gFrameCount; //number of physics frames calculated
|
||
|
int gGraphFrameCount; //number of graphical frames actually drawn
|
||
|
int gPaused=false;
|
||
|
int gNetPauseTime=0;
|
||
|
int gDisabledRestart=0;
|
||
|
float gStartPauseTime;
|
||
|
float gStartTime; //time at the start of the game (in seconds)
|
||
|
float gTimeStretch=1.0;
|
||
|
|
||
|
//Used for Time Trial
|
||
|
int gCurrentLapStart;
|
||
|
int gBestLapStart;
|
||
|
int gLastLapTime;
|
||
|
int gBestLapTime=0;
|
||
|
int gWorldRecord=0,gLocalRecord=0;
|
||
|
char gRecordName[255];
|
||
|
|
||
|
float gAccelSignDisplayIntensity,gAccelSignDisplayCorner;
|
||
|
|
||
|
//the times when the last graphics frames have been draw
|
||
|
float gLastGraphFrameTime[kGraphFrameTimeCount];
|
||
|
|
||
|
int gGameEnd; //flag to signal end of game
|
||
|
int gDisqualified;
|
||
|
int gRaceFinished; //flag to signal that at least one player has finished all laps.
|
||
|
float gFPS;
|
||
|
|
||
|
//Calculate physics for a solid entity
|
||
|
//(basically any object in the game which is not a car)
|
||
|
void SolidPhysicsEntity(tGameEntity *entity)
|
||
|
{
|
||
|
//get soldid entity definition
|
||
|
tSolidEntityPhysics *ent=(tSolidEntityPhysics*)FileGetParsedDataPtr(entity->physicsData,kParserTypeSolidEntityDesc,sizeof(tSolidEntityPhysics));
|
||
|
|
||
|
//is this entity freely movable?
|
||
|
if(ent->movable)
|
||
|
//gravitational acceleration
|
||
|
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;
|
||
|
MatrixReAdjust(entity->rVelo);
|
||
|
|
||
|
//does this entity follow a constant path?
|
||
|
if(ent->followPath)
|
||
|
if(ent->pathType==kPathTypeCircular)
|
||
|
{
|
||
|
MatrixRotY(entity->dir,ent->pathVelo*kFrameTime);
|
||
|
entity->velo=*MatrixGetZVector(entity->dir)*ent->pathVelo*ent->pathSize;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
entity->velo=Vector(
|
||
|
3*sin(gFrameCount*kFrameTime*ent->pathVelo*0.2+1)*ent->pathVelo*0.2,
|
||
|
ent->pathSize*sin(gFrameCount*kFrameTime*ent->pathVelo)*ent->pathVelo,
|
||
|
2*sin(gFrameCount*kFrameTime*ent->pathVelo*0.3-2)*ent->pathVelo*0.3
|
||
|
);
|
||
|
entity->lastActivity=gFrameCount;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
//Calculate one physics frame for all game entities
|
||
|
void PhysicsFrame()
|
||
|
{
|
||
|
//do this for every entity in the game
|
||
|
tGameEntity *entity=(tGameEntity*)gFirstEntity->next;
|
||
|
while(entity!=gFirstEntity)
|
||
|
{
|
||
|
if(entity->id>=0)
|
||
|
{
|
||
|
//is this entity handled by this machine?
|
||
|
if(entity->physicsMachine==kPhysicsLocal)
|
||
|
{
|
||
|
//this check is used to prevent entitys which are just standing still
|
||
|
//from eating up CPU cycles
|
||
|
if(entity->lastActivity+kFPS>gFrameCount)
|
||
|
{
|
||
|
|
||
|
//handle entity physics
|
||
|
switch(entity->physicsType)
|
||
|
{
|
||
|
case kPhysicsTypeCar:
|
||
|
CarPhysicsEntity(entity);
|
||
|
break;
|
||
|
case kPhysicsTypeSolid:
|
||
|
SolidPhysicsEntity(entity);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
//Matrices tend to 'drift' apart du to inaccurate
|
||
|
//floating-point operations. these routines test matrices
|
||
|
//and re-adjust them if necessary.
|
||
|
if(!MatrixVerify(entity->dir))
|
||
|
MatrixReAdjust(entity->dir);
|
||
|
if(!MatrixVerify(entity->rVelo))
|
||
|
MatrixReAdjust(entity->rVelo);
|
||
|
}
|
||
|
}
|
||
|
else if(entity->physicsType==kPhysicsTypeCar)
|
||
|
CarPhysicsEntity(entity);
|
||
|
}
|
||
|
//proceed to next entity.
|
||
|
entity=(tGameEntity*)entity->next;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//Move around all the entities.
|
||
|
void MotionFrame()
|
||
|
{
|
||
|
//do this for every entity in the game
|
||
|
tGameEntity *entity=(tGameEntity*)gFirstEntity->next;
|
||
|
while(entity!=gFirstEntity)
|
||
|
{
|
||
|
entity->oldPos=entity->pos;
|
||
|
MatrixCopy(entity->dir,entity->oldDir);
|
||
|
|
||
|
//this check is used to prevent entitys which are just standing still
|
||
|
//from eating up CPU cycles
|
||
|
if(entity->lastActivity+kFPS>gFrameCount)
|
||
|
{
|
||
|
//move entity
|
||
|
switch(entity->physicsType)
|
||
|
{
|
||
|
case kPhysicsTypeCar:
|
||
|
case kPhysicsTypeGhost:
|
||
|
CarMotionEntity(entity);
|
||
|
break;
|
||
|
case kPhysicsTypeSolid:
|
||
|
entity->pos=entity->pos+entity->velo*kFrameTime;
|
||
|
MatrixMult(entity->dir,entity->rVelo,entity->dir);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//Check if the entity is moving, and update lastActivity
|
||
|
if(entity->velo*entity->velo>kMinActivityVelo*kMinActivityVelo)
|
||
|
entity->lastActivity=gFrameCount;
|
||
|
|
||
|
//proceed to next entity.
|
||
|
entity=(tGameEntity*)entity->next;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//checks whether there is time to draw a graphics frame
|
||
|
//and calculates frame rate
|
||
|
int CheckFrameTime()
|
||
|
{
|
||
|
int optFrameCount;
|
||
|
int retval=false;
|
||
|
float curTime;
|
||
|
|
||
|
//get current time
|
||
|
curTime=(TimeGetSeconds()-gStartTime)*gTimeStretch;
|
||
|
|
||
|
//calculate number of frames we should have calculated in that time
|
||
|
optFrameCount=curTime*kFPS;
|
||
|
|
||
|
//if we have calculated more frames than required, we have
|
||
|
//time to draw a screen update.
|
||
|
if(gFrameCount>optFrameCount)
|
||
|
{
|
||
|
//calculate frame rate.
|
||
|
if(curTime-gLastGraphFrameTime[0]>0)
|
||
|
gFPS=((float)kGraphFrameTimeCount)/(curTime-gLastGraphFrameTime[0]);
|
||
|
else
|
||
|
gFPS=0;
|
||
|
MemoryMove(gLastGraphFrameTime,gLastGraphFrameTime+1,sizeof(float)*(kGraphFrameTimeCount-1));
|
||
|
gLastGraphFrameTime[kGraphFrameTimeCount-1]=curTime;
|
||
|
gGraphFrameCount++;
|
||
|
retval=true;
|
||
|
}
|
||
|
if(gFPS<10)
|
||
|
gConfig->gfxDynamics-=kFrameTime;
|
||
|
else if(gFPS<15)
|
||
|
gConfig->gfxDynamics-=kFrameTime*0.3;
|
||
|
if(gFPS>35)
|
||
|
gConfig->gfxDynamics+=kFrameTime;
|
||
|
else if(gFPS>25)
|
||
|
gConfig->gfxDynamics+=kFrameTime*0.3;
|
||
|
if(gConfig->gfxDynamics<0.0)gConfig->gfxDynamics=0;
|
||
|
if(gConfig->gfxDynamics>1.0)gConfig->gfxDynamics=1;
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
//after drawing a graphics frame, check whether there is still time
|
||
|
//left. in that case wait, so we aren't running too fast.
|
||
|
void CheckTimeSkip()
|
||
|
{
|
||
|
int optFrameCount;
|
||
|
float curTime;
|
||
|
|
||
|
curTime=(TimeGetSeconds()-gStartTime)*gTimeStretch;
|
||
|
optFrameCount=curTime*kFPS;
|
||
|
while(gFrameCount>optFrameCount+1)
|
||
|
{
|
||
|
curTime=(TimeGetSeconds()-gStartTime)*gTimeStretch;
|
||
|
optFrameCount=curTime*kFPS;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//start the frame counters
|
||
|
void StartFrameCount()
|
||
|
{
|
||
|
gStartTime=TimeGetSeconds();
|
||
|
gFrameCount=0;
|
||
|
gGraphFrameCount=0;
|
||
|
gCurrentLapStart=0;
|
||
|
gBestLapStart=0;
|
||
|
gLastLapTime=0;
|
||
|
gBestLapTime=0;
|
||
|
gWorldRecord=-1;
|
||
|
if(gGameInfo->numLaps==-1||gGameInfo->maxTime!=0)
|
||
|
TrackerFetchRecord(gGameInfo->map,gGameInfo->playerCars[0],gGameInfo->arcade,gGameInfo->reverse^gMapInfo->reverse);
|
||
|
}
|
||
|
|
||
|
void PauseGame()
|
||
|
{
|
||
|
if(!gPaused)
|
||
|
{
|
||
|
gPaused=true;
|
||
|
gStartPauseTime=TimeGetSeconds();
|
||
|
SoundPause();
|
||
|
FFBStop();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void UnPauseGame()
|
||
|
{
|
||
|
if(gPaused)
|
||
|
{
|
||
|
gPaused=false;
|
||
|
gStartTime+=TimeGetSeconds()-gStartPauseTime;
|
||
|
SoundReInit();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ResetRocks()
|
||
|
{
|
||
|
int id=0;
|
||
|
for(int i=0;i<gMapInfo->numObjs;i++)
|
||
|
{
|
||
|
if(gMapInfo->obj[i].envFlags==0||(gMapInfo->obj[i].envFlags&gEnvironment->envFlags))
|
||
|
{
|
||
|
char *extension=FileGetExtension(gMapInfo->obj[i].model);
|
||
|
if(extension)
|
||
|
if(!_stricmp(extension,kFileTypeSolidEntity))
|
||
|
{
|
||
|
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==id)
|
||
|
{
|
||
|
entity->pos=gMapInfo->obj[i].pos;
|
||
|
EulerAnglesToMatrix(gMapInfo->obj[i].dir*kDegreeRadians,entity->dir);
|
||
|
entity->velo=Vector(0,0,0);
|
||
|
MatrixIdentity(entity->rVelo);
|
||
|
entity->lastActivity=-1000;
|
||
|
}
|
||
|
entity=(tGameEntity*)entity->next;
|
||
|
}
|
||
|
}
|
||
|
id++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//tests if entity has passed the next check-point
|
||
|
//(check-points are invisible, and only used to calculate
|
||
|
//lead and trail delays).
|
||
|
void CheckPointPass(tGameEntity *entity)
|
||
|
{
|
||
|
tCarPhysics *phys=(tCarPhysics*)entity->physics;
|
||
|
if(phys->lap==-1)
|
||
|
return;
|
||
|
if(gGameInfo->numLaps!=-1)
|
||
|
{
|
||
|
if(!gCheckPoints[phys->checkPoint].passTimes[phys->lap].setBy)
|
||
|
{
|
||
|
gCheckPoints[phys->checkPoint].passTimes[phys->lap].time=gFrameCount*kFrameTime;
|
||
|
gCheckPoints[phys->checkPoint].passTimes[phys->lap].setBy=entity;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
phys->lead=gCheckPoints[phys->checkPoint].passTimes[phys->lap].time-gFrameCount*kFrameTime;
|
||
|
if(!gCheckPoints[phys->checkPoint].passTimes[phys->lap].hit)
|
||
|
{
|
||
|
tCarPhysics *phys2=(tCarPhysics*)gCheckPoints[phys->checkPoint].passTimes[phys->lap].setBy->physics;
|
||
|
phys2->lead=gFrameCount*kFrameTime-gCheckPoints[phys->checkPoint].passTimes[phys->lap].time;
|
||
|
gCheckPoints[phys->checkPoint].passTimes[phys->lap].hit=true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
gCheckPoints[phys->checkPoint].passTimes[0].time=(gFrameCount-gCurrentLapStart)*kFrameTime;
|
||
|
if(phys->lapCount>1)
|
||
|
phys->lead=gCheckPoints[phys->checkPoint].passTimes[1].time-gCheckPoints[phys->checkPoint].passTimes[0].time;
|
||
|
}
|
||
|
|
||
|
if(phys->checkPoint==0||(!gMapInfo->loop&&phys->checkPoint==kNumCheckPoints-1))
|
||
|
{
|
||
|
if(!gReplay&&entity==gViewedEntity)
|
||
|
if(phys->lapCount&&(phys->lapCount<gGameInfo->numLaps||gGameInfo->numLaps==-1))
|
||
|
{
|
||
|
float lastLapTime;
|
||
|
if(gGameInfo->numLaps!=-1)
|
||
|
lastLapTime=gFrameCount-phys->lapTimes[phys->lapCount-1];
|
||
|
else
|
||
|
lastLapTime=gFrameCount-gCurrentLapStart;
|
||
|
lastLapTime*=kFrameTime;
|
||
|
TextPrintfToBufferFormatedFading(Vector(0,0.4),0.04,kTextAlignMiddle,0.6,1.2,"Time Taken: %d:%02d'%02d",((int)lastLapTime)/60,((int)lastLapTime)%60,((int)(lastLapTime*100))%100);
|
||
|
}
|
||
|
|
||
|
if(gGameInfo->numLaps!=-1)
|
||
|
phys->lapTimes[phys->lapCount]=gFrameCount;
|
||
|
else if(!gReplay){
|
||
|
if(phys->lapCount>=1)
|
||
|
{
|
||
|
gLastLapTime=gFrameCount-gCurrentLapStart;
|
||
|
if(!gDisqualified)
|
||
|
{
|
||
|
TrackerRegisterLapTime(gGameInfo->map,gGameInfo->playerCars[0],gLastLapTime,gGameInfo->arcade,gGameInfo->reverse^gMapInfo->reverse);
|
||
|
if((gLastLapTime<gBestLapTime||gBestLapTime==0))
|
||
|
{
|
||
|
gBestLapTime=gLastLapTime;
|
||
|
gBestLapStart=gCurrentLapStart;
|
||
|
if(gBestLapTime<gLocalRecord||gLocalRecord==0)
|
||
|
{
|
||
|
gLocalRecord=gBestLapTime;
|
||
|
int found=false;
|
||
|
for(int i=0;i<gConfig->numPersonalRecords;i++)
|
||
|
if(gConfig->records[i].map==gGameInfo->map&&gConfig->records[i].car==gGameInfo->playerCars[0]
|
||
|
&&gConfig->records[i].mode==gGameInfo->arcade&&gConfig->records[i].direction==gGameInfo->reverse)
|
||
|
{
|
||
|
found=true;
|
||
|
if(gConfig->records[i].time>gLocalRecord)
|
||
|
gConfig->records[i].time=gLocalRecord;
|
||
|
}
|
||
|
if(!found&&gConfig->numPersonalRecords<kMaxPersonalRecords)
|
||
|
{
|
||
|
gConfig->records[gConfig->numPersonalRecords].time=gLocalRecord;
|
||
|
gConfig->records[gConfig->numPersonalRecords].car=gGameInfo->playerCars[0];
|
||
|
gConfig->records[gConfig->numPersonalRecords].map=gGameInfo->map;
|
||
|
gConfig->records[gConfig->numPersonalRecords].mode=gGameInfo->arcade;
|
||
|
gConfig->records[gConfig->numPersonalRecords].direction=gGameInfo->reverse;
|
||
|
gConfig->numPersonalRecords++;
|
||
|
}
|
||
|
}
|
||
|
LogToGhostLog();
|
||
|
for(int i=0;i<kNumCheckPoints;i++)
|
||
|
gCheckPoints[i].passTimes[1].time=gCheckPoints[i].passTimes[0].time;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
LogReset();
|
||
|
gReplayIndex=0;
|
||
|
gCurrentLapStart=gFrameCount;
|
||
|
}
|
||
|
if(!gReplay&&gGameInfo->maxTime!=0&&!gDisqualified)
|
||
|
if(phys->lapCount>=1)
|
||
|
TrackerRegisterLapTime(gGameInfo->map,gGameInfo->playerCars[0],phys->lapTimes[phys->lapCount]-kStartGameDelaySeconds*kFPS,gGameInfo->arcade,gGameInfo->reverse^gMapInfo->reverse);
|
||
|
|
||
|
phys->lapCount++;
|
||
|
|
||
|
if(!gReplay&&entity==gViewedEntity&&gMapInfo->loop)
|
||
|
if(phys->lapCount<gGameInfo->numLaps||gGameInfo->numLaps==-1)
|
||
|
TextPrintfToBufferFormatedFading(Vector(kFadingMessageXPos,kFadingMessageYPos),kFadingMessageSize,kTextAlignMiddle,0.9,1.8,"Lap %d",phys->lapCount);
|
||
|
else if(phys->lapCount==gGameInfo->numLaps)
|
||
|
TextPrintfToBufferFormatedFading(Vector(kFadingMessageXPos,kFadingMessageYPos),kFadingMessageSize,kTextAlignMiddle,0.9,1.8,"Final Lap");
|
||
|
|
||
|
if(gGameInfo->numLaps==-1)
|
||
|
ResetRocks();
|
||
|
|
||
|
if(phys->lapCount>gGameInfo->numLaps&&gGameInfo->numLaps!=-1)
|
||
|
{
|
||
|
if(!phys->finishTime)
|
||
|
phys->finishTime=phys->lapTimes[gGameInfo->numLaps];
|
||
|
|
||
|
if(gGameInfo->network)
|
||
|
if(gGameInfo->playerID==0)
|
||
|
for(int i=0;i<gGameInfo->numPlayers;i++)
|
||
|
if(gCarEntities[i]==entity)
|
||
|
{
|
||
|
tFinishTimeMessage m;
|
||
|
m.time=phys->finishTime;
|
||
|
m.player=i;
|
||
|
S32Swap(m.time);
|
||
|
S32Swap(m.player);
|
||
|
NetworkSendPacket(kMessageTypeFinishTime,&m,sizeof(tFinishTimeMessage),kMessagePriorityHigh,kMessageSendToAllButSelf);
|
||
|
}
|
||
|
|
||
|
if(!gRaceFinished)
|
||
|
{
|
||
|
gRaceFinished=true;
|
||
|
if(entity!=gViewedEntity&&!gReplay)
|
||
|
if(gGameInfo->network)
|
||
|
{
|
||
|
for(int i=0;i<gGameInfo->numPlayers;i++)
|
||
|
if(gCarEntities[i]==entity)
|
||
|
TextPrintfToBufferFormatedFading(Vector(0,1),0.06,kTextAlignMiddle,2,3,"%s won the race!",gGameInfo->playerNames[i]);
|
||
|
}
|
||
|
else
|
||
|
TextPrintfToBufferFormatedFading(Vector(0,1),0.06,kTextAlignMiddle,2,3,"%s won the race!",phys->car.carName);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
phys->checkPoint=(phys->checkPoint+1)%kNumCheckPoints;
|
||
|
}
|
||
|
|
||
|
void LapCheck()
|
||
|
{
|
||
|
if(gGameInfo->demolition)
|
||
|
{
|
||
|
if(gFrameCount>=gDisabledRestart+kFPS*10&&gDisabledRestart)
|
||
|
{
|
||
|
tCarPhysics *phys=(tCarPhysics*)gCarEntities[gGameInfo->playerID]->physics;
|
||
|
phys->damage=0;
|
||
|
RoadCenterCar(gCarEntities[gGameInfo->playerID]);
|
||
|
gDisabledRestart=0;
|
||
|
}
|
||
|
int numDisabled=0;
|
||
|
for(int i=0;i<gGameInfo->numPlayers;i++)
|
||
|
{
|
||
|
if(gCarEntities[i]->id>=0)
|
||
|
{
|
||
|
tCarPhysics *phys=(tCarPhysics*)gCarEntities[i]->physics;
|
||
|
if(phys->damage>=kFatalDamage)
|
||
|
numDisabled++;
|
||
|
}
|
||
|
}
|
||
|
if(numDisabled>0&&numDisabled>=gGameInfo->numPlayers-1)
|
||
|
if(!gDisabledRestart)
|
||
|
gDisabledRestart=gFrameCount;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(gFrameCount*kFrameTime>=3)
|
||
|
for(int i=0;i<gGameInfo->numPlayers;i++)
|
||
|
{
|
||
|
tCarPhysics *phys=(tCarPhysics*)gCarEntities[i]->physics;
|
||
|
if(!(gMapInfo->needsToStop&&(sqr(gCarEntities[i]->velo)>0.5)&&(phys->checkPoint==kNumCheckPoints-1)))
|
||
|
{
|
||
|
if(phys->lap<=gGameInfo->numLaps||(phys->checkPoint==0&&phys->lap==gGameInfo->numLaps+1)||gGameInfo->numLaps==-1)
|
||
|
{
|
||
|
//this doesn't all make sense but works for now.
|
||
|
if(gGameInfo->reverse){
|
||
|
if(gCheckPoints[(phys->checkPoint+1)%kNumCheckPoints].index>gCheckPoints[phys->checkPoint].index){
|
||
|
if(phys->position<gCheckPoints[phys->checkPoint].index||(phys->position>gCheckPoints[(phys->checkPoint+1)%kNumCheckPoints].index&&gMapInfo->loop))
|
||
|
CheckPointPass(gCarEntities[i]);
|
||
|
}else if(phys->position<gCheckPoints[phys->checkPoint].index&&phys->position>gCheckPoints[(phys->checkPoint+1)%kNumCheckPoints].index)
|
||
|
CheckPointPass(gCarEntities[i]);
|
||
|
}
|
||
|
else{
|
||
|
if(gCheckPoints[(phys->checkPoint+10)%kNumCheckPoints].index<gCheckPoints[phys->checkPoint].index){
|
||
|
if(phys->position>gCheckPoints[phys->checkPoint].index)
|
||
|
CheckPointPass(gCarEntities[i]);
|
||
|
}else if(phys->position>gCheckPoints[phys->checkPoint].index&&phys->position<gCheckPoints[(phys->checkPoint+10)%kNumCheckPoints].index)
|
||
|
CheckPointPass(gCarEntities[i]);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for(int j=0;j<gGameInfo->numPlayers;j++)
|
||
|
{
|
||
|
tCarPhysics *jPhys=(tCarPhysics*)gCarEntities[j]->physics;
|
||
|
if(((gGameInfo->reverse&&phys->position<jPhys->position)||(!gGameInfo->reverse&&phys->position>jPhys->position))&&phys->lap>jPhys->lap+phys->lappedPlayers[j])
|
||
|
{
|
||
|
phys->lappedPlayers[j]++;
|
||
|
if(gCarEntities[i]==gViewedEntity&&!gReplay&&gGameInfo->netID[j]!=-1&&gCarEntities[j]->id!=-1)
|
||
|
TextPrintfToBufferFormatedFading(Vector(kFadingMessageXPos,kFadingMessageYPos),kFadingMessageSize,kTextAlignMiddle,2.0,3.0,"You lapped %s!!",gGameInfo->network?gGameInfo->playerNames[j]:jPhys->car.carName);
|
||
|
else if(gCarEntities[j]==gViewedEntity&&!gReplay)
|
||
|
TextPrintfToBufferFormatedFading(Vector(kFadingMessageXPos,kFadingMessageYPos),kFadingMessageSize,kTextAlignMiddle,2.0,3.0,"You were lapped by %s!!",gGameInfo->network?gGameInfo->playerNames[i]:phys->car.carName);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
if(gGameInfo->numLaps==-1||gGameInfo->maxTime!=0)
|
||
|
TrackerWaitForLapTimeRegistry();
|
||
|
|
||
|
gAccelSignDisplayIntensity-=kFrameTime*0.5;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
#define kMinSteerResistance 0.3
|
||
|
#define kNormalSteerAlignment 0.65
|
||
|
|
||
|
#define kSteerResistanceFactor 0.4
|
||
|
#define kSteerAlignmentFactor 1.5
|
||
|
|
||
|
//creates force-feedback effects when the the players car is sliding
|
||
|
void FFBResponse()
|
||
|
{
|
||
|
tCarPhysics *phys=(tCarPhysics*)gViewedEntity->physics;
|
||
|
tCarDefinition *car=&(phys->car);
|
||
|
|
||
|
float slideVelo=0;
|
||
|
for(int i=0;i<car->numWheels;i++)
|
||
|
slideVelo+=gSurfaceTypes->types[phys->wheels[i].surfaceType].soundEnable?phys->wheels[i].slipVelo:0;
|
||
|
slideVelo/=car->numWheels;
|
||
|
slideVelo*=0.02;
|
||
|
if(slideVelo>0.5)slideVelo=0.5;
|
||
|
FFBiShockDirect(slideVelo,slideVelo);
|
||
|
|
||
|
float velo=~gViewedEntity->velo;
|
||
|
float bumpHeight=(gSurfaceTypes->types[phys->wheels[0].surfaceType].bumpHeight+gSurfaceTypes->types[phys->wheels[1].surfaceType].bumpHeight)*0.5;
|
||
|
FFBSetGoundRumble(velo,bumpHeight*10);
|
||
|
|
||
|
float wheelsSlipFactor=1-(fabs(phys->wheels[0].slipAngle)+fabs(phys->wheels[1].slipAngle))*5-(fabs(phys->wheels[0].slip)+fabs(phys->wheels[1].slip))*0.5;
|
||
|
if(wheelsSlipFactor<0)wheelsSlipFactor=0;
|
||
|
|
||
|
int wheelsOnGround=0;
|
||
|
for(int i=0;i<2;i++)
|
||
|
if(phys->wheels[i].suspension<car->wheels[i].maxSuspension)
|
||
|
wheelsOnGround++;
|
||
|
|
||
|
wheelsSlipFactor*=wheelsOnGround/2.0;
|
||
|
|
||
|
float steerResistance=(1-kMinSteerResistance)*(1/(velo*0.2+1))+kMinSteerResistance;
|
||
|
float steerAlignment=velo<10?velo/10*kNormalSteerAlignment:kNormalSteerAlignment+((velo-10)/70)*(1-kNormalSteerAlignment);
|
||
|
|
||
|
FFBSetSteerResistance(kSteerResistanceFactor*steerResistance*wheelsSlipFactor,kSteerAlignmentFactor*steerAlignment*wheelsSlipFactor);
|
||
|
}
|
||
|
|
||
|
|
||
|
tVector3 gCameraViewPos;
|
||
|
|
||
|
void GetNewTripod(tGameEntity *cameraViewEntity)
|
||
|
{
|
||
|
float side=RandomProb(0.5)?-1:1;
|
||
|
float speed;
|
||
|
gCameraEntity->pos=RoadGetNextWaypoint(cameraViewEntity->pos,&cameraViewEntity->lastRoadIndex,&side,&speed,((gCameraEntity->lastRoadIndex<cameraViewEntity->lastRoadIndex)^gGameInfo->reverse)?100:-100)+Vector(0,RandomFl(4,13),0);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
void CalcCamera()
|
||
|
{/*
|
||
|
gCameraEntity->pos=gViewedEntity->pos+Vector(0,2000,0);
|
||
|
MatrixIdentity(gCameraEntity->dir);
|
||
|
MatrixRotX(gCameraEntity->dir,PI/2);
|
||
|
return;
|
||
|
*/
|
||
|
|
||
|
tGameEntity *cameraViewEntity=gCarEntities[gReplayViewedEntityID];
|
||
|
gCameraEntity->velo=cameraViewEntity->velo;
|
||
|
tVector3 veloFlat,posFlat;
|
||
|
tVector3 cameraPos=gCameraEntity->pos;
|
||
|
switch(gCameraMode)
|
||
|
{
|
||
|
case kCameraFree:
|
||
|
if(GetInterfaceKey(kInterfaceKeyLeft))
|
||
|
MatrixRotY(gCameraEntity->dir,kFrameTime*3);
|
||
|
if(GetInterfaceKey(kInterfaceKeyRight))
|
||
|
MatrixRotY(gCameraEntity->dir,-kFrameTime*3);
|
||
|
if(GetInterfaceKey(kInterfaceKeyUp))
|
||
|
{
|
||
|
tMatrix3 m;
|
||
|
RotationVectorToMatrix(-kFrameTime*2**MatrixGetXVector(gCameraEntity->dir),m);
|
||
|
MatrixMult(gCameraEntity->dir,m,gCameraEntity->dir);
|
||
|
}
|
||
|
if(GetInterfaceKey(kInterfaceKeyDown))
|
||
|
{
|
||
|
tMatrix3 m;
|
||
|
RotationVectorToMatrix(kFrameTime*2**MatrixGetXVector(gCameraEntity->dir),m);
|
||
|
MatrixMult(gCameraEntity->dir,m,gCameraEntity->dir);
|
||
|
}
|
||
|
if(GetInterfaceKey(kInterfaceKeySpace)&&!gInputChatMode)
|
||
|
cameraPos=cameraPos+*MatrixGetZVector(gCameraEntity->dir)*kFrameTime*25;
|
||
|
gCameraEntity->velo=Vector(0,0,0);
|
||
|
break;
|
||
|
case kCameraChase:
|
||
|
veloFlat=Vector(cameraViewEntity->velo.x,0,cameraViewEntity->velo.z);
|
||
|
posFlat=Vector(cameraViewEntity->pos.x,0,cameraViewEntity->pos.z);
|
||
|
cameraPos=posFlat-(sqr(veloFlat)<sqr(1.5)?*MatrixGetZVector(cameraViewEntity->dir):!veloFlat)*(gCameraReverse?-9:9);
|
||
|
cameraPos.y=cameraViewEntity->pos.y+2.7;
|
||
|
break;
|
||
|
case kCameraChaseClose:
|
||
|
veloFlat=Vector(cameraViewEntity->velo.x,0,cameraViewEntity->velo.z);
|
||
|
posFlat=Vector(cameraViewEntity->pos.x,0,cameraViewEntity->pos.z);
|
||
|
cameraPos=posFlat-(sqr(veloFlat)<sqr(1.5)?*MatrixGetZVector(cameraViewEntity->dir):!veloFlat)*(gCameraReverse?-6:6);
|
||
|
cameraPos.y=cameraViewEntity->pos.y+1.85;
|
||
|
break;
|
||
|
case kCameraLeftSide:
|
||
|
cameraPos=cameraViewEntity->pos-*MatrixGetXVector(cameraViewEntity->dir)*(gCameraReverse?-10:10);
|
||
|
cameraPos.y=cameraViewEntity->pos.y+2;
|
||
|
break;
|
||
|
case kCameraTripod:
|
||
|
if(sqr(gCameraEntity->pos-cameraViewEntity->pos)>sqr(120))
|
||
|
GetNewTripod(cameraViewEntity);
|
||
|
cameraPos=gCameraEntity->pos;
|
||
|
gCameraEntity->velo=Vector(0,0,0);
|
||
|
break;
|
||
|
case kCameraTop:
|
||
|
{
|
||
|
float heigth=7+~cameraViewEntity->velo;
|
||
|
if(heigth>40)
|
||
|
heigth=40;
|
||
|
cameraPos=cameraViewEntity->pos+Vector(0,heigth,1);
|
||
|
}
|
||
|
break;
|
||
|
case kCameraCockpit:
|
||
|
case kCameraCockpitCarHidden:
|
||
|
{
|
||
|
tCarPhysics *phys=(tCarPhysics*)cameraViewEntity->physics;
|
||
|
tCarDefinition *car=&(phys->car);
|
||
|
cameraPos=cameraViewEntity->pos
|
||
|
+((gCameraMode==kCameraCockpit)?(*MatrixGetXVector(cameraViewEntity->dir)*car->driverPos.x):Vector(0,0,0))
|
||
|
+*MatrixGetYVector(cameraViewEntity->dir)*(car->driverPos.y+0.5)
|
||
|
+*MatrixGetZVector(cameraViewEntity->dir)*car->driverPos.z;
|
||
|
}
|
||
|
break;
|
||
|
case kCameraLeftWheel:
|
||
|
cameraPos=cameraViewEntity->pos-*MatrixGetXVector(cameraViewEntity->dir)*(gCameraReverse?-1.2:1.2)+*MatrixGetYVector(cameraViewEntity->dir)*0.4+*MatrixGetZVector(cameraViewEntity->dir)*0.5;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
|
||
|
if(gFrameCount*kFrameTime<1.5&&!gReplay)
|
||
|
{
|
||
|
float f=(gFrameCount*kFrameTime-1.5)/3*2*PI;
|
||
|
cameraPos=cameraViewEntity->pos-
|
||
|
*MatrixGetXVector(cameraViewEntity->dir)*sin(f)*15+
|
||
|
*MatrixGetYVector(cameraViewEntity->dir)*(2.0-20.0*f)-
|
||
|
*MatrixGetZVector(cameraViewEntity->dir)*cos(f)*15;
|
||
|
}
|
||
|
|
||
|
tVector3 cameraViewPos=cameraPos-cameraViewEntity->pos;
|
||
|
if(sqr(gCameraViewPos-cameraViewPos)<sqr(11)&&gCameraMode!=kCameraTripod&&gCameraMode!=kCameraFree&&cameraViewEntity==cameraViewEntity)
|
||
|
//camera interpolation
|
||
|
cameraViewPos=gCameraViewPos+(cameraViewPos-gCameraViewPos)*kFrameTime*10;
|
||
|
|
||
|
gCameraViewPos=cameraViewPos;
|
||
|
tVector3 newPos=cameraViewEntity->pos+cameraViewPos;
|
||
|
// if(sqr(gCameraEntity->pos-newPos)>25)
|
||
|
// gCameraEntity->lastRoadIndex=0;
|
||
|
gCameraEntity->pos=newPos;
|
||
|
cameraViewEntity=cameraViewEntity;
|
||
|
|
||
|
if(gCameraMode==kCameraCockpit||gCameraMode==kCameraLeftWheel||gCameraMode==kCameraCockpitCarHidden&&!(gFrameCount*kFrameTime<1.5&&!gReplay))
|
||
|
{
|
||
|
MemoryMove(gCameraEntity->dir,cameraViewEntity->dir,sizeof(tMatrix4));
|
||
|
if((gCameraMode==kCameraCockpit||gCameraMode==kCameraCockpitCarHidden)&&gCameraReverse)
|
||
|
{
|
||
|
*MatrixGetXVector(gCameraEntity->dir)=-*MatrixGetXVector(gCameraEntity->dir);
|
||
|
*MatrixGetZVector(gCameraEntity->dir)=-*MatrixGetZVector(gCameraEntity->dir);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
else if(gCameraMode!=kCameraFree)
|
||
|
{
|
||
|
*MatrixGetZVector(gCameraEntity->dir)=!(cameraViewEntity->pos-gCameraEntity->pos+Vector(0,1.2,0));
|
||
|
*MatrixGetYVector(gCameraEntity->dir)=Vector(0,1,0);
|
||
|
MatrixReAdjust(gCameraEntity->dir);
|
||
|
}
|
||
|
|
||
|
if(gCameraMode!=kCameraFree)
|
||
|
{
|
||
|
tVector3 normal;
|
||
|
float dist=GetGroundOffset(gCameraEntity->pos,&gCameraEntity->lastRoadIndex,&normal,0);
|
||
|
if(dist<0)
|
||
|
if(!isinf(dist))
|
||
|
gCameraEntity->pos=gCameraEntity->pos-normal*dist;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void MoveGhostCar()
|
||
|
{
|
||
|
if(gGameInfo->numLaps!=-1)
|
||
|
return;
|
||
|
tCarPhysics *phys=(tCarPhysics*)gCarEntities[0]->physics;
|
||
|
if(phys->lapCount<2)
|
||
|
return;
|
||
|
|
||
|
LogReplayGhostFrame();
|
||
|
}
|
||
|
|
||
|
//use this define to disable all game physics.
|
||
|
//useful to test raw graphics performance.
|
||
|
//#define __NOPHYSICS
|
||
|
|
||
|
|
||
|
//Calculate one game physics frame
|
||
|
void GameFrame()
|
||
|
{
|
||
|
/*static int p=false;
|
||
|
if(Button()&&!p){gCarEntities[0]->pos=gCameraEntity->pos;p=true;}else if(!Button())p=false;*/
|
||
|
if(!gPaused)
|
||
|
{
|
||
|
#ifndef __NOPHYSICS
|
||
|
//Move all entities
|
||
|
MotionFrame();
|
||
|
|
||
|
//check for collision between entities and ground or each other
|
||
|
CollisionFrame();
|
||
|
|
||
|
//check if cars have passed any checkpoint or lap start/finish line.
|
||
|
LapCheck();
|
||
|
|
||
|
//send local physics data to network
|
||
|
NetworkSendPhysics();
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
//get AI or user input for local cars
|
||
|
ControlFrame();
|
||
|
|
||
|
if(!gPaused)
|
||
|
{
|
||
|
#ifndef __NOPHYSICS
|
||
|
|
||
|
//Calculate car sound effects
|
||
|
SoundFrame();
|
||
|
|
||
|
//calculate all local entities physics
|
||
|
PhysicsFrame();
|
||
|
|
||
|
//process particles
|
||
|
ParticlesProcess();
|
||
|
}
|
||
|
//receive physics for remote entities
|
||
|
NetworkReceivePhysics();
|
||
|
|
||
|
if(!gPaused)
|
||
|
{
|
||
|
MoveGhostCar();
|
||
|
|
||
|
//create force-feedback effects when the the players car is sliding
|
||
|
FFBResponse();
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
CalcCamera();
|
||
|
|
||
|
//check if we have time to draw a graphics frame
|
||
|
if(CheckFrameTime()||gPaused)
|
||
|
{
|
||
|
//draw frame
|
||
|
RenderFrame(true);
|
||
|
|
||
|
//if we are too fast, waste some time
|
||
|
CheckTimeSkip();
|
||
|
|
||
|
if(gLightning<gFrameCount-10)
|
||
|
gLightning=0;
|
||
|
}
|
||
|
TextClearBuffer();
|
||
|
|
||
|
if(!gPaused)
|
||
|
gFrameCount++;
|
||
|
|
||
|
if(RandomProb(gEnvironment->flashIntensity*kFrameTime))
|
||
|
gLightning=gFrameCount;
|
||
|
|
||
|
if(gFrameCount>gNetPauseTime&&gNetPauseTime!=0)
|
||
|
PauseGame();
|
||
|
|
||
|
if(gFrameCount%30)
|
||
|
SystemPoll(true);
|
||
|
|
||
|
}
|
||
|
|
||
|
int GameReplayFrame()
|
||
|
{
|
||
|
if(!gPaused||!gBackgroundReplay)
|
||
|
{
|
||
|
CollisionFrame();
|
||
|
LapCheck();
|
||
|
SoundFrame();
|
||
|
ParticlesProcess();
|
||
|
MotionFrame();
|
||
|
LogReplayFrame();
|
||
|
if(!(gBackgroundReplay&&gInterfaceType))
|
||
|
ControlFrame();
|
||
|
PhysicsFrame();
|
||
|
}
|
||
|
CalcCamera();
|
||
|
|
||
|
if(gGameInfo->numLaps==-1&&!gBackgroundReplay)
|
||
|
TrackerWaitForLapTimeRegistry();
|
||
|
|
||
|
int hasRendered=false;
|
||
|
//check if we have time to draw a graphics frame
|
||
|
if(CheckFrameTime()||gPaused)
|
||
|
{
|
||
|
//draw frame
|
||
|
RenderFrame(true);
|
||
|
|
||
|
//if we are too fast, waste some time
|
||
|
CheckTimeSkip();
|
||
|
hasRendered=true;
|
||
|
}
|
||
|
|
||
|
if(gFrameCount%30&&!gBackgroundReplay)
|
||
|
SystemPoll(true);
|
||
|
|
||
|
if(!gPaused||!gBackgroundReplay)
|
||
|
gFrameCount++;
|
||
|
return hasRendered;
|
||
|
}
|