unit uMap;

interface

uses Classes, SysUtils, GR32, Dialogs, Graphics, Math, Windows, uRadarcol,
        uItem, uTextures, uTiledata, uConfig;

type    TMapCell = packed record
                Id: Word;
                Z: Shortint;
        end;

        TMapBlock = packed record
                Header: Cardinal;
                Cells: Array[0..63] of TMapCell;
        end;

        TBlock = packed record
                BlockID : Integer;
                Block : TMapBlock;
                Changed : Boolean;
                Serial : Integer;
        end;

        PBlock = ^TBlock;

        MapId = packed record
                Id : Word;
                IdListe : String;
        end;

        TMap = Class
private
        mulStream : TFileStream;
        BlockList : TList;
        SerialCount : Integer;
        procedure CreateGesamtMap;
        function BlockLoaded( Id : Integer ) : Boolean;
        procedure LoadBlock( Id : Integer );
        function GetBlock( BlockId : Integer ) : PBlock;
public
        Width, Height : Integer;
        Loaded : Boolean;
        GesamtMap : TBitmap32;
        constructor Create;
        destructor Free;
        procedure Load( Path : String; Width, Height : Integer );
        procedure Save;
        function GetItems( x1, x2, y1, y2 : Integer; StaticList : TList ) : TList;
        procedure ProduceTexList( TexPath, ArtPath : String );
        procedure ChangeId( X, Y, ID : Integer );
        procedure ChangeZ( X, Y, Z : Integer );
        procedure MoveZ( X, Y, MZ : Integer );
        procedure ChangeIds( IdArray : array of MapId );
        function GetZLevel( X, Y : Integer ) : Integer;
end;

var     Map : TMap;

implementation

uses uUtilities, StrLib, uGaugeForm;

constructor TMap.Create;
begin
        Loaded := False;
        GesamtMap := TBitmap32.Create;
        BlockList := TList.Create;
end;

destructor TMap.Free;
begin
        try
                mulStream.Free;
        except
        end;
end;

procedure TMap.Load( Path : String; Width, Height : Integer );
begin
        if Loaded then mulStream.Free;
        mulStream := TFileStream.Create( Path, fmOpenReadWrite );
        Self.Width := Width;
        Self.Height := Height;        
        CreateGesamtMap;
        SerialCount := 0;
        Loaded := True;
        BlockList.Clear;
end;

procedure TMap.CreateGesamtMap;
var     x, y, BlockID : integer;
        MapBlock : TMapBlock;
        MapCell : TMapCell;
begin
        if not RadarCol.loaded then begin
                showmessage( 'Radarcol not Loaded.' );
                exit;
        end;
        GesamtMap.Clear( Color32( clBtnFace ) );
        GesamtMap.Width := Width div 16;
        GesamtMap.Height := Height div 16;

        for x := 0 to (Width div 16)-1 do begin
                for y := 0 to (Height div 16)-1 do begin
                        BlockID := x * 2 * (Height div 8) + y * 2;
                        mulStream.Seek( BlockID*sizeof( TMapBlock ), soFromBeginning );
                        mulStream.Read( MapBlock, sizeof( TMapBlock ) );
                        MapCell := MapBlock.Cells[ 0 ];
                        GesamtMap.Pixel[ x, y ] := Color32( Radarcol.GetColor( MapCell.Id ) );
                end;
        end;
end;

function TMap.BlockLoaded( Id : Integer ) : Boolean;
var     I : Integer;
begin
        Result := False;
        for I := 0 to BlockList.Count-1 do
                if PBlock( BlockList.Items[I] ).BlockID = Id then begin
                        Result := True;
                        exit;
                end;
end;

function TMap.GetBlock( BlockId : Integer ) : PBlock;
var     I : Integer;
begin
        for I := 0 to BlockList.Count-1 do
                if PBlock( BLockList.Items[I] ).BlockID = BlockId then begin
                        Result := PBlock( BlockList.Items[I] );
                        exit;
                end;
        Result := nil;
end;

procedure TMap.LoadBlock( Id : Integer );
var     Block : PBlock;
        MapBlock : TMapBlock;
begin
        mulStream.Seek( Id*sizeof( TMapBlock ), soFromBeginning );
        mulStream.Read( MapBlock, sizeof( TMapBlock ) );

        new( Block );
        Block.BlockID := Id;
        Block.Block := MapBlock;
        Block.Changed := False;
        Block.Serial := SerialCount;
        SerialCount := SerialCount + 64;

        BlockList.Add( Block );
end;

procedure TMap.ProduceTexList( TexPath, ArtPath : String );
type    TTexPair = Record Id, Count : Word; end;
        PTexPair = ^TTexPair;
        function SortItems( Item1, Item2 : Pointer ) : Integer;
        begin
                if PTexPair( Item1 ).Id > PTexPair( Item2 ).Id then
                        Result := 1
                else if PTexPair( Item1 ).Id < PTexPair( Item2 ).Id then
                        Result := -1
                else
                        Result := 0;
        end;
var     MapBlock : TMapBlock;
        TexList : TList;
        I, J : Integer;
        Gefunden : Boolean;
        TexPair : PTexPair;
        Datei, Datei2 : TStringList;
        Wet : String;
begin
        if not Tiledata.Loaded then begin
                ShowMessage( 'Load Tiledata first.' );
                Exit;
        end;

        mulStream.Seek( 0, soFromBeginning );
        TexList := TList.Create;
        GaugeForm.Activate;
        while mulStream.Position < mulStream.Size do begin
                GaugeForm.SetBalken( Trunc( mulStream.Position * 100 / mulStream.Size ) );
                mulStream.Read( MapBlock, sizeof( TMapblock ) );
                for I := 0 to 63 do begin
                        Gefunden := False;
                        if TexList.Count > 0 then
                                for J := 0 to TexList.Count-1 do
                                        if MapBlock.Cells[ I ].Id = PTexPair( TexList.Items[ J ] ).Id then begin
                                                Inc( PTexPair( TexList.Items[ J ] ).Count );
                                                Gefunden := True;
                                        end;
                        if not Gefunden then begin
                                new( TexPair );
                                TexPair.Id := MapBlock.Cells[ I ].Id;
                                TexPair.Count := 1;
                                TexList.Add( TexPair );
                        end;
                end;
        end;
        GaugeForm.Deactivate;
        TexList.Sort( @SortItems );

        Datei := TStringList.Create;
        Datei2 := TStringList.Create;
        for J := 0 to TexList.Count-1 do begin
                if Tiledata.GetLandFlag( PTexPair( TexList.Items[ J ] ).Id, dLIQUID ) then
                        Wet := ';Wet'
                else
                        Wet := '';
                if Tiledata.GetLandTexId( PTexPair( TexList.Items[ J ] ).Id ) = 0 then
                        Datei2.Add( Format( '0x%x;%d;%s%s', [PTexPair( TexList.Items[ J ] ).Id,PTexPair( TexList.Items[ J ] ).Count,Tiledata.GetLandName( PTexPair( TexList.Items[ J ] ).Id ),Wet] ) )
                else
                        Datei.Add( Format( '0x%x;%d;%s;0x%x%s', [PTexPair( TexList.Items[ J ] ).Id,PTexPair( TexList.Items[ J ] ).Count,Tiledata.GetLandName( PTexPair( TexList.Items[ J ] ).Id ),Tiledata.GetLandTexId( PTexPair( TexList.Items[ J ] ).Id ),Wet] ) );
        end;
        for J := 0 to TexList.Count-1 do begin
                Dispose( PTexPair( TexList.Items[ J ] ) );
        end;
        TexList.Free;
        Datei.SaveToFile( TexPath );
        Datei.Free;
        Datei2.SaveToFile( ArtPath );
        Datei2.Free;
        showmessage( 'Done.' );
end;

procedure TMap.ChangeIds( IdArray : array of MapId );
var     I, J : Integer;
        MapBlock : TMapBlock;
        Liste : TStringList;
begin
        mulStream.Seek( 0, soFromBeginning );
        GaugeForm.Activate;
        while mulStream.Position < mulStream.Size do begin
                GaugeForm.SetBalken( Trunc( mulStream.Position * 100 / mulStream.Size ) );
                mulStream.Read( MapBlock, sizeof( TMapblock ) );
                for I := 0 to 63 do
                        for J := 0 to Length( IdArray )-1 do
                                if MapBlock.Cells[I].Id = IdArray[J].Id then begin
                                        Liste := TStringList.Create;
                                        StrLib.Split( IdArray[J].IdListe, ',', Liste );
                                        try
                                                MapBlock.Cells[I].Id := StrToInt( Liste.Strings[ Random( Liste.Count ) ] );
                                        except
                                                showmessage( Format( 'Bug bei Id 0x%x', [IdArray[J].Id] ) );
                                        end;
                                        Liste.Free;
                                        break;
                                end;
                mulStream.Seek( -sizeof( TMapBlock ), soFromCurrent );
                mulStream.Write( MapBlock, sizeof( TMapBlock ) );
        end;
        GaugeForm.Deactivate;
end;

procedure TMap.Save;
var     Block : PBlock;
        MapBlock : TMapBlock;
        I, k : Integer;
begin
        if BlockList.Count = 0 then exit;

        k := 0;
        for I := 0 to BlockList.Count-1 do begin
                Block := BlockList.Items[I];
                if Block.Changed then begin
                        MapBlock := Block.Block;
                        mulStream.Seek( Block.BlockID*sizeof( TMapBlock ), soFromBeginning );
                        mulStream.Write( MapBlock, sizeof( TMapBlock ) );
                        inc( k );
                end;
        end;

        showmessage( Format( '%d MapBlocks patched.', [k] ) );
end;

function TMap.GetZLevel( X, Y : Integer ) : Integer;
var     BlockID, I : Integer;
        MapBlock : TMapBlock;
        CX, CY : Integer;
begin
        BlockID := ( X div 8 ) * (Height div 8) + ( Y div 8 );
        if (not BlockLoaded( BlockId )) and (BlockId > 0) and (BlockId*sizeof( TMapBlock ) < mulStream.Size) then
                LoadBlock( BlockId );

        CX := X mod 8;
        CY := Y mod 8;

        for I := 0 to BlockList.Count-1 do
                if PBlock( BlockList.Items[I] ).BlockID = BlockID then begin
                        MapBlock := PBlock( BlockList.Items[I] ).Block;
                        Result := MapBlock.Cells[ CY*8+CX ].Z;
                        exit;
                end;
        Result := 0;
end;

function TMap.GetItems( x1, x2, y1, y2 : Integer; StaticList : TList ) : TList;
var     BlockID : Integer;
        MapBlock : TMapBlock;
        X, Y, BX, BY, CX, CY, PosX, PosY, J : Integer;
        MinX, MinY, MaxX, MaxY : Integer;
        NewItem : PStaticItem;
        MapCell : TMapCell;
        Block : PBlock;
        NeedTex : Boolean;
        Z1, Z2, Z3, Z4 : Integer;
begin
        MinX := Min( x1, x2 );
        MinY := Min( y1, y2 );
        MaxX := Max( x1, x2 );
        MaxY := Max( y1, y2 );

        MinX := Max( MinX, 0 );
        MinY := Max( MinY, 0 );
        MaxX := Min( MaxX, Width );
        MaxY := Min( MaxY, Height );
        Result := StaticList;

        for BY := Floor( MinY / 8 ) to Ceil( MaxY / 8 ) do begin
                for BX := Floor( MinX / 8 ) to Ceil( MaxX / 8 ) do begin
                        BlockID := BX*(Height div 8) + BY;
                        if not BlockLoaded( BlockId ) then
                                LoadBlock( BlockId );

                        Block := GetBlock( BlockID );
                        if Block = nil then exit;
                        
                        MapBlock := Block.Block;
                        CX := ( BlockId div (Height div 8) )*8;
                        CY := ( BlockId mod (Height div 8) )*8;
                        for Y := 0 to 7 do begin
                                for X := 0 to 7 do begin
                                        MapCell := MapBlock.Cells[ Y*8+X ];

                                        J := 0;
                                        PosX := CX + X;
                                        PosY := CY + Y;

                                        new( NewItem );
                                        NewItem.ID := MapCell.Id;
                                        NewItem.X := PosX;
                                        NewItem.Y := PosY;
                                        NewItem.Z := MapCell.Z;
                                        NewItem.Serial := Block.Serial + Y*8 + X;
                                        NewItem.Selected := False;
                                        NewItem.Deleted := False;
                                        NewItem.Color := 0;
                                        NewItem.isAnimated := False;

                                        Z1 := GetZLevel( PosX, PosY );
                                        Z2 := GetZLevel( PosX+1, PosY );
                                        Z3 := GetZLevel( PosX, PosY+1 );
                                        Z4 := GetZLevel( PosX+1, PosY+1 );

                                        NewItem.X0 := 0;
                                        NewItem.Y0 := 0 - Z1 * 4;

                                        NewItem.X1 := -22;
                                        NewItem.Y1 := 22 - Z3 * 4;

                                        NewItem.X2 := 0;
                                        NewItem.Y2 := 44 - Z4 * 4;

                                        NewItem.X3 := 22;
                                        NewItem.Y3 := 22 - Z2 * 4;

                                        NeedTex := True;
                                        if (Z1 = Z2) and (Z1 = Z3) and (Z1 = Z4) then
                                                NeedTex := False
                                        else if not Textures.Exists( Tiledata.GetLandTexId( MapCell.Id ) ) then
                                                NeedTex := False;

                                        if NeedTex then begin
                                                NewItem.Typ := Typ_Item_Tex;
                                                NewItem.Breite := 44;
                                                NewItem.Hoehe := 44;

                                                Result.Insert( J, NewItem );
                                        end
                                        else begin
                                                NewItem.Typ := Typ_Item_Map;
                                                NewItem.Hoehe := 44;
                                                NewItem.Breite := 44;

                                                Result.Insert( J, NewItem );
                                        end;
                                end;
                        end;
                end;
        end;
end;

procedure TMap.ChangeId( X, Y, ID : Integer );
var     BlockID, I : Integer;
        CX, CY : Integer;
begin
        BlockID := ( X div 8 ) * (Height div 8) + ( Y div 8 );
        if (not BlockLoaded( BlockId )) and (BlockId > 0) and (BlockId*sizeof( TMapBlock ) < mulStream.Size) then
                LoadBlock( BlockId );

        CX := X mod 8;
        CY := Y mod 8;

        for I := 0 to BlockList.Count-1 do
                if PBlock( BlockList.Items[I] ).BlockID = BlockID then begin
                        PBlock( BlockList.Items[I] ).Block.Cells[ CY*8+CX ].Id := ID;
                        PBlock( BlockList.Items[I] ).Changed := True;
                        exit;
                end;
end;

procedure TMap.ChangeZ( X, Y, Z : Integer );
var     BlockID, I : Integer;
        CX, CY : Integer;
begin
        BlockID := ( X div 8 ) * (Height div 8) + ( Y div 8 );
        if (not BlockLoaded( BlockId )) and (BlockId > 0) and (BlockId*sizeof( TMapBlock ) < mulStream.Size) then
                LoadBlock( BlockId );

        CX := X mod 8;
        CY := Y mod 8;

        for I := 0 to BlockList.Count-1 do
                if PBlock( BlockList.Items[I] ).BlockID = BlockID then begin
                        PBlock( BlockList.Items[I] ).Block.Cells[ CY*8+CX ].Z := Z;
                        PBlock( BlockList.Items[I] ).Changed := True;
                        exit;
                end;
end;

procedure TMap.MoveZ( X, Y, MZ : Integer );
var     BlockID, I : Integer;
        CX, CY : Integer;
begin
        BlockID := ( X div 8 ) * (Height div 8) + ( Y div 8 );
        if (not BlockLoaded( BlockId )) and (BlockId > 0) and (BlockId*sizeof( TMapBlock ) < mulStream.Size) then
                LoadBlock( BlockId );

        CX := X mod 8;
        CY := Y mod 8;

        for I := 0 to BlockList.Count-1 do
                if PBlock( BlockList.Items[I] ).BlockID = BlockID then begin
                        PBlock( BlockList.Items[I] ).Block.Cells[ CY*8+CX ].Z := PBlock( BlockList.Items[I] ).Block.Cells[ CY*8+CX ].Z + MZ;
                        PBlock( BlockList.Items[I] ).Changed := True;                        
                        exit;
                end;
end;

end.
