unit uCommonGameTypes;

interface

uses  QuadEngine,
      uSMath;

type
{$REGION '  Bullets  '}
  eBulletShapeType = ( sCircle, sRect );

  pBulletInitParam = ^rBulletInitParam;
  rBulletInitParam = record
    shape   : eBulletShapeType;
    circle  : rCircle;
    rect    : rRect;

    sprite  : IQuadTexture;
    frame   : Word;
    maxFrame: Word;
    frameDT : Single;

    speed   : rVec2f;
    damage  : Word;
  end;

  pBullet = ^rBullet;
  rBullet = record
    private
      shape     : eBulletShapeType;
      circle    : rCircle;
      rect      : rRect;

      sprite    : IQuadTexture;
      frame     : Word;
      maxFrame  : Word;
      frameDT   : Single;
      lastFUpdt : Double;

      Pos       : rVec2f;
      Speed     : rVec2f;
      _damage   : Word;
      EnemyBul  : Boolean;

      stDead    : Boolean;

      prev  : pBullet;
      next  : pBullet;
    public
      procedure Init( const param: pBulletInitParam; p: rVec2f; enemy: Boolean; currentTime: Double );
      procedure Draw;
      procedure Update( dt: Double; currentTime: Double );
      procedure UpdateFrames( dt: Double; currentTime: Double );

      function CollideWithCircle( const _circle: rCircle ): Boolean;
      function CollideWithRect( const _rect: rRect ): Boolean;

      function AddToTail( var head, tail: pBullet ): pBullet;
      procedure Delete( var head, tail: pBullet );

      property Position     : rVec2f read Pos;
      property Damage       : Word read _damage;
      property EnemyBullet  : Boolean read EnemyBul;
      property Dead         : Boolean read stDead write stDead;

      property NextBullet : pBullet read next;
      property PrevBullet : pBullet read prev;
  end;
  {$ENDREGION}

{$REGION '  Explosions  '}
  pExplosionInitParam = ^rExplosionInitParam;
  rExplosionInitParam = record
    sprite  : IQuadTexture;
    frame   : Word;
    maxFrame: Word;
    frameDT : Single;
    speed   : rVec2f;
  end;

  pExplosion = ^rExplosion;
  rExplosion = record
    private
      sprite    : IQuadTexture;
      frame     : Word;
      maxFrame  : Word;
      frameDT   : Single;
      lastFUpdt : Double;
      Pos       : rVec2f;
      Speed     : rVec2f;
      stDead    : Boolean;

      prev  : pExplosion;
      next  : pExplosion;
    public
      procedure Init( const param: pExplosionInitParam; p: rVec2f; currentTime: Double );
      procedure Draw;
      procedure Update( dt: Double; currentTime: Double );
      procedure UpdateFrames( dt: Double; currentTime: Double );

      function AddToTail( var head, tail: pExplosion ): pExplosion;
      procedure Delete( var head, tail: pExplosion );

      property Dead         : Boolean read stDead;

      property NextExplosion : pExplosion read next;
      property PrevExplosion : pExplosion read prev;
  end;
{$ENDREGION}

  rRuleMove = record
    cStartPoint, cEndPoint  : rVec2f;
    aStartPoint, aEndPoint  : Single;
    rStartPoint, rEndPoint  : Single;
    Shoot     : Boolean;
    ShootTime : Double;
    allTime   : Double;
    nextRule  : Word;
  end;

  ePlayerWeaponType = ( wNormal, wAdvanced, wHeavy );

  pPlayerInitParam = ^rPlayerInitParam;
  rPlayerInitParam = record
    rect      : rRect;
    sposition : rVec2f;
    health    : Word;
    maxHealth : Word;
    time      : Double;
    game      : TObject;
  end;

  pRuleEnemyWave = ^rRuleEnemyWave;
  rRuleEnemyWave = record
    enemyType : array of Word;
    addParam  : array of Word;
    addDT     : array of Word;
    enemyNum  : Word;
    current   : Word;
    lastAdd   : Double;
    waitWaveDT: Word;
  end;

  pEnemyInitParam = ^rEnemyInitParam;
  rEnemyInitParam = record
    moveType  : Word;
    game      : TObject;
  end;

implementation

uses  uUtils;

////////////////////////////////////////////////////////////////////////////////
///  rBullet  //////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
{$REGION '  rBullet  '}
procedure rBullet.Init(const param: pBulletInitParam; p: rVec2f; enemy: Boolean; currentTime: Double);
begin
  shape     := param.shape;
  circle    := param.circle;
  rect      := param.rect;

  sprite    := param.sprite;
  frame     := param.frame;
  maxFrame  := param.maxFrame;
  frameDT   := param.frameDT;
  lastFUpdt := currentTime;

  Pos         := p;
  circle.Pos  := p;
  rect.Center := p;
  Speed     := param.speed;
  _damage   := param.damage;
  EnemyBul  := enemy;
end;

procedure rBullet.Draw;
begin
  sprite.DrawRot( Pos.X, Pos.Y, 0, 1, $FFFFFFFF, frame );
end;

procedure rBullet.Update(dt: Double; currentTime: Double);
begin
  case shape of
    sCircle: begin
      Pos         := Pos + Speed * dt;
      circle.Pos  := circle.Pos + Speed * dt;
    end;

    sRect: begin
      Pos         := Pos + Speed * dt;
      rect.Center := rect.Center + Speed * dt;
    end;
  end;
end;

procedure rBullet.UpdateFrames(dt: Double; currentTime: Double);
begin
  if currentTime - lastFUpdt >= frameDT then begin
    Inc( frame );
    lastFUpdt := currentTime;
  end;
  if frame > maxFrame then frame := 0;
end;

function rBullet.CollideWithCircle(const _circle: rCircle): Boolean;
var distance : rVec2f;
begin
  Result := False;
  case shape of
    sCircle: begin
      Result := collideCircleVsCircle( circle, _circle );
    end;

    sRect: begin
      Result := collideCircleVsRect( _circle, rect );
    end;
  end;
end;

function rBullet.CollideWithRect(const _rect: rRect): Boolean;
begin
  Result := False;
  case shape of
    sCircle: begin
      Result := collideCircleVsRect( circle, _rect );
    end;

    sRect: begin
      Result := collideRectVsRect( rect, _rect );
    end;
  end;
end;

function rBullet.AddToTail(var head: pBullet; var tail: pBullet): pBullet;
begin
  result := New( pBullet );

  result.prev := nil;
  result.next := nil;
  if head = nil then begin
    head := result;
    tail := result;
  end else begin
    result.prev  := tail;
    tail.next := result;
    tail      := result;
  end;

  Result.stDead := False;
end;

procedure rBullet.Delete(var head: pBullet; var tail: pBullet);
begin
  if Self.prev <> nil then Self.prev.next := Self.next else head := Self.next;
  if Self.next <> nil then Self.next.prev := Self.prev else tail := Self.prev;
  Dispose( @Self );
end;
{$ENDREGION}
////////////////////////////////////////////////////////////////////////////////
///  rExplosion  ///////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
{$REGION '  rExplosion  '}
procedure rExplosion.Init(const param: pExplosionInitParam; p: rVec2f; currentTime: Double);
begin
  sprite    := param.sprite;
  frame     := param.frame;
  maxFrame  := param.maxFrame;
  frameDT   := param.frameDT;
  lastFUpdt := currentTime;
  Pos       := p;
  Speed     := param.speed;
  stDead    := False;
end;

procedure rExplosion.Draw;
begin
  if stDead then Exit;
  sprite.DrawRot( Pos.X, Pos.Y, 0, 1.0, $FFFFFFFF, frame );
end;

procedure rExplosion.Update(dt: Double; currentTime: Double);
begin
  Pos := Pos + Speed * dt;
end;

procedure rExplosion.UpdateFrames(dt: Double; currentTime: Double);
begin
  if currentTime - lastFUpdt >= frameDT then begin
    Inc( frame );
    lastFUpdt := currentTime;
  end;
  if frame > maxFrame then stDead := True;
end;

function rExplosion.AddToTail(var head: pExplosion; var tail: pExplosion): pExplosion;
begin
  result := New( pExplosion );

  result.prev := nil;
  result.next := nil;
  if head = nil then begin
    head := result;
    tail := result;
  end else begin
    result.prev  := tail;
    tail.next := result;
    tail      := result;
  end;

  Result.stDead := False;
end;

procedure rExplosion.Delete(var head: pExplosion; var tail: pExplosion);
begin
  if Self.prev <> nil then Self.prev.next := Self.next else head := Self.next;
  if Self.next <> nil then Self.next.prev := Self.prev else tail := Self.prev;
  Dispose( @Self );
end;
{$ENDREGION}
////////////////////////////////////////////////////////////////////////////////
end.
