unit Enemies;

interface

uses
  NewtonImport,
  JEN_Header,
  JEN_Math;

const
   MAX_EnemyCount = 31;

type
  TEnemyKind = (ekOne, ekTwo, ekTank); //^_^

  PEnemy = ^TEnemy;
  TEnemy = class
    Pos  : TVec3f;
    Color: TVec3f;
    Heals: Single;
    Kind : TEnemyKind;
    KillMe : Boolean;
    procedure Update(dt: LongInt); virtual; abstract;
    procedure Hit(Power: Single); virtual; abstract;
  end;

  TEnemyOne = class(TEnemy)
    constructor Create;
    destructor Destroy; override;
    var
      CubePosSize : array[0..5] of TVec4f;
      Body        : array[0..5] of PNewtonBody;
      Seed        : LongInt;
      CreateTime  : LongInt;
      LastHit     : LongInt;
      HitParam    : Single;
    procedure Update(dt: LongInt); override;
    procedure Hit(Power: Single); override;
  end;

  TEnemyTwo = class(TEnemy)
    constructor Create;
    destructor Destroy; override;
    var
      CubePosSize : array[0..5] of TVec4f;
      Body        : array[0..5] of PNewtonBody;
      Seed        : LongInt;
      CreateTime  : LongInt;
      LastHit     : LongInt;
      HitParam    : Single;
    procedure Update(dt: LongInt); override;
    procedure Hit(Power: Single); override;
  end;

  TEnemyTank = class(TEnemy)
    constructor Create;
    destructor Destroy; override;
    var
      CubePosSize : TVec4f;
      Body        : PNewtonBody;
      CreateTime  : LongInt;
      LastHit     : LongInt;
      HitParam    : Single;
    procedure Update(dt: LongInt); override;
    procedure Hit(Power: Single); override;
  end;

  TEnemiesManager = class
    constructor Create;
    destructor Destroy; override;
  private
  type TVrtxFormat = record
      PosId  : TVec4f;
      Normal : TVec3f;
    end;
  var
    FLastCreate : LongInt;
    FEnemies: array[0..MAX_EnemyCount-1] of TEnemy;

    FShader : IShaderProgram;
    FPosAttrib : IShaderAttrib;
    FNormalAttrib : IShaderAttrib;
    FMatrixUniform : IShaderUniform;
    FPosSizeUniform : IShaderUniform;
    FColorUniform : IShaderUniform;
    FPosUniform : IShaderUniform;
    FLightPosUniform : IShaderUniform;
    FVrtBuff : IGeomBuffer;
  public
    procedure OnUpdate(dt: LongInt);
    procedure OnDraw;
  end;

implementation

uses
  Noise,
  Main;

destructor TEnemyOne.Destroy;
var
  i : integer;
begin
  inherited;

  for I := 0 to 5 do
      NewtonDestroyBody(Game.World, Body[I]);
end;

constructor TEnemyOne.Create;
var
  i : LongInt;
  s,c : Single;
  m : TMat4f;
  Colision: PNewtonCollision;
begin
  CreateTime := Utils.Time;
  Seed := Random(10000);

  SinCos(random*2*pi, s, c);
  Pos := Game.Terrain.GetCenter + vec3f(s, 0, c)*100.0;
  Kind := ekOne;
  Heals := 100;

  m.Identity;

  for I := 0 to 5 do
  begin
    Colision := NewtonCreateBox(Game.World, 1, 1, 1, 0, 0);
    Body[I] := NewtonCreateBody(Game.World, Colision, @m);
    NewtonBodySetMassMatrix(Body[I], 0, 0, 0, 0);
    NewtonBodySetUserData(Body[I], self as TEnemy);
    NewtonReleaseCollision(Game.World, Colision);
  end;


end;

procedure TEnemyOne.Update(dt: LongInt);
var
  i : integer;
  P1 : TVec4f;
  P2 : TVec4f;
  Position, Dir : TVec3f;
  m : TMat4f;
  Colision: PNewtonCollision;

  function RandomVector4f(X : LongInt): TVec4f;
  begin
    Result := vec4f(PerlinNoise(x,0)*3.5,PerlinNoise(x+121,0)*3.5,PerlinNoise(x+262,0)*3.5,(PerlinNoise(x+13,0)+1.0)*0.7);
  end;

begin

  for I := 0 to 5 do
  begin
    PSeed := Seed+I;
    P1 := RandomVector4f(0+(Utils.Time + I*5158) div 10000);
    P2 := RandomVector4f(1+(Utils.Time + I*5158) div 10000);
    CubePosSize[i] := P1.lerp(P2, Frac((Utils.Time + I*5158)/10000));
    CubePosSize[i].w := CubePosSize[i].w * min((Utils.Time - CreateTime)/5000,1.0) * (1.0 + min(0, Heals));
    CubePosSize[i].x := CubePosSize[i].x + Pos.x;
    CubePosSize[i].y := CubePosSize[i].y + Pos.y+ 4;
    CubePosSize[i].z := CubePosSize[i].z + Pos.z;

    Position.x := CubePosSize[i].x;
    Position.y := CubePosSize[i].y;
    Position.z := CubePosSize[i].z;

    m.Identity;
    M.Pos := Position;
    NewtonBodySetMatrix(Body[I], @m);

    with CubePosSize[i] do
      Colision := NewtonCreateBox(Game.World, w*2, w*2, w*2, 0, 0);

    NewtonBodySetCollision(Body[I], Colision);
    NewtonReleaseCollision(Game.World, Colision);
  end;

  if ((Game.Player.GetPos - Pos).Length < 10) and (Utils.Time - LastHit > 1000) then
  begin
    Game.Player.Hit(12);
    LastHit := Utils.Time;
  end;


  HitParam := Max(0, HitParam - (dt/100));
  Color := (vec3f(0.33, 0, 1).Lerp(vec3f(0, 0.5, 1), Heals/100)) + vec3f(1, 0, 0)* HitParam;
  Dir := (Game.Player.GetPos - Pos).Normal * (dt/180);
  Pos.x := Pos.x + Dir.x;
  Pos.z := Pos.z + Dir.z;
  Pos.y := Game.Terrain.GetHeight(Pos.x, Pos.z);

  if Heals <= 0 then
    Heals := Heals - dt/1000;

  if Heals <= -1 then
    KillMe := True;

end;

procedure TEnemyOne.Hit(Power: Single);
begin
  if Heals > 0  then
  begin
    Heals := Max(Heals - Power, 0);
    HitParam := HitParam + Power/30;
  end;
end;

destructor TEnemyTwo.Destroy;
var
  i : integer;
begin
  inherited;

  for I := 0 to 5 do
      NewtonDestroyBody(Game.World, Body[I]);
end;

constructor TEnemyTwo.Create;
var
  i : LongInt;
  s,c : Single;
  m : TMat4f;
  Colision: PNewtonCollision;
begin
  CreateTime := Utils.Time;
  Seed := Random(10000);

  SinCos(random*2*pi, s, c);
  Pos := Game.Terrain.GetCenter + vec3f(s, 0, c)*100.0;
  Kind := ekTwo;
  Heals := 60;

  m.Identity;

  for I := 0 to 5 do
  begin
    Colision := NewtonCreateBox(Game.World, 1, 1, 1, 0, 0);
    Body[I] := NewtonCreateBody(Game.World, Colision, @m);
    NewtonBodySetMassMatrix(Body[I], 0, 0, 0, 0);
    NewtonBodySetUserData(Body[I], self as TEnemy);
    NewtonReleaseCollision(Game.World, Colision);
  end;


end;

procedure TEnemyTwo.Update(dt: LongInt);
var
  i : integer;
  P1 : TVec4f;
  P2 : TVec4f;
  Position, Dir : TVec3f;
  m : TMat4f;
  Colision: PNewtonCollision;

  function RandomVector4f(X : LongInt): TVec4f;
  begin
    Result := vec4f(PerlinNoise(x,0)*3.5,PerlinNoise(x+121,0)*3.5,PerlinNoise(x+262,0)*3.5,(PerlinNoise(x+13,0)+1.0)*0.7);
  end;

begin

  for I := 0 to 5 do
  begin
    PSeed := Seed+I;
    P1 := RandomVector4f(0+(Utils.Time + I*5158) div 10000);
    P2 := RandomVector4f(1+(Utils.Time + I*5158) div 10000);
    CubePosSize[i] := P1.lerp(P2, Frac((Utils.Time + I*5158)/10000));
    CubePosSize[i].w := CubePosSize[i].w * min((Utils.Time - CreateTime)/5000,1.0) * (1.0 + min(0, Heals));
    CubePosSize[i].x := CubePosSize[i].x + Pos.x;
    CubePosSize[i].y := CubePosSize[i].y + Pos.y+ 4;
    CubePosSize[i].z := CubePosSize[i].z + Pos.z;

    Position.x := CubePosSize[i].x;
    Position.y := CubePosSize[i].y;
    Position.z := CubePosSize[i].z;

    m.Identity;
    M.Pos := Position;
    NewtonBodySetMatrix(Body[I], @m);

    with CubePosSize[i] do
      Colision := NewtonCreateBox(Game.World, w*2, w*2, w*2, 0, 0);

    NewtonBodySetCollision(Body[I], Colision);
    NewtonReleaseCollision(Game.World, Colision);
  end;

  if ((Game.Player.GetPos - Pos).Length < 10) and (Utils.Time - LastHit > 1000) then
  begin
    Game.Player.Hit(18);
    LastHit := Utils.Time;
  end;

  HitParam := Max(0, HitParam - (dt/100));
  Color := (vec3f(0.0, 0.7, 0.7).Lerp(vec3f(0, 0.3, 0.3), Heals/60)) + vec3f(1, 0, 0)* HitParam;
  Dir := (Game.Player.GetPos - Pos).Normal * (dt/250);
  Pos.x := Pos.x + Dir.x;
  Pos.z := Pos.z + Dir.z;
  Pos.y := Game.Terrain.GetHeight(Pos.x, Pos.z);

  if Heals <= 0 then
    Heals := Heals - dt/1000;

  if Heals <= -1 then
    KillMe := True;

end;

procedure TEnemyTwo.Hit(Power: Single);
begin
  if Heals > 0  then
  begin
    Heals := Max(Heals - Power, 0);
    HitParam := HitParam + Power/30;
  end;
end;

destructor TEnemyTank.Destroy;
begin
  inherited;
  NewtonDestroyBody(Game.World, Body);
end;

constructor TEnemyTank.Create;
var
  i : LongInt;
  s,c : Single;
  m : TMat4f;
  Colision: PNewtonCollision;
begin
  CreateTime := Utils.Time;
  SinCos(random*2*pi, s, c);
  Pos := Game.Terrain.GetCenter + vec3f(s, 0, c)*100.0;
  Kind := ekTank;
  Heals := 300;

  m.Identity;

  Colision := NewtonCreateBox(Game.World, 6, 6, 6, 0, 0);
  Body := NewtonCreateBody(Game.World, Colision, @m);
  NewtonBodySetMassMatrix(Body, 0, 0, 0, 0);
  NewtonBodySetUserData(Body, self as TEnemy);
  NewtonReleaseCollision(Game.World, Colision);
end;

procedure TEnemyTank.Update(dt: LongInt);
var
  i : integer;
  P1 : TVec4f;
  P2 : TVec4f;
  Dir : TVec3f;
  m : TMat4f;
  Colision: PNewtonCollision;

begin
    CubePosSize.w := 3.0 * min((Utils.Time - CreateTime)/5000,1.0) * (1.0 + min(0, Heals));
    CubePosSize.x := Pos.x;
    CubePosSize.y := Pos.y+ 4;
    CubePosSize.z := Pos.z;

    m.Identity;
    M.Pos := Pos + vec3f(0, 4, 0);
    NewtonBodySetMatrix(Body, @m);

  if ((Game.Player.GetPos - Pos).Length < 10) and (Utils.Time - LastHit > 1000) then
  begin
    Game.Player.Hit(20);
    LastHit := Utils.Time;
  end;

  HitParam := Max(0, HitParam - (dt/100));
  Color := (vec3f(1.0, 0.25, 0.0).Lerp(vec3f(0.8, 0.5, 0.1), Heals/300)) + vec3f(1, 0, 0)* HitParam;
  Dir := (Game.Player.GetPos - Pos).Normal * (dt/700);
  Pos.x := Pos.x + Dir.x;
  Pos.z := Pos.z + Dir.z;
  Pos.y := Game.Terrain.GetHeight(Pos.x, Pos.z);

  if Heals <= 0 then
    Heals := Heals - dt/1000;

  if Heals <= -1 then
    KillMe := True;
end;

procedure TEnemyTank.Hit(Power: Single);
begin
  if Heals > 0  then
  begin
    Heals := Max(Heals - Power, 0);
    HitParam := HitParam + Power/30;
  end;
end;

destructor TEnemiesManager.Destroy;
var
  I : integer;
begin

  for I := 0 to MAX_EnemyCount-1 do
    if Assigned(FEnemies[i]) then
      FEnemies[i].Free;

end;

constructor TEnemiesManager.Create;
var
  I,j : integer;
  VertexBuff : array of TVrtxFormat;
begin
  FLastCreate := Utils.Time + 2000;

  SetLength(VertexBuff, 144);
  for I := 0 to 5 do
  begin
    VertexBuff[i*24+0].PosId := vec4f( -1, -1, -1, i); VertexBuff[i*24+0].Normal := vec3f(0, -1, 0);
    VertexBuff[i*24+1].PosId := vec4f(  1, -1, -1, i); VertexBuff[i*24+1].Normal := vec3f(0, -1, 0);
    VertexBuff[i*24+2].PosId := vec4f(  1, -1,  1, i); VertexBuff[i*24+2].Normal := vec3f(0, -1, 0);
    VertexBuff[i*24+3].PosId := vec4f( -1, -1,  1, i); VertexBuff[i*24+3].Normal := vec3f(0, -1, 0);

    VertexBuff[i*24+4].PosId := vec4f( -1,  1, -1, i); VertexBuff[i*24+4].Normal := vec3f(0, 1, 0);
    VertexBuff[i*24+5].PosId := vec4f( -1,  1,  1, i); VertexBuff[i*24+5].Normal := vec3f(0, 1, 0);
    VertexBuff[i*24+6].PosId := vec4f(  1,  1,  1, i); VertexBuff[i*24+6].Normal := vec3f(0, 1, 0);
    VertexBuff[i*24+7].PosId := vec4f(  1,  1, -1, i); VertexBuff[i*24+7].Normal := vec3f(0, 1, 0);

    VertexBuff[i*24+8].PosId  := vec4f( -1, -1, -1, i); VertexBuff[i*24+8].Normal := vec3f(0, 0, -1);
    VertexBuff[i*24+9].PosId  := vec4f( -1,  1, -1, i); VertexBuff[i*24+9].Normal := vec3f(0, 0, -1);
    VertexBuff[i*24+10].PosId := vec4f(  1,  1, -1, i); VertexBuff[i*24+10].Normal := vec3f(0, 0, -1);
    VertexBuff[i*24+11].PosId := vec4f(  1, -1, -1, i); VertexBuff[i*24+11].Normal := vec3f(0, 0, -1);

    VertexBuff[i*24+12].PosId := vec4f( -1, -1,  1, i); VertexBuff[i*24+12].Normal := vec3f(0, 0, 1);
    VertexBuff[i*24+13].PosId := vec4f(  1, -1,  1, i); VertexBuff[i*24+13].Normal := vec3f(0, 0, 1);
    VertexBuff[i*24+14].PosId := vec4f(  1,  1,  1, i); VertexBuff[i*24+14].Normal := vec3f(0, 0, 1);
    VertexBuff[i*24+15].PosId := vec4f( -1,  1,  1, i); VertexBuff[i*24+15].Normal := vec3f(0, 0, 1);

    VertexBuff[i*24+16].PosId := vec4f( -1, -1, -1, i); VertexBuff[i*24+16].Normal := vec3f(-1, 0, 0);
    VertexBuff[i*24+17].PosId := vec4f( -1, -1,  1, i); VertexBuff[i*24+17].Normal := vec3f(-1, 0, 0);
    VertexBuff[i*24+18].PosId := vec4f( -1,  1,  1, i); VertexBuff[i*24+18].Normal := vec3f(-1, 0, 0);
    VertexBuff[i*24+19].PosId := vec4f( -1,  1, -1, i); VertexBuff[i*24+19].Normal := vec3f(-1, 0, 0);

    VertexBuff[i*24+20].PosId := vec4f( 1, -1, -1, i); VertexBuff[i*24+20].Normal := vec3f(1, 0, 0);
    VertexBuff[i*24+21].PosId := vec4f( 1,  1, -1, i); VertexBuff[i*24+21].Normal := vec3f(1, 0, 0);
    VertexBuff[i*24+22].PosId := vec4f( 1,  1,  1, i); VertexBuff[i*24+22].Normal := vec3f(1, 0, 0);
    VertexBuff[i*24+23].PosId := vec4f( 1, -1,  1, i); VertexBuff[i*24+23].Normal := vec3f(1, 0, 0);
  end;

  FVrtBuff := Render.CreateGeomBuffer(gbVertex, 144, sizeof(TVrtxFormat), @VertexBuff[0]);

  FShader := ResMan.LoadShader('Media\Enemy.xml').Compile;
  FMatrixUniform := FShader.Uniform('Jen_ModelViewProjectionMatrix', utMat4);
  FPosSizeUniform := FShader.Uniform('CubePosSize', utVec4);
  FColorUniform := FShader.Uniform('Color', utVec3);
  FPosUniform := FShader.Uniform('Jen_ViewPos', utVec3);
  FLightPosUniform := FShader.Uniform('Jen_LightPos', utVec3);
  FPosAttrib := FShader.Attrib('PosIdAttr', atVec4f, False);
  FNormalAttrib := FShader.Attrib('NormalAttr', atVec3f, False);
end;

procedure TEnemiesManager.OnUpdate(dt: LongInt);
var
  I,J : integer;
  Dir : TVec3f;
begin

  if (Utils.Time-FLastCreate>3000-Utils.Time/120) and (random > 0.5) then
  for I := 0 to MAX_EnemyCount-1 do
    if not Assigned(FEnemies[i]) then
    begin
      case Trunc(random*1000) of
      0..400 : FEnemies[i] := TEnemyOne.Create;
      401..700 : FEnemies[i] := TEnemyTwo.Create;
      701..1000 : FEnemies[i] := TEnemyTank.Create;
      end;

      FLastCreate := Utils.Time;
      break;
    end;

  for I := 0 to MAX_EnemyCount-1 do
    if Assigned(FEnemies[i]) then
    begin
      FEnemies[i].Update(dt);
      if FEnemies[i].KillMe then
      begin
        if random > 0.3 then
          Game.BonusMng.Add(FEnemies[i].Pos);

        FEnemies[i].Free;
        FEnemies[i] := nil;
      end;

    end;

  for I := 0 to MAX_EnemyCount-1 do
    for J := 0 to MAX_EnemyCount-1 do
      if Assigned(FEnemies[i]) and Assigned(FEnemies[J]) and (i<>j) then
      begin
        Dir := (FEnemies[i].Pos-FEnemies[j].Pos);
        Dir.y:=0;
        FEnemies[i].Pos := FEnemies[i].Pos + Dir*(0.001/Dir.LengthQ);
        FEnemies[J].Pos := FEnemies[J].Pos - Dir*(0.001/Dir.LengthQ);
      end;
end;

procedure TEnemiesManager.OnDraw;
var
  ViewProjMatr : TMat4f;
  CameraPos : TVec3f;
  LightPos : TVec3f;
  i : Integer;
begin
  ViewProjMatr := (Render.Matrix[mtProj]*Render.Matrix[mtView]);
  CameraPos := Render.CameraPos;
  LightPos := Game.Terrain.GetLightPos;

  Render.DepthWrite := True;
  Render.DepthTest := True;
  Render.BlendType := btNone;

  FShader.Bind;
  FPosUniform.Value(CameraPos);

  FVrtBuff.Bind;
  FMatrixUniform.Value(ViewProjMatr);
  FPosAttrib.Value(sizeof(TVrtxFormat),0);
  FNormalAttrib.Value(sizeof(TVrtxFormat),16);
  FPosAttrib.Enable;
  FNormalAttrib.Enable;

  for I := 0 to MAX_EnemyCount-1 do
  if Assigned(FEnemies[i]) then
    begin
      FLightPosUniform.Value(LightPos);

      case FEnemies[i].Kind  of
        ekOne:
          with (FEnemies[i] as TEnemyOne) do
          begin
            FColorUniform.Value(Color);
            FPosSizeUniform.Value(CubePosSize[0],6);
            FVrtBuff.Draw(gmQuads, 144, false);
          end;
        ekTwo:
          with (FEnemies[i] as TEnemyTwo) do
          begin
            FColorUniform.Value(Color);
            FPosSizeUniform.Value(CubePosSize[0],6);
            FVrtBuff.Draw(gmQuads, 144, false);
          end;
        ekTank:
          with (FEnemies[i] as TEnemyTank) do
          begin
            FColorUniform.Value(Color);
            FPosSizeUniform.Value(CubePosSize);
            FVrtBuff.Draw(gmQuads, 24, false);
          end;
      end;


    end;

end;


end.
