unit MainForm;

{$mode objfpc}{H+}

interface

uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
  ComCtrls, Spin, ExtCtrls, Grids, EditBtn, uHCTypeDefs, uHCAnnexDefs,
  uHCAnnexHelpers, uHCUtils, types, inifiles, Windows;

type

  { TfrmMain }

  TfrmMain = class(TForm)
    SGfneSaveName: TFileNameEdit;
    GTbtnExitGame: TButton;
    tshStoreConsumed: TTabSheet;
    ZCbtnZoomGroupIn: TButton;
    ZCbtnZoomUnitIn: TButton;
    ZCbtnZoomGroupOut: TButton;
    ZCbtnZoomUnitOut: TButton;
    ZCckbEnabled: TCheckBox;
    SAckbSkipGE: TCheckBox;
    SCckbEnabled: TCheckBox;
    ZCedtLines: TSpinEdit;
    SCedtLines: TSpinEdit;
    ZClblDesc: TLabel;
    SClblDesc: TLabel;
    ZClblLines: TLabel;
    SAmemMsgLog: TMemo;
    SAckbEnabled: TCheckBox;
    SAedtLines: TSpinEdit;
    SAlblLines: TLabel;
    SAlblDesc: TLabel;
    SClblLines: TLabel;
    ZCmemMsgLog: TMemo;
    SGbtnFreeGroup: TButton;
    GAckbEnabled: TCheckBox;
    GAedtLines: TSpinEdit;
    GAlblLines: TLabel;
    GAmemMsgLog: TMemo;
    HQckbEnabled: TCheckBox;
    HQedtLines: TSpinEdit;
    HQlblLines: TLabel;
    HQmemMsgLog: TMemo;
    SGbtnFreeUnit: TButton;
    SGlblDesc1: TLabel;
    SGlblDesc2: TLabel;
    SGlblDescription1: TLabel;
    MLbtnPostMessage: TButton;
    MLedtMessageToPost: TEdit;
    memPlatDesc: TMemo;
    memShipNames: TMemo;
    memMags: TMemo;
    memBSStrings: TMemo;
    SGbtnRefresh: TButton;
    GTlblGameTimeLeft: TLabel;
    GTlblGameDateTime: TLabel;
    HHQueueEventMessage: TButton;
    GTbtnResume: TButton;
    GTckbPause: TCheckBox;
    GTpctStructs: TPageControl;
    GTtshStartGroup: TTabSheet;
    GTtshWeatherGroups: TTabSheet;
    GTtshHarpEvents: TTabSheet;
    Label1: TLabel;
    Label2: TLabel;
    GTlblGameTime: TLabel;
    GTHEEventList: TListBox;
    GEHEmemDetail: TMemo;
    GTtshSonoBuoy: TTabSheet;
    GTtshDeadUnits: TTabSheet;
    Label3: TLabel;
    Label4: TLabel;
    memAnnexesA2: TMemo;
    memAnnexesB1: TMemo;
    memAnnexesD1: TMemo;
    memAnnexesG1: TMemo;
    memAnnexesG2: TMemo;
    memAnnexesI1: TMemo;
    memAnnexesJ1: TMemo;
    memAnnexesM1: TMemo;
    memAnnexesZ1: TMemo;
    memAnnexesP1: TMemo;
    memAnnexesY1: TMemo;
    memAnnexesA1: TMemo;
    memAnnexesC1: TMemo;
    memAnnexesZ2: TMemo;
    pctAnnexes: TPageControl;
    GlblDesc: TLabel;
    SGlblUnitClassName: TLabel;
    SGckbEnabled1: TCheckBox;
    SGedtLines1: TSpinEdit;
    SGlblLines1: TLabel;
    SGmemMsgLog1: TMemo;
    SLsgLineage: TStringGrid;
    Splitter1: TSplitter;
    tshZoomChange: TTabSheet;
    tshDLLStaff: TTabSheet;
    tshGroupAllocated: TTabSheet;
    tshGroupFree: TTabSheet;
    tshHQ: TTabSheet;
    tshSaveGame: TTabSheet;
    tshActGroupUnit: TTabSheet;
    tshPlatDesc: TTabSheet;
    tshShipNames: TTabSheet;
    tshMags: TTabSheet;
    tshBSStrings: TTabSheet;
    tshAnnexZ2: TTabSheet;
    tshAnnexZ1: TTabSheet;
    tshAnnexM1: TTabSheet;
    tshAnnexJ1: TTabSheet;
    tshAnnexI1: TTabSheet;
    tshAnnexG2: TTabSheet;
    tshAnnexG1: TTabSheet;
    tshAnnexD1: TTabSheet;
    tshAnnexC1: TTabSheet;
    tshAnnexP1: TTabSheet;
    tshAnnexB1: TTabSheet;
    tshAnnexA2: TTabSheet;
    tshAnnexA1: TTabSheet;
    tshAnnexY1: TTabSheet;
    tshAnnexes: TTabSheet;
    tshGT: TTabSheet;
    tshVC: TTabSheet;
    AGckbEnabled: TCheckBox;
    AGedtLines: TSpinEdit;
    AGlblLines: TLabel;
    AGmemMsgLog: TMemo;
    GFckbEnabled: TCheckBox;
    GFedtLines: TSpinEdit;
    GFlblLines: TLabel;
    GFmemMsgLog: TMemo;
    WGckbEnabled: TCheckBox;
    VCckbEnabled: TCheckBox;
    WGedtLines: TSpinEdit;
    VCedtLines: TSpinEdit;
    WGlblLines: TLabel;
    VClblLines: TLabel;
    WGmemMsgLog: TMemo;
    SHckbEnabled: TCheckBox;
    DUckbEnabled: TCheckBox;
    SHedtLines: TSpinEdit;
    DUedtLines: TSpinEdit;
    SHlblLines: TLabel;
    DUlblLines: TLabel;
    SHmemMsgLog: TMemo;
    SGlblUnitIDName: TLabel;
    SGlblUnitInfo: TLabel;
    SGpnlUnit: TPanel;
    SGlblGroups: TLabel;
    SGlblUnits: TLabel;
    SGlbxGroups: TListBox;
    SGlbxUnits: TListBox;
    SGpnl1: TPanel;
    SGlblDesc: TLabel;
    memDLLInit: TMemo;
    DUmemMsgLog: TMemo;
    SLckbEnabled: TCheckBox;
    SGckbEnabled: TCheckBox;
    HHckbEnabled: TCheckBox;
    SLedtLines: TSpinEdit;
    SGedtLines: TSpinEdit;
    HHedtLines: TSpinEdit;
    SLlblLines: TLabel;
    SGlblLines: TLabel;
    HHlblLines: TLabel;
    SLmemMsgLog: TMemo;
    SGmemMsgLog: TMemo;
    HHmemMsgLog: TMemo;
    tshWG: TTabSheet;
    tshDU: TTabSheet;
    tshSH: TTabSheet;
    tshStartGroup: TTabSheet;
    tshDLLInit: TTabSheet;
    tshScenLoad: TTabSheet;
    tshUnitFree: TTabSheet;
    tshUnitNew: TTabSheet;
    UGckbEnabled: TCheckBox;
    UNckbEnabled: TCheckBox;
    UGedtLines: TSpinEdit;
    UFckbEnabled: TCheckBox;
    UNedtLines: TSpinEdit;
    UGlblLines: TLabel;
    UFedtLines: TSpinEdit;
    UNlblLines: TLabel;
    UGmemMsgLog: TMemo;
    tshUnitToGroup: TTabSheet;
    tshTimeFactor: TTabSheet;
    tshUnitSink: TTabSheet;
    tshUnitShowingSide: TTabSheet;
    tshUnitAlt: TTabSheet;
    tshUnitSpeed: TTabSheet;
    tshUnitCourse: TTabSheet;
    tshUnitURegion: TTabSheet;
    tshUnitShowing: TTabSheet;
    TFckbEnabled: TCheckBox;
    TFedtLines: TSpinEdit;
    TFlblLines: TLabel;
    TFmemMsgLog: TMemo;
    UFlblLines: TLabel;
    UNmemMsgLog: TMemo;
    UFmemMsgLog: TMemo;
    USSckbEnabled: TCheckBox;
    USKckbEnabled: TCheckBox;
    USSedtLines: TSpinEdit;
    USKedtLines: TSpinEdit;
    USSlblLines: TLabel;
    USKlblLines: TLabel;
    USSmemMsgLog: TMemo;
    USPckbEnabled: TCheckBox;
    UAckbEnabled: TCheckBox;
    USPedtLines: TSpinEdit;
    UAedtLines: TSpinEdit;
    USPlblLines: TLabel;
    UAlblLines: TLabel;
    USPmemMsgLog: TMemo;
    ULLckbEnabled: TCheckBox;
    MLckbEnabled: TCheckBox;
    USckbEnabled: TCheckBox;
    ULLedtLines: TSpinEdit;
    MLlblLines: TLabel;
    UAmemMsgLog: TMemo;
    USKmemMsgLog: TMemo;
    UURckbEnabled: TCheckBox;
    USedtLines: TSpinEdit;
    ULLlblLines: TLabel;
    MLmemMsgLog: TMemo;
    UCckbEnabled: TCheckBox;
    UURedtLines: TSpinEdit;
    USlblLines: TLabel;
    ULLmemMsgLog: TMemo;
    PageControl1: TPageControl;
    MLedtLines: TSpinEdit;
    tshUnitLatLon: TTabSheet;
    tshMsgLog: TTabSheet;
    tshHH: TTabSheet;
    UCedtLines: TSpinEdit;
    UURlblLines: TLabel;
    USmemMsgLog: TMemo;
    UClblLines: TLabel;
    UURmemMsgLog: TMemo;
    UCmemMsgLog: TMemo;
    VCmemMsgLog: TMemo;
    SCmemMsgLog: TMemo;
    procedure FormChangeBounds(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: boolean);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormWindowStateChange(Sender: TObject);
    procedure GFckbEnabledChange(Sender: TObject);
    procedure GTbtnExitGameClick(Sender: TObject);
    procedure GTbtnResumeClick(Sender: TObject);
    procedure GTHEEventListSelectionChange(Sender: TObject; User: boolean);
    procedure HHQueueEventMessageClick(Sender: TObject);
    procedure MLbtnPostMessageClick(Sender: TObject);
    procedure SGbtnFreeGroupClick(Sender: TObject);
    procedure SGbtnFreeUnitClick(Sender: TObject);
    procedure SGbtnRefreshClick(Sender: TObject);
    procedure SGfneSaveNameChange(Sender: TObject);
    procedure SGlbxGroupsDblClick(Sender: TObject);
    procedure SGlbxGroupsSelectionChange(Sender: TObject; User: boolean);
    procedure SGlbxUnitsDblClick(Sender: TObject);
    procedure SGlbxUnitsSelectionChange(Sender: TObject; User: boolean);
    procedure tshAnnexA1Show(Sender: TObject);
    procedure tshAnnexA2Show(Sender: TObject);
    procedure tshAnnexB1Show(Sender: TObject);
    procedure tshAnnexC1Show(Sender: TObject);
    procedure tshAnnexD1Show(Sender: TObject);
    procedure tshAnnexesShow(Sender: TObject);
    procedure tshAnnexG1Show(Sender: TObject);
    procedure tshAnnexG2Show(Sender: TObject);
    procedure tshAnnexI1Show(Sender: TObject);
    procedure tshAnnexJ1Show(Sender: TObject);
    procedure tshAnnexM1Show(Sender: TObject);
    procedure tshAnnexP1Show(Sender: TObject);
    procedure tshAnnexY1Show(Sender: TObject);
    procedure tshAnnexZ1Show(Sender: TObject);
    procedure tshAnnexZ2Show(Sender: TObject);
    procedure tshBSStringsShow(Sender: TObject);
    procedure tshMagsShow(Sender: TObject);
    procedure tshPlatDescShow(Sender: TObject);
    procedure tshShipNamesShow(Sender: TObject);
    procedure ZCbtnZoomGroupInClick(Sender: TObject);
    procedure ZCbtnZoomGroupOutClick(Sender: TObject);
    procedure ZCbtnZoomUnitInClick(Sender: TObject);
    procedure ZCbtnZoomUnitOutClick(Sender: TObject);
  private
    { private declarations }
    PGroupListSelectedGroup: PHCGroup;
    PUnitListSelectedUnit: PHCUnit;
    procedure PopulateAnnexA1();
  public
    { public declarations }
    // ALE 20130812 Big bad pointers that hold most of the game data
    PStartGroup: PHCGroup;
    PHarpEventHead: PHCHarpoonEvent;
    PSonobuoyRegionHead: PHCSonobuoyRegion;
    PAllDeadUnitsPtr: PHCDeadListItem;
    PWeatherGroups: PHCGroup;
    PVictoryConditionsList: PHCVictoryConditions;
    // ALE 20130812 transient pointers
    PSelectedUnit: PHCUnit;
    // ALE 20130812 misc globals
    //blnInvestigatingStructs: Boolean;
    // ALE 20130908 pointers to functions exported by the GE
    HCQueueHarpoonEvent: THCQueueHarpoonEvent;
    NewPtr: THCNewPtr;
    DisposPtr: THCDisposPtr;
    HCdllCheckLatLonDepth: THCdllCheckLatLonDepth;
    HCannex_id_to_disk_id: THCannex_id_to_disk_id;
    HCdisk_id_to_annex_id: THCdisk_id_to_annex_id;
    HCMessageLogLine: THCMessageLogLine;
    HCfree_Unit: THCfree_Unit;
    HCfree_Group: THCfree_Group;
    HCChangeActGroupPtr: THCChangeActGroupPtr;
    HCChangeActUnitPtr: THCChangeActUnitPtr;
    HCZoomWindow: THCZoomWindow;
    HCExitGame: THCExitGame;
    HCSaveGame: THCSaveGame;
    // ALE 20221228 to store settings
    iniSettings: TIniFile;
    // ALE 20250526 to report game exit source
    DLLVERNstr: String;
    DLLDescstr: String;
    procedure RestoreWindowPosition(pForm: TForm; pHWNDHC: THandle);
    procedure SaveWindowPosition(pForm: TForm; pHWNDHC: THandle);
  end;

var
  frmMain: TfrmMain;

implementation

uses uLazGUIGlobals, uHCDefines, uHCEventHelpers;

{$R *.lfm}

{ TfrmMain }


procedure TfrmMain.SGlbxGroupsSelectionChange(Sender: TObject; User: boolean);
var
  strSel: string;
  PHCGThis: PHCGroup;
  PHCUThis: PHCUnit;
begin
  // ALE 20130317 User selected a group, pull up unit list.
  strSel := SGlbxGroups.Items[SGlbxGroups.ItemIndex];
  PHCGThis := PStartGroup;
  while (PHCGThis <> nil) do
  begin
    if (strSel = PHCGThis^.id) then
      break;
    PHCGThis := PHCGThis^.toSouth;
  end;

  // ALE 20130317 in theory we have the group from which
  //  we can enumerate the units.
  SGlbxUnits.Items.Clear;
  SGlbxUnits.Sorted := True;
  PHCUThis := PHCGThis^.Units;
  while (PHCUThis <> nil) do
  begin
    SGlbxUnits.Items.Add(Format('%2.2s%0.3d %s',
      [PHCGThis^.id, PHCUThis^.id, PHCUThis^.Name]));
    PHCUThis := PHCUThis^.Next;
  end;

  // ALE 20230424 add ability to free Groups
  if (PHCGThis <> nil) then
  begin
    PGroupListSelectedGroup:=PHCGThis;
    SGbtnFreeGroup.Enabled:=True;
  end;

end;

procedure TfrmMain.SGlbxUnitsDblClick(Sender: TObject);
var
  strSel: string;
  PHCUThis: PHCUnit;
begin
  // ALE 20250220 Set ActUnitPtr
  strSel := SGlbxUnits.Items[SGlbxUnits.ItemIndex];
  PHCUThis := PStartGroup^.Units;
  while (PHCUThis <> nil) do
  begin
    if (Pos(Format('%2.2s%0.3d', [PHCGroup(PHCUThis^.Group)^.id, PHCUThis^.id]),
      strSel) = 1) then
    begin
      break;
    end;
    PHCUThis := PHCUThis^.toSouth;
  end;
  PSelectedUnit := PHCUThis;

  if (PSelectedUnit <> nil) then
  begin
    HCChangeActUnitPtr(PSelectedUnit);
  end;
end;

procedure TfrmMain.GTbtnResumeClick(Sender: TObject);
begin
  GTHEEventList.Clear;
  GEHEmemDetail.Clear;
  GTckbPause.Checked := False;
  GTbtnResume.Enabled := False;
  GTckbPause.Enabled := True;
  byte(PDLLBusy^) := CFALSE; // ALE 20130816
end;

procedure TfrmMain.GTHEEventListSelectionChange(Sender: TObject; User: boolean);
var
  strSel: string;
  PEvent: PHCHarpoonEvent;
  PHCGThis: PHCGroup;
  PHCUThis: PHCUnit;

  PEventMessage: PHCMessageEvent;
begin
  // ALE 20130317 User selected a group, pull up unit list.
  strSel := Copy(GTHEEventList.Items[GTHEEventList.ItemIndex], 1, HCUUID_LEN-1);
  PEvent := PHarpEventHead;
  while (PEvent <> nil) do
  begin
    if (Trim(strSel) = PEvent^.uuid) then
      break;
    PEvent := PEvent^.Next;
  end;

  // ALE 20130816 We found the event, do something.
  GEHEmemDetail.Clear;
  GEHEmemDetail.Lines.Add(Format('EventUUID: %s', [PEvent^.uuid]));
  GEHEmemDetail.Lines.Add(Format('Pointer to Event: 0x%p', [PEvent]));
  GEHEmemDetail.Lines.Add(Format('Pointer to next Event: 0x%p', [PEvent^.Next]));
  GEHEmemDetail.Lines.Add(Format('Event Type: %s',
    [HCEventType2String(PEvent^.event_type)]));
  GEHEmemDetail.Lines.Add(Format('Pointer to Group the Event applies to (if any): 0x%p',
    [PEvent^.ourGroup]));
  GEHEmemDetail.Lines.Add(Format('UnitUUID the Event applies to (if any): %s',
    [PEvent^.unit_uuid]));
  GEHEmemDetail.Lines.Add(Format('Pointer to DataPtr: 0x%p', [PEvent^.DataPtr]));
  GEHEmemDetail.Lines.Add(Format('Event Time: %8.8d', [PEvent^.Time]));
  GEHEmemDetail.Lines.Add(Format('Event Time Type: %s',
    [HCEventTimeType2String(PEvent^.time_type)]));
  GEHEmemDetail.Lines.Add(Format('Event in_scroll_flag: %d', [PEvent^.in_scroll_flag]));
  if (PEvent^.event_type = MESSAGE_EVENT) then
  begin
    PEventMessage := PEvent^.DataPtr;
    GEHEmemDetail.Lines.Add(Format('Message: %s', [PEventMessage^.Message]));

  end;
end;

// ALE 20130908 An example of setting up a HarpoonEvent that
//  will go in the HarpoonEventHead list of events that holds
//  all of the events that are not associated with course
//  points.
//  HCQueueHarpoonEvent will add it to the HarpoonEventHead
//   list in the proper order.
procedure TfrmMain.HHQueueEventMessageClick(Sender: TObject);
var
  PHarpoonEvent: PHCHarpoonEvent;
  PEventMessage: PHCMessageEvent;
begin
  PHarpoonEvent := NewPtr(SizeOf(THCHarpoonEvent));
  PEventMessage := NewPtr(SizeOf(THCMessageEvent));

  PHarpoonEvent^.uuid := GenerateUUID;
  PHarpoonEvent^.in_scroll_flag := CFALSE;
  PHarpoonEvent^.DataPtr := PEventMessage;
  PHarpoonEvent^.event_type := MESSAGE_EVENT;;
  PHarpoonEvent^.time_type := TimeDelta;
  PHarpoonEvent^.Next := nil;
  PHarpoonEvent^.ourGroup := nil;
  PHarpoonEvent^.unit_uuid := '';
  PHarpoonEvent^.Time := 5; // ALE 20130908 Show us message 5 game seconds from now
  PEventMessage^.Side := ANYSIDE;
  PEventMessage^.MessageFormat := Ord(THCTextTypes.ASCII);
  PEventMessage^.Message := FormatDateTime('yyyy-mm-dd hh:nn:ss', Now()) +
    #10 + ' Message Created';
  byte(uLazGUIGlobals.PDLLBusy^) := CTRUE;
  HCQueueHarpoonEvent(PHarpoonEvent);
  byte(uLazGUIGlobals.PDLLBusy^) := CFALSE;
end;

procedure TfrmMain.MLbtnPostMessageClick(Sender: TObject);
begin
  HCMessageLogLine(PAnsiChar(MLedtMessageToPost.Text));
end;

procedure TfrmMain.SGbtnFreeGroupClick(Sender: TObject);
begin
    if (PGroupListSelectedGroup <> nil) then
  begin
    HCfree_Group(PGroupListSelectedGroup);
    PGroupListSelectedGroup := nil;
    SGbtnFreeGroup.Enabled := False;
    SGbtnRefresh.Click;
  end;
end;

procedure TfrmMain.SGbtnFreeUnitClick(Sender: TObject);
begin
    if (PUnitListSelectedUnit <> nil) then
  begin
    HCfree_Unit(PUnitListSelectedUnit);
    PUnitListSelectedUnit := nil;
    PGroupListSelectedGroup := nil;
    SGbtnFreeUnit.Enabled := False;
    SGbtnRefresh.Click;
    SGlbxUnits.Clear;
    //SGlbxGroupsSelectionChange(Sender, True)
  end;
end;

procedure TfrmMain.SGbtnRefreshClick(Sender: TObject);
var
  PHCGThis: PHCGroup;
begin
  // ALE 20130317 Fill up the SGlbxGroups
  SGlbxGroups.Items.BeginUpdate;
  SGlbxGroups.Items.Clear;
  SGlbxGroups.Sorted := True;
  PHCGThis := PStartGroup;
  while (PHCGThis <> nil) do
  begin
    SGlbxGroups.Items.Add(PHCGThis^.id);
    PHCGThis := PHCGThis^.toSouth;
  end;
  SGlbxGroups.Items.EndUpdate;
end;

procedure TfrmMain.SGfneSaveNameChange(Sender: TObject);
begin
  // ALE 20250528 save game and indicate that it isn't an autosave
  HCSaveGame(PAnsiChar(SGfneSaveName.FileName), Byte(THCSaveGameTypes.ExportDLL));
end;

procedure TfrmMain.SGlbxGroupsDblClick(Sender: TObject);
var
  strSel: string;
  PHCGThis: PHCGroup;
  PHCUThis: PHCUnit;
begin
  // ALE 20250220 Change ActGroupPtr
  strSel := SGlbxGroups.Items[SGlbxGroups.ItemIndex];
  PHCGThis := PStartGroup;
  while (PHCGThis <> nil) do
  begin
    if (strSel = PHCGThis^.id) then
      break;
    PHCGThis := PHCGThis^.toSouth;
  end;

  if (PHCGThis <> nil) then
  begin
    HCChangeActGroupPtr(PHCGthis);
  end;

end;

procedure TfrmMain.FormChangeBounds(Sender: TObject);
begin
  SaveWindowPosition(Sender as TForm, hwndHC);
end;

procedure TfrmMain.FormCloseQuery(Sender: TObject; var CanClose: boolean);
begin
  CanClose := False;
end;

procedure TfrmMain.FormCreate(Sender: TObject);
var
  strINIFNP: String;
  hMenu: THandle;
begin
  hMenu := GetSystemMenu(Handle, False);
  DeleteMenu(hMenu, SC_CLOSE,  MF_BYCOMMAND);
  strINIFNP := ExtractFilePath(Application.ExeName) + 'ExportDLLs\lazGUI\lazGUI.ini';
  ForceDirectories(ExtractFilePath(strINIFNP));
  iniSettings := TIniFile.Create(strINIFNP);
  GFckbEnabled.Checked := iniSettings.ReadBool('Tab_tshGroupFree', 'GFckbEnabled.Checked', False);
end;

procedure TfrmMain.FormDestroy(Sender: TObject);
begin
  iniSettings.Free;
end;

procedure TfrmMain.FormWindowStateChange(Sender: TObject);
begin
  SaveWindowPosition(Sender as TForm, hwndHC);
end;

procedure TfrmMain.GFckbEnabledChange(Sender: TObject);
begin
  iniSettings.WriteBool('Tab_' + TCheckBox(Sender).Parent.Name, TCheckBox(Sender).Name + '.Checked', GFckbEnabled.Checked);
end;

procedure TfrmMain.GTbtnExitGameClick(Sender: TObject);
begin
  // ALE 20250526 close the game without warning.
  HCExitGame(PAnsiChar(Format('ExportDLL %s v%s', [DLLDescstr, DLLVERNstr])));
end;

procedure TfrmMain.SGlbxUnitsSelectionChange(Sender: TObject; User: boolean);
var
  strSel: string;
  PHCUThis: PHCUnit;
  PHCAnnexA1: PHCAnnexA1_disk;
  PHCAnnexA2: PHCAnnexA2_disk;
  PHCAnnexB1: PHCAnnexB1_disk;
  PHCAnnexP1: PHCAnnexP1_disk;
begin
  // ALE 20130317 User selected a unit, pull up unit info.
  strSel := SGlbxUnits.Items[SGlbxUnits.ItemIndex];
  PHCUThis := PStartGroup^.Units;
  while (PHCUThis <> nil) do
  begin
    if (Pos(Format('%2.2s%0.3d', [PHCGroup(PHCUThis^.Group)^.id, PHCUThis^.id]),
      strSel) = 1) then
    begin
      break;
    end;
    PHCUThis := PHCUThis^.toSouth;
  end;
  PSelectedUnit := PHCUThis;

  if (PSelectedUnit <> nil) then
  begin
    SGlblUnitIDName.Caption :=
      Format('Unit Name: %2.2s%0.3d %s', [PHCGroup(PSelectedUnit^.Group)^.id,
      PSelectedUnit^.id, PSelectedUnit^.Name]);
    case (HCAnnexHelper.HCAnnexShift(PSelectedUnit^.Annex)) of
      SHIP_FLAG:
      begin
        PHCAnnexA1 := HCAnnexHelper.HCGetAnnexA1(PSelectedUnit^.Annex);
        SGlblUnitClassName.Caption := 'Class Name: ' + PHCAnnexA1^.ClassName;
        // ALE 20150820 also could have done it in an ugly one-liner SGlblUnitClassName.Caption := 'Class Name: ' + PHCAnnexA1_disk(AnnexData^.PA1_DISK_PTR_LIST + HCannex_id_to_disk_id(PSelectedUnit^.Annex) - (HCAnnexShift(PSelectedUnit^.Annex) * 4096 + 2048))^.ClassName;
      end;
      SUB_FLAG:
      begin
        PHCAnnexA2 := HCAnnexHelper.HCGetAnnexA2(PSelectedUnit^.Annex);
        SGlblUnitClassName.Caption := 'Class Name: ' + PHCAnnexA2^.ClassName;
      end;
      PLANE_FLAG:
      begin
        PHCAnnexB1 := HCAnnexHelper.HCGetAnnexB1(PSelectedUnit^.Annex);
        SGlblUnitClassName.Caption := 'Class Name: ' + PHCAnnexB1^.ClassName;
      end;
      BASE_FLAG:
      begin
        PHCAnnexP1 := HCAnnexHelper.HCGetAnnexP1(PSelectedUnit^.Annex);
        SGlblUnitClassName.Caption := 'Class Name: ' + PHCAnnexP1^.Name;
      end;
      else
        SGlblUnitClassName.Caption := '';
    end;
  end;

  // ALE 20230424 add ability to free Units
  if (PSelectedUnit <> nil) then
  begin
    PUnitListSelectedUnit:=PSelectedUnit;
    SGbtnFreeUnit.Enabled:=True;
  end;

end;

procedure TfrmMain.tshAnnexA1Show(Sender: TObject);
begin
  PopulateAnnexA1();
end;

procedure TfrmMain.PopulateAnnexA1();
var
  intIdx: NativeUInt;
  pShip: PHCAnnexA1_disk;
begin
  if (AnnexData <> nil) then
  begin
    memAnnexesA1.Lines.BeginUpdate;
    memAnnexesA1.Clear;
    for intIdx := 0 to AnnexData^.AnnexDiskRecCount[SHIP_FLAG]-1 do
    begin
      pShip := AnnexData^.PA1_DISK_PTR_LIST + intIdx;
      memAnnexesA1.Lines.Add(Format('AnnexId=%6.0d ClassName=%s', [pShip^.DiskId, pShip^.ClassName]));
    end;
    memAnnexesA1.Lines.EndUpdate;
  end;
end;

procedure TfrmMain.tshAnnexA2Show(Sender: TObject);
var
  intIdx: NativeUInt;
  pSub: PHCAnnexA2_disk;
begin
  if (AnnexData <> nil) then
  begin
    memAnnexesA2.Lines.BeginUpdate;
    memAnnexesA2.Clear;
    for intIdx := 0 to AnnexData^.AnnexDiskRecCount[SUB_FLAG]-1 do
    begin
      pSub := AnnexData^.PA2_DISK_PTR_LIST + intIdx;
      memAnnexesA2.Lines.Add(Format('AnnexId=%6.0d ClassName=%s', [pSub^.DiskId, pSub^.ClassName]));
    end;
    memAnnexesA2.Lines.EndUpdate;
  end;
end;

procedure TfrmMain.tshAnnexB1Show(Sender: TObject);
var
  intIdx: NativeUInt;
  pAir: PHCAnnexB1_disk;
begin
  if (AnnexData <> nil) then
  begin
    memAnnexesB1.Lines.BeginUpdate;
    memAnnexesB1.Clear;
    for intIdx := 0 to AnnexData^.AnnexDiskRecCount[PLANE_FLAG]-1 do
    begin
      pAir := AnnexData^.PB1_DISK_PTR_LIST + intIdx;
      memAnnexesB1.Lines.Add(Format('AnnexId=%6.0d ClassName=%s', [pAir^.DiskId, pAir^.ClassName]));
    end;
    memAnnexesB1.Lines.EndUpdate;
  end;
end;

procedure TfrmMain.tshAnnexC1Show(Sender: TObject);
var
  intIdx: NativeUInt;
  pGun: PHCAnnexC1_disk;
begin
  if (AnnexData <> nil) then
  begin
    memAnnexesC1.Lines.BeginUpdate;
    memAnnexesC1.Clear;
    for intIdx := 0 to AnnexData^.AnnexDiskRecCount[GUN_FLAG]-1 do
    begin
      pGun := AnnexData^.PC1_DISK_PTR_LIST + intIdx;// * SizeOf(THCAnnexC1_disk);
      memAnnexesC1.Lines.Add(Format('AnnexId=%6.0d ClassName=%s', [pGun^.DiskId, pGun^.ClassName]));
    end;
    memAnnexesC1.Lines.EndUpdate;
  end;
end;

procedure TfrmMain.tshAnnexD1Show(Sender: TObject);
var
  intIdx: NativeUInt;
  pMissile: PHCAnnexD1_disk;
begin
  if (AnnexData <> nil) then
  begin
    memAnnexesD1.Lines.BeginUpdate;
    memAnnexesD1.Clear;
    for intIdx := 0 to AnnexData^.AnnexDiskRecCount[MISSILE_FLAG]-1 do
    begin
      pMissile := AnnexData^.PD1_DISK_PTR_LIST + intIdx;
      memAnnexesD1.Lines.Add(Format('AnnexId=%6.0d ClassName=%s', [pMissile^.DiskId, pMissile^.ClassName]));
    end;
    memAnnexesD1.Lines.EndUpdate;
  end;
end;

procedure TfrmMain.tshAnnexesShow(Sender: TObject);
begin
  PopulateAnnexA1();
end;

procedure TfrmMain.tshAnnexG1Show(Sender: TObject);
var
  intIdx: NativeUInt;
  pBomb: PHCAnnexG1_disk;
begin
  if (AnnexData <> nil) then
  begin
    memAnnexesG1.Lines.BeginUpdate;
    memAnnexesG1.Clear;
    for intIdx := 0 to AnnexData^.AnnexDiskRecCount[ATOG_FLAG]-1 do
    begin
      pBomb := AnnexData^.PG1_DISK_PTR_LIST + intIdx;
      memAnnexesG1.Lines.Add(Format('AnnexId=%6.0d ClassName=%s', [pBomb^.DiskId, pBomb^.ClassName]));
    end;
    memAnnexesG1.Lines.EndUpdate;
  end;
end;

procedure TfrmMain.tshAnnexG2Show(Sender: TObject);
var
  intIdx: NativeUInt;
  pECM: PHCAnnexG2_disk;
begin
  if (AnnexData <> nil) then
  begin
    memAnnexesG2.Lines.BeginUpdate;
    memAnnexesG2.Clear;
    for intIdx := 0 to AnnexData^.AnnexDiskRecCount[ECM_FLAG]-1 do
    begin
      pECM := AnnexData^.PG2_DISK_PTR_LIST + intIdx;
      memAnnexesG2.Lines.Add(Format('AnnexId=%6.0d ClassName=%s', [pECM^.DiskId, pECM^.ClassName]));
    end;
    memAnnexesG2.Lines.EndUpdate;
  end;
end;

procedure TfrmMain.tshAnnexI1Show(Sender: TObject);
var
  intIdx: NativeUInt;
  pNuke: PHCAnnexI1_disk;
begin
  if (AnnexData <> nil) then
  begin
    memAnnexesI1.Lines.BeginUpdate;
    memAnnexesI1.Clear;
    for intIdx := 0 to AnnexData^.AnnexDiskRecCount[NUKE_FLAG]-1 do
    begin
      pNuke := AnnexData^.PI1_DISK_PTR_LIST + intIdx;
      memAnnexesI1.Lines.Add(Format('AnnexId=%6.0d KillRange=%2.1f DamageRange=%2.1f', [pNuke^.DiskId, pNuke^.KillRange/10.0, pNuke^.DamageRange/10.0]));
    end;
    memAnnexesI1.Lines.EndUpdate;
  end;
end;

procedure TfrmMain.tshAnnexJ1Show(Sender: TObject);
var
  intIdx: NativeUInt;
  pRadar: PHCAnnexJ1_disk;
begin
  if (AnnexData <> nil) then
  begin
    memAnnexesJ1.Lines.BeginUpdate;
    memAnnexesJ1.Clear;
    for intIdx := 0 to AnnexData^.AnnexDiskRecCount[RADAR_FLAG]-1 do
    begin
      pRadar := AnnexData^.PJ1_DISK_PTR_LIST + intIdx;
      memAnnexesJ1.Lines.Add(Format('AnnexId=%6.0d ClassName=%s', [pRadar^.DiskId, pRadar^.ClassName]));
    end;
    memAnnexesJ1.Lines.EndUpdate;
  end;
end;

procedure TfrmMain.tshAnnexM1Show(Sender: TObject);
var
  intIdx: NativeUInt;
  pSonar: PHCAnnexM1_disk;
begin
  if (AnnexData <> nil) then
  begin
    memAnnexesM1.Lines.BeginUpdate;
    memAnnexesM1.Clear;
    for intIdx := 0 to AnnexData^.AnnexDiskRecCount[SONAR_FLAG]-1 do
    begin
      pSonar := AnnexData^.PM1_DISK_PTR_LIST + intIdx;
      memAnnexesM1.Lines.Add(Format('AnnexId=%6.0d ClassName=%s', [pSonar^.DiskId, pSonar^.ClassName]));
    end;
    memAnnexesM1.Lines.EndUpdate;
  end;
end;

procedure TfrmMain.tshAnnexP1Show(Sender: TObject);
var
  intIdx: NativeUInt;
  pBase: PHCAnnexP1_disk;
begin
  if (AnnexData <> nil) then
  begin
    memAnnexesP1.Lines.BeginUpdate;
    memAnnexesP1.Clear;
    for intIdx := 0 to AnnexData^.AnnexDiskRecCount[BASE_FLAG]-1 do
    begin
      pBase := AnnexData^.PP1_DISK_PTR_LIST + intIdx;
      memAnnexesP1.Lines.Add(Format('AnnexId=%6.0d Name=%s', [pBase^.DiskId, pBase^.Name]));
    end;
    memAnnexesP1.Lines.EndUpdate;
  end;
end;

procedure TfrmMain.tshAnnexY1Show(Sender: TObject);
var
  intIdx: NativeUInt;
  pLoadout: PHCAnnexY1_disk;
begin
  if (AnnexData <> nil) then
  begin
    memAnnexesY1.Lines.BeginUpdate;
    memAnnexesY1.Clear;
    for intIdx := 0 to AnnexData^.AnnexDiskRecCount[LOADOUT_FLAG]-1 do
    begin
      pLoadout := AnnexData^.PY1_DISK_PTR_LIST + intIdx;// * SizeOf(THCAnnexY1_disk);
      memAnnexesY1.Lines.Add(Format('AnnexId=%6.0d Range=%6.0d, DATA=%3.0d', [pLoadout^.DiskId, pLoadout^.Range, pLoadout^.DATA]));
    end;
    memAnnexesY1.Lines.EndUpdate;
  end;
end;

procedure TfrmMain.tshAnnexZ1Show(Sender: TObject);
var
  intIdx: NativeUInt;
  pMount: PHCAnnexZ1_disk;
begin
  if (AnnexData <> nil) then
  begin
    memAnnexesZ1.Lines.BeginUpdate;
    memAnnexesZ1.Clear;
    for intIdx := 0 to AnnexData^.AnnexDiskRecCount[MOUNT_FLAG]-1 do
    begin
      pMount := AnnexData^.PZ1_DISK_PTR_LIST + intIdx;
      memAnnexesZ1.Lines.Add(Format('AnnexId=%6.0d ClassName=%s NumRoundsMnt=%d', [pMount^.DiskId, pMount^.ClassName, pMount^.NumRoundsMnt]));
    end;
    memAnnexesZ1.Lines.EndUpdate;
  end;
end;

procedure TfrmMain.tshAnnexZ2Show(Sender: TObject);
var
  intIdx: NativeUInt;
  pMulti: PHCAnnexZ2_disk;
begin
  if (AnnexData <> nil) then
  begin
    memAnnexesZ2.Lines.BeginUpdate;
    memAnnexesZ2.Clear;
    for intIdx := 0 to AnnexData^.AnnexDiskRecCount[MULTI_FLAG]-1 do
    begin
      pMulti := AnnexData^.PZ2_DISK_PTR_LIST + intIdx;
      memAnnexesZ2.Lines.Add(Format('AnnexId=%6.0d FirstWeaponDiskId=%6.0d', [pMulti^.DiskId, pMulti^.WepId[0]]));
    end;
    memAnnexesZ2.Lines.EndUpdate;
  end;
end;

procedure TfrmMain.tshBSStringsShow(Sender: TObject);
var
  intIdx: NativeUInt;
  pShip: PHCAnnexA1_disk;
begin
  if (AnnexData <> nil) then
  begin
    memBSStrings.Lines.BeginUpdate;
    memBSStrings.Lines.Clear;
    for intIdx := 0 to MAX_SIDE_NAMES - 1 do
    begin
      memBSStrings.Lines.Add(Format('Side %3.0d name=%s', [intIdx, AnnexData^.PBSStrings^.SideName[intIdx]]));
    end;
    for intIdx := 0 to MAX_COUNTRIES - 1 do
    begin
      memBSStrings.Lines.Add(Format('Country %3.0d name=%s', [intIdx, AnnexData^.PBSStrings^.CountryName[intIdx]]));
    end;
    for intIdx := 0 to MAX_SHIP_TYPES - 1 do
    begin
      memBSStrings.Lines.Add(Format('Ship Type %3.0d name=%s', [intIdx, AnnexData^.PBSStrings^.ShipType[intIdx]]));
    end;
    for intIdx := 0 to MAX_AC_TYPES - 1 do
    begin
      memBSStrings.Lines.Add(Format('AC Type %3.0d name=%s', [intIdx, AnnexData^.PBSStrings^.ACType[intIdx]]));
    end;
    for intIdx := 0 to MAX_AC_SHT_TYPES - 1 do
    begin
      memBSStrings.Lines.Add(Format('AC Type (short) %3.0d name=%s', [intIdx, AnnexData^.PBSStrings^.ACShortType[intIdx]]));
    end;
    for intIdx := 0 to MAX_AC_STAT_STRS - 1 do
    begin
      memBSStrings.Lines.Add(Format('AC Status %3.0d name=%s', [intIdx, AnnexData^.PBSStrings^.ACStatusString[intIdx]]));
    end;
    for intIdx := 0 to MAX_LOADOUT_STRS - 1 do
    begin
      memBSStrings.Lines.Add(Format('Loadout %3.0d name=%s', [intIdx, AnnexData^.PBSStrings^.LoadoutString[intIdx]]));
    end;
    for intIdx := 0 to MAX_RUNWAY_SIZES - 1 do
    begin
      memBSStrings.Lines.Add(Format('Runway Size %3.0d name=%s', [intIdx, AnnexData^.PBSStrings^.RunwayString[intIdx]]));
    end;
    for intIdx := 0 to MAX_BROAD_TYPES - 1 do
    begin
      memBSStrings.Lines.Add(Format('Broad Victory Type Size %3.0d name=%s', [intIdx, AnnexData^.PBSStrings^.BroadVicType[intIdx]]));
    end;
    memBSStrings.Lines.EndUpdate;
  end;
end;

procedure TfrmMain.tshMagsShow(Sender: TObject);
var
  intIdx: NativeUInt;
  pMags: PHCMagRecsDiskPlatform;
  pMag: PHCMagRecDiskPlatform;
begin
  if (AnnexData <> nil) then
  begin
    memMags.Lines.BeginUpdate;
    memMags.Lines.Clear;
    pMags := AnnexData^.PMAG_DISK_PTR_LIST;
    while pMags <> nil do
    begin
      pMag := pMags^.Mags;
      while pMag <> nil do
      begin
        memMags.Lines.Add(Format('AnnexId=%6.0d WepId=%6.0d Count=%5.0d ReloadTime=%3.0d', [pMags^.DiskId, pMag^.WepId, pMag^.Count, pMag^.ReloadTime]));
        pMag := pMag^.next;
      end;
      pMags := pMags^.next;
    end;
    memMags.Lines.EndUpdate;
  end;
end;

procedure TfrmMain.tshPlatDescShow(Sender: TObject);
var
  intIdx: NativeUInt;
  pShip: PHCAnnexA1_disk;
begin
  if (AnnexData <> nil) then
  begin
    memPlatDesc.Lines.BeginUpdate;
    memPlatDesc.Lines.Clear;
    for intIdx := 0 to AnnexData^.PlatDescCount - 1 do
    begin
      memPlatDesc.Lines.Add(Format('TextDesc %4.0d %s', [intIdx, AnnexData^.PPLAT_DESC_LIST[intIdx].description]));
    end;
    memPlatDesc.Lines.EndUpdate;
  end;
end;

procedure TfrmMain.tshShipNamesShow(Sender: TObject);
var
  intIdx: NativeUInt;
  pShipNames: PHCNameRecsDiskPlatform;
  pShipName: PHCNameRecDiskPlatform;
begin
  if (AnnexData <> nil) then
  begin
    memShipNames.Lines.BeginUpdate;
    memShipNames.Lines.Clear;
    pShipNames := AnnexData^.PNAME_DISK_PTR_LIST;
    while pShipNames <> nil do
    begin
      pShipName := pShipNames^.names;
      while pShipName <> nil do
      begin
        memShipNames.Lines.Add(Format('AnnexId=%6.0d Name=%s', [pShipNames^.DiskId, pShipName^.name]));
        pShipName := pShipName^.next;
      end;
      pShipNames := pShipNames^.next;
    end;
    memShipNames.Lines.EndUpdate;
  end;
end;

procedure TfrmMain.ZCbtnZoomGroupInClick(Sender: TObject);
begin
  frmMain.HCZoomWindow(1,GROUP_ACTIVE);
end;

procedure TfrmMain.ZCbtnZoomGroupOutClick(Sender: TObject);
begin
  frmMain.HCZoomWindow(-1,GROUP_ACTIVE);
end;

procedure TfrmMain.ZCbtnZoomUnitInClick(Sender: TObject);
begin
  frmMain.HCZoomWindow(1,UNIT_ACTIVE);
end;

procedure TfrmMain.ZCbtnZoomUnitOutClick(Sender: TObject);
begin
  frmMain.HCZoomWindow(-1,UNIT_ACTIVE);
end;

procedure TfrmMain.RestoreWindowPosition(pForm: TForm; pHWNDHC: THandle);
var
  lFormName: String;
  lMDI: String;
  intTop, intLeft, intWidth, intHeight, intPPI: NativeInt;
  intTopParent, intLeftParent, intWidthParent, intHeightParent, intPPIParent: NativeInt;
  wsWindowState: TWindowState;
  lRectParent: TRect;
begin
  lFormName := pForm.Name;
  if (pForm.ParentWindow = pHWNDHC) then
  begin
    lMDI := 'MDI_'; // ALE 20230105 HC is the parent window
    GetWindowRect(pHWNDHC, &lRectParent);
    intTopParent := lRectParent.Top;
    intLeftParent := lRectParent.Left;
    intWidthParent := lRectParent.Width;
    intHeightParent := lRectParent.Height;
    intPPIParent:= Screen.PixelsPerInch; // ALE 20220105 implement this API someday... GetDpiForWindow(pHWNDHC);
  end
  else
  begin
    lMDI := 'SDI_'; // ALE 20230105 form floats freely independently of HC window
    intTopParent := Screen.DesktopTop;
    intLeftParent := Screen.DesktopLeft;
    intWidthParent := Screen.DesktopWidth;
    intHeightParent := Screen.DesktopHeight;
    intPPIParent:= Screen.PixelsPerInch;
  end;

  intTop := iniSettings.ReadInteger('Form_' + lFormName, lMDI + 'top', -1);
  intLeft := iniSettings.ReadInteger('Form_' + lFormName, lMDI + 'left', -1);
  intWidth := iniSettings.ReadInteger('Form_' + lFormName, lMDI + 'width', -1);
  intHeight := iniSettings.ReadInteger('Form_' + lFormName, lMDI + 'height', -1);
  intPPI := iniSettings.ReadInteger('Form_' + lFormName, lMDI + 'pixelsperinch', -1);
  wsWindowState := StrToWindowState(iniSettings.ReadString('Form_' + lFormName, lMDI + 'windowstate', 'wsNormal'));

  pForm.Visible := True;

  if pForm.WindowState <> wsWindowState then
  begin
    pForm.WindowState := wsWindowState;
  end;


  if (intTop > intTopParent) AND (intTop < intTopParent + intHeight) then
  begin
    if (intLeft > intLeftParent) AND (intTop < intLeftParent + intWidthParent) then
    begin
      pForm.Top := intTop;
      pForm.Left := intLeft;
      if (intPPI = pForm.PixelsPerInch) then
      begin
        pForm.Width := intWidth;
        pForm.Height := intHeight;
      end;
    end;
  end;

  // ALE 20230105 try to pop HC window more forward than this window
  //  if it is a floating window
  if (lMDI = 'SDI_') then
  begin
    ShowWindow(pHWNDHC, SW_SHOW);
    SetForegroundWindow(pHWNDHC);
  end;
end;

procedure TfrmMain.SaveWindowPosition(pForm: TForm; pHWNDHC: THandle);
var
  lFormName: String;
  lMDI: String;
begin
  lFormName := pForm.Name;
  if (pForm.WindowState = wsNormal) then
  begin
    if (pForm.ParentWindow = pHWNDHC) then
    begin
      lMDI := 'MDI_'; // ALE 20230105 HC is the parent window
    end
    else
    begin
      lMDI := 'SDI_'; // ALE 20230105 form floats freely independently of HC window
    end;
    iniSettings.WriteInteger('Form_' + lFormName, lMDI + 'top', pForm.Top);
    iniSettings.WriteInteger('Form_' + lFormName, lMDI + 'left', pForm.Left);
    iniSettings.WriteInteger('Form_' + lFormName, lMDI + 'width', pForm.Width);
    iniSettings.WriteInteger('Form_' + lFormName, lMDI + 'height', pForm.Height);
    iniSettings.WriteInteger('Form_' + lFormName, lMDI + 'pixelsperinch', pForm.PixelsPerInch);
  end;
  iniSettings.WriteString('Form_' + lFormName, lMDI + 'windowstate', WindowStateToStr(pForm.WindowState));
end;
end.
