diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 58cb5c386..a677f1135 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -37,7 +37,7 @@ -- === -- -- ### Author: **FlightControl** --- ### Contributions: +-- ### Contributions: **funkyfranky** -- -- === -- @@ -48,9 +48,10 @@ do -- SET_BASE --- @type SET_BASE - -- @field #table Filter - -- @field #table Set - -- @field #table List + -- @field #table Filter Table of filters. + -- @field #table Set Table of objects. + -- @field #table Index Table of indicies. + -- @field #table List Unused table. -- @field Core.Scheduler#SCHEDULER CallScheduler -- @extends Core.Base#BASE @@ -77,6 +78,10 @@ do -- SET_BASE Set = {}, List = {}, Index = {}, + Database = nil, + CallScheduler=nil, + TimeInterval=nil, + YieldInterval=nil, } @@ -224,8 +229,8 @@ do -- SET_BASE --- Adds a @{Core.Base#BASE} object in the @{Core.Set#SET_BASE}, using a given ObjectName as the index. -- @param #SET_BASE self - -- @param #string ObjectName - -- @param Core.Base#BASE Object + -- @param #string ObjectName The name of the object. + -- @param Core.Base#BASE Object The object itself. -- @return Core.Base#BASE The added BASE Object. function SET_BASE:Add( ObjectName, Object ) self:F2( { ObjectName = ObjectName, Object = Object } ) @@ -234,9 +239,14 @@ do -- SET_BASE if self.Set[ObjectName] then self:Remove( ObjectName, true ) end + + -- Add object to set. self.Set[ObjectName] = Object + + -- Add Object name to Index. table.insert( self.Index, ObjectName ) + -- Trigger Added event. self:Added( ObjectName, Object ) end @@ -253,6 +263,81 @@ do -- SET_BASE end + + --- Get the *union* of two sets. + -- @param #SET_BASE self + -- @param Core.Set#SET_BASE SetB Set *B*. + -- @return Core.Set#SET_BASE The union set, i.e. contains objects that are in set *A* **or** in set *B*. + function SET_BASE:GetSetUnion(SetB) + + local union=SET_BASE:New() + + for _,ObjectA in pairs(self.Set) do + union:AddObject(ObjectA) + end + + for _,ObjectB in pairs(SetB.Set) do + union:AddObject(ObjectB) + end + + return union + end + + --- Get the *intersection* of this set, called *A*, and another set. + -- @param #SET_BASE self + -- @param Core.Set#SET_BASE SetB Set other set, called *B*. + -- @return Core.Set#SET_BASE A set of objects that is included in set *A* **and** in set *B*. + function SET_BASE:GetSetIntersection(SetB) + + local intersection=SET_BASE:New() + + local union=self:GetSetUnion(SetB) + + for _,Object in pairs(union.Set) do + if self:IsIncludeObject(Object) and SetB:IsIncludeObject(Object) then + intersection:AddObject(intersection) + end + end + + return intersection + end + + --- Get the *complement* of two sets. + -- @param #SET_BASE self + -- @param Core.Set#SET_BASE SetB Set other set, called *B*. + -- @return Core.Set#SET_BASE The set of objects that are in set *B* but **not** in this set *A*. + function SET_BASE:GetSetComplement(SetB) + + local complement=SET_BASE:New() + + local union=self:GetSetUnion(SetA, SetB) + + for _,Object in pairs(union.Set) do + if SetA:IsIncludeObject(Object) and SetB:IsIncludeObject(Object) then + intersection:Add(intersection) + end + end + + return intersection + end + + + --- Compare two sets. + -- @param #SET_BASE self + -- @param Core.Set#SET_BASE SetA First set. + -- @param Core.Set#SET_BASE SetB Set to be merged into first set. + -- @return Core.Set#SET_BASE The set of objects that are included in SetA and SetB. + function SET_BASE:CompareSets(SetA, SetB) + + for _,ObjectB in pairs(SetB.Set) do + if SetA:IsIncludeObject(ObjectB) then + SetA:Add(ObjectB) + end + end + + return SetA + end + @@ -712,7 +797,7 @@ do -- SET_BASE --end - --- Decides whether to include the Object + --- Decides whether to include the Object. -- @param #SET_BASE self -- @param #table Object -- @return #SET_BASE self @@ -721,6 +806,16 @@ do -- SET_BASE return true end + + --- Decides whether to include the Object. + -- @param #SET_BASE self + -- @param #table Object + -- @return #SET_BASE self + function SET_BASE:IsInSet(ObjectName) + self:F3( Object ) + + return true + end --- Gets a string with all the object names. -- @param #SET_BASE self @@ -965,7 +1060,7 @@ do -- SET_GROUP -- Note that for each unit in the group that is set, a default cargo bay limit is initialized. -- @param Core.Set#SET_GROUP self -- @param Wrapper.Group#GROUP group The group which should be added to the set. - -- @return self + -- @return Core.Set#SET_GROUP self function SET_GROUP:AddGroup( group ) self:Add( group:GetName(), group ) @@ -981,7 +1076,7 @@ do -- SET_GROUP --- Add GROUP(s) to SET_GROUP. -- @param Core.Set#SET_GROUP self -- @param #string AddGroupNames A single name or an array of GROUP names. - -- @return self + -- @return Core.Set#SET_GROUP self function SET_GROUP:AddGroupsByName( AddGroupNames ) local AddGroupNamesArray = ( type( AddGroupNames ) == "table" ) and AddGroupNames or { AddGroupNames } @@ -996,7 +1091,7 @@ do -- SET_GROUP --- Remove GROUP(s) from SET_GROUP. -- @param Core.Set#SET_GROUP self -- @param Wrapper.Group#GROUP RemoveGroupNames A single name or an array of GROUP names. - -- @return self + -- @return Core.Set#SET_GROUP self function SET_GROUP:RemoveGroupsByName( RemoveGroupNames ) local RemoveGroupNamesArray = ( type( RemoveGroupNames ) == "table" ) and RemoveGroupNames or { RemoveGroupNames } @@ -1909,8 +2004,8 @@ do -- SET_UNIT --- Remove UNIT(s) from SET_UNIT. -- @param Core.Set#SET_UNIT self - -- @param Wrapper.Unit#UNIT RemoveUnitNames A single name or an array of UNIT names. - -- @return self + -- @param #table RemoveUnitNames A single name or an array of UNIT names. + -- @return Core.Set#SET_UNIT self function SET_UNIT:RemoveUnitsByName( RemoveUnitNames ) local RemoveUnitNamesArray = ( type( RemoveUnitNames ) == "table" ) and RemoveUnitNames or { RemoveUnitNames } @@ -2080,7 +2175,23 @@ do -- SET_UNIT return self end - + --- Iterate the SET_UNIT and count how many UNITs are alive. + -- @param #SET_UNIT self + -- @return #number The number of UNITs alive. + function SET_UNIT:CountAlive() + + local Set = self:GetSet() + + local CountU = 0 + for UnitID, UnitData in pairs(Set) do -- For each GROUP in SET_GROUP + if UnitData and UnitData:IsAlive() then + CountU = CountU + 1 + end + + end + + return CountU + end --- Starts the filtering. -- @param #SET_UNIT self @@ -3099,6 +3210,24 @@ do -- SET_STATIC return self end + --- Iterate the SET_STATIC and count how many STATICSs are alive. + -- @param #SET_STATIC self + -- @return #number The number of UNITs alive. + function SET_STATIC:CountAlive() + + local Set = self:GetSet() + + local CountU = 0 + for UnitID, UnitData in pairs(Set) do + if UnitData and UnitData:IsAlive() then + CountU = CountU + 1 + end + + end + + return CountU + end + --- Handles the Database to check on an event (birth) that the Object was added in the Database. -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! -- @param #SET_STATIC self diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua new file mode 100644 index 000000000..2b0591d0d --- /dev/null +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -0,0 +1,600 @@ +--- **Ops** - Office of Military Intelligence. +-- +-- **Main Features:** +-- +-- * Stuff +-- +-- === +-- +-- ### Author: **funkyfranky** +-- @module Ops.Intel +-- @image OPS_Intel.png + + +--- INTEL class. +-- @type INTEL +-- @field #string ClassName Name of the class. +-- @field #boolean Debug Debug mode. Messages to all about status. +-- @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_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 #number dTforget Time interval in seconds before a known contact which is not detected any more is forgotten. +-- @extends Core.Fsm#FSM + +--- Top Secret! +-- +-- === +-- +-- ![Banner Image](..\Presentations\CarrierAirWing\INTEL_Main.jpg) +-- +-- # The INTEL Concept +-- +-- +-- +-- @field #INTEL +INTEL = { + ClassName = "INTEL", + Debug = nil, + lid = nil, + alias = nil, + filterCategory = {}, + detectionset = nil, + Contacts = {}, + ContactsLost = {}, + ContactsUnknown = {}, +} + +--- Detected item info. +-- @type INTEL.DetectedItem +-- @field #string groupname Name of the group. +-- @field Wrapper.Group#GROUP group The contact group. +-- @field #string typename Type name of detected item. +-- @field #number category Category number. +-- @field #string categoryname Category name. +-- @field #string attribute Generalized attribute. +-- @field #number threatlevel Threat level of this item. +-- @field #number Tdetected Time stamp in abs. mission time seconds when this item was last detected. +-- @field Core.Point#COORDINATE position Last known position of the item. +-- @field DCS#Vec3 velocity 3D velocity vector. Components x,y and z in m/s. +-- @field #number speed Last known speed. +-- @field #number markerID F10 map marker ID. + +--- INTEL class version. +-- @field #string version +INTEL.version="0.0.3" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- ToDo list +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- DONE: Accept zones. +-- TODO: Reject zones. +-- TODO: Filter detection methods. +-- NOGO: SetAttributeZone --> return groups of generalized attributes in a zone. +-- DONE: Loose units only if they remain undetected for a given time interval. We want to avoid fast oscillation between detected/lost states. Maybe 1-5 min would be a good time interval?! +-- DONE: Combine units to groups for all, new and lost. +-- TODO: process detected set asynchroniously for better performance. + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new INTEL object and start the FSM. +-- @param #INTEL self +-- @param Core.Set#SET_GROUP DetectionSet Set of detection groups. +-- @param #number Coalition Coalition side. Can also be passed as a string "red", "blue" or "neutral". +-- @return #INTEL self +function INTEL:New(DetectionSet, Coalition) + + -- Inherit everything from FSM class. + local self=BASE:Inherit(self, FSM:New()) -- #INTEL + + -- Detection set. + self.detectionset=DetectionSet or SET_GROUP:New() + + -- Determine coalition from first group in set. + self.coalition=Coalition or DetectionSet:CountAlive()>0 and DetectionSet:GetFirst():GetCoalition() or nil + + -- Set alias. + self.alias="SPECTRE" + if self.coalition then + if self.coalition==coalition.side.RED then + self.alias="KGB" + elseif self.coalition==coalition.side.BLUE then + self.alias="CIA" + end + end + + -- Set some string id for output to DCS.log file. + self.lid=string.format("INTEL %s | ", self.alias) + + -- Start State. + self:SetStartState("Stopped") + + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("Stopped", "Start", "Running") -- Start FSM. + self:AddTransition("*", "Status", "*") -- INTEL status update + + self:AddTransition("*", "Detect", "*") -- Start detection run. Not implemented yet! + + self:AddTransition("*", "NewContact", "*") -- New contact has been detected. + self:AddTransition("*", "LostContact", "*") -- Contact could not be detected any more. + + + -- Defaults + self:SetForgetTime() + self:SetAcceptZones() + + ------------------------ + --- Pseudo Functions --- + ------------------------ + + --- Triggers the FSM event "Start". Starts the INTEL. Initializes parameters and starts event handlers. + -- @function [parent=#INTEL] Start + -- @param #INTEL self + + --- Triggers the FSM event "Start" after a delay. Starts the INTEL. Initializes parameters and starts event handlers. + -- @function [parent=#INTEL] __Start + -- @param #INTEL self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Stop". Stops the INTEL and all its event handlers. + -- @param #INTEL self + + --- Triggers the FSM event "Stop" after a delay. Stops the INTEL and all its event handlers. + -- @function [parent=#INTEL] __Stop + -- @param #INTEL self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Status". + -- @function [parent=#INTEL] Status + -- @param #INTEL self + + --- Triggers the FSM event "Status" after a delay. + -- @function [parent=#INTEL] __Status + -- @param #INTEL self + -- @param #number delay Delay in seconds. + + + -- Debug trace. + if false then + self.Debug=true + BASE:TraceOnOff(true) + BASE:TraceClass(self.ClassName) + BASE:TraceLevel(1) + end + self.Debug=true + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- User functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Set accept zones. Only contacts detected in this/these zone(s) are considered. +-- @param #INTEL self +-- @param Core.Set#SET_ZONE AcceptZoneSet Set of accept zones +-- @return #INTEL self +function INTEL:SetAcceptZones(AcceptZoneSet) + self.acceptzoneset=AcceptZoneSet or SET_ZONE:New() + return self +end + +--- Set accept zones. Only contacts detected in this zone are considered. +-- @param #INTEL self +-- @param Core.Zone#ZONE AcceptZone Add a zone to the accept zone set. +-- @return #INTEL self +function INTEL:AddAcceptZone(AcceptZone) + self.acceptzoneset:AddZone(AcceptZone) + return self +end + +--- Set forget contacts time interval. +-- Previously known contacts that are not detected any more, are "lost" after this time. +-- This avoids fast oscillations between a contact being detected and undetected. +-- @param #INTEL self +-- @param #number TimeInterval Time interval in seconds. Default is 120 sec. +-- @return #INTEL self +function INTEL:SetForgetTime(TimeInterval) + self.dTforget=TimeInterval or 120 + return self +end + +--- Filter unit categories. Valid categories are: +-- +-- * Unit.Category.AIRPLANE +-- * Unit.Category.HELICOPTER +-- * Unit.Category.GROUND_UNIT +-- * Unit.Category.SHIP +-- * Unit.Category.STRUCTURE +-- +-- @param #INTEL self +-- @param #table Categories Filter categories, e.g. {Unit.Category.AIRPLANE, Unit.Category.HELICOPTER}. +-- @return #INTEL self +function INTEL:SetFilterCategory(Categories) + if type(Categories)~="table" then + Categories={Categories} + end + self.filterCategory=Categories + + local text="Filter categories: " + for _,category in pairs(self.filterCategory) do + text=text..string.format("%d,", category) + end + self:I(self.lid..text) + + return self +end + +--- Filter group categories. Valid categories are: +-- +-- * Group.Category.AIRPLANE +-- * Group.Category.HELICOPTER +-- * Group.Category.GROUND +-- * Group.Category.SHIP +-- * Group.Category.TRAIN +-- +-- @param #INTEL self +-- @param #table Categories Filter categories, e.g. {Group.Category.AIRPLANE, Group.Category.HELICOPTER}. +-- @return #INTEL self +function INTEL:FilterCategoryGroup(GroupCategories) + if type(GroupCategories)~="table" then + GroupCategories={GroupCategories} + end + + self.filterCategoryGroup=GroupCategories + + local text="Filter group categories: " + for _,category in pairs(self.filterCategoryGroup) do + text=text..string.format("%d,", category) + end + self:I(self.lid..text) + + return self +end + + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Start & Status +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- On after Start event. Starts the FLIGHTGROUP FSM and event handlers. +-- @param #INTEL self +-- @param Wrapper.Group#GROUP Group Flight group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function INTEL:onafterStart(From, Event, To) + + -- Short info. + local text=string.format("Starting INTEL v%s", self.version) + self:I(self.lid..text) + + -- Start the status monitoring. + self:__Status(-math.random(10)) +end + +--- On after "Status" event. +-- @param #INTEL self +-- @param Wrapper.Group#GROUP Group Flight group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function INTEL:onafterStatus(From, Event, To) + + -- FSM state. + local fsmstate=self:GetState() + + -- Fresh arrays. + self.ContactsLost={} + self.ContactsUnknown={} + + -- Check if group has detected any units. + self:UpdateIntel() + + -- Number of total contacts. + 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) + + -- Detailed info. + if Ncontacts>0 then + text="Detected Contacts:" + for _,_contact in pairs(self.Contacts) do + local contact=_contact --#INTEL.DetectedItem + local dT=timer.getAbsTime()-contact.Tdetected + text=text..string.format("\n- %s (%s): %s, units=%d, T=%d sec", contact.categoryname, contact.attribute, contact.groupname, contact.group:CountAliveUnits(), dT) + if contact.mission then + local mission=contact.mission --Ops.Auftrag#AUFTRAG + text=text..string.format(" mission name=%s type=%s target=%s", mission.name, mission.type, mission:GetTargetName() or "unkown") + end + end + self:I(self.lid..text) + end + + self:__Status(-30) +end + + +--- Update detected items. +-- @param #INTEL self +function INTEL:UpdateIntel() + + -- Set of all detected units. + local DetectedSet=SET_UNIT:New() + + -- Loop over all units providing intel. + for _,_group in pairs(self.detectionset:GetSet()) 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) + end + end + end + + -- TODO: Filter units from reject zones. + -- TODO: Filter detection methods? + local remove={} + for _,_unit in pairs(DetectedSet.Set) do + local unit=_unit --Wrapper.Unit#UNIT + + -- Check if unit is in any of the accept zones. + if self.acceptzoneset:Count()>0 then + local inzone=false + for _,_zone in pairs(self.acceptzoneset.Set) do + local zone=_zone --Core.Zone#ZONE + if unit:IsInZone(zone) then + inzone=true + break + end + end + + -- Unit is not in accept zone ==> remove! + if not inzone then + table.insert(remove, unit:GetName()) + end + end + + -- Filter unit categories. + if #self.filterCategory>0 then + local unitcategory=unit:GetUnitCategory() + local keepit=false + for _,filtercategory in pairs(self.filterCategory) do + if unitcategory==filtercategory then + keepit=true + break + 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()) + end + end + + end + + -- Remove filtered units. + for _,unitname in pairs(remove) do + DetectedSet:Remove(unitname, true) + end + + -- Create detected contacts. + self:CreateDetectedItems(DetectedSet) + +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 + + -- Current time. + local Tnow=timer.getAbsTime() + + for _,_group in pairs(detectedgroupset.Set) do + local group=_group --Wrapper.Group#GROUP + + -- Group name. + local groupname=group:GetName() + + -- Get contact if already known. + local detecteditem=self:GetContactByName(groupname) + + if detecteditem then + --- + -- Detected item already exists ==> Update data. + --- + + detecteditem.Tdetected=Tnow + detecteditem.position=group:GetCoordinate() + detecteditem.velocity=group:GetVelocityVec3() + detecteditem.speed=group:GetVelocityMPS() + + else + --- + -- Detected item does not exist in our list yet. + --- + + -- Create new contact. + local item={} --#INTEL.DetectedItem + + item.groupname=groupname + item.group=group + item.Tdetected=Tnow + item.typename=group:GetTypeName() + item.attribute=group:GetAttribute() + item.category=group:GetCategory() + item.categoryname=group:GetCategoryName() + item.threatlevel=group:GetUnit(1):GetThreatLevel() + item.position=group:GetCoordinate() + item.velocity=group:GetVelocityVec3() + item.speed=group:GetVelocityMPS() + + -- Add contact to table. + self:AddContact(item) + + -- Trigger new contact event. + self:NewContact(item) + end + + end + + -- Now check if there some groups could not be detected any more. + for i=#self.Contacts,1,-1 do + local item=self.Contacts[i] --#INTEL.DetectedItem + + 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 + + -- Trigger LostContact event. This also adds the contact to the self.ContactsLost table. + self:LostContact(item) + + -- Remove contact from table. + self:RemoveContact(item) + + end + end + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- FSM Events +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- On after "NewContact" event. +-- @param #INTEL self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #INTEL.DetectedItem Contact Detected contact. +function INTEL:onafterNewContact(From, Event, To, Contact) + self:I(self.lid..string.format("NEW contact %s", Contact.groupname)) + table.insert(self.ContactsUnknown, Contact) +end + +--- On after "LostContact" event. +-- @param #INTEL self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #INTEL.DetectedItem Contact Detected contact. +function INTEL:onafterLostContact(From, Event, To, Contact) + self:I(self.lid..string.format("LOST contact %s", Contact.groupname)) + table.insert(self.ContactsLost, Contact) +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Misc Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create detected items. +-- @param #INTEL self +-- @param #string groupname Name of the contact group. +-- @return #INTEL.DetectedItem +function INTEL:GetContactByName(groupname) + + for i,_contact in pairs(self.Contacts) do + local contact=_contact --#INTEL.DetectedItem + if contact.groupname==groupname then + return contact + end + end + + return nil +end + +--- Add a contact to our list. +-- @param #INTEL self +-- @param #INTEL.DetectedItem Contact The contact to be removed. +function INTEL:AddContact(Contact) + table.insert(self.Contacts, Contact) +end + +--- Remove a contact from our list. +-- @param #INTEL self +-- @param #INTEL.DetectedItem Contact The contact to be removed. +function INTEL:RemoveContact(Contact) + + for i,_contact in pairs(self.Contacts) do + local contact=_contact --#INTEL.DetectedItem + + if contact.groupname==Contact.groupname then + table.remove(self.Contacts, i) + end + + end + +end + +--- Remove a contact from our list. +-- @param #INTEL self +-- @param #INTEL.DetectedItem Contact The contact to be removed. +-- @return #boolean If true, contact was not detected for at least *dTforget* seconds. +function INTEL:CheckContactLost(Contact) + + -- Group dead? + if Contact.group==nil or not Contact.group:IsAlive() then + return true + end + + -- Time since last detected. + local dT=timer.getAbsTime()-Contact.Tdetected + + local dTforget=self.dTforget + if Contact.category==Group.Category.GROUND then + dTforget=60*60*2 -- 2 hours + elseif Contact.category==Group.Category.AIRPLANE then + dTforget=60*10 -- 10 min + elseif Contact.category==Group.Category.HELICOPTER then + dTforget=60*20 -- 20 min + elseif Contact.category==Group.Category.SHIP then + dTforget=60*60 -- 1 hour + elseif Contact.category==Group.Category.TRAIN then + dTforget=60*60 -- 1 hour + end + + if dT>dTforget then + return true + else + return false + end + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------