//ai.cpp //the game's AI system for computer-controlled opponents #include #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;inumPlayers;i++) if(gCarEntities[i]!=entity) { tVector2 pos2=Vector(gCarEntities[i]->pos.x,gCarEntities[i]->pos.z); tVector2 diff=pos2-basePos1; if(sqr(diff)=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;jaiPowerCycle); //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;inumWheels;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)clutch<=1&&phys->gear<=1)) //traction control &&velothrottle+=kFrameTime*(1/kAIThrottleTime); else phys->throttle-=kFrameTime*(1/kAIThrottleReleaseTime); if(phys->throttle>maxThrottle) phys->throttle=maxThrottle; else if(phys->throttleidleThrottle) phys->throttle=phys->idleThrottle; if((velo>optimalVelo+1||optimalVelo==0)) { if((-slip*sign(angularVelo)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->gearnumGears-2) { phys->gear++; phys->lastGearSwitch=gFrameCount*kFrameTime; } if(phys->rpmshiftDownRPM&&phys->gear>1) { phys->gear--; phys->lastGearSwitch=gFrameCount*kFrameTime; } } if(gFrameCount*kFrameTimelastGearSwitch+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; } }