Redline/source/ai.cpp
maride 02061d74c2 Original 1.0.5 code
(as received from Jonas Echterhoff)
2016-04-02 14:43:55 +02:00

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;
}
}