-- ***************************************************************************** -- * Mission functions * -- ***************************************************************************** -- -- Generic Spawn object functions -- env.info('JTFF-SHAREDLIB: shared library loading...') Log_modes = { { name = "trace", color = "\27[34m", verbosity = 10}, { name = "debug", color = "\27[36m", verbosity = 5}, { name = "info", color = "\27[32m", verbosity = 1}, { name = "warn", color = "\27[33m", verbosity = 0}, { name = "error", color = "\27[31m", verbosity = 0}, { name = "fatal", color = "\27[35m", verbosity = 0}, } Log_levels = {} for i, v in ipairs(Log_modes) do Log_levels[v.name] = i end JTFF_LOGLEVEL = 'info' JTFF_verbosity_levels = {} for i, v in ipairs(Log_modes) do JTFF_verbosity_levels[v.name] = v.verbosity end Jtff_log = {} for index, mode in ipairs(Log_modes) do local nameupper = mode.name:upper() Jtff_log[mode.name] = function(message, category) -- Return early if we're below the log level if index < Log_levels[JTFF_LOGLEVEL] then return end env.info(string.format("[JTFF-%s]--%s-- %s", mode.name, category, message)) end end Use_jtff_sound_mod = false if (not(FunkmanConfig)) then Jtff_log.error(string.format("Local Funkman Bot config not specified ! Initializing empty values for Funkman"), "Initialization") FunkmanConfig = { ip= "127.0.0.1", port = 10042, } end if (not(SRSConfig)) then Jtff_log.error(string.format("Local SRS config not specified ! Initializing empty values for SRS"), "Initialization") SRSConfig= { port = 5002, path = "C:/Program Files/DCS-SimpleRadio-Standalone/ExternalAudio", ip = "127.0.0.1", } end SpawnStandardDelay = 15 Jtff_sead = SEAD:New({}) Jtff_map_marker = {} AAMAxRange = { MAX_RANGE = 0, NEZ_RANGE = 1, HALF_WAY_RMAX_NEZ = 2, TARGET_THREAT_EST = 3, RANDOM_RANGE = 4, } if (pcall( function () dofile(lfs.writedir() .. 'Scripts/net/DCSServerBot/DCSServerBot.lua') end)) then Jtff_log.debug(string.format("DCSServerBot present on server ! Including NetLibrary..."), "Initialization") Jtff_dcsbot = dcsbot else Jtff_log.error(string.format("DCSServerBot NOT present on server ! Preventing use of it..."), "Initialization") Jtff_dcsbot = nil end function SwitchGroupImmortalStatus(group) local status = not BASE:GetState(group, "isImmortal") Jtff_log.info(string.format("switch group %s to immortal status %s", group:GetName(), tostring(status)), "GENERAL") group:SetCommandImmortal(status) BASE:SetState(group, "isImmortal", status) MESSAGE:NewType("Immortal status of your group : " .. tostring(status) , MESSAGE.Type.Update):ToGroup(group) end function SwitchGroupUnlimitedFuelStatus(group) local status = not BASE:GetState(group, "isUnlimitedFuel") Jtff_log.info( string.format( "switch group %s to unlimited fuel status %s", group:GetName(), tostring(status) ), "GENERAL" ) group:CommandSetUnlimitedFuel(status) BASE:SetState(group, "isUnlimitedFuel", status) MESSAGE:NewType( "Unlimited fuel status of your group : " .. tostring(status) , MESSAGE.Type.Update ):ToGroup(group) end function SwitchGroupAirbossSubtitlesStatus(group) for index, airbossObject in ipairs(AIRBOSSArray) do for playerindex, player in ipairs(group:GetPlayerUnits()) do airbossObject:_SubtitlesOnOff(player:Name()) end end end function Give_bra_of_air_group(param) local target_group = param[1] local client_group = param[2] local settings = param[3] local coordinate_target = target_group:GetCoordinate() local coordinate_client = client_group:GetCoordinate() return string.format ("%s, %s", coordinate_target:ToStringBRA(coordinate_client, settings), coordinate_target:ToStringAspect(coordinate_client) ) end function Give_heading_speed(param) local target_group = param[1] local settings = param[2] local heading_target = target_group:GetHeading() local speed_target = target_group:GetVelocityKNOTS() if (settings:IsMetric()) then speed_target = target_group:GetVelocityKMH() return string.format ( "Heading : %.0f, Speed : %.0f km/h", heading_target, speed_target ) end return string.format ( "Heading : %.0f, Speed : %.0f kt", heading_target, speed_target ) end function TankerStatusMessage(tanker, PlayerUnit, PlayerGroup) local client = CLIENT:Find(PlayerUnit:GetDCSObject()) local setting = _DATABASE:GetPlayerSettings(client:GetPlayerName()) local tankerrefuelsystemName = "PROBE_AND_DROGUE" local isrefuelable, playerrefuelsystem=PlayerUnit:IsRefuelable() if ((playerrefuelsystem or 0) == 0) and isrefuelable then tankerrefuelsystemName = "BOOM_AND_RECEPTACLE" end local braa_message = Give_bra_of_air_group({tanker:GetGroup(), PlayerGroup, setting}) local aspect_message = Give_heading_speed({tanker:GetGroup(), setting}) local fuelState = string.format("%s Lbs", tanker:GetTemplateFuel() * 2.205) if setting:IsMetric() then fuelState = string.format("%s Kg", tanker:GetTemplateFuel()) end local timeInTheAir = 0 local timeLeftInTheAir = 0 local timeLeftString = "Time left : " local groupName = tanker:GetCallsign() for index, value in pairs(TankersArray or {}) do if( type(value) ~= 'nil') then Jtff_log.info(string.format("%s-%d-1 is in TankersArray", value.customconfig.callsign.name, value.customconfig.callsign.number),"TANKER") if (string.find(groupName, string.format("%s-%d", value.customconfig.callsign.name, value.customconfig.callsign.number), 1, true) ~= nil) then timeInTheAir = timer.getAbsTime() - value.spawnAbsTime timeLeftInTheAir = value.missionmaxduration * 60 - timeInTheAir if (UTILS.SecondsToClock(timeLeftInTheAir, true) ~= nil) then timeLeftString = timeLeftString .. UTILS.SecondsToClock(timeLeftInTheAir, true) end Jtff_log.debug(string.format("%s found in %s, time in the air : %i sec, time left %i sec", value:GetName(), groupName, timeInTheAir, timeLeftInTheAir),"TANKER") else Jtff_log.warn(string.format("%s not found in %s", value:GetName(), groupName),"TANKER") end end end local message = string.format("%s %s [%s]\nFuel State %s (%.2f)\n%s\n%s\n%s", tanker:GetName(), tanker:GetTypeName(), tankerrefuelsystemName, fuelState, tanker:GetFuel() * 100, aspect_message, braa_message, timeLeftString) MESSAGE:NewType(message, MESSAGE.Type.Overview):ToGroup(PlayerGroup) end function GetNearestTankerfromPlayerUnit(PlayerUnit, Radius) Radius=UTILS.NMToMeters(Radius or 50) local isrefuelable, playerrefuelsystem=PlayerUnit:IsRefuelable() if isrefuelable then local playerCoordinates=PlayerUnit:GetCoordinate() local unitList=playerCoordinates:ScanUnits(Radius) local coalition=PlayerUnit:GetCoalition() local minDistance = math.huge local foundTanker = nil --Wrapper.Unit#UNIT for index,unit in pairs(unitList.Set) do local istanker, tankerrefuelsystem=unit:IsTanker() if istanker and playerrefuelsystem == tankerrefuelsystem and coalition == unit:GetCoalition() and unit:IsAlive() then -- Distance. local d = unit:GetCoordinate():Get2DDistance(playerCoordinates) if d < minDistance then d = minDistance foundTanker=unit end end end return foundTanker end return nil end function FindNearestTanker(PlayerUnit, PlayerGroup, Radius) local tanker = GetNearestTankerfromPlayerUnit(PlayerUnit) if (type(tanker) ~= "nil") then TankerStatusMessage(tanker, PlayerUnit, PlayerGroup) end end function FindAllTanker(PlayerUnit, PlayerGroup, Radius) Radius=UTILS.NMToMeters(Radius or 50) local isrefuelable, playerrefuelsystem=PlayerUnit:IsRefuelable() if isrefuelable then local coord = PlayerUnit:GetCoordinate() local units = coord:ScanUnits(Radius) local coalition = PlayerUnit:GetCoalition() local tanker = nil --Wrapper.Unit#UNIT for _,_unit in pairs(units.Set) do local unit=_unit --Wrapper.Unit#UNIT local istanker, tankerrefuelsystem=unit:IsTanker() if istanker and playerrefuelsystem == tankerrefuelsystem and coalition == unit:GetCoalition() and unit:IsAlive() then tanker=unit TankerStatusMessage(tanker, PlayerUnit, PlayerGroup) end end end return nil end function NearestTankerInfo(param) FindNearestTanker( param[1], param[2], 200 ) end function AllTankersInfo(param) FindAllTanker(param[1],param[2], 200) end function TaskTankerEscort(param) local recoveryTankerObject = param[1] local EscortGroup = param[2] EscortGroup:OptionAlarmStateRed() EscortGroup:OptionROEReturnFire() --EscortGroup:TraceOn() EscortGroup:OptionRTBAmmo(true) EscortGroup:OptionRTBBingoFuel(true) local randomCoord = EscortGroup :GetCoordinate() :GetRandomCoordinateInRadius( UTILS.NMToMeters(20), UTILS.NMToMeters(15) ) randomCoord.y = UTILS.FeetToMeters(15000) --randomCoord:MarkToAll('rejointe '..EscortGroup.GroupName) EscortGroup:Route( { randomCoord:WaypointAirTurningPoint( COORDINATE.WaypointAltType.BARO, 500, {}, 'rejoin' ), randomCoord:GetRandomCoordinateInRadius( UTILS.NMToMeters(20), UTILS.NMToMeters(15) ):WaypointAirTurningPoint( COORDINATE.WaypointAltType.BARO, 500, { EscortGroup:TaskEscort( GROUP:FindByName(recoveryTankerObject.tanker.GroupName), COORDINATE:New(0, 10, 150):GetVec3(), 20, UTILS.NMToMeters(40), { 'Air' } ) }, 'escort-start' ) } ) Jtff_log.info('Escort group spawned : '.. EscortGroup.GroupName..'. Escorting '..recoveryTankerObject.tanker.GroupName,"TANKER") end function TaskGroupEscort(param) local GroupToEscortObject = param[1] local EscortingGroup = param[2] EscortingGroup:OptionAlarmStateRed() EscortingGroup:OptionROEReturnFire() --EscortGroup:TraceOn() EscortingGroup:OptionRTBAmmo(true) EscortingGroup:OptionRTBBingoFuel(true) local randomCoord = EscortingGroup :GetCoordinate() :GetRandomCoordinateInRadius( UTILS.NMToMeters(20), UTILS.NMToMeters(15) ) randomCoord.y = UTILS.FeetToMeters(15000) --randomCoord:MarkToAll('rejointe '..EscortGroup.GroupName) EscortingGroup:Route( { randomCoord:WaypointAirTurningPoint( COORDINATE.WaypointAltType.BARO, 500, {}, 'rejoin' ), randomCoord:GetRandomCoordinateInRadius( UTILS.NMToMeters(20), UTILS.NMToMeters(15) ):WaypointAirTurningPoint( COORDINATE.WaypointAltType.BARO, 500, { EscortingGroup:TaskEscort( GROUP:FindByName(GroupToEscortObject.GroupName), COORDINATE:New(0, 10, 150):GetVec3(), 20, UTILS.NMToMeters(40), { 'Air' } ) }, 'escort-start' ) } ) Jtff_log.info('Escort group spawned : '.. EscortingGroup.GroupName..'. Escorting '.. GroupToEscortObject.GroupName,"GENERAL") end function SpawnRecoveryTankerEscort(escortSpawnObject,customconfig) if (customconfig.airspawn) then return escortSpawnObject :SpawnFromCoordinate(UNIT:FindByName(customconfig.baseUnit):GetCoordinate():SetAltitude(UTILS.FeetToMeters(customconfig.altitude))) else return escortSpawnObject :SpawnAtAirbase(AIRBASE:FindByName(customconfig.baseUnit),SPAWN.Takeoff.Cold, customconfig.altitude) end end function LeaveRecovery(objAirboss) local shipID = UNIT:FindByName(objAirboss.carrier:Name()):GetDCSObject():getID() end function ResetRecoveryTanker(recoveryTankerObject) recoveryTankerObject:SetRespawnOnOff(true) recoveryTankerObject.tanker:Destroy() recoveryTankerObject:SetRespawnOnOff(recoveryTankerObject.customconfig.autorespawn) if recoveryTankerObject.customconfig.escortgroupname then recoveryTankerObject.escortGroupObject:Destroy() --recoveryTankerObject.escortGroupObject = spawnRecoveryTankerEscort(recoveryTankerObject.escortSpawnObject,recoveryTankerObject.customconfig) end end function GetMaxThreatUnit(setUnits) local setUnitsSorted = SET_UNIT:New() setUnits:ForEachUnitPerThreatLevel(10, 0, function(unit) setUnitsSorted:AddUnit(unit) end) Jtff_log.debug(string.format("Max priority unit : %s", setUnitsSorted:GetFirst():GetName()), "GENERAL") return setUnitsSorted:GetFirst() end function DestroyGroup(group_name) local set_group_alive = SET_GROUP:New():FilterPrefixes(group_name):FilterOnce() set_group_alive:ForEachGroupAlive( function(group_alive) Jtff_log.info(string.format("Group %s just removed", group_alive:GetName()),"GENERAL") if (Jtff_map_marker[group_alive:GetName()]) then COORDINATE:RemoveMark(Jtff_map_marker[group_alive:GetName()]) end group_alive:Destroy() end ) end function DestroyStatic(staticToDelete, subRangeName, index) if (staticToDelete.name ~= nil) then local staticNameToDelete = string.format("%s", staticToDelete.name) if (subRangeName ~= nil and index ~= nil) then staticNameToDelete = string.format("%s_%s_%i", subRangeName, staticToDelete.name, index) end local staticUnitToDelete = STATIC:FindByName(staticNameToDelete, false) if (staticUnitToDelete ~= nil) then Jtff_log.info(string.format("Delete static %s", staticUnitToDelete:GetDCSObject():getName()),"GENERAL") staticUnitToDelete:Destroy() end elseif (staticToDelete.type ~= nil and staticToDelete.category ~= nil and index ~= nil) then local staticNameToDelete = string.format("%s_%s_%i", subRangeName, staticToDelete.type, index) local staticUnitToDelete = STATIC:FindByName(staticNameToDelete, false) if (staticUnitToDelete ~= nil) then Jtff_log.info(string.format("Delete Static %s", staticUnitToDelete:GetDCSObject():getName()),"GENERAL") staticUnitToDelete:Destroy() end else Jtff_log.error(string.format("Static to delete has no name or type!"), "GENERAL") end end function DestroyStatics(staticsToDelete, subRangeName) for index, staticToDelete in ipairs(staticsToDelete) do DestroyStatic(staticToDelete, subRangeName, index) end end function DeleteSubRangeUnits(param) --parameters : -- 1 : groups to be destroyed -- 2 : Root range config Object -- 3 : sub (or subsub) Range Config Object -- 4 : sub (or subsub) Radio Menu -- 5 : mute switch local groupsToSpawn = param[1] local rangeConfig = param[2] local subRangeConfig = param[3] local radioCommandSubRange = param[4] local blnMute = param[5] local subRangeName = subRangeConfig.name local staticsToDelete = subRangeConfig.staticsToSpawn if (subRangeConfig.groupsToSpawn ~= nil) then for i = 1, #groupsToSpawn do DestroyGroup(groupsToSpawn[i]) end else Jtff_log.warn(string.format("No Groups in %s", subRangeName), "RANGE") end if (subRangeConfig.randomGroupsToSpawn ~= nil) then local groupsToDestroy = subRangeConfig.randomGroupsToSpawn for i = 1, #groupsToDestroy do DestroyGroup(subRangeConfig.randomGroupsToSpawn[i]) end else Jtff_log.warn(string.format("No random*W Groups in %s", subRangeName), "RANGE") end if (staticsToDelete ~= nil)then DestroyStatics(staticsToDelete, subRangeName) else Jtff_log.warn(string.format("No static in %s", subRangeName), "RANGE") end MESSAGE:NewType(string.format("Remove the site : %s-%s", rangeConfig.name, subRangeConfig.name), MESSAGE.Type.Information):ToBlue() if (not(blnMute)) then Sound2Bip:ToAll() end if radioCommandSubRange then radioCommandSubRange:RemoveSubMenus() AddSubRangeRadioMenus(radioCommandSubRange, rangeConfig, subRangeConfig) end end function DeleteWholeRangeUnits(param) local rangeConfig = param[1] local rangeCoalitionMenu = param[2] rangeCoalitionMenu:RemoveSubMenus() if (rangeConfig.subRange ~= nil ) then Sound2Bip:ToAll() MESSAGE:NewType(string.format("Removing the whole site : %s", rangeConfig.name), MESSAGE.Type.Information):ToCoalition(rangeConfig.benefit_coalition) for subIndex, subRangeConfig in ipairs(rangeConfig.subRange) do local radioMenuSubRange = MENU_COALITION:New(rangeConfig.benefit_coalition, subRangeConfig.name, rangeCoalitionMenu) if (subRangeConfig.subsubRange ~= nil) then for subsubIndex, subsubRangeConfig in ipairs(subRangeConfig.subsubRange) do local radioMenuSubSubRange = MENU_COALITION:New(rangeConfig.benefit_coalition, subsubRangeConfig.name, radioMenuSubRange) DeleteSubRangeUnits({ subsubRangeConfig.groupsToSpawn, rangeConfig, subsubRangeConfig, nil, true }) AddSubRangeRadioMenus(radioMenuSubSubRange, rangeConfig, subsubRangeConfig) end else DeleteSubRangeUnits({ subRangeConfig.groupsToSpawn, rangeConfig, subRangeConfig, nil, true }) AddSubRangeRadioMenus(radioMenuSubRange, rangeConfig, subRangeConfig) end end end AddWholeRangeCoalitionCommandMenus(rangeCoalitionMenu, rangeConfig) end function SetROE(param) local groupsToSpawn = param[1] local ROEvalue = param[2] for groupIndex = 1, #groupsToSpawn do local group_name = string.format("%s", groupsToSpawn[groupIndex]) local dcs_groups = SET_GROUP:New():FilterPrefixes(group_name):FilterOnce() dcs_groups:ForEachGroupAlive(function(group_alive) Jtff_log.info(string.format("SET ROE of group %s at %i", group_alive:GetName(), ROEvalue), "GENERAL") if (ROEvalue ~= ENUMS.ROE.WeaponHold) then group_alive:SetAIOn() end group_alive:OptionROE(ROEvalue) end) end end function SetAlarmState(param) local groupsToSpawn = param[1] local AlarmStateValue = param[2] for groupIndex = 1, #groupsToSpawn do local group_name = string.format("%s", groupsToSpawn[groupIndex]) local dcs_groups = SET_GROUP:New():FilterPrefixes(group_name):FilterOnce() dcs_groups:ForEachGroupAlive(function(group_alive) group_alive:SetAIOn() if AlarmStateValue == ENUMS.AlarmState.Auto then Jtff_log.info(string.format("SET Alarm State of group %s at AUTO", group_alive:GetName()),"GENERAL") group_alive:OptionAlarmStateAuto() elseif AlarmStateValue == ENUMS.AlarmState.Green then Jtff_log.info(string.format("SET Alarm State of group %s at Green", group_alive:GetName()),"GENERAL") group_alive:OptionAlarmStateGreen() elseif AlarmStateValue == ENUMS.AlarmState.Red then Jtff_log.info(string.format("SET Alarm State of group %s at Red", group_alive:GetName()),"GENERAL") group_alive:OptionAlarmStateRed() end end) end end function SetEngageAirWeapons(param) local groupsToSpawn = param[1] local value = param[2] for groupIndex = 1, #groupsToSpawn do local group_name = string.format("%s", groupsToSpawn[groupIndex]) local dcs_groups = SET_GROUP:New():FilterPrefixes(group_name):FilterOnce() dcs_groups:ForEachGroupAlive(function(group_alive) Jtff_log.info(string.format("SET Engage Air Weapons of group %s at %s", group_alive:GetName(), tostring(value)),"GENERAL") if (value) then group_alive:SetAIOn() end group_alive:SetOption(AI.Option.Ground.id.ENGAGE_AIR_WEAPONS, value) end) end end function SmokeOnSubRange(param) local groupsToSpawn = param[1] local displayToCoalition = param[2] for groupIndex = 1, #groupsToSpawn do local group_name = string.format("%s", groupsToSpawn[groupIndex]) local dcs_groups = SET_GROUP:New():FilterPrefixes(group_name):FilterOnce() dcs_groups:ForEachGroupAlive(function(group_alive) Jtff_log.info(string.format("Smoke on group %s", group_alive:GetName()),"GENERAL") local list_units = group_alive:GetUnits() local set_units_red = SET_UNIT:New() local set_units_blue = SET_UNIT:New() for index = 1, #list_units do local unit_tmp = list_units[index] if (unit_tmp:IsAlive() and unit_tmp:GetCoalition() == coalition.side.RED) then set_units_red:AddUnit(unit_tmp) end end if (set_units_red:CountAlive() > 0) then local unit_red_to_smoke = GetMaxThreatUnit(set_units_red) if (unit_red_to_smoke) then unit_red_to_smoke:SmokeRed() MESSAGE:NewType(string.format("[%s] Red smoke on : %s", group_alive:GetName(), unit_red_to_smoke:GetTypeName()), MESSAGE.Type.Overview):ToCoalition(displayToCoalition) end elseif (set_units_blue:CountAlive() > 0) then local unit_blue_to_smoke = GetMaxThreatUnit(set_units_blue) if (unit_blue_to_smoke) then unit_blue_to_smoke:SmokeBlue() MESSAGE:NewType(string.format("[%s] Blue smoke on : %s", group_alive:GetName(), unit_blue_to_smoke:GetTypeName()), MESSAGE.Type.Overview):ToCoalition(displayToCoalition) end end end) end end function GiveToClientGroupCoordinates(param) local groupsToSpawn = param[1] for i = 1, #groupsToSpawn do local group_name = string.format("%s", groupsToSpawn[i]) Jtff_log.debug(string.format("Coordinates of all groups with name prefix %s", group_name),"GENERAL") local dcs_groups = SET_GROUP:New():FilterPrefixes(group_name):FilterOnce() Set_AllClients:ForEachClient(function(client) if (client:IsActive()) then Jtff_log.debug(string.format("For Client %s ", client:GetName()),"GENERAL") local coordinate_txt = "" dcs_groups:ForEachGroupAlive(function(group_alive) Jtff_log.debug(string.format("Coordinates of the group %s", group_alive:GetName()),"GENERAL") local coordinate = group_alive:GetCoordinate() local setting = _DATABASE:GetPlayerSettings(client:GetPlayerName()) local coordinate_string = "" if (setting:IsA2G_LL_DDM()) then coordinate_string = coordinate:ToStringLLDDM(setting) Jtff_log.debug(string.format("%s IsA2G_LL_DDM", client:GetName()),"GENERAL") elseif (setting:IsA2G_MGRS()) then coordinate_string = coordinate:ToStringMGRS(setting) Jtff_log.debug(string.format("%s IsA2G_MGRS", client:GetName()),"GENERAL") elseif (setting:IsA2G_LL_DMS()) then coordinate_string = coordinate:ToStringLLDMS(setting) Jtff_log.debug(string.format("%s IsA2G_LL_DMS", client:GetName()),"GENERAL") elseif (setting:IsA2G_BR()) then coordinate_string = coordinate:ToStringBR(client:GetCoordinate(), setting) Jtff_log.debug(string.format("%s IsA2G_BR", client:GetName()),"GENERAL") end Jtff_log.debug(string.format("coordinate_txt [%s] : %s", group_alive:GetName(), coordinate_string),"GENERAL") coordinate_txt = string.format("%s[%s] : %s\n", coordinate_txt, group_alive:GetName(), coordinate_string) end) Jtff_log.debug(string.format("Message to Client %s : %s", client:GetName(), coordinate_txt),"GENERAL") MESSAGE:NewType(coordinate_txt, MESSAGE.Type.Detailed):ToClient(client) end end) end end function GiveListOfGroupsAliveInRange(param) local groupsToSpawn = param[1] local rangeConfig = param[2] local subRangeConfig = param[3] Jtff_log.debug(string.format("List of groups in range %s-%s", rangeConfig.name, subRangeConfig.name),"RANGE") local message = string.format("Targets groups in Range %s-%s :", rangeConfig.name, subRangeConfig.name) for i = 1, #groupsToSpawn do local group_name = string.format("%s", groupsToSpawn[i]) local dcs_groups = SET_GROUP:New():FilterPrefixes(group_name):FilterOnce() dcs_groups:ForEachGroupAlive(function(group_alive) Jtff_log.debug(string.format("group %s", group_alive:GetName()),"RANGE") message = string.format("%s %s | ", message, group_alive:GetName()); end) end Set_AllClients:ForEachClient(function(client) if (client:IsActive()) then MESSAGE:NewType(message, MESSAGE.Type.Information):ToClient(client) end end) end function GiveListOfUnitsAliveInGroup(param) local groupsToSpawn = param[1] local side = param[2] local number_to_display = param[3] for i = 1, #groupsToSpawn do local group_name = string.format("%s", groupsToSpawn[i]) Jtff_log.debug(string.format("List of units of all groups with name prefix %s", group_name),"GENERAL") local dcs_groups = SET_GROUP:New():FilterPrefixes(group_name):FilterOnce() dcs_groups:ForEachGroupAlive(function(group_alive) Jtff_log.debug(string.format("List of units of the group %s", group_alive:GetName()),"GENERAL") local info_unit_header = string.format("Units list of the group [%s]:", group_name) Set_AllClients:ForEachClient(function(client) if (client:IsActive()) then MESSAGE:NewType(info_unit_header, MESSAGE.Type.Overview):ToClient(client) end end) local list_units = group_alive:GetUnits() local set_units = SET_UNIT:New() for index = 1, #list_units do local unit_tmp = list_units[index] if (unit_tmp:IsAlive() and unit_tmp:GetCoalition() ~= side) then set_units:AddUnit(unit_tmp) Jtff_log.debug(string.format("Type : %s", unit_tmp:GetTypeName()),"GENERAL") end end local increment = 0; set_units:ForEachUnitPerThreatLevel(10, 0, function(unit_tmp) if (increment < number_to_display) then local unit_life_pourcentage = (unit_tmp:GetLife() / (unit_tmp:GetLife0() + 1)) * 100 local unit_coordinate = unit_tmp:GetCoordinate() local unit_altitude_m = unit_tmp:GetAltitude() local unit_coordinate_for_client = "" local unit_altitude_for_client = 0 local unit_altitude_for_client_unit = "" Set_AllClients:ForEachClient(function(client) if (client:IsActive()) then local setting = _DATABASE:GetPlayerSettings(client:GetPlayerName()) unit_coordinate_for_client = "" if (setting:IsA2G_LL_DDM()) then unit_coordinate_for_client = unit_coordinate:ToStringLLDDM(setting) elseif (setting:IsA2G_MGRS()) then unit_coordinate_for_client = unit_coordinate:ToStringMGRS(setting) elseif (setting:IsA2G_LL_DMS()) then unit_coordinate_for_client = unit_coordinate:ToStringLLDMS(setting) elseif (setting:IsA2G_BR()) then unit_coordinate_for_client = unit_coordinate:ToStringBR(client:GetCoordinate(), setting) end if (setting:IsImperial()) then unit_altitude_for_client = UTILS.MetersToFeet(unit_altitude_m) unit_altitude_for_client_unit = "ft" elseif (setting:IsMetric()) then unit_altitude_for_client = unit_altitude_m unit_altitude_for_client_unit = "m" end local info_unit_tmp = string.format("[%i] %s (%i", unit_tmp:GetThreatLevel(), unit_tmp:GetTypeName(), unit_life_pourcentage) .. '%),\t' .. unit_coordinate_for_client .. string.format("\tAlt: %.0f%s", unit_altitude_for_client, unit_altitude_for_client_unit) MESSAGE:NewType(info_unit_tmp, MESSAGE.Type.Overview):ToClient(client) end end) increment = increment + 1; end end) end) end end function MarkGroupOnMap(param) local groupsToSpawn = param[1] local side = param[2] for index = 1, #groupsToSpawn do local group_name = string.format("%s", groupsToSpawn[index]) Jtff_log.info(string.format("Mark on map all groups with name prefix %s", group_name),"MARK") local dcs_groups = SET_GROUP:New():FilterPrefixes(group_name):FilterOnce() dcs_groups:ForEachGroupAlive(function(group_alive) Jtff_log.info(string.format("Mark on map the group %s", group_alive:GetName()),"MARK") local coordinate = group_alive:GetCoordinate() Jtff_map_marker[group_alive:GetName()] = coordinate:MarkToCoalition(group_alive:GetName(), side) end) end end function SpawnRangesDelay(param) --parameters : -- 1 : parent Radio Menu -- 2 : Root range config Object -- 3 : subRange config Object -- 4 : delay in s before sub (or subsub) range spawn -- 5 : function calling me (No Idea why we need that) -- 6 : boolean for sound warning play -- 7 : boolean for message warning local rangeConfig = param[2] local subRangeConfig = param[3] local delay = param[4] or SpawnStandardDelay local myfunc = param[5] local sound_warning = true if (type(param[6]) ~= "nil") then sound_warning = param[6] end local message_warning = true if (type(param[7]) ~= "nil") then message_warning = param[7] end if ( sound_warning ) then Sound2Bip:ToAll() end if ( message_warning ) then MESSAGE:NewType(string.format("Warning, Range Units %s(%s) will spawn in %d sec", rangeConfig.name, subRangeConfig.name, delay), MESSAGE.Type.Update):ToAll() end TIMER:New(SpawnRanges, param):Start(delay) end function SpawnWholeRangesDelay(param) --parameters : -- 1 : parent Radio Menu -- 2 : Root range config Object -- 3 : delay in s before sub (or subsub) range spawn -- 4 : function calling me (No Idea why we need that) -- 5 : boolean for sound warning play -- 6 : boolean for message warning local parentRangeMenu = param[1] local rangeConfig = param[2] local delay = param[3] or SpawnStandardDelay local myfunc = param[4] local sound_warning = true if (type(param[5]) ~= "nil") then sound_warning = param[5] end local message_warning = true if (type(param[6]) ~= "nil") then message_warning = param[6] end parentRangeMenu:RemoveSubMenus() if (rangeConfig.subRange) then Sound2Bip:ToAll() for subIndex, subRangeConfig in ipairs(rangeConfig.subRange) do local radioMenuSubRange = MENU_COALITION:New( rangeConfig.benefit_coalition, subRangeConfig.name, parentRangeMenu ) if (subRangeConfig.subsubRange) then for subsubIndex, subsubRangeConfig in ipairs(subRangeConfig.subsubRange) do local radioMenuSubSubRange = MENU_COALITION:New( rangeConfig.benefit_coalition, subsubRangeConfig.name, radioMenuSubRange ) SpawnRangesDelay( { radioMenuSubSubRange, rangeConfig, subsubRangeConfig, delay, myfunc, sound_warning, message_warning } ) end else SpawnRangesDelay( { radioMenuSubRange, rangeConfig, subRangeConfig, delay, myfunc, sound_warning, message_warning } ) end end local CommandZoneDetroy = MENU_COALITION_COMMAND:New( rangeConfig.benefit_coalition, "Delete whole Range", parentRangeMenu, DeleteWholeRangeUnits, { rangeConfig, parentRangeMenu } ) end end function SpawnRanges(param) --parameters : -- 1 : parent Radio Menu -- 2 : Root range config Object -- 3 : subRange config Object local radioCommandSubRange = param[1] local rangeConfig = param[2] local rangeName = rangeConfig.name local subRangeConfig = param[3] local subRangeName = subRangeConfig.name local groupsToSpawn = subRangeConfig.groupsToSpawn local randomGroupsToSpawn = subRangeConfig.randomGroupsToSpawn local nbRandomGroupsToSpawn = subRangeConfig.nbRandomGroupsToSpawn or 1 local staticsToSpawn = subRangeConfig.staticsToSpawn local holdFire = subRangeConfig.holdFire local engageAirWeapons = subRangeConfig.engageAirWeapons local activateAI = subRangeConfig.AI local redAlert = subRangeConfig.redAlert Jtff_log.info(string.format("SpawnRanges : Range %s - Targets %s", rangeName, subRangeName),"RANGE") if (staticsToSpawn ~= nil) then for index, staticToSpawn in ipairs(staticsToSpawn) do local spawnStatic = {} if (type(staticToSpawn.name) ~= 'nil') then local staticNameToSpawn = string.format("%s", staticToSpawn.name) spawnStatic = SPAWNSTATIC:NewFromStatic(staticNameToSpawn) or {} if (staticToSpawn.coalition ~= nil) then if (staticToSpawn.coalition == coalition.side.BLUE) then spawnStatic = SPAWNSTATIC:NewFromStatic(staticNameToSpawn, country.id.CJTF_BLUE) or {} elseif (staticToSpawn.coalition == coalition.side.RED) then spawnStatic = SPAWNSTATIC:NewFromStatic(staticNameToSpawn, country.id.CJTF_RED) or {} else spawnStatic = SPAWNSTATIC:NewFromStatic(staticNameToSpawn, country.id.UN_PEACEKEEPERS) or {} end end local x = staticToSpawn.x local y = staticToSpawn.y local heading = staticToSpawn.heading local name = string.format("%s_%s_%i", subRangeName, staticNameToSpawn,index) local static = spawnStatic:SpawnFromCoordinate( COORDINATE:NewFromVec2( x, y ), heading, name ) Jtff_log.info(string.format("Static to spawn %s at %i,%i -> %s", static:GetDCSObject():getTypeName(), x, y, static:GetDCSObject():getName()),"RANGE") elseif (staticToSpawn.type ~= nil and staticToSpawn.category ~= nil) then local staticTypeToSpawn = string.format("%s", staticToSpawn.type) local staticCategoryToSpawn = string.format("%s", staticToSpawn.category) local x = staticToSpawn.x local y = staticToSpawn.y --spawnStatic = SPAWNSTATIC:NewFromType(staticTypeToSpawn, staticCategoryToSpawn) if (staticToSpawn.type == "big_smoke" and staticToSpawn.category == "Effects") then local landAltitude = COORDINATE:NewFromVec2( {x=x, y=y} ):GetLandHeight() trigger.action.effectSmokeBig({ x = x, y = landAltitude, z = y}, staticToSpawn.effectPreset or 1, staticToSpawn.effectTransparency or 1) else if type(staticToSpawn.coalition ~= 'nil') then if (staticToSpawn.coalition == coalition.side.BLUE) then spawnStatic = SPAWNSTATIC:NewFromType(staticTypeToSpawn, staticCategoryToSpawn, country.id.CJTF_BLUE) or {} elseif (staticToSpawn.coalition == coalition.side.RED) then spawnStatic = SPAWNSTATIC:NewFromType(staticTypeToSpawn, staticCategoryToSpawn, country.id.CJTF_RED) or {} else spawnStatic = SPAWNSTATIC:NewFromType(staticTypeToSpawn, staticCategoryToSpawn, country.id.UN_PEACEKEEPERS) or {} end end local heading = staticToSpawn.heading local name = string.format("%s_%s_%i", subRangeName, staticTypeToSpawn, index) if (staticToSpawn.shape_name ~= nil) then spawnStatic:InitShape(staticToSpawn.shape_name) end if (staticToSpawn.dead ~= nil) then spawnStatic:InitDead(staticToSpawn.dead) end local static = spawnStatic:SpawnFromCoordinate( COORDINATE:NewFromVec2( {x=x, y=y} ), heading, name ) Jtff_log.info(string.format("Static type to spawn %s at %i,%i -> %s", static:GetDCSObject():getTypeName(), x, y, static:GetDCSObject():getName()),"RANGE") end else Jtff_log.warn(string.format("Static to spawn has no name or type!"),"RANGE") end end else Jtff_log.warn(string.format("No static in %s", subRangeName),"RANGE") end if (randomGroupsToSpawn ~= nil) then local availableRandomGroupsList = randomGroupsToSpawn nbRandomGroupsToSpawn = math.min(nbRandomGroupsToSpawn,#availableRandomGroupsList) --math.randomseed(os.time()) for i = 1, nbRandomGroupsToSpawn do local randomIndex = math.random(1, #availableRandomGroupsList) local groupNameToSpawn = string.format("%s", availableRandomGroupsList[randomIndex]) local finalList = {} for index, value in ipairs(availableRandomGroupsList) do if index ~= randomIndex then table.insert(finalList, value) end end availableRandomGroupsList = finalList if (GROUP:FindByName(groupNameToSpawn) ~= nil) then local spawnGroup = SPAWN:New(groupNameToSpawn) Jtff_log.info(string.format("SPAWN %s", groupNameToSpawn), "RANGE") local groupSpawning if (subRangeConfig.spawnZone) then groupSpawning = spawnGroup:SpawnInZone(ZONE:New(subRangeConfig.spawnZone),true) else groupSpawning = spawnGroup:Spawn() end MarkGroupOnMap({{groupNameToSpawn}, rangeConfig.benefit_coalition}) if (holdFire) then groupSpawning:OptionROEHoldFire() else groupSpawning:OptionROEOpenFire() end if (engageAirWeapons) then groupSpawning:SetOption(AI.Option.Ground.id.ENGAGE_AIR_WEAPONS, true) end if (activateAI == true or activateAI == false) then groupSpawning:SetAIOnOff(activateAI) end if (redAlert == true or redAlert == false) then if (redAlert == true) then groupSpawning:OptionAlarmStateRed() else groupSpawning:OptionAlarmStateGreen() end else groupSpawning:OptionAlarmStateAuto() end if (string.find(groupNameToSpawn, "SAM") ~= nil) then Jtff_sead:UpdateSet(groupNameToSpawn) Jtff_log.info(string.format("SEAD for %s", groupNameToSpawn),"RANGE") end else Jtff_log.error(string.format("GROUP to spawn %s not found in mission", groupNameToSpawn),"RANGE") end end end if (groupsToSpawn ~= nil) then for i = 1, #groupsToSpawn do local groupNameToSpawn = string.format("%s", groupsToSpawn[i]) if (GROUP:FindByName(groupNameToSpawn) ~= nil) then local spawnGroup = SPAWN:New(groupNameToSpawn) Jtff_log.info(string.format("SPAWN %s", groupNameToSpawn), "RANGE") local groupSpawning if (subRangeConfig.spawnZone) then groupSpawning = spawnGroup:SpawnInZone(ZONE:New(subRangeConfig.spawnZone),true) else groupSpawning = spawnGroup:Spawn() end if (holdFire) then groupSpawning:OptionROEHoldFire() else groupSpawning:OptionROEOpenFire() end if (engageAirWeapons) then groupSpawning:SetOption(AI.Option.Ground.id.ENGAGE_AIR_WEAPONS, true) end if (activateAI == true or activateAI == false) then groupSpawning:SetAIOnOff(activateAI) end if (redAlert == true or redAlert == false) then if (redAlert == true) then groupSpawning:OptionAlarmStateRed() else groupSpawning:OptionAlarmStateGreen() end else groupSpawning:OptionAlarmStateAuto() end if (string.find(groupNameToSpawn, "SAM") ~= nil) then Jtff_sead:UpdateSet(groupNameToSpawn) Jtff_log.info(string.format("SEAD for %s", groupNameToSpawn),"RANGE") end else Jtff_log.error(string.format("GROUP to spawn %s not found in mission", groupNameToSpawn),"RANGE") end end MarkGroupOnMap({groupsToSpawn, rangeConfig.benefit_coalition}) else Jtff_log.warn(string.format("No GROUP to spawn in subRange %s settings !",subRangeName),"RANGE") end radioCommandSubRange:RemoveSubMenus() local CommandZoneDetroy = MENU_COALITION_COMMAND:New(rangeConfig.benefit_coalition, "Delete", radioCommandSubRange, DeleteSubRangeUnits, {groupsToSpawn, rangeConfig, subRangeConfig, radioCommandSubRange, true}) local ROE = MENU_COALITION:New(rangeConfig.benefit_coalition, "ROE", radioCommandSubRange) local ROEOpenFire = MENU_COALITION_COMMAND:New(rangeConfig.benefit_coalition, "Open Fire", ROE, SetROE, {groupsToSpawn, ENUMS.ROE.OpenFire}) local ROEReturnFire = MENU_COALITION_COMMAND:New(rangeConfig.benefit_coalition, "Return Fire", ROE, SetROE, {groupsToSpawn, ENUMS.ROE.ReturnFire}) local ROEHoldFire = MENU_COALITION_COMMAND:New(rangeConfig.benefit_coalition, "Hold Fire", ROE, SetROE, {groupsToSpawn, ENUMS.ROE.WeaponHold}) local AlarmState = MENU_COALITION:New(rangeConfig.benefit_coalition, "Alarm State", radioCommandSubRange) local AlarmStateAuto = MENU_COALITION_COMMAND:New(rangeConfig.benefit_coalition, "Auto", AlarmState, SetAlarmState, {groupsToSpawn, ENUMS.AlarmState.Auto}) local AlarmStateGreen = MENU_COALITION_COMMAND:New(rangeConfig.benefit_coalition, "Green", AlarmState, SetAlarmState, {groupsToSpawn, ENUMS.AlarmState.Green}) local AlarmStateRed = MENU_COALITION_COMMAND:New(rangeConfig.benefit_coalition, "Red", AlarmState, SetAlarmState, {groupsToSpawn, ENUMS.AlarmState.Red}) local Engage_Air_Weapons = MENU_COALITION:New(rangeConfig.benefit_coalition, "Engage Air Weapons", radioCommandSubRange) local Engage_Air_Weapons_True = MENU_COALITION_COMMAND:New(rangeConfig.benefit_coalition, "True", Engage_Air_Weapons, SetEngageAirWeapons, {groupsToSpawn, true}) local Engage_Air_Weapons_False = MENU_COALITION_COMMAND:New(rangeConfig.benefit_coalition, "False", Engage_Air_Weapons, SetEngageAirWeapons, {groupsToSpawn, false}) local CommandZoneFumigene = MENU_COALITION_COMMAND:New(rangeConfig.benefit_coalition, "Smoke", radioCommandSubRange, SmokeOnSubRange, {groupsToSpawn, rangeConfig.benefit_coalition}) local CommandZoneCoord = MENU_COALITION_COMMAND:New(rangeConfig.benefit_coalition, "Coordinates", radioCommandSubRange, GiveToClientGroupCoordinates, {groupsToSpawn}) local CommandZoneListGroup = MENU_COALITION_COMMAND:New(rangeConfig.benefit_coalition, "List Groups", radioCommandSubRange, GiveListOfGroupsAliveInRange, {groupsToSpawn, rangeConfig, subRangeConfig}) local CommandZoneList = MENU_COALITION_COMMAND:New(rangeConfig.benefit_coalition, "List Units", radioCommandSubRange, GiveListOfUnitsAliveInGroup, {groupsToSpawn, rangeConfig.benefit_coalition, 5}) MESSAGE:NewType(string.format("Units in range %s(%s) in place", rangeName, subRangeName), MESSAGE.Type.Information) :ToCoalition(rangeConfig.benefit_coalition) end function SpawnFacRangesDelay(param) local facRangeConfig = param[2] local facSubRangeConfig = param[3] local delay = param[4] or 10 MESSAGE:NewType(string.format("Warning, FAC in range %s(%s) will spawn in %d sec", facRangeConfig.name, facSubRangeConfig.name, delay), MESSAGE.Type.Update):ToBlue() TIMER:New(SpawnFacRanges, param):Start(delay) end function SpawnFacRanges(param) local radioCommandSubRange = param[1] local facRangeConfig = param[2] local facRangeName = facRangeConfig.name local facSubRangeConfig = param[3] local facSubRangeName = facSubRangeConfig.name local groupsToSpawn = facSubRangeConfig.groupsToSpawn local staticsToSpawn = facSubRangeConfig.staticsToSpawn Jtff_log.info(string.format("SpawnFacRanges : %s-%s", facRangeName, facSubRangeName),"RANGE") for i = 1, #groupsToSpawn do local groupNameToSpawn = string.format("%s", groupsToSpawn[i]) if (GROUP:FindByName(groupNameToSpawn) ~= nil) then local spawnGroup = SPAWN:New(groupNameToSpawn) Jtff_log.info(string.format("SPAWN %s", groupNameToSpawn),"RANGE") local groupSpawning if (facSubRangeConfig.spawnZone) then groupSpawning = spawnGroup:SpawnInZone(ZONE:New(facSubRangeConfig.spawnZone),true) else groupSpawning = spawnGroup:Spawn() end groupSpawning:SetCommandInvisible(true) else Jtff_log.warn(string.format("GROUP to spawn %s not found in mission", groupNameToSpawn),"RANGE") end end radioCommandSubRange:RemoveSubMenus() local CommandZoneDetroy = MENU_COALITION_COMMAND:New(facRangeConfig.benefit_coalition, "Delete", radioCommandSubRange, DeleteSubRangeUnits, { groupsToSpawn, facRangeConfig, facSubRangeConfig, radioCommandSubRange, true}) local CommandZoneFumigene = MENU_COALITION_COMMAND:New(facRangeConfig.benefit_coalition, "Smoke", radioCommandSubRange, SmokeOnSubRange, { groupsToSpawn, facRangeConfig.benefit_coalition}) local CommandZoneCoord = MENU_COALITION_COMMAND:New(facRangeConfig.benefit_coalition, "Coordinates", radioCommandSubRange, GiveToClientGroupCoordinates, {groupsToSpawn}) local CommandZoneListGroup = MENU_COALITION_COMMAND:New(facRangeConfig.benefit_coalition, "List Groups", radioCommandSubRange, GiveListOfGroupsAliveInRange, { groupsToSpawn, facRangeConfig, facSubRangeConfig }) local CommandZoneList = MENU_COALITION_COMMAND:New(facRangeConfig.benefit_coalition, "List Units", radioCommandSubRange, GiveListOfUnitsAliveInGroup, { groupsToSpawn, facRangeConfig.benefit_coalition, 5}) MESSAGE:NewType(string.format("FAC in range %s(%s) in place", facRangeName, facSubRangeName), MESSAGE.Type.Information) :ToBlue() MarkGroupOnMap({ groupsToSpawn, facRangeConfig.benefit_coalition}) end function AddSubRangeRadioMenus(radioCommandSubRange, rangeConfig, subRangeConfig) local RadioCommandAdd = MENU_COALITION_COMMAND:New( rangeConfig.benefit_coalition, "Spawn", radioCommandSubRange, SpawnRangesDelay, { radioCommandSubRange, rangeConfig, subRangeConfig, SpawnStandardDelay, AddSubRangeRadioMenus } ) end function AddWholeRangeCoalitionCommandMenus(radioCommandRange, rangeConfig) local AddWholeRangeCommand = MENU_COALITION_COMMAND:New( rangeConfig.benefit_coalition, "Spawn Whole Range", radioCommandRange, SpawnWholeRangesDelay, { radioCommandRange, rangeConfig, SpawnStandardDelay, AddWholeRangeCoalitionCommandMenus } ) local DeleteWholeRangeCommand = MENU_COALITION_COMMAND:New( rangeConfig.benefit_coalition, "Delete whole Range", radioCommandRange, DeleteWholeRangeUnits, { rangeConfig, radioCommandRange } ) return {AddWholeRangeCommand, DeleteWholeRangeCommand} end function AddFacFunction(radioCommandSubRange, facRangeConfig, facSubRangeConfig) local RadioCommandAdd = MENU_COALITION_COMMAND:New( facRangeConfig.benefit_coalition, "Spawn", radioCommandSubRange, SpawnFacRangesDelay, { radioCommandSubRange, facRangeConfig, facSubRangeConfig, SpawnStandardDelay, AddFacFunction } ) end function GetTableLng(tbl) local getN = 0 for n in pairs(tbl) do getN = getN + 1 end return getN end function GetSoundFilesPrefix() local strPrefix local lfs = require("lfs") if (Use_jtff_sound_mod) then strPrefix = lfs.writedir() .. 'Sounds/JTFF-Missions/' else strPrefix = "" end return strPrefix end function SpawnStaticListWithUnitLink(layoutData, namePrefix, unitID, countryID) for index, layoutObject in pairs(layoutData) do local staticToSpawn = { ["name"] = namePrefix .. "-" .. index, ["livery_id"] = layoutObject.livery_id or "default", ["category"] = layoutObject.category, ["offsets"] = layoutObject.offsets, ["shape_name"] = layoutObject.shape_name, ["type"] = layoutObject.type, ["groupId"] = 1, ["unitId"] = 1, ["y"] = 0, ["x"] = 0, ["heading"] = 0, ["linkUnit"] = unitID, ["linkOffset"] = true, ["dead"] = false, } coalition.addStaticObject(countryID, staticToSpawn) end end function ClientIsOnGround(client) Jtff_log.trace(string.format("Client %s is tested if alive and OnGround", client:GetPlayer() or ""),"QRA") return UNIT:Find(client:GetDCSObject()):IsAlive() and not(UNIT:Find(client:GetDCSObject()):InAir(true)) end --- find a #AIRWING object by its AirbaseName. -- @param #string airbaseName Airbase name. -- @return #AIRWING _airbase Found AIRWING object. -- @return #nil nil if no AIRWING object found. function FindAirwingByAirbaseName(airbaseName) if type(UNIT:FindByName(airbaseName)) ~= 'nil' then if type(AIRBOSSArray) ~= 'table' then return nil end for _key,_airboss in pairs(AIRBOSSArray) do if (_airboss.customconfig.carriername == airbaseName) then return _airboss.airwing end end end if type(AirbasesArray) ~= 'table' then return nil end for _key,_airbase in pairs(AirbasesArray) do if (_airbase.customconfig.name == airbaseName) then return _airbase.airwing end end return nil end --- find a #AIRBOSS object by its alias. -- @param #string airbossUnitName Airboss unit name. -- @return #AIRBOSS value Found Airboss object. -- @return #nil nil if no Airboss object found. function FindAirbossByUnitName(airbossUnitName) if type(AIRBOSSArray) ~= 'table' then return nil end for key,value in pairs(AIRBOSSArray) do if (value.customconfig.carriername == airbossUnitName) then return value end end return nil end --- find a #AIRBOSS object by its alias. -- @param #string airbossAlias Airboss alias. -- @return #AIRBOSS Found Airboss object. -- @return #nil if no Airboss object found. function FindAirbossByAlias(airbossAlias) if type(AIRBOSSArray) ~= 'table' then return nil end for index,value in pairs(AIRBOSSArray) do if (value.customconfig.alias == airbossAlias) then return value end end return nil end -- @type AirwingConfig -- @field #string name Airwing name. -- @field #SquadronConfig[] squadrons Squadrons for the airwing. --- Parse an Airwing config Object. -- @param #JsonObject config Config object to parse -- @param #string parser_name Parser name ("AIRBASE", "AIRBOSS", etc...). -- @return #AirwingConfig airwingConfigJson Parsed Airwing object function ParseAirwingConfigJson(config, parser_name) local airwingConfigJson = {} -- ************************************************************************** -- name -- ************************************************************************** if type(config.name) == "string" then airwingConfigJson.name = config.name else Jtff_log.error("Airwing name is not a string, skipping airwing configuration", parser_name) return {} end -- ************************************************************************** -- squadrons -- ************************************************************************** if type(config.squadrons) == "table" then airwingConfigJson.squadrons = {} for _, _squadconfig in ipairs(config.squadrons) do table.insert( airwingConfigJson.squadrons, ParseSquadronConfigJson(_squadconfig, parser_name) ) end else Jtff_log.error("Airwing squadrons is not a table, skipping airwing configuration", "AIRWING") config.squadrons = {} return config end return airwingConfigJson end -- @type RadioConfig -- @field #number freq Frequency for the radio. -- @field #number modulation Modulation for the radio. -- @field #number power Radio power in Watts --- Parse an Radio config Object. -- @param #JsonObject config Config object to parse -- @param #string parser_name Parser name ("AIRBASE", "AIRBOSS", etc...). -- @return #RadioConfig radioConfigJson Parsed Radio object function ParseRadioConfigJson(config, parser_name) local radioConfigJson = {} local default_freq = 251.000 local default_power = 100 local default_modulation = radio.modulation.AM -- ************************************************************************** -- freq -- ************************************************************************** radioConfigJson.freq = config.freq or default_freq -- ************************************************************************** -- modulation -- ************************************************************************** radioConfigJson.modulation = config.modulation or default_modulation -- ************************************************************************** -- power -- ************************************************************************** radioConfigJson.power = config.power or default_power return radioConfigJson end -- @type tacanConfig -- @field #number channel Channel for the TACAN. -- @field #string morse Morse code for the TACAN. -- @field #string band Band for the TACAN. --- Parse an TACAN config Object. -- @param #JsonObject config Config object to parse -- @param #string parser_name Parser name ("AIRBASE", "AIRBOSS", etc...). -- @return #tacanConfig tacanConfigJson Parsed Radio object function ParseTacanConfigJson(config, parser_name) local tacanConfigJson = {} local default_channel = 100 local default_morse = 'TEX' local default_band = 'Y' if type(config) == "table" then -- ********************************************************************** -- channel -- ********************************************************************** tacanConfigJson.channel = config.channel or default_channel -- ************************************************************************** -- morse -- ************************************************************************** tacanConfigJson.morse = config.morse or default_morse -- ************************************************************************** -- band -- ************************************************************************** tacanConfigJson.band = config.band or default_band else Jtff_log.error("TACAN config is not a table, skipping TACAN configuration", parser_name) return {} end return tacanConfigJson end -- @type SquadronConfig -- @field #string name Squadron name. -- @field #number fuellowthreshold Low Fuel threshold in percent. -- @field #boolean lowfuelrtb Enable Low Fuel RTB. -- @field #number grouping Grouping for the squadron. -- @field #number modex Modex for the squadron. -- @field #RadioConfig radio Radio configuration for the squadron. -- @field #number skill Skill for the squadron. -- @field #number turnovertime Turnover time in seconds. -- @field #number repairtime Repair time in seconds. -- @field #string livery Livery for the squadron. -- @field #PayloadConfig[] payloads Templates for the squadron. -- @field #number nb_aircrafts Number of aircrafts in the squadron. --- Parse an Squadron config Object. -- @param #JsonObject config Config object to parse -- @param #string parser_name Parser name ("AIRBASE", "AIRBOSS", etc...). -- @return #SquadronConfig squadronConfigJson Parsed Squadron object function ParseSquadronConfigJson(config, parser_name) local squadronConfigJson = {} local default_nb_aircrafts = 6 local default_fuellowthreshold = 25 local default_lowfuelrtb = false local default_grouping = 1 local default_modex = 400 local default_radio = {freq = 251.000, modulation = radio.modulation.AM, power = 100} local default_skill = AI.Skill.GOOD local default_turnovertime = 10 local default_repairtime = (default_turnovertime *2) local default_livery = "" -- ************************************************************************** -- template -- ************************************************************************** if type(config.template) == "string" then if type(GROUP:FindByName(config.template)) == 'nil' then Jtff_log.error( string.format( "Squadron template %s not found, skipping squadron template configuration", config.template ), parser_name ) return {} else squadronConfigJson.template = config.template end else Jtff_log.error("Squadron template is not a string, skipping squadron configuration", parser_name) return {} end -- ************************************************************************** -- name -- ************************************************************************** if type(config.name) == "string" then squadronConfigJson.name = config.name else Jtff_log.error("Squadron name is not a string, skipping squadron configuration", parser_name) return {} end -- ************************************************************************** -- fuellowthreshold -- ************************************************************************** squadronConfigJson.fuellowthreshold = config.fuellowthreshold or default_fuellowthreshold -- ************************************************************************** -- lowfuelrtb -- ************************************************************************** squadronConfigJson.lowfuelrtb = config.lowfuelrtb or default_lowfuelrtb -- ************************************************************************** -- grouping -- ************************************************************************** squadronConfigJson.grouping = config.grouping or default_grouping -- ************************************************************************** -- modex -- ************************************************************************** squadronConfigJson.modex = config.modex or default_modex -- ************************************************************************** -- radio -- ************************************************************************** if type(config.radio) == "table" then squadronConfigJson.radio = ParseRadioConfigJson(config.radio, parser_name) if next(squadronConfigJson.radio) == nil then Jtff_log.warn("Squadron radio is not a valid Radio object, skipping tanker configuration", parser_name) squadronConfigJson.radio = default_radio end else Jtff_log.warn( string.format( "Squadron %s radio config is not a table, apply default radio config", squadronConfigJson.name ), parser_name ) squadronConfigJson.radio = default_radio end -- ************************************************************************** -- skill -- ************************************************************************** squadronConfigJson.skill = config.skill or default_skill -- ************************************************************************** -- turnovertime -- ************************************************************************** squadronConfigJson.turnovertime = config.turnovertime or default_turnovertime -- ************************************************************************** -- repairtime -- ************************************************************************** squadronConfigJson.repairtime = config.repairtime or default_repairtime -- ************************************************************************** -- livery -- ************************************************************************** squadronConfigJson.livery = config.livery or default_livery -- ************************************************************************** -- payloads -- ************************************************************************** if type(config.payloads) == "table" then squadronConfigJson.payloads = {} for _, _payload in ipairs(config.payloads) do table.insert( squadronConfigJson.payloads, ParseSquadPayloadConfigJson( _payload, parser_name ) ) end if #(squadronConfigJson.payloads) == 0 then Jtff_log.error( string.format( "Squadron %s has no payloads, skipping squadron configuration", squadronConfigJson.name ), parser_name ) return {} end else Jtff_log.error("Airwing squadrons is not a table, skipping airwing configuration", "AIRWING") return {} end -- ************************************************************************** -- nb_aircrafts -- ************************************************************************** if type(config.nb_aircrafts) == "number" then squadronConfigJson.nb_aircrafts = config.nb_aircrafts else Jtff_log.warn( string.format( "Squadron %s nb_aircrafts is not a number, defaulting to %d", squadronConfigJson.name, default_nb_aircrafts ), parser_name ) squadronConfigJson.nb_aircrafts = default_nb_aircrafts end return squadronConfigJson end -- @type PayloadRoleConfig -- @field #AUFTRAG.Type[] roles Roles for the payload. -- @field #number perf Performance for the payload (between 1 and 100). -- @field #number qty Quantity of the payload. (-1 is unlimited) -- @type PayloadConfig -- @field #string name Payload name. -- @field #PayloadRoleConfig[] payloadconfigs Payload roles config for the payload. --- Parse a PayloadConfig config Object. -- @param #JsonObject config Config object to parse -- @param #string parser_name Parser name ("AIRBASE", "AIRBOSS", etc...). -- @return #PayloadConfig squadronPayloadConfigJson Parsed PayloadConfig object function ParsePayloadConfigJson(config, parser_name) local payloadConfigJson = {} local default_perf = 70 local default_qty = 50 -- ************************************************************************** -- roles -- ************************************************************************** if type(config.roles) == "table" then payloadConfigJson.roles = {} for _, _role in ipairs(config.roles) do table.insert(payloadConfigJson.roles, _role) end else Jtff_log.error("Payload roles is not a table, skipping payload configuration", parser_name) return {} end -- ************************************************************************** -- perf -- ************************************************************************** payloadConfigJson.perf = config.perf or default_perf -- ************************************************************************** -- qty -- ************************************************************************** payloadConfigJson.qty = config.qty or default_qty return payloadConfigJson end --- Parse an SquadronPayload config Object. -- @param #JsonObject config Config object to parse -- @param #string parser_name Parser name ("AIRBASE", "AIRBOSS", etc...). -- @return #PayloadConfig squadronPayloadConfigJson Parsed PayloadConfig object function ParseSquadPayloadConfigJson(config, parser_name) local squadronPayloadConfigJson = {} -- ************************************************************************** -- name -- ************************************************************************** if type(config.name) == "string" then if type(GROUP:FindByName(config.name)) == 'nil' then Jtff_log.error( string.format( "SquadronPayload %s not found, skipping squadron payload configuration", config.name ), parser_name ) return {} else squadronPayloadConfigJson.name = config.name end else Jtff_log.error( "SquadronPayloadTemplate name is not a string, skipping squadron Payload configuration", parser_name ) return {} end -- ************************************************************************** -- payloadconfigs -- ************************************************************************** if type(config.payloadconfigs) == "table" then squadronPayloadConfigJson.payloadconfigs = {} for _, _payloadconfig in ipairs(config.payloadconfigs) do if type(_payloadconfig) == "table" then table.insert( squadronPayloadConfigJson.payloadconfigs, ParsePayloadConfigJson(_payloadconfig, parser_name) ) else Jtff_log.error( string.format( "SquadronPayload is not a valid PayloadRoleConfig object, skipping squadron payload configuration" ), parser_name ) return {} end end if #(squadronPayloadConfigJson.payloadconfigs) == 0 then Jtff_log.error( string.format( "SquadronPayload %s has no payloads, skipping squadron Payload configuration", squadronPayloadConfigJson.name ), parser_name ) return {} end else Jtff_log.error( string.format( "SquadronPayload %s payloadconfigs is not a table, skipping squadron Payload configuration", squadronPayloadConfigJson.name ), parser_name ) return {} end return squadronPayloadConfigJson end -- @type RelativeRacetrackConfig -- @field #WRAPPER.Unit patternUnit Unit name to use for the pattern. -- @field #number front number of nm in the racetrack before the unit. -- @field #number back number of nm in the racetrack behind of the unit. -- @field #number fl Racetrack flight level. -- @field #number speed Racetrack speed in knots. --- Parse an RelativeRacetrackConfig config Object. -- @param #JsonObject config Config object to parse -- @param #string parser_name Parser name ("AIRBASE", "AIRBOSS", etc...). -- @return #RelativeRacetrackConfig racetrackConfigJson Parsed RacetrackConfig object function ParseRelativeRacetrackConfigJson(config, parser_name) local relativeRacetrackConfigJson = {} local default_front = 30 local default_back = 30 local default_fl = 080 local default_speed = 320 -- ************************************************************************** -- patternUnit -- ************************************************************************** if type(config.patternUnit) == "string" then if type(UNIT:FindByName(config.patternUnit)) == 'nil' then Jtff_log.error( string.format( "Racetrack patternUnit %s not found, skipping racetrack configuration", config.patternUnit ), parser_name ) return {} else relativeRacetrackConfigJson.patternUnit = UNIT:FindByName(config.patternUnit) end else Jtff_log.error( string.format( "Racetrack patternUnit %s is not a string, skipping racetrack configuration", config.patternUnit ), parser_name ) return {} end -- ************************************************************************** -- front -- ************************************************************************** relativeRacetrackConfigJson.front = config.front or default_front -- ************************************************************************** -- back -- ************************************************************************** relativeRacetrackConfigJson.back = config.back or default_back -- ************************************************************************** -- fl -- ************************************************************************** relativeRacetrackConfigJson.fl = config.fl or default_fl -- ************************************************************************** -- speed -- ************************************************************************** relativeRacetrackConfigJson.speed = config.speed or default_speed return relativeRacetrackConfigJson end -- @type RacetrackConfig -- @field #string coordinate MGRS Racetrack coordinates. -- @field #number fl Racetrack flight level. -- @field #number speed Racetrack speed in knots. -- @field #number heading Racetrack heading in degrees. -- @field #number leg Racetrack leg in nm. --- Parse an Racetrack config Object. -- @param #JsonObject config Config object to parse -- @param #string parser_name Parser name ("AIRBASE", "AIRBOSS", etc...). -- @return #RacetrackConfig racetrackConfigJson Parsed RacetrackConfig object function ParseRacetrackConfigJson(config, parser_name) local racetrackConfigJson = {} local default_fl = 250 local default_speed = 320 local default_heading = 090 local default_leg = 30 -- ************************************************************************** -- coordinate -- ************************************************************************** if type(config.coordinate) == "string" then racetrackConfigJson.coordinate = config.coordinate else Jtff_log.error("Racetrack coordinates is not a table, skipping racetrack configuration", parser_name) return {} end -- ************************************************************************** -- fl -- ************************************************************************** racetrackConfigJson.fl = config.fl or default_fl -- ************************************************************************** -- speed -- ************************************************************************** racetrackConfigJson.speed = config.speed or default_speed -- ************************************************************************** -- heading -- ************************************************************************** racetrackConfigJson.heading = config.heading or default_heading -- ************************************************************************** -- leg -- ************************************************************************** racetrackConfigJson.leg = config.leg or default_leg return racetrackConfigJson end -- @type AwacsCallsignConfig -- @field #string alias Alias for the awacs. -- @field #number name CallSign name for the awacs. -- @field #number number CallSign number (1-7) for the awacs. --- Parse an Awacs Callsign config Object. -- @param #JsonObject config Config object to parse -- @param #string parser_name Parser name ("AIRBASE", "AIRBOSS", etc...). -- @return #RescueHeloCallsignConfig rescueHeloCallsignConfigJson Parsed CallSign object function ParseAwacsCallsignConfigJson(config, parser_name) local awacsCallsignConfigJson = {} local default_number = 1 -- ************************************************************************** -- name -- ************************************************************************** if type(config.name) == "number" then if table.count_value(CALLSIGN.AWACS, config.name) > 0 then awacsCallsignConfigJson.name = config.name else Jtff_log.warn("AWACS Callsign name is not a valid CallSign name, skipping AWACS configuration", parser_name) awacsCallsignConfigJson.name = CALLSIGN.AWACS.Darkstar end else Jtff_log.error("AWACS Callsign name is not valid, skipping AWACS configuration", parser_name) return {} end -- ************************************************************************** -- alias -- ************************************************************************** if type(config.alias) == "string" then awacsCallsignConfigJson.alias = config.alias else Jtff_log.warn("AWACS Callsign alias is not a string, defaulting to AWACS name", parser_name) awacsCallsignConfigJson.alias = "Ford" end -- ************************************************************************** -- number -- ************************************************************************** if type(config.number) == "number" then if config.number >= 1 and config.number <= 7 then awacsCallsignConfigJson.number = config.number else Jtff_log.warn( string.format("AWACS Callsign number is not a valid number (1-7), defaulting number to %d", default_number), parser_name) awacsCallsignConfigJson.number = default_number end else Jtff_log.warn(string.format("AWACS Callsign number is not a number, defaulting number to %d", default_number), parser_name) awacsCallsignConfigJson.number = default_number end return awacsCallsignConfigJson end -- @type SquawkConfig -- @field #string mode1 Mode1 code for the aircraft. -- @field #string mode2 Mode2 code for the aircraft. -- @field #string mode3 Mode3 code for the aircraft. -- @field #string mode4 Mode4 code for the aircraft. --- Parse an SquawkConfig Object. -- @param #JsonObject config Config object to parse -- @param #string parser_name Parser name ("AIRBASE", "AIRBOSS", etc...). -- @return #SquawkConfig SquawkConfigJson Parsed SquawkConfig object function ParseSquawkConfigJson(config, parser_name) local squawkConfigJson = {} local default_mode4 = 'UN' local default_mode1 = '' local default_mode2 = '' local default_mode3 = '' -- ************************************************************************** -- mode4 -- ************************************************************************** if type(config.mode4) == "string" then if string.upper(config.mode4) == "FR" or string.lower(config.mode4) == "friend" then squawkConfigJson.mode4 = 'FR' else squawkConfigJson.mode4 = 'UN' end else Jtff_log.warn(string.format("Squawk firendOrFoe is not a string, defaulting to %s", default_mode4), parser_name) squawkConfigJson.mode4 = default_mode4 end -- ************************************************************************** -- mode1 -- ************************************************************************** if type(config.mode1) == "number" then squawkConfigJson.mode1 = string.format("%02d", config.mode1) else Jtff_log.error("Squawk mode1 is not a number, skipping mode 1 configuration", parser_name) squawkConfigJson.mode1 = default_mode1 end -- ************************************************************************** -- mode2 -- ************************************************************************** if type(config.mode2) == "number" then squawkConfigJson.mode2 = string.format("%02d", config.mode2) else Jtff_log.error("Squawk mode2 is not a number, skipping mode 2 configuration", parser_name) squawkConfigJson.mode2 = default_mode2 end -- ************************************************************************** -- mode3 -- ************************************************************************** if type(config.mode3) == "number" then squawkConfigJson.mode3 = string.format("%02d", config.mode3) else Jtff_log.error("Squawk mode3 is not a number, skipping mode 3 configuration", parser_name) squawkConfigJson.mode3 = default_mode3 end return squawkConfigJson end --- Create a Squawk string understandable by LotATC based on a SquawkConfig Object -- @param #SquawkConfig config Config object to parse -- @return #string squawkString function CreateSquawkString(squawkConfigJson) local squawkString = "@IFF:" if squawkConfigJson.mode1 ~= '' then squawkString = string.format("%s(%s)", squawkString, squawkConfigJson.mode1) end if squawkConfigJson.mode2 ~= '' then squawkString = string.format("%s[%s]", squawkString, squawkConfigJson.mode2) end if squawkConfigJson.mode3 ~= '' then squawkString = string.format("%s%s", squawkString, squawkConfigJson.mode3) end if squawkConfigJson.mode4 ~= '' then squawkString = string.format("%s%s", squawkString, squawkConfigJson.mode4) end return squawkString end -- @field #JTFF_QRA JTFF_QRA JTFF_QRA = { ClassName = "JTFF_QRA", } --- QRA Types -- @type JTFF_QRA.Type JTFF_QRA.Type = { TangoAuto = "Tango Scramble Auto Spawn", TangoManual = "Tango Scramble Manual Spawn", Alpha = "Alpha Scramble", } -- @field JTFF_INTERCEPT JTFF_INTERCEPT JTFF_INTERCEPT = { ClassName = "JTFF_INTERCEPT", } -- @type JTFF_INTERCEPT.Type JTFF_INTERCEPT.Type = { Civilian = "civil", Fighter = "fighter", Vampire = "bomber", } env.info('JTFF-SHAREDLIB: shared library loaded succesfully') SoundFilesPrefix = GetSoundFilesPrefix() Sound2Bip = USERSOUND:New( SoundFilesPrefix .. "Misc/2_Bips.ogg" ) Sound1Bip = USERSOUND:New( SoundFilesPrefix .. "Misc/Bip.ogg" ) SoundCrashWood = USERSOUND:New( SoundFilesPrefix .. "Misc/crash_wood.ogg" ) SoundQRA = USERSOUND:New( SoundFilesPrefix .. "Misc/SCRAMBLE QRA.ogg" ) Sound1Bip:ToAll()