unit Hero;

interface

uses
  _Geometry,   
  Frost.Forms, 
  Frost.Renderers,  
  Frost.Engine,
  Frost.GL,
  Frost.Utils,
  Planetoid,
  OpenGL;

const
  INVISIBLE_TIME = 6 * PI;
  HERO_HEIGHT = 0.1;
  ROTATION_FACTOR = 1.2;
  HERO_SPEED = 6;      
  SPHERE_SQR_RADIUS = 0.14 * 0.14;
  MARKER_POINT: Array [0..4] of TVector3 =  ((A : (-1.4, 0,-1)), (A : ( 1.4, 0,-1)), (A : (-1.1, 0, 0.5)), (A : ( 1.1, 0, 0.5)), (A : ( 0, 0,-1.3)));
  COLOR_LINE: Array [0..8] of TVector4 =  (
        (A : (0,0.1,  0, 1)),            
        (A : (0,0.4,  0, 1)),
        (A : (0,0.6,  0, 1)),
        (A : (0,  1,  0, 1)),
        (A : (0,0.8,0.1, 1)),
        (A : (0,0.6,0.2, 1)),
        (A : (0,0.4,0.3, 1)),
        (A : (0,0.2,0.5, 1)),
        (A : (0,0.1,0.9, 1)));

type
THero = class(TComponent)
  private            
    FHero: TGameObject;
    FLevel: SmallInt;
    FLevelAnimation: Single;
    //FLife: Single;
    FMesh: TMesh;
    FReloadTimer: Single;
    FFireSwitcher: Boolean;
    FFireSwitch: array [0..1] of TMatrix3;
    FInvincibilityTime: Single;
    function GetPosition: TVector3;
  protected
    procedure Fire(Level: Byte);
  public                        
    procedure AddLevel;        
    procedure KillLevel;
    procedure OnStart; override;
    procedure OnUpdate(const Delta: Double); override;
    procedure OnDestroy; override;
    property Position: TVector3 read GetPosition;
  public
    class var FInstance: THero;
  end;

implementation

uses
  Bullet,        
  Capsule,
  Frost.Particles,
  WorldScene;

{$REGION 'ChangeHeroMesh'}
function ChangeHeroMesh(Mesh: TMesh; Size: Single; Level: Single = 0; FInvincibilityTime: Single = 0): TMesh;
var
  i: Byte;
  Temp, Color: TVector3;
  A: Array [0..10] of TVector3;
  L1, L2, L3: Single;
begin
  L1 := -Level * 0.2 + 1;  
  L2 := -Level * 0.2 + 1.3;
  L3 := -Level * 0.2 + 1.64;
  if L1 > 1 then L1 := 1;
  if L2 > 1 then L2 := 1;
  if L3 > 1 then L3 := 1;
  
  //Center
  A[0] := Vector3( 0,  Size * 0.35,  0);
  //Back
  A[1] := Vector3( 0,  0, Size * 0.5);
  //Sides
  A[2] := Vector3(-Size,  0, Size);
  A[3] := Vector3( Size,  0, Size);
  A[4 ] := GetBesiePoint(MARKER_POINT[2], MARKER_POINT[4], MARKER_POINT[0], L1) * Size;
  A[5 ] := GetBesiePoint(MARKER_POINT[3], MARKER_POINT[4], MARKER_POINT[1], L1) * Size;

  A[6 ] := GetBesiePoint(MARKER_POINT[2], MARKER_POINT[4], MARKER_POINT[0], L2) * Size;
  A[7 ] := GetBesiePoint(MARKER_POINT[3], MARKER_POINT[4], MARKER_POINT[1], L2) * Size;
  A[8 ] := GetBesiePoint(MARKER_POINT[2], MARKER_POINT[4], MARKER_POINT[0], L3) * Size;
  A[9 ] := GetBesiePoint(MARKER_POINT[3], MARKER_POINT[4], MARKER_POINT[1], L3) * Size;


  Mesh.Vertex[0 * 3 + 0] := A[0];
  Mesh.Vertex[0 * 3 + 1] := A[1];
  Mesh.Vertex[0 * 3 + 2] := A[2];
  Mesh.Vertex[1 * 3 + 0] := A[0];
  Mesh.Vertex[1 * 3 + 1] := A[1];
  Mesh.Vertex[1 * 3 + 2] := A[3];
  Mesh.Vertex[2 * 3 + 0] := A[0];
  Mesh.Vertex[2 * 3 + 1] := A[4];
  Mesh.Vertex[2 * 3 + 2] := A[2];
  Mesh.Vertex[3 * 3 + 0] := A[0];
  Mesh.Vertex[3 * 3 + 1] := A[3];
  Mesh.Vertex[3 * 3 + 2] := A[5];
  Mesh.Vertex[4 * 3 + 0] := A[0];
  Mesh.Vertex[4 * 3 + 1] := A[6];
  Mesh.Vertex[4 * 3 + 2] := A[4];
  Mesh.Vertex[5 * 3 + 0] := A[0];
  Mesh.Vertex[5 * 3 + 1] := A[5];
  Mesh.Vertex[5 * 3 + 2] := A[7];   
  Mesh.Vertex[6 * 3 + 0] := A[0];
  Mesh.Vertex[6 * 3 + 1] := A[8];
  Mesh.Vertex[6 * 3 + 2] := A[6];
  Mesh.Vertex[7 * 3 + 0] := A[0];
  Mesh.Vertex[7 * 3 + 1] := A[7];
  Mesh.Vertex[7 * 3 + 2] := A[9];  
  Mesh.Vertex[8 * 3 + 0] := A[0];
  Mesh.Vertex[8 * 3 + 1] := A[9];
  Mesh.Vertex[8 * 3 + 2] := A[8];

  Level := Level + 3;                 
  if (Level < 0) then Level := 0;
  if (Level >= 7) then Level := 6.999;

  Color := COLOR_LINE[Trunc(Level + 1)].Lerp(COLOR_LINE[Trunc(Level)], Level - Trunc(Level)) * ((Cos(FInvincibilityTime) + 1) * 0.5);
  for i := 0 to 9 - 1 do
  begin
    Temp := GetNormal(Mesh.Vertex[i * 3 + 0], Mesh.Vertex[i * 3 + 1], Mesh.Vertex[i * 3 + 2]);
    Mesh.Normals[i * 3 + 0] := Temp;
    Mesh.Normals[i * 3 + 1] := Temp;
    Mesh.Normals[i * 3 + 2] := Temp; 
    Mesh.Textures[i * 3 + 0] := Vector2(0, 0);
    Mesh.Textures[i * 3 + 1] := Vector2(0, 0);
    Mesh.Textures[i * 3 + 2] := Vector2(0, 0);
    Mesh.Colors[i * 3 + 0] := Color;
    Mesh.Colors[i * 3 + 1] := Color;
    Mesh.Colors[i * 3 + 2] := Color;
  end;

  Mesh.Faces[ 0] := Face( 0, 2, 1);
  Mesh.Faces[ 1] := Face( 3, 4, 5);
  Mesh.Faces[ 2] := Face( 6, 7, 8);
  Mesh.Faces[ 3] := Face( 9,10,11);
  Mesh.Faces[ 4] := Face(12,13,14);
  Mesh.Faces[ 5] := Face(15,16,17);
  Mesh.Faces[ 6] := Face(18,19,20);
  Mesh.Faces[ 7] := Face(21,22,23);  
  Mesh.Faces[ 8] := Face(24,25,26);
  
  Result := Mesh;
end;

function CreateHeroMesh(Size: Single = 0.2; Level: Single = 0): TMesh;
var
  Vertex:   TVertexes;
  Normals:  TNormals;
  Textures: TTextures;
  Colors:   TColors;
  Faces: TFaces;
begin
  SetLength(Vertex, 9 * 3);
  SetLength(Textures, 9 * 3);
  SetLength(Normals, 9 * 3);
  SetLength(Colors, 9 * 3);
  SetLength(Faces, 9);

  Result := ChangeHeroMesh(TMesh.Create(Faces, Vertex, Textures, Normals, Colors), Size);
  SetLength(Vertex, 0);
  SetLength(Textures, 0);
  SetLength(Normals, 0);
  SetLength(Colors, 0);
  SetLength(Faces, 0);
end;
{$ENDREGION}

{$REGION 'Hero'}
function THero.GetPosition: TVector3;
begin
  Result := FHero.Position;
end;

procedure THero.AddLevel;
begin
  if (FLevel < 7) then
    Inc(FLevel);
  if (FLevel < 3) then
    FLevel := 3;
end;

procedure THero.KillLevel;
var
  j: Word;
begin
  if (FInvincibilityTime > 0) then
    Exit;
  //if (FLevel > 0) then
  Dec(FLevel);
  if (FLevel < 0) then
  begin
    World.Restart();   
    if (FLevel < 0) then
    begin
       //FEnable := False;
       //FMeshRenderer.Enable := False;
       for j := 0 to 30 - 1 do
         TParticleRendererLayer.Emit(
           FHero.Position,
           Vector3(Random(10) - 5, Random(10) - 5, Random(10) - 5) * 0.02,
           20, 0.3, 0.1);
       World.Stress();
    end;       
    FLevel := -1;
  end;
  FInvincibilityTime := INVISIBLE_TIME;
end;

procedure THero.OnStart;
begin
  FInstance := Self;
  FFireSwitch[0] := Rotate(-0.4, Vector3(0, 0, 1));
  FFireSwitch[1] := Rotate( 0.4, Vector3(0, 0, 1));
  FMesh := CreateHeroMesh();
  FHero := TGameObject.Create(GameObject);
  FHero.AddComponent(TMeshRenderer.CreateMeshRenderer(FMesh, TDummyMaterial.Create));
  FHero.LocalPosition := Vector3(0, TPlanetoid.Instance.Radius + HERO_HEIGHT, 0);
  GameObject.LocalPosition := VECTOR3_NULL;   
  GameObject.LocalRotation := Rotate(45, Vector3(1, 0, 0)) * GameObject.LocalRotation;
  FLevel := 2;
end;

procedure THero.OnUpdate(const Delta: Double);
var
  i, j: Word;
begin
  if (Control.Key[KEY_UP]) or (Control.Key[KEY_W]) then
    GameObject.LocalRotation := Rotate( delta * HERO_SPEED, Vector3(1, 0, 0)) * GameObject.LocalRotation;
  if (Control.Key[KEY_DOWN]) or (Control.Key[KEY_S]) then
    GameObject.LocalRotation := Rotate(-delta * HERO_SPEED, Vector3(1, 0, 0)) * GameObject.LocalRotation;
  if (Control.Key[KEY_LEFT]) or (Control.Key[KEY_A]) then
    GameObject.LocalRotation := Rotate(-delta * HERO_SPEED, Vector3(0, 0, 1)) * GameObject.LocalRotation;
  if (Control.Key[KEY_RIGHT]) or (Control.Key[KEY_D]) then
    GameObject.LocalRotation := Rotate( delta * HERO_SPEED, Vector3(0, 0, 1)) * GameObject.LocalRotation;

  GameObject.LocalRotation := Rotate((World.MouseDelta.X) * ROTATION_FACTOR * delta, Vector3(0, -1, 0)) * GameObject.LocalRotation;

  if (FReloadTimer > 0) then
  begin
    FReloadTimer := FReloadTimer - Delta;
  end;
  FLevelAnimation := FLevelAnimation * 0.9 + FLevel * 0.1;

  if (FInvincibilityTime > 0) then
  begin
    FInvincibilityTime := FInvincibilityTime - Delta * 2;
    if (FInvincibilityTime <= 0) then
    begin
      FInvincibilityTime := 0;
    end;
  end;

  ChangeHeroMesh(FMesh, 0.2 + FLevelAnimation * 0.02, FLevelAnimation - 3, FInvincibilityTime);

  if (Control.Mouse.ButtonLeft) and (FReloadTimer <= 0) then
  begin
    Fire(FLevel);
  end;
  if (Control.Mouse.ButtonRigth) then
  begin
    Control.Mouse.ButtonRigth := False;
  //  TCapsule.Add(Gameobject.LocalRotation, World.EndAction);
  end;

  for i := 0 to MAX_BULLETS do
    if (Bullets[i].Active) and (Bullets[i].Owner <> THero.FInstance.GameObject) then
    begin
      if ((Bullets[i].Position - FHero.Position).SQRMagnitude < SPHERE_SQR_RADIUS) then
      begin    
        KillLevel;       
        Bullets[i].Hide;
      end;
    end;
  //GameObject.LocalPosition := GameObject.LocalPosition * (TPlanetoid.Instance.Radius / GameObject.LocalPosition.Length);
end;

procedure THero.Fire(Level: Byte);
const
  RELOAD_SPEED = 0.6;
begin
  case Level of
    0, 1, 2, 3:begin
        TBullet.Fire(Self.GameObject,  Rotate((Random(100) - 50) * 0.01, Vector3(0, -1, 0)));
        FReloadTimer := RELOAD_SPEED * 1.6;
      end;      
    4:begin
        FFireSwitcher := not FFireSwitcher;
        if (FFireSwitcher) then
          TBullet.Fire(Self.GameObject, FFireSwitch[0] * Rotate((Random(100) - 50) * 0.01, Vector3(0, -1, 0)))
          else   
          TBullet.Fire(Self.GameObject, FFireSwitch[1] * Rotate((Random(100) - 50) * 0.01, Vector3(0, -1, 0)));
        FReloadTimer := RELOAD_SPEED * 1;
      end;
    5:begin
        TBullet.Fire(Self.GameObject, FFireSwitch[0] * Rotate((Random(100) - 50) * 0.01, Vector3(0, -1, 0)));
        TBullet.Fire(Self.GameObject, FFireSwitch[1] * Rotate((Random(100) - 50) * 0.01, Vector3(0, -1, 0)));
        FReloadTimer := RELOAD_SPEED * 0.9;
      end;      
    6:begin
        FFireSwitcher := not FFireSwitcher;
        if (FFireSwitcher) then
          TBullet.Fire(Self.GameObject, FFireSwitch[0] * Rotate((Random(100) - 50) * 0.01, Vector3(0, -1, 0)))
          else
          TBullet.Fire(Self.GameObject, FFireSwitch[1] * Rotate((Random(100) - 50) * 0.01, Vector3(0, -1, 0)));  
        TBullet.Fire(Self.GameObject, Rotate((Random(100) - 50) * 0.01, Vector3(0, -1, 0)));
        FReloadTimer := RELOAD_SPEED * 0.8;
      end;      
    7:begin
        TBullet.Fire(Self.GameObject, FFireSwitch[0] * Rotate((Random(100) - 50) * 0.01, Vector3(0, -1, 0)));
        TBullet.Fire(Self.GameObject, FFireSwitch[1] * Rotate((Random(100) - 50) * 0.01, Vector3(0, -1, 0)));
        TBullet.Fire(Self.GameObject, Rotate((Random(100) - 50) * 0.01, Vector3(0, -1, 0)));
        FReloadTimer := RELOAD_SPEED * 0.8;
      end;  
    8:begin
        TBullet.Fire(Self.GameObject, FFireSwitch[0] * Rotate((Random(100) - 50) * 0.01, Vector3(0, -1, 0)));
        TBullet.Fire(Self.GameObject, FFireSwitch[1] * Rotate((Random(100) - 50) * 0.01, Vector3(0, -1, 0)));
        TBullet.Fire(Self.GameObject, Rotate((Random(100) - 50) * 0.01, Vector3(0, -1, 0)));
        FReloadTimer := RELOAD_SPEED * 0.75;
      end;
  end;
end;

procedure THero.OnDestroy;
begin

end;
{$ENDREGION}

end.
