unit mainF;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls, ComCtrls, Menus, Buttons,

  data;


type


  TAnimationAction = class (TCollectionItem)
  public
    name : string;
    offset : integer;
    horseOffset : integer;
  end;


  TAnimation = class (TCollectionItem)
  public
    name : string;
    offset : integer;
    femaleOffset : integer;
  end;


  TAnimationGroup = class (TCollectionItem)
  public
    name : string;
    huesFile : string;
    animations : TCollection;
    combo : TComboBox;
    colorCombo : TComboBox;
    color : TColor;
    colorList : TStringList;

    constructor create (Collection: TCollection); override;
    destructor destroy; override;
  end;


  TMainForm = class(TForm)
    timerAnim: TTimer;
    pnlAnimationGroups: TPanel;
    ScrollBox: TScrollBox;
    StatusBar1: TStatusBar;
    panel4: TPanel;
    Panel3: TPanel;
    cmbDirection: TComboBox;
    cmbAction: TComboBox;
    MainMenu1: TMainMenu;
    miFile: TMenuItem;
    miHelp: TMenuItem;
    miFileExit: TMenuItem;
    miHelpAbout: TMenuItem;
    chkAnimate: TCheckBox;
    cmbScale: TComboBox;
    pnlAnim: TPanel;
    imgAnim: TImage;
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    miFileSave: TMenuItem;
    N1: TMenuItem;
    miFileLoad: TMenuItem;
    OpenDialog: TOpenDialog;
    ColorDialog: TColorDialog;
    Panel1: TPanel;
    imgIcon: TImage;
    Label4: TLabel;
    btnBackground: TButton;
    miFileExport: TMenuItem;
    SaveDialog: TSaveDialog;
    SaveDialogExport: TSaveDialog;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure ComboBoxChange(Sender: TObject);
    procedure cmbActionChange(Sender: TObject);
    procedure timerAnimTimer(Sender: TObject);
    procedure chkAnimateClick(Sender: TObject);
    procedure miFileLoadClick(Sender: TObject);
    procedure miFileSaveClick(Sender: TObject);
    procedure miFileExitClick(Sender: TObject);
    procedure miHelpAboutClick(Sender: TObject);
    procedure colorComboBoxChange(Sender: TObject);
    procedure colorComboDrawItem(Control: TWinControl; Index: Integer;
      Rect: TRect; State: TOwnerDrawState);
    procedure btnBackgroundClick(Sender: TObject);
    procedure miFileExportClick(Sender: TObject);
  private
  public
    uodata : TUOData;

    backgroundColor : TColor;
    animationGroups : TCollection;
    animationActions : TCollection;

    frameIndex : integer;

    ready : boolean;

    procedure load;
    procedure init;
    procedure draw;
  end;

var
  MainForm: TMainForm;

implementation

uses AboutD;

{$R *.DFM}


constructor TAnimationGroup.create (Collection: TCollection);
begin
  inherited;

  animations := TCollection.create (TAnimation);
  colorList := TStringList.create;
end;


destructor TAnimationGroup.destroy;
begin
  animations.free;
  colorList.free;

  inherited;
end;


procedure TMainForm.FormCreate(Sender: TObject);
begin
  imgAnim.Picture.bitmap.pixelFormat := pf24bit;

  uodata := TUOData.create;

  animationGroups := TCollection.create (TAnimationGroup);
  animationActions := TCollection.create (TAnimationAction);

  load;
  init;

  ready := true;

  draw;

  ScrollBox.autoScroll := true;
end;


procedure TMainForm.FormDestroy(Sender: TObject);
begin
  animationGroups.free;
  animationActions.free;
  uodata.free;
end;


procedure TMainForm.ComboBoxChange(Sender: TObject);
begin
  draw;
end;


procedure TMainForm.load;
var
  sl, sl0, sl1 : TStringList;
  s : string;
  animation : TAnimation;
  animationGroup : TAnimationGroup;
  animationAction : TAnimationAction;
  i, j : integer;
  mode : integer;
  zeroFound : boolean;
  c : TColor;
begin
  i := 0;
  sl := nil;
  sl0 := nil;
  sl1 := nil;
  mode := -1;
  animationGroup := nil;
  animation := nil;
  try
    sl := TStringList.create;
    sl0 := TStringList.create;
    sl1 := TStringList.create;

    sl.loadFromFile ('uocview.dat');

    while (i < sl.count) do
    begin
      s := trim (sl [i]);
      sl1.add (s);

      if copy (s, 1, 1) = '+' then
        inc (mode)
      else if (mode = 0) and (length (s) > 0) then
      begin
        sl0.CommaText := s;

        animationAction := TAnimationAction (animationActions.add);
        animationAction.name := sl0 [0];
        animationAction.offSet := strToInt (sl0 [1]);
        animationAction.horseOffSet := strToInt (sl0 [2]);

        //sl1 [sl1.count - 1] := '"' + sl0 [0] + '",' + sl0 [1] + ',-1';
      end
      else if (mode = 1) and (copy (s, 1, 1) = ';') then
      begin
        sl0.commaText := trim (copy (s, 2, length (s) - 1));

        animationGroup := TAnimationGroup (animationGroups.add);
        animationGroup.name := sl0 [0];
        animationGroup.huesFile := sl0 [1];

        //sl1 [sl1.count - 1] := ';"' + animationGroup.name + '",""';
      end
      else if (mode = 1) and (length (s) > 0) then
      begin
        sl0.CommaText := s;

        animation := TAnimation (animationGroup.animations.add);
        animation.name := sl0 [0];
        animation.offSet := strToInt (sl0 [1]);
        animation.femaleOffSet := strToInt (sl0 [2]);
      end;

      inc (i);
    end;

    // sl1.saveToFile ('uocview1.dat');

    for i := 0 to animationGroups.count - 1 do
    begin
      animationGroup := TAnimationGroup (animationGroups.items [i]);

      if (animationGroup.huesFile <> '') and fileExists (animationGroup.huesFile) then
      begin
        // load colors from a hues file

        sl.loadFromFile (animationGroup.huesFile);
        j := 0;
        zeroFound := true;
        while j < sl.count do
        begin
          s := trim (sl [j]);
          sl0.commaText := copy (s, 2, length (s) - 2);

          if sl0.count >= 3 then
          begin
            c :=
             strToInt (sl0 [0]) * $1 +
             strToInt (sl0 [1]) * $100 +
             strToInt (sl0 [2]) * $10000;

            if (c > 0) and zeroFound then
            begin
              animationGroup.colorList.add (intToStr (c));
              zeroFound := false;
            end
            else if c = 0 then
              zeroFound := true;
          end;

          j := j + 1;
        end;

      end
      else
      begin
        // set it up with some simple colors

        animationGroup.colorList.add (intToStr (clAqua));
        animationGroup.colorList.add (intToStr (clBlack + 1));
        animationGroup.colorList.add (intToStr (clBlue));
        animationGroup.colorList.add (intToStr (clDkGray));
        animationGroup.colorList.add (intToStr (clFuchsia));
        animationGroup.colorList.add (intToStr (clGray));
        animationGroup.colorList.add (intToStr (clGreen));
        animationGroup.colorList.add (intToStr (clLime));
        animationGroup.colorList.add (intToStr (clLtGray));
        animationGroup.colorList.add (intToStr (clMaroon));
        animationGroup.colorList.add (intToStr (clNavy));
        animationGroup.colorList.add (intToStr (clOlive));
        animationGroup.colorList.add (intToStr (clPurple));
        animationGroup.colorList.add (intToStr (clRed));
        animationGroup.colorList.add (intToStr (clSilver));
        animationGroup.colorList.add (intToStr (clTeal));
        animationGroup.colorList.add (intToStr (clWhite));
        animationGroup.colorList.add (intToStr (clYellow));
      end;
    end;


  finally
    sl.free;
    sl0.free;
    sl1.free;
  end;
end;


procedure TMainForm.init;
var
  i, j : integer;
  animation : TAnimation;
  animationGroup : TAnimationGroup;
  animationAction : TAnimationAction;
  combo : TComboBox;
  lbl : TLabel;
  originalHeight : integer;
begin
  originalHeight := height;
  combo := nil;

  for i := 0 to animationGroups.count - 1 do
  begin
    animationGroup := TAnimationGroup (animationGroups.items [i]);

    lbl := TLabel.create (ScrollBox);
    lbl.parent := ScrollBox;
    lbl.left := 5;
    lbl.top := 5 + i * 22 + 5;
    lbl.width := 90;
    lbl.height := 18;
    lbl.caption := animationGroup.name;

    combo := TComboBox.create (ScrollBox);
    animationGroup.combo := combo;
    combo.parent := ScrollBox;
    combo.left := 80;
    combo.top := 5 + i * 22;
    combo.width := 150;
    combo.height := 18;
    combo.style := csDropDownList;

    for j := 0 to animationGroup.animations.count - 1 do
    begin
      animation := TAnimation (animationGroup.animations.items [j]);
      combo.items.add (animation.name);
    end;
    if i = 0 then
      combo.itemIndex := 0
    else
    begin
      combo.items.add ('none');
      combo.itemIndex := combo.items.count - 1;
    end;
    combo.onChange := ComboBoxChange;

    combo := TComboBox.create (ScrollBox);
    animationGroup.colorCombo := combo;
    combo.tag := i;
    combo.parent := ScrollBox;
    combo.left := 80 + 150 + 5;
    combo.top := 5 + i * 22;
    combo.width := 50;
    combo.height := 18;
    //combo.style := csDropDownList;
    combo.style := csOwnerDrawFixed;
    combo.items.add ('');
    for j := 0 to animationGroup.colorList.count - 1 do
      combo.items.add (animationGroup.colorList [j]);
    combo.items.add ('...');
    combo.itemIndex := 0;
    combo.onChange := colorComboBoxChange;
    combo.onDrawItem := colorComboDrawItem;
  end;

  for i := 0 to animationActions.count - 1 do
  begin
    animationAction := TAnimationAction (animationActions.items [i]);
    cmbAction.items.add (animationAction.name);
  end;

  clientHeight := combo.top + combo.height + 10;

  cmbScale.itemIndex := 1;
  cmbDirection.itemIndex := 1;
  cmbAction.itemIndex := 0;

  height := originalHeight;
end;


procedure TMainForm.draw;
var
  m : TMemoryStream;
  animation : TAnimation;
  animationGroup : TAnimationGroup;
  animationAction : TAnimationAction;
  combo : TComboBox;
  i : integer;
  bitmap : TBitmap;
  dir : integer;
  flip : boolean;
  offset : longint;
  animFrame : integer;
  bWidth, bHeight : integer;
begin
  if not ready then
    exit;

  pnlAnim.color := backgroundColor;

  m := TMemoryStream.create;

  bitmap := TBitmap.create;
  bitmap.canvas.brush.color := backgroundColor;
  bitmap.width := 128;
  bitmap.height := 128;
  bitmap.pixelFormat := pf24bit;

  if cmbDirection.itemIndex <= 4 then
  begin
    dir := cmbDirection.itemIndex;
    flip := false;
  end
  else
  begin
    dir := 4 - (cmbDirection.itemIndex - 4);
    flip := true;
  end;

  animationAction := TAnimationAction (animationActions.items [cmbAction.itemIndex]);

  for i := 0 to animationGroups.count - 1 do
  begin
    animationGroup := TAnimationGroup (animationGroups.items [i]);
    combo := animationGroup.combo;

    if (i = 0) and (animationAction.horseOffset <> -1) then
    begin
      offset := 23690
              + animationAction.horseOffset
              + dir;
      //uodata.loadAnimationFromFile (m, offset);
      uodata.loadBlobFromFile (m, uodata.animResource, offset);
      animFrame := frameIndex mod PAnimationHeader (m.memory).frameCount;
      convertAnimationFrameToBitmap (m, bitmap, 64, 96, flip, 0, animFrame);
    end;

    if (combo.itemIndex <> -1) and (combo.items [combo.itemIndex] <> 'none') then
    begin
      animation := TAnimation (animationGroup.animations.items [combo.itemIndex]);
      offset := animation.offset + animationAction.offset + dir;
      //uodata.loadAnimationFromFile (m, offset);
      uodata.loadBlobFromFile (m, uodata.animResource, offset);
      animFrame := frameIndex mod PAnimationHeader (m.memory).frameCount;
      convertAnimationFrameToBitmap (m, bitmap, 64, 96, flip, animationGroup.color, animFrame);
    end;
  end;

  if cmbScale.itemIndex = 1 then
  begin
    bWidth := bitmap.width * 2;
    bHeight := bitmap.height * 2;
  end
  else if cmbScale.itemIndex = 2 then
  begin
    bWidth := bitmap.width * 4;
    bHeight := bitmap.height * 4;
  end
  else
  begin
    bWidth := bitmap.width * 1;
    bHeight := bitmap.height * 1;
  end;

  imgAnim.Picture.bitmap.height := 1;
  imgAnim.Picture.bitmap.canvas.brush.color := backgroundColor;
  imgAnim.Picture.bitmap.width := imgAnim.width;
  imgAnim.Picture.bitmap.height := imgAnim.height;
  imgAnim.Picture.bitmap.canvas.stretchDraw (
   rect (imgAnim.width div 2 - bWidth div 2,
         imgAnim.height div 2 - bHeight div 2,
         imgAnim.width div 2 - bWidth div 2 + bWidth - 1,
         imgAnim.height div 2 - bHeight div 2 + bHeight - 1), bitmap);
  //imgAnim.picture.bitmap.assign (bitmap);
  imgAnim.refresh;

  bitmap.free;
  m.free;
end;


procedure TMainForm.cmbActionChange(Sender: TObject);
begin
  draw;
end;


procedure TMainForm.timerAnimTimer(Sender: TObject);
begin
  inc (frameIndex);
  draw;
end;


procedure TMainForm.chkAnimateClick(Sender: TObject);
begin
  timerAnim.enabled := chkAnimate.checked;

  if not chkAnimate.checked then
    frameIndex := 0;

  draw;
end;


procedure TMainForm.miFileLoadClick(Sender: TObject);
var
  i, j, k, q : integer;
  sl, sl0 : TStringList;
  animationGroup : TAnimationGroup;
  animation : TAnimation;
  found : boolean;
begin
  openDialog.filename := '';
  if not openDialog.execute then
    exit;

  sl := nil;
  sl0 := nil;
  try
    ready := false;

    sl := TStringList.create;
    sl0 := TStringList.create;

    sl.loadFromFile (openDialog.filename);

    for i := 0 to animationGroups.count - 1 do
    begin
      animationGroup := TAnimationGroup (animationGroups.items [i]);
      if i = 0 then
        animationGroup.combo.itemIndex := 0
      else
        animationGroup.combo.itemIndex := animationGroup.combo.items.count - 1;
      animationGroup.colorCombo.itemIndex := 0;
    end;

    for i := 0 to sl.count - 1 do
    begin
      sl0.commaText := sl [i];

      found := false;
      for j := 0 to animationGroups.count - 1 do
      begin
        animationGroup := TAnimationGroup (animationGroups.items [j]);

        for k := 0 to animationGroup.animations.count - 1 do
        begin
          animation := TAnimation (animationGroup.animations.items [k]);

          if not found and (animationGroup.name = sl0 [0]) and (animation.name = sl0 [1]) then
          begin
            animationGroup.combo.itemIndex := k;
            animationGroup.color := strToInt (sl0 [2]);
            animationGroup.colorCombo.ItemIndex := 0;
            for q := 0 to animationGroup.colorList.count - 1 do
              if animationGroup.color = strToInt (animationGroup.colorList [q]) then
                animationGroup.colorCombo.ItemIndex := q + 1;
            if (animationGroup.color <> 0) and (animationGroup.colorCombo.ItemIndex = 0) then
              animationGroup.colorCombo.ItemIndex := animationGroup.colorCombo.items.count - 1;
            found := true;
          end;
        end;
      end;
   end;

   ready := true;
   draw;

  finally
    sl.free;
    sl0.free;

    ready := true;
  end;
end;


procedure TMainForm.miFileSaveClick(Sender: TObject);
var
  i : integer;
  sl, sl0 : TStringList;
  animationGroup : TAnimationGroup;
  animation : TAnimation;
  combo : TComboBox;
begin
  saveDialog.filename := '';
  if not saveDialog.execute then
    exit;

  sl := nil;
  sl0 := nil;
  try
    sl := TStringList.create;
    sl0 := TStringList.create;

    for i := 0 to animationGroups.count - 1 do
    begin
      animationGroup := TAnimationGroup (animationGroups.items [i]);
      combo := animationGroup.combo;
      if (combo.itemIndex <> -1) and (combo.items [combo.itemIndex] <> 'none') then
      begin
        animation := TAnimation (animationGroup.animations.items [combo.itemIndex]);

        sl0.clear;
        sl0.add (animationGroup.name);
        sl0.add (animation.name);
        sl0.add (intToStr (animationGroup.color));
        sl.add (sl0.commaText);
      end;
    end;

    sl.saveToFile (saveDialog.filename);
  finally
    sl.free;
    sl0.free;
  end;

end;

procedure TMainForm.miFileExitClick(Sender: TObject);
begin
  close;
end;


procedure TMainForm.miHelpAboutClick(Sender: TObject);
begin
  aboutDlg.showModal;
end;


procedure TMainForm.colorComboBoxChange(Sender: TObject);
var
  animationGroup : TAnimationGroup;
  combo : TComboBox;
begin
  animationGroup := TAnimationGroup (animationGroups.items [(sender as TComboBox).tag]);
  combo := animationGroup.colorCombo;

  if combo.itemIndex = 0 then
  begin
    animationGroup.color := 0;
    draw;
  end
  else if combo.itemIndex = (combo.items.count - 1) then
  begin
    ColorDialog.color := animationGroup.color;
    if not colorDialog.execute then
      exit;

    animationGroup.color := colorDialog.color;
    draw;
  end
  else
  begin
    animationGroup.color := strToInt (animationGroup.colorList [combo.itemIndex - 1]);;
    draw;
  end;
end;


procedure TMainForm.colorComboDrawItem(Control: TWinControl; Index: Integer;
  Rect: TRect; State: TOwnerDrawState);
var
  animationGroup : TAnimationGroup;
  combo : TComboBox;
  c : TColor;
begin
  animationGroup := TAnimationGroup (animationGroups.items [(Control as TComboBox).tag]);
  combo := animationGroup.colorCombo;

  if (index = 0) or (index = (combo.items.count - 1)) then
  begin
    if index = 0 then
      c := clWindow
    else
      c := animationGroup.color;
    combo.canvas.pen.color := c;
    combo.canvas.pen.width := 2;
    combo.canvas.rectangle (rect.left, rect.top, rect.right, rect.bottom);

    combo.canvas.pen.color := clCaptionText;
    combo.canvas.brush.style := bsClear;
    if index = 0 then
      combo.canvas.textout (rect.left + 1, rect.top + 1, 'none')
    else
      combo.canvas.textout (rect.left + 1, rect.top + 1, 'custom');
  end
  else if ((index > 0) and (index < (combo.items.count - 1))) then
  begin
    c := strToInt (combo.items [index]);

    combo.canvas.pen.color := c;
    combo.canvas.brush.color := c;
    combo.canvas.brush.style := bsSolid;
    combo.canvas.fillRect (rect);
  end;
end;

procedure TMainForm.btnBackgroundClick(Sender: TObject);
begin
  colorDialog.color := backgroundColor;
  if not colorDialog.execute then
    exit;

  backgroundColor := colorDialog.color;
  draw;
end;


procedure CopyBitmap (source, destination : TBitMap);
var
  dc : HDC;
  lpPal : PLOGPALETTE;
begin
  destination.width := source.Width;
  destination.height := source.Height;

  {get the screen dc}
  //dc := GetDc (0);
  //if (dc = 0) then
  //  exit;
  dc := source.Canvas.Handle;

  {do we have a palette device?}
  if (getDeviceCaps(dc, RASTERCAPS) AND RC_PALETTE) = RC_PALETTE then
  begin
    {allocate memory for a logical palette}
    GetMem (lpPal, sizeof(TLOGPALETTE) + (255 * sizeof(TPALETTEENTRY)));
    FillChar(lpPal^, sizeof(TLOGPALETTE) + (255 * sizeof(TPALETTEENTRY)), #0);

    {fill in the palette version}
    lpPal^.palVersion := $300;

    {grab the system palette entries}
    lpPal^.palNumEntries :=
    GetSystemPaletteEntries (dc,0,256,lpPal^.palPalEntry);
    if (lpPal^.PalNumEntries <> 0) then
    begin
      {create the palette}
      destination.Palette := CreatePalette(lpPal^);
    end;

    FreeMem (lpPal, sizeof (TLOGPALETTE) + (255 * sizeof(TPALETTEENTRY)));
  end;

  {copy from the screen to the bitmap}
  BitBlt (destination.Canvas.Handle,0,0,source.Width,source.Height,Dc,0,0,SRCCOPY);

  {release the screen dc}
  ReleaseDc(0, dc);
end;


procedure TMainForm.miFileExportClick(Sender: TObject);
var
  i : integer;
  animationGroup : TAnimationGroup;
  animation : TAnimation;
  animationHeader : PAnimationHeader;
  m : TMemoryStream;
  b : TBitmap;
begin
  if not saveDialogExport.execute then
    exit;

  animationGroup := TAnimationGroup (animationGroups.items [0]);
  animation := TAnimation (animationGroup.animations.items [0]);

  m := nil;
  b := nil;
  try
    m := TMemoryStream.create;
    b := TBitmap.create;

    uodata.loadBlobFromFile (m, uodata.animResource, animation.offset);

    animationHeader := PAnimationHeader (m.memory);

    for i := 0 to animationHeader.frameCount - 1 do
    begin
      frameIndex := i;
      draw;

      CopyBitmap (imgAnim.picture.bitmap, b);
      if i = 0 then
        b.saveToFile (saveDialogExport.filename)
      else
        b.saveToFile (
         extractFilePath (saveDialogExport.filename) +
         copy (extractFileName (saveDialogExport.filename), 1, length (extractFileName (saveDialogExport.filename)) - 4) +
         '-' + intToStr (i) +
         '.bmp');
    end;

    frameIndex := 0;
    draw;

  finally
    b.free;
    m.free;
  end;
end;


end.

