unit uStaticDiff;

interface

uses Classes, Windows, SysUtils, GR32, Dialogs, Graphics, Math, uArt, uItem,
        uTiledata, uAnimdata, uWorldFile;

type    TStaticDiff = class
        private
                Loaded : Boolean;
                SerialCount : Integer;
                iStream, idxStream, mulStream : TMemoryStream;
                BlockList : TList;
                ItemList : TList;
        public
                constructor Create;
                destructor Free;
                procedure Load( Directory : String );
                function Exists( BlockId : Integer ) : Boolean;
                function BlockLoaded( Id : Integer ) : Boolean;
                procedure LoadBlock( Id : Integer );
                function AddItem( Id, X, Y, Z, Color : Integer ) : Integer;
                procedure Select( Serial : Integer );
                procedure UnSelect( Serial : Integer );
                procedure Delete( Serial : Integer );
                procedure SetColor( Serial, Color : Integer );
                procedure SetId( Serial, Id : Integer );
                procedure SetPos( Serial, X, Y, Z : Integer );
                procedure Move( Serial, MX, MY, MZ : Integer );
                procedure LoadAllBlocks;
                function GetItems( x1, x2, y1, y2 : Integer; SourceList : TList ) : TList;
                function CreateMap : TBitmap32;
                procedure Save( Directory : String; Art : Integer = 0 );
                procedure SetBlockChanged( Item : PStaticItem );
        end;

        TDiffBlock = packed record
                BlockId : Integer;
                Loaded : Boolean;
                Nummer : Integer;
                Changed : Boolean;
        end;

        PDiffBlock = ^TDiffBlock;

var     StaticDiff : TStaticDiff;

implementation

uses uUtilities, StrLib, uGaugeForm;

type    TIndexRecord = packed record
                Offset: Integer;
                Length: Integer;
                Extra: Integer
        end;

constructor TStaticDiff.Create;
begin
        Loaded := False;
        iStream := TMemoryStream.Create;
        idxStream := TMemoryStream.Create;
        mulStream := TMemoryStream.Create;
        BlockList := TList.Create;
        ItemList := TList.Create;
        SerialCount := $3FFFFFF;
end;

destructor TStaticDiff.Free;
begin
        iStream.Free;
        idxStream.Free;
        mulStream.Free;
        BlockList.Free;
        ItemList.Free;
end;

procedure TStaticDiff.Load( Directory : String );
var     BlockId : Integer;
        Block : PDiffBlock;
        Nr : Integer;
begin
        BlockList.Clear;
        ItemList.Clear;
        SerialCount := $3FFFFFF;
        Nr := 0;
        iStream.LoadFromFile( Directory + 'Stadifl0.mul' );
        idxStream.LoadFromFile( Directory + 'Stadifi0.mul' );
        mulStream.LoadFromFile( Directory + 'Stadif0.mul' );

        iStream.Seek( 0, soFromBeginning );

        while iStream.Position < iStream.Size do begin
                iStream.Read( BlockId, 4 );
                new ( Block );
                Block.BlockId := BlockId;
                Block.Nummer := Nr;
                Block.Changed := False;
                inc( Nr );

                BlockList.Add( Block );
        end;
        Loaded := True;
end;

function TStaticDiff.Exists( BlockId : Integer ) : Boolean;
var     I : Integer;
begin
        if (not Loaded) or (BlockList.Count = 0) then begin
                Result := False;
                exit;
        end;

        for I := 0 to BlockList.Count-1 do begin
                if PDiffBlock( BlockList.Items[I] ).BlockId = BlockId then begin
                        Result := True;
                        exit;
                end;
        end;

        Result := False;
end;

function TStaticDiff.BlockLoaded( Id : Integer ) : Boolean;
var     I : Integer;
begin
        if (not Loaded) or (BlockList.Count = 0) then begin
                Result := False;
                exit;
        end;

        for I := 0 to BlockList.Count-1 do begin
                if PDiffBlock( BlockList.Items[I] ).BlockId = Id then begin
                        if PDiffBlock( BlockList.Items[I] ).Loaded then
                                Result := True
                        else
                                Result := False;
                        exit;
                end;
        end;
        Result := False;
end;

procedure TStaticDiff.LoadBlock( Id : Integer );
var     IndexRecord : TIndexRecord;
        Item : PStaticItem;
        X, Y : Byte;
        Pos, I : Integer;
begin
        for I := 19 to BlockList.Count-1 do begin
                if PDiffBlock( BlockList.Items[I] ).BlockId = Id then begin
                        Pos := PDiffBlock( BlockList.Items[I] ).Nummer;
                        break;
                end;
        end;

        idxStream.Seek( Pos*12, soFromBeginning );
        idxStream.Read( IndexRecord, 12 );
        mulStream.Seek( IndexRecord.Offset, soFromBeginning );

        if ( IndexRecord.Offset <> -1 ) and ( IndexRecord.Length <> -1 ) then
                while mulStream.Position < IndexRecord.Offset+IndexRecord.Length do begin
                        new( Item );
                        mulStream.Read( Item.Id, 2 );
                        mulStream.Read( X, 1 );
                        Item.X := ( Id div 512 )*8 + X;
                        mulStream.Read( Y, 1 );
                        Item.Y := ( Id mod 512 )*8 + Y;
                        mulStream.Read( Item.Z, 1 );
                        mulStream.Read( Item.Color, 2 );
                        Item.Serial := SerialCount;
                        SerialCount := SerialCount+1;
                        Item.Hoehe := Art.GetStaticTileHeight( Item.Id );
                        Item.Breite := Art.GetStaticTileWidth( Item.Id );
                        Item.Selected := False;
                        Item.Deleted := False;
                        Item.Typ := Typ_Item_Static_Diff;

                        if (Tiledata.GetStaticFlag( Item.ID, dANIMATION )) and (Animdata.GetCount( Item.ID ) > 0) then
                                Item.isAnimated := True
                        else
                                Item.isAnimated := False;

                        ItemList.Add( Item );
                end;

        for I := 0 to BlockList.Count-1 do begin
                if PDiffBlock( BlockList.Items[I] ).BlockId = Id then begin
                        PDiffBlock( BlockList.Items[I] ).Loaded := True;
                        exit;
                end;
        end;
end;

procedure TStaticDiff.LoadAllBlocks;
var     I : Integer;
begin
        if BlockList.Count = 0 then exit;

        ItemList.Clear;
        SerialCount := $3FFFFFF;

        for I := 0 to BlockList.Count-1 do begin
                LoadBlock( PDiffBlock( BlockList.Items[I] ).BlockId );
        end;
end;


function TStaticDiff.GetItems( x1, x2, y1, y2 : Integer; SourceList : TList ) : TList;
var     MinX, MinY, MaxX, MaxY : Integer;
        Item, NewItem : PStaticItem;
        X, Y, I, J, BlockId : Integer;
begin
        Result := SourceList;
        if not Loaded then exit;
        
        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, 6143 );
        MaxY := Min( MaxY, 4095 );

        for Y := ( MinY div 8 ) to Ceil( MaxY / 8 ) do
                for X := ( MinX div 8 ) to Ceil( MaxX / 8 ) do begin
                        BlockID := X*512 + Y;
                        if Exists( BlockId ) then begin
                                if not BlockLoaded( BlockId ) then begin
                                        LoadBlock( BlockId );
                                end;
                        end;
                end;

        if ItemList.Count = 0 then begin
                exit;
        end;

        for I := 0 to ItemList.Count-1 do begin
                Item := ItemList.Items[I];
                if (Item.X >= MinX) and (Item.X <= MaxX) and (Item.Y >= MinY) and (Item.Y <= MaxY) and (not Item.Deleted) then begin
                        J := 0;

                        new( NewItem );
                        NewItem.X := Item.X;
                        NewItem.Y := Item.Y;
                        NewItem.Z := Item.Z;
                        NewItem.ID := Item.ID;
                        NewItem.Serial := Item.Serial;
                        NewItem.Hoehe := Item.Hoehe;
                        NewItem.Breite := Item.Breite;
                        NewItem.Selected := Item.Selected;
                        NewItem.Deleted := Item.Deleted;
                        NewItem.Color := Item.Color;
                        NewItem.isAnimated := Item.isAnimated;                        
                        NewItem.Typ := Item.Typ;

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

function TStaticDiff.CreateMap : TBitmap32;
var     I, X, Y : integer;
begin
        Result := TBitmap32.Create;
        Result.Width := 384;
        Result.Height := 256;
        Result.Clear( Color32( clWhite ) );

        if BlockList.Count = 0 then exit;

        for I := 0 to BlockList.Count-1 do begin
                if Exists( PDiffBlock( BlockList.Items[I] ).BlockId ) then begin
                        X := PDiffBlock( BlockList.Items[I] ).BlockId div 512;
                        Y := PDiffBlock( BlockList.Items[I] ).BlockId mod 512;
                        Result.Pixel[ X div 2, Y div 2 ] := Color32( clBlack );
                end;
        end;
end;

function TStaticDiff.AddItem( Id, X, Y, Z, Color : Integer ) : Integer;
var     Item : PStaticItem;
        BlockId : Integer;
        Block : PDiffBlock;
begin
        BlockID := (X div 8)*512 + (Y div 8);
        if not Exists( BlockID ) then begin
                new( Block );
                Block.BlockId := BlockID;
                Block.Loaded := True;
                Block.Nummer := BlockList.Count;

                BlockList.Add( Block );
        end
        else if not BlockLoaded( BlockID ) then
                LoadBlock( BlockID );

        new( Item );
        Item.ID := Id;
        Item.X := X;
        Item.Y := Y;
        Item.Z := Z;
        Item.Color := Color;
        Item.Deleted := False;
        Item.Selected := False;
        Item.Serial := SerialCount;
        SerialCount := SerialCount+1;
        Item.Hoehe := Art.GetStaticTileHeight( Item.Id );
        Item.Breite := Art.GetStaticTileWidth( Item.Id );
        Item.Typ := Typ_Item_Static_Diff;

        if (Tiledata.GetStaticFlag( Item.ID, dANIMATION )) and (Animdata.GetCount( Item.ID ) > 0) then
                Item.isAnimated := True
        else
                Item.isAnimated := False;

        ItemList.Add( Item );
        Result := Item.Serial;
        SetBlockChanged( Item );
end;

procedure TStaticDiff.Select( Serial : Integer );
begin
        Serial := Serial - $3FFFFFF;
        if (Serial >= 0) and (Serial < ItemList.Count) then begin
                PStaticItem( ItemList.Items[ Serial ] ).Selected := True;
        end;
end;

procedure TStaticDiff.UnSelect( Serial : Integer );
begin
        Serial := Serial - $3FFFFFF;
        if (Serial >= 0) and (Serial < ItemList.Count) then begin
                PStaticItem( ItemList.Items[ Serial ] ).Selected := False;
        end;
end;

procedure TStaticDiff.Delete( Serial : Integer );
begin
        Serial := Serial - $3FFFFFF;
        if (Serial >= 0) and (Serial < ItemList.Count) then begin
                PStaticItem( ItemList.Items[ Serial ] ).Deleted := True;
                SetBlockChanged( PStaticItem( ItemList.Items[ Serial ] ) );
        end;
end;

procedure TStaticDiff.SetColor( Serial, Color : Integer );
begin
        Serial := Serial - $3FFFFFF;
        if (Serial >= 0) and (Serial < ItemList.Count) then begin
                PStaticItem( ItemList.Items[ Serial ] ).Color := Color;
                SetBlockChanged( PStaticItem( ItemList.Items[ Serial ] ) );
        end;
end;

procedure TStaticDiff.SetId( Serial, Id : Integer );
begin
        Serial := Serial - $3FFFFFF;
        if (Serial >= 0) and (Serial < ItemList.Count) then begin
                PStaticItem( ItemList.Items[ Serial ] ).Id := Id;
                PStaticItem( ItemList.Items[ Serial ] ).Hoehe := Art.GetStaticTileHeight( Id );
                PStaticItem( ItemList.Items[ Serial ] ).Breite := Art.GetStaticTileWidth( Id );
                if (Tiledata.GetStaticFlag( Id, dANIMATION )) and (Animdata.GetCount( Id ) > 0) then
                        PStaticItem( ItemList.Items[ Serial ] ).isAnimated := True
                else
                        PStaticItem( ItemList.Items[ Serial ] ).isAnimated := False;
                SetBlockChanged( PStaticItem( ItemList.Items[ Serial ] ) );
        end;
end;

procedure TStaticDiff.SetPos( Serial, X, Y, Z : Integer );
begin
        Serial := Serial - $3FFFFFF;
        if (Serial >= 0) and (Serial < ItemList.Count) then begin
                PStaticItem( ItemList.Items[ Serial ] ).X := X;
                PStaticItem( ItemList.Items[ Serial ] ).Y := Y;
                PStaticItem( ItemList.Items[ Serial ] ).Z := Z;
                SetBlockChanged( PStaticItem( ItemList.Items[ Serial ] ) );
        end;
end;

procedure TStaticDiff.Move( Serial, MX, MY, MZ : Integer );
begin
        Serial := Serial - $3FFFFFF;
        if (Serial >= 0) and (Serial < ItemList.Count) then begin
                PStaticItem( ItemList.Items[ Serial ] ).X := PStaticItem( ItemList.Items[ Serial ] ).X +MX;
                PStaticItem( ItemList.Items[ Serial ] ).Y := PStaticItem( ItemList.Items[ Serial ] ).Y +MY;
                PStaticItem( ItemList.Items[ Serial ] ).Z := PStaticItem( ItemList.Items[ Serial ] ).Z +MZ;
                SetBlockChanged( PStaticItem( ItemList.Items[ Serial ] ) );
        end;
end;

procedure TStaticDiff.SetBlockChanged( Item : PStaticItem );
var     BlockId, I : Integer;
begin
        BlockId := 512*( Item.X div 8 ) + ( Item.Y div 8 );
        for I := 0 to BlockList.Count-1 do
                if PDiffBlock( BlockList.Items[I] ).BlockId = BlockId then begin
                        PDiffBlock( BlockList.Items[I] ).Changed := True;
                        exit;
                end;
end;

procedure TStaticDiff.Save( Directory : String; Art : Integer = 0 );
var     Item : PStaticItem;
        WFList : TList;
        I, J, BlockID : Integer;
        X, Y : Byte;
        Block : PDiffBlock;
        IndexRecord : TIndexRecord;
begin
        if Art <> 0 then begin
                case Art of
                        1 : WFList := WorldFile.GetNewItems;
                        2 : WFList := WorldFile.GetAllItems;
                end;

                if WFList.Count <> 0 then
                        for I := 0 to WFList.Count-1 do begin
                                Item := PStaticItem( WFList.Items[I] );
                                AddItem( Item.ID, Item.X, Item.Y, Item.Z, Item.Color );
                                SetBlockChanged( Item );
                        end;

                case Art of
                        1 : WorldFile.ClearAllNew;
                        2 : WorldFile.ClearAllNew;
                end;
                WFList.Free;                
        end;

        if BlockList.Count = 0 then exit;
        if ItemList.Count = 0 then exit;

        for I := 0 to BlockList.Count-1 do begin
                Block := PDiffBlock( BlockList.Items[I] );
                if Block.Changed then begin
                        iStream.Seek( Block.Nummer*4, soFromBeginning );
                        iStream.Write( Block.BlockId, 4 );

                        mulStream.Seek( 0, soFromEnd );
                        IndexRecord.Offset := mulStream.Position;

                        for J := 0 to ItemList.Count-1 do begin
                                Item := PStaticItem( ItemList.Items[J] );
                                BlockID := (Item.X div 8)*512 + (Item.Y div 8);
                                if (BlockID =  Block.BlockId) and (not Item.Deleted) then begin
                                        mulStream.Write( Item.Id, 2 );
                                        X := Item.X mod 8;
                                        mulStream.Write( X, 1 );
                                        Y := Item.Y mod 8;
                                        mulStream.Write( Y, 1 );
                                        mulStream.Write( Item.Z , 1 );
                                        mulStream.Write( Item.Color, 2 );
                                end;
                        end;
                        IndexRecord.Length := mulStream.Position - IndexRecord.Offset;
                        IndexRecord.Extra := 0;

                        idxStream.Seek( Block.Nummer*12, soFromBeginning );
                        idxStream.Write( IndexRecord, 12 );
                end;
        end;
        iStream.SaveToFile( Directory + 'Stadifl0.mul' );
        idxStream.SaveToFile( Directory + 'Stadifi0.mul' );
        mulStream.SaveToFile( Directory + 'Stadif0.mul' );

        for I := 0 to BlockList.Count-1 do begin
                PDiffBlock( BlockList.Items[I] ).Changed := False;
                PDiffBlock( BlockList.Items[I] ).Loaded := False;
        end;
        ItemList.Clear;
end;

end.
