//-------------------------------------------------------------------
//
//  This code is copyright 2001 by G5 Software.
//  Any unauthorized usage, either in part or in whole of this code
//  is strictly prohibited. Violators WILL be prosecuted to the
//  maximum extent allowed by law.
//
//-------------------------------------------------------------------


class CBaseAITask extends CBaseUtilities
{
  //
  // *** 'virtual' functions ***
  //
  // All functions do nothing by default. Overwrite if needed
  //

  // group unit is registered in (current, active...)
  Component m_Group = null;
  String  m_CurrentEnemy = "";
  String  m_LastEnemy = "";
  int     m_EnemyReactionType = ERT_DEFENSIVE;
  int     m_NextPatrolPoint = 0;
  int     m_TotalPatrolPoints = 0;
  boolean m_InSpecialAttack = false;  // in special attack unit does not react to group events and orders
  boolean m_IsTransport = false;
//  float   m_TransportRunOffDistance = 20.0;

  int     m_FireStyle = 1; // (0 - NoFire, 1 - Nearest, 2 - Enemy)

  boolean m_HasOrder          = false; // used for walkaround units like civilians and soldiers

  boolean m_Anchored          = false;
  String  m_AnchorID          = "";
  float   m_AnchorDistanceMax = 0.0;

  String  m_TargetID          = "";   // used only in KamikazeAttack by now

  boolean m_KamikazeNP        = false;

  String  m_HomePointID       = "";   // multi-use, currently used by CWingmanTask

  Array   m_PersonalWeaponPool = [];  // item format: [ <name>, <component> ]
  Array   m_GroupWeaponPool    = [];  // item format: [ <unitID>, <weapon component> ]

  //
  // start up initialization

  void Init() {}            // called by CVehicleBehavior after assign

  void InitPersonalWeaponPool()
  {
    Component Self = (new #GameController()).GetObject(GetIdentificator());

    m_PersonalWeaponPool.clear();

//    logWarning(getIdentificator(user) + ".InitPersonalWeaponPool()");
    for (int i = 0; i < Self.m_WeaponList.size(); i++)
    {
      if ((Self.m_WeaponList[i].CanFire) && !Self.m_WeaponList[i].SpecialWeapon)
//      {
//        logMessage("    Adding weapon " + Self.m_WeaponNames[i]);
        m_PersonalWeaponPool.add([Self.m_WeaponNames[i], Self.m_WeaponList[i]]);
/*      }
      else
      {
          logMessage("    Skipping weapon " + Self.m_WeaponNames[i]);
      }*/
    }
//    logMessage("  Personal weapon pool size is " + new String(m_PersonalWeaponPool.size()));
  }

  event void OnAmmoChanged(
      Component _Weapon,
      float     _AmmoQty,
      boolean   _Unlimited
   )
  {
    if (_AmmoQty != 0)
      return;

    for (int i = 0; i < m_PersonalWeaponPool.size(); i++)
      if (m_PersonalWeaponPool[i][1] == _Weapon)
        m_PersonalWeaponPool.remove(i);
  }

  void AddToGroupWeaponPool(
      String _UnitID
   )
  {
    // first remove all weapons of this unit from pool
    RemoveFromGroupWeaponPool(_UnitID);

    Component Task = GetUnitTask(_UnitID);

    if (Task != null)
    {
      for (int j = 0; j < Task.m_PersonalWeaponPool.size(); j++)
      {
        m_GroupWeaponPool.add([_UnitID, Task.m_PersonalWeaponPool[j][1]]);
      }
    }
  }

  void RemoveFromGroupWeaponPool(
      String _UnitID
   )
  {
    for (int i = m_GroupWeaponPool.size()-1; i >= 0; i--)
    {
      if (m_GroupWeaponPool[i][0] == _UnitID)
          m_GroupWeaponPool.remove(i);
    }
  }

  boolean CheckGunFireMask(
      Component _Weapon,
      Component _Enemy,
      boolean   _Anyhow
   )
  {
    for (int i = 0; i < _Weapon.GunSpecificFireMask.size(); i++)
    {
      if ((_Weapon.GunSpecificFireMask[i][0].size() == 0)
          && (_Weapon.GunSpecificFireMask[i][1].size() == 0))
      {
//        logMessage("+++   Anyhow mask met");
        return _Anyhow;
      }

      if (checkMask(_Enemy,
          _Weapon.GunSpecificFireMask[i][0],
          _Weapon.GunSpecificFireMask[i][1]))
      {
//        logMessage("+++   Weapon suits the enemy");
        return true;
      }
    }

//    logMessage("+++   Weapon is not good for enemy");
    return false;
  }

  boolean CheckWeaponPower(
      Component _Weapon,
      String    _TargetHealth
   )
  {
//    logMessage("*** Checking weapon power against target health (" + _TargetHealth + ")");
    for (int i = 0; i < _Weapon.Power.size(); i++)
    {
//      logMessage("***   Power[" + new String(i) + "] = " + _Weapon.Power[i]);

      if (_Weapon.Power[i] == _TargetHealth)
      {
          return true;
      }
    }

    return false;
  }

  boolean CanAttackPersonally(
      String _EnemyID
   )
  {
    Component Enemy = GetMission().GetObject(_EnemyID);

    if (Enemy != null)
    {
      String Elemental = Enemy.GetElemental();
      String Health = Enemy.GetHealthParameter();

//      logWarning(getIdentificator(user) + ".CanAttackPersonally() with enemy " + _EnemyID + " [" + Elemental + ", " + Health + "]");

      if (m_PersonalWeaponPool.size() == 0)
          logError(getIdentificator(user) + ": personal weapon pool is empty");

      // search for weapon with primary mask fittable
//      logMessage("Search for primary weapon mask fit");
      for (int i = 0; i < m_PersonalWeaponPool.size(); i++)
      {
//        logMessage("  Checking weapon " + m_PersonalWeaponPool[i][0] +", masks are:");
//        logMessage("    AmmoQuantity: " + new String(m_PersonalWeaponPool[i][1].GetAmmoQuantity()));
//        logMessage("    RestrictTo: " + new String(m_PersonalWeaponPool[i][1].GunSpecificFireMask[0][0]));
//        logMessage("    Exclude:    " + new String(m_PersonalWeaponPool[i][1].GunSpecificFireMask[0][1]));
//        logMessage("    Weapon power: " + new String(m_PersonalWeaponPool[i][1].Power));

        if(m_PersonalWeaponPool[i][1].GetAmmoQuantity() != 0)
        {
//          logMessage("    Weapon has some ammo, good");

          if (CheckGunFireMask(m_PersonalWeaponPool[i][1], Enemy, false))
          {
//            logMessage("    Mask check successful");

            if (CheckWeaponPower(m_PersonalWeaponPool[i][1], Health))
            {
//              logMessage("    Weapon " + m_PersonalWeaponPool[i][0] + " can do the job");
              return true;
            }
//            else
//              logMessage("    Skipping weapon " + m_PersonalWeaponPool[i][0]);
          }
//          else
//          {
//            logMessage("    Mask check failed");
//          }
        }
//        else
//          logMessage("    Weapon is out of ammo");
      }

      // search for weapon with anyhow mask fittable
//      logMessage("Search for anyhow weapon mask fit");
      for (int i = 0; i < m_PersonalWeaponPool.size(); i++)
      {
//        logMessage("  Checking weapon " + m_PersonalWeaponPool[i][0] +", masks are:");
//        logMessage("    AmmoQuantity: " + new String(m_PersonalWeaponPool[i][1].GetAmmoQuantity()));
//        logMessage("    RestrictTo: " + new String(m_PersonalWeaponPool[i][1].GunSpecificFireMask[0][0]));
//        logMessage("    Exclude:    " + new String(m_PersonalWeaponPool[i][1].GunSpecificFireMask[0][1]));
//        logMessage("    Weapon power: " + new String(m_PersonalWeaponPool[i][1].Power));

        if (m_PersonalWeaponPool[i][1].GetAmmoQuantity() != 0)
        {
//          logMessage("    Weapon has some ammo, good");

          if (CheckGunFireMask(m_PersonalWeaponPool[i][1], Enemy, true))
          {
//            logMessage("    Mask check successful");

            if (CheckWeaponPower(m_PersonalWeaponPool[i][1], Health))
            {
//              logMessage("    Weapon " + m_PersonalWeaponPool[i][0] + " can do the job");
              return true;
            }
//            else
//              logMessage("    Skipping weapon " + m_PersonalWeaponPool[i][0]);
          }
//          else
//          {
//            logMessage("    Mask check failed");
//          }
        }
//        else
//          logMessage("    Weapon is out of ammo");
      }

    }

    return false;
  }

  boolean CanAttackAnyhow(
      String _EnemyID
   )
  {
    Component Enemy = GetMission().GetObject(_EnemyID);

    if (Enemy != null)
    {
      String Elemental = Enemy.GetElemental();

//      logWarning(getIdentificator(user) + ".CanAttackAnyhow(" + _EnemyID + "), enemy is " + Elemental);

      for (int i = 0; i < m_PersonalWeaponPool.size(); i++)
      {
        for (int j = 0; j < m_PersonalWeaponPool[i][1].GunSpecificFireMask.size(); j++)
        {
/*          logMessage("  Checking weapon " + m_PersonalWeaponPool[i][0] +", masks are:");
          logMessage("    RestrictTo: " + new String(m_PersonalWeaponPool[i][1].GunSpecificFireMask[0][0]));
          logMessage("    Exclude:    " + new String(m_PersonalWeaponPool[i][1].GunSpecificFireMask[0][1]));
*/
          if (checkMask(Enemy,
              m_PersonalWeaponPool[i][1].GunSpecificFireMask[j][0],
              m_PersonalWeaponPool[i][1].GunSpecificFireMask[j][1]))
//          {
//            logMessage("    Weapon " + m_PersonalWeaponPool[i][0] + " can do the job on " + new String(j) + " priority level");
            return true;
/*          }
          else
          {
            logMessage("    Skipping weapon " + m_PersonalWeaponPool[i][0]);
          }*/
        }
      }
    }

    return false;
  }

  // in respect to current group weapon pool
  boolean CanAttackGroup(
      String _EnemyID
   )
  {
    Component Enemy = GetMission().GetObject(_EnemyID);

    if (Enemy != null)
    {
      String Elemental = Enemy.GetElemental();
      String Health = Enemy.GetHealthParameter();

//      logWarning(getIdentificator(user) + ".CanAttackGroup(" + _EnemyID + "), enemy is [" + Elemental + ", " + Health + "]");
      for (int i = 0; i < m_GroupWeaponPool.size(); i++)
      {
/*        logMessage("  Checking weapon of unit " + m_GroupWeaponPool[i][0] + ", masks are:");
        logMessage("    RestrictTo: " + new String(m_GroupWeaponPool[i][1].GunSpecificFireMask[0][0]));
        logMessage("    Exclude:    " + new String(m_GroupWeaponPool[i][1].GunSpecificFireMask[0][1]));
*/
        if (checkMask(Enemy,
            m_GroupWeaponPool[i][1].GunSpecificFireMask[0][0],
            m_GroupWeaponPool[i][1].GunSpecificFireMask[0][1])
          && (m_GroupWeaponPool[i][1].Power == Health))
//        {
//          logMessage("    This weapon can do the job");
          return true;
/*        }
        else
        {
          logMessage("    Skipping weapon");
        }*/
      }
    }

    return false;
  }

  void SetTransportUnit(boolean _On)
  {
    m_IsTransport = _On;
  }

  //
  // events called by state control
  //

  void OnDestroyed()
  {
    if (m_Group != null)
      m_Group.OnUnitDestroyed(getIdentificator(user));
  }

  //
  // events called by Behavior

  void OnFriendTargeted() // called when friend is entering empty radar
  {
  }

  void OnEnemyTargeted() // called when enemy targeted by a gun
  {
    if (m_InSpecialAttack)
        return;

    if (m_EnemyReactionType == ERT_FRIGID)
        return;

    String TargetedEnemy = GetTargetedEnemy();

    if (m_Group != null)
    {
      if (m_Group.m_EnemyReactionType == ERT_FRIGID)
          return;

      if (TargetedEnemy == m_CurrentEnemy)
      {
        SetFireStyle_Enemy(m_CurrentEnemy);
        return;
      }

      if (m_Group.m_EnemyReactionType != ERT_PASSIVE)
      {
        SetFireStyle_Nearest();

        if (!m_Group.HasOrderedEnemies())
        {
          m_Group.OnEnemyTargeted(this);
        }
        else
        {
          if ((m_EnemyReactionType == ERT_AGGRESSIVE) &&
            (m_CurrentEnemy != ""))
          {
            m_LastEnemy = m_CurrentEnemy;
          }

          AttackEnemy(TargetedEnemy);
        }
//        AttackEnemy(TargetedEnemy);
      }
    }
    else
    {
      if (m_EnemyReactionType != ERT_PASSIVE)
        AttackEnemy(TargetedEnemy);
    }
  }

  void OnNoEnemy()       // called when all enemy disapeared from radar
  {
    if (m_InSpecialAttack)
        return;

    if (m_EnemyReactionType == ERT_FRIGID)
        return;

    if (m_Group != null)
    {
      if (m_Group.m_EnemyReactionType == ERT_FRIGID)
          return;

      if (m_Group.HasOrderedEnemies())
      {
        if (m_Group.m_CurrentOrder.m_EnemyReactionType == ERT_AGGRESSIVE)
        {
          AttackEnemy(m_Group.GetNearestOrderedEnemy(GetIdentificator()));
        }
        else  // _not_ group aggressive
        {
          String EnemyId = m_Group.GetNearestOrderedEnemyOnGroupRadar(GetIdentificator());
          if (EnemyId != "")
          {
            AttackEnemy(EnemyId);
          }
          else
          {
            GoInactive();
          }
        }
      }
      else  // _not_ m_Group.HasOrderedEnemies()
      {
        if ((m_EnemyReactionType == ERT_AGGRESSIVE) && (m_LastEnemy != null))
        {
          AttackEnemy(m_LastEnemy);
          m_LastEnemy = null;
        }
        else  // _not_ personal aggressive
        {
          GoInactive();
        }
      }  // _end_ m_Group.HasOrderedEnemies()
    }  // _end_  m_Group != null
    else
    {
      if ((m_EnemyReactionType == ERT_AGGRESSIVE) && (m_LastEnemy != null))
      {
        AttackEnemy(m_LastEnemy);
        m_LastEnemy = "";
      }
      else
      {
        if (m_FireStyle != 0)
          SetFireStyle_Nearest();
      }
    }
  }

  void OnUpdate() {}        // called every game tact for synchronization

  void OnStopped()             // called when unit has reached ordered position
  {
    if (m_KamikazeNP) // && (GetNavPointPos(m_TargetID) - getPosition(user).origin) < 10.0)
    {
//      logMessage("[KAMIKAZE] Unit " + getIdentificator(user) + ": KABOOM!");
      Component self = GetMission().GetObject(getIdentificator(user));
      if (self != null)
          self.CommitSuicide();
      return;
    }

    if (m_Group != null)
      m_Group.OnStopped(this);
  }

  void OnMoveFailed()    // called when unit can not move any more to ordered position
  {
  }

  // called from soldier attack task when soldier is targeting (and shooting) enemy
  void OnTargeting(
      String  _EnemyID,
      boolean _On
   )
  {
    // should throw it to state control
    sendEvent(0.0, getIdentificator(user), "Targeting", [_EnemyID, _On]);
  }

  // called from state control when enemy bullet hits unit
  void OnHitByEnemy(
      String _EnemyID
   )
  {
  }

  // called when FRIEND unit appears on radar (not necessarily first)
  // NB: DetailedFriends should be turned on /SetDetailedFriends(true)/
  void OnFriendCame(
      String _FriendID
   )
  {
//    logMessage(getIdentificator(user) + ": FRIEND came - " + _FriendID);

    if (m_Group != null)
    {
      Component Task = GetUnitTask(_FriendID);
      if ((Task != null) && (Task.m_Group == m_Group))
        AddToGroupWeaponPool(_FriendID);
    }
  }

  // called when FRIEND unit disappears from radar (not necessarily last)
  // NB: DetailedFriends should be turned on /SetDetailedFriends(true)/
  void OnFriendGone(
      String _FriendID
   )
  {
//    logMessage(getIdentificator(user) + ": FRIEND gone - " + _FriendID);

    if (m_Group != null)
    {
      Component Task = GetUnitTask(_FriendID);
      if (Task != null)
      {
        if (Task.m_Group == m_Group)
          RemoveFromGroupWeaponPool(_FriendID);
      }
    }
  }

  // called when ENEMY unit appears on radar (not necessarily first)
  // NB: DetailedEnemies should be turned on /SetDetailedEnemies(true)/
  void OnEnemyCame(
      String _EnemyID
   )
  {
  }

  // called when ENEMY unit disappears from radar (not necessarily last)
  // NB: DetailedEnemies should be turned on /SetDetailedEnemies(true)/
  void OnEnemyGone(
      String _EnemyID
   )
  {
  }

  String OnGetDebugInfo()   // called when behavior renders debug information
  {                         // returned string will be appended to output text
    return "";
  }

  void OnOrderedEnemyKilled() // called when the enemy (specified by SetFireStyle_Enemy) was killed
  {
    if (m_InSpecialAttack)
        return; // ?

    if (m_EnemyReactionType == ERT_FRIGID)
        return; // how possibly could we get here?

    m_CurrentEnemy = "";
    String Enemy = "";

    if (m_Group != null)
    {
      if (m_Group.m_EnemyReactionType == ERT_FRIGID)
          return;

      if (m_Group.HasOrderedEnemies())
      {
        Enemy = m_Group.GetNearestOrderedEnemyOnRadar(GetIdentificator());

        if (Enemy != "")
          AttackEnemy(Enemy);
        else  // _not_ ordered enemy on radar
        {
          Enemy = GetNearestEnemyUnitOnRadar();

          if (Enemy != "")
            AttackEnemy(Enemy);
          else  // nobody on radar
          {
            if (m_Group.m_CurrentOrder.m_EnemyReactionType == ERT_AGGRESSIVE)
              AttackEnemy(m_Group.GetNearestOrderedEnemy(GetIdentificator()));
            else  // not group aggressive
            {
              Enemy = m_Group.GetNearestOrderedEnemyOnGroupRadar(GetIdentificator());
              if(Enemy != "")
                AttackEnemy(Enemy);
              else
                GoInactive();
            }
          }  // _end_ nobody on radar
        }  // _end_ ordered enemy on radar
      }
      else  // group has no ordered enemies
      {
        Enemy = GetNearestEnemyUnitOnRadar();

        if (Enemy != "")
        {
          // #TODO - change to : inform group about enemy sighted
          AttackEnemy(Enemy);
        }
        else  // nobody on radar
        {
          if ((m_EnemyReactionType == ERT_AGGRESSIVE) && (m_LastEnemy != ""))
          {
            AttackEnemy(m_LastEnemy);
            m_LastEnemy = "";
          }
          else  // not personal aggressive
          {
            GoInactive();
          }
        } // _end_ nobody on radar
      }  // _end_ group has no ordered enemies
    }
    else  // group == null
    {
      if ((m_EnemyReactionType == ERT_AGGRESSIVE) && (m_LastEnemy != ""))
      {
        AttackEnemy(m_LastEnemy);
        m_LastEnemy = "";
      }
      else
      {
        if (m_FireStyle != 0)
          SetFireStyle_Nearest();
      }
    }
  }

  void OnLeaderLost(String _LeaderID) // called when leader (specified by SetOrder_Follow or SetOrder_Formation) is lost
  {
    if (m_Group != null)
    {
      m_Group.OnUnitLostLeader(getIdentificator(user));
    }
  }

  void OnEnemyLost(String _EnemyID)   // called when enemy (specified by SetOrder_Attack) is lost
  {
    if (m_InSpecialAttack)
        return;

    if (m_EnemyReactionType == ERT_FRIGID)
        return;

    String Enemy = "";

    if (m_Group != null)
    {
      if (m_Group.m_EnemyReactionType == ERT_FRIGID)
          return;

      if (m_Group.HasOrderedEnemies())
      {
        Enemy = m_Group.GetNearestOrderedEnemyOnRadar(GetIdentificator());

        if (Enemy != "")
        {
          AttackEnemy(Enemy);
        }
        else  // no ordered enemy on radar
        {
          Enemy = GetNearestEnemyUnitOnRadar();

          if (Enemy != "")
          {
            AttackEnemy(Enemy);
          }
          else  // nobody on radar
          {
            if (m_Group.m_CurrentOrder.m_EnemyReactionType == ERT_AGGRESSIVE)
            {
              AttackEnemy(m_Group.GetNearestOrderedEnemy(GetIdentificator()));
            }
            else
            {
              Enemy = m_Group.GetNearestOrderedEnemyOnGroupRadar(GetIdentificator());
              if (Enemy != "")
              {
                AttackEnemy(Enemy);
              }
              else
              {
                GoInactive();
              }
            }
          }
        }
      }
      else  // no ordered enemies
      {
        if (m_Group.m_EnemyReactionType != ERT_PASSIVE)
        {
          Enemy = GetNearestEnemyUnitOnRadar();

          if (Enemy != "")
          {
            AttackEnemy(Enemy);
          }
          else
          {
            if ((m_EnemyReactionType == ERT_AGGRESSIVE) && (m_LastEnemy != ""))
            {
              AttackEnemy(m_LastEnemy);
              m_LastEnemy = "";
            }
            else
            {
              GoInactive();
            }
          }
        }
      }
    }
    else
    {
      if ((m_EnemyReactionType == ERT_AGGRESSIVE) && (m_LastEnemy != ""))
      {
        AttackEnemy(m_LastEnemy);
        m_LastEnemy = "";
      }
      else
      {
        if (m_FireStyle != 0)
          SetFireStyle_Nearest();
      }
    }
  }

  //
  // Orders redirection (called from group script and code)
  //

  void MoveTo(
      Vector _Destination,
      float _Speed
   )
  {
    if (m_InSpecialAttack)
        return;

    float Speed = _Speed;

    if (Speed == 0.0)
      Speed = GetMaxSpeed();
    if (Speed > GetMaxSpeed())
      Speed = GetMaxSpeed();

    if (m_PauseNow)
      PostDelayedEvent("SetOrder_MoveTo", [_Destination, Speed], m_PauseNow);
    else
      SetOrder_MoveTo(_Destination, Speed);
  }

  void Attack(
      String _TargetID,
      float _DistanceMin,
      float _DistanceMax,
      float _MoveSpeed
   )
  {
    if (m_InSpecialAttack)
        return;

    if (m_EnemyReactionType == ERT_FRIGID)
        return;

    m_CurrentEnemy = _TargetID;

    float MoveSpeed = _MoveSpeed;
    if ((MoveSpeed == 0) || (MoveSpeed > GetMaxSpeed()))
      MoveSpeed = GetMaxSpeed();

    SetFireStyle_Enemy(_TargetID);
    SetOrder_Attack(_TargetID, MoveSpeed);
  }

  boolean KamikazeDetector = false;
  boolean m_ParachuteDetector = false;

  void KamikazeAttack(
      String _Target,
      float  _MoveSpeed
   )
  {
//    logMessage("[KAMIKAZE] Unit " + getIdentificator(user) + " received order KamikazeAttack");
    Component self = GetMission().GetObject(getIdentificator(user));

    if (self != null)
    {
      Component Detector = self.GetObject("PointCollisionDetector");
      if (Detector != null)
      {
//        logMessage("[KAMIKAZE] Unit " + getIdentificator(user) + ": turning detector ON");
        KamikazeDetector = true;
        Detector.EnableDetector(true);
      }
    }

    Component Target = (new #GameController()).GetObject(_Target);
    if (Target == null)
    {
      logError(getIdentificator(user) + ": no target - no kamikaze attack");
      return;
    }

    m_TargetID = _Target;
    m_KamikazeNP = true;

    SetOrder_MoveTo(getPosition(Target).origin, _MoveSpeed);
  }

  void SetParachuteDetector(
      boolean _Enabled
    )
  {
    m_ParachuteDetector = _Enabled;
  }

  void OnPointCollisionDetect(String _Collidor)
  {
//    logMessage("[KAMIKAZE] Unit " + getIdentificator(user) + ": collision detected");
    if (KamikazeDetector)
    {
//      logMessage("[KAMIKAZE] Unit " + getIdentificator(user) + ": BOOM!");
      Component self = GetMission().GetObject(getIdentificator(user));
      if (self != null)
          self.CommitSuicide();
    }
    else
    if (m_ParachuteDetector)
    {
      Component Self = GetMission().GetObject(getIdentificator(user));
      if (Self == null)
        return;

      Component ParachuteController = Self.GetObject("ParachuteController");
      if (ParachuteController == null)
        return;

      ParachuteController.EnableFreeFall(false);

      Self.EnableSurfaceControl();

      Component CollisionDetector = Self.GetObject("PointCollisionDetector");
      if (CollisionDetector != null)
        CollisionDetector.EnableDetector(false);

      m_ParachuteDetector = false;

      if (checkMask(Self, [], [CLASSIFICATOR_DEAD_OBJECT]))
      {
        sendEvent(0.0, getIdentificator(user), "CloseParachute", []);
        fireEvent(1.5, [], "OnParachuteLanded", [getIdentificator(user)]);
      }
      else
        OnParachuteLanded(getIdentificator(user));
    }
  }

  event void OnParachuteLanded(
      String _UnitID
    )
  {
    String SelfID = getIdentificator(user);

    if (SelfID == _UnitID)
    {
      ShowJoint(SelfID, "Parachute", false);

      ActivateBehavior(true);

      Component Self = GetMission().GetObject(SelfID);
      if (Self != null)
      {
        Component Mesh = Self.GetObject("Mesh");
        if (Mesh != null)
          SetPosition(getPosition(Mesh));
      }

      if (m_Group != null)
        m_Group.OnUnitParachuteLanded(SelfID);
    }
  }

  event void OnObjectDestroyed(
      String _ObjectID
    )
  {
    if (getIdentificator(user) == _ObjectID)
    {
      if (m_ParachuteDetector)
      {
        Component Self = GetMission().GetObject(_ObjectID);
        if (Self != null)
        {
          Component ParachuteController = Self.GetObject("ParachuteController");
          if (ParachuteController != null)
          {
            ParachuteController.SetTractiveResistance(0.0);
          }
        }
      }
    }
  }

  void Formation(
      String _LeaderID,
      Vector _Displacement,
      float _DistanceOptimum,
      float _DistanceMax,
      float _CruiserSpeed,
      float _OvertakeSpeed
   )
  {
    if (m_InSpecialAttack)
        return;

    float OvertakeSpeed = _OvertakeSpeed;
    float CruiserSpeed = _CruiserSpeed;

    if ((OvertakeSpeed == 0) || (OvertakeSpeed > GetMaxSpeed()))
      OvertakeSpeed = GetMaxSpeed();

    if (CruiserSpeed == 0)
      CruiserSpeed = GetMaxSpeed() * 2 / 3;
    if (CruiserSpeed > GetMaxSpeed())
      CruiserSpeed = GetMaxSpeed();

    SetOrder_Formation(_LeaderID, _Displacement, _DistanceOptimum, _DistanceMax, CruiserSpeed, OvertakeSpeed);
  }

  void Follow(
      String _LeaderID,
      float _DistanceMin,
      float _DistanceOptimum,
      float _DistanceMax,
      float _CruiserSpeed,
      float _OvertakeSpeed
   )
  {
    if (m_InSpecialAttack)
        return;

    float OvertakeSpeed = _OvertakeSpeed;
    float CruiserSpeed = _CruiserSpeed;

    if ((OvertakeSpeed == 0) || (OvertakeSpeed > GetMaxSpeed()))
      OvertakeSpeed = GetMaxSpeed();

    if (CruiserSpeed == 0)
      CruiserSpeed = GetMaxSpeed() * 2 / 3;
    if (CruiserSpeed > GetMaxSpeed())
      CruiserSpeed = GetMaxSpeed();

    SetOrder_Follow(_LeaderID, _DistanceMin, _DistanceOptimum, _DistanceMax, CruiserSpeed, OvertakeSpeed);
  }

  void StopNow()
  {
    SetOrder_StopNow();
  }

  void ShowUnit(
      boolean _On
   )
  {
    sendEvent(0.0, GetIdentificator(), "SetPlayOffState", [!_On]);
  }

  void SetSpecialAttackFlag(boolean _Status)
  {
    m_InSpecialAttack = _Status;
  }

  void SetEnemyReactionType(int _EnemyReactionType)
  {
    m_EnemyReactionType = _EnemyReactionType;
    if (m_EnemyReactionType == ERT_FRIGID)
        SetFireStyle_NoFire();
  }

  int m_ERTLatch = -1;

  void SetFrigidMode(boolean _On)
  {
    if (_On)
    {
      m_ERTLatch = m_EnemyReactionType;
      m_EnemyReactionType = ERT_FRIGID;
      if (IsAttacking())
      {
        SetOrder_StopNow();
        m_CurrentEnemy = "";
        SetOrderState(false);
      }
    }
    else
    {
      if (m_ERTLatch != -1)
        m_EnemyReactionType = m_ERTLatch;
    }
  }

  void SetAnchorState(boolean _On)
  {
//    logMessage("" + getIdentificator(user) + "m_Anchor = " + new String(_On));
    m_Anchored = _On;
  }

  void SetOrderState(boolean _On)
  {
    m_HasOrder = _On;
  }

  void SetHomePoint(String _HomePoint)
  {
    m_HomePointID = _HomePoint;
  }

  void SetFireStyle_NoFire()
  {
    m_FireStyle = 0;

    setFireStyle_NoFire();
  }

  void SetFireStyle_Nearest()
  {
    m_FireStyle = 1;

    setFireStyle_Nearest();
  }

  void SetFireStyle_Enemy(String _EnemyID)
  {
    m_FireStyle = 2;

    setFireStyle_Enemy(_EnemyID);
  }

  void SetFireStyle_SpecialFire(String _EnemyID)
  {
    m_FireStyle = 1;

    setFireStyle_SpecialFire(_EnemyID);
  }

  void SetOrder_MoveTo(
      Vector _Destination,
      float  _MoveSpeed
   )
  {
    SetAnchorState(false);
    SetOrderState(true);
    setOrder_MoveTo(_Destination, _MoveSpeed);
  }

  void SetOrder_MoveTo_Direct(
      Vector _Destination,
      float  _MoveSpeed
   )
  {
    SetAnchorState(false);
    SetOrderState(true);
    setOrder_MoveTo_Direct(_Destination, _MoveSpeed);
  }

  void SetOrder_MoveTo_Trace(
      Vector _Destination,
      String _TraceID,
      float  _MoveSpeed
   )
  {
    SetAnchorState(false);
    SetOrderState(true);
    setOrder_MoveTo_Trace(_Destination, _TraceID, _MoveSpeed);
  }

  void SetOrder_StopNow()
  {
    SetAnchorState(false);
    SetOrderState(false);
    setOrder_StopNow();
  }

  void SetOrder_Formation(
      String  _LeaderID,
      Vector  _FormationVector,
      float   _PosDistanceOptimum,
      float   _PosDistanceMax,
      float   _CruiserSpeed,
      float   _OvertakeSpeed
   )
  {
    SetAnchorState(false);
    SetOrderState(true);
    setOrder_Formation(
        _LeaderID,
        _FormationVector,
        _PosDistanceOptimum,
        _PosDistanceMax,
        _CruiserSpeed,
        _OvertakeSpeed,
        false);
  }

  void SetOrder_Formation_Oriented(
      String  _LeaderID,
      Vector  _FormationVector,
      float   _PosDistanceOptimum,
      float   _PosDistanceMax,
      float   _CruiserSpeed,
      float   _OvertakeSpeed
   )
  {
    SetAnchorState(false);
    SetOrderState(true);
    setOrder_Formation(
        _LeaderID,
        _FormationVector,
        _PosDistanceOptimum,
        _PosDistanceMax,
        _CruiseSpeed,
        _OvertakeSpeed,
        true);
  }

  void SetOrder_Follow(
      String  _LeaderID,
      float   _PosDistanceMin,
      float   _PosDistanceOptimum,
      float   _PosDistanceMax,
      float   _CruiserSpeed,
      float   _OvertakeSpeed
   )
  {
    SetAnchorState(false);
    SetOrderState(true);
    setOrder_Follow(
        _LeaderID,
        _PosDistanceMin,
        _PosDistanceOptimum,
        _PosDistanceMax,
        _CruiserSpeed,
        _OvertakeSpeed);
  }

  void SetOrder_Guard(
      String  _GuardPoint,
      float   _GuardRadius,
      float   _MoveSpeed
   )
  {
    SetOrder_Guard(
        _GuardPoint,
        _GuardRadius,
        _MoveSpeed,
        0);
  }

  void SetOrder_Guard(
      String  _GuardPoint,
      float   _GuardRadius,
      float   _MoveSpeed,
      int     _CrossPause
   )
  {
    SetAnchorState(true);
    SetOrderState(true);
    m_AnchorID = _GuardPoint;
    m_AnchorDistanceMax = _GuardRadius;

    // $TMP
//    logMessage(getIdentificator(user) + ": guarding point " + _GuardPoint);

    setOrder_Guard(
        _GuardPoint,
        _GuardRadius,
        _MoveSpeed,
        _CrossPause
     );
  }

  void SetOrder_Attack(
      String  _TargetID,
      float   _MoveSpeed
   )
  {
    // $LOG
//    logMessage(getIdentificator(user) + ": attacking " + _TargetID);

    if (m_EnemyReactionType == ERT_FRIGID)
        return;

    SetAnchorState(false);
    SetOrderState(true);
    m_CurrentEnemy = _TargetID;

    setOrder_Attack(
        _TargetID,
        _MoveSpeed);
  }

  void SetOrder_Attack_Anchored(
      String  _TargetID,
      float   _MoveSpeed,
      String  _AnchorID,
      float   _AnchorDistanceMax
   )
  {
    // $LOG
//    logMessage(getIdentificator(user) + ": attacking " + _TargetID + " anchored at " + _AnchorID);

    if (m_EnemyReactionType == ERT_FRIGID)
        return;

    SetAnchorState(true);
    SetOrderState(true);
    m_CurrentEnemy = _TargetID;

    setOrder_Attack_Anchored(
        _TargetID,
        _MoveSpeed,
        _AnchorID,
        _AnchorDistanceMax);
  }

  void SetOrder_SpecialAttack(
      String  _TargetID,
      float   _MoveSpeed
   )
  {
    SetAnchorState(false);
    SetOrderState(true);
    setOrder_SpecialAttack(
        _TargetID,
        _MoveSpeed);
  }

  //
  // helper functions
  //

  // attacks 'm_CurrentEnemy'
  void AttackEnemy(
    String _EnemyId
   )
  {
    if (m_InSpecialAttack)
        return;

    if (m_EnemyReactionType == ERT_FRIGID)
        return;

    m_CurrentEnemy = _EnemyId;

    SetFireStyle_Enemy(_EnemyId);

    if (m_Anchored)
      SetOrder_Attack_Anchored(
        _EnemyId,
        GetMaxSpeed(),
        m_AnchorID,
        m_AnchorDistanceMax);
    else
      SetOrder_Attack(
        _EnemyId,
        GetMaxSpeed());
  }


  boolean IsAttacking()
  {
    return (IsAlive() && (m_CurrentEnemy != ""));
  }

  void GoInactive()
  {
    m_CurrentEnemy = "";
    SetFireStyle_Nearest();
    SetOrder_StopNow();
    SetOrderState(false);
    if ((m_Group != null) && (m_Group.m_CurrentOrder.m_Order == "Attack"))
    {
      m_Group.OnUnitInactive(this);
    }
  }

  String GetName()
  {
    return getIdentificator(user); // GetIdentificator();
  }

  void SetGroup(Component _Group, int _Rank)
  {
    m_Group = _Group;
  }

  boolean HasSpecialWeapons()
  {
    Component Self = (new #GameController()).GetObject(GetIdentificator());

    for (int i = 0; i < Self.m_WeaponList.size(); i++)
    {
      if (Self.m_WeaponList[i].SpecialWeapon)
          return true;
    }

    return false;
  }

  // helpers for non-synched patrol

  boolean m_TwoWayPaused = false;
  boolean m_BackCount = false;
  int m_PauseNow = 0;
  int m_PauseInRoot = 0;
  boolean m_StartFromBack = false;

  void SetTwoWayPausedPatrol(int _RootPause, boolean _StartFromBack)
  {
    m_TwoWayPaused = true;
    m_PauseInRoot = _RootPause;
    m_StartFromBack = _StartFromBack;
  }

  void InitPatrolPointIndex(int _TotalPatrolPoints)
  {
    m_TotalPatrolPoints = _TotalPatrolPoints;
    if (m_StartFromBack)
      m_NextPatrolPoint = m_TotalPatrolPoints - 1;
    else
      m_NextPatrolPoint = 0;      // default is ...
  }

  int GetNextPatrolPoint()
  {
    // default implementation just iterates sequentially through point indexes
    int Ret;

    if (m_TwoWayPaused)
    {
      if (m_NextPatrolPoint == 0)
      {
        m_PauseNow = m_PauseInRoot;
        m_BackCount = false;
        m_NextPatrolPoint++;
      }
      else
      if (m_NextPatrolPoint == m_TotalPatrolPoints - 1)
      {
        m_PauseNow = m_PauseInRoot;
        m_BackCount = true;
        m_NextPatrolPoint--;
      }
      else
      {
        m_PauseNow = 0;
        if (m_BackCount)
            m_NextPatrolPoint--;
        else
            m_NextPatrolPoint++;
      }
      Ret = m_NextPatrolPoint;
    }
    else
    {
      Ret = m_NextPatrolPoint;

      m_NextPatrolPoint++;
      if (m_NextPatrolPoint == m_TotalPatrolPoints)
        m_NextPatrolPoint = 0;
    }

    return Ret;
  }

  // returns current rank of unit in its group
  int GetRank()
  {
    if (m_Group == null)
        return -1;

    return m_Group.GetRankOf(GetIdentificator());
  }

  String GetPersonalNavPointName()
  {
    return getIdentificator(user) + "_PersonalNP";
  }
}


class CWingmanTask extends CBaseAITask
{
  // *** constants ***

  final static float FOLLOW_DISTANCE_MIN = 180.0;  // [metres] minimum follow distance
  final static float FOLLOW_DISTANCE_OPT = 200.0;  // [metres] optimum follow distance
  final static float FOLLOW_DISTANCE_MAX = 220.0;  // [metres] maximum follow distance
  final static float FORMATION_DISTANCE_OPT = 10;
  final static float FORMATION_DISTANCE_MAX = 30;

  final static float ATTACK_DISTANCE_MIN = 300.0;  // [metres] minimum attack distance
  final static float ATTACK_DISTANCE_MAX = 500.0;  // [metres] maximum attack distance

  final static float PATROL_RADIUS = 200.0;  // [metres] radius of patrolling place
  final static float PATROL_HEIGHT = 80.0;   // [metres] height of patrolling

  final static Array FORMATION_VECTORS = [new Vector(200, 120, 30), new Vector(200, -120, 30)];

  String m_LeaderID = "";   // this wingman's player ID

  int m_WingmanRank = 0;    // wingman rank in player "group"
  int m_WingmanReactionLatch = ERT_DEFENSIVE;
  boolean m_InstantWingman = false;

  int m_HarrassmentTime = 5000; // time to wait under attack before retreat

  void Init()
  {
    CBaseAITask::Init();

    InitPersonalWeaponPool();
    SetRadarDetailedFriends(true);
    SetRadarDetailedEnemies(true);
  }


  // *** functions for overwriting ***

  Vector GetFormationVector()
  {
    return FORMATION_VECTORS[m_WingmanRank];
  };

  void SetWingmanRank(int _Rank)
  {
    m_WingmanRank = _Rank;
  }

  int GetWingmanRank()
  {
    return m_WingmanRank;
  }

  void SetInstantWingman()
  {
    m_InstantWingman = true;
  }

  void SetLeaderID(String _LeaderID)
  {
    m_LeaderID = _LeaderID;
  }

  String GetLeaderID()
  {
    return m_LeaderID;
  }


  // *** current order ***
  boolean m_Follow  = false;
  boolean m_Stay    = false;
  boolean m_RepeatStay = false;
  boolean m_Attack  = false;
  boolean m_Patrol  = false;
  boolean m_Retreat = false;
  boolean m_RechargeSequence = false;
  boolean m_SafeRecharge = false;


  // *** tasks stuff ***
  String m_FollowID     = "";
  String m_EnemyID      = "";
  String m_PatrolPlace  = "";
  Vector m_StayPlace;
  Vector m_RetreatPlace;
  String m_RechargeTarget = "";


  // *** routines ***

  void ClearState()
  {
    m_Follow  = false;
    m_Stay    = false;
    m_RepeatStay = false;
    m_Attack  = false;
    m_Patrol  = false;
    m_Retreat = false;
    m_FollowID     = "";
    m_EnemyID      = "";
    m_PatrolPlace  = "";
//    m_StayPlace    = null;
//    m_RetreatPlace = null;
  };

  float GetPlayerRadarRange()
  {
    return 300.0;
/* $COMMENTED OUT : design decision
    if (m_LeaderID == "")
    {
      logError(getIdentificator(user) + ": LeaderID not set");
      return 2000.0;
    }

    Component PlayerMainUnit = GetMission().GetObject(m_LeaderID);

    if (PlayerMainUnit == null)
    {
      logError("No " + m_LeaderID + " in mission?");
      return 2000.0;
    }
    else
    {
      return PlayerMainUnit.RadarRange;
    }
 */
  }


  // *** orders ***

  void Wingman_SetOrder(int _Order, String _LeaderID, String _TargetID)
  {
    if (_Order < 6)
    {
      GetMission().SendPersonalCockpitMessage(
          m_LeaderID,
          CCommonStrings::str_WingmanOrder +
          CCommonStrings::str_AssaultOrders[_Order].GetUpperString() +
          CCommonStrings::str_WingmanGiven +
          CCommonStrings::str_WingmanCallSigns[ m_WingmanRank],
          CCockpitColorMap::m_GoodNewsColor);
    }

    SetAnchorState(false);

    if (_Order == 0)
    {
      // FOLLOW ME
      Wingman_Follow(_LeaderID);
    }
    else
    if (_Order == 1)
    {
      // ATTACK TARGET
      Wingman_Attack(_TargetID);
    }
    else
    if (_Order == 2)
    {
      // STAY HERE
      Wingman_Stay();
    }
    else
    if (_Order == 3)
    {
      // PATROL
      Wingman_Patrol();
    }
    else
    if (_Order == 4)
    {
      // RECHARGE
      Wingman_Recharge(_LeaderID);
    }
    else
    if (_Order == 5)
    {
      // RETURN TO BASE #TODO PLACE
      Wingman_Retreat();
    }
  }

  // order "Follow me" and "Guard unit"
  void Wingman_Follow(String _FollowID)     // id of object to follow and guard
  {
    logMessage("Wingman_Follow(\"" + _FollowID + "\")");

    if (_FollowID == "")
    {
      if (m_LeaderID != "")
      {
        Component Leader = (new #GameController()).GetObject(m_LeaderID);
        if ((Leader != null) && !checkMask(Leader, [CLASSIFICATOR_DEAD_OBJECT], []))
        {
          Wingman_Follow(m_LeaderID);
          return;
        }
        else
        {
          WingmanDisconnect();
          StickToNearestLeader();
          return;
        }
      }
      else
      {
        StickToNearestLeader();
        return;
      }
    }

    if ((m_LeaderID != null) && (m_LeaderID != _FollowID))
    {
      Component GC = new #GameController();
      Component FollowLead = GC.GetObject(_FollowID);
      if ((FollowLead != null) && !checkMask(FollowLead, [CLASSIFICATOR_DEAD_OBJECT], []))
      {
        WingmanDisconnect();
        WingmanConnect(_FollowID);
        return;
      }
      else
      {
        logError(getIdentificator(user) + ".Wingman_Follow() - new leader is either null or dead");
        return;
      }
    }

    ClearState();
    m_Follow = true;
    m_FollowID = _FollowID;

    SetAnchorState(true);
    m_AnchorID = _FollowID;
    m_AnchorDistanceMax = GetPlayerRadarRange();

    float max_speed = GetMaxSpeed();
/*
    SetOrder_Follow(
      _FollowID,
      FOLLOW_DISTANCE_MIN,
      FOLLOW_DISTANCE_OPT,
      FOLLOW_DISTANCE_MAX,
      0.8 * max_speed,
      max_speed);
*/

    setOrder_Formation(
      _FollowID,
      GetFormationVector(),
      FORMATION_DISTANCE_OPT,
      FORMATION_DISTANCE_MAX,
      0.8 * max_speed,
      max_speed,
      false);
  };

  // order "Stay"
  void Wingman_Stay()
  {
    logMessage("Wingman_Stay()");

    ClearState();
    m_Stay = true;

    Vector current_position = GetNowPosition();
    current_position.z = 0.0;           // lowest possible height
    m_StayPlace = new Vector(current_position);

    SetOrder_MoveTo(m_StayPlace, GetMaxSpeed());
  };

  // order "Attack"
  void Wingman_Attack(String _EnemyID)
  {
    logMessage("Wingman_Attack(\"" + _EnemyID + "\")");

    ClearState();
    m_Attack = true;
    m_EnemyID = _EnemyID;

    SetOrder_Attack(
      _EnemyID,
      GetMaxSpeed());
  };

  // order "Patrol"
  void Wingman_Patrol()
  {
    logMessage("Wingman_Patrol(...)");

    Component Point = GetMission().GetObject(GetPersonalNavPointName());
    if (Point == null)
    {
      logError("Wingman " + getIdentificator(user) + " cannot get personal nav-point");
      return;
    }

    Component me = GetMission().GetObject(getIdentificator(user));
    if (me == null)
    {
        logError("Wingman " + getIdentificator(user) + " could not get itself");
        return;
    }

    setPosition(Point, getPosition(me));

    ClearState();
    m_Patrol = true;
    m_PatrolPlace = GetPersonalNavPointName();
    m_AnchorID = m_PatrolPlace;
    m_AnchorDistanceMax = GetPlayerRadarRange();
    SetAnchorState(true);

    setOrder_Guard(
      m_PatrolPlace,
      300.0,
      0.1 * GetMaxSpeed(),
      200);
  };

  void Wingman_Panic()
  {
// kill me
    logWarning("[Wingman_Panic] Wingman " + CCommonStrings::str_WingmanCallSigns[ m_WingmanRank]
        + " say Leader="+ m_LeaderID);
    Component Wingman = GetMission().GetObject(getIdentificator(user));

    logWarning("[Wingman_panic] Wingman last damaged " + new String(Wingman.GetLastDamager()));
// kill me
    Wingman_Retreat();
      GetMission().SendPersonalCockpitMessage(
          m_LeaderID,
          CCommonStrings::str_WingmanCallSigns[ m_WingmanRank]+
          CCommonStrings::str_WingmanPanic,
          CCockpitColorMap::m_BadNewsColor);
  }

  // order "Retreat"
  void Wingman_Retreat()
  {
    logMessage("Wingman_Retreat(...)");

    if (m_HomePointID == "")
    {
        logError("Wingman " + getIdentificator(user) + " has no place to call 'Home'");
        return;
    }

    Component RetreatPlace = GetMission().GetObject(m_HomePointID);
    if (RetreatPlace == null)
    {
      logError("Wingman " + getIdentificator(user) + ": 'Base' point identificator is invalid");
      return;
    }

    ClearState();
    m_Retreat = true;
    m_RetreatPlace = new Vector(getPosition(RetreatPlace).origin);

    if (m_RetreatPlace == null)
    {
      logError("Wingman " + getIdentificator(user) + ": 'Base' has no vector");
      return;
    }

    SetFireStyle_NoFire();
    m_WingmanReactionLatch = m_EnemyReactionType;
    SetEnemyReactionType(ERT_PASSIVE);
    SetOrder_MoveTo(m_RetreatPlace, GetMaxSpeed());
  };

  // order "Recharge"
  boolean Wingman_Recharge(String _LeaderID)
  {
    logMessage("Wingman_Recharge(...)");

    Array Recharges = GetMission().GetRechargePlacesIDList();

    if (Recharges.size() == 0)
    {
        logWarning("[Wingman] Wingman_Recharge : No recharge places found");
        return false;
    }

    Component GC = new #GameController();

    Array WingGroup = GetMission().GetPlayerObjectsIDList();
    Array WingPos;

    for (int j = 0; j < WingGroup.size(); j++)
    {
      if (WingGroup[j] != getIdentificator(user))
      {
//        logMessage(getIdentificator(user) + ": added " + WingGroup[j] + " into busy check");
        Component Wing = GC.GetObject(WingGroup[j]);
        if (Wing != null)
        {
          WingPos.add(getPosition(GC.GetObject(WingGroup[j])).origin);
          WingPos[WingPos.size() - 1].z = 0.0;
        }
      }
    }

    Vector WingmanPos = GetNowPosition();
    WingmanPos.z = 0.0;
    Vector LeaderPos = getPosition(GC.GetObject(_LeaderID)).origin;
    LeaderPos.z = 0.0;

    float MinWingmanDist = -1;
    float MinLeaderDist = -1;
    String Champion = "";

    float WingmanDist = 0;
    float LeaderDist = 0;
    float Range = 1000;    // #FIXME : tune
    Vector TempPos;
    Component TempObject;

    for(int i = 0; i < Recharges.size(); i++)
    {
      TempObject = GC.GetObject(Recharges[i][0]);
      if (TempObject != null)
      {
        String TempAffiliation = TempObject.GetAffiliation();

        if (((TempAffiliation == "FRIEND") || (TempAffiliation == "NEUTRAL")) &&
            !checkMask(TempObject, [CLASSIFICATOR_DEAD_OBJECT], []))
        {
          TempPos = new Vector(getPosition(GC.GetObject(Recharges[i][0])).origin);
          TempPos.z = 0.0;

          boolean IsBusy = false;
          // check if recharge is busy

          if (Recharges[i][1])
            continue;

          for (int k = 0; k < WingPos.size(); k++)
          {
            if ((TempPos - WingPos[k]).Magnitude() < 15.0)  // 15.0 - set radius of recharge busy detection
                IsBusy = true;
          }
          if (IsBusy)
          {
            continue;
          }

          WingmanDist = (TempPos - WingmanPos).Magnitude();
          LeaderDist  = (TempPos - LeaderPos).Magnitude();

          if (WingmanDist < Range)
          {
            if ((MinWingmanDist < 0) || (MinWingmanDist > WingmanDist))
            {
              MinWingmanDist = WingmanDist;
              Champion = Recharges[i][0];
            }
          }

          if ((MinWingmanDist < 0) && (LeaderDist < Range))
          {
            if ((MinLeaderDist < 0) || (LeaderDist < MinLeaderDist))
            {
              MinLeaderDist = LeaderDist;
              Champion = Recharges[i][0];
            }
          }
        }
      }
    }

    if (Champion == "")
    {
      logWarning("[Wingman] Wingman_Recharge : Appropriate recharge place not found");
      GetMission().SendPersonalCockpitMessage(
        m_LeaderID,
        CCommonStrings::str_WingmanCantRecharge,
        CCockpitColorMap::m_BadNewsColor
      );
      return false;
    }

    // we have the winner
    m_RechargeTarget = Champion;
    GetMission().SetRechargeState(Champion, true, getIdentificator(user));
//    Recharges[i][1] = true; // make recharge place busy
    m_RechargeSequence = true;
    m_SafeRecharge = true;
    SetOrder_MoveTo(getPosition(GC.GetObject(Champion)).origin, 0.0);
    return true;
  }

  void RechargeReroute()
  {
    if(m_RechargeSequence)
    {
      if (m_SafeRecharge)
      {
        SetOrder_StopNow();
        m_RechargeTarget = "";
        m_RechargeSequence = false;
        m_SafeRecharge = false;

        Wingman_Recharge(m_LeaderID);
      }
    }
  }

  // *** Event handlers ***

  void OnStopped()
  {
    if (m_RechargeSequence)
    {
      m_SafeRecharge = false;
      sendEvent(0.0,  getIdentificator(user), "Landing",  [m_RechargeTarget]);
      sendEvent(10.0, getIdentificator(user), "Recharge", []);
      sendEvent(15.0, getIdentificator(user), "TakeOff",  []);
      sendEvent(20.0, getIdentificator(user), "RechargeDone", []);
      return;
    }
    if (m_RepeatStay)
    {
      m_RepeatStay = false;
      Wingman_Stay();
      return;
    }
    if (m_Retreat)
    {
      ClearState();
      if (!Wingman_Recharge(m_LeaderID))
        Wingman_Patrol();
      SetEnemyReactionType(m_WingmanReactionLatch);
      return;
    }

    CBaseAITask::OnStopped();
  };

  event void RechargeDone()
  {
    InitPersonalWeaponPool();

    GetMission().SetRechargeState(m_RechargeTarget, false, "");
    m_RechargeSequence = false;

    if (m_Patrol)
        SetOrder_MoveTo (m_PatrolPlace, 0.0);
    else if (m_Follow)
      Wingman_SetOrder(0 /* FOLLOW */, m_FollowID, ""); // Wingman_Follow(m_FollowID);
    else if (m_Attack)
      Wingman_SetOrder(0 /* FOLLOW */, m_LeaderID, ""); // Wingman_Follow(m_LeaderID);
    else if (m_Stay)
    {
      m_RepeatStay = true;
      SetOrder_MoveTo(m_StayPlace, 0.0);
    }
    else if (m_Retreat)
    {
      SetOrder_Guard(m_HomePointID, 300.0, 0.1 * GetMaxSpeed());
    }
    else
        SetOrder_Guard(m_RechargeTarget, 300.0, 0.1 * GetMaxSpeed());
  }

  void OnMoveFailed()
  {
    if (m_Patrol)
      SetOrder_MoveTo(m_PatrolPlace, 0.0);
  };

  void AttackEnemy(
      String _EnemyID
   )
  {
    if (m_CurrentEnemy != "")
        GetMission().m_PlayerGroupEnemies.remove(GetMission().m_PlayerGroupEnemies.find(m_CurrentEnemy));

    GetMission().m_PlayerGroupEnemies.add(_EnemyID);

    CBaseAITask::AttackEnemy(_EnemyID);
  }

  boolean IsEnemyEngaged(
      String _EnemyID
   )
  {
    if (GetMission().m_PlayerGroupEnemies.find(_EnemyID) != -1)
        return true;

    return false;
  }

  void OnEnemyTargeted()
  {
    if (m_Attack)
        return; // players orders are uber alles

    String EnemyID = GetTargetedEnemy();

    if (IsEnemyEngaged(EnemyID))
      return;

    if (!CanAttackPersonally(EnemyID))
      return;

    if (m_Patrol || m_Follow)
      AttackEnemy(EnemyID);
  };

  void OnEnemyLost(String _EnemyID)
  {
    if (_EnemyID == m_CurrentEnemy)
    {
      GetMission().m_PlayerGroupEnemies.remove(GetMission().m_PlayerGroupEnemies.find(_EnemyID));
      m_CurrentEnemy = "";
    }

    // if more enemyes attack another enemy
    String TargetedEnemy = GetTargetedEnemy();
    int Enemies = 1;
    while (TargetedEnemy != "" && Enemies < 20)
    {
      if (!IsEnemyEngaged(TargetedEnemy) && CanAttackPersonally(TargetedEnemy))
      {
        AttackEnemy(TargetedEnemy);
        return;
      }

      TargetedEnemy = GetNextEnemyUnitOnRadar(TargetedEnemy);
      Enemies++;
    }

    if (m_Patrol)
      SetOrder_Guard(m_PatrolPlace, 300.0, 0.1 * GetMaxSpeed());
    if (m_Follow)
      Wingman_Follow(m_FollowID);
    if (m_Attack)
      Wingman_Follow(m_LeaderID);
  };

  void OnNoEnemy()
  {
    if (m_CurrentEnemy != "")
    {
        GetMission().m_PlayerGroupEnemies.remove(GetMission().m_PlayerGroupEnemies.find(m_CurrentEnemy));
        m_CurrentEnemy = "";
    }
  }

  Array Harrassers = []; // format: [<ID>, <last time>]

  int GetHarrasserIdx(
      String _EnemyID
   )
  {
    for (int i = 0; i < Harrassers.size(); i++)
    {
      if (Harrassers[i][0] == _EnemyID)
          return i;
    }
    return -1;
  }

  void OnHitByEnemy(
      String _EnemyID
   )
  {
    Component Enemy = GetMission().GetObject(_EnemyID);
    if (Enemy == null) // sanity check
        return;

    if (Enemy.GetAffiliation() == "FRIEND")
      return; // no friendly fire :)

    int CurMenace = MENACE_UNARMED;
    if (m_CurrentEnemy != "")
    {
      Component CurEnemy = GetMission().GetObject(m_CurrentEnemy);
      if (CurEnemy != null)
          CurMenace = CurEnemy.UnitMenacePower;
    }

    int i = GetHarrasserIdx(_EnemyID);
    if (i != -1) // recurrence check
    {
      if (getGameTime() - Harrassers[i][1] >= m_HarrassmentTime)
      {
        if (CanAttackAnyhow(_EnemyID))
        {
          if (Enemy.UnitMenacePower > CurMenace)
            AttackEnemy(_EnemyID);
        }
        else
        {
          sendEvent(0.0, m_LeaderID, "WingmanRetreat", [getIdentificator(user)]);
          Wingman_Panic();
        }
      }

      return;
    }

    if (!IsEnemyEngaged(_EnemyID))
    {
      if (CanAttackPersonally(_EnemyID))
      {
        if (IsAttacking() || m_Attack)
        {
          if (!(Enemy.UnitMenacePower > CurMenace))
            return;
        }

        AttackEnemy(_EnemyID);
        return;
      }

      if (!CanAttackGroup(_EnemyID))
      {
        sendEvent(0.0, m_LeaderID, "WingmanNeedsHelp", [getIdentificator(user), _EnemyID]);
        Harrassers.add([_EnemyID, getGameTime()]);
      }
    }
  }

  void OnFriendCame(
      String _FriendID
   )
  {
    if (_FriendID == m_LeaderID)
    {
      Component Player = (new #GameController()).GetObject(_FriendID);

      for (int i = 0; i < Player.m_WeaponList.size(); i++)
      {
        if (Player.m_WeaponList[i].CanFire)
            m_GroupWeaponPool.add([m_LeaderID, Player.m_WeaponList[i]]);
      }

      return;
    }

    Array Wingmen = GetMission().GetPlayerObjectsIDList();
    if (Wingmen.find(_FriendID) != -1)
      AddToGroupWeaponPool(_FriendID);
  }

  void OnFriendGone(
      String _FriendID
   )
  {
    Array Wingmen = GetMission().GetPlayerObjectsIDList();
    if (Wingmen.find(_FriendID) != -1)
        RemoveFromGroupWeaponPool(_FriendID);
  }

  void OnEnemyCame(
      String _EnemyID
   )
  {
    if (m_Attack)  // don't digress from enemy player wants to see dead
        return;

    Component Enemy = GetMission().GetObject(_EnemyID);
    if (Enemy == null) // sanity check
        return;

    if (IsEnemyEngaged(_EnemyID))
      return;

    if (!CanAttackPersonally(_EnemyID)) // unreachable
      return;

    if (IsAttacking()) // already in process, check menace
    {
      Component CurEnemy = GetMission().GetObject(m_CurrentEnemy);

      if ((CurEnemy != null)
          && (CurEnemy.UnitMenacePower < Enemy.UnitMenacePower))
        AttackEnemy(_EnemyID);
    }

    if (m_Patrol || m_Follow)  // alert state - attack
      AttackEnemy(_EnemyID);
  }

  void OnEnemyGone(
      String _EnemyID
   )
  {
  }

  event void MissionStarted()
  {
    if (m_InstantWingman)
    {
      sendEvent(float(m_WingmanRank + 1) * 3.0 + 5.0, getIdentificator(user), "TakeOff", []);
      PostDelayedEvent("StickToNearestLeader", [], (m_WingmanRank + 1) * 3000 + 10000);
    }
  }

  //
  //  Network related event processing
  //

  boolean m_ReportSticker = true;

  event void OnObjectDestroyed(
      String  _ObjectID
    )
  {
    Component GC = new #GameController();
    String GameMode = GC.GetGameMode();

    if (GameMode == "Server") //!= "Single")
    {
      if ((_ObjectID == m_LeaderID) && checkMask(user, ["HOSTMODE"], []))
      {
        Component Mission = GC.GetMission();

        String PreviousLeader = m_LeaderID;

        WingmanDisconnect();

        m_ReportSticker = false;
        StickToNearestLeader();

        String NewLeader = m_LeaderID;
        int PrevLeaderSlot = Mission.GetPlayerSlotIDByUnitID(PreviousLeader);
        int NewLeaderSlot = Mission.GetPlayerSlotIDByUnitID(NewLeader);

        Component Session = GC.GetGameSession();

//        if (GameMode == "Server")
//        {
          GC.GetMission().Server_LeaderChange(-1, getIdentificator(user), PrevLeaderSlot, NewLeaderSlot);
//          Session.FireNetEvent("Client_LeaderChange", [PrevLeaderSlot, NewLeaderSlot]);
//          fireEvent(0.0, "OnWingmanChangeLeader", [getIdentificator(user), PrevLeaderSlot, NewLeaderSlot]);
//        }
//        else if (GameMode == "Client")
//        {
//          Session.FireNetEvent("Server_LeaderChange", [getIdentificator(user), PrevLeaderSlot, NewLeaderSlot]);
//        }
      }
    }
  }

  event void WingmanDisconnect()
  {
    if (m_LeaderID != "")
    {
      Component GC = new #GameController();

      Component Player = GC.GetObject(m_LeaderID);
      if (Player != null)
      {
        if (GC.GetGameMode() == "Single")
        {
          Player.RemoveWingman(getIdentificator(user));
        }
        else
        {
          if (checkMask(Player, ["HOSTMODE"], []))
          {
            Player.RemoveWingman(getIdentificator(user));
          }
          else
          {
            Component Session = GC.GetGameSession();
            Session.FireNetEvent("Player_RemoveWingman", [m_LeaderID, getIdentificator(user)]);
          }
        }
      }
      m_LeaderID = "";
    }
  }

  event void WingmanConnect(String _LeaderID)
  {
    if (_LeaderID != "")
    {
      Component GC = new #GameController();
      m_LeaderID = _LeaderID;

      if (GC.GetGameMode() != "Single")
      {
        Component Player = GC.GetObject(_LeaderID);
        if (Player != null)
        {
          if (checkMask(Player, ["HOSTMODE"], []))
          {
            Player.AddWingman(getIdentificator(user));
          }
          else
          {
            Component Session = GC.GetGameSession();
            Session.FireNetEvent("Player_AddWingman", [_LeaderID, getIdentificator(user), m_WingmanRank]);
          }
        }

        if (m_ReportSticker)
        {
          if (GC.GetGameMode() != "Single")
          {
            GC.GetMission().Server_LeaderChange(-1, getIdentificator(user), -1, GC.GetMission().GetPlayerSlotIDByUnitID(_LeaderID));
          }
        }
      }
      else
      {
        Component Player = GC.GetObject(m_LeaderID);
        if (Player != null)
          Player.AddWingman(getIdentificator(user));
      }
      Wingman_SetOrder(0 /*Follow*/, m_LeaderID, "");
    }

    m_ReportSticker = true;
  }

  event void StickToNearestLeader()
  {
    Array PlayerList = GetMission().GetTeamPlayerObjectsIDList(GetAffiliation());

    if (PlayerList.size() == 0)
        logError("No players?");

    Vector MyPosition = GetNowPosition();

    String Champion = "";
    float ChampionDistance = FLT_MAX;

    Component GC = new #GameController();

    for (int i = 0; i < PlayerList.size(); i++)
    {
      Component TempObj = GC.GetObject(PlayerList[i]);
      if ((TempObj != null) && !checkMask(TempObj, [CLASSIFICATOR_DEAD_OBJECT], []))
      {
        Vector TempPos = getPosition(TempObj).origin;
        float  TempDist = (TempPos - MyPosition).Magnitude();

        // $LOG
        logWarning("WingmanStick - testing possible leader " + PlayerList[i] + ", distance is " + new String(TempDist));

        if (TempDist < ChampionDistance)
        {
          Champion = PlayerList[i];
          ChampionDistance = (TempPos - MyPosition).Magnitude();
        }
      }
    }

    // $LOG
    logWarning("WingmanStick - leader selected - " + Champion);

    WingmanConnect(Champion);
  }
}

class CTransportWingmanTask extends CWingmanTask
{
  final static Array FORMATION_VECTORS = [new Vector(-200, 120, 30), new Vector(-200, -120, 30)];

  int m_LandingSequence = 0;
  String  m_LandingPoint = "";
  String  m_LandFacePoint = "";

  Array CargoPoints = [];
  boolean m_InCargo = false;

  void Init()
  {
    CWingmanTask::Init();
  }

  void Wingman_SetOrder(int _Order, String _LeaderID, String _TargetID)
  {
    if (_Order < 6 && _Order != 1)
    {
      GetMission().SendPersonalCockpitMessage(
        m_LeaderID,
        CCommonStrings::str_WingmanOrder +
        CCommonStrings::str_TransportOrders[_Order].GetUpperString() +
        CCommonStrings::str_WingmanGiven +
        CCommonStrings::str_WingmanCallSigns[ m_WingmanRank],
        CCockpitColorMap::m_GoodNewsColor
      );
    }

    SetAnchorState(false);

    //
    //  Cargo heli orders
    //

    if (_Order == 0)  // FOLLOW
    {
      m_InCargo = false;
      Wingman_Follow(_LeaderID);
    }
    else
    if (_Order == 1) // ATTACK TARGET (passive fuck off)
    {
    }
    else
    if (_Order == 2) // STAY HERE
    {
      m_InCargo = false;
      Wingman_Stay();
    }
    else
    if (_Order == 3) // CARGO
    {
      Wingman_Cargo(_LeaderID);
    }
    else
    if (_Order == 4) // RECHARGE/REPAIR
    {
      m_InCargo = false;
      Wingman_Recharge(_LeaderID);
    }
    else
    if (_Order == 5)  // RETURN TO BASE
    {
      // #TODO
      m_InCargo = false;
//      logMessage("Wingman_Retreat(...) stub");
      Wingman_Retreat();
    }
  }

  void Wingman_Cargo(String _LeaderID)
  {
    // #KILLME
    logMessage("Wingman_Cargo(...)");

    if (m_InCargo)
    {
      logWarning("In cargo sequence, won't break it");
      GetMission().SendPersonalCockpitMessage(
        m_LeaderID,
        CCommonStrings::str_WingmanCallSigns[m_WingmanRank] +
        CCommonStrings::str_InCargo,
        CCockpitColorMap::m_BadNewsColor
      );
      return;
    }

    String Champion = GetPossibleLandingTarget();

    if (Champion == "")
    {
      logWarning("[Transport wingman] Wingman_Cargo : Appropriate cargo point not found");
      fireEvent(0.0, [], "TransportLanded", [getIdentificator(user),"",false]);
      return;
    }

    logMessage("[Transport wingman] Wingman_Cargo : Moving to landing point " + Champion);
    // we have the winner
    m_InCargo = true;
    m_LandingPoint = Champion;
    m_LandingSequence = 1;
    SetOrder_MoveTo(GetNavPointPos(Champion), 0.0);
  }

  String GetPossibleLandingTarget()
  {
    String Champion = "";
    String ChampionFace = "";

    if (m_InCargo)
        return Champion;

    Vector TransportPos = GetNowPosition();
    TransportPos.z = 0.0;
    Vector LeaderPos = getPosition((new #GameController()).GetObject(m_LeaderID)).origin;
    LeaderPos.z = 0.0;

    float MinTransportDist = FLT_MAX;
    float MinLeaderDist = FLT_MAX;
    float TransportDist = 0;
    float LeaderDist = 0;
    float CargoPointRange = 0;
    Vector TempPos;

    if (CargoPoints.size() == 0)
        logWarning("[Transport wingman] Wingman_Cargo : Empty cargo points array");

    // $LOG
//    logWarning(getIdentificator(user) + ": GetPossibleLandingTarget(), points array is " + new String(CargoPoints.size()) + " elements long");

    for(int i = 0; i < CargoPoints.size(); i++)
    {
      // $LOG
//      logWarning(getIdentificator(user) + ": cargo point " + new String(i));

      TempPos = new Vector(GetNavPointPos(CargoPoints[i][0]));
      TempPos.z = 0.0;
      CargoPointRange = GetNavPointRange(CargoPoints[i][0]);

      logMessage("Processing point " + CargoPoints[i][0] + " range is " + new String(CargoPointRange));

      TransportDist = (TempPos - TransportPos).Magnitude();
      LeaderDist = (TempPos - LeaderPos).Magnitude();

      logMessage("Distance to transport: " + new String(TransportDist));
      logMessage("Distance to leader: " + new String(LeaderDist));

      if (TransportDist < CargoPointRange)
      {
        if (TransportDist < MinTransportDist)
        {
          MinTransportDist = TransportDist;
          Champion = CargoPoints[i][0];
          if (CargoPoints[i].size() > 1)
            ChampionFace = CargoPoints[i][1];
          logMessage("Point fits transport");
        }
      }

      if ((MinTransportDist >= FLT_MAX) && (LeaderDist < CargoPointRange))
      {
        if (LeaderDist < MinLeaderDist)
        {
          MinLeaderDist = LeaderDist;
          Champion = CargoPoints[i][0];
          if (CargoPoints[i].size() > 1)
            ChampionFace = CargoPoints[i][1];
          logMessage("Point fits leader");
        }
      }
    }

    m_LandFacePoint = ChampionFace;
    return Champion;
  }

  event void Wingman_TakeOff()
  {
    // $TMP
    logMessage("Wingman_TakeOff()");
    sendEvent(0.0, getIdentificator(user), "CloseDoor", [true]);
    sendEvent(0.0, getIdentificator(user), "TakeOff",  []);
    PostDelayedEvent("Wingman_SetOrder", [0 /*Follow*/, m_LeaderID, ""], 5000);
  }

  void OnStopped()
  {
    if (1 == m_LandingSequence)
    {
      m_LandingSequence = 2;
      if (m_LandFacePoint != "")
      {
//        logMessage("[Transport wingman] Wingman_Cargo : rotating to face point " + m_LandFacePoint + " by cargo door");
        Component FacePoint = (new #GameController()).GetObject(m_LandFacePoint);
        if (FacePoint != null)
        {
          Vector FaceDir = getPosition(FacePoint).origin - GetNowPosition();
          float tmp = FaceDir.y;
          FaceDir.y = -FaceDir.x;
          FaceDir.x = tmp;
          FaceDir.Normalize();
          Component PNP = (new #GameController()).GetObject(GetPersonalNavPointName());
          if (PNP != null)
          {
            setPosition(PNP, eliminateRotationAlongXAndY(createMatrixFromOriginAndXVec(new Vector(1.0, 0.0, 0.0), GetNowPosition() + FaceDir * 100.0)));
            SetOrder_MoveTo_Trace(GetNowPosition(), GetPersonalNavPointName(), 0.0);
            return;
          }
          else
            logError("[Transport wingman] Wingman_Cargo : could not GetObject personal navpoint");
        }
        else
          logError("[Transport wingman] Wingman_Cargo : could not GetObject landing face point");
      }

      OnStopped();
      return;
    }
    else
    if (2 == m_LandingSequence)
    {
      m_LandingSequence = 0;
      logMessage("[Transport wingman] Wingman_Cargo : Landing to point " + m_LandingPoint);
      sendEvent(0.0,  getIdentificator(user), "Landing", [m_LandingPoint]);
      fireEvent(7.0, [], "TransportOnSurface", [getIdentificator(user)]);
      fireEvent(10.0, [], "TransportLanded", [getIdentificator(user), m_LandingPoint, true]);
      m_LandingPoint = "";
      return;
    }

    CWingmanTask::OnStopped();
  }

  event void TransportOnSurface(String _TransportID)
  {
    if (m_InCargo && (_TransportID == getIdentificator(user)))
    {
      sendEvent(0.0, getIdentificator(user), "OpenDoor", []);
    }
  }

  void OnEnemyTargeted()
  {
  }

  void OnEnemyCame(String _EnemyID) {}

  void OnEnemyLost(String _EnemyID)  {}

  void OnNoEnemy() {}

  void OnHitByEnemy(String _EnemyID)
  {
    Component Enemy = GetMission().GetObject(_EnemyID);
    if (Enemy == null) // sanity check
        return;

    if (Enemy.GetAffiliation() == "FRIEND")
    {
      return; // no friendly fire :)
    }

    int i = GetHarrasserIdx(_EnemyID);
    if (i != -1) // recurrence check
    {
      if (getGameTime() - Harrassers[i][1] >= m_HarrassmentTime)
      {
        sendEvent(0.0, m_LeaderID, "WingmanRetreat", [getIdentificator(user)]);
        Wingman_Panic();
      }

      return;
    }

    if (!CanAttackGroup(_EnemyID))
    {
      sendEvent(0.0, m_LeaderID, "WingmanNeedsHelp", [getIdentificator(user), _EnemyID]);
      Harrassers.add([_EnemyID, getGameTime()]);
    }
  }

  void SetCargoPoints(Array _CargoPoints)
  {
    CargoPoints = new Array(_CargoPoints);
  }
}


class CBaseAttackTask
{
  Array   m_AttackTarget = [];
  boolean m_Attacking    = false;
  float   AttackMinDist  = 350.0;
  float   AttackMaxDist  = 650.0;

  void AttackHero()
  {
    String TargetForAttack = GetRandomTarget();
    if (TargetForAttack != "")
    {
      m_Attacking = true;
      SetFireStyle_Enemy(TargetForAttack);
      logMessage("[ALARM] Unit "+new String(GetIdentificator())+" has begin attack unit "+new String(TargetForAttack)+" m_Attacking is "+new String(m_Attacking));
      SetOrder_Attack(TargetForAttack, GetMaxSpeed());
    }
    else
    {
      logMessage("[ALARM] Unit "+new String(GetIdentificator())+" has get empty target value. m_Attacking is "+new String(m_Attacking));
      m_Attacking = false;
      MoveToNextPatrolPoint();
    }
  }

  String GetRandomTarget()
  {
    Component Mission = GetMission();
    while (!m_AttackTarget.isEmpty())
    {
      String Target = m_AttackTarget[randnum(m_AttackTarget.size())];

      // Check target object
      Component TargetObject = Mission.GetObject(Target);
      if (TargetObject != null)
        if (TargetObject.GetLifeState())
          return Target;

      // remove invalid target from list
      int Index = m_AttackTarget.find(Target);
      m_AttackTarget.remove(Index);
    }

    return "";
  }
}

class CBaseAITask_Patrol extends CBaseAITask
{
  //
  //  ***  'pure virtual' functions
  //
  // the functions should be overwritten
  //

  // Overwrite this function to specify patrol trip
  // the function shoud return array of vectors

  Array GetPatrolTrip()
  {
    return []; // the default implementation returns empty array
  }


  //
  //  ***  'virtual' functions
  //
  // the functions can be overwritten to implement specific patrol logic
  //

  void ResetTripPointIndex()
  {
    m_TripPointIndex = 0;
  }

  boolean HasMoreTripPoints()
  {
    if (m_TripPointIndex == -1)
      return false;

    return (GetPatrolTrip().size() > 0);
  }

  void IncrementTripPointIndex()
  {
    // default implementation cycles trip points

    Array Trip = GetPatrolTrip();

    m_TripPointIndex = m_TripPointIndex + 1;

    if (m_TripPointIndex >= Trip.size())
    {
      if (m_Cycled)
        m_TripPointIndex = 0;
      else
        m_TripPointIndex = -1; // no more patrolling
    };
  }

  Vector GetNowTripPoint()
  {
    Array Trip = GetPatrolTrip();
    return (Trip[m_TripPointIndex]);
  }

  float GetTripSpeed()
  {
    // default implementation returns vehicle maximum speed
    return GetMaxSpeed(); // this property returns by script user
  }

  void MoveToNextPatrolPoint()
  {
    SetFireStyle_Nearest();

    if (HasMoreTripPoints())
    {
      // order to move to current trip point
      SetOrder_MoveTo(
        GetNowTripPoint(),
        GetTripSpeed());
    }
  }

  void AttackTargetedEnemy()
  {
    SetFireStyle_Enemy(GetTargetedEnemy());
    SetOrder_Attack(
      GetTargetedEnemy(),
      GetTripSpeed());  // #TMP:#TODO
  }


  //
  // *** operations
  //

  // call this function to set cycling flag
  void SetCycle(boolean _Cycled)
  {
    m_Cycled = _Cycled;
  };

  // call this function to start patrolling from beginning
  void StartPatrol()
  {
    ResetTripPointIndex();    // set start trip point to be next (reset patrolling trajectory)
    MoveToNextPatrolPoint();  // call function that should give order to move to next patrol point

    m_Active      = true;     // set flag that patrolling is now active
    m_AttackState = false;    // by default not in attack state
  }

  // call this function to activate patrol
  void ActivatePatrol()
  {
    if (!m_Active)
      MoveToNextPatrolPoint();  // move to next patrol point

    m_Active      = true;       // set flag that patrolling is now active
    m_AttackState = false;      // by default not in attack state
  }

  void DeactivatePatrol()
  {
    SetOrder_StopNow();         // give order to stop

    m_Active      = false;      // set flag that patrolling is now not active
    m_AttackState = false;      // clear attack state
  }


  //
  //  ***  notification handlers
  //

  // called by Behavior on initialization
  void Init()
  {
    StartPatrol();
  }

  // called by Behavior when movement target reached
  void OnStopped()
  {
    if (m_Active && !m_AttackState)
    {
      IncrementTripPointIndex(); // trip point reached. move to next one
      MoveToNextPatrolPoint();   // move to next patrol point
    }
  }

  // called by Behavior when enemy appears on radar
  void OnEnemyTargeted()
  {
    //logMessage("[EnemyOnRadar] Unit "+new String(GetIdentificator())+" approach enemy as "+new String(GetTargetedEnemy()));
    if (m_Active && !m_AttackState)
    {
      AttackTargetedEnemy(); // attack targeted enemy
      m_AttackState = true;  // set attack state flag
    };
  }

  // called by Behavior when no more enemies on radar
  void OnNoEnemy()
  {
    if (m_Active && m_AttackState)
    {
      // no more enemy on radar. continue patrolling
      m_AttackState = false;
      MoveToNextPatrolPoint();
    };
  };


  //
  // *** 'private' data and operations
  //

  boolean  m_Cycled = true;             // true for cycled patrol and false for non-cycled
  int      m_TripPointIndex = 0;
  boolean  m_AttackState    = false;
  boolean  m_Active         = true;
}

class CBaseAITask_FormationPatrol extends CBaseAITask_Patrol
{
  //
  //  ***  'pure virtual' functions
  //
  // the functions should be overwritten
  //

  // Overwrite this function to specify group name
  String GetGroupName()
  {
    return ""; // no group by default
  }

  // Overwrite this functionto specify formation vector to leader
  Vector GetFormationVector()
  {
    return new Vector(-10, 0, 0);       // default formation vector is 10 meters behind leader
  };


  //
  // *** 'virtual' function overwrite them if needed
  //

  // overwrite this function to specify overtake speed
  float GetOvertakeSpeed()
  {
    return GetTripSpeed() * 2;          // default implementation is double patrol speed
  };


  //
  // *** overwritten from parent functions
  //

  void MoveToNextPatrolPoint()
  {
    String GroupName = GetGroupName();

    if (GroupName != "")
    {
      String Commander = GetCommander(GroupName);
      if (Commander != "")
      {
        // we have leader - follow him with formation
        SetFireStyle_Nearest();
        SetOrder_Formation(
            Commander,
            GetFormationVector(),
            10, //#TMP:#TODO
            20, //#TMP:#TODO
            GetTripSpeed(),
            GetOvertakeSpeed());

        return ; // order has been given
      };
    };

    // we are leader. move to next patrol point
    CBaseAITask_Patrol::MoveToNextPatrolPoint();  // call function of base class
  }


  //
  // *** event handlers
  //

  // called by Behavior when leader (specified by SetOrder_Follow or SetOrder_Formation) is lost
  void OnLeaderLost(String _LeaderID)
  {
    MoveToNextPatrolPoint();
  };
}

class CBaseAITask_ConvoyPatrol extends CBaseAITask_Patrol
{
  //
  //  ***  'pure virtual' functions
  //
  // the functions should be overwritten
  //

  // Overwrite this function to specify group name
  String GetGroupName()
  {
    return ""; // no group by default
  }

  // Overwrite this functionto specify formation vector to leader
  float GetFollowDistance()
  {
    return 30;  // default is 30 meters distance behind leader
  };


  //
  // *** 'virtual' function overwrite them if needed
  //

  // overwrite this function to specify overtake speed
  float GetOvertakeSpeed()
  {
    return GetTripSpeed() * 2;          // default implementation is double patrol speed
  };


  //
  // *** overwritten from parent functions
  //

  void MoveToNextPatrolPoint()
  {
    String GroupName = GetGroupName();

    if (GroupName != "")
    {
      String Commander = GetCommander(GroupName);
      if (Commander != "")
      {
        // we have leader - follow him with formation
        SetFireStyle_Nearest();
        SetOrder_Follow(
            Commander,
            0.8 * GetFollowDistance(), //#TMP: #TODO
            GetFollowDistance(),
            1.3 * GetFollowDistance(), //#TMP: #TODO
            GetTripSpeed(),
            GetOvertakeSpeed());

        return ; // order has been given
      };
    };

    // we are leader. move to next patrol point
    CBaseAITask_Patrol::MoveToNextPatrolPoint();  // call function of base class
  }

  //
  // *** event handlers
  //

  // called by Behavior when leader (specified by SetOrder_Follow or SetOrder_Formation) is lost
  void OnLeaderLost(String _LeaderID)
  {
    MoveToNextPatrolPoint();
  };
}

class CFDCTask extends CBaseAITask
{
  // job state:
  // 0 - biding time
  // 1 - signaling landing
  // 2 - signaling takeoff
  int m_SignalActive = 0; // free mode by default

  String m_FriendID = "";

  boolean m_ToSignal = false;

  void Init()
  {
//    logWarning(getIdentificator(user) + " I'M HERE :)");
    // $TODO fixme
    SetRadarDetailedFriends(true);
    // $TODO ??? fix
    FeelFree(m_HomePointID);
  }

  void OnEnemyTargeted()
  {
    // $TODO panic?
  }

  void OnFriendTargeted()
  {
    // $TODO
  }

  void OnNoFriend()
  {
    // $TODO
  }

  void OnNoEnemy()
  {
    // $TODO stop panic?
  }

  void OnStopped()
  {
    // $TODO

    if (m_ToSignal)
    {
      // $TMP
//      logWarning(getIdentificator(user) + ": starting the war dance");
      Component Self = (new #GameController()).GetObject(getIdentificator(user));
      if (Self != null)
      {
        Self.StopAnimation();
        Component Friend = (new #GameController()).GetObject(getIdentificator(user));
        if (Friend != null)
            SetOrder_MoveTo_Trace(getPosition(Self).origin, m_FriendID, 0.0);

        if (m_SignalActive == 1)  // landing
            sendEvent(0.5, getIdentificator(user), "PlayAnimation", ["HeliDown", true]);
//            Self.PlayAnimation("HeliUp");
        else if (m_SignalActive == 2)
            sendEvent(0.5, getIdentificator(user), "PlayAnimation", ["HeliUp", true]);
//            Self.PlayAnimation("HeliDown");

        m_ToSignal = false;
      }
    }
  }

  void OnFriendCame(
      String _FriendID
   )
  {
    // $TMP
//    logMessage(getIdentificator(user) + ".OnFriendCame()");

    if (m_SignalActive != 0)
        return;

    Component Friend = (new #GameController()).GetObject(_FriendID);

    if (Friend != null && checkMask(Friend, ["AIR"], []))
    {
//      logWarning(getIdentificator(user) + ".OnFriendCame()");
//      fireEvent(1.0, [], "DoSignal", [_FriendID, m_HomePointID, true]);
      SetOrder_MoveTo(GetDancePosition(_FriendID), 0.0);
    }
  }

  void OnFriendGone(
      String _FriendID
   )
  {
    if (m_SignalActive != 1) // actual for landing only
        return;

    if  (_FriendID == m_FriendID)
    {
//      logMessage(getIdentificator(user) + ".OnFriendGone()");
      FeelFree(m_HomePointID);
    }
  }

  void OnEnemyLost(
      String _EnemyList
   )
  {
    // $TODO stop panic?
  }

  Vector GetDancePosition(String _VehicleID)
  {
    Vector Result = new Vector(0, 0, 0);

    Vector VehicleDir = new Vector(1.0, 0.0, 0.0);

    Component Vehicle = (new #GameController()).GetObject(_VehicleID);
    if (Vehicle != null)
    {
      VehicleDir = getPosition(Vehicle).xvec;
      VehicleDir.Normalize();
    }

    Component Point = (new #GameController()).GetObject(m_HomePointID);
    if (Point != null)
    {
      Result = getPosition(Point).origin + 30.0 * VehicleDir;
    }

    return Result;
  }

  event void DoSignal(
      String  _FriendID,        // signal receiver unit
      String  _LandingPointID,  // point of responsibility
      boolean _Landing          // true - landing, false - takeoff
   )
  {
    // $TMP
//    logMessage(getIdentificator(user) + ": received 'DoSignal' event, unit " + _FriendID + ", point " + _LandingPointID + ", landing " + new String(_Landing));
    if (m_HomePointID != _LandingPointID)
        return;

    // $TODO
//    logWarning(getIdentificator(user) + ".DoSignal(" + _FriendID + ", " + _LandingPointID + ", " + new String(_Landing) + ")");

    Component Self = (new #GameController()).GetObject(getIdentificator(user));
    if (Self != null)
        Self.StopAnimation();

    Vector FriendDir = new Vector(1.0, 0.0, 0.0);

    Component Friend = (new #GameController()).GetObject(_FriendID);
    if (Friend != null)
    {
      m_FriendID = _FriendID;
      FriendDir = getPosition(Friend).xvec;
      FriendDir.Normalize();
    }
    else
    {
      logError(getIdentificator(user) + ": object '" + _FriendID + "' is not valid object to signal to");
//      return;
    }

    Component Point = (new #GameController()).GetObject(m_HomePointID);
    if (Point == null)
    {
      logError(getIdentificator(user) + ": home point '" + m_HomePointID + "' is invalid");
      return;
    }

    Vector PointPos = getPosition(Point).origin;

    m_ToSignal = true;
    if (_Landing)
        m_SignalActive = 1;
    else
        m_SignalActive = 2;

    SetOrder_MoveTo(GetDancePosition(_FriendID),/* new Vector(PointPos + 30.0 * FriendDir),*/ 0.0);
  }

  event void FeelFree(
      String  _LandingPointID
   )
  {
    // $TMP
//    logMessage(getIdentificator(user) + ": received 'FeelFree' event, point " + _LandingPointID);
    if (m_HomePointID != _LandingPointID)
        return;

    m_ToSignal = false;
    m_SignalActive = 0;
    m_FriendID = "";

    Component Self = (new #GameController()).GetObject(getIdentificator(user));
    if (Self != null)
        Self.StopAnimation();

    // $TMP
//    logWarning(getIdentificator(user) + ".FeelFree(" + _LandingPointID + ")");

    String PersNPID = GetPersonalNavPointName();
    Component PersNP = (new #GameController()).GetObject(PersNPID);
    if (PersNP != null)
        setPosition(PersNP, getPosition(Self));
    SetOrder_Guard(PersNPID, 5.0, 2.0, 5000);
  }
}

class CBaseHumanTask extends CBaseAITask
{
  Vector Pos;
  float radius = 80.0;

  int m_TimeRun           = 60000;
  int m_TimeCorrectionRun = 60000;

  float m_WalkSpeed = 2.0;

  void Init()
  {
    Component Self = (new #GameController()).GetObject(getIdentificator(user));
    if ( Self == null)
        logError( getIdentificator(user) + ": can't get self component");
    else
      Pos = getPosition(Self).origin;
    PostDelayedEvent("TimeRun",[], m_TimeRun + randnum(120000));
    PostDelayedEvent("CorrectionRun", [], m_TimeCorrectionRun);
  }

  void TimeRun()
  {
     Vector NewPos = Pos + new Vector(rand(radius), rand(radius), 0.0);
     SetOrder_MoveTo(NewPos, m_WalkSpeed);
     PostDelayedEvent("TimeRun",[], m_TimeRun + randnum(120000));
  }

  void CorrectionRun()
  {
    Component Self = (new #GameController()).GetObject(getIdentificator(user));
    if (Self == null)
      logError( getIdentificator(user) + ": can't get self component in CorrectionRun()");
    else
    {
      if (radius < (Pos - getPosition(Self).origin).MagnitudeXY())
      {
        SetOrder_MoveTo(Pos, 0.0);
      }
      PostDelayedEvent("CorrectionRun", [], m_TimeCorrectionRun);
    }
  }

  void OnUpdate() {}        // called every game tact for synchronization

  void OnEnemyTargeted()
  {
    // $TODO panic?
    m_WalkSpeed = 0.0;
  }

  void OnFriendTargeted()
  {
    // $TODO
  }

  void OnNoFriend()
  {
    // $TODO
  }

  void OnNoEnemy()
  {
    // $TODO stop panic?
    m_WalkSpeed = 2.0;
  }

  void OnStopped()
  {
  }

  void OnEnemyLost(
      String _EnemyList
   )
  {
    // $TODO stop panic?
    m_WalkSpeed = 2.0;
  }
}

class CBaseMilitaryTask extends CBaseAITask
{
  Vector Pos;
  float radius = 80.0;
  int m_TimeRun = 60000;
  int m_TimeCorrectionRun = 60000;
  float m_WalkSpeed = 2.0;

  void Init()
  {
    Component Self = (new #GameController()).GetObject(getIdentificator(user));
    if (Self == null)
      logError(getIdentificator(user) + ": can't get self component");
    else
      Pos = getPosition(Self).origin;
    SetupWandering();
  }

  void SetupWandering()
  {
    if (!m_HasOrder)
    {
      PostDelayedEvent("TimeRun",[], m_TimeRun + randnum(120000));
      PostDelayedEvent("CorrectionRun", [], m_TimeCorrectionRun);
    }
  }

  void TimeRun()
  {
    if (!m_HasOrder)
    {
      Vector NewPos = Pos + new Vector(rand(radius), rand(radius), 0.0);
      SetOrder_MoveTo(NewPos, m_WalkSpeed);
      PostDelayedEvent("TimeRun",[], m_TimeRun + randnum(120000));
    }
  }

  void CorrectionRun()
  {
    if (!m_HasOrder)
    {
      Component Self = (new #GameController()).GetObject(getIdentificator(user));
      if (Self == null)
        logError( getIdentificator(user) + ": can't get self component in CorrectionRun()");
      else
      {
        if (radius < (Pos - getPosition(Self).origin).MagnitudeXY())
        {
          SetOrder_MoveTo(Pos, 0.0);
        }
        PostDelayedEvent("CorrectionRun", [], m_TimeCorrectionRun);
      }
    }
  }

  void GoInactive()
  {
    CBaseAITask::GoInactive();

    SetupWandering();
  }

  void SetOrder_StopNow()
  {
    CBaseAITask::SetOrder_StopNow();

    SetupWandering();
  }

  void OnUpdate() {}        // called every game tact for synchronization
}

class CBaseCivilianCarTask extends CBaseHumanTask
{
  Vector Pos;
  float radius = 600.0;

  int m_TimeRun           = 60000;
  int m_TimeCorrectionRun = 60000;

  float m_WalkSpeed = 20.0;


  void OnUpdate() {}        // called every game tact for synchronization

  void OnEnemyTargeted()
  {
    // $TODO panic?
    m_WalkSpeed = 0.0;
  }

  void OnFriendTargeted()
  {
    // $TODO
  }

  void OnNoFriend()
  {
    // $TODO
  }

  void OnNoEnemy()
  {
    // $TODO stop panic?
    m_WalkSpeed = 20.0;
  }

  void OnStopped()
  {
  }

  void OnEnemyLost(
      String _EnemyList
   )
  {
    // $TODO stop panic?
    m_WalkSpeed = 20.0;
  }
}

class CWorkerTask extends CBaseHumanTask
{
  Array States = [
      ["SetOrder_StopNow",   []],
//      ["SetOrder_Guard",  [m_HomePointID, 10.0, 2.0, 2000]],
      ["AnimateSelf",     ["Sit"]],
      ["AnimateSelf",     ["Rest"]],
      ["AnimateSelf",     ["Work"]],
      ["AnimateSelf",     ["CraneDown"]],
      ["AnimateSelf",     ["CraneUp"]]
      ];

  void Init()
  {
    Component Point = (new #GameController()).GetObject(GetPersonalNavPointName());
    if (Point == null)
        return;
    Component Self = (new #GameController()).GetObject(getIdentificator(user));
    if (Self == null)
        return;
    setPosition(Point, getPosition(Self));
    States.add(["SetOrder_Guard", [GetPersonalNavPointName(), 10.0, 2.0, 2000]]);
    PostDelayedEvent("ActionEngine", [], 10000);
  }

  void SetHomePoint(String _HomePointID)
  {
    m_HomePointID = _HomePointID;

    States.remove(States.size()-1);

    States.add(["SetOrder_Guard", [m_HomePointID, 10.0, 2.0, 2000]]);
  }

  void OnUpdate() {}        // called every game tact for synchronization

  void ActionEngine()
  {
    SetOrder_StopNow();
    AnimateSelf("");

    int nuState = randnum(States.size());
//    logMessage(getIdentificator(user) + ": new state " + new String(nuState));

//    logWarning(getIdentificator(user) + ": setting state " + States[nuState][0] + "(" + new String(States[nuState][1]) + ")");
    PostDelayedEvent("ActionEngine", [], randnum(10000));
    PostDelayedEvent(States[nuState][0], new Array(States[nuState][1]), 0);
  }

  void AnimateSelf(
      String  _State)
  {
    Component Self = (new #GameController()).GetObject(getIdentificator(user));
    if (Self != null)
    {
      if (_State == "")
          Self.StopAnimation();
      else
          Self.PlayAnimation(_State, true);
    }
  }
}

class CMultiplayerBotTask extends CBaseAITask
{
  String  m_CurrentTarget  = "";     // current target to seek and destroy
  float   m_TargetDistance = 1500.0; // distance to "get to" to far target before attacking

  String  m_RechargeTarget = "";
  boolean m_SafeRecharge   = false;

  String  m_CurrentState   = "";

  boolean m_DebugBots      = false;

  event void MissionStarted()
  {
    // $LOG
    if (m_DebugBots)
      logWarning(getIdentificator(user) + ".MissionStarted()");

    Component Behavior = GetUnitBehavior(getIdentificator(user));
    if (Behavior != null)
    {
      m_TargetDistance = Behavior.MaxRadarDistance - 200;

      // $LOG
      if (m_DebugBots)
        logMessage(getIdentificator(user) + ": target distance set to " + new String(m_TargetDistance) + ", radar range is " + new String(Behavior.MaxRadarDistance));
    }

    sendEvent(0.0, getIdentificator(user), "TakeOff", []);
    PostDelayedEvent("Reckon", [], 5000);
  }

  event void Reckon()
  {
    // $LOG
    if (m_DebugBots)
      logWarning(getIdentificator(user) + ".Reckon()");

    if (m_CurrentState == "Dead")
    {
      // $LOG
      if (m_DebugBots)
        logMessage(getIdentificator(user) + ": current state - DEAD");

      SetOrder_StopNow();
      return;
    }

    if (m_CurrentState != "Recharge")
    {
      // consider ammo
      Component Self = (new #GameController()).GetObject(GetIdentificator());

      if (Self == null)
      {
        logError( getIdentificator(user) + ": could not get self component");
        return;
      }

      // if no weapon left go get some more
      if (!HasAmmo())
      {
        // $LOG
        if (m_DebugBots)
          logMessage(getIdentificator(user) + ": no ammo on reckon - go recharge");

        GetToRecharge();
        return;
      }
    }

    // look for enemy in attacking zone

    String Candidate = GetTargetedEnemy();

    if (Candidate != "")
    {
      // $LOG
      if (m_DebugBots)
        logMessage(getIdentificator(user) + ": enemy near on reckon - attacking " + Candidate);

      // got something to shoot
      AttackEnemy(Candidate);
      return;
    }

    // consider health

    // $TODO or not TODO?
    // if (m_CurrentState != "Recharge")

    // search for far enemy

    Candidate = GetNearestEnemyUnit();

    if (Candidate != "")
    {
      // $LOG
      if (m_DebugBots)
        logMessage(getIdentificator(user) + ": considered far target on reckon - getting to " + Candidate);

      GetToTarget(Candidate);
      return;
    }
    else
    {
      // $LOG
      if (m_DebugBots)
        logMessage(getIdentificator(user) + ": no targets on reckon - go recharge");

      GetToRecharge();
    }
  }

  void GetToTarget(
      String _TargetID
    )
  {
    // $LOG
    if (m_DebugBots)
      logWarning(getIdentificator(user) + ".GetToTarget(" + _TargetID + ")");

    m_CurrentTarget = _TargetID;
    m_CurrentState = "GetToTarget";
    SetStealthMovement(true);
    SetOrder_GetToTarget(_TargetID, m_TargetDistance, 0.0);
  }

  void GetToRecharge()
  {
    // $LOG
    if (m_DebugBots)
      logWarning(getIdentificator(user) + ".GetToRecharge()");

    m_CurrentState = "GetToRecharge";

    Array Recharges = GetMission().GetRechargePlacesIDList();

    if (Recharges.size() == 0)
    {
      logWarning("[BOT] GetToRecharge : No recharge places in mission");
      Reckon();
      return;
    }

    Component GC = new #GameController();

    String Champion = "";
    float  ChampionDist = -1;

    Component TempObject;
    Vector    TempPos;
    float     TempDist;
    Vector    MyPos = GetNowPosition();
    MyPos.z = 0.0;

    for (int i = 0; i < Recharges.size(); i++)
    {
      TempObject = GC.GetObject(Recharges[i][0]);
      if (TempObject != null)
      {
        TempPos = new Vector(getPosition(GC.GetObject(Recharges[i][0])).origin);
        TempPos.z = 0.0;

        TempDist = (TempPos - MyPos).Magnitude();

        if ((ChampionDist < 0.0) || (TempDist < ChampionDist))
        {
          ChampionDist = TempDist;
          Champion = Recharges[i][0];
        }
      }
    }

    // full proof check
    if (Champion == null)
    {
      logError("[BOT] GetToRecharge : internal error");
      Reckon();
      return;
    }

    // $LOG
    if (m_DebugBots)
      logMessage(getIdentificator(user) + ": moving to recharge to " + Champion);

    m_RechargeTarget = Champion;
    m_SafeRecharge = true;
    SetOrder_MoveTo(getPosition(GC.GetObject(Champion)).origin, 0.0);
    return;
  }

  void OnStopped()
  {
    // $LOG
    if (m_DebugBots)
      logWarning(getIdentificator(user) + ".OnStopped()");

    if (m_CurrentState == "GetToRecharge")
    {
      // $LOG
      if (m_DebugBots)
        logMessage(getIdentificator(user) + ": recharge sequence");

      m_CurrentState = "Recharge";
      m_SafeRecharge = false;
      sendEvent(0.0,  getIdentificator(user), "Landing",  [m_RechargeTarget]);
      sendEvent(10.0, getIdentificator(user), "Recharge", []);
      sendEvent(15.0, getIdentificator(user), "TakeOff",  []);
      sendEvent(20.0, getIdentificator(user), "Reckon",   []);
      return;
    }
    else
    if (m_CurrentState == "Attack")
    {
      AttackEnemy(m_CurrentEnemy);
      return;
    }
    // $LOG
    else
    if (m_DebugBots)
    {
      if (m_CurrentState == "")
        logMessage(getIdentificator(user) + ": stopped in empty state");
      else
        logMessage(getIdentificator(user) + ": stopped in state " + m_CurrentState);
    }

    CBaseAITask::OnStopped();
  }

  event void OnObjectDestroyed(
      String  _ObjectID
    )
  {
    if (getIdentificator(user) == _ObjectID)
    {
      // $LOG
      if (m_DebugBots)
        logWarning(getIdentificator(user) + ".OnObjectDestroyed() - i am DEAD");

      m_CurrentState = "Dead";
    }
  }

  event void OnRespawned()
  {
    if (m_CurrentState != "Dead")
    {
      logError("[BOT] " + getIdentificator(user) + " ERROR: respawned without death");
      return;
    }
    else
    {
      // $LOG
      if (m_DebugBots)
        logWarning(getIdentificator(user) + ": respawned");

      m_CurrentState = "Respawned";
      Reckon();
    }
  }

  boolean HasAmmo()
  {
    // $LOG
    if (m_DebugBots)
      logWarning(getIdentificator(user) + ".HasAmmo()");

    Component Self = (new #GameController()).GetObject(getIdentificator(user));
    if (Self != null)
    {
      // cycle for any shootable weapon
      for (int i = 0; i < Self.m_WeaponList.size(); i++)
      {
        if (Self.m_WeaponList[i].CanFire
          && (Self.m_WeaponList[i].GetAmmoQuantity() != 0))
        {
          // $LOG
          if (m_DebugBots)
            logMessage(getIdentificator(user) + ": ammo check - positive");

          return true;
        }
      }
    }

    // $LOG
    if (m_DebugBots)
      logMessage(getIdentificator(user) + ": ammo check - negative");

    return false;
  }

  void OnTargetReached(
      String _TargetID
    )
  {
    // $LOG
    if (m_DebugBots)
      logWarning(getIdentificator(user) + ".OnTargetReached(" + _TargetID + ")");

    String NearestEnemyID = GetTargetedEnemy();
    if (NearestEnemyID != "")
    {
      // $LOG
      if (m_DebugBots)
        logMessage(getIdentificator(user) + ": enemy near - attack " + NearestEnemyID);

      AttackEnemy(NearestEnemyID);
      return;
    }
    Component Target = (new #GameController()).GetObject(_TargetID);
    if ((Target != null)
        && (!checkMask(Target, [CLASSIFICATOR_DEAD_OBJECT], [])))
    {
      // $LOG
      if (m_DebugBots)
        logMessage(getIdentificator(user) + ": planned enemy reached - attack " + _TargetID);

      AttackEnemy(_TargetID);
      return;
    }

    // $LOG
    if (m_DebugBots)
      logMessage(getIdentificator(user) + ": target reached, no enemy near - reckon");

    Reckon();
  }

  void AttackEnemy(
      String _EnemyID
    )
  {
    // $LOG
    if (m_DebugBots)
      logWarning(getIdentificator(user) + ".AttackEnemy(" + _EnemyID + ")");

    m_CurrentState = "Attack";
    m_CurrentTarget = _EnemyID;
    SetStealthMovement(false);
    SetOrder_Attack(_EnemyID, 0.0); // $FIXME : tunable speed?
  }

  void OnEnemyTargeted()
  {
    // $LOG
    if (m_DebugBots)
      logWarning(getIdentificator(user) + ".OnEnemyTargeted()");

    if (HasAmmo())
    {
      // $LOG
      if (m_DebugBots)
        logMessage(getIdentificator(user) + ": attack targeted enemy " + GetTargetedEnemy());

      AttackEnemy(GetTargetedEnemy());
    }
  }

  void OnNoEnemy()
  {
    // $LOG
    if (m_DebugBots)
      logWarning(getIdentificator(user) + ".OnNoEnemy()");

    Reckon();
  }

  void OnHitByEnemy(
      String _EnemyID
    )
  {
    // maybe later
  }

  void OnEnemyCame(
      String _EnemyID
    )
  {
    // $LOG
    if (m_DebugBots)
      logWarning(getIdentificator(user) + ".OnEnemyCame(" + _EnemyID + ")");

    if (m_CurrentState == "Attack")
    {
      Vector MyPos = GetNowPosition();
      Component CurrentEnemy = (new #GameController()).GetObject(m_CurrentTarget);
      Component NewEnemy = (new #GameController()).GetObject(_EnemyID);
      if ((CurrentEnemy != null) && (NewEnemy != null))
      {
        if ((getPosition(NewEnemy).origin - MyPos).Magnitude() < (getPosition(CurrentEnemy).origin - MyPos).Magnitude())
        {
          // $LOG
          if (m_DebugBots)
            logMessage(getIdentificator(user) + ": switching to attack closer enemy " + _EnemyID + ", previous was " + m_CurrentTarget);

          AttackEnemy(_EnemyID);
        }
      }
      else
      {
        logError(getIdentificator(user) + ": ERROR in OnEnemyCame() - attacked and new enemies are both NULL");

        return; // $UGLY silent flush
      }
    }
    else
    {
      if (HasAmmo())
      {
        // $LOG
        if (m_DebugBots)
          logMessage(getIdentificator(user) + ": enemy came, i has weapon - attack " + _EnemyID);

        AttackEnemy(_EnemyID);
      }
    }
  }

  void OnEnemyGone(
      String _EnemyID
    )
  {
    // $LOG
    if (m_DebugBots)
      logWarning(getIdentificator(user) + ".OnEnemyGone(" + _EnemyID + ")");

    if (m_CurrentTarget == _EnemyID)
    {
      // $LOG
      if (m_DebugBots)
        logMessage(getIdentificator(user) + ": current target gone - reckon");

      Reckon();
    }
  }

  void OnEnemyLost(
      String _EnemyID
    )
  {
    // $LOG
    if (m_DebugBots)
      logWarning(getIdentificator(user) + ".OnEnemyLost(" + _EnemyID + ") - reckon");

    Reckon();
  }

  event void OnAmmoChanged(
      Component _Weapon,
      float     _AmmoQty,
      boolean   _Unlimited
    )
  {
    if (!_Unlimited && (0.0 == _AmmoQty))
    {
      if (!HasAmmo())
      {
        // $LOG
        if (m_DebugBots)
          logWarning(getIdentificator(user) + ".OnAmmoChanged() - no ammo - recharge");

        GetToRecharge();
      }
    }
  }

  void OnMoveFailed()
  {
    // $LOG
    if (m_DebugBots)
      logWarning(getIdentificator(user) + ".OnMoveFailed() - reckon");

    Reckon();
  }
}

