private void executeAction(Action action) {
    actionFireMode = action.getShootingMode();

    if (Action.isProhibited(action)) {
      return;
    }

    int wpind = action.actionToInventoryIndex();
    if (bot.getCurrentWeaponIndex() != wpind) {
      bot.changeWeaponToIndex(wpind);
      //            if (bot.botHasItem(wpind)) System.out.println("chng wpn to:
      // "+CommFun.getGunName(wpind));
    }
  }
/**
 * The aiming module
 *
 * @author Piotr Gwizda�a
 */
public class RLCombatModule extends Perception {

  RLBot bot;
  double actionReward = 0;
  int actionTime = 10;
  int actionEndFrame = -1;
  int actionFireMode = Action.NO_FIRE;
  public TreeMap<Integer, WeaponScore> weaponRanking = new TreeMap<Integer, WeaponScore>();

  public static class WeaponScore implements Comparable<WeaponScore> {

    private double rewardsTotal;
    private int rewardsCount;

    public WeaponScore(double rewardsTotal, int rewardsCount) {
      this.rewardsTotal = rewardsTotal;
      this.rewardsCount = rewardsCount;
    }

    double getScore() {
      return rewardsTotal / rewardsCount;
    }

    public int compareTo(WeaponScore o) {
      double t = getScore();
      double a = o.getScore();
      if (t > a) {
        return -1;
      }
      if (t < a) {
        return 1;
      }
      return 0;
    }
  }

  class Shooting {

    long shotTime = 0;
    long hitTime = 0;
    String enemyName = null;
    double reward = 0;

    public Shooting(long shotTime, long hitTime, String enemyName) {
      this.shotTime = shotTime;
      this.hitTime = hitTime;
      this.enemyName = enemyName;
    }
  }

  LinkedList<Shooting> shootings = new LinkedList<Shooting>();
  Action[] actions = Action.getAllActionsArray();
  public static final int maxShootingsCount = 100;
  public Brain brain;
  double actionDistance = 0;
  boolean unipolar = true;

  public RLCombatModule(RLBot bot) {
    this.bot = bot;
    brain = new Brain(this, actions);

    brain.setAlpha(0.8); // learning rate
    brain.setGamma(0.3); // discounting rate
    brain.setLambda(0.6); // trace forgetting
    //        b.setUseBoltzmann(true);
    //        b.setTemperature(0.001);
    brain.setRandActions(0.1); // exploration

    //        brain.setAlpha(getRandParam(0.1, 0.8, 0.1)); //learning rate
    //        brain.setGamma(getRandParam(0, 0.9, 0.2)); //discounting rate
    //        brain.setLambda(getRandParam(0, 0.9, 0.2)); //trace forgetting
    ////        b.setUseBoltzmann(true);
    ////        b.setTemperature(0.001);
    //        brain.setRandActions(getRandParam(0.01, 0.4, 0.1)); //exploration
    //        unipolar = Rand.b();

    for (int i = 7; i < 18; i++) {
      weaponRanking.put(i, new WeaponScore(0.5, 1));
    }
  }

  double getRandParam(double from, double to, double quant) {
    int count = (int) ((to - from) / quant) + 1;
    Random r = new Random();
    return from + r.nextInt(count) * quant;
  }

  @Override
  public String toString() {
    String s =
        "alpha="
            + brain.getAlpha()
            + " gamma="
            + brain.getGamma()
            + "\nlambda="
            + brain.getLambda()
            + " rand="
            + brain.getRandActions()
            + "\nunip="
            + isUnipolar()
            + "\n";

    TreeMap<Integer, WeaponScore> newRanking = new TreeMap<Integer, WeaponScore>();
    for (Map.Entry<Integer, WeaponScore> e : weaponRanking.entrySet()) {
      newRanking.put(e.getKey(), e.getValue());
    }
    weaponRanking = newRanking;

    for (Map.Entry<Integer, WeaponScore> e : weaponRanking.entrySet()) {
      s += "\n" + CommFun.getGunName(e.getKey()) + " : " + e.getValue().getScore();
    }
    return s;
  }

  public FiringDecision getFiringDecision() {
    Vector3f playerPos = bot.getBotPosition();
    EnemyInfo chosen = null;
    float chosenRisk = Float.MAX_VALUE;
    float maxDist = -1;
    float maxError = 0;
    LinkedList<EnemyInfo> eligible = new LinkedList<EnemyInfo>();
    for (EnemyInfo ei : bot.kb.enemyInformation.values()) {

      if (ei.getBestVisibleEnemyPart(bot) == null) {
        continue;
      }

      if (ei.lastUpdateFrame + bot.cConfig.maxEnemyInfoAge4Firing < bot.getFrameNumber()) {
        continue;
      }

      float dist = CommFun.getDistanceBetweenPositions(playerPos, ei.getObjectPosition());
      float error = ei.lastPredictionError;

      if (dist > maxDist) {
        maxDist = dist;
      }
      if (error > maxError) {
        maxError = error;
      }

      eligible.add(ei);
    }

    for (EnemyInfo ei : eligible) {
      float dist = CommFun.getDistanceBetweenPositions(playerPos, ei.getObjectPosition());
      float error = ei.lastPredictionError;
      float risk = dist / maxDist + error / maxError;
      if (chosenRisk > risk) {
        chosenRisk = risk;
        chosen = ei;
      }
    }

    if (chosen == null) {
      return null;
    }
    actionDistance = CommFun.getDistanceBetweenPositions(playerPos, chosen.getObjectPosition());
    return new FiringDecision(chosen, -1);
  }

  /**
   * Returns the firing instructions
   *
   * @param fd firing decision
   * @param bot the bot
   * @return
   */
  public FiringInstructions getFiringInstructions(FiringDecision fd) {
    if (fd == null || fd.enemyInfo == null) {
      return null;
    }

    updateRewards();

    boolean reloading = bot.getWorld().getPlayer().getPlayerGun().isCoolingDown();
    Vector3f noFiringLook =
        fd.enemyInfo.predictedPos == null
            ? fd.enemyInfo.getObjectPosition()
            : fd.enemyInfo.predictedPos;
    if (reloading) {
      return getNoFiringInstructions(noFiringLook);
    }

    shootings.add(
        new Shooting(
            bot.getFrameNumber(),
            bot.getFrameNumber()
                + (long)
                    getTimeToHit(
                        bot.cConfig.getBulletSpeedForGivenGun(bot.getCurrentWeaponIndex()),
                        bot.getBotPosition(),
                        fd.enemyInfo.getObjectPosition(),
                        fd.enemyInfo.predictedPos),
            fd.enemyInfo.ent.getName()));

    if (shootings.size() > maxShootingsCount) {
      shootings.removeFirst();
    }

    if (actionEndFrame == -1) {
      // first time
    }
    if (bot.getFrameNumber() >= actionEndFrame) {
      actionEndFrame = bot.getFrameNumber() + actionTime;
      // count rewards:
      bot.rewardsCount++;
      if (actionReward <= 1 && actionReward >= -1) {
        bot.totalReward += actionReward;
      } else {
        bot.totalReward += (actionReward > 0) ? 1 : -1;
        System.out.println("excessive reward! " + actionReward);
      }

      int rc = 1;
      double rwd = actionReward;
      int cwpi = bot.getCurrentWeaponIndex();
      if (weaponRanking.get(cwpi) != null) {
        rc = weaponRanking.get(cwpi).rewardsCount + 1;
        rwd = weaponRanking.get(cwpi).rewardsTotal + actionReward;
      }
      weaponRanking.put(cwpi, new WeaponScore(rwd, rc));

      BotStatistic.getInstance().addReward(bot.getBotName(), actionReward, bot.getFrameNumber());
      //            Dbg.prn(bot.getBotName()+"@"+bot.getFrameNumber()+" r="+actionReward);
      // choose new action and execute it
      perceive();
      brain.count();
      executeAction(actions[brain.getAction()]);
      actionReward = 0;
    }

    //        return getFiringInstructionsAtHitpoint(fd, 1);

    //        return getNewPredictingFiringInstructions(bot, fd,
    // bot.cConfig.getBulletSpeedForGivenGun(bot.getCurrentWeaponIndex()));

    switch (actionFireMode) {
      case Action.FIRE:
        return getFastFiringInstructions(fd, bot);
      case Action.FIRE_PREDICTED:
        return getNewPredictingFiringInstructions(
            bot, fd, bot.cConfig.getBulletSpeedForGivenGun(bot.getCurrentWeaponIndex()));
      default:
      case Action.NO_FIRE:
        return getNoFiringInstructions(noFiringLook);
    }
  }

  FiringInstructions getNoFiringInstructions(Vector3f enemyPos) {
    FiringInstructions ret =
        new FiringInstructions(
            CommFun.getNormalizedDirectionVector(bot.getBotPosition(), enemyPos));
    ret.doFire = false;
    return ret;
  }

  public static FiringInstructions getNewPredictingFiringInstructions(
      MapBotBase bot, FiringDecision fd, float bulletSpeed) {
    Vector3f playerPos = bot.getBotPosition();
    Vector3f enemyPos = fd.enemyInfo.getBestVisibleEnemyPart(bot);

    // Calculate the time to hit
    double timeToHit = getTimeToHit(bulletSpeed, playerPos, enemyPos, fd.enemyInfo.predictedPos);
    if (timeToHit <= 1.5) {
      timeToHit = 1f;
    }

    // We add to enemy position the movement that the enemy is predicted to do in timeToHit.
    Vector3f hitPoint = CommFun.cloneVector(enemyPos);
    // movement is between bot position, not the visible part of the bot
    Vector3f movement =
        CommFun.getMovementBetweenVectors(
            fd.enemyInfo.getObjectPosition(), fd.enemyInfo.predictedPos);
    movement = CommFun.multiplyVectorByScalar(movement, (float) timeToHit);
    hitPoint.add(movement);

    return new FiringInstructions(CommFun.getNormalizedDirectionVector(playerPos, hitPoint));
  }

  /**
   * * Point the enemy and shoot.
   *
   * @param fd
   * @param playerPos
   * @return
   */
  public static FiringInstructions getFastFiringInstructions(FiringDecision fd, MapBotBase bot) {
    Vector3f to = new Vector3f(fd.enemyInfo.getBestVisibleEnemyPart(bot));
    Vector3f fireDir = CommFun.getNormalizedDirectionVector(bot.getBotPosition(), to);
    //		bot.dtalk.addToLog("Fast firing.");
    return new FiringInstructions(fireDir);
  }

  public static double getTimeToHit(
      double bulletSpeed, Vector3f playerPos, Vector3f enemyPos, Vector3f enemyPredictedPos) {

    double d = CommFun.getDistanceBetweenPositions(playerPos, enemyPos);
    double v = bulletSpeed;
    double u = CommFun.getDistanceBetweenPositions(enemyPos, enemyPredictedPos);

    Vector3f vec1 = CommFun.getNormalizedDirectionVector(playerPos, enemyPos);
    Vector3f vec2 = CommFun.getNormalizedDirectionVector(enemyPos, enemyPredictedPos);

    double alpha = Math.toRadians(vec1.angle(vec2));

    double delta =
        4 * d * d * u * u * Math.cos(alpha) * Math.cos(alpha) + 4 * (v * v - u * u) * d * d;

    double t = (2 * d * u * Math.cos(alpha) + Math.sqrt(delta)) / (2 * (v * v - u * u));

    double t2 = (2 * d * u * Math.cos(alpha) - Math.sqrt(delta)) / (2 * (v * v - u * u));

    //                System.out.println("t="+t+" v="+v+" d="+d+" u="+u+" t2="+t2);

    if (Double.isNaN(t)) {
      t = 1;
    }

    return t;
  }

  private void executeAction(Action action) {
    actionFireMode = action.getShootingMode();

    if (Action.isProhibited(action)) {
      return;
    }

    int wpind = action.actionToInventoryIndex();
    if (bot.getCurrentWeaponIndex() != wpind) {
      bot.changeWeaponToIndex(wpind);
      //            if (bot.botHasItem(wpind)) System.out.println("chng wpn to:
      // "+CommFun.getGunName(wpind));
    }
  }

  public void updateRewards() {
    if (bot.scoreCounter.getBotScore() > bot.lastBotScore) {
      bot.lastBotScore = bot.scoreCounter.getBotScore();
      // TODO: moze dac to do innego shootingu? Np. tego ktory ma hitpoint na teraz
      actionReward += 1;
    }
    //            Dbg.prn("");
    LinkedList<Shooting> toDelete = new LinkedList<Shooting>();
    for (Shooting s : shootings) {

      int damage = HitsReporter.wasHitInGivenPeriod(s.shotTime + 1, s.hitTime + 2, s.enemyName);
      if (damage > 0) {
        actionReward += (damage / 100d) * 0.2;
        toDelete.add(s);
        //                System.out.println(bot.getBotName()+" got shooting rwd - "+actionReward);
      } else if (s.hitTime + 4 < bot.getFrameNumber()) {
        toDelete.add(s);
        //                System.out.println(bot.getBotName()+" missed! - "+actionReward);
        //                actionReward -= 0.001;
      }
    }
    shootings.removeAll(toDelete);
    return;
  }

  @Override
  public boolean isUnipolar() {
    return unipolar;
  }

  @Override
  public double getReward() {
    return actionReward;
  }

  @Override
  protected void updateInputValues() {
    setNextValue(actionDistance);
    for (int i = 7; i < 18; i++) {
      setNextValue(bot.botHasItem(i));
    }
  }
}