Major changes: Improved roaming to navigate around obstacles. Though at the time I wrote it I had not noticed the very good pathfinder that already exists and is used by RobotPlayer. Removed timer based weaving. Instead it has an oscillating lag estimate used in targetting. Uses max rotation and speed, though smoothed a bit. Perhaps not smoothed enough. Predictive targetting. Will even predict curved motion, I think. Improved dodging. You'll see it weave through showers of bullets with the greatest of ease. Adjusts its skill level to prevent you from using it to improve your score. At its worst it will respond sluggishly, aim badly, and not attempt to dodge anything. Picks up flags when there is no visible target. Will drop a wider variety of flags it doesn't know how to use. Fixes: missing {} in nearest target checking, can result in not finding a target. TargetingUtils::isLocationObscured went by tank base (bottom). Was causing problems on some maps. Now goes by center. Don't jump buildings when phased. Fixed bug where it shoots at burrowers. Wasn't dodging own bullets. Didn't check if teammate stood between it and its target when firing. New problems: The path still has the occasional problems. Needs cleanup. ###In TargetingUtils.cxx: bool TargetingUtils::isLocationObscured( const float *src, const float *target ) { float dir[3]; float s[3],d[3]; s[0]=src[0]; s[1]=src[1]; s[2]=src[2]+BZDBCache::tankHeight*0.5f; d[0]=target[0]; d[1]=target[1]; d[2]=target[2]+BZDBCache::tankHeight*0.5f; getUnitVector(s, d, dir); Ray tankRay( s, dir ); float targetDistance = getTargetDistance(s, d); const Obstacle *building = ShotStrategy::getFirstBuilding(tankRay, -0.5f, targetDistance); return building != NULL; } ###In playing.cxx: ###Before the line "myTank->setAutoPilot(true);" in cmdAutoPilot: autoLastScore=myTank->getScore(); ###Before doAutoPilot: static float triangleWave(float x) { return fabs((x-floor(x))*2.0-1.0)*2.0-1.0; } static bool isJumpObscured(const float s[],const float d[], float h) { if(d[2]-s[2]>h) return true; float src[3],dst[3]; src[0]=s[0]; src[1]=s[1]; src[2]=max(s[2]+h,d[2]); dst[0]=d[0]; dst[1]=d[1]; dst[2]=src[2]; return TargetingUtils::isLocationObscured(src,dst); } /* Predicts circular motion s[]=source position v[]=source velocity d[]=predicted destination ang=source angle speed=speed rot=rotation t=time */ static void predict(const float s[], const float v[], float d[], float ang, float speed, float rot,float t) { if(v[2]!=0.0f||(rot>-0.01f && rot<0.01f)) { //if falling or not turning //assume straight line d[0]=s[0]+v[0]*t; d[1]=s[1]+v[1]*t; } else { //assume they turn and move in the same angle they face //assume they really won't turn more than 60 degrees if(rot*t>M_PI/3.0f) rot=M_PI/3.0f/t; else if(rot*t<-M_PI/3.0f) rot=-M_PI/3.0f/t; float r=speed/rot; //get radius d[0]=s[0]+(sinf(ang+rot*t)-sinf(ang))*r; d[1]=s[1]-(cosf(ang+rot*t)-cosf(ang))*r; } if(v[2]!=0.0f) { float g=BZDB.eval(StateDatabase::BZDB_GRAVITY); d[2]=s[2]+v[2]*t+g*t*t/2.0f; if(d[2]<0.0f) { d[2]=0.0f; //When they land, they will go in whichever direction they face //Calculate time to land. Good ol' Quadratic formula. float t2=(-v[2]+(v[2]<0.0f?-1.0f:1.0f)*sqrt(2.0f*g*s[2]))/g; t-=t2; ang+=rot*t2; //recalculate position //But we really don't know if they'll go forward after landing, so guess average (0.25). d[0]=s[0]+v[0]*t2+0.25f*cos(ang)*t; d[1]=s[1]+v[1]*t2+0.25f*sin(ang)*t; } } else d[2]=s[2]; } //Returns a prediction of where Player *us should fire to hit Player *them. //result in p[] //accepts lag estimate static void predictTarget(Player *us, Player *them, float p[], float lag) { //first we estimate missile speed float mspeed=BZDB.eval(StateDatabase::BZDB_SHOTSPEED); // base missile speed if(us->getFlag()==Flags::Laser) mspeed*=BZDB.eval(StateDatabase::BZDB_LASERADVEL); if(us->getFlag()==Flags::RapidFire) mspeed*=BZDB.eval(StateDatabase::BZDB_RFIREADVEL); const float *uv=us->getVelocity(); float uspeed; //our speed if(uv[2]!=0.0f) uspeed=0.0f; //forget about it if jumping else { uspeed=sqrt(uv[0]*uv[0]+uv[1]*uv[1]); if(uspeed!=0.0f) { float moveAng=atan2(uv[1],uv[0]); float faceAng=us->getAngle(); //calculate speed relative to where facing. //an imperfect solution, but usually adequate uspeed*=cosf(faceAng-moveAng); } } mspeed+=uspeed; //add our speed for better missile speed guess const float *src=us->getPosition(); const float *dest=them->getPosition(); const float *dv=them->getVelocity(); float speed=sqrt(dv[0]*dv[0]+dv[1]*dv[1]); float ang=them->getAngle(); float rot=them->getAngularVelocity()/2.0f; //divide by two because they are likely to change their minds p[0]=dest[0]; p[1]=dest[1]; p[2]=dest[2]; for(int i=0;i<3;i++) { float t=lag+(TargetingUtils::getTargetDistance(src,p) - BZDB.eval(StateDatabase::BZDB_MUZZLEFRONT) - BZDBCache::tankRadius*0.7f)/mspeed; predict(dest,dv,p,ang,speed,rot,t); } } //should hold score accumulated by auto-pilot //used to adjust suck factor int autoPoints=0; int autoLastScore=0; //previous score. Initialized in cmdAutoPilot ###In place of doAutoPilot: static void doAutoPilot(float &rotation, float &speed) { //track points earned by auto-pilot int score=myTank->getScore(); autoPoints+=score-autoLastScore; autoLastScore=score; float suck=(autoPoints+4.0f)/8.0f; if(suck<0.0f) suck=0.0f; if(suck>1.0f) suck=1.0f; //DEBUG, REMOVE!!! //suck=0.0f; float suckError=sinf(TimeKeeper::getTick().getSeconds()*1.58f)*suck; static TimeKeeper lastShot; static float subTarget[3]; static bool useSub=false; static float lastDist=0; static int trend=0; static bool allowJump=true; static bool quickFire=false; //used in some situations float jumpHeight=1.01*fabs(0.5*pow(BZDB.eval(StateDatabase::BZDB_JUMPVELOCITY),2)/BZDB.eval(StateDatabase::BZDB_GRAVITY)); //varying lag guess, on half second cycle float lagEst=0.25f+0.15f*triangleWave(TimeKeeper::getTick().getSeconds()*3.0f)+suckError*0.5f; float shotSpeed=BZDB.eval(StateDatabase::BZDB_SHOTSPEED); if(myTank->getFlag()==Flags::Laser) shotSpeed*=1000.0f; if(myTank->getFlag()==Flags::RapidFire) shotSpeed*=2.0f; PlayerId t; PlayerId target = curMaxPlayers; float pos[3]; float myAzimuth = myTank->getAngle(); float enemyAzimuth; //used in double exponential moving averages to smooth movement and make it look more natural static float prot=0.0; static float prot2=0.0; static float pspeed=0.0; static float pspeed2=0.0; memcpy(pos, myTank->getPosition(), sizeof(pos)); const bool phased = myTank->getFlag() == Flags::OscillationOverthruster || myTank->getFlag() == Flags::PhantomZone; bool expelled; const Obstacle *obstacle = myTank->getHitBuilding(pos, myAzimuth, phased, expelled); if(hypotf(myTank->getVelocity()[0],myTank->getVelocity()[1])<0.8f*BZDB.eval(StateDatabase::BZDB_TANKSPEED)) { allowJump=false; //disallow plotting of jump courses when not moving fast } float distance=Infinity; if(myTank->getFlag()!=Flags::Laser) distance = BZDB.eval(StateDatabase::BZDB_SHOTRANGE); int chkCount=1; static PlayerId lastTarget=-1; for (t = 0; t < curMaxPlayers; t++) { if (t != myTank->getId() && player[t] && player[t]->isAlive() && !player[t]->isPaused() && !player[t]->isNotResponding() && myTank->validTeamTarget(player[t])) { if(player[t]->getFlag() != Flags::PhantomZone || !player[t]->isFlagActive()) { if(player[t]->getPosition()[2]-pos[2]<=jumpHeight+(myTank->getFlag()==Flags::Burrow?1.0f:0.0f)) { // chkCount++; float d = TargetingUtils::getTargetDistance( pos, player[t]->getPosition())+fabs(TargetingUtils::getTargetAngleDifference(pos, myAzimuth, player[t]->getPosition())); if(t==lastTarget) d=d-50.0f; if(player[t]->getVelocity()[2]<0.0f) d=d-50.0f; if (d < distance) { if(myTank->getFlag() == Flags::OscillationOverthruster) { if ((player[t]->getFlag() != Flags::Stealth) || (myTank->getFlag() == Flags::Seer) || ((!TargetingUtils::isLocationObscured( pos, player[t]->getPosition())) && (TargetingUtils::getTargetAngleDifference(pos, myAzimuth, player[t]->getPosition()) <= M_PI/6.0f))) { target = t; distance = d; if(useSub) { hud->setAlert(0, "Chase", 1.0f, true); useSub=false; } } } else { if( ( (player[t]->getFlag() != Flags::Stealth) ||(myTank->getFlag() == Flags::Seer) ||((TargetingUtils::getTargetAngleDifference(pos, myAzimuth, player[t]->getPosition()) <= M_PI/6.0f) && !TargetingUtils::isLocationObscured( pos, player[t]->getPosition()))) && (!TargetingUtils::isLocationObscured( pos, player[t]->getPosition()) || (allowJump && !isJumpObscured(pos, player[t]->getPosition(), jumpHeight)))) { target = t; distance = d; if(useSub) { hud->setAlert(0, "Chase", 1.0f, true); useSub=false; } } } } } } } } lastTarget=target; if (target == curMaxPlayers) { // If relatively safe and have no flag, see if a flag is close int closestFlag = -1; if (myTank->getFlag() == Flags::Null && suck<0.7f) { float minDist = Infinity; for (int i = 0; i < numFlags; i++) { if (world->getFlag(i).type == Flags::Null || world->getFlag(i).status != FlagOnGround) continue; const float* fpos = world->getFlag(i).position; if (fpos[2] == pos[2]) { float dist = TargetingUtils::getTargetDistance( pos, fpos ); if (dist < minDist && TargetingUtils::getTargetAngleDifference(pos, myAzimuth, fpos) <= M_PI/4.0f && !TargetingUtils::isLocationObscured(pos,fpos)) { minDist = dist; closestFlag = i; } } } } if (closestFlag != -1) { const float *fpos = world->getFlag(closestFlag).position; float flagAzimuth = TargetingUtils::getTargetAzimuth( pos, fpos ); rotation = TargetingUtils::getTargetRotation( myAzimuth, flagAzimuth ); speed = M_PI/2.0f - fabs(rotation); } else { float subDist; if(useSub) { //ensures that we don't circle around some point. subDist=TargetingUtils::getTargetDistance(pos,subTarget); if(subDistlastDist && (trend&1)==1) trend++; lastDist=subDist; } bool bigcheck=false; if(useSub==false || trend>2 || TargetingUtils::isLocationObscured( pos, subTarget)) { trend=0; useSub=true; bigcheck=true; hud->setAlert(0, "Roam", 1.0f, true); } int count=bigcheck?(2+30/chkCount):(2+10/chkCount); float subT[3]; float cost; float bestCost=Infinity; float tankRadius = BZDBCache::tankRadius; float worldSize = BZDB.eval(StateDatabase::BZDB_WORLDSIZE); //look for best path opening up to finding the best target for(int i=0;igetId() && player[t] && player[t]->isAlive() && !player[t]->isPaused() && !player[t]->isNotResponding() && myTank->validTeamTarget(player[t])) { if(player[t]->getPosition()[2]-pos[2]getPosition()); if (d < distance) { if (((player[t]->getFlag() != Flags::Stealth) || (myTank->getFlag() == Flags::Seer) || (TargetingUtils::getTargetAngleDifference(pos, myAzimuth, player[t]->getPosition()) <= M_PI/6.0f)) && (!TargetingUtils::isLocationObscured( pos, player[t]->getPosition()))) { distance = d; found=true; allowJump=false; } } } } } if(found) { cost=distance+TargetingUtils::getTargetDistance(pos,subT); } else { for (t = 0; t < curMaxPlayers; t++) { if (t != myTank->getId() && player[t] && player[t]->isAlive() && !player[t]->isPaused() && !player[t]->isNotResponding() && myTank->validTeamTarget(player[t])) { if(player[t]->getPosition()[2]-pos[2]getPosition()); float tdir[3]={cosf(az), sinf(az), 0.0f}; Ray r(subT, tdir); float d=TargetingUtils::getTargetDistance( subT, player[t]->getPosition()); float d2 = d; ShotStrategy::getFirstBuilding(r, -0.5f, d2); d=d*2.0-d2; if (d < distance) { if ((player[t]->getFlag() != Flags::Stealth) || (myTank->getFlag() == Flags::Seer) || ((!TargetingUtils::isLocationObscured( subT, player[t]->getPosition())) && (TargetingUtils::getTargetAngleDifference(pos, myAzimuth, player[t]->getPosition()) <= M_PI/6.0f))) { distance = d; found=true; } } } } } if(found) { cost=1000+distance+TargetingUtils::getTargetDistance(pos,subT); } else { distance=TargetingUtils::getTargetDistance(pos,subT); cost=5000-(distance>100.0f?100.0f:distance); } } cost+=fabs(TargetingUtils::getTargetAngleDifference(pos, myAzimuth, subT)); if(bigcheck || found) { if(!bigcheck && i==0) cost-=50; if(cost0) { trend=0; } bestCost=cost; subTarget[0]=subT[0]; subTarget[1]=subT[1]; subTarget[2]=subT[2]; } } } if(bestCost>1000) allowJump=true; rotation = TargetingUtils::getTargetRotation( myAzimuth, TargetingUtils::getTargetAzimuth( pos, subTarget )); if(rotation<-M_PI/2.0||rotation>M_PI/2.0) speed=-0.5f; else speed=1.0f; } } else { // See if anyone is shootable, if so do so. Then figure out how to chase my target myTank->setTarget(player[target]); distance=TargetingUtils::getTargetDistance( pos, player[target]->getPosition()); float dir[3] = {cosf(myAzimuth), sinf(myAzimuth), 0.0f}; pos[2] += BZDB.eval(StateDatabase::BZDB_MUZZLEHEIGHT); Ray tankRay(pos, dir); pos[2] -= BZDB.eval(StateDatabase::BZDB_MUZZLEHEIGHT); if (myTank->getFlag() == Flags::ShockWave) { TimeKeeper now = TimeKeeper::getTick(); if (now - lastShot >= (BZDB.eval(StateDatabase::BZDB_RELOADTIME)*0.5f / World::getWorld()->getMaxShots())) { bool hasSWTarget = false; for (t = 0; t < curMaxPlayers; t++) { if (t != myTank->getId() && player[t] && player[t]->isAlive() && !player[t]->isPaused() && !player[t]->isNotResponding() && (player[t]->getFlag()!=Flags::Stealth || t==target)) { const float *tp = player[t]->getPosition(); float dist = TargetingUtils::getTargetDistance( pos, tp ); if (!myTank->validTeamTarget(player[t])) { if (dist*0.8 <= BZDB.eval(StateDatabase::BZDB_SHOCKOUTRADIUS)) { hasSWTarget = false; t = curMaxPlayers; } } else { if (dist*(1.0f+suckError*0.5f) <= BZDB.eval(StateDatabase::BZDB_SHOCKOUTRADIUS)) { hasSWTarget = true; } } } } if (hasSWTarget) { myTank->fireShot(); lastShot = TimeKeeper::getTick();; } } } else { TimeKeeper now = TimeKeeper::getTick(); int shotsFired=0; int maxShots=myTank->getMaxShots(); for(int i=0;igetShot(i); if(shot && !shot->isReloaded()) shotsFired++; } //max shot rate is proportional to the number of shots fired if (now - lastShot >= (BZDB.eval(StateDatabase::BZDB_RELOADTIME)*0.5f*(float)(shotsFired+maxShots)/(float)(maxShots*maxShots))*(quickFire?0.3f+0.4f*suck:1.0f)) { //float errorLimit = World::getWorld()->getMaxShots() * BZDB.eval(StateDatabase::BZDB_LOCKONANGLE) / 16.0f; //float closeErrorLimit = errorLimit * 2.0f; bool allowShot=false; bool airShot=false; //This determines if we should shoot for (t = 0; t < curMaxPlayers; t++) { Player *p=player[t]; if (t != myTank->getId() && p && p->isAlive() && !p->isPaused() && !p->isNotResponding() && myTank->validTeamTarget(p)) { if(p->getFlag() != Flags::PhantomZone || !p->isFlagActive()) { if((p->getFlag() != Flags::Burrow) || myTank->getFlag()==Flags::GuidedMissile) { const float *tp2=p->getPosition(); float tp[3]; float lag=lagEst; //quick hack for local robots for (int i = 0; i < numRobots; i++) if(robots[i] && p->getId()==robots[i]->getId()) lag=0.01f; predictTarget(myTank,p,tp,lag); float errorLimit= (myTank->getFlag() == Flags::GuidedMissile)? BZDB.eval(StateDatabase::BZDB_LOCKONANGLE) :((0.5f+suck)*BZDBCache::tankRadius/(TargetingUtils::getTargetDistance(pos,tp)+1.0f)+0.01f); if(tp[2]<=pos[2] && tp2[2]>pos[2]) tp[2]=pos[2]; if((myTank->getFlag() == Flags::GuidedMissile) || (fabs(pos[2]+BZDB.eval(StateDatabase::BZDB_MUZZLEHEIGHT) - tp[2]) < BZDBCache::tankHeight+0.5f)) { float targetDiff = fabs(suckError*0.3f+TargetingUtils::getTargetAngleDifference(pos, myAzimuth, tp )); if (targetDiff < errorLimit) { bool isTargetObscured; if (myTank->getFlag() != Flags::SuperBullet) isTargetObscured = TargetingUtils::isLocationObscured( pos, tp ); else isTargetObscured = false; if (!isTargetObscured) { if(p->getVelocity()[2]<0.0f) airShot=true; allowShot=true; if(myTank->getFlag() == Flags::GuidedMissile) target=t; t = curMaxPlayers; } } } } } } } if(allowShot) { //determines if we should abort the shot for (t = 0; t < curMaxPlayers; t++) { if (t != myTank->getId() && player[t] && player[t]->isAlive() && !player[t]->isPaused() && !player[t]->isNotResponding() && !myTank->validTeamTarget(player[t])) { if(player[t]->getFlag() != Flags::PhantomZone || !player[t]->isFlagActive()) { if((player[t]->getFlag() != Flags::Burrow) || myTank->getFlag()==Flags::GuidedMissile) { const float *tp2=player[t]->getPosition(); float tp[3]; predictTarget(myTank,player[t],tp,0.2f); float errorLimit=0.8f*BZDBCache::tankRadius/(TargetingUtils::getTargetDistance(pos,tp)+1.0f)+0.1f; if(tp[2]<=pos[2] && tp2[2]>pos[2]) tp[2]=pos[2]; if ((myTank->getFlag() == Flags::GuidedMissile) || (fabs(pos[2] - tp[2]) < 3.0f * BZDBCache::tankHeight)) { float targetDiff = fabs(TargetingUtils::getTargetAngleDifference(pos, myAzimuth, tp )); if (targetDiff < errorLimit) { bool isTargetObscured; if (myTank->getFlag() != Flags::SuperBullet) isTargetObscured = TargetingUtils::isLocationObscured( pos, tp ); else isTargetObscured = false; if (!isTargetObscured) { allowShot=false; t = curMaxPlayers; } } } } } } } } if(allowShot) { myTank->fireShot(); lastShot = now; quickFire=airShot; } } } float enemyPos[3]; predictTarget(myTank,player[target],enemyPos,lagEst); if (enemyPos[2] < 0.0f) //Roger doesn't worry about burrow enemyPos[2] = 0.0; enemyAzimuth = TargetingUtils::getTargetAzimuth( pos, enemyPos ); rotation = TargetingUtils::getTargetRotation( myAzimuth, enemyAzimuth )+suckError*0.3f; if(fabs(rotation)>M_PI/2.0f) speed=-0.5f; //If we are driving relatively towards our target and a building pops up jump over it if (fabs(rotation) < BZDB.eval(StateDatabase::BZDB_LOCKONANGLE)) { const Obstacle *building = NULL; float d = distance - 5.0f; //Make sure building is REALLY in front of player (-5) const float *velocity = myTank->getVelocity(); if ((myTank->getFlag() != Flags::SuperBullet) && pspeed>0.8) building = ShotStrategy::getFirstBuilding(tankRay, -0.5f, d); if (building && !phased) { //Never did good in math, he should really see if he can reach the building //based on jumpvel and gravity, but settles for assuming 20-50 is a good range if ((d > 20.0f) && (d < 50.0f) && (building->getType() == BoxBuilding::typeName)) { float jumpVel = BZDB.eval(StateDatabase::BZDB_JUMPVELOCITY); float maxJump = (jumpVel * jumpVel) / (2 * -BZDB.eval(StateDatabase::BZDB_GRAVITY)); if (((building->getPosition()[2] - pos[2] + building->getHeight()) ) < maxJump) { rotation=0.0f; if(fabs(prot)<0.2f) { speed = d / 50.0f; myTank->jump(); } } } else if(d<=20.0f) allowJump=false; } } const Player *target = myTank->getTarget(); float threat=TargetingUtils::getTargetAngleDifference(pos, target->getAngle(), target->getPosition()); if(threat<-M_PI) threat+=2.0f*M_PI; if(threat>M_PI) threat-=2.0f*M_PI; if (distance > BZDB.eval(StateDatabase::BZDB_SHOTSPEED)*(0.3f+0.7f*fabs(threat)/M_PI)/2.0) { } else if (target->getFlag() != Flags::Burrow && myTank->getFlag()!=Flags::ShockWave) { speed = -0.5f; rotation *= M_PI / (2.0f * fabs(rotation)); } } /*}*/ if (/*(myTank->getFlag() != Flags::Narrow) && */(myTank->getFlag() != Flags::Burrow) && suck<0.9f){ for (t = 0; t <= curMaxPlayers; t++) { Player *p = 0; if (t < curMaxPlayers) p = player[t]; else p = myTank; if (/*t == myTank->getId() ||*/ !p) continue; const int maxShots = p->getMaxShots(); float minSafety=2.0f-suck; for (int s = 0; s < maxShots; s++) { ShotPath* shot = p->getShot(s); if (!shot || shot->isExpired()) continue; // ignore invisible bullets completely for now (even when visible) // Theoretically, Roger could determine shot location, just from the sound //Can't dodge the laser much. if (shot->getFlag() == Flags::InvisibleBullet || shot->getFlag() == Flags::Laser) continue; const float* shotPos = shot->getPosition(); if ((fabs(shotPos[2] - pos[2]) > BZDBCache::tankHeight) && (shot->getFlag() != Flags::GuidedMissile)) continue; const float dist = TargetingUtils::getTargetDistance( pos, shotPos ); float adjusted=dist; if(TargetingUtils::getTargetAngleDifference(pos, myAzimuth, shotPos) >= M_PI/6.0f) { adjusted=adjusted*(suckError+2.0f)/2.0f; } //determine if the shot presents a great risk if(adjusted<200.0f*(1.0-suck)) { const float *shotVel = shot->getVelocity(); float shotAngle = atan2f(shotVel[1],shotVel[0]); float shotUnitVec[2] = {cos(shotAngle), sin(shotAngle)}; float shotAngle2 = atan2f(pos[1]-shotPos[1], pos[0]-shotPos[0]); float diff=shotAngle2-shotAngle; if (diff < -1.0f * M_PI) diff += 2.0f * M_PI; if (diff > 1.0f * M_PI) diff -= 2.0f * M_PI; float safety=dist*fabs(sin(diff))/4.82; //jump if too close if (((World::getWorld()->allowJumping() || (myTank->getFlag()) == Flags::Jumping)) && myTank->getFlag() != Flags::Narrow && suck<0.7f && safety<1.2f-suck && (adjusted < 1.6f * ( 4.82f+cos(diff)*sqrt(shotVel[0]*shotVel[0]+shotVel[1]*shotVel[1])/18.0f ) ) ) { s = maxShots; t=curMaxPlayers+1; myTank->jump(); } if (safety-M_PI/2.0f) { minSafety=safety; float desired; if(diff>0) desired=shotAngle+M_PI/2.0f; else desired=shotAngle-M_PI/2.0f; rotation=desired-myTank->getAngle(); if (rotation < -1.0f * M_PI) rotation += 2.0f * M_PI; if (rotation > 1.0f * M_PI) rotation -= 2.0f * M_PI; //2:1 preference to forward motion if(rotation-M_PI*2.0f/3.0f) { speed=1.0f; } else { speed=-0.5f; rotation=-rotation; } if(safety>1.3f) { //safe enough to turn to face origin (?) rotation=myTank->getAngle()-shotAngle; if (rotation < -1.0f * M_PI) rotation += 2.0f * M_PI; if (rotation > 1.0f * M_PI) rotation -= 2.0f * M_PI; } if(rotation<0) rotation=-1.0f; else rotation=1.0f; if(suck<0.4f) { prot2=rotation; pspeed=speed; } } } } } } } //Check for flags that it simply does not know how to use well. //And drop it. FlagType *flag=myTank->getFlag(); if(flag==Flags::Useless ||flag==Flags::Identify ||flag==Flags::MachineGun ||flag==Flags::PhantomZone ||flag==Flags::Steamroller ||flag==Flags::Thief) { serverLink->sendDropFlag(myTank->getPosition()); handleFlagDropped(myTank); } //More responsive rotation //Helpful because below this it's made less responsive rotation*=2.0f; //and limit if(rotation<-1.0f) rotation=-1.0f; if(rotation>1.0f) rotation=1.0f; if(speed>1.0f) speed=1.0f; if(speed<-0.5f) speed=-0.5f; //Sometimes this fella seals himself. Maybe NaN values are getting returned. //Check and correct if(!(speed>-0.6f&&speed<1.1f)) speed=0.0f; if(!(rotation>-1.1f&&rotation<1.1f)) rotation=0.0f; //the double filtered rotation is a burden when targetting jumpers //if(target!=curMaxPlayers && player[target]->getVelocity()[2]<0.0f && suck<0.6) prot2=rotation; float r1=0.5f+suck*0.45f; float r2=1.0f-r1; //Speed and rotation filtered to reduce jerkiness //The if() statements reduce wobble //if(prot2*rotation<0) prot2=0; prot2=prot2*r1+rotation*r2; //if(prot*prot2<0) prot=0; prot=prot*r1+prot2*r2; //if(pspeed2*speed<0) pspeed2=0; pspeed2=pspeed2*r1+speed*r2; //if(pspeed2*speed<0) pspeed=0; pspeed=pspeed*r1+pspeed2*r2; speed=pspeed; rotation=prot; }