unit uCustomSprites;

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

interface

uses SysUtils,
     Classes,
     ContNrs,
     Math,

     fwTypes,
     fwCustomEngine,
     fwAnimation,

     uWorldData;

type
  TDrawListItem    = class;
  TDrawList        = class;

  TSpriteType      = (
                       stNone,
                       stEnemy,
                       stChest,
                       stMoney,
                       stWeapon,
                       stNPC
                     );

  TCustomSprite    = class(TObject)
  protected
    fSurface         : TfwCEDrawable;
    fX               : Integer;
    fY               : Integer;
    fZ               : Integer;
    fLayer           : Integer; // -1, 0, 1 wird fuer die Sortierung benoetigt
    fWidth           : Integer;
    fHeight          : Integer;
    fHitArea         : TCERect;
    fSpriteType      : TSpriteType;
    fValue           : Integer;
    function GetSortOrder : Integer; virtual;
  public
    constructor Create(aImage : TfwCEDrawable; aLayer : Integer = 0);

    procedure MoveTo(aX, aY : Integer; aZ : Integer = 0);
    procedure Move(aDifX, aDifY : Integer; aDifZ : Integer = 0);

    function  CompareSort(aSprite : TCustomSprite) : Integer;
    procedure SetHitArea(const aRect : TCERect); virtual;
    procedure GetHitArea(var aRect : TCERect); virtual;
    procedure GetBounds(var aRect : TCERect); virtual;
    function  IsVisible(var aWorld : TWorldData) : Boolean; virtual;
    function  Hit(aItem : TCustomSprite) : Boolean; virtual;
    procedure Update(); virtual;

    procedure Draw(var aWorld : TWorldData; aScreen : TfwCESurface; aOffsetX, aOffsetY : Integer); overload; virtual;
    procedure Draw(var aWorld : TWorldData; aDrawList : TDrawList); overload; virtual;

    property Layer : Integer read fLayer;
    property SortOrder : Integer read GetSortOrder;
    property X : Integer read fX;
    property Y : Integer read fY;
    property Z : Integer read fZ;
    property Width : Integer read fWidth;
    property Height : Integer read fHeight;
    property SpriteType : TSpriteType read fSpriteType write fSpriteType;
    property Value : Integer read fValue write fValue;
  end;

  TDrawListItem    = class(TObject)
  protected
    fSprite          : TCustomSprite;
    fSurface         : TfwCEDrawable;
    fDstX            : Integer;
    fDstY            : Integer;
    fSrcX            : Integer;
    fSrcY            : Integer;
    fSrcWidth        : Integer;
    fSrcHeight       : Integer;
    fSortOrder       : Integer;
    fLayer           : Integer;
  public
    constructor Create(aSurface : TfwCEDrawable; aDstX, aDstY, aSrcX, aSrcY, aSrcWidth, aSrcHeight, aLayer, aSortOrder : Integer; aSprite : TCustomSprite = nil);
    function  Compare(aItem : TDrawListItem) : Integer;
    function  Clone : TDrawListItem;
    procedure Draw(aScreen : TfwCESurface; aOffsetX, aOffsetY : Integer);
    property Surface : TfwCEDrawable read fSurface;
    property DstX : Integer read fDstX;
    property DstY : Integer read fDstY;
    property SrcX : Integer read fSrcX;
    property SrcY : Integer read fSrcY;
    property SrcWidth : Integer read fSrcWidth;
    property SrcHeight : Integer read fSrcHeight;
    property SortOrder : Integer read fSortOrder;
    property Layer : Integer read fLayer;
  end;

  TDrawList        = class(TObject)
  protected
    fItems           : TObjectList;
    function GetCount : Integer;
    function Find(aLayer, aSortOrder : Integer; var Index : Integer) : Boolean; overload;
    function Find(aItem : TDrawListItem; var aIndex : Integer) : Boolean; overload;
  public
    constructor Create(aOwnerObjects : Boolean = False);
    destructor Destroy; override;
    procedure Clear;
    procedure Add(aItem : TDrawListItem);
    procedure CopyTo(aList : TDrawList);
    procedure MoveTo(aList : TDrawList);
    procedure Draw(aScreen : TfwCESurface; aOffsetX, aOffsetY : Integer);
    property Count : Integer read GetCount;
  end;

  TSpriteList      = class(TObject)
  protected
    fSprites         : TObjectlist;
  public
    constructor Create(aOwner : Boolean = False);
    destructor Destroy; override;
    procedure Clear;
    procedure Add(aItem : TCustomSprite);
    procedure Extract(aItem : TCustomSprite);
    function  CheckHit(aSprite : TCustomSprite) : TCustomSprite;
    procedure Draw(var aWorld : TWorldData; aDrawList : TDrawList);
    procedure Update();
  end;


implementation

uses Types;

// TCustomSprite ---------------------------------------------------------
function TCustomSprite.GetSortOrder : Integer;
begin
  Result := fY;
end;

constructor TCustomSprite.Create(aImage : TfwCEDrawable; aLayer : Integer = 0);
begin
  inherited Create;

  fSurface    := aImage;
  fX          := 0;
  fY          := 0;
  fZ          := 0;
  fLayer      := aLayer;
  fWidth      := aImage.Width;
  fHeight     := aImage.Height;
  fHitArea    := CERectCreate(fX, fY, fWidth, fHeight);
  fSpriteType := stNone;
  fValue      := 0;
end;

procedure TCustomSprite.MoveTo(aX, aY : Integer; aZ : Integer = 0);
begin
  fX := aX;
  fY := aY;
  fZ := aZ;
end;

procedure TCustomSprite.Move(aDifX, aDifY : Integer; aDifZ : Integer = 0);
begin
  fX := fX + aDifX;
  fY := fY + aDifY;
  fZ := fZ + aDifZ;
end;

function TCustomSprite.CompareSort(aSprite : TCustomSprite) : Integer;
begin
  Result := 0;
  if (fLayer = aSprite.Layer) then begin
    if (SortOrder > aSprite.SortOrder) then begin
      Result := 1;
    end else if (SortOrder < aSprite.SortOrder) then begin
      Result := -1;
    end else if (fZ > aSprite.Z) then begin
      Result := 1;
    end else if (fZ < aSprite.Z) then begin
      Result := -1;
    end;
  end else if (fLayer < 0) then begin
    Result := -1;
  end else begin
    Result := 1;
  end;
end;

procedure TCustomSprite.SetHitArea(const aRect : TCERect);
begin
  fHitArea := aRect;
end;

procedure TCustomSprite.GetHitArea(var aRect : TCERect);
begin
  aRect := fHitArea;
end;

procedure TCustomSprite.GetBounds(var aRect : TCERect);
begin
  aRect.X      := fX;
  aRect.Y      := fY;
  aRect.Width  := fWidth;
  aRect.Height := fHeight;
end;

function TCustomSprite.IsVisible(var aWorld : TWorldData) : Boolean;
var
  iX1, iY1         : Integer;
  iX2, iY2         : Integer;
  Rect             : TCERect;
begin
  Result := False;

  //
  GetBounds(Rect);

  iX1 := aWorld.Viewport.X - aWorld.OffsetX;
  iY1 := aWorld.Viewport.Y - aWorld.OffsetY;
  iX2 := iX1 + aWorld.Viewport.Width;
  iY2 := iY1 + aWorld.Viewport.Height;

  if (Rect.X > iX2) then Exit;
  if (Rect.Y > iY2) then Exit;
  if ((Rect.X + Rect.Width) < iX1) then Exit;
  if ((Rect.Y + Rect.Height) < iY1) then Exit;
  Result := True;
end;

function TCustomSprite.Hit(aItem : TCustomSprite) : Boolean;
var
  Rect1            : TCERect;
  Rect2            : TCERect;
  HitRect          : TCERect;          
begin
  GetBounds(Rect1);
  aItem.GetBounds(Rect2);

  Rect1.X      := Rect1.X + fHitArea.X;
  Rect1.Y      := Rect1.Y + fHitArea.Y;
  Rect1.Width  := Min(Rect1.Width, fHitArea.Width);
  Rect1.Height := Min(Rect1.Height, fHitArea.Height);

  aItem.GetHitArea(HitRect);
  Rect2.X      := Rect2.X + HitRect.X;
  Rect2.Y      := Rect2.Y + HitRect.Y;
  Rect2.Width  := Min(Rect2.Width, HitRect.Width);
  Rect2.Height := Min(Rect2.Height, HitRect.Height);

  Result := CERectIntersect(Rect1, Rect2);
end;

procedure TCustomSprite.Update();
begin
end;

procedure TCustomSprite.Draw(var aWorld : TWorldData; aScreen : TfwCESurface; aOffsetX, aOffsetY : Integer);
begin
  if Assigned(fSurface) then begin
    if IsVisible(aWorld) then begin
      fSurface.Blit(aScreen, aOffsetX + fX, aOffsetY + fY, 0, 0, fWidth, fHeight);
    end;
  end;
end;

procedure TCustomSprite.Draw(var aWorld : TWorldData; aDrawList : TDrawList);
begin
  if Assigned(fSurface) then begin
    if IsVisible(aWorld) then begin
      aDrawList.Add(TDrawListItem.Create(fSurface, X, Y, 0, 0, Width, Height, Layer, SortOrder));
    end;
  end;
end;

// TDrawListItem ---------------------------------------------------------
constructor TDrawListItem.Create(aSurface : TfwCEDrawable; aDstX, aDstY, aSrcX, aSrcY, aSrcWidth, aSrcHeight, aLayer, aSortOrder : Integer; aSprite : TCustomSprite = nil);
begin
  inherited Create;

  fSprite    := aSprite;
  fSurface   := aSurface;
  fDstX      := aDstX;
  fDstY      := aDstY;
  fSrcX      := aSrcX;
  fSrcY      := aSrcY;
  fSrcWidth  := aSrcWidth;
  fSrcHeight := aSrcHeight;
  fSortOrder := aSortOrder;
  fLayer     := aLayer;
end;

function TDrawListItem.Compare(aItem : TDrawListItem) : Integer;
begin
  Result := 0;
  if (fLayer = aItem.Layer) then begin
    if (SortOrder > aItem.SortOrder) then begin
      Result := 1;
    end else if (SortOrder < aItem.SortOrder) then begin
      Result := -1;
    end;
  end else if (fLayer < aItem.Layer) then begin
    Result := -1;
  end else if (fLayer > aItem.Layer) then begin
    Result := 1;
  end;
end;

function TDrawListItem.Clone : TDrawListItem;
begin
  Result := TDrawListItem.Create(Surface, DstX, DstY, SrcX, SrcY, SrcWidth, SrcHeight, Layer, SortOrder);
end;

procedure TDrawListItem.Draw(aScreen : TfwCESurface; aOffsetX, aOffsetY : Integer);
begin
  if Assigned(fSurface) then begin
    fSurface.Blit(aScreen, aOffsetX + fDstX, aOffsetY + fDstY, fSrcX, fSrcY, fSrcWidth, fSrcHeight);
  end;
end;

// TDrawList -------------------------------------------------------------
function TDrawList.GetCount : Integer;
begin
  Result := fItems.Count;
end;

function TDrawList.Find(aLayer, aSortOrder : Integer; var Index : Integer) : Boolean;
var
  iIndex           : Integer;
  Item             : TDrawListItem;
begin
  Result := False;
  for iIndex := 0 to Pred(fItems.Count) do begin
    Item := (fItems[iIndex] as TDrawListItem);
    if (Item.Layer = aLayer) then begin
      if (aSortOrder < Item.SortOrder) then begin
        Index  := iIndex;
        Result := True;
        Exit;
      end;
    end else if (aLayer > Item.Layer) then begin
      if ((iIndex + 2) < fItems.Count) then begin
        Index  := iIndex;
        Result := True;
      end;
      Exit;
    end else if (aLayer < Item.Layer) then begin
      Index  := iIndex;
      Result := True;
      Exit;
    end;
  end;
end;

function TDrawList.Find(aItem : TDrawListItem; var aIndex : Integer) : Boolean;
var
  L, H, I, C       : Integer;
  Max              : Integer;
  Item             : TDrawListItem;
begin
  Result := False;

  L   := 0;
  Max := Pred(fItems.Count);
  H   := Max;
  while (L <= H) do begin
    I := (L + H) shr 1;
    Item := (fItems[I] as TDrawListItem);
    if Assigned(Item) then begin
      C := aItem.Compare(Item);
      if (C > 0) then begin
        L := I + 1;
      end else if (C < 0) then begin
        H := I - 1;
        if (L > H) then begin
          Result := True;
          Break;
        end;
      end else if (H > L) then begin
        for I := L to H do begin
          Item := (fItems[I] as TDrawListItem);
          if Assigned(Item) then begin
            C := aItem.Compare(Item);
            if (C <> 0) then begin
              aIndex := I;
              Exit;
            end;
          end;
        end;
        aIndex := H;
        Exit;
      end else begin
        Break;
      end;
    end;
  end;
  aIndex := L;
end;

constructor TDrawList.Create(aOwnerObjects : Boolean = False);
begin
  inherited Create;

  fItems := TObjectList.Create(True);
end;

destructor TDrawList.Destroy;
begin
  Clear;
  FreeAndNil(fItems);

  inherited Destroy;
end;

procedure TDrawList.Clear;
begin
  fItems.Clear;
end;

procedure TDrawList.Add(aItem : TDrawListItem);
var
  iIndex           : Integer;
begin
  if Find(aItem.Layer, aItem.SortOrder, iIndex) then begin
  // if Find(aItem, iIndex) then begin // XXX Schnelleres Einfuegen in vorsortierter Liste
    fItems.Insert(iIndex, aItem);
    Exit;
  end;

  fItems.Add(aItem);
end;

procedure TDrawList.CopyTo(aList : TDrawList);
var
  iLoop            : Integer;
begin
  for iLoop := 0 to Pred(fItems.Count) do begin
    aList.Add((fItems[iLoop] as TDrawListItem).Clone);
  end;
end;

procedure TDrawList.MoveTo(aList : TDrawList);
var
  Item             : TDrawListItem;
begin
  while (fItems.Count > 0) do begin
    Item := (fItems[0] as TDrawListItem);
    if Assigned(Item) then begin
      fItems.Extract(Item);
      aList.Add(Item);
    end;
  end;
end;

procedure TDrawList.Draw(aScreen : TfwCESurface; aOffsetX, aOffsetY : Integer);
var
  iLoop            : Integer;
  Item             : TDrawListItem;
begin
  for iLoop := 0 to Pred(fItems.Count) do begin
    Item := (fItems[iLoop] as TDrawListItem);
    if Assigned(Item) then begin
      Item.Draw(aScreen, aOffsetX, aOffsetY);
    end;
  end;
end;

// TSpriteList -----------------------------------------------------------

function TSpriteListSort(Item1, Item2: Pointer) : Integer;
begin
  if (CARDINAL(Item1) < CARDINAL(Item2)) then begin
    Result := -1;
  end else if (CARDINAL(Item1) > CARDINAL(Item2)) then begin
    Result := 1;
  end else begin
    Result := 0;
  end;
end;

constructor TSpriteList.Create(aOwner : Boolean = False);
begin
  inherited Create;

  fSprites := TObjectList.Create(aOwner);
end;

destructor TSpriteList.Destroy;
begin
  FreeAndNil(fSprites);
  
  inherited Destroy;
end;

procedure TSpriteList.Clear;
begin
  fSprites.Clear;
end;

procedure TSpriteList.Add(aItem : TCustomSprite);
begin
  fSprites.Add(aItem);
end;

procedure TSpriteList.Extract(aItem : TCustomSprite);
begin
  fSprites.Extract(aItem);
end;

function TSpriteList.CheckHit(aSprite : TCustomSprite) : TCustomSprite;
var
  iLoop            : Integer;
  Item             : TCustomSprite;
begin
  Result := nil;
  for iLoop := 0 to Pred(fSprites.Count) do begin
    Item := (fSprites[iLoop] as TCustomSprite);
    if Assigned(Item) then begin
      if Item.Hit(aSprite) then begin
        Result := Item;
        Break;
      end; 
    end;
  end;
end;

procedure TSpriteList.Draw(var aWorld : TWorldData; aDrawList : TDrawList);
var
  iLoop            : Integer;
  Item             : TCustomSprite;
begin
  for iLoop := 0 to Pred(fSprites.Count) do begin
    Item := (fSprites[iLoop] as TCustomSprite);
    if Assigned(Item) then begin
      if Item.IsVisible(aWorld) then begin
        Item.Draw(aWorld, aDrawlist);
      end;
    end;
  end;
end;

procedure TSpriteList.Update();
var
  iLoop            : Integer;
  Item             : TCustomSprite;
begin
  for iLoop := 0 to Pred(fSprites.Count) do begin
    Item := (fSprites[iLoop] as TCustomSprite);
    if Assigned(Item) then begin
      Item.Update();
    end;
  end;
end;

end.
