unit _World;

interface

Uses
  QuadEngine,
  _Scene,
  _Map,
  _Forms,
  _Geometry,
  _SysUtils,
  _Effects,
  _Camera,  
  _Units,
  _Resources;

const
  Max_In_Team = 5;

type           
TWorld = class(TBasicScene)
  private
    FQuad: IQuadRender;
    FEffects: TEffects;  
    FMap: TMap;         
    FUnit: Array [0..Max_Units] of TBaseUnit;
    FDelta: Double;
    FCurrentUnit: TBaseUnit;
    FCurrentUnitShift: TVector2;
    FControlUnit: TBaseUnit;
    FMaxControlDistance: Single;
    FFireForce: Single;
    FNeedAngle: Single;
    FWind: TVector2;
    FCurrentPlayerInTeam: Array [0..1] of Integer;
    FCurrentTeam: Byte;              
    FWait: Single;
    FWindAngle: Single;
    FNeedWindAngle: Single;
    procedure  SetCurrentUnit(CurrentUnit: TBaseUnit);
    procedure  NextTurn();                           
    procedure  DrawAim;
    procedure  Fire();
    procedure  FireMore();
    procedure  OnHit(Position: TVector2; Power: Single; Side: Byte);
    function   FindNearest(): TBaseUnit;
    procedure  DrawWindCompass();
  public
    procedure Win;
    Procedure Lose;
    constructor Create(Quad: IQuadRender);
    destructor Destroy; override;
    procedure Render; override;
    procedure Update(dt: single); override;
    procedure Control; override;
  end;

var
  World: TWorld;

implementation

constructor TWorld.Create(Quad: IQuadRender);
var
  i:Word;
begin
  FQuad:= Quad;
  FEffects:= TEffects.Create(FQuad);
  Camera.Quad.SetBlendMode(qbmSrcAlpha);
  FMap:= TMap.Create(Camera.Quad);
  FMaxControlDistance:= Window.Height * 0.42;
  FCurrentTeam:= 1;

  FCurrentPlayerInTeam[0]:= 1;
  for i:= 0 to Max_In_Team do
    begin
      FUnit[i]:= TMainUnit.Create(Vector2(Random(3000) - 1500 - 3000, Random(3000) - 1500), Random(360), 0);
    end;
  FCurrentPlayerInTeam[1]:= Max_In_Team + 1;
  for i:= 1 to Max_In_Team do
    begin
      FUnit[i + 1 + Max_In_Team]:= TMainUnit.Create(Vector2(Random(3000) - 1500 + 3000, Random(3000) - 1500), Random(360), 1);
    end;

  NextTurn();
end;

destructor TWorld.Destroy;
var
  i:Word;
begin
  FMap.Destroy;
  for i:= 0 to Max_Units do
    if FUnit[i] <> nil then
       FUnit[i].Destroy;
  Inherited;
end;

procedure  TWorld.SetCurrentUnit(CurrentUnit: TBaseUnit);
begin
  FCurrentUnit:= CurrentUnit;
  FControlUnit:= CurrentUnit;
end;

procedure TWorld.NextTurn();
var
  Finded: Boolean;
  i: Word;
begin
  FNeedWindAngle:= Random(360);
  FWait:= 1.5;
  if (FCurrentUnit <> nil) then
    FCurrentUnit.FCanFire:= true;
  FCurrentTeam:= 1 - FCurrentTeam;
  Finded:= False;
  i:=0;
  Repeat          
    FCurrentPlayerInTeam[FCurrentTeam]:=
      FCurrentPlayerInTeam[FCurrentTeam] + 1;
    if FCurrentPlayerInTeam[FCurrentTeam] >= Max_Units then
      FCurrentPlayerInTeam[FCurrentTeam]:= 0;

    if (FUnit[FCurrentPlayerInTeam[FCurrentTeam]] <> nil) then
      if ((FUnit[FCurrentPlayerInTeam[FCurrentTeam]].FSide = FCurrentTeam) and (FUnit[FCurrentPlayerInTeam[FCurrentTeam]] is TMainUnit)) then
        Finded:= True;
    Inc(i);
    if (i > 1000) then
      Finded:= true;

  Until (Finded);
  FControlUnit:= FUnit[FCurrentPlayerInTeam[FCurrentTeam]];
  FCurrentUnit:= FUnit[FCurrentPlayerInTeam[FCurrentTeam]];
  Window.Title:= IntToStr(FCurrentTeam) + ' ' + IntToStr( FCurrentPlayerInTeam[0]) + ' ' + IntToStr( FCurrentPlayerInTeam[1]);
  FCurrentUnitShift:= Vector2(0, 0);
  FFireForce:= 0;
end;

procedure TWorld.Win();
begin

end;

procedure TWorld.OnHit(Position: TVector2; Power: Single; Side: Byte);
var
  i: Word;
  Distance: Single;
begin
  for i:=0 to Max_Units do
    if FUnit[i] <> nil then  
    if FUnit[i] is TMainUnit then
      begin
        Distance:= FUnit[i].FPosition.Distance(Position);
        if (Distance < 300) then
          begin
            FUnit[i].FLife:= FUnit[i].FLife - Power * (1 - Distance / 300);
            FEffects.AddTextEffect('-' + IntToStr(Trunc(Power * (1 - Distance / 300))), Camera.GetPos(FUnit[i].FPosition), FUnit[i].FColor, 2);
          end;         
      end;
end;

procedure TWorld.Lose();
begin

end;

procedure TWorld.Fire();
var
  i: Word;
begin
  if (not (FCurrentUnit is TMainUnit)) then
    Exit;
  if FCurrentUnit.FCanFire then
  begin
    for i:=0 to Max_Units do
      if (FUnit[i] = nil) then
        begin
          FCurrentUnit.FCanFire:= false;
          FUnit[i]:= TBullet.Create(
            FCurrentUnit.FPosition,
            GetVector(FCurrentUnit.FAngle, FFireForce * 4000),
            FFireForce * 5000, FCurrentUnit.FAngle, FWindAngle, FCurrentUnit.FSide, FMap, OnHit);
          FCurrentUnit:= FUnit[i];
          FCurrentUnitShift:= Vector2(0, 0);
          Camera.ScaleDown;
          break;
        end;
  end
  else
  begin
    FCurrentUnit.FNeedPosition:= FCurrentUnit.FPosition + GetVector(FCurrentUnit.FAngle, FFireForce * 1000);
    NextTurn;
  end;
end;

procedure TWorld.FireMore();
var
  i: Word;
  j: Word;
  r: Single;
begin
  if (not (FCurrentUnit is TMainUnit)) then
    Exit;
  if FCurrentUnit.FCanFire then
  begin                   
    FCurrentUnit.FCanFire:= false;  
    for j:=0 to 5 do
    for i:=0 to Max_Units do
      if (FUnit[i] = nil) then
        begin
          r:= Random(1000) - 500;
          FUnit[i]:= TBullet.Create(
            FCurrentUnit.FPosition,
            GetVector(FCurrentUnit.FAngle + r / 50, FFireForce * (2000 + r)),
            FFireForce * (2500 + r * 1.25), FCurrentUnit.FAngle + r / 80, FWindAngle, FCurrentUnit.FSide, FMap, OnHit);
          FCurrentUnit:= FUnit[i];
          FCurrentUnitShift:= Vector2(0, 0);
          Camera.ScaleDown;
          break;
        end;
  end
  else
  begin
    FCurrentUnit.FNeedPosition:= FCurrentUnit.FPosition + GetVector(FCurrentUnit.FAngle, FFireForce * 1000);
    NextTurn;
  end;
end;

procedure TWorld.Control;
var
  Speed: Single;
begin
  Speed:= 10 * FDelta * 100;
  if (Window.key[KEY_D] or Window.key[KEY_RIGHT]) then begin Camera.Move(-Speed, 0); end;
  if (Window.key[KEY_A] or Window.key[KEY_LEFT ]) then begin Camera.Move( Speed, 0); end;
  if (Window.key[KEY_S] or Window.key[KEY_DOWN ]) then begin Camera.Move(0, -Speed); end;
  if (Window.key[KEY_W] or Window.key[KEY_UP   ]) then begin Camera.Move(0,  Speed); end;

  if Window.Mouse.Wheel > 0 then
    Camera.ScaleUp;
  if Window.Mouse.Wheel < 0 then
    Camera.ScaleDown;
  Window.Mouse.Wheel:= 0;    
                                      
  if FCurrentUnit <> nil then
    if (FCurrentUnit is TMainUnit) then
      if (FCurrentUnit.FSide = 0) then
        FCurrentUnitShift:= Vector2(Window.Mouse.x, Window.Mouse.y) - Camera.Center;

  FNeedAngle:= GetAngle(FCurrentUnitShift);
  FFireForce:= FCurrentUnitShift.Length / FMaxControlDistance;
  if FFireForce > 1 then
    FFireForce:= 1;

  if Window.Mouse.mbl and (FCurrentUnit.FSide = 0) then
    begin
      Fire();
      Window.Mouse.mbl:= false;
    end;

  if Window.Mouse.mbr and (FCurrentUnit.FSide = 0) then
    begin
      FireMore();
      Window.Mouse.mbr:= false;
    end;


  if Window.key[KEY_SPACE] then  //esc
  begin
    FEffects.Stress(2);
    Window.key[KEY_SPACE]:= false;
  end;

  if Window.key[KEY_TILDA] or Window.key[KEY_ESC] then  //esc
  begin
    Window.key[KEY_TILDA]:= false;
    Window.key[KEY_ESC]:= false;
    Window.Close;
    //Menu.ShowMenu;
  end;
end;
                     
procedure TWorld.DrawAim;
var
  AimPosition: TVector2;
  AimLength: Single;
  i: Word;
begin                                
  AimPosition:= GetVector(FNeedAngle, FFireForce * FMaxControlDistance * 2 * Camera.Scale); 
  AimLength:= AimPosition.Length;
  if ( FCurrentUnit.FCanFire) then
  begin
    for i:= 0 to Trunc(AimLength / 32) do
      Resources.RayTexture.DrawRot(
      Camera.GetX(FCurrentUnit.FPosition.x) + AimPosition.X * (1 / (AimLength / 32) * (i)) ,
      Camera.GetY(FCurrentUnit.FPosition.y) + AimPosition.Y * (1 / (AimLength / 32) * (i)), FCurrentUnit.FAngle, 1);

    Resources.AimTexture.DrawRot(
      Camera.GetX(FCurrentUnit.FPosition.x) + AimPosition.X,
      Camera.GetY(FCurrentUnit.FPosition.y) + AimPosition.Y, 0, 1);
    Resources.Font.TextOut(
      Camera.GetX(FCurrentUnit.FPosition.x) + AimPosition.X + 30,
      Camera.GetY(FCurrentUnit.FPosition.y) + AimPosition.Y,
      0.5, PAnsiChar(IntToStr(Trunc(FFireForce * 100))));
  end
  else
  begin
    for i:= 0 to Trunc(AimLength / 32) do
      Resources.RayTexture.DrawRot(
      Camera.GetX(FCurrentUnit.FPosition.x) + GetVector(FCurrentUnit.FAngle + 90, 30).X + AimPosition.X * (1 / (AimLength / 32) * (i)),
      Camera.GetY(FCurrentUnit.FPosition.y) + GetVector(FCurrentUnit.FAngle + 90, 30).Y + AimPosition.Y * (1 / (AimLength / 32) * (i)), FCurrentUnit.FAngle, 1);
    for i:= 0 to Trunc(AimLength / 32) do
      Resources.RayTexture.DrawRot(
      Camera.GetX(FCurrentUnit.FPosition.x) + GetVector(FCurrentUnit.FAngle - 90, 30).X + AimPosition.X * (1 / (AimLength / 32) * (i)) ,
      Camera.GetY(FCurrentUnit.FPosition.y) + GetVector(FCurrentUnit.FAngle - 90, 30).Y + AimPosition.Y * (1 / (AimLength / 32) * (i)), FCurrentUnit.FAngle, 1);
  end;
end;

procedure TWorld.DrawWindCompass();
var
  i: byte;
begin
  for i:=1 to 18 do
    Resources.ArrowTexture.DrawRot(Window.Width - 100, Window.Height - 100, i* 20 + FWindAngle, 5, $50ffffff);  
  Resources.ArrowTexture.DrawRot(Window.Width - 100 + GetVector(FWindAngle, 20).X, Window.Height - 100 + GetVector(FWindAngle, 20).Y, FWindAngle, 3, $ffffffff);
end;

procedure TWorld.Render;
var
  i:Word;
begin
  FQuad.Clear($ff000000);

 { if Window.Mouse.mbl then
    begin
      FMap.AddCrater(((Vector2(Window.Mouse.x, Window.Mouse.y) - Camera.Center) / Camera.Scale + Vector2(256, 256) + Camera.Shift), Random(360), 200);
      Window.Mouse.mbl:= false;
    end;  }

  FMap.Render;
  for i:= 0 to Max_Units do
    if (FUnit[i] <> nil) then
        FUnit[i].Draw();
  FEffects.Render;
  if FCurrentUnit <> nil then
    if (FCurrentUnit is TMainUnit) then
      if (FCurrentUnit.FSide = 0) then
        DrawAim();
  DrawWindCompass();
end;

function TWorld.FindNearest(): TBaseUnit;
var
  Distance: Single;
  Finded: TBaseUnit;
  i: Word;
begin
  Distance:= 1000000;
  Finded:= nil;
  for i:= 0 to Max_Units do
    if (FUnit[i] <> nil) then
      if (FUnit[i] is TMainUnit) then
        if (FUnit[i].FSide = 0) then
          if (Distance > FCurrentUnit.FPosition.Distance(FUnit[i].FPosition)) then
            begin
              Distance:= FCurrentUnit.FPosition.Distance(FUnit[i].FPosition);
              Finded:= FUnit[i];
            end;
  Result:= Finded;
end;

procedure TWorld.Update(dt: Single);
var
  i:Word;
  Enemy: TBaseUnit;
begin
  FWindAngle:= _Geometry.RotateToAngle(FWindAngle, FNeedWindAngle, 3); 
  FDelta:= dt;
  FMap.Update(dt);   
  FEffects.Update(dt);
  FCurrentUnit.NeedAngle:= FNeedAngle;
  if FCurrentUnit <> nil then     
    if not FCurrentUnit.KillMe then
      Camera.MoveTo(FCurrentUnit.FPosition + FCurrentUnitShift / Camera.Scale)
    else
      FCurrentUnit:= FControlUnit
  else                    
      FCurrentUnit:= FControlUnit;

  if (FControlUnit = nil) then
    NextTurn;
  

  Enemy:= nil;
  if FCurrentUnit <> nil then
    if (FCurrentUnit is TMainUnit) then
      if (FCurrentUnit.FSide = 1) then
        begin
          Enemy:= FindNearest();       
          FCurrentUnit.NeedAngle:= GetAngle(FCurrentUnit.FPosition, Enemy.FPosition);
        end;    
  if (FWait < 0) then  
  if (Enemy <> nil) then
    begin
      FCurrentUnit.FAngle:= GetAngle(FCurrentUnit.FPosition, Enemy.FPosition);
      FFireForce:= (Distance(Enemy.FPosition, FCurrentUnit.FPosition)) / (5000);
      if FFireForce > 1 then
        FFireForce:= 1;

      Fire();
    end;
  FWait:= FWait - dt;

  for i:= 0 to Max_Units do
    if (FUnit[i] <> nil) then
      begin
        FUnit[i].Act(dt);
        if (FUnit[i].KillMe) then
          begin
            FUnit[i].Destroy;
            FUnit[i]:= nil;
          end;
      end;
end;

end.
