/* bzflag
 * Copyright (c) 1993-2025 Tim Riker
 *
 * This package is free software;  you can redistribute it and/or
 * modify it under the terms of the license found in the file
 * named COPYING that should have accompanied this file.
 *
 * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

// get our interface
#include "AutoPilot.h"

/* common headers */
#include "BZDBCache.h"
#include "BoxBuilding.h"

/* local headers */
#include "Roster.h"
#include "TargetingUtils.h"
#include "World.h"
#include "WorldPlayer.h"
#include "playing.h"
#include "Plan.h"

/* system implementation headers */
#include <algorithm>

typedef std::map<FlagType*, std::pair<int,int> > FlagSuccessMap;

static FlagSuccessMap   flagSuccess;
static int      totalSum = 0;
static int      totalCnt = 0;
static bool     wantJump = false;

static PlanStack planStack;

void teachAutoPilot(FlagType *type, int adjust)
{
    if (type == Flags::Null)
        return;

    FlagSuccessMap::iterator it = flagSuccess.find(type);
    if (it != flagSuccess.end())
    {
        std::pair<int,int> &pr = it->second;
        pr.first += adjust;
        pr.second++;
    }
    else
        flagSuccess[type] = std::pair<int,int>(adjust,1);
    totalSum += adjust;
    totalCnt++;
}

static bool isFlagUseful(FlagType *type)
{
    if (type == Flags::Null)
        return false;

    FlagSuccessMap::iterator it = flagSuccess.find(type);
    float flagValue;
    if (it != flagSuccess.end())
    {
        std::pair<int,int> &pr = it->second;
        if (pr.second == 0)
            flagValue = 0.0f;
        else
            flagValue = (float)pr.first / (float)pr.second;
    }
    else
        return true;

    float avg;
    if (totalCnt == 0)
        avg = 0.0f;
    else
        avg = (float)totalSum / (float)totalCnt;
    return ((float)flagValue) >= avg;
}

static ShotPath *findWorstBullet(float &minDistance)
{
    LocalPlayer *myTank = LocalPlayer::getMyTank();
    const float *pos = myTank->getPosition();
    ShotPath *minPath = NULL;

    minDistance = Infinity;
    for (int t = 0; t < curMaxPlayers; t++)
    {
        if (t == myTank->getId() || !remotePlayers[t])
            continue;

        const int maxShots = remotePlayers[t]->getMaxShots();
        for (int s = 0; s < maxShots; s++)
        {
            ShotPath* shot = remotePlayers[t]->getShot(s);
            if (!shot || shot->isExpired())
                continue;

            if ((shot->getFlag() == Flags::InvisibleBullet) &&
                    (myTank->getFlag() != Flags::Seer))
                continue; //Theoretically Roger could triangulate the sound
            if (remotePlayers[t]->isPhantomZoned() && !myTank->isPhantomZoned())
                continue;
            if ((shot->getFlag() == Flags::Laser) &&
                    (myTank->getFlag() == Flags::Cloaking))
                continue; //cloaked tanks can't die from lasers

            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);
            if (dist < minDistance)
            {
                const float *shotVel = shot->getVelocity();
                float shotAngle = atan2f(shotVel[1], shotVel[0]);
                float shotUnitVec[2] = {cosf(shotAngle), sinf(shotAngle)};

                float trueVec[2] = { (pos[0] - shotPos[0]) / dist, (pos[1] - shotPos[1]) / dist };
                float dotProd = trueVec[0] * shotUnitVec[0] + trueVec[1] * shotUnitVec[1];

                if (dotProd <= 0.1f) //pretty wide angle, evasive actions prolly aren't gonna work
                    continue;

                minDistance = dist;
                minPath = shot;
            }
        }
    }
    float oldDistance = minDistance;
    WorldPlayer *wp = World::getWorld()->getWorldWeapons();
    for (int w = 0; w < wp->getMaxShots(); w++)
    {
        ShotPath* shot = wp->getShot(w);
        if (!shot || shot->isExpired())
            continue;

        if (shot->getFlag() == Flags::InvisibleBullet && myTank->getFlag() != Flags::Seer)
            continue; //Theoretically Roger could triangulate the sound
        if (shot->getFlag() == Flags::Laser && myTank->getFlag() == Flags::Cloaking)
            continue; //cloaked tanks can't die from lasers

        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 );
        if (dist < minDistance)
        {
            const float *shotVel = shot->getVelocity();
            float shotAngle = atan2f(shotVel[1], shotVel[0]);
            float shotUnitVec[2] = {cosf(shotAngle), sinf(shotAngle)};

            float trueVec[2] = { (pos[0] - shotPos[0]) / dist, (pos[1] - shotPos[1]) / dist };
            float dotProd = trueVec[0] * shotUnitVec[0] + trueVec[1] * shotUnitVec[1];

            if (dotProd <= 0.1f) //pretty wide angle, evasive actions prolly aren't gonna work
                continue;

            minDistance = dist;
            minPath = shot;
        }
    }
    if (oldDistance < minDistance)
        minDistance = oldDistance; //pick the closer bullet
    return minPath;
}

static bool avoidDeathFall(float & UNUSED(rotation), float &speed)
{
    LocalPlayer *myTank = LocalPlayer::getMyTank();
    float pos1[3], pos2[3];
    memcpy(pos1, myTank->getPosition(), sizeof(pos1));
    memcpy(pos2, pos1, sizeof(pos1));
    pos1[2] += 10.0f * BZDBCache::tankHeight;
    float azimuth = myTank->getAngle();
    if (speed < 0.0f)
        azimuth = fmodf(float(azimuth + M_PI), float(2.0 * M_PI));
    else
        azimuth = fmodf(float(azimuth), float(2.0 * M_PI));

    pos2[0] += 8.0f * BZDBCache::tankHeight * cosf(azimuth);
    pos2[1] += 8.0f * BZDBCache::tankHeight * sinf(azimuth);
    pos2[2] += 0.01f;

    float collisionPt[3];
    if (TargetingUtils::getFirstCollisionPoint( pos1, pos2, collisionPt ))
    {
        if (collisionPt[2] < 0.0f)
            collisionPt[2] = 0.0f;
        if (collisionPt[2] < World::getWorld()->getWaterLevel())
        {
            speed = 0.0f;
            return true;
        }
    }
    else if (collisionPt[2] < (pos2[2] - 1.0f))
        speed *= 0.5f;

    return false;
}

static bool avoidBullet(float &rotation, float &speed)
{
    LocalPlayer *myTank = LocalPlayer::getMyTank();
    const float *pos = myTank->getPosition();

    if ((myTank->getFlag() == Flags::Narrow) || (myTank->getFlag() == Flags::Burrow))
        return false; // take our chances

    float minDistance;
    ShotPath *shot = findWorstBullet(minDistance);

    if ((shot == NULL) || (minDistance > 100.0f))
        return false;

    const float *shotPos = shot->getPosition();
    const float *shotVel = shot->getVelocity();
    float shotAngle = atan2f(shotVel[1],shotVel[0]);
    float shotUnitVec[2] = {cosf(shotAngle), sinf(shotAngle)};

    float trueVec[2] = {(pos[0]-shotPos[0])/minDistance,(pos[1]-shotPos[1])/minDistance};
    float dotProd = trueVec[0]*shotUnitVec[0]+trueVec[1]*shotUnitVec[1];

    if (((World::getWorld()->allowJumping() || (myTank->getFlag()) == Flags::Jumping
            || (myTank->getFlag()) == Flags::Wings))
            && (minDistance < ( std::max(dotProd, 0.5f) * BZDBCache::tankLength * 2.25f))
            && (myTank->getFlag() != Flags::NoJumping))
    {
        wantJump = true;
        return (myTank->getFlag() != Flags::Wings);
    }
    else if (dotProd > 0.96f)
    {
        speed = 1.0;
        float myAzimuth = myTank->getAngle();
        float rotation1 = TargetingUtils::normalizeAngle((float)((shotAngle + M_PI/2.0) - myAzimuth));

        float rotation2 = TargetingUtils::normalizeAngle((float)((shotAngle - M_PI/2.0) - myAzimuth));

        float zCross = shotUnitVec[0]*trueVec[1] - shotUnitVec[1]*trueVec[0];

        if (zCross > 0.0f)   //if i am to the left of the shot from shooter pov
        {
            rotation = rotation1;
            if (fabs(rotation1) < fabs(rotation2))
                speed = 1.0f;
            else if (dotProd > 0.98f)
                speed = -0.5f;
            else
                speed = 0.5f;
        }
        else
        {
            rotation = rotation2;
            if (fabs(rotation2) < fabs(rotation1))
                speed = 1.0f;
            else if (dotProd > 0.98f)
                speed = -0.5f;
            else
                speed = 0.5f;
        }
        return true;
    }
    return false;
}

static bool stuckOnWall(float &rotation, float &speed)
{
    static TimeKeeper lastStuckTime;
    static float stuckRot = 0.0f, stuckSpeed = 0.0f;

    float stuckPeriod = float(TimeKeeper::getTick() - lastStuckTime);
    if (stuckPeriod < 0.5f)
    {
        rotation = stuckRot;
        speed = stuckSpeed;
        return true;
    }
    else if (stuckPeriod < 1.0f)
    {
        rotation = stuckRot;
        speed = 1.0;
        return true;
    }

    LocalPlayer *myTank = LocalPlayer::getMyTank();
    const float *pos = myTank->getPosition();
    float myAzimuth = myTank->getAngle();

    const bool phased = (myTank->getFlag() == Flags::OscillationOverthruster)
                        || myTank->isPhantomZoned();

    if (!phased && (TargetingUtils::getOpenDistance(pos, myAzimuth) < 5.0f))
    {
        lastStuckTime = TimeKeeper::getTick();
        if (bzfrand() > 0.8f)
        {
            // Every once in a while, do something nuts
            speed = (float)(bzfrand() * 1.5f - 0.5f);
            rotation = (float)(bzfrand() * 2.0f - 1.0f);
        }
        else
        {
            float leftDistance = TargetingUtils::getOpenDistance( pos, (float)(myAzimuth + (M_PI/4.0)));
            float rightDistance = TargetingUtils::getOpenDistance( pos, (float)(myAzimuth - (M_PI/4.0)));
            if (leftDistance > rightDistance)
                rotation = 1.0f;
            else
                rotation = -1.0f;
            speed = -0.5f;
        }
        stuckRot = rotation;
        stuckSpeed = speed;
        return true;
    }
    return false;
}

static RemotePlayer *findBestTarget()
{
    RemotePlayer *target = NULL;
    LocalPlayer *myTank = LocalPlayer::getMyTank();
    const float *pos = myTank->getPosition();
    float myAzimuth = myTank->getAngle();
    float distance = Infinity;

    for (int t = 0; t < curMaxPlayers; t++)
    {
        if ((t != myTank->getId())
                &&  (remotePlayers[t])
                &&  (remotePlayers[t]->isAlive())
                &&  (!remotePlayers[t]->isPaused())
                &&  (!remotePlayers[t]->isNotResponding())
                &&  (myTank->validTeamTarget(remotePlayers[t])))
        {

            if (remotePlayers[t]->isPhantomZoned() && !myTank->isPhantomZoned()
                    && (myTank->getFlag() != Flags::ShockWave)
                    && (myTank->getFlag() != Flags::SuperBullet))
                continue;

            if ((remotePlayers[t]->getFlag() == Flags::Cloaking) &&
                    (myTank->getFlag() == Flags::Laser))
                continue;

            //perform a draft that has us chase the proposed opponent if they have our flag
            if (World::getWorld()->allowTeamFlags() &&
                    (((myTank->getTeam() == RedTeam) && (remotePlayers[t]->getFlag() == Flags::RedTeam)) ||
                     ((myTank->getTeam() == GreenTeam) && (remotePlayers[t]->getFlag() == Flags::GreenTeam)) ||
                     ((myTank->getTeam() == BlueTeam) && (remotePlayers[t]->getFlag() == Flags::BlueTeam)) ||
                     ((myTank->getTeam() == PurpleTeam) && (remotePlayers[t]->getFlag() == Flags::PurpleTeam))))
            {
                target = remotePlayers[t];
                break;
            }

            float d = TargetingUtils::getTargetDistance(pos, remotePlayers[t]->getPosition());
            bool isObscured = TargetingUtils::isLocationObscured( pos, remotePlayers[t]->getPosition());
            if (isObscured) //demote the priority of obscured enemies
                d *= 1.25f;

            if (d < distance)
            {
                if ((remotePlayers[t]->getFlag() != Flags::Stealth)
                        ||  (myTank->getFlag() == Flags::Seer)
                        ||  ((!isObscured) &&
                             (TargetingUtils::getTargetAngleDifference(pos, myAzimuth, remotePlayers[t]->getPosition()) <= 30.0f)))
                {
                    target = remotePlayers[t];
                    distance = d;
                }
            }
        }
    }

    return target;
}

static bool chasePlayer(float &rotation, float &speed)
{
    LocalPlayer *myTank = LocalPlayer::getMyTank();
    const float *pos = myTank->getPosition();

    RemotePlayer *rPlayer = findBestTarget();
    if (rPlayer == NULL)
        return false;

    myTank->setTarget(rPlayer);

    const float *targetPos = rPlayer->getPosition();
    float distance = TargetingUtils::getTargetDistance(pos, targetPos);
    if (distance > 250.0f)
        return false;

    const float *tp = rPlayer->getPosition();
    float enemyPos[3];
    //toss in some lag adjustment/future prediction - 300 millis
    memcpy(enemyPos,tp,sizeof(enemyPos));
    const float *tv = rPlayer->getVelocity();
    enemyPos[0] += 0.3f * tv[0];
    enemyPos[1] += 0.3f * tv[1];
    enemyPos[2] += 0.3f * tv[2];
    if (enemyPos[2] < 0.0f) //Roger doesn't worry about burrow
        enemyPos[2] = 0.0;

    float myAzimuth = myTank->getAngle();
    float enemyAzimuth = TargetingUtils::getTargetAzimuth( pos, tp );
    rotation = TargetingUtils::getTargetRotation( myAzimuth, enemyAzimuth );

    //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)

        float dir[3] = {cosf(myAzimuth), sinf(myAzimuth), 0.0f};
        Ray tankRay(pos, dir);

        building = ShotStrategy::getFirstBuilding(tankRay, -0.5f, d);
        if (building && !myTank->isPhantomZoned() &&
                (myTank->getFlag() != Flags::OscillationOverthruster))
        {
            //If roger can drive around it, just do that

            float leftDistance = TargetingUtils::getOpenDistance( pos, (float)(myAzimuth + (M_PI/6.0)));
            if (leftDistance > (2.0f * d))
            {
                speed = 0.5f;
                rotation = -0.5f;
                return true;
            }
            float rightDistance = TargetingUtils::getOpenDistance( pos, (float)(myAzimuth - (M_PI/6.0)));
            if (rightDistance > (2.0f * d))
            {
                speed = 0.5f;
                rotation = 0.5f;
                return true;
            }

            //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::getClassName()))
            {
                float jumpVel = BZDB.eval(StateDatabase::BZDB_JUMPVELOCITY);
                float maxJump = (jumpVel * jumpVel) / (2 * -BZDBCache::gravity);

                if (((building->getPosition()[2] - pos[2] + building->getHeight())) < maxJump)
                {
                    speed = d / 50.0f;
                    wantJump = true;
                    return true;
                }
            }
        }
    }

    // weave towards the player
    const Player *target = myTank->getTarget();
    if ((distance > (BZDB.eval(StateDatabase::BZDB_SHOTSPEED) /2.0f))
            ||  (myTank->getFiringStatus() != LocalPlayer::Ready))
    {
        float enemyUnitVec[2] = { cosf(enemyAzimuth), sinf(enemyAzimuth) };
        float myUnitVec[2] = { cosf(myAzimuth), sinf(myAzimuth) };
        float dotProd = (myUnitVec[0] * enemyUnitVec[0] + myUnitVec[1] * enemyUnitVec[1]);
        if (dotProd < 0.866f)
        {
            //if target is more than 30 degrees away, turn as fast as you can
            rotation *= (float)M_PI / (2.0f * fabs(rotation));
            speed = dotProd; //go forward inverse rel to how much you need to turn
        }
        else
        {
            int period = int(TimeKeeper::getTick().getSeconds());
            float absBias = (float)(M_PI/20.0 * (distance / 100.0));
            float bias = ((period % 4) < 2) ? absBias : -absBias;
            rotation += bias;
            rotation = TargetingUtils::normalizeAngle(rotation);
            speed = 1.0;
        }
    }
    else if (target->getFlag() != Flags::Burrow)
    {
        speed = -0.5f;
        rotation *= (float)(M_PI / (2.0 * fabs(rotation)));
    }

    return true;
}

static bool lookForFlag(float &rotation, float &speed)
{
    LocalPlayer *myTank = LocalPlayer::getMyTank();
    float pos[3];

    memcpy( pos, myTank->getPosition(), sizeof( pos ));
    if (pos[2] < 0.0f)
        pos[2] = 0.0f;
    World *world = World::getWorld();
    int closestFlag = -1;

    if ((myTank->getFlag() != Flags::Null)
            && (isFlagUseful(myTank->getFlag())))
        return false;

    float minDist = Infinity;
    int teamFlag = -1;
    for (int i = 0; i < numFlags; i++)
    {
        if ((world->getFlag(i).type == Flags::Null)
                || (world->getFlag(i).status != FlagOnGround))
            continue;

        if (world->getFlag(i).type->flagTeam != NoTeam)
            teamFlag = i;
        const float* fpos = world->getFlag(i).position;
        if (fpos[2] == pos[2])
        {
            float dist = TargetingUtils::getTargetDistance(pos, fpos);
            bool isTargetObscured = TargetingUtils::isLocationObscured(pos, fpos);
            if (isTargetObscured)
                dist *= 1.25f;

            if ((dist < 200.0f) && (dist < minDist))
            {
                minDist = dist;
                closestFlag = i;
            }
        }
    }

    if (teamFlag != -1 && (minDist < 10.0f || closestFlag == -1))
        closestFlag = teamFlag; //FIXME: should a team flag be more significant than a closer flag?
    if (closestFlag != -1)
    {
        if (minDist < 10.0f)
        {
            if (myTank->getFlag() != Flags::Null)
            {
                serverLink->sendDropFlag(myTank->getPosition());
                handleFlagDropped(myTank);
            }
        }

        const float *fpos = world->getFlag(closestFlag).position;
        float myAzimuth = myTank->getAngle();
        float flagAzimuth = TargetingUtils::getTargetAzimuth(pos, fpos);
        rotation = TargetingUtils::getTargetRotation(myAzimuth, flagAzimuth);
        speed = (float)(M_PI/2.0 - fabs(rotation));
        return true;
    }

    return false;
}

static bool navigate(float &rotation, float &speed)
{
    static TimeKeeper lastNavChange;
    static float navRot = 0.0f, navSpeed = 0.0f;

    if ((TimeKeeper::getTick() - lastNavChange) < 1.0f)
    {
        rotation = navRot;
        speed = navSpeed;
        return true;
    }

    LocalPlayer *myTank = LocalPlayer::getMyTank();
    float pos[3];

    memcpy(pos, myTank->getPosition(), sizeof(pos));
    if (pos[2] < 0.0f)
        pos[2] = 0.01f;
    float myAzimuth = myTank->getAngle();

    float leftDistance = TargetingUtils::getOpenDistance(pos, (float)(myAzimuth + (M_PI/4.0)));
    float centerDistance = TargetingUtils::getOpenDistance(pos, myAzimuth);
    float rightDistance = TargetingUtils::getOpenDistance(pos, (float)(myAzimuth - (M_PI/4.0)));
    if (leftDistance > rightDistance)
    {
        if (leftDistance > centerDistance)
            rotation = 0.75f;
        else
            rotation = 0.0f;
    }
    else
    {
        if (rightDistance > centerDistance)
            rotation = -0.75f;
        else
            rotation = 0.0f;
    }
    if (myTank->getFlag()->flagTeam != NoTeam)
    {
        World *world = World::getWorld();
        const float *temp = world->getBase(myTank->getTeam());
        if (temp == NULL)
        {
            serverLink->sendDropFlag(myTank->getPosition());
            handleFlagDropped(myTank);
        }
        else
        {
            if ((((int) *(world->getBase(myTank->getTeam())) + 2
                    >= (int) *(myTank->getPosition()))
                    || (temp[0] == pos[0] && temp[1] == pos[1])) &&
                    myTank->getFlag()->flagTeam == myTank->getTeam())
            {
                serverLink->sendDropFlag(myTank->getPosition());
                handleFlagDropped(myTank);
            }
            else
            {
                float baseAzimuth = TargetingUtils::getTargetAzimuth(pos, temp);
                rotation = TargetingUtils::getTargetRotation(myAzimuth, baseAzimuth);
                speed = (float)(M_PI/2.0 - fabs(rotation));
            }
        }
    }
    else
        speed = 1.0f;
    if (myTank->getLocation() == LocalPlayer::InAir
            && myTank->getFlag() == Flags::Wings)
        wantJump = true;

    navRot = rotation;
    navSpeed = speed;
    lastNavChange = TimeKeeper::getTick();
    return true;
}

static bool fireAtTank()
{
    static TimeKeeper lastShot;
    float pos[3];
    LocalPlayer *myTank = LocalPlayer::getMyTank();
    memcpy(pos, myTank->getPosition(), sizeof(pos));
    if (pos[2] < 0.0f)
        pos[2] = 0.01f;
    float myAzimuth = myTank->getAngle();

    float dir[3] = {cosf(myAzimuth), sinf(myAzimuth), 0.0f};
    pos[2] += myTank->getMuzzleHeight();
    Ray tankRay(pos, dir);
    pos[2] -= myTank->getMuzzleHeight();

    if (myTank->getFlag() == Flags::ShockWave)
    {
        TimeKeeper now = TimeKeeper::getTick();
        if (now - lastShot >= (1.0f / World::getWorld()->getMaxShots()))
        {
            bool hasSWTarget = false;
            for (int t = 0; t < curMaxPlayers; t++)
            {
                if (t != myTank->getId() && remotePlayers[t] &&
                        remotePlayers[t]->isAlive() && !remotePlayers[t]->isPaused() &&
                        !remotePlayers[t]->isNotResponding())
                {

                    const float *tp = remotePlayers[t]->getPosition();
                    float enemyPos[3];
                    //toss in some lag adjustment/future prediction - 300 millis
                    memcpy(enemyPos,tp,sizeof(enemyPos));
                    const float *tv = remotePlayers[t]->getVelocity();
                    enemyPos[0] += 0.3f * tv[0];
                    enemyPos[1] += 0.3f * tv[1];
                    enemyPos[2] += 0.3f * tv[2];
                    if (enemyPos[2] < 0.0f)
                        enemyPos[2] = 0.0f;
                    float dist = TargetingUtils::getTargetDistance( pos, enemyPos );
                    if (dist <= BZDB.eval(StateDatabase::BZDB_SHOCKOUTRADIUS))
                    {
                        if (!myTank->validTeamTarget(remotePlayers[t]))
                        {
                            hasSWTarget = false;
                            t = curMaxPlayers;
                        }
                        else
                            hasSWTarget = true;
                    }
                }
            }
            if (hasSWTarget)
            {
                myTank->fireShot();
                lastShot = TimeKeeper::getTick();
                return true;
            }
        }
    }
    else
    {
        TimeKeeper now = TimeKeeper::getTick();
        if (now - lastShot >= (1.0f / World::getWorld()->getMaxShots()))
        {

            float errorLimit = World::getWorld()->getMaxShots() * BZDB.eval(StateDatabase::BZDB_LOCKONANGLE) / 8.0f;
            float closeErrorLimit = errorLimit * 2.0f;

            for (int t = 0; t < curMaxPlayers; t++)
            {
                if (t != myTank->getId() && remotePlayers[t] &&
                        remotePlayers[t]->isAlive() && !remotePlayers[t]->isPaused() &&
                        !remotePlayers[t]->isNotResponding() &&
                        myTank->validTeamTarget(remotePlayers[t]))
                {

                    if (remotePlayers[t]->isPhantomZoned() && !myTank->isPhantomZoned()
                            && (myTank->getFlag() != Flags::SuperBullet)
                            && (myTank->getFlag() != Flags::ShockWave))
                        continue;

                    const float *tp = remotePlayers[t]->getPosition();
                    float enemyPos[3];
                    //toss in some lag adjustment/future prediction - 300 millis
                    memcpy(enemyPos,tp,sizeof(enemyPos));
                    const float *tv = remotePlayers[t]->getVelocity();
                    enemyPos[0] += 0.3f * tv[0];
                    enemyPos[1] += 0.3f * tv[1];
                    enemyPos[2] += 0.3f * tv[2];
                    if (enemyPos[2] < 0.0f)
                        enemyPos[2] = 0.0f;

                    float dist = TargetingUtils::getTargetDistance( pos, enemyPos );

                    if ((myTank->getFlag() == Flags::GuidedMissile) || (fabs(pos[2] - enemyPos[2]) < 2.0f * BZDBCache::tankHeight))
                    {

                        float targetDiff = TargetingUtils::getTargetAngleDifference(pos, myAzimuth, enemyPos );
                        if ((targetDiff < errorLimit)
                                ||  ((dist < (2.0f * BZDB.eval(StateDatabase::BZDB_SHOTSPEED))) && (targetDiff < closeErrorLimit)))
                        {
                            bool isTargetObscured;
                            if (myTank->getFlag() != Flags::SuperBullet)
                                isTargetObscured = TargetingUtils::isLocationObscured( pos, enemyPos );
                            else
                                isTargetObscured = false;

                            if (!isTargetObscured)
                            {
                                myTank->fireShot();
                                lastShot = now;
                                t = curMaxPlayers;
                                return true;
                            }
                        }
                    }
                }
            }
        }
    }

    return false;
}

static void dropHardFlags()
{
    LocalPlayer *myTank = LocalPlayer::getMyTank();
    FlagType *type = myTank->getFlag();
    if ((type == Flags::Useless)
            ||  (type == Flags::MachineGun)
            ||  (type == Flags::Identify)
            ||  ((type == Flags::PhantomZone) && !myTank->isFlagActive()))
    {
        serverLink->sendDropFlag(myTank->getPosition());
        handleFlagDropped(myTank);
    }
}

void    doAutoPilot(float &rotation, float &speed)
{
    wantJump = false;

    dropHardFlags(); //Perhaps we should remove this and let learning do it's work
    if (!avoidBullet(rotation, speed))
    {
        if (!stuckOnWall(rotation, speed))
        {
            if (!chasePlayer(rotation, speed))
            {
                if (!lookForFlag(rotation, speed))
                    navigate(rotation, speed);
            }
        }
    }

    avoidDeathFall(rotation, speed);

    LocalPlayer *myTank = LocalPlayer::getMyTank();
    myTank->setJumpPressed(wantJump);
    myTank->setJump();

    fireAtTank();
}


// Local Variables: ***
// mode: C++ ***
// tab-width: 4 ***
// c-basic-offset: 4 ***
// indent-tabs-mode: nil ***
// End: ***
// ex: shiftwidth=4 tabstop=4
