unit uAnim2;

interface

uses Classes, SysUtils, GR32, Dialogs, Graphics;

const   type_HighDetailed = 0;
        type_LowDetailed = 1;
        type_People = 2;

type TAnim2 = class
private
        idxStream, mulStream : TFileStream;
        function AnimCount( Id : integer ) : integer;
        function GetRealId( Id : integer ) : integer;
        function GetAnimType( Id : integer ) : word;
public
        Loaded : boolean;
        constructor Create;
        destructor Free;
        procedure Load( idxPath, mulPath : String );
        procedure DupeAnim( OrigId, TargetId : Integer; idxPath : String );
        function SaveFirstPicture( Id : integer ) : TBitmap32;
        procedure SaveAnimToFile( Id : integer; Filename : String );
        procedure SaveAnimToDirectory( Id : integer; dirPath : String );
        procedure LoadAnimFromFile( Id : integer; Filename : String );
        procedure LoadAnimFromDirectory( Id : integer; dirPath : String; UseColor : boolean );
        function AnimExists( Id : integer ) : boolean;
        function CountAllAnims : Integer;
        procedure DeleteAnim( Id : Integer );
end;

var     Anim2 : TAnim2;

implementation

uses uUtilities, StrLib, uGaugeForm;

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

        TCenter = packed record
                CenterX : integer;
                CenterY : integer;
        end;

constructor TAnim2.Create;
begin
        Loaded := false;
end;

destructor TAnim2.Free;
begin
        try
                idxStream.Free;
                mulStream.Free;
        except
        end;
end;

procedure TAnim2.Load( idxPath, mulPath : String );
begin
        idxStream := TFileStream.Create( idxPath, fmOpenReadWrite );
        mulStream := TFileStream.Create( mulPath, fmOpenReadWrite );
        Loaded := true;
end;

procedure TAnim2.DupeAnim( OrigId, TargetId : Integer; idxPath : String );
var     RealOrigId, RealTargetId, Count, Anim : Integer;
        IndexRec : TIndexRecord;
begin
        if GetAnimType( OrigId ) <> GetAnimType( TargetId ) then begin
                showmessage( 'Animtypes dont match.' );
                exit;
        end;

        RealOrigId := GetRealId( OrigId );
        RealTargetId := GetRealId( TargetId );
        Count := AnimCount( OrigId );

        if not Loaded then
                idxStream := TFileStream.Create( idxPath, fmOpenReadWrite );

        for Anim := 0 to Count-1 do begin
                idxStream.Seek( (RealOrigId+Anim)*12, soFromBeginning );
                idxStream.Read( IndexRec, sizeof( TIndexRecord ) );

                idxStream.Seek( (RealTargetId+Anim)*12, soFromBeginning );
                idxStream.Write( IndexRec, sizeof( TIndexRecord ) );
        end;

        if not Loaded then
                idxStream.Free;
end;

function TAnim2.GetRealId( Id : Integer ) : Integer;
begin
        if Id < 200 then begin
                Result := Id*110;
        end
        else begin
                Result := ( Id - 200 ) * 65 + 22000;
        end;
end;

function TAnim2.AnimCount( Id : Integer ) : Integer;
begin
        if Id < 200 then
                Result := 110
        else
                Result := 65;
end;

function TAnim2.CountAllAnims : Integer;
begin
        Result := 200; // High Detailed + Low Detailed
        Result := Result + Trunc( ( idxStream.Size - 22000*12 ) / ( sizeof( TIndexRecord )*65 ) );
end;

function TAnim2.GetAnimType( Id : integer ) : word;
begin
        If Id < 200 then
                Result := type_HighDetailed
        else
                Result := type_LowDetailed;
end;

function TAnim2.SaveFirstPicture( Id : Integer ) : TBitmap32;
var     RealId : integer;
        IndexRecord : TIndexRecord;
        Palette : PWordArray;
        LookupList : PIntegerArray;
        Pixels : PByteArray;
        FrameCount, Start : Integer;
        CenterX, CenterY : smallint;
        Breite, Hoehe : word;
        xRun, xOffset, yOffset : integer;
        Header : longWord;
        x, y, k : integer;
begin
        Result := TBitmap32.Create;
        RealId := GetRealId( Id );

        idxStream.Seek( RealId*12, soFromBeginning );
        idxStream.Read( IndexRecord, sizeof( TIndexRecord ) );

        mulStream.Seek( IndexRecord.Offset, soFromBeginning );

        GetMem( Palette, 512 );
        mulStream.Read( Palette^, 512 );
        Start := mulStream.Position;
        mulStream.Read( FrameCount, 4 );
        GetMem( LookupList, FrameCount*4 );
        mulStream.Read( LookupList^, FrameCount*4 );

        mulStream.Seek( Start + LookupList^[0], soFromBeginning );
        mulStream.Read( CenterX, 2 );
        mulStream.Read( CenterY, 2 );
        mulStream.Read( Breite, 2 );
        mulStream.Read( Hoehe, 2 );

        Result.Width := Breite;
        Result.Height := Hoehe;

        mulStream.Read( Header, 4 );

        while ( Header <> $7FFF7FFF ) do begin
                xRun := Header and $FFF;
                xOffset := ( Header shr 22 ) and 1023;
                yOffset := ( Header shr 12 ) and 1023;

                if ( xOffset and 512 ) = 512 then
                        xOffset := xOffset or ( $FFFFFFFF - 511 );
                if ( yOffset and 512 ) = 512 then
                        yOffset := yOffset or ( $FFFFFFFF - 511 );

                x := xOffset + CenterX;
                y := yOffset + CenterY + Hoehe;

                GetMem( Pixels, xRun );
                mulStream.Read( Pixels^, xRun );

                for k := 0 to xRun-1 do begin
                        if ( x < Result.Width ) and ( y < Result.Height ) and ( x >= 0 ) and ( y >= 0 ) then
                                Result.Pixel[ x, y ] := Color15to32( Palette^[ Pixels^[ k ] ] )
                        else begin
                                showmessage( 'BugPixel: ' + inttostr( Breite ) + ':' + inttostr( Hoehe ) + ',' + inttostr( CenterX ) + ':' + inttostr( CenterY ) + ',' + inttostr( xRun ) + ':' + inttostr( xOffset ) + ':' + inttostr( yOffset ) );
                        end;
                        inc( x );
                end;
                FreeMem( Pixels, xRun );
                mulStream.Read( Header, 4 );
        end;

        FreeMem( Palette, 512 );
        FreeMem( LookupList, FrameCount*4 );
end;

function TAnim2.AnimExists( Id : Integer ) : Boolean;
var     RealId : integer;
        IndexRecord : TIndexRecord;
begin
        if not Loaded then begin
                Result := False;
                exit;
        end;

        RealId := GetRealId( Id );
        idxStream.Seek( RealId*12, soFromBeginning );
        idxStream.Read( IndexRecord, sizeof( TIndexRecord ) );

        if ( IndexRecord.Offset = $FFFFFFFF ) or ( IndexRecord.Offset = -1 ) or ( IndexRecord.Length < 1 ) then begin
                Result := False;
                exit;
        end;

        Result := true;
end;

procedure TAnim2.SaveAnimToFile( Id : Integer; Filename : String );
var     RealId : integer;
        IndexRecord : TIndexRecord;
        Anim, AnimationCount : integer;
        TypFile, TypAnim : word;
        fStream : TFileStream;
        RecordList : array[0..255] of TIndexRecord;
        buf : PByteArray;
begin
        RealId := GetRealId( Id );

        fStream := TFileStream.Create( Filename, fmCreate );
        fStream.Seek( 0, soFromBeginning );
        GaugeForm.Activate;

        TypFile := typ_anim;
        TypAnim := GetAnimType( Id );
        AnimationCount := AnimCount( Id );
        fStream.Write( TypFile, 2 );
        fStream.Write( TypAnim, 2 );

        fStream.Write( RecordList, AnimationCount*12 );

        for Anim := 0 to AnimationCount-1 do begin
                GaugeForm.SetBalken( trunc( (Anim+1)*100 / AnimationCount ) );
                idxStream.Seek( (RealId+Anim)*12, soFromBeginning );
                idxStream.Read( IndexRecord, 12 );

                if ( IndexRecord.Offset <> $FFFFFFFF ) and ( IndexRecord.Offset <> -1 ) and ( IndexRecord.Length > 0 ) then begin
                        RecordList[Anim].Offset := fStream.Position;
                        RecordList[Anim].Length := IndexRecord.Length;
                        RecordList[Anim].Extra := IndexRecord.Extra;

                        mulStream.Seek( IndexRecord.Offset, soFromBeginning );
                        GetMem( buf, IndexRecord.Length );
                        mulStream.Read( buf^, IndexRecord.Length );
                        fStream.Write( buf^, IndexRecord.Length );
                        FreeMem( buf, IndexRecord.Length );
                end
                else begin
                        RecordList[Anim].Offset := IndexRecord.Offset;
                        RecordList[Anim].Length := IndexRecord.Length;
                        RecordList[Anim].Extra := IndexRecord.Extra;
                end;
        end;

        fStream.Seek( 4, soFromBeginning );
        fStream.Write( RecordList, AnimationCount*12 );
        fStream.Free;
        GaugeForm.Deactivate;
end;

procedure TAnim2.LoadAnimFromFile( Id : Integer; Filename : String );
var     RealId : integer;
        IndexRecord : TIndexRecord;
        Anim, AnimationCount : integer;
        TypFile, TypAnim : word;
        fStream : TFileStream;
        RecordList : array[0..255] of TIndexRecord;
        buf : PByteArray;
begin
        RealId := GetRealId( Id );

        fStream := TFileStream.Create( Filename, fmOpenRead );
        fStream.Seek( 0, soFromBeginning );
        GaugeForm.Activate;

        fStream.Read( TypFile, 2 );
        if TypFile <> typ_anim then begin
                showmessage( 'Not an Anim File.' );
                fStream.Free;
                exit;
        end;

        fStream.Read( TypAnim, 2 );
        if TypAnim <> GetAnimType( Id ) then begin
                showmessage( 'Wrong Anim Id ( Type ).' );
                fStream.Free;
                exit;
        end;

        AnimationCount := AnimCount( Id );

        fStream.Read( RecordList, AnimationCount*12 );

        for Anim := 0 to AnimationCount-1 do begin
                GaugeForm.SetBalken( trunc( (Anim+1)*100 / AnimationCount ) );
                idxStream.Seek( (RealId+Anim)*12, soFromBeginning );

                if ( RecordList[Anim].Offset <> $FFFFFFFF ) and ( RecordList[Anim].Offset <> -1 ) and ( RecordList[Anim].Length > 0 ) then begin
                        mulStream.Seek( 0, soFromEnd );
                        IndexRecord.Offset := mulStream.Position;
                        IndexRecord.Length := RecordList[Anim].Length;
                        IndexRecord.Extra := RecordList[Anim].Extra;

                        GetMem( buf, IndexRecord.Length );
                        fStream.Seek( RecordList[Anim].Offset, soFromBeginning );
                        fStream.Read( buf^, IndexRecord.Length );
                        mulStream.Write( buf^, IndexRecord.Length );
                        FreeMem( buf, IndexRecord.Length );
                end
                else begin
                        IndexRecord.Offset := RecordList[Anim].Offset;
                        IndexRecord.Length := RecordList[Anim].Length;
                        IndexRecord.Extra := RecordList[Anim].Extra;
                end;
                idxStream.Write( IndexRecord, 12 );
        end;

        fStream.Free;
        GaugeForm.Deactivate;
end;

procedure TAnim2.SaveAnimToDirectory( Id : Integer; dirPath : String );
var     RealId : integer;
        IndexRecord : TIndexRecord;
        Palette : PWordArray;
        LookupList : PIntegerArray;
        Pixels : PByteArray;
        FrameCount, Start, Frame, Anim : Integer;
        CenterX, CenterY : smallint;
        Breite, Hoehe : word;
        xRun, xOffset, yOffset : integer;
        Header : longWord;
        x, y, k, l : integer;
        Bitmap : TBitmap32;
        Liste, ColorList : TStringList;
begin
        RealId := GetRealId( Id );
        Liste := TStringList.Create;
        GaugeForm.Activate;
        for Anim := 0 to AnimCount( Id )-1 do begin
                GaugeForm.SetBalken( trunc( (Anim+1)*100 / AnimCount( Id ) ) );
                idxStream.Seek( ( RealId + Anim )*12, soFromBeginning );
                idxStream.Read( IndexRecord, sizeof( TIndexRecord ) );

                if ( IndexRecord.Offset <> $FFFFFFFF ) and ( IndexRecord.Offset <> -1 ) and ( IndexRecord.Length > 0 ) then begin
                        mulStream.Seek( IndexRecord.Offset, soFromBeginning );

                        GetMem( Palette, 512 );
                        mulStream.Read( Palette^, 512 );
                        Start := mulStream.Position;

                        ColorList := TStringList.Create;
                        for l := 0 to 255 do begin
                                ColorList.Add( inttostr( Palette^[l] ) );
                        end;
                        ColorList.SaveToFile( dirPath + '\palette_' + inttostr( Anim ) + '.txt' );
                        ColorList.Free;

                        mulStream.Read( FrameCount, 4 );
                        GetMem( LookupList, FrameCount*4 );
                        mulStream.Read( LookupList^, FrameCount*4 );

                        for Frame := 0 to FrameCount-1 do begin
                                Bitmap := TBitmap32.Create;
                                mulStream.Seek( Start + LookupList^[ Frame ], soFromBeginning );
                                mulStream.Read( CenterX, 2 );
                                mulStream.Read( CenterY, 2 );
                                mulStream.Read( Breite, 2 );
                                mulStream.Read( Hoehe, 2 );

                                Bitmap.Width := Breite;
                                Bitmap.Height := Hoehe;
                                Bitmap.Clear( TColor32( clWhite ) );

                                Liste.Add( inttostr( Anim ) + ':' + inttostr( Frame ) );
                                Liste.Add( 'CenterX:' + inttostr( CenterX ) );
                                Liste.Add( 'CenterY:' + inttostr( CenterY ) );

                                mulStream.Read( Header, 4 );

                                while ( Header <> $7FFF7FFF ) do begin
                                        xRun := Header and $FFF;
                                        xOffset := ( Header shr 22 ) and 1023;
                                        yOffset := ( Header shr 12 ) and 1023;

                                        if ( xOffset and 512 ) = 512 then
                                                xOffset := xOffset or ( $FFFFFFFF - 511 );
                                        if ( yOffset and 512 ) = 512 then
                                                yOffset := yOffset or ( $FFFFFFFF - 511 );

                                        x := xOffset + CenterX;
                                        y := yOffset + CenterY + Hoehe;

                                        GetMem( Pixels, xRun );
                                        mulStream.Read( Pixels^, xRun );

                                        if (Breite > 0) and (Hoehe > 0) then begin
                                                for k := 0 to xRun-1 do begin
                                                        if ( x < Bitmap.Width ) and ( y < Bitmap.Height ) and ( x >= 0 ) and ( y >= 0 ) then
                                                                Bitmap.Pixel[ x, y ] := Color15to32( Palette^[ Pixels^[ k ] ] )
                                                        else begin
                                                                showmessage( 'BugPixel: ' + inttostr( Breite ) + ':' + inttostr( Hoehe ) + ',' + inttostr( CenterX ) + ':' + inttostr( CenterY ) + ',' + inttostr( xRun ) + ':' + inttostr( xOffset ) + ':' + inttostr( yOffset ) );
                                                        end;
                                                        inc( x );
                                                end;
                                        end;
                                        FreeMem( Pixels, xRun );
                                        mulStream.Read( Header, 4 );
                                end;
                                Bitmap.SaveToFile( dirPath + '\' + inttostr( Anim ) + '_' + inttostr( Frame ) + '.bmp' );
                                Bitmap.Free;
                        end;

                        FreeMem( Palette, 512 );
                        FreeMem( LookupList, FrameCount*4 );
                end;
        end;
        GaugeForm.Deactivate;
        Liste.SaveToFile( dirPath + '\liste.txt' );
        Liste.Free;
end;

procedure TAnim2.LoadAnimFromDirectory( Id : Integer; dirPath : String; UseColor : boolean );
var     RealId : integer;
        IndexRecord : TIndexRecord;
        Palette : PWordArray;
        LookupList : PIntegerArray;
        Pixels, buf : PByteArray;
        FrameCount, Frame, Anim : Integer;
        CenterX, CenterY : smallint;
        Breite, Hoehe : word;
        xRun, xOffset, yOffset : integer;
        Header : longWord;
        x, y, k, l, p : integer;
        Bitmap : TBitmap;
        Liste, SubList, ColorList : TStringList;
        AnimNr, FrameNr : integer;
        CenterList : array[0..174, 0..15] of TCenter;
        Data : TMemoryStream;
        Color32, Color15 : integer;
        Color : Byte;
        FarbListe : array[0..1023] of byte;
        gefunden : boolean;
begin
        Liste := TStringList.Create;
        Liste.LoadFromFile( dirPath + '\liste.txt' );

        for l := 0 to ( Liste.Count div 3 )-1 do begin
                SubList := TStringList.Create;
                StrLib.Split( Liste.Strings[l*3], ':', SubList );
                AnimNr := strtoint( SubList[0] );
                FrameNr := strtoint( SubList[1] );
                SubList.Clear;
                StrLib.Split( Liste.Strings[l*3+1], ':', SubList );
                CenterList[ AnimNr ][ FrameNr ].CenterX := strtoint( SubList[1] );
                SubList.Clear;                
                StrLib.Split( Liste.Strings[l*3+2], ':', SubList );
                CenterList[ AnimNr ][ FrameNr ].CenterY := strtoint( SubList[1] );
                SubList.Free;
        end;
        Liste.Free;

        RealId := GetRealId( Id );
        mulStream.Seek( 0, soFromEnd );
        GaugeForm.Activate;
        for Anim := 0 to AnimCount( Id )-1 do begin
                GaugeForm.SetBalken( trunc( (Anim+1)*100 / AnimCount( Id )  ) );
                GetMem( Palette, 512 );
                if UseColor then begin
                        if FileExists( dirPath + '\palette_' + inttostr( Anim ) + '.txt' ) then begin
                                ColorList := TStringList.Create;
                                ColorList.LoadFromFile( dirPath + '\palette_' + inttostr( Anim ) + '.txt' );
                                for l := 0 to 255 do begin
                                        Palette^[l] := strtoint( ColorList.Strings[l] );
                                end;
                                p := 256;
                                ColorList.Free;
                        end
                        else begin
                                UseColor := false;
                        end;
                end;

                if not UseColor then begin
                        Palette^[0] := 0;
                        p := 1;
                end;

                FrameCount := 0;
                while true do begin
                        if not FileExists( dirPath + '\' + inttostr( Anim ) + '_' + inttostr( FrameCount ) + '.bmp' ) then begin
                                break;
                        end
                        else
                                inc( FrameCount );
                end;

                GetMem( LookupList, FrameCount*4 );

                Data := TMemoryStream.Create;
                Data.Seek( 0, soFromBeginning );

                if FrameCount = 0 then begin
                        IndexRecord.Offset := -1;
                        IndexRecord.Length := -1;
                        IndexRecord.Extra := -1;
                end
                else begin
                        for Frame := 0 to FrameCount-1 do begin
                                Bitmap := TBitmap.Create;
                                Bitmap.LoadFromFile( dirPath + '\' + inttostr( Anim ) + '_' + inttostr( Frame ) + '.bmp' );

                                LookupList^[ Frame ] := data.Position + FrameCount*4 + 4;

                                Breite := Bitmap.Width;
                                Hoehe := Bitmap.Height;

                                data.Write( CenterList[ Anim ][ Frame ].CenterX, 2 );
                                data.Write( CenterList[ Anim ][ Frame ].CenterY, 2 );
                                data.Write( Breite, 2 );
                                data.Write( Hoehe, 2 );

                                xRun := 0;
                                y := 0;
                                x := 0;

                                while y < Hoehe do begin
                                        Color32 := Bitmap.Canvas.Pixels[ x, y ];
                                        Color15 := ((Color32 and $F80000) shr 19) or ((Color32 and $F800) shr 6) or ((Color32 and $F8) shl 7);

                                        //showmessage( inttostr( Color15 ) );
                                        if Color15 = 0 then begin
                                                if xRun <> 0 then begin
                                                        xOffset := x - xRun - CenterList[ Anim ][ Frame ].CenterX;
                                                        yOffset := y - CenterList[ Anim ][ Frame ].CenterY - Hoehe;

                                                        //showmessage( inttostr( Header ) +':'+ inttostr( xRun ) + ':' + inttostr( xOffset ) + ':' + inttostr( yOffset ) + ':'+ inttostr( x ) +':'+ inttostr( y ) +':'+ inttostr( Hoehe ) );                                                        
                                                        if xOffset < 0 then
                                                                xOffset := xOffset + 1024;
                                                        if yOffset < 0 then
                                                                yOffset := yOffset + 1024;

                                                        Header := xRun or ( yOffset shl 12 ) or ( xOffset shl 22 );

                                                        data.Write( Header, 4 );
                                                        data.Write( FarbListe, xRun );

                                                        xRun := 0;
                                                end;
                                        end
                                        else begin
                                                gefunden := false;

                                                for k := 0 to p-1 do
                                                        if Palette^[k] = Color15 then begin
                                                                gefunden := true;
                                                                Color := k;
                                                                break;
                                                        end;

                                                if not gefunden then begin
                                                        if p = 256 then begin
                                                                showmessage( 'Farbe Zuviel: ' + inttostr( Anim ) + ':' + inttostr( Frame ) + ':' + inttostr( Color15 ) );
                                                                Color := 0;
                                                        end
                                                        else begin
                                                                Palette^[p] := Color15;
                                                                Color := p;
                                                                inc( p );
                                                        end;
                                                end;

                                                FarbListe[ xRun ] := Color;
                                                inc( xRun );
                                        end;

                                        if x = Breite-1 then begin
                                                if xRun <> 0 then begin
                                                        xOffset := x - xRun - CenterList[ Anim ][ Frame ].CenterX +1;
                                                        //+1, da normalerweise nach dem letzten pixel das inc(x) nochmal durchgelaufen ist
                                                        //hier aber nicht, deshalb kompensieren
                                                        yOffset := y - CenterList[ Anim ][ Frame ].CenterY - Hoehe;

                                                        if xOffset < 0 then
                                                                xOffset := xOffset + 1024;
                                                        if yOffset < 0 then
                                                                yOffset := yOffset + 1024;

                                                        Header := xRun or ( yOffset shl 12 ) or ( xOffset shl 22 );

                                                        data.Write( Header, 4 );
                                                        data.Write( FarbListe, xRun );

                                                        xRun := 0;
                                                end;
                                                inc( y );
                                                x := 0;
                                        end
                                        else begin
                                                inc( x );
                                        end;
                                end;

                                Bitmap.Free;
                                Header := $7FFF7FFF;
                                Data.Write( Header, 4 );
                        end;
                end;

                if Data.Size > 0 then begin
                        mulStream.Seek( 0, soFromEnd );

                        IndexRecord.Offset := mulStream.Position;
                        mulStream.Write( Palette^, 512 );
                        mulStream.Write( FrameCount, 4 );
                        mulStream.Write( LookupList^, FrameCount*4 );

                        data.Seek( 0, soFromBeginning );

                        GetMem( buf, data.Size );
                        data.Read( buf^, data.Size );
                        mulStream.Write( buf^, data.Size );
                        FreeMem( buf, data.Size );

                        IndexRecord.Length := mulStream.Position - IndexRecord.Offset;
                        IndexRecord.Extra := 0;
                end;

                idxStream.Seek( ( RealId + Anim )*12, soFromBeginning );
                idxStream.Write( IndexRecord, 12 );

                FreeMem( LookupList, FrameCount*4 );
                FreeMem( Palette, 512 );
                data.Free;
        end;
        GaugeForm.Deactivate;
end;

procedure TAnim2.DeleteAnim( Id : Integer );
var     Anim, RealId : Integer;
        IndexRecord : TIndexRecord;
begin
        RealId := GetRealId( Id );

        for Anim := 0 to AnimCount( Id )-1 do begin
                idxStream.Seek( (RealId+Anim)*12, soFromBeginning );
                IndexRecord.Offset := -1;
                IndexRecord.Length := -1;
                IndexRecord.Extra := -1;
                idxStream.Write( IndexRecord, sizeof( TIndexRecord ) );
        end;
end;

end.
