unit mainform;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, RTTICtrls, Dialogs,
  StdCtrls, ComCtrls, Spin, ExtCtrls, XMLPropStorage, IniPropStorage,
  uHCTypeDefs, uHCDefines, uHCEventHelpers, uHCAnnexDefs, uHCAnnexHelpers,
  uHCGlobals, uHCUtils, uLazGUIGlobals, PropertyStorage, Menus;

type

  { TfrmMain }

  TfrmMain = class(TForm)
    AGFlbxGroup: TListBox;
    AGFlbxUnit: TListBox;
    AGFbtnSrch: TButton;
    AirGrpForm: TTabSheet;
    AGFspnbxUnitAlt: TSpinEdit;
    AGFbtnGrp1: TButton;
    AGFbtnGrp2: TButton;
    AGFbtnMeet: TButton;
    AGFbtnASWPat: TButton;
    AGFbtnAEWPat: TButton;
    AtkBtnSrch: TButton;
    AtkLbxFGroup: TListBox;
    AtkLbxEGroup: TListBox;
    AtkLbxEUnit: TListBox;
    AtkBtnDefet: TButton;
    AtkBtnCapt: TButton;
    AGFBtnSplit: TButton;
    AGFBtnPause: TButton;
    AtkBtnCover: TButton;
    AtkBtnTarg: TButton;
    AtkBtnPause: TButton;
    AtkLbxFUnit: TListBox;
    AtkLblBeta: TLabel;
    AGFbtnCAPPat: TButton;
    AGFBtnEW: TButton;
    AGFBtnSetAlt: TButton;
    AtkBtnRepair: TButton;
    Grp1: TMenuItem;
    Grp2: TMenuItem;
    ASWpat: TMenuItem;
    CAPpat: TMenuItem;
    AEWpat: TMenuItem;
    EWpat: TMenuItem;
    MainMenu1: TMainMenu;
    MenuItem1: TMenuItem;
    MenuItem2: TMenuItem;
    MenuItem3: TMenuItem;
    MenuItem4: TMenuItem;
    PopupMenuRFLUnit: TPopupMenu;
    PopupMenuAGFgroup: TPopupMenu;
    RflChkBAutoPFinish: TCheckBox;
    SVbtnGrpUpdate: TButton;
    IniPropStorage1: TIniPropStorage;
    RflLblAmnt: TLabel;
    AGFlblNOE: TLabel;
    AGFlblE: TLabel;
    AGFlblS: TLabel;
    AtkLblExact: TLabel;
    KbscText: TMemo;
    AGFlblIFR: TLabel;
    AtkLblMG: TLabel;
    StatDeadLstBx: TListBox;
    StatDamLstBx: TListBox;
    StatSessLstBx: TListBox;
    StatVCLstbx: TListBox;
    StatusPageControl: TPageControl;
    RflBtnPause: TButton;
    RflLblRecipT: TLabel;
    RflLblSupT: TLabel;
    SETmemHelp: TMemo;
    AGFspnbxLon: TSpinEdit;
    AGFspnbxLat: TSpinEdit;
    SVLabelDetail: TLabel;
    SVListBoxUnitDetail: TListBox;
    SVLabelUnits: TLabel;
    SVLabelGroup: TLabel;
    SVListBoxGroups: TListBox;
    SVListBoxUnits: TListBox;
    RflBtnQueue: TButton;
    RflChkBTnkGrpSrch: TCheckBox;
    RflBtnTrans: TButton;
    RflLblGameTime: TLabel;
    RflChkBFind: TCheckBox;
    RflChkBPause: TCheckBox;
    RflLbxUnits: TListBox;
    RflLbxGroups: TListBox;
    RflLbxDetail: TListBox;
    Attack: TTabSheet;
    AGFSpnBxSplt: TSpinEdit;
    RefuelSheet: TTabSheet;
    RflSpinEditTank: TSpinEdit;
    RflSpinEditQuant: TSpinEdit;
    RflSpinEditRecip: TSpinEdit;
    SeaVessel: TTabSheet;
    KBShortCuts: TTabSheet;
    Status: TTabSheet;
    StatVCTSht: TTabSheet;
    StatSessTSht: TTabSheet;
    StatDamTSht: TTabSheet;
    StatDeadTSht: TTabSheet;
    TedbxPatrol: TEdit;
    SETlblMLcount: TLabel;
    MLtmemLog: TMemo;
    PageControl1: TPageControl;
    MessgLog: TTabSheet;
    Settings: TTabSheet;
    SETspnMLC: TSpinEdit;
    AGFspnbxPatrl: TSpinEdit;
    procedure AGFbtnAEWPatEnter(Sender: TObject);
    procedure AGFbtnASWPatEnter(Sender: TObject);
    procedure AGFbtnCAPPatEnter(Sender: TObject);
    procedure AGFBtnEWClick(Sender: TObject);
    procedure AGFbtnGrp1Enter(Sender: TObject);
    procedure AGFbtnGrp2Enter(Sender: TObject);
    procedure AGFbtnMeetEnter(Sender: TObject);
    procedure AGFBtnPauseClick(Sender: TObject);
 //   procedure AGFBtnReprClick(Sender: TObject);
 //   procedure AGFBtnSpAltClick(Sender: TObject);
    procedure AGFBtnSplitClick(Sender: TObject);
    procedure AGFbtnSrchEnter(Sender: TObject);
 //   procedure AGFbtnStripClick(Sender: TObject);
    procedure AGFgrpSrch();
    procedure AGFlblEClick(Sender: TObject);
    procedure AGFlblSClick(Sender: TObject);
    procedure AGFlbxGroupSelectionChange(Sender: TObject; User: boolean);
    procedure AGFlbxUnitSelectionChange(Sender: TObject; User: boolean);
    procedure AGFspnbxLonEditingDone(Sender: TObject);
  //  procedure AGFspnbxLonClick(Sender: TObject);      // changed to on exit
    procedure AGFspnbxUnitAltEditingDone(Sender: TObject);
    procedure AGFUnitUpdate();
    procedure AGFUnitListUpdate();
    procedure AtkBtnCaptClick(Sender: TObject);
    procedure AtkBtnDefetClick(Sender: TObject);
    procedure AtkBtnCoverClick(Sender: TObject);
    procedure AtkBtnRepairClick(Sender: TObject);
    procedure AtkBtnSrchEnter(Sender: TObject);
    procedure AtkBtnTargClick(Sender: TObject);
    procedure AtkGrpSrch();
    procedure AtkEGroupSrch();
//    procedure FormChangeBounds(Sender: TObject);
    procedure IniPropStorage1SavingProperties(Sender: TObject);
    procedure MenuItem1Click(Sender: TObject);
    procedure MenuItem2Click(Sender: TObject);
    procedure MenuItem3Click(Sender: TObject);
    procedure MenuItem4Click(Sender: TObject);
    procedure MessgLogMouseEnter(Sender: TObject);
    procedure RefuelSheetShow(Sender: TObject);
    procedure RefuelSheetUpdate();
    procedure RflChkBAutoPFinishClick(Sender: TObject);
    procedure RflChkBPauseClick(Sender: TObject);
    procedure RflGrpClick(Sender: TObject);
//    procedure IniPropStorage1StoredValues0Restore(Sender: TStoredValue;
//      var Value: TStoredType);
//    procedure IniPropStorage1StoredValues0Save(Sender: TStoredValue;
//      var Value: TStoredType);
    procedure SeaVesselShow(Sender: TObject);
//    procedure SetLblTopClick(Sender: TObject);
    procedure StatDamTShtShow(Sender: TObject);
    procedure StatDeadTShtShow(Sender: TObject);
    procedure SVListBoxGroupsSelectionChange(Sender: TObject; User: boolean);
    procedure SVListBoxUnitsSelectionChange(Sender: TObject; User: boolean);
    procedure SVSearchGroup();
    procedure AtkLbxEGroupSelectionChange(Sender: TObject; User: boolean);
    procedure AtkLbxEUnitSelectionChange(Sender: TObject; User: boolean);
    procedure AtkLbxFGroupSelectionChange(Sender: TObject; User: boolean);
    procedure AtkLbxFUnitSelectionChange(Sender: TObject; User: boolean);
    procedure DelPath(SGroup:PHCGroup);
    procedure RflBtnQueueClick(Sender: TObject);
    procedure RflBtnTnkrGrpSrchClick(Sender: TObject);
    procedure RflBtnTransClick(Sender: TObject);
    procedure RflChkBFindChange(Sender: TObject);
    //procedure RflChkBResumeChange(Sender: TObject);  now use generic pause button 210512
    procedure RflLbxGroupsSelectionChange(Sender: TObject; User: boolean);
    procedure SqPatrl(SGroup:PHCGroup; PatType:Integer);
    procedure RflLbxUnitFill(PRefuelGroup: PHCGroup);
    procedure RflLbxDetailFill(PRefuelEvent: PHCHarpoonEvent);
    function LatFctGrp(SGroup:PHCGroup):double;
    function isTanker(PTankAirUnit: PHCUnit): Boolean;
    function getLoiterKts(ThisGroup: PHCGroup): short;
    procedure SVLabelGroupClick(Sender: TObject);
    procedure SetGroupSpeedAlt(ThisGroup: PHCGroup; intThrottle: integer; intAltBand: uchar);
    function Side2String(const ThisSide: uchar): string;
    function HCMT2String(const pValHCMT: HCMT): string;       //  20230601
 //   function GroupID2GroupPtr(GrpID: string): PHCGroup;       //  20230607  0622 shift to HCUtils
    procedure PostMessage(strMessageEvent: String);           //  20230607

   private
    { private declarations }
  public
    { public declarations }
    PStartGroup, PSelectedGroup, PselectedEGroup, SGroup1, SGroup2, gPActGroup: PHCGroup;
    PSelectedUnit, PSelectedEUnit, gPActUnit: PHCUnit;
    intGameTime, intGameTimeLeft: long;
    strMySide:string;
    MySide, EnmySide: uchar;
    dblLatFact: Double;
    intPageActive, PatType : Integer;
    HCQueueHarpoonEvent: THCQueueHarpoonEvent;
    NewPtr: THCNewPtr;
    DisposPtr: THCDisposPtr;
    HCdllCheckLatLonDepth: THCdllCheckLatLonDepth;
    del_HarpoonEvent_list: THCdel_HarpoonEvent_list;
    PHCEventHead, PCurrentEvent, PCurrentRefuelEvent: PHCHarpoonEvent;
    PAnnexData: PHCAnnexes_disk;           //   not used ?
    PVCList: PHCVictoryConditions;          // 20230429
    HCannex_id_to_disk_id: THCannex_id_to_disk_id;
    HCdisk_id_to_annex_id: THCdisk_id_to_annex_id;
//    dt_scen_start: PHCDateTimeRec;      not being used
    PThisPlaneDisk: PHCAnnexB1_disk;
    PAllDeadUnit: PHCDeadListItem;
    function CountUnits(const pPGroup: PHCGroup): NativeUInt;
//    procedure WriteTopPosition();

  end;

var
  frmMain: TfrmMain;
  intMLC: integer;

implementation


{$R *.lfm}

{ TfrmMain }

procedure TfrmMain.PostMessage(strMessageEvent: String);
var
  PHarpoonEvent: PHCHarpoonEvent;
  PEventMessage: PHCMessageEvent;
begin
  PHarpoonEvent := NewPtr(SizeOf(THCHarpoonEvent));
  PEventMessage := NewPtr(SizeOf(THCMessageEvent));

// 250806  PHarpoonEvent^.uuid := GenerateUUID;
  PHarpoonEvent^.in_scroll_flag := CFALSE;
  PHarpoonEvent^.DataPtr := PEventMessage;
  PHarpoonEvent^.flag:= MESSAGE_EVENT or TimeNow;;
//  PHarpoonEvent^.time_type := TimeNow;
  PHarpoonEvent^.Next := nil;
  PHarpoonEvent^.ourGroup := nil;
  PHarpoonEvent^.UnitID:= NoUnit;
  PHarpoonEvent^.Time := 0;
//  250806  PEventMessage^.Side := ANYSIDE;
//  PEventMessage^.MessageFormat := Ord(THCTextTypes.ASCII);
  PEventMessage^.Message := strMessageEvent;
  byte(uLazGUIGlobals.PDLLBusy^) := CTRUE;
  HCQueueHarpoonEvent(PHarpoonEvent);
  byte(uLazGUIGlobals.PDLLBusy^) := CFALSE;
end;

//function TfrmMain.GroupID2GroupPtr(GrpID: string): PHCGroup;
//var
//  PThisGroup: PHCGroup;
//  strID: string;
//
//begin
//  PThisGroup:= PStartGroup;
//  strID:= Format('%2.2s',[GrpID]);
//  while (PThisGroup <> nil) do
//    begin
//      if (strID = copy(PThisGroup^.id,1,2)) then
//       break;
//      PThisGroup := PThisGroup^.toSouth;
//    end;
//  if PThisGroup <> Nil then result := PThisGroup;
//end;

function TfrmMain.LatFctGrp(SGroup:PHCGroup):double;
begin
    result:= Cos((90-(SGroup^.sLat/sliver1))/57.2957);
end;

procedure TfrmMain.SVLabelGroupClick(Sender: TObject);
begin
  SVSearchGroup();
end;

procedure TfrmMain.SVSearchGroup();
var
  PHCGThis: PHCGroup;
begin
  PSelectedUnit:= nil;
  SVListBoxUnits.Items.Clear;
  SVListBoxGroups.Items.Clear;
  SVListBoxUnitDetail.Items.Clear;
  SVListBoxGroups.Sorted:= True;
  PHCGThis := PStartGroup;
  while (PHCGThis <> nil) do
    begin
      If (format('%d',[PHCGThis^.side]) = strMySide)
      then
        if gPActGroup = PHCGThis                                //  240624 test
        then
        begin
        SVListBoxGroups.Items.Add(PHCGThis^.id + format(' * %d kts',[PHCGThis^.kts]));
        SVListBoxGroups.ItemIndex:=
            SVListBoxGroups.Items.IndexOf(PHCGThis^.id + format(' * %d kts',[PHCGThis^.kts]));
        end
        else                                                      // end test
        SVListBoxGroups.Items.Add(PHCGThis^.id + format('-%d kts',[PHCGThis^.kts]));
      PHCGThis := PHCGThis^.toSouth;
    end;
  if SVListBoxGroups.ItemIndex > -1
    then SVListBoxGroups.MakeCurrentVisible;    //  240707
end;

procedure TfrmMain.AGFgrpSrch();
var
  PHCGThis: PHCGroup;
begin
  PSelectedUnit:= nil;
  AGFspnbxUnitAlt.Clear;
  AGFlblNOE.Caption:= 'NOE ...';
  AGFlblIFR.Caption:= 'IFR ...';
  AGFspnbxLon.Clear;
  AGFspnbxLat.Clear;
  if HCIsValidGroup(SGroup1, PStartGroup) = CFALSE
  then begin
       AGFbtnGrp1.Caption:='Grp1';
       SGroup1:= nil;
       end;
  if HCIsValidGroup(SGroup2,PStartGroup) = CFALSE
  then  begin
        AGFbtnGrp2.Caption:='Grp2';
        SGroup2:= nil;
        end;
  AGFlbxUnit.Items.Clear;
  AGFlbxGroup.Items.Clear;
  AGFlbxGroup.Sorted:= True;
  PHCGThis := PStartGroup;

  while (PHCGThis <> nil) do
    begin
      If (format('%d',[PHCGThis^.side]) = strMySide)  then      //  why not if PHCGThis^.side = MySide ??
      begin
         AGFlbxGroup.Items.Add(PHCGThis^.id + format(' - %d kts',[PHCGThis^.kts]));
         if gPActGroup = PHCGThis then
         AGFlbxGroup.ItemIndex:=
            AGFlbxGroup.Items.IndexOf(PHCGThis^.id + format(' - %d kts',[PHCGThis^.kts]));
      end;
      PHCGThis := PHCGThis^.toSouth;
    end;
  if AGFlbxGroup.ItemIndex > -1 then
  AGFlbxGroup.MakeCurrentVisible;  //  240701 get selected gorup visible in list test
end;

procedure TfrmMain.AGFlblEClick(Sender: TObject);
begin
  if (AGFlblE.Caption = 'E') then
     AGFlblE.Caption:= 'W'
     else AGFlblE.Caption:= 'E';
end;

procedure TfrmMain.AGFlblSClick(Sender: TObject);
begin
  if (AGFlblS.Caption = 'S') then
     AGFlblS.Caption:= 'N'
     else AGFlblS.Caption:= 'S';
end;

procedure TfrmMain.AGFbtnGrp1Enter(Sender: TObject);
begin
  if PSelectedGroup <> nil then
  begin
  AGFbtnGrp1.Caption:=PSelectedGroup^.id;
  SGroup1:=PSelectedGroup;
  end
  else ShowMessage('no air group selected');
end;

procedure TfrmMain.AGFbtnASWPatEnter(Sender: TObject);
begin
  If (PSelectedGroup <> nil) then
     begin
      PatType:= 0 ;
      SqPatrl(PSelectedGroup, PatType);
     end
     else showmessage('no Group selected');
end;

procedure TfrmMain.AGFbtnCAPPatEnter(Sender: TObject);
begin
  If (PSelectedGroup <> nil) then
       begin
        PatType:= 2 ;
        SqPatrl(PSelectedGroup, PatType);
       end
       else showmessage('no Group selected');
end;

procedure TfrmMain.AGFBtnEWClick(Sender: TObject);
begin
  If (PSelectedGroup <> nil) then
       begin
        PatType:= 3;
        SqPatrl(PSelectedGroup, PatType);
       end
       else showmessage('no Group selected');
end;


procedure TfrmMain.AGFbtnAEWPatEnter(Sender: TObject);
begin
  If (PSelectedGroup <> nil) then
       begin
        PatType:= 1 ;
        SqPatrl(PSelectedGroup, PatType);
       end
       else showmessage('no Group selected');
end;

procedure TfrmMain.AGFbtnGrp2Enter(Sender: TObject);
begin
  if PSelectedGroup <> nil then
  begin
  AGFbtnGrp2.Caption:=PSelectedGroup^.id;
  SGroup2:=PSelectedGroup;
  end
  else ShowMessage('no air group selected');
end;

procedure TfrmMain.AGFbtnMeetEnter(Sender: TObject);
// to join group2 to group1 - group1 remaining
var
   PPathEvent, PNextPathEvent: PHCHarpoonEvent;
   PMessageEvent: PHCMessageEvent;
   PJoinGroupEvent: PHCJoinGroupEvent;
   PFinalPathLeg: PHCPathRec;
   num_joins, joins_done: short;
   PUnit: PHCUnit;
   MessageText: String;

begin      //should test that groups are selected   //  should test they have air unit #1
  if (SGroup1 <> nil) and (SGroup2 <> nil) then
  if (SGroup1^.Units^.Air <> nil) and (SGroup2^.Units^.Air <> nil) then
begin
  if (SGroup1^.Units^.Air^.Throttle=0)
  then  setGroupSpeedAlt(SGroup1, TH_CRUISE, SGroup1^.Units^.AltBand);  //  240722 set to cruise if was loiter/hover
     //then showmessage('group1 mustn''t loiter')
     //else
  begin
  if SGroup1^.track <> nil then
   begin
    DelPath(SGroup1);
    PFinalPathLeg:= NewPtr(SizeOf(THCPathRec));
    PFinalPathLeg^:= THCPathRec(SGroup1^.track^);   //   applies desired destination to combined group
    PFinalPathLeg^.pathEvents:= nil;      //  events pertaining to grp1 must be abandoned and not applied to grp2
   end
  else      //Group1.track = nil
  begin
    PFinalPathLeg:= nil;    // 231002 chasing crash when group1 has no course - seems to work
  SGroup1^.track:= NewPtr(SizeOf(THCPathRec));
  SGroup1^.track^.prev:= nil;
  SGroup1^.track^.next:= nil;
  end;
// group1 track preped and requires destination and events
  MessageText:= 'Groups Joined and Require Orders';

if SGroup2^.Units^.Air^.Throttle <> 0 then //if at loiter leave it alone;
//prep group2 path
begin
      if SGroup2^.track <> nil then
       DelPath(SGroup2)        //prunes path to single leg
    else
      begin
       SGroup2^.track:= NewPtr(SizeOf(THCPathRec));
       SGroup2^.track^.prev:= nil;
       SGroup2^.track^.next:= nil;
      end;
  SGroup2^.track^.pathEvents:= nil;
// group2 path preped now add destination to both groups
  SGroup1^.track^.DestLat:= (SGroup1^.sLat div (SGroup1^.kts + SGroup2^.kts)*SGroup2^.kts + SGroup2^.sLat div (SGroup1^.kts + SGroup2^.kts)*SGroup1^.kts);
  SGroup2^.track^.DestLat:=  SGroup1^.track^.DestLat;
  SGroup1^.track^.DestLon:= (SGroup1^.sLon div (SGroup1^.kts + SGroup2^.kts)*SGroup2^.kts + SGroup2^.sLon div (SGroup1^.kts + SGroup2^.kts)*SGroup1^.kts);
  SGroup2^.track^.DestLon:=  SGroup1^.track^.DestLon;
end
else         //group2 is loitering  = group1 proceeds to group2 current position
begin
  SGroup1^.track^.DestLat:=  SGroup2^.sLat;
  SGroup1^.track^.DestLon:=  SGroup2^.sLon;
end;
//tst
if PFinalPathLeg <> nil then
   if SGroup2^.track <> nil then         // it will be nil if it was loitering
   SGroup2^.track^.next:= PFinalPathLeg
   else SGroup2^.track:= PFinalPathLeg;
//end tst
//now set up path events for group1
  PPathEvent := NewPtr(SizeOf(THCHarpoonEvent));
//  250806  PPathEvent^.uuid := GenerateUUID;
  PPathEvent^.Time:= 0;
  PPathEvent^.in_scroll_flag:= 001;
  PPathEvent^.ourGroup:= SGroup1;
  PPathEvent^.UnitID := SGroup1^.Units^.id;
  // all group1 units will migrate to group2
  num_joins := CountUnits(SGroup1);
  PPathEvent^.flag := JOIN_EVENT or TimeDelta;
//  PPathEvent^.time_type:= TimeDelta;
  PPathEvent^.DataPtr := NewPtr(SizeOf(short) + num_joins * SizeOf(THCJoinGroupEvent));

  short(PPathEvent^.DataPtr^) := num_joins;
  PJoinGroupEvent := PHCJoinGroupEvent(NativeUInt(PPathEvent^.DataPtr) + SizeOf(num_joins));
  PUnit := SGroup1^.Units;
  for joins_done := 0 to num_joins - 1 do
    begin
 //     PJoinGroupEvent^.JoinGroup := SGroup2^.id;    / bandaid fix awaiting GE mods
 //     PJoinGroupEvent^.SplitGroup := SGroup1^.id;
        PJoinGroupEvent^.JoinGroup[0] := SGroup2^.id[0];
        PJoinGroupEvent^.JoinGroup[1] := SGroup2^.id[1];
        PJoinGroupEvent^.JoinGroup[2] := SGroup2^.id[2];
        PJoinGroupEvent^.JoinGroup[3] := SGroup2^.id[3];
        PJoinGroupEvent^.SplitGroup[0] := SGroup1^.id[0];
        PJoinGroupEvent^.SplitGroup[1] := SGroup1^.id[1];
        PJoinGroupEvent^.SplitGroup[2] := SGroup1^.id[2];
        PJoinGroupEvent^.SplitGroup[3] := SGroup1^.id[3];
 //

      PJoinGroupEvent^.UnitID := PUnit^.id;
      PJoinGroupEvent^.Number := PUnit^.Air^.PlatNow;
      PUnit := PUnit^.next;
      PJoinGroupEvent := PHCJoinGroupEvent(NativeUInt(PJoinGroupEvent) + SizeOf(THCJoinGroupEvent));
    end;
  //join event completed  now message event
  PMessageEvent:= NewPtr(SizeOf(THCMessageEvent));
  PMessageEvent^.Message := MessageText;       //
//  PMessageEvent^.Side := ANYSIDE;           //  ddt 20230402 upgrade structure
//  PMessageEvent^.MessageFormat := Ord(THCTextTypes.ASCII);

  PNextPathEvent:= NewPtr(SizeOf(THCHarpoonEvent));
  //  250806  PNextPathEvent^.uuid := GenerateUUID;
  PNextPathEvent^.DataPtr:= PMessageEvent;
  PNextPathEvent^.flag := MESSAGE_EVENT or TimeNow;
//    PNextPathEvent^.time_type:= TimeDelta;
  PNextPathEvent^.Time:= 5;
  PNextPathEvent^.ourGroup:= SGroup2;              //messsage must be applied to Group2 (surviving group) but queued in group1 path
  PNextPathEvent^.UnitID := SGroup2^.Units^.id;
  PNextPathEvent^.in_scroll_flag:= 001;
  PNextPathEvent^.next:= nil;
  PPathEvent^.next:= PNextPathEvent;
  SGroup1^.track^.pathEvents:= PPathEvent;
  showmessage ('groups proceeding to join');
  AGFbtnGrp1.Caption:= 'Grp1';
  AGFbtnGrp2.Caption:= 'Grp2';            //  240628 reset here so can leave at search so as to allow GE selections to have effect for join
  end;
end      //end tested groups selected
else
   showmessage('two air groups must be selected');
end;

procedure TfrmMain.AGFBtnPauseClick(Sender: TObject);
begin
  If AGFBtnPause.Caption = 'Pause' then
  begin
  Byte(PDLLBusy^) := CTRUE;
  AGFBtnPause.Caption:= 'Resume';
  AtkBtnPause.Caption:= 'Resume';
  RflBtnPause.Caption:= 'Resume';
  AGFgrpSrch();
  end
  else
  begin
    Byte(PDLLBusy^) := CFALSE;
    AGFBtnPause.Caption:= 'Pause';
    AtkBtnPause.Caption:= 'Pause';
    RflBtnPause.Caption:= 'Pause';
//    PageControl1.ActivePageIndex:= intPageActive;  //  231008    page shift  240807 got tired of this
  end;
end;

//procedure TfrmMain.AGFBtnReprClick(Sender: TObject);
//var
//  SGroup1DP: short;
//  SG1Unit: PHCUnit;
//  SG2Unit: PHCUnit;
//begin                       // need some proximity test
//  SG1Unit:= SGroup1^.Units;
//  if SG1Unit^.Air <> nil
//    then SGroup1DP:= (50 * SG1Unit^.Air^.PlatNow)    // if air units value differently
//    else SGroup1DP:= SG1Unit^.OrigDp;
//
//  //  SGroup1DP:= SG1Unit^.OrigDp;
//    while SG1Unit^.next <> nil do          // look at rest of group
//    begin
//       SG1Unit:= SG1Unit^.next;
//       if SG1Unit^.Air <> nil
//         then SGroup1DP:= SGroup1DP + (50 * SG1Unit^.Air^.PlatNow)
//         else SGroup1DP:= SGroup1DP + SG1Unit^.OrigDp;
//    end;
//
//  SG2Unit:= SGroup2^.Units;        // now look at group to be repaired
//  while (SG2Unit^.CurrDp = 0) and (SG2Unit^.next <> nil)     // if unit not damaged look at next
//  do     SG2Unit:= SG2Unit^.next ;                // shold now have first damaged unit
//
//  if (SG2Unit^.CurrDp <> 0)          // check it's damged
//  then  SG2Unit^.CurrDp:= (SG2Unit^.CurrDp) - (SGroup1DP);
//  if SG2Unit^.CurrDp < 1 then           //full repaired
//  begin
//       SG2Unit^.CurrDp:= 0;
//       ShowMessage(Format('unit %0.3d repaired',[SG2Unit^.id]));
//  end
//  else ShowMessage(Format('Unit: %0.3d Damage: %d', [SG2Unit^.id, SG2Unit^.CurrDp]));        //  part repair
//
//end;

//procedure TfrmMain.AGFBtnSpAltClick(Sender: TObject);
//var IntAlt, IntThrot: integer;
//begin
//  if PSelectedGroup <> nil then
//  begin
//    IntAlt := AGFSpnBxAlt.Value + 11;
//    case AGFSpnBxThr.Value of
//    0: IntThrot:= 0;
//    1: IntThrot:= 0 + AGFSpnBxAlt.Value;
//    2: IntThrot:= 3 + AGFSpnBxAlt.Value;
//    3: IntThrot:= 6 + AGFSpnBxAlt.Value;
//    end;
//    SetGroupSpeedAlt(PSelectedGroup, IntThrot, IntAlt);
//  end;
//end;

function TfrmMain.HCMT2String(const pValHCMT: HCMT): string;       //  20230601
//var
//  strHCMT: string;            not used?

begin
  case pValHCMT  of
       MT_AC:         result := 'Plane';
       MT_HELO:       result := 'Helo';
       MT_CARRIER:    result := 'Carrier';
       MT_SHIP:       result := 'Surface Vessel';
       MT_SUB:        result := 'Submarine';
       MT_AIRFIELD:   result := 'Airfield';
       MT_PORT:       result := 'Port';
       MT_BASE:       result := 'Port & Airfield';
       MT_CITY:       result := 'City';
       MT_MISSILE:    result := 'Missile';
       MT_TORP:       result := 'Torpedo';
       MT_SAMFIXED:   result := 'AD Fixed';
       MT_SAMMOBILE:  result := 'AD Mobile';
       MT_CONVOY:     result := 'Convoy';
       MT_ARMORED:    result := 'Armored';
       MT_REF:        result := 'Reference Point';
       MT_WEATHER:    result := 'Weather Feature';
       MT_VAR_ST_PT:  result := 'VariableStartPoint';
       MT_NUKEAREA:   result := 'Nuke Area';
      else         result := 'Unknown';
  end;
end;

procedure TfrmMain.AGFBtnSplitClick(Sender: TObject);
var
  PSplitUnitEvent: PHCSplitGroupEvent;
  PThisHCEvent: PHCHarpoonEvent;

begin
  If AGFlbxUnit.ItemIndex <> -1 then
      begin

      PThisHCEvent:= NewPtr(SizeOf(THCHarpoonEvent));
//  250806      PThisHCEvent^.uuid := GenerateUUID;
      PThisHCEvent^:= THCHarpoonEvent(PHCEventHead^);  //PHCThisHCEvent content 'old' event-head content
      PHCEventHead^.next:= PThisHCEvent;               //now put new event data into event-head content
      PHCEventHead^.DataPtr:= NewPtr(SizeOf(THCSplitGroupEvent) + SizeOf(uchar));
      uchar(PHCEventHead^.DataPtr^):= 1;                  // only splitting one unit

       PSplitUnitEvent:= PHCEventHead^.DataPtr + SizeOf(uchar);
       PSplitUnitEvent^.UnitID[0][0]:= PSelectedUnit^.id;
       PSplitUnitEvent^.UnitID[0][1]:= AGFSpnBxSplt.Value;

      PHCEventHead^.flag := SPLIT_EVENT or TimeNow;
//      PHCEventHead^.time_type := TimeNow;
      PHCEventHead^.ourGroup:= PSelectedGroup;
      PHCEventHead^.UnitID := PSelectedUnit^.id;
      PHCEventHead^.in_scroll_flag:= 000;
      PHCEventHead^.Time:= 000;       // is this needed?
      AGFgrpSrch();  // ddt 20180427 need to update unit list but event must happen first so reset grouplist
       end
  else ShowMessage('No Unit Selected');
end;


procedure TfrmMain.AGFbtnSrchEnter(Sender: TObject);
begin
  AGFgrpSrch();
end;

//procedure TfrmMain.AGFbtnStripClick(Sender: TObject);
//begin
//  if PSelectedGroup <> nil then
//  begin
//  DelPath(PSelectedGroup);
//  end
//  else
//  ShowMessage('First Select a Group');
//end;

procedure TfrmMain.AGFlbxGroupSelectionChange(Sender: TObject; User: boolean);
begin
  AGFUnitListUpdate;
end;

procedure TfrmMain.AGFUnitListUpdate();
var
  strSel: String;
  PHCGThis: PHCGroup;
  PHCUThis: PHCUnit;
begin
  PSelectedUnit:=nil;
  PSelectedGroup:=nil;
  AGFspnbxUnitAlt.Clear;
  AGFlblNOE.Caption:= '.. ..';
  AGFlblIFR.Caption:= '.. ..';
  AGFspnbxLon.Clear;
  AGFspnbxLat.Clear;
  strSel := copy(AGFlbxGroup.Items[AGFlbxGroup.ItemIndex],1,2);
  PHCGThis := PStartGroup;
  while (PHCGThis <> nil) do
  begin
    if (strSel = copy(PHCGThis^.id,1,2)) then
       break;
    PHCGThis := PHCGThis^.toSouth;
  end;
  if (PHCGThis=nil) then //the selected group is no longer valid
     begin                 //and list must be reset
     AGFgrpSrch();
     PHCGThis :=PStartGroup;
     end;                                //list reset
  PSelectedGroup:=PHCGThis;
  AGFlbxUnit.Items.Clear;
  PHCUThis := PHCGThis^.Units;
  while (PHCUThis <> nil) do
  begin
    if (PHCUThis^.Air<>nil) then
    begin
     AGFlbxUnit.Items.Add(Format('%0.3d: %d-%s AltBnd %2.2d, Fuel: %d Mins, BWR: %.0n AA, %.0n ASu, %.1n AS',
       [PHCUThis^.id, PHCAirRec(PHCUThis^.Air)^.PlatNow, PHCUThis^.name, PHCUThis^.AltBand,
       ((PHCUThis^.Air^.Endurance) div (4 * 60)),
       (PHCUThis^.BestAAWRange)/ 10,(PHCUThis^.BestASuWRange)/ 10,(PHCUThis^.BestAsWRange)/ 10]));  //ddt 20182604 added endurance readout
     if (PHCUThis^.air^.FuelToOffload > 0) then
     AGFlbxUnit.Items.Add(Format('%0.3d unit Tanker(s) with %d refuel available',[PHCUThis^.id, PHCUThis^.air^.FuelToOffload]));
    end;
    PHCUThis := PHCUThis^.next;
  end;
end;

procedure TfrmMain.AGFlbxUnitSelectionChange(Sender: TObject; User: boolean);
var
  strSel: String;
  PHCUThis: PHCUnit;
begin
  strSel := AGFlbxUnit.Items[AGFlbxUnit.ItemIndex];
  PHCUThis := PSelectedGroup^.Units;
  while (PHCUThis <> nil) do
  begin
    if (Pos(Format('%0.3d', [PHCUThis^.id]), strSel) = 1) then
    begin
      break;
    end;
    PHCUThis := PHCUThis^.next;
  end;
  PSelectedUnit := PHCUThis;
//  AGFspnbxUnitAlt.Value:=11;        ddt 20221207  seems not needed
  AGFspnbxUnitAlt.Value:= PSelectedUnit^.AltBand;
  if ((HCAnnexHelper.HCGetAnnexB1(PSelectedUnit^.Annex)^.UnitFlags AND UF_B1_NOE) > 0)    // ddt 051122 new feature
    then    AGFlblNOE.Caption:= 'NOE YES'
    else    AGFlblNOE.Caption:= 'NOE NO';

  if ((HCAnnexHelper.HCGetAnnexB1(PSelectedUnit^.Annex)^.UnitFlags AND UF_B1_refuelcapable) > 0)  // ddt 230126  new feature
    then    AGFlblIFR.Caption:= 'IFR YES'
    else    AGFlblIFR.Caption:= 'IFR NO';

  dblLatFact:= Cos((90-(PSelectedUnit^.sLat/sliver1))/57.2957);   //ddt 20221209  offset brought back

  if (PselectedUnit^.sLon > PSelectedGroup^.sLon) then
  begin
  AGFspnbxLon.Value:=(PselectedUnit^.sLon - PSelectedGroup^.sLon)/ uHCDefines.NM1*dblLatFact;
  AGFlblE.Caption:= 'E';
  end
  else
  begin
  AGFspnbxLon.Value:=(PSelectedGroup^.sLon - PselectedUnit^.sLon)/ uHCDefines.NM1*dblLatFact;
  AGFlblE.Caption:= 'W';
  end;

  if (PselectedUnit^.sLat > PSelectedGroup^.sLat) then
  begin
   AGFspnbxLat.Value:=(PselectedUnit^.sLat - PSelectedGroup^.sLat)/ uHCDefines.NM1;
   AGFlblS.Caption:= 'S';
  end
  else
  begin
  AGFspnbxLat.Value:=(PSelectedGroup^.sLat - PselectedUnit^.sLat)/ uHCDefines.NM1;
  AGFlblS.Caption:= 'N';
  end;

end;

procedure TfrmMain.AGFUnitUpdate();
begin
  AGFlbxUnit.Items.Strings[AGFlbxUnit.ItemIndex]:=Format('%0.3d: %d-%s AltBnd %2.2d, Fuel: %d Mins, BWR: %.0n AA, %.0n ASu, %.1n AS',
  [PSelectedUnit^.id, PHCAirRec(PSelectedUnit^.Air)^.PlatNow, PSelectedUnit^.name, PSelectedUnit^.AltBand,
  ((PSelectedUnit^.Air^.Endurance) div (4 * 60)), (PSelectedUnit^.BestAAWRange)/ 10,
  (PSelectedUnit^.BestASuWRange)/ 10,(PSelectedUnit^.BestAsWRange)/ 10]);         // ddt added endurance data
end;

procedure TfrmMain.AtkBtnCaptClick(Sender: TObject);
var
valDef, valDefTot, valAtk, valAtkTot, AtkSz, DefSz,RdmNum: real;
intC, AtckGType: integer;
PThisEUnit, PThisFUnit: PHCUnit;
PMnt: PHCMntRec;
PMissDisk: PHCAnnexD1_disk;
PAToGDisk: PHCAnnexG1_disk;
PGunDisk:  PHCAnnexC1_disk;
PMultiMntDisk: PHCAnnexZ2_disk;
begin

  If (PSelectedEGroup = nil) or (PSelectedGroup = Nil)
  then
  ShowMessage('Friendly group and Enemy surface group or establishment must be selected')
  else
    if  (PSelectedEGroup^.gtype <> MT_SHIP) and
    (PSelectedEGroup^.gtype <> MT_CARRIER)  and
    (PSelectedEGroup^.gtype <> MT_AIRFIELD)  and
    (PSelectedEGroup^.gtype <> MT_BASE ) and
    (PSelectedEGroup^.gtype <> MT_PORT) and
    (PSelectedEGroup^.gtype <> MT_SAMFIXED)
    then
    ShowMessage('Enemy vessel or establishment must be selected')
    else
      if PSelectedEGroup^.maxspeed >= PSelectedGroup^.maxspeed
        then
        ShowMessage('Enemy is too fast to capture')
        else
    begin
     valDef:= 0;
     valDefTot:=0;
     valAtk:= 0;
     valAtkTot:= 0;
     AtkSz:= 0;
     DefSz:= 0;
     PThisFUnit:= PSelectedGroup^.Units;
     PThisEUnit:= PSelectedEGroup^.Units;
     AtckGType:= PSelectedGroup^.gtype;

while (PThisEUnit <> nil) do              //  look at each unit of enemy group for defence
     begin
     DefSz:= DefSz + PThisEUnit^.OrigDp - PThisEUnit^.CurrDp;  // for each unit add its size in DP
       if PThisEUnit^.Mnts <> nil then            // if this unit has mounts look at each
       begin
          PMnt:= PThisEUnit^.Mnts;
          while PMnt <> nil do
               begin
               if (PMnt^.Damage = 0) and (PMnt^.WepId <> Invalid_ID)  then                      //  ignore damaged mounts
                  case HCAnnexHelper.HCAnnexShift(PMnt^.WepId) of
                  MISSILE_FLAG:
                      begin
                      PMissDisk:= HCAnnexHelper.HCGetAnnexD1(PMnt^.WepId);
                      valDef := valDef + (PMissDisk^.AirPh * 5 * PMnt^.Ammo_c[0]);    // say dp val for aam = 5
                      if AtckGType <> ShortInt('A') then
                       valDef := valDef + (PMissDisk^.SurfPh * PMissDisk^.Dp * PMnt^.Ammo_c[0]);
                      end;
                  GUN_FLAG:
                      begin
                        PGunDisk:= HCAnnexHelper.HCGetAnnexC1(PMnt^.WepId);
                        valDef:= valDef + (2 * PGunDisk^.AirPh * PMnt^.Ammo_c[0]);  // say dp val for gun = 2
                        if AtckGType <> ShortInt('A') then
                         valDef:= valDef + (PGunDisk^.Dp * PGunDisk^.SurfFarPh * PMnt^.Ammo_c[0]);
                      end;
                  MULTI_FLAG:
                       begin
                         PMultiMntDisk:= HCAnnexHelper.HCGetAnnexZ2(PMnt^.WepId);
                         intC:= 0;
                         while (PMultiMntDisk^.WepQty[intC] > 0) do
                         begin
                           case (HCAnnexHelper.HCAnnexShift(PMultiMntDisk^.WepId[intC])) of
                           GUN_FLAG:
                             begin
                             PGunDisk:= HCAnnexHelper.HCGetAnnexC1(PMultiMntDisk^.WepId[intC]);
                             valDef:= valDef + round(PGunDisk^.SurfFarPh * PGunDisk^.Dp * PMnt^.Ammo_c[intC]);      //ammo left
                             end;
                           MISSILE_FLAG:
                             begin
                             PMissDisk:= HCAnnexHelper.HCGetAnnexD1(PMultiMntDisk^.WepId[intC]);
                             valDef:= round(valDef + (PMissDisk^.SurfPh * PMissDisk^.Dp * PMnt^.Ammo_c[intC]));
                             end;
                           else ; //221123  close un referenced cases
                            end; // end case this weapon of multi-mnt
//ShowMessage('DefUnit#/Mlt-MntWep#:' + IntToStr(PThisEUnit^.id) + '/ ' + IntToStr(intC) + ': ' + FloatToStr(valDef));
                         intC:= intC + 1;
                         valDefTot := valDefTot + valDef;
                         valDef := 0;
                         end; //end this multi mount
                       end;// end case = multi-mount
                  else;  //221123  close for all cases
                  end;        // end case this weapon
                  PMnt:= PMnt^.next;
//ShowMessage('DefenceMntUnit#: ' + FloatToStr(PThisEUnit^.id) + ': ' + FloatToStr(valDef));
                  valDefTot := valDefTot + valDef;
                  valDef := 0;
               end;      // finished look at mounts this unit
       end;   //finished looking at all mounts this units
//ShowMessage('AccumDefenceUnit: ' + FloatToStr(PThisEUnit^.id) + ': ' + FloatToStr(valDefTot));
//     valDefTot:= valDefTot + valDef;      221123 movedd to indiv mounts
     valDef:= 0;
     PThisEUnit:= PThisEUnit^.next;
     end;   // looked at all defending units

//ShowMessage('FinalDefence: ' + FloatToStr(valDefTot+ DefSz));      // finished looking at group defence

     valAtk:= 0;
//      if (PSelectedGroup^.gtype = ShortInt('A')) then          //calc valatk for air groups
//      begin
//      while (PThisFUnit <> nil) do
//      begin
////     valAtk:= valAtk + ((PThisFUnit^.Air^.Endurance div 1000) * PThisFUnit^.Air^.PlatNow);  discarded method now have access to annex
//      if PThisFUnit^.Mnts <> nil then                     //  airunit look at mounts
//      begin
//        PMnt:= PThisFUnit^.Mnts;
//        while PMnt <> nil do
//        begin
//          case HCAnnexHelper.HCAnnexShift(PMnt^.WepId) of
//          MISSILE_FLAG:
//              begin
//              PMissDisk:= HCAnnexHelper.HCGetAnnexD1(PMnt^.WepId);
//              valAtk:= valAtk + round(PMissDisk^.Dp * PMissDisk^.SurfPh * PMnt^.Ammo_c[0]);   //  maybe should ignore prob of hit
//              end;
//          ATOG_FLAG:
//              begin
//              PAToGDisk:= HCAnnexHelper.HCGetAnnexG1(PMnt^.WepId);
//              valAtk:= round(valAtk + (PAToGDisk^.Dp * PAToGDisk^.Ph * PMnt^.Ammo_c[0]));
//              end;
//          end;
//ShowMessage('AttackAirMount: ' + FloatToStr(valAtk));
//          PMnt:= PMnt^.next;
//        end;
//      end;     //looked at all mounts this air unit
//ShowMessage('AttckAirUnit: ' + FloatToStr(valAtk));
//      PThisFUnit:= PThisFUnit^.next;
//      end;      // looked at all units this air group
//
//      end     // end if air group
//
//               //      if PSelectedGroup^.gtype = 'S' then
//      else       // not an air group attacker = surface or mobile
//     now look at attacker group
      while (PThisFUnit <> nil) do
      begin
      AtkSz:= AtkSz + (PThisFUnit^.OrigDp - PThisFUnit^.CurrDp);
      if PThisFUnit^.Mnts <> nil then
      begin
        PMnt:= PThisFUnit^.Mnts;
        while PMnt <> nil do
        begin
        if (PMnt^.Damage = 0) and (PMnt^.WepId <> Invalid_ID) then       // ignore damaged mounts  and 'funny' mounts
          case HCAnnexHelper.HCAnnexShift(PMnt^.WepId) of
          MISSILE_FLAG:
              begin
              PMissDisk:= HCAnnexHelper.HCGetAnnexD1(PMnt^.WepId);
              valAtk:= valAtk + round(PMissDisk^.Dp * PMissDisk^.SurfPh * PMnt^.Ammo_c[0]);
              end;
          ATOG_FLAG:
              begin
              PAToGDisk:= HCAnnexHelper.HCGetAnnexG1(PMnt^.WepId);
              valAtk:= valAtk + round(PAToGDisk^.Dp * PAToGDisk^.Ph * PMnt^.Ammo_c[0]);
              end;
          GUN_FLAG:
              begin
              PGunDisk:= HCAnnexHelper.HCGetAnnexC1(PMnt^.WepId);
              valAtk:= valAtk + round(PGunDisk^.SurfFarPh * PGunDisk^.Dp * PMnt^.Ammo_c[0]);
              end;
          MULTI_FLAG:
              begin
                PMultiMntDisk:= HCAnnexHelper.HCGetAnnexZ2(PMnt^.WepId);
                intC:= 0;
                while (PMultiMntDisk^.WepQty[intC] > 0) do
                begin
                  case (HCAnnexHelper.HCAnnexShift(PMultiMntDisk^.WepId[intC])) of
                  GUN_FLAG:
                    begin
                    PGunDisk:= HCAnnexHelper.HCGetAnnexC1(PMultiMntDisk^.WepId[intC]);
                    valAtk:= valAtk + round(PGunDisk^.SurfFarPh * PGunDisk^.Dp * PMnt^.Ammo_c[intC]);
                    end;
                  MISSILE_FLAG:
                    begin
                    PMissDisk:= HCAnnexHelper.HCGetAnnexD1(PMultiMntDisk^.WepId[intC]);
                    valAtk:= round(valAtk + (PMissDisk^.SurfPh * PMissDisk^.Dp * PMnt^.Ammo_c[intC]));
                    end;
                  else;        // 221123
                  end; // end case this weapon of multi-mnt
//ShowMessage('AtkUnit#/Mlt-MntWep#:' + IntToStr(PThisFUnit^.id) + '/ ' + IntToStr(intC) + ': ' + FloatToStr(valAtk));
                intC:= intC + 1;
                valAtkTot:= valAtkTot + valAtk;
                valAtk:= 0;
                end; //end multi mount
              end;// end case = multi-mount
          else; //  221123  case hanger ons
          end;
//ShowMessage('AtkMntUnit#:' + IntToStr( PThisFUnit^.id) +  '-' + FloatToStr(valAtk));
          valATkTot:= valAtkTot + valAtk;
          valAtk:= 0;
          PMnt:= PMnt^.next;
        end;                  // end look at this mount
      end;     //  end test unit had weapon mounts
      valAtkTot:= valAtkTot + valAtk;
      valAtk:= 0;
//ShowMessage('accumAtkUnit#:' + IntToStr(PThisFUnit^.id) + '- ' +  FloatToStr(valAtkTot));
      PThisFUnit := PThisFUnit^.next;
      end;  // looked at all units
//ShowMessage('AttackFinalRaw: ' + FloatToStr(valAtkTot));
      valAtk := valAtkTot;          // valAtk now used to input to counter attack if needed
      valAtkTot := round(valAtkTot / (1 + (( abs(PSelectedGroup^.sLat - PSelectedEGroup^.sLat) +
             abs(PSelectedGroup^.sLon - PSelectedEGroup^.sLon)) / uHCDefines.NM10) ));    // allow for range
          //will need code for mobile units and cross check legit targets all atk group types
//ShowMessage('AttckFinalwithDist: ' + FloatToStr(valAtkTot));        // finished looking at attack group

      if valAtkTot < 0 then
     valAtkTot := 0;
      RdmNum := random;
//ShowMessage('attack-Rdm/Prob: '+ FloatToStr(RdmNum) + '/ ' + FloatToStr( valAtkTot / (valAtkTot + valDefTot + DefSz)));
    If RdmNum < valAtkTot / (valAtkTot + valDefTot + Defsz) then
     begin
      PSelectedEGroup^.side:= PSelectedGroup^.side;
      ShowMessage('Attack succeeded');
     end
    else            // look at counter attack
      begin
      PSelectedEGroup^.side:= EnmySide;         // if it was a neutral it is now an enemy
      RdmNum := random;
//ShowMessage('counterattack-Rdm/Prob: ' +FloatToStr(RdmNum) + '/ ' + FloatToStr(valDefTot/(valAtk + AtkSz +  valDefTot)));

      If RdmNum < valDefTot / (valAtk + AtkSz + valDefTot) then      // should there be a distance penalty?
        begin
         PSelectedGroup^.side:= PSelectedEGroup^.side;
         ShowMessage('Enemy counter attack succeeded');      //  new code 221117
         If PSelectedGroup^.Units^.Air <> Nil
           then
           begin PSelectedGroup^.Units^.Air^.HomeUnit:= PSelectedEGroup^.Units;
                 PSelectedGroup^.Units^.Air^.Throttle:= 0;
           end
           else PSelectedGroup^.speed:= 0;
        end
      else
      if PSelectedEGroup^.side = uchar(2) then ShowMessage('You''ve created a new enemy');
      end
    end;
end;

procedure TfrmMain.AtkBtnDefetClick(Sender: TObject);
begin
  if PSelectedEGroup <> Nil
  then
  case PSelectedEGroup^.side of
  0,1: PSelectedEGroup^.side:= uchar(2);
  2: PSelectedEGroup^.side:= EnmySide;
  end
  else
  begin
    if PSelectedGroup <> Nil then
    PSelectedGroup^.side:= uchar(2)
    else ShowMessage('Select Enemy Group');
  end;

end;

procedure TfrmMain.AtkBtnCoverClick(Sender: TObject);
var
 PNewPathLeg: PHCPathRec;
 PPathEvent1: PHCHarpoonEvent;
 PEndMessage: PHCMessageEvent;
 TargetLat, TargetLon: integer;

begin
  if (PSelectedGroup <> nil) and (PSelectedEUnit <> nil)  then
  if (PSelectedGroup^.Units^.Air <> nil) and (PSelectedEUnit^.Group <> nil) then //  240807 should I check EUnit is still valid?
  begin
//  showmessage('Beta feature: send attacker to EUnit');
  if (PSelectedEUnit^.showing[0] <> 0) then
  begin
    if PSelectedEUnit^.UncertaintyRegion[0] = nil then
    begin
     TargetLat := PSelectedEUnit^.sLat;
     TargetLon := PSelectedEUnit^.sLon;
    end else
    begin
     TargetLat :=  (PSelectedEUnit^.UncertaintyRegion[0]^.sLat[0]
     + PSelectedEUnit^.UncertaintyRegion[0]^.sLat[2]) div 2;
     TargetLon := (PSelectedEUnit^.UncertaintyRegion[0]^.sLon[0]
     + PSelectedEUnit^.UncertaintyRegion[0]^.sLon[2]) div 2;
    end;
      if PSelectedgroup^.track = nil then
     begin
      PNewPathLeg := NewPtr(SizeOf(THCPathRec));
      PSelectedGroup^.track:= PNewPathLeg;
      PSelectedGroup^.track^.next:= nil;
      PSelectedGroup^.track^.prev:= nil;
     end
     else DelPath(PSelectedGroup);
      PSelectedGroup^.track^.DestLat:= TargetLat;
      PSelectedGroup^.track^.DestLon:= TargetLon;

     PEndMessage:= NewPtr(SizeOf(THCMessageEvent));
     PEndMessage^.Message:= 'Obtained position, require orders';
 //    PEndMessage^.Side := ANYSIDE;           //  ddt 20230402 upgrade structure
//  PEndMessage^.MessageFormat := Ord(THCTextTypes.ASCII);
     PPathEvent1:= NewPtr(SizeOf(THCHarpoonEvent));
//     PPathEvent1^.uuid := GenerateUUID;
     PPathEvent1^.DataPtr:= PEndMessage;
     PPathEvent1^.flag := MESSAGE_EVENT or TimeNow;
//       PPathEvent1^.time_type:= TimeDelta;
     PPathEvent1^.in_scroll_flag:= 001;
     PPathEvent1^.ourGroup:= PSelectedGroup;
     PPathEvent1^.Time:= 0;
     PPathEvent1^.UnitID := PSelectedGroup^.Units^.ID;
     PPathEvent1^.next:= nil;
     PSelectedGroup^.track^.pathEvents:= PPathEvent1;

      if PSelectedGroup^.Units^.Air <> nil then     //if air group check for loiter/hover
        begin
          if PSelectedGroup^.Units^.Air^.Throttle = 0 then
          begin
//          PSelectedGroup^.Units^.Air^.Throttle:= 1;      // ddt 20181501 now have setgroupspeedalt procedure
//          PSelectedGroup^.kts:= (2*(PSelectedGroup^.maxspeed)) div 3;
          SetGroupSpeedAlt(PSelectedGroup, 1, 12);      // cruise low altitude
          showmessage('proceeding to target location');
          end
          else showmessage('vectoring to target location');
        end
          else showmessage('expected an airgroup');        //diagnostics
//    end
//    else ShowMessage('exact position of enemy unit unknown');   // develop, go to centre of uncertainty then do asw patrol
  end
  else ShowMessage('lost contact');
  end
  else ShowMessage('Friendly Air Group and Enemy Unit must be selected');
end;

procedure TfrmMain.AtkBtnRepairClick(Sender: TObject);
var
 lIntGrpRepVal: Integer;
 lPThisRepUnit: PHCUnit;
begin
  if (PSelectedGroup<>nil) and (PSelectedUnit<>nil)
  then
  begin
    if (HCMTIsVessel(PSelectedGroup^.gtype) = True)
       and (PSelectedUnit^.CurrDp > 0)
      then
      begin
           lPThisRepUnit:= PSelectedGroup^.Units;
           lIntGrpRepVal:= 0;
        while  lPThisRepUnit<>nil do
        begin

           lIntGrpRepVal:= lIntGrpRepVal + lPThisRepUnit^.OrigDp - lPThisRepUnit^.CurrDp;
           lPThisRepUnit:= lPThisRepUnit^.next;
        end;
        if PSelectedUnit^.CurrDp < lIntGrpRepVal
           then PSelectedUnit^.CurrDp:= 0
           else PSelectedUnit^.CurrDp:= PSelectedUnit^.CurrDp - lIntGrpRepVal;
      end
    else
    if (HCMTIsAir(PSelectedGroup^.gtype) = True)
    and (PSelectedUnit^.CurrDp>0)
    then
    begin
      lPThisRepUnit:= PSelectedGroup^.Units;
      lIntGrpRepVal:= 0;
      while lPThisRepUnit<>nil do
      begin
         lIntGrpRepVal:= lINtGrpRepVal + 10;
         lPThisRepUnit:= lPThisRepUnit^.next;
      end;
      if PSelectedUnit^.CurrDp < lIntGrpRepVal
           then PSelectedUnit^.CurrDp := 0
           else PSelectedUnit^.CurrDp:= PSelectedUnit^.CurrDp - lIntGrpRepVal
    end;
    //  time to do repairs

  end
  else
  PostMessage('FGroup and FUnit must be selected');
end;

procedure TfrmMain.AtkBtnSrchEnter(Sender: TObject);
begin
  AtkGrpSrch();
  AtkEGroupSrch();
end;

procedure TfrmMain.AtkBtnTargClick(Sender: TObject);

begin
  if (PSelectedUnit <>nil) and (PSelectedEUnit <> nil) then
  if (PSelectedUnit^.Missile <> nil) then
  //if (AtkLblMG.Caption = '* CanReTarget')       //  231018 rejig to include torp and missile
  //then         // 231018 add for torp
    begin
     PSelectedUnit^.Missile^.targetUnit:= PSelectedEUnit;
     PSelectedUnit^.Missile^.tLat:=PSelectedEUnit^.sLat;
     PSelectedUnit^.Missile^.tLon:=PSelectedEUnit^.sLon;
     AtkBtnTarg.Caption:= format('%s - %0.3d', [PHCGroup(PHCUnit(PSelectedUnit^.Missile^.targetUnit)^.Group)^.id,
      PHCUnit(PSelectedUnit^.Missile^.targetUnit)^.id]);
    end;
end;

procedure TfrmMain.AtkGrpSrch();
var
  PHCGThis: PHCGroup;
//  intCntUnkn: Integer;
begin
  PSelectedUnit:= nil;
  PSelectedGroup:= nil;
  SGroup1:= nil;
  SGroup2:= nil;
  AtkLbxFGroup.Items.Clear;
  AtkLbxFUnit.Items.Clear;
  AtkLbxFGroup.Sorted:= True;
  AtkLblMG.Caption:= 'NotUsed';
  PHCGThis := PStartGroup;

  while (PHCGThis <> nil) do
    begin
      If (format('%d',[PHCGThis^.side]) = strMySide)  then
      begin
      AtkLbxFGroup.Items.Add(PHCGThis^.id + format('-%d kts',[PHCGThis^.kts]));
      if gPActGroup = PHCGThis then
         AtkLbxFGroup.ItemIndex:=
           AtkLbxFGroup.Items.IndexOf(PHCGThis^.id + format('-%d kts',[PHCGThis^.kts])); //  240625
       end;
      PHCGThis := PHCGThis^.toSouth;
    end;    //  done friendlies
  if AtkLbxFGroup.ItemIndex > -1
     then  AtkLbxFGroup.MakeCurrentVisible;  //  make any selected Fgroup visible

//  PHCGThis := PStartGroup;    //  might want to split E and My side search ?? for auto selection from GE
//  AtkLbxEGroup.Clear;
////  AtkLbxEGroup.Items.NameValueSeparator:= '_';        //  now do Enemy groups
////  intCntUnkn:= 1;
//while (PHCGThis <> nil) do
//    begin
//      if (PHCGThis^.side <> MySide) and (PHCGThis^.showing <> 0)
//      then
//      begin if ((PHCGThis^.showing and SideKnown) > 0)  // PHCGThis^.side <> UNKNOWNSIDE try alternate side known test
//            then
//            begin
//              AtkLbxEGroup.Items.Add(PHCGThis^.id);
//              if (PHCGThis = gPActGroup)
//              then
//              AtkLbxEGroup.ItemIndex:= AtkLbxEGroup.Items.IndexOf(PHCGThis^.id)
//              else
//              if gPActUnit <> nil then if (PHCGThis = gPActUnit^.Group) then
//                AtkLbxEGroup.ItemIndex:= AtkLbxEGroup.Items.IndexOf(PHCGThis^.id);
//
//            end    //  240626  selecting group from GE active group or unit
//            else    //  side is unknown
//            begin     //  240708  unknown contact
//             if gPActUnit = nil      //  no ge ActUnit
//             then
//               AtkLbxEGroup.Items.Add('00')
//             else
//             begin//  There is GE ActUnit
//               if (PHCGThis = gPActUnit^.Group)
//               then                                 //  and it is in this group
//                begin
//                  AtkLbxEGroup.Items.Add('001');
//                  PSelectedEGroup:= PHCGThis;        //  240708 trying earlier call
//                  AtkLbxEGroup.ItemIndex:= AtkLbxEGroup.Items.IndexOf('001');
//                end
//                else       //  there is ActUnit but not this group
//                 AtkLbxEGroup.Items.Add('00');
//               end;  //  240712  missed the ;
//            end;       // end  unknown contact
//      end;
//      PHCGThis := PHCGThis^.toSouth;
//    end;   // finished Enemy group listing
//    if AtkLbxEGroup.ItemIndex > -1
//       then AtkLbxEGroup.MakeCurrentVisible;  //  240701 test
end;

procedure TfrmMain.AtkEGroupSrch();
var
    PHCGThis: PHCGroup;

begin
  PSelectedEGroup:= nil;       //  this will be set if there is a GE selected EUnit
  AtkLbxEUnit.Items.Clear;
  AtkLbxEGroup.Items.Clear;
  AtkLbxEGroup.Sorted:= True;  //  240715

  PHCGThis := PStartGroup;    //  might want to split E and My side search ?? for auto selection from GE
  AtkLbxEGroup.Clear;
//  AtkLbxEGroup.Items.NameValueSeparator:= '_';        //  now do Enemy groups
//  intCntUnkn:= 1;
while (PHCGThis <> nil) do
    begin
      if (PHCGThis^.side <> MySide) and (PHCGThis^.showing <> 0)
      then
      begin if ((PHCGThis^.showing and SideKnown) > 0)  // 24-8-7
            then
            begin
              AtkLbxEGroup.Items.Add(PHCGThis^.id);
              if (PHCGThis = gPActGroup)
              then
              AtkLbxEGroup.ItemIndex:= AtkLbxEGroup.Items.IndexOf(PHCGThis^.id)
              else
              if gPActUnit <> nil then if (PHCGThis = gPActUnit^.Group) then
                AtkLbxEGroup.ItemIndex:= AtkLbxEGroup.Items.IndexOf(PHCGThis^.id);

            end    //  240626  selecting group from GE active group or unit
            else    //  side is unknown
            begin     //  240708  unknown side contact
             if gPActUnit = nil      //  no ge ActUnit
             then
               AtkLbxEGroup.Items.Add('00')
             else
             begin//  There is GE ActUnit
               if (PHCGThis = gPActUnit^.Group)
               then                                 //  and it is in this group
                begin                                // mark up all data   as if known side  240807
                  AtkLbxEGroup.Items.Add('001');
                  PSelectedEGroup:= PHCGThis;        //  240708 trying earlier call
                  AtkLbxEGroup.ItemIndex:= AtkLbxEGroup.Items.IndexOf('001');
                end
                else       //  there is pActUnit but not this group
                 AtkLbxEGroup.Items.Add('00');
               end;  //  240712  missed the ;  there was a pActUnit
            end;       // end  unknown side contact
      end;
      PHCGThis := PHCGThis^.toSouth;
    end;   // finished Enemy group listing
    if AtkLbxEGroup.ItemIndex > -1
       then AtkLbxEGroup.MakeCurrentVisible;  //  240701 test
end;
//
//procedure TfrmMain.FormChangeBounds(Sender: TObject);    // also called by click SetSpinEditTop
//begin
//  SetSpinEditTop.Value:= Top;                      //  position testing 20230105
//
//end;
//
//procedure TfrmMain.WriteTopPosition();        // fired by SetTop label mouse click
//begin
//  Top := SetSpinEditTop.Value;               // more position test 20230113
//end;

procedure TfrmMain.IniPropStorage1SavingProperties(Sender: TObject);       //2023 0105 position test
begin
   IniPropStorage1.StoredValue['frmTop']:= IntToStr(Top);  //  SetSpinEditTop.Value);     //works OK  if you set value
   IniPropStorage1.StoredValue['frmLeft']:= IntToStr(Left);     //  250725
   //   IniPropStorage1.StoredValue['frmTop']:= IntToStr(Top);                       // creeps first opening after move
end;

procedure TfrmMain.MenuItem1Click(Sender: TObject);
begin                             //  250715 validity check for popup menu
  if RflLbxUnits.itemindex <> -1
  then
  begin
  if copy(RflLbxUnits.items.Strings[RflLbxUnits.itemindex], 1,1) <> '-'
    then
    begin
    RflSpinEditTank.Value:= StrToInt(copy(RflLbxUnits.items.Strings[RflLbxUnits.itemindex], 1,3));
    RflSpinEditRecip.Value:= -1;
    end;
  end;
end;

procedure TfrmMain.MenuItem2Click(Sender: TObject);
begin                               //  240715
  if RflLbxUnits.itemindex <> -1
  then
  begin
  if copy(RflLbxUnits.items.Strings[RflLbxUnits.itemindex], 1,1) <> '-'
    then
    begin
    RflSpinEditTank.Value:= StrToInt(copy(RflLbxUnits.items.Strings[RflLbxUnits.itemindex], 1,3));
    RflSpinEditRecip.Value:= StrToInt(copy(RflLbxUnits.items.Strings[RflLbxUnits.itemindex], 1,3));
    end;
  end;
end;

procedure TfrmMain.MenuItem3Click(Sender: TObject);
begin                   //  240715
  if RflLbxUnits.itemindex <> -1
  then
  begin
  if copy(RflLbxUnits.items.Strings[RflLbxUnits.itemindex], 1,1) <> '-'
    then
    begin
    RflSpinEditTank.Value:= StrToInt(copy(RflLbxUnits.items.Strings[RflLbxUnits.itemindex], 1,3));
    end;
  end;
end;

procedure TfrmMain.MenuItem4Click(Sender: TObject);
begin
  if RflLbxUnits.itemindex <> -1
  then
  begin
  if copy(RflLbxUnits.items.Strings[RflLbxUnits.itemindex], 1,1) <> '-'
    then
    begin
    RflSpinEditRecip.Value:= StrToInt(copy(RflLbxUnits.items.Strings[RflLbxUnits.itemindex], 1,3));
     end;
  end;
end;

procedure TfrmMain.MessgLogMouseEnter(Sender: TObject);
begin                            //  240704  resume from pause if click message page
  if AGFBtnPause.Caption = 'Resume' then
     AGFBtnPause.Click;
end;

procedure TfrmMain.RefuelSheetShow(Sender: TObject);
begin
  if RflChkBFind.Checked = False  and RflChkBTnkGrpSrch.Checked = False
    then RflChkBTnkGrpSrch.Checked:= True;
  RefuelSheetUpdate();
end;

procedure TfrmMain.RefuelSheetUpdate();
var
  PEvent : PHCHarpoonEvent;
  PThisGroup: PHCGroup;
  PThisUnit: PHCUnit;

begin
  RflLbxGroups.Items.NameValueSeparator:= '_';
  if RflChkBFind.Checked = True
  then
  begin
    RflLbxUnits.Clear;
    RflLbxDetail.Clear;
    RflSpinEditRecip.Value:=0;
    RflSpinEditQuant.Value:=0;
    RflSpinEditTank.Value:=0;
    RflLbxGroups.Clear;
    PEvent:= PHCEventHead;
    while (PEvent <> nil) do
    begin
      if (((PEvent^.flag and CommandMask) = AIR_REFUEL_EVENT) and (PHCGroup(PEvent^.ourGroup)^.side = MySide)) then
      RflLbxGroups.Items.Add(Format('%3.3s_ %d',[PHCGroup(PEvent^.ourGroup)^.id, PEvent^.Time]));
      if gPActGroup = PEvent^.ourGroup then
         RflLbxGroups.ItemIndex:= RflLbxGroups.Items.IndexOfName(PHCGroup(PEvent^.ourGroup)^.id); // 240704 test
      PEvent:= PEvent^.next;
    end;
  end
  else if RflChkBTnkGrpSrch.Checked = True
    then
    begin
  RflLbxUnits.Items.Clear;
  RflLbxGroups.Items.Clear;
  RflLbxDetail.Items.Clear;
  RflSpinEditRecip.Value:=0;
  RflSpinEditQuant.Value:=0;
  RflSpinEditTank.Value:=0;
  RflLbxGroups.Sorted:=true;
  PThisGroup := PStartGroup;
  while (PThisGroup <> nil) do
    begin
      If (format('%d',[PThisGroup^.side]) = strMySide) and
        (PThisGroup^.Units^.Air <> nil)
        then
        begin           // search units of group for fuel to offload
         PThisUnit:= PThisGroup^.Units;
         while (PThisUnit <> nil) do
         begin
         If  (PThisUnit^.Air^.FuelToOffload > 0)
             then break
             else if isTanker(PThisUnit)
                  then break;
         PThisUnit := PThisUnit^.next;
         end;
         if (PThisUnit <> nil) then     // we got a tanker
           RflLbxGroups.Items.Add(PThisGroup^.id);
         if PThisGroup = gPActGroup then
            RflLbxGroups.ItemIndex:= RflLbxGroups.Items.IndexOf(PThisGroup^.id);    //  240630
        end;
      PThisGroup := PThisGroup^.toSouth;
    end;
  end;
end;

procedure TfrmMain.RflChkBAutoPFinishClick(Sender: TObject);
begin
  if RflChkBAutoPFinish.Checked = True then   //  only want one to be active
  RflChkBPause.Checked:= False;         //  240623

end;

procedure TfrmMain.RflChkBPauseClick(Sender: TObject);
begin
  if RflChkBPause.Checked = True then       // only want one to be active
  RflChkBAutoPFinish.Checked:= False;       //  240623
end;

procedure TfrmMain.RflGrpClick(Sender: TObject);
begin
  RflSpinEditTank.Value:= StrToInt(copy(RflLbxUnits.Items[RflLbxUnits.ItemIndex],1,3));
  RflBtnQueue.Click;
end;

procedure TfrmMain.SeaVesselShow(Sender: TObject);
begin
  SVSearchGroup();
end;

//procedure TfrmMain.SetLblTopClick(Sender: TObject);
//begin
//  WriteTopPosition();
//end;
//
procedure TfrmMain.StatDamTShtShow(Sender: TObject);

var
  PHCThisGroup: PHCGroup;
  PHCThisUnit: PHCUnit;
  strSide: String;
  DamFnd: Boolean;
begin
  DamFnd:= False;
  StatDamLstBx.Clear;
  PHCThisGroup:= PStartGroup;    //start top of group list    BUT will need to segregate each side
  while PHCThisGroup <> nil do
    begin
    strSide:= AnnexData^.PBSStrings^.SideName[PHCThisGroup^.side];           // will use for any damaged units
      // look for each unit in the group and list damages
      PHCThisUnit:= PHCThisGroup^.Units;  // first unit must exist
      while PHCThisUnit <> nil do
        begin
          if PHCThisUnit^.CurrDp > 0 then
          begin
          DamFnd:= True;
          StatDamLstBx.Items.Add(Format('%s: %2.2s %0.3d %s damage %d/%d',
                  [strSide, PHCThisGroup^.id, PHCThisUnit^.id, PHCThisUnit^.name,
                  PHCThisUnit^.CurrDp, PHCThisUnit^.OrigDp]));
          end;
          PHCThisUnit:= PHCThisUnit^.next;
        end;
      PHCThisGroup:= PHCThisGroup^.toSouth;
     end;
  if DamFnd = False then StatDamLstBx.Items.Add('No Damaged Units');
end;

procedure TfrmMain.StatDeadTShtShow(Sender: TObject);
var
     strClass, strSide, strType   : String;
     intS:Integer;
     PThisDeadUnit: PHCDeadListItem;
     ThisA1_disk: PHCAnnexA1_disk;
     ThisB1_disk: PHCAnnexB1_disk;

begin
//  PThisDeadUnit:= PAllDeadUnit;
  StatDeadLstBx.Items.Clear;

  if PAllDeadUnit = nil
    then StatDeadLstBx.Items.Add('No Dead Units')
    else

  begin
  for intS := 0 to 2 do
  begin
  PThisDeadUnit:= PAllDeadUnit;
  case intS of
  0: strSide:= 'Blue ';
  1: strSide:= 'Red ';
  2: strSide:= 'Neutral ';
  end;
  while PThisDeadUnit <> nil do
  begin
  if PThisDeadUnit^.side = intS then
    begin
    case PThisDeadUnit^.typeindex of
    0: begin
        strType:= 'Carrier';
       end;
    1: begin
        strType:= 'Ship';
        end;
    2: begin
        strType:= 'Submarine';
       end;
    3: begin
        strType:= 'Plane';
       end;
    4: begin
        strType:= 'Helicopter';
       end;
    5: begin
        strType:= 'Base';
       end;
    6: begin
        strType:= 'AD Fixed';
       end;
    7: begin
        strType:= 'AD Mobile';
       end;
    8: begin
        strType:= 'Armoured';
  //        intArmKill:= intArmKill + PThisDeadUnit^.count;
       end;
    9: begin
        strType:= 'Unarmoured';
  //        intUnarmKill:= intUnarmKill + PThisDeadUnit^.count;
       end;
    end;        // don't do class name for now

    // now look for class details

    case HCAnnexHelper.HCAnnexShift(PThisDeadUnit^.Annex) of
    SHIP_FLAG:
      begin
      ThisA1_disk := HCAnnexHelper.HCGetAnnexA1(PThisDeadUnit^.Annex);
      strClass:= ThisA1_disk^.ClassName;
      end;
    PLANE_FLAG:
      begin
      ThisB1_disk := HCAnnexHelper.HCGetAnnexB1(PThisDeadUnit^.Annex);
      strClass:= ThisB1_disk^.ClassName;
      end;
    BASE_FLAG:
      strClass:= PHCAnnexP1_disk(HCAnnexHelper.HCGetAnnexP1(PThisDeadUnit^.Annex))^.Name;
    SUB_FLAG:
      strClass:= PHCAnnexA2_disk(HCAnnexHelper.HCGetAnnexA2(PThisDeadUnit^.Annex))^.ClassName;
    else  strClass:= IntToStr(PThisDeadUnit^.Annex);
    end;

    // end look for class detail
    // now write data

    StatDeadLstBx.Items.Add(Format('%s side, %d %s/%s',[strSide, PThisDeadUnit^.count, strType, strClass]));
    end;  // end this was this side
    PThisDeadUnit:= PThisDeadUnit^.next;
  end;    // end loops for current side
  end;   // end all sides
  end;   // finished there were dead items
end;

procedure TfrmMain.SVListBoxGroupsSelectionChange(Sender: TObject; User: boolean);
var
  strSel: String;
  PHCGThis: PHCGroup;
  PHCUThis: PHCUnit;
begin
  PSelectedUnit:=nil;
  strSel := copy(SVListBoxGroups.Items[SVListBoxGroups.ItemIndex],1,2);
  PHCGThis := PStartGroup;
  while (PHCGThis <> nil) do
  begin
    if (strSel = copy(PHCGThis^.id,1,2)) then
       break;
    PHCGThis := PHCGThis^.toSouth;
  end;
  if (PHCGThis=nil) then //the selected group is no longer valid
     begin                 //and list must be reset
     SVSearchGroup();
     PHCGThis :=PStartGroup;
     end;                                //list reset
  PSelectedGroup:=PHCGThis;               //  PSelctedGroup now selected group in SV list
  SVListBoxUnitDetail.Items.Clear;
  SVListBoxUnits.Items.Clear;
  SVListBoxUnitDetail.Items.Add(Format('Group Speed: %d kts, Course: %d degs',[PHCGThis^.kts, PHCGThis^.course]));
  if PHCGThis^.EnemyRange < 200 then
     SVListBoxUnitDetail.Items.Add('Enemy Range' + IntToStr(PHCGThis^.EnemyRange));   //  240630 test for AIWINdow
  PHCUThis := PHCGThis^.Units;                          // ^^ added 20230518
  while (PHCUThis <> nil) do
  begin
  if HCMTIsAir(PHCUThis^.utype) = true
    then
      SVListBoxUnits.Items.Add(Format('%0.3d - %d %s',
        [PHCUThis^.id, PHCUThis^.Air^.PlatNow, PHCUThis^.name]))
    else
      SVListBoxUnits.Items.Add(Format('%0.3d - %s', [PHCUThis^.id, PHCUThis^.name]));
  if PHCUThis = gPActUnit then                   //  240624 test against unit selected in GE for indexing
     begin
     SVListBoxUnits.ItemIndex:= SVListBoxUnits.Items.Count -1;
     end;
  PHCUThis := PHCUThis^.next;
  end;
end;

procedure TfrmMain.SVListBoxUnitsSelectionChange(Sender: TObject; User: boolean);
var
  strSel, strDepthAlt, strSubDepth, strWepName, strText, strType, strMissFlag, strTargType: String;
  ThisWepID: ID;
  isSub, isBase, isAir, isShip: Boolean;
  PHCUThis: PHCUnit;
  RLat, RLon, WepRnge, WepRangeA, WepRangeS: Double;
  intDepthAlt, intSubDepth, MinsToGo, intC, intEndMins, intEndNM: integer;
  PmyPlanes: PHCPlaneRec;
  PThisMnt: PHCMntRec;
  PThisMag: PHCMagRec;

  PThisMount: PHCAnnexZ1_disk;
  PThisMulti: PHCAnnexZ2_disk;
  PThisLoadout: PHCAnnexY1_disk;

  PThisMissileDisk: PHCAnnexD1_disk;

  PHCAnnexA1: PHCAnnexA1_disk;
  PHCAnnexA2: PHCAnnexA2_disk;
  PHCAnnexB1: PHCAnnexB1_disk;
  PHCAnnexP1: PHCAnnexP1_disk;
  PHCAnnexC1: PHCAnnexC1_disk;
  PHCAnnexD1: PHCAnnexD1_disk;
  PHCAnnexE1: PHCAnnexE1_disk;
  PHCAnnexF1: PHCAnnexF1_disk;
  PHCAnnexG1: PHCAnnexG1_disk;
  PHCAnnexG2: PHCAnnexG2_disk;
  PHCAnnexM1: PHCAnnexM1_disk;


begin
  strSel := SVListBoxUnits.Items[SVListBoxUnits.ItemIndex];
  PHCUThis := PSelectedGroup^.Units;
  while (PHCUThis <> nil) do
  begin
    if (Pos(Format('%0.3d', [PHCUThis^.id]), strSel) = 1) then        // need to get right group
    begin
      break;
    end;
    PHCUThis := PHCUThis^.next;
  end;
  if (PHCUThis <> nil) then          // bring test forward one line
  begin
  PSelectedUnit := PHCUThis;     // ddt add unit information
  isSub:= False;      // False until
  isAir:= False;
  isShip:= False;
  isBase:= False;     // shown otherwise
    SVListBoxUnitDetail.Items.Clear;
    // first look up unit disk index for class name
    case (HCAnnexHelper.HCAnnexShift(PSelectedUnit^.Annex)) of
          SHIP_FLAG:
          begin
            PHCAnnexA1 := HCAnnexHelper.HCGetAnnexA1(PSelectedUnit^.Annex);
            strText := 'Ship Class Name: ' + PHCAnnexA1^.ClassName;
            strType:= 'Type: ' + AnnexData^.PBSStrings^.ShipType[PHCAnnexA1^.Subtype];
            isShip:= True;  //  240611
          end;
          SUB_FLAG:
          begin                                             //  20230514  ship and sub detail type
            isSub:= True;
            PHCAnnexA2 := HCAnnexHelper.HCGetAnnexA2(PSelectedUnit^.Annex);
            strText := 'Submarine Class Name: ' + PHCAnnexA2^.ClassName;
            strType:= 'Type: ' + AnnexData^.PBSStrings^.ShipType[PHCAnnexA2^.Subtype];
          end;
          PLANE_FLAG:
          begin
            isAir := True;
            PHCAnnexB1 := HCAnnexHelper.HCGetAnnexB1(PSelectedUnit^.Annex);
            strText := 'Aircraft Class: ' + PHCAnnexB1^.ClassName;
            strType:= 'Type: ' + AnnexData^.PBSStrings^.ACType[PHCAnnexB1^.Subtype];
          end;
          BASE_FLAG:
          begin
            PHCAnnexP1 := HCAnnexHelper.HCGetAnnexP1(PSelectedUnit^.Annex);
            isBase:= True;
            strText := 'Base Name: ' + PHCAnnexP1^.Name;
            strType:= '';
          end;
          else
            strText := '';
        end;
    //
    SVListBoxUnitDetail.Items.Add(strText);
    SVListBoxUnitDetail.Items.Add(Format('%2.2s%0.3d Name: %s  %s',
           [PHCGroup(PSelectedUnit^.Group)^.id, PSelectedUnit^.id, PSelectedUnit^.name, strType]));
    SVListBoxUnitDetail.Items.Add(format('best weapon range: AAW- %d, ASUW- %d, ASW- %0.1n',[PSelectedUnit^.BestAAWRange div 10, PSelectedUnit^.BestASuWRange div 10, PSelectedUnit^.BestASWRange / 10]));
//  calculate water depth at unit location  for ship and sub
    if isShip or isSub then
    begin
      RLat:= 90-(PSelectedUnit^.sLat/sliver1);       // have to convert game slat/slon to real lat/lon
      RLon:= PSelectedUnit^.sLon/sliver1;
//      SVListBoxUnitDetail.Items.Add(format('lat: %3.2f; lon: %3.2f',[RLat, RLon]));
      intDepthAlt:= HCdllCheckLatLonDepth(RLat, RLon);     // water depth/land altitude at geographic point of unit
      case intDepthAlt of
      0: strDepthAlt := 'Water depth: Extreme';
      1: strDepthAlt := 'Water depth: Vdeep';
      2: strDepthAlt := 'Water depth: Deep';
      3: strDepthAlt := 'Water depth: Intermediate';
      4: strDepthAlt := 'Water depth: Shallow';
      5: strDepthAlt := 'Water depth: Snorkel';
      6: strDepthAlt := 'WARNING Water depth: SeaLevel';
      else strDepthAlt := 'Unknown water depth';
      end;                                   //  240612
    end;
//  end for ship and sub set water depth
    // test for base => omit next line
    if not isBase then
      SVListBoxUnitDetail.Items.Add(format('course: %3.3d, kts: %d, MaxKts: %d',
       [PSelectedUnit^.course, PSelectedUnit^.kts, PSelectedUnit^.maxspeed]));
    if PSelectedUnit^.AgroundFlag = 1 then
      SVListBoxUnitDetail.Items.Add('WARNING - Vessel is AGROUND');
    if isShip then
      begin
      SVListboxUnitDetail.Items.Add(format('Noise: %d;  BaseNoise: %d',
        [PSelectedUnit^.SourceLevel, PSelectedUnit^.BaseSourceLevel])); //240611
      SVListboxUnitDetail.Items.Add(strDepthAlt);
      end;
    if isSub then
     begin             //  240612
         SVListboxUnitDetail.Items.Add(format('Depth: %s;  MaxDepth: %s;  %s',
          [HCAltBand2String(PSelectedUnit^.AltBand), HCAltBand2String(PHCAnnexA2^.MaxDepth), strDepthAlt]));
         SVListboxUnitDetail.Items.Add(format('Noise: %d; BaseNoise: %d',
           [PSelectedUnit^.SourceLevel, PSelectedUnit^.BaseSourceLevel]));  //  240612
     end;
    if isAir then
    begin
    SVListboxUnitDetail.Items.Add(format(' Number Now/Original: %d/ %d',
        [PSelectedUnit^.Air^.PlatNow, PSelectedUnit^.Air^.PlatStart]));

    intEndMins:= (PSelectedUnit^.Air^.Endurance) div (4 * 60);    //  240721
    intEndNM:= (intEndMins * PSelectedUnit^.kts) div 60;
    SVListboxUnitDetail.Items.Add(format('Endurance: %d mins, Range: %d nm',
        [intEndMins, intEndNM]));   // 240721
    if ((PHCAnnexB1^.UnitFlags AND UF_B1_NOE) > 0)    // 240707  & 240805
    then    strText:= 'NOE YES'
    else    strText:= 'NOE NO';
    if  ((PHCAnnexB1^.UnitFlags AND UF_B1_refuelcapable) > 0)
    then strText:= strText + ', IFR YES'
    else strText:= strText + ', IFR NO';
    if ((PHCAnnexB1^.UnitFlags AND UF_B1_MAD) > 0)
    then strText:= strText + ', MAD YES'
    else strText:= strText + ', MAD NO';
    SVListboxUnitDetail.Items.Add(strText);   //  240805 end change
    SVListboxUnitDetail.Items.Add(format('Base: %2.2s:%3.3d, %s',
      [PHCGroup(PHCUnit(PSelectedUnit^.Air^.HomeUnit)^.Group)^.id, PHCUnit(PSelectedUnit^.Air^.HomeUnit)^.id
      , PHCUnit(PSelectedUnit^.Air^.HomeUnit)^.name]));  //  240806
    intDepthAlt:=  PSelectedUnit^.AltBand;
    case intDepthAlt  of
  11: strDepthAlt := ' VLow';
  12: strDepthAlt := ' Low';
  13: strDepthAlt := ' Medium';
  14: strDepthAlt := ' High';
  15: strDepthAlt := ' VHigh';
  else strDepthAlt:= ' unknown';              //  240612
    end;
    SVListboxUnitDetail.Items.Add('Altitude now/max: %s/ %s',
        [strDepthAlt, HCAltBand2String(PHCAnnexB1^.MaxAlt)]);    // 240715 add max alt
    if PSelectedUnit^.Mnts <> nil then      // must be a loadout
      begin
      SVListBoxUnitDetail.Items.Add('Weapons and Fittings ..........');
      PThisMnt:= PSelectedUnit^.Mnts;
      while PThisMnt <> nil do
        begin
        if HCAnnexHelper.HCAnnexShift(PThisMnt^.MntId) = LOADOUT_FLAG then
           begin
           PThisLoadout:= HCAnnexHelper.HCGetAnnexY1(PThisMnt^.MntId);
           if PThisMnt^.WepId <> Invalid_ID
             then
             begin
             ThisWepID:= PThisMnt^.WepId;
               begin
                  case (HCAnnexShift(ThisWepId)) of
                  GUN_FLAG:
                    begin
                    PHCAnnexC1:= HCAnnexHelper.HCGetAnnexC1(ThisWepId);
                    strWepName:=  Format('%s; ARange: %d NM, SRange: %d NM',
                            [PHCAnnexC1^.ClassName, (PHCAnnexC1^.AirRange div 10), (PHCAnnexC1^.SurfRange div 10)]);
                    end;
                  MISSILE_FLAG:
                    begin
                    PHCAnnexD1:= HCAnnexHelper.HCGetAnnexD1(ThisWepId);
                    strWepName:= Format('%s; ARange: %d NM, SRange: %d NM, MinRange: %d NM',
                    [PHCAnnexD1^.ClassName, (PHCAnnexD1^.AirRange div 10), (PHCAnnexD1^.SurfRange div 10),
                    (PHCAnnexD1^.MinRange div 10)]);
                    end;
                  ASW_FLAG:
                  begin
                  PHCAnnexE1:= HCAnnexHelper.HCGetAnnexE1(ThisWepId);
                  strWepName:= Format('%s: MaxRange: %d MinRange: %d',
                            [PHCAnnexE1^.ClassName, (PHCAnnexE1^.MaxRange div 10), (PHCAnnexE1^.MinRange div 10)]);
                  end;
                  TORP_FLAG:
                  begin
                  PHCAnnexF1:= HCAnnexHelper.HCGetAnnexF1(ThisWepId);
                  strWepName:= Format('%s; MaxRange: %d/%d MaxDepth %s',
                          [PHCAnnexF1^.ClassName, (PHCAnnexF1^.MaxRange[0] div 10), (PHCAnnexF1^.MaxRange[1] div 10),
                          HCAltBand2String(PHCAnnexF1^.MaxDepth)]);
                  end;
                  ATOG_FLAG:
                  begin
                  PHCAnnexG1:= HCAnnexHelper.HCGetAnnexG1(ThisWepId);
                  strWepName:= 'weptype ' + intToStr(PHCAnnexG1^.WType) + '/' + PHCAnnexG1^.ClassName;
                  end;
                  ECM_FLAG:
                  begin
                  PHCAnnexG2:= HCAnnexHelper.HCGetAnnexG2(ThisWepId);
                  strWepName:= PHCAnnexG2^.ClassName;
                  end;
                  SONAR_FLAG:
                  begin
                  PHCAnnexM1:= HCAnnexHelper.HCGetAnnexM1(ThisWepId);
                  strWepName:= PHCAnnexM1^.ClassName;
                  end;
                  else
                    strWepName:= format('misc - WepID %d',[ThisWepId]);
                  end;  //  end look at  mount
                    SVListBoxUnitDetail.Items.Add('  ' + IntToStr(PThisMnt^.Ammo_c[0]) + ' - ' + strWepName); // 240307
                    strWepName:= '';
               end;
             end;
           end;
        PThisMnt:= PThisMnt^.next;
        end;
      end; //  end it had a mnt <> nil
    //  add throttle detail
    SVListBoxUnitDetail.Items.Add('Throttle/AltBand = Speed Table:');
    SVListBoxUnitDetail.Items.Add(format('Loiter/hover - all Alts = %d knts',[PHCAnnexB1^.SpeedLevels[0]]));
//  going to low level data      for intC := 1 to  (PLANE_SPEED_LEVELS - 1) do
    SVListBoxUnitDetail.Items.Append(format('Cruise, Low/Med/High = %d/ %d/ %d kts',
        [PHCAnnexB1^.SpeedLevels[1], PHCAnnexB1^.SpeedLevels[2], PHCAnnexB1^.SpeedLevels[3]]));
    SVListBoxUnitDetail.Items.Append(format('Military, Low/Med/High = %d/ %d/ %d kts',
        [PHCAnnexB1^.SpeedLevels[4], PHCAnnexB1^.SpeedLevels[5], PHCAnnexB1^.SpeedLevels[6]]));
    SVListBoxUnitDetail.Items.Append(format('Reheat, Low/Med/High = %d/ %d/ %d kts',
        [PHCAnnexB1^.SpeedLevels[7], PHCAnnexB1^.SpeedLevels[8], PHCAnnexB1^.SpeedLevels[9]]));
  //  finished altband  - speed table
    end;  // end isAir is true
    if isAir = False then
    SVListBoxUnitDetail.Items.Add(format('Damage Points Now/Orig: %d/ %d',[PSelectedUnit^.CurrDp, PSelectedUnit^.OrigDp]));
// if not aircraft need to run thru mnts to show weapons
  if ((PSelectedUnit^.Mnts <> nil) and (isAir = False)) then
  begin
    SVListBoxUnitDetail.Items.Add(' ');
    SVListBoxUnitDetail.Items.Add('* Mounts *');
    PThisMnt:= PSelectedUnit^.Mnts;
    while (PThisMnt <> nil) do
    begin

    if (HCAnnexHelper.HCAnnexShift(PThisMnt^.MntId) = MOUNT_FLAG)  then
  begin
    PThisMount:= HCAnnexHelper.HCGetAnnexZ1(PThisMnt^.MntId);
    if PThisMnt^.Damage = 0
      then
      SVListBoxUnitDetail.Items.Add(format('* %s: %d mounts; %d tubes, %d targets',
      [PThisMount^.ClassName, PThisMount^.NumMounts, PThisMount^.NumTubes, PThisMount^.NumTargets]))
      else             //  240611 damage
      SVListBoxUnitDetail.Items.Add(format('* %s: %d mounts; %d tubes, %d targets, Damage: %d',
      [PThisMount^.ClassName, PThisMount^.NumMounts, PThisMount^.NumTubes, PThisMount^.NumTargets, PThisMnt^.Damage]));
    if PThisMnt^.WepId <> Invalid_ID then
    begin
    ThisWepID:= PThisMount^.WepId;    // ddt 20171103 test if mount is a weapon, if so list weapon data
// 20171106  end
 //20171106    else // assume its a weapon  but could be aircraft load out so must test
  // 20171106  if PThisMnt^.WepId <> Invalid_ID then ThisWepID:= PThisMnt^.WepId;      // ddt 20171103 add test valid value

    case (HCAnnexHelper.HCAnnexShift(ThisWepId)) of
      MULTI_FLAG:
    begin
          PThisMulti:= HCAnnexHelper.HCGetAnnexZ2(ThisWepId);
          intC:= 0;
          while (PThisMulti^.WepQty[intC] > 0) do
          begin
           WepRnge:= 0;
           case (HCAnnexHelper.HCAnnexShift(PThisMulti^.WepId[intC])) of
    GUN_FLAG:
      begin
      PHCAnnexC1:= HCAnnexHelper.HCGetAnnexC1(PThisMulti^.WepId[intC]);
      strWepName:= PHCAnnexC1^.ClassName;
      if  PHCAnnexC1^.AirRange  >  PHCAnnexC1^.SurfRange
        then   WepRnge:= PHCAnnexC1^.AirRange
        else WepRnge:= PHCAnnexC1^.SurfRange;
      end;
    MISSILE_FLAG:
      begin
      PHCAnnexD1:= HCAnnexHelper.HCGetAnnexD1(PThisMulti^.WepId[intC]);
      if PHCAnnexD1^.AirRange > PHCAnnexD1^.SurfRange
        then  WepRnge:= PHCAnnexD1^.AirRange
        else  WepRnge:= PHCAnnexD1^.SurfRange;            //  missile flag test
      //strMissFlag := '';
      //if  (PHCAnnexD1^.MissileFlags >= MF_SURF_ATTACK)  then strMissFlag:= 'SurfAttack'
      //    else if (PHCAnnexD1^.MissileFlags >= MF_LAND_ATTACK) then strMissFlag:= 'LandAttack';
      strWepName:= Format('%s; MinRange: %d NM',
          [PHCAnnexD1^.ClassName, (PHCAnnexD1^.MinRange div 10)]);
      end;
    ASW_FLAG:
    begin
    PHCAnnexE1:= HCAnnexHelper.HCGetAnnexE1(PThisMulti^.WepId[intC]);
    WepRnge:= PHCAnnexE1^.MaxRange;
    strWepName:= Format('%s; MinRange: %d NM',[PHCAnnexE1^.ClassName, (PHCAnnexE1^.MinRange div 10)]);
    end;
    TORP_FLAG:
    begin
    PHCAnnexF1:= HCAnnexHelper.HCGetAnnexF1(PThisMulti^.WepId[intC]);
    if PHCAnnexF1^.MaxRange[0] > PHCAnnexF1^.MaxRange[1]
      then  WepRnge:= PHCAnnexF1^.MaxRange[0]
      else  WepRnge := PHCAnnexF1^.MaxRange[1];
    if PHCAnnexF1^.SubDp=0 then
       strTargType:= ' - Surface Target only'
       else if PHCAnnexF1^.SurfDp=0 then
            strTargType:= ' - Sub Target only'
            else strTargType:= ' - Sub or Surface Target';    //  240611
    strWepName:= PHCAnnexF1^.ClassName + strTargType;         //   240611
    end;
    ATOG_FLAG:
    begin
    PHCAnnexG1:= HCAnnexHelper.HCGetAnnexG1(PThisMulti^.WepId[intC]);
    strWepName:= PHCAnnexG1^.ClassName;
    WepRnge:= PHCAnnexG1^.MaxRange;
    end;
    ECM_FLAG:
    begin
    PHCAnnexG2:= HCAnnexHelper.HCGetAnnexG2(PThisMulti^.WepId[intC]);
    strWepName:= PHCAnnexG2^.ClassName;
    end;
    else
      strWepName:= format('misc - WepID %d',[ThisWepId]);
    end;
          //  ddt 211110  added THisMnt-ammo count
            SVListBoxUnitDetail.Items.Add(format(' load %d of %d - %s: MaxRange: %0.1n',
            [PThisMnt^.Ammo_c[intC], PThisMulti^.WepQty[intC], strWepName, WepRnge/10]));
          intC:= intC + 1;
          end;     //  end one weapon of multi mount
    end;      // end case of multi mount

    GUN_FLAG:
      begin
      PHCAnnexC1:= HCAnnexHelper.HCGetAnnexC1(ThisWepId);
 //     if  PHCAnnexC1^.AirRange  >  PHCAnnexC1^.SurfRange
           WepRangeA:= PHCAnnexC1^.AirRange;
           WepRangeS:= PHCAnnexC1^.SurfRange;
      // 211110 ddt line swap      SVListBoxUnitDetail.Items.Add('  Load: ' + PHCAnnexC1^.ClassName + '- Range: ' + FloatToStr(WepRnge/10));
      SVListBoxUnitDetail.Items.Add(' load ' + IntToStr(PThisMnt^.Ammo_c[0]) + '/'
        + IntToStr(PThisMount^.NumRoundsMnt * PThisMount^.NumMounts) + ' - ' + PHCAnnexC1^.ClassName
        + '- Air/Surface Range: ' + FloatToStr(WepRangeA/10) + '/' + FloatToStr(WepRangeS/10));
      end;    // could specify bothair and surf range
    MISSILE_FLAG:
      begin
      PHCAnnexD1:= HCAnnexHelper.HCGetAnnexD1(ThisWepId);
//      if PHCAnnexD1^.AirRange > PHCAnnexD1^.SurfRange        DDT 211113 now specify both ari and surface range
          WepRangeA:= PHCAnnexD1^.AirRange;
          WepRangeS:= PHCAnnexD1^.SurfRange;    //ddt211110 line below
      SVListBoxUnitDetail.Items.Add(' load ' + IntToStr(PThisMnt^.Ammo_c[0])+ '/'
        + IntToStr(PThisMount^.NumRoundsMnt * PThisMount^.NumMounts) + ' -  ' + PHCAnnexD1^.ClassName);
      //strMissFlag := '';
      //if  (PHCAnnexD1^.MissileFlags >= MF_SURF_ATTACK)  then strMissFlag:= 'SurfAttack'
      //    else if (PHCAnnexD1^.MissileFlags >= MF_LAND_ATTACK) then strMissFlag:= 'LandAttack';
      SVListBoxUnitDetail.Items.Add('- Air/Surface Range: ' + FloatToStr(WepRangeA/10) + '/'
        + FloatToStr(WepRangeS/10) + ': MinRange: ' + FloatToStr((PHCAnnexD1^.MinRange)/10));      // specify both air and surf range
      end;                                                             // and MisFlag test
    ASW_FLAG:
    begin
    PHCAnnexE1:= HCAnnexHelper.HCGetAnnexE1(ThisWepId);
    WepRnge:= PHCAnnexE1^.MaxRange;     //211110
    SVListBoxUnitDetail.Items.Add(' load ' + IntToStr(PThisMnt^.Ammo_c[0])+ '/'
        + IntToStr(PThisMount^.NumRoundsMnt * PThisMount^.NumMounts) + ' - ' + PHCAnnexE1^.ClassName + '- Range: ' + FloatToStr(WepRnge/10));
    end;
    TORP_FLAG:
    begin
    PHCAnnexF1:= HCAnnexHelper.HCGetAnnexF1(ThisWepId);
    if PHCAnnexF1^.SubDp=0 then
       strTargType:= ' - Surface'
       else if PHCAnnexF1^.SurfDp=0 then
            strTargType:= ' - Sub'
            else strTargType:= ' - Sub or Surf';    //  240611
    if PHCAnnexF1^.MaxRange[0] > PHCAnnexF1^.MaxRange[1]
      then  WepRnge:= PHCAnnexF1^.MaxRange[0]
      else  WepRnge := PHCAnnexF1^.MaxRange[1];        //  211110
    SVListBoxUnitDetail.Items.Add(' load ' + IntToStr(PThisMnt^.Ammo_c[0])+ '/'
        + IntToStr(PThisMount^.NumRoundsMnt * PThisMount^.NumMounts) + ' - ' + PHCAnnexF1^.ClassName
        + '- Range: ' + FloatToStr(WepRnge/10) + strTargType);      //  240611
    end;
    ATOG_FLAG:
    begin
    PHCAnnexG1:= HCAnnexHelper.HCGetAnnexG1(ThisWepId);         //  211110
    SVListBoxUnitDetail.Items.Add(' load ' + IntToStr(PThisMnt^.Ammo_c[0])+ '/'
        + IntToStr(PThisMount^.NumRoundsMnt * PThisMount^.NumMounts) + ' - ' + PHCAnnexG1^.ClassName);
    SVListBoxUnitDetail.Items.Add('Range Max: ' + IntToStr(PHCAnnexG1^.MaxRange) + 'NM');      //20230514
    end;
    ECM_FLAG:
    begin
    PHCAnnexG2:= HCAnnexHelper.HCGetAnnexG2(ThisWepId);
    SVListBoxUnitDetail.Items.Add('  - ECM ' + PHCAnnexG2^.ClassName);
    end;
    else
      SVListBoxUnitDetail.Items.Add(format('  misc - WepID %d',[ThisWepId]));
    end;
    // end case weapon type
    end; // end test for weapon type mount,go to nextmount
    PThisMnt:= PThisMnt^.next;
    end;       // end mount was wep mount
   end;   // end  each mount
  end; //end if mnts

//  test for landed air units
  if (PSelectedUnit^.myPlanes <> nil) then
  begin
    PmyPlanes:= PSelectedUnit^.myPlanes;
    SVListBoxUnitDetail.Items.Add('');      //20171106  insert blank line in data list
    SVListBoxUnitDetail.Items.Add('* aircraft landed *');
    while (PmyPlanes <> nil) do
    begin
      if PmyPlanes^.Time = 0 then MinsToGo:= 0 else
      MinstoGo:= (PmyPlanes^.Time - intGameTime) div 60;
      SVListBoxUnitDetail.Items.Add(format('%d-%s; ready time: %d',[PmyPlanes^.PlatNow, PmyPlanes^.name, MinsToGo]));
            // need to get loadouts?
      PmyPlanes:= PmyPlanes^.next;
    end;
  end;
// end test for landed air units


// look for magazines          211101 ddt
  if (PSelectedUnit^.Mags <> nil) then
  begin
  SVListBoxUnitDetail.Items.Add('');     //  231019  add a blank line for ease of reading
  SVListBoxUnitDetail.Items.Add('* Magazines *');
  PThisMag:= PSelectedUnit^.Mags;
  while (PThisMag <> nil) do
    begin
    //try to get annex info
    case (HCAnnexShift(PThisMag^.WepId)) of
    GUN_FLAG:
      begin
      PHCAnnexC1:= HCAnnexHelper.HCGetAnnexC1(PThismag^.WepId);
      strWepName:= PHCAnnexC1^.ClassName;
      end;
    MISSILE_FLAG:
      begin
      PHCAnnexD1:= HCAnnexHelper.HCGetAnnexD1(PThismag^.WepId);
      strWepName:= PHCAnnexD1^.ClassName;
      end;
    ASW_FLAG:
    begin
    PHCAnnexE1:= HCAnnexHelper.HCGetAnnexE1(PThismag^.WepId);
    strWepName:= PHCAnnexE1^.ClassName;
    end;
    TORP_FLAG:
    begin
    PHCAnnexF1:= HCAnnexHelper.HCGetAnnexF1(PThismag^.WepId);
    strWepName:= PHCAnnexF1^.ClassName;
    end;
     // moreweapons?
    else
      strWepName:= 'unknown weapon'
    end;
    //
    SVListBoxUnitDetail.Items.Add(format('%s: Count: %d, ReloadTime: %d',[strWepName, PThisMag^.Count, PThisMag^.ReloadTime]));
    PThisMag:= PThisMag^.next;
    end;
  end;   //end mags        211101 ddt
  end   //  end whole thing
  else
  SVListBoxUnitDetail.Items.Add('bad unit selection?'); // something strange went on
end;


procedure TfrmMain.AtkLbxEGroupSelectionChange(Sender: TObject; User: boolean);
var
  strSel, strUnitID: String;
  PHCGThis: PHCGroup;
  PHCUThis: PHCUnit;

begin
  PSelectedEUnit:=nil;      //  240804 fix from PSelectedUnit := nil
  strSel := copy(AtkLbxEGroup.Items[AtkLbxEGroup.ItemIndex],1,2);
  if strSel= '00'
  then     // unknown group side
  begin
      AtkLbxEUnit.Clear;
      AtkLbxEUnit.Items.Add('unknown side');
      if copy(AtkLbxEGroup.Items[AtkLbxEGroup.ItemIndex],1,3) = '001'
        then   //  then its the group of the ActUnit but unknown side
        begin
        PSelectedEGroup:= gPActUnit^.Group;
        PHCUThis := PSelectedEGroup^.Units;
        while (PHCUThis <> nil) do
        begin
          If (PHCUThis^.showing[0] <> 0) then  //need to test for showing
          begin
          if PHCUThis^.UncertaintyRegion[0] = nil  // exact pos known
             then strUnitID:= Format('%3.3d *',[PHCUThis^.id])
             else strUnitID:= Format('%3.3d',[ PHCUThis^.id]);
           if PHCUThis^.Data[0] >= cNAME             //  20230529 filtering data by contact quality ??
             then                                            //   unit class, including type and size, known
             begin
             if PHCUThis^.Air = nil
             then AtkLbxEUnit.Items.Add(strUnitID +'_'+ Format(' %s DP: %d/%d',
                 [PHCUThis^.name, PHCUThis^.CurrDp, PHCUThis^.OrigDp]))
             else AtkLbxEUnit.Items.Add(strUnitID +'_'+ Format(' %d/%d-%s',
                 [PHCUThis^.Air^.PlatNow, PHCUThis^.Air^.PlatStart, PHCUThis^.name]));
             AtkLbxEUnit.Items.Add(Format('BWR: %.0n AA, %.0n ASu, %.1n AS',
                 [(PHCUThis^.BestAAWRange) /10, (PHCUThis^.BestASuWRange) /10, (PHCUThis^.BestASWRange) /10]));
             end
             else        //   name not known
             if PHCUThis^.Data[0] >= cTYPE
             then AtkLbxEUnit.Items.Add (strUnitID +'_'+
                     format(' unknown %s',[HCMT2String(PHCUThis^.utype)]))
             else AtkLbxEUnit.Items.Add(strUnitID +'_' + ' unidentified contact');;
           if gPActUnit = PHCUThis
             then
             begin
             AtkLbxEUnit.ItemIndex:= AtkLbxEUnit.Items.IndexOfName(strUnitID);  // 240626
             PSelectedEUnit:= PHCUThis;
             end;
                  // finished split for uncertainty   20230527  added weapon range data
          end;  //  otherwise do nothing as unit not detected
          PHCUThis := PHCUThis^.next;
        end;    //  iterated thru the group units
        end; //  it was the group of the ActUnit  if not selected unit group then don't want to list info
  end
  else   //  it was a known group side
  begin
  PHCGThis := PStartGroup;
  while (PHCGThis <> nil) do
  begin
    if (strSel = copy(PHCGThis^.id,1,2)) then
       break;
    PHCGThis := PHCGThis^.toSouth;
  end;
  if (PHCGThis=nil) then //the selected group is no longer valid
     begin                 //and list must be reset
     AtkGrpSrch();
     PHCGThis :=PStartGroup;
     end;                                //list reset
  PSelectedEGroup:=PHCGThis;
  AtkLbxEUnit.Clear;
  AtkLbxEUnit.Items.NameValueSeparator:= '_';   //  240626 indexing by name
  PHCUThis := PHCGThis^.Units;
  while (PHCUThis <> nil) do
  begin
    if (PHCUThis^.showing[0] <> 0) then  //need to test for showing
    begin
    if PHCUThis^.UncertaintyRegion[0] = nil  // exact pos known
       then strUnitID:= Format('%3.3d: *',[PHCUThis^.id])
       else strUnitID:= Format('%3.3d:',[ PHCUThis^.id]);
     if PHCUThis^.Data[0] >= cNAME             //  20230529 filtering data by contact quality ??
       then                                            //   unit class, including type and size, known
       begin
         if PHCUThis^.Air = nil
         then AtkLbxEUnit.Items.Add(strUnitID +'_'+ Format(' %s DP: %d/%d',
             [PHCUThis^.name, PHCUThis^.CurrDp, PHCUThis^.OrigDp]))
         else AtkLbxEUnit.Items.Add(strUnitID +'_'+ Format(' %d/%d-%s',
             [PHCUThis^.Air^.PlatNow, PHCUThis^.Air^.PlatStart, PHCUThis^.name]));
       AtkLbxEUnit.Items.Add(Format('BWR: %.0n AA, %.0n ASu, %.1n AS',
           [(PHCUThis^.BestAAWRange) /10, (PHCUThis^.BestASuWRange) /10, (PHCUThis^.BestASWRange) /10]));
       end
       else        //   name not known
       if PHCUThis^.Data[0] >= cTYPE
          then AtkLbxEUnit.Items.Add (strUnitID +'_'+
               format(' unknown %s',[HCMT2String(PHCUThis^.utype)]))
          else AtkLbxEUnit.Items.Add(strUnitID +'_'+
               ' unidentified contact');
     if gPActUnit = PHCUThis
       then
       begin
       AtkLbxEUnit.ItemIndex:= AtkLbxEUnit.Items.IndexOfName(strUnitID);  // 240626
       PSelectedEUnit:= PHCUThis;                                        //  240711  and above in unknowns
       end;
            // finished split for uncertainty   20230527  added weapon range data
    end;  //  otherwise do nothing as unit not detected
    PHCUThis := PHCUThis^.next;
  end;
  end; //  end split group was a known side
end;

procedure TfrmMain.AtkLbxEUnitSelectionChange(Sender: TObject; User: boolean);
begin
    if PSelectedEGroup <> nil        //  240804  case where unit selection changed manually and no known EGroup
    then
    begin
    PSelectedEUnit:= PSelectedEGroup^.Units;
    while
     PSelectedEUnit <> nil
     do
     begin
     if  copy(AtkLbxEUnit.Items[AtkLbxEUnit.ItemIndex],1,3) = Format('%0.3d',[PSelectedEUnit^.id])
       then break;
     PSelectedEUnit:= PSelectedEUnit^.next;
     end;
    end;   // else no selected EGroup or it isn't known   240804
end;
procedure TfrmMain.AtkLbxFGroupSelectionChange(Sender: TObject; User: boolean);
var
  strSel: String;
  PHCGThis: PHCGroup;
  PHCUThis: PHCUnit;
  PHCAnnexD1: PHCAnnexD1_disk;
  PHCAnnexTorp: PHCAnnexF1_disk;

begin
  AtkLblMG.Caption:= '';                                     //  231017
  strSel := copy(AtklbxFGroup.Items[AtklbxFGroup.ItemIndex],1,2);
  PHCGThis := PStartGroup;
  while (PHCGThis <> nil) do
  begin
    if (strSel = copy(PHCGThis^.id,1,2)) then
       break;
    PHCGThis := PHCGThis^.toSouth;
  end;
  if (PHCGThis=nil) then //the selected group is no longer valid
     begin                 //and list must be reset
     AtkgrpSrch();
     PHCGThis :=PStartGroup;
     end;                                //list reset
  PSelectedGroup:=PHCGThis;
//
  AtkLbxFUnit.Items.Clear;
  AtkLbxFUnit.Sorted := False;       //  240706 adding distance to enemy
  if PHCGThis^.Units^.Missile <> Nil then
begin
  AtkLblMG.Caption:= '**=CanReTarget';
  PHCUThis := PHCGThis^.Units;
  while (PHCUThis <> nil) do
  begin
  if (PHCUThis^.Missile <>nil)
  then
     if (PHCUThis^.utype = MT_MISSILE)         //  is missile or asroc
     then
        if PHCUThis^.Annex < 20480
        then        // 231018    is not asroc
          begin      // is a missile
          PHCAnnexD1:=  HCAnnexHelper.HCGetAnnexD1(PHCUThis^.Annex);
          //if (PHCUThis^.Missile^.guid_phase >= GT_MIDCOURSE)
          //   then
               AtkLbxFUnit.Items.Add(Format('%0.3d: %d-%s',
              [PHCUThis^.id, PHCUThis^.Missile^.PlatNow, PHCAnnexD1^.ClassName]))
             //else
             // AtkLbxFUnit.Items.Add(Format('%0.3d: %d-%s',
             // [PHCUThis^.id, PHCUThis^.Missile^.PlatNow, PHCAnnexD1^.ClassName]));
          end
        else       //  is asroc?
            AtkLbxFUnit.Items.Add(Format('%0.3d: %d-ASROC',
              [PHCUThis^.id, PHCUThis^.Missile^.PlatNow]));    // end is missile
      if PHCUThis^.utype = MT_TORP
      then                                                     //  is torpedo
        begin                                                 //  231018 testing for retarget
           PHCAnnexTorp:= HCAnnexHelper.HCGetAnnexF1(PHCUThis^.Annex);
           if (PHCUThis^.Missile^.guid_phase = 1)
           then AtkLbxFUnit.Items.Add(Format('%0.3d: ** %d-%s',
                [PHCUThis^.id, PHCUThis^.Missile^.PlatNow, PHCAnnexTorp^.ClassName]))
           else AtkLbxFUnit.Items.Add(Format('%0.3d: %d-%s',
                [PHCUThis^.id, PHCUThis^.Missile^.PlatNow, PHCAnnexTorp^.ClassName]));
        end;
  PHCUThis := PHCUThis^.next;
  end;
  if PSelectedGroup^.Units^.Missile^.targetUnit <> nil
  then                              //  240807 hiding grp id for unknown side
    begin
    if (PHCGroup(PHCUnit(PSelectedGroup^.Units^.Missile^.targetUnit)^.Group)^.showing and SideKnown > 0)
    then
   AtkBtnTarg.Caption:= format('%s',[PHCGroup(PHCUnit(PSelectedGroup^.Units^.Missile^.targetUnit)^.Group)^.id])
    else
   AtkBtnTarg.Caption:= 'NoID';
    end
  else
    AtkBtnTarg.Caption:= 'No Target';
end                                     //  end is a missle group
  else                                  //  test for air begin  want to just list units for asw cover
  If (PSelectedGroup^.Units^.Air<>nil) then
    begin
    PHCUThis := PSelectedGroup^.Units;
    if PSelectedGroup^.EnemyRange < 50 then
       AtkLbxFUnit.Items.Add('Enemy Range: %d NM',[PSelectedGroup^.EnemyRange]);
    while (PHCUThis <> nil) do
      begin
       AtkLbxFUnit.Items.Add(Format('%0.3d: %d-%s',
          [PHCUThis^.id, PHCAirRec(PHCUThis^.Air)^.PlatNow, PHCUThis^.name]));
       PHCUThis := PHCUThis^.next;
      end;
    end
    else    //  not missile or air group    240706
    begin
       PHCUThis := PSelectedGroup^.Units;
       if PSelectedGroup^.EnemyRange < 30 then
       begin
       AtkLbxFUnit.Items.Add('ER-Enemy Range: %d NM',[PSelectedGroup^.EnemyRange]);
       while (PHCUThis <> nil) do
        begin
         AtkLbxFUnit.Items.Add(Format('%0.3d- %s ER: %d NM',
         [PHCUThis^.id, PHCUThis^.name, PHCUThis^.ClosestActualRange[0]]));
         PHCUThis := PHCUThis^.next;                     //  ^^ test closest actual range  240706
        end;
       end             // closest enemy < 30 nm
       else
       while (PHCUThis <> nil) do
        begin
         AtkLbxFUnit.Items.Add(Format('%0.3d- %s',[PHCUThis^.id, PHCUThis^.name]));
         PHCUThis := PHCUThis^.next;
        end;
    end;           // end not missile or airgroup
    //             231017 test for air finish
end;

procedure TfrmMain.AtkLbxFUnitSelectionChange(Sender: TObject; User: boolean);
var
   strSel: String;
   PHCUThis: PHCUnit;
begin
  strSel := AtkLbxFUnit.Items[AtkLbxFUnit.ItemIndex];
  if HCIsValidGroup(PSelectedGroup, PStartGroup) = 1 then  // test that group still exists  true=1, false =0
  begin
  PHCUThis := PSelectedGroup^.Units;
  while (PHCUThis <> nil) do
  begin
    if (Pos(Format('%0.3d', [PHCUThis^.id]), strSel) = 1) then
    begin
      break;
    end;
    PHCUThis := PHCUThis^.next;
  end;
  if PHCUThis <> nil
  then
    begin
        PSelectedUnit := PHCUThis;
    if PSelectedUnit^.Missile <> nil then
      if PSelectedUnit^.Missile^.targetUnit <> nil
      then
      AtkBtnTarg.Caption:= format('%s - %0.3d', [PHCGroup(PHCUnit(PSelectedUnit^.Missile^.targetUnit)^.Group)^.id,
      PHCUnit(PSelectedUnit^.Missile^.targetUnit)^.id])
      else AtkBtnTarg.Caption:= 'NoTarg';
      //if (PSelectedUnit^.utype = MT_MISSILE)                       //  231017 test that missile and not torpedo
      //then if (PSelectedUnit^.Missile^.guid_phase < GT_MIDCOURSE)
      //     then AtkLblMG.Caption:= '* NoReTarget'
      //     else AtkLblMG.Caption:= '* CanReTarget'
      //else if (PSelectedUnit^.utype = MT_TORP)                // 231018 add torp case
      //     then if (PSelectedUnit^.Missile^.guid_phase = 1)
      //          then AtkLblMG.Caption:= '* CanReTarget'
      //          else AtkLblMG.Caption:= '* NoReTarget';
    end
  // need to look at if a torpedo can it be retargetted
  else AtkGrpSrch();
  end
  else AtkGrpSrch();
end;


procedure TfrmMain.AGFspnbxLonEditingDone(Sender: TObject);
begin
  if PSelectedUnit <> nil then
  begin
  if (AGFlblE.Caption = 'E') then
     PselectedUnit^.sLon:= PSelectedGroup^.sLon + Round( uHCDefines.NM1*(AGFspnbxLon.Value)/dblLatFact)
     else PselectedUnit^.sLon:= PSelectedGroup^.sLon - Round( uHCDefines.NM1*(AGFspnbxLon.Value)/dblLatFact);
  if (AGFlblS.Caption = 'S') then
     PSelectedUnit^.sLat:= PSelectedGroup^.sLat + uHCDefines.NM1*(AGFspnbxLat.Value)
     else PSelectedUnit^.sLat:= PSelectedGroup^.sLat - uHCDefines.NM1*(AGFspnbxLat.Value);
  end
  else ShowMessage('No Unit selected');
end;

procedure TfrmMain.AGFspnbxUnitAltEditingDone(Sender: TObject);
var
   intMaxAltBand, intThrot, intOldThrot, intKts: integer;
   PThisUnit: PHCUnit;
begin
  If PselectedUnit <> nil then
  begin
  //  check max alt of aircraft           ddt 20150901
    PThisPlaneDisk:= HCAnnexHelper.HCGetAnnexB1(PSelectedUnit^.Annex);
    intMaxAltBand:= PThisPlaneDisk^.MaxAlt;
    if intMaxAltBand < AGFspnbxUnitAlt.Value then
    AGFspnbxUnitAlt.Value:= intMaxAltBand;
    PSelectedUnit^.AltBand:=AGFspnbxUnitAlt.Value;
  //   check if speed must reduce ddt 20150902 first reset throttle
    intOldThrot:= PSelectedUnit^.Air^.Throttle;
    case intOldThrot of
    0: intThrot:= 0;
    1..3:  case  PSelectedUnit^.AltBand of
          11..12: intThrot:= 1;
          13:     intThrot:= 2;
          14..15: intThrot:= 3;
          end;
    4..6:  case  PSelectedUnit^.AltBand of
          11..12: intThrot:= 4;
          13:     intThrot:= 5;
          14..15: intThrot:= 6;
          end;
    7..9:  case  PSelectedUnit^.AltBand of
          11..12: intThrot:= 7;
          13:     intThrot:= 8;
          14..15: intThrot:= 9;
          end;
    end;
                                  //check if plane needs to slow for new alt
    if PSelectedUnit^.kts > PThisPlaneDisk^.SpeedLevels[intThrot] then
    begin
    intKts:= PThisPlaneDisk^.SpeedLevels[intThrot];
    PSelectedUnit^.kts:= intKts;
// check group kts - unit may be in formation about surface unit or in an air group
    if PHCGroup(PSelectedUnit^.Group)^.kts > PSelectedUnit^.kts then
      PHCGroup(PSelectedUnit^.Group)^.kts:= PSelectedUnit^.kts;
//should check if unit can reduce throttle but maintain kts
    end;       // group should be now correct kts
  //
  AGFUnitUpdate();
  end
  else ShowMessage('No unit selected');
end;

procedure TfrmMain.DelPath(SGroup:PHCGroup);
var
  PPathLeg, PPathLegPrev: PHCPathRec;
begin
  // ALE 20130411 Delete the existing path leg by leg.
  PPathLeg := SGroup^.track;
  if ((PPathLeg <> nil) AND (PPathLeg^.next <> nil)) then
  begin
    PPathLegPrev := nil;
    while (PPathLeg^.next <> nil) do
    begin
      PPathLegPrev := PPathLeg;
      PPathLeg := PPathLeg^.next;
    end;
  end;
  // ALE 20130411 PPathLeg should now point to the last
  //  leg of the group's path, work backwards freeing them.
  if (PPathLeg <> nil) then
  begin
    while (PPathLeg <> SGroup^.track) do
    begin
      PPathLegPrev := PPathLeg^.prev;
      del_HarpoonEvent_list(PPathLeg^.pathEvents);
      DisposPtr(PPathLeg);
      PPathLeg := PPathLegPrev;
    end;
    PPathleg^.next:= nil;
    end;
end;

procedure TfrmMain.RflBtnQueueClick(Sender: TObject);
var
  PNewRefuelEvent: PHCAirRefuelEvent;
  PNewEvent: PHCHarpoonEvent;
  PURefuelTank: PHCUnit;
begin
  PURefuelTank:= PSelectedGroup^.Units;
  while (PURefuelTank <> nil) do
  begin
    if (PURefuelTank^.id = RflSpinEditTank.Value) and (PURefuelTank^.Air^.FuelToOffload > 0)
      then break;
    PURefuelTank:= PURefuelTank^.next;
  end;      // should have tank units
  if (PURefuelTank = nil) then //an error
  RflLbxDetail.Items.Add('error - no suitable supply tanker unit selected')
  else
  begin
    RflLbxDetail.Items.Add(Format('refuel queued unit %3.3d as tanker',[PURefuelTank^.id]));
    PNewRefuelEvent:= NewPtr(SizeOf(THCAirREfuelEvent));
    PNewEvent:= NewPtr(SizeOf(THCHarpoonEvent));
//  250806    PNewEvent^.uuid := GenerateUUID;
    PNewRefuelEvent^.fuelingUnit:= PURefuelTank^.id;
    PNewRefuelEvent^.now_refueling:= CFalse;
    PNewRefuelEvent^.fuel_range:= PUREfuelTank^.Air^.FuelToOffload * PURefuelTank^.Air^.PlatNow;
    PNewRefuelEvent^.num_tankers:= PURefuelTank^.Air^.PlatNow;
    PNewRefuelEvent^.side:= MySide;        // group tanker type and recipient units not required
    PNewEvent^.DataPtr:= PNewRefuelEvent;
    PNewEvent^.next:= nil;
    PNewEvent^.flag := AIR_REFUEL_EVENT or TimeDelta;
 //     PNewEvent^.time_type:= TimeDelta;
    PNewEvent^.in_scroll_flag:= CFALSE;
    PNewEvent^.ourGroup:= PSelectedGroup;
    PNewEvent^.Time:= 30;
    PNewEvent^.UnitID:= PURefuelTank^.ID;
    HCQueueHarpoonEvent(PNewEvent);       // dllbusy was called earlier
  end;
end;

procedure TfrmMain.RflBtnTnkrGrpSrchClick(Sender: TObject);
var
  PThisGroup: PHCGroup;
  PThisUnit: PHCUnit;

begin
  if (RflChkBTnkGrpSrch.Checked = True) then
  begin;
          //Byte(PDLLBusy^) := CTRUE;     now use generic pause button
//    if AGFBtnPause.Caption = 'Pause' then
  //  AGFBtnPause.Click;            // pause game function    - 231005  - remove puase action
  RflChkBFind.Checked:= False;
         //RflChkBResume.Checked:= False;    no longer use this method
  RflLbxUnits.Items.Clear;
  RflLbxGroups.Items.Clear;
  RflLbxDetail.Items.Clear;
  RflSpinEditRecip.Value:=0;
  RflSpinEditQuant.Value:=0;
  RflSpinEditTank.Value:=0;
  RflLbxGroups.Sorted:= True;
  PThisGroup := PStartGroup;
  while (PThisGroup <> nil) do
    begin
      If (format('%d',[PThisGroup^.side]) = strMySide) and
        (PThisGroup^.Units^.Air <> nil)
        then
        begin           // search units of group for fuel to offload
         PThisUnit:= PThisGroup^.Units;
         while (PThisUnit <> nil) do
         begin
         If  (PThisUnit^.Air^.FuelToOffload > 0)
             then break
             else if isTanker(PThisUnit)
                  then break;
         PThisUnit := PThisUnit^.next;
         end;
         if (PThisUnit <> nil) then     // we got a tanker
           RflLbxGroups.Items.Add(PThisGroup^.id);
         if PThisGroup = gPActGroup then
            RflLbxGroups.ItemIndex:= RflLbxGroups.Items.IndexOf(PThisGroup^.id);     //  240630
        end;
      PThisGroup := PThisGroup^.toSouth;
    end;
  end;
end;

procedure TfrmMain.RflBtnTransClick(Sender: TObject);
var
  PTankUnit, PRecipUnit: PHCUnit;
  intFuelTrans: integer;
  PNewRefuelEvent: PHCAirRefuelEvent;
  PNewEvent: PHCHarpoonEvent;

begin
  if (PSelectedGroup = nil)
  then  RflLbxUnits.Items.Add('Select a group')
  else
  begin
    PTankUnit:= PSelectedGroup^.Units;
    while (PTankUnit <> nil) do
    begin
      if (PTankUnit^.id = RflSpinEditTank.Value) then
      break
      else PTankUnit:= PTankUnit^.next;
    end;         //  PTankUnit should now be selected supply tank unit
    if (PTankUnit = nil)
    then RflLbxDetail.Items.Add('bad supply tanker unit selection')
    else
      begin
      if not  ((PTankUnit^.air^.FuelToOffload > 0) or isTanker(PTankUnit))
      then
      RflLbxDetail.Items.Add('supply unit has insufficient fuel or not a tanker')
      else
      if (RflSpinEditTank.Value = RflSpinEditRecip.Value) then       // internal tranfer from refuel
      begin
        intFuelTrans:= RflSpinEditQuant.Value;
        if (intFuelTrans > PTankUnit^.Air^.FuelToOffload)
        then intFuelTrans:= PTankUnit^.Air^.FuelToOffload;
        PTankUnit^.Air^.FuelToOffload:= PTankUnit^.Air^.FuelToOffload - intFuelTrans;
        PTankUnit^.Air^.Endurance:= PTankUnit^.Air^.Endurance + (4 * 3600 * intFuelTrans div PTankUnit^.kts);
        RflLbxDetail.Items.Add(Format('added %d mins endurance from refuel unit %d',
            [60 * intFuelTrans div PTankUnit^.kts, PTankUnit^.id]));
        RflSpinEditQuant.Value:= 0;
        RflLbxUnitFill(PSelectedGroup);      //  230102 tidyup output
      end
      else
        if RflSpinEditRecip.Value = -1 then // internal transfer to refuel
        begin
        intFuelTrans:= RflSpinEditQuant.Value;                   // restrict to 2/3 current main tank load
        if ((4 * 3600 * intFuelTrans div PTankUnit^.kts) >= 2*(PTankUnit^.Air^.Endurance) div 3) then
        intFuelTrans:=  ((PTankUnit^.Air^.Endurance * PTankUnit^.kts) div (6 * 3600));
            if PTankUnit^.Air^.FuelToOffload < 1       // had no fuel to off-load, can be negative?
               then PTankUnit^.Air^.FuelToOffload:= intFuelTrans
               else PTankUnit^.Air^.FuelToOffload:= PTankUnit^.Air^.FuelToOffload + intFuelTrans;
        PTankUnit^.Air^.Endurance:= PTankUnit^.Air^.Endurance - (4 * 3600 * intFuelTrans div PTankUnit^.kts);
        RflLbxDetail.Items.Add(Format('added %d n.m. to refuel unit# %d',[intFuelTrans, PTankUnit^.id]));
        RflSpinEditQuant.Value:= 0;                          //  240702 ^^ added tank ID
        RflSpinEditRecip.Value:= 0;
        RflLbxUnitFill(PSelectedGroup);     //  231002  tidyup
      end
      else
         // transfer to another unit, first find recip unit
      begin
            PRecipUnit:= PSelectedGroup^.Units;
            while (PRecipUnit <> nil) do
            begin
              if (PRecipUnit^.id = RflSpinEditRecip.Value) then
              break
              else PRecipUnit:= PRecipUnit^.next;
            end;         //  PRecipUnit now is selected recipient unit
            if (PRecipUnit = nil)
              then RflLbxDetail.Items.Add('bad recipient unit selection')
              else
              begin          // fill in a new event for refuel specific (other tanker) unit
                              // need to test for not over commit fuel
    if  (RflSpinEditQuant.Value * PRecipUnit^.Air^.PlatNow)
      > (PTankUnit^.Air^.FuelToOffload * PTankUnit^.Air^.PlatNow)
    then RflSpinEditQuant.Value:=
    (PTankUnit^.Air^.FuelToOffload * PTankUnit^.Air^.PlatNow div PRecipUnit^.Air^.PlatNow);
               RflLbxDetail.Items.Add(Format('queued to refuel from tanker unit %d to unit %d',[RflSpinEditTank.Value, RflSpinEditRecip.Value]));
               RflLbxDetail.Items.Add('Refueling will take some time');
               RflLbxDetail.Items.Add('Set autoP End for reminder?');
               PNewRefuelEvent:= NewPtr(SizeOf(THCAirREfuelEvent));
               PNewEvent:= NewPtr(SizeOf(THCHarpoonEvent));
               //  250806               PNewEvent^.uuid := GenerateUUID;
               PNewRefuelEvent^.fuelingUnit:= PTankUnit^.id;
               PNewRefuelEvent^.now_refueling:= CTRUE;
               PNewRefuelEvent^.fuel_range:= PTankUnit^.Air^.FuelToOffload * PTankUnit^.Air^.PlatNow;
               PNewRefuelEvent^.num_tankers:= PTankUnit^.Air^.PlatNow;
               PNewRefuelEvent^.planestofuel[0]:= PRecipUnit^.id;
               PNewRefuelEvent^.planestofuel[1]:= 255;
               PNewRefuelEvent^.fuel_per_plane[0]:= RflSpinEditQuant.Value;
               PNewRefuelEvent^.fuel_per_plane[1]:= 0;
               PNewRefuelEvent^.side:= MySide;        // group and tanker type not required
               PNewEvent^.DataPtr:= PNewRefuelEvent;
               PNewEvent^.next:= nil;
               PNewEvent^.flag := AIR_REFUEL_EVENT or TimeDelta;
 //                PNewEvent^.time_type:= TimeDelta;
               PNewEvent^.in_scroll_flag:= CFALSE;
               PNewEvent^.ourGroup:= PSelectedGroup;
               PNewEvent^.Time:= 60;
               PNewEvent^.UnitID := PTankUnit^.ID;
               HCQueueHarpoonEvent(PNewEvent);
            end;   // we had a second unit (tanker) or non-tanker and have queued refuel
       end;
    end;  // finshed fuel transfer,
  end;
end;

// need procedure setgroupspeed/alt = cycle down units getting min max alt then min speed val for spec thrott
// 0 hover/loit any alt; 1 cruise low; 2 cruise med; 3 cruise high; 4 mil low; 5 mil med; 6 mil high; 7; burn low;
// 8; burn med; 9 burn high.     check if NOE, Vhigh capable?
// need spec PGroup, throttle and altband (incase loiter) then set kts and throttle.
procedure TfrmMain.SetGroupSpeedAlt(ThisGroup: PHCGroup; intThrottle: integer; intAltBand: uchar);
var
  PThisUnit: PHCUnit;
  GroupKts: short;                            // note, very low and very high alts aren't implimented
  startAltBand: uchar;
begin
  if  (ThisGroup^.Units^.Air <> nil) then            //only for airgroups    use group*type might be better?
  begin
  startAltBand:= intAltBand;
  PThisUnit:= ThisGroup^.Units;
  // ddt we must first check all units can operate at requested alt
  repeat
   PThisPlaneDisk:= HCAnnexHelper.HCGetAnnexB1(PthisUnit^.Annex);
   if (PThisPlaneDisk^.MaxAlt < intAltBand) then intAltBand := PThisPlaneDisk^.MaxAlt;
   //  240528  test if altband is vlow is unit noe capaable or helo
   if (intAltBand = 11) then
      if ((PThisPlaneDisk^.UnitFlags AND UF_B1_NOE) <= 0)
        and not(PThisUnit^.utype = MT_HELO) then intAltBand := 12;  // ?
   PThisUnit:= PThisUnit^.next;
  until (PThisUnit = nil);   // gone thru all units and have altitude val
                                   // if altBand has been restricted then modify throttle value
  if (startAltBand <> intAltBand) and (intThrottle > 0)
    then intThrottle:= intThrottle - (startAltBand - intAltBand);
  // if requested throttle = burner then must test if available all units
  if intThrottle > 6 then
  begin
  PThisUnit:= ThisGroup^.Units;
  repeat
   PThisPlaneDisk:= HCAnnexHelper.HCGetAnnexB1(PthisUnit^.Annex);
   if (PThisPlaneDisk^.SpeedLevels[intThrottle]) = 0 then
   begin intThrottle := intThrottle - 3;      // max altitudes are already tested
   break;
   end;
   PThisUnit:= PThisUnit^.next;
  until PThisUnit = nil;
  end;
  //
                                  // then set kts to min speed of respective units for requested throttle
  PThisUnit:= ThisGroup^.Units;
  PThisPlaneDisk:= HCAnnexHelper.HCGetAnnexB1(PThisUnit^.Annex);  // start with 1st unit nowcheck if any slower units
  GroupKts:= PThisPlaneDisk^.SpeedLevels[intThrottle];
  while (PThisUnit^.next <> nil) do
  begin
   PThisUnit:= PThisUnit^.next;
   PThisPlaneDisk:= HCAnnexHelper.HCGetAnnexB1(PThisUnit^.Annex);
   if PThisPlaneDisk^.SpeedLevels[intThrottle] < GroupKts
     then GroupKts:= PThisPlaneDisk^.SpeedLevels[intThrottle];
  end;           // should have desired data, now apply to group
           // but first need to test for loiter and helos hover
  if intThrottle = 0 then
  begin
  PThisUnit:= ThisGroup^.Units;
  repeat
    PThisUnit^.Air^.Throttle:= 0;
    PThisUnit^.AltBand:= intAltBand;
    PThisPlaneDisk:= HCAnnexHelper.HCGetAnnexB1(PThisUnit^.Annex);
    if PThisPlaneDisk^.AType = 'H'
    then
        begin
        PThisUnit^.kts:= 0;
        GroupKts:= 0;
        end
    else PThisUnit^.kts:= PThisPlaneDisk^.SpeedLevels[0];
    PThisUnit:= PThisUnit^.next;
  until (PThisUnit = nil);
  end
  else
  begin                  // start sub procedure for non loiter situation
  PThisUnit:= ThisGroup^.Units;
  repeat
    PThisUnit^.Air^.Throttle:= intThrottle;       // could insert steps to test if a reduced throttle will meet group*kts
    PThisUnit^.AltBand:= intAltBand;                 // no idea what GE would think of that?
    PThisUnit^.kts:= GroupKts;
    PThisUnit:= PThisUnit^.next;
  until (PThisUnit = nil);
  end;                          // end sub procedure for non loiter situation
    ThisGroup^.kts:= GroupKts;

  end           // finished, unless group wasn't air group
  else showmessage('oops,something requested setting altitude/throttle of non airgroup');
end;

function TfrmMain.isTanker(PTankAirUnit: PHCUnit): Boolean;
var
  PThisMnt: PHCMntRec;          // don't confuse with PThisMount
  //  PAtogDisk: PHCAnnexG1_Disk;     }DDT 210701 now use unit loadout ID
  ThisWepID: ID;
  PThisLoadout: PHCAnnexY1_disk;
  PThisMount: PHCAnnexZ1_disk;
begin
  result:= False;     // unless made true
  if PTankAirUnit^.Air <> nil then
    if PTankAirUnit^.Mnts <> nil then
      begin
      PThisMnt:= PTankAirUnit^.Mnts;
      while PThisMnt <> nil do
        begin
        //  ThisWepID:= PThisMount^.WepId;    DDT 210701  now using unit loadout id
        //  if (HCAnnexHelper.HCAnnexShift(ThisWepID) = ATOG_FLAG) then
        if (HCAnnexShift(PThisMnt^.MntId) = LOADOUT_FLAG)  then      // then its a load out, check if a tanker mission
          begin
          //PAtogDisk:= HCAnnexHelper.HCGetAnnexG1(ThisWepID);  }
          //if (PAtogDisk^.ClassName= 'Refuel Store') or        } not using this logic now
          //   (PAtogDisk^.ClassName= 'Refuel store')  then     }
          PThisLoadout:= HCAnnexHelper.HCGetAnnexY1(PThisMnt^.MntId);
          if HCMissionMap[PThisLoadout^.MissionID] = Ord(THCMissionType.MISSION_TANKER) then
            begin
            result:= True;
            break
            end;
          end;
        PThisMnt:= PThisMnt^.next;
        end;       // looked at all mounts
      end; // there were mounts
end;

procedure TfrmMain.RflChkBFindChange(Sender: TObject);     //list IFR active groups
var
  PEvent: PHCHarpoonEvent;
begin
  if (RflChkBFind.Checked = True) then
  begin

    RflChkBTnkGrpSrch.Checked:= False;
    RflLbxUnits.Clear;
    RflLbxDetail.Clear;
    RflSpinEditRecip.Value:=0;
    RflSpinEditQuant.Value:=0;
    RflSpinEditTank.Value:=0;
 //     if AGFBtnPause.Caption ='Pause' then    //20210622 test if not paused paused
   //   AGFBtnPause.Click;  //Byte(PDLLBusy^):= CTRUE; now use generic pause buttons     - 231005 remove the pause action
    RflLbxGroups.Clear;
    RflLbxGroups.Items.NameValueSeparator:= '_';
    PEvent:= PHCEventHead;
    while (PEvent <> nil) do
    begin
      if (((PEvent^.flag and CommandMask) = AIR_REFUEL_EVENT) and (PHCGroup(PEvent^.ourGroup)^.side = MySide)) then
      RflLbxGroups.Items.Add(Format('%3.3s_ %d',[PHCGroup(PEvent^.ourGroup)^.id, PEvent^.Time]));
      if gPActGroup = PEvent^.ourGroup then                        //  test 240704
         RflLbxGroups.ItemIndex:= RflLbxGroups.Items.IndexOfName(PHCGroup(PEvent^.ourGroup)^.id);
      PEvent:= PEvent^.next;
    end;
  end;
end;

//procedure TfrmMain.RflChkBResumeChange(Sender: TObject);
//begin
//  if (RflChkBResume.Checked = True) then
//  begin
//      if RflChkBPause.Checked = True then
//      begin
//      RflChkBFind.Checked:= False;
//      RflChkBTnkGrpSrch.Checked:= False;
//      RflLbxGroups.Clear;
//      RflLbxUnits.Clear;
//      RflLbxDetail.Clear;
//      RflSpinEditTank.Value:=0;
//      RflSpinEditRecip.Value:=0;
//      RflSpinEditQuant.Value:=0;
//      PCurrentEvent:= nil;
//      PSelectedGroup:= nil;
//      PageControl1.ActivePageIndex:= intPageActive;
//      end;
//    Byte(PDLLBusy^):= CFALSE;
//  end;
//end;

procedure TfrmMain.RflLbxGroupsSelectionChange(Sender: TObject; User: boolean);
var
  strGroupID: string;
  PThisEvent: PHCHarpoonEvent;

begin
  RflSpinEditRecip.Value:=0;
  RflSpinEditQuant.Value:=0;
  RflSpinEditTank.Value:=0;
  RflLbxDetail.Clear;   //231002  tidy up output

  if (RflChkBFind.Checked = True) then
  begin
    strGroupID:= copy(RflLbxGroups.Items[RflLbxGroups.ItemIndex],1,2);
    PThisEvent:= PHCEventHead;
    while (PThisEvent <> nil) do
    begin
      if (PThisEvent^.ourGroup <> nil) then
        if (strGroupID = copy(PHCGroup(PThisEvent^.ourGroup)^.id,1,2))  then
          if ((PThisEvent^.flag and CommandMask) = AIR_REFUEL_EVENT)
          then
          break;
     PThisEvent:= PThisEvent^.next;
    end;
                          // ddt we should have the group
  if (PThisEvent <> nil) then
  begin
   RflLbxUnitFill(PThisEvent^.ourGroup);
   RflLbxDetailFill(PthisEvent);
   PCurrentRefuelEvent:= PthisEvent;
  end
  else    RflLbxUnits.Items.Add('error?');

  end
  else      // not a refuel event search
  begin
   PSelectedGroup:= PStartGroup;
   strGroupID:= copy(RflLbxGroups.Items[RflLbxGroups.ItemIndex],1,2);
   while (PSelectedGroup <> nil) do
   begin
     if (strGroupID = copy(PSelectedGroup^.id,1,2)) then
     break;
     PSelectedGroup:= PSelectedGroup^.toSouth;
   end;
   if (PSelectedGroup <> nil) then
     RflLbxUnitFill(PSelectedGroup);
  end;
end;

procedure TfrmMain.SqPatrl(SGroup:PHCGroup;PatType:Integer);
var
   PPathLeg, PPathLegNext: PHCPathRec;
   fctr, intLegCnt, PatAltBand: integer;
   cLat, cLon: LongInt;
   LatFactor: double;
   PPathEvent1, PPathEvent2, PPathEvent3: PHCHarpoonEvent;
   PSpeedAltEventLoiter, PSpeedAltEventReset: PHCSpeedAltEvent;
   PEndMessage: PHCMessageEvent;
   PRadarSet: PHCIntermittentSensorsEvent;
   intSortieKts: short;
   SortieThrottle: uchar;

  begin
     if (SGroup^.Units^.Air = nil) then
  ShowMessage('Air Groups Only')
     else
    begin
//     if (SGroup^.Units^.Air^.Throttle=0) then
//      begin          setting cruise to patrol area for all situations
//  need to test if NOE and patrol for ASW VLOW if yes   ditto vhigh

      ShowMessage('Setting AirGroup to Cruise at Patrol altitude');
        case PatType of
     0:     begin
            PatAltBand:= 11;     //  240528 set to vlow if allowed (tested in SetGroupSpeedAlt
            sortieThrottle:=1;
            end;
     1:     begin
            PatAltBand:= 15;
            sortieThrottle:= 3;
            end;
     2:     begin
            PatAltBand:= 13;
            sortieThrottle:= 2;
            end;
     3:     begin
            PatAltBand:= 15;
            sortieThrottle:= 3;
            end;
        end;
        SetGroupSpeedAlt(SGroup, sortieThrottle, PatAltBand);               //  group,throttle,altband
 //     end;    ddt 20180429 now setting cruise to patrol area for all situations
//     else     ddt 1711??    called setgroupspeedalt so don't need to stop

     intSortieKts:= SGroup^.kts;      //used to reset  speed altband and throttle
     SortieThrottle:= SGroup^.Units^.Air^.Throttle;
     PatAltBand:= SGroup^.Units^.AltBand;
     //  test for 0 size pattern and if so process existing path ELSE process hexPatrol
    if AGFSpnBxpatrl.Value = 0
    then
    begin
    if SGroup^.track <> nil then
     begin
     PPathLeg:=SGroup^.track;
     while (PPathLeg^.next <> nil) do     // if only one leg then should warn need two leg minimum
       begin
       // .................
     PPathEvent1:= NewPtr(SizeOf(THCHarpoonEvent));
//  250806     PPathEvent1^.uuid := GenerateUUID;
     PPathEvent2:= NewPtr(SizeOf(THCHarpoonEvent));
     //  250806     PPathEvent2^.uuid := GenerateUUID;
     PSpeedAltEventReset:= NewPtr(SizeOf(THCSpeedAltEvent));
     PSpeedAltEventReset^.newAltBand:= PatAltBand;
     PSpeedAltEventReset^.newSpeed:= intSortieKts;
     PSpeedAltEventReset^.newThrottle:= SortieThrottle;
     PSpeedAltEventLoiter:= NewPtr(SizeOf(THCSpeedAltEvent));
     PSpeedAltEventLoiter^.newAltBand:= PatAltBand;
     PSpeedAltEventLoiter^.newThrottle:= 0;
     PSpeedAltEventLoiter^.newSpeed:= getLoiterKts(SGroup);
     Case PatType of             // set radar, no radar asw
//    0: begin
//        end;
     1: begin
     PRadarSet:= NewPtr(SizeOf(THCIntermittentSensorsEvent));
     PRadarSet^.Duration:=60;
     PRadarSet^.gtype:=000;
     PRadarSet^.Zones[0]:=1;         //set AS on main zone
     PRadarSet^.Zones[1]:=1;         //set SS  on main zone
     PRadarSet^.Interval:=10;
     PRadarSet^.Variance:=0;
     PRadarSet^.Toggle:=000;         //start on
        end;
     2: begin
     PRadarSet:= NewPtr(SizeOf(THCIntermittentSensorsEvent));
     PRadarSet^.Duration:=60;
     PRadarSet^.gtype:=000;
     PRadarSet^.Zones[0]:=1;         //set AS on main zone
     PRadarSet^.Zones[1]:=1;         //set SS  on main zone
     PRadarSet^.Interval:=5;
     PRadarSet^.Variance:=0;
     PRadarSet^.Toggle:=000;         //start on
        end;
     3: begin        //  EW PatType
     PRadarSet:= NewPtr(SizeOf(THCIntermittentSensorsEvent));
     PRadarSet^.Duration:=150;
     PRadarSet^.gtype:=000;
     PRadarSet^.Zones[0]:=1;         //set AS on main zone
     PRadarSet^.Zones[1]:=1;         //set SS  on main zone
     PRadarSet^.Interval:=3;
     PRadarSet^.Variance:=0;
     PRadarSet^.Toggle:=001;         //start on
        end;
     end;          // end case Pat Type to set radar
     if (patType <> 0)
     then
     begin
     PPathEvent3:= NewPtr(SizeOf(THCHarpoonEvent));
//  250806     PPathEvent3^.uuid := GenerateUUID;
     PPathEvent3^.DataPtr:=PRadarSet;
     PPathEvent3^.flag := INTERMITTENT_EVENT or TimeNow;
 //    PPathEvent3^.time_type:= TimeNow;
     PPathEvent3^.Time:= 0;
     PPathEvent3^.in_scroll_flag:= 001;
     PPathEvent3^.ourGroup:= SGroup;
     PPathEvent3^.next:= nil;
     PPathEvent2^.next:= PPathEvent3;
     end
     else
     PPathEvent2^.next:= nil;  // asw patType = no radar

     PPathEvent2^.ourGroup:= SGroup;
     PPathEvent2^.UnitID:= NoUnit;       // Will this work?
     PPathEvent2^.Time:= 120;   // 2 mins at loiter hover
     PPathEvent2^.flag := ALT_SPEED_EVENT or TimeDelta;
//     PPathEvent2^.time_type:= TimeDelta;
     PPathEvent2^.in_scroll_flag:= 001;
     PPathEvent2^.DataPtr:=PSpeedAltEventReset;          // have set up leg event 2 same for every pat type
     //case patType of
     //0: begin
     PPathEvent1^.DataPtr:= PSpeedAltEventLoiter;
     PPathEvent1^.flag := ALT_SPEED_EVENT or TimeDelta;
//     PPathEvent1^.time_type:= TimeNow;
     //   end;
     //1: begin
     //PPathEvent1^.DataPtr:=PRadarSet;
     //PPathEvent1^.event_type := INTERMITTENT_EVENT;  // or TimeDelta;
     //  PPathEvent1^.time_type:= TimeDelta;
     //   end;
     //2: begin
     //PPathEvent1^.DataPtr:=PRadarSet;
     //PPathEvent1^.event_type := INTERMITTENT_EVENT;  // or TimeDelta;
     //  PPathEvent1^.time_type:= TimeDelta;
     //   end;
//     end;        // end case setting up leg event 1 for respective pat type
     PPathEvent1^.UnitID:= NoUnit;
     PPathEvent1^.in_scroll_flag:= 001;
     PPathEvent1^.ourGroup:= SGroup;
     PPathEvent1^.Time:= 0;
     PPathEvent1^.next:= PPathEvent2;

     PPathLeg^.pathEvents:= PPathEvent1;
       //................
       PPathLeg:= PPathLeg^.next;
       end;       //  end going down legs
     end; // end there was a track else should warn need a track or a pattern size
     end       // end pat size zero
    else
     begin                            // start guts
     DelPath(SGroup);              //strips pointers & events out of any multi point path and delete path
     fctr:=AGFspnbxPatrl.Value div 2;      //approx NM scaling of box size first loop
     LatFactor:= LatFctGrp(SGroup);   // corrects sLon for 'high' latitudes

     PPathLeg:= NewPtr(SizeOf(THCPathRec));
     if (SGroup^.track <> nil) then
     begin
       cLat:= SGroup^.track^.DestLat;
       cLon:= SGroup^.track^.DestLon;    //add patrol legs around  end of current path leg
     end
     else
     begin
       cLat:= SGroup^.sLat;
       cLon:= SGroup^.sLon;
     end;
                                         // now setup first patrol leg
     PPathLeg^.DestLat:=cLat; // - (NM1*fctr) ddt 171116 mod pattern to spiral out from centre
     PPathLeg^.DestLon:=cLon;

     PPathEvent1:= NewPtr(SizeOf(THCHarpoonEvent));
//  250806     PPathEvent1^.uuid := GenerateUUID;
     PPathEvent2:= NewPtr(SizeOf(THCHarpoonEvent));
//  250806     PPathEvent2^.uuid := GenerateUUID;

     PSpeedAltEventReset:= NewPtr(SizeOf(THCSpeedAltEvent));
     PSpeedAltEventReset^.newAltBand:= PatAltBand;
     PSpeedAltEventReset^.newSpeed:= intSortieKts;
     PSpeedAltEventReset^.newThrottle:= SortieThrottle;
     Case PatType of
     0: begin
     PSpeedAltEventLoiter:= NewPtr(SizeOf(THCSpeedAltEvent));
     PSpeedAltEventLoiter^.newAltBand:= PatAltBand;
     PSpeedAltEventLoiter^.newThrottle:= 0;
     PSpeedAltEventLoiter^.newSpeed:= getLoiterKts(SGroup);
        end;
     1: begin
     PRadarSet:= NewPtr(SizeOf(THCIntermittentSensorsEvent));
     PRadarSet^.Duration:=60;
     PRadarSet^.gtype:=000;
     PRadarSet^.Zones[0]:=1;         //set AS on main zone
     PRadarSet^.Zones[1]:=1;         //set SS  on main zone
     PRadarSet^.Interval:=10;
     PRadarSet^.Variance:=0;
     PRadarSet^.Toggle:=000;         //start on
        end;
     2: begin
     PRadarSet:= NewPtr(SizeOf(THCIntermittentSensorsEvent));
     PRadarSet^.Duration:=60;
     PRadarSet^.gtype:=000;
     PRadarSet^.Zones[0]:=1;         //set AS on main zone
     PRadarSet^.Zones[1]:=1;         //set SS  on main zone
     PRadarSet^.Interval:=5;
     PRadarSet^.Variance:=0;
     PRadarSet^.Toggle:=000;         //start on
        end;
     3: begin
     PRadarSet:= NewPtr(SizeOf(THCIntermittentSensorsEvent));
     PRadarSet^.Duration:=150;
     PRadarSet^.gtype:=000;
     PRadarSet^.Zones[0]:=1;         //set AS on main zone
     PRadarSet^.Zones[1]:=1;         //set SS  on main zone
     PRadarSet^.Interval:=3;
     PRadarSet^.Variance:=0;
     PRadarSet^.Toggle:=000;         //start on
        end;
     end;
     PPathEvent2^.next:= nil;
     PPathEvent2^.ourGroup:= SGroup;
     PPathEvent2^.UnitID:= NoUnit;
     ;       // Will this work?
     PPathEvent2^.Time:= 150;   // 2 1/2 mins at loiter hover
     PPathEvent2^.flag := ALT_SPEED_EVENT or TimeDelta;
 //      PPathEvent2^.time_type:= TimeDelta;
     PPathEvent2^.in_scroll_flag:= 001;
     PPathEvent2^.DataPtr:=PSpeedAltEventReset;
     case patType of
     0: begin
     PPathEvent1^.DataPtr:= PSpeedAltEventLoiter;
     PPathEvent1^.flag := ALT_SPEED_EVENT or TimeDelta;
 //    PPathEvent1^.time_type:= TimeDelta;
        end;
     1: begin
     PPathEvent1^.DataPtr:=PRadarSet;
     PPathEvent1^.flag := INTERMITTENT_EVENT or TimeDelta;
//       PPathEvent1^.time_type:= TimeDelta;
        end;
     2: begin
     PPathEvent1^.DataPtr:=PRadarSet;
     PPathEvent1^.flag := INTERMITTENT_EVENT or TimeDelta;
//     PPathEvent1^.time_type:= TimeDelta;
        end;
     3: begin
     PPathEvent1^.DataPtr:=PRadarSet;
     PPathEvent1^.flag := INTERMITTENT_EVENT or TimeDelta;
//     PPathEvent1^.time_type:= TimeDelta;
        end;
     end;
     PPathEvent1^.UnitID:= NoUnit;
     PPathEvent1^.in_scroll_flag:= 001;
     PPathEvent1^.ourGroup:= SGroup;
     PPathEvent1^.Time:= 0;
     PPathEvent1^.next:= PPathEvent2;

     PPathLeg^.pathEvents:= PPathEvent1;

     SGroup^.track:=PPathLeg;
                                                     //now do remaining patrol legs
     for intLegCnt := 1 to 16   do                    // add to pattern by increasing intLegCnt and adding
     begin
     PPathLegNext:= NewPtr(SizeOf(THCPathRec));          // appropriate 'case' items
     PPathLeg^.next:= PPathLegNext;
     PPathLegNext^.prev:= PPathLeg;
     PPathLeg:= PPathLegNext;

     case intLegCnt of
     1,9: begin
     PPathLeg^.DestLat:=cLat - Round(NM1*fctr*0.6);
     PPathLeg^.DestLon:=cLon + Round(NM1*fctr*0.6 / LatFactor);
        end;
     2,10: begin
     PPathLeg^.DestLat:=cLat;
     PPathLeg^.DestLon:=cLon + Round(NM1*fctr / LatFactor);
        end;
     3,11: begin
     PPathLeg^.DestLat:=cLat + Round(NM1*fctr*0.7);
     PPathLeg^.DestLon:=cLon + Round(NM1*fctr*0.7 / LatFactor);
        end;
     4,12: begin
     PPathLeg^.DestLat:=cLat + (NM1*fctr);
     PPathLeg^.DestLon:=cLon;
        end;
     5,13: begin
     PPathLeg^.DestLat:=cLat + Round(NM1*fctr*0.7);
     PPathLeg^.DestLon:=cLon - Round(NM1*fctr*0.7 / LatFactor);
        end;
     6,14: begin
     PPathLeg^.DestLat:=cLat;
     PPathLeg^.DestLon:=cLon - Round(NM1*fctr / LatFactor);
        end;
     7,15: begin
     PPathLeg^.DestLat:=cLat - Round(NM1*fctr*0.7);
     PPathLeg^.DestLon:=cLon - Round(NM1*fctr*0.7 / LatFactor);
        end;
     8: begin
     fctr:= 2 * fctr;       // div 3 ddt 171116 modify pattern to spiral out from centre
     PPathLeg^.DestLat:=cLat - (NM1*fctr);
     PPathLeg^.DestLon:=cLon;
        end;
     16: begin                      // returns to pattern centre
     PPathLeg^.DestLat:= cLat;
     PPathLeg^.DestLon:= clon;
         end;
     end;

    PPathEvent1:= NewPtr(SizeOf(THCHarpoonEvent));
//  250806    PPathEvent1^.uuid := GenerateUUID;
    PPathEvent2:= NewPtr(SizeOf(THCHarpoonEvent));
//  250806    PPathEvent2^.uuid := GenerateUUID;
    PSpeedAltEventLoiter:= NewPtr(SizeOf(THCSpeedAltEvent));
    PSpeedAltEventReset:= NewPtr(SizeOf(THCSpeedAltEvent));
    PSpeedAltEventReset^.newAltBand:= SGroup^.Units^.AltBand;
    PSpeedAltEventReset^.newSpeed:= intSortieKts;
    PSpeedAltEventReset^.newThrottle:= SortieThrottle;
    PSpeedAltEventLoiter^.newAltBand:= PatAltBand;
    PSpeedAltEventLoiter^.newThrottle:= 0;
    PSpeedAltEventLoiter^.newSpeed:= getLoiterKts(SGroup);     //  set to speed from platform annex

    PPathEvent2^.next:= nil;
    PPathEvent2^.ourGroup:= SGroup;
    PPathEvent2^.UnitID:= NoUnit;
    PPathEvent2^.Time:= 120;                 // 2 mins at loiter/hover
    PPathEvent2^.flag := ALT_SPEED_EVENT or TimeDelta;
//      PPathEvent2^.time_type:= TimeDelta;
    PPathEvent2^.in_scroll_flag:= 001;
    PPathEvent2^.DataPtr:=PSpeedAltEventReset;

    PPathEvent1^.DataPtr:= PSpeedAltEventLoiter;
    PPathEvent1^.flag := ALT_SPEED_EVENT or TimeDelta;
//      PPathEvent1^.time_type:= TimeDelta;
    PPathEvent1^.in_scroll_flag:= 001;
    PPathEvent1^.ourGroup:= SGroup;
    PPathEvent1^.Time:= 0;
    PPathEvent1^.UnitID:= NoUnit;
    PPathEvent1^.next:= PPathEvent2;

    PPathLeg^.pathEvents:= PPathEvent1;
     end;
     PPathLeg^.next:=nil;
     PEndMessage:= NewPtr(SizeOf(THCMessageEvent));
     PEndMessage^.Message:= 'Patrol complete, require orders';
//     PEndMessage^.Side := ANYSIDE;           //  ddt 20230402 upgrade structure
//  PEndMessage^.MessageFormat := Ord(THCTextTypes.ASCII);
     PPathEvent2^.DataPtr:= PEndMessage;
     PPathEvent2^.flag := MESSAGE_EVENT or TimeDelta;
//       PPathEvent2^.time_type:= TimeDelta;
     PPathEvent2^.in_scroll_flag:= 001;
     PPathEvent2^.ourGroup:= SGroup;
     PPathEvent2^.Time:= 0;
       PPathEvent2^.UnitID:= SGroup^.Units^.id;
     end;                   // end 'guts' hexPatrol
    end;                    // was air group and finishsed
end;

function TfrmMain.getLoiterKts(ThisGroup: PHCGroup): short;
var
    PThisUnit: PHCUnit;
    ltrKts: short;
begin
  PThisUnit:= ThisGroup^.Units;
  PThisPlaneDisk:= HCAnnexHelper.HCGetAnnexB1(PThisUnit^.Annex);  // start with 1st unit nowcheck if any slower units
  ltrKts:= PThisPlaneDisk^.SpeedLevels[0];
  while (PThisUnit^.next <> nil) do
    begin
    PThisUnit:= PThisUnit^.next;
    PThisPlaneDisk:= HCAnnexHelper.HCGetAnnexB1(PThisUnit^.Annex);
    if PThisPlaneDisk^.AType = 'H'
    then
        ltrKts:= 0         // + should break
    else if ltrKts > PThisPlaneDisk^.SpeedLevels[0]
          then  ltrKts := PThisPlaneDisk^.SpeedLevels[0];
    end;
  Result:= ltrKts;
end;

procedure TfrmMain.RflLbxUnitFill(PRefuelGroup: PHCGroup);
var
   PUnit: PHCUnit;
   PUnitMnt: PHCMntRec;
   PUnitLoadout: PHCAnnexY1_disk;
   PUnitAnnex: PHCAnnexB1_disk;
   intEndMins, intEndNM, intFullNM, intCrseEndNM: integer;
begin
   RflLbxUnits.Clear;
   PUnit:= PRefuelGroup^.Units;
   RflLbxUnits.Items.Add(Format('Grp: %d kts %d deg',[PUnit^.kts, PRefuelGroup^.course]));
     // could put in loadout range from annexdata   done    240620
   while (PUnit <> nil) do
   begin
    intEndMins:= (PUnit^.Air^.Endurance) div (4 * 60);
    intEndNM:= (intEndMins * PUnit^.kts) div 60;
    PUnitMnt:= PUnit^.Mnts;
    PUnitAnnex:= HCAnnexHelper.HCGetAnnexB1(PUnit^.Annex);
    if PUnitMnt <> nil then
       if  HCAnnexHelper.HCAnnexShift(PUnitMnt^.MntId) = LOADOUT_FLAG then
           begin
           PUnitLoadout:= HCAnnexHelper.HCGetAnnexY1(PUnitMnt^.MntId);
           intFullNM:= (PUnitLoadout^.Range);
           intCrseEndNM:= (PUnitAnnex^.SpeedLevels[1] + PUnitAnnex^.SpeedLevels[3]) div 2; // interem calc
           intCrseEndNM:= (intCrseEndNM * intEndMins )div 60;                              // 240623
           end;
    RflLbxUnits.Items.Add(Format('%0.3d: %d-%s',[PUnit^.id, PUnit^.Air^.PlatNow, PUnit^.name]));
    RflLbxUnits.Items.Add(Format('- %d mins %dnm, %d/ %d',        //  240715 ready to filter for popup menu
      [intEndMins, intEndNM, intCrseEndNM, intFullNM]));
    if (PUnit^.Air^.FuelToOffload >0)
    then RflLbxUnits.Items.Append(Format('- IFR ready: %d nm per tanker',[PUnit^.Air^.FuelToOffload]));
    PUnit:= PUnit^.next;
   end; // unit box complete,

end;

procedure TfrmMain.RflLbxDetailFill(PRefuelEvent: PHCHarpoonEvent);
var
   PRefuelEventDetail: PHCAirRefuelEvent;
   strUnitID, strFuelling: string;
   intTotFuel, N: integer;
begin
  RflLbxDetail.Clear;
  PRefuelEventDetail:= PRefuelEvent^.DataPtr;
 //
  strUnitID:= Format('%3.3d',[PRefuelEventDetail^.fuelingUnit]);
  PSelectedUnit:= PHCGroup(PRefuelEvent^.ourGroup)^.Units;
  while (PSelectedUnit <> nil) do
  begin
    if (strUnitID = Format('%3.3d',[PSelectedUnit^.id])) then
    break;
    PSelectedUnit:= PSelectedUnit^.next;
  end;
  if (PSelectedUnit <> nil) then    // need to test for nil pselectedunit for if tanker split off
  begin
  intTotFuel:= (PSelectedUnit^.Air^.FuelToOffload) * strToInt(Format('%d',[PRefuelEventDetail^.num_tankers]));
  if (PRefuelEventDetail^.now_refueling = 0) then
    strFuelling:= 'No'
    else strFuelling := 'Yes';
  //
  RflLbxDetail.Items.Add(Format('Fueling Unit- %2.2s:%3.3d', [PHCGroup(PRefuelEvent^.ourGroup)^.id, PRefuelEventDetail^.fuelingUnit]));    //  231002 tidyup output
  RflLbxDetail.Items.Add(Format('Number of Tankers: %2.2d - %s', [PRefuelEventDetail^.num_tankers, PSelectedUnit^.name]));
  RflLbxDetail.Items.Add('Fueling: ' + strFuelling);
  RflLbxDetail.Items.Add(Format('Original Refuel Range: %4.4d', [PRefuelEventDetail^.fuel_range]));
  RflLbxDetail.Items.Add(Format('Available Refuel Range %4.4d',[intTotFuel]));
  RflLbxDetail.Items.Add('. . . . . . . .');
  // now do the queue
  N := 0;
  repeat
  begin
  RflLbxDetail.Items.Add(Format('# %d: unit %3.3d', [N, PRefuelEventDetail^.planestofuel[N]]));
  If PRefuelEventDetail^.planestofuel[N] = 255 then
    RflLbxDetail.Items.Add('Refuel cycle finished')
    else
    if
      PRefuelEventDetail^.planestofuel[N] = 205 then
      RflLbxDetail.Items.Add('Awaiting start of refuel?')
      else
      RflLbxDetail.Items.Add(Format('Per Plane Fuel: %4.4d', [PRefuelEventDetail^.fuel_per_plane[N]]));
  N := N+1;
  end;
  until  PRefuelEventDetail^.planestofuel[N-1] > 200;
  PCurrentEvent:= PRefuelEvent;
  end
  else RflLbxDetail.Items.Add('refuel tanker has been split off?');
end;

function TfrmMain.Side2String(const ThisSide: uchar): string;

  begin
    case ThisSide of
    0: result:= 'Blue';
    1: result:= 'Red';
    2: result:= 'Green';
    else result:= 'Unknown';
    end;
  end;

//** Counts number of units in a Group, value 0 if no valid group**
function TfrmMain.CountUnits(const pPGroup: PHCGroup): NativeUInt;
var
  lIntUnits: NativeUInt;
  lPUnit: PHCUnit;
begin
  lIntUnits := 0;
  if (pPGroup <> nil) then
  begin
    lPUnit := pPGroup^.Units;
    while (lPUnit <> nil) do
    begin
      Inc(lIntUnits);
      lPUnit := lPUnit^.Next;
    end;
  end;
//  Result := lIntUnits;   220230626
    exit(lIntUnits);      // if no valid group then will exit with value 0 (zero)
end;

end.

