02061d74c2
(as received from Jonas Echterhoff)
293 lines
9.7 KiB
C++
293 lines
9.7 KiB
C++
//ai.cpp
|
|
//the game's AI system for computer-controlled opponents
|
|
#include <math.h>
|
|
#include "entities.h"
|
|
#include "controls.h"
|
|
#include "carphysics.h"
|
|
#include "gameframe.h"
|
|
#include "roads.h"
|
|
|
|
#define kAIMaxThottleSlip 0.05
|
|
#define kAIMinSpinAngularVelo (6*PI)
|
|
#define kAIThrottleTime 0.2
|
|
#define kAIThrottleReleaseTime 0.4
|
|
#define kAIMaxBrakeSlip 0.05
|
|
#define kAIBrakeTime 0.4
|
|
#define kAIBrakeReleaseTime 0.2
|
|
#define kAIWaypointAheadDistance (gGameInfo->arcade==kGameModeTurbo?25:15)
|
|
#define kAIMaxSteering 2
|
|
|
|
float AISteering(tCarDefinition *car,tGameEntity *entity,tVector3 wayPoint,float *overSteer)
|
|
{
|
|
// Will adjust the cars steering in order to make the car head to wayPoint.
|
|
|
|
tVector3 carDir=!Vector(MatrixGetZVector(entity->dir)->x,0,MatrixGetZVector(entity->dir)->z);
|
|
tVector3 wayDir=!Vector(entity->pos.x-wayPoint.x,0,entity->pos.z-wayPoint.z);
|
|
tVector3 veloDir=(VectorZero(entity->velo)?carDir:!entity->velo);
|
|
*overSteer=(carDir%veloDir).y;
|
|
float angleSin=(carDir%wayDir).y;
|
|
if(fabs(angleSin)>1.0)
|
|
angleSin=sign(angleSin);
|
|
float angle=-asin(angleSin);
|
|
float steering=angle/car->wheels[0].maxAngle;
|
|
if(steering>kAIMaxSteering)steering=kAIMaxSteering;
|
|
if(steering<-kAIMaxSteering)steering=-kAIMaxSteering;
|
|
return steering;
|
|
}
|
|
|
|
#define kAIMinCarDistance 4
|
|
#define kAICollisionPredictPrecission 20
|
|
#define kAIMaxCollisionDelay 5.0
|
|
#define kAICarLength 2
|
|
#define kAIVeloAdd 2.0
|
|
|
|
float AIGetCollisionDelay(tGameEntity *entity,tGameEntity **obstacle)
|
|
{
|
|
tVector2 basePos1=Vector(entity->pos.x,entity->pos.z);
|
|
tVector2 velo1=Vector(entity->velo.x,entity->velo.z);
|
|
tVector2 dir1=Vector(MatrixGetZVector(entity->dir)->x,MatrixGetZVector(entity->dir)->z);
|
|
float fVelo1=~velo1;
|
|
if(fVelo1>0.1)
|
|
velo1=velo1*((1/fVelo1)*(fVelo1+kAIVeloAdd));
|
|
else
|
|
velo1=dir1*kAIVeloAdd;
|
|
int minDelay=kAICollisionPredictPrecission;
|
|
for(int i=0;i<gGameInfo->numPlayers;i++)
|
|
if(gCarEntities[i]!=entity)
|
|
{
|
|
tVector2 pos2=Vector(gCarEntities[i]->pos.x,gCarEntities[i]->pos.z);
|
|
tVector2 diff=pos2-basePos1;
|
|
if(sqr(diff)<sqr(120)&&diff*dir1>=0)
|
|
//is the other car close in front of us?
|
|
{
|
|
tVector2 pos1=basePos1;
|
|
tVector2 velo2=Vector(gCarEntities[i]->velo.x,gCarEntities[i]->velo.z);
|
|
tVector2 dir2=Vector(MatrixGetZVector(gCarEntities[i]->dir)->x,MatrixGetZVector(gCarEntities[i]->dir)->z);
|
|
for(int j=0;j<kAICollisionPredictPrecission&&j<minDelay;j++)
|
|
{
|
|
pos1=pos1+velo1*(kAIMaxCollisionDelay/kAICollisionPredictPrecission);
|
|
pos2=pos2+velo2*(kAIMaxCollisionDelay/kAICollisionPredictPrecission);
|
|
if(sqr(pos1+dir1*kAICarLength-pos2+dir2*kAICarLength)<sqr(kAIMinCarDistance))
|
|
{minDelay=j;*obstacle=gCarEntities[i];}
|
|
else if(sqr(pos1-dir1*kAICarLength-pos2+dir2*kAICarLength)<sqr(kAIMinCarDistance))
|
|
{minDelay=j;*obstacle=gCarEntities[i];}
|
|
else if(sqr(pos1+dir1*kAICarLength-pos2-dir2*kAICarLength)<sqr(kAIMinCarDistance))
|
|
{minDelay=j;*obstacle=gCarEntities[i];}
|
|
else if(sqr(pos1-dir1*kAICarLength-pos2-dir2*kAICarLength)<sqr(kAIMinCarDistance))
|
|
{minDelay=j;*obstacle=gCarEntities[i];}
|
|
}
|
|
}
|
|
}
|
|
return (minDelay+1)*(kAIMaxCollisionDelay/kAICollisionPredictPrecission);
|
|
}
|
|
|
|
//Calculate the Maximal Throttle an AI player may get, which may be more than
|
|
//100% to help the AI a little.
|
|
float AIMaxThrottle(tCarPhysics *phys)
|
|
{
|
|
if(gFrameCount*kFrameTime<kStartGameDelaySeconds)
|
|
return 1;
|
|
//"power cycle" for more random AI behavior
|
|
float base=1.0+0.25*sin((gFrameCount*kFrameTime)*0.1+phys->aiPowerCycle);
|
|
|
|
//help for players in the back of the field.
|
|
float aid=(-phys->lead-8)/22;
|
|
if(aid<0)aid=0;
|
|
if(aid>1)aid=1;
|
|
|
|
return base+aid*0.6;
|
|
}
|
|
|
|
void AIBrakeThrottle(tCarDefinition *car,tCarPhysics *phys,float velo,float optimalVelo)
|
|
{
|
|
float driveSlip=0,slip=0,angularVelo=0;
|
|
float maxThrottle=AIMaxThrottle(phys);
|
|
|
|
for(int i=0;i<car->numWheels;i++)
|
|
{
|
|
driveSlip+=phys->wheels[i].slip*car->wheels[i].powered;
|
|
slip+=phys->wheels[i].slip;
|
|
angularVelo+=fabs(phys->wheels[i].angularVelo*car->wheels[i].powered);
|
|
}
|
|
slip/=car->numWheels;
|
|
float maxSlip=velo<20?0.2-velo/20*(0.2-kAIMaxThottleSlip):kAIMaxThottleSlip;
|
|
|
|
if((driveSlip*sign(phys->gear)<maxSlip||angularVelo<kAIMinSpinAngularVelo||(phys->clutch<=1&&phys->gear<=1)) //traction control
|
|
&&velo<optimalVelo) //we don't want to drive too fast!
|
|
phys->throttle+=kFrameTime*(1/kAIThrottleTime);
|
|
else
|
|
phys->throttle-=kFrameTime*(1/kAIThrottleReleaseTime);
|
|
|
|
if(phys->throttle>maxThrottle)
|
|
phys->throttle=maxThrottle;
|
|
else if(phys->throttle<phys->idleThrottle)
|
|
phys->throttle=phys->idleThrottle;
|
|
|
|
if((velo>optimalVelo+1||optimalVelo==0))
|
|
{
|
|
if((-slip*sign(angularVelo)<kAIMaxBrakeSlip)||(phys->brake<0.1))
|
|
phys->brake+=kFrameTime*(1/kAIBrakeTime);
|
|
else
|
|
phys->brake-=kFrameTime*(1/kAIBrakeTime);
|
|
phys->arcadeBrake+=kFrameTime*(1/kAIBrakeTime);
|
|
}
|
|
else{
|
|
phys->arcadeBrake-=kFrameTime*(1/kAIBrakeReleaseTime);
|
|
phys->brake-=kFrameTime*(1/kAIBrakeReleaseTime);
|
|
}
|
|
|
|
if(phys->arcadeBrake>1)
|
|
phys->arcadeBrake=1;
|
|
else if(phys->arcadeBrake<0)
|
|
phys->arcadeBrake=0;
|
|
if(phys->brake>1)
|
|
phys->brake=1;
|
|
else if(phys->brake<0)
|
|
phys->brake=0;
|
|
}
|
|
|
|
#define kAIOvertakeRoadAnalyseDistance 50 //length of approaching road section to be evaluated for overtaking
|
|
#define kAIOvertakeMinVeloDiffPossible 10
|
|
#define kAIOvertakeAbortDistance 60
|
|
#define kAIOvertakeFinishDistance 20
|
|
#define kAIOvertakeLaneChangeTime 3.0
|
|
|
|
void AIEvaluateOvertaking(tGameEntity *overtaker,tGameEntity *obstacle,float track)
|
|
{
|
|
tCarPhysics *phys=(tCarPhysics*)overtaker->physics;
|
|
if(phys->overtaking)//are we already overtaking?
|
|
return;
|
|
if(overtaker->velo**MatrixGetZVector(overtaker->dir)<=obstacle->velo**MatrixGetZVector(overtaker->dir)) //are we faster?
|
|
return;
|
|
float minTrack,maxTrack,minSpeed;
|
|
RoadGetWayPointData(overtaker->lastRoadIndex,kAIOvertakeRoadAnalyseDistance,&minTrack,&maxTrack,&minSpeed);
|
|
if(minSpeed*0.75<~obstacle->velo+kAIOvertakeMinVeloDiffPossible) //is it possible to drive faster than obstacle all the time?
|
|
return;
|
|
// if(fabs(minTrack-maxTrack)>1)
|
|
// return;
|
|
phys->overtaking=obstacle;
|
|
phys->overtakeSide=fabs(1+minTrack)>fabs(1-maxTrack)?-1:1;
|
|
}
|
|
|
|
void ControlEntityAIInput(tGameEntity *entity)
|
|
//The game's AI - adjusts all the controls of the car passed in entity.
|
|
{
|
|
if(entity->physicsType!=kPhysicsTypeCar)//is entity really a car?
|
|
return;
|
|
tCarPhysics *phys=(tCarPhysics*)entity->physics;
|
|
tCarDefinition *car=&(phys->car);
|
|
float velo=~entity->velo;
|
|
|
|
|
|
//determine course
|
|
float optimalVelo;
|
|
float track=phys->overTakeProgress+0.4*sin((gFrameCount*kFrameTime)*0.16+phys->aiRouteCycle);
|
|
if(track<-1)track=-1;
|
|
else if(track>1)track=1;
|
|
tVector3 wayPoint=RoadGetNextWaypoint(entity->pos,&entity->lastRoadIndex,&track,&optimalVelo,kAIWaypointAheadDistance);
|
|
|
|
if(gGameInfo->arcade==kGameModeArcade||gGameInfo->arcade==kGameModeTurbo)
|
|
optimalVelo*=1.3;
|
|
|
|
if(phys->lapCount>gGameInfo->numLaps)
|
|
optimalVelo=0;
|
|
|
|
//see if we are about to crash into another car if we are to remain on our course.
|
|
tGameEntity *obstacle;
|
|
float del=AIGetCollisionDelay(entity,&obstacle);
|
|
|
|
//if we will crash into another car within the next 5 seconds, see if we can overtake
|
|
if(del<=5)
|
|
AIEvaluateOvertaking(entity,obstacle,track);
|
|
|
|
if(del>5&&phys->stuckTime>10*kFPS)
|
|
//if we are stuck, recover car.
|
|
{
|
|
RoadCenterCar(entity);
|
|
phys->overtaking=NULL;
|
|
phys->stuckTime=0;
|
|
}
|
|
|
|
//if we are overtaking
|
|
//phys->lightFlags&=~(kLightFlagLIndLight|kLightFlagRIndLight);
|
|
if(phys->overtaking)
|
|
{
|
|
if(fabs(phys->overTakeProgress)<1)
|
|
{
|
|
phys->overTakeProgress+=(1/kAIOvertakeLaneChangeTime)*kFrameTime*sign(phys->overtakeSide);
|
|
if(fabs(phys->overTakeProgress)>1)
|
|
phys->overTakeProgress=sign(phys->overTakeProgress);
|
|
/*if(phys->lapCount<=gGameInfo->numLaps)
|
|
phys->lightFlags|=(phys->overtakeSide==(gGameInfo->reverse?-1:1)?kLightFlagLIndLight:kLightFlagRIndLight);*/
|
|
}
|
|
float dist=-(phys->overtaking->pos-entity->pos)**MatrixGetZVector(entity->dir);
|
|
if(dist>kAIOvertakeFinishDistance)//are we finished overtaking?
|
|
phys->overtaking=NULL;
|
|
else if(dist<-kAIOvertakeAbortDistance)//or are we too slow to overtake?
|
|
phys->overtaking=NULL;
|
|
if(fabs(phys->overtakeSide-track)<0.5)
|
|
phys->overtaking=NULL;
|
|
}
|
|
else if(fabs(phys->overTakeProgress)>0)
|
|
{
|
|
int s=sign(phys->overTakeProgress);
|
|
phys->overTakeProgress-=(1/kAIOvertakeLaneChangeTime)*kFrameTime*s;
|
|
//if(phys->lapCount<=gGameInfo->numLaps)
|
|
//phys->lightFlags|=(s==-1?kLightFlagLIndLight:kLightFlagRIndLight);
|
|
if(s!=sign(phys->overTakeProgress))
|
|
phys->overTakeProgress=0;
|
|
}
|
|
|
|
//steering
|
|
float overSteer;
|
|
phys->steering=AISteering(car,entity,wayPoint,&overSteer);
|
|
|
|
//brake / throttle
|
|
AIBrakeThrottle(car,phys,velo,optimalVelo);
|
|
|
|
if(gFrameCount*kFrameTime>=kStartGameDelaySeconds)
|
|
{
|
|
//gear shifting / clutch control
|
|
if(phys->gear==0)
|
|
{
|
|
phys->gear=1;
|
|
phys->lastGearSwitch=gFrameCount*kFrameTime;
|
|
}
|
|
if(phys->gear>=1&&(gFrameCount-1)*kFrameTime>phys->lastGearSwitch+car->gearSwitchTime&&gFrameCount*kFrameTime>=kStartGameDelaySeconds+1.5)
|
|
{
|
|
if(phys->rpm>car->maxRPM&&phys->gear<car->numGears-2)
|
|
{
|
|
phys->gear++;
|
|
phys->lastGearSwitch=gFrameCount*kFrameTime;
|
|
}
|
|
if(phys->rpm<car->shiftDownRPM&&phys->gear>1)
|
|
{
|
|
phys->gear--;
|
|
phys->lastGearSwitch=gFrameCount*kFrameTime;
|
|
}
|
|
}
|
|
|
|
if(gFrameCount*kFrameTime<phys->lastGearSwitch+car->gearSwitchTime)
|
|
{
|
|
float switchTime=(gFrameCount*kFrameTime-phys->lastGearSwitch)/car->gearSwitchTime;
|
|
phys->clutch=switchTime;
|
|
if(phys->rpm<2000)
|
|
phys->clutch=(phys->rpm-car->idleRPM)/(2000-car->idleRPM);
|
|
if(switchTime<0.5)
|
|
phys->throttle=phys->idleThrottle;
|
|
}
|
|
else
|
|
if(phys->rpm<2000)
|
|
phys->clutch=(phys->rpm-car->idleRPM)/(2000-car->idleRPM);
|
|
else
|
|
phys->clutch=1;
|
|
}
|
|
|
|
if(overSteer*velo>20)
|
|
if(car->wheels[2].powered)
|
|
{
|
|
phys->clutch=0;
|
|
phys->throttle=0;
|
|
}
|
|
}
|