unit _Objects;

interface

Uses
  _Geometry,
  _Resources,
  _Forms,    
  _SysUtils,
  _Scene,
  Classes,
  QuadEngine;

{$REGION 'CONSTANTS'}
const
  ZOOM_LIMITER = 3;

  S_NEUTRAL  = 0;
  S_PLAYER   = 1;
  S_COMPUTER = 2;

  //Object mouse actions - central, and t/b/r/l buttons
  BTN_CENTER = 0;
  BTN_TOP    = 1;
  BTN_RIGHT  = 2;
  BTN_BOTTOM = 3;
  BTN_LEFT   = 4;

  A_TRY_TO_CREATE_FACTORY = 0;
  A_TRY_TO_UGPRADE_FACTORY = 1;
  A_TRY_TO_CREATE_INTERCEPTOR = 2;
  A_TRY_TO_CREATE_HELICOPTER = 3;
  A_TRY_TO_UPGRADE_BASE = 4;

  BUTTON_POS: Array [0..4] of TVector2 =
  (
  (A : ( 0, 0)),
  (A : ( 0, 50)),
  (A : (-50, 0)),
  (A : ( 0,-50)),
  (A : ( 50, 0))
  );
    
  MAX_PLAYERS = 5;
{$ENDREGION}

type
  TMethod = procedure of object;
  TAction = procedure(parameter: Integer) of object;

TMapManager = class;

TWeapon = class;

{$REGION 'TMapObject - abstract objects on the map'}
TMapObject = class
  protected       
    class var PZoom     : ^Single;
    class var PViewZoom : ^Single;
    class var PCamera   : ^TVector2;

    function GetZoom    : Single;   inline;
    function GetViewZoom: Single;   inline;
    function GetCamera  : TVector2; inline;
  private
    class var FManager: TMapManager;
  private
    FPosition: TVector3;
    FColor: Cardinal; //side Color;
    FLife: Single; //side Color;

    FWeapon: TWeapon;
    
    property FZoom    : Single read GetZoom;
    property FViewZoom: Single read GetViewZoom;
    property FCamera  : TVector2 read GetCamera;
  public
    KillMe: Boolean;
    Side: Byte;     
    UnderFire: ShortInt;
    class procedure SetCameraAndZoom(var Camera: TVector2; var Zoom: Single; var ViewZoom: Single);
    class procedure SetControlManager(var Manager: TMapManager); 
    destructor Destroy; override;
    procedure Act(dt:single); virtual; abstract;//For moneys etc
    procedure DrawShadow; virtual; 
    procedure Draw; virtual; abstract;
    procedure OnClick(PlayerSide: Byte; Position: Byte); virtual;
    procedure OnHover(PlayerSide: Byte; Position: Byte); virtual;
    procedure OnHit(Damage: Integer); virtual;
    property Position: TVector3 read FPosition;// write FPosition;
  end;
{$ENDREGION}
                     
TPlayer = record
    Credits: Integer;
    MainBase: TMapObject;
    AiAction: Byte; 
  end;

{$REGION 'TResFactory - resource point, can be set in resource factory'}
TResFactory = class(TMapObject)
  public
    const RESOURCE = 0;
    const LEVEL_1  = 1;
    const LEVEL_2  = 2;
    const LEVEL_3  = 3;
    const BUILDING_COST = 200;
    const UPDATE_1_COST = 200;
    const UPDATE_2_COST = 250;
    const BUILDING_TIME = 5;
    const UPDATE_TIME   = 2;
    const INCOME_TIME   = 3;
    const LEVL_1_INCOME = 6;
    const LEVL_2_INCOME = 10;
    const LEVL_3_INCOME = 14;
  private
    FLife: Single;
    Level: Byte;//0- resource 1-3 factory   
    Transport: TMapObject;         
    Timer: Single;
    UpdateIconTimer: Single;        
    procedure StartFactoryCreation(NewSide: Integer);
    procedure CreateFactory(NewSide: Integer);
    procedure StartFactoryUpdation(NewLevel: Integer);
    procedure UpdateFactory(NewLevel: Integer);
  public
    constructor Create(NewPosition: TVector3);
    procedure Act(dt:single); override;
    procedure Draw; override;
    procedure OnClick(PlayerSide: Byte; Position: Byte); override;
    procedure OnHover(PlayerSide: Byte; Position: Byte); override;
    procedure OnHit(Damage: Integer); Override;
end;
{$ENDREGION}

{$REGION 'TBase - main player/computer base'}
TBase = class(TMapObject)
  private
    const UPGRADE_COST = 100;
    const ANTIAIR_COST = 100;
    const HELI_COST  = 100;
    const JET_COST     = 100;
  private
    FIndicator: TMapObject;
    Level: Byte;
    Timer: Single;
    UpdateIconTimer: Single;
    procedure UpdateAntiAir(Param: Integer);
    procedure CreateJet(Param: Integer);
    procedure CreateHeli(Param: Integer);
  public
    constructor Create(NewPosition: TVector3; NewSide: Byte);
    procedure Act(dt:single); override;
    procedure Draw; override;
    procedure OnClick(PlayerSide: Byte; Position: Byte); override;
    procedure OnHover(PlayerSide: Byte; Position: Byte); override;
    procedure OnHit(Damage: Integer); override;
  end;
{$ENDREGION}

{$REGION 'TIndicator - building/ someprocess indicator'}
TIndicator = class(TMapObject)
  private
    FTimer: Single;
    FLifeTime:  Single;  
    FAnimation: Single;
    FIsActionDone: Boolean;
    FParam: Integer;
    FAction: TAction;
  public
    constructor CreateIndicator(  
        Source: TMapObject;   
        LifeTime: Single;
        Parameter: Integer;
        Action: TAction);
    procedure Act(dt:single); override;
    procedure Draw; override;
  end;
{$ENDREGION}

{$REGION 'TTransport - AirCraft transport'}
TTransport = class(TMapObject)
  private
    const TRANSPORT_SPEED = 25;
  private
    FSource,
    FDest: TMapObject;
    FParam: Integer;
    FAction: TAction;
    FDirection: Single;     
    FHeight: Single;
  public
    constructor CreateTransport(
        Source, Destination: TMapObject;
        Parameter: Integer;
        Action: TAction;
        NewSide: Byte);
    procedure Act(dt:single); override;
    procedure Draw; override;
    procedure DrawShadow; override; 
    procedure OnHit(Damage: Integer); override; 
  end;
{$ENDREGION}
                
{$REGION 'TInterceptor (jet fighter) - main user air force unit'}
TInterceptor = class(TMapObject)
  public //Jet State
    const JET_MAX_FUEL = 35;//in seconds
    const JET_MAX_ROCKETS = 4;//in pairs
    const JET_SPEED = 40;
    const JET_LIFE   = 3;
    const JS_DOCKED  = 0;
    const JS_FLY     = 1;
    const JS_BACK    = 3;
  private         
    FFuel: Single;
    FLife: Single;
    FViewShift: TVector2;
    FRandomDir: TVector2;
    FDirection: Single;
    FWayPoint: TVector2; 
    FHeight: Single;
    FState: Byte;
    FWait: Single;//Waiting time
    FDock: TMapObject;
    WayPointShowTime: Single;
    function GetViewPosition: TVector2;   
    procedure ShowWayPoint;
  public
    Tag: Byte;
    constructor Create(
        Source: TMapObject;
        NewSide: Byte);
    procedure Act(dt:single); override;
    procedure Draw; override;
    procedure DrawShadow; override;
    procedure SetInterfacePosition(Position: Byte);   
    procedure FlyToPoint(Point: TVector2);   
    procedure Wait(WaitingTime: Single);
    procedure DockToObj(Base: TMapObject);   
    procedure OnHit(Damage: Integer); Override;
    function IsDocked: Boolean;
    property Dock: TMapObject read FDock;
    property ViewPosition: TVector2 read GetViewPosition;
  end;
{$ENDREGION}
                
{$REGION 'THelicopter (anti base unit) - only interceptors can hit it'}
THelicopter = class(TMapObject)
  public //Jet State     
    const HELI_MAX_FUEL = 50;
    const HELI_MAX_ROCKETS = 8;//in pairs
    const HELI_SPEED = 30;
    const HELI_LIFE  = 4;
    const HS_DOCKED  = 0;
    const HS_FLY     = 1;
    const HS_COMBAT  = 2;
    const HS_BACK    = 3;
    const HS_DEAD    = 4;
  private             
    FFuel: Single;
    FLife: Integer;
    FViewShift: TVector2;   
    FRandomDir: TVector2;
    FDirection: Single;
    FRotation: Single;
    FWayPoint: TVector2;
    FHeight: Single;
    FState: Byte;
    FWait: Single;//Waiting time
    FDock: TMapObject;
    WayPointShowTime: Single;
    function GetViewPosition: TVector2;   
    procedure ShowWayPoint;
  public
    Tag: Byte;
    constructor Create(
        Source: TMapObject;
        NewSide: Byte);
    procedure Act(dt:single); override;
    procedure Draw; override;
    procedure DrawShadow; override;
    procedure SetInterfacePosition(Position: Byte);   
    procedure FlyToPoint(Point: TVector2);   
    procedure Wait(WaitingTime: Single);
    procedure DockToObj(Base: TMapObject);   
    procedure OnHit(Damage: Integer); Override;
    function IsDocked: Boolean;
    property Dock: TMapObject read FDock;
    property ViewPosition: TVector2 read GetViewPosition;
  end;
{$ENDREGION}

{$REGION 'TBlow - Blow Effect'}
TBlow = class(TMapObject)
  private
    FLife,
    FLifeTime:  Single;
    FSize: Single;
    FAlpha: Single; 
  public
    constructor CreateBlow(
        Source: TMapObject;
        Life,
        Size: Single);
    procedure Act(dt:single); override;
    procedure Draw; override;
  end;   
{$ENDREGION}

{$REGION 'TText - Text out effect'}
TText = class(TMapObject)
  private
    FLife,
    FLifeTime:  Single;
    FAlpha: Single;
    FShift: Single;
    FText: String;
  public
    constructor CreateText(
        Source: TMapObject;
        Life: Single;
        Text: String;
        Color: Cardinal = $ffffffff);
    procedure Act(dt:single); override;
    procedure Draw; override;
  end;   
{$ENDREGION}

{$REGION 'TRocket - Main air Weapon'}
TRocket = class(TMapObject)
  private
    const LIFE_TIME: Single = 4.0;
    const ROCKET_SPEED: Single = 180;
  private
    FDestination: TMapObject;
    FLifeTime:  Single;
    FDirection: Single;   
    FAction: TAction;
    FParam: Integer;   
    FSpeed: Single;
  public
    constructor CreateRocket(
        Source,
        Dest: TMapObject;
        Parameter: Integer;
        Action: TAction;
        Direction: Single = 0);
    procedure Act(dt:single); override;
    procedure Draw; override;
  end;   
{$ENDREGION}

{$REGION 'TMapManager'}
TMapManager = class(TMapObject)
  private
    MapObject: TList;

    MouseClicked: Boolean;  
    FSelectedObject: TMapObject; 
    procedure Attack(dt: Single);
    procedure AiAct(id: Integer);
  public                      
    Player: array [0..MAX_PLAYERS] of TPlayer;
    constructor Create;
    destructor Destroy; overload;
    procedure Render;
    procedure Control;
    procedure Update(dt: single);  
    //Actions
    procedure DockJet(Jet: TInterceptor);  
    procedure DockHeli(Heli: THelicopter);
    procedure AddCreditsToPlayer(Credits: Integer; Side: Byte);
    procedure AddMapObject(NewObject: TMapObject); 
    procedure WaitForTurn(Jet: TInterceptor);
    procedure WaitHeli(Heli: THelicopter);
    function SendTransport(Source: TMapObject; Dest: TMapObject; DestAction: TAction; Param: Integer; Side: Byte): TMapObject;
    function GetPlayerCredits(Credits: Integer; Side: Byte): Boolean;
    function SelectRect(TLP, BRP: TVector2): TList;
    function TryToFindNearestResource(Base: TMapObject): TMapObject;
    function TryToFindNearestFactory(Base: TMapObject): TMapObject;
    function TryToFindNearestBuilding(Obj: TMapObject): TMapObject;
    property SelectedObject: TMapObject read FSelectedObject;  
    function TryToFindNearestEnemyBuilding(Obj: TMapObject): TMapObject;
    function FindNextDockedHelicopter(Side: Byte): TMapObject;
    function FindNextDockedJet(Side: Byte): TMapObject;  
    function TryToFindHelicopter(Jet: TMapObject): TMapObject;
end;
{$ENDREGION}

{$REGION 'TWeapon -  main interceptor weapon - can fight Bases, Helicopters and other Jets'}
TWeapon = class
  private
    const RELOAD_TIME: Single = 3;
    const DAMAGE = 1;
  public
    const FIRE_RANGE: Single = 150;
  private
    FTimer: Single;
  private
    class var FManager: TMapManager;
  public
    FReloadTime: Single;
    FRockets: Integer;
    class procedure SetControlManager(var Manager: TMapManager);
    procedure Act(dt:single); virtual;
    procedure Fire(Attacker, Enemy: TMapObject); virtual;
    function CanFire: Boolean; virtual;
    function CanAttack(Obj: TMapObject): Boolean; virtual;
  end;
{$ENDREGION}

{$REGION 'TAtrillery - Helicopters - can fight Ground Buildings'}
TAtrillery = class(TWeapon)
  private
    const RELOAD_TIME: Single = 4;
    const DAMAGE = 1;
  public
    const FIRE_RANGE: Single = 180;
  public
    procedure Act(dt:single); override;
    procedure Fire(Attacker, Enemy: TMapObject); override;
    function CanFire: Boolean; override;
    function CanAttack(Obj: TMapObject): Boolean; override;
  end;
{$ENDREGION}

{$REGION 'TAntiAir - Anti buildings'}
TAntiAir = class(TWeapon)
  private
    const RELOAD_TIME: Single = 3; 
    const DAMAGE = 1;
  public
    const FIRE_RANGE: Single = 160;
  public                        
    constructor Create; 
    procedure Act(dt:single); override;
    procedure Fire(Attacker, Enemy: TMapObject); override;
    function CanFire: Boolean; override;
    function CanAttack(Obj: TMapObject): Boolean; override;
  end;
{$ENDREGION}

implementation  

{$REGION 'TMapObjectManager'}
constructor TMapManager.Create;
var
  i: Byte;
begin
  TMapObject.SetControlManager(Self);
  TWeapon.SetControlManager(Self);
  //Payers
  for i:=0 To MAX_PLAYERS do
    begin
      Player[i].Credits:= 600;
      Player[i].MainBase:= nil;
    end;
  //Units
  MapObject:= TList.Create;
  //Bases
end;

destructor TMapManager.Destroy;
begin
  MapObject.Free;
  MapObject:= nil;
  inherited;
end;

procedure TMapManager.Control;
var
  i, j: Word;
  ZoomCorrection: Single;
  Mouse: TVector2;
begin
  FSelectedObject:= nil;
  ZoomCorrection:= FZoom;
  if ZoomCorrection > ZOOM_LIMITER then
    ZoomCorrection:= ZOOM_LIMITER;
  Mouse:= (Vector2(Window.Mouse.X, Window.Mouse.Y) / FZoom - FCamera);
//Mouse hover
  for i:= 0 to MapObject.Count - 1  do
    if MapObject[i] <> nil then
      for j:= BTN_CENTER to BTN_LEFT do
        if (Mouse + BUTTON_POS[j] / ZoomCorrection).Distance(TMapObject(MapObject[i]).Position)< 25 / (ZoomCorrection) then
          begin
            TMapObject(MapObject[i]).OnHover(S_PLAYER, j);
            if j = BTN_CENTER then
              FSelectedObject:= TMapObject(MapObject[i]);
          end;
//Mouse click
  if Window.Mouse.mbl and not MouseClicked then
    for i:= 0 to MapObject.Count - 1  do
      if MapObject[i] <> nil then
        for j:= BTN_CENTER to BTN_LEFT do
          if (Mouse +  BUTTON_POS[j] / ZoomCorrection).Distance(TMapObject(MapObject[i]).Position)< 25 / (ZoomCorrection) then
            begin
              TMapObject(MapObject[i]).OnClick(S_PLAYER, j);
              break;        
              break;
            end;   
  MouseClicked:= Window.Mouse.mbl;
end;

procedure TMapManager.Render;
var
  i: Word;
begin
  //Units and buildings
  for i:= 0 to MapObject.Count - 1  do
    if MapObject[i] <> nil then
      TMapObject(MapObject[i]).DrawShadow;
  for i:= 0 to MapObject.Count - 1  do
    if MapObject[i] <> nil then
      TMapObject(MapObject[i]).Draw;
end;

procedure TMapManager.Update(dt: single);
var
  i: Word;
begin
  //Ai Actions
  if Random(15) = 0 then
    AiAct(2);       
  if Random(15) = 0 then
    AiAct(3);
  //Act all objects
  for i:= 0 to MapObject.Count - 1  do
    if MapObject[i] <> nil then
      TMapObject(MapObject[i]).Act(dt);
  //Check for dead
  for i:= 0 to MapObject.Count - 1  do
    if MapObject[i] <> nil then
      if TMapObject(MapObject[i]).KillMe then
        begin
          TMapObject(MapObject[i]).Free;
          MapObject[i]:= nil;
        end;
  //Fire Each Other
  Attack(dt);
end;

procedure TMapManager.AddMapObject(NewObject: TMapObject);
begin
  MapObject.Add(NewObject);
end;

procedure TMapManager.Attack(dt: Single);
var
  i: Word;
  procedure FindEnemy(Attacker: TMapObject);
  var
    j: Word;
  begin
    for j:= 0 to MapObject.Count - 1  do
      if (MapObject[j] <> nil) then
        if (TMapObject(MapObject[j]) <> Attacker) then
          if Attacker.Side <> TMapObject(MapObject[j]).Side then
            if Attacker.FWeapon.CanAttack(TMapObject(MapObject[j])) then
              if (TVector2(Attacker.FPosition).Distance(TMapObject(MapObject[j]).FPosition) < Attacker.FWeapon.FIRE_RANGE) then
                Attacker.FWeapon.Fire(Attacker, TMapObject(MapObject[j]));
  end;
begin
  for i:= 0 to MapObject.Count - 1  do
    if MapObject[i] <> nil then
      if TMapObject(MapObject[i]).FWeapon <> nil then //All armed objects
        if TMapObject(MapObject[i]).FWeapon.CanFire then
          FindEnemy(TMapObject(MapObject[i]));
end;
{$ENDREGION}

{$REGION 'Ai'}
function RandomVector: TVector2;
begin
  Result.Vector2(Random(200) - 100, Random(200) - 100);
end;

procedure TMapManager.AiAct(id: Integer);
var
  Point,
  FreeUnit: TMapObject;
begin
  if Player[id].MainBase = nil then
    Exit;
  case Player[id].AiAction of
    A_TRY_TO_CREATE_FACTORY:
      if Player[id].Credits > TResFactory.BUILDING_COST then
        begin
          Point:= TryToFindNearestResource(Player[id].MainBase);
          if Point <> nil then
            TResFactory(Point).OnClick(id, BTN_CENTER);
          Player[id].AiAction:= Random(5) * Random(2);
        end;
    A_TRY_TO_UGPRADE_FACTORY:
      if Player[id].Credits > TResFactory.UPDATE_2_COST then
        begin                 
          Point:= TryToFindNearestFactory(Player[id].MainBase);
          if Point <> nil then
            begin
              TResFactory(Point).OnClick(id, BTN_CENTER)
            end;
          Player[id].AiAction:= Random(5) * Random(2);
        end;
    A_TRY_TO_CREATE_INTERCEPTOR:
      if (Player[id].Credits >  TBase.JET_COST) and (TBase(Player[id].MainBase).FIndicator = nil) then
        begin
          Player[id].MainBase.OnClick(id, BTN_TOP);
          Player[id].AiAction:= Random(5) * Random(2);
        end;
    A_TRY_TO_CREATE_HELICOPTER:
      if (Player[id].Credits > TBase.HELI_COST) and (TBase(Player[id].MainBase).FIndicator = nil) then
        begin
          Player[id].MainBase.OnClick(id, BTN_RIGHT);
          Player[id].AiAction:= Random(5) * Random(2);
        end;
    A_TRY_TO_UPGRADE_BASE:
      if (Player[id].Credits > TBase.UPGRADE_COST) and (TBase(Player[id].MainBase).FIndicator = nil) then
        begin
          Player[id].MainBase.OnClick(id, BTN_LEFT);
          Player[id].AiAction:= Random(5) * Random(2);
        end;
  end;
  //Units
  FreeUnit:= Self.FindNextDockedHelicopter(id);
  if FreeUnit <> nil then
    begin
      Point:= Self.TryToFindNearestEnemyBuilding(Player[id].MainBase);
      if Point <> nil then
        begin
          THelicopter(FreeUnit).FlyToPoint(Point.Position + RandomVector);
        end;
    end;
  FreeUnit:= nil;
  //Units
  FreeUnit:= Self.FindNextDockedJet(id);
  if FreeUnit <> nil then
    begin
      Point:= Self.TryToFindNearestResource(Player[id].MainBase);
      if Point <> nil then
        begin
          TInterceptor(FreeUnit).FlyToPoint(Point.Position + RandomVector * 0.5);
        end;
      Point:= Self.TryToFindHelicopter(FreeUnit);
      if Point <> nil then
        begin
          TInterceptor(FreeUnit).FlyToPoint(Point.Position + RandomVector * 0.5);
        end;
    end;
end;                                                    
{$ENDREGION}

{$REGION 'Units Actions'}
function TMapManager.FindNextDockedHelicopter(Side: Byte): TMapObject;
Var
  i: Word;
begin
  //find docked on this object Jets
  for i:= 0 to MapObject.Count - 1  do
    if MapObject[i] <> nil then
      begin
        if TMapObject(MapObject[i]) is THelicopter then
          if (THelicopter(MapObject[i]).IsDocked) and (THelicopter(MapObject[i]).side = Side) then
            begin
              Result:= TMapObject(MapObject[i]);
              Exit;
            end;
      end;
  Result:= nil;
end;

function TMapManager.FindNextDockedJet(Side: Byte): TMapObject;
Var
  i: Word;
begin
  //find docked on this object Jets
  for i:= 0 to MapObject.Count - 1  do
    if MapObject[i] <> nil then
      begin
        if TMapObject(MapObject[i]) is TInterceptor then
          if (TInterceptor(MapObject[i]).IsDocked) and (TInterceptor(MapObject[i]).side = Side) then
            begin
              Result:= TMapObject(MapObject[i]);
              Exit;
            end;
      end;
  Result:= nil;
end;

function TMapManager.TryToFindNearestEnemyBuilding(Obj: TMapObject): TMapObject;
Var                                                     
  i: Word;
  Side: Byte;                                        
  Nearest: Single;
  NearestObj: TMapObject;
begin
  Side:= 0;
  if Obj is TBase then
    Side:= TBase(Obj).Side;
  Nearest:= 100000;
  NearestObj:= nil;
  //find docked on this object Jets
  for i:= 0 to MapObject.Count - 1  do
    if MapObject[i] <> nil then
      begin
        if TMapObject(MapObject[i]) is TResFactory then
          if (TResFactory(MapObject[i]).Level > 0) and (Side <> TResFactory(MapObject[i]).Side) then
            if Obj.Position.Distance(TMapObject(MapObject[i]).Position) < Nearest then
              begin
                Nearest:= Obj.Position.Distance(TMapObject(MapObject[i]).Position);
                NearestObj:= TMapObject(MapObject[i]);
              end;       
        if TMapObject(MapObject[i]) is TBase then           
          if (Side <> TBase(MapObject[i]).Side) then
            if Obj.Position.Distance(TMapObject(MapObject[i]).Position) < Nearest then
              begin
                Nearest:= Obj.Position.Distance(TMapObject(MapObject[i]).Position);
                NearestObj:= TMapObject(MapObject[i]);
              end;
      end;
  Result:= NearestObj;        
end;

function TMapManager.TryToFindNearestBuilding(Obj: TMapObject): TMapObject;
Var                                                     
  i: Word;
  Side: Byte;                                        
  Nearest: Single;
  NearestObj: TMapObject;
begin
  Side:= 0;
  if Obj is TInterceptor then
    Side:= TInterceptor(Obj).Side;   
  if Obj is THelicopter then
    Side:= THelicopter(Obj).Side; 
  Nearest:= 100000;
  NearestObj:= nil;
  //find docked on this object Jets
  for i:= 0 to MapObject.Count - 1  do
    if MapObject[i] <> nil then
      begin
        if TMapObject(MapObject[i]) is TResFactory then
          if (TResFactory(MapObject[i]).Level > 0) and (Side = TResFactory(MapObject[i]).Side) then
            if Obj.Position.Distance(TMapObject(MapObject[i]).Position) < Nearest then
              begin
                Nearest:= Obj.Position.Distance(TMapObject(MapObject[i]).Position);
                NearestObj:= TMapObject(MapObject[i]);
              end;       
        if TMapObject(MapObject[i]) is TBase then           
          if (Side = TBase(MapObject[i]).Side) then
            if Obj.Position.Distance(TMapObject(MapObject[i]).Position) < Nearest then
              begin
                Nearest:= Obj.Position.Distance(TMapObject(MapObject[i]).Position);
                NearestObj:= TMapObject(MapObject[i]);
              end;
      end;
  Result:= NearestObj;        
end;

function TMapManager.TryToFindNearestFactory(Base: TMapObject): TMapObject;
Var
  i: Word;
  Nearest: Single;  
  NearestObj: TMapObject;
begin
  Nearest:= 100000;
  NearestObj:= nil;
  //find docked on this object Jets
  for i:= 0 to MapObject.Count - 1  do
    if MapObject[i] <> nil then
      if TMapObject(MapObject[i]) is TResFactory then
        if (TResFactory(MapObject[i]).Level > 0) and (TResFactory(MapObject[i]).Side = Base.Side) then
          if Base.Position.Distance(TResFactory(MapObject[i]).Position) < Nearest then
            begin
              Nearest:= Base.Position.Distance(TResFactory(MapObject[i]).Position);
              NearestObj:= TResFactory(MapObject[i]);
            end;
  Result:= NearestObj;        
end;

function TMapManager.TryToFindHelicopter(Jet: TMapObject): TMapObject;
Var
  i: Word;
  Nearest: Single;  
  NearestObj: TMapObject;
begin
  Nearest:= 100000;
  NearestObj:= nil;
  //find docked on this object Jets
  for i:= 0 to MapObject.Count - 1  do
    if MapObject[i] <> nil then
      if TMapObject(MapObject[i]) is THelicopter then
        if  (THelicopter(MapObject[i]).Side <> TInterceptor(Jet).Side) and
            ( not THelicopter(MapObject[i]).IsDocked) then
          if Jet.Position.Distance(TResFactory(MapObject[i]).Position) < Nearest then
            begin
              Nearest:= Jet.Position.Distance(TMapObject(MapObject[i]).Position);
              NearestObj:= TResFactory(MapObject[i]);
            end;
  Result:= NearestObj;        
end;

function TMapManager.TryToFindNearestResource(Base: TMapObject): TMapObject;
Var
  i: Word;
  Nearest: Single;  
  NearestObj: TMapObject;
begin
  Nearest:= 100000;
  NearestObj:= nil;
  //find docked on this object Jets
  for i:= 0 to MapObject.Count - 1  do
    if MapObject[i] <> nil then
      if TMapObject(MapObject[i]) is TResFactory then
        if (TResFactory(MapObject[i]).Level = 0) and (TResFactory(MapObject[i]).Transport = nil) then
          if Base.Position.Distance(TResFactory(MapObject[i]).Position) < Nearest then
            begin
              Nearest:= Base.Position.Distance(TResFactory(MapObject[i]).Position);
              NearestObj:= TResFactory(MapObject[i]);
            end;
  Result:= NearestObj;        
end;
                   
procedure TMapManager.WaitForTurn(Jet: TInterceptor);
Var
  i,
  cnt: Word;
begin
  cnt:= 0;
  //find docked on this object Jets
  for i:= 0 to MapObject.Count - 1  do
    if MapObject[i] <> nil then
      begin
        if TMapObject(MapObject[i]) is TInterceptor then
          if TInterceptor(MapObject[i]) <> Jet then
            if (TInterceptor(MapObject[i]).Dock <> nil) and (TInterceptor(MapObject[i]).IsDocked) then
              if TInterceptor(MapObject[i]).Dock = Jet.Dock then
                begin
                  inc(cnt);
                  TInterceptor(MapObject[i]).Wait(0.15 * (cnt) + Random(5) * 0.01);
                end;
      end;
end;

procedure TMapManager.WaitHeli(Heli: THelicopter);
Var
  i,
  cnt: Word;
begin       
  cnt:= 0;
  //find docked on this object Heli     
  for i:= 0 to MapObject.Count - 1  do
    if MapObject[i] <> nil then
      begin
        if TMapObject(MapObject[i]) is THelicopter then
          if THelicopter(MapObject[i]) <> Heli then
            if (THelicopter(MapObject[i]).Dock <> nil) and (THelicopter(MapObject[i]).IsDocked) then
              if THelicopter(MapObject[i]).Dock = Heli.Dock then
                begin
                  inc(cnt);
                  THelicopter(MapObject[i]).Wait(0.15 * (cnt) + Random(5) * 0.01);
                end;
      end;
end;

procedure TMapManager.DockJet(Jet: TInterceptor);
Var
  i, Count:Word;
begin
  //find docked on this object Jets
  Count:= 0;
  for i:= 0 to MapObject.Count - 1  do
    if MapObject[i] <> nil then
      if TMapObject(MapObject[i]) is TInterceptor then
        if TMapObject(MapObject[i]) <> Jet then
          begin
            if (TInterceptor(MapObject[i]).Dock <> nil) and (TInterceptor(MapObject[i]).IsDocked) then
              if TInterceptor(MapObject[i]).Dock = Jet.Dock then
                Inc(Count);
          end;
  Jet.SetInterfacePosition(Count + 1);
end;    

procedure TMapManager.DockHeli(Heli: THelicopter);
Var
  i, Count:Word;
begin
  //find docked on this object Heli
  Count:= 0;
  for i:= 0 to MapObject.Count - 1  do
    if MapObject[i] <> nil then
      if TMapObject(MapObject[i]) is THelicopter then
        if TMapObject(MapObject[i]) <> Heli then
          begin
            if (THelicopter(MapObject[i]).Dock <> nil) and (THelicopter(MapObject[i]).IsDocked) then
              if THelicopter(MapObject[i]).Dock = Heli.Dock then
                Inc(Count);
          end;
  Heli.SetInterfacePosition(Count + 1);
end;

function TMapManager.SelectRect(TLP, BRP: TVector2): TList;
var
  i: Word;
  Selected: TList;
  Temp: Single;
begin
  if TLP.X > BRP.X then
    begin
      Temp:= TLP.X;
      TLP.X:= BRP.X;
      BRP.X:= Temp;
    end;      
  if TLP.Y > BRP.Y then
    begin
      Temp:= TLP.Y;
      TLP.Y:= BRP.Y;
      BRP.Y:= Temp;
    end;                               
  Selected:= TList.Create;
  for i:= 0 to MapObject.Count - 1  do
    if MapObject[i] <> nil then
      begin
        if TMapObject(MapObject[i]) is TInterceptor then
          if TInterceptor(MapObject[i]).Side = S_PLAYER then
            if TInterceptor(MapObject[i]).ViewPosition.InRectangle(TLP, BRP) then
              Selected.Add(TMapObject(MapObject[i]));
        if TMapObject(MapObject[i]) is THelicopter then
          if THelicopter(MapObject[i]).Side = S_PLAYER then
            if THelicopter(MapObject[i]).ViewPosition.InRectangle(TLP, BRP) then
              Selected.Add(TMapObject(MapObject[i]));
      end;
  Result:= Selected;
end;

function  TMapManager.GetPlayerCredits(Credits: Integer; Side: Byte): Boolean;
begin
  Result:= (Player[Side].Credits >= Credits);
  if Result then
    Player[Side].Credits:= Player[Side].Credits - Credits;
end;

procedure TMapManager.AddCreditsToPlayer;
begin
  Player[Side].Credits:= Player[Side].Credits + Credits;
end;

function TMapManager.SendTransport(Source: TMapObject; Dest: TMapObject; DestAction: TAction; Param: Integer; Side: Byte): TMapObject;
var
  Transport: TTransport;
begin
  Transport:= TTransport.CreateTransport(Source, Dest, Param, DestAction, Side);
  MapObject.Add(Transport);
  result:= Transport;
end;
{$ENDREGION}

{$REGION 'TMapObject'}
destructor TMapObject.Destroy;
begin
  if FWeapon <> nil  then
    FWeapon.Free;
end;

function TMapObject.GetZoom: Single;
begin
  Result:= PZoom^;
end;

function TMapObject.GetViewZoom: Single;
begin
  Result:= PViewZoom^;
end;

function TMapObject.GetCamera: TVector2;  
begin
  Result:= PCamera^;
end;

class procedure TMapObject.SetCameraAndZoom(var Camera: TVector2; var Zoom: Single; var ViewZoom: Single);
begin
  PCamera:= @Camera;
  PZoom:= @Zoom;   
  PViewZoom:= @ViewZoom;
end;

class procedure TMapObject.SetControlManager(var Manager: TMapManager);
begin
  FManager:= Manager;
end;

procedure TMapObject.DrawShadow;
begin

end;

procedure TMapObject.OnClick(PlayerSide: Byte; Position: Byte);
begin

end;

procedure TMapObject.OnHover(PlayerSide: Byte; Position: Byte);
begin

end;

procedure TMapObject.OnHit(Damage: Integer);
begin

end;
{$ENDREGION}

{$REGION 'Side Color'}
function GetSideColor(Side: Byte): Cardinal;
begin
  case Side of
    0:Result:= $FFFFFFFF;//Neytral
    1:Result:= $FF53b3df;//You
    2:Result:= $FFd6493b;//Enemy
    3:Result:= $FF00FF00;//Alias
    else Result:= $FFFFFFFF;
  end;
end;
{$ENDREGION}

{$REGION 'TResFactory'}
constructor TResFactory.Create;
begin
  FPosition:= NewPosition;
  FColor:= $ffffffff;
  Side:= 0;          
  Level:= 0;
  Timer:= Random(100) / 100 * INCOME_TIME;
end;

procedure TResFactory.StartFactoryCreation(NewSide: Integer);
begin          
  if NewSide = 0 then //Transport Down  
    begin      
      Transport:= nil;       
      Level:= 0;
      Exit;
    end;         
  Side:= NewSide;
  FManager.AddMapObject(TIndicator.CreateIndicator(self, BUILDING_TIME, NewSide, CreateFactory));
end;

procedure TResFactory.CreateFactory(NewSide: Integer);
begin                          
  FWeapon:= TAntiAir.Create;
  Side:= NewSide;
  Level:= 1;
  FColor:= GetSideColor(NewSide);
  Transport:= nil;
end;

procedure TResFactory.StartFactoryUpdation(NewLevel: Integer);
begin
  if NewLevel = 0 then //Transport Down  
    begin      
      Transport:= nil;
      Exit;
    end;
  FManager.AddMapObject(TIndicator.CreateIndicator(self, UPDATE_TIME, NewLevel, UpdateFactory));     
end;

procedure TResFactory.UpdateFactory(NewLevel: Integer);
begin             
  Level:= NewLevel;
  Transport:= nil;
end;

procedure TResFactory.OnClick(PlayerSide: Byte; Position: Byte);
begin   
  case Level of
    RESOURCE:begin
        if (Transport = nil) then
          if FManager.GetPlayerCredits(BUILDING_COST, PlayerSide) then
            begin              
              Transport:= FManager.SendTransport(
                FManager.Player[PlayerSide].MainBase,
                Self,
                Self.StartFactoryCreation,
                PlayerSide,
                PlayerSide);
            end;
      end;
    LEVEL_1:begin
        if (Transport = nil) and (PlayerSide = side) then
          if FManager.GetPlayerCredits(UPDATE_1_COST, PlayerSide) then
            begin               
              Transport:= FManager.SendTransport(
                FManager.Player[PlayerSide].MainBase,
                Self,
                Self.StartFactoryUpdation,
                Level + 1,
                PlayerSide);
            end;
      end;
    LEVEL_2:begin
        if (Transport = nil) and (PlayerSide = side) then
          if FManager.GetPlayerCredits(UPDATE_2_COST, PlayerSide) then
            begin
              Transport:= FManager.SendTransport(
                FManager.Player[PlayerSide].MainBase,
                Self,
                Self.StartFactoryUpdation,
                Level + 1,
                PlayerSide);
            end;
      end;
    LEVEL_3:;
  end;
end;

procedure TResFactory.OnHover(PlayerSide: Byte; Position: Byte);
begin
  if Position = BTN_CENTER then
    UpdateIconTimer:= UpdateIconTimer + 0.08;
  if UpdateIconTimer > 1 then
    UpdateIconTimer:= 1;
end;

procedure TResFactory.Act(dt:single); //Factory add money each Act(dt:single)
var
  Income: Integer;
begin        
  if FWeapon <> nil then
    FWeapon.Act(dt);
  UpdateIconTimer:= UpdateIconTimer - 0.04;
  if UpdateIconTimer < 0 then
    UpdateIconTimer:= 0;
  Timer:= Timer + dt;      
  case Level of
    LEVEL_1 :if FLife < 8 then FLife:= FLife + dt;
    LEVEL_2 :if FLife < 16 then FLife:= FLife + dt;
    LEVEL_3 :if FLife < 22 then FLife:= FLife + dt;
  end;
  if Timer > INCOME_TIME then
  begin
    Timer:= 0;
    case Level of
      RESOURCE:Income:= 0;
      LEVEL_1 :Income:= LEVL_1_INCOME;
      LEVEL_2 :Income:= LEVL_2_INCOME;
      LEVEL_3 :Income:= LEVL_3_INCOME;
    end;
    if Income>0 then
    begin
      FManager.AddMapObject(TText.CreateText(Self, 1.6, '+'+IntToStr(Income), FColor));
      FManager.AddCreditsToPlayer( Income , Side);
    end;
  end;
end;

procedure TResFactory.Draw;
var
  Obj: Byte;
  Cost: Word;
  Color: Cardinal;
begin
  Obj:= 15;
  case Level of
    0:Obj:= R_RESOURCE; //resource
    1:Obj:= R_FACTORY_1; //resource
    2:Obj:= R_FACTORY_2; //resource
    3:Obj:= R_FACTORY_3; //resource
  end;
  Resources.UnitsTexture.DrawRot((FPosition.X + FCamera.X) * FZoom, (FPosition.Y + FCamera.Y) * FZoom, 0, 0.12 * FViewZoom, FColor, Obj);
  Color:= trunc($ff * UpdateIconTimer) * $1000000 + $ffffff;
  Resources.UnitsTexture.DrawRot((FPosition.X + FCamera.X) * FZoom + 5 * FViewZoom, (FPosition.Y + FCamera.Y) * FZoom - 3 * FViewZoom, 0, 0.09 * FViewZoom, Color, R_UPGRADE);
  Case Level of
    RESOURCE: Cost:= BUILDING_COST;
    LEVEL_1: Cost:= UPDATE_1_COST;
    LEVEL_2: Cost:= UPDATE_2_COST;
    LEVEL_3: Cost:= 0;
  End;
  Resources.Font.TextOut((FPosition.X + FCamera.X) * FZoom + 7 * FViewZoom, (FPosition.Y + FCamera.Y) * FZoom - 3 * FViewZoom, 0.3, PAnsiChar(IntToStr(Cost)), Color);
end;  

procedure TResFactory.OnHit(Damage: Integer);
begin
  FLife:= FLife - 4;
  if FLife < 0 then
    begin
      Level:= 0;
      if FWeapon <> nil then
        FWeapon.Free;
      FWeapon:= nil;    
      FManager.AddMapObject(
        TBlow.CreateBlow(Self, 2, 3));
      Side:=0;
      FColor:= GetSideColor(Side);
      Self.Transport:= nil;
    end;
end;
{$ENDREGION}

{$REGION 'TBase'}
procedure TBase.UpdateAntiAir;
begin
  if TAntiAir(FWeapon).FReloadTime > 1.1 then
    TAntiAir(FWeapon).FReloadTime:= TAntiAir(FWeapon).FReloadTime - 1;
  FIndicator:= nil;   
  FManager.AddMapObject(TText.CreateText(Self, 1.6, 'Upgrade complete.', FColor));
end;

procedure TBase.CreateJet;
begin
  FManager.AddMapObject(TInterceptor.Create(Self, Self.Side));
  FManager.AddMapObject(TText.CreateText(Self, 1.6, 'Helicopter Created.', FColor));
  FIndicator:= nil;
end;

procedure TBase.CreateHeli;
begin
  FManager.AddMapObject(THelicopter.Create(Self, Self.Side));
  FManager.AddMapObject(TText.CreateText(Self, 1.6, 'Helicopter Created.', FColor));
  FIndicator:= nil;
end;

constructor TBase.Create;
begin
  FWeapon:= TAntiAir.Create;
  Side:= NewSide;
  FManager.Player[NewSide].MainBase:= Self;
  FPosition:= NewPosition;
  FColor:= GetSideColor(Side);
  TAntiAir(FWeapon).FReloadTime:= 5;
end;

procedure TBase.Act(dt:single); //Factory add money each Act(dt:single)
begin
  if FLife < 40 then FLife:= FLife + dt;
  if FWeapon <> nil then
    FWeapon.Act(dt);
  UpdateIconTimer:= UpdateIconTimer - 0.04;
  if UpdateIconTimer < 0 then
    UpdateIconTimer:= 0;
  Timer:= Timer + dt;
  if Timer > 5 then
  begin
    Timer:= 0;
    case Level of
      0:FManager.AddCreditsToPlayer( + 10, Side);
      1:FManager.AddCreditsToPlayer( + 20, Side);
      2:FManager.AddCreditsToPlayer( + 30, Side);
      3:FManager.AddCreditsToPlayer( + 40, Side);
    end;
    FManager.AddMapObject(TText.CreateText(Self, 2, '+'+IntToStr(10), FColor));
  end;
end;

procedure TBase.Draw;
var
  Color: Cardinal;
begin
  Resources.UnitsTexture.DrawRot((FPosition.X + FCamera.X) * FZoom, (FPosition.Y + FCamera.Y) * FZoom, 0, 0.14 * FViewZoom, FColor, R_BASE);

  Color:= trunc($ff * UpdateIconTimer) * $1000000 + $ffffff;

  Resources.Font.TextOutAligned((FPosition.X + FCamera.X) * FZoom + (14 + 5) * FViewZoom, (FPosition.Y + FCamera.Y) * FZoom - 2.5 * FViewZoom, 0.1 * FViewZoom, PAnsiChar(IntToStr(HELI_COST)), Color);
  if TAntiAir(FWeapon).FReloadTime > 1.2 then
    Resources.Font.TextOutAligned((FPosition.X + FCamera.X) * FZoom - (14 + 5) * FViewZoom, (FPosition.Y + FCamera.Y) * FZoom - 2.5 * FViewZoom, 0.1 * FViewZoom, PAnsiChar(IntToStr(ANTIAIR_COST)), Color, qfaRight);
//  Resources.Font.TextOutAligned((FPosition.X + FCamera.X) * FZoom + 5 * FViewZoom, (FPosition.Y + FCamera.Y) * FZoom + (14 - 2.5) * FViewZoom, 0.1* FViewZoom, PAnsiChar(IntToStr(UPGRADE_COST)), Color);
  Resources.Font.TextOutAligned((FPosition.X + FCamera.X) * FZoom - 5 * FViewZoom, (FPosition.Y + FCamera.Y) * FZoom - (14 + 2.5) * FViewZoom, 0.1* FViewZoom, PAnsiChar(IntToStr(JET_COST)), Color, qfaRight);

  Resources.UnitsTexture.DrawRot((FPosition.X + FCamera.X) * FZoom + 14* FViewZoom, (FPosition.Y + FCamera.Y) * FZoom     , 0, 0.08 * FViewZoom, Color, R_HELIBUE);
  Resources.UnitsTexture.DrawRot((FPosition.X + FCamera.X) * FZoom - 14* FViewZoom, (FPosition.Y + FCamera.Y) * FZoom     , 0, 0.08 * FViewZoom, Color, R_ANTIAIR);
//  Resources.UnitsTexture.DrawRot((FPosition.X + FCamera.X) * FZoom     , (FPosition.Y + FCamera.Y) * FZoom + 14* FViewZoom, 0, 0.12 * FViewZoom, Color, R_UPGRADE);
  Resources.UnitsTexture.DrawRot((FPosition.X + FCamera.X) * FZoom     , (FPosition.Y + FCamera.Y) * FZoom - 14* FViewZoom, 0, 0.08 * FViewZoom, Color, R_JETBUE);
end;

procedure TBase.OnClick(PlayerSide: Byte; Position: Byte);
begin
  if FIndicator <> nil then
    Exit;
  
  if PlayerSide <> Side then
    Exit;
  case position of
    BTN_LEFT:if FManager.GetPlayerCredits(ANTIAIR_COST, PlayerSide) then
        begin
          FIndicator:= TIndicator.CreateIndicator(self, 2, 0, UpdateAntiAir);
          FManager.AddMapObject(FIndicator);
        end;
    BTN_TOP:if FManager.GetPlayerCredits(JET_COST, PlayerSide) then
        begin
          FIndicator:= TIndicator.CreateIndicator(self, 3, 0, CreateJet);
          FManager.AddMapObject(FIndicator);
        end;
    BTN_RIGHT:if FManager.GetPlayerCredits(HELI_COST, PlayerSide) then
        begin
          FIndicator:= TIndicator.CreateIndicator(self, 3, 0, CreateHeli);
          FManager.AddMapObject(FIndicator);
        end;
    BTN_BOTTOM:;
  end;
  if FWeapon.RELOAD_TIME > 1.2 then
  //World.AddCreditsToPlayer( + 10, PlayerSide);
end;             

procedure TBase.OnHover(PlayerSide: Byte; Position: Byte);
begin
  if Side <> PlayerSide then
    Exit;
  UpdateIconTimer:= UpdateIconTimer + 0.08;
  if UpdateIconTimer > 1 then
    UpdateIconTimer:= 1;
end;

procedure TBase.OnHit(Damage: Integer);
begin
  FLife:= FLife - 4;
  if FLife < 0 then
    begin
      case Side of
        1://Win
          begin                       
            Menu.Result('You lose!');
            Manager.SetScene(Menu);
          end;
        2,3://Lose
          begin
            Menu.Result('Victory!');
            Manager.SetScene(Menu);
          end;
      end;
    end;
end;
{$ENDREGION}

{$REGION 'TIndicator'}
constructor TIndicator.CreateIndicator(
        Source: TMapObject;
        LifeTime: Single;
        Parameter: Integer;
        Action: TAction);
begin
  FParam:= Parameter;
  FAction:= Action;
  FPosition:= Source.FPosition;
  Side:= Parameter;
  FLifeTime:= LifeTime;
  FTimer:= LifeTime;    
  FAnimation:= 0;
  FIsActionDone:= false;
  FParam:= Parameter;
  FColor:= GetSideColor(Source.Side);
end;

procedure TIndicator.Act(dt:single);
begin
  FTimer:= FTimer - dt;
  if (FTimer < 0) and not FIsActionDone then
    begin
      FAction(FParam);
      FIsActionDone:= true;
    end;
  if FTimer < -0.2 then
    begin    
      KillMe:= true;
    end;

  FAnimation:= 1;       
  if FTimer > FLifeTime - 0.3 then
    begin
      FAnimation:= Abs (FTimer - FLifeTime) * 3.3;
    end;
  if FTimer < 0.3 then
    begin
      FAnimation:= (FTimer) * 3.3;
    end;
end;

procedure TIndicator.Draw;
begin
  Resources.DrawIndicator((FPosition.X + FCamera.X) * FZoom, (FPosition.Y + FCamera.Y) * FZoom, FViewZoom * FAnimation, (FLifeTime - FTimer) / FLifeTime, FColor);
end;
{$ENDREGION}

{$REGION 'Transport'}
constructor TTransport.CreateTransport(
        Source, Destination: TMapObject;
        Parameter: Integer;
        Action: TAction;
        NewSide: Byte);
begin                 
  FSource:= Source;
  FDest:= Destination;
  FParam:= Parameter;
  FAction:= Action;
  Side:= NewSide;
  FPosition:= Source.FPosition;
  FDirection:= 0;
  FHeight:= 0;
  FColor:= GetSideColor(Source.Side);
end;

procedure TTransport.Act(dt:single);
begin
  FHeight:= FHeight + dt * 3;
  if FHeight > 5 then
    FHeight:= 5;

  FDirection:= RotateToAngle(FDirection, GetAngle(FDest.FPosition - FPosition), 0.4);
  FPosition:= FPosition + GetVector(FDirection, TRANSPORT_SPEED * dt);
  if FPosition.Distance(FDest.FPosition) < 5 then
    begin
      FAction(FParam);
      KillMe:= true;
    end;
end;

procedure TTransport.DrawShadow;
begin
  //shadow
  Resources.UnitsTexture.DrawRot(
      (FPosition.X + FCamera.X + FHeight) * FZoom,
      (FPosition.Y + FCamera.Y + FHeight) * FZoom,
      FDirection, 0.1 * FViewZoom,
      $50000000, R_TRANSPORT);
end;

procedure TTransport.Draw;
begin
  Resources.UnitsTexture.DrawRot(
      (FPosition.X + FCamera.X) * FZoom,
      (FPosition.Y + FCamera.Y) * FZoom,
      FDirection, 0.1 * FViewZoom,
      FColor, R_TRANSPORT);
end;        

procedure TTransport.OnHit(Damage: Integer);
begin
  FManager.AddMapObject(
    TBlow.CreateBlow(Self, 2.5, 4));   
  FAction(0);//Message about transport death
  Self.KillMe:= true;
end;
{$ENDREGION}

{$REGION 'TInterceptor'}
constructor TInterceptor.Create;
begin
  FWeapon:= TWeapon.Create;
  FPosition:= Source.Position;
  FWayPoint:= FPosition;
  FDock:= Source;
  FFuel:= JET_MAX_FUEL;
  FWayPoint:= FDock.FPosition;
  FDirection:= 0;
  FLife:= JET_LIFE;
  FState:= JS_BACK;
  Side:= NewSide;
  FColor:= GetSideColor(Side);
end;

procedure TInterceptor.Act(dt:single);
var
  Speed: Single;
  Nearest: TMapObject;
begin
  FWeapon.Act(dt);

  //Fuel
  if FState = JS_FLY then
  begin
    FFuel:= FFuel - dt;
    if FFuel < 0 then //End of fuel
      begin
        Nearest:= FManager.TryToFindNearestBuilding(Self);
        if Nearest <> nil then
          begin
            Self.DockToObj(Nearest);
          end
        else
          begin
            OnHit(1);
            OnHit(1);  
            OnHit(1);
            OnHit(1);
          end;
      end;
  end;       

  if FWeapon.FRockets<=0 then
    FFuel:= 0;

  Speed:= JET_SPEED;
  FWait:= FWait - dt;
  if FWait < 0 then
    FWait:= 0;      
  if WayPointShowTime > 0 then
    WayPointShowTime:= WayPointShowTime - dt;
  case FState of
    JS_DOCKED:
      begin
        FFuel:= JET_MAX_FUEL;
        FWeapon.FRockets:= JET_MAX_ROCKETS;
        FLife:= JET_LIFE;
      end;
    JS_BACK, JS_FLY:
      begin
        if not(FWait>0) then
        begin
          FHeight:= FHeight + dt * 1.8;
          if FHeight > 3 then
            FHeight:= 3;
          FDirection:= RotateToAngle(FDirection, GetAngle(FWayPoint - FPosition), 0.4);
          FPosition:= FPosition + GetVector(FDirection, Speed * dt);   
          FViewShift:= FViewShift * 0.998 + FRandomDir * 0.002;
          if (Random(200) = 0) then
            FRandomDir:= Vector2(Random(100) - 50, Random(100) - 50) * 0.1;
        end;
      end;
  end;
  if (FState = JS_BACK) then
    if FPosition.Distance(FDock.FPosition) < 5 then
      begin
        FManager.DockJet(Self);
      end;
end;    

procedure TInterceptor.DrawShadow;
begin
  case FState of
    JS_DOCKED:;
    JS_BACK,
    JS_FLY:
      begin
        //shadow
        Resources.UnitsTexture.DrawRot(
            (FPosition.X + FViewShift.X + FCamera.X + FHeight) * FZoom,
            (FPosition.Y + FViewShift.Y + FCamera.Y + FHeight) * FZoom,
            FDirection,
            FViewZoom * 0.1,
            $50000000,
            R_JET);
      end;
  end;
end;

procedure TInterceptor.Draw;
var
  ViewPosition: TVector2;
begin
  case FState of
    JS_DOCKED:
      Resources.UnitsTexture.DrawRot(
          (FPosition.X + FCamera.X) * FZoom + FViewShift.X * FViewZoom,
          (FPosition.Y + FCamera.Y) * FZoom + FViewShift.Y * FViewZoom,
          FDirection,
          0.1 * FViewZoom,
          FColor - $88000000,
          R_JET);
    JS_BACK,
    JS_FLY:
      begin
        if WayPointShowTime > 0 then
          Resources.UnitsTexture.DrawRot(
              (FWayPoint.X + FCamera.X) * FZoom,
              (FWayPoint.Y + FCamera.Y) * FZoom,
              0, 0.05 * FViewZoom,
              FColor - $80000000, R_RESOURCE);
        Resources.UnitsTexture.DrawRot(
            (FPosition.X + FViewShift.X + FCamera.X) * FZoom,
            (FPosition.Y + FViewShift.Y + FCamera.Y) * FZoom,
            FDirection, 0.1 * FViewZoom,
            FColor, R_JET);
      end;
  end;
end;

function TInterceptor.GetViewPosition(): TVector2;
begin
  Result:= (FPosition + FCamera) * FZoom + FViewShift * FViewZoom;
end;

procedure TInterceptor.SetInterfacePosition(Position: Byte);
begin
  FState:= JS_DOCKED;
  FDirection:= 0;
  FPosition:= FDock.FPosition;
  FViewShift:= Vector2(Position * 6 + 6, -12);
end;

function TInterceptor.IsDocked: Boolean;
begin
  Result:= (FState = JS_DOCKED);
end;  

procedure TInterceptor.Wait(WaitingTime: Single);
begin
  if WaitingTime > FWait then
    FWait:= WaitingTime;
end;
                
procedure TInterceptor.ShowWayPoint;
begin
  WayPointShowTime:= 3;
end;

procedure TInterceptor.FlyToPoint(Point: TVector2);
begin
  if IsDocked then
    begin
      FManager.WaitForTurn(Self);
      FDirection:= GetRound(Random(10)-5);
      FViewShift:= Vector2(0,0);
    end;
  ShowWayPoint;
  FState:= JS_FLY;             
  FWayPoint:= Point;
end;

procedure TInterceptor.DockToObj(Base :TMapObject);
begin
  if Base.Side <> Self.Side then
    Exit;
  if IsDocked then
    begin
      FManager.WaitForTurn(Self);
      FDirection:= GetRound(Random(10)-5);
    end;       
  ShowWayPoint;
  FState:= JS_BACK;
  FWayPoint:= Base.Position;   
  FDock:= Base;
end;

procedure TInterceptor.OnHit(Damage: Integer);
begin
  FLife:= FLife - 1;
  if FLife <= 0 then
    begin
      FManager.AddMapObject(
        TBlow.CreateBlow(Self, 2, 3));
      Self.KillMe:= true;
    end;
end;
{$ENDREGION}

{$REGION 'THelicopter'}
constructor THelicopter.Create;
begin
  FWeapon:= TAtrillery.Create;
  FRotation:= Random(360);
  FPosition:= Source.Position;
  FWayPoint:= FPosition;
  FDock:= Source;
  FWayPoint:= FDock.FPosition;
  FDirection:= 0;
  FFuel:= HELI_MAX_FUEL;
  FLife:= HELI_LIFE;
  FState:= HS_BACK;
  Side:= NewSide;
  FColor:= GetSideColor(Side);
end;

procedure THelicopter.Act(dt:single);
var
  Speed: Single;
  Angle: Single;
  Nearest: TMapObject;
begin
  FWeapon.Act(dt);
  Speed:= HELI_SPEED;
  FWait:= FWait - dt;
  
  //Fuel
  if FState = HS_FLY then
  begin
    FFuel:= FFuel - dt;
    if FFuel < 0 then //End of fuel
      begin
        Nearest:= FManager.TryToFindNearestBuilding(Self);
        if Nearest <> nil then
          begin
            Self.DockToObj(Nearest);
          end
        else
          begin
            OnHit(1);
            OnHit(1);  
            OnHit(1);
            OnHit(1);
            OnHit(1);
            OnHit(1);
          end;
      end;
  end;
  if FWeapon.FRockets<=0 then
    FFuel:= 0;

  FRotation:= GetRound(FRotation + dt * 500);
  if FWait < 0 then
    FWait:= 0;      
  if WayPointShowTime > 0 then
    WayPointShowTime:= WayPointShowTime - dt;
  case FState of
    HS_DOCKED:
      begin
        FLife:= HELI_LIFE;
        FFuel:= HELI_MAX_FUEL;  
        FWeapon.FRockets:= HELI_MAX_ROCKETS;
      end;
    HS_BACK, HS_FLY:
      begin
        if not(FWait>0) then
        begin
          FHeight:= FHeight + dt * 1.8;
          if FHeight > 3 then
            FHeight:= 3;
          Angle:= GetAngle(FWayPoint - FPosition);
          FDirection:= RotateToAngle(FDirection, Angle, 0.4); 
          if FPosition.Distance(FWayPoint) > 5 then
            FPosition:= FPosition + GetVector(Angle, Speed * dt);
          FViewShift:= FViewShift * 0.995 + FRandomDir * 0.005;
          if (Random(200) = 0) then
            FRandomDir:= Vector2(Random(100) - 50, Random(100) - 50) * 0.3;
        end;
      end;
    HS_COMBAT:;
    HS_DEAD  :;
  end;
  if (FState = HS_BACK) then
    if FPosition.Distance(FDock.FPosition) < 5 then
      begin
        FManager.DockHeli(Self);
      end;
end;    

procedure THelicopter.DrawShadow;
begin
  case FState of
    HS_DOCKED:;
    HS_BACK,
    HS_COMBAT,
    HS_DEAD,
    HS_FLY:
      begin
        //shadow
        Resources.UnitsTexture.DrawRot(
            (FPosition.X + FViewShift.X  + FCamera.X + FHeight) * FZoom,
            (FPosition.Y + FViewShift.Y  + FCamera.Y + FHeight) * FZoom,
            FDirection,
            FViewZoom * 0.1,
            $50000000,
            R_HELI);
        Resources.UnitsTexture.DrawRot(
            (FPosition.X + FViewShift.X  + FCamera.X + FHeight) * FZoom,
            (FPosition.Y + FViewShift.Y  + FCamera.Y + FHeight) * FZoom,
            FRotation,
            FViewZoom * 0.1,
            $30000000,
            R_HELIVINT);
      end;
  end;
end;

procedure THelicopter.Draw;
var
  ViewPosition: TVector2;
begin
  case FState of
    HS_DOCKED:
      begin
        Resources.UnitsTexture.DrawRot(
            (FPosition.X + FCamera.X) * FZoom + FViewShift.X * FViewZoom,
            (FPosition.Y + FCamera.Y) * FZoom + FViewShift.Y * FViewZoom,
            FDirection,
            0.1 * FViewZoom,
            FColor - $88000000,
            R_HELI);
        Resources.UnitsTexture.DrawRot(
            (FPosition.X + FCamera.X) * FZoom + FViewShift.X * FViewZoom,
            (FPosition.Y + FCamera.Y) * FZoom + FViewShift.Y * FViewZoom,
            FDirection + 22,
            0.1 * FViewZoom,
            FColor - $98000000,
            R_HELIVINT);
      end;
    HS_BACK,
    HS_COMBAT,
    HS_DEAD,
    HS_FLY:
      begin
        if WayPointShowTime > 0 then
          Resources.UnitsTexture.DrawRot(
              (FWayPoint.X + FCamera.X) * FZoom,
              (FWayPoint.Y + FCamera.Y) * FZoom,
              0, 0.05 * FViewZoom,
              FColor - $80000000, R_RESOURCE);
        Resources.UnitsTexture.DrawRot(
            (FPosition.X + FViewShift.X + FCamera.X) * FZoom,
            (FPosition.Y + FViewShift.Y  + FCamera.Y) * FZoom,
            FDirection, 0.1 * FViewZoom,
            FColor, R_HELI);
        Resources.UnitsTexture.DrawRot(
            (FPosition.X + FViewShift.X  + FCamera.X) * FZoom,
            (FPosition.Y + FViewShift.Y  + FCamera.Y) * FZoom,
            FRotation, 0.1 * FViewZoom,
            FColor - $40000000, R_HELIVINT);
      end;
  end;
end;

function THelicopter.GetViewPosition(): TVector2;
begin
  Result:= (FPosition + FCamera) * FZoom + FViewShift * FViewZoom;
end;

procedure THelicopter.SetInterfacePosition(Position: Byte);
begin
  FState:= HS_DOCKED;
  FDirection:= 0;
  FPosition:= FDock.FPosition;
  FViewShift:= Vector2( -Position * 6 - 6, 12);
end;

function THelicopter.IsDocked: Boolean;
begin
  Result:= (FState = HS_DOCKED);
end;

procedure THelicopter.Wait(WaitingTime: Single);
begin
  if WaitingTime > FWait then
    FWait:= WaitingTime;
end;

procedure THelicopter.ShowWayPoint;
begin
  WayPointShowTime:= 3;
end;

procedure THelicopter.FlyToPoint(Point: TVector2);
begin
  if IsDocked then
    begin
      FManager.WaitHeli(Self);
      FDirection:= GetRound(Random(10)-5);
      FViewShift:= Vector2(0,0);
    end;
  ShowWayPoint;
  FState:= HS_FLY;
  FWayPoint:= Point;
end;

procedure THelicopter.DockToObj(Base :TMapObject);
begin           
  if Base.Side <> Self.Side then
    Exit;   
  if IsDocked then
    begin
      FManager.WaitHeli(Self);
      FDirection:= GetRound(Random(10)-5);  
      FViewShift:= Vector2(0,0);
    end;
  ShowWayPoint;
  FState:= HS_BACK;
  FWayPoint:= Base.Position;
  FDock:= Base;
end;

procedure THelicopter.OnHit(Damage: Integer);
begin
  FLife:= FLife - 1;
  if FLife <= 0 then
    begin
      FManager.AddMapObject(
        TBlow.CreateBlow(Self, 2, 3));
      Self.KillMe:= true;
    end;
end;
{$ENDREGION}

{$REGION 'Rocket'}
constructor TRocket.CreateRocket(
        Source,
        Dest: TMapObject;
        Parameter: Integer;
        Action: TAction;
        Direction: Single = 0);
begin
  FPosition:= Source.FPosition;
  FDestination:= Dest;
  FDestination.UnderFire:= FDestination.UnderFire + 1;
  FDirection:= Direction;//GetRound(GetAngle(FDestination.FPosition - FPosition) + Random(90) - 45);
  FLifeTime:= LIFE_TIME;  
  FAction:= Action;
  FParam:= Parameter;
  FSpeed:= 20;
end;

procedure TRocket.Act(dt:single);
var
  Correction: Integer;
  procedure AutoDestroy;
  begin                
    FManager.AddMapObject(
      TBlow.CreateBlow(Self, 1.5, 1));
    KillMe:= true;
    if FDestination <> nil then
      FDestination.UnderFire:= FDestination.UnderFire - 1;
  end;
begin
  Correction:= 0;
  FLifeTime:= FLifeTime - dt;
  if FLifeTime < 0 then
    AutoDestroy;
  if FDestination <> nil then
    begin
      FDirection:= RotateToAngle(FDirection, GetAngle(FDestination.FPosition - FPosition), 3.8);
      if FPosition.Distance(FDestination.FPosition) > 30 then              
        Correction:= Random(30) - 15;
      if FPosition.Distance(FDestination.FPosition) < 12 then
      begin
        if (Random(2) = 0) then
          if (FDestination is THelicopter) or
             (FDestination is TInterceptor) then
            FDestination:= nil;
        if FDestination <> nil then
          begin
            FAction(FParam);
            AutoDestroy;
          end;
      end;
    end;
  FSpeed:= FSpeed + FSpeed * dt * 1.1;
  if FSpeed > ROCKET_SPEED then
    FSpeed:= ROCKET_SPEED;  
  FPosition:= FPosition + GetVector(FDirection + Correction, FSpeed * dt);
end;

procedure TRocket.Draw;
begin
  Resources.UnitsTexture.DrawRot(
      (FPosition.X + FCamera.X) * FZoom,
      (FPosition.Y + FCamera.Y) * FZoom,
      FDirection, 0.06 * FViewZoom,
      $fff0f0f0, R_ROCKET);
end;
{$ENDREGION}

{$REGION 'TBlow'}
constructor TBlow.CreateBlow(Source: TMapObject; Life, Size: Single);
begin
  FLife:= Life;
  FLifeTime:= Life;
  FPosition:= Source.Position + Vector2(Random(20 - 10), Random(20 - 10));
  FSize:= Size;
end;

procedure TBlow.Act(dt:single);
begin
  FLifeTime:= FLifeTime - dt;
  if FLifeTime<=0 then
    begin
      KillMe:= true;
      FLifeTime:= 0;
    end;
    
  FAlpha:= 1;
  if (FLifeTime > FLife * 0.75) then
    FAlpha:= (FLife * 0.75 - FLifeTime) / FLife * 4;
  if (FLifeTime < FLife * 0.5) then
    FAlpha:= (FLifeTime) / FLife * 2;
end;

procedure TBlow.Draw;
begin
  FColor:= Round($80 * FAlpha) * $1000000 + $ffffff;
  Resources.UnitsTexture.DrawRot(
      (FPosition.X + FCamera.X) * FZoom,
      (FPosition.Y + FCamera.Y) * FZoom,
      0, 0.2 * FSize * FAlpha,
      FColor, R_BLOW);
end;
{$ENDREGION}

{$REGION 'TText'}
constructor TText.CreateText(Source: TMapObject; Life: Single; Text: String; Color: Cardinal = $ffffffff);
begin
  FText:= Text;
  FLife:= Life;
  FLifeTime:= Life;
  FPosition:= Source.Position;
  FColor:= Color - $ff000000;
  FShift:= 6;
end;

procedure TText.Act(dt:single);
begin
  FLifeTime:= FLifeTime - dt;
  if FLifeTime<=0 then
    begin
      KillMe:= true;
      FLifeTime:= 0;
    end;
  FShift:= FShift + dt * 5;
  FAlpha:= 1;
  if (FLifeTime > FLife * 0.75) then
    FAlpha:= ((FLife * 0.75 - FLifeTime) / FLife) * 4;
  if FAlpha > 1 then
    FAlpha:= 1;
  if (FLifeTime < FLife * 0.25) then
    FAlpha:= (FLifeTime / FLife) * 4;
end;

procedure TText.Draw;
var
  Color: Cardinal;
begin
  Color:= Round($ff * FAlpha) * $1000000 + FColor;
  Resources.Font.TextOutAligned(
      (FPosition.X + FCamera.X) * FZoom,
      (FPosition.Y + FCamera.Y) * FZoom - FShift * FViewZoom - 10,
      0.4,
      PAnsiChar(FText),
      Color,
      qfaCenter);
end;
{$ENDREGION}

{$REGION 'TWeapon'}
procedure TWeapon.Act(dt:single);
begin
  if FTimer > 0  then
    FTimer:= FTimer - dt
  else
    if Random(20) = 0 then
      FTimer:= 0.6;
end;

procedure TWeapon.Fire(Attacker, Enemy: TMapObject);
var
  i: Byte;
  direction: Single;
begin
  if FRockets <= 0 then
    Exit;
  if FTimer > 0 then
    Exit;     
  Dec(FRockets);
  direction:= 0;
  if Attacker is TInterceptor then
    direction:= TInterceptor(Attacker).FDirection;
  FTimer:= RELOAD_TIME;
  for i:=0 to 1 do
    FManager.AddMapObject(
      TRocket.CreateRocket(
        Attacker,
        Enemy,
        DAMAGE,
        Enemy.OnHit,
        direction - 20 + i * 40)
      );
end;

function TWeapon.CanFire: Boolean;
begin
  Result:= Ftimer <= 0;
end;

function TWeapon.CanAttack(Obj: TMapObject): Boolean;
begin
  Result:= false;
  if (Obj is TInterceptor) then
    if (not TInterceptor(Obj).IsDocked) then   
      if (TInterceptor(Obj).UnderFire < 4) then
        Result:= true;    
{  if (Obj is TBase) then
    Result:= true;              
  if (Obj is TResFactory) then
    if (TResFactory(Obj).Level > 0) then
      Result:= true;   }
  if (Obj is THelicopter) then
    if (not THelicopter(Obj).IsDocked) then
      if (THelicopter(Obj).UnderFire < 4) then
        Result:= true;     
  if (Obj is TTransport) then
    Result:= true;        
end;

class procedure TWeapon.SetControlManager(var Manager: TMapManager);
begin
  FManager:= Manager;
end;
{$ENDREGION}

{$REGION 'TAtrillery'}
procedure TAtrillery.Act(dt:single);
begin
  if FTimer > 0  then
    FTimer:= FTimer - dt
  else
    if Random(20) = 0 then
      FTimer:= 0.6;
end;

procedure TAtrillery.Fire(Attacker, Enemy: TMapObject);
var
  i: Byte;
  direction: Single;
begin       
  if FRockets <= 0 then
    Exit;
  if FTimer > 0 then
    Exit;
  Dec(FRockets);
  direction:= 0;
  if Attacker is TInterceptor then
    direction:= TInterceptor(Attacker).FDirection;
 //   Window.Title:= 'qwer';
  FTimer:= RELOAD_TIME;
  for i:=0 to 1 do
   begin
    FManager.AddMapObject(
      TRocket.CreateRocket(
        Attacker,
        Enemy,
        DAMAGE,
        Enemy.OnHit,
        direction - 20 + i * 40)
      );
   end;
end;

function TAtrillery.CanFire: Boolean;
begin
  Result:= Ftimer <= 0;
end;

function TAtrillery.CanAttack(Obj: TMapObject): Boolean;
begin
  Result:= false;
  if (Obj is TBase) then
    Result:= true;              
  if (Obj is TResFactory) then
    if (TResFactory(Obj).Level > 0) then
      Result:= true;
end;
{$ENDREGION}

{$REGION 'TAntiAir'}
constructor TAntiAir.Create;
begin
  inherited;
  FReloadTime:= 5;
end;

procedure TAntiAir.Act(dt:single);
begin
  if FTimer > 0  then
    FTimer:= FTimer - dt
  else
    if Random(20) = 0 then
      FTimer:= 0.3;
end;

procedure TAntiAir.Fire(Attacker, Enemy: TMapObject);
var
  i: Byte;
  direction: Single;
begin
  if FTimer > 0 then
    Exit;
  direction:= 0;
  if Attacker is TInterceptor then
    direction:= TInterceptor(Attacker).FDirection;
 //   Window.Title:= 'qwer';
  FTimer:= FReloadTime;
  for i:=0 to 1 do
   begin
    FManager.AddMapObject(
      TRocket.CreateRocket(
        Attacker,
        Enemy,
        DAMAGE,
        Enemy.OnHit,
        direction - 20 + i * 40)
      );
   end;
end;

function TAntiAir.CanFire: Boolean;
begin
  Result:= Ftimer <= 0;
end;

function TAntiAir.CanAttack(Obj: TMapObject): Boolean;
begin
  Result:= false;
  if (Obj is TInterceptor) then
    Result:= true;              
  if (Obj is TTransport) then
    Result:= true;
end;
{$ENDREGION}

end.
