library lazGUI; {$mode delphi}{$H+} uses Classes { you can add units after this }, uHCDefines, mainform, uHCGCI, uHCGlobals, uHCTypeDefs, uHCEventHelpers, uHCDateTime, uHCAnnexDefs, uHCAnnexHelpers, uHCUtils, uLazGUIGlobals, Windows, Forms, ShellAPI, SysUtils, Types, Dialogs, Interfaces, Controls, DateUtils; const DLLVERN = 38; DLLVERINTN = 45; var frmMain: TfrmMain; {*** Return DLL internal version, i.e. revision 5 of my DLL ***} function DLLVer(): word; export; cdecl; begin Result := DLLVERN; end; {*** Return DLL interface version, i.e. revision 1 of my HCE interface spec ***} function DLLVerInt(): word; export; cdecl; begin Result := DLLVERINTN; end; {*** Return DLL description, 255 characters max ***} function DLLDesc(): PAnsiChar; export; cdecl; begin Result := 'lazarus GUI test DLL by Tony Eischens'; end; {*** Free DLL ***} procedure DLLFree(); export; cdecl; begin // Do any cleanup here, this is the end frmMain.Free; end; {*** Init DLL ***} procedure DLLInit(hwndHC: HWND; pathHC: PAnsiChar; verHC: PAnsiChar; verSaveGame: longword; funcRefs: PHCDLL_FUNC_REF; PGEClosing: Pointer; PDLLBusy: Pointer; verInterface: longword; srandSeed: longword; harpoon_uuid_session: PAnsiChar; hclicense_dt_expire: Int64; hclicense_licensee: PAnsiChar; usblicense_desc: PAnsiChar; RESRSRStructureVersion: ulong; AnnexVersion: ushort; GameComponent: uchar; pLosTable: PHCLosTable); export; cdecl; var funcTmp: PHCDLL_FUNC_REF; begin // here we initialize globals and such for the life of the DLL Application.Initialize; // ALE 20120410 Freezes HC Application.Run; uLazGUIGlobals.PGEClosing := PGEClosing; uLazGUIGlobals.PDLLBusy := PDLLBusy; uLazGUIGlobals.hwndHC:=hwndHC; uLazGUIGlobals.gGameComponent:=GameComponent; uLazGUIGlobals.gDLLPathFileName:=GetModuleName(hInstance); // ALE 20250912 works 4 both Delphi and FreePascal frmMain := TfrmMain.Create(nil); frmMain.ParentWindow := hwndHC; frmMain.Caption := 'lazarus GUI Export DLL v' + IntToStr(DLLVERN) + ', ExportDLL Interface v' + IntToStr(DLLVERINTN); frmMain.memDLLInit.Lines.Add('lazarus GUI Export DLL v' + IntToStr(DLLVERN)); frmMain.memDLLInit.Lines.Add('ExportDLL Interface v' + IntToStr(DLLVERINTN)); frmMain.memDLLInit.Lines.Add('hwndHC = 0x' + IntToHex(hwndHC, 8)); frmMain.memDLLInit.Lines.Add('pathHC = ' + pathHC); frmMain.memDLLInit.Lines.Add('pathFileNameDLL = ' + uLazGUIGlobals.gDLLPathFileName); if (GameComponent = GC_UNK) then frmMain.memDLLInit.Lines.Add('Game Component = Unknown') else if (GameComponent = GC_GE) then frmMain.memDLLInit.Lines.Add('Game Component = Game Engine (GE)') else if (GameComponent = GC_SE) then frmMain.memDLLInit.Lines.Add('Game Component = Scenario Editor (SE)'); frmMain.memDLLInit.Lines.Add('verHC = ' + verHC); frmMain.memDLLInit.Lines.Add('verSaveGame = 0x' + IntToHex(verSaveGame, 8)); frmMain.memDLLInit.Lines.Add(Format('funcRefs = 0x%p', [funcRefs])); frmMain.memDLLInit.Lines.Add(Format('GE ExportDLL Interface Version = %d', [verInterface])); frmMain.memDLLInit.Lines.Add('RESRSRStructureVersion = ' + IntToStr(RESRSRStructureVersion)); frmMain.memDLLInit.Lines.Add('AnnexVersion = ' + IntToStr(AnnexVersion)); frmMain.memDLLInit.Lines.Add('harpoon_uuid_session = ' + harpoon_uuid_session); frmMain.memDLLInit.Lines.Add(Format('LosTable[SubOrbit][SubOrbit]=%dnm, LosTable[SubOrbit][VHigh]=%dnm', [pLosTable^[AltBands-SubOrbit][AltBands-SubOrbit], pLosTable^[AltBands-SubOrbit][AltBands-VHigh]])); frmMain.DLLDescstr:=DLLDesc(); frmMain.DLLVERNstr:=IntToStr(DLLVERN); uLazGUIGlobals.intVerInterface := DLLVERINTN; uLazGUIGlobals.intVerSaveGame := verSaveGame; // ALE 20170224 Alert the user that the GE is too old for this ExportDLL if (verInterface < DLLVERINTN) then begin MessageDlg('GE too old', GetModuleName(hInstance) + ' is designed for HC2025.013+. Please ' + ' update your GE or disable this ExportDLL.', mtWarning, [mbOK], 0); end; // ALE 20130908 List the function pointers sent over by the GE // then capture one of them for example usage funcTmp := funcRefs; while (funcTmp <> nil) do begin frmMain.memDLLInit.Lines.Add(#9 + Format('funcRef: 0x%p %s', [funcTmp^.funcPtr, funcTmp^.funcName])); if (funcTmp^.funcName = 'NewPtr') then frmMain.NewPtr := funcTmp^.funcPtr else if (funcTmp^.funcName = 'DispPtr') then frmMain.DisposPtr := THCDisposPtr(funcTmp^.funcPtr) else if (funcTmp^.funcName = 'QueueHarpoonEvent') then frmMain.HCQueueHarpoonEvent := THCQueueHarpoonEvent(funcTmp^.funcPtr) else if (funcTmp^.funcName = 'dllCheckLatLonDepth') then frmMain.HCdllCheckLatLonDepth := THCdllCheckLatLonDepth(funcTmp^.funcPtr) else if (funcTmp^.funcName = 'annex_id_to_disk_id') then frmMain.HCannex_id_to_disk_id := THCannex_id_to_disk_id(funcTmp^.funcPtr) else if (funcTmp^.funcName = 'disk_id_to_annex_id') then frmMain.HCdisk_id_to_annex_id := THCdisk_id_to_annex_id(funcTmp^.funcPtr) else if (funcTmp^.funcName = 'MessageLogLine') then frmMain.HCMessageLogLine := THCMessageLogLine(funcTmp^.funcPtr) else if (funcTmp^.funcName = 'free_Unit') then frmMain.HCfree_Unit := THCfree_Unit(funcTmp^.funcPtr) else if (funcTmp^.funcName = 'free_Group') then frmMain.HCfree_Group := THCfree_Group(funcTmp^.funcPtr) else if (funcTmp^.funcName = 'ChangeActGroupPtr') then frmMain.HCChangeActGroupPtr := THCChangeActGroupPtr(funcTmp^.funcPtr) else if (funcTmp^.funcName = 'ChangeActUnitPtr') then frmMain.HCChangeActUnitPtr := THCChangeActUnitPtr(funcTmp^.funcPtr) else if (funcTmp^.funcName = 'ZoomWindow') then frmMain.HCZoomWindow := THCZoomWindow(funcTmp^.funcPtr) else if (funcTmp^.funcName = 'ExitGame') then frmMain.HCExitGame := THCExitGame(funcTmp^.funcPtr) else if (funcTmp^.funcName = 'SaveGame') then frmMain.HCSaveGame := THCSaveGame(funcTmp^.funcPtr); funcTmp := funcTmp^.Next; end; end; procedure UnitLatLon(UnitID: byte; GroupID: PAnsiChar; Lat: double; Lon: double); export; cdecl; begin if (frmMain.ULLckbEnabled.Checked = True) then begin frmMain.ULLmemMsgLog.Lines.Insert(0, Format('%8.8d: GroupID=%s, UnitID=%2.2d, Lat=%3.2f, Lon=%3.2f, Depth/Altitude=%d', [intGameTime, GroupID, UnitID, Lat, Lon, frmMain.HCdllCheckLatLonDepth(Lat, Lon)])); if (frmMain.ULLmemMsgLog.Lines.Count > frmMain.ULLedtLines.Value) then begin frmMain.ULLmemMsgLog.Lines.Delete(frmMain.ULLedtLines.Value); end; end; end; procedure UnitShowing(UnitID: byte; GroupID: PAnsiChar; Showing: byte); export; cdecl; begin if (frmMain.USckbEnabled.Checked = True) then begin frmMain.USmemMsgLog.Lines.Insert(0, Format('%8.8d: GroupID=%s, UnitID=%2.2d, Showing=%d', [intGameTime, GroupID, UnitID, Showing])); if (frmMain.USmemMsgLog.Lines.Count > frmMain.USedtLines.Value) then begin frmMain.USmemMsgLog.Lines.Delete(frmMain.USedtLines.Value); end; end; end; procedure UnitURegion(UnitID: byte; GroupID: PAnsiChar; pUR: PTURegion); export; cdecl; var intC1, intC2: integer; strText: string; begin if (frmMain.UURckbEnabled.Checked = True) then begin strText := Format('%8.8d: GroupID=%s, UnitID=%2.2d, UnitURegion', [intGameTime, GroupID, UnitID]); for intC1 := 0 to SIDES - 1 do begin for intC2 := 0 to 3 do // Four corners of uncertainty region begin // Dump data to text file // (double)((sLon*360.0)/SLIVER360)) // (double)(90-(sLat*360.0/SLIVER360)) strText := strText + Format(', %8.8f, %8.8f', [(90 - (pUR^[intC1]^.sLat[intC2] * 360.0 / SLIVER360)), ((pUR^[intC1]^.sLon[intC2] * 360.0) / SLIVER360)]); end; end; frmMain.UURmemMsgLog.Lines.Insert(0, strText); if (frmMain.UURmemMsgLog.Lines.Count > frmMain.UURedtLines.Value) then begin frmMain.UURmemMsgLog.Lines.Delete(frmMain.UURedtLines.Value); end; end; end; procedure UnitCourse(UnitID: byte; GroupID: PAnsiChar; course: smallint); export; cdecl; begin if (frmMain.UCckbEnabled.Checked = True) then begin frmMain.UCmemMsgLog.Lines.Insert(0, Format('%8.8d: GroupID=%s, UnitID=%2.2d, Course (deg)=%d', [intGameTime, GroupID, UnitID, course])); if (frmMain.UCmemMsgLog.Lines.Count > frmMain.UCedtLines.Value) then begin frmMain.UCmemMsgLog.Lines.Delete(frmMain.UCedtLines.Value); end; end; end; procedure UnitSpeed(UnitID: byte; GroupID: PAnsiChar; speed: smallint); export; cdecl; begin if (frmMain.USPckbEnabled.Checked = True) then begin frmMain.USPmemMsgLog.Lines.Insert(0, Format('%8.8d: GroupID=%s, UnitID=%2.2d, Speed (kts)=%d', [intGameTime, GroupID, UnitID, speed])); if (frmMain.USPmemMsgLog.Lines.Count > frmMain.USPedtLines.Value) then begin frmMain.USPmemMsgLog.Lines.Delete(frmMain.USPedtLines.Value); end; end; end; procedure UnitAlt(UnitID: byte; GroupID: PAnsiChar; alt: byte); export; cdecl; begin if (frmMain.UAckbEnabled.Checked = True) then begin frmMain.UAmemMsgLog.Lines.Insert(0, Format('%8.8d: GroupID=%s, UnitID=%2.2d, Altitude Band=%d', [intGameTime, GroupID, UnitID, alt])); if (frmMain.UAmemMsgLog.Lines.Count > frmMain.UAedtLines.Value) then begin frmMain.UAmemMsgLog.Lines.Delete(frmMain.UAedtLines.Value); end; end; end; procedure UnitShowingSide(UnitID: byte; GroupID: PAnsiChar; showing: PTSidesUChar); export; cdecl; var intC: integer; strText: string; begin if (frmMain.USSckbEnabled.Checked = True) then begin strText := ''; for intC := 0 to SIDES - 1 do begin // Dump data to text file strText := strText + Format('%3.3d', [showing^[intC]]); end; frmMain.USSmemMsgLog.Lines.Insert(0, Format('%8.8d: GroupID=%s, UnitID=%2.2d, Showing=%s', [intGameTime, GroupID, UnitID, strText])); if (frmMain.USSmemMsgLog.Lines.Count > frmMain.USSedtLines.Value) then begin frmMain.USSmemMsgLog.Lines.Delete(frmMain.USSedtLines.Value); end; end; end; procedure UnitSink(UnitID: byte; GroupID: PAnsiChar); export; cdecl; begin if (frmMain.USKckbEnabled.Checked = True) then begin frmMain.USKmemMsgLog.Lines.Insert(0, Format('%8.8d: GroupID=%s, UnitID=%2.2d, Sinking', [intGameTime, GroupID, UnitID])); if (frmMain.USKmemMsgLog.Lines.Count > frmMain.USKedtLines.Value) then begin frmMain.USKmemMsgLog.Lines.Delete(frmMain.USKedtLines.Value); end; end; end; procedure UnitToGroup(UnitID: byte; GroupID, GroupIDold: PAnsiChar); export; cdecl; begin if (frmMain.UGckbEnabled.Checked = True) then begin frmMain.UGmemMsgLog.Lines.Insert(0, Format('%8.8d: GroupID=%s, UnitID=%2.2d, UnitToGroup OldGroup=%s', [intGameTime, GroupID, UnitID, GroupIDold])); if (frmMain.UGmemMsgLog.Lines.Count > frmMain.UGedtLines.Value) then begin frmMain.UGmemMsgLog.Lines.Delete(frmMain.UGedtLines.Value); end; end; end; procedure UnitNew(UnitID: byte; GroupID: PAnsiChar; utype: shortint; side: byte; annexID: word; Name: PAnsiChar); export; cdecl; begin if (frmMain.UNckbEnabled.Checked = True) then begin frmMain.UNmemMsgLog.Lines.Insert(0, Format('%8.8d: GroupID=%s, UnitID=%2.2d, UnitNew Type=%d, Side=%d, AnnexID=%d, Name=%s', [intGameTime, GroupID, UnitID, utype, side, annexID, Name])); if (frmMain.UNmemMsgLog.Lines.Count > frmMain.UNedtLines.Value) then begin frmMain.UNmemMsgLog.Lines.Delete(frmMain.UNedtLines.Value); end; end; end; procedure UnitFree(UnitID: byte; GroupID: PAnsiChar); export; cdecl; begin if (frmMain.UFckbEnabled.Checked = True) then begin frmMain.UFmemMsgLog.Lines.Insert(0, Format('%8.8d: GroupID=%s, UnitID=%2.2d, UnitFree', [intGameTime, GroupID, UnitID])); if (frmMain.UFmemMsgLog.Lines.Count > frmMain.UFedtLines.Value) then begin frmMain.UFmemMsgLog.Lines.Delete(frmMain.UFedtLines.Value); end; end; end; procedure ScenLoad(strHarpPath: PAnsiChar; intScenNum: smallint; strScenFPath: PAnsiChar; intBSNum: shortint; strBSFName: PAnsiChar; intSide: byte; intGameTimeIn: Long; intGameTimeLeftIn: Long; AnnexDataIn: PHCAnnexes_disk; DatabaseVersionDBIn: PHCDATABASE_VERSION; ScenDataIn: PHCSCENARIO_DATA; ScenSaveDataIn: PHCSCENARIO_SAVE_DATA; GlobalsIn: PHCGlobals) export; cdecl; var lLLI: PHCLINEAGE_LIST_ITEM; lLineageStr: String; lRow: NativeInt; begin FullyLoaded := False; intGameTime := intGameTimeIn; // ALE 20150712 Now that we receive the game time here, store it intGameTimeLeft := intGameTimeLeftIn; // ALE 20150712 Now that we receive the game time left here, store it // ALE 20170923 replaced in interface version 21 with ScenData dt_scen_start := dt_scen_start_in; // ALE 20150712 Scenario Starting date and time AnnexData := AnnexDataIn; // ALE 20150813 Database/Platform information if (HCAnnexHelper = nil) then begin HCAnnexHelper := THCAnnexHelper.Create(AnnexDataIn, frmMain.HCannex_id_to_disk_id, uLazGUIGlobals.intVerInterface, uLazGUIGlobals.intVerSaveGame); end; DatabaseVersionDB := DatabaseVersionDBIn; // ALE 20170923 current database ScenData := ScenDataIn; // ALE 20170923 unique identifiers, lineage, etc. ScenSaveData := ScenSaveDataIn; // ALE 20170923 save UUIDs and Scenario Orders uLazGUIGlobals.g := GlobalsIn; if (frmMain.SLckbEnabled.Checked = True) then begin frmMain.SLmemMsgLog.Lines.Insert(0, Format('%s ScenLoad HC Path=%s, Scen#=%d, ScenFNP=%s, BattleSet=%d, BattleSet=%s, PlayerSide=%d, GameTime=%d, ScenStart %4.4d-%2.2d-%2.2d, %2.2d:%2.2d:%2.2d', [FormatDateTime('hh:nn:ss.zzz', Now()), strHarpPath, intScenNum, strScenFPath, intBSNum, strBSFName, intSide, intGameTimeIn, ScenData^.dt_scen_start.year, ScenData^.dt_scen_start.month, ScenData^.dt_scen_start.day, ScenData^.dt_scen_start.hour, ScenData^.dt_scen_start.minute, ScenData^.dt_scen_start.second])); if (frmMain.SLmemMsgLog.Lines.Count > frmMain.SLedtLines.Value) then begin frmMain.SLmemMsgLog.Lines.Delete(frmMain.SLedtLines.Value); end; end; // ALE 20170923 Enumerate the scenario lineage if (ScenData^.Lineage.Count > 0) then begin lRow := 1; frmMain.SLsgLineage.RowCount := ScenData^.Lineage.Count + 1; frmMain.SLsgLineage.Cells[0, 0] := ''; frmMain.SLsgLineage.Cells[1, 0] := 'Date & Time Saved'; frmMain.SLsgLineage.Cells[2, 0] := 'Scen Format Ver'; frmMain.SLsgLineage.Cells[3, 0] := 'SE version'; frmMain.SLsgLineage.Cells[4, 0] := 'DB flavor'; frmMain.SLsgLineage.Cells[5, 0] := 'uuid'; lLLI := @ScenData^.Lineage.Top; while (lLLI <> nil) do begin frmMain.SLsgLineage.Cells[0, lRow] := IntToStr(lRow); frmMain.SLsgLineage.Cells[1, lRow] := FormatDateTime('yyyy-MM-dd hh:nn:ss', UnixToDateTime(lLLI^.dtstamp)); frmMain.SLsgLineage.Cells[2, lRow] := IntToStr(lLLI^.ScenarioVersion); frmMain.SLsgLineage.Cells[3, lRow] := lLLI^.SEVersion; frmMain.SLsgLineage.Cells[4, lRow] := lLLI^.DatabaseVersion.RESFile.flavor; frmMain.SLsgLineage.Cells[5, lRow] := lLLI^.uuid; Inc(lRow); lLLI := lLLI^.next; end; end; // ALE 20250528 pre-fill a potential save game name frmMain.SGfneSaveName.InitialDir:=strHarpPath; if FileExists(strScenFPath) then frmMain.SGfneSaveName.FileName := strScenFPath; // TODO ALE 20250528 this is the scenario name, not save name, fix this! // ALE 20221228 this may be a good spot to restore the form position and state frmMain.RestoreWindowPosition(frmMain, hwndHC); end; procedure DBLoaded(AnnexDataIn: PHCAnnexes_disk; AnnexDataMemIn: PHCAnnexes_mem; intBSNum: shortint; DatabaseVersionDBIn: PHCDATABASE_VERSION) export; cdecl; begin // ALE 20250823 intBSNum and DatabaseVersionDBIn added for HC 2025.025, DLL Interface v45 AnnexData := AnnexDataIn; // ALE 20150813 Database/Platform information if (AnnexDataMemIn = nil) then begin AnnexData := AnnexDataIn; HCAnnexHelper := THCAnnexHelper.Create(AnnexDataIn, frmMain.HCannex_id_to_disk_id, uLazGUIGlobals.intVerInterface, uLazGUIGlobals.intVerSaveGame); // ALE 20250226 this is a great place to modify platform information. From here, // the GE applies logic to derive the AnnexData_mem so we want to modify this // underlying 'disk' annex information first so that the AnnexData_mem derivation // uses those modified entries. end else begin AnnexData_mem := AnnexDataMemIn; HCAnnexMemHelper := THCAnnexMemHelper.Create(AnnexDataMemIn, frmMain.HCannex_id_to_disk_id); // ALE 20250220 testing... ShowMessage('Ship first speed: ' + IntToStr(AnnexDataMemIn.PA1_MEM_PTR_LIST.Speed[1])); //ShowMessage('Ship 2048: ' + HCAnnexMemHelper.HCGetAnnexMemA1(2048)^.disk.ClassName // + ' Speed: ' + IntToStr(HCAnnexMemHelper.HCGetAnnexMemA1(2048)^.Speed[0])); end; end; procedure TimeFactor(TimeFactor: longint); export; cdecl; begin if (frmMain.TFckbEnabled.Checked = True) then begin frmMain.TFmemMsgLog.Lines.Insert(0, Format('%8.8d: TimeFactor=%d', [intGameTime, TimeFactor])); if (frmMain.TFmemMsgLog.Lines.Count > frmMain.TFedtLines.Value) then begin frmMain.TFmemMsgLog.Lines.Delete(frmMain.TFedtLines.Value); end; end; end; procedure MsgLog(MsgLine: PAnsiChar); export; cdecl; begin if (frmMain.MLckbEnabled.Checked = True) then begin ShowMessage(BoolToStr(frmMain.MLedtMessageToPost.Enabled, True)); frmMain.MLmemMsgLog.Lines.Insert(0, Format('%8.8d: %s', [intGameTime, MsgLine])); if (frmMain.MLmemMsgLog.Lines.Count > frmMain.MLedtLines.Value) then begin frmMain.MLmemMsgLog.Lines.Delete(frmMain.MLedtLines.Value); end; end; end; procedure GameTime(GameTime: Long; GameTimeLeft: Long); export; cdecl; var PEvent: PHCHarpoonEvent; PGroup: PHCGroup; EventType: ulong; strGroupID: string; dtNow: THCDateTimeRec; lUnit_Uuid: String; begin intGameTime := GameTime; intGameTimeLeft := GameTimeLeft; // ALE 20150712 store remaining time frmMain.GTlblGameTime.Caption := 'GameTime: ' + Format('%8.8d', [intGameTime]); frmMain.GTlblGameTimeLeft.Caption := 'GameTimeLeft: ' + Format('%8.8d', [intGameTimeLeft]); dtNow := HCDateTimeInc(ScenData.dt_scen_start, intGameTime); frmMain.GTlblGameDateTime.Caption := 'GameDateTime: ' + Format('%4.4d-%2.2d-%2.2d %2.2d:%2.2d:%2.2d', [dtNow.year, dtNow.month, dtNow.day, dtNow.hour, dtNow.minute, dtNow.second]); // ALE 20130812 If the user wants to look at data structures // then make sure the GE doesn't keep processing until the // user is done. You would use this mechanism to manipulate // the data structures so that they maintain a consistent state. // ALE 20130816 You don't have to make use of PDLLBusy if you don't // mind the UI freezing during your processing. Freezing is a fine // approach for things like moving units or firing missiles that // don't require immediate user feedback. // ALE 20130816 Note also that GameTime is called at the beginning of the // game second. Any of the enabled stock GE effects will run after this function // or if using PDLLBusy then once you unbusy any DLLs. if (frmMain.GTckbPause.Checked) then begin byte(PDLLBusy^) := CTRUE; // ALE 20130816 don't process other events in GE frmMain.GTbtnResume.Enabled := True; frmMain.GTckbPause.Enabled := False; frmMain.PageControl1.ActivePageIndex := 21; // GameTime/InvestigateStructures // ALE 20130816 this would be a good place to start your thread that does the long processing // ALE 20170601 Harpoon Events PEvent := frmMain.PHarpEventHead; frmMain.GTHEEventList.Items.BeginUpdate; frmMain.GTHEEventList.Clear; frmMain.GEHEmemDetail.Clear; while (PEvent <> nil) do begin EventType := PEvent^.event_type; strGroupID := ' '; if (PEvent^.ourGroup <> nil) then strGroupID := PHCGroup(PEvent^.ourGroup)^.id; lUnit_Uuid := PEvent^.unit_uuid; if (lUnit_Uuid = '') then begin lUnit_Uuid := ' '; // ALE 20230328 stupid formatting hack end; frmMain.GTHEEventList.Items.Add(Format('%s %8.5d 0x%p %s %s %3.3d %1.1d %s', [PEvent^.uuid, PEvent^.Time, PEvent, strGroupID, lUnit_Uuid, EventType, PEvent^.in_scroll_flag, HCEventType2String(EventType)])); PEvent := PEvent^.Next; end; frmMain.GTHEEventList.Items.EndUpdate; // ALE 20170601 Groups/Units PGroup := frmMain.PStartGroup; end; end; procedure StartGroup(StartGroup: PHCGroup); export; cdecl; var PHCGThis: PHCGroup; begin if (frmMain.SGckbEnabled.Checked = True) then begin if (StartGroup <> nil) then begin if (StartGroup^.Units <> nil) then begin frmMain.SGmemMsgLog.Lines.Insert(0, Format('%8.8d: %s %s', [intGameTime, StartGroup^.id, StartGroup^.Units^.Name])); end else begin frmMain.SGmemMsgLog.Lines.Insert(0, Format('%8.8d: %s', [intGameTime, StartGroup^.id])); end; end else begin frmMain.SGmemMsgLog.Lines.Insert(0, Format('%8.8d: StartGroup is NULL', [intGameTime])); end; if (frmMain.SGmemMsgLog.Lines.Count > frmMain.SGedtLines.Value) then begin frmMain.SGmemMsgLog.Lines.Delete(frmMain.SGedtLines.Value); end; // ALE 20130317 Fill up the SGlbxGroups frmMain.PStartGroup := StartGroup; frmMain.SGlbxGroups.Items.Clear; frmMain.SGlbxGroups.Sorted := True; PHCGThis := StartGroup; while (PHCGThis <> nil) do begin frmMain.SGlbxGroups.Items.Add(PHCGThis^.id); PHCGThis := PHCGThis^.toSouth; end; end; frmMain.PStartGroup := StartGroup; end; procedure HarpoonEventHead(HarpEventHead: PHCHarpoonEvent); export; cdecl; begin if (frmMain.HHckbEnabled.Checked = True) then begin if (HarpEventHead <> nil) then begin frmMain.HHmemMsgLog.Lines.Insert(0, Format('%8.8d: 0x%p', [intGameTime, HarpEventHead])); end else begin frmMain.HHmemMsgLog.Lines.Insert(0, Format('%8.8d: HarpoonEventHead is NULL', [intGameTime])); end; if (frmMain.HHmemMsgLog.Lines.Count > frmMain.HHedtLines.Value) then begin frmMain.HHmemMsgLog.Lines.Delete(frmMain.HHedtLines.Value); end; end; frmMain.PHarpEventHead := HarpEventHead; end; procedure HarpoonEventQueued(HarpEvent: PHCHarpoonEvent); export; cdecl; begin if (frmMain.HQckbEnabled.Checked = True) then begin if (HarpEvent <> nil) then begin frmMain.HQmemMsgLog.Lines.Insert(0, Format('%8.8d: 0x%p', [intGameTime, HarpEvent])); end else begin frmMain.HQmemMsgLog.Lines.Insert(0, Format('%8.8d: HarpoonEvent is NULL', [intGameTime])); end; if (frmMain.HQmemMsgLog.Lines.Count > frmMain.HQedtLines.Value) then begin frmMain.HQmemMsgLog.Lines.Delete(frmMain.HQedtLines.Value); end; end; //frmMain.PHarpEvent := HarpEvent; end; procedure SonobuoyRegionHead(SonobuoyRegionHead: PHCSonobuoyRegion); export; cdecl; begin if (frmMain.SHckbEnabled.Checked = True) then begin if (SonobuoyRegionHead <> nil) then begin frmMain.SHmemMsgLog.Lines.Insert(0, Format('%8.8d: 0x%p', [intGameTime, SonobuoyRegionHead])); end else begin frmMain.SHmemMsgLog.Lines.Insert(0, Format('%8.8d: SonobuoyRegionHead is NULL', [intGameTime])); end; if (frmMain.SHmemMsgLog.Lines.Count > frmMain.SHedtLines.Value) then begin frmMain.SHmemMsgLog.Lines.Delete(frmMain.SHedtLines.Value); end; end; frmMain.PSonobuoyRegionHead := SonobuoyRegionHead; end; procedure AllDeadUnitsPtr(AllDeadUnitsPtr: PHCDeadListItem); export; cdecl; begin if (frmMain.DUckbEnabled.Checked = True) then begin if (AllDeadUnitsPtr <> nil) then begin frmMain.DUmemMsgLog.Lines.Insert(0, Format('%8.8d: 0x%p', [intGameTime, AllDeadUnitsPtr])); end else begin frmMain.DUmemMsgLog.Lines.Insert(0, Format('%8.8d: AllDeadUnitsPtr is NULL', [intGameTime])); end; if (frmMain.DUmemMsgLog.Lines.Count > frmMain.DUedtLines.Value) then begin frmMain.DUmemMsgLog.Lines.Delete(frmMain.DUedtLines.Value); end; end; frmMain.PAllDeadUnitsPtr := AllDeadUnitsPtr; end; procedure WeatherGroups(WeatherGroups: PHCGroup); export; cdecl; begin if (frmMain.WGckbEnabled.Checked = True) then begin if (WeatherGroups <> nil) then begin frmMain.WGmemMsgLog.Lines.Insert(0, Format('%8.8d: 0x%p', [intGameTime, WeatherGroups])); end else begin frmMain.WGmemMsgLog.Lines.Insert(0, Format('%8.8d: WeatherGroups is NULL', [intGameTime])); end; if (frmMain.WGmemMsgLog.Lines.Count > frmMain.WGedtLines.Value) then begin frmMain.WGmemMsgLog.Lines.Delete(frmMain.WGedtLines.Value); end; end; frmMain.PWeatherGroups := WeatherGroups; end; procedure VictoryConditionsList(pVictoryConditionsList: PHCVictoryConditions); export; cdecl; begin if (frmMain.VCckbEnabled.Checked = True) then begin if (pVictoryConditionsList <> nil) then begin frmMain.VCmemMsgLog.Lines.Insert(0, Format('%8.8d: 0x%p', [intGameTime, pVictoryConditionsList])); end else begin frmMain.VCmemMsgLog.Lines.Insert(0, Format('%8.8d: VictoryConditionsList is NULL', [intGameTime])); end; if (frmMain.VCmemMsgLog.Lines.Count > frmMain.VCedtLines.Value) then begin frmMain.VCmemMsgLog.Lines.Delete(frmMain.VCedtLines.Value); end; end; frmMain.PVictoryConditionsList := pVictoryConditionsList; end; procedure VictoryConditionAchieved(VictoryCondition: PHCVictoryConditions); export; cdecl; begin frmMain.VCmemMsgLog.Lines.Insert(0, Format('%8.8d: %s Victory Condition #%d achieved', [intGameTime, AnnexData^.PBSStrings^.SideName[VictoryCondition^.WhichSide], VictoryCondition^.id])); if (frmMain.VCmemMsgLog.Lines.Count > frmMain.VCedtLines.Value) then begin frmMain.VCmemMsgLog.Lines.Delete(frmMain.VCedtLines.Value); end; end; procedure VictoryConditionLevels(whichSide: uchar; enemySide: uchar; vicLevel: uchar); export; cdecl; begin frmMain.VCmemMsgLog.Lines.Insert(0, Format('%8.8d: %s Victory Level %s achieved', [intGameTime, AnnexData^.PBSStrings^.SideName[whichSide], HCVictoryConditionLevel(vicLevel)])); // ALE 20211213 We can figure out if this is the first achievement of the level if (g^.VictoryConditionLevels.firstMinimumVictory.gameTime = uLazGUIGlobals.intGameTime) then begin frmMain.VCmemMsgLog.Lines.Insert(0, Format('%8.8d: First Minimum Victory Achievement', [intGameTime])); end; if (g^.VictoryConditionLevels.firstTotalVictory.gameTime = uLazGUIGlobals.intGameTime) then begin frmMain.VCmemMsgLog.Lines.Insert(0, Format('%8.8d: First Total Victory Achievement', [intGameTime])); end; if (frmMain.VCmemMsgLog.Lines.Count > frmMain.VCedtLines.Value) then begin frmMain.VCmemMsgLog.Lines.Delete(frmMain.VCedtLines.Value); end; end; procedure ActGroupPtr(ActGroup: PHCGroup); export; cdecl; begin if (frmMain.AGckbEnabled.Checked = True) then begin if (ActGroup <> nil) then begin frmMain.AGmemMsgLog.Lines.Insert(0, Format('%8.8d: Group %s', [intGameTime, ActGroup.id])); //if (HCMTIsAir(ActGroup^.gtype)) then // ShowMessage('oh crap'); end else begin frmMain.AGmemMsgLog.Lines.Insert(0, Format('%8.8d: Group is NULL', [intGameTime])); end; if (frmMain.AGmemMsgLog.Lines.Count > frmMain.AGedtLines.Value) then begin frmMain.AGmemMsgLog.Lines.Delete(frmMain.AGedtLines.Value); end; end; end; procedure ActUnitPtr(ActUnit: PHCUnit); export; cdecl; begin if (frmMain.AGckbEnabled.Checked = True) then begin if (ActUnit <> nil) then begin frmMain.AGmemMsgLog.Lines.Insert(0, Format('%8.8d: Unit %s %3.3d %s', [intGameTime, ActUnit.Group.id, ActUnit.id, ActUnit.Name])); end else begin frmMain.AGmemMsgLog.Lines.Insert(0, Format('%8.8d: Unit is NULL', [intGameTime])); end; if (frmMain.AGmemMsgLog.Lines.Count > frmMain.AGedtLines.Value) then begin frmMain.AGmemMsgLog.Lines.Delete(frmMain.AGedtLines.Value); end; end; end; procedure SaveGame(FileNameIn: PAnsiChar; ScenSaveDataIn: PHCSCENARIO_SAVE_DATA; SaveGameType: uchar); export; cdecl; begin if (frmMain.SGckbEnabled1.Checked = True) then begin frmMain.SGmemMsgLog1.Lines.Insert(0, Format('%8.8d: Game Saved: uuid=%s, uuid_prev=%s, savetype=%s, file=%s', [intGameTime, ScenSaveDataIn.uuid, ScenSaveDataIn.uuid_prev, HCSaveGameType2String(THCSaveGameTypes(SaveGameType)), FileNameIn])); if (frmMain.SGmemMsgLog1.Lines.Count > frmMain.SGedtLines1.Value) then begin frmMain.SGmemMsgLog1.Lines.Delete(frmMain.SGedtLines1.Value); end; end; end; procedure SaveScen(FileNameIn: PAnsiChar; ScenDataIn: PHCSCENARIO_DATA); export; cdecl; begin if (frmMain.SGckbEnabled1.Checked = True) then begin frmMain.SGmemMsgLog1.Lines.Insert(0, Format('%8.8d: Scenario Saved: uuid=%s, file=%s', [intGameTime, ScenDataIn^.Lineage.Bottom^.uuid, FileNameIn])); if (frmMain.SGmemMsgLog1.Lines.Count > frmMain.SGedtLines1.Value) then begin frmMain.SGmemMsgLog1.Lines.Delete(frmMain.SGedtLines1.Value); end; end; end; procedure GroupAllocated(pGroup: PHCGroup); export; cdecl; begin if (frmMain.GAckbEnabled.Checked = True) then begin frmMain.GAmemMsgLog.Lines.Insert(0, Format('%8.8d: Group=0x%p, GroupUUID=%s, GroupAllocated', [intGameTime, pGroup, pGroup^.UUID])); if (frmMain.GAmemMsgLog.Lines.Count > frmMain.GAedtLines.Value) then begin frmMain.GAmemMsgLog.Lines.Delete(frmMain.GAedtLines.Value); end; end; end; function GroupFree(pGroup: PHCGroup): CBOOL; export; cdecl; begin byte(PDLLBusy^) := CTRUE; if (frmMain.GFckbEnabled.Checked = True) then begin frmMain.GFmemMsgLog.Lines.Insert(0, Format('%8.8d: GroupID=%s, GroupUUID=%s, GroupFree', [intGameTime, pGroup^.id, pGroup^.UUID])); if (frmMain.GFmemMsgLog.Lines.Count > frmMain.GFedtLines.Value) then begin frmMain.GFmemMsgLog.Lines.Delete(frmMain.GFedtLines.Value); end; end; byte(PDLLBusy^) := CFALSE; result := CFALSE; // ALE 20220206 we didn't free the group end; { procedure AssignGroupId(pGroup: PHCGroup); export; cdecl; begin // ALE 20230101 One can assign a Group Id here. The GE expects a GroupId to // be assigned if this procedure is called so this is not one to monitor // without assigning a GroupId // ALE 20220204 logic for group ids // Blue AA -> Mz, aa -> mz // Red ZZ -> Na, zz -> na // Green 0A -> 9Z // We shall populate a list of all group ids // Index, Group Id, Used by side // byte(PDLLBusy^) := CTRUE; if (NOT gFullyLoaded) then begin RecalcUsedGroupIds(); end; if (gIds[pGroup^.side][AVAIL].Count > 0) then begin lIds := gIds[pGroup^.side][AVAIL][0]; // ALE 20220205 now move the Id to the used list gIds[pGroup^.side][USED].Add(lIds); gIds[pGroup^.side][AVAIL].Delete(0); // ALE 20220205 and do the assignment pGroup^.id[0] := lIds[1]; pGroup^.id[1] := lIds[2]; end else begin // ALE 20220205 no more Ids available HCMessageLogLine(PChar('!!!Couldn''t assign ' + AnnexData.PBSStrings^.SideName[pGroup^.side] + ' Group ID, all used up!!!')); end; byte(PDLLBusy^) := CFALSE; end; } procedure ScenLoaded(intWhichRound: Long); export; cdecl; var lAU: NativeInt; lIdC1, lIdC2: NativeInt; lC1, lC2: NativeInt; begin // ALE 20220327 once we hit SCENLOADED_EXPORTDLLS_DONE, we can stop // checking for existing group id assignment each time we have a new group //RecalcUsedGroupIds(); if intWhichRound = SCENLOADED_INTERNAL_DONE then begin // ALE 20220205 Finally we need to look at any Group Ids already in the // scenario we just loaded and rationalize them by putting them into the // used bucket. Keep in mind, an already used Blue or Red group id // could fall into either the new Blue or Red bucket frmMain.SLmemMsgLog.Lines.Insert(0, FormatDateTime('hh:nn:ss.zzz', Now()) + ' ScenLoaded SCENLOADED_INTERNAL_DONE'); end else // SCENLOADED_EXPORTDLLS_DONE begin // ALE 20220216 The scenario is really beginning now // it is a notification message only, don't modify the HC // game state here, it should ahve been done in SCENLOADED_INTERNAL_DONE FullyLoaded := True; //HCMessageLogLine(PChar('GroupIdMgr ScenLoaded Blue Ids Used: ' //+ IntToStr(gIds[BLUESIDE][USED].Count) + ' Red Ids Used: ' //+ IntToStr(gIds[REDSIDE][USED].Count) + ' Green Ids Used: ' //+ IntToStr(gIds[GREENSIDE][USED].Count))); frmMain.SLmemMsgLog.Lines.Insert(0, FormatDateTime('hh:nn:ss.zzz', Now()) + ' ScenLoaded SCENLOADED_EXPORTDLLS_DONE'); end; if (frmMain.SLmemMsgLog.Lines.Count > frmMain.SLedtLines.Value) then begin frmMain.SLmemMsgLog.Lines.Delete(frmMain.SLedtLines.Value); end; end; function Staff(side: short; button: short; picnum: short; message: pchar; unit_involved: PHCUNIT; outcome: plong): CBOOL; export; cdecl; var lUnitInvolved, lGroupInvolved: String; begin result := CFALSE; outcome^ := 2; // IDCANCEL if (frmMain.SAckbEnabled.Checked) then begin if (unit_involved <> nil) then begin lUnitInvolved := Format('%3.3d', [unit_involved^.id]); if (unit_involved^.Group <> nil) then begin lGroupInvolved := unit_involved^.Group^.id; end; end; frmMain.SAmemMsgLog.Lines.Insert(0, FormatDateTime('hh:nn:ss.zzz', Now()) + Format(' side=%d, picnum=%d, message=%s, Group=%s, Unit=%s, outcome=%d', [side, picnum, message, lGroupInvolved, lUnitInvolved, outcome^])); if (frmMain.SAckbSkipGE.Checked) then result := CTRUE; if (frmMain.SAmemMsgLog.Lines.Count > frmMain.SAedtLines.Value) then begin frmMain.SAmemMsgLog.Lines.Delete(frmMain.SAedtLines.Value); end; end; end; procedure ZoomChanged(GroupZoom: short; UnitZoom: short); export; cdecl; begin if (frmMain.ZCckbEnabled.Checked) then begin frmMain.ZCmemMsgLog.Lines.Insert(0, FormatDateTime('hh:nn:ss.zzz', Now()) + Format(' ZoomChanged Group=%d, Unit=%d', [GroupZoom, UnitZoom])); if (frmMain.ZCmemMsgLog.Lines.Count > frmMain.ZCedtLines.Value) then begin frmMain.ZCmemMsgLog.Lines.Delete(frmMain.ZCedtLines.Value); end; end; end; // ALE 20250301 void dllexStoreConsumed(UnitPtr pLaunchUnit, MntRecPtr pLaunchMount, ushort ourMount, uchar mountIndex, ushort pQty, UnitPtr pWeapUnit, UnitPtr pTargetUnit, GroupPtr pTargetGroup) // ALE 20250228 tell ExportDLL when we use a weapon/store procedure StoreConsumed(pLaunchUnit: PHCUNIT; pLaunchMount: PHCMntRec; ourMount: ushort; mountIndex: uchar; pQty: ushort; pWeapUnit: PHCUNIT; pTargetUnit: PHCUNIT; pTargetGroup: PHCGROUP); export; cdecl; var lLaunchUnit, lWeaponUnit, lTargetUnit: String; lWepId: ID; begin if (frmMain.SCckbEnabled.Checked) then begin if (pLaunchUnit <> nil) then lLaunchUnit := Format('%2s%3.3d', [pLaunchUnit^.Group^.id, pLaunchUnit^.id]); if (pWeapUnit <> nil) then lWeaponUnit := Format('%2s%3.3d', [pWeapUnit^.Group^.id, pWeapUnit^.id]); if ((pTargetUnit <> nil) AND (pTargetUnit^.Group <> nil)) then lTargetUnit := Format('%2s%3.3d', [pTargetUnit^.Group^.id, pTargetUnit^.id]); if (pLaunchMount <> nil) then lWepId := pLaunchMount^.WepId else lWepId := INVALID_ID; frmMain.SCmemMsgLog.Lines.Insert(0, FormatDateTime('hh:nn:ss.zzz', Now()) + Format(' StoreConsumed LaunchUnit=%s, WeaponUnit=%s, TargetUnit=%s, Quantity=%d, WepAnnexId=%d', [lLaunchUnit, lWeaponUnit, lTargetUnit, pQty, lWepId])); if (frmMain.SCmemMsgLog.Lines.Count > frmMain.SCedtLines.Value) then begin frmMain.SCmemMsgLog.Lines.Delete(frmMain.SCedtLines.Value); end; end; end; exports DLLVer, DLLVerInt, DLLDesc, DLLFree, DLLInit, UnitLatLon, UnitShowing, UnitURegion, UnitCourse, UnitSpeed, UnitAlt, UnitShowingSide, UnitSink, UnitToGroup, UnitNew, UnitFree, ScenLoad, TimeFactor, MsgLog, GameTime , StartGroup // ALE 20130205 Sends updated GroupPtr whenever that changes , HarpoonEventHead // ALE 20130805 Sends updated soonest HarpoonEvent pointer whenever it changes , HarpoonEventQueued // ALE 20200825 Sends HarpoonEvent pointer when an event is added , SonobuoyRegionHead // ALE 20130812 Sends updated pointer to all sonobuoys when it changes , AllDeadUnitsPtr // ALE 20130812 Sends updated pointer to the list of killed units used for victory condition evaluation , WeatherGroups // ALE 20130812 Sends update GroupPtr whenever the top weather group changes , VictoryConditionsList // ALE 20130812 Sends a pointer to the list of victory conditions when a scenario is loaded , ActGroupPtr // ALE 20160305 Sends pointer to the Group the player just selected or NULL , ActUnitPtr // ALE 20160305 Sends pointer to the Unit the player just selected or NULL , SaveGame // ALE 20170923 UUIDs to associate any data with the right saved game file , VictoryConditionAchieved // ALE 20211212 Lets us know when a VC is achieved , VictoryConditionLevels // ALE 20211213 Lets us know when a VC level is reached by a side , GroupFree // ALE 20230101 Lets us know when a Group structure is freed { , AssignGroupId // ALE 20220203 Lets us assign a Group Id } , ScenLoaded // ALE 20220216 So we really know when a scenario was completely loaded , GroupAllocated // ALE 20230102 Called when memory for a group is allocated and an initial set of fields are initialized like uuid but not group id , DBLoaded // ALE 20250209 Called after DB annexes are loaded , SaveScen // ALE 20250227 Called at end of scenario saving , Staff // ALE 20250228 Staff Assistant dialog which we can prevent the GE from showing , ZoomChanged // ALE 20250228 Group or Unit window zoom level change , StoreConsumed // ALE 20250301 fired a 'weapon' ; end.