unit uStatic;

interface

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

type TStatics = class
private
        idxStream, mulStream: TStream;
        miStream, mmStream : TMemoryStream;
        fiStream, fmStream : TFileStream;
        ItemList, BlockList : TList;
        SerialCount : Integer;
        function BlockLoaded( Id : Integer ) : Boolean;
        procedure LoadBlock( Id : Integer );
public
        Width, Height : Integer;
        loaded: Boolean;
        constructor Create;
        destructor Free;
        procedure Load( idxPath, mulPath : String; Width, Height : Integer );
        procedure Save( idxPath, mulPath : String; Art : Integer = 0 );
        function GetItems( x1, x2, y1, y2 : Integer ) : TList;
        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 );
        function CreateStatic( Id, X, Y, Z, Color : Integer ) : Integer;
        procedure SetBlockChanged( Item : PStaticItem ); overload;
        procedure SetBlockChanged( BlockID : Integer ); overload;
        procedure ReIndex( idxPath, mulPath : String );
        procedure Clear;
        function CreateStaticMap : TBitmap32;
        procedure IDSearch( Path : String );
        procedure CleanUp;
end;

        TStaticBlock = packed record
                BlockId : Integer;
                Changed : Boolean;
        end;

        PStaticBlock = ^TStaticBlock;

var     Statics : TStatics;

implementation

uses uUtilities, StrLib, uGaugeForm, uMulpatcher;

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

constructor TStatics.Create;
begin
        miStream := TMemoryStream.Create;
        mmStream := TMemoryStream.Create;
        loaded := False;
        ItemList := TList.Create;
        BlockList := TList.Create;
end;

destructor TStatics.Free;
begin
        miStream.Free;
        mmStream.Free;
        if MainForm.LowMemory then begin
                fiStream.Free;
                fmStream.Free;
        end;
        ItemList.Free;
end;

procedure TStatics.Load( idxPath, mulPath : String; Width, Height : Integer );
begin
        if not MainForm.LowMemory then begin
                miStream.LoadFromFile( idxPath );
                mmStream.LoadFromFile( mulPath );
                idxStream := miStream;
                mulStream := mmStream;
        end
        else begin
                fiStream := TFileStream.Create( idxPath, fmOpenReadWrite );
                fmStream := TFileStream.Create( mulPath, fmOpenReadWrite );
                idxStream := fiStream;
                mulStream := fmStream;
        end;
        
        ItemList.Clear;
        BlockList.Clear;
        SerialCount := $1FFFFFF;
        loaded := true;

        Self.Width := Width;
        Self.Height := Height;
end;

procedure TStatics.Save( idxPath, mulPath : String; Art : Integer );
var     I, k, J, L : Integer;
        X, Y : Byte;
        IndexRecord : TIndexRecord;
        Item : PStaticItem;
        WFList : TList;
        BlockID : Integer;
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] );
                                if not Item.Deleted then
                                        CreateStatic( Item.ID, Item.X, Item.Y, Item.Z, Item.Color );
                                BlockID := (Item.X div 8)*(Height div 8) + (Item.Y div 8);
                                if not BlockLoaded( BlockId ) then
                                        LoadBlock( BlockID );
                                SetBlockChanged( Item );
                        end;

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

        if BlockList.Count = 0 then begin
                if not MainForm.LowMemory then begin
                        miStream.SaveToFile( idxPath );
                        mmStream.SaveToFile( mulPath );
                end;
                exit;
        end;
        GaugeForm.Activate;

        k := 0;
        for I := 0 to BlockList.Count-1 do begin
                GaugeForm.SetBalken( trunc( 100*I/(BlockList.Count-1) ) );
                if PStaticBlock( BlockList.Items[I] ).Changed then begin
                        BlockId := PStaticBlock( BlockList.Items[I] ).BlockId;
                        L := 0;
                        mulStream.Seek( 0, soFromEnd );
                        IndexRecord.Offset := mulStream.Position;
                        IndexRecord.Extra := 0;
                        if ItemList.Count <> 0 then begin
                                for J := 0 to ItemList.Count-1 do begin
                                        Item := PStaticItem( ItemList.Items[J] );
                                        if ( not Item.Deleted ) and (BlockId = (Height div 8)*( Item.X div 8 ) + ( Item.Y div 8 )) 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 );
                                                inc( L );
                                        end;
                                end;
                        end;
                        if L = 0 then begin
                                IndexRecord.Offset := -1;
                                IndexRecord.Length := -1;
                                IndexRecord.Extra := -1;
                        end
                        else begin
                                IndexRecord.Length := mulStream.Position - IndexRecord.Offset;
                        end;
                        idxStream.Seek( BlockId*12, soFromBeginning );
                        idxStream.Write( IndexRecord, 12 );
                        inc( k );
                end;
        end;
        GaugeForm.Deactivate;

        if not MainForm.LowMemory then begin
                miStream.SaveToFile( idxPath );
                mmStream.SaveToFile( mulPath );
        end;

        ItemList.Clear;
        BlockList.Clear;
        SerialCount := $1FFFFFF;
        showmessage( Format( '%d Static-Blocks Patched.', [k] ) );
end;

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

procedure TStatics.LoadBlock( Id : Integer );
var     IndexRecord : TIndexRecord;
        Item : PStaticItem;
        X, Y : Byte;
        Block : PStaticBlock;
begin
        idxStream.Seek( Id*12, soFromBeginning );
        idxStream.Read( IndexRecord, 12 );
        mulStream.Seek( IndexRecord.Offset, soFromBeginning );

        while mulStream.Position < IndexRecord.Offset+IndexRecord.Length do begin
                new( Item );
                mulStream.Read( Item.Id, 2 );
                mulStream.Read( X, 1 );
                Item.X := ( Id div (Height div 8) )*8 + X;
                mulStream.Read( Y, 1 );
                Item.Y := ( Id mod (Height div 8) )*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;

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

                ItemList.Add( Item );
        end;
        new( Block );
        Block.BlockId := Id;
        Block.Changed := False;
        BlockList.Add( Block );
end;

function TStatics.GetItems( x1, x2, y1, y2 : Integer ) : TList;
var     MinX, MinY, MaxX, MaxY : Integer;
        Item, NewItem : PStaticItem;
        X, Y, I, J, BlockId : 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 := TList.Create;
        for Y := ( MinY div 8 ) to Ceil( MaxY / 8 ) do
                for X := ( MinX div 8 ) to Ceil( MaxX / 8 ) do begin
                        BlockID := X*(Height div 8) + Y;
                        if not BlockLoaded( BlockID ) then
                                LoadBlock( BlockID );
                end;
        if ItemList.Count = 0 then exit;

        for I := 0 to ItemList.Count-1 do begin
                Item := ItemList.Items[I];
                BlockID := (Item.X div 8)*(Height div 8) + (Item.Y div 8);
                if (Item.X >= MinX) and (Item.X <= MaxX) and (Item.Y >= MinY) and (Item.Y <= MaxY) and (not Item.Deleted) and (not StaticDiff.Exists( BlockId )) 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;

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

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

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

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

procedure TStatics.SetId( Serial, Id : Integer );
begin
        Serial := Serial - $1FFFFFF;
        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 TStatics.SetPos( Serial, X, Y, Z : Integer );
begin
        Serial := Serial - $1FFFFFF;
        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;

function TStatics.CreateStatic( Id, X, Y, Z, Color : Integer ) : Integer;
var     Item : PStaticItem;
        BlockID : Integer;
begin
        BlockID := (X div 8)*(Height div 8) + (Y div 8);
        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;

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

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

procedure TStatics.Move( Serial, MX, MY, MZ : Integer );
begin
        Serial := Serial - $1FFFFFF;
        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 TStatics.SetBlockChanged( Item : PStaticItem );
var     BlockId, I : Integer;
begin
        BlockId := (Height div 8)*( Item.X div 8 ) + ( Item.Y div 8 );
        for I := 0 to BlockList.Count-1 do
                if PStaticBlock( BlockList.Items[I] ).BlockId = BlockId then begin
                        PStaticBlock( BlockList.Items[I] ).Changed := True;
                        exit;
                end;
end;

procedure TStatics.SetBlockChanged( BlockID : Integer );
var     I : Integer;
begin
        for I := 0 to BlockList.Count-1 do
                if PStaticBlock( BlockList.Items[I] ).BlockId = BlockId then begin
                        PStaticBlock( BlockList.Items[I] ).Changed := True;
                        exit;
                end;
end;

procedure TStatics.ReIndex( idxPath, mulPath : String );
var     Id : Integer;
        iStream, mStream : TFileStream;
        IndexRecord, OldIndexRecord : TIndexRecord;
begin
        if FileExists( idxPath ) then
                DeleteFile( idxPath );
        if FileExists( mulPath ) then
                DeleteFile( mulPath );

        iStream := TFileStream.Create( idxPath, fmCreate );
        mStream := TFileStream.Create( mulPath, fmCreate );
        iStream.Seek( 0, soFromBeginning );
        mStream.Seek( 0, soFromBeginning );

        GaugeForm.Activate;

        for Id := 0 to (idxStream.Size div 12)-1 do begin
                GaugeForm.SetBalken( trunc( Id*100 / ((idxStream.Size div 12)-1) ) );

                idxStream.Seek( Id*12, soFromBeginning );
                idxStream.Read( OldIndexRecord, 12 );

                if (OldIndexRecord.Offset = -1) or (OldIndexRecord.Length < 1) then begin
                        IndexRecord.Offset := -1;
                        IndexRecord.Length := -1;
                        IndexRecord.Extra := -1;
                        iStream.Write( IndexRecord, 12 );
                end
                else begin
                        mulStream.Seek( OldIndexRecord.Offset, soFromBeginning );
                        IndexRecord.Offset := mStream.Position;
                        IndexRecord.Length := OldIndexRecord.Length;
                        IndexRecord.Extra := OldIndexRecord.Extra;

                        iStream.Write( IndexRecord, 12 );
                        mStream.CopyFrom( mulStream, IndexRecord.Length );
                end;
        end;

        iStream.Free;
        mStream.Free;
        GaugeForm.Deactivate;
end;

procedure TStatics.Clear;
var     I : Integer;
        IndexRecord : TIndexRecord;
        Count : Integer;
begin
        idxStream.Seek( 0, soFromBeginning );
        GaugeForm.Activate;
        Count := ( idxStream.Size div 12 )-1;
        for I := 0 to Count do begin
                GaugeForm.SetBalken( Trunc( 100*I/Count ) );
                IndexRecord.Offset := -1;
                IndexRecord.Length := -1;
                IndexRecord.Extra := -1;
                idxStream.Write( IndexRecord, 12 );
        end;
        GaugeForm.Deactivate;
        mmStream.Clear;
        if MainForm.LowMemory then begin
                fmStream.Size := 0;
        end;

        ItemList.Clear;
        BlockList.Clear;
        SerialCount := $1FFFFFF;        
        showmessage( 'Statics cleared.' );
end;

function TStatics.CreateStaticMap : TBitmap32;
var     IndexRecord : TIndexRecord;
        PX, PY, Block : Integer;
        X, Y : Byte;
begin
        Result := TBitmap32.Create;
        Result.SetSize( 6144, 4096 );
        Result.Clear( Color32( clWhite ) );

        Block := 0;
        idxStream.Seek( 0, soFromBeginning );
        GaugeForm.Activate;
        while idxStream.Position < idxStream.Size do begin
                GaugeForm.SetBalken( Trunc( 100 * idxStream.Position / idxStream.Size ) );
                idxStream.Read( IndexRecord, 12 );
                if ( IndexRecord.Offset = -1 ) or ( IndexRecord.Length = -1 ) then begin
                        Inc( Block );
                        continue;
                end;

                mulStream.Seek( IndexRecord.Offset, soFromBeginning );
                while mulStream.Position < IndexRecord.Offset+IndexRecord.Length do begin
                        mulStream.Seek( 2, soFromCurrent );
                        mulStream.Read( X, 1 );
                        PX := ( Block div (Height div 8) )*8 + X;
                        mulStream.Read( Y, 1 );
                        PY := ( Block mod (Height div 8) )*8 + Y;
                        mulStream.Seek( 3, soFromCurrent );
                        Result.Pixel[ PX, PY ] := Color32( clBlack );
                end;
                inc( Block );
        end;
        GaugeForm.Deactivate;
end;

procedure TStatics.IDSearch( Path : String );
var     SearchID : Integer;
        IndexRecord : TIndexRecord;
        Liste : TStringList;
        Id, Color : Word;
        X, Y : Byte;
        Z : ShortInt;
        BlockID : LongWord;
begin
        if not Loaded then
                exit;

        SearchID := StrToInt( InputBox( 'ID Searching...', 'Choose ID', '0x0' ) );
        Liste := TStringList.Create;

        GaugeForm.Activate;

        idxStream.Seek( 0, soFromBeginning );
        while idxStream.Position < idxStream.Size do begin
                GaugeForm.SetBalken( Trunc( 100*idxStream.Position/idxStream.Size ) );
                idxStream.Read( IndexRecord, 12 );
                BlockID := idxStream.Position div 12;
                if (IndexRecord.Offset <> -1) and (IndexRecord.Length <> -1) then begin
                        mulStream.Seek( IndexRecord.Offset, soFromBeginning );
                        while mulStream.Position < IndexRecord.Offset + IndexRecord.Length do begin
                                mulStream.Read( Id, 2 );
                                mulStream.Read( X, 1 );
                                X := ( BlockID div (Height div 8) )*8 + X;
                                mulStream.Read( Y, 1 );
                                Y := ( BlockID mod (Height div 8) )*8 + Y;                                
                                mulStream.Read( Z, 1 );
                                mulStream.Read( Color, 2 );
                                if Id = SearchID then
                                        Liste.Add( Format( 'Item found: 0x%x %d,%d,%d Color: 0x%x', [Id,X,Y,Z,Color] ) );
                        end;
                end;
        end;
        Liste.SaveToFile( Path + Format( '0x%x.txt', [SearchID] ) );
        Liste.Free;
        GaugeForm.Deactivate;
end;

procedure TStatics.CleanUp;
type    TStaItem = packed record
                Id : Word;
                X, Y : Byte;
                Z : ShortInt;
                Color : Word;
        end;
        PStaItem = ^TStaItem;

var     mulStreamNew : TMemoryStream;
        Liste : TList;
        Item : PStaItem;
        IndexRecord : TIndexRecord;
        I : Integer;
        Id, Color : Word;
        X, Y : Byte;
        Z : ShortInt;
        Gefunden : Boolean;
        CountDel, CountOld, CountNew : Integer;
begin
        GaugeForm.Activate;

        mulStreamNew := TMemoryStream.Create;
        Liste := TList.Create;
        CountOld := 0;
        CountNew := 0;
        CountDel := 0;

        idxStream.Seek( 0, soFromBeginning );
        while idxStream.Position < idxStream.Size do begin
                GaugeForm.SetBalken( Trunc( 100*idxStream.Position/idxStream.Size ) );
                idxStream.Read( IndexRecord, 12 );
                if (IndexRecord.Offset <> -1) and (IndexRecord.Length <> -1) then begin
                        mulStream.Seek( IndexRecord.Offset, soFromBeginning );
                        while mulStream.Position < IndexRecord.Offset + IndexRecord.Length do begin
                                mulStream.Read( Id, 2 );
                                mulStream.Read( X, 1 );
                                mulStream.Read( Y, 1 );
                                mulStream.Read( Z, 1 );
                                mulStream.Read( Color, 2 );

                                Inc( CountOld );

                                Gefunden := False;
                                for I := 0 to Liste.Count-1 do begin
                                        Item := PStaItem( Liste.Items[ I ] );
                                        if (Item.Id = Id) and (Item.X = X) and (Item.Y = Y) and (Item.Z = Z) and (Item.Color = Color) then begin
                                                Inc( CountDel );
                                                Gefunden := True;
                                                break;
                                        end;
                                end;

                                if not Gefunden then begin
                                        new( Item );
                                        Item.Id := Id;
                                        Item.X := X;
                                        Item.Y := Y;
                                        Item.Z := Z;
                                        Item.Color := Color;
                                        Liste.Add( Item );

                                        Inc( CountNew );
                                end;
                        end;

                        IndexRecord.Offset := mulStreamNew.Position;
                        IndexRecord.Length := Liste.Count*7;
                        idxStream.Seek( -12, soFromCurrent );
                        idxStream.Write( IndexRecord, 12 );

                        for I := Liste.Count-1 downto 0 do begin
                                Item := PStaItem( Liste.Items[ I ] );
                                mulStreamNew.Write( Item.Id, 2 );
                                mulStreamNew.Write( Item.X, 1 );
                                mulStreamNew.Write( Item.Y, 1 );
                                mulStreamNew.Write( Item.Z, 1 );
                                mulStreamNew.Write( Item.Color, 2 );
                                Dispose( Item );
                        end;

                        Liste.Clear;
                end;
        end;

        mulStream.Size := mulStreamNew.Size;
        mulStream.Seek( 0, soFromBeginning );
        mulStreamNew.Seek( 0, soFromBeginning );
        mulStream.CopyFrom( mulStreamNew, mulStreamNew.Size );

        Liste.Free;
        mulStreamNew.Free;

        GaugeForm.Deactivate;

        ShowMessage( Format( '%d of %d static items deleted, %d left.', [CountDel,CountOld,CountNew] ) );
end;

end.
