program isotest;

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

uses SysUtils,
     ContNrs,
     fwTypes,
     fwLogging,
     fwCustomEngine,
     fwPlattform,
     fwAnimation,

     uCustomSprites,
     uSprites,
     uWorldData,
     uWorld,
     uItems;

const
  SCREEN_WIDTH     = 320;
  SCREEN_HEIGHT    = 240;
  SCREEN_BPP       = 16;
  SCREEN_MAPX      = 160;
  SCREEN_MAPY      = 0;
  TILE_XSIZE       = 32;
  TILE_YSIZE       = 16;

function GetDirection(aX, aY : Integer) : TAnimDirection;
begin
  Result := adNone;
  if (aX = 0) and (aY = 0) then begin
    Exit;
  end else if (aX < 0) and (aY = 0) then begin
    Result := adLeft;
  end else if (aX < 0) and (aY < 0) then begin
    Result := adLeftUp;
  end else if (aX = 0) and (aY < 0) then begin
    Result := adUp;
  end else if (aX > 0) and (aY < 0) then begin
    Result := adUpRight;
  end else if (aX > 0) and (aY = 0) then begin
    Result := adRight;
  end else if (aX > 0) and (aY > 0) then begin
    Result := adRightDown;
  end else if (aX = 0) and (aY > 0) then begin
    Result := adDown;
  end else if (aX < 0) and (aY > 0) then begin
    Result := adDownLeft;
  end;
end;

function AddTile(var aWorld : TWorldData; aTile : Char; aSurface : TfwCEDrawable; aX, aY, aWidth, aHeight : Integer; aResource : TObjectList = nil) : Integer;
var
  Tile             : TfwCESubSurface;
begin
  Result := -1;
  Tile   := TfwCESubSurface.Create(aSurface, aX, aY, aWidth, aHeight);
  if Assigned(Tile) then begin
    if Assigned(aResource) then begin
      aResource.Add(Tile);
    end;
    aWorld.Tiles[Ord(aTile)] := Tile;
    Result := Ord(aTile);
  end;
end;

function AddSprite(aSurface : TfwCEDrawable; aX, aY, aWidth, aHeight : Integer; aResource : TSpriteList) : TCustomSprite;
var
  SubSurface       : TfwCESubSurface;
begin
  Result  := nil;
  SubSurface := TfwCESubSurface.Create(aSurface, aX, aY, aWidth, aHeight);
  if Assigned(SubSurface) then begin
    Result := TAnimSprite.Create(SubSurface, 0, 0);
    if Assigned(Result) then begin
      aResource.Add(Result);
    end;
  end;
end;

procedure AddMoney(aXPos, aYPos : Integer; aSurface : TfwCEDrawable; aX, aY, aWidth, aHeight : Integer; aValue : Integer; aResource : TSpriteList);
begin
  with AddSprite(aSurface, aX, aY, aWidth, aHeight, aResource) do begin
    SpriteType := stMoney;
    Value      := aValue;
    MoveTo(aXPos, aYPos);
    SetHitArea(CERectCreate(10, 6, 14, 8));
  end;
end;

procedure AddChest(aXPos, aYPos : Integer; aSurface : TfwCEDrawable; aX, aY, aWidth, aHeight : Integer; aValue : Integer; aResource : TSpriteList);
begin
  with AddSprite(aSurface, aX, aY, aWidth, aHeight, aResource) do begin
    SpriteType := stChest;
    Value      := aValue;
    MoveTo(aXPos, aYPos);
    SetHitArea(CERectCreate(2, 16, 28, 16));
  end;
end;

var
  iScrollPlayerX   : Integer;
  iScrollPlayerY   : Integer;
  iScrollMapX      : Integer;
  iScrollMapY      : Integer;
  iFeetX           : Integer;
  iFeetY           : Integer;
  iGold            : Integer;
  sLastHit         : String;
  uFrameTime       : Cardinal;
  uDrawTime        : Cardinal;
  MapViewport      : TCERect;
  HUDViewport      : TCERect;
  Screen           : TfwCESurface;
  Tileset01        : TfwCESurface;
  Walls            : TfwCESurface;
  PlayerImg        : TfwCESurface;
  SpiderImg        : TfwCESurface;
  Steps            : TfwCESound;
  BgMusic          : TfwCEMusic;
  Font             : TfwCEFont;
  Timer            : TfwCETimer;
  WorldData        : TWorldData;
  SpiderAnimation  : TfwAnimSequences;
  Player           : TAnimSprite;
  Spider1          : TAnimSprite;
  Spider2          : TAnimSprite;
  SpiderMovements  : TSpriteMovement;
  SpriteList       : TSpriteList;
  DrawList         : TDrawList;
  Resources        : TObjectlist;
  HitItem          : TCustomSprite;
  HitTime          : Cardinal;
begin
  // setup graphic engine
  fwLOG('Starting game');

  SetHAL(TfwEngine.Create(false, false));
  try
    if not Assigned(HAL()) then begin
	    fwLOG('CAANOO Engine coudn''t initialized');
	    Exit;
	  end;

    HAL().Caption := 'ISO Test';

    fwLOG('init screen');
    Screen := HAL().Init(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP);
    if Assigned(Screen) then begin
      fwLOG('load resource');
      Resources := TObjectlist.Create;
      try
        Font := HAL().CreateFont('data/font/arial.ttf', 14, Resources);
        if Assigned(Font) then begin
          HAL().ClearScreen($00000000);
          Font.RenderTo(Screen, 120, 110, 'Loading ...', $00FFFFFF);
          HAL().Flip(Screen);
        end;

        BgMusic := HAL().CreateMusic('data/music/prologue.mp3', Resources);
        if Assigned(BgMusic) then begin
          BgMusic.Play(40);
        end;
        Sleep(250);

        Tileset01  := HAL().CreateSurface('data/images/isotileset01.bmp', $00FF00FF, Resources);
        Walls      := HAL().CreateSurface('data/images/dungeon_wall_flare.png', $00FF00FF, Resources);
        PlayerImg  := HAL().CreateSurface('data/images/caveman.bmp', $00FF00FF, Resources);
        SpiderImg  := HAL().CreateSurface('data/images/red_spider_all.bmp', $00FF00FF, Resources);
        Steps      := HAL().CreateSound('data/sound/steps.wav', 250, Resources);

        Player := TAnimSprite.Create(PlayerImg, 20, 36);
        if Assigned(Player) then begin
          Resources.Add(Player);
          Player.AddSequence(siStandS, 0,  748, 40, 44, 1, 40, 0); // steht S
          Player.AddSequence(siStandSW, 40,  748, 40, 44, 1, 40, 0); // steht SW
          Player.AddSequence(siStandW, 80,  748, 40, 44, 1, 40, 0); // steht W
          Player.AddSequence(siStandWN, 120,  748, 40, 44, 1, 40, 0); // steht WN
          Player.AddSequence(siStandN, 160,  748, 40, 44, 1, 40, 0); // steht N
          Player.AddSequence(siStandNE, 200,  748, 40, 44, 1, 40, 0); // steht NO
          Player.AddSequence(siStandE, 240,  748, 40, 44, 1, 40, 0); // steht O
          Player.AddSequence(siStandES, 280,  748, 40, 44, 1, 40, 0); // steht OS
          Player.AddSequence(siWalkingWN, adLeft,  0, 1276, 40, 44, 8, 40, 0); // laeuft WN
          Player.AddSequence(siWalkingN, adLeftUp,  0, 1188, 40, 44, 8, 40, 0); // laeuft N
          Player.AddSequence(siWalkingNE, adUp,  0, 1232, 40, 44, 8, 40, 0); // laeuft NO
          Player.AddSequence(siWalkingE, adUpRight,  0, 1144, 40, 44, 8, 40, 0); // laeuft O
          Player.AddSequence(siWalkingES, adRight,  0, 1364, 40, 44, 8, 40, 0); // laeuft OS
          Player.AddSequence(siWalkingS, adRightDown, 0, 1320, 40, 44, 8, 40, 0); // laeuft S
          Player.AddSequence(siWalkingSW, adDown, 0, 1408, 40, 44, 8, 40, 0); // laeuft SW
          Player.AddSequence(siWalkingW, adDownLeft, 0, 1452, 40, 44, 8, 40, 0); // laeuft W
          Player.SwitchToSquence(siStandES);
          Player.SetHitArea(CERectCreate(16, 36, 8, 8));
        end;

        SpiderAnimation := TfwAnimSequences.Create;
        if Assigned(SpiderAnimation) then begin
          Resources.Add(SpiderAnimation);
          SpiderAnimation.AddSequence(siStandE,      0,  864, 36, 36, 1, 36, 0); // steht S
          SpiderAnimation.AddSequence(siStandES,    36,  864, 36, 36, 1, 36, 0); // steht SW
          SpiderAnimation.AddSequence(siStandS,     72,  864, 36, 36, 1, 36, 0); // steht W
          SpiderAnimation.AddSequence(siStandSW,   108,  864, 36, 36, 1, 36, 0); // steht WN
          SpiderAnimation.AddSequence(siStandW,    144,  864, 36, 36, 1, 36, 0); // steht N
          SpiderAnimation.AddSequence(siStandWN,   180,  864, 36, 36, 1, 36, 0); // steht NO
          SpiderAnimation.AddSequence(siStandN,    216,  864, 36, 36, 1, 36, 0); // steht O
          SpiderAnimation.AddSequence(siStandNE,   252,  864, 36, 36, 1, 36, 0); // steht OS
          SpiderAnimation.AddSequence(siWalkingS,    0, 1332, 36, 36, 8, 36, 0); // laeuft S
          SpiderAnimation.AddSequence(siWalkingSW,   0, 1404, 36, 36, 8, 36, 0); // laeuft SW
          SpiderAnimation.AddSequence(siWalkingW,    0, 1440, 36, 36, 8, 36, 0); // laeuft W
          SpiderAnimation.AddSequence(siWalkingWN,   0, 1296, 36, 36, 8, 36, 0); // steht S
          SpiderAnimation.AddSequence(siWalkingN,    0, 1224, 36, 36, 8, 36, 0); // laeuft N
          SpiderAnimation.AddSequence(siWalkingNE,   0, 1260, 36, 36, 8, 36, 0); // laeuft NO
          SpiderAnimation.AddSequence(siWalkingE,    0, 1188, 36, 36, 8, 36, 0); // laeuft O
          SpiderAnimation.AddSequence(siWalkingES,   0, 1368, 36, 36, 8, 36, 0); // laeuft OS
        end;

        SpiderMovements := TSpriteMovement.Create;
        if Assigned(SpiderMovements) then begin
          Resources.Add(SpiderMovements);
          SpiderMovements.Add(siWalkingWN, 0, 7, -2, 0);
          SpiderMovements.Add(siStandN, 0, 0, 0);
          SpiderMovements.Add(siStandNE, 0, 0, 0);
          SpiderMovements.Add(siStandE, 0, 0, 0);
          SpiderMovements.Add(siWalkingES, 0, 7, +2, 0);
          SpiderMovements.Add(siStandS, 0, 0, 0);
          SpiderMovements.Add(siStandSW, 0, 0, 0);
          SpiderMovements.Add(siStandW, 0, 0, 0);
        end;

        Spider1 := TAnimSprite.Create(SpiderImg, 18, 30);
        if Assigned(Spider1) then begin
          Resources.Add(Spider1);
          Spider1.Animations := SpiderAnimation;
          Spider1.SwitchToSquence(siWalkingWN);
          Spider1.SetFrameTime(100);
          Spider1.Movements := SpiderMovements;
        end;

        Spider2 := TAnimSprite.Create(SpiderImg, 18, 30);
        if Assigned(Spider2) then begin
          Resources.Add(Spider2);
          Spider2.Animations := SpiderAnimation;
          Spider2.SwitchToSquence(siWalkingWN);
          Spider2.SetFrameTime(100);
          Spider2.Movements := SpiderMovements;
        end;

        fwLOG('init world map');
        InitMap(WorldData, SCREEN_WIDTH, SCREEN_HEIGHT, TILE_XSIZE, TILE_YSIZE);

        WorldData.Tiles[0] := nil;
        AddTile(WorldData, CHR(1), Tileset01, 34, 1, 32, 16);
        AddTile(WorldData, 'A', Walls, 336, 64, 32, 56);
        AddTile(WorldData, 'B', Walls,  82,  0, 32, 56);
        AddTile(WorldData, 'F', Walls, 402, 64, 32, 56);
        AddTile(WorldData, 'G', Walls, 272,  0, 32, 56);
        AddTile(WorldData, 'K', Walls, 271, 64, 32, 56);
        AddTile(WorldData, 'L', Walls, 206,  0, 32, 56);
        AddTile(WorldData, 'P', Walls, 464, 65, 32, 56);
        AddTile(WorldData, 'Q', Walls, 402,  0, 32, 56);
        AddTile(WorldData, 'T', Walls,  80, 64, 32, 56);

        LoadMap('data/levels/test01.txt', WorldData);
        MoveToMap(WorldData, 0, 0);

        Player.MoveTo(32, 32);
        Spider1.MoveTo(160, 120);
        Spider2.MoveTo(200, 240);

        uFrameTime := HAL().GetTicks;

        Timer := TfwCETimer.Create(1, 16);
        if Assigned(Timer) then begin
          // Bewegungstimer fuer den Spieler
          Resources.Add(Timer);
        end;

        MapViewport.X      := 0;
        MapViewport.Y      := 0;
        MapViewport.Width  := 320;
        MapViewport.Height := 160;
        WorldData.Viewport := MapViewport;

        HUDViewport.X      := 0;
        HUDViewport.Y      := 0;
        HUDViewport.Width  := 320;
        HUDViewport.Height := 200;

        // Liste mit den sortieren Sprites nach dem Y - Position
        DrawList := TDrawList.Create;
        Resources.Add(DrawList);

        SpriteList := TSpriteList.Create;
        Resources.Add(SpriteList);

        AddMoney(200, 260, Tileset01, 76,  1, 14, 16, 5, SpriteList);
        AddMoney(300, 360, Tileset01, 76,  1, 14, 16, 5, SpriteList);
        AddMoney(300, 460, Tileset01, 76,  1, 14, 16, 5, SpriteList);
        AddChest(300, 600, Tileset01,  1, 18, 32, 32, 5, SpriteList);

        SpriteList.Add(Spider1);
        SpriteList.Add(Spider2);

        sLastHit := '';
        HitTime  := 0;
        iGold    := 0;
        
        fwLOG('enter main loop');
        HAL().StartFPS();
        while (not HAL().ExitKeyPressed()) do begin
          iScrollPlayerX := 0;
          iScrollPlayerY := 0;
          iFeetX         := Player.X;
          iFeetY         := Player.Y;

          // show graphics
          uDrawTime  := HAL().GetTicks;

          HAL().ClearScreen($00000000);

          // Setze den Ausgabebereich
          Screen.SetClipRect(MapViewport);

          // Zeichne die unterterste Ebene immer
          DrawFloor(Screen, WorldData, SCREEN_MAPX - WorldData.TileXHalf, SCREEN_MAPY);

          DrawList.Clear;
          DrawWalls(DrawList, WorldData, SCREEN_MAPX, SCREEN_MAPY);
          SpriteList.Draw(WorldData, DrawList);
          Player.Draw(WorldData, DrawList);

          // Und jetzt alles auf einmal auf den Bildschirm
          DrawList.Draw(Screen, SCREEN_MAPX, SCREEN_MAPY);

          // Loesche Clipping fuer die Anzeige des HUDs
          Screen.ClearClipRect();

          uDrawTime := HAL().GetTicks - uDrawTime;
          if Assigned(Font) then begin
            uFrameTime := HAL().GetTicks - uFrameTime;

            Font.RenderTo(Screen, 10, 180, 'Gold  ' + IntToStr(iGold), $00FFFFFF);

            Font.RenderTo(Screen, 10, 200, 'Draw  ' + IntToStr(uDrawTime), $00FFFFFF);
            Font.RenderTo(Screen, 10, 215, 'Frame ' + IntToStr(uFrameTime), $00FFFFFF);

            Font.RenderTo(Screen, 80, 200, 'Map X ' + IntToStr(WorldData.OffsetX), $00FFFFFF);
            Font.RenderTo(Screen, 80, 215, 'Map Y ' + IntToStr(WorldData.OffsetY), $00FFFFFF);

            Font.RenderTo(Screen, 160, 200, 'Player X ' + IntToStr(iFeetX), $00FFFFFF);
            Font.RenderTo(Screen, 160, 215, 'Player Y ' + IntToStr(iFeetY), $00FFFFFF);

            Font.RenderTo(Screen, 250, 200, 'Items ' + IntToStr(DrawList.Count), $00FFFFFF);
            Font.RenderTo(Screen, 250, 215, 'FPS   ' + IntToStr(HAL().GetFPS), $00FFFFFF);
          end;
          uFrameTime := HAL().GetTicks;
          HAL().Flip(Screen, False);

          HitItem := SpriteList.CheckHit(Player);
          if Assigned(HitItem) then begin
            if (HitItem.SpriteType = stMoney) then begin
              SpriteList.Extract(HitItem);
              iGold    := iGold + HitItem.Value;
              HitTime  := HAL().GetTicks;
              sLastHit := HitItem.ClassName;
              FreeAndNil(HitItem);
            end;
          end else if (HAL().GetTicks() > (HitTime + 100)) then begin
            sLastHit := '';                
          end;

          // update control all
          HAL().Idle();
          HAL().UpdateInput();

          if HAL().Controls.KeyPressed(LEFT_BUTTON) then begin
            iScrollPlayerX := iScrollPlayerX - 2;
          end;

          if HAL().Controls.KeyPressed(UP_BUTTON) then begin
            iScrollPlayerY := iScrollPlayerY - 2;
          end;

          if HAL().Controls.KeyPressed(RIGHT_BUTTON) then begin
            iScrollPlayerX := iScrollPlayerX + 2;
          end;

          if HAL().Controls.KeyPressed(DOWN_BUTTON) then begin
            iScrollPlayerY := iScrollPlayerY + 2;
          end;

          if (iScrollPlayerX <> 0) or (iScrollPlayerY <> 0) then begin
            if IsWalkabe(WorldData, iFeetX + (iScrollPlayerX * 6), iFeetY + (iScrollPlayerY * 6))
            then begin
              if Timer.TimeOut() then begin
                Timer.Update;
                // Pruefe ob der Spieler innerhalb der Karte ist
                if ((iFeetX + iScrollPlayerX) < 0) or ((iFeetX + iScrollPlayerX) > (255 * 16))
                then begin
                  iScrollPlayerX := 0;
                end;

                if ((iFeetY + iScrollPlayerY) < 0) or ((iFeetY + iScrollPlayerY) > (255 * 16))
                then begin
                  iScrollPlayerY := 0;
                end;

                if (iScrollPlayerX <> 0) or (iScrollPlayerY <> 0) then begin
                  // Spieler kann sich bewegen
                  case GetDirection(iScrollPlayerX, iScrollPlayerY) of
                    adLeft      : Player.SwitchToSquence(siWalkingWN);
                    adLeftUp    : Player.SwitchToSquence(siWalkingN);
                    adUp        : Player.SwitchToSquence(siWalkingNE);
                    adUpRight   : Player.SwitchToSquence(siWalkingE);
                    adRight     : Player.SwitchToSquence(siWalkingES);
                    adRightDown : Player.SwitchToSquence(siWalkingS);
                    adDown      : Player.SwitchToSquence(siWalkingSW);
                    adDownLeft  : Player.SwitchToSquence(siWalkingW);
                  else
                    //
                  end;

                  // Muss eventuell die Karte gescrollt werden
                  iScrollMapX := -iScrollPlayerX;
                  iScrollMapY := -iScrollPlayerY;
                  if (Player.X <= 80) or (Player.X > (251 * 16)) then begin
                    iScrollMapX := 0;
                  end;

                  if (Player.Y <= 80) or (Player.Y > (251 * 16)) then begin
                    iScrollMapY := 0;
                  end;

                  Player.Move(iScrollPlayerX, iScrollPlayerY);
                  if not Steps.Playing then begin
                    Steps.Play(80);
                  end;

                  if (iScrollMapX <> 0) or (iScrollMapY <> 0) then begin
                    ScrollMap(WorldData, iScrollMapX, iScrollMapY);
                  end;
                end;
              end;
            end;
          end else begin
            if not HAL().Controls.AnyKeyDown then begin
              case Player.Direction of
                adLeft      : Player.SwitchToSquence(siStandWN);
                adLeftUp    : Player.SwitchToSquence(siStandN);
                adUp        : Player.SwitchToSquence(siStandNE);
                adUpRight   : Player.SwitchToSquence(siStandE);
                adRight     : Player.SwitchToSquence(siStandES);
                adRightDown : Player.SwitchToSquence(siStandS);
                adDown      : Player.SwitchToSquence(siStandSW);
                adDownLeft  : Player.SwitchToSquence(siStandW);
              else
                //
              end;
            end;
          end;

          // Animationen go go
          Player.NextFrame();
          SpriteList.Update();
        end;
      finally
        FreeAndNil(Resources);
      end;
    end;
  except
    on E : Exception do begin
      fwLog(E.Message);
    end;
  end;
end.

