unit uWorld;

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

interface

uses Types,
     SysUtils,

     fwLogging,
     fwCustomEngine,

     uCustomSprites,
     uWorldData;

procedure InitMap(var aWorld : TWorldData; aScreenWidth, aScreenHeight, aTileWidth, aTileHeight : Integer);
procedure MapXYToISO(var aWorld : TWorldData; aX, aY : Integer; var aISOX, aISOY : Integer);
procedure WorldXYToScreen(var aWorld : TWorldData; aX, aY : Integer; var aISOX, aISOY : Integer);
procedure ResetMap(var aWorld : TWorldData);
procedure SetBorder(aWorld : TWorldData; aWall : Integer);
procedure DrawRoom(aWorld : TWorldData; aX1, aY1, aX2, aY2 : Integer; const aWalls : array of Integer);
procedure DrawDoor(aWorld : TWorldData; aX1, aY1, aDirX, aDirY : Integer; const aWalls : array of Integer);

function  IsWalkabe(aWorld : TWorldData; aX, aY : Integer) : Boolean;
procedure MoveToMap(var aWorld : TWorldData; aX, aY : Integer);
procedure ScrollMap(var aWorld : TWorldData; aOffsetX, aOffsetY : Integer);

function  LoadMap(const aFilename : String; var aWorld : TWorldData) : Boolean;

procedure DrawFloor(aScreen : TfwCESurface; var aWorld : TWorldData; aOffsetX : Integer = 0; aOffsetY : Integer = 0);
procedure DrawWalls(aDrawList : TDrawList; var aWorld : TWorldData; aOffsetX : Integer = 0; aOffsetY : Integer = 0);

implementation

function Min(const aValue1, aValue2 : Integer) : Integer;
begin
  Result := aValue2;
  if (aValue1 < aValue2) then begin
    Result := aValue1;
  end;
end;

function Max(const aValue1, aValue2 : Integer) : Integer;
begin
  Result := aValue2;
  if (aValue1 > aValue2) then begin
    Result := aValue1;
  end;
end;

// -----------------------------------------------------------------------
procedure InitMap(var aWorld : TWorldData; aScreenWidth, aScreenHeight, aTileWidth, aTileHeight : Integer);
begin
  aWorld.ScreenWidth  := aScreenWidth;
  aWorld.ScreenHeight := aScreenHeight;
  aWorld.ScreenXHalf  := aScreenWidth div 2;
  aWorld.ScreenYHalf  := aScreenHeight div 2;
  aWorld.Map          := TWorldMap.Create(cMapSizeX, cMapSizeY);          

  aWorld.TileWidth    := aTileWidth;
  aWorld.TileHeight   := aTileHeight;
  aWorld.TileXHalf    := aTileWidth div 2;
  aWorld.TileYHalf    := aTileHeight div 2;
  aWorld.TileRScale   := aTileWidth / aTileHeight;
  aWorld.TileIScale   := aTileWidth div aTileHeight;
  aWorld.IsRealScale  := (Abs((aWorld.TileIScale - aWorld.TileRScale)) > 0.0001);
  FillChar(aWorld.Tiles, SizeOf(aWorld.Tiles), #0);
  ResetMap(aWorld);
end;

procedure MapXYToISO(var aWorld : TWorldData; aX, aY : Integer; var aISOX, aISOY : Integer);
begin
  aISOX := aX - aY;
  if aWorld.IsRealScale then begin
    aISOY := Trunc((aX + aY) / aWorld.TileRScale);
  end else begin
    aISOY := Trunc((aX + aY) div aWorld.TileIScale);
  end;
end;

procedure WorldXYToScreen(var aWorld : TWorldData; aX, aY : Integer; var aISOX, aISOY : Integer);
begin
  aISOX := (aWorld.OffsetX + aX) - (aWorld.OffsetY + aY);
  if aWorld.IsRealScale then begin
    aISOY := Trunc(((aWorld.OffsetX + aX) + (aWorld.OffsetY + aY)) / aWorld.TileRScale);
  end else begin
    aISOY := Trunc(((aWorld.OffsetX + aX) + (aWorld.OffsetY + aY)) div aWorld.TileIScale);
  end;
end;

procedure ResetMap(var aWorld : TWorldData);
var
  iX, iY            : Integer;
  iOffsX, iOffsY    : Integer;
begin
  for iY := 0 to Pred(aWorld.Map.Height) do begin
    for iX := 0 to Pred(aWorld.Map.Width) do begin
      MapXYToISO(aWorld, iX * aWorld.TileXHalf, iY * aWorld.TileHeight, iOffsX, iOffsY);
      aWorld.Map[iX, iY]^.Floor     := 1;
      aWorld.Map[iX, iY]^.Wall      := 0;
      aWorld.Map[iX, iY]^.X         := iOffsX;
      aWorld.Map[iX, iY]^.Y         := iOffsY;
      aWorld.Map[iX, iY]^.SortOrder := iOffsY + aWorld.TileYHalf;
    end;
  end;
end;

procedure SetBorder(aWorld : TWorldData; aWall : Integer);
var
  iX, iY            : Integer;
  iOffsX, iOffsY    : Integer;
begin
  for iY := 0 to Pred(aWorld.Map.Height) do begin
    for iX := 0 to Pred(aWorld.Map.Width) do begin
      if (iY > 0) and (iY < Pred(aWorld.Map.Height)) then begin
        if (iX > 0) and (iX < Pred(aWorld.Map.Width)) then Continue;
      end;

      MapXYToISO(aWorld, iX * aWorld.TileXHalf, iY * aWorld.TileHeight, iOffsX, iOffsY);
      aWorld.Map[iX, iY]^.Floor := 0;
      aWorld.Map[iX, iY]^.Wall  := aWall;
      aWorld.Map[iX, iY]^.X     := iOffsX;
      aWorld.Map[iX, iY]^.Y     := iOffsY;
    end;
  end;
end;

procedure DrawRoom(aWorld : TWorldData; aX1, aY1, aX2, aY2 : Integer; const aWalls : array of Integer);
var
  iX, iY           : Integer;
  iWall            : Integer;
  iOffsX, iOffsY   : Integer;
begin
  Randomize;
  for iY := aY1 to aY2 do begin
    for iX := aX1 to aY2 do begin
      if (iY > aY1) and (iY < aY2) then begin
        if (iX > aX1) and (iX < aX2) then Continue;
      end;

      iWall := Random(High(aWalls) + 1);
      MapXYToISO(aWorld, iX * aWorld.TileXHalf, iY * aWorld.TileHeight, iOffsX, iOffsY);
      aWorld.Map[iX, iY]^.Floor := 0;
      aWorld.Map[iX, iY]^.Wall  := aWalls[iWall];
      aWorld.Map[iX, iY]^.X     := iOffsX;
      aWorld.Map[iX, iY]^.Y     := iOffsY;
    end;
  end;
end;

procedure DrawDoor(aWorld : TWorldData; aX1, aY1, aDirX, aDirY : Integer; const aWalls : array of Integer);
var
  iWall            : Integer;
  iOffsX, iOffsY   : Integer;
begin
  Randomize;

  iWall := Random(High(aWalls) + 1);
  MapXYToISO(aWorld, aX1 * aWorld.TileXHalf, aY1 * aWorld.TileHeight, iOffsX, iOffsY);
  aWorld.Map[aX1, aY1]^.Floor := aWalls[iWall];
  aWorld.Map[aX1, aY1]^.Wall  := 0;
  aWorld.Map[aX1, aY1]^.X     := iOffsX;
  aWorld.Map[aX1, aY1]^.Y     := iOffsY;
end;

function IsMoveMapValid(var aWorld : TWorldData; aOffsetX, aOffsetY : Integer) : Boolean;
var
  iNewX            : Integer;
  iNewY            : Integer;
begin
  MapXYToISO(aWorld, aWorld.OffsetX + aOffsetX, aWorld.OffsetY + aOffsetY, iNewX, iNewY);
  Result := (iNewX >= 0) and (iNewY >= 0);
end;

function IsWalkabe(aWorld : TWorldData; aX, aY : Integer) : Boolean;
var
  iTileX           : Integer;
  iTileY           : Integer;
begin
  Result := True;
  iTileX := aX div aWorld.TileXHalf;
  iTileY := aY div aWorld.TileHeight;
  if (aWorld.Map[iTileX, iTileY]^.Floor <> 1) then begin
    Result := False;
  end;
end;

procedure MoveToMap(var aWorld : TWorldData; aX, aY : Integer);
begin
  with aWorld do begin
    aWorld.OffsetX := aX;
    aWorld.OffsetY := aY;
  end;
end;

procedure ScrollMap(var aWorld : TWorldData; aOffsetX, aOffsetY : Integer);
begin
  with aWorld do begin
    aWorld.OffsetX := Min(aWorld.OffsetX + aOffsetX, 0);
    aWorld.OffsetY := Min(aWorld.OffsetY + aOffsetY, 0);
  end;
end;

function LoadMap(const aFilename : String; var aWorld : TWorldData) : Boolean;

  function ReadMap(var aMapFile : System.Text) : Boolean;
  var
    iLoop            : Integer;
    iLine            : Integer;
    sLine            : String;
    cLine            : array[0..1023] of Char;
  begin
    iLine := 0;
    while not EOF(aMapFile) do begin
      ReadLn(aMapFile, sLine);
      sLine := Trim(sLine);
      if (sLine <> '') then begin
        if SameText(sLine, '[MAPEND]') then begin
          // Karte wurde gelesen
          Break;
        end;

        StrPLCopy(cLine, sLine, SizeOf(cLine) - 1);
        for iLoop := 0 to Pred(StrLen(cLine)) do begin
           if (cLine[iLoop] = ' ') then begin
             aWorld.Map[iLoop, iLine]^.Floor := 0;
             aWorld.Map[iLoop, iLine]^.Wall  := 0;
           end else if (cLine[iLoop] = '#') then begin
             aWorld.Map[iLoop, iLine]^.Floor := 1;
             aWorld.Map[iLoop, iLine]^.Wall  := 0;
           end else begin
             aWorld.Map[iLoop, iLine]^.Floor := 0;
             aWorld.Map[iLoop, iLine]^.Wall  := Ord(cLine[iLoop]);
           end;
        end;
        iLine := iLine + 1;
      end;
    end;

    Result := (iLine > 0);
  end;

var
  sLine            : String;
  MapFile          : System.Text;
begin
  Result := False;
  try
    AssignFile(MapFile, aFilename);
    Reset(MapFile);
    while not EOF(MapFile) do begin
      ReadLn(MapFile, sLine);
      sLine := Trim(sLine);
      if (sLine <> '') then begin
        if SameText(sLine, '[MAPSTART]') then begin
          Result := ReadMap(MapFile);
        end;
      end;
    end;
  except
    on E : Exception do begin
      fwLOG(E.Message + '"' + aFilename + '"');
    end;
  end;
end;

(*
// Karte wird nicht Bildschirm fuellend gezeichnet, sondern als Rombus
procedure DrawFloor(aScreen : TfwCESurface; var aWorld : TWorldData; aOffsetX : Integer = 0; aOffsetY : Integer = 0);
var
  iLoopX           : Integer;
  iLoopY           : Integer;
  iWOfsX           : Integer;
  iWOfsY           : Integer;
  iPosX            : Integer;
  iPosY            : Integer;
  iMinCol          : Integer;
  iMinRow          : Integer;
  iMaxCol          : Integer;
  iMaxRow          : Integer;
  iTile            : Integer;
begin
  // Bestimme die erste anzuzeigende Tile in dem 2D -Array. Der aktuelle
  // Kartenursprung ist in aord.OffsetX und aWorld.OffsetY hinterlegt.
  MapXYToISO(aWorld, -aWorld.OffsetX, -aWorld.OffsetY, iWOfsX, iWOfsY);
  if aWorld.IsRealScale then begin
    iPosX  := Trunc(((iWOfsY / aWorld.TileYHalf) + (iWOfsX / aWorld.TileXHalf)) / aWorld.TileRScale);
    iPosY  := Trunc(((iWOfsY / aWorld.TileYHalf) - (iWOfsX / aWorld.TileXHalf)) / aWorld.TileRScale);
  end else begin
    iPosX  := ((iWOfsY div aWorld.TileYHalf) + (iWOfsX div aWorld.TileXHalf)) div aWorld.TileIScale;
    iPosY  := ((iWOfsY div aWorld.TileYHalf) - (iWOfsX div aWorld.TileXHalf)) div aWorld.TileIScale;
  end;
  
  iMinCol := Max(0, iPosX);
  iMinRow := Max(0, iPosY);
  iMaxCol := Min(aWorld.Map.Width - 1, iMinCol + (aWorld.Viewport.Width div aWorld.TileXHalf));
  iMaxRow := Min(aWorld.Map.Height - 1, iMinRow + (aWorld.Viewport.Height div aWorld.TileYHalf));

  for iLoopY := iMinRow to iMaxRow do begin
    for iLoopX := iMinCol to iMaxCol do begin
      // Pruefe die Grenzen, damit keine fehlerhaften Array - Zugriffe
      // erfolgen, welche das Spiel beenden.
      iTile  := aWorld.Map[iLoopX, iLoopY]^.Floor;
      if (iTile > 0) then begin
        if Assigned(aWorld.Tiles[iTile]) then begin
          // Berechne die Posistion auf dem Bildschirm
          iPosX := -iWOfsX + aWorld.Map[iLoopX, iLoopY].X;
          iPosY := -iWOfsY + aWorld.Map[iLoopX, iLoopY].Y;

          aWorld.Tiles[iTile].Blit(aScreen, aOffsetX + iPosX, aOffsetY + iPosY);
        end;
      end;
    end;
  end;
end;
*)

// Karte soll Bildschirm fuellend gezeichnet werden
procedure DrawFloor(aScreen : TfwCESurface; var aWorld : TWorldData; aOffsetX : Integer = 0; aOffsetY : Integer = 0);
var
  iLoopX           : Integer;
  iLoopY           : Integer;
  iWOfsX           : Integer;
  iWOfsY           : Integer;
  iPosX            : Integer;
  iPosY            : Integer;
  iMinCol          : Integer;
  iMinRow          : Integer;
  iMaxCol          : Integer;
  iMaxRow          : Integer;
  iTile            : Integer;
begin
  // Bestimme die erste anzuzeigende Tile in dem 2D -Array. Der aktuelle
  // Kartenursprung ist in aord.OffsetX und aWorld.OffsetY hinterlegt.
  MapXYToISO(aWorld, -aWorld.OffsetX, -aWorld.OffsetY, iWOfsX, iWOfsY);
(*  
  if aWorld.IsRealScale then begin
    iPosX  := Trunc(((iWOfsY / aWorld.TileYHalf) + (iWOfsX / aWorld.TileXHalf)) / aWorld.TileRScale);
    iPosY  := Trunc(((iWOfsY / aWorld.TileYHalf) - (iWOfsX / aWorld.TileXHalf)) / aWorld.TileRScale);
  end else begin
    iPosX  := ((iWOfsY div aWorld.TileYHalf) + (iWOfsX div aWorld.TileXHalf)) div aWorld.TileIScale;
    iPosY  := ((iWOfsY div aWorld.TileYHalf) - (iWOfsX div aWorld.TileXHalf)) div aWorld.TileIScale;
  end;
*)
  iPosX   := (-aWorld.OffsetX div aWorld.TileXHalf) - (aWorld.Viewport.Width div aWorld.TileWidth div 2);
  iPosY   := (-aWorld.OffsetY div aWorld.TileHeight) - (aWorld.Viewport.Height div aWorld.TileHeight div 2);
  
  iMinCol := Max(0, iPosX);
  iMinRow := Max(0, iPosY);
  iMaxCol := Min(aWorld.Map.Width - 1, iMinCol + (aWorld.Viewport.Width div aWorld.TileXHalf));
  iMaxRow := Min(aWorld.Map.Height - 1, iMinRow + (aWorld.Viewport.Height div aWorld.TileYHalf));

  for iLoopY := iMinRow to iMaxRow do begin
    for iLoopX := iMinCol to iMaxCol do begin

      // Pruefe die Grenzen, damit keine fehlerhaften Array - Zugriffe
      // erfolgen, welche das Spiel beenden.
      iTile  := aWorld.Map[iLoopX, iLoopY]^.Floor;
      if (iTile > 0) then begin
        if Assigned(aWorld.Tiles[iTile]) then begin
          // Berechne die Posistion auf dem Bildschirm
          iPosX := aOffsetX - iWOfsX + aWorld.Map[iLoopX, iLoopY].X;
          iPosY := aOffsetY - iWOfsY + aWorld.Map[iLoopX, iLoopY].Y;

          if ((iPosX + aWorld.Tiles[iTile].Width) < aWorld.Viewport.X) then Continue;
          if ((iPosY + aWorld.Tiles[iTile].Height) < aWorld.Viewport.Y) then Continue;

          if (iPosX > (aWorld.Viewport.X + aWorld.Viewport.Width)) then Continue;
          if (iPosY > (aWorld.Viewport.Y + aWorld.Viewport.Height)) then Continue;

          aWorld.Tiles[iTile].Blit(aScreen, iPosX, iPosY);
        end;
      end;
    end;
  end;
end;

procedure DrawWalls(aDrawList : TDrawList; var aWorld : TWorldData; aOffsetX : Integer = 0; aOffsetY : Integer = 0);
var
  iLoopX           : Integer;
  iLoopY           : Integer;
  iWOfsX           : Integer;
  iWOfsY           : Integer;
  iPosX            : Integer;
  iPosY            : Integer;
  iMinCol          : Integer;
  iMinRow          : Integer;
  iMaxCol          : Integer;
  iMaxRow          : Integer;
  iTile            : Integer;
  Surface          : TfwCEDrawable;
begin
  // Bestimme die erste anzuzeigende Tile in dem 2D -Array. Der aktuelle
  // Kartenursprung ist in aord.OffsetX und aWorld.OffsetY hinterlegt.
  MapXYToISO(aWorld, -aWorld.OffsetX, -aWorld.OffsetY, iWOfsX, iWOfsY);
(*
  if aWorld.IsRealScale then begin
    iPosX  := Trunc(((iWOfsY div aWorld.TileYHalf) + (iWOfsX div aWorld.TileXHalf)) / aWorld.TileRScale);
    iPosY  := Trunc(0.5 * ((iWOfsY div aWorld.TileYHalf) - (iWOfsX div aWorld.TileXHalf)) / aWorld.TileRScale);
  end else begin
    iPosX  := ((iWOfsY div aWorld.TileYHalf) + (iWOfsX div aWorld.TileXHalf)) div aWorld.TileIScale;
    iPosY  := ((iWOfsY div aWorld.TileYHalf) - (iWOfsX div aWorld.TileXHalf)) div aWorld.TileIScale;
  end;
*)
  iPosX   := (-aWorld.OffsetX div aWorld.TileXHalf) - (aWorld.Viewport.Width div aWorld.TileWidth div 2);
  iPosY   := (-aWorld.OffsetY div aWorld.TileHeight) - (aWorld.Viewport.Height div aWorld.TileHeight div 2);

  iMinCol := Max(0, iPosX);
  iMinRow := Max(0, iPosY);
  iMaxCol := Min(aWorld.Map.Width - 1, iMinCol + (aWorld.Viewport.Width div aWorld.TileXHalf) + 2);
  iMaxRow := Min(aWorld.Map.Height - 1, iMinRow + (aWorld.Viewport.Height div aWorld.TileYHalf) + 2);

  for iLoopY := iMinRow to iMaxRow do begin
    for iLoopX := iMinCol to iMaxCol do begin
      iTile := aWorld.Map[iLoopX, iLoopY]^.Wall;
      if (iTile > 0) then begin
        Surface := aWorld.Tiles[iTile];
        if Assigned(Surface) then begin
          // Berechne die Posistion auf dem Bildschirm
          iPosX := -iWOfsX + aWorld.Map[iLoopX, iLoopY].X - aWorld.TileXHalf;
          iPosY := -iWOfsY + aWorld.Map[iLoopX, iLoopY].Y - (Surface.Height - aWorld.TileHeight);

          if ((aOffsetX + iPosX + Surface.Width) < aWorld.Viewport.X) then Continue;
          if ((aOffsetY + iPosY + Surface.Height) < aWorld.Viewport.Y) then Continue;

          if ((aOffsetX + iPosX) > (aWorld.Viewport.X + aWorld.Viewport.Width)) then Continue;
          if ((aOffsetY + iPosY) > (aWorld.Viewport.Y + aWorld.Viewport.Height)) then Continue;

          aDrawList.Add(TDrawListItem.Create(Surface, iPosX, iPosY, 0, 0, Surface.Width, Surface.Height, 0, iPosY + Surface.Height));
        end;
      end;
    end;
  end;
end;

end.
