diff --git a/Scripts/DCS-ExportScript/ExportsModules/AH-64D_BLK_II.lua b/Scripts/DCS-ExportScript/ExportsModules/AH-64D_BLK_II.lua index 0b14e68..2bad3b7 100644 --- a/Scripts/DCS-ExportScript/ExportsModules/AH-64D_BLK_II.lua +++ b/Scripts/DCS-ExportScript/ExportsModules/AH-64D_BLK_II.lua @@ -1,4 +1,9 @@ -- AH64D_BLK_II +-- https://github.com/asherao/DCS-ExportScripts + +local base = _G -- game information +local os = base.os -- time +local Terrain = require('terrain') -- map info ExportScript.FoundDCSModule = true ExportScript.Version.AH64D_BLK_II = "1.2.1" @@ -716,30 +721,9 @@ ExportScript.ConfigArguments = -- Pointed to by ProcessIkarusDCSHighImportance function ExportScript.ProcessIkarusDCSConfigHighImportance(mainPanelDevice) - --[[ - every frame export to Ikarus - Example from A-10C - Get Radio Frequencies - get data from device - local lUHFRadio = GetDevice(54) - ExportScript.Tools.SendData("ExportID", "Format") - ExportScript.Tools.SendData(2000, string.format("%7.3f", lUHFRadio:get_frequency()/1000000)) -- <- special function for get frequency data - ExportScript.Tools.SendData(2000, ExportScript.Tools.RoundFreqeuncy((UHF_RADIO:get_frequency()/1000000))) -- ExportScript.Tools.RoundFreqeuncy(frequency (MHz|KHz), format ("7.3"), PrefixZeros (false), LeastValue (0.025)) - ]] end function ExportScript.ProcessDACConfigHighImportance(mainPanelDevice) - --[[ - every frame export to DAC - Example from A-10C - Get Radio Frequencies - get data from device - local UHF_RADIO = GetDevice(54) - ExportScript.Tools.SendDataDAC("ExportID", "Format") - ExportScript.Tools.SendDataDAC("ExportID", "Format", HardwareConfigID) - ExportScript.Tools.SendDataDAC("2000", string.format("%7.3f", UHF_RADIO:get_frequency()/1000000)) - ExportScript.Tools.SendDataDAC("2000", ExportScript.Tools.RoundFreqeuncy((UHF_RADIO:get_frequency()/1000000))) -- ExportScript.Tools.RoundFreqeuncy(frequency (MHz|KHz), format ("7.3"), PrefixZeros (false), LeastValue (0.025)) - ]] end ----------------------------------------------------- @@ -749,34 +733,28 @@ end -- Pointed to by ExportScript.ProcessIkarusDCSConfigLowImportance function ExportScript.ProcessIkarusDCSConfigLowImportance(mainPanelDevice) - --[[ - export in low tick interval to Ikarus - Example from A-10C - Get Radio Frequencies - get data from device - local lUHFRadio = GetDevice(54) - ExportScript.Tools.SendData("ExportID", "Format") - ExportScript.Tools.SendData(2000, string.format("%7.3f", lUHFRadio:get_frequency()/1000000)) -- <- special function for get frequency data - ExportScript.Tools.SendData(2000, ExportScript.Tools.RoundFreqeuncy((UHF_RADIO:get_frequency()/1000000))) -- ExportScript.Tools.RoundFreqeuncy(frequency (MHz|KHz), format ("7.3"), PrefixZeros (false), LeastValue (0.025)) - ]] ExportScript.CountermeasureReadouts(mainPanelDevice) + --ExportScript.MfdReadouts(mainPanelDevice) --Testing in progress + --ExportScript.TSD(mainPanelDevice) -- Disabled: see note in Function + + if LoIsObjectExportAllowed() then -- returns true if world objects data is available + if LoIsOwnshipExportAllowed() then -- returns true if ownship data is available + ExportScript.LoAircraftInfo(mainPanelDevice) -- Provides a lot of aircraft properties + ExportScript.AirportInfo(mainPanelDevice) -- Provides info on the two closest airports + ExportScript.WindsAloft(mainPanelDevice) -- Gets winds at the aircraft + ExportScript.GroundRadar(mainPanelDevice) -- Reports 2 closest friendlies and 2 enemies (Use in Single Player) + ExportScript.AirRadar(mainPanelDevice) -- Reports 2 closest friendlies and 2 enemies (Use in Single Player) + ExportScript.IglaHunter(mainPanelDevice) -- Locates closest Igla (Use in Single Player) + end + end + end function ExportScript.ProcessDACConfigLowImportance(mainPanelDevice) - --[[ - export in low tick interval to DAC - Example from A-10C - Get Radio Frequencies - get data from device - local UHF_RADIO = GetDevice(54) - ExportScript.Tools.SendDataDAC("ExportID", "Format") - ExportScript.Tools.SendDataDAC("ExportID", "Format", HardwareConfigID) - ExportScript.Tools.SendDataDAC("2000", string.format("%7.3f", UHF_RADIO:get_frequency()/1000000)) - ExportScript.Tools.SendDataDAC("2000", ExportScript.Tools.RoundFreqeuncy((UHF_RADIO:get_frequency()/1000000))) -- ExportScript.Tools.RoundFreqeuncy(frequency (MHz|KHz), format ("7.3"), PrefixZeros (false), LeastValue (0.025)) - ]] --===================================================================================== + ExportScript.Tools.WriteToLog('CMSP ') --[[ ExportScript.Tools.WriteToLog('list_cockpit_params(): '..ExportScript.Tools.dump(list_cockpit_params())) ExportScript.Tools.WriteToLog('CMSP: '..ExportScript.Tools.dump(list_indication(7))) @@ -803,10 +781,11 @@ end -- Custom functions -- ----------------------------- +------------------------------------- +--- Apache Flare and Chaff Counts --- +------------------------------------- + function ExportScript.CountermeasureReadouts(mainPanelDevice) - ------------------------------------- - --- Apache Flare and Chaff Counts --- - ------------------------------------- local CmwsInfo_24 = ExportScript.Tools.split(list_indication(24), "%c")--this contains the formated table of the kneeboard @@ -834,4 +813,838 @@ function ExportScript.CountermeasureReadouts(mainPanelDevice) ExportScript.Tools.SendData(3004, string.format("F " .. txt_FLARES_Count .. "\nC " .. txt_CHAFFS_Count)) ExportScript.Tools.SendData(3005, string.format("FLARE\n" .. txt_FLARES_Count)) ExportScript.Tools.SendData(3006, string.format("CHAFF\n" .. txt_CHAFFS_Count)) + ExportScript.Tools.SendData(3999, string.format(ExportScript.Tools.dump(CmwsInfo_24))) +end + +------------------------- +-- Apache MFD Readouts -- +------------------------- + +function ExportScript.MfdReadouts(mainPanelDevice) + --[[ This can be tested by creating three "Momentary Button/Displays" + -- Change the Title Text Change on DCS Update Settings DCS ID to 5000, 5001, and 5002. + -- When in cockpit the values will populate. + -- Press the FCR, WPN, and TSD buttons to see them change. + -- You can remove this comment from your code.]] + + --this contains the formated table of the Pilot Left MFD + local MfdPltLeftInfo_6 = ExportScript.Tools.split(list_indication(6), "%c") + + -- init the names of the containers for the variables + local button_T1 + local button_T2 + + for k,v in pairs(MfdPltLeftInfo_6) do -- start searching through the list_indication + + if v == "PB1_1" then -- reference the list_indication to get the correct entry + button_T1 = MfdPltLeftInfo_6[k+1] + end + + if v == "PB2_3" or v == "PB2_1" then -- this one seems to change depending on the screen... + button_T2 = MfdPltLeftInfo_6[k+1] + end + + end + + -- if a value was not present, it wasn't populated + -- fill it with something else to indicate that to the user + -- otherwise, keep its value + -- Don't forget to add the NilOrEmpty() function in the General Helper Functions section + button_T1 = NilOrEmpty(button_T1) + button_T2 = NilOrEmpty(button_T2) + + ExportScript.Tools.SendData(5000, string.format(button_T1)) + ExportScript.Tools.SendData(5001, string.format(button_T2)) + +end + +--------------------- +-- Apache TSD Info -- +--------------------- + +function ExportScript.TSD(mainPanelDevice) + -- Note: Unfortunately, this info does not update unless the TSD is visible. + -- Therefore, implementation of this fucntion is on hold. + + local list_indication_8 = ExportScript.Tools.split(list_indication(8), "%c")--this contains the formated table of the kneeboard + + for k,v in pairs(list_indication_8) do + if v == "NextWaypointStatusWindow_text_1" then + NextWptReadout1 = list_indication_8[k+1] + end + end + + for k,v in pairs(list_indication_8) do + if v == "NextWaypointStatusWindow_text_2" then + NextWptReadout2 = list_indication_8[k+1] + end + end + + for k,v in pairs(list_indication_8) do + if v == "NextWaypointStatusWindow_text_3" then + NextWptReadout3 = list_indication_8[k+1] + end + end + + for k,v in pairs(list_indication_8) do + if v == "NextWaypointStatusWindow_text_4" then + NextWptReadout4 = list_indication_8[k+1] + end + end + + -- TODO: Debug endurance readout + -- possible issue may be due to the colon in string + for k,v in pairs(list_indication_8) do + if v == "EnduranceStatusWindow_text_1" then + Endurance = list_indication_8[k+1] + end + end + + for k,v in pairs(list_indication_8) do + if v == "WindStatusWindow_CALM_text" then + WindStatus = list_indication_8[k+1] + end + end + + NextWptReadout1 = trim(NextWptReadout1) + NextWptReadout2 = trim(NextWptReadout2) + NextWptReadout3 = trim(NextWptReadout3) + NextWptReadout4 = trim(NextWptReadout4) + Endurance = trim(Endurance) + WindStatus = trim(WindStatus) + + + ExportScript.Tools.SendData(3007, string.format(NextWptReadout1)) + ExportScript.Tools.SendData(3008, string.format(NextWptReadout2)) + ExportScript.Tools.SendData(3009, string.format(NextWptReadout3)) + ExportScript.Tools.SendData(3010, string.format(NextWptReadout4)) + ExportScript.Tools.SendData(3011, string.format(Endurance)) +end + +function ExportScript.LoAircraftInfo(mainPanelDevice) + + -- General + local aircraftName = LoGetSelfData().Name -- DCS Name of the aircraft eg "F-5E-3" + local pilotName = LoGetPilotName() -- Logbook Pilot name + + -- Times DCS times are default in seconds + local dcsModelTime = LoGetModelTime() -- time since aircraft spawn + local missionStartTime = LoGetMissionStartTime() -- second after midnight that the mission started + local dcsTimeLocal = formatTime(LoGetMissionStartTime() + LoGetModelTime()) -- up-to-date time in dcs + local utcOffset = -1 * Terrain.GetTerrainConfig('SummerTimeDelta') * 3600 -- eg -1 * 4 * 3600 (for seconds to get hours) + local dcsTimeUtc = formatTime(dcsModelTime + LoGetMissionStartTime() + utcOffset) -- dcs zulu time + local realTimeLocal = os.date("%H-%M-%S") -- real life time + local realTimeUtc = os.date("!%H-%M-%S") -- real life zulu time + --local playTime = formatTime(DCS.getRealTime()) -- does not work, export environment no access + + -- Player Aircraft Properties + local altMsl_meters = LoGetAltitudeAboveSeaLevel() + local altMsl_feet = meters2feet(altMsl_meters) + local altAgl_meters = LoGetAltitudeAboveGroundLevel() + local altAgl_feet = meters2feet(altAgl_meters) + + local verticalVelocity_metric = LoGetVerticalVelocity() + local verticalVelocity_imperial = metersPerSecond2feetPerMinute(LoGetVerticalVelocity()) + + local ias_metric = LoGetIndicatedAirSpeed() + local ias_knots = metersPerSecond2knots(LoGetIndicatedAirSpeed()) + local ias_mph = metersPerSecond2milesPerHour(LoGetIndicatedAirSpeed()) + + local tas_metric = LoGetTrueAirSpeed() + local tas_knots = metersPerSecond2knots(LoGetTrueAirSpeed()) + local tas_mph = metersPerSecond2milesPerHour(LoGetTrueAirSpeed()) + + local speed_mach = LoGetMachNumber() + local accel_g = LoGetAccelerationUnits().y + local aoa = LoGetAngleOfAttack() + + --local atmosphericPressure_mmhg = LoGetBasicAtmospherePressure() -- does not seem to work + + local aircraftPitch, aircraftBank, aircraftYawTrue = LoGetADIPitchBankYaw() + aircraftPitch = aircraftPitch * 57.3 + aircraftBank = aircraftBank * 57.3 + aircraftYawTrue = aircraftYawTrue * 57.3 -- true heading + local aircraftYawMagnetic = LoGetMagneticYaw() * 57.3 -- magnetic heading + local aircraftHeading = aircraftYawMagnetic -- this cound be negative + if aircraftHeading < 0 then aircraftHeading = aircraftHeading + 360 end -- removes the negative + local magneticVariance = aircraftYawTrue - aircraftYawMagnetic -- works for all maps + + local selfData = LoGetSelfData() -- relative the the player + local lLatitude = selfData.LatLongAlt.Lat + local lLongitude = selfData.LatLongAlt.Long + local mgrs = Terrain.GetMGRScoordinates(LoGetSelfData().Position.x, LoGetSelfData().Position.z) + local mgrsTable = mgrsTableize(mgrs) -- format is mgrsTable[1][1], mgrsTable[1][2], mgrsTable[1][3], mgrsTable[1][4] + + local aircraftHeadingTrue = selfData.Heading * 57.3 -- true yeading (same as trueYaw for fixed wing aircraft) + + -- Engine Info + local engineInfo = LoGetEngineInfo() + local lEngineRPMleft = engineInfo.RPM.left -- ENG1 RPM % + local lEngineRPMright = engineInfo.RPM.right -- ENG2 RPM % + local lEngineFuelInternal = engineInfo.fuel_internal -- 1 = full. 0 = empty. Includes external tanks for FF aircraft + local lEngineFuelExternal = engineInfo.fuel_external -- TANK2 (EXT) (KG) -- does not seem to work for FF modules + local lEngineFuelTotal = lEngineFuelInternal + lEngineFuelExternal + local lEngineTempLeft = engineInfo.Temperature.left -- ENG1 EGT ºC. May get odd numbers + local lEngineTempRight = engineInfo.Temperature.right -- ENG2 EGT ºC. May get odd numbers + + local lFuelConsumptionLeft = engineInfo.FuelConsumption.left -- {left ,right},kg per sec + local lFuelConsumptionRight = engineInfo.FuelConsumption.right -- {left ,right},kg per sec + local lFuelConsumptionTotal = lFuelConsumptionLeft + lFuelConsumptionRight -- total,kg per sec + local lHydraulicPressureLeft = engineInfo.HydraulicPressure.left -- {left ,right},kg per square centimeter + local lHydraulicPressureRight = engineInfo.HydraulicPressure.right -- {left ,right},kg per square centimeter + + ExportScript.Tools.SendData(8000, aircraftName) + + ExportScript.Tools.SendData(8001, pilotName) + + ExportScript.Tools.SendData(8002, 'Real Time\n'.. realTimeLocal .. '\nDCS Time\n' .. dcsTimeLocal) -- clocks + + ExportScript.Tools.SendData(8003, 'HDG ' .. prefixZerosFixedLength(round(aircraftHeading,0),3) .. 'º' + .. '\nALT ' .. format_int(round(altMsl_feet,-1)) .. ' ft' + .. '\nIAS ' .. round(ias_knots,0) .. ' kts' + .. '\nV/S ' .. format_int(round(verticalVelocity_imperial,-2)) .. ' ft/min' + ) -- Aircraft Instrument panel (western) + + ExportScript.Tools.SendData(8004, 'HDG ' .. prefixZerosFixedLength(round(aircraftHeading,0),3) .. 'º' + .. '\nALT ' .. format_int(round(altMsl_meters,-1)) .. ' m' + .. '\nIAS ' .. round(ias_metric,0) .. ' km/h' + .. '\nV/S ' .. format_int(round(verticalVelocity_metric,0)) .. ' m/s' + ) -- Aircraft Instrument panel (eastern) + + ExportScript.Tools.SendData(8005, 'HDG ' .. prefixZerosFixedLength(round(aircraftHeading,0),3) .. 'º' + .. '\nALT ' .. format_int(round(altMsl_feet,-1)) .. ' ft' + .. '\nIAS ' .. round(ias_mph,0) .. ' mph' + .. '\nV/S ' .. format_int(round(verticalVelocity_imperial,-2)) .. ' ft/min' + ) -- Aircraft Instrument panel (western ww2) + + ExportScript.Tools.SendData(8006, "Lat-Long-DMS\n" .. formatCoord("DMS",true, lLatitude) + .. "\n" .. formatCoord("DMS",false, lLongitude) + ) -- Player coordinates in DMS + + ExportScript.Tools.SendData(8007, "Lat-Long-DDM\n" .. formatCoord("DDM",true, lLatitude) + .. "\n" .. formatCoord("DDM",false, lLongitude) + ) -- Player coordinates in DDM + + ExportScript.Tools.SendData(8008, 'MGRS\n'.. mgrsTable[1][1] .. ' ' .. mgrsTable[1][2] + .. '\n' .. mgrsTable[1][3] .. ' ' .. mgrsTable[1][4] + ) -- Player coordinates in MGRS on 2 rows + title + + ExportScript.Tools.SendData(8009, 'Mag Var\n' .. format_int(round(magneticVariance, 2))) -- also called magnetic deviation + + -- Example for using the Lo Data. Feel free to make your own! + ExportScript.Tools.SendData(8010, format_int(round(kgPerSecond2poundPerHour(lFuelConsumptionLeft), -1))) -- fuel use in pph + +end +function ExportScript.AirportInfo(mainPanelDevice) + + local airdromes = LoGetWorldObjects("airdromes") -- returns a list of runways and their popperties + local airportInfo = {} -- contains generated table of important properties + -- the table will be sorted by nearest airport first + -- for this table: + -- airportInfo[1] is the first element + -- airportInfo[1][1] is the airport name of the first element/airport + -- airportInfo[1][2] is the distance to the airport of the first element/airport + -- airportInfo[1][3] is the bearing to the airport of the first element/airport + -- airportInfo[1][4] is the extimated time en route + -- airportInfo[1][5] is the direction of the wind + -- airportInfo[1][6] is the windStrength of the wind + -- airportInfo[1][7] is the main runway heading + -- airportInfo[1][8] is the reverse of the main runway + -- airportInfo[1][9] is the prefered runway based on winds + + for key,value in pairs(airdromes) do + + -- remove the woRunWay entries so that only named runways are in the list + if value.Name ~= 'woRunWay' then + + -- get the distance from the player to the runway + local distance = getdistance(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, "nm") + + -- get the direction from the player to the runway + local bearing = getBearing(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, true) + + -- estimate the runway heading based on the reported values + local runwayHeading = round(value.Heading * 57.3,-1) / 10 + if runwayHeading < 0 then + runwayHeading = 36 + runwayHeading + end + -- Reverse it for the reciprocal runway + local runwayHeadingReciprocal + if runwayHeading > 18 then + runwayHeadingReciprocal = runwayHeading-18 + else + runwayHeadingReciprocal = runwayHeading+18 + end + + local ete = distance / metersPerSecond2knots(LoGetTrueAirSpeed()) * (60 * 60) --based on tas bc dcs is flat... + -- if ete is more than 24hrs, make it 24 hrs, which shows up as 00-00-00 + -- this case is for choppers and aircraft that arent moving + if ete > 86400 then ete = 86400 end + ete = formatTime(ete) + + -- wind at airport calculations. Each airport has slighty different winds + -- https://forum.dcs.world/topic/165136-logetwindatpoint-in-exportlua/#comment-3294428 + -- LoGetWindAtPoint(x,y,z,is_radio_alt), 2 meters off the ground for the "wind sensor" + local vx,_vy,vz,_absolute_height = LoGetWindAtPoint(value.Position.x,2,value.Position.y,true) + local windDirectionInRadians = math.atan2(vz,vx) + local windDirection = windDirectionInRadians * 57.3 + local windStrength = math.sqrt((vx)^2 + (vz)^2) + if windDirection < 0 then + windDirection = 360 + windDirection + end + -- Convert to direction to from direction + if windDirection > 180 then + windDirection = windDirection - 180 + else + windDirection = windDirection + 180 + end + + -- Calculate the prefered runway for landing + -- if the rounded runway is within +- 9 of the rounded wind, then it is prefered + local windRounded = round(windDirection, -1) + if windRounded >= runwayHeading - 9 and windRounded <= runwayHeading + 9 then + runwayHeadingPrefered = runwayHeading + else + runwayHeadingPrefered = runwayHeadingReciprocal + end + + -- Populate the table with the important info for each airport + table.insert(airportInfo, -- the table name + {value.Name, -- airport name [1] + distance, bearing, ete, --[2][3][4] + windDirection,windStrength, --wind direction [5], wind Strength [6] + runwayHeading, runwayHeadingReciprocal,runwayHeadingPrefered}) -- [7][8][9] + + end -- end of woRunWay + end -- end of FOR loop + + -- sort the table based on the second value, which is distance + -- https://stackoverflow.com/questions/51276613/how-to-sort-table-by-value-and-then-print-index-in-order + -- https://www.tutorialspoint.com/sort-function-in-lua-programming + table.sort(airportInfo, function(a,b) return a[2] < b[2] end) + + -- Primary Airport (closest) + ExportScript.Tools.SendData(8101, airportInfo[1][1] .. '\n' -- name of airport + --[[.. 'BRG ']] .. format_int(addZeros3(round(airportInfo[1][3],0))) .. 'º ' -- bearing + --[[.. 'DIST ']] .. format_int(round(airportInfo[1][2], 0)) .. 'nm\n' -- distance + .. 'ETE ' .. airportInfo[1][4] .. '\n' -- estimated time in route + .. '' .. prefixZerosFixedLength(round(airportInfo[1][5], 0),3) .. 'º ' -- wind bearing + .. round(metersPerSecond2knots(airportInfo[1][6]),0) .. 'kts' -- wing strength + .. '\n' .. prefixZerosFixedLength(airportInfo[1][7],2) -- runway 1 + .. '-' .. prefixZerosFixedLength(airportInfo[1][8],2) -- runway 2 + .. ' (' .. prefixZerosFixedLength(airportInfo[1][9],2) .. ')') -- prefered runway based on wind in parens + + -- Secondary Airport (second closest) + ExportScript.Tools.SendData(8102, airportInfo[2][1] .. '\n' -- name of airport + --[[.. 'BRG ']] .. format_int(addZeros3(round(airportInfo[2][3],0))) .. 'º ' -- bearing + --[[.. 'DIST ']] .. format_int(round(airportInfo[2][2], 0)) .. 'nm\n' -- distance + .. 'ETE ' .. airportInfo[2][4] .. '\n' -- estimated time in route + .. '' .. prefixZerosFixedLength(round(airportInfo[2][5], 0),3) .. 'º ' -- wind bearing + .. round(metersPerSecond2knots(airportInfo[2][6]),0) .. 'kts' -- wing strength + .. '\n' .. prefixZerosFixedLength(airportInfo[2][7],2) -- runway 1 + .. '-' .. prefixZerosFixedLength(airportInfo[2][8],2) -- runway 2 + .. ' (' .. prefixZerosFixedLength(airportInfo[2][9],2) .. ')') -- prefered runway based on wind in parens +end + +function ExportScript.WindsAloft(mainPanelDevice) + + -- Winds relative to the aircraft, aka, winds aloft + local windAloft = LoGetVectorWindVelocity() + local windStrengthAloft = math.sqrt((windAloft.x)^2 + (windAloft.z)^2) + local windDirectionAloft = math.deg(math.atan2(windAloft.z, windAloft.x)) + if windDirectionAloft < 0 then + windDirectionAloft = 360 + windDirectionAloft + end + + -- Convert to direction to from direction + if windDirectionAloft > 180 then + windDirectionAloft = windDirectionAloft - 180 + else + windDirectionAloft = windDirectionAloft + 180 + end + + ExportScript.Tools.SendData(8100, 'Wind Aloft\n' .. addZeros3(round(windDirectionAloft,0)) .. 'º ' + .. round(metersPerSecond2knots(windStrengthAloft,0)) .. 'kts' + ) -- winds at the aircraft +end +function ExportScript.GroundRadar(mainPanelDevice) -- may return some odd things + + local tableOfUnits = LoGetWorldObjects('units') + + local tableOfGround = {} + -- relative to the player... + local tableOfGround_friendly = {} + local tableOfGround_friendlyReports = {} + local tableOfGround_enemy = {} + local tableOfGround_enemyReports = {} + + for key,value in pairs(tableOfUnits) do + if value.Type.level1 == 2 then + table.insert(tableOfGround, value) + end + end + + local selfData = LoGetSelfData() + local selfCoalitionID = selfData.CoalitionID + + for key,value in pairs(tableOfGround) do + if value.CoalitionID == selfCoalitionID then + table.insert(tableOfGround_friendly, value) + else + table.insert(tableOfGround_enemy, value) + end + end + + -- TODO: only do enemy reports if there is an awacs unit(?) + for key,value in pairs(tableOfGround_enemy) do + local distance = getdistance(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, "nm") + + local bearing = getBearing(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, true) + + table.insert(tableOfGround_enemyReports, -- the table name + {value.Name, distance, bearing}) --[1][2][3] + end + table.sort(tableOfGround_enemyReports, function(a,b) return a[2] < b[2] end) -- sort based on the second value, which is distance + + + for key,value in pairs(tableOfGround_friendly) do + local distance = getdistance(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, "nm") + + local bearing = getBearing(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, true) + + table.insert(tableOfGround_friendlyReports, -- the table name + {value.Name, distance, bearing}) --[1][2][3] + + end + table.sort(tableOfGround_friendlyReports, function(a,b) return a[2] < b[2] end) -- sort based on the second value, which is distance + + local string_8200 = 'No Ground\nEnemy\nDetected' + if tableOfGround_enemyReports[1] ~= nill then + string_8200 = 'Enemy Ground\n' .. tableOfGround_enemyReports[1][1] + .. '\n ' .. prefixZerosFixedLength(tableOfGround_enemyReports[1][3],3) -- bearing + .. 'º ' .. round(tableOfGround_enemyReports[1][2],0) .. 'nm'--distance + end + + local string_8201 = 'No Ground\nEnemy\nDetected' + if tableOfGround_enemyReports[2] ~= nill then + string_8201 = 'Enemy Ground\n'.. tableOfGround_enemyReports[2][1] + .. '\n ' .. prefixZerosFixedLength(tableOfGround_enemyReports[2][3],3) -- bearing + .. 'º ' .. round(tableOfGround_enemyReports[2][2],0) .. 'nm'--distance + end + + local string_8202 = 'No Ground\nFriend\nDetected' + if tableOfGround_friendlyReports[1] ~= nill then + string_8202 = 'Friend Ground\n' .. tableOfGround_friendlyReports[1][1] + .. '\n ' .. prefixZerosFixedLength(tableOfGround_friendlyReports[1][3],3) -- bearing + .. 'º ' .. round(tableOfGround_friendlyReports[1][2],0) .. 'nm'--distance + end + + local string_8203 = 'No Ground\nFriend\nDetected' + if tableOfGround_friendlyReports[2] ~= nill then + string_8203 = 'Friend Ground\n' .. tableOfGround_friendlyReports[2][1] + .. '\n ' .. prefixZerosFixedLength(tableOfGround_friendlyReports[2][3],3) -- bearing + .. 'º ' .. round(tableOfGround_friendlyReports[2][2],0) .. 'nm'--distance + end + + ExportScript.Tools.SendData(8200, string_8200) + ExportScript.Tools.SendData(8201, string_8201) + ExportScript.Tools.SendData(8202, string_8202) + ExportScript.Tools.SendData(8203, string_8203) +end + +function ExportScript.AirRadar(mainPanelDevice) + + local tableOfUnits = LoGetWorldObjects('units') + + local tableOfAircraft = {} + -- relative to the player... + local tableOfAircraft_friendly = {} + local tableOfAircraft_friendlyReports = {} + local tableOfAircraft_enemy = {} + local tableOfAircraft_enemyReports = {} + + for key,value in pairs(tableOfUnits) do + if value.Type.level1 == 1 then + table.insert(tableOfAircraft, value) + end + end + + local selfData = LoGetSelfData() + local selfCoalitionID = selfData.CoalitionID + + for key,value in pairs(tableOfAircraft) do + if value.CoalitionID == selfCoalitionID then + table.insert(tableOfAircraft_friendly, value) + else + table.insert(tableOfAircraft_enemy, value) + end + end + + -- TODO: only do enemy reports if there is a awacs unit + for key,value in pairs(tableOfAircraft_enemy) do + local distance = getdistance(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, "nm") + + local bearing = getBearing(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, true) + + table.insert(tableOfAircraft_enemyReports, -- the table name + {value.Name, distance, bearing}) --[1][2][3] + + -- https://stackoverflow.com/questions/51276613/how-to-sort-table-by-value-and-then-print-index-in-order + -- https://www.tutorialspoint.com/sort-function-in-lua-programming + + end + table.sort(tableOfAircraft_enemyReports, function(a,b) return a[2] < b[2] end) -- sort based on the second value, which is distance + + + for key,value in pairs(tableOfAircraft_friendly) do -- [1] will always be the player + local distance = getdistance(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, "nm") + + local bearing = getBearing(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, true) + + table.insert(tableOfAircraft_friendlyReports, -- the table name + {value.Name, distance, bearing}) --[1][2][3] + + end + table.sort(tableOfAircraft_friendlyReports, function(a,b) return a[2] < b[2] end) -- sort based on the second value, which is distance + + local string_8210 = 'No Air\nEnemy\nDetected' + if tableOfAircraft_enemyReports[1] ~= nill then + string_8210 = 'Enemy Air\n' .. tableOfAircraft_enemyReports[1][1] + .. '\n ' .. prefixZerosFixedLength(tableOfAircraft_enemyReports[1][3],3) -- bearing + .. 'º ' .. round(tableOfAircraft_enemyReports[1][2],0) .. 'nm'--distance + end + + local string_8211 = 'No Air\nEnemy\nDetected' + if tableOfAircraft_enemyReports[2] ~= nill then + string_8211 = 'Enemy Air\n' .. tableOfAircraft_enemyReports[2][1] + .. '\n ' .. prefixZerosFixedLength(tableOfAircraft_enemyReports[2][3],3) -- bearing + .. 'º ' .. round(tableOfAircraft_enemyReports[2][2],0) .. 'nm'--distance + end + + local string_8212 = 'No Air\nFriend\nDetected' + if tableOfAircraft_friendlyReports[2] ~= nill then + string_8212 = 'Friend Air\n' .. tableOfAircraft_friendlyReports[2][1] + .. '\n ' .. prefixZerosFixedLength(tableOfAircraft_friendlyReports[2][3],3) -- bearing + .. 'º ' .. round(tableOfAircraft_friendlyReports[2][2],0) .. 'nm'--distance + end + + local string_8213 = 'No Air\nFriend\nDetected' + if tableOfAircraft_friendlyReports[3] ~= nill then + string_8213 = 'Friend Air\n' .. tableOfAircraft_friendlyReports[3][1] + .. '\n ' .. prefixZerosFixedLength(tableOfAircraft_friendlyReports[3][3],3) -- bearing + .. 'º ' .. round(tableOfAircraft_friendlyReports[3][2],0) .. 'nm'--distance + end + + ExportScript.Tools.SendData(8210,string_8210) + ExportScript.Tools.SendData(8211, string_8211) + ExportScript.Tools.SendData(8212, string_8212) + ExportScript.Tools.SendData(8213, string_8213) +end + +function ExportScript.IglaHunter(mainPanelDevice) -- Locates the nearest Igla + + local tableOfUnits = LoGetWorldObjects('units') + local selfData = LoGetSelfData() + local selfCoalitionID = selfData.CoalitionID + + local tableOfIgla = {} + local tableOfIgla_report = {} + + --TODO: Might have to refine this. + for key,value in pairs(tableOfUnits) do + if value.CoalitionID ~= selfCoalitionID then + if value.Type.level3 == 27 then + if value.Type.level2 == 16 then + if value.Type.level1 == 2 then + if value.Type.level4 == 55 or 54 or 53 or 52 or 62 then + table.insert(tableOfIgla, value) + end + end + end + end + end + end + + --if tableOfIgla ~= null then + for key,value in pairs(tableOfIgla) do + local distance = getdistance(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, "nm") + + local bearing = getBearing(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, true) + + table.insert(tableOfIgla_report, -- the table name + {value.Name, distance, bearing}) --[1][2][3] + --end + end + table.sort(tableOfIgla_report, function(a,b) return a[2] < b[2] end) -- sort based on the second value, which is distance + + local string_8666 = 'Igla Hunter\nSearching...' + if tableOfIgla_report[1] ~= nill then + string_8666 = 'Igla Detected\n' .. tableOfIgla_report[1][1] + .. '\n ' .. prefixZerosFixedLength(tableOfIgla_report[1][3],3) -- bearing + .. 'º ' .. round(tableOfIgla_report[1][2],0) .. ' nm'--distance + end + + ExportScript.Tools.SendData(8666, string_8666) + +end +---------------------- +-- Helper Functions -- +---------------------- +function ExportScript.Linearize(current_value, raw_tab, final_tab) + -- (c) scoobie + if current_value <= raw_tab[1] then + return final_tab[1] + end + for index, value in pairs(raw_tab) do + if current_value <= value then + local ft = final_tab[index] + local rt = raw_tab[index] + return (current_value - rt) * (ft - final_tab[index - 1]) / (rt - raw_tab[index - 1]) + ft + end + end + -- we shouldn't be here, so something went wrong - return arbitrary max. final value, maybe the user will notice the problem: + return final_tab[#final_tab] +end + +function trim(s) --http://lua-users.org/wiki/CommonFunctions + -- from PiL2 20.4 + return (s:gsub("^%s*(.-)%s*$", "%1")) +end +function formatTime(time) + local seconds = math.floor(time) % 60 + local minutes = math.floor(time / 60) % 60 + local hours = math.floor(time / (60 * 60)) % 24 + return string.format("%02d", hours) .. "-" .. string.format("%02d", minutes) .. "-" .. string.format("%02d", seconds) +end + +function meters2feet(meters) + local feet = meters * 3.281 + return feet +end + +function feet2meters(feet) + local meters = feet / 3.281 + return feet +end + +function metersPerSecond2milesPerHour(metersPerSecond) + local milesPerHour = metersPerSecond * 2.237 + return milesPerHour +end + +function metersPerSecond2knots(metersPerSecond) + local knots = metersPerSecond * 1.944 + return knots +end + +function kgPerSecond2poundPerHour(kgPerSecond) + poundPerHour = kgPerSecond * 7937 + return poundPerHour +end + +function metersPerSecond2feetPerMinute(metersPerSecond) + local feetPerMinute = metersPerSecond * 197 + return feetPerMinute +end + +function round(num, numDecimalPlaces) --http://lua-users.org/wiki/SimpleRound + local mult = 10^(numDecimalPlaces or 0) + return math.floor(num * mult + 0.5) / mult +end + +function format_int(number) --https://stackoverflow.com/questions/10989788/format-integer-in-lua + local i, j, minus, int, fraction = tostring(number):find('([-]?)(%d+)([.]?%d*)') + -- reverse the int-string and append a comma to all blocks of 3 digits + int = int:reverse():gsub("(%d%d%d)", "%1,") + -- reverse the int-string back remove an optional comma and put the + -- optional minus and fractional part back + return minus .. int:reverse():gsub("^,", "") .. fraction +end + +function formatCoord(type, isLat, d) + local h + if isLat then + if d < 0 then + h = 'S' + d = -d + else + h = 'N' + end + else + if d < 0 then + h = 'W' + d = -d + else + h = 'E' + end + end + + local g = math.floor(d) + local m = math.floor(d * 60 - g * 60) + local s = d * 3600 - g * 3600 - m * 60 + + if type == "DMS" then -- Degree Minutes Seconds + s = math.floor(s * 100) / 100 + return string.format('%s %2d°%.2d\'%05.2f"', h, g, m, s) + elseif type == "DDM" then -- Degree Decimal Minutes + s = math.floor(s / 60 * 1000) + return string.format('%s %2d°%02d.%3.3d\'', h, g, m, s) + else -- Decimal Degrees + return string.format('%f',d) + end +end + +function getdistance(lat1,lat2,lon1,lon2,unit) -- https://www.geeksforgeeks.org/program-distance-two-points-earth/ + --Example Locations + --lat1 = 42.1578 -- POTI + --lat2 = 42.3269 -- HONI + --lon1 = 41.6777 + --lon2 = 42.4122 + + local lon1 = toRadians(lon1) + local lon2 = toRadians(lon2) + local lat1 = toRadians(lat1) + local lat2 = toRadians(lat2) + + -- Haversine formula + local dlon = lon2 - lon1 + local dlat = lat2 - lat1 + local a = math.pow(math.sin(dlat / 2), 2) + + math.cos(lat1) * math.cos(lat2) * + math.pow(math.sin(dlon / 2),2) + + local c = 2 * math.asin(math.sqrt(a)) + + local r -- Radius of earth in X. + if unit == 'nm' then + r = 6371 / 1.852 -- times 1.852 because I could not find a good NM source + elseif unit == 'km' then + r = 6371 -- Use 6371 for kilometers + elseif unit == 'miles' then + r = 3956 -- Use 3956 for miles + elseif unit == 'meters' then + r = 6371 * 1000 + end + + -- calculate the result + return (c * r) +end + +function toRadians(angleIn10thofaDegree) + return (angleIn10thofaDegree * math.pi) / 180 +end + + +function getBearing(lat1,lat2,lon1,lon2, magnetic) + local bearing_rad = math.atan2(lon2 - lon1, lat2 - lat1) + if bearing_rad < 0 then + bearing_rad = bearing_rad + (2 * math.pi) + end + + bearing = math.deg(bearing_rad) + + -- start calculation for getting the magnetic bar + local _aircraftPitch, _aircraftBank, aircraftYawTrue = LoGetADIPitchBankYaw() + aircraftYawTrue = aircraftYawTrue * 57.3 -- actually heading + local aircraftYawMagnetic = LoGetMagneticYaw() * 57.3 + local magneticVariance = aircraftYawTrue - aircraftYawMagnetic + + if magnetic == true then + bearing = bearing - magneticVariance + end + + -- correction for bearings less than 0 due to the calculation above + if bearing < 0 then + bearing = bearing + 360 + end + + return bearing +end + +function addZeros3(number) + number = string.format("%.1d" , number) + if #number == 2 then + number = "0" .. number + elseif #number == 1 then + number = "00" .. number + end + return number +end + +function prefixZerosFixedLength(number, digitLength) -- prefixZerosFixedLength(99, 3) --> 099 + number = string.format("%.1d" , number) -- make the number a string + local zerosToAdd = digitLength - #number + s = '' + for i = 1, zerosToAdd do + s = s .. 0 + end + return s .. number +end + +function mgrsTableize(mgrsString) + -- Reference: https://upload.wikimedia.org/wikipedia/commons/b/b7/Universal_Transverse_Mercator_zones.svg + -- example: 38 T LM 12345 54321 + -- (\d+\s\w)\s(\w+)\s(.+)\s(.+) --c# version of regex + -- UTMZone = string, + -- MGRSDigraph = string, + -- Easting = number, + -- Northing = number + local UTMZone , MGRSDigraph, Easting, Northing = mgrsString:match('(%d+%s%w)%s(%w+)%s(.+)%s(.+)') + local mgrsTbl = {} + table.insert(mgrsTbl, {UTMZone,MGRSDigraph,Easting,Northing}) + return mgrsTbl +end + +function isNilOrEmpty(value) + if value == "" or value == nil then + return true + else + return false + end +end + +function NilOrEmpty(value) + if value == "" then + return 'empty' + elseif value == nil then + return 'empty' + else + return value + end end \ No newline at end of file diff --git a/Scripts/DCS-ExportScript/ExportsModules/F-16C_50.lua b/Scripts/DCS-ExportScript/ExportsModules/F-16C_50.lua index c51fb7b..7ac53bb 100644 --- a/Scripts/DCS-ExportScript/ExportsModules/F-16C_50.lua +++ b/Scripts/DCS-ExportScript/ExportsModules/F-16C_50.lua @@ -706,9 +706,9 @@ function ExportScript.ProcessIkarusDCSConfigLowImportance(mainPanelDevice) ExportScript.LoAircraftInfo(mainPanelDevice) -- Provides a lot of aircraft properties ExportScript.AirportInfo(mainPanelDevice) -- Provides info on the two closest airports ExportScript.WindsAloft(mainPanelDevice) -- Gets winds at the aircraft - ExportScript.GroundRadar(mainPanelDevice) -- Reports 2 closest friendlies and 2 enemies - ExportScript.AirRadar(mainPanelDevice) -- Reports 2 closest friendlies and 2 enemies - ExportScript.IglaHunter(mainPanelDevice) -- Locates closest Igla + ExportScript.GroundRadar(mainPanelDevice) -- Reports 2 closest friendlies and 2 enemies (Use in Single Player) + ExportScript.AirRadar(mainPanelDevice) -- Reports 2 closest friendlies and 2 enemies (Use in Single Player) + ExportScript.IglaHunter(mainPanelDevice) -- Locates closest Igla (Use in Single Player) end end @@ -1954,7 +1954,6 @@ function ExportScript.Linearize(current_value, raw_tab, final_tab) end function trim(s) --http://lua-users.org/wiki/CommonFunctions - -- from PiL2 20.4 return (s:gsub("^%s*(.-)%s*$", "%1")) end diff --git a/Scripts/DCS-ExportScript/ExportsModules/F-5E-3.lua b/Scripts/DCS-ExportScript/ExportsModules/F-5E-3.lua index 0266aaf..6d2c4c5 100644 --- a/Scripts/DCS-ExportScript/ExportsModules/F-5E-3.lua +++ b/Scripts/DCS-ExportScript/ExportsModules/F-5E-3.lua @@ -532,9 +532,9 @@ function ExportScript.ProcessIkarusDCSConfigLowImportance(mainPanelDevice) ExportScript.LoAircraftInfo(mainPanelDevice) -- Provides a lot of aircraft properties ExportScript.AirportInfo(mainPanelDevice) -- Provides info on the two closest airports ExportScript.WindsAloft(mainPanelDevice) -- Gets winds at the aircraft - ExportScript.GroundRadar(mainPanelDevice) -- Reports 2 closest friendlies and 2 enemies - ExportScript.AirRadar(mainPanelDevice) -- Reports 2 closest friendlies and 2 enemies - ExportScript.IglaHunter(mainPanelDevice) -- Locates closest Igla + ExportScript.GroundRadar(mainPanelDevice) -- Reports 2 closest friendlies and 2 enemies (Use in Single Player) + ExportScript.AirRadar(mainPanelDevice) -- Reports 2 closest friendlies and 2 enemies (Use in Single Player) + ExportScript.IglaHunter(mainPanelDevice) -- Locates closest Igla (Use in Single Player) end end diff --git a/Scripts/DCS-ExportScript/ExportsModules/F-86F Sabre.lua b/Scripts/DCS-ExportScript/ExportsModules/F-86F Sabre.lua index 5ec3d3e..1e3316d 100644 --- a/Scripts/DCS-ExportScript/ExportsModules/F-86F Sabre.lua +++ b/Scripts/DCS-ExportScript/ExportsModules/F-86F Sabre.lua @@ -1,8 +1,11 @@ -- F-86 Export +-- https://github.com/asherao/DCS-ExportScripts +local base = _G -- game information +local os = base.os -- time +local Terrain = require('terrain') -- map info ExportScript.FoundDCSModule = true ExportScript.Version.F86 = "1.2.1" ---ExportScript.NoLuaExportBeforeNextFrame = true ExportScript.ConfigEveryFrameArguments = { @@ -365,7 +368,16 @@ function ExportScript.ProcessIkarusDCSConfigLowImportance(mainPanelDevice) ExportScript.Tools.IkarusCockpitLights(mainPanelDevice, {654,813,811,812}) -- Compass Light Switch, Instrument Panel Primary Light Rheostat, Instrument Panel Auxiliary Light Rheostat, Console and Panel Light Rheostat - + if LoIsObjectExportAllowed() then -- returns true if world objects data is available + if LoIsOwnshipExportAllowed() then -- returns true if ownship data is available + ExportScript.LoAircraftInfo(mainPanelDevice) -- Provides a lot of aircraft properties + ExportScript.AirportInfo(mainPanelDevice) -- Provides info on the two closest airports + ExportScript.WindsAloft(mainPanelDevice) -- Gets winds at the aircraft + ExportScript.GroundRadar(mainPanelDevice) -- Reports 2 closest friendlies and 2 enemies (Use in Single Player) + ExportScript.AirRadar(mainPanelDevice) -- Reports 2 closest friendlies and 2 enemies (Use in Single Player) + ExportScript.IglaHunter(mainPanelDevice) -- Locates closest Igla (Use in Single Player) + end + end end function ExportScript.ProcessDACConfigLowImportance(mainPanelDevice) @@ -432,10 +444,730 @@ function ExportScript.Radios(mainPanelDevice) ExportScript.Tools.SendData(3003, "ADF\n" .. ADF_Freq) end ------------------------------ --- Helper functions -- ------------------------------ +function ExportScript.LoAircraftInfo(mainPanelDevice) + + -- General + local aircraftName = LoGetSelfData().Name -- DCS Name of the aircraft eg "F-5E-3" + local pilotName = LoGetPilotName() -- Logbook Pilot name + + -- Times DCS times are default in seconds + local dcsModelTime = LoGetModelTime() -- time since aircraft spawn + local missionStartTime = LoGetMissionStartTime() -- second after midnight that the mission started + local dcsTimeLocal = formatTime(LoGetMissionStartTime() + LoGetModelTime()) -- up-to-date time in dcs + local utcOffset = -1 * Terrain.GetTerrainConfig('SummerTimeDelta') * 3600 -- eg -1 * 4 * 3600 (for seconds to get hours) + local dcsTimeUtc = formatTime(dcsModelTime + LoGetMissionStartTime() + utcOffset) -- dcs zulu time + local realTimeLocal = os.date("%H-%M-%S") -- real life time + local realTimeUtc = os.date("!%H-%M-%S") -- real life zulu time + --local playTime = formatTime(DCS.getRealTime()) -- does not work, export environment no access + + -- Player Aircraft Properties + local altMsl_meters = LoGetAltitudeAboveSeaLevel() + local altMsl_feet = meters2feet(altMsl_meters) + local altAgl_meters = LoGetAltitudeAboveGroundLevel() + local altAgl_feet = meters2feet(altAgl_meters) + + local verticalVelocity_metric = LoGetVerticalVelocity() + local verticalVelocity_imperial = metersPerSecond2feetPerMinute(LoGetVerticalVelocity()) + + local ias_metric = LoGetIndicatedAirSpeed() + local ias_knots = metersPerSecond2knots(LoGetIndicatedAirSpeed()) + local ias_mph = metersPerSecond2milesPerHour(LoGetIndicatedAirSpeed()) ------------------- --- Notes -- ------------------- \ No newline at end of file + local tas_metric = LoGetTrueAirSpeed() + local tas_knots = metersPerSecond2knots(LoGetTrueAirSpeed()) + local tas_mph = metersPerSecond2milesPerHour(LoGetTrueAirSpeed()) + + local speed_mach = LoGetMachNumber() + local accel_g = LoGetAccelerationUnits().y + local aoa = LoGetAngleOfAttack() + + --local atmosphericPressure_mmhg = LoGetBasicAtmospherePressure() -- does not seem to work + + local aircraftPitch, aircraftBank, aircraftYawTrue = LoGetADIPitchBankYaw() + aircraftPitch = aircraftPitch * 57.3 + aircraftBank = aircraftBank * 57.3 + aircraftYawTrue = aircraftYawTrue * 57.3 -- true heading + local aircraftYawMagnetic = LoGetMagneticYaw() * 57.3 -- magnetic heading + local aircraftHeading = aircraftYawMagnetic -- this cound be negative + if aircraftHeading < 0 then aircraftHeading = aircraftHeading + 360 end -- removes the negative + local magneticVariance = aircraftYawTrue - aircraftYawMagnetic -- works for all maps + + local selfData = LoGetSelfData() -- relative the the player + local lLatitude = selfData.LatLongAlt.Lat + local lLongitude = selfData.LatLongAlt.Long + local mgrs = Terrain.GetMGRScoordinates(LoGetSelfData().Position.x, LoGetSelfData().Position.z) + local mgrsTable = mgrsTableize(mgrs) -- format is mgrsTable[1][1], mgrsTable[1][2], mgrsTable[1][3], mgrsTable[1][4] + + local aircraftHeadingTrue = selfData.Heading * 57.3 -- true yeading (same as trueYaw for fixed wing aircraft) + + -- Engine Info + local engineInfo = LoGetEngineInfo() + local lEngineRPMleft = engineInfo.RPM.left -- ENG1 RPM % + local lEngineRPMright = engineInfo.RPM.right -- ENG2 RPM % + local lEngineFuelInternal = engineInfo.fuel_internal -- 1 = full. 0 = empty. Includes external tanks for FF aircraft + local lEngineFuelExternal = engineInfo.fuel_external -- TANK2 (EXT) (KG) -- does not seem to work for FF modules + local lEngineFuelTotal = lEngineFuelInternal + lEngineFuelExternal + local lEngineTempLeft = engineInfo.Temperature.left -- ENG1 EGT ºC. May get odd numbers + local lEngineTempRight = engineInfo.Temperature.right -- ENG2 EGT ºC. May get odd numbers + + local lFuelConsumptionLeft = engineInfo.FuelConsumption.left -- {left ,right},kg per sec + local lFuelConsumptionRight = engineInfo.FuelConsumption.right -- {left ,right},kg per sec + local lFuelConsumptionTotal = lFuelConsumptionLeft + lFuelConsumptionRight -- total,kg per sec + local lHydraulicPressureLeft = engineInfo.HydraulicPressure.left -- {left ,right},kg per square centimeter + local lHydraulicPressureRight = engineInfo.HydraulicPressure.right -- {left ,right},kg per square centimeter + + ExportScript.Tools.SendData(8000, aircraftName) + + ExportScript.Tools.SendData(8001, pilotName) + + ExportScript.Tools.SendData(8002, 'Real Time\n'.. realTimeLocal .. '\nDCS Time\n' .. dcsTimeLocal) -- clocks + + ExportScript.Tools.SendData(8003, 'HDG ' .. prefixZerosFixedLength(round(aircraftHeading,0),3) .. 'º' + .. '\nALT ' .. format_int(round(altMsl_feet,-1)) .. ' ft' + .. '\nIAS ' .. round(ias_knots,0) .. ' kts' + .. '\nV/S ' .. format_int(round(verticalVelocity_imperial,-2)) .. ' ft/min' + ) -- Aircraft Instrument panel (western) + + ExportScript.Tools.SendData(8004, 'HDG ' .. prefixZerosFixedLength(round(aircraftHeading,0),3) .. 'º' + .. '\nALT ' .. format_int(round(altMsl_meters,-1)) .. ' m' + .. '\nIAS ' .. round(ias_metric,0) .. ' km/h' + .. '\nV/S ' .. format_int(round(verticalVelocity_metric,0)) .. ' m/s' + ) -- Aircraft Instrument panel (eastern) + + ExportScript.Tools.SendData(8005, 'HDG ' .. prefixZerosFixedLength(round(aircraftHeading,0),3) .. 'º' + .. '\nALT ' .. format_int(round(altMsl_feet,-1)) .. ' ft' + .. '\nIAS ' .. round(ias_mph,0) .. ' mph' + .. '\nV/S ' .. format_int(round(verticalVelocity_imperial,-2)) .. ' ft/min' + ) -- Aircraft Instrument panel (western ww2) + + ExportScript.Tools.SendData(8006, "Lat-Long-DMS\n" .. formatCoord("DMS",true, lLatitude) + .. "\n" .. formatCoord("DMS",false, lLongitude) + ) -- Player coordinates in DMS + + ExportScript.Tools.SendData(8007, "Lat-Long-DDM\n" .. formatCoord("DDM",true, lLatitude) + .. "\n" .. formatCoord("DDM",false, lLongitude) + ) -- Player coordinates in DDM + + ExportScript.Tools.SendData(8008, 'MGRS\n'.. mgrsTable[1][1] .. ' ' .. mgrsTable[1][2] + .. '\n' .. mgrsTable[1][3] .. ' ' .. mgrsTable[1][4] + ) -- Player coordinates in MGRS on 2 rows + title + + ExportScript.Tools.SendData(8009, 'Mag Var\n' .. format_int(round(magneticVariance, 2))) -- also called magnetic deviation + + -- Example for using the Lo Data. Feel free to make your own! + ExportScript.Tools.SendData(8010, format_int(round(kgPerSecond2poundPerHour(lFuelConsumptionLeft), -1))) -- fuel use in pph + +end +function ExportScript.AirportInfo(mainPanelDevice) + + local airdromes = LoGetWorldObjects("airdromes") -- returns a list of runways and their popperties + local airportInfo = {} -- contains generated table of important properties + -- the table will be sorted by nearest airport first + -- for this table: + -- airportInfo[1] is the first element + -- airportInfo[1][1] is the airport name of the first element/airport + -- airportInfo[1][2] is the distance to the airport of the first element/airport + -- airportInfo[1][3] is the bearing to the airport of the first element/airport + -- airportInfo[1][4] is the extimated time en route + -- airportInfo[1][5] is the direction of the wind + -- airportInfo[1][6] is the windStrength of the wind + -- airportInfo[1][7] is the main runway heading + -- airportInfo[1][8] is the reverse of the main runway + -- airportInfo[1][9] is the prefered runway based on winds + + for key,value in pairs(airdromes) do + + -- remove the woRunWay entries so that only named runways are in the list + if value.Name ~= 'woRunWay' then + + -- get the distance from the player to the runway + local distance = getdistance(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, "nm") + + -- get the direction from the player to the runway + local bearing = getBearing(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, true) + + -- estimate the runway heading based on the reported values + local runwayHeading = round(value.Heading * 57.3,-1) / 10 + if runwayHeading < 0 then + runwayHeading = 36 + runwayHeading + end + -- Reverse it for the reciprocal runway + local runwayHeadingReciprocal + if runwayHeading > 18 then + runwayHeadingReciprocal = runwayHeading-18 + else + runwayHeadingReciprocal = runwayHeading+18 + end + + local ete = distance / metersPerSecond2knots(LoGetTrueAirSpeed()) * (60 * 60) --based on tas bc dcs is flat... + -- if ete is more than 24hrs, make it 24 hrs, which shows up as 00-00-00 + -- this case is for choppers and aircraft that arent moving + if ete > 86400 then ete = 86400 end + ete = formatTime(ete) + + -- wind at airport calculations. Each airport has slighty different winds + -- https://forum.dcs.world/topic/165136-logetwindatpoint-in-exportlua/#comment-3294428 + -- LoGetWindAtPoint(x,y,z,is_radio_alt), 2 meters off the ground for the "wind sensor" + local vx,_vy,vz,_absolute_height = LoGetWindAtPoint(value.Position.x,2,value.Position.y,true) + local windDirectionInRadians = math.atan2(vz,vx) + local windDirection = windDirectionInRadians * 57.3 + local windStrength = math.sqrt((vx)^2 + (vz)^2) + if windDirection < 0 then + windDirection = 360 + windDirection + end + -- Convert to direction to from direction + if windDirection > 180 then + windDirection = windDirection - 180 + else + windDirection = windDirection + 180 + end + + -- Calculate the prefered runway for landing + -- if the rounded runway is within +- 9 of the rounded wind, then it is prefered + local windRounded = round(windDirection, -1) + if windRounded >= runwayHeading - 9 and windRounded <= runwayHeading + 9 then + runwayHeadingPrefered = runwayHeading + else + runwayHeadingPrefered = runwayHeadingReciprocal + end + + -- Populate the table with the important info for each airport + table.insert(airportInfo, -- the table name + {value.Name, -- airport name [1] + distance, bearing, ete, --[2][3][4] + windDirection,windStrength, --wind direction [5], wind Strength [6] + runwayHeading, runwayHeadingReciprocal,runwayHeadingPrefered}) -- [7][8][9] + + end -- end of woRunWay + end -- end of FOR loop + + -- sort the table based on the second value, which is distance + -- https://stackoverflow.com/questions/51276613/how-to-sort-table-by-value-and-then-print-index-in-order + -- https://www.tutorialspoint.com/sort-function-in-lua-programming + table.sort(airportInfo, function(a,b) return a[2] < b[2] end) + + -- Primary Airport (closest) + ExportScript.Tools.SendData(8101, airportInfo[1][1] .. '\n' -- name of airport + --[[.. 'BRG ']] .. format_int(addZeros3(round(airportInfo[1][3],0))) .. 'º ' -- bearing + --[[.. 'DIST ']] .. format_int(round(airportInfo[1][2], 0)) .. 'nm\n' -- distance + .. 'ETE ' .. airportInfo[1][4] .. '\n' -- estimated time in route + .. '' .. prefixZerosFixedLength(round(airportInfo[1][5], 0),3) .. 'º ' -- wind bearing + .. round(metersPerSecond2knots(airportInfo[1][6]),0) .. 'kts' -- wing strength + .. '\n' .. prefixZerosFixedLength(airportInfo[1][7],2) -- runway 1 + .. '-' .. prefixZerosFixedLength(airportInfo[1][8],2) -- runway 2 + .. ' (' .. prefixZerosFixedLength(airportInfo[1][9],2) .. ')') -- prefered runway based on wind in parens + + -- Secondary Airport (second closest) + ExportScript.Tools.SendData(8102, airportInfo[2][1] .. '\n' -- name of airport + --[[.. 'BRG ']] .. format_int(addZeros3(round(airportInfo[2][3],0))) .. 'º ' -- bearing + --[[.. 'DIST ']] .. format_int(round(airportInfo[2][2], 0)) .. 'nm\n' -- distance + .. 'ETE ' .. airportInfo[2][4] .. '\n' -- estimated time in route + .. '' .. prefixZerosFixedLength(round(airportInfo[2][5], 0),3) .. 'º ' -- wind bearing + .. round(metersPerSecond2knots(airportInfo[2][6]),0) .. 'kts' -- wing strength + .. '\n' .. prefixZerosFixedLength(airportInfo[2][7],2) -- runway 1 + .. '-' .. prefixZerosFixedLength(airportInfo[2][8],2) -- runway 2 + .. ' (' .. prefixZerosFixedLength(airportInfo[2][9],2) .. ')') -- prefered runway based on wind in parens +end + +function ExportScript.WindsAloft(mainPanelDevice) + + -- Winds relative to the aircraft, aka, winds aloft + local windAloft = LoGetVectorWindVelocity() + local windStrengthAloft = math.sqrt((windAloft.x)^2 + (windAloft.z)^2) + local windDirectionAloft = math.deg(math.atan2(windAloft.z, windAloft.x)) + if windDirectionAloft < 0 then + windDirectionAloft = 360 + windDirectionAloft + end + + -- Convert to direction to from direction + if windDirectionAloft > 180 then + windDirectionAloft = windDirectionAloft - 180 + else + windDirectionAloft = windDirectionAloft + 180 + end + + ExportScript.Tools.SendData(8100, 'Wind Aloft\n' .. addZeros3(round(windDirectionAloft,0)) .. 'º ' + .. round(metersPerSecond2knots(windStrengthAloft,0)) .. 'kts' + ) -- winds at the aircraft +end +function ExportScript.GroundRadar(mainPanelDevice) -- may return some odd things + + local tableOfUnits = LoGetWorldObjects('units') + + local tableOfGround = {} + -- relative to the player... + local tableOfGround_friendly = {} + local tableOfGround_friendlyReports = {} + local tableOfGround_enemy = {} + local tableOfGround_enemyReports = {} + + for key,value in pairs(tableOfUnits) do + if value.Type.level1 == 2 then + table.insert(tableOfGround, value) + end + end + + local selfData = LoGetSelfData() + local selfCoalitionID = selfData.CoalitionID + + for key,value in pairs(tableOfGround) do + if value.CoalitionID == selfCoalitionID then + table.insert(tableOfGround_friendly, value) + else + table.insert(tableOfGround_enemy, value) + end + end + + -- TODO: only do enemy reports if there is an awacs unit(?) + for key,value in pairs(tableOfGround_enemy) do + local distance = getdistance(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, "nm") + + local bearing = getBearing(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, true) + + table.insert(tableOfGround_enemyReports, -- the table name + {value.Name, distance, bearing}) --[1][2][3] + end + table.sort(tableOfGround_enemyReports, function(a,b) return a[2] < b[2] end) -- sort based on the second value, which is distance + + + for key,value in pairs(tableOfGround_friendly) do + local distance = getdistance(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, "nm") + + local bearing = getBearing(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, true) + + table.insert(tableOfGround_friendlyReports, -- the table name + {value.Name, distance, bearing}) --[1][2][3] + + end + table.sort(tableOfGround_friendlyReports, function(a,b) return a[2] < b[2] end) -- sort based on the second value, which is distance + + local string_8200 = 'No Ground\nEnemy\nDetected' + if tableOfGround_enemyReports[1] ~= nill then + string_8200 = 'Enemy Ground\n' .. tableOfGround_enemyReports[1][1] + .. '\n ' .. prefixZerosFixedLength(tableOfGround_enemyReports[1][3],3) -- bearing + .. 'º ' .. round(tableOfGround_enemyReports[1][2],0) .. 'nm'--distance + end + + local string_8201 = 'No Ground\nEnemy\nDetected' + if tableOfGround_enemyReports[2] ~= nill then + string_8201 = 'Enemy Ground\n'.. tableOfGround_enemyReports[2][1] + .. '\n ' .. prefixZerosFixedLength(tableOfGround_enemyReports[2][3],3) -- bearing + .. 'º ' .. round(tableOfGround_enemyReports[2][2],0) .. 'nm'--distance + end + + local string_8202 = 'No Ground\nFriend\nDetected' + if tableOfGround_friendlyReports[1] ~= nill then + string_8202 = 'Friend Ground\n' .. tableOfGround_friendlyReports[1][1] + .. '\n ' .. prefixZerosFixedLength(tableOfGround_friendlyReports[1][3],3) -- bearing + .. 'º ' .. round(tableOfGround_friendlyReports[1][2],0) .. 'nm'--distance + end + + local string_8203 = 'No Ground\nFriend\nDetected' + if tableOfGround_friendlyReports[2] ~= nill then + string_8203 = 'Friend Ground\n' .. tableOfGround_friendlyReports[2][1] + .. '\n ' .. prefixZerosFixedLength(tableOfGround_friendlyReports[2][3],3) -- bearing + .. 'º ' .. round(tableOfGround_friendlyReports[2][2],0) .. 'nm'--distance + end + + ExportScript.Tools.SendData(8200, string_8200) + ExportScript.Tools.SendData(8201, string_8201) + ExportScript.Tools.SendData(8202, string_8202) + ExportScript.Tools.SendData(8203, string_8203) +end + +function ExportScript.AirRadar(mainPanelDevice) + + local tableOfUnits = LoGetWorldObjects('units') + + local tableOfAircraft = {} + -- relative to the player... + local tableOfAircraft_friendly = {} + local tableOfAircraft_friendlyReports = {} + local tableOfAircraft_enemy = {} + local tableOfAircraft_enemyReports = {} + + for key,value in pairs(tableOfUnits) do + if value.Type.level1 == 1 then + table.insert(tableOfAircraft, value) + end + end + + local selfData = LoGetSelfData() + local selfCoalitionID = selfData.CoalitionID + + for key,value in pairs(tableOfAircraft) do + if value.CoalitionID == selfCoalitionID then + table.insert(tableOfAircraft_friendly, value) + else + table.insert(tableOfAircraft_enemy, value) + end + end + + -- TODO: only do enemy reports if there is a awacs unit + for key,value in pairs(tableOfAircraft_enemy) do + local distance = getdistance(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, "nm") + + local bearing = getBearing(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, true) + + table.insert(tableOfAircraft_enemyReports, -- the table name + {value.Name, distance, bearing}) --[1][2][3] + + -- https://stackoverflow.com/questions/51276613/how-to-sort-table-by-value-and-then-print-index-in-order + -- https://www.tutorialspoint.com/sort-function-in-lua-programming + + end + table.sort(tableOfAircraft_enemyReports, function(a,b) return a[2] < b[2] end) -- sort based on the second value, which is distance + + + for key,value in pairs(tableOfAircraft_friendly) do -- [1] will always be the player + local distance = getdistance(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, "nm") + + local bearing = getBearing(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, true) + + table.insert(tableOfAircraft_friendlyReports, -- the table name + {value.Name, distance, bearing}) --[1][2][3] + + end + table.sort(tableOfAircraft_friendlyReports, function(a,b) return a[2] < b[2] end) -- sort based on the second value, which is distance + + local string_8210 = 'No Air\nEnemy\nDetected' + if tableOfAircraft_enemyReports[1] ~= nill then + string_8210 = 'Enemy Air\n' .. tableOfAircraft_enemyReports[1][1] + .. '\n ' .. prefixZerosFixedLength(tableOfAircraft_enemyReports[1][3],3) -- bearing + .. 'º ' .. round(tableOfAircraft_enemyReports[1][2],0) .. 'nm'--distance + end + + local string_8211 = 'No Air\nEnemy\nDetected' + if tableOfAircraft_enemyReports[2] ~= nill then + string_8211 = 'Enemy Air\n' .. tableOfAircraft_enemyReports[2][1] + .. '\n ' .. prefixZerosFixedLength(tableOfAircraft_enemyReports[2][3],3) -- bearing + .. 'º ' .. round(tableOfAircraft_enemyReports[2][2],0) .. 'nm'--distance + end + + local string_8212 = 'No Air\nFriend\nDetected' + if tableOfAircraft_friendlyReports[2] ~= nill then + string_8212 = 'Friend Air\n' .. tableOfAircraft_friendlyReports[2][1] + .. '\n ' .. prefixZerosFixedLength(tableOfAircraft_friendlyReports[2][3],3) -- bearing + .. 'º ' .. round(tableOfAircraft_friendlyReports[2][2],0) .. 'nm'--distance + end + + local string_8213 = 'No Air\nFriend\nDetected' + if tableOfAircraft_friendlyReports[3] ~= nill then + string_8213 = 'Friend Air\n' .. tableOfAircraft_friendlyReports[3][1] + .. '\n ' .. prefixZerosFixedLength(tableOfAircraft_friendlyReports[3][3],3) -- bearing + .. 'º ' .. round(tableOfAircraft_friendlyReports[3][2],0) .. 'nm'--distance + end + + ExportScript.Tools.SendData(8210,string_8210) + ExportScript.Tools.SendData(8211, string_8211) + ExportScript.Tools.SendData(8212, string_8212) + ExportScript.Tools.SendData(8213, string_8213) +end + +function ExportScript.IglaHunter(mainPanelDevice) -- Locates the nearest Igla + + local tableOfUnits = LoGetWorldObjects('units') + local selfData = LoGetSelfData() + local selfCoalitionID = selfData.CoalitionID + + local tableOfIgla = {} + local tableOfIgla_report = {} + + --TODO: Might have to refine this. + for key,value in pairs(tableOfUnits) do + if value.CoalitionID ~= selfCoalitionID then + if value.Type.level3 == 27 then + if value.Type.level2 == 16 then + if value.Type.level1 == 2 then + if value.Type.level4 == 55 or 54 or 53 or 52 or 62 then + table.insert(tableOfIgla, value) + end + end + end + end + end + end + + --if tableOfIgla ~= null then + for key,value in pairs(tableOfIgla) do + local distance = getdistance(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, "nm") + + local bearing = getBearing(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, true) + + table.insert(tableOfIgla_report, -- the table name + {value.Name, distance, bearing}) --[1][2][3] + --end + end + table.sort(tableOfIgla_report, function(a,b) return a[2] < b[2] end) -- sort based on the second value, which is distance + + local string_8666 = 'Igla Hunter\nSearching...' + if tableOfIgla_report[1] ~= nill then + string_8666 = 'Igla Detected\n' .. tableOfIgla_report[1][1] + .. '\n ' .. prefixZerosFixedLength(tableOfIgla_report[1][3],3) -- bearing + .. 'º ' .. round(tableOfIgla_report[1][2],0) .. ' nm'--distance + end + + ExportScript.Tools.SendData(8666, string_8666) + +end +---------------------- +-- Helper Functions -- +---------------------- +function ExportScript.Linearize(current_value, raw_tab, final_tab) + -- (c) scoobie + if current_value <= raw_tab[1] then + return final_tab[1] + end + for index, value in pairs(raw_tab) do + if current_value <= value then + local ft = final_tab[index] + local rt = raw_tab[index] + return (current_value - rt) * (ft - final_tab[index - 1]) / (rt - raw_tab[index - 1]) + ft + end + end + -- we shouldn't be here, so something went wrong - return arbitrary max. final value, maybe the user will notice the problem: + return final_tab[#final_tab] +end + +function trim(s) --http://lua-users.org/wiki/CommonFunctions + -- from PiL2 20.4 + return (s:gsub("^%s*(.-)%s*$", "%1")) +end +function formatTime(time) + local seconds = math.floor(time) % 60 + local minutes = math.floor(time / 60) % 60 + local hours = math.floor(time / (60 * 60)) % 24 + return string.format("%02d", hours) .. "-" .. string.format("%02d", minutes) .. "-" .. string.format("%02d", seconds) +end + +function meters2feet(meters) + local feet = meters * 3.281 + return feet +end + +function feet2meters(feet) + local meters = feet / 3.281 + return feet +end + +function metersPerSecond2milesPerHour(metersPerSecond) + local milesPerHour = metersPerSecond * 2.237 + return milesPerHour +end + +function metersPerSecond2knots(metersPerSecond) + local knots = metersPerSecond * 1.944 + return knots +end + +function kgPerSecond2poundPerHour(kgPerSecond) + poundPerHour = kgPerSecond * 7937 + return poundPerHour +end + +function metersPerSecond2feetPerMinute(metersPerSecond) + local feetPerMinute = metersPerSecond * 197 + return feetPerMinute +end + +function round(num, numDecimalPlaces) --http://lua-users.org/wiki/SimpleRound + local mult = 10^(numDecimalPlaces or 0) + return math.floor(num * mult + 0.5) / mult +end + +function format_int(number) --https://stackoverflow.com/questions/10989788/format-integer-in-lua + local i, j, minus, int, fraction = tostring(number):find('([-]?)(%d+)([.]?%d*)') + -- reverse the int-string and append a comma to all blocks of 3 digits + int = int:reverse():gsub("(%d%d%d)", "%1,") + -- reverse the int-string back remove an optional comma and put the + -- optional minus and fractional part back + return minus .. int:reverse():gsub("^,", "") .. fraction +end + +function formatCoord(type, isLat, d) + local h + if isLat then + if d < 0 then + h = 'S' + d = -d + else + h = 'N' + end + else + if d < 0 then + h = 'W' + d = -d + else + h = 'E' + end + end + + local g = math.floor(d) + local m = math.floor(d * 60 - g * 60) + local s = d * 3600 - g * 3600 - m * 60 + + if type == "DMS" then -- Degree Minutes Seconds + s = math.floor(s * 100) / 100 + return string.format('%s %2d°%.2d\'%05.2f"', h, g, m, s) + elseif type == "DDM" then -- Degree Decimal Minutes + s = math.floor(s / 60 * 1000) + return string.format('%s %2d°%02d.%3.3d\'', h, g, m, s) + else -- Decimal Degrees + return string.format('%f',d) + end +end + +function getdistance(lat1,lat2,lon1,lon2,unit) -- https://www.geeksforgeeks.org/program-distance-two-points-earth/ + --Example Locations + --lat1 = 42.1578 -- POTI + --lat2 = 42.3269 -- HONI + --lon1 = 41.6777 + --lon2 = 42.4122 + + local lon1 = toRadians(lon1) + local lon2 = toRadians(lon2) + local lat1 = toRadians(lat1) + local lat2 = toRadians(lat2) + + -- Haversine formula + local dlon = lon2 - lon1 + local dlat = lat2 - lat1 + local a = math.pow(math.sin(dlat / 2), 2) + + math.cos(lat1) * math.cos(lat2) * + math.pow(math.sin(dlon / 2),2) + + local c = 2 * math.asin(math.sqrt(a)) + + local r -- Radius of earth in X. + if unit == 'nm' then + r = 6371 / 1.852 -- times 1.852 because I could not find a good NM source + elseif unit == 'km' then + r = 6371 -- Use 6371 for kilometers + elseif unit == 'miles' then + r = 3956 -- Use 3956 for miles + elseif unit == 'meters' then + r = 6371 * 1000 + end + + -- calculate the result + return (c * r) +end + +function toRadians(angleIn10thofaDegree) + return (angleIn10thofaDegree * math.pi) / 180 +end + + +function getBearing(lat1,lat2,lon1,lon2, magnetic) + local bearing_rad = math.atan2(lon2 - lon1, lat2 - lat1) + if bearing_rad < 0 then + bearing_rad = bearing_rad + (2 * math.pi) + end + + bearing = math.deg(bearing_rad) + + -- start calculation for getting the magnetic bar + local _aircraftPitch, _aircraftBank, aircraftYawTrue = LoGetADIPitchBankYaw() + aircraftYawTrue = aircraftYawTrue * 57.3 -- actually heading + local aircraftYawMagnetic = LoGetMagneticYaw() * 57.3 + local magneticVariance = aircraftYawTrue - aircraftYawMagnetic + + if magnetic == true then + bearing = bearing - magneticVariance + end + + -- correction for bearings less than 0 due to the calculation above + if bearing < 0 then + bearing = bearing + 360 + end + + return bearing +end + +function addZeros3(number) + number = string.format("%.1d" , number) + if #number == 2 then + number = "0" .. number + elseif #number == 1 then + number = "00" .. number + end + return number +end + +function prefixZerosFixedLength(number, digitLength) -- prefixZerosFixedLength(99, 3) --> 099 + number = string.format("%.1d" , number) -- make the number a string + local zerosToAdd = digitLength - #number + s = '' + for i = 1, zerosToAdd do + s = s .. 0 + end + return s .. number +end + +function mgrsTableize(mgrsString) + -- Reference: https://upload.wikimedia.org/wikipedia/commons/b/b7/Universal_Transverse_Mercator_zones.svg + -- example: 38 T LM 12345 54321 + -- (\d+\s\w)\s(\w+)\s(.+)\s(.+) --c# version of regex + -- UTMZone = string, + -- MGRSDigraph = string, + -- Easting = number, + -- Northing = number + local UTMZone , MGRSDigraph, Easting, Northing = mgrsString:match('(%d+%s%w)%s(%w+)%s(.+)%s(.+)') + local mgrsTbl = {} + table.insert(mgrsTbl, {UTMZone,MGRSDigraph,Easting,Northing}) + return mgrsTbl +end + +function isNilOrEmpty(value) + if value == "" or value == nil then + return true + else + return false + end +end + +function NilOrEmpty(value) + if value == "" then + return 'empty' + elseif value == nil then + return 'empty' + else + return value + end +end diff --git a/Scripts/DCS-ExportScript/ExportsModules/MosquitoFBMkVI.lua b/Scripts/DCS-ExportScript/ExportsModules/MosquitoFBMkVI.lua index 45c907f..486b1fb 100644 --- a/Scripts/DCS-ExportScript/ExportsModules/MosquitoFBMkVI.lua +++ b/Scripts/DCS-ExportScript/ExportsModules/MosquitoFBMkVI.lua @@ -7,9 +7,15 @@ Please report any bugs, conflicts, or fixes on the github. https://github.com/asherao/DCS-ExportScripts See the bottom of the file for notes. Tiles and unique exports will be enabled after testing. +~Bailey *****DISCLAIMER***** --]] +-- https://github.com/asherao/DCS-ExportScripts +local base = _G -- game information +local os = base.os -- time +local Terrain = require('terrain') -- map info + ExportScript.FoundDCSModule = true ExportScript.Version.MosquitoFBMkVI = "1.2.1" @@ -106,9 +112,9 @@ ExportScript.ConfigEveryFrameArguments = [102] = "%.4f", -- Clock start/stop twist {0.0, 1.0} [103] = "%.4f", -- Voltimeter {0.0, 1.0} - [104] = "%.4f", -- T1154M1Gauge - [105] = "%.4f", -- T1154M2Gauge - [106] = "%.4f", -- T1154M3Gauge + [104] = "%.4f", -- unknown ??? + [105] = "%.4f", -- unknown ??? + [106] = "%.4f", -- unknown ??? [110] = "%.4f", -- Rudder trim hand knob needle {-1.0, 1.0} [115] = "%.4f", -- Bomb Doors Lever {-1.0, 1.0} @@ -520,6 +526,17 @@ function ExportScript.ProcessIkarusDCSConfigLowImportance(mainPanelDevice) ExportScript.oxygenTile(mainPanelDevice) ExportScript.BestPowerTiles(mainPanelDevice) ExportScript.MaxSpeedTiles(mainPanelDevice) + + if LoIsObjectExportAllowed() then -- returns true if world objects data is available + if LoIsOwnshipExportAllowed() then -- returns true if ownship data is available + ExportScript.LoAircraftInfo(mainPanelDevice) -- Provides a lot of aircraft properties + ExportScript.AirportInfo(mainPanelDevice) -- Provides info on the two closest airports + ExportScript.WindsAloft(mainPanelDevice) -- Gets winds at the aircraft + ExportScript.GroundRadar(mainPanelDevice) -- Reports 2 closest friendlies and 2 enemies (Use in Single Player) + ExportScript.AirRadar(mainPanelDevice) -- Reports 2 closest friendlies and 2 enemies (Use in Single Player) + ExportScript.IglaHunter(mainPanelDevice) -- Locates closest Igla (Use in Single Player) + end + end end function ExportScript.ProcessDACConfigLowImportance(mainPanelDevice) @@ -1528,6 +1545,509 @@ function ExportScript.StallSpeeds(mainPanelDevice) end +function ExportScript.LoAircraftInfo(mainPanelDevice) + + -- General + local aircraftName = LoGetSelfData().Name -- DCS Name of the aircraft eg "F-5E-3" + local pilotName = LoGetPilotName() -- Logbook Pilot name + + -- Times DCS times are default in seconds + local dcsModelTime = LoGetModelTime() -- time since aircraft spawn + local missionStartTime = LoGetMissionStartTime() -- second after midnight that the mission started + local dcsTimeLocal = formatTime(LoGetMissionStartTime() + LoGetModelTime()) -- up-to-date time in dcs + local utcOffset = -1 * Terrain.GetTerrainConfig('SummerTimeDelta') * 3600 -- eg -1 * 4 * 3600 (for seconds to get hours) + local dcsTimeUtc = formatTime(dcsModelTime + LoGetMissionStartTime() + utcOffset) -- dcs zulu time + local realTimeLocal = os.date("%H-%M-%S") -- real life time + local realTimeUtc = os.date("!%H-%M-%S") -- real life zulu time + --local playTime = formatTime(DCS.getRealTime()) -- does not work, export environment no access + + -- Player Aircraft Properties + local altMsl_meters = LoGetAltitudeAboveSeaLevel() + local altMsl_feet = meters2feet(altMsl_meters) + local altAgl_meters = LoGetAltitudeAboveGroundLevel() + local altAgl_feet = meters2feet(altAgl_meters) + + local verticalVelocity_metric = LoGetVerticalVelocity() + local verticalVelocity_imperial = metersPerSecond2feetPerMinute(LoGetVerticalVelocity()) + + local ias_metric = LoGetIndicatedAirSpeed() + local ias_knots = metersPerSecond2knots(LoGetIndicatedAirSpeed()) + local ias_mph = metersPerSecond2milesPerHour(LoGetIndicatedAirSpeed()) + + local tas_metric = LoGetTrueAirSpeed() + local tas_knots = metersPerSecond2knots(LoGetTrueAirSpeed()) + local tas_mph = metersPerSecond2milesPerHour(LoGetTrueAirSpeed()) + + local speed_mach = LoGetMachNumber() + local accel_g = LoGetAccelerationUnits().y + local aoa = LoGetAngleOfAttack() + + --local atmosphericPressure_mmhg = LoGetBasicAtmospherePressure() -- does not seem to work + + local aircraftPitch, aircraftBank, aircraftYawTrue = LoGetADIPitchBankYaw() + aircraftPitch = aircraftPitch * 57.3 + aircraftBank = aircraftBank * 57.3 + aircraftYawTrue = aircraftYawTrue * 57.3 -- true heading + local aircraftYawMagnetic = LoGetMagneticYaw() * 57.3 -- magnetic heading + local aircraftHeading = aircraftYawMagnetic -- this cound be negative + if aircraftHeading < 0 then aircraftHeading = aircraftHeading + 360 end -- removes the negative + local magneticVariance = aircraftYawTrue - aircraftYawMagnetic -- works for all maps + + local selfData = LoGetSelfData() -- relative the the player + local lLatitude = selfData.LatLongAlt.Lat + local lLongitude = selfData.LatLongAlt.Long + local mgrs = Terrain.GetMGRScoordinates(LoGetSelfData().Position.x, LoGetSelfData().Position.z) + local mgrsTable = mgrsTableize(mgrs) -- format is mgrsTable[1][1], mgrsTable[1][2], mgrsTable[1][3], mgrsTable[1][4] + + local aircraftHeadingTrue = selfData.Heading * 57.3 -- true yeading (same as trueYaw for fixed wing aircraft) + + -- Engine Info + local engineInfo = LoGetEngineInfo() + local lEngineRPMleft = engineInfo.RPM.left -- ENG1 RPM % + local lEngineRPMright = engineInfo.RPM.right -- ENG2 RPM % + local lEngineFuelInternal = engineInfo.fuel_internal -- 1 = full. 0 = empty. Includes external tanks for FF aircraft + local lEngineFuelExternal = engineInfo.fuel_external -- TANK2 (EXT) (KG) -- does not seem to work for FF modules + local lEngineFuelTotal = lEngineFuelInternal + lEngineFuelExternal + local lEngineTempLeft = engineInfo.Temperature.left -- ENG1 EGT ºC. May get odd numbers + local lEngineTempRight = engineInfo.Temperature.right -- ENG2 EGT ºC. May get odd numbers + + local lFuelConsumptionLeft = engineInfo.FuelConsumption.left -- {left ,right},kg per sec + local lFuelConsumptionRight = engineInfo.FuelConsumption.right -- {left ,right},kg per sec + local lFuelConsumptionTotal = lFuelConsumptionLeft + lFuelConsumptionRight -- total,kg per sec + local lHydraulicPressureLeft = engineInfo.HydraulicPressure.left -- {left ,right},kg per square centimeter + local lHydraulicPressureRight = engineInfo.HydraulicPressure.right -- {left ,right},kg per square centimeter + + ExportScript.Tools.SendData(8000, aircraftName) + + ExportScript.Tools.SendData(8001, pilotName) + + ExportScript.Tools.SendData(8002, 'Real Time\n'.. realTimeLocal .. '\nDCS Time\n' .. dcsTimeLocal) -- clocks + + ExportScript.Tools.SendData(8003, 'HDG ' .. prefixZerosFixedLength(round(aircraftHeading,0),3) .. 'º' + .. '\nALT ' .. format_int(round(altMsl_feet,-1)) .. ' ft' + .. '\nIAS ' .. round(ias_knots,0) .. ' kts' + .. '\nV/S ' .. format_int(round(verticalVelocity_imperial,-2)) .. ' ft/min' + ) -- Aircraft Instrument panel (western) + + ExportScript.Tools.SendData(8004, 'HDG ' .. prefixZerosFixedLength(round(aircraftHeading,0),3) .. 'º' + .. '\nALT ' .. format_int(round(altMsl_meters,-1)) .. ' m' + .. '\nIAS ' .. round(ias_metric,0) .. ' km/h' + .. '\nV/S ' .. format_int(round(verticalVelocity_metric,0)) .. ' m/s' + ) -- Aircraft Instrument panel (eastern) + + ExportScript.Tools.SendData(8005, 'HDG ' .. prefixZerosFixedLength(round(aircraftHeading,0),3) .. 'º' + .. '\nALT ' .. format_int(round(altMsl_feet,-1)) .. ' ft' + .. '\nIAS ' .. round(ias_mph,0) .. ' mph' + .. '\nV/S ' .. format_int(round(verticalVelocity_imperial,-2)) .. ' ft/min' + ) -- Aircraft Instrument panel (western ww2) + + ExportScript.Tools.SendData(8006, "Lat-Long-DMS\n" .. formatCoord("DMS",true, lLatitude) + .. "\n" .. formatCoord("DMS",false, lLongitude) + ) -- Player coordinates in DMS + + ExportScript.Tools.SendData(8007, "Lat-Long-DDM\n" .. formatCoord("DDM",true, lLatitude) + .. "\n" .. formatCoord("DDM",false, lLongitude) + ) -- Player coordinates in DDM + + ExportScript.Tools.SendData(8008, 'MGRS\n'.. mgrsTable[1][1] .. ' ' .. mgrsTable[1][2] + .. '\n' .. mgrsTable[1][3] .. ' ' .. mgrsTable[1][4] + ) -- Player coordinates in MGRS on 2 rows + title + + ExportScript.Tools.SendData(8009, 'Mag Var\n' .. format_int(round(magneticVariance, 2))) -- also called magnetic deviation + + -- Example for using the Lo Data. Feel free to make your own! + ExportScript.Tools.SendData(8010, format_int(round(kgPerSecond2poundPerHour(lFuelConsumptionLeft), -1))) -- fuel use in pph + +end +function ExportScript.AirportInfo(mainPanelDevice) + + local airdromes = LoGetWorldObjects("airdromes") -- returns a list of runways and their popperties + local airportInfo = {} -- contains generated table of important properties + -- the table will be sorted by nearest airport first + -- for this table: + -- airportInfo[1] is the first element + -- airportInfo[1][1] is the airport name of the first element/airport + -- airportInfo[1][2] is the distance to the airport of the first element/airport + -- airportInfo[1][3] is the bearing to the airport of the first element/airport + -- airportInfo[1][4] is the extimated time en route + -- airportInfo[1][5] is the direction of the wind + -- airportInfo[1][6] is the windStrength of the wind + -- airportInfo[1][7] is the main runway heading + -- airportInfo[1][8] is the reverse of the main runway + -- airportInfo[1][9] is the prefered runway based on winds + + for key,value in pairs(airdromes) do + + -- remove the woRunWay entries so that only named runways are in the list + if value.Name ~= 'woRunWay' then + + -- get the distance from the player to the runway + local distance = getdistance(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, "nm") + + -- get the direction from the player to the runway + local bearing = getBearing(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, true) + + -- estimate the runway heading based on the reported values + local runwayHeading = round(value.Heading * 57.3,-1) / 10 + if runwayHeading < 0 then + runwayHeading = 36 + runwayHeading + end + -- Reverse it for the reciprocal runway + local runwayHeadingReciprocal + if runwayHeading > 18 then + runwayHeadingReciprocal = runwayHeading-18 + else + runwayHeadingReciprocal = runwayHeading+18 + end + + local ete = distance / metersPerSecond2knots(LoGetTrueAirSpeed()) * (60 * 60) --based on tas bc dcs is flat... + -- if ete is more than 24hrs, make it 24 hrs, which shows up as 00-00-00 + -- this case is for choppers and aircraft that arent moving + if ete > 86400 then ete = 86400 end + ete = formatTime(ete) + + -- wind at airport calculations. Each airport has slighty different winds + -- https://forum.dcs.world/topic/165136-logetwindatpoint-in-exportlua/#comment-3294428 + -- LoGetWindAtPoint(x,y,z,is_radio_alt), 2 meters off the ground for the "wind sensor" + local vx,_vy,vz,_absolute_height = LoGetWindAtPoint(value.Position.x,2,value.Position.y,true) + local windDirectionInRadians = math.atan2(vz,vx) + local windDirection = windDirectionInRadians * 57.3 + local windStrength = math.sqrt((vx)^2 + (vz)^2) + if windDirection < 0 then + windDirection = 360 + windDirection + end + -- Convert to direction to from direction + if windDirection > 180 then + windDirection = windDirection - 180 + else + windDirection = windDirection + 180 + end + + -- Calculate the prefered runway for landing + -- if the rounded runway is within +- 9 of the rounded wind, then it is prefered + local windRounded = round(windDirection, -1) + if windRounded >= runwayHeading - 9 and windRounded <= runwayHeading + 9 then + runwayHeadingPrefered = runwayHeading + else + runwayHeadingPrefered = runwayHeadingReciprocal + end + + -- Populate the table with the important info for each airport + table.insert(airportInfo, -- the table name + {value.Name, -- airport name [1] + distance, bearing, ete, --[2][3][4] + windDirection,windStrength, --wind direction [5], wind Strength [6] + runwayHeading, runwayHeadingReciprocal,runwayHeadingPrefered}) -- [7][8][9] + + end -- end of woRunWay + end -- end of FOR loop + + -- sort the table based on the second value, which is distance + -- https://stackoverflow.com/questions/51276613/how-to-sort-table-by-value-and-then-print-index-in-order + -- https://www.tutorialspoint.com/sort-function-in-lua-programming + table.sort(airportInfo, function(a,b) return a[2] < b[2] end) + + -- Primary Airport (closest) + ExportScript.Tools.SendData(8101, airportInfo[1][1] .. '\n' -- name of airport + --[[.. 'BRG ']] .. format_int(addZeros3(round(airportInfo[1][3],0))) .. 'º ' -- bearing + --[[.. 'DIST ']] .. format_int(round(airportInfo[1][2], 0)) .. 'nm\n' -- distance + .. 'ETE ' .. airportInfo[1][4] .. '\n' -- estimated time in route + .. '' .. prefixZerosFixedLength(round(airportInfo[1][5], 0),3) .. 'º ' -- wind bearing + .. round(metersPerSecond2knots(airportInfo[1][6]),0) .. 'kts' -- wing strength + .. '\n' .. prefixZerosFixedLength(airportInfo[1][7],2) -- runway 1 + .. '-' .. prefixZerosFixedLength(airportInfo[1][8],2) -- runway 2 + .. ' (' .. prefixZerosFixedLength(airportInfo[1][9],2) .. ')') -- prefered runway based on wind in parens + + -- Secondary Airport (second closest) + ExportScript.Tools.SendData(8102, airportInfo[2][1] .. '\n' -- name of airport + --[[.. 'BRG ']] .. format_int(addZeros3(round(airportInfo[2][3],0))) .. 'º ' -- bearing + --[[.. 'DIST ']] .. format_int(round(airportInfo[2][2], 0)) .. 'nm\n' -- distance + .. 'ETE ' .. airportInfo[2][4] .. '\n' -- estimated time in route + .. '' .. prefixZerosFixedLength(round(airportInfo[2][5], 0),3) .. 'º ' -- wind bearing + .. round(metersPerSecond2knots(airportInfo[2][6]),0) .. 'kts' -- wing strength + .. '\n' .. prefixZerosFixedLength(airportInfo[2][7],2) -- runway 1 + .. '-' .. prefixZerosFixedLength(airportInfo[2][8],2) -- runway 2 + .. ' (' .. prefixZerosFixedLength(airportInfo[2][9],2) .. ')') -- prefered runway based on wind in parens +end + +function ExportScript.WindsAloft(mainPanelDevice) + + -- Winds relative to the aircraft, aka, winds aloft + local windAloft = LoGetVectorWindVelocity() + local windStrengthAloft = math.sqrt((windAloft.x)^2 + (windAloft.z)^2) + local windDirectionAloft = math.deg(math.atan2(windAloft.z, windAloft.x)) + if windDirectionAloft < 0 then + windDirectionAloft = 360 + windDirectionAloft + end + + -- Convert to direction to from direction + if windDirectionAloft > 180 then + windDirectionAloft = windDirectionAloft - 180 + else + windDirectionAloft = windDirectionAloft + 180 + end + + ExportScript.Tools.SendData(8100, 'Wind Aloft\n' .. addZeros3(round(windDirectionAloft,0)) .. 'º ' + .. round(metersPerSecond2knots(windStrengthAloft,0)) .. 'kts' + ) -- winds at the aircraft +end +function ExportScript.GroundRadar(mainPanelDevice) -- may return some odd things + + local tableOfUnits = LoGetWorldObjects('units') + + local tableOfGround = {} + -- relative to the player... + local tableOfGround_friendly = {} + local tableOfGround_friendlyReports = {} + local tableOfGround_enemy = {} + local tableOfGround_enemyReports = {} + + for key,value in pairs(tableOfUnits) do + if value.Type.level1 == 2 then + table.insert(tableOfGround, value) + end + end + + local selfData = LoGetSelfData() + local selfCoalitionID = selfData.CoalitionID + + for key,value in pairs(tableOfGround) do + if value.CoalitionID == selfCoalitionID then + table.insert(tableOfGround_friendly, value) + else + table.insert(tableOfGround_enemy, value) + end + end + + -- TODO: only do enemy reports if there is an awacs unit(?) + for key,value in pairs(tableOfGround_enemy) do + local distance = getdistance(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, "nm") + + local bearing = getBearing(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, true) + + table.insert(tableOfGround_enemyReports, -- the table name + {value.Name, distance, bearing}) --[1][2][3] + end + table.sort(tableOfGround_enemyReports, function(a,b) return a[2] < b[2] end) -- sort based on the second value, which is distance + + + for key,value in pairs(tableOfGround_friendly) do + local distance = getdistance(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, "nm") + + local bearing = getBearing(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, true) + + table.insert(tableOfGround_friendlyReports, -- the table name + {value.Name, distance, bearing}) --[1][2][3] + + end + table.sort(tableOfGround_friendlyReports, function(a,b) return a[2] < b[2] end) -- sort based on the second value, which is distance + + local string_8200 = 'No Ground\nEnemy\nDetected' + if tableOfGround_enemyReports[1] ~= nill then + string_8200 = 'Enemy Ground\n' .. tableOfGround_enemyReports[1][1] + .. '\n ' .. prefixZerosFixedLength(tableOfGround_enemyReports[1][3],3) -- bearing + .. 'º ' .. round(tableOfGround_enemyReports[1][2],0) .. 'nm'--distance + end + + local string_8201 = 'No Ground\nEnemy\nDetected' + if tableOfGround_enemyReports[2] ~= nill then + string_8201 = 'Enemy Ground\n'.. tableOfGround_enemyReports[2][1] + .. '\n ' .. prefixZerosFixedLength(tableOfGround_enemyReports[2][3],3) -- bearing + .. 'º ' .. round(tableOfGround_enemyReports[2][2],0) .. 'nm'--distance + end + + local string_8202 = 'No Ground\nFriend\nDetected' + if tableOfGround_friendlyReports[1] ~= nill then + string_8202 = 'Friend Ground\n' .. tableOfGround_friendlyReports[1][1] + .. '\n ' .. prefixZerosFixedLength(tableOfGround_friendlyReports[1][3],3) -- bearing + .. 'º ' .. round(tableOfGround_friendlyReports[1][2],0) .. 'nm'--distance + end + + local string_8203 = 'No Ground\nFriend\nDetected' + if tableOfGround_friendlyReports[2] ~= nill then + string_8203 = 'Friend Ground\n' .. tableOfGround_friendlyReports[2][1] + .. '\n ' .. prefixZerosFixedLength(tableOfGround_friendlyReports[2][3],3) -- bearing + .. 'º ' .. round(tableOfGround_friendlyReports[2][2],0) .. 'nm'--distance + end + + ExportScript.Tools.SendData(8200, string_8200) + ExportScript.Tools.SendData(8201, string_8201) + ExportScript.Tools.SendData(8202, string_8202) + ExportScript.Tools.SendData(8203, string_8203) +end + +function ExportScript.AirRadar(mainPanelDevice) + + local tableOfUnits = LoGetWorldObjects('units') + + local tableOfAircraft = {} + -- relative to the player... + local tableOfAircraft_friendly = {} + local tableOfAircraft_friendlyReports = {} + local tableOfAircraft_enemy = {} + local tableOfAircraft_enemyReports = {} + + for key,value in pairs(tableOfUnits) do + if value.Type.level1 == 1 then + table.insert(tableOfAircraft, value) + end + end + + local selfData = LoGetSelfData() + local selfCoalitionID = selfData.CoalitionID + + for key,value in pairs(tableOfAircraft) do + if value.CoalitionID == selfCoalitionID then + table.insert(tableOfAircraft_friendly, value) + else + table.insert(tableOfAircraft_enemy, value) + end + end + + -- TODO: only do enemy reports if there is a awacs unit + for key,value in pairs(tableOfAircraft_enemy) do + local distance = getdistance(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, "nm") + + local bearing = getBearing(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, true) + + table.insert(tableOfAircraft_enemyReports, -- the table name + {value.Name, distance, bearing}) --[1][2][3] + + -- https://stackoverflow.com/questions/51276613/how-to-sort-table-by-value-and-then-print-index-in-order + -- https://www.tutorialspoint.com/sort-function-in-lua-programming + + end + table.sort(tableOfAircraft_enemyReports, function(a,b) return a[2] < b[2] end) -- sort based on the second value, which is distance + + + for key,value in pairs(tableOfAircraft_friendly) do -- [1] will always be the player + local distance = getdistance(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, "nm") + + local bearing = getBearing(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, true) + + table.insert(tableOfAircraft_friendlyReports, -- the table name + {value.Name, distance, bearing}) --[1][2][3] + + end + table.sort(tableOfAircraft_friendlyReports, function(a,b) return a[2] < b[2] end) -- sort based on the second value, which is distance + + local string_8210 = 'No Air\nEnemy\nDetected' + if tableOfAircraft_enemyReports[1] ~= nill then + string_8210 = 'Enemy Air\n' .. tableOfAircraft_enemyReports[1][1] + .. '\n ' .. prefixZerosFixedLength(tableOfAircraft_enemyReports[1][3],3) -- bearing + .. 'º ' .. round(tableOfAircraft_enemyReports[1][2],0) .. 'nm'--distance + end + + local string_8211 = 'No Air\nEnemy\nDetected' + if tableOfAircraft_enemyReports[2] ~= nill then + string_8211 = 'Enemy Air\n' .. tableOfAircraft_enemyReports[2][1] + .. '\n ' .. prefixZerosFixedLength(tableOfAircraft_enemyReports[2][3],3) -- bearing + .. 'º ' .. round(tableOfAircraft_enemyReports[2][2],0) .. 'nm'--distance + end + + local string_8212 = 'No Air\nFriend\nDetected' + if tableOfAircraft_friendlyReports[2] ~= nill then + string_8212 = 'Friend Air\n' .. tableOfAircraft_friendlyReports[2][1] + .. '\n ' .. prefixZerosFixedLength(tableOfAircraft_friendlyReports[2][3],3) -- bearing + .. 'º ' .. round(tableOfAircraft_friendlyReports[2][2],0) .. 'nm'--distance + end + + local string_8213 = 'No Air\nFriend\nDetected' + if tableOfAircraft_friendlyReports[3] ~= nill then + string_8213 = 'Friend Air\n' .. tableOfAircraft_friendlyReports[3][1] + .. '\n ' .. prefixZerosFixedLength(tableOfAircraft_friendlyReports[3][3],3) -- bearing + .. 'º ' .. round(tableOfAircraft_friendlyReports[3][2],0) .. 'nm'--distance + end + + ExportScript.Tools.SendData(8210,string_8210) + ExportScript.Tools.SendData(8211, string_8211) + ExportScript.Tools.SendData(8212, string_8212) + ExportScript.Tools.SendData(8213, string_8213) +end + +function ExportScript.IglaHunter(mainPanelDevice) -- Locates the nearest Igla + + local tableOfUnits = LoGetWorldObjects('units') + local selfData = LoGetSelfData() + local selfCoalitionID = selfData.CoalitionID + + local tableOfIgla = {} + local tableOfIgla_report = {} + + --TODO: Might have to refine this. + for key,value in pairs(tableOfUnits) do + if value.CoalitionID ~= selfCoalitionID then + if value.Type.level3 == 27 then + if value.Type.level2 == 16 then + if value.Type.level1 == 2 then + if value.Type.level4 == 55 or 54 or 53 or 52 or 62 then + table.insert(tableOfIgla, value) + end + end + end + end + end + end + + --if tableOfIgla ~= null then + for key,value in pairs(tableOfIgla) do + local distance = getdistance(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, "nm") + + local bearing = getBearing(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, true) + + table.insert(tableOfIgla_report, -- the table name + {value.Name, distance, bearing}) --[1][2][3] + --end + end + table.sort(tableOfIgla_report, function(a,b) return a[2] < b[2] end) -- sort based on the second value, which is distance + + local string_8666 = 'Igla Hunter\nSearching...' + if tableOfIgla_report[1] ~= nill then + string_8666 = 'Igla Detected\n' .. tableOfIgla_report[1][1] + .. '\n ' .. prefixZerosFixedLength(tableOfIgla_report[1][3],3) -- bearing + .. 'º ' .. round(tableOfIgla_report[1][2],0) .. ' nm'--distance + end + + ExportScript.Tools.SendData(8666, string_8666) + +end + --[[ ------------------------------ -- Ideas for implementation -- @@ -1677,6 +2197,9 @@ end -- General Helper Functions -- ------------------------------ +---------------------- +-- Helper Functions -- +---------------------- function ExportScript.Linearize(current_value, raw_tab, final_tab) -- (c) scoobie if current_value <= raw_tab[1] then @@ -1693,29 +2216,197 @@ function ExportScript.Linearize(current_value, raw_tab, final_tab) return final_tab[#final_tab] end +function trim(s) --http://lua-users.org/wiki/CommonFunctions + -- from PiL2 20.4 + return (s:gsub("^%s*(.-)%s*$", "%1")) +end +function formatTime(time) + local seconds = math.floor(time) % 60 + local minutes = math.floor(time / 60) % 60 + local hours = math.floor(time / (60 * 60)) % 24 + return string.format("%02d", hours) .. "-" .. string.format("%02d", minutes) .. "-" .. string.format("%02d", seconds) +end + +function meters2feet(meters) + local feet = meters * 3.281 + return feet +end + +function feet2meters(feet) + local meters = feet / 3.281 + return feet +end + +function metersPerSecond2milesPerHour(metersPerSecond) + local milesPerHour = metersPerSecond * 2.237 + return milesPerHour +end + +function metersPerSecond2knots(metersPerSecond) + local knots = metersPerSecond * 1.944 + return knots +end + +function kgPerSecond2poundPerHour(kgPerSecond) + poundPerHour = kgPerSecond * 7937 + return poundPerHour +end + +function metersPerSecond2feetPerMinute(metersPerSecond) + local feetPerMinute = metersPerSecond * 197 + return feetPerMinute +end function round(num, numDecimalPlaces) --http://lua-users.org/wiki/SimpleRound local mult = 10^(numDecimalPlaces or 0) return math.floor(num * mult + 0.5) / mult end - function format_int(number) --https://stackoverflow.com/questions/10989788/format-integer-in-lua - local i, j, minus, int, fraction = tostring(number):find('([-]?)(%d+)([.]?%d*)') - -- reverse the int-string and append a comma to all blocks of 3 digits int = int:reverse():gsub("(%d%d%d)", "%1,") - -- reverse the int-string back remove an optional comma and put the -- optional minus and fractional part back return minus .. int:reverse():gsub("^,", "") .. fraction end +function formatCoord(type, isLat, d) + local h + if isLat then + if d < 0 then + h = 'S' + d = -d + else + h = 'N' + end + else + if d < 0 then + h = 'W' + d = -d + else + h = 'E' + end + end + + local g = math.floor(d) + local m = math.floor(d * 60 - g * 60) + local s = d * 3600 - g * 3600 - m * 60 + + if type == "DMS" then -- Degree Minutes Seconds + s = math.floor(s * 100) / 100 + return string.format('%s %2d°%.2d\'%05.2f"', h, g, m, s) + elseif type == "DDM" then -- Degree Decimal Minutes + s = math.floor(s / 60 * 1000) + return string.format('%s %2d°%02d.%3.3d\'', h, g, m, s) + else -- Decimal Degrees + return string.format('%f',d) + end +end + +function getdistance(lat1,lat2,lon1,lon2,unit) -- https://www.geeksforgeeks.org/program-distance-two-points-earth/ + --Example Locations + --lat1 = 42.1578 -- POTI + --lat2 = 42.3269 -- HONI + --lon1 = 41.6777 + --lon2 = 42.4122 + + local lon1 = toRadians(lon1) + local lon2 = toRadians(lon2) + local lat1 = toRadians(lat1) + local lat2 = toRadians(lat2) + + -- Haversine formula + local dlon = lon2 - lon1 + local dlat = lat2 - lat1 + local a = math.pow(math.sin(dlat / 2), 2) + + math.cos(lat1) * math.cos(lat2) * + math.pow(math.sin(dlon / 2),2) + + local c = 2 * math.asin(math.sqrt(a)) + + local r -- Radius of earth in X. + if unit == 'nm' then + r = 6371 / 1.852 -- times 1.852 because I could not find a good NM source + elseif unit == 'km' then + r = 6371 -- Use 6371 for kilometers + elseif unit == 'miles' then + r = 3956 -- Use 3956 for miles + elseif unit == 'meters' then + r = 6371 * 1000 + end + + -- calculate the result + return (c * r) +end + +function toRadians(angleIn10thofaDegree) + return (angleIn10thofaDegree * math.pi) / 180 +end + + +function getBearing(lat1,lat2,lon1,lon2, magnetic) + local bearing_rad = math.atan2(lon2 - lon1, lat2 - lat1) + if bearing_rad < 0 then + bearing_rad = bearing_rad + (2 * math.pi) + end + + bearing = math.deg(bearing_rad) + + -- start calculation for getting the magnetic bar + local _aircraftPitch, _aircraftBank, aircraftYawTrue = LoGetADIPitchBankYaw() + aircraftYawTrue = aircraftYawTrue * 57.3 -- actually heading + local aircraftYawMagnetic = LoGetMagneticYaw() * 57.3 + local magneticVariance = aircraftYawTrue - aircraftYawMagnetic + + if magnetic == true then + bearing = bearing - magneticVariance + end + + -- correction for bearings less than 0 due to the calculation above + if bearing < 0 then + bearing = bearing + 360 + end + + return bearing +end + +function addZeros3(number) + number = string.format("%.1d" , number) + if #number == 2 then + number = "0" .. number + elseif #number == 1 then + number = "00" .. number + end + return number +end + +function prefixZerosFixedLength(number, digitLength) -- prefixZerosFixedLength(99, 3) --> 099 + number = string.format("%.1d" , number) -- make the number a string + local zerosToAdd = digitLength - #number + s = '' + for i = 1, zerosToAdd do + s = s .. 0 + end + return s .. number +end + +function mgrsTableize(mgrsString) + -- Reference: https://upload.wikimedia.org/wikipedia/commons/b/b7/Universal_Transverse_Mercator_zones.svg + -- example: 38 T LM 12345 54321 + -- (\d+\s\w)\s(\w+)\s(.+)\s(.+) --c# version of regex + -- UTMZone = string, + -- MGRSDigraph = string, + -- Easting = number, + -- Northing = number + local UTMZone , MGRSDigraph, Easting, Northing = mgrsString:match('(%d+%s%w)%s(%w+)%s(.+)%s(.+)') + local mgrsTbl = {} + table.insert(mgrsTbl, {UTMZone,MGRSDigraph,Easting,Northing}) + return mgrsTbl +end function kts2mph(kts) -- converts kts to floored mph local mph = kts * 1.15078 mph = math.floor(mph) return mph -end - +end \ No newline at end of file diff --git a/Scripts/DCS-ExportScript/ExportsModules/P-51D.lua b/Scripts/DCS-ExportScript/ExportsModules/P-51D.lua index 3bb3158..5ffdd3c 100644 --- a/Scripts/DCS-ExportScript/ExportsModules/P-51D.lua +++ b/Scripts/DCS-ExportScript/ExportsModules/P-51D.lua @@ -1,5 +1,10 @@ -- P-51D-25-NA Export +-- https://github.com/asherao/DCS-ExportScripts +local base = _G -- game information +local os = base.os -- time +local Terrain = require('terrain') -- map info + ExportScript.FoundDCSModule = true ExportScript.Version.P51D25NA = "1.2.1" @@ -278,16 +283,17 @@ end -- Pointed to by ExportScript.ProcessIkarusDCSConfigLowImportance function ExportScript.ProcessIkarusDCSConfigLowImportance(mainPanelDevice) - --[[ - export in low tick interval to Ikarus - Example from A-10C - Get Radio Frequencies - get data from device - local lUHFRadio = GetDevice(54) - ExportScript.Tools.SendData("ExportID", "Format") - ExportScript.Tools.SendData(2000, string.format("%7.3f", lUHFRadio:get_frequency()/1000000)) -- <- special function for get frequency data - ExportScript.Tools.SendData(2000, ExportScript.Tools.RoundFreqeuncy((UHF_RADIO:get_frequency()/1000000))) -- ExportScript.Tools.RoundFreqeuncy(frequency (MHz|KHz), format ("7.3"), PrefixZeros (false), LeastValue (0.025)) - ]] + + if LoIsObjectExportAllowed() then -- returns true if world objects data is available + if LoIsOwnshipExportAllowed() then -- returns true if ownship data is available + ExportScript.LoAircraftInfo(mainPanelDevice) -- Provides a lot of aircraft properties + ExportScript.AirportInfo(mainPanelDevice) -- Provides info on the two closest airports + ExportScript.WindsAloft(mainPanelDevice) -- Gets winds at the aircraft + ExportScript.GroundRadar(mainPanelDevice) -- Reports 2 closest friendlies and 2 enemies (Use in Single Player) + ExportScript.AirRadar(mainPanelDevice) -- Reports 2 closest friendlies and 2 enemies (Use in Single Player) + ExportScript.IglaHunter(mainPanelDevice) -- Locates closest Igla (Use in Single Player) + end + end end function ExportScript.ProcessDACConfigLowImportance(mainPanelDevice) @@ -329,3 +335,714 @@ end ----------------------------- -- Custom functions -- ----------------------------- + +function ExportScript.LoAircraftInfo(mainPanelDevice) + + -- General + local aircraftName = LoGetSelfData().Name -- DCS Name of the aircraft eg "F-5E-3" + local pilotName = LoGetPilotName() -- Logbook Pilot name + + -- Times DCS times are default in seconds + local dcsModelTime = LoGetModelTime() -- time since aircraft spawn + local missionStartTime = LoGetMissionStartTime() -- second after midnight that the mission started + local dcsTimeLocal = formatTime(LoGetMissionStartTime() + LoGetModelTime()) -- up-to-date time in dcs + local utcOffset = -1 * Terrain.GetTerrainConfig('SummerTimeDelta') * 3600 -- eg -1 * 4 * 3600 (for seconds to get hours) + local dcsTimeUtc = formatTime(dcsModelTime + LoGetMissionStartTime() + utcOffset) -- dcs zulu time + local realTimeLocal = os.date("%H-%M-%S") -- real life time + local realTimeUtc = os.date("!%H-%M-%S") -- real life zulu time + --local playTime = formatTime(DCS.getRealTime()) -- does not work, export environment no access + + -- Player Aircraft Properties + local altMsl_meters = LoGetAltitudeAboveSeaLevel() + local altMsl_feet = meters2feet(altMsl_meters) + local altAgl_meters = LoGetAltitudeAboveGroundLevel() + local altAgl_feet = meters2feet(altAgl_meters) + + local verticalVelocity_metric = LoGetVerticalVelocity() + local verticalVelocity_imperial = metersPerSecond2feetPerMinute(LoGetVerticalVelocity()) + + local ias_metric = LoGetIndicatedAirSpeed() + local ias_knots = metersPerSecond2knots(LoGetIndicatedAirSpeed()) + local ias_mph = metersPerSecond2milesPerHour(LoGetIndicatedAirSpeed()) + + local tas_metric = LoGetTrueAirSpeed() + local tas_knots = metersPerSecond2knots(LoGetTrueAirSpeed()) + local tas_mph = metersPerSecond2milesPerHour(LoGetTrueAirSpeed()) + + local speed_mach = LoGetMachNumber() + local accel_g = LoGetAccelerationUnits().y + local aoa = LoGetAngleOfAttack() + + --local atmosphericPressure_mmhg = LoGetBasicAtmospherePressure() -- does not seem to work + + local aircraftPitch, aircraftBank, aircraftYawTrue = LoGetADIPitchBankYaw() + aircraftPitch = aircraftPitch * 57.3 + aircraftBank = aircraftBank * 57.3 + aircraftYawTrue = aircraftYawTrue * 57.3 -- true heading + local aircraftYawMagnetic = LoGetMagneticYaw() * 57.3 -- magnetic heading + local aircraftHeading = aircraftYawMagnetic -- this cound be negative + if aircraftHeading < 0 then aircraftHeading = aircraftHeading + 360 end -- removes the negative + local magneticVariance = aircraftYawTrue - aircraftYawMagnetic -- works for all maps + + local selfData = LoGetSelfData() -- relative the the player + local lLatitude = selfData.LatLongAlt.Lat + local lLongitude = selfData.LatLongAlt.Long + local mgrs = Terrain.GetMGRScoordinates(LoGetSelfData().Position.x, LoGetSelfData().Position.z) + local mgrsTable = mgrsTableize(mgrs) -- format is mgrsTable[1][1], mgrsTable[1][2], mgrsTable[1][3], mgrsTable[1][4] + + local aircraftHeadingTrue = selfData.Heading * 57.3 -- true yeading (same as trueYaw for fixed wing aircraft) + + -- Engine Info + local engineInfo = LoGetEngineInfo() + local lEngineRPMleft = engineInfo.RPM.left -- ENG1 RPM % + local lEngineRPMright = engineInfo.RPM.right -- ENG2 RPM % + local lEngineFuelInternal = engineInfo.fuel_internal -- 1 = full. 0 = empty. Includes external tanks for FF aircraft + local lEngineFuelExternal = engineInfo.fuel_external -- TANK2 (EXT) (KG) -- does not seem to work for FF modules + local lEngineFuelTotal = lEngineFuelInternal + lEngineFuelExternal + local lEngineTempLeft = engineInfo.Temperature.left -- ENG1 EGT ºC. May get odd numbers + local lEngineTempRight = engineInfo.Temperature.right -- ENG2 EGT ºC. May get odd numbers + + local lFuelConsumptionLeft = engineInfo.FuelConsumption.left -- {left ,right},kg per sec + local lFuelConsumptionRight = engineInfo.FuelConsumption.right -- {left ,right},kg per sec + local lFuelConsumptionTotal = lFuelConsumptionLeft + lFuelConsumptionRight -- total,kg per sec + local lHydraulicPressureLeft = engineInfo.HydraulicPressure.left -- {left ,right},kg per square centimeter + local lHydraulicPressureRight = engineInfo.HydraulicPressure.right -- {left ,right},kg per square centimeter + + ExportScript.Tools.SendData(8000, aircraftName) + + ExportScript.Tools.SendData(8001, pilotName) + + ExportScript.Tools.SendData(8002, 'Real Time\n'.. realTimeLocal .. '\nDCS Time\n' .. dcsTimeLocal) -- clocks + + ExportScript.Tools.SendData(8003, 'HDG ' .. prefixZerosFixedLength(round(aircraftHeading,0),3) .. 'º' + .. '\nALT ' .. format_int(round(altMsl_feet,-1)) .. ' ft' + .. '\nIAS ' .. round(ias_knots,0) .. ' kts' + .. '\nV/S ' .. format_int(round(verticalVelocity_imperial,-2)) .. ' ft/min' + ) -- Aircraft Instrument panel (western) + + ExportScript.Tools.SendData(8004, 'HDG ' .. prefixZerosFixedLength(round(aircraftHeading,0),3) .. 'º' + .. '\nALT ' .. format_int(round(altMsl_meters,-1)) .. ' m' + .. '\nIAS ' .. round(ias_metric,0) .. ' km/h' + .. '\nV/S ' .. format_int(round(verticalVelocity_metric,0)) .. ' m/s' + ) -- Aircraft Instrument panel (eastern) + + ExportScript.Tools.SendData(8005, 'HDG ' .. prefixZerosFixedLength(round(aircraftHeading,0),3) .. 'º' + .. '\nALT ' .. format_int(round(altMsl_feet,-1)) .. ' ft' + .. '\nIAS ' .. round(ias_mph,0) .. ' mph' + .. '\nV/S ' .. format_int(round(verticalVelocity_imperial,-2)) .. ' ft/min' + ) -- Aircraft Instrument panel (western ww2) + + ExportScript.Tools.SendData(8006, "Lat-Long-DMS\n" .. formatCoord("DMS",true, lLatitude) + .. "\n" .. formatCoord("DMS",false, lLongitude) + ) -- Player coordinates in DMS + + ExportScript.Tools.SendData(8007, "Lat-Long-DDM\n" .. formatCoord("DDM",true, lLatitude) + .. "\n" .. formatCoord("DDM",false, lLongitude) + ) -- Player coordinates in DDM + + ExportScript.Tools.SendData(8008, 'MGRS\n'.. mgrsTable[1][1] .. ' ' .. mgrsTable[1][2] + .. '\n' .. mgrsTable[1][3] .. ' ' .. mgrsTable[1][4] + ) -- Player coordinates in MGRS on 2 rows + title + + ExportScript.Tools.SendData(8009, 'Mag Var\n' .. format_int(round(magneticVariance, 2))) -- also called magnetic deviation + + -- Example for using the Lo Data. Feel free to make your own! + ExportScript.Tools.SendData(8010, format_int(round(kgPerSecond2poundPerHour(lFuelConsumptionLeft), -1))) -- fuel use in pph + +end +function ExportScript.AirportInfo(mainPanelDevice) + + local airdromes = LoGetWorldObjects("airdromes") -- returns a list of runways and their popperties + local airportInfo = {} -- contains generated table of important properties + -- the table will be sorted by nearest airport first + -- for this table: + -- airportInfo[1] is the first element + -- airportInfo[1][1] is the airport name of the first element/airport + -- airportInfo[1][2] is the distance to the airport of the first element/airport + -- airportInfo[1][3] is the bearing to the airport of the first element/airport + -- airportInfo[1][4] is the extimated time en route + -- airportInfo[1][5] is the direction of the wind + -- airportInfo[1][6] is the windStrength of the wind + -- airportInfo[1][7] is the main runway heading + -- airportInfo[1][8] is the reverse of the main runway + -- airportInfo[1][9] is the prefered runway based on winds + + for key,value in pairs(airdromes) do + + -- remove the woRunWay entries so that only named runways are in the list + if value.Name ~= 'woRunWay' then + + -- get the distance from the player to the runway + local distance = getdistance(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, "nm") + + -- get the direction from the player to the runway + local bearing = getBearing(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, true) + + -- estimate the runway heading based on the reported values + local runwayHeading = round(value.Heading * 57.3,-1) / 10 + if runwayHeading < 0 then + runwayHeading = 36 + runwayHeading + end + -- Reverse it for the reciprocal runway + local runwayHeadingReciprocal + if runwayHeading > 18 then + runwayHeadingReciprocal = runwayHeading-18 + else + runwayHeadingReciprocal = runwayHeading+18 + end + + local ete = distance / metersPerSecond2knots(LoGetTrueAirSpeed()) * (60 * 60) --based on tas bc dcs is flat... + -- if ete is more than 24hrs, make it 24 hrs, which shows up as 00-00-00 + -- this case is for choppers and aircraft that arent moving + if ete > 86400 then ete = 86400 end + ete = formatTime(ete) + + -- wind at airport calculations. Each airport has slighty different winds + -- https://forum.dcs.world/topic/165136-logetwindatpoint-in-exportlua/#comment-3294428 + -- LoGetWindAtPoint(x,y,z,is_radio_alt), 2 meters off the ground for the "wind sensor" + local vx,_vy,vz,_absolute_height = LoGetWindAtPoint(value.Position.x,2,value.Position.y,true) + local windDirectionInRadians = math.atan2(vz,vx) + local windDirection = windDirectionInRadians * 57.3 + local windStrength = math.sqrt((vx)^2 + (vz)^2) + if windDirection < 0 then + windDirection = 360 + windDirection + end + -- Convert to direction to from direction + if windDirection > 180 then + windDirection = windDirection - 180 + else + windDirection = windDirection + 180 + end + + -- Calculate the prefered runway for landing + -- if the rounded runway is within +- 9 of the rounded wind, then it is prefered + local windRounded = round(windDirection, -1) + if windRounded >= runwayHeading - 9 and windRounded <= runwayHeading + 9 then + runwayHeadingPrefered = runwayHeading + else + runwayHeadingPrefered = runwayHeadingReciprocal + end + + -- Populate the table with the important info for each airport + table.insert(airportInfo, -- the table name + {value.Name, -- airport name [1] + distance, bearing, ete, --[2][3][4] + windDirection,windStrength, --wind direction [5], wind Strength [6] + runwayHeading, runwayHeadingReciprocal,runwayHeadingPrefered}) -- [7][8][9] + + end -- end of woRunWay + end -- end of FOR loop + + -- sort the table based on the second value, which is distance + -- https://stackoverflow.com/questions/51276613/how-to-sort-table-by-value-and-then-print-index-in-order + -- https://www.tutorialspoint.com/sort-function-in-lua-programming + table.sort(airportInfo, function(a,b) return a[2] < b[2] end) + + -- Primary Airport (closest) + ExportScript.Tools.SendData(8101, airportInfo[1][1] .. '\n' -- name of airport + --[[.. 'BRG ']] .. format_int(addZeros3(round(airportInfo[1][3],0))) .. 'º ' -- bearing + --[[.. 'DIST ']] .. format_int(round(airportInfo[1][2], 0)) .. 'nm\n' -- distance + .. 'ETE ' .. airportInfo[1][4] .. '\n' -- estimated time in route + .. '' .. prefixZerosFixedLength(round(airportInfo[1][5], 0),3) .. 'º ' -- wind bearing + .. round(metersPerSecond2knots(airportInfo[1][6]),0) .. 'kts' -- wing strength + .. '\n' .. prefixZerosFixedLength(airportInfo[1][7],2) -- runway 1 + .. '-' .. prefixZerosFixedLength(airportInfo[1][8],2) -- runway 2 + .. ' (' .. prefixZerosFixedLength(airportInfo[1][9],2) .. ')') -- prefered runway based on wind in parens + + -- Secondary Airport (second closest) + ExportScript.Tools.SendData(8102, airportInfo[2][1] .. '\n' -- name of airport + --[[.. 'BRG ']] .. format_int(addZeros3(round(airportInfo[2][3],0))) .. 'º ' -- bearing + --[[.. 'DIST ']] .. format_int(round(airportInfo[2][2], 0)) .. 'nm\n' -- distance + .. 'ETE ' .. airportInfo[2][4] .. '\n' -- estimated time in route + .. '' .. prefixZerosFixedLength(round(airportInfo[2][5], 0),3) .. 'º ' -- wind bearing + .. round(metersPerSecond2knots(airportInfo[2][6]),0) .. 'kts' -- wing strength + .. '\n' .. prefixZerosFixedLength(airportInfo[2][7],2) -- runway 1 + .. '-' .. prefixZerosFixedLength(airportInfo[2][8],2) -- runway 2 + .. ' (' .. prefixZerosFixedLength(airportInfo[2][9],2) .. ')') -- prefered runway based on wind in parens +end + +function ExportScript.WindsAloft(mainPanelDevice) + + -- Winds relative to the aircraft, aka, winds aloft + local windAloft = LoGetVectorWindVelocity() + local windStrengthAloft = math.sqrt((windAloft.x)^2 + (windAloft.z)^2) + local windDirectionAloft = math.deg(math.atan2(windAloft.z, windAloft.x)) + if windDirectionAloft < 0 then + windDirectionAloft = 360 + windDirectionAloft + end + + -- Convert to direction to from direction + if windDirectionAloft > 180 then + windDirectionAloft = windDirectionAloft - 180 + else + windDirectionAloft = windDirectionAloft + 180 + end + + ExportScript.Tools.SendData(8100, 'Wind Aloft\n' .. addZeros3(round(windDirectionAloft,0)) .. 'º ' + .. round(metersPerSecond2knots(windStrengthAloft,0)) .. 'kts' + ) -- winds at the aircraft +end +function ExportScript.GroundRadar(mainPanelDevice) -- may return some odd things + + local tableOfUnits = LoGetWorldObjects('units') + + local tableOfGround = {} + -- relative to the player... + local tableOfGround_friendly = {} + local tableOfGround_friendlyReports = {} + local tableOfGround_enemy = {} + local tableOfGround_enemyReports = {} + + for key,value in pairs(tableOfUnits) do + if value.Type.level1 == 2 then + table.insert(tableOfGround, value) + end + end + + local selfData = LoGetSelfData() + local selfCoalitionID = selfData.CoalitionID + + for key,value in pairs(tableOfGround) do + if value.CoalitionID == selfCoalitionID then + table.insert(tableOfGround_friendly, value) + else + table.insert(tableOfGround_enemy, value) + end + end + + -- TODO: only do enemy reports if there is an awacs unit(?) + for key,value in pairs(tableOfGround_enemy) do + local distance = getdistance(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, "nm") + + local bearing = getBearing(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, true) + + table.insert(tableOfGround_enemyReports, -- the table name + {value.Name, distance, bearing}) --[1][2][3] + end + table.sort(tableOfGround_enemyReports, function(a,b) return a[2] < b[2] end) -- sort based on the second value, which is distance + + + for key,value in pairs(tableOfGround_friendly) do + local distance = getdistance(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, "nm") + + local bearing = getBearing(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, true) + + table.insert(tableOfGround_friendlyReports, -- the table name + {value.Name, distance, bearing}) --[1][2][3] + + end + table.sort(tableOfGround_friendlyReports, function(a,b) return a[2] < b[2] end) -- sort based on the second value, which is distance + + local string_8200 = 'No Ground\nEnemy\nDetected' + if tableOfGround_enemyReports[1] ~= nill then + string_8200 = 'Enemy Ground\n' .. tableOfGround_enemyReports[1][1] + .. '\n ' .. prefixZerosFixedLength(tableOfGround_enemyReports[1][3],3) -- bearing + .. 'º ' .. round(tableOfGround_enemyReports[1][2],0) .. 'nm'--distance + end + + local string_8201 = 'No Ground\nEnemy\nDetected' + if tableOfGround_enemyReports[2] ~= nill then + string_8201 = 'Enemy Ground\n'.. tableOfGround_enemyReports[2][1] + .. '\n ' .. prefixZerosFixedLength(tableOfGround_enemyReports[2][3],3) -- bearing + .. 'º ' .. round(tableOfGround_enemyReports[2][2],0) .. 'nm'--distance + end + + local string_8202 = 'No Ground\nFriend\nDetected' + if tableOfGround_friendlyReports[1] ~= nill then + string_8202 = 'Friend Ground\n' .. tableOfGround_friendlyReports[1][1] + .. '\n ' .. prefixZerosFixedLength(tableOfGround_friendlyReports[1][3],3) -- bearing + .. 'º ' .. round(tableOfGround_friendlyReports[1][2],0) .. 'nm'--distance + end + + local string_8203 = 'No Ground\nFriend\nDetected' + if tableOfGround_friendlyReports[2] ~= nill then + string_8203 = 'Friend Ground\n' .. tableOfGround_friendlyReports[2][1] + .. '\n ' .. prefixZerosFixedLength(tableOfGround_friendlyReports[2][3],3) -- bearing + .. 'º ' .. round(tableOfGround_friendlyReports[2][2],0) .. 'nm'--distance + end + + ExportScript.Tools.SendData(8200, string_8200) + ExportScript.Tools.SendData(8201, string_8201) + ExportScript.Tools.SendData(8202, string_8202) + ExportScript.Tools.SendData(8203, string_8203) +end + +function ExportScript.AirRadar(mainPanelDevice) + + local tableOfUnits = LoGetWorldObjects('units') + + local tableOfAircraft = {} + -- relative to the player... + local tableOfAircraft_friendly = {} + local tableOfAircraft_friendlyReports = {} + local tableOfAircraft_enemy = {} + local tableOfAircraft_enemyReports = {} + + for key,value in pairs(tableOfUnits) do + if value.Type.level1 == 1 then + table.insert(tableOfAircraft, value) + end + end + + local selfData = LoGetSelfData() + local selfCoalitionID = selfData.CoalitionID + + for key,value in pairs(tableOfAircraft) do + if value.CoalitionID == selfCoalitionID then + table.insert(tableOfAircraft_friendly, value) + else + table.insert(tableOfAircraft_enemy, value) + end + end + + -- TODO: only do enemy reports if there is a awacs unit + for key,value in pairs(tableOfAircraft_enemy) do + local distance = getdistance(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, "nm") + + local bearing = getBearing(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, true) + + table.insert(tableOfAircraft_enemyReports, -- the table name + {value.Name, distance, bearing}) --[1][2][3] + + -- https://stackoverflow.com/questions/51276613/how-to-sort-table-by-value-and-then-print-index-in-order + -- https://www.tutorialspoint.com/sort-function-in-lua-programming + + end + table.sort(tableOfAircraft_enemyReports, function(a,b) return a[2] < b[2] end) -- sort based on the second value, which is distance + + + for key,value in pairs(tableOfAircraft_friendly) do -- [1] will always be the player + local distance = getdistance(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, "nm") + + local bearing = getBearing(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, true) + + table.insert(tableOfAircraft_friendlyReports, -- the table name + {value.Name, distance, bearing}) --[1][2][3] + + end + table.sort(tableOfAircraft_friendlyReports, function(a,b) return a[2] < b[2] end) -- sort based on the second value, which is distance + + local string_8210 = 'No Air\nEnemy\nDetected' + if tableOfAircraft_enemyReports[1] ~= nill then + string_8210 = 'Enemy Air\n' .. tableOfAircraft_enemyReports[1][1] + .. '\n ' .. prefixZerosFixedLength(tableOfAircraft_enemyReports[1][3],3) -- bearing + .. 'º ' .. round(tableOfAircraft_enemyReports[1][2],0) .. 'nm'--distance + end + + local string_8211 = 'No Air\nEnemy\nDetected' + if tableOfAircraft_enemyReports[2] ~= nill then + string_8211 = 'Enemy Air\n' .. tableOfAircraft_enemyReports[2][1] + .. '\n ' .. prefixZerosFixedLength(tableOfAircraft_enemyReports[2][3],3) -- bearing + .. 'º ' .. round(tableOfAircraft_enemyReports[2][2],0) .. 'nm'--distance + end + + local string_8212 = 'No Air\nFriend\nDetected' + if tableOfAircraft_friendlyReports[2] ~= nill then + string_8212 = 'Friend Air\n' .. tableOfAircraft_friendlyReports[2][1] + .. '\n ' .. prefixZerosFixedLength(tableOfAircraft_friendlyReports[2][3],3) -- bearing + .. 'º ' .. round(tableOfAircraft_friendlyReports[2][2],0) .. 'nm'--distance + end + + local string_8213 = 'No Air\nFriend\nDetected' + if tableOfAircraft_friendlyReports[3] ~= nill then + string_8213 = 'Friend Air\n' .. tableOfAircraft_friendlyReports[3][1] + .. '\n ' .. prefixZerosFixedLength(tableOfAircraft_friendlyReports[3][3],3) -- bearing + .. 'º ' .. round(tableOfAircraft_friendlyReports[3][2],0) .. 'nm'--distance + end + + ExportScript.Tools.SendData(8210,string_8210) + ExportScript.Tools.SendData(8211, string_8211) + ExportScript.Tools.SendData(8212, string_8212) + ExportScript.Tools.SendData(8213, string_8213) +end + +function ExportScript.IglaHunter(mainPanelDevice) -- Locates the nearest Igla + + local tableOfUnits = LoGetWorldObjects('units') + local selfData = LoGetSelfData() + local selfCoalitionID = selfData.CoalitionID + + local tableOfIgla = {} + local tableOfIgla_report = {} + + --TODO: Might have to refine this. + for key,value in pairs(tableOfUnits) do + if value.CoalitionID ~= selfCoalitionID then + if value.Type.level3 == 27 then + if value.Type.level2 == 16 then + if value.Type.level1 == 2 then + if value.Type.level4 == 55 or 54 or 53 or 52 or 62 then + table.insert(tableOfIgla, value) + end + end + end + end + end + end + + --if tableOfIgla ~= null then + for key,value in pairs(tableOfIgla) do + local distance = getdistance(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, "nm") + + local bearing = getBearing(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, true) + + table.insert(tableOfIgla_report, -- the table name + {value.Name, distance, bearing}) --[1][2][3] + --end + end + table.sort(tableOfIgla_report, function(a,b) return a[2] < b[2] end) -- sort based on the second value, which is distance + + local string_8666 = 'Igla Hunter\nSearching...' + if tableOfIgla_report[1] ~= nill then + string_8666 = 'Igla Detected\n' .. tableOfIgla_report[1][1] + .. '\n ' .. prefixZerosFixedLength(tableOfIgla_report[1][3],3) -- bearing + .. 'º ' .. round(tableOfIgla_report[1][2],0) .. ' nm'--distance + end + + ExportScript.Tools.SendData(8666, string_8666) + +end + +---------------------- +-- Helper Functions -- +---------------------- +function ExportScript.Linearize(current_value, raw_tab, final_tab) + -- (c) scoobie + if current_value <= raw_tab[1] then + return final_tab[1] + end + for index, value in pairs(raw_tab) do + if current_value <= value then + local ft = final_tab[index] + local rt = raw_tab[index] + return (current_value - rt) * (ft - final_tab[index - 1]) / (rt - raw_tab[index - 1]) + ft + end + end + -- we shouldn't be here, so something went wrong - return arbitrary max. final value, maybe the user will notice the problem: + return final_tab[#final_tab] +end + +function trim(s) --http://lua-users.org/wiki/CommonFunctions + -- from PiL2 20.4 + return (s:gsub("^%s*(.-)%s*$", "%1")) +end +function formatTime(time) + local seconds = math.floor(time) % 60 + local minutes = math.floor(time / 60) % 60 + local hours = math.floor(time / (60 * 60)) % 24 + return string.format("%02d", hours) .. "-" .. string.format("%02d", minutes) .. "-" .. string.format("%02d", seconds) +end + +function meters2feet(meters) + local feet = meters * 3.281 + return feet +end + +function feet2meters(feet) + local meters = feet / 3.281 + return feet +end + +function metersPerSecond2milesPerHour(metersPerSecond) + local milesPerHour = metersPerSecond * 2.237 + return milesPerHour +end + +function metersPerSecond2knots(metersPerSecond) + local knots = metersPerSecond * 1.944 + return knots +end + +function kgPerSecond2poundPerHour(kgPerSecond) + poundPerHour = kgPerSecond * 7937 + return poundPerHour +end + +function metersPerSecond2feetPerMinute(metersPerSecond) + local feetPerMinute = metersPerSecond * 197 + return feetPerMinute +end + +function round(num, numDecimalPlaces) --http://lua-users.org/wiki/SimpleRound + local mult = 10^(numDecimalPlaces or 0) + return math.floor(num * mult + 0.5) / mult +end + +function format_int(number) --https://stackoverflow.com/questions/10989788/format-integer-in-lua + local i, j, minus, int, fraction = tostring(number):find('([-]?)(%d+)([.]?%d*)') + -- reverse the int-string and append a comma to all blocks of 3 digits + int = int:reverse():gsub("(%d%d%d)", "%1,") + -- reverse the int-string back remove an optional comma and put the + -- optional minus and fractional part back + return minus .. int:reverse():gsub("^,", "") .. fraction +end + +function formatCoord(type, isLat, d) + local h + if isLat then + if d < 0 then + h = 'S' + d = -d + else + h = 'N' + end + else + if d < 0 then + h = 'W' + d = -d + else + h = 'E' + end + end + + local g = math.floor(d) + local m = math.floor(d * 60 - g * 60) + local s = d * 3600 - g * 3600 - m * 60 + + if type == "DMS" then -- Degree Minutes Seconds + s = math.floor(s * 100) / 100 + return string.format('%s %2d°%.2d\'%05.2f"', h, g, m, s) + elseif type == "DDM" then -- Degree Decimal Minutes + s = math.floor(s / 60 * 1000) + return string.format('%s %2d°%02d.%3.3d\'', h, g, m, s) + else -- Decimal Degrees + return string.format('%f',d) + end +end + +function getdistance(lat1,lat2,lon1,lon2,unit) -- https://www.geeksforgeeks.org/program-distance-two-points-earth/ + --Example Locations + --lat1 = 42.1578 -- POTI + --lat2 = 42.3269 -- HONI + --lon1 = 41.6777 + --lon2 = 42.4122 + + local lon1 = toRadians(lon1) + local lon2 = toRadians(lon2) + local lat1 = toRadians(lat1) + local lat2 = toRadians(lat2) + + -- Haversine formula + local dlon = lon2 - lon1 + local dlat = lat2 - lat1 + local a = math.pow(math.sin(dlat / 2), 2) + + math.cos(lat1) * math.cos(lat2) * + math.pow(math.sin(dlon / 2),2) + + local c = 2 * math.asin(math.sqrt(a)) + + local r -- Radius of earth in X. + if unit == 'nm' then + r = 6371 / 1.852 -- times 1.852 because I could not find a good NM source + elseif unit == 'km' then + r = 6371 -- Use 6371 for kilometers + elseif unit == 'miles' then + r = 3956 -- Use 3956 for miles + elseif unit == 'meters' then + r = 6371 * 1000 + end + + -- calculate the result + return (c * r) +end + +function toRadians(angleIn10thofaDegree) + return (angleIn10thofaDegree * math.pi) / 180 +end + + +function getBearing(lat1,lat2,lon1,lon2, magnetic) + local bearing_rad = math.atan2(lon2 - lon1, lat2 - lat1) + if bearing_rad < 0 then + bearing_rad = bearing_rad + (2 * math.pi) + end + + bearing = math.deg(bearing_rad) + + -- start calculation for getting the magnetic bar + local _aircraftPitch, _aircraftBank, aircraftYawTrue = LoGetADIPitchBankYaw() + aircraftYawTrue = aircraftYawTrue * 57.3 -- actually heading + local aircraftYawMagnetic = LoGetMagneticYaw() * 57.3 + local magneticVariance = aircraftYawTrue - aircraftYawMagnetic + + if magnetic == true then + bearing = bearing - magneticVariance + end + + -- correction for bearings less than 0 due to the calculation above + if bearing < 0 then + bearing = bearing + 360 + end + + return bearing +end + +function addZeros3(number) + number = string.format("%.1d" , number) + if #number == 2 then + number = "0" .. number + elseif #number == 1 then + number = "00" .. number + end + return number +end + +function prefixZerosFixedLength(number, digitLength) -- prefixZerosFixedLength(99, 3) --> 099 + number = string.format("%.1d" , number) -- make the number a string + local zerosToAdd = digitLength - #number + s = '' + for i = 1, zerosToAdd do + s = s .. 0 + end + return s .. number +end + +function mgrsTableize(mgrsString) + -- Reference: https://upload.wikimedia.org/wikipedia/commons/b/b7/Universal_Transverse_Mercator_zones.svg + -- example: 38 T LM 12345 54321 + -- (\d+\s\w)\s(\w+)\s(.+)\s(.+) --c# version of regex + -- UTMZone = string, + -- MGRSDigraph = string, + -- Easting = number, + -- Northing = number + local UTMZone , MGRSDigraph, Easting, Northing = mgrsString:match('(%d+%s%w)%s(%w+)%s(.+)%s(.+)') + local mgrsTbl = {} + table.insert(mgrsTbl, {UTMZone,MGRSDigraph,Easting,Northing}) + return mgrsTbl +end diff --git a/Scripts/DCS-ExportScript/ExportsModules/UH-1H.lua b/Scripts/DCS-ExportScript/ExportsModules/UH-1H.lua index a6644f7..d9964e6 100644 --- a/Scripts/DCS-ExportScript/ExportsModules/UH-1H.lua +++ b/Scripts/DCS-ExportScript/ExportsModules/UH-1H.lua @@ -1,4 +1,9 @@ -- Uh-1H +-- https://github.com/asherao/DCS-ExportScripts + +local base = _G -- game information +local os = base.os -- time +local Terrain = require('terrain') -- map info ExportScript.FoundDCSModule = true ExportScript.Version.UH1H = "1.2.1" @@ -623,6 +628,21 @@ function ExportScript.ProcessIkarusDCSConfigLowImportance(mainPanelDevice) end ExportScript.Tools.SendData(2007, string.format("%s", lADF_ARN83)) ]] + + ExportScript.UhfRadioPresets(mainPanelDevice) -- AN/ARC-164 UHF Preset List + ExportScript.CrewStatusRepeater(mainPanelDevice) -- Crew Status Window + ExportScript.RadioFreqs(mainPanelDevice) + + if LoIsObjectExportAllowed() then -- returns true if world objects data is available + if LoIsOwnshipExportAllowed() then -- returns true if ownship data is available + ExportScript.LoAircraftInfo(mainPanelDevice) -- Provides a lot of aircraft properties + ExportScript.AirportInfo(mainPanelDevice) -- Provides info on the two closest airports + ExportScript.WindsAloft(mainPanelDevice) -- Gets winds at the aircraft + ExportScript.GroundRadar(mainPanelDevice) -- Reports 2 closest friendlies and 2 enemies (Use in Single Player) + ExportScript.AirRadar(mainPanelDevice) -- Reports 2 closest friendlies and 2 enemies (Use in Single Player) + ExportScript.IglaHunter(mainPanelDevice) -- Locates closest Igla (Use in Single Player) + end + end end function ExportScript.ProcessDACConfigLowImportance(mainPanelDevice) @@ -904,4 +924,849 @@ function ExportScript.ProcessDACConfigLowImportance(mainPanelDevice) ExportScript.Tools.WriteToLog(ltmp2..' (metatable): '..ExportScript.Tools.dump(getmetatable(ltmp1))) end ]] -end \ No newline at end of file +end + +---------------------- +-- Custom functions -- +---------------------- +function ExportScript.CrewStatusRepeater(mainPanelDevice) -- Crew Status Window + + local crewStatusInfo = parse_indication(6) -- contains table of the status of the crew + + if crewStatusInfo.txt_mem0 ~= null then -- if there is a pilot entry + ExportScript.Tools.SendData(3000,'Pilot\n' .. crewStatusInfo.txt_status0 + .. '\nAmmo ' .. trim(crewStatusInfo.txt_ammo0) + .. '\nBurst ' .. trim(crewStatusInfo.txt_burst0)) + else + ExportScript.Tools.SendData(3000,'Pilot\n' .. '--\n'.. '--\n'.. '--') + end + + if crewStatusInfo.txt_mem1 ~= null then -- if there is a copilot entry + ExportScript.Tools.SendData(3001,'Co-pilot\nROE ' .. crewStatusInfo.txt_status1 + .. '\nAmmo ' .. trim(crewStatusInfo.txt_ammo1) + .. '\nBurst ' .. trim(crewStatusInfo.txt_burst1)) + else + ExportScript.Tools.SendData(3001,'Co-pilot\n' .. '--\n'.. '--\n'.. '--') + end + + if crewStatusInfo.txt_mem2 ~= null then -- if there is a LH Gunner entry + ExportScript.Tools.SendData(3002,'LH Gunner\nROE ' .. crewStatusInfo.txt_status2 + .. '\nAmmo ' .. trim(crewStatusInfo.txt_ammo2) + .. '\nBurst ' .. trim(crewStatusInfo.txt_burst2)) + else + ExportScript.Tools.SendData(3002,'LH Gunner\n' .. '--\n'.. '--\n'.. '--') + end + + if crewStatusInfo.txt_mem3 ~= null then -- if there is a RH Gunner entry + ExportScript.Tools.SendData(3003,'RH Gunner\nROE ' .. crewStatusInfo.txt_status3 + .. '\nAmmo ' .. trim(crewStatusInfo.txt_ammo3) + .. '\nBurst ' .. trim(crewStatusInfo.txt_burst3)) + else + ExportScript.Tools.SendData(3003,'RH Gunner\n' .. '--\n'.. '--\n'.. '--') + end +end + +function ExportScript.UhfRadioPresets(mainPanelDevice) -- AN/ARC-164 UHF Preset List + + local UhfPresetReadout = ExportScript.Tools.getListIndicatorValue(7) -- uhf radio presets + + local UhfCh1 = UhfPresetReadout.SheetTable_Channel1 + local UhfCh2 = UhfPresetReadout.SheetTable_Channel2 + local UhfCh3 = UhfPresetReadout.SheetTable_Channel3 + local UhfCh4 = UhfPresetReadout.SheetTable_Channel4 + local UhfCh5= UhfPresetReadout.SheetTable_Channel5 + local UhfCh6 = UhfPresetReadout.SheetTable_Channel6 + local UhfCh7 = UhfPresetReadout.SheetTable_Channel7 + local UhfCh8 = UhfPresetReadout.SheetTable_Channel8 + local UhfCh9 = UhfPresetReadout.SheetTable_Channel9 + local UhfCh10 = UhfPresetReadout.SheetTable_Channel10 + local UhfCh11 = UhfPresetReadout.SheetTable_Channel11 + local UhfCh12 = UhfPresetReadout.SheetTable_Channel12 + local UhfCh13 = UhfPresetReadout.SheetTable_Channel13 + local UhfCh14 = UhfPresetReadout.SheetTable_Channel14 + local UhfCh15 = UhfPresetReadout.SheetTable_Channel15 + local UhfCh16 = UhfPresetReadout.SheetTable_Channel16 + local UhfCh17 = UhfPresetReadout.SheetTable_Channel17 + local UhfCh18 = UhfPresetReadout.SheetTable_Channel18 + local UhfCh19 = UhfPresetReadout.SheetTable_Channel19 + local UhfCh20 = UhfPresetReadout.SheetTable_Channel20 + + + ExportScript.Tools.SendData(3016, "UHF 1-4" + .. "\n" .. UhfCh1 + .. "\n" .. UhfCh2 + .. "\n" .. UhfCh3 + .. "\n" .. UhfCh4 + ) + + ExportScript.Tools.SendData(3017, "UHF 5-8" + .. "\n" .. UhfCh5 + .. "\n" .. UhfCh6 + .. "\n" .. UhfCh7 + .. "\n" .. UhfCh8 + ) + + + ExportScript.Tools.SendData(3018, "UHF 9-12" + .. "\n" .. UhfCh9 + .. "\n" .. UhfCh10 + .. "\n" .. UhfCh11 + .. "\n" .. UhfCh12 + ) + + ExportScript.Tools.SendData(3019, "UHF 13-16" + .. "\n" .. UhfCh13 + .. "\n" .. UhfCh14 + .. "\n" .. UhfCh15 + .. "\n" .. UhfCh16 + ) + + ExportScript.Tools.SendData(3020, "UHF 16-20" + .. "\n" .. UhfCh17 + .. "\n" .. UhfCh18 + .. "\n" .. UhfCh19 + .. "\n" .. UhfCh20 + ) + +end + +function ExportScript.RadioFreqs(mainPanelDevice) -- TODO: More radios + local radoioVhf = ExportScript.Tools.RoundFreqeuncy(GetDevice(20):get_frequency()/1000000) -- "116.000" + local radioUhf = ExportScript.Tools.RoundFreqeuncy(GetDevice(22):get_frequency()/1000000) -- "251.000" + local radioHf = ExportScript.Tools.RoundFreqeuncy(GetDevice(23):get_frequency()/1000000) -- " 30.000" + + ExportScript.Tools.SendData(3010, "VHF\n" .. radoioVhf .. ' MHz') + ExportScript.Tools.SendData(3011, "UHF\n" .. radioUhf .. ' MHz') + ExportScript.Tools.SendData(3012, "HF\n" .. radioHf .. ' MHz') + ExportScript.Tools.SendData(3013, "Radio Stack\n" .. radoioVhf .. ' MHz\n' + .. radioUhf .. ' MHz\n' .. radioHf .. ' MHz') +end + +function ExportScript.LoAircraftInfo(mainPanelDevice) + + -- General + local aircraftName = LoGetSelfData().Name -- DCS Name of the aircraft eg "F-5E-3" + local pilotName = LoGetPilotName() -- Logbook Pilot name + + -- Times DCS times are default in seconds + local dcsModelTime = LoGetModelTime() -- time since aircraft spawn + local missionStartTime = LoGetMissionStartTime() -- second after midnight that the mission started + local dcsTimeLocal = formatTime(LoGetMissionStartTime() + LoGetModelTime()) -- up-to-date time in dcs + local utcOffset = -1 * Terrain.GetTerrainConfig('SummerTimeDelta') * 3600 -- eg -1 * 4 * 3600 (for seconds to get hours) + local dcsTimeUtc = formatTime(dcsModelTime + LoGetMissionStartTime() + utcOffset) -- dcs zulu time + local realTimeLocal = os.date("%H-%M-%S") -- real life time + local realTimeUtc = os.date("!%H-%M-%S") -- real life zulu time + --local playTime = formatTime(DCS.getRealTime()) -- does not work, export environment no access + + -- Player Aircraft Properties + local altMsl_meters = LoGetAltitudeAboveSeaLevel() + local altMsl_feet = meters2feet(altMsl_meters) + local altAgl_meters = LoGetAltitudeAboveGroundLevel() + local altAgl_feet = meters2feet(altAgl_meters) + + local verticalVelocity_metric = LoGetVerticalVelocity() + local verticalVelocity_imperial = metersPerSecond2feetPerMinute(LoGetVerticalVelocity()) + + local ias_metric = LoGetIndicatedAirSpeed() + local ias_knots = metersPerSecond2knots(LoGetIndicatedAirSpeed()) + local ias_mph = metersPerSecond2milesPerHour(LoGetIndicatedAirSpeed()) + + local tas_metric = LoGetTrueAirSpeed() + local tas_knots = metersPerSecond2knots(LoGetTrueAirSpeed()) + local tas_mph = metersPerSecond2milesPerHour(LoGetTrueAirSpeed()) + + local speed_mach = LoGetMachNumber() + local accel_g = LoGetAccelerationUnits().y + local aoa = LoGetAngleOfAttack() + + --local atmosphericPressure_mmhg = LoGetBasicAtmospherePressure() -- does not seem to work + + local aircraftPitch, aircraftBank, aircraftYawTrue = LoGetADIPitchBankYaw() + aircraftPitch = aircraftPitch * 57.3 + aircraftBank = aircraftBank * 57.3 + aircraftYawTrue = aircraftYawTrue * 57.3 -- true heading + local aircraftYawMagnetic = LoGetMagneticYaw() * 57.3 -- magnetic heading + local aircraftHeading = aircraftYawMagnetic -- this cound be negative + if aircraftHeading < 0 then aircraftHeading = aircraftHeading + 360 end -- removes the negative + local magneticVariance = aircraftYawTrue - aircraftYawMagnetic -- works for all maps + + local selfData = LoGetSelfData() -- relative the the player + local lLatitude = selfData.LatLongAlt.Lat + local lLongitude = selfData.LatLongAlt.Long + local mgrs = Terrain.GetMGRScoordinates(LoGetSelfData().Position.x, LoGetSelfData().Position.z) + local mgrsTable = mgrsTableize(mgrs) -- format is mgrsTable[1][1], mgrsTable[1][2], mgrsTable[1][3], mgrsTable[1][4] + + local aircraftHeadingTrue = selfData.Heading * 57.3 -- true yeading (same as trueYaw for fixed wing aircraft) + + -- Engine Info + local engineInfo = LoGetEngineInfo() + local lEngineRPMleft = engineInfo.RPM.left -- ENG1 RPM % + local lEngineRPMright = engineInfo.RPM.right -- ENG2 RPM % + local lEngineFuelInternal = engineInfo.fuel_internal -- 1 = full. 0 = empty. Includes external tanks for FF aircraft + local lEngineFuelExternal = engineInfo.fuel_external -- TANK2 (EXT) (KG) -- does not seem to work for FF modules + local lEngineFuelTotal = lEngineFuelInternal + lEngineFuelExternal + local lEngineTempLeft = engineInfo.Temperature.left -- ENG1 EGT ºC. May get odd numbers + local lEngineTempRight = engineInfo.Temperature.right -- ENG2 EGT ºC. May get odd numbers + + local lFuelConsumptionLeft = engineInfo.FuelConsumption.left -- {left ,right},kg per sec + local lFuelConsumptionRight = engineInfo.FuelConsumption.right -- {left ,right},kg per sec + local lFuelConsumptionTotal = lFuelConsumptionLeft + lFuelConsumptionRight -- total,kg per sec + local lHydraulicPressureLeft = engineInfo.HydraulicPressure.left -- {left ,right},kg per square centimeter + local lHydraulicPressureRight = engineInfo.HydraulicPressure.right -- {left ,right},kg per square centimeter + + ExportScript.Tools.SendData(8000, aircraftName) + + ExportScript.Tools.SendData(8001, pilotName) + + ExportScript.Tools.SendData(8002, 'Real Time\n'.. realTimeLocal .. '\nDCS Time\n' .. dcsTimeLocal) -- clocks + + ExportScript.Tools.SendData(8003, 'HDG ' .. prefixZerosFixedLength(round(aircraftHeading,0),3) .. 'º' + .. '\nALT ' .. format_int(round(altMsl_feet,-1)) .. ' ft' + .. '\nIAS ' .. round(ias_knots,0) .. ' kts' + .. '\nV/S ' .. format_int(round(verticalVelocity_imperial,-2)) .. ' ft/min' + ) -- Aircraft Instrument panel (western) + + ExportScript.Tools.SendData(8004, 'HDG ' .. prefixZerosFixedLength(round(aircraftHeading,0),3) .. 'º' + .. '\nALT ' .. format_int(round(altMsl_meters,-1)) .. ' m' + .. '\nIAS ' .. round(ias_metric,0) .. ' km/h' + .. '\nV/S ' .. format_int(round(verticalVelocity_metric,0)) .. ' m/s' + ) -- Aircraft Instrument panel (eastern) + + ExportScript.Tools.SendData(8005, 'HDG ' .. prefixZerosFixedLength(round(aircraftHeading,0),3) .. 'º' + .. '\nALT ' .. format_int(round(altMsl_feet,-1)) .. ' ft' + .. '\nIAS ' .. round(ias_mph,0) .. ' mph' + .. '\nV/S ' .. format_int(round(verticalVelocity_imperial,-2)) .. ' ft/min' + ) -- Aircraft Instrument panel (western ww2) + + ExportScript.Tools.SendData(8006, "Lat-Long-DMS\n" .. formatCoord("DMS",true, lLatitude) + .. "\n" .. formatCoord("DMS",false, lLongitude) + ) -- Player coordinates in DMS + + ExportScript.Tools.SendData(8007, "Lat-Long-DDM\n" .. formatCoord("DDM",true, lLatitude) + .. "\n" .. formatCoord("DDM",false, lLongitude) + ) -- Player coordinates in DDM + + ExportScript.Tools.SendData(8008, 'MGRS\n'.. mgrsTable[1][1] .. ' ' .. mgrsTable[1][2] + .. '\n' .. mgrsTable[1][3] .. ' ' .. mgrsTable[1][4] + ) -- Player coordinates in MGRS on 2 rows + title + + ExportScript.Tools.SendData(8009, 'Mag Var\n' .. format_int(round(magneticVariance, 2))) -- also called magnetic deviation + + -- Example for using the Lo Data. Feel free to make your own! + ExportScript.Tools.SendData(8010, format_int(round(kgPerSecond2poundPerHour(lFuelConsumptionLeft), -1))) -- fuel use in pph + +end +function ExportScript.AirportInfo(mainPanelDevice) + + local airdromes = LoGetWorldObjects("airdromes") -- returns a list of runways and their popperties + local airportInfo = {} -- contains generated table of important properties + -- the table will be sorted by nearest airport first + -- for this table: + -- airportInfo[1] is the first element + -- airportInfo[1][1] is the airport name of the first element/airport + -- airportInfo[1][2] is the distance to the airport of the first element/airport + -- airportInfo[1][3] is the bearing to the airport of the first element/airport + -- airportInfo[1][4] is the extimated time en route + -- airportInfo[1][5] is the direction of the wind + -- airportInfo[1][6] is the windStrength of the wind + -- airportInfo[1][7] is the main runway heading + -- airportInfo[1][8] is the reverse of the main runway + -- airportInfo[1][9] is the prefered runway based on winds + + for key,value in pairs(airdromes) do + + -- remove the woRunWay entries so that only named runways are in the list + if value.Name ~= 'woRunWay' then + + -- get the distance from the player to the runway + local distance = getdistance(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, "nm") + + -- get the direction from the player to the runway + local bearing = getBearing(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, true) + + -- estimate the runway heading based on the reported values + local runwayHeading = round(value.Heading * 57.3,-1) / 10 + if runwayHeading < 0 then + runwayHeading = 36 + runwayHeading + end + -- Reverse it for the reciprocal runway + local runwayHeadingReciprocal + if runwayHeading > 18 then + runwayHeadingReciprocal = runwayHeading-18 + else + runwayHeadingReciprocal = runwayHeading+18 + end + + local ete = distance / metersPerSecond2knots(LoGetTrueAirSpeed()) * (60 * 60) --based on tas bc dcs is flat... + -- if ete is more than 24hrs, make it 24 hrs, which shows up as 00-00-00 + -- this case is for choppers and aircraft that arent moving + if ete > 86400 then ete = 86400 end + ete = formatTime(ete) + + -- wind at airport calculations. Each airport has slighty different winds + -- https://forum.dcs.world/topic/165136-logetwindatpoint-in-exportlua/#comment-3294428 + -- LoGetWindAtPoint(x,y,z,is_radio_alt), 2 meters off the ground for the "wind sensor" + local vx,_vy,vz,_absolute_height = LoGetWindAtPoint(value.Position.x,2,value.Position.y,true) + local windDirectionInRadians = math.atan2(vz,vx) + local windDirection = windDirectionInRadians * 57.3 + local windStrength = math.sqrt((vx)^2 + (vz)^2) + if windDirection < 0 then + windDirection = 360 + windDirection + end + -- Convert to direction to from direction + if windDirection > 180 then + windDirection = windDirection - 180 + else + windDirection = windDirection + 180 + end + + -- Calculate the prefered runway for landing + -- if the rounded runway is within +- 9 of the rounded wind, then it is prefered + local windRounded = round(windDirection, -1) + if windRounded >= runwayHeading - 9 and windRounded <= runwayHeading + 9 then + runwayHeadingPrefered = runwayHeading + else + runwayHeadingPrefered = runwayHeadingReciprocal + end + + -- Populate the table with the important info for each airport + table.insert(airportInfo, -- the table name + {value.Name, -- airport name [1] + distance, bearing, ete, --[2][3][4] + windDirection,windStrength, --wind direction [5], wind Strength [6] + runwayHeading, runwayHeadingReciprocal,runwayHeadingPrefered}) -- [7][8][9] + + end -- end of woRunWay + end -- end of FOR loop + + -- sort the table based on the second value, which is distance + -- https://stackoverflow.com/questions/51276613/how-to-sort-table-by-value-and-then-print-index-in-order + -- https://www.tutorialspoint.com/sort-function-in-lua-programming + table.sort(airportInfo, function(a,b) return a[2] < b[2] end) + + -- Primary Airport (closest) + ExportScript.Tools.SendData(8101, airportInfo[1][1] .. '\n' -- name of airport + --[[.. 'BRG ']] .. format_int(addZeros3(round(airportInfo[1][3],0))) .. 'º ' -- bearing + --[[.. 'DIST ']] .. format_int(round(airportInfo[1][2], 0)) .. 'nm\n' -- distance + .. 'ETE ' .. airportInfo[1][4] .. '\n' -- estimated time in route + .. '' .. prefixZerosFixedLength(round(airportInfo[1][5], 0),3) .. 'º ' -- wind bearing + .. round(metersPerSecond2knots(airportInfo[1][6]),0) .. 'kts' -- wing strength + .. '\n' .. prefixZerosFixedLength(airportInfo[1][7],2) -- runway 1 + .. '-' .. prefixZerosFixedLength(airportInfo[1][8],2) -- runway 2 + .. ' (' .. prefixZerosFixedLength(airportInfo[1][9],2) .. ')') -- prefered runway based on wind in parens + + -- Secondary Airport (second closest) + ExportScript.Tools.SendData(8102, airportInfo[2][1] .. '\n' -- name of airport + --[[.. 'BRG ']] .. format_int(addZeros3(round(airportInfo[2][3],0))) .. 'º ' -- bearing + --[[.. 'DIST ']] .. format_int(round(airportInfo[2][2], 0)) .. 'nm\n' -- distance + .. 'ETE ' .. airportInfo[2][4] .. '\n' -- estimated time in route + .. '' .. prefixZerosFixedLength(round(airportInfo[2][5], 0),3) .. 'º ' -- wind bearing + .. round(metersPerSecond2knots(airportInfo[2][6]),0) .. 'kts' -- wing strength + .. '\n' .. prefixZerosFixedLength(airportInfo[2][7],2) -- runway 1 + .. '-' .. prefixZerosFixedLength(airportInfo[2][8],2) -- runway 2 + .. ' (' .. prefixZerosFixedLength(airportInfo[2][9],2) .. ')') -- prefered runway based on wind in parens +end + +function ExportScript.WindsAloft(mainPanelDevice) + + -- Winds relative to the aircraft, aka, winds aloft + local windAloft = LoGetVectorWindVelocity() + local windStrengthAloft = math.sqrt((windAloft.x)^2 + (windAloft.z)^2) + local windDirectionAloft = math.deg(math.atan2(windAloft.z, windAloft.x)) + if windDirectionAloft < 0 then + windDirectionAloft = 360 + windDirectionAloft + end + + -- Convert to direction to from direction + if windDirectionAloft > 180 then + windDirectionAloft = windDirectionAloft - 180 + else + windDirectionAloft = windDirectionAloft + 180 + end + + ExportScript.Tools.SendData(8100, 'Wind Aloft\n' .. addZeros3(round(windDirectionAloft,0)) .. 'º ' + .. round(metersPerSecond2knots(windStrengthAloft,0)) .. 'kts' + ) -- winds at the aircraft +end +function ExportScript.GroundRadar(mainPanelDevice) -- may return some odd things + + local tableOfUnits = LoGetWorldObjects('units') + + local tableOfGround = {} + -- relative to the player... + local tableOfGround_friendly = {} + local tableOfGround_friendlyReports = {} + local tableOfGround_enemy = {} + local tableOfGround_enemyReports = {} + + for key,value in pairs(tableOfUnits) do + if value.Type.level1 == 2 then + table.insert(tableOfGround, value) + end + end + + local selfData = LoGetSelfData() + local selfCoalitionID = selfData.CoalitionID + + for key,value in pairs(tableOfGround) do + if value.CoalitionID == selfCoalitionID then + table.insert(tableOfGround_friendly, value) + else + table.insert(tableOfGround_enemy, value) + end + end + + -- TODO: only do enemy reports if there is an awacs unit(?) + for key,value in pairs(tableOfGround_enemy) do + local distance = getdistance(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, "nm") + + local bearing = getBearing(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, true) + + table.insert(tableOfGround_enemyReports, -- the table name + {value.Name, distance, bearing}) --[1][2][3] + end + table.sort(tableOfGround_enemyReports, function(a,b) return a[2] < b[2] end) -- sort based on the second value, which is distance + + + for key,value in pairs(tableOfGround_friendly) do + local distance = getdistance(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, "nm") + + local bearing = getBearing(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, true) + + table.insert(tableOfGround_friendlyReports, -- the table name + {value.Name, distance, bearing}) --[1][2][3] + + end + table.sort(tableOfGround_friendlyReports, function(a,b) return a[2] < b[2] end) -- sort based on the second value, which is distance + + local string_8200 = 'No Ground\nEnemy\nDetected' + if tableOfGround_enemyReports[1] ~= nill then + string_8200 = 'Enemy Ground\n' .. tableOfGround_enemyReports[1][1] + .. '\n ' .. prefixZerosFixedLength(tableOfGround_enemyReports[1][3],3) -- bearing + .. 'º ' .. round(tableOfGround_enemyReports[1][2],0) .. 'nm'--distance + end + + local string_8201 = 'No Ground\nEnemy\nDetected' + if tableOfGround_enemyReports[2] ~= nill then + string_8201 = 'Enemy Ground\n'.. tableOfGround_enemyReports[2][1] + .. '\n ' .. prefixZerosFixedLength(tableOfGround_enemyReports[2][3],3) -- bearing + .. 'º ' .. round(tableOfGround_enemyReports[2][2],0) .. 'nm'--distance + end + + local string_8202 = 'No Ground\nFriend\nDetected' + if tableOfGround_friendlyReports[1] ~= nill then + string_8202 = 'Friend Ground\n' .. tableOfGround_friendlyReports[1][1] + .. '\n ' .. prefixZerosFixedLength(tableOfGround_friendlyReports[1][3],3) -- bearing + .. 'º ' .. round(tableOfGround_friendlyReports[1][2],0) .. 'nm'--distance + end + + local string_8203 = 'No Ground\nFriend\nDetected' + if tableOfGround_friendlyReports[2] ~= nill then + string_8203 = 'Friend Ground\n' .. tableOfGround_friendlyReports[2][1] + .. '\n ' .. prefixZerosFixedLength(tableOfGround_friendlyReports[2][3],3) -- bearing + .. 'º ' .. round(tableOfGround_friendlyReports[2][2],0) .. 'nm'--distance + end + + ExportScript.Tools.SendData(8200, string_8200) + ExportScript.Tools.SendData(8201, string_8201) + ExportScript.Tools.SendData(8202, string_8202) + ExportScript.Tools.SendData(8203, string_8203) +end + +function ExportScript.AirRadar(mainPanelDevice) + + local tableOfUnits = LoGetWorldObjects('units') + + local tableOfAircraft = {} + -- relative to the player... + local tableOfAircraft_friendly = {} + local tableOfAircraft_friendlyReports = {} + local tableOfAircraft_enemy = {} + local tableOfAircraft_enemyReports = {} + + for key,value in pairs(tableOfUnits) do + if value.Type.level1 == 1 then + table.insert(tableOfAircraft, value) + end + end + + local selfData = LoGetSelfData() + local selfCoalitionID = selfData.CoalitionID + + for key,value in pairs(tableOfAircraft) do + if value.CoalitionID == selfCoalitionID then + table.insert(tableOfAircraft_friendly, value) + else + table.insert(tableOfAircraft_enemy, value) + end + end + + -- TODO: only do enemy reports if there is a awacs unit + for key,value in pairs(tableOfAircraft_enemy) do + local distance = getdistance(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, "nm") + + local bearing = getBearing(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, true) + + table.insert(tableOfAircraft_enemyReports, -- the table name + {value.Name, distance, bearing}) --[1][2][3] + + -- https://stackoverflow.com/questions/51276613/how-to-sort-table-by-value-and-then-print-index-in-order + -- https://www.tutorialspoint.com/sort-function-in-lua-programming + + end + table.sort(tableOfAircraft_enemyReports, function(a,b) return a[2] < b[2] end) -- sort based on the second value, which is distance + + + for key,value in pairs(tableOfAircraft_friendly) do -- [1] will always be the player + local distance = getdistance(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, "nm") + + local bearing = getBearing(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, true) + + table.insert(tableOfAircraft_friendlyReports, -- the table name + {value.Name, distance, bearing}) --[1][2][3] + + end + table.sort(tableOfAircraft_friendlyReports, function(a,b) return a[2] < b[2] end) -- sort based on the second value, which is distance + + local string_8210 = 'No Air\nEnemy\nDetected' + if tableOfAircraft_enemyReports[1] ~= nill then + string_8210 = 'Enemy Air\n' .. tableOfAircraft_enemyReports[1][1] + .. '\n ' .. prefixZerosFixedLength(tableOfAircraft_enemyReports[1][3],3) -- bearing + .. 'º ' .. round(tableOfAircraft_enemyReports[1][2],0) .. 'nm'--distance + end + + local string_8211 = 'No Air\nEnemy\nDetected' + if tableOfAircraft_enemyReports[2] ~= nill then + string_8211 = 'Enemy Air\n' .. tableOfAircraft_enemyReports[2][1] + .. '\n ' .. prefixZerosFixedLength(tableOfAircraft_enemyReports[2][3],3) -- bearing + .. 'º ' .. round(tableOfAircraft_enemyReports[2][2],0) .. 'nm'--distance + end + + local string_8212 = 'No Air\nFriend\nDetected' + if tableOfAircraft_friendlyReports[2] ~= nill then + string_8212 = 'Friend Air\n' .. tableOfAircraft_friendlyReports[2][1] + .. '\n ' .. prefixZerosFixedLength(tableOfAircraft_friendlyReports[2][3],3) -- bearing + .. 'º ' .. round(tableOfAircraft_friendlyReports[2][2],0) .. 'nm'--distance + end + + local string_8213 = 'No Air\nFriend\nDetected' + if tableOfAircraft_friendlyReports[3] ~= nill then + string_8213 = 'Friend Air\n' .. tableOfAircraft_friendlyReports[3][1] + .. '\n ' .. prefixZerosFixedLength(tableOfAircraft_friendlyReports[3][3],3) -- bearing + .. 'º ' .. round(tableOfAircraft_friendlyReports[3][2],0) .. 'nm'--distance + end + + ExportScript.Tools.SendData(8210,string_8210) + ExportScript.Tools.SendData(8211, string_8211) + ExportScript.Tools.SendData(8212, string_8212) + ExportScript.Tools.SendData(8213, string_8213) +end + +function ExportScript.IglaHunter(mainPanelDevice) -- Locates the nearest Igla + + local tableOfUnits = LoGetWorldObjects('units') + local selfData = LoGetSelfData() + local selfCoalitionID = selfData.CoalitionID + + local tableOfIgla = {} + local tableOfIgla_report = {} + + --TODO: Might have to refine this. + for key,value in pairs(tableOfUnits) do + if value.CoalitionID ~= selfCoalitionID then + if value.Type.level3 == 27 then + if value.Type.level2 == 16 then + if value.Type.level1 == 2 then + if value.Type.level4 == 55 or 54 or 53 or 52 or 62 then + table.insert(tableOfIgla, value) + end + end + end + end + end + end + + --if tableOfIgla ~= null then + for key,value in pairs(tableOfIgla) do + local distance = getdistance(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, "nm") + + local bearing = getBearing(LoGetSelfData().LatLongAlt.Lat, + value.LatLongAlt.Lat, + LoGetSelfData().LatLongAlt.Long, + value.LatLongAlt.Long, true) + + table.insert(tableOfIgla_report, -- the table name + {value.Name, distance, bearing}) --[1][2][3] + --end + end + table.sort(tableOfIgla_report, function(a,b) return a[2] < b[2] end) -- sort based on the second value, which is distance + + local string_8666 = 'Igla Hunter\nSearching...' + if tableOfIgla_report[1] ~= nill then + string_8666 = 'Igla Detected\n' .. tableOfIgla_report[1][1] + .. '\n ' .. prefixZerosFixedLength(tableOfIgla_report[1][3],3) -- bearing + .. 'º ' .. round(tableOfIgla_report[1][2],0) .. ' nm'--distance + end + + ExportScript.Tools.SendData(8666, string_8666) + +end + +---------------------- +-- Helper Functions -- +---------------------- +function ExportScript.Linearize(current_value, raw_tab, final_tab) + -- (c) scoobie + if current_value <= raw_tab[1] then + return final_tab[1] + end + for index, value in pairs(raw_tab) do + if current_value <= value then + local ft = final_tab[index] + local rt = raw_tab[index] + return (current_value - rt) * (ft - final_tab[index - 1]) / (rt - raw_tab[index - 1]) + ft + end + end + -- we shouldn't be here, so something went wrong - return arbitrary max. final value, maybe the user will notice the problem: + return final_tab[#final_tab] +end + +function trim(s) --http://lua-users.org/wiki/CommonFunctions + -- from PiL2 20.4 + return (s:gsub("^%s*(.-)%s*$", "%1")) +end +function formatTime(time) + local seconds = math.floor(time) % 60 + local minutes = math.floor(time / 60) % 60 + local hours = math.floor(time / (60 * 60)) % 24 + return string.format("%02d", hours) .. "-" .. string.format("%02d", minutes) .. "-" .. string.format("%02d", seconds) +end + +function meters2feet(meters) + local feet = meters * 3.281 + return feet +end + +function feet2meters(feet) + local meters = feet / 3.281 + return feet +end + +function metersPerSecond2milesPerHour(metersPerSecond) + local milesPerHour = metersPerSecond * 2.237 + return milesPerHour +end + +function metersPerSecond2knots(metersPerSecond) + local knots = metersPerSecond * 1.944 + return knots +end + +function kgPerSecond2poundPerHour(kgPerSecond) + poundPerHour = kgPerSecond * 7937 + return poundPerHour +end + +function metersPerSecond2feetPerMinute(metersPerSecond) + local feetPerMinute = metersPerSecond * 197 + return feetPerMinute +end + +function round(num, numDecimalPlaces) --http://lua-users.org/wiki/SimpleRound + local mult = 10^(numDecimalPlaces or 0) + return math.floor(num * mult + 0.5) / mult +end + +function format_int(number) --https://stackoverflow.com/questions/10989788/format-integer-in-lua + local i, j, minus, int, fraction = tostring(number):find('([-]?)(%d+)([.]?%d*)') + -- reverse the int-string and append a comma to all blocks of 3 digits + int = int:reverse():gsub("(%d%d%d)", "%1,") + -- reverse the int-string back remove an optional comma and put the + -- optional minus and fractional part back + return minus .. int:reverse():gsub("^,", "") .. fraction +end + +function formatCoord(type, isLat, d) + local h + if isLat then + if d < 0 then + h = 'S' + d = -d + else + h = 'N' + end + else + if d < 0 then + h = 'W' + d = -d + else + h = 'E' + end + end + + local g = math.floor(d) + local m = math.floor(d * 60 - g * 60) + local s = d * 3600 - g * 3600 - m * 60 + + if type == "DMS" then -- Degree Minutes Seconds + s = math.floor(s * 100) / 100 + return string.format('%s %2d°%.2d\'%05.2f"', h, g, m, s) + elseif type == "DDM" then -- Degree Decimal Minutes + s = math.floor(s / 60 * 1000) + return string.format('%s %2d°%02d.%3.3d\'', h, g, m, s) + else -- Decimal Degrees + return string.format('%f',d) + end +end + +function getdistance(lat1,lat2,lon1,lon2,unit) -- https://www.geeksforgeeks.org/program-distance-two-points-earth/ + --Example Locations + --lat1 = 42.1578 -- POTI + --lat2 = 42.3269 -- HONI + --lon1 = 41.6777 + --lon2 = 42.4122 + + local lon1 = toRadians(lon1) + local lon2 = toRadians(lon2) + local lat1 = toRadians(lat1) + local lat2 = toRadians(lat2) + + -- Haversine formula + local dlon = lon2 - lon1 + local dlat = lat2 - lat1 + local a = math.pow(math.sin(dlat / 2), 2) + + math.cos(lat1) * math.cos(lat2) * + math.pow(math.sin(dlon / 2),2) + + local c = 2 * math.asin(math.sqrt(a)) + + local r -- Radius of earth in X. + if unit == 'nm' then + r = 6371 / 1.852 -- times 1.852 because I could not find a good NM source + elseif unit == 'km' then + r = 6371 -- Use 6371 for kilometers + elseif unit == 'miles' then + r = 3956 -- Use 3956 for miles + elseif unit == 'meters' then + r = 6371 * 1000 + end + + -- calculate the result + return (c * r) +end + +function toRadians(angleIn10thofaDegree) + return (angleIn10thofaDegree * math.pi) / 180 +end + + +function getBearing(lat1,lat2,lon1,lon2, magnetic) + local bearing_rad = math.atan2(lon2 - lon1, lat2 - lat1) + if bearing_rad < 0 then + bearing_rad = bearing_rad + (2 * math.pi) + end + + bearing = math.deg(bearing_rad) + + -- start calculation for getting the magnetic bar + local _aircraftPitch, _aircraftBank, aircraftYawTrue = LoGetADIPitchBankYaw() + aircraftYawTrue = aircraftYawTrue * 57.3 -- actually heading + local aircraftYawMagnetic = LoGetMagneticYaw() * 57.3 + local magneticVariance = aircraftYawTrue - aircraftYawMagnetic + + if magnetic == true then + bearing = bearing - magneticVariance + end + + -- correction for bearings less than 0 due to the calculation above + if bearing < 0 then + bearing = bearing + 360 + end + + return bearing +end + +function addZeros3(number) + number = string.format("%.1d" , number) + if #number == 2 then + number = "0" .. number + elseif #number == 1 then + number = "00" .. number + end + return number +end + +function prefixZerosFixedLength(number, digitLength) -- prefixZerosFixedLength(99, 3) --> 099 + number = string.format("%.1d" , number) -- make the number a string + local zerosToAdd = digitLength - #number + s = '' + for i = 1, zerosToAdd do + s = s .. 0 + end + return s .. number +end + +function mgrsTableize(mgrsString) + -- Reference: https://upload.wikimedia.org/wikipedia/commons/b/b7/Universal_Transverse_Mercator_zones.svg + -- example: 38 T LM 12345 54321 + -- (\d+\s\w)\s(\w+)\s(.+)\s(.+) --c# version of regex + -- UTMZone = string, + -- MGRSDigraph = string, + -- Easting = number, + -- Northing = number + local UTMZone , MGRSDigraph, Easting, Northing = mgrsString:match('(%d+%s%w)%s(%w+)%s(.+)%s(.+)') + local mgrsTbl = {} + table.insert(mgrsTbl, {UTMZone,MGRSDigraph,Easting,Northing}) + return mgrsTbl +end + +function isNilOrEmpty(value) + if value == "" or value == nil then + return true + else + return false + end +end + +function NilOrEmpty(value) + if value == "" then + return 'empty' + elseif value == nil then + return 'empty' + else + return value + end +end