diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index b6dc7ffc3..8b7872cbf 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -1591,10 +1591,9 @@ function AIRWING:onafterAssetSpawned(From, Event, To, group, asset, request) -- Get the SQUADRON of the asset. local squadron=self:GetSquadronOfAsset(asset) - -- Set default TACAN channel. + -- Get TACAN channel. local Tacan=squadron:FetchTacan() if Tacan then - flightgroup:SwitchTACAN(Tacan, Morse, UnitName, Band) asset.tacan=Tacan end @@ -1617,14 +1616,27 @@ function AIRWING:onafterAssetSpawned(From, Event, To, group, asset, request) -- Add mission to flightgroup queue. if mission then + + if Tacan then + mission:SetTACAN(Tacan, Morse, UnitName, Band) + end -- Add mission to flightgroup queue. asset.flightgroup:AddMission(mission) -- Trigger event. self:FlightOnMission(flightgroup, mission) + + else + + if Tacan then + flightgroup:SwitchTACAN(Tacan, Morse, UnitName, Band) + end + end + + -- Add group to the detection set of the WINGCOMMANDER. if self.wingcommander and self.wingcommander.chief then self.wingcommander.chief.detectionset:AddGroup(asset.flightgroup.group) diff --git a/Moose Development/Moose/Ops/ChiefOfStaff.lua b/Moose Development/Moose/Ops/ChiefOfStaff.lua index 682872dbc..6c24eb4d5 100644 --- a/Moose Development/Moose/Ops/ChiefOfStaff.lua +++ b/Moose Development/Moose/Ops/ChiefOfStaff.lua @@ -150,8 +150,6 @@ function CHIEF:New(AgentSet, Coalition) BASE:TraceClass(self.ClassName) BASE:TraceLevel(1) end - self.Debug=true - return self end @@ -427,7 +425,7 @@ function CHIEF:onafterStatus(From, Event, To) mission.nassets=1 -- Missons are repeated max 3 times on failure. - mission.missionRepeatMax=3 + mission.NrepeatFailure=3 -- Set mission contact. contact.mission=mission diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index 26a2f0af3..25618611f 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -18,13 +18,17 @@ -- @field #string lid Class id string for output to DCS log file. -- @field #number coalition Coalition side number, e.g. `coalition.side.RED`. -- @field #string alias Name of the agency. --- @field #table filterCategory Category filters. +-- @field Core.Set#SET_GROUP detectionset Set of detection groups, aka agents. +-- @field #table filterCategory Filter for unit categories. +-- @field #table filterCategoryGroup Filter for group categories. -- @field Core.Set#SET_ZONE acceptzoneset Set of accept zones. If defined, only contacts in these zones are considered. -- @field Core.Set#SET_ZONE rejectzoneset Set of reject zones. Contacts in these zones are not considered, even if they are in accept zones. -- @field #table Contacts Table of detected items. -- @field #table ContactsLost Table of lost detected items. -- @field #table ContactsUnknown Table of new detected items. -- @field #table Clusters Clusters of detected groups. +-- @field #boolean clusteranalysis If true, create clusters of detected targets. +-- @field #boolean clustermarkers If true, create cluster markers on F10 map. -- @field #number clustercounter Running number of clusters. -- @field #number dTforget Time interval in seconds before a known contact which is not detected any more is forgotten. -- @extends Core.Fsm#FSM @@ -188,7 +192,6 @@ function INTEL:New(DetectionSet, Coalition) BASE:TraceClass(self.ClassName) BASE:TraceLevel(1) end - self.Debug=true return self end @@ -241,6 +244,7 @@ function INTEL:SetFilterCategory(Categories) if type(Categories)~="table" then Categories={Categories} end + self.filterCategory=Categories local text="Filter categories: " @@ -261,7 +265,7 @@ end -- * Group.Category.TRAIN -- -- @param #INTEL self --- @param #table Categories Filter categories, e.g. {Group.Category.AIRPLANE, Group.Category.HELICOPTER}. +-- @param #table GroupCategories Filter categories, e.g. `{Group.Category.AIRPLANE, Group.Category.HELICOPTER}`. -- @return #INTEL self function INTEL:FilterCategoryGroup(GroupCategories) if type(GroupCategories)~="table" then @@ -279,6 +283,17 @@ function INTEL:FilterCategoryGroup(GroupCategories) return self end +--- Enable or disable cluster analysis of detected targets. +-- Targets will be grouped in coupled clusters. +-- @param #INTEL self +-- @param #boolean Switch If true, enable cluster analysis. +-- @param #boolean Markers If true, place markers on F10 map. +-- @return #INTEL self +function INTEL:SetClusterAnalysis(Switch, Markers) + self.clusteranalysis=Switch + self.clustermarkers=Markers + return self +end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Start & Status @@ -320,12 +335,14 @@ function INTEL:onafterStatus(From, Event, To) local Ncontacts=#self.Contacts -- Short info. - local text=string.format("Status %s [Agents=%s]: Contacts=%d, New=%d, Lost=%d", fsmstate, self.detectionset:CountAlive(), Ncontacts, #self.ContactsUnknown, #self.ContactsLost) - self:I(self.lid..text) + if self.verbose>=1 then + local text=string.format("Status %s [Agents=%s]: Contacts=%d, New=%d, Lost=%d", fsmstate, self.detectionset:CountAlive(), Ncontacts, #self.ContactsUnknown, #self.ContactsLost) + self:I(self.lid..text) + end -- Detailed info. - if Ncontacts>0 then - text="Detected Contacts:" + if self.verbose>=2 and Ncontacts>0 then + local text="Detected Contacts:" for _,_contact in pairs(self.Contacts) do local contact=_contact --#INTEL.Contact local dT=timer.getAbsTime()-contact.Tdetected @@ -347,28 +364,29 @@ end function INTEL:UpdateIntel() -- Set of all detected units. - local DetectedSet=SET_UNIT:New() + local DetectedUnits={} -- Loop over all units providing intel. - for _,_group in pairs(self.detectionset:GetSet()) do + for _,_group in pairs(self.detectionset.Set or {}) do local group=_group --Wrapper.Group#GROUP + if group and group:IsAlive() then + for _,_recce in pairs(group:GetUnits()) do local recce=_recce --Wrapper.Unit#UNIT - -- Get set of detected units. - local detectedunitset=recce:GetDetectedUnitSet() - - -- Add detected units to all set. - DetectedSet=DetectedSet:GetSetUnion(detectedunitset) + -- Get detected units. + self:GetDetectedUnits(recce, DetectedUnits) + end + end end -- TODO: Filter units from reject zones. -- TODO: Filter detection methods? local remove={} - for _,_unit in pairs(DetectedSet.Set) do + for unitname,_unit in pairs(DetectedUnits) do local unit=_unit --Wrapper.Unit#UNIT -- Check if unit is in any of the accept zones. @@ -384,7 +402,7 @@ function INTEL:UpdateIntel() -- Unit is not in accept zone ==> remove! if not inzone then - table.insert(remove, unit:GetName()) + table.insert(remove, unitname) end end @@ -399,8 +417,8 @@ function INTEL:UpdateIntel() end end if not keepit then - self:I(self.lid..string.format("Removing unit %s category=%d", unit:GetName(), unit:GetCategory())) - table.insert(remove, unit:GetName()) + self:I(self.lid..string.format("Removing unit %s category=%d", unitname, unit:GetCategory())) + table.insert(remove, unitname) end end @@ -408,14 +426,26 @@ function INTEL:UpdateIntel() -- Remove filtered units. for _,unitname in pairs(remove) do - DetectedSet:Remove(unitname, true) + DetectedUnits[unitname]=nil + end + + -- Create detected groups. + local DetectedGroups={} + for unitname,_unit in pairs(DetectedUnits) do + local unit=_unit --Wrapper.Unit#UNIT + local group=unit:GetGroup() + if group then + DetectedGroups[group:GetName()]=group + end end -- Create detected contacts. - self:CreateDetectedItems(DetectedSet) + self:CreateDetectedItems(DetectedGroups) -- Paint a picture of the battlefield. - self:PaintPicture() + if self.clusteranalysis then + self:PaintPicture() + end end @@ -425,32 +455,15 @@ end --- Create detected items. -- @param #INTEL self --- @param Core.Set#SET_UNIT detectedunitset Set of detected units. -function INTEL:CreateDetectedItems(detectedunitset) - - local detectedgroupset=SET_GROUP:New() - - -- Convert detected UNIT set to detected GROUP set. - for _,_unit in pairs(detectedunitset:GetSet()) do - local unit=_unit --Wrapper.Unit#UNIT - - local group=unit:GetGroup() - - if group and group:IsAlive() then - local groupname=group:GetName() - detectedgroupset:Add(groupname, group) - end - - end +-- @param #table DetectedGroups Table of detected Groups +function INTEL:CreateDetectedItems(DetectedGroups) -- Current time. local Tnow=timer.getAbsTime() - for _,_group in pairs(detectedgroupset.Set) do + for groupname,_group in pairs(DetectedGroups) do local group=_group --Wrapper.Group#GROUP - -- Group name. - local groupname=group:GetName() -- Get contact if already known. local detecteditem=self:GetContactByName(groupname) @@ -498,8 +511,6 @@ function INTEL:CreateDetectedItems(detectedunitset) for i=#self.Contacts,1,-1 do local item=self.Contacts[i] --#INTEL.Contact - local group=detectedgroupset:FindGroup(item.groupname) - -- Check if deltaT>Tforget. We dont want quick oscillations between detected and undetected states. if self:_CheckContactLost(item) then @@ -514,6 +525,41 @@ function INTEL:CreateDetectedItems(detectedunitset) end +--- Return the detected target groups of the controllable as a @{SET_GROUP}. +-- The optional parametes specify the detection methods that can be applied. +-- If no detection method is given, the detection will use all the available methods by default. +-- @param #INTEL self +-- @param Wrapper.Unit#UNIT Unit The unit detecting. +-- @param #boolean DetectVisual (Optional) If *false*, do not include visually detected targets. +-- @param #boolean DetectOptical (Optional) If *false*, do not include optically detected targets. +-- @param #boolean DetectRadar (Optional) If *false*, do not include targets detected by radar. +-- @param #boolean DetectIRST (Optional) If *false*, do not include targets detected by IRST. +-- @param #boolean DetectRWR (Optional) If *false*, do not include targets detected by RWR. +-- @param #boolean DetectDLINK (Optional) If *false*, do not include targets detected by data link. +function INTEL:GetDetectedUnits(Unit, DetectedUnits, DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK) + + -- Get detected DCS units. + local detectedtargets=Unit:GetDetectedTargets(DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK) + + for DetectionObjectID, Detection in pairs(detectedtargets or {}) do + local DetectedObject=Detection.object -- DCS#Object + + if DetectedObject and DetectedObject:isExist() and DetectedObject.id_<50000000 then + + local unit=UNIT:Find(DetectedObject) + + if unit and unit:IsAlive() then + + local unitname=unit:GetName() + + DetectedUnits[unitname]=unit + + end + end + end + +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FSM Events ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -960,7 +1006,7 @@ end -- @param #INTEL self -- @param #INTEL.Cluster cluster The cluster. -- @return #INTEL self -function INTEL:UpdateClusterMarker(cluster, newcoordinate) +function INTEL:UpdateClusterMarker(cluster) -- Create a marker. local text=string.format("Cluster #%d. Size %d, TLsum=%d", cluster.index, cluster.size, cluster.threatlevelSum) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 4de641e41..369efcc47 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -449,7 +449,9 @@ AIRBASE.TerminalType = { -- @field Core.Point#COORDINATE position Position of runway start. -- @field Core.Point#COORDINATE endpoint End point of runway. --- Registration. +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Registration +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Create a new AIRBASE from DCSAirbase. -- @param #AIRBASE self @@ -491,6 +493,7 @@ function AIRBASE:Register(AirbaseName) self:GetCoordinate() if vec2 then + -- TODO: For ships we need a moving zone. self.AirbaseZone=ZONE_RADIUS:New( AirbaseName, vec2, 2500 ) else self:E(string.format("ERROR: Cound not get position Vec2 of airbase %s", AirbaseName)) @@ -499,7 +502,9 @@ function AIRBASE:Register(AirbaseName) return self end --- Reference methods. +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Reference methods +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Finds a AIRBASE from the _DATABASE using a DCSAirbase object. -- @param #AIRBASE self @@ -622,6 +627,38 @@ function AIRBASE:GetID(unique) end +--- Get category of airbase. +-- @param #AIRBASE self +-- @return #number Category of airbase from GetDesc().category. +function AIRBASE:GetAirbaseCategory() + return self.category +end + +--- Check if airbase is an airdrome. +-- @param #AIRBASE self +-- @return #boolean If true, airbase is an airdrome. +function AIRBASE:IsAirdrome() + return self.isAirdrome +end + +--- Check if airbase is a helipad. +-- @param #AIRBASE self +-- @return #boolean If true, airbase is a helipad. +function AIRBASE:IsHelipad() + return self.isHelipad +end + +--- Check if airbase is a ship. +-- @param #AIRBASE self +-- @return #boolean If true, airbase is a ship. +function AIRBASE:IsShip() + return self.isShip +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Parking +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + --- Returns a table of parking data for a given airbase. If the optional parameter *available* is true only available parking will be returned, otherwise all parking at the base is returned. Term types have the following enumerated values: -- -- * 16 : Valid spawn points on runway @@ -765,8 +802,10 @@ function AIRBASE:_InitParkingSpots() self.parkingByID={} self.NparkingTotal=0 - self.NparkingX=0 - self.NparkingY=0 + self.NparkingTerminal={} + for _,terminalType in pairs(AIRBASE.TerminalType) do + self.NparkingTerminal[terminalType]=0 + end -- Put coordinates of parking spots into table. for _,spot in pairs(parkingdata) do @@ -782,24 +821,11 @@ function AIRBASE:_InitParkingSpots() park.TerminalType=spot.Term_Type park.TOAC=spot.TO_AC - if park.TerminalID==AIRBASE.TerminalType.FighterAircraft then - - elseif park.TerminalID==AIRBASE.TerminalType.HelicopterOnly then - - elseif park.TerminalID==AIRBASE.TerminalType.HelicopterUsable then - - elseif park.TerminalID==AIRBASE.TerminalType.OpenBig then - - elseif park.TerminalID==AIRBASE.TerminalType.OpenMed then - - elseif park.TerminalID==AIRBASE.TerminalType.OpenMedOrBig then - - elseif park.TerminalID==AIRBASE.TerminalType.Runway then - - elseif park.TerminalID==AIRBASE.TerminalType.Shelter then - - end - + for _,terminalType in pairs(AIRBASE.TerminalType) do + if self._CheckTerminalType(terminalType, park.TerminalType) then + self.NparkingTerminal[terminalType]=self.NparkingTerminal[terminalType]+1 + end + end self.parkingByID[park.TerminalID]=park table.insert(self.parking, park) @@ -1134,104 +1160,6 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius, return validspots end ---- Function that checks if at leat one unit of a group has been spawned close to a spawn point on the runway. --- @param #AIRBASE self --- @param Wrapper.Group#GROUP group Group to be checked. --- @param #number radius Radius around the spawn point to be checked. Default is 50 m. --- @param #boolean despawn If true, the group is destroyed. --- @return #boolean True if group is within radius around spawn points on runway. -function AIRBASE:CheckOnRunWay(group, radius, despawn) - - -- Default radius. - radius=radius or 50 - - -- We only check at real airbases (not FARPS or ships). - if self:GetAirbaseCategory()~=Airbase.Category.AIRDROME then - return false - end - - if group and group:IsAlive() then - - -- Debug. - self:T(string.format("%s, checking if group %s is on runway?",self:GetName(), group:GetName())) - - -- Get coordinates on runway. - local runwaypoints=self:GetParkingSpotsCoordinates(AIRBASE.TerminalType.Runway) - - -- Get units of group. - local units=group:GetUnits() - - -- Loop over units. - for _,_unit in pairs(units) do - - local unit=_unit --Wrapper.Unit#UNIT - - -- Check if unit is alive and not in air. - if unit and unit:IsAlive() and not unit:InAir() then - self:T(string.format("%s, checking if unit %s is on runway?",self:GetName(), unit:GetName())) - - -- Loop over runway spawn points. - for _i,_coord in pairs(runwaypoints) do - - -- Distance between unit and spawn pos. - local dist=unit:GetCoordinate():Get2DDistance(_coord) - - -- Mark unit spawn points for debugging. - --unit:GetCoordinate():MarkToAll(string.format("unit %s distance to rwy %d = %d",unit:GetName(),_i, dist)) - - -- Check if unit is withing radius. - if dist radius %.1f m. Despawn = %s.", self:GetName(), unit:GetName(), group:GetName(),_i, dist, radius, tostring(despawn))) - --unit:FlareGreen() - end - - end - else - self:T(string.format("%s, checking if unit %s of group %s is on runway. Unit is NOT alive.",self:GetName(), unit:GetName(), group:GetName())) - end - end - else - self:T(string.format("%s, checking if group %s is on runway. Group is NOT alive.",self:GetName(), group:GetName())) - end - - return false -end - ---- Check if airbase is an airdrome. --- @param #AIRBASE self --- @return #boolean If true, airbase is an airdrome. -function AIRBASE:IsAirdrome() - return self.isAirdrome -end - ---- Check if airbase is a helipad. --- @param #AIRBASE self --- @return #boolean If true, airbase is a helipad. -function AIRBASE:IsHelipad() - return self.isHelipad -end - ---- Check if airbase is a ship. --- @param #AIRBASE self --- @return #boolean If true, airbase is a ship. -function AIRBASE:IsShip() - return self.isShip -end - ---- Get category of airbase. --- @param #AIRBASE self --- @return #number Category of airbase from GetDesc().category. -function AIRBASE:GetAirbaseCategory() - return self.category -end - --- Helper function to check for the correct terminal type including "artificial" ones. -- @param #number Term_Type Termial type from getParking routine. -- @param #AIRBASE.TerminalType termtype Terminal type from AIRBASE.TerminalType enumerator. @@ -1278,6 +1206,10 @@ function AIRBASE._CheckTerminalType(Term_Type, termtype) return match end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Runway +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + --- Get runways data. Only for airdromes! -- @param #AIRBASE self -- @param #number magvar (Optional) Magnetic variation in degrees. @@ -1507,3 +1439,73 @@ function AIRBASE:GetActiveRunway(magvar) return runways[iact] end + +--- Function that checks if at leat one unit of a group has been spawned close to a spawn point on the runway. +-- @param #AIRBASE self +-- @param Wrapper.Group#GROUP group Group to be checked. +-- @param #number radius Radius around the spawn point to be checked. Default is 50 m. +-- @param #boolean despawn If true, the group is destroyed. +-- @return #boolean True if group is within radius around spawn points on runway. +function AIRBASE:CheckOnRunWay(group, radius, despawn) + + -- Default radius. + radius=radius or 50 + + -- We only check at real airbases (not FARPS or ships). + if self:GetAirbaseCategory()~=Airbase.Category.AIRDROME then + return false + end + + if group and group:IsAlive() then + + -- Debug. + self:T(string.format("%s, checking if group %s is on runway?",self:GetName(), group:GetName())) + + -- Get coordinates on runway. + local runwaypoints=self:GetParkingSpotsCoordinates(AIRBASE.TerminalType.Runway) + + -- Get units of group. + local units=group:GetUnits() + + -- Loop over units. + for _,_unit in pairs(units) do + + local unit=_unit --Wrapper.Unit#UNIT + + -- Check if unit is alive and not in air. + if unit and unit:IsAlive() and not unit:InAir() then + self:T(string.format("%s, checking if unit %s is on runway?",self:GetName(), unit:GetName())) + + -- Loop over runway spawn points. + for _i,_coord in pairs(runwaypoints) do + + -- Distance between unit and spawn pos. + local dist=unit:GetCoordinate():Get2DDistance(_coord) + + -- Mark unit spawn points for debugging. + --unit:GetCoordinate():MarkToAll(string.format("unit %s distance to rwy %d = %d",unit:GetName(),_i, dist)) + + -- Check if unit is withing radius. + if dist radius %.1f m. Despawn = %s.", self:GetName(), unit:GetName(), group:GetName(),_i, dist, radius, tostring(despawn))) + --unit:FlareGreen() + end + + end + else + self:T(string.format("%s, checking if unit %s of group %s is on runway. Unit is NOT alive.",self:GetName(), unit:GetName(), group:GetName())) + end + end + else + self:T(string.format("%s, checking if group %s is on runway. Group is NOT alive.",self:GetName(), group:GetName())) + end + + return false +end