unit fwAnimation;
// -----------------------------------------------------------------------
// Autor:   Thomas Seban
// Created: any time in 2011 :)
// -----------------------------------------------------------------------

{$IFDEF FPC}
  {$MODE DELPHI}
{$ENDIF}

interface

uses fwCustomEngine,

     fwObject,
     fwObjectlist,
     fwTypes;

type
  TfwAnimFrame     = TCERect;
  TfwAnimSequences = class;

  TOnAnimationStart= procedure (aData : TObject) of Object;
  TOnAnimationEnd  = procedure (aData : TObject) of Object;

  TAnimDirection   = (
                       adNone,
                       adLeft,
                       adLeftUp,
                       adUp,
                       adUpRight,
                       adRight,
                       adRightDown,
                       adDown,
                       adDownLeft
                     );

  TSequenceID      = (
                       siNone         = 0,
                       siStandW       = 1,
                       siStandWN      = 2,
                       siStandN       = 3,
                       siStandNE      = 4,
                       siStandE       = 5,
                       siStandES      = 6,
                       siStandS       = 7,
                       siStandSW      = 8,
                       siWalkingW     = 9,
                       siWalkingWN    = 10,
                       siWalkingN     = 11,
                       siWalkingNE    = 12,
                       siWalkingE     = 13,
                       siWalkingES    = 14,
                       siWalkingS     = 15,
                       siWalkingSW    = 16,
                       siRunningW     = 17,
                       siRunningWN    = 18,
                       siRunningN     = 19,
                       siRunningNE    = 20,
                       siRunningE     = 21,
                       siRunningES    = 22,
                       siRunningS     = 23,
                       siRunningSW    = 24,
                       siDieing01     = 25,
                       siDieing02     = 26,
                       siDieing03     = 27,
                       siDieing04     = 28,
                       siDieing05     = 29
                     );

  TfwAnimSequence  = class(TfwObject)
  protected
    fOwner           : TfwAnimSequences;
    fSequenceID      : TSequenceID;
    fOffsetX         : Integer;  // Adding this offset to sprite position
    fOffsetY         : Integer;
    fDirection       : TAnimDirection;
    fAnimSpeed       : Cardinal;
    fAutoDie         : Boolean;
    fAutoLoop        : Boolean;
    fNext            : TfwAnimSequence;
    fFrames          : array of TfwAnimFrame;
    function  GetCount : Integer;
    function  GetRect(aPos : Integer) : TfwAnimFrame;
  public
    constructor Create(aOwner :  TfwAnimSequences; aSequenceID : TSequenceID = siNone; aDirection : TAnimDirection = adNone; aNext : TfwAnimSequence = nil);
    destructor Destroy; override;
    procedure AddFrame(aPosX, aPosY, aWidth, aHeight : Integer);
    procedure AddFrames(aPosX, aPosY, aWidth, aHeight, aCount, aStepX, aStepY : Integer);
    property AutoDie : Boolean read fAutoDie write fAutoDie;
    property AutoLoop : Boolean read fAutoLoop write fAutoLoop;
    property AnimSpeed : Cardinal read fAnimSpeed write fAnimSpeed;
    property Count : Integer read GetCount;
    property Direction : TAnimDirection read fDirection;
    property Items[aPos : Integer] : TfwAnimFrame read GetRect; default;
    property Next : TfwAnimSequence read fNext;
    property OffsetX : Integer read fOffsetX write fOffsetX;
    property OffsetY : Integer read fOffsetY write fOffsetY;
    property Owner : TfwAnimSequences read fOwner;
    property SequenceID : TSequenceID read fSequenceID;
  end;

  TfwAnimSequences = class(TfwObjectList)
  protected
    function  GetItem(aIndex : Integer) : TfwAnimSequence;
  public
    procedure Init; override;
    function  AddSequence(aSequenceID : TSequenceID = siNone; aDirection : TAnimDirection = adNone; aNext : TfwAnimSequence = nil) : TfwAnimSequence; overload;
    procedure AddSequence(aSequenceID : TSequenceID; aDirection : TAnimDirection; aPosX, aPosY, aWidth, aHeight, aCount, aStepX, aStepY : Integer); overload;
    procedure AddSequence(aSequenceID : TSequenceID; aPosX, aPosY, aWidth, aHeight, aCount, aStepX, aStepY : Integer); overload;
    procedure AddFrame(aSequence, aPosX, aPosY, aWidth, aHeight : Integer);
    procedure AddFrames(aSequence, aPosX, aPosY, aWidth, aHeight, aCount, aStepX, aStepY : Integer);
    function  GetRect(aSequence, aPos : Integer) : TfwAnimFrame;
    property Items[aIndex : Integer] : TfwAnimSequence read GetItem; default;
  end;

  TfwAnimDoneEvent = function (aOwner : TfwObject; aAnimation : TfwAnimSequence; aFrame : Integer) : Boolean of Object;

  TfwAnimController= class(TObject)
  protected
    fOwner           : TfwObject;
    fAnimation       : TfwAnimSequences;
    fSpeed           : Cardinal;
    fLastTick        : Cardinal;
    fAnimDone        : TfwAnimDoneEvent;
    fFrame           : Integer;
    fSequence        : Integer;
    fDieSequence     : Integer;
    procedure SetAnimation(aAnimation : TfwAnimSequences);
    function  GetAnimation(aSequence : Integer) : TfwAnimSequence;
    procedure SetSequence(aSequence : Integer);
    function  GetActive : TfwAnimSequence;
    procedure SetDieSequence(aSequence : Integer);
    function  GetDieSequence : Integer;
    procedure SetAutoDie(aAutoDie : Boolean);
    procedure SetAutoLoop(aAutoLoop : Boolean);
  public
    constructor Create(aOwner : TfwObject);
    destructor Destroy; override;
    function  GetRect : TfwAnimFrame;
    function HasAnimation : Boolean;
    function DoAnimation : Boolean; virtual;
    property Active : TfwAnimSequence read GetActive;
    property Animation : TfwAnimSequences read fAnimation write SetAnimation;
    property AnimDone : TfwAnimDoneEvent read fAnimDone write fAnimDone;
    property AutoDie : Boolean write SetAutoDie;
    property AutoLoop : Boolean write SetAutoLoop;
    property DieSequence : Integer read GetDieSequence write SetDieSequence;
    property Speed : Cardinal read fSpeed write fSpeed;
    property Frame : Integer read fFrame;
    property Sequence : Integer read fSequence write SetSequence;
    property Items[aSequence : Integer] : TfwAnimSequence read GetAnimation; default; 
  end;

implementation

// TfwAnimSequence -------------------------------------------------------
function TfwAnimSequence.GetCount : Integer;
begin
  Result := Length(fFrames);
end;

function TfwAnimSequence.GetRect(aPos : Integer) : TfwAnimFrame;
begin
  Result := fFrames[aPos mod Count];
end;

constructor TfwAnimSequence.Create(aOwner : TfwAnimSequences; aSequenceID : TSequenceID = siNone; aDirection : TAnimDirection = adNone; aNext : TfwAnimSequence = nil);
begin
  inherited Create;

  fOwner      := aOwner;
  fSequenceID := aSequenceID;
  fDirection  := aDirection;
  fNext       := aNext;
  fAnimSpeed  := 0;
  fOffsetX    := 0;
  fOffsetY    := 0;
  SetLength(fFrames, 0);
end;

destructor TfwAnimSequence.Destroy;
begin
  SetLength(fFrames, 0);
  
  inherited Destroy;
end;

procedure TfwAnimSequence.AddFrame(aPosX, aPosY, aWidth, aHeight : Integer);
var
  iPos             : Integer;
begin
  iPos := Count;

  SetLength(fFrames, iPos + 1);
  fFrames[iPos].X      := aPosX;
  fFrames[iPos].Y      := aPosY;
  fFrames[iPos].Width  := aWidth;
  fFrames[iPos].Height := aHeight;
end;

procedure TfwAnimSequence.AddFrames(aPosX, aPosY, aWidth, aHeight, aCount, aStepX, aStepY : Integer);
var
  iLoop            : Integer;
begin
  for iLoop := 0 to Pred(aCount) do begin
    AddFrame(aPosX, aPosY, aWidth, aHeight);
    Inc(aPosX, aStepX);
    Inc(aPosY, aStepY);
  end;
end;

// TfwAnimSequences ------------------------------------------------------
function TfwAnimSequences.GetItem(aIndex : Integer) : TfwAnimSequence;
begin
  Result := (inherited Items[aIndex] as TfwAnimSequence);
end;

procedure TfwAnimSequences.Init;
begin
  inherited Init; 
end;

function TfwAnimSequences.AddSequence(aSequenceID : TSequenceID = siNone; aDirection : TAnimDirection = adNone; aNext : TfwAnimSequence = nil) : TfwAnimSequence;
begin
  Result := TfwAnimSequence.Create(Self, aSequenceID, aDirection, aNext);
  if Assigned(Result) then begin
    Add(Result);
  end;
end;

procedure TfwAnimSequences.AddSequence(aSequenceID : TSequenceID; aDirection : TAnimDirection; aPosX, aPosY, aWidth, aHeight, aCount, aStepX, aStepY : Integer);
begin
  with AddSequence(aSequenceID, aDirection) do begin
    AddFrames(aPosX, aPosY, aWidth, aHeight, aCount, aStepX, aStepY);
  end;
end;

procedure TfwAnimSequences.AddSequence(aSequenceID : TSequenceID; aPosX, aPosY, aWidth, aHeight, aCount, aStepX, aStepY : Integer);
begin
  AddSequence(aSequenceID, adNone, aPosX, aPosY, aWidth, aHeight, aCount, aStepX, aStepY);
end;

procedure TfwAnimSequences.AddFrame(aSequence, aPosX, aPosY, aWidth, aHeight : Integer);
var
  Sequence         : TfwAnimSequence;
begin
  Sequence := Items[aSequence];
  if Assigned(Sequence) then begin
    Sequence.AddFrame(aPosX, aPosY, aWidth, aHeight);
  end;
end;

procedure TfwAnimSequences.AddFrames(aSequence, aPosX, aPosY, aWidth, aHeight, aCount, aStepX, aStepY : Integer);
var
  Sequence         : TfwAnimSequence;
  iLoop            : Integer;
begin
  Sequence := Items[aSequence];
  if Assigned(Sequence) then begin
    for iLoop := 0 to Pred(aCount) do begin
      Sequence.AddFrame(aPosX, aPosY, aWidth, aHeight);
      Inc(aPosX, aStepX);
      Inc(aPosY, aStepY);
    end;
  end;
end;

function TfwAnimSequences.GetRect(aSequence, aPos : Integer) : TfwAnimFrame;
var
  Sequence         : TfwAnimSequence;
begin
  Sequence := Items[aSequence];
  if Assigned(Sequence) then begin
    Result := Sequence[aPos];
  end;
end;

// TfwAnimController -----------------------------------------------------
procedure TfwAnimController.SetAnimation(aAnimation : TfwAnimSequences);
begin
  fAnimation := aAnimation;
  fFrame     := 0;
  fSequence  := 0;
end;

procedure TfwAnimController.SetSequence(aSequence : Integer);
begin
  if (aSequence >= 0) then begin
    if Assigned(fAnimation) then begin
      if (aSequence < fAnimation.Count) then begin
        fSequence := aSequence;
      end;
    end;
  end;
end;

function TfwAnimController.GetAnimation(aSequence : Integer) : TfwAnimSequence;
begin
  Result := nil;
  if Assigned(fAnimation) then begin
    if (aSequence < fAnimation.Count) then begin
      Result := fAnimation[aSequence];
    end;
  end;
end;

function TfwAnimController.GetDieSequence : Integer;
var
  iLoop            : Integer;
begin
  Result := fDieSequence;
  if (fDieSequence < 0) then begin
    if Assigned(fAnimation) then begin
      for iLoop := 0 to Pred(fAnimation.Count) do begin
        if fAnimation[iLoop].AutoDie then begin
          Result := iLoop;
          Exit;
        end;
      end;
    end;
  end;
end;

procedure TfwAnimController.SetDieSequence(aSequence : Integer);
begin
  fDieSequence := aSequence;
end;

function TfwAnimController.GetActive : TfwAnimSequence;
begin
  Result := nil;
  if Assigned(fAnimation) then begin
    Result := fAnimation[Sequence];
  end;
end;

procedure TfwAnimController.SetAutoDie(aAutoDie : Boolean);
var
  iLoop            : Integer;
begin
  for iLoop := 0 to Pred(fAnimation.Count) do begin
    fAnimation[iLoop].AutoDie := aAutoDie;
  end;
end;

procedure TfwAnimController.SetAutoLoop(aAutoLoop : Boolean);
var
  iLoop            : Integer;
begin
  for iLoop := 0 to Pred(fAnimation.Count) do begin
    fAnimation[iLoop].AutoLoop := aAutoLoop;
  end;
end;

constructor TfwAnimController.Create(aOwner : TfwObject);
begin
  inherited Create;

  fOwner       := aOwner;
  fAnimation   := nil;
  fSpeed       := 50;
  fLastTick    := 0;
  fAnimDone    := nil;
  fFrame       := 0;
  fSequence    := 0;
  fDieSequence := -1;
end;

destructor TfwAnimController.Destroy;
begin
  inherited Destroy;
end;

function TfwAnimController.GetRect : TfwAnimFrame;
begin
  if Assigned(fAnimation) then begin
    Result := fAnimation.GetRect(fSequence, fFrame);
  end else begin
    Result := CERectCreate(0, 0, 0, 0);
  end;
end;

function TfwAnimController.HasAnimation : Boolean;
begin
  Result := False;
  if Assigned(fAnimation) then begin
    if (fAnimation.Count > 0) then begin
      Result := True;
    end;
  end;
end;

function TfwAnimController.DoAnimation : Boolean;
var
  uAnimSpeed       : Cardinal;
  bAutoLoop        : Boolean;
begin
  Result := False;
  if Assigned(fAnimation) then begin
    if ((fSequence >= 0) and (fSequence < fAnimation.Count)) then begin
      uAnimSpeed := fAnimation[fSequence].AnimSpeed;
      if (uAnimSpeed = 0) then begin
        uAnimSpeed := fSpeed;
      end;

      if (uAnimSpeed > 0) then begin
        if ((fLastTick + uAnimSpeed) > HAL.GetTicks()) then begin
          Exit;
        end;
      end;

      if (fFrame < Pred(fAnimation[fSequence].Count)) then begin
        // Das Ende ist nicht erreicht, erhoehe den Frame-Counter
        Inc(fFrame);
        fLastTick := HAL.GetTicks();
      end else begin
        bAutoLoop := fAnimation[fSequence].AutoLoop;
        if Assigned(fAnimDone) then begin
          bAutoLoop := fAnimDone(fOwner, fAnimation[fSequence], fFrame);
        end;

        if bAutoLoop then begin
          fFrame := 0;
        end else begin
          Result := True;
        end;
      end;
    end;
  end;
end;

end.
